@zodmon/core 0.10.0 → 0.11.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 +124 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +524 -125
- package/dist/index.d.ts +524 -125
- package/dist/index.js +122 -88
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -69,6 +69,7 @@ __export(index_exports, {
|
|
|
69
69
|
createExpressionBuilder: () => createExpressionBuilder,
|
|
70
70
|
deleteMany: () => deleteMany,
|
|
71
71
|
deleteOne: () => deleteOne,
|
|
72
|
+
deriveProjectedSchema: () => deriveProjectedSchema,
|
|
72
73
|
extractComparableOptions: () => extractComparableOptions,
|
|
73
74
|
extractDbName: () => extractDbName,
|
|
74
75
|
extractFieldIndexes: () => extractFieldIndexes,
|
|
@@ -83,6 +84,7 @@ __export(index_exports, {
|
|
|
83
84
|
index: () => index,
|
|
84
85
|
insertMany: () => insertMany,
|
|
85
86
|
insertOne: () => insertOne,
|
|
87
|
+
isInclusionProjection: () => isInclusionProjection,
|
|
86
88
|
isOid: () => isOid,
|
|
87
89
|
objectId: () => objectId,
|
|
88
90
|
oid: () => oid,
|
|
@@ -110,30 +112,24 @@ var $avg = (field) => ({
|
|
|
110
112
|
__accum: true,
|
|
111
113
|
expr: { $avg: field }
|
|
112
114
|
});
|
|
113
|
-
|
|
114
|
-
__accum: true,
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
expr: { $
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
__accum: true,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
expr: { $push: field }
|
|
132
|
-
});
|
|
133
|
-
var $addToSet = (field) => ({
|
|
134
|
-
__accum: true,
|
|
135
|
-
expr: { $addToSet: field }
|
|
136
|
-
});
|
|
115
|
+
function $min(field) {
|
|
116
|
+
return { __accum: true, expr: { $min: field } };
|
|
117
|
+
}
|
|
118
|
+
function $max(field) {
|
|
119
|
+
return { __accum: true, expr: { $max: field } };
|
|
120
|
+
}
|
|
121
|
+
function $first(field) {
|
|
122
|
+
return { __accum: true, expr: { $first: field } };
|
|
123
|
+
}
|
|
124
|
+
function $last(field) {
|
|
125
|
+
return { __accum: true, expr: { $last: field } };
|
|
126
|
+
}
|
|
127
|
+
function $push(field) {
|
|
128
|
+
return { __accum: true, expr: { $push: field } };
|
|
129
|
+
}
|
|
130
|
+
function $addToSet(field) {
|
|
131
|
+
return { __accum: true, expr: { $addToSet: field } };
|
|
132
|
+
}
|
|
137
133
|
function createAccumulatorBuilder() {
|
|
138
134
|
return {
|
|
139
135
|
count: () => ({ __accum: true, expr: { $sum: 1 } }),
|
|
@@ -209,9 +205,9 @@ var ZodmonError = class extends Error {
|
|
|
209
205
|
/** The underlying error that caused this error, if any. */
|
|
210
206
|
cause;
|
|
211
207
|
constructor(message, collection2, options) {
|
|
212
|
-
super(message);
|
|
208
|
+
super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
213
209
|
this.collection = collection2;
|
|
214
|
-
if (options?.cause) {
|
|
210
|
+
if (options?.cause !== void 0) {
|
|
215
211
|
this.cause = options.cause;
|
|
216
212
|
}
|
|
217
213
|
}
|
|
@@ -1339,6 +1335,54 @@ function resolveSortKeys(sortSpec) {
|
|
|
1339
1335
|
return entries;
|
|
1340
1336
|
}
|
|
1341
1337
|
|
|
1338
|
+
// src/query/projection.ts
|
|
1339
|
+
function isIncludeValue(value) {
|
|
1340
|
+
return value === 1 || value === true;
|
|
1341
|
+
}
|
|
1342
|
+
function isExcludeValue(value) {
|
|
1343
|
+
return value === 0 || value === false;
|
|
1344
|
+
}
|
|
1345
|
+
function isInclusionProjection(projection) {
|
|
1346
|
+
for (const key of Object.keys(projection)) {
|
|
1347
|
+
if (key === "_id") continue;
|
|
1348
|
+
const value = projection[key];
|
|
1349
|
+
if (value !== void 0 && isIncludeValue(value)) return true;
|
|
1350
|
+
}
|
|
1351
|
+
return false;
|
|
1352
|
+
}
|
|
1353
|
+
function buildPickMask(projection, schemaKeys) {
|
|
1354
|
+
const mask = {};
|
|
1355
|
+
const idValue = projection._id;
|
|
1356
|
+
if (!(idValue !== void 0 && isExcludeValue(idValue)) && schemaKeys.has("_id")) {
|
|
1357
|
+
mask._id = true;
|
|
1358
|
+
}
|
|
1359
|
+
for (const key of Object.keys(projection)) {
|
|
1360
|
+
if (key === "_id") continue;
|
|
1361
|
+
const value = projection[key];
|
|
1362
|
+
if (value !== void 0 && isIncludeValue(value) && schemaKeys.has(key)) {
|
|
1363
|
+
mask[key] = true;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
return mask;
|
|
1367
|
+
}
|
|
1368
|
+
function buildOmitMask(projection, schemaKeys) {
|
|
1369
|
+
const mask = {};
|
|
1370
|
+
for (const key of Object.keys(projection)) {
|
|
1371
|
+
const value = projection[key];
|
|
1372
|
+
if (value !== void 0 && isExcludeValue(value) && schemaKeys.has(key)) {
|
|
1373
|
+
mask[key] = true;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
return mask;
|
|
1377
|
+
}
|
|
1378
|
+
function deriveProjectedSchema(schema, projection) {
|
|
1379
|
+
const schemaKeys = new Set(Object.keys(schema.shape));
|
|
1380
|
+
if (isInclusionProjection(projection)) {
|
|
1381
|
+
return schema.pick(buildPickMask(projection, schemaKeys));
|
|
1382
|
+
}
|
|
1383
|
+
return schema.omit(buildOmitMask(projection, schemaKeys));
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1342
1386
|
// src/query/cursor.ts
|
|
1343
1387
|
var TypedFindCursor = class {
|
|
1344
1388
|
/** @internal */
|
|
@@ -1357,6 +1401,8 @@ var TypedFindCursor = class {
|
|
|
1357
1401
|
/** @internal */
|
|
1358
1402
|
sortSpec;
|
|
1359
1403
|
/** @internal */
|
|
1404
|
+
projectedSchema;
|
|
1405
|
+
/** @internal */
|
|
1360
1406
|
constructor(cursor, definition, mode, nativeCollection, filter) {
|
|
1361
1407
|
this.cursor = cursor;
|
|
1362
1408
|
this.schema = definition.schema;
|
|
@@ -1365,6 +1411,7 @@ var TypedFindCursor = class {
|
|
|
1365
1411
|
this.nativeCollection = nativeCollection;
|
|
1366
1412
|
this.filter = filter;
|
|
1367
1413
|
this.sortSpec = null;
|
|
1414
|
+
this.projectedSchema = null;
|
|
1368
1415
|
}
|
|
1369
1416
|
/**
|
|
1370
1417
|
* Set the sort order for the query.
|
|
@@ -1438,6 +1485,40 @@ var TypedFindCursor = class {
|
|
|
1438
1485
|
this.cursor.hint(indexName);
|
|
1439
1486
|
return this;
|
|
1440
1487
|
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Apply a projection to narrow the returned fields.
|
|
1490
|
+
*
|
|
1491
|
+
* Inclusion projections (`{ name: 1 }`) return only the specified fields
|
|
1492
|
+
* plus `_id` (unless `_id: 0`). Exclusion projections (`{ email: 0 }`)
|
|
1493
|
+
* return all fields except those excluded.
|
|
1494
|
+
*
|
|
1495
|
+
* The cursor's output type is narrowed at compile time. A derived Zod
|
|
1496
|
+
* schema is built for runtime validation of the projected fields.
|
|
1497
|
+
*
|
|
1498
|
+
* Projects from the original document type, not from a previous projection.
|
|
1499
|
+
* Calling `.project()` twice overrides the previous projection.
|
|
1500
|
+
*
|
|
1501
|
+
* @param spec - Type-safe projection document.
|
|
1502
|
+
* @returns A new cursor with the narrowed output type.
|
|
1503
|
+
*
|
|
1504
|
+
* @example
|
|
1505
|
+
* ```ts
|
|
1506
|
+
* const names = await find(users, {})
|
|
1507
|
+
* .project({ name: 1 })
|
|
1508
|
+
* .sort({ name: 1 })
|
|
1509
|
+
* .toArray()
|
|
1510
|
+
* // names[0].name ✓
|
|
1511
|
+
* // names[0].email TS error
|
|
1512
|
+
* ```
|
|
1513
|
+
*/
|
|
1514
|
+
project(spec) {
|
|
1515
|
+
this.cursor.project(spec);
|
|
1516
|
+
this.projectedSchema = deriveProjectedSchema(
|
|
1517
|
+
this.schema,
|
|
1518
|
+
spec
|
|
1519
|
+
);
|
|
1520
|
+
return this;
|
|
1521
|
+
}
|
|
1441
1522
|
async paginate(opts) {
|
|
1442
1523
|
const sortRecord = this.sortSpec ? this.sortSpec : null;
|
|
1443
1524
|
const sortKeys2 = resolveSortKeys(sortRecord);
|
|
@@ -1554,8 +1635,9 @@ var TypedFindCursor = class {
|
|
|
1554
1635
|
if (this.mode === false || this.mode === "passthrough") {
|
|
1555
1636
|
return raw2;
|
|
1556
1637
|
}
|
|
1638
|
+
const schema = this.projectedSchema ?? this.schema;
|
|
1557
1639
|
try {
|
|
1558
|
-
return
|
|
1640
|
+
return schema.parse(raw2);
|
|
1559
1641
|
} catch (err) {
|
|
1560
1642
|
if (err instanceof import_zod3.z.ZodError) {
|
|
1561
1643
|
throw new ZodmonValidationError(this.collectionName, err, raw2);
|
|
@@ -1568,7 +1650,8 @@ var TypedFindCursor = class {
|
|
|
1568
1650
|
// src/crud/find.ts
|
|
1569
1651
|
async function findOne(handle, filter, options) {
|
|
1570
1652
|
checkUnindexedFields(handle.definition, filter);
|
|
1571
|
-
const
|
|
1653
|
+
const project = options && "project" in options ? options.project : void 0;
|
|
1654
|
+
const findOptions = project ? { projection: project } : void 0;
|
|
1572
1655
|
let raw2;
|
|
1573
1656
|
try {
|
|
1574
1657
|
raw2 = await handle.native.findOne(filter, findOptions);
|
|
@@ -1580,8 +1663,12 @@ async function findOne(handle, filter, options) {
|
|
|
1580
1663
|
if (mode === false || mode === "passthrough") {
|
|
1581
1664
|
return raw2;
|
|
1582
1665
|
}
|
|
1666
|
+
const schema = project ? deriveProjectedSchema(
|
|
1667
|
+
handle.definition.schema,
|
|
1668
|
+
project
|
|
1669
|
+
) : handle.definition.schema;
|
|
1583
1670
|
try {
|
|
1584
|
-
return
|
|
1671
|
+
return schema.parse(raw2);
|
|
1585
1672
|
} catch (err) {
|
|
1586
1673
|
if (err instanceof import_zod4.z.ZodError) {
|
|
1587
1674
|
throw new ZodmonValidationError(handle.definition.name, err, raw2);
|
|
@@ -1601,7 +1688,12 @@ function find(handle, filter, options) {
|
|
|
1601
1688
|
const raw2 = handle.native.find(filter);
|
|
1602
1689
|
const cursor = raw2;
|
|
1603
1690
|
const mode = options?.validate !== void 0 ? options.validate : handle.definition.options.validation;
|
|
1604
|
-
|
|
1691
|
+
const typedCursor = new TypedFindCursor(cursor, handle.definition, mode, handle.native, filter);
|
|
1692
|
+
const project = options && "project" in options ? options.project : void 0;
|
|
1693
|
+
if (project) {
|
|
1694
|
+
return typedCursor.project(project);
|
|
1695
|
+
}
|
|
1696
|
+
return typedCursor;
|
|
1605
1697
|
}
|
|
1606
1698
|
|
|
1607
1699
|
// src/crud/insert.ts
|
|
@@ -1750,70 +1842,12 @@ var CollectionHandle = class {
|
|
|
1750
1842
|
async insertMany(docs) {
|
|
1751
1843
|
return await insertMany(this, docs);
|
|
1752
1844
|
}
|
|
1753
|
-
/**
|
|
1754
|
-
* Find a single document matching the filter.
|
|
1755
|
-
*
|
|
1756
|
-
* Queries MongoDB, then validates the fetched document against the collection's
|
|
1757
|
-
* Zod schema. Validation mode is resolved from the per-query option, falling
|
|
1758
|
-
* back to the collection-level default (which defaults to `'strict'`).
|
|
1759
|
-
*
|
|
1760
|
-
* @param filter - Type-safe filter to match documents.
|
|
1761
|
-
* @param options - Optional projection and validation overrides.
|
|
1762
|
-
* @returns The matched document, or `null` if no document matches.
|
|
1763
|
-
* @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
|
|
1764
|
-
*
|
|
1765
|
-
* @example
|
|
1766
|
-
* ```ts
|
|
1767
|
-
* const users = db.use(Users)
|
|
1768
|
-
* const user = await users.findOne({ name: 'Ada' })
|
|
1769
|
-
* if (user) console.log(user.role)
|
|
1770
|
-
* ```
|
|
1771
|
-
*/
|
|
1772
1845
|
async findOne(filter, options) {
|
|
1773
1846
|
return await findOne(this, filter, options);
|
|
1774
1847
|
}
|
|
1775
|
-
/**
|
|
1776
|
-
* Find a single document matching the filter, or throw if none exists.
|
|
1777
|
-
*
|
|
1778
|
-
* Behaves identically to {@link findOne} but throws {@link ZodmonNotFoundError}
|
|
1779
|
-
* instead of returning `null` when no document matches the filter.
|
|
1780
|
-
*
|
|
1781
|
-
* @param filter - Type-safe filter to match documents.
|
|
1782
|
-
* @param options - Optional projection and validation overrides.
|
|
1783
|
-
* @returns The matched document (never null).
|
|
1784
|
-
* @throws {ZodmonNotFoundError} When no document matches the filter.
|
|
1785
|
-
* @throws {ZodmonValidationError} When the fetched document fails schema validation in strict mode.
|
|
1786
|
-
*
|
|
1787
|
-
* @example
|
|
1788
|
-
* ```ts
|
|
1789
|
-
* const users = db.use(Users)
|
|
1790
|
-
* const user = await users.findOneOrThrow({ name: 'Ada' })
|
|
1791
|
-
* console.log(user.role) // guaranteed non-null
|
|
1792
|
-
* ```
|
|
1793
|
-
*/
|
|
1794
1848
|
async findOneOrThrow(filter, options) {
|
|
1795
1849
|
return await findOneOrThrow(this, filter, options);
|
|
1796
1850
|
}
|
|
1797
|
-
/**
|
|
1798
|
-
* Find all documents matching the filter, returning a chainable typed cursor.
|
|
1799
|
-
*
|
|
1800
|
-
* The cursor is lazy — no query is executed until a terminal method
|
|
1801
|
-
* (`toArray`, `for await`) is called. Use `sort`, `skip`, and `limit`
|
|
1802
|
-
* to shape the query before executing.
|
|
1803
|
-
*
|
|
1804
|
-
* @param filter - Type-safe filter to match documents.
|
|
1805
|
-
* @param options - Optional validation overrides.
|
|
1806
|
-
* @returns A typed cursor for chaining query modifiers.
|
|
1807
|
-
*
|
|
1808
|
-
* @example
|
|
1809
|
-
* ```ts
|
|
1810
|
-
* const users = db.use(Users)
|
|
1811
|
-
* const admins = await users.find({ role: 'admin' })
|
|
1812
|
-
* .sort({ name: 1 })
|
|
1813
|
-
* .limit(10)
|
|
1814
|
-
* .toArray()
|
|
1815
|
-
* ```
|
|
1816
|
-
*/
|
|
1817
1851
|
find(filter, options) {
|
|
1818
1852
|
return find(this, filter, options);
|
|
1819
1853
|
}
|
|
@@ -2402,6 +2436,7 @@ var $ = {
|
|
|
2402
2436
|
createExpressionBuilder,
|
|
2403
2437
|
deleteMany,
|
|
2404
2438
|
deleteOne,
|
|
2439
|
+
deriveProjectedSchema,
|
|
2405
2440
|
extractComparableOptions,
|
|
2406
2441
|
extractDbName,
|
|
2407
2442
|
extractFieldIndexes,
|
|
@@ -2416,6 +2451,7 @@ var $ = {
|
|
|
2416
2451
|
index,
|
|
2417
2452
|
insertMany,
|
|
2418
2453
|
insertOne,
|
|
2454
|
+
isInclusionProjection,
|
|
2419
2455
|
isOid,
|
|
2420
2456
|
objectId,
|
|
2421
2457
|
oid,
|