@tursodatabase/serverless 1.2.0-pre.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/async-lock.d.ts +6 -0
- package/dist/async-lock.js +22 -0
- package/dist/compat/index.d.ts +1 -147
- package/dist/compat/index.js +1 -882
- package/dist/{compat/index.d.cts → compat.d.ts} +11 -13
- package/dist/compat.js +395 -0
- package/dist/connection.d.ts +197 -0
- package/dist/connection.js +312 -0
- package/dist/error.d.ts +19 -0
- package/dist/error.js +24 -0
- package/dist/index.d.ts +5 -530
- package/dist/index.js +6 -1138
- package/dist/protocol.d.ts +120 -0
- package/dist/protocol.js +199 -0
- package/dist/session.d.ts +93 -0
- package/dist/session.js +307 -0
- package/dist/statement.d.ts +161 -0
- package/dist/statement.js +308 -0
- package/package.json +1 -1
- package/dist/compat/index.cjs +0 -885
- package/dist/index.cjs +0 -1146
- package/dist/index.d.cts +0 -530
package/dist/compat/index.js
CHANGED
|
@@ -1,882 +1 @@
|
|
|
1
|
-
|
|
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.isSafeInteger(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 != null ? 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 };
|
|
1
|
+
export * from '../compat.js';
|