@vibeorm/adapter-bun 2.0.0-alpha.9 → 2.0.1
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 +6 -4
- 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 +494 -55
- package/dist/index.js.map +10 -8
- package/dist/params.d.ts +19 -1
- package/dist/params.d.ts.map +1 -1
- 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"
|
|
@@ -124,13 +126,150 @@ function toPgArrayLiteral(values) {
|
|
|
124
126
|
});
|
|
125
127
|
return `{${elements.join(",")}}`;
|
|
126
128
|
}
|
|
129
|
+
function serializeDate(params) {
|
|
130
|
+
const { value } = params;
|
|
131
|
+
if (Number.isNaN(value.getTime()))
|
|
132
|
+
return value;
|
|
133
|
+
const year = value.getUTCFullYear();
|
|
134
|
+
if (year < 1 || year > 9999)
|
|
135
|
+
return value;
|
|
136
|
+
return value.toISOString();
|
|
137
|
+
}
|
|
127
138
|
function serializeParams(params) {
|
|
128
|
-
return params.values.
|
|
139
|
+
return params.values.map((value) => value instanceof Date ? serializeDate({ value }) : value);
|
|
129
140
|
}
|
|
130
141
|
function serializeParamsRaw(params) {
|
|
131
|
-
return params.values.map((value) =>
|
|
142
|
+
return params.values.map((value) => {
|
|
143
|
+
if (Array.isArray(value))
|
|
144
|
+
return toPgArrayLiteral(value);
|
|
145
|
+
if (value instanceof Date)
|
|
146
|
+
return serializeDate({ value });
|
|
147
|
+
return value;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// packages/adapter-bun/src/transaction-budget.ts
|
|
152
|
+
import { VibeError as VibeError4 } from "@vibeorm/schema";
|
|
153
|
+
var DEFAULT_DEADLINE_CLOCK = () => Date.now();
|
|
154
|
+
function validateTransactionDeadline(params) {
|
|
155
|
+
const { options, support, provider } = params;
|
|
156
|
+
const deadline = options?.deadline;
|
|
157
|
+
if (deadline === undefined)
|
|
158
|
+
return;
|
|
159
|
+
if (support === "unsupported") {
|
|
160
|
+
throw new VibeError4({
|
|
161
|
+
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
162
|
+
message: `${provider} cannot enforce a transaction deadline \u2014 remove the \`deadline\` option`,
|
|
163
|
+
meta: { provider, feature: "transactionDeadline", supported: support }
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (!Number.isInteger(deadline.totalMs) || deadline.totalMs <= 0) {
|
|
167
|
+
throw new VibeError4({
|
|
168
|
+
code: "VIBE_VALIDATION",
|
|
169
|
+
message: `transaction deadline \`totalMs\` must be a positive integer of milliseconds, received ${String(deadline.totalMs)}`,
|
|
170
|
+
meta: { provider, feature: "transactionDeadline", totalMs: deadline.totalMs }
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
const requested = deadline.enforcement ?? "between-statements";
|
|
174
|
+
if (requested === "cancel-running-statements" && support !== "cancel-running-statements") {
|
|
175
|
+
throw new VibeError4({
|
|
176
|
+
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
177
|
+
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',
|
|
178
|
+
meta: { provider, feature: "transactionDeadline", requested, supported: support }
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function startTransactionBudget(params) {
|
|
183
|
+
const { options, provider } = params;
|
|
184
|
+
const clock = params.clock ?? DEFAULT_DEADLINE_CLOCK;
|
|
185
|
+
const deadline = options?.deadline;
|
|
186
|
+
const totalMs = deadline === undefined ? null : deadline.totalMs;
|
|
187
|
+
const enforcement = deadline === undefined ? null : deadline.enforcement ?? "between-statements";
|
|
188
|
+
const expiresAt = totalMs === null ? null : clock() + totalMs;
|
|
189
|
+
let outcome = null;
|
|
190
|
+
const budget = {
|
|
191
|
+
enforcement,
|
|
192
|
+
totalMs,
|
|
193
|
+
statementTimeoutMs: options?.timeout ?? null,
|
|
194
|
+
remainingMs() {
|
|
195
|
+
return expiresAt === null ? null : Math.max(0, expiresAt - clock());
|
|
196
|
+
},
|
|
197
|
+
expired() {
|
|
198
|
+
return expiresAt !== null && clock() >= expiresAt;
|
|
199
|
+
},
|
|
200
|
+
assertUsable(assertParams) {
|
|
201
|
+
if (outcome !== null) {
|
|
202
|
+
throw transactionClosedError({ provider, outcome, stage: assertParams.stage });
|
|
203
|
+
}
|
|
204
|
+
if (expiresAt !== null && clock() >= expiresAt) {
|
|
205
|
+
throw transactionDeadlineError({
|
|
206
|
+
provider,
|
|
207
|
+
stage: assertParams.stage,
|
|
208
|
+
totalMs,
|
|
209
|
+
overdueMs: clock() - expiresAt
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
close(closeParams) {
|
|
214
|
+
if (outcome === null)
|
|
215
|
+
outcome = closeParams.outcome;
|
|
216
|
+
},
|
|
217
|
+
get outcome() {
|
|
218
|
+
return outcome;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
return budget;
|
|
222
|
+
}
|
|
223
|
+
function engineStatementBudgetMs(params) {
|
|
224
|
+
const { budget } = params;
|
|
225
|
+
if (budget.enforcement !== "cancel-running-statements")
|
|
226
|
+
return null;
|
|
227
|
+
const remaining = budget.remainingMs();
|
|
228
|
+
if (remaining === null)
|
|
229
|
+
return null;
|
|
230
|
+
const totalBound = Math.max(1, Math.ceil(remaining));
|
|
231
|
+
return budget.statementTimeoutMs !== null && budget.statementTimeoutMs > 0 ? Math.min(totalBound, budget.statementTimeoutMs) : totalBound;
|
|
232
|
+
}
|
|
233
|
+
function transactionDeadlineError(params) {
|
|
234
|
+
const { provider, stage, totalMs, overdueMs } = params;
|
|
235
|
+
const what = stage === "commit" ? "the transaction was rolled back instead of committed" : "the statement was never sent and the transaction is being rolled back";
|
|
236
|
+
return new VibeError4({
|
|
237
|
+
code: "VIBE_TRANSACTION",
|
|
238
|
+
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.",
|
|
239
|
+
meta: { provider, reason: "transaction-deadline", stage, totalMs, overdueMs: Math.max(0, Math.round(overdueMs)) }
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function transactionClosedError(params) {
|
|
243
|
+
const { provider, outcome, stage } = params;
|
|
244
|
+
return new VibeError4({
|
|
245
|
+
code: "VIBE_TRANSACTION",
|
|
246
|
+
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",
|
|
247
|
+
meta: { provider, reason: "transaction-closed", outcome, stage }
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
function transactionOutcomeUnknownError(params) {
|
|
251
|
+
const { provider, totalMs, cause } = params;
|
|
252
|
+
return new VibeError4({
|
|
253
|
+
code: "VIBE_TRANSACTION",
|
|
254
|
+
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.",
|
|
255
|
+
meta: {
|
|
256
|
+
provider,
|
|
257
|
+
reason: "transaction-outcome-unknown",
|
|
258
|
+
outcome: "unknown",
|
|
259
|
+
totalMs,
|
|
260
|
+
rollbackFailed: true
|
|
261
|
+
},
|
|
262
|
+
cause
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function isTransactionDeadlineError(error) {
|
|
266
|
+
const reason = error?.meta?.reason;
|
|
267
|
+
return reason === "transaction-deadline";
|
|
132
268
|
}
|
|
133
269
|
|
|
270
|
+
// packages/adapter-bun/src/savepoint-gate.ts
|
|
271
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
272
|
+
|
|
134
273
|
// packages/adapter-bun/src/savepoints.ts
|
|
135
274
|
function createSavepointCounter() {
|
|
136
275
|
return { n: 0 };
|
|
@@ -139,9 +278,224 @@ function nextSavepointName(params) {
|
|
|
139
278
|
return `vibeorm_sp_${params.counter.n++}`;
|
|
140
279
|
}
|
|
141
280
|
|
|
281
|
+
// packages/adapter-bun/src/savepoint-gate.ts
|
|
282
|
+
var CURRENT = new AsyncLocalStorage;
|
|
283
|
+
function createSavepointScope() {
|
|
284
|
+
return { depth: 0, state: { counter: createSavepointCounter(), open: null } };
|
|
285
|
+
}
|
|
286
|
+
function runRootScope(params) {
|
|
287
|
+
return CURRENT.run(params.scope, params.body);
|
|
288
|
+
}
|
|
289
|
+
function scopeTurn(params) {
|
|
290
|
+
const open = params.scope.state.open;
|
|
291
|
+
if (mayProceed({ scope: params.scope, open }))
|
|
292
|
+
return;
|
|
293
|
+
return waitForTurn({ scope: params.scope });
|
|
294
|
+
}
|
|
295
|
+
async function runNestedSavepoint(params) {
|
|
296
|
+
const { scope, execute, body } = params;
|
|
297
|
+
const node = await acquire({ scope });
|
|
298
|
+
try {
|
|
299
|
+
if (execute !== undefined)
|
|
300
|
+
await execute(`SAVEPOINT ${node.name}`);
|
|
301
|
+
let result;
|
|
302
|
+
try {
|
|
303
|
+
result = await CURRENT.run(node.scope, () => body(node.scope));
|
|
304
|
+
} catch (error) {
|
|
305
|
+
await drainDescendants({ node });
|
|
306
|
+
if (execute !== undefined) {
|
|
307
|
+
try {
|
|
308
|
+
await execute(`ROLLBACK TO SAVEPOINT ${node.name}`);
|
|
309
|
+
} catch {}
|
|
310
|
+
}
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
await drainDescendants({ node });
|
|
314
|
+
if (execute !== undefined)
|
|
315
|
+
await execute(`RELEASE SAVEPOINT ${node.name}`);
|
|
316
|
+
return result;
|
|
317
|
+
} finally {
|
|
318
|
+
closeNode({ node });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function mayProceed(params) {
|
|
322
|
+
const { scope, open } = params;
|
|
323
|
+
if (open === null)
|
|
324
|
+
return true;
|
|
325
|
+
if (open.scope === scope)
|
|
326
|
+
return true;
|
|
327
|
+
if (open.scope.depth < scope.depth)
|
|
328
|
+
return true;
|
|
329
|
+
return CURRENT.getStore() === open.scope;
|
|
330
|
+
}
|
|
331
|
+
async function waitForTurn(params) {
|
|
332
|
+
for (;; ) {
|
|
333
|
+
const open = params.scope.state.open;
|
|
334
|
+
if (open === null || mayProceed({ scope: params.scope, open }))
|
|
335
|
+
return;
|
|
336
|
+
await open.settled;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
async function acquire(params) {
|
|
340
|
+
const { scope } = params;
|
|
341
|
+
for (;; ) {
|
|
342
|
+
const open = scope.state.open;
|
|
343
|
+
if (mayProceed({ scope, open }))
|
|
344
|
+
return pushNode({ scope, parent: open });
|
|
345
|
+
await open.settled;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function pushNode(params) {
|
|
349
|
+
const { scope, parent } = params;
|
|
350
|
+
const state = scope.state;
|
|
351
|
+
let settle = () => {
|
|
352
|
+
return;
|
|
353
|
+
};
|
|
354
|
+
const settled = new Promise((resolve) => {
|
|
355
|
+
settle = () => resolve();
|
|
356
|
+
});
|
|
357
|
+
const node = {
|
|
358
|
+
scope: { depth: (parent === null ? 0 : parent.scope.depth) + 1, state },
|
|
359
|
+
name: nextSavepointName({ counter: state.counter }),
|
|
360
|
+
parent,
|
|
361
|
+
settled,
|
|
362
|
+
settle
|
|
363
|
+
};
|
|
364
|
+
state.open = node;
|
|
365
|
+
return node;
|
|
366
|
+
}
|
|
367
|
+
function closeNode(params) {
|
|
368
|
+
const { node } = params;
|
|
369
|
+
const state = node.scope.state;
|
|
370
|
+
if (state.open === node)
|
|
371
|
+
state.open = node.parent;
|
|
372
|
+
node.settle();
|
|
373
|
+
}
|
|
374
|
+
async function drainDescendants(params) {
|
|
375
|
+
const state = params.node.scope.state;
|
|
376
|
+
for (;; ) {
|
|
377
|
+
const open = state.open;
|
|
378
|
+
if (open === null || open === params.node || open.scope.depth <= params.node.scope.depth)
|
|
379
|
+
return;
|
|
380
|
+
await open.settled;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
142
384
|
// packages/adapter-bun/src/template-cache.ts
|
|
385
|
+
import { VibeError as VibeError5 } from "@vibeorm/schema";
|
|
386
|
+
function isIdentChar(char) {
|
|
387
|
+
if (char === undefined)
|
|
388
|
+
return false;
|
|
389
|
+
return /[A-Za-z0-9_]/.test(char);
|
|
390
|
+
}
|
|
391
|
+
function skipQuoted(text, start, quote) {
|
|
392
|
+
const escaping = quote === "'" && (text[start - 1] === "e" || text[start - 1] === "E") && !isIdentChar(text[start - 2]);
|
|
393
|
+
let index = start + 1;
|
|
394
|
+
while (index < text.length) {
|
|
395
|
+
const char = text[index];
|
|
396
|
+
if (escaping && char === "\\") {
|
|
397
|
+
index += 2;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (char === quote) {
|
|
401
|
+
if (text[index + 1] === quote) {
|
|
402
|
+
index += 2;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
return index + 1;
|
|
406
|
+
}
|
|
407
|
+
index += 1;
|
|
408
|
+
}
|
|
409
|
+
return text.length;
|
|
410
|
+
}
|
|
411
|
+
function skipBlockComment(text, start) {
|
|
412
|
+
let depth = 0;
|
|
413
|
+
let index = start;
|
|
414
|
+
while (index < text.length) {
|
|
415
|
+
if (text[index] === "/" && text[index + 1] === "*") {
|
|
416
|
+
depth += 1;
|
|
417
|
+
index += 2;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (text[index] === "*" && text[index + 1] === "/") {
|
|
421
|
+
depth -= 1;
|
|
422
|
+
index += 2;
|
|
423
|
+
if (depth === 0)
|
|
424
|
+
return index;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
index += 1;
|
|
428
|
+
}
|
|
429
|
+
return text.length;
|
|
430
|
+
}
|
|
431
|
+
function skipDollarQuoted(text, start) {
|
|
432
|
+
const opener = /^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/.exec(text.slice(start));
|
|
433
|
+
if (opener === null)
|
|
434
|
+
return -1;
|
|
435
|
+
const tag = opener[0];
|
|
436
|
+
const close = text.indexOf(tag, start + tag.length);
|
|
437
|
+
return close === -1 ? text.length : close + tag.length;
|
|
438
|
+
}
|
|
439
|
+
function scanPlaceholders(params) {
|
|
440
|
+
const { text } = params;
|
|
441
|
+
const chunks = [];
|
|
442
|
+
const indices = [];
|
|
443
|
+
let chunkStart = 0;
|
|
444
|
+
let index = 0;
|
|
445
|
+
while (index < text.length) {
|
|
446
|
+
const char = text[index];
|
|
447
|
+
if (char === "'" || char === '"') {
|
|
448
|
+
index = skipQuoted(text, index, char);
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (char === "-" && text[index + 1] === "-") {
|
|
452
|
+
const newline = text.indexOf(`
|
|
453
|
+
`, index);
|
|
454
|
+
index = newline === -1 ? text.length : newline + 1;
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (char === "/" && text[index + 1] === "*") {
|
|
458
|
+
index = skipBlockComment(text, index);
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (char === "$") {
|
|
462
|
+
const dollarEnd = skipDollarQuoted(text, index);
|
|
463
|
+
if (dollarEnd !== -1) {
|
|
464
|
+
index = dollarEnd;
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
let digits = index + 1;
|
|
468
|
+
while (digits < text.length && text[digits] !== undefined && /[0-9]/.test(text[digits]))
|
|
469
|
+
digits += 1;
|
|
470
|
+
if (digits > index + 1) {
|
|
471
|
+
chunks.push(text.slice(chunkStart, index));
|
|
472
|
+
indices.push(Number(text.slice(index + 1, digits)));
|
|
473
|
+
chunkStart = digits;
|
|
474
|
+
index = digits;
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
index += 1;
|
|
479
|
+
}
|
|
480
|
+
chunks.push(text.slice(chunkStart));
|
|
481
|
+
const template = Object.assign(chunks, { raw: chunks });
|
|
482
|
+
return { template, indices };
|
|
483
|
+
}
|
|
143
484
|
function templateChunks(params) {
|
|
144
|
-
return params.
|
|
485
|
+
return [...scanPlaceholders(params).template];
|
|
486
|
+
}
|
|
487
|
+
function bindTemplateValues(params) {
|
|
488
|
+
const { text, indices, values } = params;
|
|
489
|
+
return indices.map((placeholder) => {
|
|
490
|
+
if (placeholder < 1 || placeholder > values.length) {
|
|
491
|
+
throw new VibeError5({
|
|
492
|
+
code: "VIBE_VALIDATION",
|
|
493
|
+
message: `bun:sql prepared statement references $${placeholder} but only ${values.length} parameter(s) were given`,
|
|
494
|
+
meta: { text, placeholder, provided: values.length }
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
return values[placeholder - 1];
|
|
498
|
+
});
|
|
145
499
|
}
|
|
146
500
|
function createTemplateCache(params) {
|
|
147
501
|
const { max } = params;
|
|
@@ -155,15 +509,14 @@ function createTemplateCache(params) {
|
|
|
155
509
|
templates.set(text, existing);
|
|
156
510
|
return existing;
|
|
157
511
|
}
|
|
158
|
-
const
|
|
159
|
-
const template = Object.assign(chunks, { raw: chunks });
|
|
512
|
+
const prepared = scanPlaceholders({ text });
|
|
160
513
|
if (templates.size >= max) {
|
|
161
514
|
const oldest = templates.keys().next().value;
|
|
162
515
|
if (oldest !== undefined)
|
|
163
516
|
templates.delete(oldest);
|
|
164
517
|
}
|
|
165
|
-
templates.set(text,
|
|
166
|
-
return
|
|
518
|
+
templates.set(text, prepared);
|
|
519
|
+
return prepared;
|
|
167
520
|
},
|
|
168
521
|
size() {
|
|
169
522
|
return templates.size;
|
|
@@ -172,7 +525,7 @@ function createTemplateCache(params) {
|
|
|
172
525
|
}
|
|
173
526
|
|
|
174
527
|
// packages/adapter-bun/src/transaction-sql.ts
|
|
175
|
-
import { VibeError as
|
|
528
|
+
import { VibeError as VibeError6 } from "@vibeorm/schema";
|
|
176
529
|
var ISOLATION_LEVEL_SQL = Object.freeze({
|
|
177
530
|
ReadCommitted: "READ COMMITTED",
|
|
178
531
|
RepeatableRead: "REPEATABLE READ",
|
|
@@ -185,7 +538,7 @@ function beginStatement(params) {
|
|
|
185
538
|
function statementTimeoutStatement(params) {
|
|
186
539
|
const { timeout } = params;
|
|
187
540
|
if (!Number.isInteger(timeout) || timeout < 0) {
|
|
188
|
-
throw new
|
|
541
|
+
throw new VibeError6({
|
|
189
542
|
code: "VIBE_VALIDATION",
|
|
190
543
|
message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,
|
|
191
544
|
meta: { timeout }
|
|
@@ -211,6 +564,8 @@ function refuseNestedTransactionOptions(params) {
|
|
|
211
564
|
passed.push("isolationLevel");
|
|
212
565
|
if (options.timeout !== undefined)
|
|
213
566
|
passed.push("timeout");
|
|
567
|
+
if (options.deadline !== undefined)
|
|
568
|
+
passed.push("deadline");
|
|
214
569
|
if (passed.length === 0)
|
|
215
570
|
return;
|
|
216
571
|
const meta = { provider, nested: true };
|
|
@@ -218,9 +573,11 @@ function refuseNestedTransactionOptions(params) {
|
|
|
218
573
|
meta.isolationLevel = options.isolationLevel;
|
|
219
574
|
if (options.timeout !== undefined)
|
|
220
575
|
meta.timeout = options.timeout;
|
|
221
|
-
|
|
576
|
+
if (options.deadline !== undefined)
|
|
577
|
+
meta.deadline = options.deadline;
|
|
578
|
+
throw new VibeError6({
|
|
222
579
|
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,
|
|
580
|
+
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
581
|
meta
|
|
225
582
|
});
|
|
226
583
|
}
|
|
@@ -236,10 +593,19 @@ class CallbackError extends Error {
|
|
|
236
593
|
}
|
|
237
594
|
var DIALECT = "postgres";
|
|
238
595
|
var PROVIDER = "postgresql";
|
|
239
|
-
var
|
|
596
|
+
var BUDGETS = Object.freeze({
|
|
597
|
+
acquireTimeout: true,
|
|
598
|
+
statementTimeout: "all-statements",
|
|
599
|
+
transactionDeadline: "cancel-running-statements"
|
|
600
|
+
});
|
|
601
|
+
var WIRE_TEXT = Object.freeze({ dateTime: "native", json: "text", numbers: "native" });
|
|
602
|
+
var WIRE_PARSED = Object.freeze({ dateTime: "native", json: "parsed", numbers: "native" });
|
|
240
603
|
function bunAdapter(options) {
|
|
241
604
|
const usePrepared = options?.preparedStatements ?? false;
|
|
242
605
|
const templates = createTemplateCache({ max: options?.stmtCacheMax ?? 1000 });
|
|
606
|
+
let activeWire = WIRE_TEXT;
|
|
607
|
+
let wireSettled = false;
|
|
608
|
+
let wireProbe = null;
|
|
243
609
|
let instance = null;
|
|
244
610
|
let rawSession = null;
|
|
245
611
|
function getSql() {
|
|
@@ -258,11 +624,27 @@ function bunAdapter(options) {
|
|
|
258
624
|
instance = url === undefined ? new SQL(sqlOptions) : new SQL(url, sqlOptions);
|
|
259
625
|
return instance;
|
|
260
626
|
}
|
|
627
|
+
async function probeJsonWire(source) {
|
|
628
|
+
const probe = renderDiagnostic({ dialect: postgresDialect, query: "postgresJsonWire" });
|
|
629
|
+
try {
|
|
630
|
+
const rows = usePrepared ? await source(templates.getTemplate({ text: probe.text }).template) : await source.unsafe(probe.text, []);
|
|
631
|
+
const probed = rows[0]?.probe;
|
|
632
|
+
if (typeof probed === "object" && probed !== null)
|
|
633
|
+
activeWire = WIRE_PARSED;
|
|
634
|
+
} catch {} finally {
|
|
635
|
+
wireSettled = true;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
261
638
|
async function runExecute(params) {
|
|
639
|
+
if (!wireSettled) {
|
|
640
|
+
wireProbe ??= probeJsonWire(params.source);
|
|
641
|
+
await wireProbe;
|
|
642
|
+
}
|
|
262
643
|
const values = serializeParams({ values: params.values });
|
|
263
644
|
if (usePrepared) {
|
|
264
|
-
const
|
|
265
|
-
|
|
645
|
+
const prepared = templates.getTemplate({ text: params.text });
|
|
646
|
+
const bound = bindTemplateValues({ text: params.text, indices: prepared.indices, values });
|
|
647
|
+
return runMapped(() => params.source(prepared.template, ...bound));
|
|
266
648
|
}
|
|
267
649
|
return runMapped(() => params.source.unsafe(params.text, values));
|
|
268
650
|
}
|
|
@@ -272,61 +654,79 @@ function bunAdapter(options) {
|
|
|
272
654
|
return { rows, affectedRows: readAffectedRows({ rows }) };
|
|
273
655
|
}
|
|
274
656
|
function createAdapter(params) {
|
|
275
|
-
const { source, parentMode,
|
|
657
|
+
const { source, parentMode, scope, budget } = params;
|
|
658
|
+
async function applyEngineBudget() {
|
|
659
|
+
if (budget === undefined)
|
|
660
|
+
return;
|
|
661
|
+
const ms = engineStatementBudgetMs({ budget });
|
|
662
|
+
if (ms === null)
|
|
663
|
+
return;
|
|
664
|
+
await runMapped(() => source.unsafe(statementTimeoutStatement({ timeout: ms })));
|
|
665
|
+
}
|
|
276
666
|
async function transactionViaSavepoint(fn, nestedOptions) {
|
|
277
667
|
refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });
|
|
668
|
+
budget?.assertUsable({ stage: "nested" });
|
|
278
669
|
const transactional = source;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
parentMode: "tx",
|
|
285
|
-
savepointCounter
|
|
286
|
-
});
|
|
670
|
+
const driverSavepoint = transactional.savepoint?.bind(transactional);
|
|
671
|
+
if (driverSavepoint !== undefined) {
|
|
672
|
+
return runNestedSavepoint({
|
|
673
|
+
scope,
|
|
674
|
+
body: async (child) => {
|
|
287
675
|
try {
|
|
288
|
-
return await
|
|
676
|
+
return await driverSavepoint(async (savepointSql) => {
|
|
677
|
+
const nested = createAdapter({
|
|
678
|
+
source: savepointSql,
|
|
679
|
+
parentMode: "tx",
|
|
680
|
+
scope: child
|
|
681
|
+
});
|
|
682
|
+
try {
|
|
683
|
+
return await fn(nested);
|
|
684
|
+
} catch (error) {
|
|
685
|
+
throw new CallbackError(error);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
289
688
|
} catch (error) {
|
|
290
|
-
|
|
689
|
+
if (error instanceof CallbackError)
|
|
690
|
+
throw error.inner;
|
|
691
|
+
throw mapBunDriverError({ error });
|
|
291
692
|
}
|
|
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;
|
|
693
|
+
}
|
|
694
|
+
});
|
|
311
695
|
}
|
|
696
|
+
return runNestedSavepoint({
|
|
697
|
+
scope,
|
|
698
|
+
execute: (statement) => runMapped(() => transactional.unsafe(statement)),
|
|
699
|
+
body: (child) => fn(createAdapter({ source: transactional, parentMode: "tx", scope: child, budget }))
|
|
700
|
+
});
|
|
312
701
|
}
|
|
313
702
|
async function transactionViaPool(fn, transactionOptions) {
|
|
314
703
|
const pool = source;
|
|
315
|
-
|
|
704
|
+
validateTransactionDeadline({
|
|
705
|
+
options: transactionOptions,
|
|
706
|
+
support: BUDGETS.transactionDeadline,
|
|
707
|
+
provider: PROVIDER
|
|
708
|
+
});
|
|
709
|
+
if (transactionOptions?.isolationLevel === undefined && transactionOptions?.timeout === undefined && transactionOptions?.deadline === undefined) {
|
|
710
|
+
const budget3 = startTransactionBudget({ provider: PROVIDER });
|
|
316
711
|
try {
|
|
317
|
-
|
|
712
|
+
const result = await pool.begin(async (txSql) => {
|
|
713
|
+
const txScope = createSavepointScope();
|
|
318
714
|
const txAdapter = createAdapter({
|
|
319
715
|
source: txSql,
|
|
320
716
|
parentMode: "tx",
|
|
321
|
-
|
|
717
|
+
scope: txScope,
|
|
718
|
+
budget: budget3
|
|
322
719
|
});
|
|
323
720
|
try {
|
|
324
|
-
return await fn(txAdapter);
|
|
721
|
+
return await runRootScope({ scope: txScope, body: () => fn(txAdapter) });
|
|
325
722
|
} catch (error) {
|
|
326
723
|
throw new CallbackError(error);
|
|
327
724
|
}
|
|
328
725
|
});
|
|
726
|
+
budget3.close({ outcome: "committed" });
|
|
727
|
+
return result;
|
|
329
728
|
} catch (error) {
|
|
729
|
+
budget3.close({ outcome: "rolled-back" });
|
|
330
730
|
if (error instanceof CallbackError)
|
|
331
731
|
throw error.inner;
|
|
332
732
|
throw mapBunDriverError({ error });
|
|
@@ -334,22 +734,37 @@ function bunAdapter(options) {
|
|
|
334
734
|
}
|
|
335
735
|
const timeoutText = transactionOptions.timeout === undefined ? undefined : statementTimeoutStatement({ timeout: transactionOptions.timeout });
|
|
336
736
|
const reserved = await runMapped(() => pool.reserve());
|
|
737
|
+
const budget2 = startTransactionBudget({ options: transactionOptions, provider: PROVIDER });
|
|
337
738
|
try {
|
|
338
739
|
await runMapped(() => reserved.unsafe(beginStatement({ isolationLevel: transactionOptions.isolationLevel })));
|
|
339
740
|
if (timeoutText !== undefined)
|
|
340
741
|
await runMapped(() => reserved.unsafe(timeoutText));
|
|
742
|
+
const txScope = createSavepointScope();
|
|
341
743
|
const txAdapter = createAdapter({
|
|
342
744
|
source: reserved,
|
|
343
745
|
parentMode: "tx",
|
|
344
|
-
|
|
746
|
+
scope: txScope,
|
|
747
|
+
budget: budget2
|
|
345
748
|
});
|
|
346
|
-
const result = await fn(txAdapter);
|
|
749
|
+
const result = await runRootScope({ scope: txScope, body: () => fn(txAdapter) });
|
|
750
|
+
budget2.assertUsable({ stage: "commit" });
|
|
347
751
|
await runMapped(() => reserved.unsafe("COMMIT"));
|
|
752
|
+
budget2.close({ outcome: "committed" });
|
|
348
753
|
return result;
|
|
349
754
|
} catch (error) {
|
|
350
755
|
try {
|
|
351
756
|
await reserved.unsafe("ROLLBACK");
|
|
352
|
-
|
|
757
|
+
budget2.close({ outcome: "rolled-back" });
|
|
758
|
+
} catch {
|
|
759
|
+
budget2.close({ outcome: "unknown" });
|
|
760
|
+
if (budget2.totalMs !== null && isTransactionDeadlineError(error)) {
|
|
761
|
+
throw transactionOutcomeUnknownError({
|
|
762
|
+
provider: PROVIDER,
|
|
763
|
+
totalMs: budget2.totalMs,
|
|
764
|
+
cause: error
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
}
|
|
353
768
|
throw error;
|
|
354
769
|
} finally {
|
|
355
770
|
reserved.release();
|
|
@@ -358,11 +773,24 @@ function bunAdapter(options) {
|
|
|
358
773
|
return {
|
|
359
774
|
dialect: DIALECT,
|
|
360
775
|
provider: PROVIDER,
|
|
361
|
-
|
|
776
|
+
budgets: BUDGETS,
|
|
777
|
+
get wire() {
|
|
778
|
+
return activeWire;
|
|
779
|
+
},
|
|
362
780
|
async execute(execParams) {
|
|
781
|
+
const turn = scopeTurn({ scope });
|
|
782
|
+
if (turn !== undefined)
|
|
783
|
+
await turn;
|
|
784
|
+
budget?.assertUsable({ stage: "statement" });
|
|
785
|
+
await applyEngineBudget();
|
|
363
786
|
return runExecute({ source, ...execParams });
|
|
364
787
|
},
|
|
365
788
|
async executeUnsafe(execParams) {
|
|
789
|
+
const turn = scopeTurn({ scope });
|
|
790
|
+
if (turn !== undefined)
|
|
791
|
+
await turn;
|
|
792
|
+
budget?.assertUsable({ stage: "statement" });
|
|
793
|
+
await applyEngineBudget();
|
|
366
794
|
return runExecuteUnsafe({ source, ...execParams });
|
|
367
795
|
},
|
|
368
796
|
async transaction(fn, transactionOptions) {
|
|
@@ -389,13 +817,16 @@ function bunAdapter(options) {
|
|
|
389
817
|
return createAdapter({
|
|
390
818
|
source: getSql(),
|
|
391
819
|
parentMode: "pool",
|
|
392
|
-
|
|
820
|
+
scope: createSavepointScope()
|
|
393
821
|
});
|
|
394
822
|
}
|
|
395
823
|
return {
|
|
396
824
|
dialect: DIALECT,
|
|
397
825
|
provider: PROVIDER,
|
|
398
|
-
|
|
826
|
+
budgets: BUDGETS,
|
|
827
|
+
get wire() {
|
|
828
|
+
return activeWire;
|
|
829
|
+
},
|
|
399
830
|
async execute(params) {
|
|
400
831
|
return poolAdapter().execute(params);
|
|
401
832
|
},
|
|
@@ -461,14 +892,21 @@ function readAffectedRows(params) {
|
|
|
461
892
|
return rows.length;
|
|
462
893
|
}
|
|
463
894
|
export {
|
|
895
|
+
validateTransactionDeadline,
|
|
896
|
+
transactionOutcomeUnknownError,
|
|
897
|
+
transactionDeadlineError,
|
|
898
|
+
transactionClosedError,
|
|
464
899
|
toPgArrayLiteral,
|
|
465
900
|
templateChunks,
|
|
466
901
|
statementTimeoutStatement,
|
|
902
|
+
startTransactionBudget,
|
|
467
903
|
serializeParamsRaw,
|
|
468
904
|
serializeParams,
|
|
469
905
|
refuseNestedTransactionOptions,
|
|
470
906
|
nextSavepointName,
|
|
471
907
|
mapBunDriverError,
|
|
908
|
+
isTransactionDeadlineError,
|
|
909
|
+
engineStatementBudgetMs,
|
|
472
910
|
createTemplateCache,
|
|
473
911
|
createSavepointCounter,
|
|
474
912
|
classifyRawTransactionControl,
|
|
@@ -478,8 +916,9 @@ export {
|
|
|
478
916
|
beginStatement,
|
|
479
917
|
applyStartupOptions,
|
|
480
918
|
ISOLATION_LEVEL_SQL,
|
|
919
|
+
DEFAULT_DEADLINE_CLOCK,
|
|
481
920
|
BUN_SQLSTATE_REASONS,
|
|
482
921
|
BUN_SQLSTATE_ERROR_CODES
|
|
483
922
|
};
|
|
484
923
|
|
|
485
|
-
//# debugId=
|
|
924
|
+
//# debugId=86F466725BC7074B64756E2164756E21
|