@spine-event-engine/storage-datastore 2.0.0-snapshot.18 → 2.0.0-snapshot.20

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.
package/REFERENCE.md CHANGED
@@ -109,12 +109,14 @@ This is deliberately the overlap, not MySQL parity: nested or disjunctive
109
109
  predicates and provider-illegal inequality/order shapes reject before provider
110
110
  access. Normalized plans have no offset; `RecordQuery.offset` is separate.
111
111
 
112
- The internal Entity commit reads current and immutable keys then applies current,
113
- enabled histories, and delivery events in one Datastore transaction. It rejects
114
- more than 25 entity groups, 500 mutations, or its conservative transaction-size
115
- limit before opening the transaction. Current mismatch returns `conflict`.
116
- An already-applied identical retry returns `committed`; divergent immutable
117
- content fails. Only ABORTED provider failures retry, for at most three attempts.
112
+ The provider Entity commit writes the supplied current record, enabled histories,
113
+ and delivery events in one Datastore transaction. It does not compare current
114
+ state with an expected previous record. The operation rejects more than 25
115
+ entity groups, 500 mutations, or its conservative transaction-size limit before
116
+ opening the transaction. Success resolves without a value. Identical immutable
117
+ retries remain accepted; divergent immutable content fails. Associated records
118
+ are still completed when the current record already matches the supplied value.
119
+ Only ABORTED provider failures retry, for at most three attempts.
118
120
 
119
121
  History reads use stable finite keyset pages. State history provides backward,
120
122
  state-at-time, trim, and truncate behavior; event history provides backward and
@@ -1,9 +1,11 @@
1
1
  import { type Message } from "@bufbuild/protobuf";
2
- import { type EntityCommitInput, type EntityCommitResult, type EntityCommitStorage, type EntityEventHistoryPort, type EntityRecordStorage, type EntityStateHistoryPort, type EntityStorageInput } from "@spine-event-engine/storage/provider";
2
+ import { type EntityCommitInput, type EntityCommitStorage, type EntityEventHistoryPort, type EntityRecordStorage, type EntityStateHistoryPort, type EntityStorageInput } from "@spine-event-engine/storage/provider";
3
3
  import { RecordSpec, type RecordStorage } from "@spine-event-engine/storage";
4
4
  /**
5
5
  * Opens one generated record family with its resolved Datastore layout.
6
6
  *
7
+ * @typeParam I Record identifier type.
8
+ * @typeParam R Stored Protobuf message type.
7
9
  * @param spec The generated record-family contract.
8
10
  * @param group The optional generated storage group.
9
11
  * @returns The opened record-storage handle.
@@ -11,6 +13,9 @@ import { RecordSpec, type RecordStorage } from "@spine-event-engine/storage";
11
13
  export type OpenEntityRecords = <I, R extends Message>(spec: RecordSpec<I, R>, group?: import("@spine-event-engine/storage").StorageGroup) => RecordStorage<I, R>;
12
14
  /**
13
15
  * Groups Entity current and optional history record families.
16
+ *
17
+ * @typeParam I Entity identifier type.
18
+ * @typeParam S Entity state message type.
14
19
  */
15
20
  export declare class DatastoreEntityStorage<I, S extends Message> {
16
21
  #private;
@@ -61,22 +66,89 @@ export declare class DatastoreEntityCommitStorage implements EntityCommitStorage
61
66
  /**
62
67
  * Commits one Entity state transition and its generated history records.
63
68
  *
69
+ * @typeParam I Entity identifier type.
70
+ * @typeParam S Entity state message type.
64
71
  * @param input The Entity mutation to commit.
65
- * @returns The committed or conflicting outcome.
72
+ * @returns Completion after Datastore commits the mutation.
73
+ */
74
+ commit<I, S extends Message>(input: EntityCommitInput<I, S>): Promise<void>;
75
+ /**
76
+ * Retries the prepared Datastore transaction after bounded aborts.
77
+ *
78
+ * @typeParam I Entity identifier type.
79
+ * @typeParam S Entity state type.
80
+ * @param input Supplies current and immutable records.
81
+ * @param current Provides current Entity storage.
82
+ * @param states Provides state history when enabled.
83
+ * @param diagnostics Provides diagnostic history when enabled.
84
+ * @param events Provides delivery Event storage.
85
+ * @returns Completion after a successful transaction.
66
86
  */
67
- commit<I, S extends Message>(input: EntityCommitInput<I, S>): Promise<EntityCommitResult>;
68
87
  private run;
88
+ /**
89
+ * Builds affected rows before opening a transaction.
90
+ *
91
+ * @typeParam I Entity identifier type.
92
+ * @param input Supplies records to materialize.
93
+ * @param current Provides current Entity storage.
94
+ * @param states Provides state history when enabled.
95
+ * @param diagnostics Provides diagnostic history when enabled.
96
+ * @param events Provides delivery Event storage.
97
+ * @returns Deduplicated rows for the transaction.
98
+ */
69
99
  private prepare;
100
+ /**
101
+ * Applies one prepared transaction attempt and rolls back on failure.
102
+ *
103
+ * @typeParam I Entity identifier type.
104
+ * @param current Opens a transaction for the Entity scope.
105
+ * @param prepared Supplies current and immutable rows.
106
+ * @returns Completion after the transaction commits.
107
+ */
70
108
  private tryCommit;
109
+ /**
110
+ * Loads affected keys in stable order within one transaction.
111
+ *
112
+ * @param transaction Supplies the active Datastore transaction.
113
+ * @param prepared Supplies affected rows.
114
+ * @returns Existing data indexed by key.
115
+ */
71
116
  private load;
72
- private conflict;
117
+ /**
118
+ * Rejects divergent data already stored under immutable keys.
119
+ *
120
+ * @param prepared Supplies proposed immutable rows.
121
+ * @param values Supplies existing data for affected keys.
122
+ */
73
123
  private validateImmutable;
124
+ /**
125
+ * Writes missing immutable rows and changed current state in the transaction.
126
+ *
127
+ * @param transaction Supplies the active Datastore transaction.
128
+ * @param prepared Supplies current and immutable rows.
129
+ * @param values Supplies existing data for affected keys.
130
+ */
74
131
  private apply;
132
+ /**
133
+ * Closes temporary record-family handles after the commit attempt.
134
+ *
135
+ * @param current Supplies current Entity storage.
136
+ * @param states Supplies state history when enabled.
137
+ * @param diagnostics Supplies diagnostic history when enabled.
138
+ * @param events Supplies delivery Event storage.
139
+ */
75
140
  private closeRecords;
76
141
  /**
77
142
  * Closes this commit storage to further commits.
78
143
  */
79
144
  close(): void;
145
+ /**
146
+ * Validates the Entity, tenant, history, and Event boundaries of a commit.
147
+ *
148
+ * @typeParam I Entity identifier type.
149
+ * @typeParam S Entity state type.
150
+ * @param input Supplies the proposed commit.
151
+ */
80
152
  private validate;
81
153
  }
82
154
  //# sourceMappingURL=entity-history.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"entity-history.d.ts","sourceRoot":"","sources":["../../src/datastore/entity-history.ts"],"names":[],"mappings":"AAcA,OAAO,EAA+B,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAQ/E,OAAO,EAKL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACxB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAe7E;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,EACnD,IAAI,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EACtB,KAAK,CAAC,EAAE,OAAO,6BAA6B,EAAE,YAAY,KACvD,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAEzB;;GAEG;AACH,qBAAa,sBAAsB,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO;;IAGtD;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAEzC;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAAC,CAAC;IAE3C;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAI9C;;;;;OAKG;gBACS,KAAK,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,iBAAiB;IAsB3E;;OAEG;IACH,KAAK,IAAI,IAAI;IAOb;;;;OAIG;IACH,MAAM,IAAI,OAAO;CAGlB;AAED;;GAEG;AACH,qBAAa,4BAA6B,YAAW,mBAAmB;;IAUpE,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAR9B;;;;;OAKG;gBAEgB,KAAK,EAAE,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,EAC3C,WAAW,EAAE,iBAAiB;IAGjD;;;;;OAKG;IACG,MAAM,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,EAAE,KAAK,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC;YA4BjF,GAAG;IAuBjB,OAAO,CAAC,OAAO;YAeD,SAAS;YAoBT,IAAI;YAgBJ,QAAQ;IAetB,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,KAAK;IAYb,OAAO,CAAC,YAAY;IAYpB;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,QAAQ;CAcjB"}
1
+ {"version":3,"file":"entity-history.d.ts","sourceRoot":"","sources":["../../src/datastore/entity-history.ts"],"names":[],"mappings":"AAcA,OAAO,EAA+B,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAQ/E,OAAO,EAKL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACxB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAe7E;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,EACnD,IAAI,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EACtB,KAAK,CAAC,EAAE,OAAO,6BAA6B,EAAE,YAAY,KACvD,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAEzB;;;;;GAKG;AACH,qBAAa,sBAAsB,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO;;IAGtD;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAEzC;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAAC,CAAC;IAE3C;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAM9C;;;;;OAKG;gBACS,KAAK,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,iBAAiB;IAsB3E;;OAEG;IACH,KAAK,IAAI,IAAI;IAOb;;;;OAIG;IACH,MAAM,IAAI,OAAO;CAGlB;AAED;;GAEG;AACH,qBAAa,4BAA6B,YAAW,mBAAmB;;IAUpE,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAR9B;;;;;OAKG;gBAEgB,KAAK,EAAE,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,EAC3C,WAAW,EAAE,iBAAiB;IAGjD;;;;;;;OAOG;IACG,MAAM,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,EAAE,KAAK,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BjF;;;;;;;;;;;OAWG;YACW,GAAG;IAwBjB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,OAAO;IAef;;;;;;;OAOG;YACW,SAAS;IAiBvB;;;;;;OAMG;YACW,IAAI;IAgBlB;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;;;;OAMG;IACH,OAAO,CAAC,KAAK;IAYb;;;;;;;OAOG;IACH,OAAO,CAAC,YAAY;IAYpB;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ;CAcjB"}
@@ -23,6 +23,9 @@ import { Datastore } from "@google-cloud/datastore";
23
23
  import { DatastoreRecordStorage } from "./record-storage.js";
24
24
  /**
25
25
  * Groups Entity current and optional history record families.
26
+ *
27
+ * @typeParam I Entity identifier type.
28
+ * @typeParam S Entity state message type.
26
29
  */
27
30
  export class DatastoreEntityStorage {
28
31
  // prettier-ignore
@@ -100,8 +103,10 @@ export class DatastoreEntityCommitStorage {
100
103
  /**
101
104
  * Commits one Entity state transition and its generated history records.
102
105
  *
106
+ * @typeParam I Entity identifier type.
107
+ * @typeParam S Entity state message type.
103
108
  * @param input The Entity mutation to commit.
104
- * @returns The committed or conflicting outcome.
109
+ * @returns Completion after Datastore commits the mutation.
105
110
  */
106
111
  async commit(input) {
107
112
  this.validate(input);
@@ -116,18 +121,31 @@ export class DatastoreEntityCommitStorage {
116
121
  : undefined;
117
122
  const events = this.openRecords(eventStoreRecordSpec);
118
123
  try {
119
- return await this.run(input, current, states, diagnostics, events);
124
+ await this.run(input, current, states, diagnostics, events);
120
125
  }
121
126
  finally {
122
127
  this.closeRecords(current, states, diagnostics, events);
123
128
  }
124
129
  }
130
+ /**
131
+ * Retries the prepared Datastore transaction after bounded aborts.
132
+ *
133
+ * @typeParam I Entity identifier type.
134
+ * @typeParam S Entity state type.
135
+ * @param input Supplies current and immutable records.
136
+ * @param current Provides current Entity storage.
137
+ * @param states Provides state history when enabled.
138
+ * @param diagnostics Provides diagnostic history when enabled.
139
+ * @param events Provides delivery Event storage.
140
+ * @returns Completion after a successful transaction.
141
+ */
125
142
  async run(input, current, states, diagnostics, events) {
126
143
  const prepared = this.prepare(input, current, states, diagnostics, events);
127
144
  validateCommitSize(prepared.map((row) => row.entity));
128
145
  for (let attempt = 0; attempt < 3; attempt += 1) {
129
146
  try {
130
- return await this.tryCommit(input, current, prepared);
147
+ await this.tryCommit(current, prepared);
148
+ return;
131
149
  }
132
150
  catch (error) {
133
151
  if (isAborted(error) && attempt < 2) {
@@ -139,6 +157,17 @@ export class DatastoreEntityCommitStorage {
139
157
  }
140
158
  throw new Error("Datastore transaction did not complete.");
141
159
  }
160
+ /**
161
+ * Builds affected rows before opening a transaction.
162
+ *
163
+ * @typeParam I Entity identifier type.
164
+ * @param input Supplies records to materialize.
165
+ * @param current Provides current Entity storage.
166
+ * @param states Provides state history when enabled.
167
+ * @param diagnostics Provides diagnostic history when enabled.
168
+ * @param events Provides delivery Event storage.
169
+ * @returns Deduplicated rows for the transaction.
170
+ */
142
171
  prepare(input, current, states, diagnostics, events) {
143
172
  return coalesceImmutableRows([
144
173
  ...preparedRows(current, [input.next], false),
@@ -147,23 +176,35 @@ export class DatastoreEntityCommitStorage {
147
176
  ...preparedRows(events, input.events ?? [], true),
148
177
  ]);
149
178
  }
150
- async tryCommit(input, current, prepared) {
179
+ /**
180
+ * Applies one prepared transaction attempt and rolls back on failure.
181
+ *
182
+ * @typeParam I Entity identifier type.
183
+ * @param current Opens a transaction for the Entity scope.
184
+ * @param prepared Supplies current and immutable rows.
185
+ * @returns Completion after the transaction commits.
186
+ */
187
+ async tryCommit(current, prepared) {
151
188
  const transaction = current.transaction();
152
189
  try {
153
190
  await transaction.run();
154
191
  const values = await this.load(transaction, prepared);
155
- if (await this.conflict(transaction, input, current, values))
156
- return "conflict";
157
192
  this.validateImmutable(prepared, values);
158
193
  this.apply(transaction, prepared, values);
159
194
  await transaction.commit();
160
- return "committed";
161
195
  }
162
196
  catch (error) {
163
197
  await rollback(transaction);
164
198
  throw error;
165
199
  }
166
200
  }
201
+ /**
202
+ * Loads affected keys in stable order within one transaction.
203
+ *
204
+ * @param transaction Supplies the active Datastore transaction.
205
+ * @param prepared Supplies affected rows.
206
+ * @returns Existing data indexed by key.
207
+ */
167
208
  async load(transaction, prepared) {
168
209
  const keys = [...uniqueKeys(prepared.map((row) => row.entity.key))].sort((left, right) => keyId(left).localeCompare(keyId(right)));
169
210
  const values = [];
@@ -174,14 +215,12 @@ export class DatastoreEntityCommitStorage {
174
215
  ]);
175
216
  return new Map(values);
176
217
  }
177
- async conflict(transaction, input, current, values) {
178
- const live = values.get(keyId(current.transactionEntity(input.next).key));
179
- const expected = input.expected === undefined ? undefined : current.transactionEntity(input.expected).data;
180
- if (sameData(live, expected) || sameData(live, current.transactionEntity(input.next).data))
181
- return false;
182
- await transaction.rollback();
183
- return true;
184
- }
218
+ /**
219
+ * Rejects divergent data already stored under immutable keys.
220
+ *
221
+ * @param prepared Supplies proposed immutable rows.
222
+ * @param values Supplies existing data for affected keys.
223
+ */
185
224
  validateImmutable(prepared, values) {
186
225
  for (const row of prepared.filter((item) => item.immutable)) {
187
226
  const previous = values.get(keyId(row.entity.key));
@@ -189,6 +228,13 @@ export class DatastoreEntityCommitStorage {
189
228
  throw new Error("Immutable history record has divergent content.");
190
229
  }
191
230
  }
231
+ /**
232
+ * Writes missing immutable rows and changed current state in the transaction.
233
+ *
234
+ * @param transaction Supplies the active Datastore transaction.
235
+ * @param prepared Supplies current and immutable rows.
236
+ * @param values Supplies existing data for affected keys.
237
+ */
192
238
  apply(transaction, prepared, values) {
193
239
  for (const row of prepared) {
194
240
  const previous = values.get(keyId(row.entity.key));
@@ -196,6 +242,14 @@ export class DatastoreEntityCommitStorage {
196
242
  transaction.save(row.entity);
197
243
  }
198
244
  }
245
+ /**
246
+ * Closes temporary record-family handles after the commit attempt.
247
+ *
248
+ * @param current Supplies current Entity storage.
249
+ * @param states Supplies state history when enabled.
250
+ * @param diagnostics Supplies diagnostic history when enabled.
251
+ * @param events Supplies delivery Event storage.
252
+ */
199
253
  closeRecords(current, states, diagnostics, events) {
200
254
  current.close();
201
255
  states?.close();
@@ -208,6 +262,13 @@ export class DatastoreEntityCommitStorage {
208
262
  close() {
209
263
  this.#open = false;
210
264
  }
265
+ /**
266
+ * Validates the Entity, tenant, history, and Event boundaries of a commit.
267
+ *
268
+ * @typeParam I Entity identifier type.
269
+ * @typeParam S Entity state type.
270
+ * @param input Supplies the proposed commit.
271
+ */
211
272
  validate(input) {
212
273
  if (!this.#open)
213
274
  throw new Error("Entity commit storage is closed.");
@@ -222,22 +283,52 @@ export class DatastoreEntityCommitStorage {
222
283
  validateEvents(input.events ?? []);
223
284
  }
224
285
  }
286
+ /**
287
+ * Provides the current Entity records through one record-family handle.
288
+ *
289
+ * @typeParam I Entity identifier type.
290
+ * @typeParam S Entity state message type.
291
+ */
225
292
  class CurrentStorage {
226
293
  input;
227
294
  records;
295
+ /**
296
+ * Creates current-record access for one Entity storage contract.
297
+ *
298
+ * @param input Defines the Entity identity and columns.
299
+ * @param records Provides current Entity record storage.
300
+ */
228
301
  constructor(input, records) {
229
302
  this.input = input;
230
303
  this.records = records;
231
304
  }
305
+ /**
306
+ * Reads one current Entity record.
307
+ *
308
+ * @param id Identifies the Entity.
309
+ * @returns The current record, or undefined when none is stored.
310
+ */
232
311
  read(id) {
233
312
  return this.records.read(id);
234
313
  }
314
+ /**
315
+ * Stores the current record for its Entity.
316
+ *
317
+ * @param record Supplies the current Entity record.
318
+ * @returns Completion after the record is stored.
319
+ */
235
320
  async write(record) {
236
321
  const id = record.entityId === undefined ? undefined : this.input.id.unpack(record.entityId);
237
322
  if (id === undefined)
238
323
  throw new Error("Entity current record ID does not match its Entity ID schema.");
239
324
  await this.records.write(record);
240
325
  }
326
+ /**
327
+ * Returns current Entity records and their configured columns.
328
+ *
329
+ * @param plan Specifies the normalized query.
330
+ * @returns Matching Entity records and column values.
331
+ */
241
332
  async query(plan) {
242
333
  return (await this.records.queryPlanEntries(plan)).map((entry) => ({
243
334
  ...entry,
@@ -245,17 +336,43 @@ class CurrentStorage {
245
336
  }));
246
337
  }
247
338
  }
339
+ /**
340
+ * Provides retained Entity state history through one record-family handle.
341
+ *
342
+ * @typeParam I Entity identifier type.
343
+ * @typeParam S Entity state message type.
344
+ */
248
345
  class StateHistory {
249
346
  input;
250
347
  records;
348
+ /**
349
+ * Creates state-history access for one Entity storage contract.
350
+ *
351
+ * @param input Defines the Entity identity and state schema.
352
+ * @param records Provides retained state-history records.
353
+ */
251
354
  constructor(input, records) {
252
355
  this.input = input;
253
356
  this.records = records;
254
357
  }
358
+ /**
359
+ * Stores one immutable state-history record.
360
+ *
361
+ * @param record Supplies the state-history record.
362
+ * @returns Completion after the record is retained.
363
+ */
255
364
  append(record) {
256
365
  this.requireOpen();
257
366
  return immutable(this.records, record);
258
367
  }
368
+ /**
369
+ * Reads recent state-history records in reverse order.
370
+ *
371
+ * @param id Identifies the Entity.
372
+ * @param depth Sets the maximum number of records to read.
373
+ * @param from Sets an optional upper version bound.
374
+ * @returns Matching records from newest to oldest.
375
+ */
259
376
  async backward(id, depth, from) {
260
377
  this.requireOpen();
261
378
  requireDepth(depth);
@@ -267,6 +384,13 @@ class StateHistory {
267
384
  { property: "created", direction: "desc" },
268
385
  ], depth);
269
386
  }
387
+ /**
388
+ * Reads the Entity state recorded at or before a time.
389
+ *
390
+ * @param id Identifies the Entity.
391
+ * @param time Sets the latest creation time to include.
392
+ * @returns The matching state, or undefined when none exists.
393
+ */
270
394
  async stateAt(id, time) {
271
395
  this.requireOpen();
272
396
  const page = await this.provider().queryProviderPage({
@@ -282,6 +406,13 @@ class StateHistory {
282
406
  ? undefined
283
407
  : fromBinary(this.input.stateSchema, found.state.value);
284
408
  }
409
+ /**
410
+ * Stores no more than the newest state-history records for an Entity.
411
+ *
412
+ * @param id Identifies the Entity.
413
+ * @param keep Sets the number of newest records to retain.
414
+ * @returns Completion after older records are removed.
415
+ */
285
416
  async trim(id, keep) {
286
417
  this.requireOpen();
287
418
  requireKeep(keep);
@@ -300,6 +431,12 @@ class StateHistory {
300
431
  break;
301
432
  }
302
433
  }
434
+ /**
435
+ * Removes state-history records created before a time.
436
+ *
437
+ * @param time Sets the exclusive creation-time boundary.
438
+ * @returns Completion after matching records are removed.
439
+ */
303
440
  async truncate(time) {
304
441
  this.requireOpen();
305
442
  for (;;) {
@@ -316,16 +453,35 @@ class StateHistory {
316
453
  break;
317
454
  }
318
455
  }
456
+ /**
457
+ * Closes the state-history record handle.
458
+ */
319
459
  close() {
320
460
  this.records.close();
321
461
  }
462
+ /**
463
+ * Returns the Datastore provider for state-history queries.
464
+ *
465
+ * @returns The typed Datastore record-storage handle.
466
+ */
322
467
  provider() {
323
468
  return this.records;
324
469
  }
470
+ /**
471
+ * Rejects operations after the record handle closes.
472
+ */
325
473
  requireOpen() {
326
474
  if (!this.records.isOpen())
327
475
  throw new Error("Entity history storage is closed.");
328
476
  }
477
+ /**
478
+ * Reads a bounded sequence of matching state-history pages.
479
+ *
480
+ * @param filters Specifies the Datastore filters.
481
+ * @param order Specifies the Datastore sort order.
482
+ * @param depth Sets the maximum number of records to read.
483
+ * @returns Matching records in page order.
484
+ */
329
485
  async pages(filters, order, depth) {
330
486
  const result = [];
331
487
  let cursor;
@@ -344,17 +500,43 @@ class StateHistory {
344
500
  return result;
345
501
  }
346
502
  }
503
+ /**
504
+ * Provides retained Entity Event history through one record-family handle.
505
+ *
506
+ * @typeParam I Entity identifier type.
507
+ * @typeParam S Entity state message type.
508
+ */
347
509
  class EventHistory {
348
510
  input;
349
511
  records;
512
+ /**
513
+ * Creates Event-history access for one Entity storage contract.
514
+ *
515
+ * @param input Defines the Entity identity and state schema.
516
+ * @param records Provides retained Event-history records.
517
+ */
350
518
  constructor(input, records) {
351
519
  this.input = input;
352
520
  this.records = records;
353
521
  }
522
+ /**
523
+ * Stores one immutable Entity Event-history record.
524
+ *
525
+ * @param event Supplies the Event-history record.
526
+ * @returns Completion after the Event is retained.
527
+ */
354
528
  append(event) {
355
529
  this.requireOpen();
356
530
  return immutable(this.records, event);
357
531
  }
532
+ /**
533
+ * Reads recent Entity Events in reverse order.
534
+ *
535
+ * @param id Identifies the Entity.
536
+ * @param depth Sets the maximum number of Events to read.
537
+ * @param from Sets an optional upper version bound.
538
+ * @returns Matching Events from newest to oldest.
539
+ */
358
540
  async backward(id, depth, from) {
359
541
  this.requireOpen();
360
542
  requireDepth(depth);
@@ -380,6 +562,12 @@ class EventHistory {
380
562
  }
381
563
  return result;
382
564
  }
565
+ /**
566
+ * Removes Entity Events created before a time.
567
+ *
568
+ * @param time Sets the exclusive creation-time boundary.
569
+ * @returns Completion after matching Events are removed.
570
+ */
383
571
  async truncate(time) {
384
572
  this.requireOpen();
385
573
  for (;;) {
@@ -396,17 +584,37 @@ class EventHistory {
396
584
  break;
397
585
  }
398
586
  }
587
+ /**
588
+ * Closes the Event-history record handle.
589
+ */
399
590
  close() {
400
591
  this.records.close();
401
592
  }
593
+ /**
594
+ * Returns the Datastore provider for Event-history queries.
595
+ *
596
+ * @returns The typed Datastore record-storage handle.
597
+ */
402
598
  provider() {
403
599
  return this.records;
404
600
  }
601
+ /**
602
+ * Rejects operations after the record handle closes.
603
+ */
405
604
  requireOpen() {
406
605
  if (!this.records.isOpen())
407
606
  throw new Error("Entity history storage is closed.");
408
607
  }
409
608
  }
609
+ /**
610
+ * Stores a history record once and rejects divergent content for its ID.
611
+ *
612
+ * @typeParam I Record identifier type.
613
+ * @typeParam R Stored Protobuf message type.
614
+ * @param records Provides immutable history record storage.
615
+ * @param record Supplies the history record to retain.
616
+ * @returns Completion after the record is present.
617
+ */
410
618
  async function immutable(records, record) {
411
619
  const id = records.recordSpec.idValueIn(record);
412
620
  if (await records.compareAndSet(id, undefined, record))
@@ -417,6 +625,16 @@ async function immutable(records, record) {
417
625
  if (!same(toBinary(records.recordSpec.recordType, existing), toBinary(records.recordSpec.recordType, record)))
418
626
  throw new Error("Immutable history record has divergent content.");
419
627
  }
628
+ /**
629
+ * Prepares Datastore rows for one record family.
630
+ *
631
+ * @typeParam I Record identifier type.
632
+ * @typeParam R Stored Protobuf message type.
633
+ * @param storage Provides the record-family handle when enabled.
634
+ * @param records Supplies records to prepare.
635
+ * @param immutable Marks whether existing rows must remain unchanged.
636
+ * @returns The prepared rows for the transaction.
637
+ */
420
638
  function preparedRows(storage, records, immutable) {
421
639
  if (storage === undefined) {
422
640
  if (records.length > 0)
@@ -430,6 +648,13 @@ function preparedRows(storage, records, immutable) {
430
648
  entity: storage.transactionEntity(record),
431
649
  }));
432
650
  }
651
+ /**
652
+ * Removes duplicate immutable rows and rejects conflicting content.
653
+ *
654
+ * @typeParam R Prepared row type.
655
+ * @param rows Supplies prepared current and history rows.
656
+ * @returns Rows with duplicate immutable keys removed.
657
+ */
433
658
  function coalesceImmutableRows(rows) {
434
659
  const seen = new Map();
435
660
  return rows.filter((row) => {
@@ -456,6 +681,15 @@ function requireKeep(value) {
456
681
  if (!Number.isSafeInteger(value) || value < 0)
457
682
  throw new Error("History retention must be a non-negative safe integer.");
458
683
  }
684
+ /**
685
+ * Creates a Datastore filter for one Entity identifier.
686
+ *
687
+ * @typeParam I Entity identifier type.
688
+ * @typeParam S Entity state message type.
689
+ * @param input Defines the Entity ID schema.
690
+ * @param id Identifies the Entity to match.
691
+ * @returns The Datastore Entity ID filter.
692
+ */
459
693
  function entityFilter(input, id) {
460
694
  return { property: "entity_id", operator: "=", value: input.id.pack(id) };
461
695
  }
@@ -492,6 +726,12 @@ function abortBackoff(attempt) {
492
726
  const delayMs = 20 * (attempt + 1) + Math.floor(Math.random() * 40);
493
727
  return new Promise((resolve) => setTimeout(resolve, delayMs));
494
728
  }
729
+ /**
730
+ * Rolls back a failed Datastore transaction when possible.
731
+ *
732
+ * @param transaction Supplies the transaction to roll back.
733
+ * @returns Completion after rollback finishes or fails.
734
+ */
495
735
  async function rollback(transaction) {
496
736
  try {
497
737
  await transaction.rollback();
@@ -520,6 +760,13 @@ function validateEvents(events) {
520
760
  new Set(ids).size !== ids.length)
521
761
  throw new Error("Entity commit requires non-blank unique delivery-event IDs.");
522
762
  }
763
+ /**
764
+ * Checks that current and history records identify the committed Entity.
765
+ *
766
+ * @typeParam I Entity identifier type.
767
+ * @typeParam S Entity state message type.
768
+ * @param input Supplies the Entity commit to check.
769
+ */
523
770
  function validateCommitEntityId(input) {
524
771
  const nextId = input.next.entityId === undefined ? undefined : input.entity.id.unpack(input.next.entityId);
525
772
  if (nextId === undefined || input.entity.id.key(nextId) !== input.entity.id.key(input.entityId))