@vibeorm/adapter-bun 1.3.0 → 2.0.0-alpha.2

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/dist/index.js ADDED
@@ -0,0 +1,485 @@
1
+ // @bun
2
+ // packages/adapter-bun/src/index.ts
3
+ var {SQL } = globalThis.Bun;
4
+
5
+ // packages/adapter-bun/src/connection-url.ts
6
+ import { VibeError } from "@vibeorm/schema";
7
+ function buildStartupOptions(params) {
8
+ const { planCacheMode, statementTimeout } = params;
9
+ const parts = [];
10
+ if (planCacheMode !== "auto")
11
+ parts.push(`-c plan_cache_mode=${planCacheMode}`);
12
+ if (statementTimeout !== undefined) {
13
+ if (!Number.isInteger(statementTimeout) || statementTimeout < 0) {
14
+ throw new VibeError({
15
+ code: "VIBE_VALIDATION",
16
+ message: `statementTimeout must be a non-negative integer of milliseconds, received ${String(statementTimeout)}`,
17
+ meta: { statementTimeout }
18
+ });
19
+ }
20
+ parts.push(`-c statement_timeout=${statementTimeout}`);
21
+ }
22
+ return parts.length === 0 ? undefined : parts.join(" ");
23
+ }
24
+ function applyStartupOptions(params) {
25
+ const { url, startupOptions } = params;
26
+ const separator = url.includes("?") ? "&" : "?";
27
+ return `${url}${separator}options=${encodeURIComponent(startupOptions)}`;
28
+ }
29
+
30
+ // packages/adapter-bun/src/errors.ts
31
+ import { VibeError as VibeError2 } from "@vibeorm/schema";
32
+ var BUN_SQLSTATE_ERROR_CODES = Object.freeze({
33
+ "23505": "VIBE_UNIQUE_VIOLATION",
34
+ "23503": "VIBE_FK_VIOLATION",
35
+ "23502": "VIBE_VALIDATION",
36
+ "23514": "VIBE_VALIDATION",
37
+ "22P02": "VIBE_VALIDATION",
38
+ "40001": "VIBE_TRANSACTION",
39
+ "40P01": "VIBE_TRANSACTION",
40
+ "57014": "VIBE_TRANSACTION"
41
+ });
42
+ var BUN_SQLSTATE_REASONS = Object.freeze({
43
+ "57014": "timeout"
44
+ });
45
+ var SQLSTATE_PATTERN = /^[0-9A-Z]{5}$/;
46
+ function readStringField(params) {
47
+ const value = params.source[params.key];
48
+ return typeof value === "string" && value.length > 0 ? value : undefined;
49
+ }
50
+ function bunErrorSqlstate(params) {
51
+ const { error } = params;
52
+ if (error === null || typeof error !== "object")
53
+ return;
54
+ const source = error;
55
+ for (const key of ["code", "errno"]) {
56
+ const value = readStringField({ source, key });
57
+ if (value !== undefined && SQLSTATE_PATTERN.test(value))
58
+ return value;
59
+ }
60
+ return;
61
+ }
62
+ function mapBunDriverError(params) {
63
+ const { error } = params;
64
+ if (error instanceof VibeError2)
65
+ return error;
66
+ const source = error !== null && typeof error === "object" ? error : {};
67
+ const sqlstate = bunErrorSqlstate({ error });
68
+ const code = (sqlstate === undefined ? undefined : BUN_SQLSTATE_ERROR_CODES[sqlstate]) ?? "VIBE_ADAPTER";
69
+ const meta = {};
70
+ if (sqlstate !== undefined)
71
+ meta.sqlstate = sqlstate;
72
+ const constraint = readStringField({ source, key: "constraint" });
73
+ if (constraint !== undefined)
74
+ meta.constraint = constraint;
75
+ const table = readStringField({ source, key: "table" });
76
+ if (table !== undefined)
77
+ meta.table = table;
78
+ const column = readStringField({ source, key: "column" });
79
+ if (column !== undefined)
80
+ meta.column = column;
81
+ const reason = sqlstate === undefined ? undefined : BUN_SQLSTATE_REASONS[sqlstate];
82
+ if (reason !== undefined)
83
+ meta.reason = reason;
84
+ const driverCode = readStringField({ source, key: "code" });
85
+ if (driverCode !== undefined && driverCode !== sqlstate)
86
+ meta.driverCode = driverCode;
87
+ const message = readStringField({ source, key: "message" }) ?? (typeof error === "string" ? error : "unknown bun:sql driver error");
88
+ return new VibeError2({ code, message, meta, cause: error });
89
+ }
90
+ async function runMapped(fn) {
91
+ try {
92
+ return await fn();
93
+ } catch (error) {
94
+ throw mapBunDriverError({ error });
95
+ }
96
+ }
97
+
98
+ // packages/adapter-bun/src/params.ts
99
+ import { VibeError as VibeError3 } from "@vibeorm/schema";
100
+ function quoteElement(value) {
101
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
102
+ }
103
+ function toPgArrayLiteral(values) {
104
+ const elements = values.map((value) => {
105
+ if (value === null || value === undefined)
106
+ return "NULL";
107
+ if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
108
+ return String(value);
109
+ }
110
+ if (value instanceof Date)
111
+ return quoteElement(value.toISOString());
112
+ if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
113
+ throw new VibeError3({
114
+ code: "VIBE_VALIDATION",
115
+ message: "binary values are not supported inside a postgres array literal",
116
+ meta: { driver: "bun:sql" }
117
+ });
118
+ }
119
+ if (typeof value === "string")
120
+ return quoteElement(value);
121
+ if (typeof value === "object")
122
+ return quoteElement(JSON.stringify(value));
123
+ return quoteElement(String(value));
124
+ });
125
+ return `{${elements.join(",")}}`;
126
+ }
127
+ function serializeParams(params) {
128
+ return params.values.slice();
129
+ }
130
+ function serializeParamsRaw(params) {
131
+ return params.values.map((value) => Array.isArray(value) ? toPgArrayLiteral(value) : value);
132
+ }
133
+
134
+ // packages/adapter-bun/src/savepoints.ts
135
+ function createSavepointCounter() {
136
+ return { n: 0 };
137
+ }
138
+ function nextSavepointName(params) {
139
+ return `vibeorm_sp_${params.counter.n++}`;
140
+ }
141
+
142
+ // packages/adapter-bun/src/template-cache.ts
143
+ function templateChunks(params) {
144
+ return params.text.split(/\$\d+/);
145
+ }
146
+ function createTemplateCache(params) {
147
+ const { max } = params;
148
+ const templates = new Map;
149
+ return {
150
+ getTemplate(templateParams) {
151
+ const { text } = templateParams;
152
+ const existing = templates.get(text);
153
+ if (existing !== undefined) {
154
+ templates.delete(text);
155
+ templates.set(text, existing);
156
+ return existing;
157
+ }
158
+ const chunks = templateChunks({ text });
159
+ const template = Object.assign(chunks, { raw: chunks });
160
+ if (templates.size >= max) {
161
+ const oldest = templates.keys().next().value;
162
+ if (oldest !== undefined)
163
+ templates.delete(oldest);
164
+ }
165
+ templates.set(text, template);
166
+ return template;
167
+ },
168
+ size() {
169
+ return templates.size;
170
+ }
171
+ };
172
+ }
173
+
174
+ // packages/adapter-bun/src/transaction-sql.ts
175
+ import { VibeError as VibeError4 } from "@vibeorm/schema";
176
+ var ISOLATION_LEVEL_SQL = Object.freeze({
177
+ ReadCommitted: "READ COMMITTED",
178
+ RepeatableRead: "REPEATABLE READ",
179
+ Serializable: "SERIALIZABLE"
180
+ });
181
+ function beginStatement(params) {
182
+ const { isolationLevel } = params;
183
+ return isolationLevel === undefined ? "BEGIN" : `BEGIN ISOLATION LEVEL ${ISOLATION_LEVEL_SQL[isolationLevel]}`;
184
+ }
185
+ function statementTimeoutStatement(params) {
186
+ const { timeout } = params;
187
+ if (!Number.isInteger(timeout) || timeout < 0) {
188
+ throw new VibeError4({
189
+ code: "VIBE_VALIDATION",
190
+ message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,
191
+ meta: { timeout }
192
+ });
193
+ }
194
+ return `SET LOCAL statement_timeout = ${timeout}`;
195
+ }
196
+ var RAW_OPEN_PATTERN = /^\s*(?:begin|start\s+transaction)\b[^;]*;?\s*$/i;
197
+ var RAW_CLOSE_PATTERN = /^\s*(?:commit|end|rollback|abort)(?:\s+(?:transaction|work))?\s*;?\s*$/i;
198
+ function classifyRawTransactionControl(params) {
199
+ if (RAW_OPEN_PATTERN.test(params.text))
200
+ return "open";
201
+ if (RAW_CLOSE_PATTERN.test(params.text))
202
+ return "close";
203
+ return null;
204
+ }
205
+ function refuseNestedTransactionOptions(params) {
206
+ const { options, provider } = params;
207
+ if (options === undefined)
208
+ return;
209
+ const passed = [];
210
+ if (options.isolationLevel !== undefined)
211
+ passed.push("isolationLevel");
212
+ if (options.timeout !== undefined)
213
+ passed.push("timeout");
214
+ if (passed.length === 0)
215
+ return;
216
+ const meta = { provider, nested: true };
217
+ if (options.isolationLevel !== undefined)
218
+ meta.isolationLevel = options.isolationLevel;
219
+ if (options.timeout !== undefined)
220
+ meta.timeout = options.timeout;
221
+ throw new VibeError4({
222
+ 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, and a nested timeout is not enforceable; pass options on the top-level $transaction`,
224
+ meta
225
+ });
226
+ }
227
+
228
+ // packages/adapter-bun/src/index.ts
229
+ class CallbackError extends Error {
230
+ inner;
231
+ constructor(inner) {
232
+ super("vibeorm: transaction callback failed");
233
+ this.name = "CallbackError";
234
+ this.inner = inner;
235
+ }
236
+ }
237
+ var DIALECT = "postgres";
238
+ var PROVIDER = "postgresql";
239
+ var WIRE = Object.freeze({ dateTime: "native", json: "text", numbers: "native" });
240
+ function bunAdapter(options) {
241
+ const usePrepared = options?.preparedStatements ?? false;
242
+ const templates = createTemplateCache({ max: options?.stmtCacheMax ?? 1000 });
243
+ let instance = null;
244
+ let rawSession = null;
245
+ function getSql() {
246
+ if (instance !== null)
247
+ return instance;
248
+ const sqlOptions = { max: options?.max ?? 10 };
249
+ if (options?.connectionTimeout !== undefined) {
250
+ sqlOptions.connectionTimeout = Math.max(1, Math.ceil(options.connectionTimeout / 1000));
251
+ }
252
+ const startupOptions = buildStartupOptions({
253
+ planCacheMode: options?.planCacheMode ?? "force_custom_plan",
254
+ statementTimeout: options?.statementTimeout
255
+ });
256
+ const baseUrl = options?.url ?? process.env.DATABASE_URL;
257
+ const url = baseUrl === undefined || startupOptions === undefined ? baseUrl : applyStartupOptions({ url: baseUrl, startupOptions });
258
+ instance = url === undefined ? new SQL(sqlOptions) : new SQL(url, sqlOptions);
259
+ return instance;
260
+ }
261
+ async function runExecute(params) {
262
+ const values = serializeParams({ values: params.values });
263
+ if (usePrepared) {
264
+ const template = templates.getTemplate({ text: params.text });
265
+ return runMapped(() => params.source(template, ...values));
266
+ }
267
+ return runMapped(() => params.source.unsafe(params.text, values));
268
+ }
269
+ async function runExecuteUnsafe(params) {
270
+ const values = params.values === undefined ? undefined : serializeParamsRaw({ values: params.values });
271
+ const rows = await runMapped(() => params.source.unsafe(params.text, values));
272
+ return { rows, affectedRows: readAffectedRows({ rows }) };
273
+ }
274
+ function createAdapter(params) {
275
+ const { source, parentMode, savepointCounter } = params;
276
+ async function transactionViaSavepoint(fn, nestedOptions) {
277
+ refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });
278
+ const transactional = source;
279
+ if (typeof transactional.savepoint === "function") {
280
+ try {
281
+ return await transactional.savepoint(async (savepointSql) => {
282
+ const nested = createAdapter({
283
+ source: savepointSql,
284
+ parentMode: "tx",
285
+ savepointCounter
286
+ });
287
+ try {
288
+ return await fn(nested);
289
+ } catch (error) {
290
+ throw new CallbackError(error);
291
+ }
292
+ });
293
+ } catch (error) {
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;
311
+ }
312
+ }
313
+ async function transactionViaPool(fn, transactionOptions) {
314
+ const pool = source;
315
+ if (transactionOptions?.isolationLevel === undefined && transactionOptions?.timeout === undefined) {
316
+ try {
317
+ return await pool.begin(async (txSql) => {
318
+ const txAdapter = createAdapter({
319
+ source: txSql,
320
+ parentMode: "tx",
321
+ savepointCounter: createSavepointCounter()
322
+ });
323
+ try {
324
+ return await fn(txAdapter);
325
+ } catch (error) {
326
+ throw new CallbackError(error);
327
+ }
328
+ });
329
+ } catch (error) {
330
+ if (error instanceof CallbackError)
331
+ throw error.inner;
332
+ throw mapBunDriverError({ error });
333
+ }
334
+ }
335
+ const timeoutText = transactionOptions.timeout === undefined ? undefined : statementTimeoutStatement({ timeout: transactionOptions.timeout });
336
+ const reserved = await runMapped(() => pool.reserve());
337
+ try {
338
+ await runMapped(() => reserved.unsafe(beginStatement({ isolationLevel: transactionOptions.isolationLevel })));
339
+ if (timeoutText !== undefined)
340
+ await runMapped(() => reserved.unsafe(timeoutText));
341
+ const txAdapter = createAdapter({
342
+ source: reserved,
343
+ parentMode: "tx",
344
+ savepointCounter: createSavepointCounter()
345
+ });
346
+ const result = await fn(txAdapter);
347
+ await runMapped(() => reserved.unsafe("COMMIT"));
348
+ return result;
349
+ } catch (error) {
350
+ try {
351
+ await reserved.unsafe("ROLLBACK");
352
+ } catch {}
353
+ throw error;
354
+ } finally {
355
+ reserved.release();
356
+ }
357
+ }
358
+ return {
359
+ dialect: DIALECT,
360
+ provider: PROVIDER,
361
+ wire: WIRE,
362
+ async execute(execParams) {
363
+ return runExecute({ source, ...execParams });
364
+ },
365
+ async executeUnsafe(execParams) {
366
+ return runExecuteUnsafe({ source, ...execParams });
367
+ },
368
+ async transaction(fn, transactionOptions) {
369
+ return parentMode === "pool" ? transactionViaPool(fn, transactionOptions) : transactionViaSavepoint(fn, transactionOptions);
370
+ },
371
+ async connect() {
372
+ if (parentMode !== "pool")
373
+ return;
374
+ const reserved = await runMapped(() => source.reserve());
375
+ reserved.release();
376
+ },
377
+ async disconnect() {
378
+ if (parentMode !== "pool")
379
+ return;
380
+ await runMapped(() => source.close());
381
+ instance = null;
382
+ },
383
+ formatArrayParam(values) {
384
+ return toPgArrayLiteral(values);
385
+ }
386
+ };
387
+ }
388
+ function poolAdapter() {
389
+ return createAdapter({
390
+ source: getSql(),
391
+ parentMode: "pool",
392
+ savepointCounter: createSavepointCounter()
393
+ });
394
+ }
395
+ return {
396
+ dialect: DIALECT,
397
+ provider: PROVIDER,
398
+ wire: WIRE,
399
+ async execute(params) {
400
+ return poolAdapter().execute(params);
401
+ },
402
+ async executeUnsafe(params) {
403
+ const control = classifyRawTransactionControl({ text: params.text });
404
+ if (rawSession !== null) {
405
+ if (control === "close") {
406
+ const session = rawSession;
407
+ rawSession = null;
408
+ try {
409
+ return await runExecuteUnsafe({ source: session, ...params });
410
+ } finally {
411
+ session.release();
412
+ }
413
+ }
414
+ return runExecuteUnsafe({ source: rawSession, ...params });
415
+ }
416
+ if (control === "open") {
417
+ const reserved = await runMapped(() => getSql().reserve());
418
+ try {
419
+ const result = await runExecuteUnsafe({ source: reserved, ...params });
420
+ rawSession = reserved;
421
+ return result;
422
+ } catch (error) {
423
+ reserved.release();
424
+ throw error;
425
+ }
426
+ }
427
+ return poolAdapter().executeUnsafe(params);
428
+ },
429
+ async transaction(fn, transactionOptions) {
430
+ return poolAdapter().transaction(fn, transactionOptions);
431
+ },
432
+ async connect() {
433
+ return poolAdapter().connect();
434
+ },
435
+ async disconnect() {
436
+ if (rawSession !== null) {
437
+ const session = rawSession;
438
+ rawSession = null;
439
+ try {
440
+ await session.unsafe("ROLLBACK");
441
+ } catch {}
442
+ session.release();
443
+ }
444
+ if (instance === null)
445
+ return;
446
+ const closing = instance;
447
+ instance = null;
448
+ await runMapped(() => closing.close());
449
+ },
450
+ formatArrayParam(values) {
451
+ return toPgArrayLiteral(values);
452
+ }
453
+ };
454
+ }
455
+ function readAffectedRows(params) {
456
+ const { rows } = params;
457
+ if (typeof rows.count === "number")
458
+ return rows.count;
459
+ if (typeof rows.affectedRows === "number")
460
+ return rows.affectedRows;
461
+ return rows.length;
462
+ }
463
+ export {
464
+ toPgArrayLiteral,
465
+ templateChunks,
466
+ statementTimeoutStatement,
467
+ serializeParamsRaw,
468
+ serializeParams,
469
+ refuseNestedTransactionOptions,
470
+ nextSavepointName,
471
+ mapBunDriverError,
472
+ createTemplateCache,
473
+ createSavepointCounter,
474
+ classifyRawTransactionControl,
475
+ bunErrorSqlstate,
476
+ bunAdapter,
477
+ buildStartupOptions,
478
+ beginStatement,
479
+ applyStartupOptions,
480
+ ISOLATION_LEVEL_SQL,
481
+ BUN_SQLSTATE_REASONS,
482
+ BUN_SQLSTATE_ERROR_CODES
483
+ };
484
+
485
+ //# debugId=08E3C28E82D7C93A64756E2164756E21
@@ -0,0 +1,16 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts", "../src/connection-url.ts", "../src/errors.ts", "../src/params.ts", "../src/savepoints.ts", "../src/template-cache.ts", "../src/transaction-sql.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * @vibeorm/adapter-bun — bun:sql (PostgreSQL) adapter for VibeORM v2.\n *\n * Uses Bun's built-in SQL driver and its internal connection pool. Owns\n * optional prepared statements (synthetic tagged templates), transactions\n * (nested → savepoints), array-literal parameter formatting and driver-error\n * mapping. Only `VibeError` ever escapes.\n */\n\nimport { SQL } from \"bun\";\nimport type { DatabaseAdapter, QueryResult, TransactionOptions, WireFidelity } from \"@vibeorm/runtime\";\nimport type { Dialect, Provider } from \"@vibeorm/schema\";\nimport { applyStartupOptions, buildStartupOptions, type PlanCacheMode } from \"./connection-url.ts\";\nimport { mapBunDriverError, runMapped } from \"./errors.ts\";\nimport { serializeParams, serializeParamsRaw, toPgArrayLiteral } from \"./params.ts\";\nimport { createSavepointCounter, nextSavepointName, type SavepointCounter } from \"./savepoints.ts\";\nimport { createTemplateCache } from \"./template-cache.ts\";\nimport {\n beginStatement,\n classifyRawTransactionControl,\n refuseNestedTransactionOptions,\n statementTimeoutStatement,\n} from \"./transaction-sql.ts\";\n\n// ─── Minimal bun:sql surface ──────────────────────────────────────\n\n/**\n * What bun:sql resolves a query to: the row array, with driver metadata\n * (`count`, `command`) attached as properties.\n */\ntype BunRows = Record<string, unknown>[] & { count?: number; affectedRows?: number };\n\n/** Anything that executes SQL through bun:sql. */\ntype BunExecutor = {\n (strings: TemplateStringsArray, ...values: unknown[]): Promise<BunRows>;\n unsafe(text: string, values?: unknown[]): Promise<BunRows>;\n};\n\n/**\n * The handle passed to `sql.begin(cb)`. Modern bun:sql exposes `savepoint(fn)`\n * for sub-transactions; `begin()` throws inside a transaction (\"use\n * savepoint() instead\"), so it is never called here. Older builds lack\n * `savepoint`, which is why it is optional and probed at runtime.\n */\ntype BunTransaction = BunExecutor & {\n savepoint?: <T>(fn: (sp: BunTransaction) => Promise<T>) => Promise<T>;\n};\n\n/** A connection checked out with `sql.reserve()` — no `savepoint`, must be released. */\ntype BunReserved = BunExecutor & { release(): void };\n\n/** The pool-level bun:sql client. */\ntype BunInstance = BunExecutor & {\n begin<T>(fn: (tx: BunTransaction) => Promise<T>): Promise<T>;\n reserve(): Promise<BunReserved>;\n close(): Promise<void>;\n};\n\n/** Any bun:sql handle the adapter can be built on top of. */\ntype BunSource = BunInstance | BunReserved | BunTransaction;\n\n/**\n * Whether a handle is the pool (can open a real transaction) or is already\n * inside one (must nest through savepoints).\n */\ntype ParentMode = \"pool\" | \"tx\";\n\n/**\n * Wrapper that marks an error as \"thrown by the user's transaction callback\",\n * so it can be rethrown untouched instead of being mapped as a driver failure\n * after bun:sql has passed it back out of `begin()` / `savepoint()`.\n */\nclass CallbackError extends Error {\n readonly inner: unknown;\n\n constructor(inner: unknown) {\n super(\"vibeorm: transaction callback failed\");\n this.name = \"CallbackError\";\n this.inner = inner;\n }\n}\n\n// ─── Options ──────────────────────────────────────────────────────\n\n/** Options for {@link bunAdapter}. */\nexport type BunAdapterOptions = {\n /** PostgreSQL connection URL. Defaults to `process.env.DATABASE_URL`, then bun's `PG*` env vars. */\n readonly url?: string;\n /** Maximum pooled connections (default 10). */\n readonly max?: number;\n /**\n * Pool-wide `statement_timeout` in milliseconds, sent as a startup parameter\n * so every connection inherits it. A per-transaction `timeout` overrides it\n * for that transaction.\n */\n readonly statementTimeout?: number;\n /**\n * Milliseconds to wait for a connection to be established. Passed to bun:sql\n * as `connectionTimeout` (which expects **seconds**; converted here).\n */\n readonly connectionTimeout?: number;\n /**\n * Execute ORM-path queries as tagged templates, creating named prepared\n * statements (default false). Saves planning time but lets PostgreSQL fall\n * back to a generic plan after ~5 executions — leave off unless profiling\n * says otherwise. bun:sql's `prepare: false` constructor option is never set\n * (it triggers a known bun:sql performance regression).\n */\n readonly preparedStatements?: boolean;\n /** Maximum entries in the synthetic template cache (default 1000, LRU). */\n readonly stmtCacheMax?: number;\n /** PostgreSQL `plan_cache_mode` for every connection (default `force_custom_plan`). */\n readonly planCacheMode?: PlanCacheMode;\n};\n\n// ─── Adapter ──────────────────────────────────────────────────────\n\nconst DIALECT: Dialect = \"postgres\";\nconst PROVIDER: Provider = \"postgresql\";\n\n/**\n * bun:sql wire (board #30, live-verified on both execute paths): timestamptz\n * → `Date`, int4/float8 → numbers. (int8 arrives as a string — BigInt\n * decoding is real work and never pruned.)\n *\n * json is declared \"text\", NOT \"parsed\": this adapter routes every statement\n * through `sql.unsafe()`, and Bun's unsafe path skips the JSON type parser —\n * json/jsonb arrive as raw JSON TEXT (verified on Bun 1.3.14: the tagged-\n * template path parses, `sql.unsafe` with or without params does not). The\n * \"parsed\" claim held on the template path only; declaring it here made the\n * runtime prune the Json decoder and leak raw strings to callers — caught by\n * the CI server lane for this adapter (board #34).\n */\nconst WIRE: WireFidelity = Object.freeze({ dateTime: \"native\", json: \"text\", numbers: \"native\" } as const);\n\n/**\n * Create a VibeORM adapter backed by bun:sql.\n *\n * @example\n * ```ts\n * const adapter = bunAdapter({ url: process.env.DATABASE_URL, max: 10 });\n * await adapter.connect();\n * const rows = await adapter.execute({ text: 'SELECT * FROM \"User\" WHERE \"id\" = $1', values: [1] });\n * ```\n */\nexport function bunAdapter(options?: BunAdapterOptions): DatabaseAdapter {\n const usePrepared = options?.preparedStatements ?? false;\n const templates = createTemplateCache({ max: options?.stmtCacheMax ?? 1000 });\n\n let instance: BunInstance | null = null;\n\n /**\n * The reserved connection of an OPEN raw transaction — session affinity for\n * the raw path. @vibeorm/migrate wraps transactional DDL in literal `BEGIN`\n * / `COMMIT` / `ROLLBACK` sent through `executeUnsafe`; bun:sql refuses\n * transaction control through `sql.unsafe()` on a pooled instance\n * (`ERR_POSTGRES_UNSAFE_TRANSACTION`), so a pure `BEGIN` reserves one\n * connection, every raw statement until the matching `COMMIT`/`ROLLBACK`\n * rides it, and the close releases it. The ORM path (`execute`) and\n * `transaction()` stay on the pool — they never participate in a raw\n * transaction. Concurrent raw transactions on one adapter are unsupported\n * (they were driver-refused before this pinning existed).\n */\n let rawSession: BunReserved | null = null;\n\n function getSql(): BunInstance {\n if (instance !== null) return instance;\n\n const sqlOptions: Record<string, unknown> = { max: options?.max ?? 10 };\n if (options?.connectionTimeout !== undefined) {\n // bun:sql expects seconds; the VibeORM surface is milliseconds everywhere.\n sqlOptions.connectionTimeout = Math.max(1, Math.ceil(options.connectionTimeout / 1000));\n }\n\n const startupOptions = buildStartupOptions({\n planCacheMode: options?.planCacheMode ?? \"force_custom_plan\",\n statementTimeout: options?.statementTimeout,\n });\n const baseUrl = options?.url ?? process.env.DATABASE_URL;\n const url =\n baseUrl === undefined || startupOptions === undefined\n ? baseUrl\n : applyStartupOptions({ url: baseUrl, startupOptions });\n\n // Without a URL, bun:sql falls back to PG* environment variables (startup\n // options cannot be attached in that case).\n instance =\n url === undefined\n ? (new SQL(sqlOptions) as unknown as BunInstance)\n : (new SQL(url, sqlOptions) as unknown as BunInstance);\n return instance;\n }\n\n async function runExecute(params: {\n source: BunExecutor;\n text: string;\n values: unknown[];\n }): Promise<Record<string, unknown>[]> {\n const values = serializeParams({ values: params.values });\n if (usePrepared) {\n const template = templates.getTemplate({ text: params.text });\n return runMapped(() => params.source(template, ...values));\n }\n return runMapped(() => params.source.unsafe(params.text, values));\n }\n\n async function runExecuteUnsafe(params: {\n source: BunExecutor;\n text: string;\n values?: unknown[];\n }): Promise<QueryResult> {\n const values = params.values === undefined ? undefined : serializeParamsRaw({ values: params.values });\n const rows = await runMapped(() => params.source.unsafe(params.text, values));\n return { rows, affectedRows: readAffectedRows({ rows }) };\n }\n\n /**\n * Build the adapter facade over any bun:sql handle. `parentMode` decides\n * whether a nested `transaction()` opens a real transaction or a savepoint;\n * `savepointCounter` is shared by reference across one top-level transaction\n * so sibling and nested savepoints never collide.\n */\n function createAdapter(params: {\n source: BunSource;\n parentMode: ParentMode;\n savepointCounter: SavepointCounter;\n }): DatabaseAdapter {\n const { source, parentMode, savepointCounter } = params;\n\n /** Nested transaction: a savepoint on the connection we are already on. */\n async function transactionViaSavepoint<T>(\n fn: (txAdapter: DatabaseAdapter) => Promise<T>,\n nestedOptions?: TransactionOptions,\n ): Promise<T> {\n refuseNestedTransactionOptions({ options: nestedOptions, provider: PROVIDER });\n const transactional = source as BunTransaction;\n\n // Modern bun:sql: the in-transaction handle drives savepoints itself.\n if (typeof transactional.savepoint === \"function\") {\n try {\n return await transactional.savepoint(async (savepointSql) => {\n const nested = createAdapter({\n source: savepointSql,\n parentMode: \"tx\",\n savepointCounter,\n });\n try {\n return await fn(nested);\n } catch (error) {\n throw new CallbackError(error);\n }\n });\n } catch (error) {\n if (error instanceof CallbackError) throw error.inner;\n throw mapBunDriverError({ error });\n }\n }\n\n // Reserved connections (and older bun:sql) expose no savepoint helper —\n // drive SAVEPOINT / RELEASE / ROLLBACK TO by hand.\n const savepoint = nextSavepointName({ counter: savepointCounter });\n await runMapped(() => transactional.unsafe(`SAVEPOINT ${savepoint}`));\n try {\n const nested = createAdapter({ source: transactional, parentMode: \"tx\", savepointCounter });\n const result = await fn(nested);\n await runMapped(() => transactional.unsafe(`RELEASE SAVEPOINT ${savepoint}`));\n return result;\n } catch (error) {\n // Best effort: never mask the original failure with a rollback failure.\n try {\n await transactional.unsafe(`ROLLBACK TO SAVEPOINT ${savepoint}`);\n } catch {\n /* rollback is best-effort */\n }\n throw error;\n }\n }\n\n /** Top-level transaction on the pool. */\n async function transactionViaPool<T>(\n fn: (txAdapter: DatabaseAdapter) => Promise<T>,\n transactionOptions?: TransactionOptions,\n ): Promise<T> {\n const pool = source as BunInstance;\n\n // Fast path: bun:sql drives BEGIN/COMMIT itself.\n if (transactionOptions?.isolationLevel === undefined && transactionOptions?.timeout === undefined) {\n try {\n return await pool.begin(async (txSql) => {\n const txAdapter = createAdapter({\n source: txSql,\n parentMode: \"tx\",\n savepointCounter: createSavepointCounter(),\n });\n try {\n return await fn(txAdapter);\n } catch (error) {\n throw new CallbackError(error);\n }\n });\n } catch (error) {\n if (error instanceof CallbackError) throw error.inner;\n throw mapBunDriverError({ error });\n }\n }\n\n // Options path: reserve one connection and drive the transaction by hand,\n // because bun:sql's begin() takes no isolation/timeout options.\n const timeoutText =\n transactionOptions.timeout === undefined\n ? undefined\n : statementTimeoutStatement({ timeout: transactionOptions.timeout });\n const reserved = await runMapped(() => pool.reserve());\n try {\n await runMapped(() =>\n reserved.unsafe(beginStatement({ isolationLevel: transactionOptions.isolationLevel })),\n );\n if (timeoutText !== undefined) await runMapped(() => reserved.unsafe(timeoutText));\n const txAdapter = createAdapter({\n source: reserved,\n parentMode: \"tx\",\n savepointCounter: createSavepointCounter(),\n });\n const result = await fn(txAdapter);\n await runMapped(() => reserved.unsafe(\"COMMIT\"));\n return result;\n } catch (error) {\n try {\n await reserved.unsafe(\"ROLLBACK\");\n } catch {\n /* rollback is best-effort */\n }\n throw error;\n } finally {\n reserved.release();\n }\n }\n\n return {\n dialect: DIALECT,\n provider: PROVIDER,\n wire: WIRE,\n\n async execute(execParams) {\n return runExecute({ source, ...execParams });\n },\n\n async executeUnsafe(execParams) {\n return runExecuteUnsafe({ source, ...execParams });\n },\n\n async transaction<T>(\n fn: (txAdapter: DatabaseAdapter) => Promise<T>,\n transactionOptions?: TransactionOptions,\n ): Promise<T> {\n return parentMode === \"pool\"\n ? transactionViaPool(fn, transactionOptions)\n : transactionViaSavepoint(fn, transactionOptions);\n },\n\n async connect(): Promise<void> {\n if (parentMode !== \"pool\") return;\n const reserved = await runMapped(() => (source as BunInstance).reserve());\n reserved.release();\n },\n\n async disconnect(): Promise<void> {\n if (parentMode !== \"pool\") return;\n await runMapped(() => (source as BunInstance).close());\n instance = null;\n },\n\n formatArrayParam(values: unknown[]): unknown {\n return toPgArrayLiteral(values);\n },\n };\n }\n\n /** Pool-level facade — the bun:sql client is created on first use. */\n function poolAdapter(): DatabaseAdapter {\n return createAdapter({\n source: getSql(),\n parentMode: \"pool\",\n savepointCounter: createSavepointCounter(),\n });\n }\n\n return {\n dialect: DIALECT,\n provider: PROVIDER,\n wire: WIRE,\n\n async execute(params) {\n return poolAdapter().execute(params);\n },\n\n async executeUnsafe(params) {\n const control = classifyRawTransactionControl({ text: params.text });\n if (rawSession !== null) {\n if (control === \"close\") {\n const session = rawSession;\n rawSession = null;\n try {\n return await runExecuteUnsafe({ source: session, ...params });\n } finally {\n session.release();\n }\n }\n // A nested raw BEGIN lands here too — postgres treats it as a warning\n // no-op on the open session, exactly like a single connection would.\n return runExecuteUnsafe({ source: rawSession, ...params });\n }\n if (control === \"open\") {\n const reserved = await runMapped(() => getSql().reserve());\n try {\n const result = await runExecuteUnsafe({ source: reserved, ...params });\n rawSession = reserved;\n return result;\n } catch (error) {\n reserved.release();\n throw error;\n }\n }\n return poolAdapter().executeUnsafe(params);\n },\n\n async transaction<T>(\n fn: (txAdapter: DatabaseAdapter) => Promise<T>,\n transactionOptions?: TransactionOptions,\n ): Promise<T> {\n return poolAdapter().transaction(fn, transactionOptions);\n },\n\n async connect(): Promise<void> {\n return poolAdapter().connect();\n },\n\n async disconnect(): Promise<void> {\n if (rawSession !== null) {\n // An abandoned raw transaction must not leak an in-transaction\n // connection; roll it back best-effort before the pool closes.\n const session = rawSession;\n rawSession = null;\n try {\n await session.unsafe(\"ROLLBACK\");\n } catch {\n /* the pool is closing; the session is going away regardless */\n }\n session.release();\n }\n if (instance === null) return;\n const closing = instance;\n instance = null;\n await runMapped(() => closing.close());\n },\n\n formatArrayParam(values: unknown[]): unknown {\n return toPgArrayLiteral(values);\n },\n };\n}\n\n/**\n * Rows affected by a bun:sql statement: the driver attaches `count` (and, on\n * some versions, `affectedRows`) to the returned array; otherwise the number of\n * returned rows is the best available answer.\n */\nfunction readAffectedRows(params: { rows: BunRows }): number {\n const { rows } = params;\n if (typeof rows.count === \"number\") return rows.count;\n if (typeof rows.affectedRows === \"number\") return rows.affectedRows;\n return rows.length;\n}\n\n// ─── Re-exports (driver-error table, params, helpers) ─────────────\n\nexport {\n BUN_SQLSTATE_ERROR_CODES,\n BUN_SQLSTATE_REASONS,\n bunErrorSqlstate,\n mapBunDriverError,\n} from \"./errors.ts\";\nexport { serializeParams, serializeParamsRaw, toPgArrayLiteral } from \"./params.ts\";\nexport { applyStartupOptions, buildStartupOptions } from \"./connection-url.ts\";\nexport type { PlanCacheMode } from \"./connection-url.ts\";\nexport { createSavepointCounter, nextSavepointName } from \"./savepoints.ts\";\nexport type { SavepointCounter } from \"./savepoints.ts\";\nexport { createTemplateCache, templateChunks } from \"./template-cache.ts\";\nexport type { TemplateCache } from \"./template-cache.ts\";\nexport {\n ISOLATION_LEVEL_SQL,\n beginStatement,\n classifyRawTransactionControl,\n refuseNestedTransactionOptions,\n statementTimeoutStatement,\n} from \"./transaction-sql.ts\";\nexport type { RawTransactionControl } from \"./transaction-sql.ts\";\n",
6
+ "/**\n * Connection-URL handling for bun:sql.\n *\n * Per-connection PostgreSQL settings (`plan_cache_mode`, `statement_timeout`)\n * travel in the `options` startup parameter, which the server parses for\n * `-c key=value` pairs — so every connection in bun's internal pool inherits\n * them without a per-query round trip.\n *\n * v1 lesson (LEARNINGS.md): bun:sql forwards *unrecognised* URL parameters to\n * the server as runtime configuration, so a libpq client-side option such as\n * `connect_timeout` must never be put in the URL (PostgreSQL rejects it as an\n * unrecognised configuration parameter). Connection timeouts use bun:sql's\n * `connectionTimeout` constructor option instead.\n */\n\nimport { VibeError } from \"@vibeorm/schema\";\n\n/**\n * PostgreSQL plan-cache behaviour for the implicit prepared statements bun:sql\n * creates. `force_custom_plan` replans with the real parameter values (≈0.1 ms\n * per query) and keeps index selection optimal on range/LIKE/skewed filters —\n * `auto` lets PostgreSQL switch to a generic plan after ~5 executions.\n */\nexport type PlanCacheMode = \"auto\" | \"force_custom_plan\" | \"force_generic_plan\";\n\n/**\n * Build the `options` startup string, or `undefined` when nothing needs setting.\n *\n * @throws VibeError `VIBE_VALIDATION` when `statementTimeout` is not a\n * non-negative integer — the value ends up inside the startup string verbatim.\n */\nexport function buildStartupOptions(params: {\n planCacheMode: PlanCacheMode;\n statementTimeout?: number;\n}): string | undefined {\n const { planCacheMode, statementTimeout } = params;\n const parts: string[] = [];\n if (planCacheMode !== \"auto\") parts.push(`-c plan_cache_mode=${planCacheMode}`);\n if (statementTimeout !== undefined) {\n if (!Number.isInteger(statementTimeout) || statementTimeout < 0) {\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: `statementTimeout must be a non-negative integer of milliseconds, received ${String(statementTimeout)}`,\n meta: { statementTimeout },\n });\n }\n parts.push(`-c statement_timeout=${statementTimeout}`);\n }\n return parts.length === 0 ? undefined : parts.join(\" \");\n}\n\n/** Append the `options` startup parameter to a connection URL. */\nexport function applyStartupOptions(params: { url: string; startupOptions: string }): string {\n const { url, startupOptions } = params;\n const separator = url.includes(\"?\") ? \"&\" : \"?\";\n return `${url}${separator}options=${encodeURIComponent(startupOptions)}`;\n}\n",
7
+ "/**\n * Driver-error mapping for `bun:sql`.\n *\n * bun:sql raises `SQL.PostgresError` for server failures and for client-side\n * problems alike. Server failures carry the PostgreSQL SQLSTATE — depending on\n * the Bun version on `code` or on `errno` — while client-side failures use a\n * `ERR_POSTGRES_*` identifier in `code`. Both shapes are read here, so the\n * mapping is stable across Bun versions. Constitution rule 5: only `VibeError`\n * escapes the adapter.\n */\n\nimport { VibeError, type VibeErrorCode } from \"@vibeorm/schema\";\n\n// ─── SQLSTATE table ───────────────────────────────────────────────\n\n/**\n * PostgreSQL SQLSTATE → stable VibeError code. Frozen and mirrored by tests;\n * anything absent from this table (including every `ERR_POSTGRES_*` connection\n * or auth failure) maps to `VIBE_ADAPTER`.\n */\nexport const BUN_SQLSTATE_ERROR_CODES: Readonly<Record<string, VibeErrorCode>> = Object.freeze({\n /** unique_violation */\n \"23505\": \"VIBE_UNIQUE_VIOLATION\",\n /** foreign_key_violation */\n \"23503\": \"VIBE_FK_VIOLATION\",\n /** not_null_violation */\n \"23502\": \"VIBE_VALIDATION\",\n /** check_violation */\n \"23514\": \"VIBE_VALIDATION\",\n /**\n * invalid_text_representation — a value the server cannot parse into the\n * column's type (out-of-range enum value, `\"abc\"` for an integer). Bad input,\n * so the same code sqlite reports for its enum CHECK violation.\n */\n \"22P02\": \"VIBE_VALIDATION\",\n /** serialization_failure */\n \"40001\": \"VIBE_TRANSACTION\",\n /** deadlock_detected */\n \"40P01\": \"VIBE_TRANSACTION\",\n /** query_canceled — statement_timeout fired */\n \"57014\": \"VIBE_TRANSACTION\",\n});\n\n/** SQLSTATEs that additionally explain themselves through `meta.reason`. */\nexport const BUN_SQLSTATE_REASONS: Readonly<Record<string, string>> = Object.freeze({\n \"57014\": \"timeout\",\n});\n\n// ─── Reading driver errors ────────────────────────────────────────\n\n/** A PostgreSQL SQLSTATE is exactly five digits/uppercase letters (e.g. `40P01`). */\nconst SQLSTATE_PATTERN = /^[0-9A-Z]{5}$/;\n\nfunction readStringField(params: { source: Record<string, unknown>; key: string }): string | undefined {\n const value = params.source[params.key];\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n/**\n * Extract the SQLSTATE from a bun:sql error, accepting it on either `code` or\n * `errno`. `ERR_POSTGRES_*` identifiers are not SQLSTATEs and yield `undefined`.\n */\nexport function bunErrorSqlstate(params: { error: unknown }): string | undefined {\n const { error } = params;\n if (error === null || typeof error !== \"object\") return undefined;\n const source = error as Record<string, unknown>;\n for (const key of [\"code\", \"errno\"]) {\n const value = readStringField({ source, key });\n if (value !== undefined && SQLSTATE_PATTERN.test(value)) return value;\n }\n return undefined;\n}\n\n// ─── Mapping ──────────────────────────────────────────────────────\n\n/**\n * Map any error thrown by bun:sql onto a `VibeError`.\n *\n * A `VibeError` passes through unchanged, so an error raised deeper inside the\n * ORM (or by a nested adapter call) is never re-wrapped.\n */\nexport function mapBunDriverError(params: { error: unknown }): VibeError {\n const { error } = params;\n if (error instanceof VibeError) return error;\n\n const source: Record<string, unknown> =\n error !== null && typeof error === \"object\" ? (error as Record<string, unknown>) : {};\n const sqlstate = bunErrorSqlstate({ error });\n const code: VibeErrorCode =\n (sqlstate === undefined ? undefined : BUN_SQLSTATE_ERROR_CODES[sqlstate]) ?? \"VIBE_ADAPTER\";\n\n const meta: Record<string, unknown> = {};\n if (sqlstate !== undefined) meta.sqlstate = sqlstate;\n const constraint = readStringField({ source, key: \"constraint\" });\n if (constraint !== undefined) meta.constraint = constraint;\n const table = readStringField({ source, key: \"table\" });\n if (table !== undefined) meta.table = table;\n const column = readStringField({ source, key: \"column\" });\n if (column !== undefined) meta.column = column;\n const reason = sqlstate === undefined ? undefined : BUN_SQLSTATE_REASONS[sqlstate];\n if (reason !== undefined) meta.reason = reason;\n // Keep bun's own identifier when it is not a SQLSTATE — it is the only clue\n // a connection/auth/protocol failure carries.\n const driverCode = readStringField({ source, key: \"code\" });\n if (driverCode !== undefined && driverCode !== sqlstate) meta.driverCode = driverCode;\n\n const message =\n readStringField({ source, key: \"message\" }) ??\n (typeof error === \"string\" ? error : \"unknown bun:sql driver error\");\n\n return new VibeError({ code, message, meta, cause: error });\n}\n\n/** Run a driver call, mapping anything it throws onto a `VibeError`. */\nexport async function runMapped<T>(fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (error) {\n throw mapBunDriverError({ error });\n }\n}\n",
8
+ "/**\n * Parameter serialization for bun:sql.\n *\n * Verified against Bun 1.3 (re-verified on 1.3.14; `tests/live.test.ts` and\n * the notes below):\n * - a plain object or array bound to a `json`/`jsonb` column is JSON-encoded by\n * bun:sql itself — encoding it here as well double-encodes it and stores a\n * JSON *string* (`jsonb_typeof` = `\"string\"`), so the ORM path must not touch it;\n * - a plain JS array bound to a scalar-list column (`text[]`, `int[]`) is NOT\n * turned into a PostgreSQL array literal by bun:sql — it arrives as `a,b` and\n * the server rejects it with `malformed array literal`. Scalar lists therefore\n * have to be pre-rendered with {@link toPgArrayLiteral}, which is exactly what\n * the adapter's `formatArrayParam` returns.\n *\n * Hence the split (LEARNINGS.md): the ORM path passes values through (scalar\n * lists already arrived as literal strings), while the raw path renders plain JS\n * arrays as array literals so `db.$queryRawUnsafe('… = ANY($1)', [1, 2, 3])`\n * matches rows.\n */\n\nimport { VibeError } from \"@vibeorm/schema\";\n\n// ─── PostgreSQL array literals ────────────────────────────────────\n\n/** Quote and escape one array element: `\"` and `\\` are the special characters. */\nfunction quoteElement(value: string): string {\n return `\"${value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"')}\"`;\n}\n\n/**\n * Render a JS array as a PostgreSQL array literal string.\n *\n * Element handling:\n * - `null` / `undefined` → unquoted `NULL`\n * - `number` / `bigint` / `boolean` → unquoted primitive\n * - `Date` → quoted ISO-8601 (PostgreSQL cannot parse `String(date)`)\n * - `string` → quoted, with `\"` and `\\` escaped (so commas, braces and quotes\n * inside an element are safe)\n * - any other object, **including a nested array** → quoted `JSON.stringify`,\n * which is what a `Json[]` scalar list needs (PostgreSQL scalar lists are\n * one-dimensional, so a nested array is a JSON value, not a matrix)\n * - binary (`Uint8Array` / `ArrayBuffer`) → unsupported, throws\n *\n * @throws VibeError `VIBE_VALIDATION` for binary elements.\n */\nexport function toPgArrayLiteral(values: readonly unknown[]): string {\n const elements = values.map((value) => {\n if (value === null || value === undefined) return \"NULL\";\n if (typeof value === \"number\" || typeof value === \"bigint\" || typeof value === \"boolean\") {\n return String(value);\n }\n if (value instanceof Date) return quoteElement(value.toISOString());\n if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: \"binary values are not supported inside a postgres array literal\",\n meta: { driver: \"bun:sql\" },\n });\n }\n if (typeof value === \"string\") return quoteElement(value);\n if (typeof value === \"object\") return quoteElement(JSON.stringify(value));\n return quoteElement(String(value));\n });\n return `{${elements.join(\",\")}}`;\n}\n\n// ─── Serialization ────────────────────────────────────────────────\n\n/**\n * ORM hot path: pass every value straight to bun:sql.\n *\n * Scalar lists already arrived as array-literal strings from\n * `formatArrayParam`, and bun:sql JSON-encodes objects and arrays for\n * `json`/`jsonb` columns by itself — re-encoding here would store a\n * double-encoded JSON string.\n */\nexport function serializeParams(params: { values: readonly unknown[] }): unknown[] {\n return params.values.slice();\n}\n\n/**\n * Raw path: render plain JS arrays as PostgreSQL array literals so\n * `db.$queryRawUnsafe('… WHERE \"id\" = ANY($1)', [1, 2, 3])` matches rows;\n * everything else (objects included) goes to bun:sql untouched.\n *\n * Trade-off (v1 lesson): binding a JS array **into** a `jsonb` column from a raw\n * query means calling `JSON.stringify` yourself.\n */\nexport function serializeParamsRaw(params: { values: readonly unknown[] }): unknown[] {\n return params.values.map((value) => (Array.isArray(value) ? toPgArrayLiteral(value) : value));\n}\n",
9
+ "/**\n * Savepoint naming for nested transactions.\n *\n * v1 lesson (LEARNINGS.md): the counter is shared **by reference** across every\n * adapter created for one top-level transaction, so sibling and deeply nested\n * savepoints can never collide on a name.\n */\n\n/** Mutable counter shared by reference across one top-level transaction. */\nexport type SavepointCounter = { n: number };\n\n/** Fresh counter — one per top-level transaction. */\nexport function createSavepointCounter(): SavepointCounter {\n return { n: 0 };\n}\n\n/**\n * Allocate the next savepoint name (`vibeorm_sp_0`, `vibeorm_sp_1`, …) and\n * advance the shared counter. The name is an identifier by construction, so it\n * is safe to interpolate into `SAVEPOINT` / `RELEASE` / `ROLLBACK TO` SQL.\n */\nexport function nextSavepointName(params: { counter: SavepointCounter }): string {\n return `vibeorm_sp_${params.counter.n++}`;\n}\n",
10
+ "/**\n * Synthetic tagged-template cache — bun:sql's prepared-statement path.\n *\n * bun:sql only creates a *named* prepared statement (one PostgreSQL keeps a\n * plan for) when the query arrives as a tagged template. Generated SQL is a\n * plain string, so with `preparedStatements: true` the adapter turns each SQL\n * text into a synthetic `TemplateStringsArray` — split on the `$1`, `$2`, …\n * placeholders — and reuses that same object identity for every execution.\n *\n * The cache is LRU-bounded so a long-running process never grows it without\n * limit.\n */\n\n/** LRU map from SQL text to the synthetic template used to execute it. */\nexport type TemplateCache = {\n /** Template for `text`, creating one on first sight. Stable by identity. */\n getTemplate(params: { text: string }): TemplateStringsArray;\n /** Number of texts currently held — diagnostics and tests. */\n size(): number;\n};\n\n/** Split a SQL text on `$n` placeholders into the literal chunks around them. */\nexport function templateChunks(params: { text: string }): string[] {\n return params.text.split(/\\$\\d+/);\n}\n\n/**\n * Create a template cache holding at most `max` texts (least recently used\n * evicted first).\n */\nexport function createTemplateCache(params: { max: number }): TemplateCache {\n const { max } = params;\n const templates = new Map<string, TemplateStringsArray>();\n\n return {\n getTemplate(templateParams: { text: string }): TemplateStringsArray {\n const { text } = templateParams;\n const existing = templates.get(text);\n if (existing !== undefined) {\n // Re-insert to mark most-recently-used.\n templates.delete(text);\n templates.set(text, existing);\n return existing;\n }\n const chunks = templateChunks({ text });\n const template = Object.assign(chunks, { raw: chunks }) as unknown as TemplateStringsArray;\n if (templates.size >= max) {\n const oldest = templates.keys().next().value;\n if (oldest !== undefined) templates.delete(oldest);\n }\n templates.set(text, template);\n return template;\n },\n\n size(): number {\n return templates.size;\n },\n };\n}\n",
11
+ "/**\n * Transaction-control SQL text (postgres dialect, bun:sql flavour).\n *\n * Used by the reserved-connection path, which issues `BEGIN` itself so that\n * isolation level and statement timeout can be applied — bun:sql's\n * `sql.begin(fn)` fast path takes no such options.\n */\n\nimport type { IsolationLevel, TransactionOptions } from \"@vibeorm/runtime\";\nimport { VibeError } from \"@vibeorm/schema\";\n\n/** VibeORM isolation level → PostgreSQL isolation level keyword. */\nexport const ISOLATION_LEVEL_SQL: Readonly<Record<IsolationLevel, string>> = Object.freeze({\n ReadCommitted: \"READ COMMITTED\",\n RepeatableRead: \"REPEATABLE READ\",\n Serializable: \"SERIALIZABLE\",\n});\n\n/** `BEGIN`, with an `ISOLATION LEVEL` clause when the caller asked for one. */\nexport function beginStatement(params: { isolationLevel?: IsolationLevel }): string {\n const { isolationLevel } = params;\n return isolationLevel === undefined\n ? \"BEGIN\"\n : `BEGIN ISOLATION LEVEL ${ISOLATION_LEVEL_SQL[isolationLevel]}`;\n}\n\n/**\n * `SET LOCAL statement_timeout = <ms>` — transaction-scoped, so PostgreSQL\n * cancels an over-long statement with SQLSTATE 57014 (mapped to\n * `VIBE_TRANSACTION` with `meta.reason = \"timeout\"`).\n *\n * @throws VibeError `VIBE_VALIDATION` when the timeout is not a non-negative\n * integer — the value is interpolated into SQL, so it is never trusted blindly.\n */\nexport function statementTimeoutStatement(params: { timeout: number }): string {\n const { timeout } = params;\n if (!Number.isInteger(timeout) || timeout < 0) {\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: `transaction timeout must be a non-negative integer of milliseconds, received ${String(timeout)}`,\n meta: { timeout },\n });\n }\n return `SET LOCAL statement_timeout = ${timeout}`;\n}\n\n// ─── Raw transaction control (session affinity) ─────────────────\n\n/** How a raw statement steers the adapter's session-affine raw transaction. */\nexport type RawTransactionControl = \"open\" | \"close\" | null;\n\nconst RAW_OPEN_PATTERN: RegExp = /^\\s*(?:begin|start\\s+transaction)\\b[^;]*;?\\s*$/i;\nconst RAW_CLOSE_PATTERN: RegExp = /^\\s*(?:commit|end|rollback|abort)(?:\\s+(?:transaction|work))?\\s*;?\\s*$/i;\n\n/**\n * Classify a raw statement as transaction control.\n *\n * @vibeorm/migrate drives transactional DDL by sending literal `BEGIN` /\n * `COMMIT` / `ROLLBACK` through its stateless `SqlExecutor` — sound only when\n * every statement in between rides the SAME connection. The adapter uses this\n * to pin one connection for the whole raw transaction (\"open\" reserves,\n * \"close\" releases). On a pooled bun:sql instance an unpinned `BEGIN` is\n * refused outright by the driver (`ERR_POSTGRES_UNSAFE_TRANSACTION`); other\n * pool drivers would only work by checkout luck.\n *\n * Deliberately strict: only PURE single-statement control text matches\n * (`BEGIN`, `BEGIN ISOLATION LEVEL …;`, `COMMIT`, `ROLLBACK WORK`, `END`,\n * `ABORT`). Compound scripts, `ROLLBACK TO SAVEPOINT`, two-phase\n * `COMMIT PREPARED` and `COMMIT AND CHAIN` (which keeps a transaction open)\n * never engage the pinning.\n */\nexport function classifyRawTransactionControl(params: { text: string }): RawTransactionControl {\n if (RAW_OPEN_PATTERN.test(params.text)) return \"open\";\n if (RAW_CLOSE_PATTERN.test(params.text)) return \"close\";\n return null;\n}\n\n// ─── Nested transaction options (the cross-adapter contract) ──────\n\n/**\n * Refuse options on a NESTED `transaction()` call — the same contract on every\n * adapter (see the `DatabaseAdapter.transaction` JSDoc in @vibeorm/runtime):\n * a nested transaction is a SAVEPOINT, and a savepoint can neither change the\n * isolation level of the transaction it joins nor enforce its own timeout.\n * Silently dropping the option (the pre-#3 behaviour) hid exactly that.\n *\n * @throws VibeError `VIBE_VALIDATION` when `isolationLevel` or `timeout` is\n * present (an `undefined` or empty options object is accepted).\n */\nexport function refuseNestedTransactionOptions(params: {\n options?: TransactionOptions;\n provider: string;\n}): void {\n const { options, provider } = params;\n if (options === undefined) return;\n\n const passed: string[] = [];\n if (options.isolationLevel !== undefined) passed.push(\"isolationLevel\");\n if (options.timeout !== undefined) passed.push(\"timeout\");\n if (passed.length === 0) return;\n\n const meta: Record<string, unknown> = { provider, nested: true };\n if (options.isolationLevel !== undefined) meta.isolationLevel = options.isolationLevel;\n if (options.timeout !== undefined) meta.timeout = options.timeout;\n\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: `nested transactions run as savepoints and cannot honor ${passed.join(\" or \")} — a savepoint cannot change the isolation level of the transaction it joins, and a nested timeout is not enforceable; pass options on the top-level $transaction`,\n meta,\n });\n}\n"
12
+ ],
13
+ "mappings": "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",
14
+ "debugId": "08E3C28E82D7C93A64756E2164756E21",
15
+ "names": []
16
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Parameter serialization for bun:sql.
3
+ *
4
+ * Verified against Bun 1.3 (re-verified on 1.3.14; `tests/live.test.ts` and
5
+ * the notes below):
6
+ * - a plain object or array bound to a `json`/`jsonb` column is JSON-encoded by
7
+ * bun:sql itself — encoding it here as well double-encodes it and stores a
8
+ * JSON *string* (`jsonb_typeof` = `"string"`), so the ORM path must not touch it;
9
+ * - a plain JS array bound to a scalar-list column (`text[]`, `int[]`) is NOT
10
+ * turned into a PostgreSQL array literal by bun:sql — it arrives as `a,b` and
11
+ * the server rejects it with `malformed array literal`. Scalar lists therefore
12
+ * have to be pre-rendered with {@link toPgArrayLiteral}, which is exactly what
13
+ * the adapter's `formatArrayParam` returns.
14
+ *
15
+ * Hence the split (LEARNINGS.md): the ORM path passes values through (scalar
16
+ * lists already arrived as literal strings), while the raw path renders plain JS
17
+ * arrays as array literals so `db.$queryRawUnsafe('… = ANY($1)', [1, 2, 3])`
18
+ * matches rows.
19
+ */
20
+ /**
21
+ * Render a JS array as a PostgreSQL array literal string.
22
+ *
23
+ * Element handling:
24
+ * - `null` / `undefined` → unquoted `NULL`
25
+ * - `number` / `bigint` / `boolean` → unquoted primitive
26
+ * - `Date` → quoted ISO-8601 (PostgreSQL cannot parse `String(date)`)
27
+ * - `string` → quoted, with `"` and `\` escaped (so commas, braces and quotes
28
+ * inside an element are safe)
29
+ * - any other object, **including a nested array** → quoted `JSON.stringify`,
30
+ * which is what a `Json[]` scalar list needs (PostgreSQL scalar lists are
31
+ * one-dimensional, so a nested array is a JSON value, not a matrix)
32
+ * - binary (`Uint8Array` / `ArrayBuffer`) → unsupported, throws
33
+ *
34
+ * @throws VibeError `VIBE_VALIDATION` for binary elements.
35
+ */
36
+ export declare function toPgArrayLiteral(values: readonly unknown[]): string;
37
+ /**
38
+ * ORM hot path: pass every value straight to bun:sql.
39
+ *
40
+ * Scalar lists already arrived as array-literal strings from
41
+ * `formatArrayParam`, and bun:sql JSON-encodes objects and arrays for
42
+ * `json`/`jsonb` columns by itself — re-encoding here would store a
43
+ * double-encoded JSON string.
44
+ */
45
+ export declare function serializeParams(params: {
46
+ values: readonly unknown[];
47
+ }): unknown[];
48
+ /**
49
+ * Raw path: render plain JS arrays as PostgreSQL array literals so
50
+ * `db.$queryRawUnsafe('… WHERE "id" = ANY($1)', [1, 2, 3])` matches rows;
51
+ * everything else (objects included) goes to bun:sql untouched.
52
+ *
53
+ * Trade-off (v1 lesson): binding a JS array **into** a `jsonb` column from a raw
54
+ * query means calling `JSON.stringify` yourself.
55
+ */
56
+ export declare function serializeParamsRaw(params: {
57
+ values: readonly unknown[];
58
+ }): unknown[];
59
+ //# sourceMappingURL=params.d.ts.map