@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.
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
@@ -4,10 +4,46 @@ import { RecordQuery, RecordStorage, type NormalizedQueryPlan, type RecordEntry,
4
4
  import type { PoolClient } from "pg";
5
5
  import type { PostgresParameter } from "./parameter.js";
6
6
  import type { PostgresTableSpec } from "./storage-factory.js";
7
+ /**
8
+ * Admits one complete public provider operation before application callbacks run.
9
+ * @internal
10
+ * @typeParam T The operation result.
11
+ * @param work The admitted operation.
12
+ * @returns The operation outcome.
13
+ */
14
+ export type OnPublicOperation = <T>(work: () => Promise<T>) => Promise<T>;
7
15
  /**
8
16
  * Manages PostgreSQL clients for one record-family handle.
9
17
  */
10
18
  export interface PostgresRecordLifecycle {
19
+ /**
20
+ * Resolves the selected database and its server-selected schema.
21
+ *
22
+ * @returns Completion after binding.
23
+ */
24
+ resolve(): Promise<void>;
25
+ /**
26
+ * Prepares factory-shared physical readiness for the resolved table.
27
+ *
28
+ * @param table The canonical physical table definition.
29
+ * @param customCreate Optional custom create SQL producer.
30
+ * @returns Completion after creation or remembered compatible readiness.
31
+ */
32
+ prepare(table: PostgresTableSpec<unknown, never>, customCreate?: () => string): Promise<void>;
33
+ /**
34
+ * Records one admitted asynchronous operation through factory close.
35
+ *
36
+ * @typeParam T The operation result.
37
+ * @param work The admitted operation promise.
38
+ * @returns The same operation outcome.
39
+ */
40
+ track<T>(work: Promise<T>): Promise<T>;
41
+ /**
42
+ * Checks the factory fence for public record operations.
43
+ *
44
+ * @returns Whether a new public operation may start.
45
+ */
46
+ publicOpen(): boolean;
11
47
  /**
12
48
  * Names the selected database for advisory coordination.
13
49
  */
@@ -55,14 +91,6 @@ export interface PostgresRecordExecutor<I, R extends Message> {
55
91
  * @returns Stored record when present.
56
92
  */
57
93
  read(client: PoolClient, id: I, lock?: "for-update"): Promise<R | undefined>;
58
- /**
59
- * Checks an immutable record on the Agent transaction client.
60
- *
61
- * @param client Active transaction client.
62
- * @param record Proposed immutable record.
63
- * @returns Completion after collision validation.
64
- */
65
- assertImmutable(client: PoolClient, record: R): Promise<void>;
66
94
  /**
67
95
  * Writes an immutable record on the Agent transaction client.
68
96
  *
@@ -124,9 +152,11 @@ export interface PostgresRecordExecutor<I, R extends Message> {
124
152
  */
125
153
  export declare class PostgresRecordStorage<I, R extends Message> extends RecordStorage<I, R> {
126
154
  #private;
127
- private readonly table;
155
+ private readonly tableNameValue;
156
+ private readonly onSchema;
128
157
  private readonly lifecycle;
129
158
  private readonly onClose;
159
+ private readonly create?;
130
160
  /**
131
161
  * Reports that compare-and-set uses a conditional SQL mutation.
132
162
  */
@@ -136,13 +166,20 @@ export declare class PostgresRecordStorage<I, R extends Message> extends RecordS
136
166
  *
137
167
  * @param context Identifies the storage boundary for this handle.
138
168
  * @param spec Defines the record type, ID type, and declared columns.
139
- * @param table Supplies the resolved physical table layout.
169
+ * @param tableNameValue Supplies the locally resolved physical table name.
170
+ * @param onSchema Builds the physical layout after schema resolution.
140
171
  * @param lifecycle Acquires clients for the selected database and schema.
141
172
  * @param onClose Removes this handle from the factory's live set.
142
173
  * @param create Optionally supplies custom create-table SQL.
143
174
  * @param stringifiers Converts message IDs and columns reversibly.
144
175
  */
145
- constructor(context: StorageContext, spec: RecordSpec<I, R>, table: PostgresTableSpec<I, R>, lifecycle: PostgresRecordLifecycle, onClose: () => void, create?: () => string, stringifiers?: StringifierRegistry);
176
+ constructor(context: StorageContext, spec: RecordSpec<I, R>, tableNameValue: string, onSchema: (schema: string) => PostgresTableSpec<I, R>, lifecycle: PostgresRecordLifecycle, onClose: () => void, create?: ((table: PostgresTableSpec<I, R>) => string) | undefined, stringifiers?: StringifierRegistry);
177
+ /**
178
+ * Returns the bound layout after preparation.
179
+ *
180
+ * @returns The resolved physical layout.
181
+ */
182
+ private get table();
146
183
  /**
147
184
  * Returns the resolved physical table name.
148
185
  *
@@ -155,6 +192,24 @@ export declare class PostgresRecordStorage<I, R extends Message> extends RecordS
155
192
  * @returns A promise that resolves after compatible initialization.
156
193
  */
157
194
  prepare(): Promise<void>;
195
+ /**
196
+ * Prepares an already admitted operation, including after the factory fence.
197
+ *
198
+ * @returns Completion after table preparation.
199
+ */
200
+ private prepareInternal;
201
+ /**
202
+ * Binds the destination and prepares its physical table.
203
+ *
204
+ * @returns Completion after physical table preparation.
205
+ */
206
+ private prepareBound;
207
+ /**
208
+ * Resolves the database before constructing the schema-bound initializer.
209
+ *
210
+ * @returns Completion after the initializer is available.
211
+ */
212
+ private bind;
158
213
  /**
159
214
  * Opens package-local bounded history operations for this record family.
160
215
  *
@@ -166,6 +221,64 @@ export declare class PostgresRecordStorage<I, R extends Message> extends RecordS
166
221
  * Closes this handle and unregisters it from the factory.
167
222
  */
168
223
  close(): void;
224
+ /**
225
+ * Registers a complete public operation before invoking record callbacks.
226
+ *
227
+ * @typeParam T The operation result.
228
+ * @param work Performs the public record operation.
229
+ * @returns The complete admitted outcome.
230
+ */
231
+ private admit;
232
+ /**
233
+ * Deletes one public record under the factory admission fence.
234
+ * @inheritdoc
235
+ */
236
+ delete(id: I): Promise<boolean>;
237
+ /**
238
+ * Reads one public record under the factory admission fence.
239
+ * @inheritdoc
240
+ */
241
+ read(id: I): Promise<R | undefined>;
242
+ /**
243
+ * Indexes public records under the factory admission fence.
244
+ * @inheritdoc
245
+ */
246
+ index(query?: RecordQuery<I>): Promise<readonly I[]>;
247
+ /**
248
+ * Queries public records under the factory admission fence.
249
+ * @inheritdoc
250
+ */
251
+ query(query?: RecordQuery<I>): Promise<readonly R[]>;
252
+ /**
253
+ * Queries public record entries under the factory admission fence.
254
+ * @inheritdoc
255
+ */
256
+ queryEntries(query?: RecordQuery<I>): Promise<readonly RecordEntry<I, R>[]>;
257
+ /**
258
+ * Queries a normalized plan under the factory admission fence.
259
+ * @inheritdoc
260
+ */
261
+ queryPlan(plan: NormalizedQueryPlan<I>): Promise<readonly R[]>;
262
+ /**
263
+ * Queries normalized entries under the factory admission fence.
264
+ * @inheritdoc
265
+ */
266
+ queryPlanEntries(plan: NormalizedQueryPlan<I>): Promise<readonly RecordEntry<I, R>[]>;
267
+ /**
268
+ * Writes a public record under the factory admission fence.
269
+ * @inheritdoc
270
+ */
271
+ write(record: R): Promise<void>;
272
+ /**
273
+ * Compares and sets a public record under the factory admission fence.
274
+ * @inheritdoc
275
+ */
276
+ compareAndSet(id: I, expected: R | undefined, next: R | undefined): Promise<boolean>;
277
+ /**
278
+ * Writes a public batch under the factory admission fence.
279
+ * @inheritdoc
280
+ */
281
+ writeAll(records: Iterable<R>): Promise<void>;
169
282
  /**
170
283
  * Writes an immutable record or confirms an identical existing payload.
171
284
  *
@@ -214,6 +327,12 @@ export declare class PostgresRecordStorage<I, R extends Message> extends RecordS
214
327
  * @returns The decoded records admitted by the plan.
215
328
  */
216
329
  protected queryPlanRecordEntries(plan: NormalizedQueryPlan<I>): Promise<readonly RecordEntry<I, R>[]>;
330
+ /**
331
+ * Checks plan columns and bind count before connecting to PostgreSQL.
332
+ *
333
+ * @param plan The normalized query definition.
334
+ */
335
+ private validatePlan;
217
336
  /**
218
337
  * Compares and conditionally mutates one row with a raw-byte SQL guard.
219
338
  *
@@ -273,6 +392,14 @@ export declare class PostgresRecordStorage<I, R extends Message> extends RecordS
273
392
  * @returns The value returned by the work.
274
393
  */
275
394
  private using;
395
+ /**
396
+ * Executes one prepared record operation on a released client.
397
+ *
398
+ * @typeParam T The operation result.
399
+ * @param work Performs the client operation.
400
+ * @returns The operation result.
401
+ */
402
+ private usePrepared;
276
403
  /**
277
404
  * Executes Agent-scoped record work under one PostgreSQL transaction.
278
405
  *
@@ -281,6 +408,14 @@ export declare class PostgresRecordStorage<I, R extends Message> extends RecordS
281
408
  * @returns Result after commit.
282
409
  */
283
410
  private transaction;
411
+ /**
412
+ * Executes one prepared Agent transaction on a released client.
413
+ *
414
+ * @typeParam T The transaction result.
415
+ * @param work Performs work on the transaction client.
416
+ * @returns The committed result.
417
+ */
418
+ private transactionPrepared;
284
419
  /**
285
420
  * Calculates a stable Agent advisory key in this database and schema.
286
421
  *
@@ -1 +1 @@
1
- {"version":3,"file":"record-storage.d.ts","sourceRoot":"","sources":["../../src/postgres/record-storage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"record-storage.d.ts","sourceRoot":"","sources":["../../src/postgres/record-storage.ts"],"names":[],"mappings":"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"}
@@ -12,13 +12,12 @@
12
12
  * the License.
13
13
  */
14
14
  import { fromBinary, ScalarType, toBinary } from "@bufbuild/protobuf";
15
- import { StringifierRegistry } from "@spine-event-engine/core";
15
+ import { defaultStringifierRegistry, StringifierRegistry } from "@spine-event-engine/core";
16
16
  import { ColumnMappings, defaultQueryCandidateLimit, RecordQuery, RecordStorage, } from "@spine-event-engine/storage";
17
17
  import { createHash } from "node:crypto";
18
18
  import { PostgresColumnMapping } from "./column-mapping.js";
19
19
  import { PostgresStorageDataError, PostgresStorageOperationError, PostgresClientDisposal, } from "./errors.js";
20
20
  import { PostgresIdColumn } from "./id-column.js";
21
- import { PostgresTableInitializer } from "./table-initializer.js";
22
21
  const maximumNormalizedPlanBinds = 1_000;
23
22
  /**
24
23
  * Stores one record family in one qualified PostgreSQL table.
@@ -27,35 +26,50 @@ const maximumNormalizedPlanBinds = 1_000;
27
26
  * @typeParam R The stored Protobuf record type.
28
27
  */
29
28
  export class PostgresRecordStorage extends RecordStorage {
30
- table;
29
+ tableNameValue;
30
+ onSchema;
31
31
  lifecycle;
32
32
  onClose;
33
+ create;
33
34
  /**
34
35
  * Reports that compare-and-set uses a conditional SQL mutation.
35
36
  */
36
37
  atomicCompareAndSet = true;
37
38
  #idColumn;
38
39
  #columns;
39
- #initializer;
40
+ #binding;
41
+ #table;
40
42
  /**
41
43
  * Creates a PostgreSQL record-family handle.
42
44
  *
43
45
  * @param context Identifies the storage boundary for this handle.
44
46
  * @param spec Defines the record type, ID type, and declared columns.
45
- * @param table Supplies the resolved physical table layout.
47
+ * @param tableNameValue Supplies the locally resolved physical table name.
48
+ * @param onSchema Builds the physical layout after schema resolution.
46
49
  * @param lifecycle Acquires clients for the selected database and schema.
47
50
  * @param onClose Removes this handle from the factory's live set.
48
51
  * @param create Optionally supplies custom create-table SQL.
49
52
  * @param stringifiers Converts message IDs and columns reversibly.
50
53
  */
51
- constructor(context, spec, table, lifecycle, onClose, create, stringifiers = new StringifierRegistry()) {
54
+ constructor(context, spec, tableNameValue, onSchema, lifecycle, onClose, create, stringifiers = defaultStringifierRegistry) {
52
55
  super(context, spec);
53
- this.table = table;
56
+ this.tableNameValue = tableNameValue;
57
+ this.onSchema = onSchema;
54
58
  this.lifecycle = lifecycle;
55
59
  this.onClose = onClose;
60
+ this.create = create;
56
61
  this.#idColumn = new PostgresIdColumn(spec.idType, stringifiers);
57
62
  this.#columns = new PostgresColumnMapping(stringifiers);
58
- this.#initializer = new PostgresTableInitializer(lifecycle, table.schema, table, create);
63
+ }
64
+ /**
65
+ * Returns the bound layout after preparation.
66
+ *
67
+ * @returns The resolved physical layout.
68
+ */
69
+ get table() {
70
+ if (this.#table === undefined)
71
+ throw new Error("PostgreSQL record table is not bound.");
72
+ return this.#table;
59
73
  }
60
74
  /**
61
75
  * Returns the resolved physical table name.
@@ -63,7 +77,7 @@ export class PostgresRecordStorage extends RecordStorage {
63
77
  * @returns The physical table name without its schema.
64
78
  */
65
79
  get tableName() {
66
- return this.table.tableName;
80
+ return this.tableNameValue;
67
81
  }
68
82
  /**
69
83
  * Prepares this table before a record operation.
@@ -71,7 +85,39 @@ export class PostgresRecordStorage extends RecordStorage {
71
85
  * @returns A promise that resolves after compatible initialization.
72
86
  */
73
87
  prepare() {
74
- return this.#initializer.prepare();
88
+ return this.admit(() => this.prepareInternal());
89
+ }
90
+ /**
91
+ * Prepares an already admitted operation, including after the factory fence.
92
+ *
93
+ * @returns Completion after table preparation.
94
+ */
95
+ prepareInternal() {
96
+ return this.lifecycle.track(Promise.resolve().then(() => this.prepareBound()));
97
+ }
98
+ /**
99
+ * Binds the destination and prepares its physical table.
100
+ *
101
+ * @returns Completion after physical table preparation.
102
+ */
103
+ async prepareBound() {
104
+ this.#binding ??= this.bind().catch((error) => {
105
+ this.#binding = undefined;
106
+ throw error;
107
+ });
108
+ await this.#binding;
109
+ const create = this.create;
110
+ await this.lifecycle.prepare(this.table, create === undefined ? undefined : () => create(this.table));
111
+ }
112
+ /**
113
+ * Resolves the database before constructing the schema-bound initializer.
114
+ *
115
+ * @returns Completion after the initializer is available.
116
+ */
117
+ async bind() {
118
+ await this.lifecycle.resolve();
119
+ const table = this.onSchema(this.lifecycle.schema);
120
+ this.#table = table;
75
121
  }
76
122
  /**
77
123
  * Opens package-local bounded history operations for this record family.
@@ -85,11 +131,6 @@ export class PostgresRecordStorage extends RecordStorage {
85
131
  transaction: (work) => this.transaction(work),
86
132
  using: (work) => this.using(work),
87
133
  read: (client, id, lock) => this.readOn(client, id, lock === "for-update"),
88
- assertImmutable: async (client, record) => {
89
- const existing = await this.readOn(client, this.recordSpec.idValueIn(record));
90
- if (existing !== undefined && !this.same(existing, record))
91
- throw new PostgresStorageOperationError("PostgreSQL immutable record collides.");
92
- },
93
134
  appendImmutable: (client, record) => this.appendImmutableOn(client, record),
94
135
  write: (client, record) => this.writeOn(client, record),
95
136
  query: (client, sql, values) => this.historyEntries(client, sql, values),
@@ -107,14 +148,104 @@ export class PostgresRecordStorage extends RecordStorage {
107
148
  super.close();
108
149
  this.onClose();
109
150
  }
151
+ /**
152
+ * Registers a complete public operation before invoking record callbacks.
153
+ *
154
+ * @typeParam T The operation result.
155
+ * @param work Performs the public record operation.
156
+ * @returns The complete admitted outcome.
157
+ */
158
+ admit(work) {
159
+ if (!this.isOpen() || !this.lifecycle.publicOpen())
160
+ return Promise.reject(new Error("PostgreSQL record storage is closed."));
161
+ const { promise, resolve, reject } = Promise.withResolvers();
162
+ void this.lifecycle.track(promise);
163
+ try {
164
+ void work().then(resolve, reject);
165
+ }
166
+ catch (error) {
167
+ reject(error);
168
+ }
169
+ return promise;
170
+ }
171
+ /**
172
+ * Deletes one public record under the factory admission fence.
173
+ * @inheritdoc
174
+ */
175
+ delete(id) {
176
+ return this.admit(() => super.delete(id));
177
+ }
178
+ /**
179
+ * Reads one public record under the factory admission fence.
180
+ * @inheritdoc
181
+ */
182
+ read(id) {
183
+ return this.admit(() => super.read(id));
184
+ }
185
+ /**
186
+ * Indexes public records under the factory admission fence.
187
+ * @inheritdoc
188
+ */
189
+ index(query = {}) {
190
+ return this.admit(() => super.index(query));
191
+ }
192
+ /**
193
+ * Queries public records under the factory admission fence.
194
+ * @inheritdoc
195
+ */
196
+ query(query = {}) {
197
+ return this.admit(() => super.query(query));
198
+ }
199
+ /**
200
+ * Queries public record entries under the factory admission fence.
201
+ * @inheritdoc
202
+ */
203
+ queryEntries(query = {}) {
204
+ return this.admit(() => super.queryEntries(query));
205
+ }
206
+ /**
207
+ * Queries a normalized plan under the factory admission fence.
208
+ * @inheritdoc
209
+ */
210
+ queryPlan(plan) {
211
+ return this.admit(async () => (await super.queryPlanEntries(plan)).map((entry) => entry.record));
212
+ }
213
+ /**
214
+ * Queries normalized entries under the factory admission fence.
215
+ * @inheritdoc
216
+ */
217
+ queryPlanEntries(plan) {
218
+ return this.admit(() => super.queryPlanEntries(plan));
219
+ }
220
+ /**
221
+ * Writes a public record under the factory admission fence.
222
+ * @inheritdoc
223
+ */
224
+ write(record) {
225
+ return this.admit(() => super.write(record));
226
+ }
227
+ /**
228
+ * Compares and sets a public record under the factory admission fence.
229
+ * @inheritdoc
230
+ */
231
+ compareAndSet(id, expected, next) {
232
+ return this.admit(() => super.compareAndSet(id, expected, next));
233
+ }
234
+ /**
235
+ * Writes a public batch under the factory admission fence.
236
+ * @inheritdoc
237
+ */
238
+ writeAll(records) {
239
+ return this.admit(() => super.writeAll(records));
240
+ }
110
241
  /**
111
242
  * Writes an immutable record or confirms an identical existing payload.
112
243
  *
113
244
  * @param record Provides the immutable record to store.
114
245
  * @returns A promise that resolves when the payload is stored or confirmed.
115
246
  */
116
- async writeImmutable(record) {
117
- await this.using((client) => this.appendImmutableOn(client, record)).catch((error) => {
247
+ writeImmutable(record) {
248
+ return this.admit(() => this.using((client) => this.appendImmutableOn(client, record))).catch((error) => {
118
249
  throw operationError(error);
119
250
  });
120
251
  }
@@ -125,7 +256,7 @@ export class PostgresRecordStorage extends RecordStorage {
125
256
  * @returns A promise that rejects when the stored payload differs.
126
257
  */
127
258
  async assertImmutable(record) {
128
- const existing = await this.read(this.recordSpec.idValueIn(record));
259
+ const existing = await this.admit(() => super.read(this.recordSpec.idValueIn(record)));
129
260
  if (existing === undefined || this.same(existing, record))
130
261
  return;
131
262
  throw new PostgresStorageOperationError("PostgreSQL immutable record collides.");
@@ -159,8 +290,7 @@ export class PostgresRecordStorage extends RecordStorage {
159
290
  async queryRecordEntries(query) {
160
291
  if (query.ids?.length === 0)
161
292
  return [];
162
- const compiled = this.recordQuery(query);
163
- return this.using((client) => this.entries(client, compiled));
293
+ return this.using((client) => this.entries(client, this.recordQuery(query)));
164
294
  }
165
295
  /**
166
296
  * Returns complete normalized-plan pushdown capabilities.
@@ -180,8 +310,28 @@ export class PostgresRecordStorage extends RecordStorage {
180
310
  * @returns The decoded records admitted by the plan.
181
311
  */
182
312
  async queryPlanRecordEntries(plan) {
183
- const compiled = this.planQuery(plan);
184
- return this.using((client) => this.entries(client, compiled));
313
+ this.validatePlan(plan);
314
+ return this.using((client) => this.entries(client, this.planQuery(plan)));
315
+ }
316
+ /**
317
+ * Checks plan columns and bind count before connecting to PostgreSQL.
318
+ *
319
+ * @param plan The normalized query definition.
320
+ */
321
+ validatePlan(plan) {
322
+ for (const item of plan.order ?? [])
323
+ this.column(item.column);
324
+ const visit = (predicate) => {
325
+ if (predicate.kind === "comparison")
326
+ this.column(predicate.column);
327
+ if (predicate.kind === "all" || predicate.kind === "either")
328
+ for (const child of predicate.predicates)
329
+ visit(child);
330
+ };
331
+ if (plan.predicate !== undefined)
332
+ visit(plan.predicate);
333
+ if (!plan.exhaustive && this.predicateBindCount(plan.predicate) >= maximumNormalizedPlanBinds)
334
+ throw new PostgresStorageOperationError("PostgreSQL normalized query exceeds the 1000-parameter bind budget.");
185
335
  }
186
336
  /**
187
337
  * Compares and conditionally mutates one row with a raw-byte SQL guard.
@@ -294,8 +444,18 @@ export class PostgresRecordStorage extends RecordStorage {
294
444
  * @param work Performs the client work.
295
445
  * @returns The value returned by the work.
296
446
  */
297
- async using(work) {
298
- await this.prepare();
447
+ using(work) {
448
+ return this.lifecycle.track(this.usePrepared(work));
449
+ }
450
+ /**
451
+ * Executes one prepared record operation on a released client.
452
+ *
453
+ * @typeParam T The operation result.
454
+ * @param work Performs the client operation.
455
+ * @returns The operation result.
456
+ */
457
+ async usePrepared(work) {
458
+ await this.prepareInternal();
299
459
  const client = await this.lifecycle.acquire();
300
460
  let discard = false;
301
461
  try {
@@ -316,8 +476,18 @@ export class PostgresRecordStorage extends RecordStorage {
316
476
  * @param work Operations to run on the acquired client.
317
477
  * @returns Result after commit.
318
478
  */
319
- async transaction(work) {
320
- await this.prepare();
479
+ transaction(work) {
480
+ return this.lifecycle.track(this.transactionPrepared(work));
481
+ }
482
+ /**
483
+ * Executes one prepared Agent transaction on a released client.
484
+ *
485
+ * @typeParam T The transaction result.
486
+ * @param work Performs work on the transaction client.
487
+ * @returns The committed result.
488
+ */
489
+ async transactionPrepared(work) {
490
+ await this.prepareInternal();
321
491
  const client = await this.lifecycle.acquire();
322
492
  let releaseFailure;
323
493
  try {
@@ -721,7 +891,10 @@ export class PostgresRecordStorage extends RecordStorage {
721
891
  afterTerm(column, direction, value, bind) {
722
892
  if (value === null)
723
893
  return direction === "desc" ? "FALSE" : `${column} IS NOT NULL`;
724
- return `(${column} IS NOT NULL AND ${column} ${direction === "desc" ? "<" : ">"} ${bind.add(value)})`;
894
+ const comparison = `(${column} IS NOT NULL AND ${column} ${direction === "desc" ? "<" : ">"} ${bind.add(value)})`;
895
+ return direction === "desc" && column !== '"ID"'
896
+ ? `(${comparison} OR ${column} IS NULL)`
897
+ : comparison;
725
898
  }
726
899
  /**
727
900
  * Builds SQL for one normalized query predicate recursively.