@spine-event-engine/storage 2.0.0-snapshot.23 → 2.0.0-snapshot.25
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/README.md +12 -3
- package/REFERENCE.md +90 -63
- package/dist/entity/agent-history-record-spec.d.ts +8 -0
- package/dist/entity/agent-history-record-spec.d.ts.map +1 -1
- package/dist/entity/agent-history-record-spec.js +12 -1
- package/dist/entity/agent-history-record-spec.js.map +1 -1
- package/dist/entity/agent-history.d.ts +25 -0
- package/dist/entity/agent-history.d.ts.map +1 -1
- package/dist/entity/agent-history.js.map +1 -1
- package/dist/entity/history-conformance.d.ts +38 -8
- package/dist/entity/history-conformance.d.ts.map +1 -1
- package/dist/entity/history-conformance.js +103 -58
- package/dist/entity/history-conformance.js.map +1 -1
- package/dist/event/event-store.d.ts +80 -74
- package/dist/event/event-store.d.ts.map +1 -1
- package/dist/event/event-store.js +264 -264
- package/dist/event/event-store.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/agent-model-preferences.d.ts +76 -0
- package/dist/internal/agent-model-preferences.d.ts.map +1 -0
- package/dist/internal/agent-model-preferences.js +133 -0
- package/dist/internal/agent-model-preferences.js.map +1 -0
- package/dist/internal/entity-commit.d.ts +92 -17
- package/dist/internal/entity-commit.d.ts.map +1 -1
- package/dist/internal/entity-commit.js +45 -2
- package/dist/internal/entity-commit.js.map +1 -1
- package/dist/internal/entity-history.d.ts +1 -7
- package/dist/internal/entity-history.d.ts.map +1 -1
- package/dist/internal/entity-history.js +0 -5
- package/dist/internal/entity-history.js.map +1 -1
- package/dist/internal/event-store.d.ts +1 -1
- package/dist/internal/event-store.d.ts.map +1 -1
- package/dist/internal/event-store.js +1 -1
- package/dist/internal/event-store.js.map +1 -1
- package/dist/internal/storage-preparation.d.ts +88 -0
- package/dist/internal/storage-preparation.d.ts.map +1 -0
- package/dist/internal/storage-preparation.js +41 -0
- package/dist/internal/storage-preparation.js.map +1 -0
- package/dist/internal/tenancy.d.ts +7 -0
- package/dist/internal/tenancy.d.ts.map +1 -1
- package/dist/memory/in-memory-agent-history.d.ts.map +1 -1
- package/dist/memory/in-memory-agent-history.js +22 -7
- package/dist/memory/in-memory-agent-history.js.map +1 -1
- package/dist/memory/in-memory-entity-commit.d.ts +237 -42
- package/dist/memory/in-memory-entity-commit.d.ts.map +1 -1
- package/dist/memory/in-memory-entity-commit.js +529 -325
- package/dist/memory/in-memory-entity-commit.js.map +1 -1
- package/dist/memory/in-memory-entity-history.d.ts.map +1 -1
- package/dist/memory/in-memory-entity-history.js +4 -3
- package/dist/memory/in-memory-entity-history.js.map +1 -1
- package/dist/memory/in-memory-storage-backend.d.ts +33 -1
- package/dist/memory/in-memory-storage-backend.d.ts.map +1 -1
- package/dist/memory/in-memory-storage-backend.js +118 -1
- package/dist/memory/in-memory-storage-backend.js.map +1 -1
- package/dist/memory/in-memory-storage-factory.d.ts +72 -13
- package/dist/memory/in-memory-storage-factory.d.ts.map +1 -1
- package/dist/memory/in-memory-storage-factory.js +199 -30
- package/dist/memory/in-memory-storage-factory.js.map +1 -1
- package/dist/memory/tenant-records.d.ts +1 -1
- package/dist/memory/tenant-records.d.ts.map +1 -1
- package/dist/memory/tenant-records.js +3 -2
- package/dist/memory/tenant-records.js.map +1 -1
- package/dist/provider.d.ts +2 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +2 -1
- package/dist/provider.js.map +1 -1
- package/dist/record/record-query.d.ts +10 -2
- package/dist/record/record-query.d.ts.map +1 -1
- package/dist/record/record-query.js +9 -0
- package/dist/record/record-query.js.map +1 -1
- package/dist/storage/storage-factory.d.ts +27 -4
- package/dist/storage/storage-factory.d.ts.map +1 -1
- package/dist/storage/storage-factory.js +30 -1
- package/dist/storage/storage-factory.js.map +1 -1
- package/dist/storage/storage.d.ts +2 -1
- package/dist/storage/storage.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/dist/entity/agent-execution-record-spec.d.ts +0 -87
- package/dist/entity/agent-execution-record-spec.d.ts.map +0 -1
- package/dist/entity/agent-execution-record-spec.js +0 -230
- package/dist/entity/agent-execution-record-spec.js.map +0 -1
- package/dist/entity/agent-execution-sizes.d.ts +0 -30
- package/dist/entity/agent-execution-sizes.d.ts.map +0 -1
- package/dist/entity/agent-execution-sizes.js +0 -51
- package/dist/entity/agent-execution-sizes.js.map +0 -1
- package/dist/entity/agent-execution-transitions.d.ts +0 -71
- package/dist/entity/agent-execution-transitions.d.ts.map +0 -1
- package/dist/entity/agent-execution-transitions.js +0 -182
- package/dist/entity/agent-execution-transitions.js.map +0 -1
- package/dist/entity/agent-execution-values.d.ts +0 -142
- package/dist/entity/agent-execution-values.d.ts.map +0 -1
- package/dist/entity/agent-execution-values.js +0 -250
- package/dist/entity/agent-execution-values.js.map +0 -1
- package/dist/entity/agent-execution.d.ts +0 -234
- package/dist/entity/agent-execution.d.ts.map +0 -1
- package/dist/entity/agent-execution.js +0 -15
- package/dist/entity/agent-execution.js.map +0 -1
- package/dist/internal/agent-execution.d.ts +0 -51
- package/dist/internal/agent-execution.d.ts.map +0 -1
- package/dist/internal/agent-execution.js +0 -53
- package/dist/internal/agent-execution.js.map +0 -1
- package/dist/internal/delivery-cleanup.d.ts +0 -99
- package/dist/internal/delivery-cleanup.d.ts.map +0 -1
- package/dist/internal/delivery-cleanup.js +0 -38
- package/dist/internal/delivery-cleanup.js.map +0 -1
- package/dist/memory/agent-execution-head-index.d.ts +0 -52
- package/dist/memory/agent-execution-head-index.d.ts.map +0 -1
- package/dist/memory/agent-execution-head-index.js +0 -91
- package/dist/memory/agent-execution-head-index.js.map +0 -1
- package/dist/memory/in-memory-agent-execution.d.ts +0 -21
- package/dist/memory/in-memory-agent-execution.d.ts.map +0 -1
- package/dist/memory/in-memory-agent-execution.js +0 -587
- package/dist/memory/in-memory-agent-execution.js.map +0 -1
- package/dist/memory/memory-delivery-cleanup.d.ts +0 -32
- package/dist/memory/memory-delivery-cleanup.d.ts.map +0 -1
- package/dist/memory/memory-delivery-cleanup.js +0 -61
- package/dist/memory/memory-delivery-cleanup.js.map +0 -1
|
@@ -11,430 +11,634 @@
|
|
|
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 { clone, toBinary } from "@bufbuild/protobuf";
|
|
15
15
|
import { EventSchema } from "@spine-event-engine/proto";
|
|
16
|
+
import { AgentHistoryEntrySchema, ModelPreferenceSchema, } from "@spine-event-engine/proto/agent";
|
|
16
17
|
import { EntityRecordSchema } from "@spine-event-engine/proto/generated/spine/server/entity/entity_pb.js";
|
|
17
|
-
import {
|
|
18
|
+
import { eventStoreRecordSpec, EventStoreGroups } from "../event/event-store.js";
|
|
18
19
|
import { eventHistorySpec, stateHistorySpec } from "../entity/entity-history-record-spec.js";
|
|
20
|
+
import { EntityCommitSnapshots, } from "../internal/entity-commit.js";
|
|
21
|
+
import { AgentModelPreferences, } from "../internal/agent-model-preferences.js";
|
|
19
22
|
import { TenantBoundary } from "../internal/tenancy.js";
|
|
20
|
-
import {
|
|
23
|
+
import { MemoryEntityStorageFactory, ENTITY_SCOPE_MUTATION_KEY, InMemoryEntityHistory, MemoryEntityEventHistory, } from "./in-memory-entity-history.js";
|
|
21
24
|
import { InMemoryRecordStorage } from "./in-memory-record-storage.js";
|
|
22
25
|
import { TenantRecords } from "./tenant-records.js";
|
|
26
|
+
import { AgentHistoryIndex } from "./agent-history-index.js";
|
|
27
|
+
import { AgentHistoryMutationQueue } from "./agent-history-mutation-queue.js";
|
|
28
|
+
import { InMemoryStorageBackend } from "./in-memory-storage-backend.js";
|
|
29
|
+
import { KeyedSerialQueue } from "./in-memory-entity-history.js";
|
|
23
30
|
/**
|
|
24
|
-
*
|
|
31
|
+
* Writes in-memory Entity records in order and retains a completed prefix.
|
|
25
32
|
*/
|
|
26
33
|
export class MemoryEntityCommitStorage {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
entities;
|
|
35
|
+
openRecords;
|
|
36
|
+
input;
|
|
37
|
+
backend;
|
|
38
|
+
onClose;
|
|
39
|
+
/**
|
|
40
|
+
* Tenant boundary selected by this handle.
|
|
41
|
+
*/
|
|
31
42
|
#tenantKey;
|
|
43
|
+
/**
|
|
44
|
+
* Whether this handle uses multitenant storage.
|
|
45
|
+
*/
|
|
32
46
|
#multitenant;
|
|
47
|
+
/**
|
|
48
|
+
* Whether this handle accepts commits.
|
|
49
|
+
*/
|
|
33
50
|
#open = true;
|
|
51
|
+
#active = new Set();
|
|
52
|
+
#agent;
|
|
53
|
+
/**
|
|
54
|
+
* Repeatable asynchronous close completion.
|
|
55
|
+
*/
|
|
56
|
+
#closing;
|
|
57
|
+
#idleOutcome;
|
|
58
|
+
#entity;
|
|
59
|
+
/**
|
|
60
|
+
* Retained state-history records.
|
|
61
|
+
*/
|
|
62
|
+
#states;
|
|
63
|
+
#diagnostics;
|
|
64
|
+
#delivery;
|
|
34
65
|
/**
|
|
35
|
-
* Creates
|
|
66
|
+
* Creates one source- and tenant-bound commit handle.
|
|
36
67
|
*
|
|
37
|
-
* @param entities Opens
|
|
38
|
-
* @param
|
|
39
|
-
* @param
|
|
40
|
-
* @param
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this
|
|
45
|
-
this
|
|
46
|
-
this
|
|
68
|
+
* @param entities Opens current Entity storage.
|
|
69
|
+
* @param openRecords Opens generated history and Event record slices.
|
|
70
|
+
* @param input Captures the Entity source and tenant boundary.
|
|
71
|
+
* @param backend Shared tenant memory families for preferences and audit.
|
|
72
|
+
* @param onClose Releases factory tracking after terminal cleanup.
|
|
73
|
+
*/
|
|
74
|
+
constructor(entities, openRecords, input, backend, onClose = () => undefined) {
|
|
75
|
+
this.entities = entities;
|
|
76
|
+
this.openRecords = openRecords;
|
|
77
|
+
this.input = input;
|
|
78
|
+
this.backend = backend;
|
|
79
|
+
this.onClose = onClose;
|
|
47
80
|
this.#tenantKey = TenantBoundary.of(input.context).key;
|
|
48
81
|
this.#multitenant = input.context.multitenant;
|
|
49
82
|
}
|
|
50
83
|
/**
|
|
51
|
-
*
|
|
84
|
+
* Binds shared Agent families only when this handle performs Agent work.
|
|
85
|
+
* @returns Cached preference, audit, and mutation-queue families.
|
|
86
|
+
*/
|
|
87
|
+
agentFamilies() {
|
|
88
|
+
if (this.#agent !== undefined)
|
|
89
|
+
return this.#agent;
|
|
90
|
+
const tenant = TenantBoundary.of(this.input.context);
|
|
91
|
+
this.#agent = {
|
|
92
|
+
preferences: InMemoryStorageBackend.bind(this.backend, "entity", tenant, `agent-preferences:${this.input.stateSchema.typeName}`, () => new Map()),
|
|
93
|
+
history: InMemoryStorageBackend.bind(this.backend, "entity", tenant, `agent-history:${this.input.stateSchema.typeName}`, () => new Map()),
|
|
94
|
+
queue: AgentHistoryMutationQueue.bind(this.backend, tenant),
|
|
95
|
+
};
|
|
96
|
+
return this.#agent;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Writes history, diagnostics, delivery Events, then current state.
|
|
52
100
|
*
|
|
53
101
|
* @typeParam I Entity identifier type.
|
|
54
102
|
* @typeParam S Entity state type.
|
|
55
|
-
* @param input
|
|
56
|
-
* @returns Completion after
|
|
103
|
+
* @param input Supplies the proposed mutation.
|
|
104
|
+
* @returns Completion after every record has been stored.
|
|
57
105
|
*/
|
|
58
106
|
commit(input) {
|
|
59
|
-
this
|
|
60
|
-
this
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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);
|
|
107
|
+
this.requireOpen();
|
|
108
|
+
this.requireCompatible(input);
|
|
109
|
+
return this.track(() => {
|
|
110
|
+
const snapshot = EntityCommitSnapshots.capture(input);
|
|
111
|
+
return this.apply(snapshot);
|
|
112
|
+
});
|
|
86
113
|
}
|
|
87
114
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* @
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
+
* Reads a detached current preference image from the retained tenant family.
|
|
116
|
+
* @param entityId Typed Agent identifier.
|
|
117
|
+
* @returns Complete selections or absent record.
|
|
118
|
+
*/
|
|
119
|
+
readAgentPreferences(entityId) {
|
|
120
|
+
this.requireOpen();
|
|
121
|
+
return this.track(async () => {
|
|
122
|
+
const key = AgentModelPreferences.key(this.input, entityId);
|
|
123
|
+
const record = this.agentFamilies().preferences.get(await this.preferenceSlot(key));
|
|
124
|
+
if (record === undefined)
|
|
125
|
+
return undefined;
|
|
126
|
+
AgentModelPreferences.requireIdentity(record, key);
|
|
127
|
+
return AgentModelPreferences.copy(record.preferences);
|
|
115
128
|
});
|
|
116
|
-
return input?.events?.length
|
|
117
|
-
? eventStoreAccess.withLock(this.#factory, entity.context, work)
|
|
118
|
-
: work();
|
|
119
129
|
}
|
|
120
130
|
/**
|
|
121
|
-
*
|
|
131
|
+
* Commits Agent state, final audit, and preferences under shared memory queues.
|
|
132
|
+
* @param input Captured initial images and prepared transition.
|
|
133
|
+
* @returns Completion after one conditional group.
|
|
134
|
+
* @typeParam I Typed Agent identifier.
|
|
135
|
+
* @typeParam S Generated Agent state.
|
|
122
136
|
*/
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
137
|
+
commitAgent(input) {
|
|
138
|
+
this.requireOpen();
|
|
139
|
+
return this.track(async () => {
|
|
140
|
+
const entityId = this.input.id.clone(input.entityId);
|
|
141
|
+
const key = AgentModelPreferences.key(this.input, entityId);
|
|
142
|
+
const expected = input.expectedPreferences?.map((item) => clone(ModelPreferenceSchema, item));
|
|
143
|
+
const next = AgentModelPreferences.record(key, input.preferences);
|
|
144
|
+
const entityCommit = input.entityCommit &&
|
|
145
|
+
EntityCommitSnapshots.capture(input.entityCommit);
|
|
146
|
+
const history = input.historyEntries?.map((entry) => clone(AgentHistoryEntrySchema, entry)) ?? [];
|
|
147
|
+
this.validateAgentCommit(entityId, input.expectedVersion, entityCommit);
|
|
148
|
+
const agentKey = this.input.id.key(entityId);
|
|
149
|
+
const slot = await this.preferenceSlot(key);
|
|
150
|
+
return this.agentFamilies().queue.run(agentKey, () => this.commitAgentLocked(entityId, slot, input.expectedVersion, expected, next, entityCommit, history));
|
|
151
|
+
});
|
|
128
152
|
}
|
|
129
153
|
/**
|
|
130
|
-
*
|
|
154
|
+
* Validates the fixed Entity identity and one-version transition.
|
|
155
|
+
* @param entityId Captured Agent identifier.
|
|
156
|
+
* @param expectedVersion Initial Entity Version.
|
|
157
|
+
* @param entityCommit Optional prepared current-state mutation.
|
|
131
158
|
*/
|
|
132
|
-
|
|
133
|
-
|
|
159
|
+
validateAgentCommit(entityId, expectedVersion, entityCommit) {
|
|
160
|
+
if (!Number.isSafeInteger(expectedVersion) || expectedVersion < 0)
|
|
161
|
+
throw new TypeError("Agent commit requires a valid initial Entity Version.");
|
|
162
|
+
if (entityCommit === undefined)
|
|
163
|
+
return;
|
|
164
|
+
this.requireCompatible(entityCommit);
|
|
165
|
+
if (this.input.id.key(entityId) !== this.input.id.key(entityCommit.entityId) ||
|
|
166
|
+
entityCommit.next.version?.number !== expectedVersion + 1)
|
|
167
|
+
throw new Error("Agent commit requires the same Entity ID and one new Version.");
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Records final audit and preference replacements inside the Entity mutation.
|
|
171
|
+
* @param entityId Captured Agent identifier.
|
|
172
|
+
* @param slot Bounded preference record slot.
|
|
173
|
+
* @param expectedVersion Initial Entity Version.
|
|
174
|
+
* @param expected Initial preference image.
|
|
175
|
+
* @param next Complete next preference record.
|
|
176
|
+
* @param entityCommit Optional prepared current-state mutation.
|
|
177
|
+
* @param history Captured final audit rows.
|
|
178
|
+
* @returns Completion after one conditional group.
|
|
179
|
+
*/
|
|
180
|
+
commitAgentLocked(entityId, slot, expectedVersion, expected, next, entityCommit, history) {
|
|
181
|
+
const agentKey = this.input.id.key(entityId);
|
|
182
|
+
const priorIndex = this.agentFamilies().history.get(agentKey);
|
|
183
|
+
const index = priorIndex ?? new AgentHistoryIndex();
|
|
184
|
+
const staged = index.withEntries(history);
|
|
185
|
+
const prior = this.agentFamilies().preferences.get(slot);
|
|
186
|
+
const extra = this.agentPublication(slot, next, expected, prior, agentKey, index, priorIndex, staged);
|
|
187
|
+
return this.runAgentConditional(this.input, entityId, expectedVersion, entityCommit, extra);
|
|
134
188
|
}
|
|
135
189
|
/**
|
|
136
|
-
*
|
|
190
|
+
* Replaces preference and final audit rows with rollback for failed Entity work.
|
|
191
|
+
* @param slot Bounded preference record slot.
|
|
192
|
+
* @param next Complete next preference record.
|
|
193
|
+
* @param expected Initial preference image.
|
|
194
|
+
* @param prior Previously retained preference record.
|
|
195
|
+
* @param agentKey Canonical Agent identifier.
|
|
196
|
+
* @param index Current or new audit index.
|
|
197
|
+
* @param priorIndex Previously retained audit index.
|
|
198
|
+
* @param staged Reversible final audit batch.
|
|
199
|
+
* @returns Reversible publication callbacks.
|
|
200
|
+
*/
|
|
201
|
+
agentPublication(slot, next, expected, prior, agentKey, index, priorIndex, staged) {
|
|
202
|
+
return {
|
|
203
|
+
apply: () => {
|
|
204
|
+
if (next.key === undefined)
|
|
205
|
+
throw new TypeError("Agent preference record requires identity.");
|
|
206
|
+
if (prior !== undefined)
|
|
207
|
+
AgentModelPreferences.requireIdentity(prior, next.key);
|
|
208
|
+
if (!AgentModelPreferences.same(prior?.preferences, expected))
|
|
209
|
+
throw new Error("Agent model preference image conflict.");
|
|
210
|
+
staged.apply();
|
|
211
|
+
this.agentFamilies().history.set(agentKey, index);
|
|
212
|
+
this.agentFamilies().preferences.set(slot, next);
|
|
213
|
+
},
|
|
214
|
+
restore: () => {
|
|
215
|
+
staged.restore();
|
|
216
|
+
if (priorIndex === undefined)
|
|
217
|
+
this.agentFamilies().history.delete(agentKey);
|
|
218
|
+
if (prior === undefined)
|
|
219
|
+
this.agentFamilies().preferences.delete(slot);
|
|
220
|
+
else
|
|
221
|
+
this.agentFamilies().preferences.set(slot, prior);
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Calculates a bounded v1 slot from complete preference identity evidence.
|
|
227
|
+
* @param key Complete generated Agent identity.
|
|
228
|
+
* @returns Fixed 67-character provider slot.
|
|
229
|
+
*/
|
|
230
|
+
async preferenceSlot(key) {
|
|
231
|
+
const runtime = globalThis;
|
|
232
|
+
const bytes = new runtime.TextEncoder().encode(AgentModelPreferences.text(key));
|
|
233
|
+
const hash = new Uint8Array(await runtime.crypto.subtle.digest("SHA-256", bytes));
|
|
234
|
+
return `v1:${Array.from(hash, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Performs the captured conditional Agent change.
|
|
238
|
+
*
|
|
239
|
+
* @param entity The Entity persistence contract.
|
|
240
|
+
* @param entityId The captured Entity identifier.
|
|
241
|
+
* @param expectedVersion The required prior Entity version.
|
|
242
|
+
* @param extra The additional conditional records.
|
|
243
|
+
* @param input The captured Entity persistence input.
|
|
244
|
+
* @returns Completion after the conditional Agent change settles.
|
|
245
|
+
* @typeParam I Entity identifier or record type.
|
|
246
|
+
* @typeParam S Generated Entity state message type.
|
|
247
|
+
*/
|
|
248
|
+
runAgentConditional(entity, entityId, expectedVersion, input, extra) {
|
|
249
|
+
const backend = this.entities.backend(entity);
|
|
250
|
+
const key = entity.id.key(entityId);
|
|
251
|
+
const snapshot = input === undefined ? undefined : EntityCommitSnapshots.capture(input);
|
|
252
|
+
if (snapshot !== undefined) {
|
|
253
|
+
this.requireCompatible(snapshot);
|
|
254
|
+
this.requireEnabledHistories(snapshot);
|
|
255
|
+
this.validateRows(snapshot);
|
|
256
|
+
if (snapshot.diagnostics?.length || snapshot.events?.length)
|
|
257
|
+
throw new Error("Agent completion cannot save delivery Events as Entity commit obligations.");
|
|
258
|
+
}
|
|
259
|
+
return backend.mutationQueue.run(ENTITY_SCOPE_MUTATION_KEY, () => this.applyAgentConditional(entity, backend, key, expectedVersion, snapshot, extra));
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Applies one prepared Agent transition and restores affected rows on failure.
|
|
137
263
|
*
|
|
138
264
|
* @typeParam I Entity identifier type.
|
|
139
265
|
* @typeParam S Entity state type.
|
|
140
|
-
* @param
|
|
141
|
-
* @param
|
|
266
|
+
* @param entity Configured Entity storage layout.
|
|
267
|
+
* @param backend Shared current records for this Entity scope.
|
|
268
|
+
* @param key Canonical Entity ID key.
|
|
269
|
+
* @param expectedVersion Version read before handler execution.
|
|
270
|
+
* @param snapshot Optional captured Entity state transition.
|
|
271
|
+
* @param extra Applies or restores Agent audit records and model preferences.
|
|
272
|
+
* @returns Completion after the transition is stored.
|
|
142
273
|
*/
|
|
143
|
-
|
|
144
|
-
const
|
|
274
|
+
applyAgentConditional(entity, backend, key, expectedVersion, snapshot, extra) {
|
|
275
|
+
const prior = backend.current.get(key);
|
|
276
|
+
if ((prior?.version?.number ?? 0) !== expectedVersion)
|
|
277
|
+
throw new Error("Agent execution initial Entity Version is no longer current.");
|
|
278
|
+
const layout = snapshot?.states?.length ? stateHistorySpec(entity.stateSchema) : undefined;
|
|
279
|
+
const states = layout === undefined
|
|
280
|
+
? undefined
|
|
281
|
+
: this.openRecords(entity.context, layout.spec, layout.group);
|
|
282
|
+
const prepared = layout === undefined
|
|
283
|
+
? []
|
|
284
|
+
: (snapshot?.states ?? []).map((record) => layout.spec.materialize(record));
|
|
285
|
+
const priorStates = prepared.map((record) => states?.capture(record.id));
|
|
145
286
|
try {
|
|
146
|
-
|
|
147
|
-
|
|
287
|
+
this.writeAgentStates(states, prepared);
|
|
288
|
+
if (snapshot !== undefined)
|
|
289
|
+
backend.current.set(key, snapshot.next);
|
|
290
|
+
extra.apply();
|
|
148
291
|
}
|
|
149
|
-
|
|
150
|
-
|
|
292
|
+
catch (error) {
|
|
293
|
+
extra.restore();
|
|
294
|
+
if (prior === undefined)
|
|
295
|
+
backend.current.delete(key);
|
|
296
|
+
else
|
|
297
|
+
backend.current.set(key, prior);
|
|
298
|
+
for (const priorState of priorStates)
|
|
299
|
+
if (priorState !== undefined)
|
|
300
|
+
states?.apply(priorState);
|
|
301
|
+
throw error;
|
|
151
302
|
}
|
|
303
|
+
return Promise.resolve();
|
|
152
304
|
}
|
|
153
305
|
/**
|
|
154
|
-
*
|
|
306
|
+
* Writes immutable Agent state history after checking each Version slot.
|
|
155
307
|
*
|
|
156
|
-
* @
|
|
157
|
-
* @
|
|
158
|
-
* @param input Supplies configured histories and delivery Events.
|
|
159
|
-
* @returns Live affected record families and their layouts.
|
|
308
|
+
* @param states State-history records when that family is configured.
|
|
309
|
+
* @param prepared Materialized state rows from the captured transition.
|
|
160
310
|
*/
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
311
|
+
writeAgentStates(states, prepared) {
|
|
312
|
+
if (states === undefined)
|
|
313
|
+
return;
|
|
314
|
+
for (const record of prepared) {
|
|
315
|
+
const existing = states.read(record.id);
|
|
316
|
+
if (existing !== undefined &&
|
|
317
|
+
!this.sameBytes(toBinary(EntityRecordSchema, existing), toBinary(EntityRecordSchema, record.record)))
|
|
318
|
+
throw new Error("Agent state history collides with another Version.");
|
|
319
|
+
states.write(record);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Closes retained resources after admitted work settles.
|
|
324
|
+
*
|
|
325
|
+
* @returns The repeatable close outcome.
|
|
326
|
+
*/
|
|
327
|
+
close() {
|
|
328
|
+
this.#open = false;
|
|
329
|
+
if (this.#idleOutcome !== undefined)
|
|
330
|
+
return (this.#closing ??=
|
|
331
|
+
this.#idleOutcome.error === undefined
|
|
332
|
+
? Promise.resolve()
|
|
333
|
+
: Promise.reject(this.#idleOutcome.error));
|
|
334
|
+
return (this.#closing ??= this.drain());
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Closes retained adapters only when no commit is admitted.
|
|
338
|
+
*/
|
|
339
|
+
closeIdle() {
|
|
340
|
+
if (this.#active.size)
|
|
341
|
+
throw new Error("Entity commit cannot close idle while work is active.");
|
|
342
|
+
if (this.#idleOutcome !== undefined) {
|
|
343
|
+
if (this.#idleOutcome.error !== undefined)
|
|
344
|
+
throw this.#idleOutcome.error;
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (this.#closing !== undefined)
|
|
348
|
+
throw new Error("Entity commit close is in progress.");
|
|
349
|
+
this.#open = false;
|
|
350
|
+
const failure = this.closeRetained();
|
|
351
|
+
this.#idleOutcome = failure === undefined ? {} : { error: failure };
|
|
352
|
+
this.onClose();
|
|
353
|
+
if (failure !== undefined)
|
|
354
|
+
throw failure;
|
|
179
355
|
}
|
|
180
356
|
/**
|
|
181
|
-
*
|
|
357
|
+
* Applies one captured mutation without cross-record compensation.
|
|
182
358
|
*
|
|
183
359
|
* @typeParam I Entity identifier type.
|
|
184
360
|
* @typeParam S Entity state type.
|
|
185
|
-
* @param input Supplies
|
|
186
|
-
* @
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return {
|
|
203
|
-
live,
|
|
204
|
-
key,
|
|
205
|
-
previous,
|
|
206
|
-
...ids,
|
|
207
|
-
stagedBackend,
|
|
208
|
-
stagedStates,
|
|
209
|
-
stagedDiagnostics,
|
|
210
|
-
stagedEvents,
|
|
211
|
-
entity,
|
|
212
|
-
};
|
|
361
|
+
* @param input Supplies immutable record snapshots.
|
|
362
|
+
* @returns Completion after every requested record write.
|
|
363
|
+
*/
|
|
364
|
+
async apply(input) {
|
|
365
|
+
this.requireEnabledHistories(input);
|
|
366
|
+
this.validateRows(input);
|
|
367
|
+
const [entity, states, diagnostics, delivery] = this.openCommit(input);
|
|
368
|
+
for (const state of input.states ?? [])
|
|
369
|
+
await states?.append(state);
|
|
370
|
+
for (const diagnostic of input.diagnostics ?? [])
|
|
371
|
+
await diagnostics?.append(diagnostic);
|
|
372
|
+
for (const event of input.events ?? []) {
|
|
373
|
+
if (event.id === undefined || delivery === undefined)
|
|
374
|
+
throw new Error("Entity commit requires delivery-event IDs.");
|
|
375
|
+
await this.appendEvent(delivery, event.id, event);
|
|
376
|
+
}
|
|
377
|
+
await entity.current.write(input.next);
|
|
213
378
|
}
|
|
214
379
|
/**
|
|
215
|
-
*
|
|
380
|
+
* Acquires only missing record families and retains them for this handle.
|
|
216
381
|
*
|
|
217
382
|
* @typeParam I Entity identifier type.
|
|
218
383
|
* @typeParam S Entity state type.
|
|
219
|
-
* @param input Supplies
|
|
220
|
-
* @
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
384
|
+
* @param input Supplies requested history and delivery records.
|
|
385
|
+
* @returns Retained Entity and requested family handles.
|
|
386
|
+
*/
|
|
387
|
+
openCommit(input) {
|
|
388
|
+
const acquired = [];
|
|
389
|
+
try {
|
|
390
|
+
const entity = this.#entity ?? this.openCurrent(input, acquired);
|
|
391
|
+
const states = input.states?.length
|
|
392
|
+
? (this.#states ?? this.openStates(input, acquired))
|
|
393
|
+
: undefined;
|
|
394
|
+
const diagnostics = input.diagnostics?.length
|
|
395
|
+
? (this.#diagnostics ?? this.openDiagnostics(input, acquired))
|
|
396
|
+
: undefined;
|
|
397
|
+
const delivery = input.events?.length
|
|
398
|
+
? (this.#delivery ?? this.openDelivery(input, acquired))
|
|
399
|
+
: undefined;
|
|
400
|
+
this.#entity = entity;
|
|
401
|
+
if (states !== undefined)
|
|
402
|
+
this.#states = states;
|
|
403
|
+
if (diagnostics !== undefined)
|
|
404
|
+
this.#diagnostics = diagnostics;
|
|
405
|
+
if (delivery !== undefined)
|
|
406
|
+
this.#delivery = delivery;
|
|
407
|
+
return [entity, states, diagnostics, delivery];
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
const failures = this.closeGroup(acquired);
|
|
411
|
+
if (failures.length)
|
|
412
|
+
throw new AggregateError([error, ...failures], "Entity commit acquisition failed.");
|
|
413
|
+
throw error;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Acquires the current Entity adapter once.
|
|
418
|
+
*
|
|
419
|
+
* @param acquired Newly acquired handles to close on failure.
|
|
420
|
+
* @param input The captured Entity persistence input.
|
|
421
|
+
* @returns The retained current Entity record handle.
|
|
422
|
+
* @typeParam I Entity identifier or record type.
|
|
423
|
+
* @typeParam S Generated Entity state message type.
|
|
424
|
+
*/
|
|
425
|
+
openCurrent(input, acquired) {
|
|
426
|
+
const entityInput = { ...input.entity };
|
|
427
|
+
delete entityInput.stateHistoryStorage;
|
|
428
|
+
delete entityInput.eventHistoryStorage;
|
|
429
|
+
const entity = this.entities.create(entityInput);
|
|
430
|
+
acquired.push(entity);
|
|
431
|
+
return entity;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Acquires retained state-history storage and its adapter.
|
|
435
|
+
*
|
|
436
|
+
* @param acquired Newly acquired handles to close on failure.
|
|
437
|
+
* @param input The captured Entity persistence input.
|
|
438
|
+
* @returns The retained state-history record handle.
|
|
439
|
+
* @typeParam I Entity identifier or record type.
|
|
440
|
+
* @typeParam S Generated Entity state message type.
|
|
441
|
+
*/
|
|
442
|
+
openStates(input, acquired) {
|
|
443
|
+
const states = input.states?.length ? stateHistorySpec(input.entity.stateSchema) : undefined;
|
|
444
|
+
if (states === undefined)
|
|
445
|
+
throw new Error("State history was not requested.");
|
|
446
|
+
const records = new InMemoryRecordStorage(input.context, states.spec, () => this.openRecords(input.context, states.spec, states.group));
|
|
447
|
+
acquired.push(records);
|
|
448
|
+
const adapter = new InMemoryEntityHistory({
|
|
449
|
+
id: input.entity.id,
|
|
450
|
+
records,
|
|
451
|
+
stateSchema: input.entity.stateSchema,
|
|
452
|
+
queue: this.entities.backend(input.entity).mutationQueue,
|
|
229
453
|
});
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
454
|
+
return adapter;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Acquires retained diagnostic history storage and its adapter.
|
|
458
|
+
*
|
|
459
|
+
* @param acquired Newly acquired handles to close on failure.
|
|
460
|
+
* @param input The captured Entity persistence input.
|
|
461
|
+
* @returns The retained diagnostic Event record handle.
|
|
462
|
+
* @typeParam I Entity identifier or record type.
|
|
463
|
+
* @typeParam S Generated Entity state message type.
|
|
464
|
+
*/
|
|
465
|
+
openDiagnostics(input, acquired) {
|
|
466
|
+
const layout = eventHistorySpec(input.entity.stateSchema);
|
|
467
|
+
const records = new InMemoryRecordStorage(input.context, layout.spec, () => this.openRecords(input.context, layout.spec, layout.group));
|
|
468
|
+
acquired.push(records);
|
|
469
|
+
return new MemoryEntityEventHistory({
|
|
470
|
+
id: input.entity.id,
|
|
471
|
+
records,
|
|
472
|
+
queue: this.entities.backend(input.entity).mutationQueue,
|
|
235
473
|
});
|
|
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 };
|
|
239
474
|
}
|
|
240
475
|
/**
|
|
241
|
-
*
|
|
476
|
+
* Acquires retained delivery Event storage.
|
|
242
477
|
*
|
|
243
|
-
* @
|
|
244
|
-
* @
|
|
245
|
-
* @
|
|
246
|
-
* @
|
|
478
|
+
* @param acquired Newly acquired handles to close on failure.
|
|
479
|
+
* @param input The captured Entity persistence input.
|
|
480
|
+
* @returns The retained delivery Event record handle.
|
|
481
|
+
* @typeParam I Entity identifier or record type.
|
|
482
|
+
* @typeParam S Generated Entity state message type.
|
|
247
483
|
*/
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
stage.deliveryIds.some((id) => stage.stagedEvents?.read(id) !== undefined))
|
|
254
|
-
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);
|
|
484
|
+
openDelivery(input, acquired) {
|
|
485
|
+
const group = EventStoreGroups.forContext(input.context.name);
|
|
486
|
+
const records = new InMemoryRecordStorage(input.context, eventStoreRecordSpec, () => this.openRecords(input.context, eventStoreRecordSpec, group));
|
|
487
|
+
acquired.push(records);
|
|
488
|
+
return records;
|
|
261
489
|
}
|
|
262
490
|
/**
|
|
263
|
-
*
|
|
491
|
+
* Registers provider work before invoking the operation.
|
|
264
492
|
*
|
|
265
|
-
* @param
|
|
266
|
-
* @
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
493
|
+
* @param action The admitted provider operation.
|
|
494
|
+
* @returns The admitted provider operation completion.
|
|
495
|
+
* @typeParam Value Result type of the admitted provider operation.
|
|
496
|
+
*/
|
|
497
|
+
track(action) {
|
|
498
|
+
let complete;
|
|
499
|
+
let fail;
|
|
500
|
+
const work = new Promise((resolve, reject) => {
|
|
501
|
+
complete = resolve;
|
|
502
|
+
fail = reject;
|
|
503
|
+
});
|
|
504
|
+
this.#active.add(work);
|
|
505
|
+
void work.then(() => this.#active.delete(work), () => this.#active.delete(work));
|
|
275
506
|
try {
|
|
276
|
-
|
|
277
|
-
change.apply();
|
|
278
|
-
backend.current.set(stage.key, next);
|
|
279
|
-
extra?.apply();
|
|
507
|
+
action().then(complete, fail);
|
|
280
508
|
}
|
|
281
509
|
catch (error) {
|
|
282
|
-
|
|
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;
|
|
510
|
+
fail(error);
|
|
290
511
|
}
|
|
512
|
+
return work;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Waits for admitted commits and reports every retained close failure.
|
|
516
|
+
*
|
|
517
|
+
* @returns Completion after admitted work and cleanup settle.
|
|
518
|
+
*/
|
|
519
|
+
async drain() {
|
|
520
|
+
await Promise.allSettled([...this.#active]);
|
|
521
|
+
const failure = this.closeRetained();
|
|
522
|
+
this.onClose();
|
|
523
|
+
if (failure !== undefined)
|
|
524
|
+
throw failure;
|
|
291
525
|
}
|
|
292
526
|
/**
|
|
293
|
-
*
|
|
527
|
+
* Closes retained adapters once after no commit remains active.
|
|
528
|
+
*
|
|
529
|
+
* @returns A cleanup failure, if any.
|
|
530
|
+
*/
|
|
531
|
+
closeRetained() {
|
|
532
|
+
const ports = [];
|
|
533
|
+
for (const port of [this.#delivery, this.#diagnostics, this.#states, this.#entity]) {
|
|
534
|
+
if (port !== undefined)
|
|
535
|
+
ports.push(port);
|
|
536
|
+
}
|
|
537
|
+
this.#delivery = undefined;
|
|
538
|
+
this.#diagnostics = undefined;
|
|
539
|
+
this.#states = undefined;
|
|
540
|
+
this.#entity = undefined;
|
|
541
|
+
const failures = this.closeGroup(ports);
|
|
542
|
+
if (failures.length === 1)
|
|
543
|
+
return failures[0];
|
|
544
|
+
return failures.length
|
|
545
|
+
? new AggregateError(failures, "Entity commit close failed.")
|
|
546
|
+
: undefined;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Closes every newly acquired handle and collects cleanup failures.
|
|
550
|
+
*
|
|
551
|
+
* @param ports The acquired record handles.
|
|
552
|
+
* @returns Cleanup failures from acquired handles.
|
|
553
|
+
*/
|
|
554
|
+
closeGroup(ports) {
|
|
555
|
+
const failures = [];
|
|
556
|
+
for (const port of ports) {
|
|
557
|
+
try {
|
|
558
|
+
port.close();
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
failures.push(error);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return failures;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Writes one immutable delivery Event or accepts an identical retry.
|
|
568
|
+
*
|
|
569
|
+
* @param storage Supplies atomic single-record insertion.
|
|
570
|
+
* @param id Identifies the delivery Event.
|
|
571
|
+
* @param event Supplies the captured Event.
|
|
572
|
+
* @returns Completion after an identical Event is stored.
|
|
573
|
+
*/
|
|
574
|
+
async appendEvent(storage, id, event) {
|
|
575
|
+
if (await storage.compareAndSet(id, undefined, event))
|
|
576
|
+
return;
|
|
577
|
+
const prior = await storage.read(id);
|
|
578
|
+
if (prior === undefined ||
|
|
579
|
+
!this.sameBytes(toBinary(EventSchema, prior), toBinary(EventSchema, event))) {
|
|
580
|
+
throw new Error("Entity commit requires unique delivery-event IDs.");
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Compares two stored Protobuf byte sequences.
|
|
585
|
+
*
|
|
586
|
+
* @param left First byte sequence.
|
|
587
|
+
* @param right Second byte sequence.
|
|
588
|
+
* @returns Whether every byte matches.
|
|
589
|
+
*/
|
|
590
|
+
sameBytes(left, right) {
|
|
591
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Validates current identity and duplicate delivery IDs before writes.
|
|
294
595
|
*
|
|
295
596
|
* @typeParam I Entity identifier type.
|
|
296
597
|
* @typeParam S Entity state type.
|
|
297
|
-
* @param
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
};
|
|
598
|
+
* @param input Supplies the captured mutation.
|
|
599
|
+
*/
|
|
600
|
+
validateRows(input) {
|
|
601
|
+
const nextId = input.next.entityId === undefined ? undefined : input.entity.id.unpack(input.next.entityId);
|
|
602
|
+
if (nextId === undefined || input.entity.id.key(nextId) !== input.entity.id.key(input.entityId))
|
|
603
|
+
throw new Error("Entity commit current record identifies another Entity.");
|
|
604
|
+
const ids = (input.events ?? []).map((event) => event.id?.value);
|
|
605
|
+
if (ids.some((id) => id === undefined || id.trim() === "") || new Set(ids).size !== ids.length)
|
|
606
|
+
throw new Error("Entity commit requires unique delivery-event IDs.");
|
|
321
607
|
}
|
|
322
608
|
/**
|
|
323
|
-
* Rejects
|
|
609
|
+
* Rejects disabled history families.
|
|
324
610
|
*
|
|
325
611
|
* @typeParam I Entity identifier type.
|
|
326
612
|
* @typeParam S Entity state type.
|
|
327
|
-
* @param input Supplies the
|
|
613
|
+
* @param input Supplies the proposed histories.
|
|
328
614
|
*/
|
|
329
|
-
|
|
330
|
-
if ((input.states?.length ?? 0) > 0 && !input.entity.stateHistory)
|
|
615
|
+
requireEnabledHistories(input) {
|
|
616
|
+
if ((input.states?.length ?? 0) > 0 && !input.entity.stateHistory)
|
|
331
617
|
throw new Error("Entity commit cannot append state history when it is disabled.");
|
|
332
|
-
|
|
333
|
-
if ((input.diagnostics?.length ?? 0) > 0 && !input.entity.eventHistory) {
|
|
618
|
+
if ((input.diagnostics?.length ?? 0) > 0 && !input.entity.eventHistory)
|
|
334
619
|
throw new Error("Entity commit cannot append event history when it is disabled.");
|
|
335
|
-
}
|
|
336
620
|
}
|
|
337
621
|
/**
|
|
338
|
-
* Checks the tenant and
|
|
622
|
+
* Checks the tenant and source captured by this handle.
|
|
339
623
|
*
|
|
340
624
|
* @typeParam I Entity identifier type.
|
|
341
625
|
* @typeParam S Entity state type.
|
|
342
626
|
* @param input Supplies the requested scope.
|
|
343
627
|
*/
|
|
344
|
-
|
|
628
|
+
requireCompatible(input) {
|
|
345
629
|
if (input.context.multitenant !== this.#multitenant ||
|
|
346
630
|
TenantBoundary.of(input.context).key !== this.#tenantKey ||
|
|
347
631
|
TenantBoundary.of(input.entity.context).key !== this.#tenantKey ||
|
|
348
|
-
input.entity.sourceType.typeName !== this
|
|
632
|
+
input.entity.sourceType.typeName !== this.input.sourceType.typeName) {
|
|
349
633
|
throw new Error("Entity commit handle cannot commit another Entity storage scope.");
|
|
350
634
|
}
|
|
351
635
|
}
|
|
352
636
|
/**
|
|
353
637
|
* Rejects commits after this handle closes.
|
|
354
638
|
*/
|
|
355
|
-
|
|
639
|
+
requireOpen() {
|
|
356
640
|
if (!this.#open)
|
|
357
641
|
throw new Error("Entity commit storage is closed.");
|
|
358
642
|
}
|
|
359
643
|
}
|
|
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
644
|
//# sourceMappingURL=in-memory-entity-commit.js.map
|