@lossless.org/client 1.1.0 → 1.3.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.
Files changed (34) hide show
  1. package/.smartconfig.json +1 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/nosqldb/classes.atomicdelete.js +3 -3
  4. package/dist_ts/nosqldb/classes.atomicfindoneandupdate.d.ts +20 -0
  5. package/dist_ts/nosqldb/classes.atomicfindoneandupdate.js +45 -2
  6. package/dist_ts/nosqldb/classes.atomicupdate.js +4 -4
  7. package/dist_ts/nosqldb/classes.collection.d.ts +37 -1
  8. package/dist_ts/nosqldb/classes.collection.js +235 -30
  9. package/dist_ts/nosqldb/classes.collectiontopology.d.ts +19 -0
  10. package/dist_ts/nosqldb/classes.collectiontopology.js +29 -3
  11. package/dist_ts/nosqldb/classes.cursor.d.ts +12 -1
  12. package/dist_ts/nosqldb/classes.cursor.js +20 -3
  13. package/dist_ts/nosqldb/classes.doc.d.ts +138 -11
  14. package/dist_ts/nosqldb/classes.doc.js +490 -91
  15. package/dist_ts/nosqldb/classes.exactpersistence.js +10 -2
  16. package/dist_ts/nosqldb/classes.namespaceinspection.js +2 -2
  17. package/dist_ts/nosqldb/classes.persistence.d.ts +6 -0
  18. package/dist_ts/nosqldb/classes.persistence.js +13 -1
  19. package/dist_ts/nosqldb/classes.session.d.ts +15 -1
  20. package/dist_ts/nosqldb/classes.session.js +32 -6
  21. package/package.json +4 -1
  22. package/readme.md +38 -2
  23. package/ts/00_commitinfo_data.ts +1 -1
  24. package/ts/nosqldb/classes.atomicdelete.ts +2 -4
  25. package/ts/nosqldb/classes.atomicfindoneandupdate.ts +69 -2
  26. package/ts/nosqldb/classes.atomicupdate.ts +3 -6
  27. package/ts/nosqldb/classes.collection.ts +372 -35
  28. package/ts/nosqldb/classes.collectiontopology.ts +56 -1
  29. package/ts/nosqldb/classes.cursor.ts +22 -2
  30. package/ts/nosqldb/classes.doc.ts +819 -132
  31. package/ts/nosqldb/classes.exactpersistence.ts +9 -1
  32. package/ts/nosqldb/classes.namespaceinspection.ts +1 -1
  33. package/ts/nosqldb/classes.persistence.ts +17 -0
  34. package/ts/nosqldb/classes.session.ts +52 -13
@@ -38,6 +38,8 @@ export interface ISmartdataCollectionTopologyIndex {
38
38
  export interface ISmartdataExpectedCollectionTopology {
39
39
  readonly collectionName: string;
40
40
  readonly indexes: readonly ISmartdataCollectionTopologyIndex[];
41
+ /** Undeclared installed indexes the model accepts by name, never verifies. */
42
+ readonly toleratedIndexNames: readonly string[];
41
43
  }
42
44
 
43
45
  export interface ISmartdataCollectionTopologyInspection {
@@ -45,7 +47,13 @@ export interface ISmartdataCollectionTopologyInspection {
45
47
  readonly status: TSmartdataCollectionTopologyStatus;
46
48
  readonly reasonCodes: readonly TSmartdataCollectionTopologyReasonCode[];
47
49
  readonly expectedIndexes: readonly ISmartdataCollectionTopologyIndex[];
50
+ /** Installed indexes SmartData declares and verifies. */
48
51
  readonly actualIndexes: readonly ISmartdataCollectionTopologyIndex[];
52
+ /**
53
+ * Installed indexes the model tolerates by name. They are observed, never
54
+ * verified, created or dropped, and they never make the topology divergent.
55
+ */
56
+ readonly toleratedIndexes: readonly string[];
49
57
  }
50
58
 
51
59
  type TCollectionModelSchemaResolver = () => INormalizedCollectionModelSchema;
@@ -295,9 +303,37 @@ export const getExpectedCollectionTopologyForSchema = (
295
303
  return Object.freeze({
296
304
  collectionName: schema.collectionName,
297
305
  indexes,
306
+ toleratedIndexNames: Object.freeze(
307
+ [...schema.toleratedIndexNames].sort(compareSmartdataTopologyStrings),
308
+ ),
298
309
  });
299
310
  };
300
311
 
312
+ /**
313
+ * @internal A single-field ascending unique index is what makes a declared
314
+ * identity globally constraining, whatever the index is called.
315
+ */
316
+ export const isIdentityIndexFor = (
317
+ indexArg: INormalizedCollectionModelSchema['indexes'][number],
318
+ fieldArg: string,
319
+ ): boolean =>
320
+ indexArg.options.unique === true &&
321
+ indexArg.key.length === 1 &&
322
+ indexArg.key[0][0] === fieldArg &&
323
+ indexArg.key[0][1] === 1;
324
+
325
+ /**
326
+ * @internal Name of the unique index that backs an identity field in a bound
327
+ * schema. Index-creation fallbacks resolve it here so that a model naming its
328
+ * own backing index never gets a second unique index over the same key.
329
+ */
330
+ export const getIdentityIndexName = (
331
+ schemaArg: INormalizedCollectionModelSchema | undefined,
332
+ fieldArg: string,
333
+ ): string | undefined =>
334
+ schemaArg?.indexes.find((indexArg) => isIdentityIndexFor(indexArg, fieldArg))
335
+ ?.name;
336
+
301
337
  const normalizeActualIndex = (
302
338
  rawIndexArg: unknown,
303
339
  ): IActualIndexNormalization => {
@@ -487,6 +523,7 @@ const createInspection = (
487
523
  statusArg: TSmartdataCollectionTopologyStatus,
488
524
  reasonsArg: ReadonlySet<TSmartdataCollectionTopologyReasonCode>,
489
525
  actualIndexesArg: readonly ISmartdataCollectionTopologyIndex[],
526
+ toleratedIndexesArg: readonly string[] = [],
490
527
  ): ISmartdataCollectionTopologyInspection =>
491
528
  Object.freeze({
492
529
  collectionName: expectedArg.collectionName,
@@ -494,6 +531,9 @@ const createInspection = (
494
531
  reasonCodes: buildReasonCodes(reasonsArg),
495
532
  expectedIndexes: expectedArg.indexes,
496
533
  actualIndexes: sortIndexes(actualIndexesArg),
534
+ toleratedIndexes: Object.freeze(
535
+ [...toleratedIndexesArg].sort(compareSmartdataTopologyStrings),
536
+ ),
497
537
  });
498
538
 
499
539
  export const inspectCollectionTopologyForModel = async (
@@ -574,8 +614,17 @@ export const inspectCollectionTopologySnapshot = (
574
614
  }
575
615
  const normalizedIndexes: ISmartdataCollectionTopologyIndex[] = [];
576
616
  const unsupportedNames = new Set<string>();
617
+ const toleratedNames = new Set(expected.toleratedIndexNames);
618
+ const toleratedIndexes: string[] = [];
577
619
  for (const rawIndex of rawIndexes) {
578
620
  const normalized = normalizeActualIndex(rawIndex);
621
+ if (normalized.name && toleratedNames.has(normalized.name)) {
622
+ // A tolerated index belongs to whoever created it — a migration, an
623
+ // operator — so it is observed by name and nothing about its keys or
624
+ // options is asserted. SmartData neither creates nor drops it.
625
+ toleratedIndexes.push(normalized.name);
626
+ continue;
627
+ }
579
628
  if (!normalized.supported) {
580
629
  reasons.add('unsupported_actual_index');
581
630
  if (normalized.name) {
@@ -635,7 +684,13 @@ export const inspectCollectionTopologySnapshot = (
635
684
  )
636
685
  ? 'compatible'
637
686
  : 'divergent';
638
- return createInspection(expected, status, reasons, normalizedIndexes);
687
+ return createInspection(
688
+ expected,
689
+ status,
690
+ reasons,
691
+ normalizedIndexes,
692
+ toleratedIndexes,
693
+ );
639
694
  } catch {
640
695
  throw new SmartdataPersistenceError(
641
696
  'unsupported_operation',
@@ -10,9 +10,20 @@ export class SmartdataDbCursor<T = any> {
10
10
  // INSTANCE
11
11
  public mongodbCursor: plugins.mongodb.FindCursor<T>;
12
12
  private smartdataDbDoc: typeof SmartDataDbDoc;
13
- constructor(cursorArg: plugins.mongodb.FindCursor<T>, dbDocArg: typeof SmartDataDbDoc) {
13
+ /**
14
+ * Releases an owned SmartData session lease the cursor holds for its whole
15
+ * lifetime. Supplied by the operation that created the cursor; inert for a
16
+ * raw driver session or no session at all.
17
+ */
18
+ private releaseSession: () => void;
19
+ constructor(
20
+ cursorArg: plugins.mongodb.FindCursor<T>,
21
+ dbDocArg: typeof SmartDataDbDoc,
22
+ releaseSessionArg: () => void = () => {},
23
+ ) {
14
24
  this.mongodbCursor = cursorArg;
15
25
  this.smartdataDbDoc = dbDocArg;
26
+ this.releaseSession = releaseSessionArg;
16
27
  }
17
28
 
18
29
  public async next(closeAtEnd = true): Promise<T | null> {
@@ -73,8 +84,17 @@ export class SmartdataDbCursor<T = any> {
73
84
  return convertedResult;
74
85
  }
75
86
 
87
+ /**
88
+ * Closes the driver cursor and releases an owned session lease even when the
89
+ * driver's own cleanup rejects, so a failed close can never strand the
90
+ * session and block every later operation on it.
91
+ */
76
92
  public async close() {
77
- await this.mongodbCursor.close();
93
+ try {
94
+ await this.mongodbCursor.close();
95
+ } finally {
96
+ this.releaseSession();
97
+ }
78
98
  }
79
99
 
80
100
  private async closeAfterError(errorArg: unknown): Promise<never> {