@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
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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))
|