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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +31 -14
  2. package/REFERENCE.md +40 -22
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/postgres/agent-history.d.ts +60 -18
  7. package/dist/postgres/agent-history.d.ts.map +1 -1
  8. package/dist/postgres/agent-history.js +114 -37
  9. package/dist/postgres/agent-history.js.map +1 -1
  10. package/dist/postgres/column-mapping.d.ts.map +1 -1
  11. package/dist/postgres/column-mapping.js +6 -3
  12. package/dist/postgres/column-mapping.js.map +1 -1
  13. package/dist/postgres/entity-commit.d.ts +83 -14
  14. package/dist/postgres/entity-commit.d.ts.map +1 -1
  15. package/dist/postgres/entity-commit.js +383 -61
  16. package/dist/postgres/entity-commit.js.map +1 -1
  17. package/dist/postgres/entity-history.d.ts +4 -2
  18. package/dist/postgres/entity-history.d.ts.map +1 -1
  19. package/dist/postgres/entity-history.js +105 -32
  20. package/dist/postgres/entity-history.js.map +1 -1
  21. package/dist/postgres/id-column.d.ts.map +1 -1
  22. package/dist/postgres/id-column.js +6 -3
  23. package/dist/postgres/id-column.js.map +1 -1
  24. package/dist/postgres/record-storage.d.ts +146 -11
  25. package/dist/postgres/record-storage.d.ts.map +1 -1
  26. package/dist/postgres/record-storage.js +200 -27
  27. package/dist/postgres/record-storage.js.map +1 -1
  28. package/dist/postgres/storage-factory.d.ts +265 -22
  29. package/dist/postgres/storage-factory.d.ts.map +1 -1
  30. package/dist/postgres/storage-factory.js +784 -124
  31. package/dist/postgres/storage-factory.js.map +1 -1
  32. package/dist/postgres/table-initializer.d.ts +7 -4
  33. package/dist/postgres/table-initializer.d.ts.map +1 -1
  34. package/dist/postgres/table-initializer.js +21 -15
  35. package/dist/postgres/table-initializer.js.map +1 -1
  36. package/dist/postgres/table-resolver.d.ts +15 -1
  37. package/dist/postgres/table-resolver.d.ts.map +1 -1
  38. package/dist/postgres/table-resolver.js +36 -7
  39. package/dist/postgres/table-resolver.js.map +1 -1
  40. package/dist/tsconfig.tsbuildinfo +1 -1
  41. package/package.json +5 -5
  42. package/dist/postgres/agent-execution.d.ts +0 -285
  43. package/dist/postgres/agent-execution.d.ts.map +0 -1
  44. package/dist/postgres/agent-execution.js +0 -648
  45. package/dist/postgres/agent-execution.js.map +0 -1
@@ -1,10 +1,10 @@
1
1
  import { type Message } from "@bufbuild/protobuf";
2
+ import { type ModelPreference } from "@spine-event-engine/proto/agent";
2
3
  import { type RecordSpec, type StorageGroup } from "@spine-event-engine/storage";
3
- import { type EntityCommitInput, type EntityCommitStorage, type EntityStorageInput } from "@spine-event-engine/storage/provider";
4
+ import { type AgentCommitInput, type EntityCommitInput, type EntityCommitStorage, type EntityStorageInput } from "@spine-event-engine/storage/provider";
4
5
  import { PostgresRecordStorage } from "./record-storage.js";
5
- import type { PoolClient } from "pg";
6
6
  /**
7
- * Opens one PostgreSQL record family used by an Entity commit.
7
+ * Acquires one PostgreSQL record family used by an Entity commit.
8
8
  *
9
9
  * @typeParam I Record identifier type.
10
10
  * @typeParam R Stored Protobuf record type.
@@ -25,7 +25,7 @@ export declare class PostgresEntityCommitStorage<I, S extends Message> implement
25
25
  * Creates a PostgreSQL Entity commit handle for one Entity storage boundary.
26
26
  *
27
27
  * @param entity Defines the captured Entity source, state, and tenant scope.
28
- * @param open Opens temporary current and history record-family handles.
28
+ * @param open Opens current and optional history record-family handles.
29
29
  * @param onClose Removes this handle from the factory's live-handle set.
30
30
  */
31
31
  constructor(entity: EntityStorageInput<I, S>, open: OpenRecords, onClose: () => void);
@@ -39,17 +39,74 @@ export declare class PostgresEntityCommitStorage<I, S extends Message> implement
39
39
  */
40
40
  commit<Id, State extends Message>(input: EntityCommitInput<Id, State>): Promise<void>;
41
41
  /**
42
- * Prepares one Agent-fenced Entity mutation for its existing transaction client.
42
+ * Registers a commit before invoking provider construction callbacks.
43
43
  *
44
- * @typeParam Id Typed Entity identifier.
45
- * @typeParam State Entity state message type.
46
- * @param input Changed Entity state and immutable history.
47
- * @returns A transaction-bound conditional mutation and its close callback.
44
+ * @param action The admitted provider operation.
45
+ * @returns The admitted provider operation completion.
46
+ * @typeParam T Result type of the admitted provider operation.
48
47
  */
49
- prepareAgentConditional<Id, State extends Message>(input: EntityCommitInput<Id, State>): Promise<{
50
- apply(client: PoolClient, expectedVersion: number): Promise<void>;
51
- close(): void;
52
- }>;
48
+ private track;
49
+ /**
50
+ * Reads one detached preference image through the retained private family.
51
+ * @param entityId Typed Agent identifier.
52
+ * @returns Current complete image or absence.
53
+ */
54
+ readAgentPreferences(entityId: unknown): Promise<readonly ModelPreference[] | undefined>;
55
+ /**
56
+ * Commits state, final audit, and complete preferences in one native transaction.
57
+ * @param input Captured Agent transition and initial images.
58
+ * @returns Completion after the conditional group commits.
59
+ * @typeParam Id Typed Agent identifier.
60
+ * @typeParam State Generated Agent state.
61
+ */
62
+ commitAgent<Id, State extends Message>(input: AgentCommitInput<Id, State>): Promise<void>;
63
+ /**
64
+ * Acquires the retained current, Agent audit, and preference families.
65
+ *
66
+ * @returns The retained family group.
67
+ */
68
+ private agentRecords;
69
+ /**
70
+ * Checks the captured Entity scope and next version before native writes.
71
+ * @param id Captured Agent identifier.
72
+ * @param version Initial Entity version.
73
+ * @param mutation Optional prepared Entity mutation.
74
+ * @typeParam Id Typed Agent identifier.
75
+ * @typeParam State Generated Agent state.
76
+ */
77
+ private validateAgent;
78
+ /**
79
+ * Applies one transaction lock even when preference and current rows are absent.
80
+ * @param records Retained families.
81
+ * @param id Captured Agent ID.
82
+ * @param version Initial Entity version.
83
+ * @param expected Initial preference image.
84
+ * @param next Complete replacement record.
85
+ * @param mutation Optional Entity mutation.
86
+ * @param history Final audit entries.
87
+ * @typeParam Id Typed Agent identifier.
88
+ * @typeParam State Generated Agent state.
89
+ * @returns Completion after the conditional Agent transaction commits.
90
+ */
91
+ private commitAgentPrepared;
92
+ /**
93
+ * Adds final Agent audit through the admitted transaction client.
94
+ * @param client Active native transaction.
95
+ * @param storage Retained Agent history family.
96
+ * @param id Captured Agent identifier.
97
+ * @param history Final audit entries.
98
+ * @returns Completion after final audit rows are appended.
99
+ */
100
+ private appendAgentHistory;
101
+ /**
102
+ * Persists an admitted captured mutation, including a safe retry.
103
+ *
104
+ * @param prepared The immutable prepared mutation.
105
+ * @returns Completion after the captured mutation is persisted.
106
+ * @typeParam Id Entity identifier or record type.
107
+ * @typeParam State Generated Entity state message type.
108
+ */
109
+ private persist;
53
110
  /**
54
111
  * Determines whether a driver serialization failure occurred through provider error causes.
55
112
  *
@@ -59,8 +116,20 @@ export declare class PostgresEntityCommitStorage<I, S extends Message> implement
59
116
  private retryable;
60
117
  /**
61
118
  * Closes this handle to new commits while allowing started work to settle.
119
+ *
120
+ * @returns Completion after admitted work and retained records close.
121
+ */
122
+ close(): Promise<void>;
123
+ /**
124
+ * Closes only when no commit remains admitted.
125
+ */
126
+ closeIdle(): void;
127
+ /**
128
+ * Waits for admitted commits before releasing factory tracking.
129
+ *
130
+ * @returns Completion after admitted work and cleanup settle.
62
131
  */
63
- close(): void;
132
+ private drain;
64
133
  /**
65
134
  * Applies one validated Entity commit as ordered independent writes.
66
135
  *
@@ -1 +1 @@
1
- {"version":3,"file":"entity-commit.d.ts","sourceRoot":"","sources":["../../src/postgres/entity-commit.ts"],"names":[],"mappings":"AAcA,OAAO,EAAY,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAO5D,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAML,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,sCAAsC,CAAC;AAE9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAErC;;;;;GAKG;AACH,KAAK,WAAW,GAAG,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,EACtC,IAAI,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EACtB,KAAK,CAAC,EAAE,YAAY,KACjB,qBAAqB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAwIjC;;;;;GAKG;AACH,qBAAa,2BAA2B,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,CAAE,YAAW,mBAAmB;;IAWzF,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAV1B;;;;;;OAMG;gBAEgB,MAAM,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,EAChC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,MAAM,IAAI;IAGtC;;;;;;;OAOG;IACG,MAAM,CAAC,EAAE,EAAE,KAAK,SAAS,OAAO,EAAE,KAAK,EAAE,iBAAiB,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB3F;;;;;;;OAOG;IACG,uBAAuB,CAAC,EAAE,EAAE,KAAK,SAAS,OAAO,EACrD,KAAK,EAAE,iBAAiB,CAAC,EAAE,EAAE,KAAK,CAAC,GAClC,OAAO,CAAC;QACT,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAClE,KAAK,IAAI,IAAI,CAAC;KACf,CAAC;IAmBF;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAWjB;;OAEG;IACH,KAAK,IAAI,IAAI;IAOb;;;;;;;;OAQG;YACW,KAAK;IAWnB;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ;IAkBhB;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IAepB;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;CAWzB"}
1
+ {"version":3,"file":"entity-commit.d.ts","sourceRoot":"","sources":["../../src/postgres/entity-commit.ts"],"names":[],"mappings":"AAcA,OAAO,EAAmB,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,iCAAiC,CAAC;AAOzC,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAUL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,sCAAsC,CAAC;AAG9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAG5D;;;;;GAKG;AACH,KAAK,WAAW,GAAG,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,EACtC,IAAI,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EACtB,KAAK,CAAC,EAAE,YAAY,KACjB,qBAAqB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAoOjC;;;;;GAKG;AACH,qBAAa,2BAA2B,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,CAAE,YAAW,mBAAmB;;IAsBzF,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAV1B;;;;;;OAMG;gBAEgB,MAAM,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,EAChC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,MAAM,IAAI;IAGtC;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,EAAE,KAAK,SAAS,OAAO,EAAE,KAAK,EAAE,iBAAiB,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAUrF;;;;;;OAMG;IACH,OAAO,CAAC,KAAK;IAoBb;;;;OAIG;IACH,oBAAoB,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,eAAe,EAAE,GAAG,SAAS,CAAC;IAiBxF;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,EAAE,KAAK,SAAS,OAAO,EAAE,KAAK,EAAE,gBAAgB,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BzF;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAkBpB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAgBrB;;;;;;;;;;;;OAYG;YACW,mBAAmB;IAkCjC;;;;;;;OAOG;YACW,kBAAkB;IAchC;;;;;;;OAOG;YACW,OAAO;IAkCrB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAWjB;;;;OAIG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAUtB;;OAEG;IACH,SAAS,IAAI,IAAI;IA0BjB;;;;OAIG;YACW,KAAK;IAkBnB;;;;;;;;OAQG;YACW,KAAK;IAWnB;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ;IAiBhB;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IAepB;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;CAWzB"}
@@ -11,14 +11,16 @@
11
11
  * or implied. See the License for the specific language governing permissions and limitations under
12
12
  * the License.
13
13
  */
14
- import { toBinary } from "@bufbuild/protobuf";
14
+ import { clone, toBinary } from "@bufbuild/protobuf";
15
+ import { AgentHistoryEntrySchema, } from "@spine-event-engine/proto/agent";
15
16
  import { EntityRecordSchema, } from "@spine-event-engine/proto/generated/spine/server/entity/entity_pb.js";
16
17
  import {} from "@spine-event-engine/storage";
17
- import { TenantBoundary, EntityCommitSnapshots, eventHistorySpec, eventStoreRecordSpec, stateHistorySpec, } from "@spine-event-engine/storage/provider";
18
+ import { TenantBoundary, AgentHistoryRecords, AgentModelPreferences, EntityCommitSnapshots, eventHistorySpec, eventStoreRecordSpec, EventStoreGroups, stateHistorySpec, } from "@spine-event-engine/storage/provider";
19
+ import { AgentHistoryHash } from "./agent-history.js";
18
20
  import { PostgresStorageErrors, PostgresStorageOperationError } from "./errors.js";
19
21
  import { PostgresRecordStorage } from "./record-storage.js";
20
22
  /**
21
- * Opens and closes the exact record families participating in one commit.
23
+ * Stores current and optional record families across commits on one handle.
22
24
  *
23
25
  * @typeParam I Entity identifier type.
24
26
  * @typeParam S Entity state message type.
@@ -28,23 +30,78 @@ class PostgresCommitRecords {
28
30
  states;
29
31
  diagnostics;
30
32
  events;
33
+ history;
34
+ preferences;
31
35
  #handles;
32
36
  /**
33
- * Opens the current record family and each enabled immutable family.
37
+ * Whether this record family is closed.
38
+ */
39
+ #closed = false;
40
+ /**
41
+ * Acquires the current record family for one Entity scope.
34
42
  *
35
- * @param input Describes the Entity commit and enabled histories.
36
43
  * @param open Opens one record family.
44
+ * @param entity The Entity persistence contract.
45
+ */
46
+ constructor(entity, open) {
47
+ this.current = open(entity.recordSpec);
48
+ this.#handles = [this.current];
49
+ }
50
+ /**
51
+ * Acquires only missing requested families, rolling back a failed new group.
52
+ *
53
+ * @param input The captured Entity persistence input.
54
+ * @param open Opens a generated record family.
55
+ */
56
+ ensure(input, open) {
57
+ const acquired = [];
58
+ try {
59
+ const states = input.states?.length
60
+ ? (this.states ?? this.openState(input, open, acquired))
61
+ : undefined;
62
+ const diagnostics = input.diagnostics?.length
63
+ ? (this.diagnostics ?? this.openDiagnostic(input, open, acquired))
64
+ : undefined;
65
+ const events = input.events?.length
66
+ ? (this.events ?? this.openEvents(input, open, acquired))
67
+ : undefined;
68
+ this.states = states ?? this.states;
69
+ this.diagnostics = diagnostics ?? this.diagnostics;
70
+ this.events = events ?? this.events;
71
+ this.#handles.push(...acquired);
72
+ }
73
+ catch (error) {
74
+ const failures = this.closeGroup(acquired);
75
+ if (failures.length)
76
+ throw new AggregateError([error, ...failures], "PostgreSQL family acquisition failed.");
77
+ throw error;
78
+ }
79
+ }
80
+ /**
81
+ * Stores the two Agent record families, rolling back only failed acquisition.
82
+ * @param open Opens a private record family.
37
83
  */
38
- constructor(input, open) {
39
- const current = open(input.entity.recordSpec);
40
- const handles = [current];
41
- this.current = current;
42
- this.states = input.states?.length ? this.openState(input, open, handles) : undefined;
43
- this.diagnostics = input.diagnostics?.length
44
- ? this.openDiagnostic(input, open, handles)
45
- : undefined;
46
- this.events = input.events?.length ? this.openEvents(open, handles) : undefined;
47
- this.#handles = handles;
84
+ ensureAgent(open) {
85
+ const acquired = [];
86
+ try {
87
+ const history = this.history ??
88
+ open(AgentHistoryRecords.spec((value) => AgentHistoryHash.value(value)), AgentHistoryRecords.group);
89
+ if (this.history === undefined)
90
+ acquired.push(history);
91
+ const preferences = this.preferences ??
92
+ open(AgentModelPreferences.spec((value) => AgentHistoryHash.value(value)), AgentModelPreferences.group);
93
+ if (this.preferences === undefined)
94
+ acquired.push(preferences);
95
+ this.history = history;
96
+ this.preferences = preferences;
97
+ this.#handles.push(...acquired);
98
+ }
99
+ catch (error) {
100
+ const failures = this.closeGroup(acquired);
101
+ if (failures.length)
102
+ throw new AggregateError([error, ...failures], "PostgreSQL Agent family acquisition failed.");
103
+ throw error;
104
+ }
48
105
  }
49
106
  /**
50
107
  * Prepares every record family before ordered writes begin.
@@ -79,14 +136,38 @@ class PostgresCommitRecords {
79
136
  await this.current.historyExecutor().write(client, snapshot.next);
80
137
  }
81
138
  /**
82
- * Closes every temporary record-family handle.
139
+ * Closes every retained record-family handle.
83
140
  */
84
141
  close() {
85
- for (const handle of this.#handles)
86
- handle.close();
142
+ if (this.#closed)
143
+ return;
144
+ this.#closed = true;
145
+ const failures = this.closeGroup(this.#handles);
146
+ if (failures.length === 1)
147
+ throw failures[0];
148
+ if (failures.length)
149
+ throw new AggregateError(failures, "PostgreSQL commit records close failed.");
150
+ }
151
+ /**
152
+ * Closes every newly acquired handle and collects cleanup failures.
153
+ *
154
+ * @param handles The acquired record handles.
155
+ * @returns Cleanup failures from acquired handles.
156
+ */
157
+ closeGroup(handles) {
158
+ const failures = [];
159
+ for (const handle of handles) {
160
+ try {
161
+ handle.close();
162
+ }
163
+ catch (error) {
164
+ failures.push(error);
165
+ }
166
+ }
167
+ return failures;
87
168
  }
88
169
  /**
89
- * Opens the Entity state-history record family.
170
+ * Acquires the Entity state-history record family.
90
171
  *
91
172
  * @param input Describes the Entity state schema.
92
173
  * @param open Opens one record family.
@@ -100,7 +181,7 @@ class PostgresCommitRecords {
100
181
  return handle;
101
182
  }
102
183
  /**
103
- * Opens the Entity diagnostic-event history record family.
184
+ * Acquires the Entity diagnostic-event history record family.
104
185
  *
105
186
  * @param input Describes the Entity state schema.
106
187
  * @param open Opens one record family.
@@ -114,14 +195,15 @@ class PostgresCommitRecords {
114
195
  return handle;
115
196
  }
116
197
  /**
117
- * Opens the shared event-store record family.
198
+ * Acquires the shared event-store record family.
118
199
  *
200
+ * @param input Supplies the context and Event family.
119
201
  * @param open Opens one record family.
120
202
  * @param handles Collects handles that must close after the commit.
121
203
  * @returns The event-store record storage.
122
204
  */
123
- openEvents(open, handles) {
124
- const handle = open(eventStoreRecordSpec);
205
+ openEvents(input, open, handles) {
206
+ const handle = open(eventStoreRecordSpec, EventStoreGroups.forContext(input.context.name));
125
207
  handles.push(handle);
126
208
  return handle;
127
209
  }
@@ -137,11 +219,18 @@ export class PostgresEntityCommitStorage {
137
219
  open;
138
220
  onClose;
139
221
  #open = true;
222
+ #records;
223
+ /**
224
+ * Synchronous idle-close result, including any cleanup failure.
225
+ */
226
+ #idleOutcome;
227
+ #active = new Set();
228
+ #closing;
140
229
  /**
141
230
  * Creates a PostgreSQL Entity commit handle for one Entity storage boundary.
142
231
  *
143
232
  * @param entity Defines the captured Entity source, state, and tenant scope.
144
- * @param open Opens temporary current and history record-family handles.
233
+ * @param open Opens current and optional history record-family handles.
145
234
  * @param onClose Removes this handle from the factory's live-handle set.
146
235
  */
147
236
  constructor(entity, open, onClose) {
@@ -157,11 +246,203 @@ export class PostgresEntityCommitStorage {
157
246
  * @param input Defines the next current record plus histories.
158
247
  * @returns Completion after every requested record write.
159
248
  */
160
- async commit(input) {
161
- this.validate(input);
162
- const prepared = EntityCommitSnapshots.capture(input);
163
- const records = new PostgresCommitRecords(prepared, this.open);
249
+ commit(input) {
250
+ if (!this.#open)
251
+ return Promise.reject(new PostgresStorageOperationError("Entity commit storage is closed."));
252
+ return this.track(() => {
253
+ this.validate(input);
254
+ const prepared = EntityCommitSnapshots.capture(input);
255
+ return this.persist(prepared);
256
+ });
257
+ }
258
+ /**
259
+ * Registers a commit before invoking provider construction callbacks.
260
+ *
261
+ * @param action The admitted provider operation.
262
+ * @returns The admitted provider operation completion.
263
+ * @typeParam T Result type of the admitted provider operation.
264
+ */
265
+ track(action) {
266
+ let complete;
267
+ let fail;
268
+ const work = new Promise((resolve, reject) => {
269
+ complete = resolve;
270
+ fail = reject;
271
+ });
272
+ this.#active.add(work);
273
+ void work.then(() => this.#active.delete(work), () => this.#active.delete(work));
274
+ try {
275
+ action().then(complete, fail);
276
+ }
277
+ catch (error) {
278
+ fail(error);
279
+ }
280
+ return work;
281
+ }
282
+ /**
283
+ * Reads one detached preference image through the retained private family.
284
+ * @param entityId Typed Agent identifier.
285
+ * @returns Current complete image or absence.
286
+ */
287
+ readAgentPreferences(entityId) {
288
+ if (!this.#open)
289
+ return Promise.reject(new PostgresStorageOperationError("Entity commit storage is closed."));
290
+ return this.track(async () => {
291
+ const key = AgentModelPreferences.key(this.entity, entityId);
292
+ const records = this.agentRecords();
293
+ await records.prepare();
294
+ const slot = `v1:${AgentHistoryHash.value(AgentModelPreferences.text(key))}`;
295
+ const storage = records.preferences;
296
+ if (storage === undefined)
297
+ throw new Error("Agent preference storage is unavailable.");
298
+ const found = await storage.read(slot);
299
+ if (found === undefined)
300
+ return undefined;
301
+ AgentModelPreferences.requireIdentity(found, key);
302
+ return AgentModelPreferences.copy(found.preferences);
303
+ });
304
+ }
305
+ /**
306
+ * Commits state, final audit, and complete preferences in one native transaction.
307
+ * @param input Captured Agent transition and initial images.
308
+ * @returns Completion after the conditional group commits.
309
+ * @typeParam Id Typed Agent identifier.
310
+ * @typeParam State Generated Agent state.
311
+ */
312
+ commitAgent(input) {
313
+ if (!this.#open)
314
+ return Promise.reject(new PostgresStorageOperationError("Entity commit storage is closed."));
315
+ return this.track(async () => {
316
+ const id = this.entity.id.clone(input.entityId);
317
+ const key = AgentModelPreferences.key(this.entity, id);
318
+ const expected = input.expectedPreferences === undefined
319
+ ? undefined
320
+ : AgentModelPreferences.copy(input.expectedPreferences);
321
+ const next = AgentModelPreferences.record(key, input.preferences);
322
+ const mutation = input.entityCommit && EntityCommitSnapshots.capture(input.entityCommit);
323
+ const history = input.historyEntries?.map((entry) => clone(AgentHistoryEntrySchema, entry)) ?? [];
324
+ this.validateAgent(id, input.expectedVersion, mutation);
325
+ const records = this.agentRecords();
326
+ if (mutation !== undefined)
327
+ records.ensure(mutation, this.open);
328
+ await records.prepare();
329
+ await this.commitAgentPrepared(records, id, input.expectedVersion, expected, next, mutation, history);
330
+ });
331
+ }
332
+ /**
333
+ * Acquires the retained current, Agent audit, and preference families.
334
+ *
335
+ * @returns The retained family group.
336
+ */
337
+ agentRecords() {
338
+ const records = this.#records ?? new PostgresCommitRecords(this.entity, this.open);
339
+ try {
340
+ records.ensureAgent(this.open);
341
+ this.#records ??= records;
342
+ return records;
343
+ }
344
+ catch (error) {
345
+ if (this.#records === undefined) {
346
+ try {
347
+ records.close();
348
+ }
349
+ catch (cleanup) {
350
+ throw new AggregateError([error, cleanup], "PostgreSQL Agent family cleanup failed.");
351
+ }
352
+ }
353
+ throw error;
354
+ }
355
+ }
356
+ /**
357
+ * Checks the captured Entity scope and next version before native writes.
358
+ * @param id Captured Agent identifier.
359
+ * @param version Initial Entity version.
360
+ * @param mutation Optional prepared Entity mutation.
361
+ * @typeParam Id Typed Agent identifier.
362
+ * @typeParam State Generated Agent state.
363
+ */
364
+ validateAgent(id, version, mutation) {
365
+ if (!Number.isSafeInteger(version) || version < 0)
366
+ throw new TypeError("Agent commit requires a valid initial Entity Version.");
367
+ if (mutation === undefined)
368
+ return;
369
+ this.validate(mutation);
370
+ if (this.entity.id.key(id) !== this.entity.id.key(mutation.entityId) ||
371
+ mutation.next.version?.number !== version + 1)
372
+ throw new Error("Agent commit requires the same Entity ID and one new Version.");
373
+ }
374
+ /**
375
+ * Applies one transaction lock even when preference and current rows are absent.
376
+ * @param records Retained families.
377
+ * @param id Captured Agent ID.
378
+ * @param version Initial Entity version.
379
+ * @param expected Initial preference image.
380
+ * @param next Complete replacement record.
381
+ * @param mutation Optional Entity mutation.
382
+ * @param history Final audit entries.
383
+ * @typeParam Id Typed Agent identifier.
384
+ * @typeParam State Generated Agent state.
385
+ * @returns Completion after the conditional Agent transaction commits.
386
+ */
387
+ async commitAgentPrepared(records, id, version, expected, next, mutation, history) {
388
+ const preferenceStorage = records.preferences;
389
+ const historyStorage = records.history;
390
+ if (preferenceStorage === undefined || historyStorage === undefined || next.key === undefined)
391
+ throw new Error("Agent commit storage is incomplete.");
392
+ const preferences = preferenceStorage.historyExecutor();
393
+ const key = next.key;
394
+ const slot = `v1:${AgentHistoryHash.value(AgentModelPreferences.text(key))}`;
395
+ await preferences.transaction(async (client) => {
396
+ await client.query("SELECT pg_advisory_xact_lock($1)", [
397
+ preferences.lock("agent-preferences", slot),
398
+ ]);
399
+ const prior = await preferences.read(client, slot, "for-update");
400
+ if (prior !== undefined)
401
+ AgentModelPreferences.requireIdentity(prior, key);
402
+ if (!AgentModelPreferences.same(prior?.preferences, expected))
403
+ throw new Error("Agent model preferences changed during the live turn.");
404
+ if (mutation === undefined) {
405
+ const current = await records.current.historyExecutor().read(client, id, "for-update");
406
+ if ((current?.version?.number ?? 0) !== version)
407
+ throw new Error("Agent initial Entity Version is no longer current.");
408
+ }
409
+ else
410
+ await records.applyConditional(client, mutation, version);
411
+ await this.appendAgentHistory(client, historyStorage, id, history);
412
+ await preferences.write(client, next);
413
+ });
414
+ }
415
+ /**
416
+ * Adds final Agent audit through the admitted transaction client.
417
+ * @param client Active native transaction.
418
+ * @param storage Retained Agent history family.
419
+ * @param id Captured Agent identifier.
420
+ * @param history Final audit entries.
421
+ * @returns Completion after final audit rows are appended.
422
+ */
423
+ async appendAgentHistory(client, storage, id, history) {
424
+ const executor = storage.historyExecutor();
425
+ for (const entry of history)
426
+ await executor.appendImmutable(client, AgentHistoryRecords.record(this.entity.stateSchema.typeName, this.entity.id.key(id), entry));
427
+ }
428
+ /**
429
+ * Persists an admitted captured mutation, including a safe retry.
430
+ *
431
+ * @param prepared The immutable prepared mutation.
432
+ * @returns Completion after the captured mutation is persisted.
433
+ * @typeParam Id Entity identifier or record type.
434
+ * @typeParam State Generated Entity state message type.
435
+ */
436
+ async persist(prepared) {
437
+ let records = this.#records;
438
+ let fresh = false;
164
439
  try {
440
+ if (records === undefined) {
441
+ records = new PostgresCommitRecords(this.entity, this.open);
442
+ fresh = true;
443
+ }
444
+ records.ensure(prepared, this.open);
445
+ this.#records ??= records;
165
446
  await records.prepare();
166
447
  try {
167
448
  await this.apply(prepared, records);
@@ -173,37 +454,16 @@ export class PostgresEntityCommitStorage {
173
454
  }
174
455
  }
175
456
  catch (error) {
457
+ if (fresh && this.#records === undefined) {
458
+ try {
459
+ records?.close();
460
+ }
461
+ catch (cleanup) {
462
+ throw new AggregateError([error, cleanup], "PostgreSQL commit acquisition failed.");
463
+ }
464
+ }
176
465
  throw PostgresStorageErrors.operation(error);
177
466
  }
178
- finally {
179
- records.close();
180
- }
181
- }
182
- /**
183
- * Prepares one Agent-fenced Entity mutation for its existing transaction client.
184
- *
185
- * @typeParam Id Typed Entity identifier.
186
- * @typeParam State Entity state message type.
187
- * @param input Changed Entity state and immutable history.
188
- * @returns A transaction-bound conditional mutation and its close callback.
189
- */
190
- async prepareAgentConditional(input) {
191
- this.validate(input);
192
- const snapshot = EntityCommitSnapshots.capture(input);
193
- const records = new PostgresCommitRecords(snapshot, this.open);
194
- try {
195
- await records.prepare();
196
- }
197
- catch (error) {
198
- records.close();
199
- throw error;
200
- }
201
- return {
202
- apply: (client, expectedVersion) => records.applyConditional(client, snapshot, expectedVersion),
203
- close: () => {
204
- records.close();
205
- },
206
- };
207
467
  }
208
468
  /**
209
469
  * Determines whether a driver serialization failure occurred through provider error causes.
@@ -225,12 +485,76 @@ export class PostgresEntityCommitStorage {
225
485
  }
226
486
  /**
227
487
  * Closes this handle to new commits while allowing started work to settle.
488
+ *
489
+ * @returns Completion after admitted work and retained records close.
228
490
  */
229
491
  close() {
230
- if (this.#open) {
231
- this.#open = false;
492
+ this.#open = false;
493
+ if (this.#idleOutcome !== undefined)
494
+ return (this.#closing ??=
495
+ this.#idleOutcome.error === undefined
496
+ ? Promise.resolve()
497
+ : Promise.reject(this.#idleOutcome.error));
498
+ return (this.#closing ??= this.drain());
499
+ }
500
+ /**
501
+ * Closes only when no commit remains admitted.
502
+ */
503
+ closeIdle() {
504
+ if (this.#active.size)
505
+ throw new Error("Entity commit cannot close idle while work is active.");
506
+ if (this.#idleOutcome !== undefined) {
507
+ if (this.#idleOutcome.error !== undefined)
508
+ throw this.#idleOutcome.error;
509
+ return;
510
+ }
511
+ if (this.#closing !== undefined)
512
+ throw new Error("Entity commit close is in progress.");
513
+ this.#open = false;
514
+ const errors = [];
515
+ try {
516
+ this.#records?.close();
517
+ }
518
+ catch (error) {
519
+ errors.push(error);
520
+ }
521
+ this.#records = undefined;
522
+ try {
523
+ this.onClose();
524
+ }
525
+ catch (error) {
526
+ errors.push(error);
527
+ }
528
+ const failure = errors.length > 1 ? new AggregateError(errors) : errors[0];
529
+ this.#idleOutcome = failure === undefined ? {} : { error: failure };
530
+ if (failure !== undefined)
531
+ throw failure;
532
+ }
533
+ /**
534
+ * Waits for admitted commits before releasing factory tracking.
535
+ *
536
+ * @returns Completion after admitted work and cleanup settle.
537
+ */
538
+ async drain() {
539
+ await Promise.allSettled([...this.#active]);
540
+ const errors = [];
541
+ try {
542
+ this.#records?.close();
543
+ }
544
+ catch (error) {
545
+ errors.push(error);
546
+ }
547
+ this.#records = undefined;
548
+ try {
232
549
  this.onClose();
233
550
  }
551
+ catch (error) {
552
+ errors.push(error);
553
+ }
554
+ if (errors.length === 1)
555
+ throw errors[0];
556
+ if (errors.length)
557
+ throw new AggregateError(errors, "PostgreSQL commit close failed.");
234
558
  }
235
559
  /**
236
560
  * Applies one validated Entity commit as ordered independent writes.
@@ -260,8 +584,6 @@ export class PostgresEntityCommitStorage {
260
584
  * @param input Defines the Entity records to validate.
261
585
  */
262
586
  validate(input) {
263
- if (!this.#open)
264
- throw new PostgresStorageOperationError("Entity commit storage is closed.");
265
587
  if (input.entity.sourceType.typeName !== this.entity.sourceType.typeName)
266
588
  throw new PostgresStorageOperationError("Entity commit source type is incompatible.");
267
589
  if (input.entity.stateSchema.typeName !== this.entity.stateSchema.typeName)