# MongoDB Client

Execute MongoDB operations with full type safety and runtime validation.

## Methods

| Method                                               | Description                 |
| ---------------------------------------------------- | --------------------------- |
| `run(collection, action, schema, params, metadata?)` | Execute a MongoDB operation |

## Actions

| Action            | Description                 |
| ----------------- | --------------------------- |
| `find`            | Find multiple documents     |
| `findOne`         | Find a single document      |
| `insertOne`       | Insert a document           |
| `insertMany`      | Insert multiple documents   |
| `updateOne`       | Update a single document    |
| `updateMany`      | Update multiple documents   |
| `deleteOne`       | Delete a single document    |
| `deleteMany`      | Delete multiple documents   |
| `aggregate`       | Run an aggregation pipeline |
| `count`           | Count documents             |
| `distinct`        | Get distinct values         |
| `listCollections` | List collections            |

## Usage

### Find Documents

```typescript
import { api, z, mongodb } from "@superblocksteam/sdk-api";

// Integration ID from the integrations panel
const PROD_MONGODB = "a1b2c3d4-5678-90ab-cdef-mongodb00001";

const UserSchema = z.array(
  z.object({
    _id: z.string(),
    name: z.string(),
    email: z.string(),
  }),
);

export default api({
  integrations: {
    mongodb: mongodb(PROD_MONGODB),
  },
  name: "MongoDBExample",
  input: z.object({
    status: z.string(),
  }),
  output: z.object({
    users: UserSchema,
  }),
  async run(ctx, { status }) {
    const users = await ctx.integrations.mongodb.run(
      "users",
      "find",
      UserSchema,
      {
        query: { status: status },
        limit: 10,
      },
    );

    return { users };
  },
});
```

### Find One Document

```typescript
const UserSchema = z
  .object({
    _id: z.string(),
    name: z.string(),
    email: z.string(),
  })
  .nullable()
  .optional();

const user = await ctx.integrations.mongodb.run(
  "users",
  "findOne",
  UserSchema,
  {
    query: { _id: { $oid: userId } },
  },
);

// findOne returns undefined (not null) when no document matches,
// so use .nullable().optional() and coalesce if needed:
const result = user ?? null;
```

### Insert Document

```typescript
const InsertResultSchema = z.object({
  insertedId: z.string(),
});

const result = await ctx.integrations.mongodb.run(
  "users",
  "insertOne",
  InsertResultSchema,
  {
    document: {
      name: "John Doe",
      email: "john@example.com",
      createdAt: new Date().toISOString(),
    },
  },
);
```

### Update Document

```typescript
const UpdateResultSchema = z.object({
  matchedCount: z.coerce.number(),
  modifiedCount: z.coerce.number(),
});

const result = await ctx.integrations.mongodb.run(
  "users",
  "updateOne",
  UpdateResultSchema,
  {
    filter: { _id: { $oid: userId } },
    update: {
      $set: { status: "active", updatedAt: new Date().toISOString() },
    },
  },
);
```

### Delete Document

```typescript
const DeleteResultSchema = z.object({
  deletedCount: z.coerce.number(),
});

const result = await ctx.integrations.mongodb.run(
  "users",
  "deleteOne",
  DeleteResultSchema,
  {
    filter: { _id: { $oid: userId } },
  },
);
```

### Aggregation Pipeline

```typescript
const AggregateResultSchema = z.array(
  z.object({
    _id: z.string(),
    count: z.coerce.number(),
    totalAmount: z.coerce.number(),
  }),
);

const result = await ctx.integrations.mongodb.run(
  "orders",
  "aggregate",
  AggregateResultSchema,
  {
    pipeline: [
      { $match: { status: "completed" } },
      {
        $group: {
          _id: "$customerId",
          count: { $sum: 1 },
          totalAmount: { $sum: "$amount" },
        },
      },
    ],
  },
);
```

### Count Documents

```typescript
const count = await ctx.integrations.mongodb.run(
  "users",
  "count",
  z.coerce.number(),
  {
    query: { status: "active" },
  },
);
```

### Find with Projection and Sort

```typescript
const users = await ctx.integrations.mongodb.run("users", "find", UserSchema, {
  query: { status: "active" },
  projection: { name: 1, email: 1 },
  sort: { createdAt: -1 },
  limit: 10,
  skip: 0,
});
```

## Parameters

| Parameter    | Type     | Description                  |
| ------------ | -------- | ---------------------------- |
| `query`      | `object` | Filter/query object          |
| `filter`     | `object` | Alias for query              |
| `document`   | `object` | Document to insert           |
| `update`     | `object` | Update operations            |
| `pipeline`   | `array`  | Aggregation pipeline         |
| `projection` | `object` | Fields to include/exclude    |
| `sort`       | `object` | Sort specification           |
| `limit`      | `number` | Max documents to return      |
| `skip`       | `number` | Documents to skip            |
| `field`      | `string` | Field for distinct operation |

## Trace Metadata

All methods accept an optional `metadata` parameter as the last argument for diagnostics labeling. See the [root SDK README](../../../README.md#trace-metadata) for details.

## Error Handling

```typescript
import {
  RestApiValidationError,
  IntegrationError,
} from "@superblocksteam/sdk-api";

try {
  const users = await ctx.integrations.mongodb.run(
    "users",
    "find",
    UserSchema,
    {
      query: { status: "active" },
    },
  );
} catch (error) {
  if (error instanceof RestApiValidationError) {
    console.error("Validation failed:", error.details.zodError);
  } else if (error instanceof IntegrationError) {
    console.error("MongoDB error:", error.message);
  }
}
```

## Gotchas

### Numeric fields are returned as strings

MongoDB numeric values (e.g. `int32`, `int64`, `double`) are returned as strings by the integration. Using `z.number()` in your schema will fail with `"Expected number, received string"`. Use `z.coerce.number()` instead to automatically convert string values to numbers:

```typescript
// ❌ Will fail — MongoDB returns numbers as strings
const Schema = z.object({ rating: z.number() });

// ✅ Correct — coerces string values to numbers
const Schema = z.object({ rating: z.coerce.number() });
```

### ObjectId fields require `$oid` syntax

When querying by `_id` or any ObjectId field, passing a plain string does not match. You must wrap the value using MongoDB Extended JSON `$oid` syntax:

```typescript
// ❌ Will not match — returns null/undefined
const bad = { query: { _id: userId } };

// ✅ Correct — uses Extended JSON $oid wrapper
const good = { query: { _id: { $oid: userId } } };
```

This applies to `findOne`, `updateOne`, `deleteOne`, and any operation filtering by ObjectId fields.

### `findOne` returns `undefined` when no document matches

When no document is found, `findOne` returns `undefined` rather than `null`. If your schema uses only `.nullable()`, Zod will reject `undefined` with `"Expected object, received undefined"`. Use `.nullable().optional()` to handle both cases:

```typescript
// ❌ Will fail when no document matches
const Schema = z.object({ name: z.string() }).nullable();

// ✅ Correct — handles both null and undefined
const Schema = z.object({ name: z.string() }).nullable().optional();

// Coalesce to null if needed
const result = (await ctx.integrations.mongodb.run(...)) ?? null;
```

## API Reference

- [MongoDB Manual](https://www.mongodb.com/docs/manual/)
- [Query Operators](https://www.mongodb.com/docs/manual/reference/operator/query/)
- [Aggregation](https://www.mongodb.com/docs/manual/aggregation/)
