@lossless.org/client 1.20.2 → 1.21.1

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.
Files changed (41) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/nosqldb/classes.atomicdelete.js +3 -3
  3. package/dist_ts/nosqldb/classes.atomicfindoneandupdate.js +6 -3
  4. package/dist_ts/nosqldb/classes.atomicupdate.js +4 -4
  5. package/dist_ts/nosqldb/classes.bootstraprecord.js +10 -5
  6. package/dist_ts/nosqldb/classes.collection.js +53 -16
  7. package/dist_ts/nosqldb/classes.collectiontopology.d.ts +8 -1
  8. package/dist_ts/nosqldb/classes.collectiontopology.js +10 -2
  9. package/dist_ts/nosqldb/classes.cursor.d.ts +4 -1
  10. package/dist_ts/nosqldb/classes.cursor.js +14 -3
  11. package/dist_ts/nosqldb/classes.db.d.ts +22 -0
  12. package/dist_ts/nosqldb/classes.db.js +25 -1
  13. package/dist_ts/nosqldb/classes.doc.js +35 -5
  14. package/dist_ts/nosqldb/classes.exactpersistence.d.ts +6 -0
  15. package/dist_ts/nosqldb/classes.exactpersistence.js +70 -13
  16. package/dist_ts/nosqldb/classes.readtracker.d.ts +89 -0
  17. package/dist_ts/nosqldb/classes.readtracker.js +876 -0
  18. package/dist_ts/nosqldb/classes.session.d.ts +30 -7
  19. package/dist_ts/nosqldb/classes.session.js +119 -26
  20. package/dist_ts/nosqldb/classes.storedinspection.js +12 -4
  21. package/dist_ts/nosqldb/classes.testdatabase.js +16 -9
  22. package/dist_ts/nosqldb/classes.writeobserver.d.ts +11 -0
  23. package/dist_ts/nosqldb/classes.writeobserver.js +1 -1
  24. package/package.json +1 -1
  25. package/readme.md +1 -0
  26. package/ts/00_commitinfo_data.ts +1 -1
  27. package/ts/nosqldb/classes.atomicdelete.ts +16 -2
  28. package/ts/nosqldb/classes.atomicfindoneandupdate.ts +5 -0
  29. package/ts/nosqldb/classes.atomicupdate.ts +10 -1
  30. package/ts/nosqldb/classes.bootstraprecord.ts +7 -4
  31. package/ts/nosqldb/classes.collection.ts +56 -12
  32. package/ts/nosqldb/classes.collectiontopology.ts +15 -1
  33. package/ts/nosqldb/classes.cursor.ts +14 -1
  34. package/ts/nosqldb/classes.db.ts +32 -0
  35. package/ts/nosqldb/classes.doc.ts +32 -2
  36. package/ts/nosqldb/classes.exactpersistence.ts +93 -19
  37. package/ts/nosqldb/classes.readtracker.ts +1031 -0
  38. package/ts/nosqldb/classes.session.ts +166 -30
  39. package/ts/nosqldb/classes.storedinspection.ts +15 -3
  40. package/ts/nosqldb/classes.testdatabase.ts +14 -8
  41. package/ts/nosqldb/classes.writeobserver.ts +11 -0
@@ -136,4 +136,4 @@ export const captureSmartdataWrite = (dbArg, writeArg) => {
136
136
  }
137
137
  };
138
138
  };
139
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy53cml0ZW9ic2VydmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvbm9zcWxkYi9jbGFzc2VzLndyaXRlb2JzZXJ2ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDakUsT0FBTyxFQUFFLHlCQUF5QixFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDckUsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQXlEdEMsTUFBTSxxQkFBcUIsR0FBRyxFQUFFLENBQUM7QUFDakMsTUFBTSxhQUFhLEdBQUcsSUFBSSxPQUFPLEVBQW9DLENBQUM7QUFFdEUsTUFBTSx3QkFBd0IsR0FBRyxDQUFDLFVBQW1CLEVBQWtCLEVBQUU7SUFDdkUsTUFBTSxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsR0FBRyxpQkFBaUIsQ0FBQyxVQUFVLEVBQUUsQ0FBQyxRQUFRLEVBQUUsVUFBVSxDQUFDLENBQUMsQ0FBQztJQUNuRixJQUFJLE9BQU8sUUFBUSxLQUFLLFVBQVUsRUFBRSxDQUFDO1FBQ25DLE1BQU0sSUFBSSx5QkFBeUIsQ0FBQyxrQkFBa0IsRUFDcEQsMERBQTBELENBQUMsQ0FBQztJQUNoRSxDQUFDO0lBQ0QsSUFDRSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDO1dBQ25CLE1BQU0sQ0FBQyxNQUFNLEtBQUssQ0FBQztXQUNuQixNQUFNLENBQUMsTUFBTSxHQUFHLHFCQUFxQjtXQUNyQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUUsQ0FDMUIsT0FBTyxRQUFRLEtBQUssUUFBUTtlQUN6QixRQUFRLENBQUMsTUFBTSxLQUFLLENBQUM7ZUFDckIsUUFBUSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUM7ZUFDdEIsUUFBUSxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQztXQUMzQixJQUFJ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
139
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy53cml0ZW9ic2VydmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvbm9zcWxkYi9jbGFzc2VzLndyaXRlb2JzZXJ2ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDakUsT0FBTyxFQUFFLHlCQUF5QixFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDckUsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQW9FdEMsTUFBTSxxQkFBcUIsR0FBRyxFQUFFLENBQUM7QUFDakMsTUFBTSxhQUFhLEdBQUcsSUFBSSxPQUFPLEVBQW9DLENBQUM7QUFFdEUsTUFBTSx3QkFBd0IsR0FBRyxDQUFDLFVBQW1CLEVBQWtCLEVBQUU7SUFDdkUsTUFBTSxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsR0FBRyxpQkFBaUIsQ0FBQyxVQUFVLEVBQUUsQ0FBQyxRQUFRLEVBQUUsVUFBVSxDQUFDLENBQUMsQ0FBQztJQUNuRixJQUFJLE9BQU8sUUFBUSxLQUFLLFVBQVUsRUFBRSxDQUFDO1FBQ25DLE1BQU0sSUFBSSx5QkFBeUIsQ0FBQyxrQkFBa0IsRUFDcEQsMERBQTBELENBQUMsQ0FBQztJQUNoRSxDQUFDO0lBQ0QsSUFDRSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDO1dBQ25CLE1BQU0sQ0FBQyxNQUFNLEtBQUssQ0FBQztXQUNuQixNQUFNLENBQUMsTUFBTSxHQUFHLHFCQUFxQjtXQUNyQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUUsQ0FDMUIsT0FBTyxRQUFRLEtBQUssUUFBUTtlQUN6QixRQUFRLENBQUMsTUFBTSxLQUFLLENBQUM7ZUFDckIsUUFBUSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUM7ZUFDdEIsUUFBUSxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQztXQUMzQixJQUFJ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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lossless.org/client",
3
- "version": "1.20.2",
3
+ "version": "1.21.1",
4
4
  "private": false,
5
5
  "description": "One typed client for NoSQLDB, MongoDB, SQLDB, MariaDB, ClickHouse and S3 object storage.",
6
6
  "exports": {
package/readme.md CHANGED
@@ -117,6 +117,7 @@ Family imports preserve the established SmartData and SmartBucket names and cons
117
117
  | Per-instance model binding | `bindDocToManager(manager, Model)` returns a subclass of a model declared with `@managed()` and no manager argument, named like the model and bound to that manager, so several databases use the same models at once; any other binding is refused with `invalid_configuration` |
118
118
  | Subclasses need their own binding | a subclass of a bound model that declares decorators or a persistence policy of its own and no `@Collection()`, `@managed()` or `defineCollectionModel()` binding is refused with `invalid_configuration` on every read and write, so it never writes through the parent's schema; an undecorated subclass keeps the inherited binding |
119
119
  | Observing this process's writes | `db.observeWrites({ fields, listener })` — one frozen report per acknowledged model write, `{ collection, kind, documents, anchors }` with the requested top-level fields of the written documents and of the selector's equality anchors; writes inside a `db.createSession()` transaction are reported after the commit, never after a rollback; works on NoSQLDB, where `watch()` is unsupported |
120
+ | Following this process's reads | `db.trackReads(fn)` resolves `{ result, reads }`; `reads.changed` turns true once a write this process makes through the database's models could have changed what a model read of `fn` returned, judged by the read's query and the ids it returned, and a write it cannot place counts as one that did, so a cache keyed by `reads` rebuilds only what a write touched; works on NoSQLDB |
120
121
  | Disposable test databases on a unix socket | `SmartdataTestDatabase.create({ connection: { socketPath } })` from `@lossless.org/client/testsupport` reaches an engine that listens only on a unix-domain socket, fault helpers included; the loopback `host`/`port` form is unchanged |
121
122
 
122
123
  `docs/source/smartdata/readme.md` documents each one with its refusals.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@lossless.org/client',
6
- version: '1.20.2',
6
+ version: '1.21.1',
7
7
  description: 'One typed client for NoSQLDB, MongoDB, SQLDB, MariaDB, ClickHouse and S3 object storage.'
8
8
  }
@@ -40,7 +40,14 @@ export const executeAtomicDelete = async <T>(
40
40
  },
41
41
  );
42
42
  if (result.deletedCount > 0) {
43
- reportSmartdataCollectionWrite(collectionArg, optsArg?.session, 'delete', null, filterArg);
43
+ reportSmartdataCollectionWrite(
44
+ collectionArg,
45
+ optsArg?.session,
46
+ 'delete',
47
+ null,
48
+ filterArg,
49
+ { images: 'none' },
50
+ );
44
51
  }
45
52
  return result;
46
53
  },
@@ -84,7 +91,14 @@ export const executeAtomicDeleteMany = async <T>(
84
91
  },
85
92
  );
86
93
  if (result.deletedCount > 0) {
87
- reportSmartdataCollectionWrite(collectionArg, optsArg?.session, 'delete', null, filterArg);
94
+ reportSmartdataCollectionWrite(
95
+ collectionArg,
96
+ optsArg?.session,
97
+ 'delete',
98
+ null,
99
+ filterArg,
100
+ { images: 'none' },
101
+ );
88
102
  }
89
103
  return result;
90
104
  },
@@ -146,6 +146,10 @@ export const executeAtomicFindOneAndUpdate = async <T>(
146
146
  classified.status === 'upserted' ? 'insert' : 'update',
147
147
  classified.document ? [classified.document] : null,
148
148
  filterArg,
149
+ {
150
+ images: classified.document ? optionsArg.returnDocument : 'none',
151
+ update: updateArg,
152
+ },
149
153
  );
150
154
  }
151
155
  return classified;
@@ -233,6 +237,7 @@ export const executeAtomicFindOneAndReplace = async <T>(
233
237
  ? [classified.document]
234
238
  : [classified.document, replacementArg],
235
239
  filterArg,
240
+ { images: optionsArg.returnDocument === 'after' ? 'after' : 'beforeAndAfter' },
236
241
  );
237
242
  }
238
243
  return classified;
@@ -57,6 +57,7 @@ export const executeAtomicUpdate = async <T>(
57
57
  result.upsertedCount > 0 ? 'insert' : 'update',
58
58
  optsArg?.observedDocument ? [optsArg.observedDocument] : null,
59
59
  filterArg,
60
+ { images: 'none', update: updateArg },
60
61
  );
61
62
  }
62
63
  return result;
@@ -122,6 +123,7 @@ export const executeAtomicUpsertMany = async <T>(
122
123
  Object.hasOwn(upsertedIdsArg ?? {}, indexArg) ? 'insert' : 'update',
123
124
  null,
124
125
  operationArg.filter,
126
+ { images: 'none', update: operationArg.update },
125
127
  );
126
128
  });
127
129
  };
@@ -206,7 +208,14 @@ export const executeAtomicUpdateMany = async <T>(
206
208
  },
207
209
  );
208
210
  if (result.matchedCount > 0) {
209
- reportSmartdataCollectionWrite(collectionArg, optsArg?.session, 'update', null, filterArg);
211
+ reportSmartdataCollectionWrite(
212
+ collectionArg,
213
+ optsArg?.session,
214
+ 'update',
215
+ null,
216
+ filterArg,
217
+ { images: 'none', update: updateArg },
218
+ );
210
219
  }
211
220
  return result;
212
221
  } catch (errorArg) {
@@ -6,7 +6,8 @@ import { snapshotBoundedBsonDocument } from './classes.bsonsnapshot.js';
6
6
  import { validateOrdinaryStoredDocument } from './classes.ordinarypersistence.js';
7
7
  import { SmartdataPersistenceError, isMongoDuplicateKeyError } from './classes.persistence.js';
8
8
  import { readBoundedInspectionCursor } from './classes.namespaceinspection.js';
9
- import { reportSmartdataWrite } from './classes.session.js';
9
+ import { reportSmartdataWrite, reportSmartdataWriteFailure } from './classes.session.js';
10
+ import { runSmartdataWrite } from './classes.readtracker.js';
10
11
  import {
11
12
  collectionModelSchemaResolverSymbol, collectionTopologyDbInspectionSymbol,
12
13
  getExpectedCollectionTopologyForSchema, inspectCollectionTopologySnapshot,
@@ -63,7 +64,7 @@ export const insertBootstrapRecord = async (
63
64
  let outcome: TBootstrapOutcome;
64
65
  try {
65
66
  // Explicit preparation uses this same connection-generation guard.
66
- outcome = await dbArg[collectionTopologyDbInspectionSymbol](async (mongoDbArg, assertCurrentArg) => {
67
+ outcome = await runSmartdataWrite(dbArg, () => dbArg[collectionTopologyDbInspectionSymbol](async (mongoDbArg, assertCurrentArg) => {
67
68
  budget.check();
68
69
  const namespaces = await readBoundedInspectionCursor(mongoDbArg.listCollections(
69
70
  { name: schema.collectionName },
@@ -91,18 +92,20 @@ export const insertBootstrapRecord = async (
91
92
  } catch (errorArg) {
92
93
  return isMongoDuplicateKeyError(errorArg) ? 'unique_conflict' : 'ambiguous_write';
93
94
  }
94
- });
95
+ }));
95
96
  } catch {
97
+ if (dispatched) reportSmartdataWriteFailure(dbArg, undefined, schema.collectionName);
96
98
  throw new SmartdataPersistenceError(dispatched ? 'ambiguous_write' : 'unsupported_operation',
97
99
  dispatched ? 'Bootstrap insertion outcome is uncertain; inspect its explicit ID before proceeding.'
98
100
  : 'Bootstrap insertion could not inspect the current database connection.');
99
101
  }
100
102
  if (outcome === 'inserted') {
101
103
  reportSmartdataWrite(dbArg, undefined, {
102
- collection: schema.collectionName, kind: 'insert', documents: [document],
104
+ collection: schema.collectionName, kind: 'insert', documents: [document], images: 'after',
103
105
  });
104
106
  return Object.freeze({ acknowledged: true, insertedId: document._id as string });
105
107
  }
108
+ if (outcome === 'ambiguous_write') reportSmartdataWriteFailure(dbArg, undefined, schema.collectionName);
106
109
  const messages = {
107
110
  unique_conflict: 'Bootstrap record conflicts with a unique index.',
108
111
  invalid_configuration: 'Bootstrap insertion requires absent or exact primary-only collection topology.',
@@ -35,6 +35,7 @@ import {
35
35
  leaseOrdinarySmartdataSession,
36
36
  reportSmartdataCollectionWrite,
37
37
  runWithOrdinarySmartdataSession,
38
+ trackSmartdataRead,
38
39
  type TSmartdataOrdinarySession,
39
40
  } from './classes.session.js';
40
41
  import {
@@ -2422,12 +2423,20 @@ export class SmartdataCollection<T> {
2422
2423
  { ordinaryWrite: false, timeoutMS: opts?.timeoutMS },
2423
2424
  async (rawSessionArg, deadlineArg) => {
2424
2425
  await deadlineArg.join(this.init());
2425
- return this.mongoDbCollection.findOne(filterObject, {
2426
+ const tracked = trackSmartdataRead(this, opts?.session, {
2427
+ kind: 'documents',
2428
+ filter: filterObject,
2429
+ limited: true,
2430
+ });
2431
+ const document = await this.mongoDbCollection.findOne(filterObject, {
2426
2432
  projection: opts?.projection,
2427
2433
  maxTimeMS: opts?.maxTimeMS,
2428
2434
  session: rawSessionArg,
2429
2435
  ...deadlineArg.commandOptions(),
2430
2436
  });
2437
+ if (document) tracked?.record(document);
2438
+ tracked?.settle();
2439
+ return document;
2431
2440
  },
2432
2441
  );
2433
2442
  }
@@ -2448,10 +2457,14 @@ export class SmartdataCollection<T> {
2448
2457
  });
2449
2458
  try {
2450
2459
  await this.init();
2460
+ const tracked = trackSmartdataRead(this, opts?.session, {
2461
+ kind: 'documents',
2462
+ filter: filterObjectArg,
2463
+ });
2451
2464
  const cursor = this.mongoDbCollection.find(filterObjectArg, {
2452
2465
  session: lease.rawSession,
2453
2466
  });
2454
- return new SmartdataDbCursor(cursor, dbDocArg, lease.release);
2467
+ return new SmartdataDbCursor(cursor, dbDocArg, lease.release, tracked);
2455
2468
  } catch (cursorCreationError) {
2456
2469
  lease.release();
2457
2470
  throw cursorCreationError;
@@ -2470,6 +2483,10 @@ export class SmartdataCollection<T> {
2470
2483
  { ordinaryWrite: false, timeoutMS: opts?.timeoutMS },
2471
2484
  async (rawSessionArg, deadlineArg) => {
2472
2485
  await deadlineArg.join(this.init());
2486
+ const tracked = trackSmartdataRead(this, opts?.session, {
2487
+ kind: 'documents',
2488
+ filter: filterObject,
2489
+ });
2473
2490
  const cursor = this.mongoDbCollection.find(filterObject, {
2474
2491
  session: rawSessionArg,
2475
2492
  ...deadlineArg.cursorOptions(),
@@ -2482,6 +2499,10 @@ export class SmartdataCollection<T> {
2482
2499
  (cursorArg) => cursorArg.toArray(),
2483
2500
  deadlineArg.signal,
2484
2501
  );
2502
+ if (tracked) {
2503
+ for (const document of result) tracked.record(document);
2504
+ tracked.settle();
2505
+ }
2485
2506
 
2486
2507
  // In-memory check for duplicate _id values (should never happen)
2487
2508
  if (result.length > 0) {
@@ -2625,7 +2646,9 @@ export class SmartdataCollection<T> {
2625
2646
  ...deadlineArg.commandOptions(),
2626
2647
  });
2627
2648
  if (result.acknowledged) {
2628
- reportSmartdataCollectionWrite(this, opts?.session, 'insert', [saveableObject]);
2649
+ reportSmartdataCollectionWrite(
2650
+ this, opts?.session, 'insert', [saveableObject], undefined, { images: 'after' },
2651
+ );
2629
2652
  }
2630
2653
  return result;
2631
2654
  } catch (err: any) {
@@ -2668,6 +2691,12 @@ export class SmartdataCollection<T> {
2668
2691
  { ordinaryWrite: false, timeoutMS: opts?.timeoutMS, signal: opts?.signal },
2669
2692
  async (rawSessionArg, deadlineArg) => {
2670
2693
  await deadlineArg.join(this.init());
2694
+ // Totals depend on every matching document, not on which ones come back.
2695
+ trackSmartdataRead(this, opts?.session, {
2696
+ kind: 'documents',
2697
+ filter: matchArg ?? {},
2698
+ matches: true,
2699
+ });
2671
2700
  const pipeline: plugins.mongodb.Document[] = [];
2672
2701
  if (matchArg && Object.keys(matchArg).length > 0) {
2673
2702
  pipeline.push({ $match: matchArg });
@@ -2741,7 +2770,9 @@ export class SmartdataCollection<T> {
2741
2770
  .map((indexArg) => saveableObjects[Number(indexArg)])
2742
2771
  .filter((documentArg) => documentArg !== undefined);
2743
2772
  if (inserted.length > 0) {
2744
- reportSmartdataCollectionWrite(this, opts?.session, 'insert', inserted);
2773
+ reportSmartdataCollectionWrite(
2774
+ this, opts?.session, 'insert', inserted, undefined, { images: 'after' },
2775
+ );
2745
2776
  }
2746
2777
  };
2747
2778
  try {
@@ -2806,7 +2837,9 @@ export class SmartdataCollection<T> {
2806
2837
  .map((indexArg) => prepared[Number(indexArg)])
2807
2838
  .filter((documentArg) => documentArg !== undefined);
2808
2839
  if (inserted.length > 0) {
2809
- reportSmartdataCollectionWrite(this, optsArg.session, 'insert', inserted);
2840
+ reportSmartdataCollectionWrite(
2841
+ this, optsArg.session, 'insert', inserted, undefined, { images: 'after' },
2842
+ );
2810
2843
  }
2811
2844
  };
2812
2845
  try {
@@ -2881,15 +2914,16 @@ export class SmartdataCollection<T> {
2881
2914
  }
2882
2915
  updateableObject[key] = saveableObject[key];
2883
2916
  }
2917
+ const update = {
2918
+ $set: updateableObject,
2919
+ ...(Object.keys(insertOnlyObject).length > 0
2920
+ ? { $setOnInsert: insertOnlyObject }
2921
+ : {}),
2922
+ };
2884
2923
  try {
2885
2924
  const result = await this.mongoDbCollection.updateOne(
2886
2925
  identifiableObject,
2887
- {
2888
- $set: updateableObject,
2889
- ...(Object.keys(insertOnlyObject).length > 0
2890
- ? { $setOnInsert: insertOnlyObject }
2891
- : {}),
2892
- },
2926
+ update,
2893
2927
  { upsert: true, session: rawSessionArg, ...deadlineArg.commandOptions() },
2894
2928
  );
2895
2929
  if (result.upsertedCount > 0 || result.matchedCount > 0) {
@@ -2899,6 +2933,7 @@ export class SmartdataCollection<T> {
2899
2933
  result.upsertedCount > 0 ? 'insert' : 'update',
2900
2934
  [saveableObject],
2901
2935
  identifiableObject,
2936
+ { images: 'none', update },
2902
2937
  );
2903
2938
  }
2904
2939
  return result;
@@ -2939,6 +2974,7 @@ export class SmartdataCollection<T> {
2939
2974
  'delete',
2940
2975
  observedDocument ? [observedDocument] : null,
2941
2976
  identifiableObject,
2977
+ { images: 'none' },
2942
2978
  );
2943
2979
  }
2944
2980
  return result;
@@ -2990,6 +3026,11 @@ export class SmartdataCollection<T> {
2990
3026
  { ordinaryWrite: false, timeoutMS: opts?.timeoutMS, signal: opts?.signal },
2991
3027
  async (rawSessionArg, deadlineArg) => {
2992
3028
  await deadlineArg.join(this.init());
3029
+ const tracked = trackSmartdataRead(this, opts?.session, {
3030
+ kind: 'count',
3031
+ filter: filterObject,
3032
+ hint: opts?.hint,
3033
+ });
2993
3034
  const pipeline: plugins.mongodb.Document[] = [{ $match: filterObject }];
2994
3035
  if (opts?.limit !== undefined) {
2995
3036
  pipeline.push({ $limit: opts.limit });
@@ -3001,9 +3042,11 @@ export class SmartdataCollection<T> {
3001
3042
  ...deadlineArg.commandOptions(),
3002
3043
  session: rawSessionArg,
3003
3044
  });
3004
- return runCursorOperation(cursor, async (cursorArg) =>
3045
+ const count = await runCursorOperation(cursor, async (cursorArg) =>
3005
3046
  (await cursorArg.next())?.count ?? 0,
3006
3047
  deadlineArg.signal);
3048
+ tracked?.settle();
3049
+ return count;
3007
3050
  },
3008
3051
  );
3009
3052
  }
@@ -3065,6 +3108,7 @@ export class SmartdataCollection<T> {
3065
3108
  duplicateFields: Array<{ field: string; duplicateValues: number }>;
3066
3109
  }> {
3067
3110
  await this.init();
3111
+ trackSmartdataRead(this, undefined, { kind: 'collection' });
3068
3112
  const result = {
3069
3113
  ok: true,
3070
3114
  estimatedCount: 0,
@@ -415,17 +415,31 @@ export const getExpectedCollectionTopologyForSchema = (
415
415
 
416
416
  /**
417
417
  * @internal A single-field ascending unique index is what makes a declared
418
- * identity globally constraining, whatever the index is called.
418
+ * identity globally constraining, whatever the index is called. A partial one
419
+ * does not: documents outside its filter may share a value.
419
420
  */
420
421
  export const isIdentityIndexFor = (
421
422
  indexArg: INormalizedCollectionModelSchema['indexes'][number],
422
423
  fieldArg: string,
423
424
  ): boolean =>
424
425
  indexArg.options.unique === true &&
426
+ indexArg.options.partialFilterExpression === undefined &&
425
427
  indexArg.key.length === 1 &&
426
428
  indexArg.key[0][0] === fieldArg &&
427
429
  indexArg.key[0][1] === 1;
428
430
 
431
+ /**
432
+ * @internal The identity fields whose identity index holds every document, so
433
+ * a sort by one orders them all. A sparse index leaves out the documents that
434
+ * lack the field, which tie.
435
+ */
436
+ export const getOrderingIdentityFields = (
437
+ schemaArg: INormalizedCollectionModelSchema,
438
+ ): readonly string[] =>
439
+ schemaArg.identityFields.filter((fieldArg) =>
440
+ schemaArg.indexes.some((indexArg) =>
441
+ isIdentityIndexFor(indexArg, fieldArg) && indexArg.options.sparse !== true));
442
+
429
443
  /**
430
444
  * @internal Name of the unique index that backs an identity field in a bound
431
445
  * schema. Index-creation fallbacks resolve it here so that a model naming its
@@ -1,4 +1,5 @@
1
1
  import { SmartDataDbDoc } from './classes.doc.js';
2
+ import type { ISmartdataReadRecorder } from './classes.readtracker.js';
2
3
  import * as plugins from './plugins.js';
3
4
 
4
5
  /**
@@ -16,14 +17,23 @@ export class SmartdataDbCursor<T = any> {
16
17
  * raw driver session or no session at all.
17
18
  */
18
19
  private releaseSession: () => void;
20
+ /** Records the read for open read sets; it settles once the cursor is exhausted. */
21
+ private readRecorder?: ISmartdataReadRecorder;
19
22
  constructor(
20
23
  cursorArg: plugins.mongodb.FindCursor<T>,
21
24
  dbDocArg: typeof SmartDataDbDoc,
22
25
  releaseSessionArg: () => void = () => {},
26
+ readRecorderArg?: ISmartdataReadRecorder,
23
27
  ) {
24
- this.mongodbCursor = cursorArg;
28
+ this.mongodbCursor = readRecorderArg
29
+ ? cursorArg.map((documentArg) => {
30
+ readRecorderArg.record(documentArg);
31
+ return documentArg;
32
+ })
33
+ : cursorArg;
25
34
  this.smartdataDbDoc = dbDocArg;
26
35
  this.releaseSession = releaseSessionArg;
36
+ this.readRecorder = readRecorderArg;
27
37
  }
28
38
 
29
39
  public async next(closeAtEnd = true): Promise<T | null> {
@@ -34,6 +44,7 @@ export class SmartdataDbCursor<T = any> {
34
44
  return await this.closeAfterError(readError);
35
45
  }
36
46
  if (nextDocument === null) {
47
+ this.readRecorder?.settle();
37
48
  if (closeAtEnd) {
38
49
  await this.close();
39
50
  }
@@ -59,6 +70,7 @@ export class SmartdataDbCursor<T = any> {
59
70
  await forEachFuncArg(nextClassInstance as any);
60
71
  }
61
72
  } while (nextDocument);
73
+ this.readRecorder?.settle();
62
74
  } catch (iterationError) {
63
75
  if (closeCursorAtEnd) {
64
76
  await this.closeAfterError(iterationError);
@@ -74,6 +86,7 @@ export class SmartdataDbCursor<T = any> {
74
86
  let convertedResult: T[];
75
87
  try {
76
88
  const result = await this.mongodbCursor.toArray();
89
+ this.readRecorder?.settle();
77
90
  convertedResult = result.map((itemArg) =>
78
91
  this.smartdataDbDoc.createInstanceFromMongoDbNativeDoc(itemArg),
79
92
  ) as T[];
@@ -24,8 +24,15 @@ import {
24
24
  addSmartdataWriteObserver,
25
25
  type ISmartdataWriteObserverOptions,
26
26
  } from './classes.writeobserver.js';
27
+ import {
28
+ changeSmartdataReadSetsOfDb,
29
+ trackSmartdataReads,
30
+ type ISmartdataTrackedReads,
31
+ } from './classes.readtracker.js';
27
32
 
28
33
  export { SmartdataSession } from './classes.session.js';
34
+ export { SmartdataReadSet } from './classes.readtracker.js';
35
+ export type { ISmartdataTrackedReads } from './classes.readtracker.js';
29
36
  export type {
30
37
  ISmartdataTransactionOptions,
31
38
  TSmartdataOrdinarySession,
@@ -358,6 +365,7 @@ export class SmartdataDb {
358
365
  this.beginLifecycleOperation('close');
359
366
  this.transitionLifecycle('closing');
360
367
  this.status = 'disconnected';
368
+ changeSmartdataReadSetsOfDb(this);
361
369
  const mongoDbClient = this.pendingClient ?? this.mongoDbClient;
362
370
  try {
363
371
  const closeErrors: unknown[] = [];
@@ -544,6 +552,30 @@ export class SmartdataDb {
544
552
  return addSmartdataWriteObserver(this, optionsArg);
545
553
  }
546
554
 
555
+ /**
556
+ * Runs `fnArg` and records every model read it makes on this database:
557
+ * `findOne`, `findAll`, `getCursor`, counts, pages and the exact reads.
558
+ * Each read is kept as its query and the ids it returned. The returned
559
+ * `reads.changed` turns true once a write this process makes through the
560
+ * database's models could have changed any of those results; a write it
561
+ * cannot place, such as an update by a selector it cannot evaluate, counts
562
+ * as one that did. A cache keyed by `reads` rebuilds only what such a write
563
+ * touched.
564
+ *
565
+ * Writes of other processes, raw driver access and migrations are not
566
+ * followed. A read inside a transaction, a read of another database and a
567
+ * write made inside `fnArg` mark the set changed at once. Queries are
568
+ * evaluated by MongoDB's simple binary comparison, so a read of a view or of
569
+ * a collection with a default collation marks the set changed as well; each
570
+ * read collection is checked once per connection when `fnArg` returns.
571
+ * Close a set that is no longer needed.
572
+ */
573
+ public async trackReads<TResult>(
574
+ fnArg: () => TResult | Promise<TResult>,
575
+ ): Promise<ISmartdataTrackedReads<TResult>> {
576
+ return trackSmartdataReads(this, fnArg);
577
+ }
578
+
547
579
  /**
548
580
  * Creates a narrowly scoped MongoDB administration facade bound to this
549
581
  * SmartData lifecycle. Applications never need direct driver access.
@@ -43,6 +43,7 @@ import {
43
43
  isOrdinarySessionInTransaction,
44
44
  leaseOrdinarySmartdataSession,
45
45
  runWithOrdinarySmartdataSession,
46
+ trackSmartdataRead,
46
47
  type TSmartdataOrdinarySession,
47
48
  } from './classes.session.js';
48
49
  import {
@@ -6055,24 +6056,39 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
6055
6056
  timeoutMS,
6056
6057
  }, async (rawSessionArg, deadlineArg) => {
6057
6058
  await deadlineArg.join(collection.init());
6059
+ const sort = { [optionsArg.sortField]: sortDirection, [uniqueField]: sortDirection } as const;
6060
+ const tracked = trackSmartdataRead(collection, optionsArg.session, {
6061
+ kind: 'documents',
6062
+ filter: selector,
6063
+ sort,
6064
+ limited: true,
6065
+ });
6058
6066
  const rawCursor = collection.mongoDbCollection
6059
6067
  .find(selector, { session: rawSessionArg, ...deadlineArg.cursorOptions() })
6060
- .sort({ [optionsArg.sortField]: sortDirection, [uniqueField]: sortDirection })
6068
+ .sort(sort)
6061
6069
  .limit(limit + 1);
6062
6070
  return runCursorOperation(rawCursor, async (cursorArg) => {
6063
6071
  const rawDocuments = await cursorArg.toArray();
6072
+ if (tracked) {
6073
+ for (const rawDocument of rawDocuments) tracked.record(rawDocument);
6074
+ tracked.settle();
6075
+ }
6064
6076
  const hasMore = rawDocuments.length > limit;
6065
6077
  const pageRows = rawDocuments.slice(0, limit);
6066
6078
  const documents = pageRows.map(
6067
6079
  (rawDocument: any) => (this as any).createInstanceFromMongoDbNativeDoc(rawDocument) as T,
6068
6080
  );
6069
6081
  const lastRow: any = pageRows[pageRows.length - 1];
6082
+ const countTracked = optionsArg.withTotal
6083
+ ? trackSmartdataRead(collection, optionsArg.session, { kind: 'count', filter: baseSelector })
6084
+ : undefined;
6070
6085
  const total = optionsArg.withTotal
6071
6086
  ? await collection.mongoDbCollection.countDocuments(baseSelector, {
6072
6087
  session: rawSessionArg,
6073
6088
  ...deadlineArg.commandOptions(),
6074
6089
  })
6075
6090
  : undefined;
6091
+ countTracked?.settle();
6076
6092
  return {
6077
6093
  documents,
6078
6094
  ...(hasMore && lastRow
@@ -6211,8 +6227,14 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
6211
6227
  });
6212
6228
  try {
6213
6229
  await collection.init();
6230
+ const filter = (this as any).normalizeReadFilter(filterArg);
6231
+ // A modifier changes the query in ways a read set cannot follow, and a
6232
+ // skipped page depends on the documents before it.
6233
+ const tracked = trackSmartdataRead(collection, session, modifier
6234
+ ? { kind: 'collection' }
6235
+ : { kind: 'documents', filter, sort, hint, matches: skip !== undefined && skip > 0, limited: Boolean(limit) });
6214
6236
  let rawCursor: plugins.mongodb.FindCursor<any> =
6215
- collection.mongoDbCollection.find((this as any).normalizeReadFilter(filterArg), {
6237
+ collection.mongoDbCollection.find(filter, {
6216
6238
  projection,
6217
6239
  session: lease.rawSession,
6218
6240
  hint,
@@ -6261,6 +6283,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
6261
6283
  rawCursor,
6262
6284
  this as any as typeof SmartDataDbDoc,
6263
6285
  lease.release,
6286
+ tracked,
6264
6287
  );
6265
6288
  } catch (cursorCreationError) {
6266
6289
  lease.release();
@@ -6309,12 +6332,19 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
6309
6332
  defaultTimeoutMS: 5_000,
6310
6333
  signal: opts.signal,
6311
6334
  }, async (rawSessionArg, deadlineArg) => {
6335
+ const tracked = trackSmartdataRead(collection, opts.session, {
6336
+ kind: 'documents', filter, sort, hint, matches: skip > 0, limited: true,
6337
+ });
6312
6338
  const rawCursor = collection.mongoDbCollection.find(filter, {
6313
6339
  projection, sort, hint, skip, limit, maxTimeMS,
6314
6340
  ...deadlineArg.cursorOptions(), session: rawSessionArg,
6315
6341
  });
6316
6342
  return runCursorOperation(rawCursor, async (cursorArg) => {
6317
6343
  const documents = await cursorArg.toArray();
6344
+ if (tracked) {
6345
+ for (const document of documents) tracked.record(document);
6346
+ tracked.settle();
6347
+ }
6318
6348
  return documents.map((documentArg) =>
6319
6349
  (this as any).createInstanceFromMongoDbNativeDoc(documentArg) as T,
6320
6350
  );