@lossless.org/client 1.22.1 → 1.23.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 (35) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/nosqldb/classes.bootstraprecord.js +4 -4
  3. package/dist_ts/nosqldb/classes.connection.d.ts +3 -0
  4. package/dist_ts/nosqldb/classes.connection.js +3 -2
  5. package/dist_ts/nosqldb/classes.db.d.ts +31 -0
  6. package/dist_ts/nosqldb/classes.db.js +45 -2
  7. package/dist_ts/nosqldb/classes.doc.js +5 -5
  8. package/dist_ts/nosqldb/classes.exactpersistence.d.ts +3 -2
  9. package/dist_ts/nosqldb/classes.exactpersistence.js +8 -7
  10. package/dist_ts/nosqldb/classes.readtracker.d.ts +6 -4
  11. package/dist_ts/nosqldb/classes.readtracker.js +42 -10
  12. package/dist_ts/nosqldb/classes.session.d.ts +7 -6
  13. package/dist_ts/nosqldb/classes.session.js +91 -23
  14. package/dist_ts/nosqldb/classes.writelog.d.ts +129 -0
  15. package/dist_ts/nosqldb/classes.writelog.js +497 -0
  16. package/dist_ts/nosqldb/classes.writeobserver.d.ts +13 -0
  17. package/dist_ts/nosqldb/classes.writeobserver.js +47 -25
  18. package/dist_ts/nosqldb/classes.writerevisions.d.ts +18 -7
  19. package/dist_ts/nosqldb/classes.writerevisions.js +179 -30
  20. package/dist_ts/nosqldb/engineidentity.d.ts +6 -0
  21. package/dist_ts/nosqldb/engineidentity.js +14 -1
  22. package/package.json +2 -2
  23. package/readme.md +11 -10
  24. package/ts/00_commitinfo_data.ts +1 -1
  25. package/ts/nosqldb/classes.bootstraprecord.ts +3 -3
  26. package/ts/nosqldb/classes.connection.ts +5 -1
  27. package/ts/nosqldb/classes.db.ts +68 -0
  28. package/ts/nosqldb/classes.doc.ts +4 -4
  29. package/ts/nosqldb/classes.exactpersistence.ts +8 -7
  30. package/ts/nosqldb/classes.readtracker.ts +41 -7
  31. package/ts/nosqldb/classes.session.ts +107 -26
  32. package/ts/nosqldb/classes.writelog.ts +701 -0
  33. package/ts/nosqldb/classes.writeobserver.ts +62 -28
  34. package/ts/nosqldb/classes.writerevisions.ts +224 -32
  35. package/ts/nosqldb/engineidentity.ts +16 -0
@@ -21,9 +21,11 @@ import {
21
21
  captureSmartdataWrite,
22
22
  hasSmartdataWriteObservers,
23
23
  type ISmartdataWrite,
24
+ type ISmartdataWriteReport,
24
25
  type TSmartdataWriteKind,
25
26
  } from './classes.writeobserver.js';
26
27
  import {
28
+ accountSmartdataOwnWriteRevisions,
27
29
  captureSmartdataCollectionChange,
28
30
  captureSmartdataReadChange,
29
31
  hasSmartdataReadScope,
@@ -35,12 +37,22 @@ import {
35
37
  type ISmartdataReadSpec,
36
38
  } from './classes.readtracker.js';
37
39
  import {
40
+ beginSmartdataWriteRevisionCommit,
38
41
  flushSmartdataWriteRevisions,
39
42
  isSmartdataWriteRevisionsActive,
43
+ noteSmartdataWriteRevisionEntries,
40
44
  readPreviousRevisionIds,
41
45
  sharedRevisionId,
42
46
  writeRevisionsPartitionField,
43
47
  } from './classes.writerevisions.js';
48
+ import {
49
+ deliverSmartdataWriteLogCommit,
50
+ flushSmartdataWriteLog,
51
+ isSmartdataWriteLogActive,
52
+ requiresSmartdataWriteTransactions,
53
+ writeLogReportOf,
54
+ type ISmartdataWriteLogCommit,
55
+ } from './classes.writelog.js';
44
56
 
45
57
  interface ISmartdataSessionBinding {
46
58
  session: SmartdataSession;
@@ -70,6 +82,10 @@ interface ISmartdataSessionBinding {
70
82
  transactionWrites: ISmartdataTransactionWrite[];
71
83
  /** The write-revision counters the current attempt's writes bump right before its commit. */
72
84
  revisionBumps: Set<string>;
85
+ /** The reports of the current attempt's writes, which its write-log entry carries. */
86
+ logReports: ISmartdataWriteReport[];
87
+ /** Collections the current attempt changed in ways no report describes. */
88
+ logUnplaced: Set<string>;
73
89
  operationSettled?: Promise<void>;
74
90
  resolveOperationSettled?: () => void;
75
91
  invalidated: boolean;
@@ -361,7 +377,15 @@ export class SmartdataSession {
361
377
  let attemptInterruption: { error: unknown } | undefined;
362
378
  /** The session-ended rejection the last attempt failed with, and how far that transaction had got. */
363
379
  let sessionEnded: { error: SmartdataPersistenceError; end: TSmartdataTransactionEnd } | undefined;
380
+ let finishRevisionCommit: (() => void) | undefined;
381
+ /** The last attempt's write-log entry until the transaction's outcome is known. */
382
+ let logCommit: ISmartdataWriteLogCommit | undefined;
364
383
  const runAttempt = async () => {
384
+ finishRevisionCommit?.();
385
+ finishRevisionCommit = undefined;
386
+ // The driver starts another attempt only once the last one was aborted.
387
+ logCommit?.settle();
388
+ logCommit = undefined;
365
389
  const attempt = Symbol('smartdataTransactionAttempt');
366
390
  binding.transactionAttempt = attempt;
367
391
  binding.transactionCallbackOpen = true;
@@ -370,6 +394,8 @@ export class SmartdataSession {
370
394
  binding.transactionCommitReached = false;
371
395
  binding.transactionWrites = [];
372
396
  binding.revisionBumps = new Set();
397
+ binding.logReports = [];
398
+ binding.logUnplaced = new Set();
373
399
  callbackError = undefined;
374
400
  attemptInterruption = undefined;
375
401
  try {
@@ -421,16 +447,22 @@ export class SmartdataSession {
421
447
  }
422
448
  // An abort that arrived while the callback ran rolls the attempt back.
423
449
  signal?.throwIfAborted();
424
- // The writes' revisions commit with them. A failure fails the attempt
425
- // before its commit, and a transient one is retried with it.
450
+ // The writes' revisions and write-log entry commit with them. A
451
+ // failure fails the attempt before its commit, and a transient one is
452
+ // retried with it.
453
+ const mongoDb = binding.client.db(binding.owner.smartdataOptions.mongoDbName);
426
454
  if (binding.revisionBumps.size > 0) {
427
- await flushSmartdataWriteRevisions(
428
- binding.client.db(binding.owner.smartdataOptions.mongoDbName),
429
- binding.rawSession,
430
- binding.revisionBumps,
431
- signal,
432
- );
455
+ finishRevisionCommit = beginSmartdataWriteRevisionCommit(binding.owner, binding.revisionBumps);
456
+ await flushSmartdataWriteRevisions(mongoDb, binding.rawSession, binding.revisionBumps, signal);
433
457
  }
458
+ logCommit = await flushSmartdataWriteLog(
459
+ binding.owner,
460
+ mongoDb,
461
+ binding.rawSession,
462
+ binding.logReports,
463
+ binding.logUnplaced,
464
+ signal,
465
+ );
434
466
  // The driver commits next; a deadline that expires or a session that
435
467
  // ends from here on leaves the commit outcome unknown.
436
468
  binding.transactionCommitReached = true;
@@ -509,17 +541,25 @@ export class SmartdataSession {
509
541
  let result: TResult | undefined;
510
542
  let transactionEnded: TSmartdataTransactionEnd | undefined;
511
543
  let committedWrites: ISmartdataTransactionWrite[] = [];
544
+ let committedRevisionBumps: ReadonlySet<string> = new Set();
545
+ let returnedFromCommit = false;
512
546
  try {
513
547
  result = timeoutMS === undefined
514
548
  ? await binding.rawSession.withTransaction(runAttempt)
515
549
  : await binding.rawSession.withTransaction(runAttempt, { timeoutMS });
516
550
  transactionEnded = binding.transactionEnded;
517
551
  committedWrites = binding.transactionWrites;
552
+ committedRevisionBumps = binding.revisionBumps;
553
+ returnedFromCommit = true;
518
554
  } catch (errorArg) {
519
555
  const { error, outcome } = settleTransactionRejection(errorArg);
520
556
  if (outcome === 'unknown') changeReadsOfTransaction(binding.transactionWrites);
521
557
  throw rejectTransaction(error, outcome);
522
558
  } finally {
559
+ if (!returnedFromCommit) {
560
+ finishRevisionCommit?.();
561
+ logCommit?.settle();
562
+ }
523
563
  binding.transactionActive = false;
524
564
  binding.transactionTimeoutMS = undefined;
525
565
  binding.transactionSignal = undefined;
@@ -529,15 +569,33 @@ export class SmartdataSession {
529
569
  binding.transactionEnded = undefined;
530
570
  binding.transactionWrites = [];
531
571
  binding.revisionBumps = new Set();
572
+ binding.logReports = [];
573
+ binding.logUnplaced = new Set();
532
574
  }
533
- if (transactionEnded) {
534
- const outcome = commitOutcomeOfTransactionEnd(transactionEnded);
535
- if (outcome === 'unknown') changeReadsOfTransaction(committedWrites);
536
- throw rejectTransaction(createTransactionSessionEndedError(transactionEnded), outcome);
537
- }
538
- changeReadsOfTransaction(committedWrites);
539
- for (const write of committedWrites) {
540
- write.deliver?.();
575
+ try {
576
+ if (transactionEnded) {
577
+ const outcome = commitOutcomeOfTransactionEnd(transactionEnded);
578
+ try {
579
+ if (outcome === 'unknown') changeReadsOfTransaction(committedWrites);
580
+ } finally {
581
+ finishRevisionCommit?.();
582
+ }
583
+ throw rejectTransaction(createTransactionSessionEndedError(transactionEnded), outcome);
584
+ }
585
+ try {
586
+ changeReadsOfTransaction(committedWrites);
587
+ accountSmartdataOwnWriteRevisions(binding.owner, committedRevisionBumps);
588
+ } finally {
589
+ finishRevisionCommit?.();
590
+ }
591
+ noteSmartdataWriteRevisionEntries(binding.owner, committedRevisionBumps);
592
+ for (const write of committedWrites) {
593
+ write.deliver?.();
594
+ }
595
+ // Followers hear of the commit before they may read it from the journal.
596
+ if (logCommit) deliverSmartdataWriteLogCommit(binding.owner, logCommit);
597
+ } finally {
598
+ logCommit?.settle();
541
599
  }
542
600
  return result;
543
601
  }
@@ -578,6 +636,8 @@ export const createSmartdataSession = (ownerArg: SmartdataDb): SmartdataSession
578
636
  transactionCommitReached: false,
579
637
  transactionWrites: [],
580
638
  revisionBumps: new Set(),
639
+ logReports: [],
640
+ logUnplaced: new Set(),
581
641
  invalidated: false,
582
642
  };
583
643
  bindings.set(session, binding);
@@ -834,18 +894,19 @@ const revisionTransactionSessionOf = (ownerArg: SmartdataDb): SmartdataSession |
834
894
 
835
895
  /**
836
896
  * Whether a write must start a transaction of its own to carry its write
837
- * revisions: the database keeps them and the write runs outside an owned
838
- * transaction. A raw driver session cannot carry them and is refused.
897
+ * revisions and write-log entry: the database keeps either and the write runs
898
+ * outside an owned transaction. A raw driver session cannot carry them and is
899
+ * refused.
839
900
  */
840
- export const needsSmartdataRevisionTransaction = (
901
+ export const needsSmartdataWriteTransaction = (
841
902
  sessionArg: TSmartdataOrdinarySession | undefined,
842
903
  ownerArg: SmartdataDb,
843
904
  ): boolean => {
844
- if (!isSmartdataWriteRevisionsActive(ownerArg)) return false;
905
+ if (!requiresSmartdataWriteTransactions(ownerArg)) return false;
845
906
  if (sessionArg !== undefined && !isSmartdataSession(sessionArg)) {
846
907
  throw new SmartdataPersistenceError(
847
908
  'unsupported_operation',
848
- 'Writes of a database with write revisions run on sessions of db.createSession(), not raw driver sessions.',
909
+ 'Writes of a database with write revisions or a write log run on sessions of db.createSession(), not raw driver sessions.',
849
910
  );
850
911
  }
851
912
  return sessionArg === undefined || !resolveBinding(sessionArg, ownerArg).transactionActive;
@@ -860,13 +921,13 @@ const transactionOptionsOf = (
860
921
  });
861
922
 
862
923
  /**
863
- * Runs one write of a database with write revisions in a transaction, so its
864
- * revisions commit with it: on the caller's owned session, or on a session of
924
+ * Runs one write of a database with write revisions or a write log in a
925
+ * transaction, so its revisions and entry commit with it: on the caller's owned session, or on a session of
865
926
  * its own that ends with the call. The call's deadline and signal bound the
866
927
  * whole transaction; the collection is prepared before it starts, since its
867
928
  * DDL cannot run inside.
868
929
  */
869
- export const runSmartdataRevisionTransaction = async <TResult>(
930
+ export const runSmartdataWriteTransaction = async <TResult>(
870
931
  sessionArg: SmartdataSession | undefined,
871
932
  ownerArg: SmartdataDb,
872
933
  optionsArg: Pick<
@@ -916,8 +977,8 @@ export const runWithOrdinarySmartdataSession = async <TResult>(
916
977
  ) => Promise<TResult>,
917
978
  ): Promise<TResult> => {
918
979
  optionsArg.signal?.throwIfAborted();
919
- if (optionsArg.ordinaryWrite && needsSmartdataRevisionTransaction(sessionArg, ownerArg)) {
920
- return runSmartdataRevisionTransaction(
980
+ if (optionsArg.ordinaryWrite && needsSmartdataWriteTransaction(sessionArg, ownerArg)) {
981
+ return runSmartdataWriteTransaction(
921
982
  sessionArg as SmartdataSession | undefined,
922
983
  ownerArg,
923
984
  optionsArg,
@@ -965,6 +1026,21 @@ const bufferTransactionWrite = (
965
1026
  }
966
1027
  };
967
1028
 
1029
+ /**
1030
+ * Adds a write's report, or a collection no report describes, to the
1031
+ * write-log entry of an owned transaction's current attempt. A write that
1032
+ * settles after its attempt ended rolls back with that attempt.
1033
+ */
1034
+ const logTransactionWrite = (
1035
+ bindingArg: ISmartdataSessionBinding,
1036
+ writeArg: { report: ISmartdataWriteReport } | { unplaced: string },
1037
+ ): void => {
1038
+ const attempt = transactionContext.getStore()?.attempt;
1039
+ if (attempt === undefined || attempt !== bindingArg.transactionAttempt) return;
1040
+ if ('report' in writeArg) bindingArg.logReports.push(writeArg.report);
1041
+ else bindingArg.logUnplaced.add(writeArg.unplaced);
1042
+ };
1043
+
968
1044
  /**
969
1045
  * Adds write-revision counters to the current attempt of an owned transaction.
970
1046
  * A write that settles after its attempt ended rolls back with that attempt.
@@ -1026,6 +1102,8 @@ export const reportSmartdataWrite = (
1026
1102
  if (field !== undefined && isSmartdataWriteRevisionsActive(ownerArg)) {
1027
1103
  bumpTransactionRevisions(binding, writeRevisionIdsOfWrite(writeArg, field));
1028
1104
  }
1105
+ const report = writeLogReportOf(ownerArg, writeArg);
1106
+ if (report) logTransactionWrite(binding, { report });
1029
1107
  }
1030
1108
  const changeReads = captureSmartdataReadChange(ownerArg, writeArg, buffered);
1031
1109
  const deliver = captureSmartdataWrite(ownerArg, writeArg);
@@ -1054,6 +1132,9 @@ export const reportSmartdataWriteFailure = (
1054
1132
  if (binding && buffered && isSmartdataWriteRevisionsActive(ownerArg)) {
1055
1133
  bumpTransactionRevisions(binding, [sharedRevisionId(collectionNameArg)]);
1056
1134
  }
1135
+ if (binding && buffered && isSmartdataWriteLogActive(ownerArg)) {
1136
+ logTransactionWrite(binding, { unplaced: collectionNameArg });
1137
+ }
1057
1138
  const changeReads = captureSmartdataCollectionChange(ownerArg, collectionNameArg, buffered);
1058
1139
  if (!changeReads) return;
1059
1140
  if (binding && buffered) {