@zodmon/core 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +364 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +558 -138
- package/dist/index.d.ts +558 -138
- package/dist/index.js +356 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,16 +1,344 @@
|
|
|
1
1
|
// src/client/client.ts
|
|
2
2
|
import { MongoClient } from "mongodb";
|
|
3
3
|
|
|
4
|
+
// src/crud/find.ts
|
|
5
|
+
import { z as z2 } from "zod";
|
|
6
|
+
|
|
7
|
+
// src/errors/not-found.ts
|
|
8
|
+
var ZodmonNotFoundError = class extends Error {
|
|
9
|
+
name = "ZodmonNotFoundError";
|
|
10
|
+
/** The MongoDB collection name where the query found no results. */
|
|
11
|
+
collection;
|
|
12
|
+
constructor(collection2) {
|
|
13
|
+
super(`Document not found in "${collection2}"`);
|
|
14
|
+
this.collection = collection2;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/errors/validation.ts
|
|
19
|
+
var ZodmonValidationError = class extends Error {
|
|
20
|
+
name = "ZodmonValidationError";
|
|
21
|
+
/** The MongoDB collection name where the validation failed. */
|
|
22
|
+
collection;
|
|
23
|
+
/** The original Zod validation error with detailed issue information. */
|
|
24
|
+
zodError;
|
|
25
|
+
constructor(collection2, zodError) {
|
|
26
|
+
const fields = zodError.issues.map((issue) => {
|
|
27
|
+
const path = issue.path.join(".") || "(root)";
|
|
28
|
+
return `${path} (${issue.message})`;
|
|
29
|
+
}).join(", ");
|
|
30
|
+
super(`Validation failed for "${collection2}": ${fields}`);
|
|
31
|
+
this.collection = collection2;
|
|
32
|
+
this.zodError = zodError;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// src/query/cursor.ts
|
|
37
|
+
import { z } from "zod";
|
|
38
|
+
var TypedFindCursor = class {
|
|
39
|
+
/** @internal */
|
|
40
|
+
cursor;
|
|
41
|
+
/** @internal */
|
|
42
|
+
schema;
|
|
43
|
+
/** @internal */
|
|
44
|
+
collectionName;
|
|
45
|
+
/** @internal */
|
|
46
|
+
mode;
|
|
47
|
+
/** @internal */
|
|
48
|
+
constructor(cursor, definition, mode) {
|
|
49
|
+
this.cursor = cursor;
|
|
50
|
+
this.schema = definition.schema;
|
|
51
|
+
this.collectionName = definition.name;
|
|
52
|
+
this.mode = mode;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Set the sort order for the query.
|
|
56
|
+
*
|
|
57
|
+
* Only top-level document fields are accepted as sort keys.
|
|
58
|
+
* Values must be `1` (ascending) or `-1` (descending).
|
|
59
|
+
*
|
|
60
|
+
* @param spec - Sort specification mapping field names to sort direction.
|
|
61
|
+
* @returns `this` for chaining.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* find(users, {}).sort({ name: 1, age: -1 }).toArray()
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
sort(spec) {
|
|
69
|
+
this.cursor.sort(spec);
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Skip the first `n` documents in the result set.
|
|
74
|
+
*
|
|
75
|
+
* @param n - Number of documents to skip.
|
|
76
|
+
* @returns `this` for chaining.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* find(users, {}).skip(10).limit(10).toArray() // page 2
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
skip(n) {
|
|
84
|
+
this.cursor.skip(n);
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Limit the number of documents returned.
|
|
89
|
+
*
|
|
90
|
+
* @param n - Maximum number of documents to return.
|
|
91
|
+
* @returns `this` for chaining.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* find(users, {}).limit(10).toArray() // at most 10 docs
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
limit(n) {
|
|
99
|
+
this.cursor.limit(n);
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Execute the query and return all matching documents as an array.
|
|
104
|
+
*
|
|
105
|
+
* Each document is validated against the collection's Zod schema
|
|
106
|
+
* according to the resolved validation mode.
|
|
107
|
+
*
|
|
108
|
+
* @returns Array of validated documents.
|
|
109
|
+
* @throws {ZodmonValidationError} When a document fails schema validation in strict/strip mode.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* const admins = await find(users, { role: 'admin' }).toArray()
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
async toArray() {
|
|
117
|
+
const raw2 = await this.cursor.toArray();
|
|
118
|
+
return raw2.map((doc) => this.validateDoc(doc));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Async iterator for streaming documents one at a time.
|
|
122
|
+
*
|
|
123
|
+
* Each yielded document is validated against the collection's Zod schema.
|
|
124
|
+
* Memory-efficient for large result sets.
|
|
125
|
+
*
|
|
126
|
+
* @yields Validated documents one at a time.
|
|
127
|
+
* @throws {ZodmonValidationError} When a document fails schema validation.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* for await (const user of find(users, {})) {
|
|
132
|
+
* console.log(user.name)
|
|
133
|
+
* }
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
async *[Symbol.asyncIterator]() {
|
|
137
|
+
for await (const doc of this.cursor) {
|
|
138
|
+
yield this.validateDoc(doc);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** @internal Validate a single raw document against the schema. */
|
|
142
|
+
validateDoc(raw2) {
|
|
143
|
+
if (this.mode === false || this.mode === "passthrough") {
|
|
144
|
+
return raw2;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
return this.schema.parse(raw2);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
if (err instanceof z.ZodError) {
|
|
150
|
+
throw new ZodmonValidationError(this.collectionName, err);
|
|
151
|
+
}
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// src/crud/find.ts
|
|
158
|
+
async function findOne(handle, filter, options) {
|
|
159
|
+
const findOptions = options?.project ? { projection: options.project } : void 0;
|
|
160
|
+
const raw2 = await handle.native.findOne(filter, findOptions);
|
|
161
|
+
if (!raw2) return null;
|
|
162
|
+
const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
|
|
163
|
+
if (mode === false || mode === "passthrough") {
|
|
164
|
+
return raw2;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
return handle.definition.schema.parse(raw2);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
if (err instanceof z2.ZodError) {
|
|
170
|
+
throw new ZodmonValidationError(handle.definition.name, err);
|
|
171
|
+
}
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function findOneOrThrow(handle, filter, options) {
|
|
176
|
+
const doc = await findOne(handle, filter, options);
|
|
177
|
+
if (!doc) {
|
|
178
|
+
throw new ZodmonNotFoundError(handle.definition.name);
|
|
179
|
+
}
|
|
180
|
+
return doc;
|
|
181
|
+
}
|
|
182
|
+
function find(handle, filter, options) {
|
|
183
|
+
const raw2 = handle.native.find(filter);
|
|
184
|
+
const cursor = raw2;
|
|
185
|
+
const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
|
|
186
|
+
return new TypedFindCursor(cursor, handle.definition, mode);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/crud/insert.ts
|
|
190
|
+
import { z as z3 } from "zod";
|
|
191
|
+
async function insertOne(handle, doc) {
|
|
192
|
+
let parsed;
|
|
193
|
+
try {
|
|
194
|
+
parsed = handle.definition.schema.parse(doc);
|
|
195
|
+
} catch (err) {
|
|
196
|
+
if (err instanceof z3.ZodError) {
|
|
197
|
+
throw new ZodmonValidationError(handle.definition.name, err);
|
|
198
|
+
}
|
|
199
|
+
throw err;
|
|
200
|
+
}
|
|
201
|
+
await handle.native.insertOne(parsed);
|
|
202
|
+
return parsed;
|
|
203
|
+
}
|
|
204
|
+
async function insertMany(handle, docs) {
|
|
205
|
+
if (docs.length === 0) return [];
|
|
206
|
+
const parsed = [];
|
|
207
|
+
for (const doc of docs) {
|
|
208
|
+
try {
|
|
209
|
+
parsed.push(handle.definition.schema.parse(doc));
|
|
210
|
+
} catch (err) {
|
|
211
|
+
if (err instanceof z3.ZodError) {
|
|
212
|
+
throw new ZodmonValidationError(handle.definition.name, err);
|
|
213
|
+
}
|
|
214
|
+
throw err;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
await handle.native.insertMany(parsed);
|
|
218
|
+
return parsed;
|
|
219
|
+
}
|
|
220
|
+
|
|
4
221
|
// src/client/handle.ts
|
|
5
222
|
var CollectionHandle = class {
|
|
6
223
|
/** The collection definition containing schema, name, and index metadata. */
|
|
7
224
|
definition;
|
|
8
|
-
/** The underlying MongoDB driver collection, typed to
|
|
225
|
+
/** The underlying MongoDB driver collection, typed to the inferred document type. */
|
|
9
226
|
native;
|
|
10
227
|
constructor(definition, native) {
|
|
11
228
|
this.definition = definition;
|
|
12
229
|
this.native = native;
|
|
13
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Insert a single document into the collection.
|
|
233
|
+
*
|
|
234
|
+
* Validates the input against the collection's Zod schema before writing.
|
|
235
|
+
* Schema defaults (including auto-generated `_id`) are applied during
|
|
236
|
+
* validation. Returns the full document with all defaults filled in.
|
|
237
|
+
*
|
|
238
|
+
* @param doc - The document to insert. Fields with `.default()` are optional.
|
|
239
|
+
* @returns The inserted document with `_id` and all defaults applied.
|
|
240
|
+
* @throws {ZodmonValidationError} When the document fails schema validation.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* const users = db.use(Users)
|
|
245
|
+
* const user = await users.insertOne({ name: 'Ada' })
|
|
246
|
+
* console.log(user._id) // ObjectId (auto-generated)
|
|
247
|
+
* console.log(user.role) // 'user' (schema default)
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
async insertOne(doc) {
|
|
251
|
+
return await insertOne(this, doc);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Insert multiple documents into the collection.
|
|
255
|
+
*
|
|
256
|
+
* Validates every document against the collection's Zod schema before
|
|
257
|
+
* writing any to MongoDB. If any document fails validation, none are
|
|
258
|
+
* inserted (fail-fast before the driver call).
|
|
259
|
+
*
|
|
260
|
+
* @param docs - The documents to insert.
|
|
261
|
+
* @returns The inserted documents with `_id` and all defaults applied.
|
|
262
|
+
* @throws {ZodmonValidationError} When any document fails schema validation.
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```ts
|
|
266
|
+
* const created = await users.insertMany([
|
|
267
|
+
* { name: 'Ada' },
|
|
268
|
+
* { name: 'Bob', role: 'admin' },
|
|
269
|
+
* ])
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
async insertMany(docs) {
|
|
273
|
+
return await insertMany(this, docs);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Find a single document matching the filter.
|
|
277
|
+
*
|
|
278
|
+
* Queries MongoDB, then validates the fetched document against the collection's
|
|
279
|
+
* Zod schema. Validation mode is resolved from the per-query option, falling
|
|
280
|
+
* back to the collection-level default (which defaults to `'strict'`).
|
|
281
|
+
*
|
|
282
|
+
* @param filter - Type-safe filter to match documents.
|
|
283
|
+
* @param options - Optional projection and validation overrides.
|
|
284
|
+
* @returns The matched document, or `null` if no document matches.
|
|
285
|
+
* @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```ts
|
|
289
|
+
* const users = db.use(Users)
|
|
290
|
+
* const user = await users.findOne({ name: 'Ada' })
|
|
291
|
+
* if (user) console.log(user.role)
|
|
292
|
+
* ```
|
|
293
|
+
*/
|
|
294
|
+
async findOne(filter, options) {
|
|
295
|
+
return await findOne(this, filter, options);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Find a single document matching the filter, or throw if none exists.
|
|
299
|
+
*
|
|
300
|
+
* Behaves identically to {@link findOne} but throws {@link ZodmonNotFoundError}
|
|
301
|
+
* instead of returning `null` when no document matches the filter.
|
|
302
|
+
*
|
|
303
|
+
* @param filter - Type-safe filter to match documents.
|
|
304
|
+
* @param options - Optional projection and validation overrides.
|
|
305
|
+
* @returns The matched document (never null).
|
|
306
|
+
* @throws {ZodmonNotFoundError} When no document matches the filter.
|
|
307
|
+
* @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```ts
|
|
311
|
+
* const users = db.use(Users)
|
|
312
|
+
* const user = await users.findOneOrThrow({ name: 'Ada' })
|
|
313
|
+
* console.log(user.role) // guaranteed non-null
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
async findOneOrThrow(filter, options) {
|
|
317
|
+
return await findOneOrThrow(this, filter, options);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Find all documents matching the filter, returning a chainable typed cursor.
|
|
321
|
+
*
|
|
322
|
+
* The cursor is lazy — no query is executed until a terminal method
|
|
323
|
+
* (`toArray`, `for await`) is called. Use `sort`, `skip`, and `limit`
|
|
324
|
+
* to shape the query before executing.
|
|
325
|
+
*
|
|
326
|
+
* @param filter - Type-safe filter to match documents.
|
|
327
|
+
* @param options - Optional validation overrides.
|
|
328
|
+
* @returns A typed cursor for chaining query modifiers.
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* ```ts
|
|
332
|
+
* const users = db.use(Users)
|
|
333
|
+
* const admins = await users.find({ role: 'admin' })
|
|
334
|
+
* .sort({ name: 1 })
|
|
335
|
+
* .limit(10)
|
|
336
|
+
* .toArray()
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
find(filter, options) {
|
|
340
|
+
return find(this, filter, options);
|
|
341
|
+
}
|
|
14
342
|
};
|
|
15
343
|
|
|
16
344
|
// src/client/client.ts
|
|
@@ -37,7 +365,10 @@ var Database = class {
|
|
|
37
365
|
use(def) {
|
|
38
366
|
this._collections.set(def.name, def);
|
|
39
367
|
const native = this._db.collection(def.name);
|
|
40
|
-
return new CollectionHandle(
|
|
368
|
+
return new CollectionHandle(
|
|
369
|
+
def,
|
|
370
|
+
native
|
|
371
|
+
);
|
|
41
372
|
}
|
|
42
373
|
/**
|
|
43
374
|
* Synchronize indexes defined in registered collections with MongoDB.
|
|
@@ -85,13 +416,14 @@ function createClient(uri, dbNameOrOptions, maybeOptions) {
|
|
|
85
416
|
}
|
|
86
417
|
|
|
87
418
|
// src/collection/collection.ts
|
|
88
|
-
import {
|
|
419
|
+
import { ObjectId as ObjectId2 } from "mongodb";
|
|
420
|
+
import { z as z7 } from "zod";
|
|
89
421
|
|
|
90
422
|
// src/schema/extensions.ts
|
|
91
|
-
import { z as
|
|
423
|
+
import { z as z5 } from "zod";
|
|
92
424
|
|
|
93
425
|
// src/schema/ref.ts
|
|
94
|
-
import { z } from "zod";
|
|
426
|
+
import { z as z4 } from "zod";
|
|
95
427
|
var refMetadata = /* @__PURE__ */ new WeakMap();
|
|
96
428
|
function getRefMetadata(schema) {
|
|
97
429
|
if (typeof schema !== "object" || schema === null) return void 0;
|
|
@@ -99,7 +431,7 @@ function getRefMetadata(schema) {
|
|
|
99
431
|
}
|
|
100
432
|
var REF_GUARD = /* @__PURE__ */ Symbol.for("zodmon_ref");
|
|
101
433
|
function installRefExtension() {
|
|
102
|
-
const proto =
|
|
434
|
+
const proto = z4.ZodType.prototype;
|
|
103
435
|
if (REF_GUARD in proto) return;
|
|
104
436
|
Object.defineProperty(proto, "ref", {
|
|
105
437
|
value(collection2) {
|
|
@@ -126,7 +458,7 @@ function getIndexMetadata(schema) {
|
|
|
126
458
|
}
|
|
127
459
|
var GUARD = /* @__PURE__ */ Symbol.for("zodmon_extensions");
|
|
128
460
|
function installExtensions() {
|
|
129
|
-
const proto =
|
|
461
|
+
const proto = z5.ZodType.prototype;
|
|
130
462
|
if (GUARD in proto) return;
|
|
131
463
|
Object.defineProperty(proto, "index", {
|
|
132
464
|
/**
|
|
@@ -225,10 +557,10 @@ installExtensions();
|
|
|
225
557
|
|
|
226
558
|
// src/schema/object-id.ts
|
|
227
559
|
import { ObjectId } from "mongodb";
|
|
228
|
-
import { z as
|
|
560
|
+
import { z as z6 } from "zod";
|
|
229
561
|
var OBJECT_ID_HEX = /^[a-f\d]{24}$/i;
|
|
230
562
|
function objectId() {
|
|
231
|
-
return
|
|
563
|
+
return z6.custom((val) => {
|
|
232
564
|
if (val instanceof ObjectId) return true;
|
|
233
565
|
return typeof val === "string" && OBJECT_ID_HEX.test(val);
|
|
234
566
|
}, "Invalid ObjectId").transform((val) => val instanceof ObjectId ? val : ObjectId.createFromHexString(val));
|
|
@@ -246,8 +578,8 @@ function extractFieldIndexes(shape) {
|
|
|
246
578
|
return result;
|
|
247
579
|
}
|
|
248
580
|
function collection(name, shape, options) {
|
|
249
|
-
const resolvedShape = "_id" in shape ? shape : { _id: objectId(), ...shape };
|
|
250
|
-
const schema =
|
|
581
|
+
const resolvedShape = "_id" in shape ? shape : { _id: objectId().default(() => new ObjectId2()), ...shape };
|
|
582
|
+
const schema = z7.object(resolvedShape);
|
|
251
583
|
const fieldIndexes = extractFieldIndexes(shape);
|
|
252
584
|
const { indexes: compoundIndexes, validation, ...rest } = options ?? {};
|
|
253
585
|
return {
|
|
@@ -298,14 +630,14 @@ function index(fields) {
|
|
|
298
630
|
}
|
|
299
631
|
|
|
300
632
|
// src/helpers/oid.ts
|
|
301
|
-
import { ObjectId as
|
|
633
|
+
import { ObjectId as ObjectId3 } from "mongodb";
|
|
302
634
|
function oid(value) {
|
|
303
|
-
if (value === void 0) return new
|
|
304
|
-
if (value instanceof
|
|
305
|
-
return
|
|
635
|
+
if (value === void 0) return new ObjectId3();
|
|
636
|
+
if (value instanceof ObjectId3) return value;
|
|
637
|
+
return ObjectId3.createFromHexString(value);
|
|
306
638
|
}
|
|
307
639
|
function isOid(value) {
|
|
308
|
-
return value instanceof
|
|
640
|
+
return value instanceof ObjectId3;
|
|
309
641
|
}
|
|
310
642
|
|
|
311
643
|
// src/query/operators.ts
|
|
@@ -346,13 +678,21 @@ export {
|
|
|
346
678
|
CollectionHandle,
|
|
347
679
|
Database,
|
|
348
680
|
IndexBuilder,
|
|
681
|
+
TypedFindCursor,
|
|
682
|
+
ZodmonNotFoundError,
|
|
683
|
+
ZodmonValidationError,
|
|
349
684
|
collection,
|
|
350
685
|
createClient,
|
|
351
686
|
extractDbName,
|
|
352
687
|
extractFieldIndexes,
|
|
688
|
+
find,
|
|
689
|
+
findOne,
|
|
690
|
+
findOneOrThrow,
|
|
353
691
|
getIndexMetadata,
|
|
354
692
|
getRefMetadata,
|
|
355
693
|
index,
|
|
694
|
+
insertMany,
|
|
695
|
+
insertOne,
|
|
356
696
|
installExtensions,
|
|
357
697
|
installRefExtension,
|
|
358
698
|
isOid,
|