tangled
alpha
login
or
join now
knotbin.com
/
nozzle
0
fork
atom
Thin MongoDB ODM built for Standard Schema
mongodb
zod
deno
0
fork
atom
overview
issues
pulls
pipelines
zod to standard schema
knotbin.com
6 months ago
16a48fb2
1b2aeb2c
+87
-35
4 changed files
expand all
collapse all
unified
split
deno.json
deno.lock
model.ts
schema.ts
+1
deno.json
···
9
"test:watch": "deno run -A scripts/test.ts --mock --watch"
10
},
11
"imports": {
0
12
"zod": "jsr:@zod/zod@^4.0.17",
13
"mongodb": "npm:mongodb@^6.18.0"
14
}
···
9
"test:watch": "deno run -A scripts/test.ts --mock --watch"
10
},
11
"imports": {
12
+
"@standard-schema/spec": "jsr:@standard-schema/spec@^1.0.0",
13
"zod": "jsr:@zod/zod@^4.0.17",
14
"mongodb": "npm:mongodb@^6.18.0"
15
}
+5
deno.lock
···
1
{
2
"version": "5",
3
"specifiers": {
0
4
"jsr:@std/assert@*": "1.0.13",
5
"jsr:@std/assert@^1.0.13": "1.0.13",
6
"jsr:@std/internal@^1.0.10": "1.0.10",
···
11
"npm:mongodb@^6.18.0": "6.18.0"
12
},
13
"jsr": {
0
0
0
14
"@std/assert@1.0.13": {
15
"integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29",
16
"dependencies": [
···
105
},
106
"workspace": {
107
"dependencies": [
0
108
"jsr:@zod/zod@^4.0.17",
109
"npm:mongodb@^6.18.0"
110
]
···
1
{
2
"version": "5",
3
"specifiers": {
4
+
"jsr:@standard-schema/spec@1": "1.0.0",
5
"jsr:@std/assert@*": "1.0.13",
6
"jsr:@std/assert@^1.0.13": "1.0.13",
7
"jsr:@std/internal@^1.0.10": "1.0.10",
···
12
"npm:mongodb@^6.18.0": "6.18.0"
13
},
14
"jsr": {
15
+
"@standard-schema/spec@1.0.0": {
16
+
"integrity": "4f20bbcf34e92b92f8c01589b958abc7c87385fa9a96170cecdc643d4d5737c0"
17
+
},
18
"@std/assert@1.0.13": {
19
"integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29",
20
"dependencies": [
···
109
},
110
"workspace": {
111
"dependencies": [
112
+
"jsr:@standard-schema/spec@1",
113
"jsr:@zod/zod@^4.0.17",
114
"npm:mongodb@^6.18.0"
115
]
+69
-29
model.ts
···
1
-
import type { z } from "zod";
2
import type {
3
Collection,
4
DeleteResult,
···
12
} from "mongodb";
13
import { ObjectId } from "mongodb";
14
import { getDb } from "./client.ts";
15
-
import type { InsertType } from "./schema.ts";
16
17
-
export class Model<T extends z.ZodObject> {
18
-
private collection: Collection<z.infer<T>>;
19
private schema: T;
20
21
constructor(collectionName: string, schema: T) {
22
-
this.collection = getDb().collection<z.infer<T>>(collectionName);
0
0
0
0
23
this.schema = schema;
24
}
25
26
-
async insertOne(data: InsertType<T>): Promise<InsertOneResult<z.infer<T>>> {
27
-
const validatedData = this.schema.parse(data);
0
0
0
0
0
0
0
0
28
return await this.collection.insertOne(
29
-
validatedData as OptionalUnlessRequiredId<z.infer<T>>,
30
);
31
}
32
33
async insertMany(
34
-
data: InsertType<T>[],
35
-
): Promise<InsertManyResult<z.infer<T>>> {
36
-
const validatedData = data.map((item) => this.schema.parse(item));
0
0
0
0
0
0
0
0
0
37
return await this.collection.insertMany(
38
-
validatedData as OptionalUnlessRequiredId<z.infer<T>>[],
0
0
39
);
40
}
41
42
-
async find(query: Filter<z.infer<T>>): Promise<(WithId<z.infer<T>>)[]> {
0
0
43
return await this.collection.find(query).toArray();
44
}
45
46
-
async findOne(query: Filter<z.infer<T>>): Promise<WithId<z.infer<T>> | null> {
0
0
47
return await this.collection.findOne(query);
48
}
49
50
-
async findById(id: string | ObjectId): Promise<WithId<z.infer<T>> | null> {
0
0
51
const objectId = typeof id === "string" ? new ObjectId(id) : id;
52
-
return await this.findOne({ _id: objectId } as Filter<z.infer<T>>);
0
0
53
}
54
55
async update(
56
-
query: Filter<z.infer<T>>,
57
-
data: Partial<z.infer<T>>,
58
): Promise<UpdateResult> {
59
return await this.collection.updateMany(query, { $set: data });
60
}
61
62
async updateOne(
63
-
query: Filter<z.infer<T>>,
64
-
data: Partial<z.infer<T>>,
65
): Promise<UpdateResult> {
66
return await this.collection.updateOne(query, { $set: data });
67
}
68
69
async replaceOne(
70
-
query: Filter<z.infer<T>>,
71
-
data: InsertType<T>,
72
): Promise<UpdateResult> {
73
-
const validatedData = this.schema.parse(data);
0
0
0
0
0
0
74
return await this.collection.replaceOne(
75
query,
76
-
validatedData as OptionalUnlessRequiredId<z.infer<T>>,
77
);
78
}
79
80
-
async delete(query: Filter<z.infer<T>>): Promise<DeleteResult> {
0
0
81
return await this.collection.deleteMany(query);
82
}
83
84
-
async deleteOne(query: Filter<z.infer<T>>): Promise<DeleteResult> {
0
0
85
return await this.collection.deleteOne(query);
86
}
87
88
-
async count(query: Filter<z.infer<T>>): Promise<number> {
89
return await this.collection.countDocuments(query);
90
}
91
···
95
96
// Pagination support for find
97
async findPaginated(
98
-
query: Filter<z.infer<T>>,
99
options: { skip?: number; limit?: number; sort?: Document } = {},
100
-
): Promise<(WithId<z.infer<T>>)[]> {
101
return await this.collection
102
.find(query)
103
.skip(options.skip ?? 0)
···
1
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
2
import type {
3
Collection,
4
DeleteResult,
···
12
} from "mongodb";
13
import { ObjectId } from "mongodb";
14
import { getDb } from "./client.ts";
0
15
16
+
export class Model<T extends StandardSchemaV1<unknown, Document>> {
17
+
private collection: Collection<StandardSchemaV1.InferOutput<T>>;
18
private schema: T;
19
20
constructor(collectionName: string, schema: T) {
21
+
this.collection = getDb().collection<
22
+
StandardSchemaV1.InferOutput<T> & Document
23
+
>(
24
+
collectionName,
25
+
);
26
this.schema = schema;
27
}
28
29
+
async insertOne(
30
+
data: StandardSchemaV1.InferInput<T>,
31
+
): Promise<InsertOneResult<StandardSchemaV1.InferOutput<T>>> {
32
+
const result = this.schema["~standard"].validate(data);
33
+
if (result instanceof Promise) {
34
+
throw new Error("Async validation not supported");
35
+
}
36
+
if (result.issues) {
37
+
throw new Error(`Validation failed: ${JSON.stringify(result.issues)}`);
38
+
}
39
return await this.collection.insertOne(
40
+
result.value as OptionalUnlessRequiredId<StandardSchemaV1.InferOutput<T>>,
41
);
42
}
43
44
async insertMany(
45
+
data: StandardSchemaV1.InferInput<T>[],
46
+
): Promise<InsertManyResult<StandardSchemaV1.InferOutput<T>>> {
47
+
const validatedData = data.map((item) => {
48
+
const result = this.schema["~standard"].validate(item);
49
+
if (result instanceof Promise) {
50
+
throw new Error("Async validation not supported");
51
+
}
52
+
if (result.issues) {
53
+
throw new Error(`Validation failed: ${JSON.stringify(result.issues)}`);
54
+
}
55
+
return result.value;
56
+
});
57
return await this.collection.insertMany(
58
+
validatedData as OptionalUnlessRequiredId<
59
+
StandardSchemaV1.InferOutput<T>
60
+
>[],
61
);
62
}
63
64
+
async find(
65
+
query: Filter<StandardSchemaV1.InferOutput<T>>,
66
+
): Promise<(WithId<StandardSchemaV1.InferOutput<T>>)[]> {
67
return await this.collection.find(query).toArray();
68
}
69
70
+
async findOne(
71
+
query: Filter<StandardSchemaV1.InferOutput<T>>,
72
+
): Promise<WithId<StandardSchemaV1.InferOutput<T>> | null> {
73
return await this.collection.findOne(query);
74
}
75
76
+
async findById(
77
+
id: string | ObjectId,
78
+
): Promise<WithId<StandardSchemaV1.InferOutput<T>> | null> {
79
const objectId = typeof id === "string" ? new ObjectId(id) : id;
80
+
return await this.findOne(
81
+
{ _id: objectId } as Filter<StandardSchemaV1.InferOutput<T>>,
82
+
);
83
}
84
85
async update(
86
+
query: Filter<StandardSchemaV1.InferOutput<T>>,
87
+
data: Partial<StandardSchemaV1.InferOutput<T>>,
88
): Promise<UpdateResult> {
89
return await this.collection.updateMany(query, { $set: data });
90
}
91
92
async updateOne(
93
+
query: Filter<StandardSchemaV1.InferOutput<T>>,
94
+
data: Partial<StandardSchemaV1.InferOutput<T>>,
95
): Promise<UpdateResult> {
96
return await this.collection.updateOne(query, { $set: data });
97
}
98
99
async replaceOne(
100
+
query: Filter<StandardSchemaV1.InferOutput<T>>,
101
+
data: StandardSchemaV1.InferInput<T>,
102
): Promise<UpdateResult> {
103
+
const result = this.schema["~standard"].validate(data);
104
+
if (result instanceof Promise) {
105
+
throw new Error("Async validation not supported");
106
+
}
107
+
if (result.issues) {
108
+
throw new Error(`Validation failed: ${JSON.stringify(result.issues)}`);
109
+
}
110
return await this.collection.replaceOne(
111
query,
112
+
result.value as OptionalUnlessRequiredId<StandardSchemaV1.InferOutput<T>>,
113
);
114
}
115
116
+
async delete(
117
+
query: Filter<StandardSchemaV1.InferOutput<T>>,
118
+
): Promise<DeleteResult> {
119
return await this.collection.deleteMany(query);
120
}
121
122
+
async deleteOne(
123
+
query: Filter<StandardSchemaV1.InferOutput<T>>,
124
+
): Promise<DeleteResult> {
125
return await this.collection.deleteOne(query);
126
}
127
128
+
async count(query: Filter<StandardSchemaV1.InferOutput<T>>): Promise<number> {
129
return await this.collection.countDocuments(query);
130
}
131
···
135
136
// Pagination support for find
137
async findPaginated(
138
+
query: Filter<StandardSchemaV1.InferOutput<T>>,
139
options: { skip?: number; limit?: number; sort?: Document } = {},
140
+
): Promise<(WithId<StandardSchemaV1.InferOutput<T>>)[]> {
141
return await this.collection
142
.find(query)
143
.skip(options.skip ?? 0)
+12
-6
schema.ts
···
1
-
import type { z } from "zod";
2
import type { ObjectId } from "mongodb";
3
4
-
export type InferModel<T extends z.ZodObject> = z.infer<T> & {
5
-
_id?: ObjectId;
6
-
};
0
0
0
0
7
8
-
export type InsertType<T extends z.ZodObject> =
9
-
& Omit<z.infer<T>, "createdAt">
0
0
10
& { createdAt?: Date };
···
1
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
2
import type { ObjectId } from "mongodb";
3
4
+
export type InferModel<
5
+
T extends StandardSchemaV1<unknown, Record<string, unknown>>,
6
+
> =
7
+
& StandardSchemaV1.InferOutput<T>
8
+
& {
9
+
_id?: ObjectId;
10
+
};
11
12
+
export type InsertType<
13
+
T extends StandardSchemaV1<unknown, Record<string, unknown>>,
14
+
> =
15
+
& Omit<StandardSchemaV1.InferOutput<T>, "createdAt">
16
& { createdAt?: Date };