@tursodatabase/serverless 1.1.1 → 1.1.2-pre.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.
@@ -1 +1,882 @@
1
- export * from '../compat.js';
1
+ // src/error.ts
2
+ var DatabaseError = class _DatabaseError extends Error {
3
+ constructor(message, code, rawCode, cause) {
4
+ super(message);
5
+ this.name = "DatabaseError";
6
+ this.code = code;
7
+ this.rawCode = rawCode;
8
+ this.cause = cause;
9
+ Object.setPrototypeOf(this, _DatabaseError.prototype);
10
+ }
11
+ };
12
+ var TimeoutError = class _TimeoutError extends DatabaseError {
13
+ constructor(message = "Query timed out", cause) {
14
+ super(message, "TIMEOUT", void 0, cause);
15
+ this.name = "TimeoutError";
16
+ Object.setPrototypeOf(this, _TimeoutError.prototype);
17
+ }
18
+ };
19
+
20
+ // src/protocol.ts
21
+ function encodeValue(value) {
22
+ if (value === null || value === void 0) {
23
+ return { type: "null" };
24
+ }
25
+ if (typeof value === "number") {
26
+ if (!Number.isFinite(value)) {
27
+ throw new Error("Only finite numbers (not Infinity or NaN) can be passed as arguments");
28
+ }
29
+ if (Number.isInteger(value)) {
30
+ return { type: "integer", value: value.toString() };
31
+ }
32
+ return { type: "float", value };
33
+ }
34
+ if (typeof value === "bigint") {
35
+ return { type: "integer", value: value.toString() };
36
+ }
37
+ if (typeof value === "boolean") {
38
+ return { type: "integer", value: value ? "1" : "0" };
39
+ }
40
+ if (typeof value === "string") {
41
+ return { type: "text", value };
42
+ }
43
+ if (value instanceof ArrayBuffer || value instanceof Uint8Array) {
44
+ const base64 = btoa(String.fromCharCode(...new Uint8Array(value)));
45
+ return { type: "blob", base64 };
46
+ }
47
+ return { type: "text", value: String(value) };
48
+ }
49
+ function decodeValue(value, safeIntegers = false) {
50
+ switch (value.type) {
51
+ case "null":
52
+ return null;
53
+ case "integer":
54
+ if (safeIntegers) {
55
+ return BigInt(value.value);
56
+ }
57
+ return parseInt(value.value, 10);
58
+ case "float":
59
+ return value.value;
60
+ case "text":
61
+ return value.value;
62
+ case "blob":
63
+ if (value.base64 !== void 0 && value.base64 !== null) {
64
+ let b64 = value.base64;
65
+ while (b64.length % 4 !== 0) {
66
+ b64 += "=";
67
+ }
68
+ const binaryString = atob(b64);
69
+ const bytes = new Uint8Array(binaryString.length);
70
+ for (let i = 0; i < binaryString.length; i++) {
71
+ bytes[i] = binaryString.charCodeAt(i);
72
+ }
73
+ return Buffer.from(bytes);
74
+ }
75
+ return Buffer.alloc(0);
76
+ default:
77
+ return null;
78
+ }
79
+ }
80
+ var ENCRYPTION_KEY_HEADER = "x-turso-encryption-key";
81
+ function wrapAbortError(error) {
82
+ if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) {
83
+ throw new TimeoutError("Query timed out");
84
+ }
85
+ throw error;
86
+ }
87
+ async function executeCursor(url, authToken, request, remoteEncryptionKey, signal) {
88
+ const headers = {
89
+ "Content-Type": "application/json"
90
+ };
91
+ if (authToken) {
92
+ headers["Authorization"] = `Bearer ${authToken}`;
93
+ }
94
+ if (remoteEncryptionKey) {
95
+ headers[ENCRYPTION_KEY_HEADER] = remoteEncryptionKey;
96
+ }
97
+ let response;
98
+ try {
99
+ response = await fetch(`${url}/v3/cursor`, {
100
+ method: "POST",
101
+ headers,
102
+ body: JSON.stringify(request),
103
+ signal
104
+ });
105
+ } catch (error) {
106
+ wrapAbortError(error);
107
+ }
108
+ if (!response.ok) {
109
+ let errorMessage = `HTTP error! status: ${response.status}`;
110
+ try {
111
+ const errorBody = await response.text();
112
+ const errorData = JSON.parse(errorBody);
113
+ if (errorData.message) {
114
+ errorMessage = errorData.message;
115
+ }
116
+ } catch {
117
+ }
118
+ throw new DatabaseError(errorMessage);
119
+ }
120
+ const reader = response.body?.getReader();
121
+ if (!reader) {
122
+ throw new DatabaseError("No response body");
123
+ }
124
+ const decoder = new TextDecoder();
125
+ let buffer = "";
126
+ let cursorResponse;
127
+ try {
128
+ while (!cursorResponse) {
129
+ const { done, value } = await reader.read();
130
+ if (done) break;
131
+ buffer += decoder.decode(value, { stream: true });
132
+ const newlineIndex = buffer.indexOf("\n");
133
+ if (newlineIndex !== -1) {
134
+ const line = buffer.slice(0, newlineIndex).trim();
135
+ buffer = buffer.slice(newlineIndex + 1);
136
+ if (line) {
137
+ cursorResponse = JSON.parse(line);
138
+ break;
139
+ }
140
+ }
141
+ }
142
+ } catch (error) {
143
+ reader.releaseLock();
144
+ wrapAbortError(error);
145
+ }
146
+ if (!cursorResponse) {
147
+ reader.releaseLock();
148
+ throw new DatabaseError("No cursor response received");
149
+ }
150
+ async function* parseEntries() {
151
+ try {
152
+ let newlineIndex;
153
+ while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
154
+ const line = buffer.slice(0, newlineIndex).trim();
155
+ buffer = buffer.slice(newlineIndex + 1);
156
+ if (line) {
157
+ yield JSON.parse(line);
158
+ }
159
+ }
160
+ while (true) {
161
+ let readResult;
162
+ try {
163
+ readResult = await reader.read();
164
+ } catch (error) {
165
+ wrapAbortError(error);
166
+ }
167
+ if (readResult.done) break;
168
+ buffer += decoder.decode(readResult.value, { stream: true });
169
+ while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
170
+ const line = buffer.slice(0, newlineIndex).trim();
171
+ buffer = buffer.slice(newlineIndex + 1);
172
+ if (line) {
173
+ yield JSON.parse(line);
174
+ }
175
+ }
176
+ }
177
+ if (buffer.trim()) {
178
+ yield JSON.parse(buffer.trim());
179
+ }
180
+ } finally {
181
+ reader.releaseLock();
182
+ }
183
+ }
184
+ return { response: cursorResponse, entries: parseEntries() };
185
+ }
186
+ async function executePipeline(url, authToken, request, remoteEncryptionKey, signal) {
187
+ const headers = {
188
+ "Content-Type": "application/json"
189
+ };
190
+ if (authToken) {
191
+ headers["Authorization"] = `Bearer ${authToken}`;
192
+ }
193
+ if (remoteEncryptionKey) {
194
+ headers[ENCRYPTION_KEY_HEADER] = remoteEncryptionKey;
195
+ }
196
+ let response;
197
+ try {
198
+ response = await fetch(`${url}/v3/pipeline`, {
199
+ method: "POST",
200
+ headers,
201
+ body: JSON.stringify(request),
202
+ signal
203
+ });
204
+ } catch (error) {
205
+ wrapAbortError(error);
206
+ }
207
+ if (!response.ok) {
208
+ throw new DatabaseError(`HTTP error! status: ${response.status}`);
209
+ }
210
+ return response.json();
211
+ }
212
+
213
+ // src/session.ts
214
+ function normalizeUrl(url) {
215
+ return url.replace(/^libsql:\/\//, "https://");
216
+ }
217
+ function isValidIdentifier(str) {
218
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(str);
219
+ }
220
+ var Session = class {
221
+ constructor(config) {
222
+ this.baton = null;
223
+ this.config = config;
224
+ this.baseUrl = normalizeUrl(config.url);
225
+ }
226
+ createAbortSignal(queryOptions) {
227
+ const timeout = queryOptions?.queryTimeout ?? this.config.defaultQueryTimeout;
228
+ if (timeout != null && timeout > 0) {
229
+ return AbortSignal.timeout(timeout);
230
+ }
231
+ return void 0;
232
+ }
233
+ /**
234
+ * Describe a SQL statement to get its column metadata.
235
+ *
236
+ * @param sql - The SQL statement to describe
237
+ * @returns Promise resolving to the statement description
238
+ */
239
+ async describe(sql, queryOptions) {
240
+ const request = {
241
+ baton: this.baton,
242
+ requests: [{
243
+ type: "describe",
244
+ sql
245
+ }]
246
+ };
247
+ let response;
248
+ try {
249
+ response = await executePipeline(this.baseUrl, this.config.authToken, request, this.config.remoteEncryptionKey, this.createAbortSignal(queryOptions));
250
+ } catch (e) {
251
+ this.baton = null;
252
+ throw e;
253
+ }
254
+ this.baton = response.baton;
255
+ if (response.base_url) {
256
+ this.baseUrl = response.base_url;
257
+ }
258
+ if (response.results && response.results[0]) {
259
+ const result = response.results[0];
260
+ if (result.type === "error") {
261
+ throw new DatabaseError(result.error?.message || "Describe execution failed", result.error?.code);
262
+ }
263
+ if (result.response?.type === "describe" && result.response.result) {
264
+ return result.response.result;
265
+ }
266
+ }
267
+ throw new DatabaseError("Unexpected describe response");
268
+ }
269
+ /**
270
+ * Execute a SQL statement and return all results.
271
+ *
272
+ * @param sql - The SQL statement to execute
273
+ * @param args - Optional array of parameter values or object with named parameters
274
+ * @param safeIntegers - Whether to return integers as BigInt
275
+ * @returns Promise resolving to the complete result set
276
+ */
277
+ async execute(sql, args = [], safeIntegers = false, queryOptions) {
278
+ const { response, entries } = await this.executeRaw(sql, args, queryOptions);
279
+ const result = await this.processCursorEntries(entries, safeIntegers);
280
+ return result;
281
+ }
282
+ /**
283
+ * Execute a SQL statement and return the raw response and entries.
284
+ *
285
+ * @param sql - The SQL statement to execute
286
+ * @param args - Optional array of parameter values or object with named parameters
287
+ * @returns Promise resolving to the raw response and cursor entries
288
+ */
289
+ async executeRaw(sql, args = [], queryOptions) {
290
+ let positionalArgs = [];
291
+ let namedArgs = [];
292
+ if (Array.isArray(args)) {
293
+ positionalArgs = args.map(encodeValue);
294
+ } else {
295
+ const keys = Object.keys(args);
296
+ const isNumericKeys = keys.length > 0 && keys.every((key) => /^\d+$/.test(key));
297
+ if (isNumericKeys) {
298
+ const sortedKeys = keys.sort((a, b) => parseInt(a) - parseInt(b));
299
+ const maxIndex = parseInt(sortedKeys[sortedKeys.length - 1]);
300
+ positionalArgs = new Array(maxIndex);
301
+ for (const key of sortedKeys) {
302
+ const index = parseInt(key) - 1;
303
+ positionalArgs[index] = encodeValue(args[key]);
304
+ }
305
+ for (let i = 0; i < positionalArgs.length; i++) {
306
+ if (positionalArgs[i] === void 0) {
307
+ positionalArgs[i] = { type: "null" };
308
+ }
309
+ }
310
+ } else {
311
+ namedArgs = Object.entries(args).map(([name, value]) => ({
312
+ name,
313
+ value: encodeValue(value)
314
+ }));
315
+ }
316
+ }
317
+ const request = {
318
+ baton: this.baton,
319
+ batch: {
320
+ steps: [{
321
+ stmt: {
322
+ sql,
323
+ args: positionalArgs,
324
+ named_args: namedArgs,
325
+ want_rows: true
326
+ }
327
+ }]
328
+ }
329
+ };
330
+ let result;
331
+ try {
332
+ result = await executeCursor(this.baseUrl, this.config.authToken, request, this.config.remoteEncryptionKey, this.createAbortSignal(queryOptions));
333
+ } catch (e) {
334
+ this.baton = null;
335
+ throw e;
336
+ }
337
+ const { response, entries } = result;
338
+ this.baton = response.baton;
339
+ if (response.base_url) {
340
+ this.baseUrl = response.base_url;
341
+ }
342
+ return { response, entries };
343
+ }
344
+ /**
345
+ * Process cursor entries into a structured result.
346
+ *
347
+ * @param entries - Async generator of cursor entries
348
+ * @returns Promise resolving to the processed result
349
+ */
350
+ async processCursorEntries(entries, safeIntegers = false) {
351
+ let columns = [];
352
+ let columnTypes = [];
353
+ let rows = [];
354
+ let rowsAffected = 0;
355
+ let lastInsertRowid;
356
+ for await (const entry of entries) {
357
+ switch (entry.type) {
358
+ case "step_begin":
359
+ if (entry.cols) {
360
+ columns = entry.cols.map((col) => col.name);
361
+ columnTypes = entry.cols.map((col) => col.decltype || "");
362
+ }
363
+ break;
364
+ case "row":
365
+ if (entry.row) {
366
+ const decodedRow = entry.row.map((value) => decodeValue(value, safeIntegers));
367
+ const rowObject = this.createRowObject(decodedRow, columns);
368
+ rows.push(rowObject);
369
+ }
370
+ break;
371
+ case "step_end":
372
+ if (entry.affected_row_count !== void 0) {
373
+ rowsAffected = entry.affected_row_count;
374
+ }
375
+ if (entry.last_insert_rowid !== void 0 && entry.last_insert_rowid !== null) {
376
+ lastInsertRowid = typeof entry.last_insert_rowid === "number" ? entry.last_insert_rowid : parseInt(entry.last_insert_rowid, 10);
377
+ }
378
+ break;
379
+ case "step_error":
380
+ case "error":
381
+ throw new DatabaseError(entry.error?.message || "SQL execution failed", entry.error?.code);
382
+ }
383
+ }
384
+ return {
385
+ columns,
386
+ columnTypes,
387
+ rows,
388
+ rowsAffected,
389
+ lastInsertRowid
390
+ };
391
+ }
392
+ /**
393
+ * Create a row object with both array and named property access.
394
+ *
395
+ * @param values - Array of column values
396
+ * @param columns - Array of column names
397
+ * @returns Row object with dual access patterns
398
+ */
399
+ createRowObject(values, columns) {
400
+ const row = [...values];
401
+ columns.forEach((column, index) => {
402
+ if (column && isValidIdentifier(column)) {
403
+ Object.defineProperty(row, column, {
404
+ value: values[index],
405
+ enumerable: false,
406
+ writable: false,
407
+ configurable: true
408
+ });
409
+ }
410
+ });
411
+ return row;
412
+ }
413
+ /**
414
+ * Execute multiple SQL statements in a batch.
415
+ *
416
+ * @param statements - Array of SQL statements to execute
417
+ * @returns Promise resolving to batch execution results
418
+ */
419
+ async batch(statements, queryOptions) {
420
+ const request = {
421
+ baton: this.baton,
422
+ batch: {
423
+ steps: statements.map((sql) => ({
424
+ stmt: {
425
+ sql,
426
+ args: [],
427
+ named_args: [],
428
+ want_rows: false
429
+ }
430
+ }))
431
+ }
432
+ };
433
+ let batchResult;
434
+ try {
435
+ batchResult = await executeCursor(this.baseUrl, this.config.authToken, request, this.config.remoteEncryptionKey, this.createAbortSignal(queryOptions));
436
+ } catch (e) {
437
+ this.baton = null;
438
+ throw e;
439
+ }
440
+ const { response, entries } = batchResult;
441
+ this.baton = response.baton;
442
+ if (response.base_url) {
443
+ this.baseUrl = response.base_url;
444
+ }
445
+ let totalRowsAffected = 0;
446
+ let lastInsertRowid;
447
+ for await (const entry of entries) {
448
+ switch (entry.type) {
449
+ case "step_end":
450
+ if (entry.affected_row_count !== void 0) {
451
+ totalRowsAffected += entry.affected_row_count;
452
+ }
453
+ if (entry.last_insert_rowid !== void 0 && entry.last_insert_rowid !== null) {
454
+ lastInsertRowid = typeof entry.last_insert_rowid === "number" ? entry.last_insert_rowid : parseInt(entry.last_insert_rowid, 10);
455
+ }
456
+ break;
457
+ case "step_error":
458
+ case "error":
459
+ throw new DatabaseError(entry.error?.message || "Batch execution failed", entry.error?.code);
460
+ }
461
+ }
462
+ return {
463
+ rowsAffected: totalRowsAffected,
464
+ lastInsertRowid
465
+ };
466
+ }
467
+ /**
468
+ * Execute a sequence of SQL statements separated by semicolons.
469
+ *
470
+ * @param sql - SQL string containing multiple statements separated by semicolons
471
+ * @returns Promise resolving when all statements are executed
472
+ */
473
+ async sequence(sql, queryOptions) {
474
+ const request = {
475
+ baton: this.baton,
476
+ requests: [{
477
+ type: "sequence",
478
+ sql
479
+ }]
480
+ };
481
+ let seqResponse;
482
+ try {
483
+ seqResponse = await executePipeline(this.baseUrl, this.config.authToken, request, this.config.remoteEncryptionKey, this.createAbortSignal(queryOptions));
484
+ } catch (e) {
485
+ this.baton = null;
486
+ throw e;
487
+ }
488
+ this.baton = seqResponse.baton;
489
+ if (seqResponse.base_url) {
490
+ this.baseUrl = seqResponse.base_url;
491
+ }
492
+ if (seqResponse.results && seqResponse.results[0]) {
493
+ const result = seqResponse.results[0];
494
+ if (result.type === "error") {
495
+ throw new DatabaseError(result.error?.message || "Sequence execution failed", result.error?.code);
496
+ }
497
+ }
498
+ }
499
+ /**
500
+ * Close the session.
501
+ *
502
+ * This sends a close request to the server to properly clean up the stream
503
+ * before resetting the local state.
504
+ */
505
+ async close() {
506
+ if (this.baton) {
507
+ try {
508
+ const request = {
509
+ baton: this.baton,
510
+ requests: [{
511
+ type: "close"
512
+ }]
513
+ };
514
+ await executePipeline(this.baseUrl, this.config.authToken, request, this.config.remoteEncryptionKey);
515
+ } catch {
516
+ }
517
+ }
518
+ this.baton = null;
519
+ this.baseUrl = "";
520
+ }
521
+ };
522
+
523
+ // src/async-lock.ts
524
+ var AsyncLock = class {
525
+ constructor() {
526
+ this.locked = false;
527
+ this.queue = [];
528
+ }
529
+ async acquire() {
530
+ if (!this.locked) {
531
+ this.locked = true;
532
+ return;
533
+ }
534
+ return new Promise((resolve) => {
535
+ this.queue.push(resolve);
536
+ });
537
+ }
538
+ release() {
539
+ const next = this.queue.shift();
540
+ if (next) {
541
+ next();
542
+ } else {
543
+ this.locked = false;
544
+ }
545
+ }
546
+ };
547
+
548
+ // src/compat.ts
549
+ var LibsqlError = class extends Error {
550
+ constructor(message, code, extendedCode, rawCode, cause) {
551
+ super(message);
552
+ this.name = "LibsqlError";
553
+ this.code = code;
554
+ this.extendedCode = extendedCode;
555
+ this.rawCode = rawCode;
556
+ this.cause = cause;
557
+ }
558
+ };
559
+ var LibSQLClient = class {
560
+ constructor(config) {
561
+ this.execLock = new AsyncLock();
562
+ this._closed = false;
563
+ this._defaultSafeIntegers = false;
564
+ this.validateConfig(config);
565
+ const sessionConfig = {
566
+ url: config.url,
567
+ authToken: config.authToken || "",
568
+ remoteEncryptionKey: config.remoteEncryptionKey
569
+ };
570
+ this.sessionConfig = sessionConfig;
571
+ this.session = new Session(sessionConfig);
572
+ }
573
+ validateConfig(config) {
574
+ const unsupportedOptions = [];
575
+ if (config.encryptionKey !== void 0) {
576
+ unsupportedOptions.push({ key: "encryptionKey", value: config.encryptionKey });
577
+ }
578
+ if (config.syncUrl !== void 0) {
579
+ unsupportedOptions.push({ key: "syncUrl", value: config.syncUrl });
580
+ }
581
+ if (config.syncInterval !== void 0) {
582
+ unsupportedOptions.push({ key: "syncInterval", value: config.syncInterval });
583
+ }
584
+ if (config.readYourWrites !== void 0) {
585
+ unsupportedOptions.push({ key: "readYourWrites", value: config.readYourWrites });
586
+ }
587
+ if (config.offline !== void 0) {
588
+ unsupportedOptions.push({ key: "offline", value: config.offline });
589
+ }
590
+ if (config.tls !== void 0) {
591
+ unsupportedOptions.push({ key: "tls", value: config.tls });
592
+ }
593
+ if (config.intMode !== void 0) {
594
+ unsupportedOptions.push({ key: "intMode", value: config.intMode });
595
+ }
596
+ if (config.fetch !== void 0) {
597
+ unsupportedOptions.push({ key: "fetch", value: config.fetch });
598
+ }
599
+ if (config.concurrency !== void 0) {
600
+ unsupportedOptions.push({ key: "concurrency", value: config.concurrency });
601
+ }
602
+ if (unsupportedOptions.length > 0) {
603
+ const optionsList = unsupportedOptions.map((opt) => `'${opt.key}'`).join(", ");
604
+ throw new LibsqlError(
605
+ `Unsupported configuration options: ${optionsList}. Only 'url', 'authToken', and 'remoteEncryptionKey' are supported in the serverless compatibility layer.`,
606
+ "UNSUPPORTED_CONFIG"
607
+ );
608
+ }
609
+ if (!config.url) {
610
+ throw new LibsqlError("Missing required 'url' configuration option", "MISSING_URL");
611
+ }
612
+ }
613
+ get closed() {
614
+ return this._closed;
615
+ }
616
+ get protocol() {
617
+ return "http";
618
+ }
619
+ normalizeStatement(stmt) {
620
+ if (typeof stmt === "string") {
621
+ return { sql: stmt, args: [] };
622
+ }
623
+ const args = stmt.args || [];
624
+ if (Array.isArray(args)) {
625
+ return { sql: stmt.sql, args };
626
+ }
627
+ return { sql: stmt.sql, args: Object.values(args) };
628
+ }
629
+ convertResult(result) {
630
+ const resultSet = {
631
+ columns: result.columns || [],
632
+ columnTypes: result.columnTypes || [],
633
+ rows: result.rows || [],
634
+ rowsAffected: result.rowsAffected || 0,
635
+ lastInsertRowid: result.lastInsertRowid ? BigInt(result.lastInsertRowid) : void 0,
636
+ toJSON() {
637
+ return {
638
+ columns: this.columns,
639
+ columnTypes: this.columnTypes,
640
+ rows: this.rows,
641
+ rowsAffected: this.rowsAffected,
642
+ lastInsertRowid: this.lastInsertRowid?.toString()
643
+ };
644
+ }
645
+ };
646
+ return resultSet;
647
+ }
648
+ async execute(stmtOrSql, args) {
649
+ await this.execLock.acquire();
650
+ try {
651
+ if (this._closed) {
652
+ throw new LibsqlError("Client is closed", "CLIENT_CLOSED");
653
+ }
654
+ let normalizedStmt;
655
+ if (typeof stmtOrSql === "string") {
656
+ const normalizedArgs = args ? Array.isArray(args) ? args : Object.values(args) : [];
657
+ normalizedStmt = { sql: stmtOrSql, args: normalizedArgs };
658
+ } else {
659
+ normalizedStmt = this.normalizeStatement(stmtOrSql);
660
+ }
661
+ const result = await this.session.execute(normalizedStmt.sql, normalizedStmt.args, this._defaultSafeIntegers);
662
+ return this.convertResult(result);
663
+ } catch (error) {
664
+ if (error instanceof LibsqlError) {
665
+ throw error;
666
+ }
667
+ throw mapDatabaseError(error, "EXECUTE_ERROR");
668
+ } finally {
669
+ this.execLock.release();
670
+ }
671
+ }
672
+ async batch(stmts, mode) {
673
+ await this.execLock.acquire();
674
+ try {
675
+ if (this._closed) {
676
+ throw new LibsqlError("Client is closed", "CLIENT_CLOSED");
677
+ }
678
+ const sqlStatements = stmts.map((stmt) => {
679
+ const normalized = this.normalizeStatement(stmt);
680
+ return normalized.sql;
681
+ });
682
+ const result = await this.session.batch(sqlStatements);
683
+ return [this.convertResult(result)];
684
+ } catch (error) {
685
+ if (error instanceof LibsqlError) {
686
+ throw error;
687
+ }
688
+ throw mapDatabaseError(error, "BATCH_ERROR");
689
+ } finally {
690
+ this.execLock.release();
691
+ }
692
+ }
693
+ async migrate(stmts) {
694
+ return this.batch(stmts, "write");
695
+ }
696
+ modeToBeginSql(mode) {
697
+ switch (mode) {
698
+ case "write":
699
+ return "BEGIN IMMEDIATE";
700
+ case "deferred":
701
+ return "BEGIN DEFERRED";
702
+ case "read":
703
+ default:
704
+ return "BEGIN";
705
+ }
706
+ }
707
+ async transaction(mode) {
708
+ await this.execLock.acquire();
709
+ if (this._closed) {
710
+ this.execLock.release();
711
+ throw new LibsqlError("Client is closed", "CLIENT_CLOSED");
712
+ }
713
+ const txSession = new Session(this.sessionConfig);
714
+ let txClosed = false;
715
+ let cleanupStarted = false;
716
+ const ensureOpen = () => {
717
+ if (txClosed) {
718
+ throw new LibsqlError("Transaction is closed", "TRANSACTION_CLOSED");
719
+ }
720
+ };
721
+ const closeTx = async () => {
722
+ if (cleanupStarted) return;
723
+ cleanupStarted = true;
724
+ txClosed = true;
725
+ try {
726
+ await txSession.close();
727
+ } finally {
728
+ this.execLock.release();
729
+ }
730
+ };
731
+ const executeInTx = async (stmt) => {
732
+ ensureOpen();
733
+ const normalized = this.normalizeStatement(stmt);
734
+ try {
735
+ const result = await txSession.execute(normalized.sql, normalized.args, this._defaultSafeIntegers);
736
+ return this.convertResult(result);
737
+ } catch (error) {
738
+ throw mapDatabaseError(error, "EXECUTE_ERROR");
739
+ }
740
+ };
741
+ try {
742
+ await txSession.sequence(this.modeToBeginSql(mode));
743
+ } catch (error) {
744
+ await closeTx();
745
+ throw mapDatabaseError(error, "BEGIN_ERROR");
746
+ }
747
+ return {
748
+ execute: async (stmtOrSql, args) => {
749
+ if (typeof stmtOrSql === "string") {
750
+ const normalizedArgs = args ? Array.isArray(args) ? args : Object.values(args) : [];
751
+ return executeInTx({ sql: stmtOrSql, args: normalizedArgs });
752
+ }
753
+ return executeInTx(stmtOrSql);
754
+ },
755
+ batch: async (stmts) => {
756
+ ensureOpen();
757
+ const results = [];
758
+ for (const stmt of stmts) {
759
+ results.push(await executeInTx(stmt));
760
+ }
761
+ return results;
762
+ },
763
+ executeMultiple: async (sql) => {
764
+ ensureOpen();
765
+ try {
766
+ await txSession.sequence(sql);
767
+ } catch (error) {
768
+ throw mapDatabaseError(error, "EXECUTE_MULTIPLE_ERROR");
769
+ }
770
+ },
771
+ commit: async () => {
772
+ ensureOpen();
773
+ try {
774
+ await txSession.sequence("COMMIT");
775
+ } catch (error) {
776
+ throw mapDatabaseError(error, "COMMIT_ERROR");
777
+ } finally {
778
+ await closeTx();
779
+ }
780
+ },
781
+ rollback: async () => {
782
+ ensureOpen();
783
+ try {
784
+ await txSession.sequence("ROLLBACK");
785
+ } catch (error) {
786
+ throw mapDatabaseError(error, "ROLLBACK_ERROR");
787
+ } finally {
788
+ await closeTx();
789
+ }
790
+ },
791
+ close: () => {
792
+ if (txClosed) return;
793
+ txClosed = true;
794
+ void txSession.sequence("ROLLBACK").catch(() => void 0).finally(() => {
795
+ void closeTx();
796
+ });
797
+ },
798
+ get closed() {
799
+ return txClosed;
800
+ }
801
+ };
802
+ }
803
+ async executeMultiple(sql) {
804
+ await this.execLock.acquire();
805
+ try {
806
+ if (this._closed) {
807
+ throw new LibsqlError("Client is closed", "CLIENT_CLOSED");
808
+ }
809
+ await this.session.sequence(sql);
810
+ } catch (error) {
811
+ if (error instanceof LibsqlError) {
812
+ throw error;
813
+ }
814
+ throw mapDatabaseError(error, "EXECUTE_MULTIPLE_ERROR");
815
+ } finally {
816
+ this.execLock.release();
817
+ }
818
+ }
819
+ async sync() {
820
+ throw new LibsqlError("Sync not supported for remote databases", "NOT_SUPPORTED");
821
+ }
822
+ close() {
823
+ this._closed = true;
824
+ this.session.close().catch((error) => {
825
+ console.error("Error closing session:", error);
826
+ });
827
+ }
828
+ };
829
+ function createClient(config) {
830
+ return new LibSQLClient(config);
831
+ }
832
+ var sqliteBaseErrorCodes = /* @__PURE__ */ new Set([
833
+ "SQLITE_ERROR",
834
+ "SQLITE_INTERNAL",
835
+ "SQLITE_PERM",
836
+ "SQLITE_ABORT",
837
+ "SQLITE_BUSY",
838
+ "SQLITE_LOCKED",
839
+ "SQLITE_NOMEM",
840
+ "SQLITE_READONLY",
841
+ "SQLITE_INTERRUPT",
842
+ "SQLITE_IOERR",
843
+ "SQLITE_CORRUPT",
844
+ "SQLITE_NOTFOUND",
845
+ "SQLITE_FULL",
846
+ "SQLITE_CANTOPEN",
847
+ "SQLITE_PROTOCOL",
848
+ "SQLITE_EMPTY",
849
+ "SQLITE_SCHEMA",
850
+ "SQLITE_TOOBIG",
851
+ "SQLITE_CONSTRAINT",
852
+ "SQLITE_MISMATCH",
853
+ "SQLITE_MISUSE",
854
+ "SQLITE_NOLFS",
855
+ "SQLITE_AUTH",
856
+ "SQLITE_FORMAT",
857
+ "SQLITE_RANGE",
858
+ "SQLITE_NOTADB",
859
+ "SQLITE_NOTICE",
860
+ "SQLITE_WARNING"
861
+ ]);
862
+ function parseErrorCode(serverCode) {
863
+ if (sqliteBaseErrorCodes.has(serverCode)) {
864
+ return { code: serverCode };
865
+ }
866
+ for (const base of sqliteBaseErrorCodes) {
867
+ if (serverCode.startsWith(base + "_")) {
868
+ return { code: base, extendedCode: serverCode };
869
+ }
870
+ }
871
+ return { code: serverCode };
872
+ }
873
+ function mapDatabaseError(error, fallbackCode) {
874
+ if (error instanceof DatabaseError && error.code) {
875
+ const { code, extendedCode } = parseErrorCode(error.code);
876
+ return new LibsqlError(error.message, code, extendedCode, error.rawCode, error);
877
+ }
878
+ const cause = error instanceof Error ? error : void 0;
879
+ return new LibsqlError(error.message ?? String(error), fallbackCode, void 0, void 0, cause);
880
+ }
881
+
882
+ export { LibsqlError, createClient };