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