@proteinjs/db 1.25.2 → 1.27.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 (63) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +21 -0
  3. package/dist/generated/index.js +7 -7
  4. package/dist/generated/index.js.map +1 -1
  5. package/dist/generated/test/index.js +5 -5
  6. package/dist/generated/test/index.js.map +1 -1
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +2 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/src/Db.d.ts +66 -6
  12. package/dist/src/Db.d.ts.map +1 -1
  13. package/dist/src/Db.js +242 -30
  14. package/dist/src/Db.js.map +1 -1
  15. package/dist/src/MigrationRunner.d.ts.map +1 -1
  16. package/dist/src/MigrationRunner.js +6 -0
  17. package/dist/src/MigrationRunner.js.map +1 -1
  18. package/dist/src/UpdatePreserving.d.ts +34 -0
  19. package/dist/src/UpdatePreserving.d.ts.map +1 -0
  20. package/dist/src/UpdatePreserving.js +72 -0
  21. package/dist/src/UpdatePreserving.js.map +1 -0
  22. package/dist/src/auth/TableAuth.d.ts +9 -0
  23. package/dist/src/auth/TableAuth.d.ts.map +1 -1
  24. package/dist/src/auth/TableAuth.js +36 -5
  25. package/dist/src/auth/TableAuth.js.map +1 -1
  26. package/dist/src/auth/TableServiceAuth.d.ts +7 -0
  27. package/dist/src/auth/TableServiceAuth.d.ts.map +1 -1
  28. package/dist/src/auth/TableServiceAuth.js +34 -0
  29. package/dist/src/auth/TableServiceAuth.js.map +1 -1
  30. package/dist/src/reference/ArrayMembershipOps.d.ts +69 -0
  31. package/dist/src/reference/ArrayMembershipOps.d.ts.map +1 -0
  32. package/dist/src/reference/ArrayMembershipOps.js +146 -0
  33. package/dist/src/reference/ArrayMembershipOps.js.map +1 -0
  34. package/dist/src/services/DbService.d.ts +38 -0
  35. package/dist/src/services/DbService.d.ts.map +1 -1
  36. package/dist/src/services/DbService.js.map +1 -1
  37. package/dist/src/transaction/Transaction.d.ts +19 -2
  38. package/dist/src/transaction/Transaction.d.ts.map +1 -1
  39. package/dist/src/transaction/Transaction.js +38 -0
  40. package/dist/src/transaction/Transaction.js.map +1 -1
  41. package/dist/src/transaction/TransactionContextFactory.d.ts +18 -1
  42. package/dist/src/transaction/TransactionContextFactory.d.ts.map +1 -1
  43. package/dist/test/ArrayMembershipOps.test.d.ts +2 -0
  44. package/dist/test/ArrayMembershipOps.test.d.ts.map +1 -0
  45. package/dist/test/ArrayMembershipOps.test.js +147 -0
  46. package/dist/test/ArrayMembershipOps.test.js.map +1 -0
  47. package/dist/test/TableServiceAuth.test.js +108 -4
  48. package/dist/test/TableServiceAuth.test.js.map +1 -1
  49. package/generated/index.ts +18 -21
  50. package/generated/test/index.ts +41 -44
  51. package/index.ts +2 -0
  52. package/package.json +6 -5
  53. package/src/Db.ts +202 -24
  54. package/src/MigrationRunner.ts +6 -0
  55. package/src/UpdatePreserving.ts +89 -0
  56. package/src/auth/TableAuth.ts +17 -4
  57. package/src/auth/TableServiceAuth.ts +36 -1
  58. package/src/reference/ArrayMembershipOps.ts +155 -0
  59. package/src/services/DbService.ts +38 -0
  60. package/src/transaction/Transaction.ts +52 -2
  61. package/src/transaction/TransactionContextFactory.ts +19 -1
  62. package/test/ArrayMembershipOps.test.ts +160 -0
  63. package/test/TableServiceAuth.test.ts +148 -4
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../generated/test/index.ts"],"names":[],"mappings":";AAAA,oCAAoC;;;;;;;;;;;;;;;;AAEpC,+BAA6B;AAC7B,6BAA2B;AAC3B,iCAA+B;AAC/B,iCAA+B;AAC/B,iCAA+B;AAC/B,8BAA4B;AAC5B,gCAA8B;AAC9B,2BAAyB;AACzB,kBAAgB;AAChB,gBAAc;AAGd,4BAA4B;AAE5B,IAAM,WAAW,GAAG,uj0FAAuj0F,CAAC;AAG5k0F,4BAA4B;AAE5B,0FAAgF;AAChF,yCAAwC;AACxC,0FAA+E;AAC/E,0FAAgF;AAChF,0FAA+E;AAC/E,0FAAgF;AAChF,0FAA+E;AAC/E,0FAA2E;AAC3E,0FAA8E;AAC9E,0FAAmF;AACnF,2FAAkF;AAClF,2FAA6E;AAC7E,2FAA2E;AAC3E,sFAAyE;AACzE,wEAA0E;AAC1E,wEAA8E;AAC9E,4GAAiG;AACjG,4GAAwF;AACxF,4GAAwF;AACxF,4GAA8F;AAC9F,wFAA8E;AAC9E,wFAAqF;AACrF,wFAAiF;AACjF,sFAA4F;AAC5F,sFAAgG;AAChG,mCAAkC;AAClC,iEAAgE;AAChE,6DAA4D;AAC5D,2FAA0F;AAC1F,iFAAgF;AAChF,yEAAwE;AACxE,2DAA0D;AAC1D,qEAAoE;AACpE,0EAAyE;AACzE,kEAAiE;AACjE,6EAA4E;AAE5E,IAAM,WAAW,GAAG;IACnB,8BAA8B,EAAE,wCAAc;IAC9C,qBAAqB,EAAE,aAAK;IAC5B,6BAA6B,EAAE,uCAAa;IAC5C,8BAA8B,EAAE,wCAAc;IAC9C,6BAA6B,EAAE,uCAAa;IAC5C,8BAA8B,EAAE,wCAAc;IAC9C,6BAA6B,EAAE,uCAAa;IAC5C,yBAAyB,EAAE,mCAAS;IACpC,4BAA4B,EAAE,sCAAY;IAC1C,iCAAiC,EAAE,2CAAiB;IACpD,gCAAgC,EAAE,2CAAgB;IAClD,2BAA2B,EAAE,sCAAW;IACxC,yBAAyB,EAAE,oCAAS;IACpC,yBAAyB,EAAE,iCAAS;IACpC,iCAAiC,EAAE,kCAAiB;IACpD,qCAAqC,EAAE,sCAAqB;IAC5D,sCAAsC,EAAE,yDAAsB;IAC9D,6BAA6B,EAAE,gDAAa;IAC5C,6BAA6B,EAAE,gDAAa;IAC5C,mCAAmC,EAAE,sDAAmB;IACxD,6BAA6B,EAAE,sCAAa;IAC5C,oCAAoC,EAAE,6CAAoB;IAC1D,gCAAgC,EAAE,yCAAgB;IAClD,4CAA4C,EAAE,oDAA4B;IAC1E,gDAAgD,EAAE,wDAAgC;IAClF,kBAAkB,EAAE,OAAE;IACtB,iCAAiC,EAAE,qCAAiB;IACpD,+BAA+B,EAAE,iCAAe;IAChD,wCAAwC,EAAE,mDAAwB;IAClE,mCAAmC,EAAE,yCAAmB;IACxD,+BAA+B,EAAE,iCAAe;IAChD,yBAAyB,EAAE,qBAAS;IACpC,8BAA8B,EAAE,+BAAc;IAC9C,kCAAkC,EAAE,uCAAkB;IACtD,8BAA8B,EAAE,+BAAc;IAC9C,iCAAiC,EAAE,qCAAiB;CACpD,CAAC;AAGF,kCAAkC;AAElC,oDAAyD;AACzD,6BAAgB,CAAC,KAAK,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAGjD,mDAAiC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../generated/test/index.ts"],"names":[],"mappings":";AAAA,oCAAoC;;;;;;;;;;;;;;;;AAEpC,+BAA6B;AAC7B,6BAA2B;AAC3B,iCAA+B;AAC/B,iCAA+B;AAC/B,iCAA+B;AAC/B,8BAA4B;AAC5B,gCAA8B;AAC9B,2BAAyB;AACzB,kBAAgB;AAChB,gBAAc;AAGd,4BAA4B;AAE5B,IAAM,WAAW,GAAG,gj8FAAgj8F,CAAC;AAGrk8F,4BAA4B;AAE5B,0FAAgF;AAChF,yCAAwC;AACxC,0FAA+E;AAC/E,0FAAgF;AAChF,0FAA+E;AAC/E,0FAAgF;AAChF,0FAA+E;AAC/E,0FAA2E;AAC3E,0FAA8E;AAC9E,0FAAmF;AACnF,2FAAkF;AAClF,2FAA6E;AAC7E,2FAA2E;AAC3E,sFAAyE;AACzE,wEAA0E;AAC1E,wEAA8E;AAC9E,4GAAiG;AACjG,4GAAwF;AACxF,4GAAwF;AACxF,4GAA8F;AAC9F,wFAA8E;AAC9E,wFAAqF;AACrF,wFAAiF;AACjF,sFAA4F;AAC5F,sFAAgG;AAChG,mCAAkC;AAClC,iEAAgE;AAChE,6DAA4D;AAC5D,2DAA0D;AAC1D,qEAAoE;AACpE,2FAA0F;AAC1F,iFAAgF;AAChF,yEAAwE;AACxE,0EAAyE;AACzE,kEAAiE;AACjE,6EAA4E;AAE5E,IAAM,WAAW,GAAG;IACnB,8BAA8B,EAAE,wCAAc;IAC9C,qBAAqB,EAAE,aAAK;IAC5B,6BAA6B,EAAE,uCAAa;IAC5C,8BAA8B,EAAE,wCAAc;IAC9C,6BAA6B,EAAE,uCAAa;IAC5C,8BAA8B,EAAE,wCAAc;IAC9C,6BAA6B,EAAE,uCAAa;IAC5C,yBAAyB,EAAE,mCAAS;IACpC,4BAA4B,EAAE,sCAAY;IAC1C,iCAAiC,EAAE,2CAAiB;IACpD,gCAAgC,EAAE,2CAAgB;IAClD,2BAA2B,EAAE,sCAAW;IACxC,yBAAyB,EAAE,oCAAS;IACpC,yBAAyB,EAAE,iCAAS;IACpC,iCAAiC,EAAE,kCAAiB;IACpD,qCAAqC,EAAE,sCAAqB;IAC5D,sCAAsC,EAAE,yDAAsB;IAC9D,6BAA6B,EAAE,gDAAa;IAC5C,6BAA6B,EAAE,gDAAa;IAC5C,mCAAmC,EAAE,sDAAmB;IACxD,6BAA6B,EAAE,sCAAa;IAC5C,oCAAoC,EAAE,6CAAoB;IAC1D,gCAAgC,EAAE,yCAAgB;IAClD,4CAA4C,EAAE,oDAA4B;IAC1E,gDAAgD,EAAE,wDAAgC;IAClF,kBAAkB,EAAE,OAAE;IACtB,iCAAiC,EAAE,qCAAiB;IACpD,+BAA+B,EAAE,iCAAe;IAChD,yBAAyB,EAAE,qBAAS;IACpC,8BAA8B,EAAE,+BAAc;IAC9C,wCAAwC,EAAE,mDAAwB;IAClE,mCAAmC,EAAE,yCAAmB;IACxD,+BAA+B,EAAE,iCAAe;IAChD,kCAAkC,EAAE,uCAAkB;IACtD,8BAA8B,EAAE,+BAAc;IAC9C,iCAAiC,EAAE,qCAAiB;CACpD,CAAC;AAGF,kCAAkC;AAElC,oDAAyD;AACzD,6BAAgB,CAAC,KAAK,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAGjD,mDAAiC"}
package/dist/index.d.ts CHANGED
@@ -5,7 +5,9 @@ export * from './src/auth/TableServiceAuth';
5
5
  export * from './src/Columns';
6
6
  export * from './src/Record';
7
7
  export * from './src/reference/ReferenceArray';
8
+ export * from './src/reference/ArrayMembershipOps';
8
9
  export * from './src/reference/Reference';
10
+ export * from './src/UpdatePreserving';
9
11
  export * from './src/reference/ReferenceCache';
10
12
  export * from './src/RecordIterator';
11
13
  export * from './src/source/SourceRecord';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qCAAqC,CAAC;AACpD,cAAc,6CAA6C,CAAC;AAE5D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAE1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAE5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,uCAAuC,CAAC;AACtD,cAAc,yCAAyC,CAAC;AAExD,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,gCAAgC,CAAC;AAC/C,cAAc,oCAAoC,CAAC;AACnD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qCAAqC,CAAC;AACpD,cAAc,6CAA6C,CAAC;AAE5D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAE1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAE5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,uCAAuC,CAAC;AACtD,cAAc,yCAAyC,CAAC;AAExD,cAAc,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -21,7 +21,9 @@ __exportStar(require("./src/auth/TableServiceAuth"), exports);
21
21
  __exportStar(require("./src/Columns"), exports);
22
22
  __exportStar(require("./src/Record"), exports);
23
23
  __exportStar(require("./src/reference/ReferenceArray"), exports);
24
+ __exportStar(require("./src/reference/ArrayMembershipOps"), exports);
24
25
  __exportStar(require("./src/reference/Reference"), exports);
26
+ __exportStar(require("./src/UpdatePreserving"), exports);
25
27
  __exportStar(require("./src/reference/ReferenceCache"), exports);
26
28
  __exportStar(require("./src/RecordIterator"), exports);
27
29
  __exportStar(require("./src/source/SourceRecord"), exports);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,8CAA4B;AAC5B,uDAAqC;AACrC,8DAA4C;AAC5C,gDAA8B;AAC9B,+CAA6B;AAC7B,iEAA+C;AAC/C,4DAA0C;AAC1C,iEAA+C;AAC/C,uDAAqC;AACrC,4DAA0C;AAC1C,4DAA0C;AAC1C,qDAAmC;AACnC,gEAA8C;AAC9C,sEAAoD;AACpD,8EAA4D;AAE5D,gEAA8C;AAC9C,8DAA4C;AAC5C,4DAA0C;AAE1C,sDAAoC;AACpC,8DAA4C;AAE5C,2DAAyC;AACzC,wEAAsD;AACtD,0EAAwD;AAExD,sDAAoC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,8CAA4B;AAC5B,uDAAqC;AACrC,8DAA4C;AAC5C,gDAA8B;AAC9B,+CAA6B;AAC7B,iEAA+C;AAC/C,qEAAmD;AACnD,4DAA0C;AAC1C,yDAAuC;AACvC,iEAA+C;AAC/C,uDAAqC;AACrC,4DAA0C;AAC1C,4DAA0C;AAC1C,qDAAmC;AACnC,gEAA8C;AAC9C,sEAAoD;AACpD,8EAA4D;AAE5D,gEAA8C;AAC9C,8DAA4C;AAC5C,4DAA0C;AAE1C,sDAAoC;AACpC,8DAA4C;AAE5C,2DAAyC;AACzC,wEAAsD;AACtD,0EAAwD;AAExD,sDAAoC"}
package/dist/src/Db.d.ts CHANGED
@@ -5,7 +5,9 @@ import { Table } from './Table';
5
5
  import { Record, SerializedRecord } from './Record';
6
6
  import { ParameterizationConfig, Statement } from '@proteinjs/db-query';
7
7
  import { TableManager } from './schema/TableManager';
8
- import { DefaultTransactionContextFactory } from './transaction/TransactionContextFactory';
8
+ import { DefaultTransactionContextFactory, PostCommitHook } from './transaction/TransactionContextFactory';
9
+ import { ArrayMembershipUpdate } from './reference/ArrayMembershipOps';
10
+ import { PreservedPath } from './UpdatePreserving';
9
11
  /** get `Db` if on server, and `DbService` if on browser */
10
12
  export declare const getDb: <R extends Record = Record>() => Db<R>;
11
13
  export declare const getDbAsSystem: <R extends Record = Record>() => Db<R>;
@@ -40,7 +42,6 @@ export declare class Db<R extends Record = Record> implements DbService<R> {
40
42
  private statementConfigFactory;
41
43
  private auth;
42
44
  private tableWatcherRunner;
43
- private currentTransaction?;
44
45
  private transactionContextFactory;
45
46
  serviceMetadata: Service['serviceMetadata'];
46
47
  constructor(dbDriver?: DbDriver, getTable?: (tableName: string) => Table<any>, transactionContextFactory?: DefaultTransactionContextFactory, runAsSystem?: boolean);
@@ -51,6 +52,27 @@ export declare class Db<R extends Record = Record> implements DbService<R> {
51
52
  get<T extends R>(table: Table<T>, query: Query<T>, options?: QueryOptions<T>): Promise<T>;
52
53
  insert<T extends R>(table: Table<T>, record: Omit<T, keyof R>): Promise<T>;
53
54
  update<T extends R>(table: Table<T>, record: Partial<T>, query?: Query<T>): Promise<number>;
55
+ /**
56
+ * Apply commutative membership ops (add/remove/move) to a `ReferenceArrayColumn`
57
+ * read-modify-write against COMMITTED truth, so concurrent membership writers
58
+ * converge instead of last-write-wins clobbering each other (the write-side
59
+ * lost-update class). Self-wraps in a transaction when called outside one; the
60
+ * driver's abort/retry re-executes the read, so a retried transaction applies
61
+ * its ops to fresh truth rather than replaying a stale list snapshot.
62
+ *
63
+ * Returns the update count (0 when the ops are a no-op against committed truth
64
+ * or the record no longer exists — a concurrently deleted record wins).
65
+ */
66
+ updateArrayMembership<T extends R>(table: Table<T>, update: ArrayMembershipUpdate): Promise<number>;
67
+ /**
68
+ * Update with committed-truth preservation for column sub-paths the writer does
69
+ * not own (see `UpdatePreserving.ts`). The payload's listed paths are overlaid
70
+ * with their committed values read inside the same transaction, so this write
71
+ * commutes with the writers that own those paths (e.g. a structural editor op
72
+ * writing a JSON object's styling while a debounced text save owns `content`).
73
+ * Self-wraps in a transaction when called outside one. Plain-JSON columns only.
74
+ */
75
+ updatePreserving<T extends R>(table: Table<T>, record: Partial<T>, preserve: PreservedPath[]): Promise<number>;
54
76
  delete<T extends R>(table: Table<T>, query: Query<T>): Promise<number>;
55
77
  private runColumnBeforeDeletes;
56
78
  private runCascadeDeletions;
@@ -79,13 +101,19 @@ export declare class Db<R extends Record = Record> implements DbService<R> {
79
101
  /**
80
102
  * Run a transaction.
81
103
  *
82
- * Use this db instance for any operation you want to include in the transaction.
83
- *
84
- * Note: This method uses Db instance state. Usually it is best to create a new instance
85
- * of Db to run a transaction.
104
+ * Db instances are STATELESS with respect to transactions: every operation resolves the
105
+ * ambient transaction (AsyncLocalStorage) at call time, so any Db instance used inside the
106
+ * transaction body whenever it was constructed rides the transaction. There is no way
107
+ * to issue an operation outside a transaction from inside its body, and no second session
108
+ * is ever acquired inside one (the historical pool-wedge class is unrepresentable —
109
+ * plans/DB_PERF_PLAN.md P2, in the consumer repo).
86
110
  *
87
111
  * Note: Nested transactions are not supported; will throw.
88
112
  *
113
+ * Note: work spawned inside the body but NOT awaited by it escapes the transaction's
114
+ * lifetime while still holding its context — such work fails loudly on its next db
115
+ * operation (see TransactionContextData.ended). Await everything inside the body, or run
116
+ * it outside the transaction.
89
117
  *
90
118
  * Example:
91
119
  *
@@ -101,6 +129,38 @@ export declare class Db<R extends Record = Record> implements DbService<R> {
101
129
  * ```
102
130
  */
103
131
  runTransaction<T>(fn: () => Promise<T>): Promise<T>;
132
+ /**
133
+ * Run `hook` once the write unit currently executing is durably committed.
134
+ *
135
+ * - Inside a `runTransaction` scope: the hook is queued on the ambient transaction context and
136
+ * runs after the transaction COMMITS. Hooks never run on rollback.
137
+ * - Outside a transaction: every DML statement auto-commits when it resolves, so the hook runs
138
+ * (awaited) immediately. Callers are responsible for invoking this only from points where
139
+ * the triggering statement has already executed — e.g. `after*` table watchers, never
140
+ * `before*` ones.
141
+ *
142
+ * For side effects that must observe committed truth — e.g. socket notifications that trigger
143
+ * client refetches: a pre-commit emission lets a client refetch read pre-commit rows and
144
+ * "resurrect" state the transaction is deleting.
145
+ */
146
+ runAfterCommit(hook: PostCommitHook): Promise<void>;
147
+ /**
148
+ * A fresh instance for self-wrapping RMW verbs (`updateArrayMembership`, `updatePreserving`)
149
+ * in a transaction. `runTransaction` carries the open transaction as INSTANCE state, and these
150
+ * verbs also serve the `DbService` RPC path, where one long-lived Db instance handles
151
+ * concurrent requests — self-wrapping on `this` would leak one request's transaction into
152
+ * another's ops (or spuriously reject it as a nested transaction). Same driver/tables/system
153
+ * mode; only the transaction state is isolated.
154
+ */
155
+ private newSelfWrapDb;
156
+ /**
157
+ * The transaction every driver call rides, resolved from the ambient context AT CALL TIME
158
+ * (statelessness is the safety property: operations inside a transaction body always ride
159
+ * it; operations outside always use the pool). The one remaining failure shape — work that
160
+ * escaped a finished transaction's body while holding its context — throws here by name;
161
+ * handing the driver an ended transaction would fail anyway, with a far worse error.
162
+ */
163
+ private transactionForDriver;
104
164
  private chunk;
105
165
  }
106
166
  //# sourceMappingURL=Db.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Db.d.ts","sourceRoot":"","sources":["../../src/Db.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Db.d.ts","sourceRoot":"","sources":["../../src/Db.ts"],"names":[],"mappings":"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"}
package/dist/src/Db.js CHANGED
@@ -35,6 +35,15 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
35
35
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
36
  }
37
37
  };
38
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
39
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
40
+ if (ar || !(i in from)) {
41
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
42
+ ar[i] = from[i];
43
+ }
44
+ }
45
+ return to.concat(ar || Array.prototype.slice.call(from));
46
+ };
38
47
  Object.defineProperty(exports, "__esModule", { value: true });
39
48
  exports.Db = exports.getDbAsSystem = exports.getDb = void 0;
40
49
  var DbService_1 = require("./services/DbService");
@@ -53,6 +62,8 @@ var TransactionContextFactory_1 = require("./transaction/TransactionContextFacto
53
62
  var util_1 = require("@proteinjs/util");
54
63
  var Reference_1 = require("./reference/Reference");
55
64
  var ReferenceArray_1 = require("./reference/ReferenceArray");
65
+ var ArrayMembershipOps_1 = require("./reference/ArrayMembershipOps");
66
+ var UpdatePreserving_1 = require("./UpdatePreserving");
56
67
  /** get `Db` if on server, and `DbService` if on browser */
57
68
  var getDb = function () {
58
69
  return typeof self === 'undefined' ? new Db() : (0, DbService_1.getDbService)();
@@ -81,10 +92,6 @@ var Db = /** @class */ (function () {
81
92
  this.transactionContextFactory = transactionContextFactory
82
93
  ? transactionContextFactory
83
94
  : this.getDefaultTransactionContextFactory();
84
- var transactionContext = this.transactionContextFactory.getTransactionContext();
85
- if (transactionContext.currentTransaction) {
86
- this.currentTransaction = transactionContext.currentTransaction;
87
- }
88
95
  }
89
96
  Db.getDefaultDbDriver = function () {
90
97
  if (!Db.defaultDbDriver) {
@@ -168,7 +175,7 @@ var Db = /** @class */ (function () {
168
175
  generateInsert = function (config) {
169
176
  return new db_query_1.StatementFactory().insert(table.name, serializedRecord, _this.statementConfigFactory.getStatementConfig(config));
170
177
  };
171
- return [4 /*yield*/, this.dbDriver.runDml(generateInsert, this.currentTransaction)];
178
+ return [4 /*yield*/, this.dbDriver.runDml(generateInsert, this.transactionForDriver())];
172
179
  case 5:
173
180
  _a.sent();
174
181
  return [4 /*yield*/, this.tableWatcherRunner.runAfterInsertTableWatchers(table, recordCopy)];
@@ -225,7 +232,7 @@ var Db = /** @class */ (function () {
225
232
  generateUpdate = function (config) {
226
233
  return new db_query_1.StatementFactory().update(table.name, serializedRecord, qb, _this.statementConfigFactory.getStatementConfig(config));
227
234
  };
228
- return [4 /*yield*/, this.dbDriver.runDml(generateUpdate, this.currentTransaction)];
235
+ return [4 /*yield*/, this.dbDriver.runDml(generateUpdate, this.transactionForDriver())];
229
236
  case 5:
230
237
  recordUpdateCount = _b.sent();
231
238
  return [4 /*yield*/, this.tableWatcherRunner.runAfterUpdateTableWatchers(table, recordUpdateCount, recordCopy, qb)];
@@ -236,6 +243,121 @@ var Db = /** @class */ (function () {
236
243
  });
237
244
  });
238
245
  };
246
+ /**
247
+ * Apply commutative membership ops (add/remove/move) to a `ReferenceArrayColumn`
248
+ * read-modify-write against COMMITTED truth, so concurrent membership writers
249
+ * converge instead of last-write-wins clobbering each other (the write-side
250
+ * lost-update class). Self-wraps in a transaction when called outside one; the
251
+ * driver's abort/retry re-executes the read, so a retried transaction applies
252
+ * its ops to fresh truth rather than replaying a stale list snapshot.
253
+ *
254
+ * Returns the update count (0 when the ops are a no-op against committed truth
255
+ * or the record no longer exists — a concurrently deleted record wins).
256
+ */
257
+ Db.prototype.updateArrayMembership = function (table, update) {
258
+ var _a, _b;
259
+ return __awaiter(this, void 0, void 0, function () {
260
+ var db_1, column, qb, rows, currentRefArray, currentIds, _c, ids, changed, record;
261
+ var _this = this;
262
+ return __generator(this, function (_d) {
263
+ switch (_d.label) {
264
+ case 0:
265
+ if (!!this.transactionContextFactory.getTransactionContext().currentTransaction) return [3 /*break*/, 2];
266
+ db_1 = this.newSelfWrapDb();
267
+ return [4 /*yield*/, db_1.runTransaction(function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
268
+ switch (_a.label) {
269
+ case 0: return [4 /*yield*/, db_1.updateArrayMembership(table, update)];
270
+ case 1: return [2 /*return*/, _a.sent()];
271
+ }
272
+ }); }); })];
273
+ case 1: return [2 /*return*/, _d.sent()];
274
+ case 2:
275
+ column = table.columns[update.columnPropertyName];
276
+ if (!column || ((_a = column.constructor) === null || _a === void 0 ? void 0 : _a.name) !== 'ReferenceArrayColumn') {
277
+ throw new Error("updateArrayMembership requires a ReferenceArrayColumn; '".concat(update.columnPropertyName, "' on table '").concat(table.name, "' is not one"));
278
+ }
279
+ qb = new QueryBuilderFactory_1.QueryBuilderFactory().getQueryBuilder(table);
280
+ return [4 /*yield*/, this.addColumnQueries(table, qb, 'write')];
281
+ case 3:
282
+ _d.sent();
283
+ qb.select({ fields: ['id', update.columnPropertyName] });
284
+ qb.condition({ field: 'id', operator: '=', value: update.recordId });
285
+ return [4 /*yield*/, this._query(table, qb)];
286
+ case 4:
287
+ rows = _d.sent();
288
+ if (rows.length === 0) {
289
+ return [2 /*return*/, 0];
290
+ }
291
+ currentRefArray = rows[0][update.columnPropertyName];
292
+ currentIds = (_b = currentRefArray === null || currentRefArray === void 0 ? void 0 : currentRefArray._ids) !== null && _b !== void 0 ? _b : [];
293
+ _c = (0, ArrayMembershipOps_1.applyArrayMembershipOps)(currentIds, update.ops), ids = _c.ids, changed = _c.changed;
294
+ if (!changed) {
295
+ return [2 /*return*/, 0];
296
+ }
297
+ record = { id: update.recordId };
298
+ record[update.columnPropertyName] = new ReferenceArray_1.ReferenceArray(column.referenceTable, ids);
299
+ return [4 /*yield*/, this.update(table, record)];
300
+ case 5: return [2 /*return*/, _d.sent()];
301
+ }
302
+ });
303
+ });
304
+ };
305
+ /**
306
+ * Update with committed-truth preservation for column sub-paths the writer does
307
+ * not own (see `UpdatePreserving.ts`). The payload's listed paths are overlaid
308
+ * with their committed values read inside the same transaction, so this write
309
+ * commutes with the writers that own those paths (e.g. a structural editor op
310
+ * writing a JSON object's styling while a debounced text save owns `content`).
311
+ * Self-wraps in a transaction when called outside one. Plain-JSON columns only.
312
+ */
313
+ Db.prototype.updatePreserving = function (table, record, preserve) {
314
+ return __awaiter(this, void 0, void 0, function () {
315
+ var db_2, applicable, qb, rows, recordCopy, _i, applicable_1, p;
316
+ var _this = this;
317
+ return __generator(this, function (_a) {
318
+ switch (_a.label) {
319
+ case 0:
320
+ if (!!this.transactionContextFactory.getTransactionContext().currentTransaction) return [3 /*break*/, 2];
321
+ db_2 = this.newSelfWrapDb();
322
+ return [4 /*yield*/, db_2.runTransaction(function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
323
+ switch (_a.label) {
324
+ case 0: return [4 /*yield*/, db_2.updatePreserving(table, record, preserve)];
325
+ case 1: return [2 /*return*/, _a.sent()];
326
+ }
327
+ }); }); })];
328
+ case 1: return [2 /*return*/, _a.sent()];
329
+ case 2:
330
+ if (!record.id) {
331
+ throw new Error("updatePreserving must be called with a record with an id property");
332
+ }
333
+ applicable = preserve.filter(function (p) { return record[p.columnPropertyName] !== undefined; });
334
+ if (!(applicable.length === 0)) return [3 /*break*/, 4];
335
+ return [4 /*yield*/, this.update(table, record)];
336
+ case 3: return [2 /*return*/, _a.sent()];
337
+ case 4:
338
+ qb = new QueryBuilderFactory_1.QueryBuilderFactory().getQueryBuilder(table);
339
+ return [4 /*yield*/, this.addColumnQueries(table, qb, 'write')];
340
+ case 5:
341
+ _a.sent();
342
+ qb.select({ fields: __spreadArray(['id'], applicable.map(function (p) { return p.columnPropertyName; }), true) });
343
+ qb.condition({ field: 'id', operator: '=', value: record.id });
344
+ return [4 /*yield*/, this._query(table, qb)];
345
+ case 6:
346
+ rows = _a.sent();
347
+ if (rows.length === 0) {
348
+ return [2 /*return*/, 0];
349
+ }
350
+ recordCopy = Object.assign({}, record);
351
+ for (_i = 0, applicable_1 = applicable; _i < applicable_1.length; _i++) {
352
+ p = applicable_1[_i];
353
+ recordCopy[p.columnPropertyName] = (0, UpdatePreserving_1.overlayPreservedPaths)(rows[0][p.columnPropertyName], recordCopy[p.columnPropertyName], p.paths, p.whenType);
354
+ }
355
+ return [4 /*yield*/, this.update(table, recordCopy)];
356
+ case 7: return [2 /*return*/, _a.sent()];
357
+ }
358
+ });
359
+ });
360
+ };
239
361
  Db.prototype.delete = function (table, query) {
240
362
  return __awaiter(this, void 0, void 0, function () {
241
363
  var qb, recordsToDelete, recordsToDeleteIds, deleteQb, generateDelete, recordDeleteCount;
@@ -268,7 +390,7 @@ var Db = /** @class */ (function () {
268
390
  return [4 /*yield*/, this.tableWatcherRunner.runBeforeDeleteTableWatchers(table, recordsToDelete, qb, deleteQb)];
269
391
  case 4:
270
392
  _a.sent();
271
- return [4 /*yield*/, this.dbDriver.runDml(generateDelete, this.currentTransaction)];
393
+ return [4 /*yield*/, this.dbDriver.runDml(generateDelete, this.transactionForDriver())];
272
394
  case 5:
273
395
  recordDeleteCount = _a.sent();
274
396
  return [4 /*yield*/, this.runCascadeDeletions(table, recordsToDelete)];
@@ -581,7 +703,7 @@ var Db = /** @class */ (function () {
581
703
  generateQuery = function (config) {
582
704
  return qb.toSql(_this.statementConfigFactory.getStatementConfig(config));
583
705
  };
584
- return [4 /*yield*/, this.dbDriver.runQuery(generateQuery, this.currentTransaction)];
706
+ return [4 /*yield*/, this.dbDriver.runQuery(generateQuery, this.transactionForDriver())];
585
707
  case 1:
586
708
  serializedRecords = _a.sent();
587
709
  recordSerializer = new Record_1.RecordSerializer(table);
@@ -660,7 +782,7 @@ var Db = /** @class */ (function () {
660
782
  generateQuery = function (config) {
661
783
  return qb.toSql(_this.statementConfigFactory.getStatementConfig(config));
662
784
  };
663
- return [4 /*yield*/, this.dbDriver.runQuery(generateQuery, this.currentTransaction)];
785
+ return [4 /*yield*/, this.dbDriver.runQuery(generateQuery, this.transactionForDriver())];
664
786
  case 2:
665
787
  result = _a.sent();
666
788
  return [2 /*return*/, result[0]['count']];
@@ -736,13 +858,19 @@ var Db = /** @class */ (function () {
736
858
  /**
737
859
  * Run a transaction.
738
860
  *
739
- * Use this db instance for any operation you want to include in the transaction.
740
- *
741
- * Note: This method uses Db instance state. Usually it is best to create a new instance
742
- * of Db to run a transaction.
861
+ * Db instances are STATELESS with respect to transactions: every operation resolves the
862
+ * ambient transaction (AsyncLocalStorage) at call time, so any Db instance used inside the
863
+ * transaction body whenever it was constructed rides the transaction. There is no way
864
+ * to issue an operation outside a transaction from inside its body, and no second session
865
+ * is ever acquired inside one (the historical pool-wedge class is unrepresentable —
866
+ * plans/DB_PERF_PLAN.md P2, in the consumer repo).
743
867
  *
744
868
  * Note: Nested transactions are not supported; will throw.
745
869
  *
870
+ * Note: work spawned inside the body but NOT awaited by it escapes the transaction's
871
+ * lifetime while still holding its context — such work fails loudly on its next db
872
+ * operation (see TransactionContextData.ended). Await everything inside the body, or run
873
+ * it outside the transaction.
746
874
  *
747
875
  * Example:
748
876
  *
@@ -759,46 +887,130 @@ var Db = /** @class */ (function () {
759
887
  */
760
888
  Db.prototype.runTransaction = function (fn) {
761
889
  return __awaiter(this, void 0, void 0, function () {
890
+ var postCommitHooks, result, _i, postCommitHooks_1, postCommitHook, error_1;
762
891
  var _this = this;
763
892
  return __generator(this, function (_a) {
764
893
  switch (_a.label) {
765
894
  case 0:
766
- if (this.currentTransaction) {
767
- throw new Error("Nested transactions are not supported. A transaction is already running on this Db instance.");
895
+ if (this.transactionContextFactory.getTransactionContext().currentTransaction) {
896
+ throw new Error("Nested transactions are not supported. A transaction is already running in this context.");
768
897
  }
898
+ postCommitHooks = [];
769
899
  return [4 /*yield*/, this.dbDriver.runTransaction(function (transaction) { return __awaiter(_this, void 0, void 0, function () {
770
- var _this = this;
900
+ var contextData;
771
901
  return __generator(this, function (_a) {
772
902
  switch (_a.label) {
773
903
  case 0:
774
- this.currentTransaction = transaction;
904
+ // A fresh ambient store per attempt: the transaction, the attempt's post-commit hook
905
+ // queue (shared by every Db instance created inside — see runAfterCommit), and the
906
+ // ended-tombstone below.
907
+ postCommitHooks = [];
908
+ contextData = { currentTransaction: transaction, postCommitHooks: postCommitHooks };
775
909
  _a.label = 1;
776
910
  case 1:
777
911
  _a.trys.push([1, , 3, 4]);
778
- return [4 /*yield*/, this.transactionContextFactory.runInContext(transaction, function () { return __awaiter(_this, void 0, void 0, function () {
779
- var result;
780
- return __generator(this, function (_a) {
781
- switch (_a.label) {
782
- case 0: return [4 /*yield*/, fn()];
783
- case 1:
784
- result = _a.sent();
785
- return [2 /*return*/, result];
786
- }
787
- });
788
- }); })];
912
+ return [4 /*yield*/, this.transactionContextFactory.runInContext(contextData, fn)];
789
913
  case 2: return [2 /*return*/, _a.sent()];
790
914
  case 3:
791
- this.currentTransaction = undefined;
915
+ // Tombstone the store: detached work spawned inside the body still holds it by
916
+ // reference — the flag turns its next db operation into a loud error instead of a
917
+ // silent op on a finished transaction (see transactionForDriver).
918
+ contextData.ended = true;
792
919
  return [7 /*endfinally*/];
793
920
  case 4: return [2 /*return*/];
794
921
  }
795
922
  });
796
923
  }); })];
797
- case 1: return [2 /*return*/, _a.sent()];
924
+ case 1:
925
+ result = _a.sent();
926
+ _i = 0, postCommitHooks_1 = postCommitHooks;
927
+ _a.label = 2;
928
+ case 2:
929
+ if (!(_i < postCommitHooks_1.length)) return [3 /*break*/, 7];
930
+ postCommitHook = postCommitHooks_1[_i];
931
+ _a.label = 3;
932
+ case 3:
933
+ _a.trys.push([3, 5, , 6]);
934
+ return [4 /*yield*/, postCommitHook()];
935
+ case 4:
936
+ _a.sent();
937
+ return [3 /*break*/, 6];
938
+ case 5:
939
+ error_1 = _a.sent();
940
+ this.logger.error({ message: "Post-commit hook failed", error: error_1 });
941
+ return [3 /*break*/, 6];
942
+ case 6:
943
+ _i++;
944
+ return [3 /*break*/, 2];
945
+ case 7: return [2 /*return*/, result];
946
+ }
947
+ });
948
+ });
949
+ };
950
+ /**
951
+ * Run `hook` once the write unit currently executing is durably committed.
952
+ *
953
+ * - Inside a `runTransaction` scope: the hook is queued on the ambient transaction context and
954
+ * runs after the transaction COMMITS. Hooks never run on rollback.
955
+ * - Outside a transaction: every DML statement auto-commits when it resolves, so the hook runs
956
+ * (awaited) immediately. Callers are responsible for invoking this only from points where
957
+ * the triggering statement has already executed — e.g. `after*` table watchers, never
958
+ * `before*` ones.
959
+ *
960
+ * For side effects that must observe committed truth — e.g. socket notifications that trigger
961
+ * client refetches: a pre-commit emission lets a client refetch read pre-commit rows and
962
+ * "resurrect" state the transaction is deleting.
963
+ */
964
+ Db.prototype.runAfterCommit = function (hook) {
965
+ return __awaiter(this, void 0, void 0, function () {
966
+ var transactionContext;
967
+ return __generator(this, function (_a) {
968
+ switch (_a.label) {
969
+ case 0:
970
+ transactionContext = this.transactionContextFactory.getTransactionContext();
971
+ if (transactionContext.currentTransaction) {
972
+ if (!transactionContext.postCommitHooks) {
973
+ // runTransaction is the only opener of transaction scopes and always seeds the queue —
974
+ // an active transaction without one means a second scope owner appeared. Fail loudly
975
+ // rather than run the hook at (wrong) pre-commit time.
976
+ throw new Error("Active transaction context has no post-commit hook queue. Transaction scopes must be opened via Db.runTransaction.");
977
+ }
978
+ transactionContext.postCommitHooks.push(hook);
979
+ return [2 /*return*/];
980
+ }
981
+ return [4 /*yield*/, hook()];
982
+ case 1:
983
+ _a.sent();
984
+ return [2 /*return*/];
798
985
  }
799
986
  });
800
987
  });
801
988
  };
989
+ /**
990
+ * A fresh instance for self-wrapping RMW verbs (`updateArrayMembership`, `updatePreserving`)
991
+ * in a transaction. `runTransaction` carries the open transaction as INSTANCE state, and these
992
+ * verbs also serve the `DbService` RPC path, where one long-lived Db instance handles
993
+ * concurrent requests — self-wrapping on `this` would leak one request's transaction into
994
+ * another's ops (or spuriously reject it as a nested transaction). Same driver/tables/system
995
+ * mode; only the transaction state is isolated.
996
+ */
997
+ Db.prototype.newSelfWrapDb = function () {
998
+ return new Db(this.dbDriver, this.getTable, this.transactionContextFactory, this.runAsSystem);
999
+ };
1000
+ /**
1001
+ * The transaction every driver call rides, resolved from the ambient context AT CALL TIME
1002
+ * (statelessness is the safety property: operations inside a transaction body always ride
1003
+ * it; operations outside always use the pool). The one remaining failure shape — work that
1004
+ * escaped a finished transaction's body while holding its context — throws here by name;
1005
+ * handing the driver an ended transaction would fail anyway, with a far worse error.
1006
+ */
1007
+ Db.prototype.transactionForDriver = function () {
1008
+ var context = this.transactionContextFactory.getTransactionContext();
1009
+ if (context.ended) {
1010
+ throw new Error("Db operation issued in the context of a transaction that already ended: work spawned inside a runTransaction body (and not awaited by it) survived the transaction. Await the work inside the body, or run it outside the transaction.");
1011
+ }
1012
+ return context.currentTransaction;
1013
+ };
802
1014
  // Utility: simple chunker
803
1015
  Db.prototype.chunk = function (arr, size) {
804
1016
  if (size <= 0) {