@adobe/data 0.9.61 → 0.9.63

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 (97) hide show
  1. package/dist/ecs/archetype/archetype.d.ts +9 -2
  2. package/dist/ecs/archetype/archetype.js.map +1 -1
  3. package/dist/ecs/archetype/create-archetype.js +5 -2
  4. package/dist/ecs/archetype/create-archetype.js.map +1 -1
  5. package/dist/ecs/database/concurrency/concurrency-strategy.d.ts +86 -0
  6. package/dist/ecs/database/concurrency/concurrency-strategy.js +3 -0
  7. package/dist/ecs/database/concurrency/concurrency-strategy.js.map +1 -0
  8. package/dist/ecs/database/concurrency/immediate-concurrency.d.ts +22 -0
  9. package/dist/ecs/database/concurrency/immediate-concurrency.js +74 -0
  10. package/dist/ecs/database/concurrency/immediate-concurrency.js.map +1 -0
  11. package/dist/ecs/database/concurrency/index.d.ts +4 -0
  12. package/dist/ecs/database/concurrency/index.js +5 -0
  13. package/dist/ecs/database/concurrency/index.js.map +1 -0
  14. package/dist/ecs/database/concurrency/rebase-replay-concurrency.d.ts +20 -0
  15. package/dist/ecs/database/concurrency/rebase-replay-concurrency.js +34 -0
  16. package/dist/ecs/database/concurrency/rebase-replay-concurrency.js.map +1 -0
  17. package/dist/ecs/database/concurrency/roll-forward-concurrency.d.ts +33 -0
  18. package/dist/ecs/database/concurrency/roll-forward-concurrency.js +115 -0
  19. package/dist/ecs/database/concurrency/roll-forward-concurrency.js.map +1 -0
  20. package/dist/ecs/database/concurrency/roll-forward-concurrency.test.d.ts +1 -0
  21. package/dist/ecs/database/concurrency/roll-forward-concurrency.test.js +136 -0
  22. package/dist/ecs/database/concurrency/roll-forward-concurrency.test.js.map +1 -0
  23. package/dist/ecs/database/create-plugin.d.ts +1 -1
  24. package/dist/ecs/database/database.d.ts +29 -7
  25. package/dist/ecs/database/database.index.performance.test.js +5 -5
  26. package/dist/ecs/database/database.index.performance.test.js.map +1 -1
  27. package/dist/ecs/database/database.index.test.js +556 -51
  28. package/dist/ecs/database/database.index.test.js.map +1 -1
  29. package/dist/ecs/database/database.index.type-test.js +34 -10
  30. package/dist/ecs/database/database.index.type-test.js.map +1 -1
  31. package/dist/ecs/database/database.js.map +1 -1
  32. package/dist/ecs/database/database.reset.test.js +2 -1
  33. package/dist/ecs/database/database.reset.test.js.map +1 -1
  34. package/dist/ecs/database/index-registry/create-index-registry.d.ts +38 -9
  35. package/dist/ecs/database/index-registry/create-index-registry.js +67 -20
  36. package/dist/ecs/database/index-registry/create-index-registry.js.map +1 -1
  37. package/dist/ecs/database/index-registry/create-index.d.ts +10 -2
  38. package/dist/ecs/database/index-registry/create-index.js +44 -47
  39. package/dist/ecs/database/index-registry/create-index.js.map +1 -1
  40. package/dist/ecs/database/index.d.ts +1 -0
  41. package/dist/ecs/database/index.js +1 -0
  42. package/dist/ecs/database/index.js.map +1 -1
  43. package/dist/ecs/database/observe-index-entities.d.ts +26 -0
  44. package/dist/ecs/database/observe-index-entities.js +66 -0
  45. package/dist/ecs/database/observe-index-entities.js.map +1 -0
  46. package/dist/ecs/database/observed/create-observed-database.js +1 -1
  47. package/dist/ecs/database/observed/create-observed-database.js.map +1 -1
  48. package/dist/ecs/database/observed/observed-database.d.ts +1 -1
  49. package/dist/ecs/database/public/create-database.d.ts +12 -27
  50. package/dist/ecs/database/public/create-database.js +131 -37
  51. package/dist/ecs/database/public/create-database.js.map +1 -1
  52. package/dist/ecs/database/public/create-database.test.js +8 -7
  53. package/dist/ecs/database/public/create-database.test.js.map +1 -1
  54. package/dist/ecs/database/public/create-transaction-dispatcher.d.ts +4 -2
  55. package/dist/ecs/database/public/create-transaction-dispatcher.js +2 -8
  56. package/dist/ecs/database/public/create-transaction-dispatcher.js.map +1 -1
  57. package/dist/ecs/database/reconciling/create-rebase-replay-applier.d.ts +26 -0
  58. package/dist/ecs/database/reconciling/create-rebase-replay-applier.js +121 -0
  59. package/dist/ecs/database/reconciling/create-rebase-replay-applier.js.map +1 -0
  60. package/dist/ecs/database/reconciling/create-reconciling-database.d.ts +11 -0
  61. package/dist/ecs/database/reconciling/create-reconciling-database.js +28 -137
  62. package/dist/ecs/database/reconciling/create-reconciling-database.js.map +1 -1
  63. package/dist/ecs/database/transactional-store/create-transactional-store.js.map +1 -1
  64. package/dist/ecs/database/transactional-store/transactional-store.d.ts +1 -1
  65. package/dist/ecs/entity-location-table/create-entity-location-table.js +2 -2
  66. package/dist/ecs/entity-location-table/create-entity-location-table.js.map +1 -1
  67. package/dist/ecs/entity-location-table/entity-location-table.d.ts +7 -1
  68. package/dist/ecs/store/core/core.d.ts +7 -1
  69. package/dist/ecs/store/core/create-core.js +3 -3
  70. package/dist/ecs/store/core/create-core.js.map +1 -1
  71. package/dist/ecs/store/core/select-entities.js +6 -3
  72. package/dist/ecs/store/core/select-entities.js.map +1 -1
  73. package/dist/ecs/store/index-types.d.ts +53 -17
  74. package/dist/ecs/store/public/create-store.js +32 -6
  75. package/dist/ecs/store/public/create-store.js.map +1 -1
  76. package/dist/ecs/store/public/create-store.test.js +25 -0
  77. package/dist/ecs/store/public/create-store.test.js.map +1 -1
  78. package/dist/ecs/store/store.d.ts +10 -3
  79. package/dist/ecs/store/store.js.map +1 -1
  80. package/dist/functions/compare.d.ts +13 -0
  81. package/dist/functions/compare.js +15 -0
  82. package/dist/functions/compare.js.map +1 -0
  83. package/dist/functions/compare.test.d.ts +1 -0
  84. package/dist/functions/compare.test.js +24 -0
  85. package/dist/functions/compare.test.js.map +1 -0
  86. package/dist/functions/index.d.ts +1 -0
  87. package/dist/functions/index.js +1 -0
  88. package/dist/functions/index.js.map +1 -1
  89. package/dist/test-setup.js +2 -2
  90. package/dist/test-setup.js.map +1 -1
  91. package/dist/tsconfig.tsbuildinfo +1 -1
  92. package/package.json +3 -1
  93. package/references/data-lit/package.json +1 -1
  94. package/references/data-lit-tictactoe/package.json +1 -1
  95. package/references/data-react/package.json +1 -1
  96. package/references/data-react-hello/package.json +1 -1
  97. package/references/data-react-pixie/package.json +1 -1
@@ -20,12 +20,19 @@ interface BaseArchetype {
20
20
  }
21
21
  export interface ReadonlyArchetype<C extends RequiredComponents> extends BaseArchetype, ReadonlyTable<C> {
22
22
  readonly components: ComponentSet<StringKeyof<C>>;
23
- toData: () => unknown;
23
+ /**
24
+ * Serialize the archetype. When `copy` is true each column buffer is
25
+ * detached (`.copy()`) so the snapshot survives later mutation of the live
26
+ * archetype; otherwise the snapshot references the live column buffers
27
+ * (faster, but only valid until the next mutation).
28
+ */
29
+ toData: (copy?: boolean) => unknown;
24
30
  }
25
31
  export interface Archetype<C extends RequiredComponents = RequiredComponents> extends BaseArchetype, Table<C> {
26
32
  readonly components: ComponentSet<StringKeyof<C>>;
27
33
  insert: <T extends EntityInsertValues<C>>(rowData: Exact<EntityInsertValues<C>, T>) => Entity;
28
- toData: () => unknown;
34
+ /** See {@link ReadonlyArchetype.toData}. */
35
+ toData: (copy?: boolean) => unknown;
29
36
  fromData: (data: unknown) => void;
30
37
  }
31
38
  export type FromArchetype<T> = T extends ReadonlyArchetype<infer C> ? {
@@ -1 +1 @@
1
- {"version":3,"file":"archetype.js","sourceRoot":"","sources":["../../../src/ecs/archetype/archetype.ts"],"names":[],"mappings":"AA+CA,gDAAgD;AAChD,CAAC;IAKG,2BAA2B;IAC3B,MAAM,eAAe,GAAG,CAAC,IAAmB,EAAE,EAAE;QAC5C,MAAM,SAAS,GAAoB,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAChF,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB;IAC7C,CAAC,CAAC;IAEF,2CAA2C;IAC3C,MAAM,iBAAiB,GAAG,CAAC,IAAmB,EAAE,EAAE;QAC9C,MAAM,WAAW,GAAoB,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAChG,oDAAoD;QACpD,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC7B,CAAC,CAAC;AACN,CAAC"}
1
+ {"version":3,"file":"archetype.js","sourceRoot":"","sources":["../../../src/ecs/archetype/archetype.ts"],"names":[],"mappings":"AAsDA,gDAAgD;AAChD,CAAC;IAKG,2BAA2B;IAC3B,MAAM,eAAe,GAAG,CAAC,IAAmB,EAAE,EAAE;QAC5C,MAAM,SAAS,GAAoB,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAChF,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB;IAC7C,CAAC,CAAC;IAEF,2CAA2C;IAC3C,MAAM,iBAAiB,GAAG,CAAC,IAAmB,EAAE,EAAE;QAC9C,MAAM,WAAW,GAAoB,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAChG,oDAAoD;QACpD,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC7B,CAAC,CAAC;AACN,CAAC"}
@@ -134,8 +134,11 @@ export const createArchetype = (components, id, entityLocationTable) => {
134
134
  ...table,
135
135
  components: componentSet,
136
136
  insert: createEntity,
137
- toData: () => ({
138
- columns: archetype.columns,
137
+ toData: (copy = false) => ({
138
+ columns: copy
139
+ ? Object.fromEntries(Object.entries(archetype.columns)
140
+ .map(([name, column]) => [name, column.copy()]))
141
+ : archetype.columns,
139
142
  rowCount: archetype.rowCount,
140
143
  rowCapacity: archetype.rowCapacity,
141
144
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"create-archetype.js","sourceRoot":"","sources":["../../../src/ecs/archetype/create-archetype.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"create-archetype.js","sourceRoot":"","sources":["../../../src/ecs/archetype/create-archetype.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ import type { Entity } from "../../entity/entity.js";
2
+ import type { TransactionContext, TransactionResult } from "../transactional-store/index.js";
3
+ import type { TransactionEnvelope } from "../reconciling/reconciling-database.js";
4
+ /**
5
+ * The interface every concurrency strategy must satisfy.
6
+ *
7
+ * A strategy encapsulates all decisions about how locally-initiated
8
+ * transactions are applied and how inbound envelopes (from a sync service
9
+ * or another local source) are reconciled against the current store state.
10
+ *
11
+ * Two built-in strategies are provided:
12
+ *
13
+ * - `createImmediateConcurrency()` — commits apply immediately, no
14
+ * rollback queue. Use this as the base database for external wrappers
15
+ * (e.g. a collaborative-editing layer that manages its own replay buffer).
16
+ *
17
+ * - `createRebaseReplayConcurrency(userId)` — commits apply locally as
18
+ * transients and wait for the sync server's echoed committed envelope to
19
+ * promote them. Full rollback-and-replay queue, cross-peer id-determinism.
20
+ */
21
+ export interface ConcurrencyStrategy {
22
+ /**
23
+ * When true, the dispatcher applies "commit" intents as transients
24
+ * (negative time) and waits for a server echo to promote them.
25
+ * When false, commits apply immediately with positive time.
26
+ */
27
+ readonly deferredCommit: boolean;
28
+ /**
29
+ * Peer/session identifier stamped on every outbound envelope.
30
+ * Required for strategies that participate in a multi-peer sync protocol.
31
+ */
32
+ readonly userId?: number | string;
33
+ /**
34
+ * Process a transaction envelope. Called by the dispatcher for each
35
+ * locally-initiated transaction and by sync services for inbound commits.
36
+ * Does NOT fire `observe.envelopes` (that is the dispatcher's job).
37
+ */
38
+ readonly apply: (envelope: TransactionEnvelope) => TransactionResult<unknown> | undefined;
39
+ /**
40
+ * Cancel a pending transient by compound (userId, id) key.
41
+ * No-op for strategies that have no transient queue.
42
+ */
43
+ readonly cancel: (id: number, userId?: number | string) => void;
44
+ /**
45
+ * Called by `db.reset()` before the store is cleared.
46
+ * Strategies with a transient queue should clear it here.
47
+ */
48
+ readonly onReset: () => void;
49
+ /**
50
+ * Called immediately before `db.toData()` serializes the store.
51
+ * Strategies with a transient queue should roll back transients here so
52
+ * the snapshot contains only committed state.
53
+ *
54
+ * When this hook (together with `onAfterToData`) is present, `db.toData()`
55
+ * serializes a detached copy of the store between the two hooks, so the
56
+ * `onAfterToData` replay cannot corrupt the returned snapshot. See
57
+ * `Database.toData()`.
58
+ */
59
+ readonly onBeforeToData?: () => void;
60
+ /**
61
+ * Called immediately after `db.toData()` serializes the store.
62
+ * Strategies that roll back in `onBeforeToData` should replay here to
63
+ * restore the in-progress transient state.
64
+ */
65
+ readonly onAfterToData?: () => void;
66
+ /**
67
+ * Called immediately after `db.fromData()` loads a snapshot into the
68
+ * store. Strategies with a transient queue should replay pending
69
+ * transients here so in-progress work is visible after a data load.
70
+ */
71
+ readonly onAfterFromData?: () => void;
72
+ }
73
+ /**
74
+ * Factory that creates a `ConcurrencyStrategy` bound to the given execute
75
+ * and transaction-lookup functions. Called once per database at construction.
76
+ *
77
+ * `execute` is the observed database's execute — it applies a transaction,
78
+ * fires observers, and returns the mutation record.
79
+ *
80
+ * `getTransaction` looks up a registered transaction function by name,
81
+ * returning undefined when the name has not yet been registered via extend.
82
+ */
83
+ export type ConcurrencyStrategyFactory = (execute: (fn: (ctx: TransactionContext<any, any, any>) => void | Entity, options?: {
84
+ transient?: boolean;
85
+ userId?: number | string;
86
+ }) => TransactionResult<unknown>, getTransaction: (name: string) => ((ctx: TransactionContext<any, any, any>, args: unknown) => void | Entity) | undefined) => ConcurrencyStrategy;
@@ -0,0 +1,3 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ export {};
3
+ //# sourceMappingURL=concurrency-strategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency-strategy.js","sourceRoot":"","sources":["../../../../src/ecs/database/concurrency/concurrency-strategy.ts"],"names":[],"mappings":"AAAA,uDAAuD"}
@@ -0,0 +1,22 @@
1
+ import type { ConcurrencyStrategyFactory } from "./concurrency-strategy.js";
2
+ /**
3
+ * Lighter-weight concurrency strategy that applies commits and transients
4
+ * immediately with no global rollback-and-replay cycle.
5
+ *
6
+ * Key differences from {@link createRebaseReplayConcurrency}:
7
+ *
8
+ * - No deferred-commit mode — `db.transactions.X(args)` commits
9
+ * synchronously with positive time.
10
+ * - No global rebase: when an inbound committed envelope arrives, it is
11
+ * applied directly without rolling back other pending transients. This
12
+ * means entity-id allocation is NOT deterministic across peers under
13
+ * concurrent edits — use this only when there is a single writer or
14
+ * when an external layer manages replay (e.g. a collaborative-editing
15
+ * wrapper).
16
+ * - Async generator transients are still supported: each yield applies
17
+ * the intermediate state immediately and rolls it back when the next
18
+ * yield or the final commit arrives. Cancel rolls back the last
19
+ * applied transient for that transaction. Transients from different
20
+ * concurrent transactions do NOT interact (no cross-transaction rebase).
21
+ */
22
+ export declare const createImmediateConcurrency: () => ConcurrencyStrategyFactory;
@@ -0,0 +1,74 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { applyOperations } from "../transactional-store/apply-operations.js";
3
+ /**
4
+ * Lighter-weight concurrency strategy that applies commits and transients
5
+ * immediately with no global rollback-and-replay cycle.
6
+ *
7
+ * Key differences from {@link createRebaseReplayConcurrency}:
8
+ *
9
+ * - No deferred-commit mode — `db.transactions.X(args)` commits
10
+ * synchronously with positive time.
11
+ * - No global rebase: when an inbound committed envelope arrives, it is
12
+ * applied directly without rolling back other pending transients. This
13
+ * means entity-id allocation is NOT deterministic across peers under
14
+ * concurrent edits — use this only when there is a single writer or
15
+ * when an external layer manages replay (e.g. a collaborative-editing
16
+ * wrapper).
17
+ * - Async generator transients are still supported: each yield applies
18
+ * the intermediate state immediately and rolls it back when the next
19
+ * yield or the final commit arrives. Cancel rolls back the last
20
+ * applied transient for that transaction. Transients from different
21
+ * concurrent transactions do NOT interact (no cross-transaction rebase).
22
+ */
23
+ export const createImmediateConcurrency = () => (execute, getTransaction) => {
24
+ // Per-transaction store of the last-applied transient result, keyed
25
+ // by compound `"userId:id"`. Used to roll back a transient when a
26
+ // newer transient, commit, or cancel arrives for the same transaction.
27
+ const pending = new Map();
28
+ const key = (id, userId) => `${userId}:${id}`;
29
+ const rollbackPending = (id, userId) => {
30
+ const k = key(id, userId);
31
+ const prior = pending.get(k);
32
+ if (prior) {
33
+ execute(t => applyOperations(t, prior.undo), { transient: true, userId: undefined });
34
+ pending.delete(k);
35
+ }
36
+ };
37
+ return {
38
+ deferredCommit: false,
39
+ apply(envelope) {
40
+ const { id, userId, time, args } = envelope;
41
+ if (time === 0) {
42
+ rollbackPending(id, userId);
43
+ return undefined;
44
+ }
45
+ if (time < 0) {
46
+ // Roll back the previous transient for this transaction (if any),
47
+ // then apply the new one. No interaction with other transactions.
48
+ rollbackPending(id, userId);
49
+ const fn = getTransaction(envelope.name);
50
+ if (!fn)
51
+ throw new Error(`Unknown transaction: ${envelope.name}`);
52
+ const result = execute(t => fn(t, args), { transient: true, userId });
53
+ const isNoOp = result.redo.length === 0 && result.undo.length === 0;
54
+ if (!isNoOp)
55
+ pending.set(key(id, userId), result);
56
+ return result;
57
+ }
58
+ // Positive time: commit. Roll back any pending transient for this
59
+ // transaction, then apply the committed state.
60
+ rollbackPending(id, userId);
61
+ const fn = getTransaction(envelope.name);
62
+ if (!fn)
63
+ throw new Error(`Unknown transaction: ${envelope.name}`);
64
+ return execute(t => fn(t, args), { transient: false, userId });
65
+ },
66
+ cancel(id, userId) {
67
+ rollbackPending(id, userId);
68
+ },
69
+ onReset() {
70
+ pending.clear();
71
+ },
72
+ };
73
+ };
74
+ //# sourceMappingURL=immediate-concurrency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"immediate-concurrency.js","sourceRoot":"","sources":["../../../../src/ecs/database/concurrency/immediate-concurrency.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export type { ConcurrencyStrategy, ConcurrencyStrategyFactory } from "./concurrency-strategy.js";
2
+ export { createImmediateConcurrency } from "./immediate-concurrency.js";
3
+ export { createRebaseReplayConcurrency } from "./rebase-replay-concurrency.js";
4
+ export { createRollForwardConcurrency } from "./roll-forward-concurrency.js";
@@ -0,0 +1,5 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ export { createImmediateConcurrency } from "./immediate-concurrency.js";
3
+ export { createRebaseReplayConcurrency } from "./rebase-replay-concurrency.js";
4
+ export { createRollForwardConcurrency } from "./roll-forward-concurrency.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/ecs/database/concurrency/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAGvD,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,6BAA6B,EAAE,MAAM,gCAAgC,CAAC;AAC/E,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { ConcurrencyStrategyFactory } from "./concurrency-strategy.js";
2
+ /**
3
+ * Concurrency strategy that implements the optimistic rebase-replay protocol
4
+ * required for multi-peer synchronisation.
5
+ *
6
+ * Behaviour:
7
+ * - Local "commit" intents are applied as transients (negative time) and
8
+ * wait for the sync server's echoed committed envelope to promote them
9
+ * via rollback-and-replay. This is required for cross-peer entity-id
10
+ * determinism under concurrent edits.
11
+ * - Every outbound envelope is stamped with `userId` so the reconciler's
12
+ * compound `(userId, id)` key keeps concurrent peers' id counters from
13
+ * colliding.
14
+ * - `onBeforeToData` / `onAfterToData` roll back and replay the transient
15
+ * queue around serialisation so snapshots always contain only committed state.
16
+ *
17
+ * @param userId Stable peer/session identifier unique across all peers
18
+ * sharing the same sync server.
19
+ */
20
+ export declare const createRebaseReplayConcurrency: (userId: number | string) => ConcurrencyStrategyFactory;
@@ -0,0 +1,34 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { createRebaseReplayApplier } from "../reconciling/create-rebase-replay-applier.js";
3
+ /**
4
+ * Concurrency strategy that implements the optimistic rebase-replay protocol
5
+ * required for multi-peer synchronisation.
6
+ *
7
+ * Behaviour:
8
+ * - Local "commit" intents are applied as transients (negative time) and
9
+ * wait for the sync server's echoed committed envelope to promote them
10
+ * via rollback-and-replay. This is required for cross-peer entity-id
11
+ * determinism under concurrent edits.
12
+ * - Every outbound envelope is stamped with `userId` so the reconciler's
13
+ * compound `(userId, id)` key keeps concurrent peers' id counters from
14
+ * colliding.
15
+ * - `onBeforeToData` / `onAfterToData` roll back and replay the transient
16
+ * queue around serialisation so snapshots always contain only committed state.
17
+ *
18
+ * @param userId Stable peer/session identifier unique across all peers
19
+ * sharing the same sync server.
20
+ */
21
+ export const createRebaseReplayConcurrency = (userId) => (...args) => {
22
+ const applier = createRebaseReplayApplier(...args);
23
+ return {
24
+ deferredCommit: true,
25
+ userId,
26
+ apply: applier.apply,
27
+ cancel: applier.cancel,
28
+ onReset: applier.onReset,
29
+ onBeforeToData: applier.rollbackAllTransients,
30
+ onAfterToData: applier.replayAllTransients,
31
+ onAfterFromData: applier.replayAllTransients,
32
+ };
33
+ };
34
+ //# sourceMappingURL=rebase-replay-concurrency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rebase-replay-concurrency.js","sourceRoot":"","sources":["../../../../src/ecs/database/concurrency/rebase-replay-concurrency.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,gDAAgD,CAAC;AAG3F;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,MAAuB,EAA8B,EAAE,CACjG,CAAC,GAAG,IAAI,EAAuB,EAAE;IAC7B,MAAM,OAAO,GAAG,yBAAyB,CAAC,GAAG,IAAI,CAAC,CAAC;IACnD,OAAO;QACH,cAAc,EAAE,IAAI;QACpB,MAAM;QACN,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,cAAc,EAAE,OAAO,CAAC,qBAAqB;QAC7C,aAAa,EAAE,OAAO,CAAC,mBAAmB;QAC1C,eAAe,EAAE,OAAO,CAAC,mBAAmB;KAC/C,CAAC;AACN,CAAC,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { ConcurrencyStrategyFactory } from "./concurrency-strategy.js";
2
+ /**
3
+ * Roll-forward reconciliation: a client-side optimistic concurrency model for
4
+ * collaborative / multi-peer editing. Provided to prove the
5
+ * {@link ConcurrencyStrategy} seam can host a fundamentally different
6
+ * reconciliation engine than {@link createRebaseReplayConcurrency} — it is
7
+ * not currently wired into a real transport.
8
+ *
9
+ * The defining difference from {@link createRebaseReplayConcurrency}:
10
+ *
11
+ * - **Roll-forward, not re-execute.** On rebase, each pending local edit is
12
+ * replayed by re-applying its captured post-image tuples, *not* by
13
+ * re-running its transaction function. A transaction that reads current
14
+ * state and derives a new value (e.g. "add 10 to whatever is there")
15
+ * therefore preserves the value it originally produced, regardless of how
16
+ * the confirmed base shifted underneath it. Re-execution would instead
17
+ * recompute against the new base.
18
+ *
19
+ * The rebase sequence on a confirmed (positive-time) delta:
20
+ * 1. roll back all pending entries newest-to-oldest (apply their undo),
21
+ * 2. apply the confirmed delta,
22
+ * 3. replay the remaining pending entries oldest-to-newest by re-applying
23
+ * their captured redo tuples, recapturing fresh undo against the new base.
24
+ *
25
+ * Local transients carry monotonically increasing |time| (they are appended
26
+ * as the user edits), so a newly-captured entry always sorts last; the buffer
27
+ * is kept in arrival order and a new entry is materialized on top of the
28
+ * replayed survivors.
29
+ *
30
+ * @param userId Stable peer/session identifier stamped on every outbound
31
+ * envelope, unique across all peers sharing the same sync server.
32
+ */
33
+ export declare const createRollForwardConcurrency: (userId: number | string) => ConcurrencyStrategyFactory;
@@ -0,0 +1,115 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { applyOperations } from "../transactional-store/apply-operations.js";
3
+ /**
4
+ * Roll-forward reconciliation: a client-side optimistic concurrency model for
5
+ * collaborative / multi-peer editing. Provided to prove the
6
+ * {@link ConcurrencyStrategy} seam can host a fundamentally different
7
+ * reconciliation engine than {@link createRebaseReplayConcurrency} — it is
8
+ * not currently wired into a real transport.
9
+ *
10
+ * The defining difference from {@link createRebaseReplayConcurrency}:
11
+ *
12
+ * - **Roll-forward, not re-execute.** On rebase, each pending local edit is
13
+ * replayed by re-applying its captured post-image tuples, *not* by
14
+ * re-running its transaction function. A transaction that reads current
15
+ * state and derives a new value (e.g. "add 10 to whatever is there")
16
+ * therefore preserves the value it originally produced, regardless of how
17
+ * the confirmed base shifted underneath it. Re-execution would instead
18
+ * recompute against the new base.
19
+ *
20
+ * The rebase sequence on a confirmed (positive-time) delta:
21
+ * 1. roll back all pending entries newest-to-oldest (apply their undo),
22
+ * 2. apply the confirmed delta,
23
+ * 3. replay the remaining pending entries oldest-to-newest by re-applying
24
+ * their captured redo tuples, recapturing fresh undo against the new base.
25
+ *
26
+ * Local transients carry monotonically increasing |time| (they are appended
27
+ * as the user edits), so a newly-captured entry always sorts last; the buffer
28
+ * is kept in arrival order and a new entry is materialized on top of the
29
+ * replayed survivors.
30
+ *
31
+ * @param userId Stable peer/session identifier stamped on every outbound
32
+ * envelope, unique across all peers sharing the same sync server.
33
+ */
34
+ export const createRollForwardConcurrency = (userId) => (execute, getTransaction) => {
35
+ const pending = [];
36
+ const indexOfEntry = (id, entryUserId) => pending.findIndex(e => e.id === id && e.userId === entryUserId);
37
+ const rollbackAll = () => {
38
+ for (let i = pending.length - 1; i >= 0; i--) {
39
+ execute(t => applyOperations(t, pending[i].undo), { transient: true, userId: undefined });
40
+ }
41
+ };
42
+ // Roll-forward: re-apply each pending entry's captured post-image
43
+ // tuples and recapture fresh undo against the (possibly changed) base.
44
+ const replayAll = () => {
45
+ for (const entry of pending) {
46
+ const result = execute(t => applyOperations(t, entry.redo), { transient: true, userId: entry.userId });
47
+ entry.undo = result.undo;
48
+ }
49
+ };
50
+ // Run a transaction function once to materialize its effect and capture
51
+ // the post-image. This is the only place a transaction function runs;
52
+ // every subsequent rebase rolls the captured tuples forward instead.
53
+ const runTransaction = (envelope) => {
54
+ const fn = getTransaction(envelope.name);
55
+ if (!fn)
56
+ throw new Error(`Unknown transaction: ${envelope.name}`);
57
+ return execute(t => fn(t, envelope.args), { transient: envelope.time < 0, userId: envelope.userId });
58
+ };
59
+ const removeAndRebuild = (id, entryUserId) => {
60
+ const idx = indexOfEntry(id, entryUserId);
61
+ if (idx === -1)
62
+ return;
63
+ rollbackAll();
64
+ pending.splice(idx, 1);
65
+ replayAll();
66
+ };
67
+ return {
68
+ deferredCommit: true,
69
+ userId,
70
+ apply(envelope) {
71
+ const { id, userId: envelopeUserId, time } = envelope;
72
+ // Cancel: drop the matching pending entry and rebuild.
73
+ if (time === 0) {
74
+ removeAndRebuild(id, envelopeUserId);
75
+ return undefined;
76
+ }
77
+ // Optimistic local transient: roll back, drop any prior version
78
+ // of this transaction, replay the survivors, then run the fn on
79
+ // top and capture its post-image tuples.
80
+ if (time < 0) {
81
+ rollbackAll();
82
+ const existing = indexOfEntry(id, envelopeUserId);
83
+ if (existing !== -1)
84
+ pending.splice(existing, 1);
85
+ replayAll();
86
+ const result = runTransaction(envelope);
87
+ const isNoOp = result.redo.length === 0 && result.undo.length === 0;
88
+ if (!isNoOp) {
89
+ pending.push({ id, userId: envelopeUserId, time, redo: result.redo, undo: result.undo });
90
+ }
91
+ return result;
92
+ }
93
+ // Confirmed delta (positive time): roll back all pending, drop
94
+ // our own echo (now authoritative), apply the confirmed delta,
95
+ // then roll the survivors forward.
96
+ rollbackAll();
97
+ const echo = indexOfEntry(id, envelopeUserId);
98
+ if (echo !== -1)
99
+ pending.splice(echo, 1);
100
+ const result = runTransaction(envelope);
101
+ replayAll();
102
+ return result;
103
+ },
104
+ cancel(id, cancelUserId) {
105
+ removeAndRebuild(id, cancelUserId);
106
+ },
107
+ onReset() {
108
+ pending.length = 0;
109
+ },
110
+ onBeforeToData: rollbackAll,
111
+ onAfterToData: replayAll,
112
+ onAfterFromData: replayAll,
113
+ };
114
+ };
115
+ //# sourceMappingURL=roll-forward-concurrency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roll-forward-concurrency.js","sourceRoot":"","sources":["../../../../src/ecs/database/concurrency/roll-forward-concurrency.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,136 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ //
3
+ // Verifies the pluggable-concurrency seam by running a client-side
4
+ // roll-forward replay strategy through the public `createDatabase` API, and
5
+ // contrasting it with the built-in rebase-replay strategy on an identical
6
+ // scenario.
7
+ import { describe, it, expect, vi } from "vitest";
8
+ import { Database } from "../database.js";
9
+ import { createRollForwardConcurrency } from "./roll-forward-concurrency.js";
10
+ import { createRebaseReplayConcurrency } from "./rebase-replay-concurrency.js";
11
+ // A single numeric component is enough to expose the roll-forward vs
12
+ // re-execute difference: one transaction derives its value from current state.
13
+ const plugin = Database.Plugin.create({
14
+ components: {
15
+ counter: { type: "number" },
16
+ },
17
+ resources: {},
18
+ archetypes: {
19
+ Counter: ["counter"],
20
+ },
21
+ transactions: {
22
+ createCounter(t, args) {
23
+ return t.archetypes.Counter.insert({ counter: args.counter });
24
+ },
25
+ // Reads current value and adds 10 — its result depends on the base
26
+ // state it runs against. This is the transaction whose replay semantics
27
+ // distinguish roll-forward from re-execute.
28
+ bumpFromCurrent(t, args) {
29
+ const current = t.read(args.entity)?.counter ?? 0;
30
+ t.update(args.entity, { counter: current + 10 });
31
+ },
32
+ setAbsolute(t, args) {
33
+ t.update(args.entity, { counter: args.value });
34
+ },
35
+ },
36
+ });
37
+ const makeDb = (concurrency) => Database.create(plugin, { concurrency });
38
+ // Seed a server-confirmed entity (positive time) so the entity exists in the
39
+ // committed base before any optimistic local edit. Returns its id.
40
+ const seedCommittedCounter = (db, value) => {
41
+ const result = db.apply({ id: 1, userId: "seed", name: "createCounter", args: { counter: value }, time: Date.now() });
42
+ return result.value;
43
+ };
44
+ describe("createRollForwardConcurrency — pluggable concurrency viability", () => {
45
+ it("plugs into createDatabase and exposes itself as the database concurrency strategy", () => {
46
+ const db = makeDb(createRollForwardConcurrency("A"));
47
+ expect(db.concurrency.deferredCommit).toBe(true);
48
+ expect(db.concurrency.userId).toBe("A");
49
+ });
50
+ it("applies a server-confirmed delta directly", () => {
51
+ const db = makeDb(createRollForwardConcurrency("A"));
52
+ const entity = seedCommittedCounter(db, 42);
53
+ expect(db.read(entity)?.counter).toBe(42);
54
+ });
55
+ it("makes a local optimistic transient visible immediately", () => {
56
+ const db = makeDb(createRollForwardConcurrency("A"));
57
+ const entity = seedCommittedCounter(db, 0);
58
+ db.transactions.bumpFromCurrent({ entity });
59
+ // Deferred-commit: applied locally as a transient before any echo.
60
+ expect(db.read(entity)?.counter).toBe(10);
61
+ });
62
+ it("replays pending edits by rolling captured post-images forward, not by re-executing", () => {
63
+ const db = makeDb(createRollForwardConcurrency("A"));
64
+ const entity = seedCommittedCounter(db, 0);
65
+ // Local optimistic edit: 0 -> 10, captured post-image = "set counter 10".
66
+ db.transactions.bumpFromCurrent({ entity });
67
+ expect(db.read(entity)?.counter).toBe(10);
68
+ // A peer's confirmed delta moves the base out from under the pending edit.
69
+ db.apply({ id: 99, userId: "B", name: "setAbsolute", args: { entity, value: 100 }, time: Date.now() + 1 });
70
+ // Roll-forward replays the *captured tuple* (set counter 10), so the
71
+ // pending edit's original post-image wins — NOT 110 (which is what
72
+ // re-executing bumpFromCurrent against the new base of 100 would give).
73
+ expect(db.read(entity)?.counter).toBe(10);
74
+ });
75
+ it("cancel discards a pending transient and restores committed state", () => {
76
+ const db = makeDb(createRollForwardConcurrency("A"));
77
+ const entity = seedCommittedCounter(db, 5);
78
+ // Drive a cancellable transient through the dispatcher via an async
79
+ // generator that yields once then throws (cleanly tears down).
80
+ const observer = vi.fn();
81
+ const unobserve = db.observe.entity(entity)(observer);
82
+ db.transactions.bumpFromCurrent({ entity });
83
+ expect(db.read(entity)?.counter).toBe(15);
84
+ // Cancel the pending entry by its dispatcher-assigned id. The first
85
+ // (and only) wrapped transaction gets id 1 from the dispatcher counter.
86
+ db.cancel(1, "A");
87
+ expect(db.read(entity)?.counter).toBe(5);
88
+ unobserve();
89
+ });
90
+ it("reset clears the pending buffer", () => {
91
+ const db = makeDb(createRollForwardConcurrency("A"));
92
+ const entity = seedCommittedCounter(db, 0);
93
+ db.transactions.bumpFromCurrent({ entity });
94
+ expect(db.read(entity)?.counter).toBe(10);
95
+ db.reset();
96
+ // Store is empty and the pending buffer is gone — re-seeding starts clean.
97
+ const reseeded = seedCommittedCounter(db, 7);
98
+ expect(db.read(reseeded)?.counter).toBe(7);
99
+ });
100
+ it("toData serializes only committed state and leaves the live transient intact", () => {
101
+ const db = makeDb(createRollForwardConcurrency("A"));
102
+ const entity = seedCommittedCounter(db, 0);
103
+ db.transactions.bumpFromCurrent({ entity });
104
+ expect(db.read(entity)?.counter).toBe(10);
105
+ const snapshot = db.toData();
106
+ // The live optimistic transient survives the rollback→serialize→replay.
107
+ expect(db.read(entity)?.counter).toBe(10);
108
+ // The snapshot is a detached copy of committed state only: loading it
109
+ // into a fresh database yields the committed base (0), not the pending
110
+ // optimistic value (10). This must hold even though `onAfterToData`
111
+ // replayed the transient back onto the live store after serialization.
112
+ const loaded = Database.create(plugin);
113
+ loaded.fromData(snapshot);
114
+ const loadedValues = loaded.select(["counter"]).map(e => loaded.read(e)?.counter);
115
+ expect(loadedValues).toEqual([0]);
116
+ });
117
+ });
118
+ describe("roll-forward vs rebase-replay — same seam, different replay semantics", () => {
119
+ // Identical scenario, swapping only the concurrency strategy factory. The
120
+ // divergent result proves the two engines are genuinely different and that
121
+ // the pluggable seam selects between them with no other changes.
122
+ const runRebaseScenario = (concurrency) => {
123
+ const db = makeDb(concurrency);
124
+ const entity = seedCommittedCounter(db, 0);
125
+ db.transactions.bumpFromCurrent({ entity });
126
+ db.apply({ id: 99, userId: "B", name: "setAbsolute", args: { entity, value: 100 }, time: Date.now() + 1 });
127
+ return db.read(entity)?.counter ?? NaN;
128
+ };
129
+ it("roll-forward preserves the captured post-image (10)", () => {
130
+ expect(runRebaseScenario(createRollForwardConcurrency("A"))).toBe(10);
131
+ });
132
+ it("rebase-replay re-executes against the new base (110)", () => {
133
+ expect(runRebaseScenario(createRebaseReplayConcurrency("A"))).toBe(110);
134
+ });
135
+ });
136
+ //# sourceMappingURL=roll-forward-concurrency.test.js.map