@spine-event-engine/storage-postgres 2.0.0-snapshot.24 → 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 +31 -14
- package/REFERENCE.md +40 -22
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/postgres/agent-history.d.ts +60 -18
- package/dist/postgres/agent-history.d.ts.map +1 -1
- package/dist/postgres/agent-history.js +114 -37
- package/dist/postgres/agent-history.js.map +1 -1
- package/dist/postgres/column-mapping.d.ts.map +1 -1
- package/dist/postgres/column-mapping.js +6 -3
- package/dist/postgres/column-mapping.js.map +1 -1
- package/dist/postgres/entity-commit.d.ts +83 -14
- package/dist/postgres/entity-commit.d.ts.map +1 -1
- package/dist/postgres/entity-commit.js +383 -61
- package/dist/postgres/entity-commit.js.map +1 -1
- package/dist/postgres/entity-history.d.ts +4 -2
- package/dist/postgres/entity-history.d.ts.map +1 -1
- package/dist/postgres/entity-history.js +105 -32
- package/dist/postgres/entity-history.js.map +1 -1
- package/dist/postgres/id-column.d.ts.map +1 -1
- package/dist/postgres/id-column.js +6 -3
- package/dist/postgres/id-column.js.map +1 -1
- package/dist/postgres/record-storage.d.ts +146 -11
- package/dist/postgres/record-storage.d.ts.map +1 -1
- package/dist/postgres/record-storage.js +200 -27
- package/dist/postgres/record-storage.js.map +1 -1
- package/dist/postgres/storage-factory.d.ts +265 -22
- package/dist/postgres/storage-factory.d.ts.map +1 -1
- package/dist/postgres/storage-factory.js +784 -124
- package/dist/postgres/storage-factory.js.map +1 -1
- package/dist/postgres/table-initializer.d.ts +7 -4
- package/dist/postgres/table-initializer.d.ts.map +1 -1
- package/dist/postgres/table-initializer.js +21 -15
- package/dist/postgres/table-initializer.js.map +1 -1
- package/dist/postgres/table-resolver.d.ts +15 -1
- package/dist/postgres/table-resolver.d.ts.map +1 -1
- package/dist/postgres/table-resolver.js +36 -7
- package/dist/postgres/table-resolver.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/dist/postgres/agent-execution.d.ts +0 -285
- package/dist/postgres/agent-execution.d.ts.map +0 -1
- package/dist/postgres/agent-execution.js +0 -648
- 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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
42
|
+
* Registers a commit before invoking provider construction callbacks.
|
|
43
43
|
*
|
|
44
|
-
* @
|
|
45
|
-
* @
|
|
46
|
-
* @
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
139
|
+
* Closes every retained record-family handle.
|
|
83
140
|
*/
|
|
84
141
|
close() {
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
161
|
-
this
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
231
|
-
|
|
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)
|