@vibeorm/adapter-bun 2.0.0-alpha.8 → 2.0.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.
- package/README.md +2 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +477 -53
- package/dist/index.js.map +9 -7
- package/dist/savepoint-gate.d.ts +86 -0
- package/dist/savepoint-gate.d.ts.map +1 -0
- package/dist/template-cache.d.ts +46 -3
- package/dist/template-cache.d.ts.map +1 -1
- package/dist/transaction-budget.d.ts +144 -0
- package/dist/transaction-budget.d.ts.map +1 -0
- package/dist/transaction-sql.d.ts.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/adapter-bun/src/index.ts
|
|
3
3
|
var {SQL } = globalThis.Bun;
|
|
4
|
+
import { postgresDialect, renderDiagnostic } from "@vibeorm/sql";
|
|
4
5
|
|
|
5
6
|
// packages/adapter-bun/src/connection-url.ts
|
|
6
7
|
import { VibeError } from "@vibeorm/schema";
|
|
@@ -35,6 +36,7 @@ var BUN_SQLSTATE_ERROR_CODES = Object.freeze({
|
|
|
35
36
|
"23502": "VIBE_VALIDATION",
|
|
36
37
|
"23514": "VIBE_VALIDATION",
|
|
37
38
|
"22P02": "VIBE_VALIDATION",
|
|
39
|
+
"42501": "VIBE_PERMISSION_DENIED",
|
|
38
40
|
"40001": "VIBE_TRANSACTION",
|
|
39
41
|
"40P01": "VIBE_TRANSACTION",
|
|
40
42
|
"57014": "VIBE_TRANSACTION"
|
|
@@ -131,6 +133,128 @@ function serializeParamsRaw(params) {
|
|
|
131
133
|
return params.values.map((value) => Array.isArray(value) ? toPgArrayLiteral(value) : value);
|
|
132
134
|
}
|
|
133
135
|
|
|
136
|
+
// packages/adapter-bun/src/transaction-budget.ts
|
|
137
|
+
import { VibeError as VibeError4 } from "@vibeorm/schema";
|
|
138
|
+
var DEFAULT_DEADLINE_CLOCK = () => Date.now();
|
|
139
|
+
function validateTransactionDeadline(params) {
|
|
140
|
+
const { options, support, provider } = params;
|
|
141
|
+
const deadline = options?.deadline;
|
|
142
|
+
if (deadline === undefined)
|
|
143
|
+
return;
|
|
144
|
+
if (support === "unsupported") {
|
|
145
|
+
throw new VibeError4({
|
|
146
|
+
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
147
|
+
message: `${provider} cannot enforce a transaction deadline \u2014 remove the \`deadline\` option`,
|
|
148
|
+
meta: { provider, feature: "transactionDeadline", supported: support }
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (!Number.isInteger(deadline.totalMs) || deadline.totalMs <= 0) {
|
|
152
|
+
throw new VibeError4({
|
|
153
|
+
code: "VIBE_VALIDATION",
|
|
154
|
+
message: `transaction deadline \`totalMs\` must be a positive integer of milliseconds, received ${String(deadline.totalMs)}`,
|
|
155
|
+
meta: { provider, feature: "transactionDeadline", totalMs: deadline.totalMs }
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const requested = deadline.enforcement ?? "between-statements";
|
|
159
|
+
if (requested === "cancel-running-statements" && support !== "cancel-running-statements") {
|
|
160
|
+
throw new VibeError4({
|
|
161
|
+
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
162
|
+
message: `${provider} cannot cancel a statement that is already running, so the requested ` + "`cancel-running-statements` deadline guarantee is refused rather than silently downgraded \u2014 " + 'ask for "between-statements" if a budget checked between statements is enough',
|
|
163
|
+
meta: { provider, feature: "transactionDeadline", requested, supported: support }
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function startTransactionBudget(params) {
|
|
168
|
+
const { options, provider } = params;
|
|
169
|
+
const clock = params.clock ?? DEFAULT_DEADLINE_CLOCK;
|
|
170
|
+
const deadline = options?.deadline;
|
|
171
|
+
const totalMs = deadline === undefined ? null : deadline.totalMs;
|
|
172
|
+
const enforcement = deadline === undefined ? null : deadline.enforcement ?? "between-statements";
|
|
173
|
+
const expiresAt = totalMs === null ? null : clock() + totalMs;
|
|
174
|
+
let outcome = null;
|
|
175
|
+
const budget = {
|
|
176
|
+
enforcement,
|
|
177
|
+
totalMs,
|
|
178
|
+
statementTimeoutMs: options?.timeout ?? null,
|
|
179
|
+
remainingMs() {
|
|
180
|
+
return expiresAt === null ? null : Math.max(0, expiresAt - clock());
|
|
181
|
+
},
|
|
182
|
+
expired() {
|
|
183
|
+
return expiresAt !== null && clock() >= expiresAt;
|
|
184
|
+
},
|
|
185
|
+
assertUsable(assertParams) {
|
|
186
|
+
if (outcome !== null) {
|
|
187
|
+
throw transactionClosedError({ provider, outcome, stage: assertParams.stage });
|
|
188
|
+
}
|
|
189
|
+
if (expiresAt !== null && clock() >= expiresAt) {
|
|
190
|
+
throw transactionDeadlineError({
|
|
191
|
+
provider,
|
|
192
|
+
stage: assertParams.stage,
|
|
193
|
+
totalMs,
|
|
194
|
+
overdueMs: clock() - expiresAt
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
close(closeParams) {
|
|
199
|
+
if (outcome === null)
|
|
200
|
+
outcome = closeParams.outcome;
|
|
201
|
+
},
|
|
202
|
+
get outcome() {
|
|
203
|
+
return outcome;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
return budget;
|
|
207
|
+
}
|
|
208
|
+
function engineStatementBudgetMs(params) {
|
|
209
|
+
const { budget } = params;
|
|
210
|
+
if (budget.enforcement !== "cancel-running-statements")
|
|
211
|
+
return null;
|
|
212
|
+
const remaining = budget.remainingMs();
|
|
213
|
+
if (remaining === null)
|
|
214
|
+
return null;
|
|
215
|
+
const totalBound = Math.max(1, Math.ceil(remaining));
|
|
216
|
+
return budget.statementTimeoutMs !== null && budget.statementTimeoutMs > 0 ? Math.min(totalBound, budget.statementTimeoutMs) : totalBound;
|
|
217
|
+
}
|
|
218
|
+
function transactionDeadlineError(params) {
|
|
219
|
+
const { provider, stage, totalMs, overdueMs } = params;
|
|
220
|
+
const what = stage === "commit" ? "the transaction was rolled back instead of committed" : "the statement was never sent and the transaction is being rolled back";
|
|
221
|
+
return new VibeError4({
|
|
222
|
+
code: "VIBE_TRANSACTION",
|
|
223
|
+
message: `transaction deadline of ${totalMs} ms expired ${Math.max(0, Math.round(overdueMs))} ms ago \u2014 ${what}. ` + "Nothing is retried automatically: work the callback did outside the database may already have happened.",
|
|
224
|
+
meta: { provider, reason: "transaction-deadline", stage, totalMs, overdueMs: Math.max(0, Math.round(overdueMs)) }
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function transactionClosedError(params) {
|
|
228
|
+
const { provider, outcome, stage } = params;
|
|
229
|
+
return new VibeError4({
|
|
230
|
+
code: "VIBE_TRANSACTION",
|
|
231
|
+
message: `this transaction has already finished (${outcome}), so its client can no longer be used \u2014 ` + "a handle kept past the end of `$transaction` would run outside the transaction, on a connection " + "that now belongs to someone else",
|
|
232
|
+
meta: { provider, reason: "transaction-closed", outcome, stage }
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
function transactionOutcomeUnknownError(params) {
|
|
236
|
+
const { provider, totalMs, cause } = params;
|
|
237
|
+
return new VibeError4({
|
|
238
|
+
code: "VIBE_TRANSACTION",
|
|
239
|
+
message: `transaction deadline of ${totalMs} ms expired and the rollback that should have cleaned up failed \u2014 ` + "the commit state of this transaction is UNKNOWN. It is not retried automatically; inspect the data " + "before deciding.",
|
|
240
|
+
meta: {
|
|
241
|
+
provider,
|
|
242
|
+
reason: "transaction-outcome-unknown",
|
|
243
|
+
outcome: "unknown",
|
|
244
|
+
totalMs,
|
|
245
|
+
rollbackFailed: true
|
|
246
|
+
},
|
|
247
|
+
cause
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
function isTransactionDeadlineError(error) {
|
|
251
|
+
const reason = error?.meta?.reason;
|
|
252
|
+
return reason === "transaction-deadline";
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// packages/adapter-bun/src/savepoint-gate.ts
|
|
256
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
257
|
+
|
|
134
258
|
// packages/adapter-bun/src/savepoints.ts
|
|
135
259
|
function createSavepointCounter() {
|
|
136
260
|
return { n: 0 };
|
|
@@ -139,9 +263,224 @@ function nextSavepointName(params) {
|
|
|
139
263
|
return `vibeorm_sp_${params.counter.n++}`;
|
|
140
264
|
}
|
|
141
265
|
|
|
266
|
+
// packages/adapter-bun/src/savepoint-gate.ts
|
|
267
|
+
var CURRENT = new AsyncLocalStorage;
|
|
268
|
+
function createSavepointScope() {
|
|
269
|
+
return { depth: 0, state: { counter: createSavepointCounter(), open: null } };
|
|
270
|
+
}
|
|
271
|
+
function runRootScope(params) {
|
|
272
|
+
return CURRENT.run(params.scope, params.body);
|
|
273
|
+
}
|
|
274
|
+
function scopeTurn(params) {
|
|
275
|
+
const open = params.scope.state.open;
|
|
276
|
+
if (mayProceed({ scope: params.scope, open }))
|
|
277
|
+
return;
|
|
278
|
+
return waitForTurn({ scope: params.scope });
|
|
279
|
+
}
|
|
280
|
+
async function runNestedSavepoint(params) {
|
|
281
|
+
const { scope, execute, body } = params;
|
|
282
|
+
const node = await acquire({ scope });
|
|
283
|
+
try {
|
|
284
|
+
if (execute !== undefined)
|
|
285
|
+
await execute(`SAVEPOINT ${node.name}`);
|
|
286
|
+
let result;
|
|
287
|
+
try {
|
|
288
|
+
result = await CURRENT.run(node.scope, () => body(node.scope));
|
|
289
|
+
} catch (error) {
|
|
290
|
+
await drainDescendants({ node });
|
|
291
|
+
if (execute !== undefined) {
|
|
292
|
+
try {
|
|
293
|
+
await execute(`ROLLBACK TO SAVEPOINT ${node.name}`);
|
|
294
|
+
} catch {}
|
|
295
|
+
}
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
await drainDescendants({ node });
|
|
299
|
+
if (execute !== undefined)
|
|
300
|
+
await execute(`RELEASE SAVEPOINT ${node.name}`);
|
|
301
|
+
return result;
|
|
302
|
+
} finally {
|
|
303
|
+
closeNode({ node });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function mayProceed(params) {
|
|
307
|
+
const { scope, open } = params;
|
|
308
|
+
if (open === null)
|
|
309
|
+
return true;
|
|
310
|
+
if (open.scope === scope)
|
|
311
|
+
return true;
|
|
312
|
+
if (open.scope.depth < scope.depth)
|
|
313
|
+
return true;
|
|
314
|
+
return CURRENT.getStore() === open.scope;
|
|
315
|
+
}
|
|
316
|
+
async function waitForTurn(params) {
|
|
317
|
+
for (;; ) {
|
|
318
|
+
const open = params.scope.state.open;
|
|
319
|
+
if (open === null || mayProceed({ scope: params.scope, open }))
|
|
320
|
+
return;
|
|
321
|
+
await open.settled;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function acquire(params) {
|
|
325
|
+
const { scope } = params;
|
|
326
|
+
for (;; ) {
|
|
327
|
+
const open = scope.state.open;
|
|
328
|
+
if (mayProceed({ scope, open }))
|
|
329
|
+
return pushNode({ scope, parent: open });
|
|
330
|
+
await open.settled;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function pushNode(params) {
|
|
334
|
+
const { scope, parent } = params;
|
|
335
|
+
const state = scope.state;
|
|
336
|
+
let settle = () => {
|
|
337
|
+
return;
|
|
338
|
+
};
|
|
339
|
+
const settled = new Promise((resolve) => {
|
|
340
|
+
settle = () => resolve();
|
|
341
|
+
});
|
|
342
|
+
const node = {
|
|
343
|
+
scope: { depth: (parent === null ? 0 : parent.scope.depth) + 1, state },
|
|
344
|
+
name: nextSavepointName({ counter: state.counter }),
|
|
345
|
+
parent,
|
|
346
|
+
settled,
|
|
347
|
+
settle
|
|
348
|
+
};
|
|
349
|
+
state.open = node;
|
|
350
|
+
return node;
|
|
351
|
+
}
|
|
352
|
+
function closeNode(params) {
|
|
353
|
+
const { node } = params;
|
|
354
|
+
const state = node.scope.state;
|
|
355
|
+
if (state.open === node)
|
|
356
|
+
state.open = node.parent;
|
|
357
|
+
node.settle();
|
|
358
|
+
}
|
|
359
|
+
async function drainDescendants(params) {
|
|
360
|
+
const state = params.node.scope.state;
|
|
361
|
+
for (;; ) {
|
|
362
|
+
const open = state.open;
|
|
363
|
+
if (open === null || open === params.node || open.scope.depth <= params.node.scope.depth)
|
|
364
|
+
return;
|
|
365
|
+
await open.settled;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
142
369
|
// packages/adapter-bun/src/template-cache.ts
|
|
370
|
+
import { VibeError as VibeError5 } from "@vibeorm/schema";
|
|
371
|
+
function isIdentChar(char) {
|
|
372
|
+
if (char === undefined)
|
|
373
|
+
return false;
|
|
374
|
+
return /[A-Za-z0-9_]/.test(char);
|
|
375
|
+
}
|
|
376
|
+
function skipQuoted(text, start, quote) {
|
|
377
|
+
const escaping = quote === "'" && (text[start - 1] === "e" || text[start - 1] === "E") && !isIdentChar(text[start - 2]);
|
|
378
|
+
let index = start + 1;
|
|
379
|
+
while (index < text.length) {
|
|
380
|
+
const char = text[index];
|
|
381
|
+
if (escaping && char === "\\") {
|
|
382
|
+
index += 2;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (char === quote) {
|
|
386
|
+
if (text[index + 1] === quote) {
|
|
387
|
+
index += 2;
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
return index + 1;
|
|
391
|
+
}
|
|
392
|
+
index += 1;
|
|
393
|
+
}
|
|
394
|
+
return text.length;
|
|
395
|
+
}
|
|
396
|
+
function skipBlockComment(text, start) {
|
|
397
|
+
let depth = 0;
|
|
398
|
+
let index = start;
|
|
399
|
+
while (index < text.length) {
|
|
400
|
+
if (text[index] === "/" && text[index + 1] === "*") {
|
|
401
|
+
depth += 1;
|
|
402
|
+
index += 2;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if (text[index] === "*" && text[index + 1] === "/") {
|
|
406
|
+
depth -= 1;
|
|
407
|
+
index += 2;
|
|
408
|
+
if (depth === 0)
|
|
409
|
+
return index;
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
index += 1;
|
|
413
|
+
}
|
|
414
|
+
return text.length;
|
|
415
|
+
}
|
|
416
|
+
function skipDollarQuoted(text, start) {
|
|
417
|
+
const opener = /^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/.exec(text.slice(start));
|
|
418
|
+
if (opener === null)
|
|
419
|
+
return -1;
|
|
420
|
+
const tag = opener[0];
|
|
421
|
+
const close = text.indexOf(tag, start + tag.length);
|
|
422
|
+
return close === -1 ? text.length : close + tag.length;
|
|
423
|
+
}
|
|
424
|
+
function scanPlaceholders(params) {
|
|
425
|
+
const { text } = params;
|
|
426
|
+
const chunks = [];
|
|
427
|
+
const indices = [];
|
|
428
|
+
let chunkStart = 0;
|
|
429
|
+
let index = 0;
|
|
430
|
+
while (index < text.length) {
|
|
431
|
+
const char = text[index];
|
|
432
|
+
if (char === "'" || char === '"') {
|
|
433
|
+
index = skipQuoted(text, index, char);
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (char === "-" && text[index + 1] === "-") {
|
|
437
|
+
const newline = text.indexOf(`
|
|
438
|
+
`, index);
|
|
439
|
+
index = newline === -1 ? text.length : newline + 1;
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (char === "/" && text[index + 1] === "*") {
|
|
443
|
+
index = skipBlockComment(text, index);
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (char === "$") {
|
|
447
|
+
const dollarEnd = skipDollarQuoted(text, index);
|
|
448
|
+
if (dollarEnd !== -1) {
|
|
449
|
+
index = dollarEnd;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
let digits = index + 1;
|
|
453
|
+
while (digits < text.length && text[digits] !== undefined && /[0-9]/.test(text[digits]))
|
|
454
|
+
digits += 1;
|
|
455
|
+
if (digits > index + 1) {
|
|
456
|
+
chunks.push(text.slice(chunkStart, index));
|
|
457
|
+
indices.push(Number(text.slice(index + 1, digits)));
|
|
458
|
+
chunkStart = digits;
|
|
459
|
+
index = digits;
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
index += 1;
|
|
464
|
+
}
|
|
465
|
+
chunks.push(text.slice(chunkStart));
|
|
466
|
+
const template = Object.assign(chunks, { raw: chunks });
|
|
467
|
+
return { template, indices };
|
|
468
|
+
}
|
|
143
469
|
function templateChunks(params) {
|
|
144
|
-
return params.
|
|
470
|
+
return [...scanPlaceholders(params).template];
|
|
471
|
+
}
|
|
472
|
+
function bindTemplateValues(params) {
|
|
473
|
+
const { text, indices, values } = params;
|
|
474
|
+
return indices.map((placeholder) => {
|
|
475
|
+
if (placeholder < 1 || placeholder > values.length) {
|
|
476
|
+
throw new VibeError5({
|
|
477
|
+
code: "VIBE_VALIDATION",
|
|
478
|
+
message: `bun:sql prepared statement references $${placeholder} but only ${values.length} parameter(s) were given`,
|
|
479
|
+
meta: { text, placeholder, provided: values.length }
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
return values[placeholder - 1];
|
|
483
|
+
});
|
|
145
484
|
}
|
|
146
485
|
function createTemplateCache(params) {
|
|
147
486
|
const { max } = params;
|
|
@@ -155,15 +494,14 @@ function createTemplateCache(params) {
|
|
|
155
494
|
templates.set(text, existing);
|
|
156
495
|
return existing;
|
|
157
496
|
}
|
|
158
|
-
const
|
|
159
|
-
const template = Object.assign(chunks, { raw: chunks });
|
|
497
|
+
const prepared = scanPlaceholders({ text });
|
|
160
498
|
if (templates.size >= max) {
|
|
161
499
|
const oldest = templates.keys().next().value;
|
|
162
500
|
if (oldest !== undefined)
|
|
163
501
|
templates.delete(oldest);
|
|
164
502
|
}
|
|
165
|
-
templates.set(text,
|
|
166
|
-
return
|
|
503
|
+
templates.set(text, prepared);
|
|
504
|
+
return prepared;
|
|
167
505
|
},
|
|
168
506
|
size() {
|
|
169
507
|
return templates.size;
|
|
@@ -172,7 +510,7 @@ function createTemplateCache(params) {
|
|
|
172
510
|
}
|
|
173
511
|
|
|
174
512
|
// packages/adapter-bun/src/transaction-sql.ts
|
|
175
|
-
import { VibeError as
|
|
513
|
+
import { VibeError as VibeError6 } from "@vibeorm/schema";
|
|
176
514
|
var ISOLATION_LEVEL_SQL = Object.freeze({
|
|
177
515
|
ReadCommitted: "READ COMMITTED",
|
|
178
516
|
RepeatableRead: "REPEATABLE READ",
|
|
@@ -185,7 +523,7 @@ function beginStatement(params) {
|
|
|
185
523
|
function statementTimeoutStatement(params) {
|
|
186
524
|
const { timeout } = params;
|
|
187
525
|
if (!Number.isInteger(timeout) || timeout < 0) {
|
|
188
|
-
throw new
|
|
526
|
+
throw new VibeError6({
|
|
189
527
|
code: "VIBE_VALIDATION",
|
|
190
528
|
message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,
|
|
191
529
|
meta: { timeout }
|
|
@@ -211,6 +549,8 @@ function refuseNestedTransactionOptions(params) {
|
|
|
211
549
|
passed.push("isolationLevel");
|
|
212
550
|
if (options.timeout !== undefined)
|
|
213
551
|
passed.push("timeout");
|
|
552
|
+
if (options.deadline !== undefined)
|
|
553
|
+
passed.push("deadline");
|
|
214
554
|
if (passed.length === 0)
|
|
215
555
|
return;
|
|
216
556
|
const meta = { provider, nested: true };
|
|
@@ -218,9 +558,11 @@ function refuseNestedTransactionOptions(params) {
|
|
|
218
558
|
meta.isolationLevel = options.isolationLevel;
|
|
219
559
|
if (options.timeout !== undefined)
|
|
220
560
|
meta.timeout = options.timeout;
|
|
221
|
-
|
|
561
|
+
if (options.deadline !== undefined)
|
|
562
|
+
meta.deadline = options.deadline;
|
|
563
|
+
throw new VibeError6({
|
|
222
564
|
code: "VIBE_VALIDATION",
|
|
223
|
-
message: `nested transactions run as savepoints and cannot honor ${passed.join(" or ")} \u2014 a savepoint cannot change the isolation level of the transaction it joins,
|
|
565
|
+
message: `nested transactions run as savepoints and cannot honor ${passed.join(" or ")} \u2014 a savepoint cannot change the isolation level of the transaction it joins, a nested timeout is not enforceable, and a nested deadline would need its own session setting on a connection it does not own (it inherits the top-level one instead); pass options on the top-level $transaction`,
|
|
224
566
|
meta
|
|
225
567
|
});
|
|
226
568
|
}
|
|
@@ -236,10 +578,19 @@ class CallbackError extends Error {
|
|
|
236
578
|
}
|
|
237
579
|
var DIALECT = "postgres";
|
|
238
580
|
var PROVIDER = "postgresql";
|
|
239
|
-
var
|
|
581
|
+
var BUDGETS = Object.freeze({
|
|
582
|
+
acquireTimeout: true,
|
|
583
|
+
statementTimeout: "all-statements",
|
|
584
|
+
transactionDeadline: "cancel-running-statements"
|
|
585
|
+
});
|
|
586
|
+
var WIRE_TEXT = Object.freeze({ dateTime: "native", json: "text", numbers: "native" });
|
|
587
|
+
var WIRE_PARSED = Object.freeze({ dateTime: "native", json: "parsed", numbers: "native" });
|
|
240
588
|
function bunAdapter(options) {
|
|
241
589
|
const usePrepared = options?.preparedStatements ?? false;
|
|
242
590
|
const templates = createTemplateCache({ max: options?.stmtCacheMax ?? 1000 });
|
|
591
|
+
let activeWire = WIRE_TEXT;
|
|
592
|
+
let wireSettled = false;
|
|
593
|
+
let wireProbe = null;
|
|
243
594
|
let instance = null;
|
|
244
595
|
let rawSession = null;
|
|
245
596
|
function getSql() {
|
|
@@ -258,11 +609,27 @@ function bunAdapter(options) {
|
|
|
258
609
|
instance = url === undefined ? new SQL(sqlOptions) : new SQL(url, sqlOptions);
|
|
259
610
|
return instance;
|
|
260
611
|
}
|
|
612
|
+
async function probeJsonWire(source) {
|
|
613
|
+
const probe = renderDiagnostic({ dialect: postgresDialect, query: "postgresJsonWire" });
|
|
614
|
+
try {
|
|
615
|
+
const rows = usePrepared ? await source(templates.getTemplate({ text: probe.text }).template) : await source.unsafe(probe.text, []);
|
|
616
|
+
const probed = rows[0]?.probe;
|
|
617
|
+
if (typeof probed === "object" && probed !== null)
|
|
618
|
+
activeWire = WIRE_PARSED;
|
|
619
|
+
} catch {} finally {
|
|
620
|
+
wireSettled = true;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
261
623
|
async function runExecute(params) {
|
|
624
|
+
if (!wireSettled) {
|
|
625
|
+
wireProbe ??= probeJsonWire(params.source);
|
|
626
|
+
await wireProbe;
|
|
627
|
+
}
|
|
262
628
|
const values = serializeParams({ values: params.values });
|
|
263
629
|
if (usePrepared) {
|
|
264
|
-
const
|
|
265
|
-
|
|
630
|
+
const prepared = templates.getTemplate({ text: params.text });
|
|
631
|
+
const bound = bindTemplateValues({ text: params.text, indices: prepared.indices, values });
|
|
632
|
+
return runMapped(() => params.source(prepared.template, ...bound));
|
|
266
633
|
}
|
|
267
634
|
return runMapped(() => params.source.unsafe(params.text, values));
|
|
268
635
|
}
|
|
@@ -272,61 +639,79 @@ function bunAdapter(options) {
|
|
|
272
639
|
return { rows, affectedRows: readAffectedRows({ rows }) };
|
|
273
640
|
}
|
|
274
641
|
function createAdapter(params) {
|
|
275
|
-
const { source, parentMode,
|
|
642
|
+
const { source, parentMode, scope, budget } = params;
|
|
643
|
+
async function applyEngineBudget() {
|
|
644
|
+
if (budget === undefined)
|
|
645
|
+
return;
|
|
646
|
+
const ms = engineStatementBudgetMs({ budget });
|
|
647
|
+
if (ms === null)
|
|
648
|
+
return;
|
|
649
|
+
await runMapped(() => source.unsafe(statementTimeoutStatement({ timeout: ms })));
|
|
650
|
+
}
|
|
276
651
|
async function transactionViaSavepoint(fn, nestedOptions) {
|
|
277
652
|
refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });
|
|
653
|
+
budget?.assertUsable({ stage: "nested" });
|
|
278
654
|
const transactional = source;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
parentMode: "tx",
|
|
285
|
-
savepointCounter
|
|
286
|
-
});
|
|
655
|
+
const driverSavepoint = transactional.savepoint?.bind(transactional);
|
|
656
|
+
if (driverSavepoint !== undefined) {
|
|
657
|
+
return runNestedSavepoint({
|
|
658
|
+
scope,
|
|
659
|
+
body: async (child) => {
|
|
287
660
|
try {
|
|
288
|
-
return await
|
|
661
|
+
return await driverSavepoint(async (savepointSql) => {
|
|
662
|
+
const nested = createAdapter({
|
|
663
|
+
source: savepointSql,
|
|
664
|
+
parentMode: "tx",
|
|
665
|
+
scope: child
|
|
666
|
+
});
|
|
667
|
+
try {
|
|
668
|
+
return await fn(nested);
|
|
669
|
+
} catch (error) {
|
|
670
|
+
throw new CallbackError(error);
|
|
671
|
+
}
|
|
672
|
+
});
|
|
289
673
|
} catch (error) {
|
|
290
|
-
|
|
674
|
+
if (error instanceof CallbackError)
|
|
675
|
+
throw error.inner;
|
|
676
|
+
throw mapBunDriverError({ error });
|
|
291
677
|
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
if (error instanceof CallbackError)
|
|
295
|
-
throw error.inner;
|
|
296
|
-
throw mapBunDriverError({ error });
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
const savepoint = nextSavepointName({ counter: savepointCounter });
|
|
300
|
-
await runMapped(() => transactional.unsafe(`SAVEPOINT ${savepoint}`));
|
|
301
|
-
try {
|
|
302
|
-
const nested = createAdapter({ source: transactional, parentMode: "tx", savepointCounter });
|
|
303
|
-
const result = await fn(nested);
|
|
304
|
-
await runMapped(() => transactional.unsafe(`RELEASE SAVEPOINT ${savepoint}`));
|
|
305
|
-
return result;
|
|
306
|
-
} catch (error) {
|
|
307
|
-
try {
|
|
308
|
-
await transactional.unsafe(`ROLLBACK TO SAVEPOINT ${savepoint}`);
|
|
309
|
-
} catch {}
|
|
310
|
-
throw error;
|
|
678
|
+
}
|
|
679
|
+
});
|
|
311
680
|
}
|
|
681
|
+
return runNestedSavepoint({
|
|
682
|
+
scope,
|
|
683
|
+
execute: (statement) => runMapped(() => transactional.unsafe(statement)),
|
|
684
|
+
body: (child) => fn(createAdapter({ source: transactional, parentMode: "tx", scope: child, budget }))
|
|
685
|
+
});
|
|
312
686
|
}
|
|
313
687
|
async function transactionViaPool(fn, transactionOptions) {
|
|
314
688
|
const pool = source;
|
|
315
|
-
|
|
689
|
+
validateTransactionDeadline({
|
|
690
|
+
options: transactionOptions,
|
|
691
|
+
support: BUDGETS.transactionDeadline,
|
|
692
|
+
provider: PROVIDER
|
|
693
|
+
});
|
|
694
|
+
if (transactionOptions?.isolationLevel === undefined && transactionOptions?.timeout === undefined && transactionOptions?.deadline === undefined) {
|
|
695
|
+
const budget3 = startTransactionBudget({ provider: PROVIDER });
|
|
316
696
|
try {
|
|
317
|
-
|
|
697
|
+
const result = await pool.begin(async (txSql) => {
|
|
698
|
+
const txScope = createSavepointScope();
|
|
318
699
|
const txAdapter = createAdapter({
|
|
319
700
|
source: txSql,
|
|
320
701
|
parentMode: "tx",
|
|
321
|
-
|
|
702
|
+
scope: txScope,
|
|
703
|
+
budget: budget3
|
|
322
704
|
});
|
|
323
705
|
try {
|
|
324
|
-
return await fn(txAdapter);
|
|
706
|
+
return await runRootScope({ scope: txScope, body: () => fn(txAdapter) });
|
|
325
707
|
} catch (error) {
|
|
326
708
|
throw new CallbackError(error);
|
|
327
709
|
}
|
|
328
710
|
});
|
|
711
|
+
budget3.close({ outcome: "committed" });
|
|
712
|
+
return result;
|
|
329
713
|
} catch (error) {
|
|
714
|
+
budget3.close({ outcome: "rolled-back" });
|
|
330
715
|
if (error instanceof CallbackError)
|
|
331
716
|
throw error.inner;
|
|
332
717
|
throw mapBunDriverError({ error });
|
|
@@ -334,22 +719,37 @@ function bunAdapter(options) {
|
|
|
334
719
|
}
|
|
335
720
|
const timeoutText = transactionOptions.timeout === undefined ? undefined : statementTimeoutStatement({ timeout: transactionOptions.timeout });
|
|
336
721
|
const reserved = await runMapped(() => pool.reserve());
|
|
722
|
+
const budget2 = startTransactionBudget({ options: transactionOptions, provider: PROVIDER });
|
|
337
723
|
try {
|
|
338
724
|
await runMapped(() => reserved.unsafe(beginStatement({ isolationLevel: transactionOptions.isolationLevel })));
|
|
339
725
|
if (timeoutText !== undefined)
|
|
340
726
|
await runMapped(() => reserved.unsafe(timeoutText));
|
|
727
|
+
const txScope = createSavepointScope();
|
|
341
728
|
const txAdapter = createAdapter({
|
|
342
729
|
source: reserved,
|
|
343
730
|
parentMode: "tx",
|
|
344
|
-
|
|
731
|
+
scope: txScope,
|
|
732
|
+
budget: budget2
|
|
345
733
|
});
|
|
346
|
-
const result = await fn(txAdapter);
|
|
734
|
+
const result = await runRootScope({ scope: txScope, body: () => fn(txAdapter) });
|
|
735
|
+
budget2.assertUsable({ stage: "commit" });
|
|
347
736
|
await runMapped(() => reserved.unsafe("COMMIT"));
|
|
737
|
+
budget2.close({ outcome: "committed" });
|
|
348
738
|
return result;
|
|
349
739
|
} catch (error) {
|
|
350
740
|
try {
|
|
351
741
|
await reserved.unsafe("ROLLBACK");
|
|
352
|
-
|
|
742
|
+
budget2.close({ outcome: "rolled-back" });
|
|
743
|
+
} catch {
|
|
744
|
+
budget2.close({ outcome: "unknown" });
|
|
745
|
+
if (budget2.totalMs !== null && isTransactionDeadlineError(error)) {
|
|
746
|
+
throw transactionOutcomeUnknownError({
|
|
747
|
+
provider: PROVIDER,
|
|
748
|
+
totalMs: budget2.totalMs,
|
|
749
|
+
cause: error
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
}
|
|
353
753
|
throw error;
|
|
354
754
|
} finally {
|
|
355
755
|
reserved.release();
|
|
@@ -358,11 +758,24 @@ function bunAdapter(options) {
|
|
|
358
758
|
return {
|
|
359
759
|
dialect: DIALECT,
|
|
360
760
|
provider: PROVIDER,
|
|
361
|
-
|
|
761
|
+
budgets: BUDGETS,
|
|
762
|
+
get wire() {
|
|
763
|
+
return activeWire;
|
|
764
|
+
},
|
|
362
765
|
async execute(execParams) {
|
|
766
|
+
const turn = scopeTurn({ scope });
|
|
767
|
+
if (turn !== undefined)
|
|
768
|
+
await turn;
|
|
769
|
+
budget?.assertUsable({ stage: "statement" });
|
|
770
|
+
await applyEngineBudget();
|
|
363
771
|
return runExecute({ source, ...execParams });
|
|
364
772
|
},
|
|
365
773
|
async executeUnsafe(execParams) {
|
|
774
|
+
const turn = scopeTurn({ scope });
|
|
775
|
+
if (turn !== undefined)
|
|
776
|
+
await turn;
|
|
777
|
+
budget?.assertUsable({ stage: "statement" });
|
|
778
|
+
await applyEngineBudget();
|
|
366
779
|
return runExecuteUnsafe({ source, ...execParams });
|
|
367
780
|
},
|
|
368
781
|
async transaction(fn, transactionOptions) {
|
|
@@ -389,13 +802,16 @@ function bunAdapter(options) {
|
|
|
389
802
|
return createAdapter({
|
|
390
803
|
source: getSql(),
|
|
391
804
|
parentMode: "pool",
|
|
392
|
-
|
|
805
|
+
scope: createSavepointScope()
|
|
393
806
|
});
|
|
394
807
|
}
|
|
395
808
|
return {
|
|
396
809
|
dialect: DIALECT,
|
|
397
810
|
provider: PROVIDER,
|
|
398
|
-
|
|
811
|
+
budgets: BUDGETS,
|
|
812
|
+
get wire() {
|
|
813
|
+
return activeWire;
|
|
814
|
+
},
|
|
399
815
|
async execute(params) {
|
|
400
816
|
return poolAdapter().execute(params);
|
|
401
817
|
},
|
|
@@ -461,14 +877,21 @@ function readAffectedRows(params) {
|
|
|
461
877
|
return rows.length;
|
|
462
878
|
}
|
|
463
879
|
export {
|
|
880
|
+
validateTransactionDeadline,
|
|
881
|
+
transactionOutcomeUnknownError,
|
|
882
|
+
transactionDeadlineError,
|
|
883
|
+
transactionClosedError,
|
|
464
884
|
toPgArrayLiteral,
|
|
465
885
|
templateChunks,
|
|
466
886
|
statementTimeoutStatement,
|
|
887
|
+
startTransactionBudget,
|
|
467
888
|
serializeParamsRaw,
|
|
468
889
|
serializeParams,
|
|
469
890
|
refuseNestedTransactionOptions,
|
|
470
891
|
nextSavepointName,
|
|
471
892
|
mapBunDriverError,
|
|
893
|
+
isTransactionDeadlineError,
|
|
894
|
+
engineStatementBudgetMs,
|
|
472
895
|
createTemplateCache,
|
|
473
896
|
createSavepointCounter,
|
|
474
897
|
classifyRawTransactionControl,
|
|
@@ -478,8 +901,9 @@ export {
|
|
|
478
901
|
beginStatement,
|
|
479
902
|
applyStartupOptions,
|
|
480
903
|
ISOLATION_LEVEL_SQL,
|
|
904
|
+
DEFAULT_DEADLINE_CLOCK,
|
|
481
905
|
BUN_SQLSTATE_REASONS,
|
|
482
906
|
BUN_SQLSTATE_ERROR_CODES
|
|
483
907
|
};
|
|
484
908
|
|
|
485
|
-
//# debugId=
|
|
909
|
+
//# debugId=81824F43B962EC8264756E2164756E21
|