@lossless.org/client 1.20.2 → 1.21.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 (38) 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.cursor.d.ts +4 -1
  8. package/dist_ts/nosqldb/classes.cursor.js +14 -3
  9. package/dist_ts/nosqldb/classes.db.d.ts +22 -0
  10. package/dist_ts/nosqldb/classes.db.js +25 -1
  11. package/dist_ts/nosqldb/classes.doc.js +35 -5
  12. package/dist_ts/nosqldb/classes.exactpersistence.d.ts +6 -0
  13. package/dist_ts/nosqldb/classes.exactpersistence.js +70 -13
  14. package/dist_ts/nosqldb/classes.readtracker.d.ts +87 -0
  15. package/dist_ts/nosqldb/classes.readtracker.js +876 -0
  16. package/dist_ts/nosqldb/classes.session.d.ts +30 -7
  17. package/dist_ts/nosqldb/classes.session.js +116 -26
  18. package/dist_ts/nosqldb/classes.storedinspection.js +12 -4
  19. package/dist_ts/nosqldb/classes.testdatabase.js +16 -9
  20. package/dist_ts/nosqldb/classes.writeobserver.d.ts +11 -0
  21. package/dist_ts/nosqldb/classes.writeobserver.js +1 -1
  22. package/package.json +1 -1
  23. package/readme.md +1 -0
  24. package/ts/00_commitinfo_data.ts +1 -1
  25. package/ts/nosqldb/classes.atomicdelete.ts +16 -2
  26. package/ts/nosqldb/classes.atomicfindoneandupdate.ts +5 -0
  27. package/ts/nosqldb/classes.atomicupdate.ts +10 -1
  28. package/ts/nosqldb/classes.bootstraprecord.ts +7 -4
  29. package/ts/nosqldb/classes.collection.ts +56 -12
  30. package/ts/nosqldb/classes.cursor.ts +14 -1
  31. package/ts/nosqldb/classes.db.ts +32 -0
  32. package/ts/nosqldb/classes.doc.ts +32 -2
  33. package/ts/nosqldb/classes.exactpersistence.ts +93 -19
  34. package/ts/nosqldb/classes.readtracker.ts +1028 -0
  35. package/ts/nosqldb/classes.session.ts +163 -30
  36. package/ts/nosqldb/classes.storedinspection.ts +15 -3
  37. package/ts/nosqldb/classes.testdatabase.ts +14 -8
  38. package/ts/nosqldb/classes.writeobserver.ts +11 -0
@@ -10,9 +10,12 @@ import type { SmartdataSession } from './classes.session.js';
10
10
  import {
11
11
  acquireSmartdataSession,
12
12
  reportSmartdataCollectionWrite,
13
+ reportSmartdataWriteFailure,
13
14
  settleSmartdataOperationError,
14
15
  resolveSmartdataOperationDeadline,
16
+ trackSmartdataRead,
15
17
  } from './classes.session.js';
18
+ import { runSmartdataWrite } from './classes.readtracker.js';
16
19
  import {
17
20
  type SmartdataOperationDeadline,
18
21
  requireOperationSignal,
@@ -1135,6 +1138,35 @@ export class SmartdataExactCollection<
1135
1138
  }
1136
1139
  }
1137
1140
 
1141
+ /**
1142
+ * Runs one exact write through `runOperation`. A write that fails once
1143
+ * started may still have applied, so it changes every open read set that
1144
+ * read the collection.
1145
+ */
1146
+ private async runWriteOperation<TResult>(
1147
+ optionsArg: IExactBoundedOptions,
1148
+ operationArg: (
1149
+ sessionArg: plugins.mongodb.ClientSession | undefined,
1150
+ deadlineArg: SmartdataOperationDeadline,
1151
+ ) => Promise<TResult>,
1152
+ ): Promise<TResult> {
1153
+ let started = false;
1154
+ try {
1155
+ return await runSmartdataWrite(this.collection.smartdataDb, () =>
1156
+ this.runOperation(optionsArg, (sessionArg, deadlineArg) => {
1157
+ started = true;
1158
+ return operationArg(sessionArg, deadlineArg);
1159
+ }));
1160
+ } catch (errorArg) {
1161
+ if (started) {
1162
+ reportSmartdataWriteFailure(
1163
+ this.collection.smartdataDb, optionsArg.session, this.collection.collectionName,
1164
+ );
1165
+ }
1166
+ throw errorArg;
1167
+ }
1168
+ }
1169
+
1138
1170
  /**
1139
1171
  * The error of a non-transactional write that its client deadline or
1140
1172
  * signal interrupted after the write was handed to the driver: the command
@@ -1531,7 +1563,7 @@ export class SmartdataExactCollection<
1531
1563
  documentArg: TModel | TExactPersistedDocument<TPersisted>,
1532
1564
  optionsArg: IExactBoundedOptions = {},
1533
1565
  ): Promise<TExactInsertResult<TPersisted>> {
1534
- return this.runOperation(optionsArg, async (sessionArg, deadlineArg) => {
1566
+ return this.runWriteOperation(optionsArg, async (sessionArg, deadlineArg) => {
1535
1567
  const persistedBytes = await this.createPersistedDocument(documentArg);
1536
1568
  const persistedDocument = this.decodeCanonical<
1537
1569
  TExactPersistedDocument<TPersisted>
@@ -1554,7 +1586,8 @@ export class SmartdataExactCollection<
1554
1586
  const reportReconciledInsert = (storedArg: TStoredDocument<TPersisted>): void => {
1555
1587
  if (storedArg._id.equals(expectedStoredDocument._id)) {
1556
1588
  reportSmartdataCollectionWrite(
1557
- this.collection, optionsArg.session, 'insert', [expectedStoredDocument],
1589
+ this.collection, optionsArg.session, 'insert', [expectedStoredDocument], undefined,
1590
+ { images: 'after' },
1558
1591
  );
1559
1592
  }
1560
1593
  };
@@ -1652,7 +1685,8 @@ export class SmartdataExactCollection<
1652
1685
  );
1653
1686
  }
1654
1687
  reportSmartdataCollectionWrite(
1655
- this.collection, optionsArg.session, 'insert', [expectedStoredDocument],
1688
+ this.collection, optionsArg.session, 'insert', [expectedStoredDocument], undefined,
1689
+ { images: 'after' },
1656
1690
  );
1657
1691
  return { status: 'inserted', document: expectedStoredDocument };
1658
1692
  });
@@ -1677,8 +1711,14 @@ export class SmartdataExactCollection<
1677
1711
  optionsArg: IExactBoundedOptions = {},
1678
1712
  ): Promise<TStoredDocument<TPersisted> | null> {
1679
1713
  return this.runOperation(optionsArg, async (sessionArg, deadlineArg) => {
1714
+ const filter = convertFilterForMongoDb(filterArg);
1715
+ const tracked = trackSmartdataRead(this.collection, optionsArg.session, {
1716
+ kind: 'documents',
1717
+ filter,
1718
+ limited: true,
1719
+ });
1680
1720
  const cursor = this.collection.mongoDbCollection.find(
1681
- convertFilterForMongoDb(filterArg),
1721
+ filter,
1682
1722
  {
1683
1723
  session: sessionArg,
1684
1724
  singleBatch: true,
@@ -1694,6 +1734,8 @@ export class SmartdataExactCollection<
1694
1734
  let storedDocument: TStoredDocument<TPersisted> | null;
1695
1735
  try {
1696
1736
  const rawDocument = await cursor.next();
1737
+ if (rawDocument) tracked?.record(rawDocument);
1738
+ tracked?.settle();
1697
1739
  storedDocument = rawDocument
1698
1740
  ? this.readStoredDocument(rawDocument).document
1699
1741
  : null;
@@ -1714,8 +1756,17 @@ export class SmartdataExactCollection<
1714
1756
  optionsArg: IFindStoredOptions<TPersisted> = {},
1715
1757
  ): Promise<Array<TStoredDocument<TPersisted>>> {
1716
1758
  return this.runOperation(optionsArg, async (sessionArg, deadlineArg) => {
1759
+ const filter = convertFilterForMongoDb(optionsArg.filter || {});
1760
+ // A skipped page depends on the documents before it, which it never returns.
1761
+ const tracked = trackSmartdataRead(this.collection, optionsArg.session, {
1762
+ kind: 'documents',
1763
+ filter,
1764
+ sort: optionsArg.sort,
1765
+ matches: optionsArg.skip !== undefined && optionsArg.skip > 0,
1766
+ limited: optionsArg.limit !== undefined,
1767
+ });
1717
1768
  let cursor = this.collection.mongoDbCollection.find(
1718
- convertFilterForMongoDb(optionsArg.filter || {}),
1769
+ filter,
1719
1770
  { session: sessionArg, ...deadlineArg.cursorOptions() },
1720
1771
  );
1721
1772
  // The driver abort listener does not await close, so every close must join
@@ -1738,6 +1789,8 @@ export class SmartdataExactCollection<
1738
1789
  cursor = cursor.limit(optionsArg.limit);
1739
1790
  }
1740
1791
  const rawDocuments = await cursor.toArray();
1792
+ for (const rawDocument of rawDocuments) tracked?.record(rawDocument);
1793
+ tracked?.settle();
1741
1794
  storedDocuments = rawDocuments.map((documentArg) =>
1742
1795
  this.readStoredDocument(documentArg).document,
1743
1796
  );
@@ -1758,12 +1811,16 @@ export class SmartdataExactCollection<
1758
1811
  filterArg: MongoFilter<TStoredDocument<TPersisted>> = {},
1759
1812
  optionsArg: IExactBoundedOptions = {},
1760
1813
  ): Promise<number> {
1761
- return this.runOperation(optionsArg, async (sessionArg, deadlineArg) =>
1762
- this.collection.mongoDbCollection.countDocuments(
1763
- convertFilterForMongoDb(filterArg),
1814
+ return this.runOperation(optionsArg, async (sessionArg, deadlineArg) => {
1815
+ const filter = convertFilterForMongoDb(filterArg);
1816
+ const tracked = trackSmartdataRead(this.collection, optionsArg.session, { kind: 'count', filter });
1817
+ const count = await this.collection.mongoDbCollection.countDocuments(
1818
+ filter,
1764
1819
  { session: sessionArg, ...deadlineArg.commandOptions() },
1765
- )
1766
- );
1820
+ );
1821
+ tracked?.settle();
1822
+ return count;
1823
+ });
1767
1824
  }
1768
1825
 
1769
1826
  public async distinct<
@@ -1775,15 +1832,21 @@ export class SmartdataExactCollection<
1775
1832
  ): Promise<Array<TExactDistinctValue<
1776
1833
  TExactDocumentPathValue<TStoredDocument<TPersisted>, TField>
1777
1834
  >>> {
1778
- return this.runOperation(optionsArg, async (sessionArg, deadlineArg) =>
1779
- (await this.collection.mongoDbCollection.distinct(
1835
+ return this.runOperation(optionsArg, async (sessionArg, deadlineArg) => {
1836
+ const filter = convertFilterForMongoDb(filterArg);
1837
+ trackSmartdataRead(this.collection, optionsArg.session, {
1838
+ kind: 'documents',
1839
+ filter,
1840
+ matches: true,
1841
+ });
1842
+ return (await this.collection.mongoDbCollection.distinct(
1780
1843
  fieldArg as string,
1781
- convertFilterForMongoDb(filterArg),
1844
+ filter,
1782
1845
  { session: sessionArg, ...deadlineArg.commandOptions() },
1783
1846
  )) as Array<TExactDistinctValue<
1784
1847
  TExactDocumentPathValue<TStoredDocument<TPersisted>, TField>
1785
- >>
1786
- );
1848
+ >>;
1849
+ });
1787
1850
  }
1788
1851
 
1789
1852
  public async transition(
@@ -1793,7 +1856,7 @@ export class SmartdataExactCollection<
1793
1856
  },
1794
1857
  sessionOptionsArg: IExactBoundedOptions = {},
1795
1858
  ): Promise<TExactTransitionResult<TPersisted>> {
1796
- return this.runOperation(sessionOptionsArg, async (sessionArg, deadlineArg) => {
1859
+ return this.runWriteOperation(sessionOptionsArg, async (sessionArg, deadlineArg) => {
1797
1860
  const { document: current, bodyBytes: currentBodyBytes } =
1798
1861
  this.readStoredDocument(optionsArg.current);
1799
1862
  const currentIdentity = this.validateReconcileIdentity(current);
@@ -1841,9 +1904,15 @@ export class SmartdataExactCollection<
1841
1904
  ? { _id: current._id, _smartdataRevision: current._smartdataRevision }
1842
1905
  : { _id: current._id, _smartdataRevision: { $exists: false } };
1843
1906
 
1907
+ // Only a revision pins the stored body the selector matched.
1844
1908
  const reportTransition = (): void => {
1845
1909
  reportSmartdataCollectionWrite(
1846
- this.collection, sessionOptionsArg.session, 'replace', [current, expectedStoredDocument], selector,
1910
+ this.collection,
1911
+ sessionOptionsArg.session,
1912
+ 'replace',
1913
+ [current, expectedStoredDocument],
1914
+ selector,
1915
+ { images: current._smartdataRevision !== undefined ? 'beforeAndAfter' : 'after' },
1847
1916
  );
1848
1917
  };
1849
1918
  const replaceOptions = {
@@ -1925,7 +1994,7 @@ export class SmartdataExactCollection<
1925
1994
  sessionOptionsArg: IExactBoundedOptions = {},
1926
1995
  ): Promise<TExactDeleteResult> {
1927
1996
  const { current, identity } = this.canonicalizeDeleteCurrent(optionsArg);
1928
- return this.runOperation(sessionOptionsArg, async (sessionArg, deadlineArg) => {
1997
+ return this.runWriteOperation(sessionOptionsArg, async (sessionArg, deadlineArg) => {
1929
1998
  const selector = current._smartdataRevision !== undefined
1930
1999
  ? {
1931
2000
  _id: current._id,
@@ -1958,7 +2027,12 @@ export class SmartdataExactCollection<
1958
2027
  result.deletedCount === 1
1959
2028
  ) {
1960
2029
  reportSmartdataCollectionWrite(
1961
- this.collection, sessionOptionsArg.session, 'delete', [current], selector,
2030
+ this.collection,
2031
+ sessionOptionsArg.session,
2032
+ 'delete',
2033
+ [current],
2034
+ selector,
2035
+ { images: current._smartdataRevision !== undefined ? 'before' : 'none' },
1962
2036
  );
1963
2037
  return { status: 'deleted', deletedCount: 1 };
1964
2038
  }