@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 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
- var $min = (field) => ({
114
- __accum: true,
115
- expr: { $min: field }
116
- });
117
- var $max = (field) => ({
118
- __accum: true,
119
- expr: { $max: field }
120
- });
121
- var $first = (field) => ({
122
- __accum: true,
123
- expr: { $first: field }
124
- });
125
- var $last = (field) => ({
126
- __accum: true,
127
- expr: { $last: field }
128
- });
129
- var $push = (field) => ({
130
- __accum: true,
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 this.schema.parse(raw2);
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 findOptions = options?.project ? { projection: options.project } : void 0;
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 handle.definition.schema.parse(raw2);
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
- return new TypedFindCursor(cursor, handle.definition, mode, handle.native, filter);
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,