Thin MongoDB ODM built for Standard Schema
mongodb zod deno

back to zod i go

knotbin.com 722faea3 85074949

verified
+2 -6
README.md
···
## ✨ Features
-
- **Schema-first:** Define and validate collections using any schema validator
-
that supports [Standard Schema](https://standardschema.dev).
- **Type-safe operations:** Auto-complete and strict typings for `insert`,
`find`, `update`, and `delete`.
- **Minimal & modular:** No decorators or magic. Just clean, composable APIs.
···
## 🚀 Quick Start
-
Examples below use Zod but any schema validator that supports
-
[Standard Schema](https://standardschema.dev) will work.
-
### 1. Define a schema
```ts
// src/schemas/user.ts
-
import { z } from "zod";
export const userSchema = z.object({
name: z.string(),
···
## ✨ Features
+
- **Schema-first:** Define and validate collections using [Zod](https://zod.dev).
- **Type-safe operations:** Auto-complete and strict typings for `insert`,
`find`, `update`, and `delete`.
- **Minimal & modular:** No decorators or magic. Just clean, composable APIs.
···
## 🚀 Quick Start
### 1. Define a schema
```ts
// src/schemas/user.ts
+
import { z } from "@zod/zod";
export const userSchema = z.object({
name: z.string(),
-1
deno.json
···
"exports": "./mod.ts",
"license": "MIT",
"imports": {
-
"@standard-schema/spec": "jsr:@standard-schema/spec@^1.0.0",
"@std/assert": "jsr:@std/assert@^1.0.16",
"@zod/zod": "jsr:@zod/zod@^4.1.13",
"mongodb": "npm:mongodb@^6.18.0",
···
"exports": "./mod.ts",
"license": "MIT",
"imports": {
"@std/assert": "jsr:@std/assert@^1.0.16",
"@zod/zod": "jsr:@zod/zod@^4.1.13",
"mongodb": "npm:mongodb@^6.18.0",
-5
deno.lock
···
{
"version": "5",
"specifiers": {
-
"jsr:@standard-schema/spec@1": "1.0.0",
"jsr:@std/assert@*": "1.0.13",
"jsr:@std/assert@^1.0.13": "1.0.13",
"jsr:@std/assert@^1.0.16": "1.0.16",
···
"npm:mongodb@^6.18.0": "6.18.0"
},
"jsr": {
-
"@standard-schema/spec@1.0.0": {
-
"integrity": "4f20bbcf34e92b92f8c01589b958abc7c87385fa9a96170cecdc643d4d5737c0"
-
},
"@std/assert@1.0.13": {
"integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29",
"dependencies": [
···
},
"workspace": {
"dependencies": [
-
"jsr:@standard-schema/spec@1",
"jsr:@std/assert@^1.0.16",
"jsr:@zod/zod@^4.1.13",
"npm:mongodb-memory-server-core@^10.3.0",
···
{
"version": "5",
"specifiers": {
"jsr:@std/assert@*": "1.0.13",
"jsr:@std/assert@^1.0.13": "1.0.13",
"jsr:@std/assert@^1.0.16": "1.0.16",
···
"npm:mongodb@^6.18.0": "6.18.0"
},
"jsr": {
"@std/assert@1.0.13": {
"integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29",
"dependencies": [
···
},
"workspace": {
"dependencies": [
"jsr:@std/assert@^1.0.16",
"jsr:@zod/zod@^4.1.13",
"npm:mongodb-memory-server-core@^10.3.0",
+1 -1
examples/user.ts
···
-
import { z } from "jsr:@zod/zod";
import { ObjectId } from "mongodb";
import {
connect,
···
+
import { z } from "@zod/zod";
import { ObjectId } from "mongodb";
import {
connect,
+29 -44
model.ts
···
-
import type { StandardSchemaV1 } from "@standard-schema/spec";
import type {
Collection,
DeleteResult,
···
import { ObjectId } from "mongodb";
import { getDb } from "./client.ts";
-
// Type alias for cleaner code
-
type Schema = StandardSchemaV1<unknown, Document>;
-
type Infer<T extends Schema> = StandardSchemaV1.InferOutput<T>;
-
type Input<T extends Schema> = StandardSchemaV1.InferInput<T>;
-
// Helper function to make StandardSchemaV1 validation as simple as Zod's parse()
-
function parse<T extends Schema>(schema: T, data: unknown): Infer<T> {
-
const result = schema["~standard"].validate(data);
-
if (result instanceof Promise) {
-
throw new Error("Async validation not supported");
}
-
if (result.issues) {
-
throw new Error(`Validation failed: ${JSON.stringify(result.issues)}`);
-
}
-
return result.value;
}
-
// Helper function to validate partial update data
-
// Uses schema.partial() if available (e.g., Zod)
function parsePartial<T extends Schema>(
schema: T,
-
data: Partial<Infer<T>>,
-
): Partial<Infer<T>> {
-
// Get partial schema if available
-
const partialSchema = (
-
typeof schema === "object" &&
-
schema !== null &&
-
"partial" in schema &&
-
typeof (schema as { partial?: () => unknown }).partial === "function"
-
)
-
? (schema as { partial: () => T }).partial()
-
: schema;
-
-
const result = partialSchema["~standard"].validate(data);
-
if (result instanceof Promise) {
-
throw new Error("Async validation not supported");
}
-
if (result.issues) {
-
throw new Error(`Update validation failed: ${JSON.stringify(result.issues)}`);
-
}
-
return result.value as Partial<Infer<T>>;
}
export class Model<T extends Schema> {
···
private schema: T;
constructor(collectionName: string, schema: T) {
-
this.collection = getDb().collection<Infer<T> & Document>(collectionName);
this.schema = schema;
}
···
async update(
query: Filter<Infer<T>>,
-
data: Partial<Infer<T>>,
-
): Promise<UpdateResult> {
const validatedData = parsePartial(this.schema, data);
-
return await this.collection.updateMany(query, { $set: validatedData });
}
async updateOne(
query: Filter<Infer<T>>,
-
data: Partial<Infer<T>>,
-
): Promise<UpdateResult> {
const validatedData = parsePartial(this.schema, data);
-
return await this.collection.updateOne(query, { $set: validatedData });
}
async replaceOne(
query: Filter<Infer<T>>,
data: Input<T>,
-
): Promise<UpdateResult> {
const validatedData = parse(this.schema, data);
return await this.collection.replaceOne(
query,
-
validatedData as OptionalUnlessRequiredId<Infer<T>>,
);
}
···
+
import type { z } from "@zod/zod";
import type {
Collection,
DeleteResult,
···
import { ObjectId } from "mongodb";
import { getDb } from "./client.ts";
+
// Type alias for cleaner code - Zod schema
+
type Schema = z.ZodObject;
+
type Infer<T extends Schema> = z.infer<T> & Document;
+
type Input<T extends Schema> = z.input<T>;
+
// Helper function to validate data using Zod
+
function parse<T extends Schema>(schema: T, data: Input<T>): Infer<T> {
+
const result = schema.safeParse(data);
+
if (!result.success) {
+
throw new Error(`Validation failed: ${JSON.stringify(result.error.issues)}`);
}
+
return result.data as Infer<T>;
}
+
// Helper function to validate partial update data using Zod's partial()
function parsePartial<T extends Schema>(
schema: T,
+
data: Partial<z.infer<T>>,
+
): Partial<z.infer<T>> {
+
const result = schema.partial().safeParse(data);
+
if (!result.success) {
+
throw new Error(`Update validation failed: ${JSON.stringify(result.error.issues)}`);
}
+
return result.data as Partial<z.infer<T>>;
}
export class Model<T extends Schema> {
···
private schema: T;
constructor(collectionName: string, schema: T) {
+
this.collection = getDb().collection<Infer<T>>(collectionName);
this.schema = schema;
}
···
async update(
query: Filter<Infer<T>>,
+
data: Partial<z.infer<T>>,
+
): Promise<UpdateResult<Infer<T>>> {
const validatedData = parsePartial(this.schema, data);
+
return await this.collection.updateMany(query, { $set: validatedData as Partial<Infer<T>> });
}
async updateOne(
query: Filter<Infer<T>>,
+
data: Partial<z.infer<T>>,
+
): Promise<UpdateResult<Infer<T>>> {
const validatedData = parsePartial(this.schema, data);
+
return await this.collection.updateOne(query, { $set: validatedData as Partial<Infer<T>> });
}
async replaceOne(
query: Filter<Infer<T>>,
data: Input<T>,
+
): Promise<UpdateResult<Infer<T>>> {
const validatedData = parse(this.schema, data);
+
// Remove _id from validatedData for replaceOne (it will use the query's _id)
+
const { _id, ...withoutId } = validatedData as Infer<T> & { _id?: unknown };
return await this.collection.replaceOne(
query,
+
withoutId as Infer<T>,
);
}
+3 -3
schema.ts
···
-
import type { StandardSchemaV1 } from "@standard-schema/spec";
import type { ObjectId } from "mongodb";
-
type Schema = StandardSchemaV1<unknown, Record<string, unknown>>;
-
type Infer<T extends Schema> = StandardSchemaV1.InferOutput<T>;
export type InferModel<T extends Schema> = Infer<T> & {
_id?: ObjectId;
···
+
import type { z } from "@zod/zod";
import type { ObjectId } from "mongodb";
+
type Schema = z.ZodObject;
+
type Infer<T extends Schema> = z.infer<T>;
export type InferModel<T extends Schema> = Infer<T> & {
_id?: ObjectId;