@spine-event-engine/storage 2.0.0-snapshot.23 → 2.0.0-snapshot.24

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 (60) hide show
  1. package/REFERENCE.md +32 -13
  2. package/dist/entity/agent-execution-transitions.d.ts +14 -13
  3. package/dist/entity/agent-execution-transitions.d.ts.map +1 -1
  4. package/dist/entity/agent-execution-transitions.js +20 -37
  5. package/dist/entity/agent-execution-transitions.js.map +1 -1
  6. package/dist/entity/agent-execution-values.d.ts +4 -3
  7. package/dist/entity/agent-execution-values.d.ts.map +1 -1
  8. package/dist/entity/agent-execution-values.js +13 -12
  9. package/dist/entity/agent-execution-values.js.map +1 -1
  10. package/dist/entity/agent-execution.d.ts +6 -5
  11. package/dist/entity/agent-execution.d.ts.map +1 -1
  12. package/dist/event/event-store.d.ts +30 -72
  13. package/dist/event/event-store.d.ts.map +1 -1
  14. package/dist/event/event-store.js +124 -268
  15. package/dist/event/event-store.js.map +1 -1
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/internal/entity-commit.d.ts +30 -11
  21. package/dist/internal/entity-commit.d.ts.map +1 -1
  22. package/dist/internal/entity-commit.js +45 -2
  23. package/dist/internal/entity-commit.js.map +1 -1
  24. package/dist/internal/event-store.d.ts +1 -1
  25. package/dist/internal/event-store.d.ts.map +1 -1
  26. package/dist/internal/event-store.js +1 -1
  27. package/dist/internal/event-store.js.map +1 -1
  28. package/dist/memory/in-memory-agent-execution.d.ts.map +1 -1
  29. package/dist/memory/in-memory-agent-execution.js +3 -3
  30. package/dist/memory/in-memory-agent-execution.js.map +1 -1
  31. package/dist/memory/in-memory-entity-commit.d.ts +100 -38
  32. package/dist/memory/in-memory-entity-commit.d.ts.map +1 -1
  33. package/dist/memory/in-memory-entity-commit.js +191 -323
  34. package/dist/memory/in-memory-entity-commit.js.map +1 -1
  35. package/dist/memory/in-memory-storage-factory.d.ts +1 -1
  36. package/dist/memory/in-memory-storage-factory.d.ts.map +1 -1
  37. package/dist/memory/in-memory-storage-factory.js +2 -7
  38. package/dist/memory/in-memory-storage-factory.js.map +1 -1
  39. package/dist/memory/tenant-records.d.ts +1 -1
  40. package/dist/memory/tenant-records.d.ts.map +1 -1
  41. package/dist/memory/tenant-records.js +3 -2
  42. package/dist/memory/tenant-records.js.map +1 -1
  43. package/dist/provider.d.ts +0 -1
  44. package/dist/provider.d.ts.map +1 -1
  45. package/dist/provider.js +0 -1
  46. package/dist/provider.js.map +1 -1
  47. package/dist/record/record-query.d.ts +10 -2
  48. package/dist/record/record-query.d.ts.map +1 -1
  49. package/dist/record/record-query.js +9 -0
  50. package/dist/record/record-query.js.map +1 -1
  51. package/dist/tsconfig.tsbuildinfo +1 -1
  52. package/package.json +3 -3
  53. package/dist/internal/delivery-cleanup.d.ts +0 -99
  54. package/dist/internal/delivery-cleanup.d.ts.map +0 -1
  55. package/dist/internal/delivery-cleanup.js +0 -38
  56. package/dist/internal/delivery-cleanup.js.map +0 -1
  57. package/dist/memory/memory-delivery-cleanup.d.ts +0 -32
  58. package/dist/memory/memory-delivery-cleanup.d.ts.map +0 -1
  59. package/dist/memory/memory-delivery-cleanup.js +0 -61
  60. package/dist/memory/memory-delivery-cleanup.js.map +0 -1
@@ -11,430 +11,298 @@
11
11
  * or implied. See the License for the specific language governing permissions and limitations under
12
12
  * the License.
13
13
  */
14
- import { clone } from "@bufbuild/protobuf";
14
+ import { toBinary } from "@bufbuild/protobuf";
15
15
  import { EventSchema } from "@spine-event-engine/proto";
16
16
  import { EntityRecordSchema } from "@spine-event-engine/proto/generated/spine/server/entity/entity_pb.js";
17
- import { eventStoreAccess, eventStoreRecordSpec } from "../event/event-store.js";
17
+ import { eventStoreRecordSpec } from "../event/event-store.js";
18
18
  import { eventHistorySpec, stateHistorySpec } from "../entity/entity-history-record-spec.js";
19
+ import { EntityCommitSnapshots, } from "../internal/entity-commit.js";
19
20
  import { TenantBoundary } from "../internal/tenancy.js";
20
- import { InMemoryEntityStorage, KeyedSerialQueue, MemoryEntityStorageFactory, ENTITY_SCOPE_MUTATION_KEY, } from "./in-memory-entity-history.js";
21
+ import { MemoryEntityStorageFactory, ENTITY_SCOPE_MUTATION_KEY, } from "./in-memory-entity-history.js";
21
22
  import { InMemoryRecordStorage } from "./in-memory-record-storage.js";
22
23
  import { TenantRecords } from "./tenant-records.js";
23
24
  /**
24
- * Implements provider Entity commits for one shared in-memory backend.
25
+ * Writes in-memory Entity records in order and retains a completed prefix.
25
26
  */
26
27
  export class MemoryEntityCommitStorage {
27
- #entities;
28
- #factory;
29
- #openRecords;
30
- #input;
28
+ entities;
29
+ openRecords;
30
+ input;
31
+ /**
32
+ * Tenant boundary selected by this handle.
33
+ */
31
34
  #tenantKey;
35
+ /**
36
+
37
+ * Whether this handle uses multitenant storage.
38
+
39
+ */
32
40
  #multitenant;
41
+ /**
42
+
43
+ * Whether this handle accepts commits.
44
+
45
+ */
33
46
  #open = true;
34
47
  /**
35
- * Creates a commit handle bound to one Entity source type.
48
+ * Creates one source- and tenant-bound commit handle.
36
49
  *
37
- * @param entities Opens the matching current Entity storage.
38
- * @param factory Provides the Event Store coordination lock.
39
- * @param openRecords Opens exact generic-record backing slices.
40
- * @param input Defines the Entity storage source type.
50
+ * @param entities Opens current Entity storage.
51
+ * @param openRecords Opens generated history and Event record slices.
52
+ * @param input Captures the Entity source and tenant boundary.
41
53
  */
42
- constructor(entities, factory, openRecords, input) {
43
- this.#entities = entities;
44
- this.#factory = factory;
45
- this.#openRecords = openRecords;
46
- this.#input = input;
54
+ constructor(entities, openRecords, input) {
55
+ this.entities = entities;
56
+ this.openRecords = openRecords;
57
+ this.input = input;
47
58
  this.#tenantKey = TenantBoundary.of(input.context).key;
48
59
  this.#multitenant = input.context.multitenant;
49
60
  }
50
61
  /**
51
- * Applies one fully preflighted in-memory Entity commit.
62
+ * Writes history, diagnostics, delivery Events, then current state.
52
63
  *
53
64
  * @typeParam I Entity identifier type.
54
65
  * @typeParam S Entity state type.
55
- * @param input Defines the current record, retained histories, and delivery events.
56
- * @returns Completion after the in-memory commit.
66
+ * @param input Supplies the proposed mutation.
67
+ * @returns Completion after every record has been stored.
57
68
  */
58
69
  commit(input) {
59
- this.#requireOpen();
60
- this.#requireCompatible(input);
61
- const tenant = TenantBoundary.of(input.context);
62
- const context = tenant.tenantId === undefined
63
- ? { name: input.context.name, multitenant: false }
64
- : { name: input.context.name, multitenant: true, tenantId: tenant.tenantId };
65
- const snapshot = {
66
- ...input,
67
- context,
68
- entity: { ...input.entity, context },
69
- entityId: input.entity.id.clone(input.entityId),
70
- next: clone(EntityRecordSchema, input.next),
71
- ...(input.states === undefined
72
- ? {}
73
- : { states: input.states.map((record) => clone(EntityRecordSchema, record)) }),
74
- ...(input.diagnostics === undefined
75
- ? {}
76
- : { diagnostics: input.diagnostics.map((event) => clone(EventSchema, event)) }),
77
- ...(input.events === undefined
78
- ? {}
79
- : { events: input.events.map((event) => clone(EventSchema, event)) }),
80
- };
81
- const backend = this.#entities.backend(snapshot.entity);
82
- const work = () => backend.mutationQueue.run(ENTITY_SCOPE_MUTATION_KEY, () => this.#commit(snapshot, backend));
83
- return snapshot.events === undefined || snapshot.events.length === 0
84
- ? work()
85
- : eventStoreAccess.withLock(this.#factory, snapshot.context, work);
70
+ this.requireOpen();
71
+ this.requireCompatible(input);
72
+ return this.apply(EntityCommitSnapshots.capture(input));
86
73
  }
87
74
  /**
88
- * Applies an Agent completion only while the current Entity Version matches.
75
+ * Commits one Agent transition under the Entity scope queue and Version fence.
89
76
  *
90
- * @typeParam I Typed Entity identifier.
91
- * @typeParam S Generated Entity state.
92
- * @param entity Existing Entity layout.
93
- * @param entityId Typed Entity identifier.
94
- * @param expectedVersion Version observed before Agent handlers ran.
95
- * @param input Current-state mutation, absent for a no-op.
96
- * @param extra Staged execution and history publication with restoration.
97
- * @returns Completion after conditional Entity and execution publication.
77
+ * @typeParam I Entity identifier type.
78
+ * @typeParam S Entity state type.
79
+ * @param entity Configured Entity storage layout.
80
+ * @param entityId Typed Agent Entity identifier.
81
+ * @param expectedVersion Version read before the Agent handler ran.
82
+ * @param input Optional changed Entity state and history.
83
+ * @param extra Applies or restores the Agent execution record with the Entity state.
84
+ * @returns Completion after the fenced transition is stored.
98
85
  */
99
- commitConditional(entity, entityId, expectedVersion, input, extra) {
100
- this.#requireOpen();
101
- const backend = this.#entities.backend(entity);
102
- const work = () => backend.mutationQueue.run(ENTITY_SCOPE_MUTATION_KEY, async () => {
103
- this.#checkCurrentVersion(backend, entity.id.key(entityId), expectedVersion);
104
- if (input === undefined) {
105
- try {
106
- extra.apply();
107
- }
108
- catch (error) {
109
- extra.restore();
110
- throw error;
111
- }
112
- return;
113
- }
114
- await this.#commit(input, backend, extra);
115
- });
116
- return input?.events?.length
117
- ? eventStoreAccess.withLock(this.#factory, entity.context, work)
118
- : work();
86
+ commitAgentConditional(entity, entityId, expectedVersion, input, extra) {
87
+ this.requireOpen();
88
+ const backend = this.entities.backend(entity);
89
+ const key = entity.id.key(entityId);
90
+ const snapshot = input === undefined ? undefined : EntityCommitSnapshots.capture(input);
91
+ if (snapshot !== undefined) {
92
+ this.requireCompatible(snapshot);
93
+ this.requireEnabledHistories(snapshot);
94
+ this.validateRows(snapshot);
95
+ if (snapshot.diagnostics?.length || snapshot.events?.length)
96
+ throw new Error("Agent completion cannot save delivery Events as Entity commit obligations.");
97
+ }
98
+ return backend.mutationQueue.run(ENTITY_SCOPE_MUTATION_KEY, () => this.applyAgentConditional(entity, backend, key, expectedVersion, snapshot, extra));
119
99
  }
120
100
  /**
121
- * Checks the current Version while the Entity mutation queue is held.
101
+ * Applies one prepared Agent transition and restores affected rows on failure.
102
+ *
103
+ * @typeParam I Entity identifier type.
104
+ * @typeParam S Entity state type.
105
+ * @param entity Configured Entity storage layout.
106
+ * @param backend Shared current records for this Entity scope.
107
+ * @param key Canonical Entity ID key.
108
+ * @param expectedVersion Version read before handler execution.
109
+ * @param snapshot Optional captured Entity state transition.
110
+ * @param extra Applies or restores the Agent execution record.
111
+ * @returns Completion after the transition is stored.
122
112
  */
123
- #checkCurrentVersion(backend, key, expected) {
124
- const current = backend.current.get(key);
125
- const actual = current?.version?.number ?? 0;
126
- if (actual !== expected)
113
+ applyAgentConditional(entity, backend, key, expectedVersion, snapshot, extra) {
114
+ const prior = backend.current.get(key);
115
+ if ((prior?.version?.number ?? 0) !== expectedVersion)
127
116
  throw new Error("Agent execution initial Entity Version is no longer current.");
117
+ const layout = snapshot?.states?.length ? stateHistorySpec(entity.stateSchema) : undefined;
118
+ const states = layout === undefined
119
+ ? undefined
120
+ : this.openRecords(entity.context, layout.spec, layout.group);
121
+ const prepared = layout === undefined
122
+ ? []
123
+ : (snapshot?.states ?? []).map((record) => layout.spec.materialize(record));
124
+ const priorStates = prepared.map((record) => states?.capture(record.id));
125
+ try {
126
+ this.writeAgentStates(states, prepared);
127
+ if (snapshot !== undefined)
128
+ backend.current.set(key, snapshot.next);
129
+ extra.apply();
130
+ }
131
+ catch (error) {
132
+ extra.restore();
133
+ if (prior === undefined)
134
+ backend.current.delete(key);
135
+ else
136
+ backend.current.set(key, prior);
137
+ for (const priorState of priorStates)
138
+ if (priorState !== undefined)
139
+ states?.apply(priorState);
140
+ throw error;
141
+ }
142
+ return Promise.resolve();
143
+ }
144
+ /**
145
+ * Writes immutable Agent state history after checking each Version slot.
146
+ *
147
+ * @param states State-history records when that family is configured.
148
+ * @param prepared Materialized state rows from the captured transition.
149
+ */
150
+ writeAgentStates(states, prepared) {
151
+ if (states === undefined)
152
+ return;
153
+ for (const record of prepared) {
154
+ const existing = states.read(record.id);
155
+ if (existing !== undefined &&
156
+ !this.sameBytes(toBinary(EntityRecordSchema, existing), toBinary(EntityRecordSchema, record.record)))
157
+ throw new Error("Agent state history collides with another Version.");
158
+ states.write(record);
159
+ }
128
160
  }
129
161
  /**
130
- * Closes this commit handle without closing sibling handles.
162
+ * Closes this handle to new commits.
131
163
  */
132
164
  close() {
133
165
  this.#open = false;
134
166
  }
135
167
  /**
136
- * Prepares and publishes one affected-record Entity commit under its existing queue.
168
+ * Applies one captured mutation without cross-record compensation.
137
169
  *
138
170
  * @typeParam I Entity identifier type.
139
171
  * @typeParam S Entity state type.
140
- * @param input Supplies current and immutable records.
141
- * @param liveBackend Supplies the live current-record map and queue.
172
+ * @param input Supplies immutable record snapshots.
173
+ * @returns Completion after every requested record write.
142
174
  */
143
- async #commit(input, liveBackend, extra) {
144
- const stage = this.stage(input, liveBackend);
175
+ async apply(input) {
176
+ this.requireEnabledHistories(input);
177
+ this.validateRows(input);
178
+ const [entity, stateStorage, diagnosticStorage, eventStorage] = this.openCommit(input);
145
179
  try {
146
- await this.#writeStage(input, stage);
147
- this.#publish(stage, liveBackend, extra);
180
+ for (const state of input.states ?? [])
181
+ await entity.states.append(state);
182
+ for (const diagnostic of input.diagnostics ?? [])
183
+ await entity.events.append(diagnostic);
184
+ for (const event of input.events ?? []) {
185
+ if (event.id === undefined || eventStorage === undefined)
186
+ throw new Error("Entity commit requires delivery-event IDs.");
187
+ await this.appendEvent(eventStorage, event.id, event);
188
+ }
189
+ await entity.current.write(input.next);
148
190
  }
149
191
  finally {
150
- stage.entity.close();
192
+ entity.close();
193
+ stateStorage?.close();
194
+ diagnosticStorage?.close();
195
+ eventStorage?.close();
151
196
  }
152
197
  }
153
198
  /**
154
- * Opens only record families used by one commit.
199
+ * Opens the record families needed by one captured mutation.
155
200
  *
156
201
  * @typeParam I Entity identifier type.
157
202
  * @typeParam S Entity state type.
158
- * @param input Supplies configured histories and delivery Events.
159
- * @returns Live affected record families and their layouts.
203
+ * @param input Supplies requested history and delivery records.
204
+ * @returns Entity and record handles to close after the mutation.
160
205
  */
161
- live(input) {
162
- this.#requireEnabledHistories(input);
163
- const stateLayout = input.states?.length
164
- ? stateHistorySpec(input.entity.stateSchema)
165
- : undefined;
166
- const eventLayout = input.diagnostics?.length
206
+ openCommit(input) {
207
+ const states = input.states?.length ? stateHistorySpec(input.entity.stateSchema) : undefined;
208
+ const diagnostics = input.diagnostics?.length
167
209
  ? eventHistorySpec(input.entity.stateSchema)
168
210
  : undefined;
169
- const states = stateLayout === undefined
211
+ const stateStorage = states === undefined
170
212
  ? undefined
171
- : this.#openRecords(input.context, stateLayout.spec, stateLayout.group);
172
- const diagnostics = eventLayout === undefined
213
+ : new InMemoryRecordStorage(input.context, states.spec, () => this.openRecords(input.context, states.spec, states.group));
214
+ const diagnosticStorage = diagnostics === undefined
173
215
  ? undefined
174
- : this.#openRecords(input.context, eventLayout.spec, eventLayout.group);
175
- const events = input.events?.length
176
- ? this.#openRecords(input.context, eventStoreRecordSpec)
216
+ : new InMemoryRecordStorage(input.context, diagnostics.spec, () => this.openRecords(input.context, diagnostics.spec, diagnostics.group));
217
+ const eventStorage = input.events?.length
218
+ ? new InMemoryRecordStorage(input.context, eventStoreRecordSpec, () => this.openRecords(input.context, eventStoreRecordSpec))
177
219
  : undefined;
178
- return { stateLayout, eventLayout, states, diagnostics, events };
220
+ const entityInput = { ...input.entity };
221
+ delete entityInput.stateHistoryStorage;
222
+ delete entityInput.eventHistoryStorage;
223
+ const entity = this.entities.create({
224
+ ...entityInput,
225
+ ...(stateStorage === undefined ? {} : { stateHistoryStorage: stateStorage }),
226
+ ...(diagnosticStorage === undefined ? {} : { eventHistoryStorage: diagnosticStorage }),
227
+ });
228
+ return [entity, stateStorage, diagnosticStorage, eventStorage];
179
229
  }
180
230
  /**
181
- * Captures affected rows and creates isolated adapters for preparation.
231
+ * Writes one immutable delivery Event or accepts an identical retry.
182
232
  *
183
- * @typeParam I Entity identifier type.
184
- * @typeParam S Entity state type.
185
- * @param input Supplies the proposed record changes.
186
- * @param backend Supplies the live current-record map.
187
- * @returns The prepared affected-record workspace.
233
+ * @param storage Supplies atomic single-record insertion.
234
+ * @param id Identifies the delivery Event.
235
+ * @param event Supplies the captured Event.
236
+ * @returns Completion after an identical Event is stored.
188
237
  */
189
- stage(input, backend) {
190
- const live = this.live(input);
191
- const key = input.entity.id.key(input.entityId);
192
- const previous = backend.current.get(key);
193
- const ids = this.#ids(input, live);
194
- const stagedBackend = {
195
- current: new Map(previous === undefined ? [] : [[key, previous]]),
196
- mutationQueue: new KeyedSerialQueue(),
197
- };
198
- const stagedStates = InMemoryCommitValues.stage(live.states, ids.stateIds);
199
- const stagedDiagnostics = InMemoryCommitValues.stage(live.diagnostics, ids.diagnosticIds);
200
- const stagedEvents = InMemoryCommitValues.stage(live.events, ids.deliveryIds);
201
- const entity = new InMemoryEntityStorage(this.#stagedInput(input.entity, live.stateLayout, stagedStates, live.eventLayout, stagedDiagnostics), stagedBackend);
202
- return {
203
- live,
204
- key,
205
- previous,
206
- ...ids,
207
- stagedBackend,
208
- stagedStates,
209
- stagedDiagnostics,
210
- stagedEvents,
211
- entity,
212
- };
238
+ async appendEvent(storage, id, event) {
239
+ if (await storage.compareAndSet(id, undefined, event))
240
+ return;
241
+ const prior = await storage.read(id);
242
+ if (prior === undefined ||
243
+ !this.sameBytes(toBinary(EventSchema, prior), toBinary(EventSchema, event))) {
244
+ throw new Error("Entity commit requires unique delivery-event IDs.");
245
+ }
213
246
  }
214
247
  /**
215
- * Materializes the storage identities and delivery rows before live application.
248
+ * Compares two stored Protobuf byte sequences.
216
249
  *
217
- * @typeParam I Entity identifier type.
218
- * @typeParam S Entity state type.
219
- * @param input Supplies the changed records.
220
- * @param live Supplies generated history layouts.
221
- * @returns Affected keys and cloned delivery rows.
250
+ * @param left First byte sequence.
251
+ * @param right Second byte sequence.
252
+ * @returns Whether every byte matches.
222
253
  */
223
- #ids(input, live) {
224
- const stateIds = (input.states ?? []).map((record) => {
225
- const layout = live.stateLayout;
226
- if (layout === undefined)
227
- throw new TypeError("Cannot read properties of undefined (reading 'spec')");
228
- return layout.spec.materialize(record).id;
229
- });
230
- const diagnosticIds = (input.diagnostics ?? []).map((event) => {
231
- const layout = live.eventLayout;
232
- if (layout === undefined)
233
- throw new TypeError("Cannot read properties of undefined (reading 'spec')");
234
- return layout.spec.materialize(event).id;
235
- });
236
- const materialized = (input.events ?? []).map((event) => eventStoreRecordSpec.materialize(clone(eventStoreRecordSpec.recordType, event)));
237
- const deliveryIds = materialized.map((record) => record.id);
238
- return { stateIds, diagnosticIds, materialized, deliveryIds };
254
+ sameBytes(left, right) {
255
+ return left.length === right.length && left.every((value, index) => value === right[index]);
239
256
  }
240
257
  /**
241
- * Validates and writes only staged records before any live change.
258
+ * Validates current identity and duplicate delivery IDs before writes.
242
259
  *
243
260
  * @typeParam I Entity identifier type.
244
261
  * @typeParam S Entity state type.
245
- * @param input Supplies the proposed record changes.
246
- * @param stage Supplies affected-record adapters and materialized Events.
262
+ * @param input Supplies the captured mutation.
247
263
  */
248
- async #writeStage(input, stage) {
264
+ validateRows(input) {
249
265
  const nextId = input.next.entityId === undefined ? undefined : input.entity.id.unpack(input.next.entityId);
250
- if (nextId === undefined || input.entity.id.key(nextId) !== stage.key)
266
+ if (nextId === undefined || input.entity.id.key(nextId) !== input.entity.id.key(input.entityId))
251
267
  throw new Error("Entity commit current record identifies another Entity.");
252
- if (new Set(stage.deliveryIds.map((id) => id.value)).size !== stage.deliveryIds.length ||
253
- stage.deliveryIds.some((id) => stage.stagedEvents?.read(id) !== undefined))
268
+ const ids = (input.events ?? []).map((event) => event.id?.value);
269
+ if (ids.some((id) => id === undefined || id.trim() === "") || new Set(ids).size !== ids.length)
254
270
  throw new Error("Entity commit requires unique delivery-event IDs.");
255
- await stage.entity.current.write(input.next);
256
- for (const state of input.states ?? [])
257
- await stage.entity.states.append(state);
258
- for (const diagnostic of input.diagnostics ?? [])
259
- await stage.entity.events.append(diagnostic);
260
- stage.stagedEvents?.writeAll(stage.materialized);
261
- }
262
- /**
263
- * Applies prepared rows synchronously and restores affected entries on failure.
264
- *
265
- * @param stage Supplies the complete affected-record preparation.
266
- * @param backend Supplies the live current-record map.
267
- */
268
- #publish(stage, backend, extra) {
269
- const changes = [
270
- ...InMemoryCommitValues.changes(stage.live.states, stage.stagedStates, stage.stateIds),
271
- ...InMemoryCommitValues.changes(stage.live.diagnostics, stage.stagedDiagnostics, stage.diagnosticIds),
272
- ...InMemoryCommitValues.changes(stage.live.events, stage.stagedEvents, stage.deliveryIds),
273
- ];
274
- const next = stage.stagedBackend.current.get(stage.key);
275
- try {
276
- for (const change of changes)
277
- change.apply();
278
- backend.current.set(stage.key, next);
279
- extra?.apply();
280
- }
281
- catch (error) {
282
- extra?.restore();
283
- if (stage.previous === undefined)
284
- backend.current.delete(stage.key);
285
- else
286
- backend.current.set(stage.key, stage.previous);
287
- for (const change of changes.reverse())
288
- change.restore();
289
- throw error;
290
- }
291
- }
292
- /**
293
- * Connects affected staged history slices to the existing Entity adapters.
294
- *
295
- * @typeParam I Entity identifier type.
296
- * @typeParam S Entity state type.
297
- * @param entity Supplies the Entity storage layout.
298
- * @param stateLayout Supplies the enabled state-history layout.
299
- * @param states Supplies the affected staged state rows.
300
- * @param eventLayout Supplies the enabled diagnostic-history layout.
301
- * @param diagnostics Supplies the affected staged diagnostic rows.
302
- * @returns The staged Entity storage layout.
303
- */
304
- #stagedInput(entity, stateLayout, states, eventLayout, diagnostics) {
305
- const base = { ...entity };
306
- delete base.stateHistoryStorage;
307
- delete base.eventHistoryStorage;
308
- return {
309
- ...base,
310
- ...(stateLayout === undefined || states === undefined
311
- ? {}
312
- : {
313
- stateHistoryStorage: new InMemoryRecordStorage(entity.context, stateLayout.spec, () => states),
314
- }),
315
- ...(eventLayout === undefined || diagnostics === undefined
316
- ? {}
317
- : {
318
- eventHistoryStorage: new InMemoryRecordStorage(entity.context, eventLayout.spec, () => diagnostics),
319
- }),
320
- };
321
271
  }
322
272
  /**
323
- * Rejects history rows when their corresponding history is disabled.
273
+ * Rejects disabled history families.
324
274
  *
325
275
  * @typeParam I Entity identifier type.
326
276
  * @typeParam S Entity state type.
327
- * @param input Supplies the requested histories.
277
+ * @param input Supplies the proposed histories.
328
278
  */
329
- #requireEnabledHistories(input) {
330
- if ((input.states?.length ?? 0) > 0 && !input.entity.stateHistory) {
279
+ requireEnabledHistories(input) {
280
+ if ((input.states?.length ?? 0) > 0 && !input.entity.stateHistory)
331
281
  throw new Error("Entity commit cannot append state history when it is disabled.");
332
- }
333
- if ((input.diagnostics?.length ?? 0) > 0 && !input.entity.eventHistory) {
282
+ if ((input.diagnostics?.length ?? 0) > 0 && !input.entity.eventHistory)
334
283
  throw new Error("Entity commit cannot append event history when it is disabled.");
335
- }
336
284
  }
337
285
  /**
338
- * Checks the tenant and Entity source type captured by this handle.
286
+ * Checks the tenant and source captured by this handle.
339
287
  *
340
288
  * @typeParam I Entity identifier type.
341
289
  * @typeParam S Entity state type.
342
290
  * @param input Supplies the requested scope.
343
291
  */
344
- #requireCompatible(input) {
292
+ requireCompatible(input) {
345
293
  if (input.context.multitenant !== this.#multitenant ||
346
294
  TenantBoundary.of(input.context).key !== this.#tenantKey ||
347
295
  TenantBoundary.of(input.entity.context).key !== this.#tenantKey ||
348
- input.entity.sourceType.typeName !== this.#input.sourceType.typeName) {
296
+ input.entity.sourceType.typeName !== this.input.sourceType.typeName) {
349
297
  throw new Error("Entity commit handle cannot commit another Entity storage scope.");
350
298
  }
351
299
  }
352
300
  /**
353
301
  * Rejects commits after this handle closes.
354
302
  */
355
- #requireOpen() {
303
+ requireOpen() {
356
304
  if (!this.#open)
357
305
  throw new Error("Entity commit storage is closed.");
358
306
  }
359
307
  }
360
- /**
361
- * Prepares only affected materialized rows for an in-memory commit.
362
- */
363
- const InMemoryCommitValues = Object.freeze({
364
- /**
365
- * Captures the affected live rows in an isolated tenant slice.
366
- *
367
- * @typeParam I Record identifier type.
368
- * @typeParam R Record message type.
369
- * @param live Supplies the live tenant slice when enabled.
370
- * @param ids Identifies affected slots.
371
- * @returns An isolated slice with only affected prior entries.
372
- */
373
- stage(live, ids) {
374
- if (live === undefined)
375
- return undefined;
376
- const staged = new TenantRecords();
377
- for (const id of ids)
378
- staged.apply(live.capture(id));
379
- return staged;
380
- },
381
- /**
382
- * Builds synchronous apply and restore operations for affected slots.
383
- *
384
- * @typeParam I Record identifier type.
385
- * @typeParam R Record message type.
386
- * @param live Supplies the live tenant slice when enabled.
387
- * @param staged Supplies fully prepared replacements.
388
- * @param ids Identifies affected slots.
389
- * @returns Operations that preserve complete prior entries and absence.
390
- */
391
- changes(live, staged, ids) {
392
- if (live === undefined || staged === undefined)
393
- return [];
394
- const seen = new Set();
395
- return ids.flatMap((id) => {
396
- const prior = live.capture(id);
397
- if (seen.has(prior.key))
398
- return [];
399
- seen.add(prior.key);
400
- const prepared = staged.capture(id);
401
- return [new MemoryCommitChange(live, prepared, prior)];
402
- });
403
- },
404
- });
405
- /**
406
- * Applies one affected materialized row and restores its complete prior slot.
407
- *
408
- * @typeParam I Record identifier type.
409
- * @typeParam R Record message type.
410
- */
411
- class MemoryCommitChange {
412
- live;
413
- prepared;
414
- prior;
415
- /**
416
- * Captures the prepared and prior versions of one slot.
417
- *
418
- * @param live Supplies the affected tenant slice.
419
- * @param prepared Supplies the replacement entry or absence.
420
- * @param prior Supplies the complete prior entry or absence.
421
- */
422
- constructor(live, prepared, prior) {
423
- this.live = live;
424
- this.prepared = prepared;
425
- this.prior = prior;
426
- }
427
- /**
428
- * Applies the fully prepared materialized row.
429
- */
430
- apply() {
431
- this.live.apply(this.prepared);
432
- }
433
- /**
434
- * Restores the complete prior row or absence.
435
- */
436
- restore() {
437
- this.live.apply(this.prior);
438
- }
439
- }
440
308
  //# sourceMappingURL=in-memory-entity-commit.js.map