@oliphaunt/wasix-ts 0.1.0

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 (169) hide show
  1. package/ARCHITECTURE.md +655 -0
  2. package/CHANGELOG.md +33 -0
  3. package/LICENSE +21 -0
  4. package/README.md +404 -0
  5. package/THIRD_PARTY_NOTICES.md +20 -0
  6. package/lib/archive.d.ts +21 -0
  7. package/lib/archive.js +336 -0
  8. package/lib/asset-source.d.ts +4 -0
  9. package/lib/asset-source.js +15 -0
  10. package/lib/byte-channel.d.ts +27 -0
  11. package/lib/byte-channel.js +170 -0
  12. package/lib/client-common.d.ts +6 -0
  13. package/lib/client-common.js +36 -0
  14. package/lib/client.d.ts +7 -0
  15. package/lib/client.js +24 -0
  16. package/lib/database-root.d.ts +19 -0
  17. package/lib/database-root.js +139 -0
  18. package/lib/database.d.ts +135 -0
  19. package/lib/database.js +1039 -0
  20. package/lib/descriptor-validation.d.ts +10 -0
  21. package/lib/descriptor-validation.js +75 -0
  22. package/lib/direct-client-common.d.ts +50 -0
  23. package/lib/direct-client-common.js +717 -0
  24. package/lib/direct-client.d.ts +4 -0
  25. package/lib/direct-client.js +13 -0
  26. package/lib/direct.node.d.ts +2 -0
  27. package/lib/direct.node.js +2 -0
  28. package/lib/errors.d.ts +25 -0
  29. package/lib/errors.js +34 -0
  30. package/lib/extension-descriptor.d.ts +14 -0
  31. package/lib/extension-descriptor.js +419 -0
  32. package/lib/extensions.d.ts +68 -0
  33. package/lib/extensions.js +769 -0
  34. package/lib/host/LICENSE +21 -0
  35. package/lib/host/index.d.mts +123 -0
  36. package/lib/host/index.mjs +11 -0
  37. package/lib/host/provenance.json +16 -0
  38. package/lib/host/wasmer_js_bg.wasm +0 -0
  39. package/lib/host/worker.mjs +11 -0
  40. package/lib/host-runtime.d.ts +5 -0
  41. package/lib/host-runtime.js +13 -0
  42. package/lib/icu-descriptor.d.ts +3 -0
  43. package/lib/icu-descriptor.js +92 -0
  44. package/lib/index.bun.d.ts +2 -0
  45. package/lib/index.bun.js +2 -0
  46. package/lib/index.d.ts +2 -0
  47. package/lib/index.deno.d.ts +2 -0
  48. package/lib/index.deno.js +2 -0
  49. package/lib/index.js +2 -0
  50. package/lib/index.node.d.ts +2 -0
  51. package/lib/index.node.js +2 -0
  52. package/lib/internal-common.d.ts +12 -0
  53. package/lib/internal-common.js +274 -0
  54. package/lib/internal.d.ts +5 -0
  55. package/lib/internal.js +42 -0
  56. package/lib/internal.node.d.ts +5 -0
  57. package/lib/internal.node.js +8 -0
  58. package/lib/native-addon.d.ts +111 -0
  59. package/lib/native-addon.js +223 -0
  60. package/lib/native-server.d.ts +21 -0
  61. package/lib/native-server.js +109 -0
  62. package/lib/native-session.d.ts +60 -0
  63. package/lib/native-session.js +565 -0
  64. package/lib/node-actor.d.ts +7 -0
  65. package/lib/node-actor.js +10 -0
  66. package/lib/node-client-common.d.ts +9 -0
  67. package/lib/node-client-common.js +35 -0
  68. package/lib/node-client.d.ts +4 -0
  69. package/lib/node-client.js +13 -0
  70. package/lib/node-direct.d.ts +7 -0
  71. package/lib/node-direct.js +10 -0
  72. package/lib/node-worker-options.d.ts +5 -0
  73. package/lib/node-worker-options.js +65 -0
  74. package/lib/node-worker-port.d.ts +4 -0
  75. package/lib/node-worker-port.js +116 -0
  76. package/lib/node-worker.d.ts +1 -0
  77. package/lib/node-worker.js +38 -0
  78. package/lib/pgwire-connection.d.ts +60 -0
  79. package/lib/pgwire-connection.js +528 -0
  80. package/lib/pgwire.d.ts +3 -0
  81. package/lib/pgwire.js +105 -0
  82. package/lib/physical-archive.d.ts +29 -0
  83. package/lib/physical-archive.js +527 -0
  84. package/lib/protocol.d.ts +1 -0
  85. package/lib/protocol.js +1 -0
  86. package/lib/public.d.ts +4 -0
  87. package/lib/public.js +3 -0
  88. package/lib/query.d.ts +1 -0
  89. package/lib/query.js +1 -0
  90. package/lib/rpc.d.ts +203 -0
  91. package/lib/rpc.js +84 -0
  92. package/lib/runtime-descriptor.d.ts +3 -0
  93. package/lib/runtime-descriptor.js +79 -0
  94. package/lib/server.node.d.ts +1 -0
  95. package/lib/server.node.js +1 -0
  96. package/lib/startup-config.d.ts +2 -0
  97. package/lib/startup-config.js +19 -0
  98. package/lib/storage/bun.d.ts +6 -0
  99. package/lib/storage/bun.js +6 -0
  100. package/lib/storage/deno.d.ts +7 -0
  101. package/lib/storage/deno.js +7 -0
  102. package/lib/storage/incremental-storage.d.ts +25 -0
  103. package/lib/storage/incremental-storage.js +154 -0
  104. package/lib/storage/indexed-db-provider.d.ts +40 -0
  105. package/lib/storage/indexed-db-provider.js +259 -0
  106. package/lib/storage/indexed-db.d.ts +9 -0
  107. package/lib/storage/indexed-db.js +11 -0
  108. package/lib/storage/node.d.ts +10 -0
  109. package/lib/storage/node.js +13 -0
  110. package/lib/storage/opfs-pool.d.ts +31 -0
  111. package/lib/storage/opfs-pool.js +1271 -0
  112. package/lib/storage/opfs-provider.d.ts +4 -0
  113. package/lib/storage/opfs-provider.js +257 -0
  114. package/lib/storage/opfs.d.ts +8 -0
  115. package/lib/storage/opfs.js +10 -0
  116. package/lib/storage/restore-cleanup.d.ts +4 -0
  117. package/lib/storage/restore-cleanup.js +22 -0
  118. package/lib/storage/web-lock.d.ts +2 -0
  119. package/lib/storage/web-lock.js +64 -0
  120. package/lib/storage-provider.d.ts +47 -0
  121. package/lib/storage-provider.js +141 -0
  122. package/lib/storage-snapshot.d.ts +44 -0
  123. package/lib/storage-snapshot.js +274 -0
  124. package/lib/storage.d.ts +46 -0
  125. package/lib/storage.js +83 -0
  126. package/lib/tool-runtime.d.ts +43 -0
  127. package/lib/tool-runtime.js +93 -0
  128. package/lib/tool-worker-common.d.ts +48 -0
  129. package/lib/tool-worker-common.js +97 -0
  130. package/lib/tool-worker.d.ts +1 -0
  131. package/lib/tool-worker.js +10 -0
  132. package/lib/types.d.ts +230 -0
  133. package/lib/types.js +1 -0
  134. package/lib/wasix-runtime.d.ts +24 -0
  135. package/lib/wasix-runtime.js +186 -0
  136. package/lib/worker-client.d.ts +4 -0
  137. package/lib/worker-client.js +45 -0
  138. package/lib/worker-dispatch.d.ts +11 -0
  139. package/lib/worker-dispatch.js +174 -0
  140. package/lib/worker-entry.bun.d.ts +2 -0
  141. package/lib/worker-entry.bun.js +2 -0
  142. package/lib/worker-entry.d.ts +2 -0
  143. package/lib/worker-entry.deno.d.ts +2 -0
  144. package/lib/worker-entry.deno.js +2 -0
  145. package/lib/worker-entry.js +2 -0
  146. package/lib/worker-entry.node.d.ts +2 -0
  147. package/lib/worker-entry.node.js +2 -0
  148. package/lib/worker-node-client.d.ts +4 -0
  149. package/lib/worker-node-client.js +55 -0
  150. package/lib/worker-rpc.d.ts +36 -0
  151. package/lib/worker-rpc.js +422 -0
  152. package/lib/worker-transfer.d.ts +6 -0
  153. package/lib/worker-transfer.js +11 -0
  154. package/lib/worker.d.ts +1 -0
  155. package/lib/worker.js +21 -0
  156. package/lib/zstd.d.ts +4 -0
  157. package/lib/zstd.js +12 -0
  158. package/node_modules/@oliphaunt/js-core/README.md +7 -0
  159. package/node_modules/@oliphaunt/js-core/dist/commonjs/protocol.d.ts +1 -0
  160. package/node_modules/@oliphaunt/js-core/dist/commonjs/protocol.js +22 -0
  161. package/node_modules/@oliphaunt/js-core/dist/commonjs/query.d.ts +255 -0
  162. package/node_modules/@oliphaunt/js-core/dist/commonjs/query.js +2068 -0
  163. package/node_modules/@oliphaunt/js-core/dist/module/package.json +3 -0
  164. package/node_modules/@oliphaunt/js-core/dist/module/protocol.d.ts +1 -0
  165. package/node_modules/@oliphaunt/js-core/dist/module/protocol.js +19 -0
  166. package/node_modules/@oliphaunt/js-core/dist/module/query.d.ts +255 -0
  167. package/node_modules/@oliphaunt/js-core/dist/module/query.js +2039 -0
  168. package/node_modules/@oliphaunt/js-core/package.json +21 -0
  169. package/package.json +122 -0
@@ -0,0 +1,1039 @@
1
+ import { composeWasixStorageFailure, WasixStorageError } from './errors.js';
2
+ import { assertNoTransactionChain, decodeQueryResult, describeQuery, errorWithNotices, extendedQuery, inspectManagedTransactionResponse, inspectReadyForQuery, parseCommandResponse, parseDescribeResponse, parseExecResponse, parseQueryRawResponse, planQuery, structuredSimpleQuery, toUint8Array, } from './query.js';
3
+ const transactionPinnedMessage = 'Oliphaunt WASIX database is pinned to an active transaction; use the callback transaction handle';
4
+ const CLOSE_DEADLINE_MS = 120_000;
5
+ /** @internal Buffered protocol fallbacks use the same observable callback granularity. */
6
+ export const WASIX_PROTOCOL_CALLBACK_CHUNK_BYTES = 64 * 1024;
7
+ const pgDumpTargets = new WeakSet();
8
+ const nativeToolTargets = new WeakSet();
9
+ const protocolConnectionTargets = new WeakSet();
10
+ const transactionPinnedTargets = new WeakSet();
11
+ /** @internal Publish a promise before starting reentrant ownership teardown. */
12
+ export function createWasixDeferred() {
13
+ let resolve;
14
+ let reject;
15
+ const promise = new Promise((accept, decline) => {
16
+ resolve = accept;
17
+ reject = decline;
18
+ });
19
+ return { promise, resolve, reject };
20
+ }
21
+ /** @internal Validate the explicit PostgreSQL startup identity once at open. */
22
+ export function normalizeWasixDatabaseIdentity(username, database) {
23
+ assertWasixStartupIdentityValue('username', username);
24
+ assertWasixStartupIdentityValue('database', database);
25
+ return { username, database };
26
+ }
27
+ function assertWasixStartupIdentityValue(name, value) {
28
+ if (value.trim().length === 0)
29
+ throw new TypeError(`${name} must not be empty`);
30
+ if (value.includes('\0'))
31
+ throw new TypeError(`${name} must not contain NUL bytes`);
32
+ }
33
+ /**
34
+ * Tracks only session owners, never their public database handles.
35
+ *
36
+ * The finalizer's held value is an opaque generation token. The session lives
37
+ * in a separately keyed table, so an already queued finalizer can atomically
38
+ * prove that it still owns the exact registration before scheduling cleanup.
39
+ *
40
+ * @internal
41
+ */
42
+ export class WasixForgottenDatabaseRegistry {
43
+ #active = new Map();
44
+ #finalizer;
45
+ #schedule;
46
+ constructor(createRegistry = (cleanup) => new FinalizationRegistry(cleanup), schedule = queueMicrotask) {
47
+ this.#schedule = schedule;
48
+ this.#finalizer = createRegistry((generation) => this.#finalize(generation));
49
+ }
50
+ register(owner, session) {
51
+ // Object identity is the generation. It cannot wrap, be forged by a stale
52
+ // callback, or retain the public owner through the held value.
53
+ const generation = Object.freeze({});
54
+ this.#finalizer.register(owner, generation, generation);
55
+ this.#active.set(generation, session);
56
+ return Object.freeze({ generation });
57
+ }
58
+ unregister(registration) {
59
+ // Remove the cleanup authority first. A finalizer callback that was already
60
+ // queued then observes a stale generation and is a harmless no-op.
61
+ this.#active.delete(registration.generation);
62
+ this.#finalizer.unregister(registration.generation);
63
+ }
64
+ #finalize(generation) {
65
+ const session = this.#active.get(generation);
66
+ if (session === undefined)
67
+ return;
68
+ this.#active.delete(generation);
69
+ // Finalization callbacks must stay nonblocking. Caller-realm teardown may
70
+ // execute synchronous guest code, so even invoking close is deferred until
71
+ // after the callback returns. Cleanup is intentionally unobservable best
72
+ // effort; explicit close remains the only way to observe its outcome.
73
+ this.#schedule(() => {
74
+ void closeForgottenSession(session).catch(() => undefined);
75
+ });
76
+ }
77
+ }
78
+ async function closeForgottenSession(session) {
79
+ if (session.abort !== undefined) {
80
+ await session.abort();
81
+ return;
82
+ }
83
+ await session.close();
84
+ }
85
+ const forgottenDatabaseRegistry = typeof FinalizationRegistry === 'undefined' ? undefined : new WasixForgottenDatabaseRegistry();
86
+ /** @internal Public database state machine; construction stays behind Oliphaunt.open(). */
87
+ export class WasixDatabaseImpl {
88
+ #session;
89
+ #identity;
90
+ #forgottenRegistry;
91
+ #forgottenRegistration;
92
+ #resources = new Set();
93
+ #pendingProtocolConnections = new Set();
94
+ #tail = Promise.resolve();
95
+ #closed = false;
96
+ #closing = false;
97
+ #closeAttempt;
98
+ #persistenceFailure;
99
+ #transactionFailure;
100
+ #activeTransaction = false;
101
+ #transactionRunning = false;
102
+ #streamCallbackActive = false;
103
+ constructor(session, forgottenRegistry = forgottenDatabaseRegistry) {
104
+ this.#session = session;
105
+ this.#forgottenRegistry = forgottenRegistry;
106
+ this.#forgottenRegistration = forgottenRegistry?.register(this, session);
107
+ this.#identity = session.identity ?? {
108
+ username: 'postgres',
109
+ database: 'postgres',
110
+ };
111
+ if (session.runPgDump !== undefined)
112
+ pgDumpTargets.add(this);
113
+ if (session.runTool !== undefined)
114
+ nativeToolTargets.add(this);
115
+ if (session.supportsProtocolConnections === true && session.serve !== undefined) {
116
+ protocolConnectionTargets.add(this);
117
+ }
118
+ }
119
+ get closed() {
120
+ return this.#closed || this.#session.terminalState?.terminal === true;
121
+ }
122
+ async execute(sql, parameters = [], options = {}) {
123
+ this.#assertAvailable();
124
+ const plan = planQuery(sql, parameters, snapshotParameterOptions(options));
125
+ return this.#serialize(() => this.#runDatabasePlanUnlocked(plan, parseCommandResponse));
126
+ }
127
+ async query(sql, parameters = [], options = {}) {
128
+ this.#assertAvailable();
129
+ const stableOptions = snapshotQueryOptions(options);
130
+ const plan = planQuery(sql, parameters, stableOptions);
131
+ return this.#serialize(() => this.#runDatabasePlanUnlocked(plan, (response) => decodeQueryResult(parseQueryRawResponse(response), stableOptions)));
132
+ }
133
+ async queryRaw(sql, parameters = [], options = {}) {
134
+ this.#assertAvailable();
135
+ const plan = planQuery(sql, parameters, snapshotParameterOptions(options));
136
+ return this.#serialize(() => this.#runDatabasePlanUnlocked(plan, parseQueryRawResponse));
137
+ }
138
+ async exec(sql, options = {}) {
139
+ this.#assertAvailable();
140
+ const input = structuredSimpleQuery(sql);
141
+ const stableOptions = snapshotReadOptions(options);
142
+ return this.#serialize(() => this.#runDatabaseStructuredUnlocked(input, (response) => parseExecResponse(response, stableOptions)));
143
+ }
144
+ async describe(sql, parameterTypeOids = []) {
145
+ this.#assertAvailable();
146
+ const input = describeQuery(sql, [...parameterTypeOids]);
147
+ return this.#serialize(() => this.#runDatabaseStructuredUnlocked(input, parseDescribeResponse));
148
+ }
149
+ async execProtocolRaw(input) {
150
+ return this.#execOwned(toUint8Array(input).slice());
151
+ }
152
+ async execProtocolRawStream(input, onChunk) {
153
+ if (typeof onChunk !== 'function') {
154
+ throw new TypeError('raw protocol stream callback must be a function');
155
+ }
156
+ const consumer = this.#protocolChunkConsumer(onChunk);
157
+ this.#assertAvailable();
158
+ const bytes = toUint8Array(input).slice();
159
+ await this.#serialize(async () => {
160
+ this.#assertHealthy();
161
+ if (this.#session.execStream === undefined) {
162
+ let response;
163
+ try {
164
+ response = await this.#session.exec(bytes, 'sync');
165
+ }
166
+ catch (error) {
167
+ this.#recordUnknownExchange(error, 'streaming PostgreSQL transport outcome is unknown');
168
+ throw error;
169
+ }
170
+ for (let offset = 0; offset < response.length; offset += WASIX_PROTOCOL_CALLBACK_CHUNK_BYTES) {
171
+ consumer.callback(response.slice(offset, offset + WASIX_PROTOCOL_CALLBACK_CHUNK_BYTES));
172
+ }
173
+ return;
174
+ }
175
+ let outcome;
176
+ try {
177
+ outcome = await this.#session.execStream(bytes, consumer.callback, 'sync');
178
+ }
179
+ catch (error) {
180
+ this.#recordUnknownExchange(error, 'streaming PostgreSQL transport outcome is unknown');
181
+ throw error;
182
+ }
183
+ this.#resolveProtocolStreamOutcome(outcome, consumer.failure);
184
+ });
185
+ }
186
+ async backup() {
187
+ this.#assertAvailable();
188
+ return this.#serialize(async () => {
189
+ this.#assertHealthy();
190
+ if (this.#session.backup === undefined) {
191
+ throw new Error('this WASIX execution session does not support physical backup');
192
+ }
193
+ const bytes = await this.#session.backup();
194
+ await this.#syncPersistence('operation');
195
+ return bytes;
196
+ });
197
+ }
198
+ /** @internal Run pg_dump in the database-owning execution realm without another transport. */
199
+ runPgDump(options) {
200
+ this.#assertAvailable();
201
+ return this.#serialize(async () => {
202
+ this.#assertHealthy();
203
+ if (this.#session.runPgDump === undefined) {
204
+ throw new Error('this WASIX database entrypoint does not support pg_dump');
205
+ }
206
+ return this.#session.runPgDump(options);
207
+ });
208
+ }
209
+ /** @internal Run a packaged tool without the legacy Wasmer tool worker. */
210
+ runNativeTool(options) {
211
+ this.#assertAvailable();
212
+ return this.#serialize(async () => {
213
+ this.#assertHealthy();
214
+ if (this.#session.runTool === undefined) {
215
+ throw new Error('this WASIX database entrypoint does not support native tools');
216
+ }
217
+ return this.#session.runTool(options);
218
+ });
219
+ }
220
+ /** @internal Serialized connection ownership used by optional server/tool packages. */
221
+ runProtocolConnection(connection, mode) {
222
+ return this.reserveProtocolConnection(connection, mode).start();
223
+ }
224
+ /** @internal Reserve call order without entering the guest before a client is ready. */
225
+ reserveProtocolConnection(connection, mode) {
226
+ this.#assertAvailable();
227
+ let state = 'pending';
228
+ let cancellation;
229
+ let resolveStart;
230
+ const startDecision = new Promise((resolve) => {
231
+ resolveStart = resolve;
232
+ });
233
+ const pending = {
234
+ cancel(error) {
235
+ if (state !== 'pending')
236
+ return;
237
+ state = 'canceled';
238
+ cancellation = error;
239
+ resolveStart?.(false);
240
+ },
241
+ };
242
+ this.#pendingProtocolConnections.add(pending);
243
+ const serving = this.#serialize(async () => {
244
+ const start = await startDecision;
245
+ this.#pendingProtocolConnections.delete(pending);
246
+ if (!start)
247
+ return;
248
+ this.#assertHealthy();
249
+ if (this.#session.serve === undefined) {
250
+ throw new Error('this WASIX database entrypoint does not support protocol connections');
251
+ }
252
+ if (!this.#session.supportsProtocolConnections) {
253
+ throw new Error('WASIX psql and local servers require @oliphaunt/wasix-ts/worker');
254
+ }
255
+ try {
256
+ await this.#session.serve(connection, mode);
257
+ }
258
+ catch (error) {
259
+ if (error instanceof WasixStorageError)
260
+ this.#persistenceFailure = error;
261
+ throw error;
262
+ }
263
+ });
264
+ return {
265
+ start() {
266
+ if (state === 'canceled') {
267
+ throw cancellation ?? new Error('Oliphaunt WASIX protocol connection was canceled');
268
+ }
269
+ if (state === 'pending') {
270
+ state = 'started';
271
+ resolveStart?.(true);
272
+ }
273
+ return serving;
274
+ },
275
+ cancel() {
276
+ pending.cancel(new Error('Oliphaunt WASIX protocol connection was canceled before start'));
277
+ },
278
+ };
279
+ }
280
+ #execOwned(bytes) {
281
+ this.#assertAvailable();
282
+ return this.#serialize(async () => {
283
+ // A persistence failure may have been ahead of this operation in the
284
+ // serialized queue. Recheck here so queued work cannot run against the
285
+ // unpublished, deliberately poisoned generation.
286
+ this.#assertHealthy();
287
+ try {
288
+ return await this.#session.exec(bytes, 'sync');
289
+ }
290
+ catch (error) {
291
+ if (error instanceof WasixStorageError)
292
+ this.#persistenceFailure = error;
293
+ throw error;
294
+ }
295
+ });
296
+ }
297
+ async #runDatabasePlanUnlocked(plan, decode) {
298
+ if (plan.kind === 'complete') {
299
+ return this.#runDatabaseStructuredUnlocked(plan.input, decode);
300
+ }
301
+ const describedExchange = await this.#exchangeKnownUnlocked(plan.input, 'database');
302
+ if (describedExchange.status !== 'idle') {
303
+ return this.#finishDatabaseExchangeUnlocked(describedExchange, parseDescribeResponse, 'operation');
304
+ }
305
+ let description;
306
+ try {
307
+ description = parseDescribeResponse(describedExchange.response);
308
+ }
309
+ catch (error) {
310
+ return this.#publishLogicalFailureUnlocked(error, 'operation');
311
+ }
312
+ let input;
313
+ try {
314
+ input = plan.bind(description.parameterTypeOids);
315
+ }
316
+ catch (error) {
317
+ return this.#publishLogicalFailureUnlocked(errorWithNotices(error, description.notices), 'operation');
318
+ }
319
+ const finalExchange = await this.#exchangeKnownUnlocked(input, 'database');
320
+ return this.#finishDatabaseExchangeUnlocked(finalExchange, (response) => {
321
+ try {
322
+ return prependNotices(decode(response), description.notices);
323
+ }
324
+ catch (error) {
325
+ throw errorWithNotices(error, description.notices);
326
+ }
327
+ }, 'operation');
328
+ }
329
+ async #runDatabaseStructuredUnlocked(input, decode, boundary = 'operation') {
330
+ const exchange = await this.#exchangeKnownUnlocked(input, 'database');
331
+ return this.#finishDatabaseExchangeUnlocked(exchange, decode, boundary);
332
+ }
333
+ async #finishDatabaseExchangeUnlocked(exchange, decode, boundary) {
334
+ if (exchange.status !== 'idle') {
335
+ await this.#executeTransactionControlUnlocked('ROLLBACK');
336
+ }
337
+ let result;
338
+ let failure;
339
+ try {
340
+ result = decode(exchange.response);
341
+ if (exchange.status !== 'idle') {
342
+ failure = new Error(`structured database operation ended with PostgreSQL transaction status ${exchange.status}; Oliphaunt rolled it back`);
343
+ if (isNoticeCarrier(result)) {
344
+ failure = errorWithNotices(failure, result.notices);
345
+ }
346
+ }
347
+ }
348
+ catch (error) {
349
+ failure = error;
350
+ }
351
+ try {
352
+ await this.#syncPersistence(boundary);
353
+ }
354
+ catch (persistenceError) {
355
+ if (failure !== undefined) {
356
+ throw composeWasixStorageFailure(persistenceError, 'operation also failed', failure);
357
+ }
358
+ throw persistenceError;
359
+ }
360
+ if (failure !== undefined)
361
+ throw failure;
362
+ return result;
363
+ }
364
+ async #publishLogicalFailureUnlocked(failure, boundary) {
365
+ try {
366
+ await this.#syncPersistence(boundary);
367
+ }
368
+ catch (persistenceError) {
369
+ throw composeWasixStorageFailure(persistenceError, 'operation also failed', failure);
370
+ }
371
+ throw failure;
372
+ }
373
+ async #runTransactionPlanUnlocked(plan, decode) {
374
+ if (plan.kind === 'complete') {
375
+ return this.#runTransactionStructuredUnlocked(plan.input, decode);
376
+ }
377
+ const described = await this.#runTransactionStructuredUnlocked(plan.input, parseDescribeResponse);
378
+ let input;
379
+ try {
380
+ input = plan.bind(described.parameterTypeOids);
381
+ }
382
+ catch (error) {
383
+ throw errorWithNotices(error, described.notices);
384
+ }
385
+ try {
386
+ return prependNotices(await this.#runTransactionStructuredUnlocked(input, decode), described.notices);
387
+ }
388
+ catch (error) {
389
+ throw errorWithNotices(error, described.notices);
390
+ }
391
+ }
392
+ async #runTransactionStructuredUnlocked(input, decode) {
393
+ const exchange = await this.#exchangeKnownUnlocked(input, 'transaction');
394
+ return decode(exchange.response);
395
+ }
396
+ async #exchangeKnownUnlocked(input, scope) {
397
+ this.#assertHealthy();
398
+ let response;
399
+ try {
400
+ response = await this.#session.exec(input, 'defer');
401
+ }
402
+ catch (error) {
403
+ this.#recordUnknownExchange(error, 'structured PostgreSQL transport outcome is unknown');
404
+ throw error;
405
+ }
406
+ let status;
407
+ try {
408
+ status =
409
+ scope === 'transaction'
410
+ ? inspectManagedTransactionResponse(response)
411
+ : inspectReadyForQuery(response);
412
+ }
413
+ catch (error) {
414
+ this.#recordUnknownExchange(error, 'structured PostgreSQL response has no valid readiness boundary');
415
+ throw error;
416
+ }
417
+ return { response, status };
418
+ }
419
+ async #executeTransactionControlUnlocked(sql) {
420
+ const exchange = await this.#exchangeKnownUnlocked(extendedQuery(sql, []), 'control');
421
+ let result;
422
+ try {
423
+ result = parseCommandResponse(exchange.response);
424
+ }
425
+ catch (error) {
426
+ if (sql === 'BEGIN' && exchange.status !== 'idle') {
427
+ await this.#executeTransactionControlUnlocked('ROLLBACK').catch(() => undefined);
428
+ }
429
+ else if (sql !== 'BEGIN') {
430
+ this.#transactionFailure = asError(error, `${sql} outcome is unknown`);
431
+ }
432
+ throw error;
433
+ }
434
+ if (sql === 'BEGIN') {
435
+ if (result.commandTag === 'BEGIN' && exchange.status === 'transaction')
436
+ return undefined;
437
+ const error = transactionBoundaryError(sql, result.commandTag, exchange.status);
438
+ if (exchange.status !== 'idle') {
439
+ await this.#executeTransactionControlUnlocked('ROLLBACK');
440
+ }
441
+ throw error;
442
+ }
443
+ if (sql === 'COMMIT') {
444
+ if (result.commandTag === 'COMMIT' && exchange.status === 'idle')
445
+ return 'committed';
446
+ if (result.commandTag === 'ROLLBACK' && exchange.status === 'idle')
447
+ return 'rolledBack';
448
+ }
449
+ else if (result.commandTag === 'ROLLBACK' && exchange.status === 'idle') {
450
+ return undefined;
451
+ }
452
+ const error = transactionBoundaryError(sql, result.commandTag, exchange.status);
453
+ this.#transactionFailure = error;
454
+ throw error;
455
+ }
456
+ #recordUnknownExchange(error, message) {
457
+ if (error instanceof WasixStorageError) {
458
+ this.#persistenceFailure = error;
459
+ }
460
+ else {
461
+ this.#transactionFailure ??= asError(error, message);
462
+ }
463
+ }
464
+ #resolveProtocolStreamOutcome(outcome, failure) {
465
+ try {
466
+ resolveProtocolStreamOutcome(outcome, failure);
467
+ }
468
+ catch (error) {
469
+ if (error instanceof WasixProtocolStreamInvariantError) {
470
+ this.#recordUnknownExchange(error, 'streaming PostgreSQL transport outcome is unknown');
471
+ }
472
+ throw error;
473
+ }
474
+ }
475
+ async transaction(body) {
476
+ this.#assertAvailable();
477
+ if (typeof body !== 'function') {
478
+ throw new TypeError('Oliphaunt WASIX transaction body must be a function');
479
+ }
480
+ this.#activeTransaction = true;
481
+ transactionPinnedTargets.add(this);
482
+ const attempt = this.#serialize(async () => {
483
+ // A close may cancel this transaction while it is still queued behind
484
+ // an unstarted protocol reservation. Once execution enters PostgreSQL,
485
+ // close remains prohibited until the callback transaction is finished.
486
+ this.#assertOpen();
487
+ this.#transactionRunning = true;
488
+ const transaction = new WasixTransactionImpl((plan, decode) => this.#runTransactionPlanUnlocked(plan, decode), (input, decode) => this.#runTransactionStructuredUnlocked(input, decode), () => this.#executeTransactionControlUnlocked('ROLLBACK').then(() => undefined));
489
+ try {
490
+ try {
491
+ await this.#executeTransactionControlUnlocked('BEGIN');
492
+ }
493
+ catch (error) {
494
+ if (this.#transactionFailure === undefined && this.#persistenceFailure === undefined) {
495
+ try {
496
+ await this.#syncPersistence('operation');
497
+ }
498
+ catch (persistenceError) {
499
+ throw composeWasixStorageFailure(persistenceError, 'transaction also failed', error);
500
+ }
501
+ }
502
+ throw error;
503
+ }
504
+ let result;
505
+ try {
506
+ result = await body(transaction);
507
+ await transaction.sealAndDrain();
508
+ }
509
+ catch (error) {
510
+ transaction.seal();
511
+ await transaction.drain();
512
+ let rolledBack = transaction.rolledBack;
513
+ let rollbackFailure;
514
+ if (transaction.rollbackStarted) {
515
+ try {
516
+ await transaction.waitForRollback();
517
+ rolledBack = transaction.rolledBack;
518
+ }
519
+ catch (rollbackError) {
520
+ rollbackFailure = rollbackError;
521
+ }
522
+ }
523
+ else if (this.#transactionFailure === undefined &&
524
+ this.#persistenceFailure === undefined) {
525
+ try {
526
+ await this.#executeTransactionControlUnlocked('ROLLBACK');
527
+ rolledBack = true;
528
+ }
529
+ catch (rollbackError) {
530
+ rollbackFailure = rollbackError;
531
+ }
532
+ }
533
+ if (rollbackFailure !== undefined && rollbackFailure !== error) {
534
+ throw transactionCallbackAggregate(error, rollbackFailure, 'transaction callback and rollback both failed');
535
+ }
536
+ if (rolledBack) {
537
+ try {
538
+ await this.#syncPersistence('operation');
539
+ }
540
+ catch (persistenceError) {
541
+ throw composeWasixStorageFailure(persistenceError, 'transaction also failed', error);
542
+ }
543
+ }
544
+ const sessionFailure = this.#persistenceFailure ?? this.#transactionFailure;
545
+ const databaseFailure = sessionFailure === undefined ? undefined : (transaction.firstFailure ?? sessionFailure);
546
+ if (databaseFailure !== undefined && databaseFailure !== error) {
547
+ throw transactionCallbackAggregate(error, databaseFailure, 'transaction callback and an independent database failure both occurred');
548
+ }
549
+ throw error;
550
+ }
551
+ if (transaction.rolledBack) {
552
+ await this.#syncPersistence('operation');
553
+ return result;
554
+ }
555
+ if (this.#transactionFailure !== undefined && transaction.firstFailure !== undefined) {
556
+ throw transaction.firstFailure;
557
+ }
558
+ const outcome = await this.#executeTransactionControlUnlocked('COMMIT');
559
+ if (outcome === 'committed') {
560
+ await this.#syncPersistence('operation');
561
+ return result;
562
+ }
563
+ const primary = transaction.firstFailure ??
564
+ new Error('PostgreSQL rolled back the transaction instead of committing');
565
+ try {
566
+ await this.#syncPersistence('operation');
567
+ }
568
+ catch (persistenceError) {
569
+ throw composeWasixStorageFailure(persistenceError, 'transaction also failed', primary);
570
+ }
571
+ throw primary;
572
+ }
573
+ finally {
574
+ transaction.deactivate();
575
+ this.#transactionRunning = false;
576
+ }
577
+ });
578
+ return await attempt.finally(() => {
579
+ this.#activeTransaction = false;
580
+ transactionPinnedTargets.delete(this);
581
+ });
582
+ }
583
+ close() {
584
+ try {
585
+ this.#assertNotInProtocolStreamCallback();
586
+ }
587
+ catch (error) {
588
+ return Promise.reject(error);
589
+ }
590
+ if (this.#closeAttempt !== undefined) {
591
+ return this.#closeAttempt;
592
+ }
593
+ if (this.#transactionRunning) {
594
+ return Promise.reject(new Error('cannot close Oliphaunt WASIX while a transaction is active'));
595
+ }
596
+ if (this.#forgottenRegistration !== undefined) {
597
+ this.#forgottenRegistry?.unregister(this.#forgottenRegistration);
598
+ }
599
+ this.#closing = true;
600
+ const closing = new Error('Oliphaunt WASIX database is closing');
601
+ for (const connection of this.#pendingProtocolConnections)
602
+ connection.cancel(closing);
603
+ const orderlyClose = this.#serialize(() => this.#session.close());
604
+ const sessionClose = this.#session.abort === undefined
605
+ ? orderlyClose
606
+ : withDeadline(orderlyClose, CLOSE_DEADLINE_MS, () => this.#session.abort?.());
607
+ const attempt = (async () => {
608
+ let sessionFailed = false;
609
+ let sessionFailure;
610
+ try {
611
+ await sessionClose;
612
+ }
613
+ catch (error) {
614
+ sessionFailed = true;
615
+ sessionFailure = error;
616
+ }
617
+ let cleanupFailed = false;
618
+ let cleanupFailure;
619
+ try {
620
+ await this.#closeResources();
621
+ }
622
+ catch (error) {
623
+ cleanupFailed = true;
624
+ cleanupFailure = error;
625
+ }
626
+ finally {
627
+ // Session close is a terminal ownership transfer, not a retryable
628
+ // operation. Worker close always destroys its transport, while direct
629
+ // close always attempts provider and allocation release. Advertising a
630
+ // reusable handle after either path rejects would be false.
631
+ this.#closed = true;
632
+ this.#closing = false;
633
+ }
634
+ if (sessionFailed && cleanupFailed) {
635
+ throw new AggregateError([sessionFailure, cleanupFailure], 'Oliphaunt WASIX database and resource cleanup both failed');
636
+ }
637
+ if (sessionFailed)
638
+ throw sessionFailure;
639
+ if (cleanupFailed)
640
+ throw cleanupFailure;
641
+ })();
642
+ this.#closeAttempt = attempt;
643
+ return attempt;
644
+ }
645
+ /** @internal Register package-owned state that must not outlive this database. */
646
+ registerResource(resource) {
647
+ this.#assertAvailable();
648
+ this.#resources.add(resource);
649
+ }
650
+ /** @internal Identity used by private protocol clients such as pg_dump and psql. */
651
+ get identity() {
652
+ return this.#identity;
653
+ }
654
+ async [Symbol.asyncDispose]() {
655
+ await this.close();
656
+ }
657
+ #assertOpen() {
658
+ this.#assertNotInProtocolStreamCallback();
659
+ const terminalFailure = this.#session.terminalState?.failure;
660
+ if (this.#closed || terminalFailure !== undefined) {
661
+ throw new Error('Oliphaunt WASIX database is closed', {
662
+ ...(terminalFailure === undefined ? {} : { cause: terminalFailure }),
663
+ });
664
+ }
665
+ if (this.#closing) {
666
+ throw new Error('Oliphaunt WASIX database is closing');
667
+ }
668
+ this.#assertHealthy();
669
+ }
670
+ #assertAvailable() {
671
+ this.#assertOpen();
672
+ if (this.#activeTransaction) {
673
+ throw new Error(transactionPinnedMessage);
674
+ }
675
+ }
676
+ #assertHealthy() {
677
+ if (this.#transactionFailure !== undefined) {
678
+ throw new Error('Oliphaunt WASIX database cannot be used after a transaction outcome became unknown; close and reopen it', { cause: this.#transactionFailure });
679
+ }
680
+ if (this.#persistenceFailure !== undefined) {
681
+ throw new WasixStorageError('Oliphaunt WASIX database cannot be used after a persistence boundary failed; close and reopen it', {
682
+ code: this.#persistenceFailure.code,
683
+ commitState: this.#persistenceFailure.commitState,
684
+ cause: this.#persistenceFailure,
685
+ });
686
+ }
687
+ }
688
+ #assertNotInProtocolStreamCallback() {
689
+ if (this.#streamCallbackActive) {
690
+ throw new Error('raw protocol stream callback must not reenter the same Oliphaunt database or transaction');
691
+ }
692
+ }
693
+ #protocolChunkConsumer(callback) {
694
+ return synchronousProtocolChunkConsumer(callback, () => {
695
+ this.#assertNotInProtocolStreamCallback();
696
+ this.#streamCallbackActive = true;
697
+ }, () => {
698
+ this.#streamCallbackActive = false;
699
+ });
700
+ }
701
+ async #syncPersistence(boundary) {
702
+ try {
703
+ await this.#session.sync(boundary);
704
+ }
705
+ catch (error) {
706
+ const failure = error instanceof WasixStorageError
707
+ ? error
708
+ : new WasixStorageError(`WASIX persistence ${boundary} failed`, {
709
+ code: 'publication-failed',
710
+ commitState: 'unknown',
711
+ cause: error,
712
+ });
713
+ // PostgreSQL may already have completed the operation in the live
714
+ // filesystem. No later query may widen the gap after publication fails.
715
+ this.#persistenceFailure = failure;
716
+ throw failure;
717
+ }
718
+ }
719
+ #serialize(operation) {
720
+ const result = this.#tail.then(operation, operation);
721
+ this.#tail = result.then(() => undefined, () => undefined);
722
+ return result;
723
+ }
724
+ async #closeResources() {
725
+ const resources = [...this.#resources];
726
+ // Every registered owner gets exactly one teardown attempt. Drop the
727
+ // ownership graph before awaiting userland cleanup so a failed terminal
728
+ // close cannot retain otherwise collectible resources indefinitely.
729
+ this.#resources.clear();
730
+ const results = await Promise.allSettled(resources.map(async (resource) => resource.close()));
731
+ const failures = [];
732
+ for (let index = 0; index < results.length; index += 1) {
733
+ const result = results[index];
734
+ if (result.status === 'rejected') {
735
+ failures.push(result.reason);
736
+ }
737
+ }
738
+ if (failures.length > 0) {
739
+ throw new AggregateError(failures, 'Oliphaunt WASIX database resource cleanup failed');
740
+ }
741
+ }
742
+ }
743
+ /** @internal Optional packages acquire exclusive ownership without expanding the public class. */
744
+ export function runWasixProtocolConnection(database, connection, mode) {
745
+ assertWasixProtocolConnectionTarget(database);
746
+ return database.runProtocolConnection(connection, mode);
747
+ }
748
+ /** @internal Execute pg_dump in the realm that owns this database. */
749
+ export function runWasixPgDumpProcess(database, options) {
750
+ assertWasixPgDumpTarget(database);
751
+ return database.runPgDump(options);
752
+ }
753
+ /** @internal Return undefined only when this realm uses the browser Wasmer backend. */
754
+ export function tryRunWasixNativeToolProcess(database, options) {
755
+ assertWasixDatabaseTarget(database);
756
+ if (!nativeToolTargets.has(database))
757
+ return undefined;
758
+ return database.runNativeTool(options);
759
+ }
760
+ /** @internal Reserve a Worker-owned connection in the database FIFO. */
761
+ export function reserveWasixProtocolConnection(database, connection, mode) {
762
+ assertWasixProtocolConnectionTarget(database);
763
+ return database.reserveProtocolConnection(connection, mode);
764
+ }
765
+ /** @internal Fail before loading a tool or opening a listener for unsupported placement. */
766
+ export function assertWasixProtocolConnectionTarget(database) {
767
+ assertWasixDatabaseTarget(database);
768
+ if (!protocolConnectionTargets.has(database)) {
769
+ throw new Error('WASIX psql and local servers require @oliphaunt/wasix-ts/worker');
770
+ }
771
+ }
772
+ /** @internal Accept both public entrypoints that own the co-located runner. */
773
+ function assertWasixPgDumpTarget(database) {
774
+ assertWasixDatabaseTarget(database);
775
+ if (!pgDumpTargets.has(database)) {
776
+ throw new Error('this WASIX database entrypoint does not support pg_dump');
777
+ }
778
+ }
779
+ function assertWasixDatabaseTarget(database) {
780
+ if (!(database instanceof WasixDatabaseImpl)) {
781
+ throw new TypeError('database is not an @oliphaunt/wasix-ts handle');
782
+ }
783
+ if (transactionPinnedTargets.has(database)) {
784
+ throw new Error(transactionPinnedMessage);
785
+ }
786
+ }
787
+ /** @internal Attach an optional-package resource without adding public database methods. */
788
+ export function registerWasixDatabaseResource(database, resource) {
789
+ assertWasixProtocolConnectionTarget(database);
790
+ database.registerResource(resource);
791
+ }
792
+ /** @internal Return the exact startup identity owned by a WASIX database handle. */
793
+ export function getWasixDatabaseIdentity(database) {
794
+ assertWasixDatabaseTarget(database);
795
+ return database.identity;
796
+ }
797
+ function withDeadline(operation, milliseconds, expire) {
798
+ return new Promise((resolve, reject) => {
799
+ let expired = false;
800
+ const timer = setTimeout(() => {
801
+ expired = true;
802
+ const timeout = new WasixCloseTimeoutError(milliseconds);
803
+ void Promise.resolve()
804
+ .then(expire)
805
+ .then(() => reject(timeout), (abortFailure) => reject(new AggregateError([timeout, abortFailure], 'Oliphaunt WASIX orderly close timed out and forced termination failed')));
806
+ }, milliseconds);
807
+ void operation.then((value) => {
808
+ if (expired)
809
+ return;
810
+ clearTimeout(timer);
811
+ resolve(value);
812
+ }, (error) => {
813
+ if (expired)
814
+ return;
815
+ clearTimeout(timer);
816
+ reject(error);
817
+ });
818
+ });
819
+ }
820
+ function transactionCallbackAggregate(callback, secondary, message) {
821
+ return new AggregateError([callback, secondary], message);
822
+ }
823
+ class WasixCloseTimeoutError extends Error {
824
+ constructor(milliseconds) {
825
+ super(`Oliphaunt WASIX close exceeded ${milliseconds}ms; worker termination was requested`);
826
+ this.name = 'WasixCloseTimeoutError';
827
+ }
828
+ }
829
+ class WasixTransactionImpl {
830
+ #runPlan;
831
+ #runStructured;
832
+ #rollbackControl;
833
+ #tail = Promise.resolve();
834
+ #state = 'active';
835
+ #failed = false;
836
+ #firstFailure;
837
+ #rollbackAttempt;
838
+ #rolledBack = false;
839
+ constructor(runPlan, runStructured, rollbackControl) {
840
+ this.#runPlan = runPlan;
841
+ this.#runStructured = runStructured;
842
+ this.#rollbackControl = rollbackControl;
843
+ }
844
+ get closed() {
845
+ return this.#state === 'closed';
846
+ }
847
+ get rollbackStarted() {
848
+ return this.#rollbackAttempt !== undefined;
849
+ }
850
+ get rolledBack() {
851
+ return this.#rolledBack;
852
+ }
853
+ get firstFailure() {
854
+ return this.#firstFailure;
855
+ }
856
+ execute(sql, parameters = [], options = {}) {
857
+ return this.#publicPromise(() => {
858
+ assertNoTransactionChain(sql);
859
+ const plan = planQuery(sql, parameters, snapshotParameterOptions(options));
860
+ return this.#enqueue(() => this.#runPlan(plan, parseCommandResponse));
861
+ });
862
+ }
863
+ query(sql, parameters = [], options = {}) {
864
+ return this.#publicPromise(() => {
865
+ assertNoTransactionChain(sql);
866
+ const stableOptions = snapshotQueryOptions(options);
867
+ const plan = planQuery(sql, parameters, stableOptions);
868
+ return this.#enqueue(async () => decodeQueryResult(await this.#runPlan(plan, parseQueryRawResponse), stableOptions));
869
+ });
870
+ }
871
+ queryRaw(sql, parameters = [], options = {}) {
872
+ return this.#publicPromise(() => {
873
+ assertNoTransactionChain(sql);
874
+ const plan = planQuery(sql, parameters, snapshotParameterOptions(options));
875
+ return this.#enqueue(() => this.#runPlan(plan, parseQueryRawResponse));
876
+ });
877
+ }
878
+ exec(sql, options = {}) {
879
+ return this.#publicPromise(() => {
880
+ assertNoTransactionChain(sql);
881
+ const input = structuredSimpleQuery(sql);
882
+ const stableOptions = snapshotReadOptions(options);
883
+ return this.#enqueue(() => this.#runStructured(input, (response) => parseExecResponse(response, stableOptions)));
884
+ });
885
+ }
886
+ describe(sql, parameterTypeOids = []) {
887
+ return this.#publicPromise(() => {
888
+ const input = describeQuery(sql, [...parameterTypeOids]);
889
+ return this.#enqueue(() => this.#runStructured(input, parseDescribeResponse));
890
+ });
891
+ }
892
+ rollback() {
893
+ try {
894
+ this.#assertActive();
895
+ }
896
+ catch (error) {
897
+ return Promise.reject(error);
898
+ }
899
+ this.#state = 'finishing';
900
+ const operation = this.#enqueueFinishing(this.#rollbackControl);
901
+ const attempt = operation.then(() => {
902
+ this.#rolledBack = true;
903
+ this.#state = 'closed';
904
+ }, (error) => {
905
+ this.#state = 'closed';
906
+ throw error;
907
+ });
908
+ this.#rollbackAttempt = attempt;
909
+ return attempt;
910
+ }
911
+ deactivate() {
912
+ this.#state = 'closed';
913
+ }
914
+ seal() {
915
+ if (this.#state === 'active')
916
+ this.#state = 'finishing';
917
+ }
918
+ async drain() {
919
+ await this.#tail;
920
+ }
921
+ async sealAndDrain() {
922
+ this.seal();
923
+ await this.#tail;
924
+ await this.#rollbackAttempt;
925
+ }
926
+ async waitForRollback() {
927
+ await this.#rollbackAttempt;
928
+ }
929
+ #assertActive() {
930
+ if (this.#state === 'finishing')
931
+ throw new Error('transaction is finishing');
932
+ if (this.#state === 'closed')
933
+ throw new Error('transaction is no longer active');
934
+ }
935
+ #publicPromise(operation) {
936
+ try {
937
+ return operation();
938
+ }
939
+ catch (error) {
940
+ return Promise.reject(error);
941
+ }
942
+ }
943
+ #enqueue(operation) {
944
+ try {
945
+ this.#assertActive();
946
+ }
947
+ catch (error) {
948
+ return Promise.reject(error);
949
+ }
950
+ return this.#enqueueFinishing(operation);
951
+ }
952
+ #enqueueFinishing(operation) {
953
+ const result = this.#tail.then(operation);
954
+ this.#tail = result.then(() => undefined, (error) => {
955
+ if (!this.#failed) {
956
+ this.#failed = true;
957
+ this.#firstFailure = error;
958
+ }
959
+ });
960
+ return result;
961
+ }
962
+ }
963
+ function snapshotParameterOptions(options) {
964
+ return Object.freeze({
965
+ ...(options.encoders === undefined ? {} : { encoders: Object.freeze({ ...options.encoders }) }),
966
+ });
967
+ }
968
+ function snapshotReadOptions(options) {
969
+ return Object.freeze({
970
+ rowMode: options.rowMode,
971
+ valueMode: options.valueMode,
972
+ ...(options.decoders === undefined ? {} : { decoders: Object.freeze({ ...options.decoders }) }),
973
+ });
974
+ }
975
+ function snapshotQueryOptions(options) {
976
+ return Object.freeze({
977
+ ...snapshotReadOptions(options),
978
+ ...snapshotParameterOptions(options),
979
+ });
980
+ }
981
+ function prependNotices(result, notices) {
982
+ if (notices.length > 0)
983
+ result.notices.unshift(...notices);
984
+ return result;
985
+ }
986
+ function isNoticeCarrier(value) {
987
+ return (value !== null &&
988
+ typeof value === 'object' &&
989
+ Array.isArray(value.notices));
990
+ }
991
+ function transactionBoundaryError(expected, actual, status) {
992
+ return new Error(`PostgreSQL transaction command expected ${expected} with its matching readiness status, got ${actual ?? 'no command tag'} with ${status}`);
993
+ }
994
+ function synchronousProtocolChunkConsumer(callback, enter = () => undefined, leave = () => undefined) {
995
+ const consumer = {
996
+ callback(chunk) {
997
+ enter();
998
+ try {
999
+ const result = callback(chunk);
1000
+ if (!isThenable(result))
1001
+ return;
1002
+ // The returned task cannot participate in synchronous backpressure.
1003
+ // Mark its rejection handled before failing deterministically.
1004
+ void Promise.resolve(result).catch(() => undefined);
1005
+ throw new TypeError('raw protocol stream callback must complete synchronously and must not return a Promise or thenable');
1006
+ }
1007
+ catch (error) {
1008
+ consumer.failure ??= { error };
1009
+ throw error;
1010
+ }
1011
+ finally {
1012
+ leave();
1013
+ }
1014
+ },
1015
+ };
1016
+ return consumer;
1017
+ }
1018
+ function resolveProtocolStreamOutcome(outcome, failure) {
1019
+ if (outcome === 'callbackAborted' && failure === undefined) {
1020
+ throw new WasixProtocolStreamInvariantError('WASIX runtime confirmed protocol callback recovery without a retained callback failure');
1021
+ }
1022
+ if (outcome === 'complete' && failure !== undefined) {
1023
+ throw new WasixProtocolStreamInvariantError('WASIX runtime reported protocol stream success after rejecting its callback');
1024
+ }
1025
+ if (failure !== undefined) {
1026
+ throw failure.error;
1027
+ }
1028
+ }
1029
+ class WasixProtocolStreamInvariantError extends Error {
1030
+ }
1031
+ function isThenable(value) {
1032
+ return (((typeof value === 'object' && value !== null) || typeof value === 'function') &&
1033
+ typeof value.then === 'function');
1034
+ }
1035
+ function asError(value, fallback) {
1036
+ if (value instanceof Error)
1037
+ return value;
1038
+ return new Error(fallback, { cause: value });
1039
+ }