@lossless.org/client 1.22.2 → 1.23.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 (32) 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 +41 -1
  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.session.d.ts +7 -6
  11. package/dist_ts/nosqldb/classes.session.js +88 -36
  12. package/dist_ts/nosqldb/classes.writelog.d.ts +129 -0
  13. package/dist_ts/nosqldb/classes.writelog.js +497 -0
  14. package/dist_ts/nosqldb/classes.writeobserver.d.ts +13 -0
  15. package/dist_ts/nosqldb/classes.writeobserver.js +47 -25
  16. package/dist_ts/nosqldb/classes.writerevisions.d.ts +40 -4
  17. package/dist_ts/nosqldb/classes.writerevisions.js +174 -37
  18. package/dist_ts/nosqldb/engineidentity.d.ts +6 -0
  19. package/dist_ts/nosqldb/engineidentity.js +14 -1
  20. package/package.json +2 -2
  21. package/readme.md +11 -10
  22. package/ts/00_commitinfo_data.ts +1 -1
  23. package/ts/nosqldb/classes.bootstraprecord.ts +3 -3
  24. package/ts/nosqldb/classes.connection.ts +5 -1
  25. package/ts/nosqldb/classes.db.ts +64 -0
  26. package/ts/nosqldb/classes.doc.ts +4 -4
  27. package/ts/nosqldb/classes.exactpersistence.ts +8 -7
  28. package/ts/nosqldb/classes.session.ts +107 -38
  29. package/ts/nosqldb/classes.writelog.ts +701 -0
  30. package/ts/nosqldb/classes.writeobserver.ts +62 -28
  31. package/ts/nosqldb/classes.writerevisions.ts +225 -41
  32. package/ts/nosqldb/engineidentity.ts +16 -0
@@ -21,6 +21,7 @@ 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 {
@@ -36,13 +37,22 @@ import {
36
37
  type ISmartdataReadSpec,
37
38
  } from './classes.readtracker.js';
38
39
  import {
39
- beginSmartdataWriteRevisionCommit,
40
+ beginSmartdataWriteRevisionAttempt,
40
41
  flushSmartdataWriteRevisions,
41
42
  isSmartdataWriteRevisionsActive,
43
+ type ISmartdataWriteRevisionAttempt,
42
44
  readPreviousRevisionIds,
43
45
  sharedRevisionId,
44
46
  writeRevisionsPartitionField,
45
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';
46
56
 
47
57
  interface ISmartdataSessionBinding {
48
58
  session: SmartdataSession;
@@ -72,6 +82,12 @@ interface ISmartdataSessionBinding {
72
82
  transactionWrites: ISmartdataTransactionWrite[];
73
83
  /** The write-revision counters the current attempt's writes bump right before its commit. */
74
84
  revisionBumps: Set<string>;
85
+ /** The slots of those counters the current attempt holds. */
86
+ revisionAttempt?: ISmartdataWriteRevisionAttempt;
87
+ /** The reports of the current attempt's writes, which its write-log entry carries. */
88
+ logReports: ISmartdataWriteReport[];
89
+ /** Collections the current attempt changed in ways no report describes. */
90
+ logUnplaced: Set<string>;
75
91
  operationSettled?: Promise<void>;
76
92
  resolveOperationSettled?: () => void;
77
93
  invalidated: boolean;
@@ -363,10 +379,17 @@ export class SmartdataSession {
363
379
  let attemptInterruption: { error: unknown } | undefined;
364
380
  /** The session-ended rejection the last attempt failed with, and how far that transaction had got. */
365
381
  let sessionEnded: { error: SmartdataPersistenceError; end: TSmartdataTransactionEnd } | undefined;
366
- let finishRevisionCommit: (() => void) | undefined;
382
+ let revisionAttempt: ISmartdataWriteRevisionAttempt | undefined;
383
+ /** The last attempt's write-log entry until the transaction's outcome is known. */
384
+ let logCommit: ISmartdataWriteLogCommit | undefined;
367
385
  const runAttempt = async () => {
368
- finishRevisionCommit?.();
369
- finishRevisionCommit = undefined;
386
+ // The last attempt's slots end after this one starts, so it bumps others.
387
+ const attemptStartedAt = performance.now();
388
+ revisionAttempt?.finish(false);
389
+ revisionAttempt = beginSmartdataWriteRevisionAttempt(binding.owner, attemptStartedAt);
390
+ // The driver starts another attempt only once the last one was aborted.
391
+ logCommit?.settle();
392
+ logCommit = undefined;
370
393
  const attempt = Symbol('smartdataTransactionAttempt');
371
394
  binding.transactionAttempt = attempt;
372
395
  binding.transactionCallbackOpen = true;
@@ -375,6 +398,9 @@ export class SmartdataSession {
375
398
  binding.transactionCommitReached = false;
376
399
  binding.transactionWrites = [];
377
400
  binding.revisionBumps = new Set();
401
+ binding.revisionAttempt = revisionAttempt;
402
+ binding.logReports = [];
403
+ binding.logUnplaced = new Set();
378
404
  callbackError = undefined;
379
405
  attemptInterruption = undefined;
380
406
  try {
@@ -426,17 +452,22 @@ export class SmartdataSession {
426
452
  }
427
453
  // An abort that arrived while the callback ran rolls the attempt back.
428
454
  signal?.throwIfAborted();
429
- // The writes' revisions commit with them. A failure fails the attempt
430
- // before its commit, and a transient one is retried with it.
455
+ // The writes' revisions and write-log entry commit with them. A
456
+ // failure fails the attempt before its commit, and a transient one is
457
+ // retried with it.
458
+ const mongoDb = binding.client.db(binding.owner.smartdataOptions.mongoDbName);
431
459
  if (binding.revisionBumps.size > 0) {
432
- finishRevisionCommit = beginSmartdataWriteRevisionCommit(binding.owner, binding.revisionBumps);
433
- await flushSmartdataWriteRevisions(
434
- binding.client.db(binding.owner.smartdataOptions.mongoDbName),
435
- binding.rawSession,
436
- binding.revisionBumps,
437
- signal,
438
- );
460
+ revisionAttempt.commit();
461
+ await flushSmartdataWriteRevisions(mongoDb, binding.rawSession, revisionAttempt.slots, signal);
439
462
  }
463
+ logCommit = await flushSmartdataWriteLog(
464
+ binding.owner,
465
+ mongoDb,
466
+ binding.rawSession,
467
+ binding.logReports,
468
+ binding.logUnplaced,
469
+ signal,
470
+ );
440
471
  // The driver commits next; a deadline that expires or a session that
441
472
  // ends from here on leaves the commit outcome unknown.
442
473
  binding.transactionCommitReached = true;
@@ -530,7 +561,10 @@ export class SmartdataSession {
530
561
  if (outcome === 'unknown') changeReadsOfTransaction(binding.transactionWrites);
531
562
  throw rejectTransaction(error, outcome);
532
563
  } finally {
533
- if (!returnedFromCommit) finishRevisionCommit?.();
564
+ if (!returnedFromCommit) {
565
+ revisionAttempt?.finish(false);
566
+ logCommit?.settle();
567
+ }
534
568
  binding.transactionActive = false;
535
569
  binding.transactionTimeoutMS = undefined;
536
570
  binding.transactionSignal = undefined;
@@ -540,24 +574,33 @@ export class SmartdataSession {
540
574
  binding.transactionEnded = undefined;
541
575
  binding.transactionWrites = [];
542
576
  binding.revisionBumps = new Set();
577
+ binding.revisionAttempt = undefined;
578
+ binding.logReports = [];
579
+ binding.logUnplaced = new Set();
543
580
  }
544
- if (transactionEnded) {
545
- const outcome = commitOutcomeOfTransactionEnd(transactionEnded);
581
+ try {
582
+ if (transactionEnded) {
583
+ const outcome = commitOutcomeOfTransactionEnd(transactionEnded);
584
+ try {
585
+ if (outcome === 'unknown') changeReadsOfTransaction(committedWrites);
586
+ } finally {
587
+ revisionAttempt?.finish(false);
588
+ }
589
+ throw rejectTransaction(createTransactionSessionEndedError(transactionEnded), outcome);
590
+ }
546
591
  try {
547
- if (outcome === 'unknown') changeReadsOfTransaction(committedWrites);
592
+ changeReadsOfTransaction(committedWrites);
593
+ accountSmartdataOwnWriteRevisions(binding.owner, committedRevisionBumps);
548
594
  } finally {
549
- finishRevisionCommit?.();
595
+ revisionAttempt?.finish(true);
550
596
  }
551
- throw rejectTransaction(createTransactionSessionEndedError(transactionEnded), outcome);
552
- }
553
- try {
554
- changeReadsOfTransaction(committedWrites);
555
- accountSmartdataOwnWriteRevisions(binding.owner, committedRevisionBumps);
597
+ for (const write of committedWrites) {
598
+ write.deliver?.();
599
+ }
600
+ // Followers hear of the commit before they may read it from the journal.
601
+ if (logCommit) deliverSmartdataWriteLogCommit(binding.owner, logCommit);
556
602
  } finally {
557
- finishRevisionCommit?.();
558
- }
559
- for (const write of committedWrites) {
560
- write.deliver?.();
603
+ logCommit?.settle();
561
604
  }
562
605
  return result;
563
606
  }
@@ -598,6 +641,8 @@ export const createSmartdataSession = (ownerArg: SmartdataDb): SmartdataSession
598
641
  transactionCommitReached: false,
599
642
  transactionWrites: [],
600
643
  revisionBumps: new Set(),
644
+ logReports: [],
645
+ logUnplaced: new Set(),
601
646
  invalidated: false,
602
647
  };
603
648
  bindings.set(session, binding);
@@ -854,18 +899,19 @@ const revisionTransactionSessionOf = (ownerArg: SmartdataDb): SmartdataSession |
854
899
 
855
900
  /**
856
901
  * Whether a write must start a transaction of its own to carry its write
857
- * revisions: the database keeps them and the write runs outside an owned
858
- * transaction. A raw driver session cannot carry them and is refused.
902
+ * revisions and write-log entry: the database keeps either and the write runs
903
+ * outside an owned transaction. A raw driver session cannot carry them and is
904
+ * refused.
859
905
  */
860
- export const needsSmartdataRevisionTransaction = (
906
+ export const needsSmartdataWriteTransaction = (
861
907
  sessionArg: TSmartdataOrdinarySession | undefined,
862
908
  ownerArg: SmartdataDb,
863
909
  ): boolean => {
864
- if (!isSmartdataWriteRevisionsActive(ownerArg)) return false;
910
+ if (!requiresSmartdataWriteTransactions(ownerArg)) return false;
865
911
  if (sessionArg !== undefined && !isSmartdataSession(sessionArg)) {
866
912
  throw new SmartdataPersistenceError(
867
913
  'unsupported_operation',
868
- 'Writes of a database with write revisions run on sessions of db.createSession(), not raw driver sessions.',
914
+ 'Writes of a database with write revisions or a write log run on sessions of db.createSession(), not raw driver sessions.',
869
915
  );
870
916
  }
871
917
  return sessionArg === undefined || !resolveBinding(sessionArg, ownerArg).transactionActive;
@@ -880,13 +926,13 @@ const transactionOptionsOf = (
880
926
  });
881
927
 
882
928
  /**
883
- * Runs one write of a database with write revisions in a transaction, so its
884
- * revisions commit with it: on the caller's owned session, or on a session of
929
+ * Runs one write of a database with write revisions or a write log in a
930
+ * transaction, so its revisions and entry commit with it: on the caller's owned session, or on a session of
885
931
  * its own that ends with the call. The call's deadline and signal bound the
886
932
  * whole transaction; the collection is prepared before it starts, since its
887
933
  * DDL cannot run inside.
888
934
  */
889
- export const runSmartdataRevisionTransaction = async <TResult>(
935
+ export const runSmartdataWriteTransaction = async <TResult>(
890
936
  sessionArg: SmartdataSession | undefined,
891
937
  ownerArg: SmartdataDb,
892
938
  optionsArg: Pick<
@@ -936,8 +982,8 @@ export const runWithOrdinarySmartdataSession = async <TResult>(
936
982
  ) => Promise<TResult>,
937
983
  ): Promise<TResult> => {
938
984
  optionsArg.signal?.throwIfAborted();
939
- if (optionsArg.ordinaryWrite && needsSmartdataRevisionTransaction(sessionArg, ownerArg)) {
940
- return runSmartdataRevisionTransaction(
985
+ if (optionsArg.ordinaryWrite && needsSmartdataWriteTransaction(sessionArg, ownerArg)) {
986
+ return runSmartdataWriteTransaction(
941
987
  sessionArg as SmartdataSession | undefined,
942
988
  ownerArg,
943
989
  optionsArg,
@@ -985,6 +1031,21 @@ const bufferTransactionWrite = (
985
1031
  }
986
1032
  };
987
1033
 
1034
+ /**
1035
+ * Adds a write's report, or a collection no report describes, to the
1036
+ * write-log entry of an owned transaction's current attempt. A write that
1037
+ * settles after its attempt ended rolls back with that attempt.
1038
+ */
1039
+ const logTransactionWrite = (
1040
+ bindingArg: ISmartdataSessionBinding,
1041
+ writeArg: { report: ISmartdataWriteReport } | { unplaced: string },
1042
+ ): void => {
1043
+ const attempt = transactionContext.getStore()?.attempt;
1044
+ if (attempt === undefined || attempt !== bindingArg.transactionAttempt) return;
1045
+ if ('report' in writeArg) bindingArg.logReports.push(writeArg.report);
1046
+ else bindingArg.logUnplaced.add(writeArg.unplaced);
1047
+ };
1048
+
988
1049
  /**
989
1050
  * Adds write-revision counters to the current attempt of an owned transaction.
990
1051
  * A write that settles after its attempt ended rolls back with that attempt.
@@ -995,7 +1056,10 @@ const bumpTransactionRevisions = (
995
1056
  ): void => {
996
1057
  const attempt = transactionContext.getStore()?.attempt;
997
1058
  if (attempt === undefined || attempt !== bindingArg.transactionAttempt) return;
998
- for (const id of idsArg) bindingArg.revisionBumps.add(id);
1059
+ for (const id of idsArg) {
1060
+ bindingArg.revisionBumps.add(id);
1061
+ bindingArg.revisionAttempt?.bump(id);
1062
+ }
999
1063
  };
1000
1064
 
1001
1065
  /**
@@ -1046,6 +1110,8 @@ export const reportSmartdataWrite = (
1046
1110
  if (field !== undefined && isSmartdataWriteRevisionsActive(ownerArg)) {
1047
1111
  bumpTransactionRevisions(binding, writeRevisionIdsOfWrite(writeArg, field));
1048
1112
  }
1113
+ const report = writeLogReportOf(ownerArg, writeArg);
1114
+ if (report) logTransactionWrite(binding, { report });
1049
1115
  }
1050
1116
  const changeReads = captureSmartdataReadChange(ownerArg, writeArg, buffered);
1051
1117
  const deliver = captureSmartdataWrite(ownerArg, writeArg);
@@ -1074,6 +1140,9 @@ export const reportSmartdataWriteFailure = (
1074
1140
  if (binding && buffered && isSmartdataWriteRevisionsActive(ownerArg)) {
1075
1141
  bumpTransactionRevisions(binding, [sharedRevisionId(collectionNameArg)]);
1076
1142
  }
1143
+ if (binding && buffered && isSmartdataWriteLogActive(ownerArg)) {
1144
+ logTransactionWrite(binding, { unplaced: collectionNameArg });
1145
+ }
1077
1146
  const changeReads = captureSmartdataCollectionChange(ownerArg, collectionNameArg, buffered);
1078
1147
  if (!changeReads) return;
1079
1148
  if (binding && buffered) {