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