@tursodatabase/serverless 1.2.0-pre.2 → 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/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 -966
- 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 -593
- package/dist/index.js +6 -1264
- 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 -969
- package/dist/index.cjs +0 -1272
- package/dist/index.d.cts +0 -593
package/dist/index.js
CHANGED
|
@@ -1,1264 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
async acquire() {
|
|
8
|
-
if (!this.locked) {
|
|
9
|
-
this.locked = true;
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
return new Promise((resolve) => {
|
|
13
|
-
this.queue.push(resolve);
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
release() {
|
|
17
|
-
const next = this.queue.shift();
|
|
18
|
-
if (next) {
|
|
19
|
-
next();
|
|
20
|
-
} else {
|
|
21
|
-
this.locked = false;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// src/error.ts
|
|
27
|
-
var DatabaseError = class _DatabaseError extends Error {
|
|
28
|
-
constructor(message, code, rawCode, cause) {
|
|
29
|
-
super(message);
|
|
30
|
-
this.name = "DatabaseError";
|
|
31
|
-
this.code = code;
|
|
32
|
-
this.rawCode = rawCode;
|
|
33
|
-
this.cause = cause;
|
|
34
|
-
Object.setPrototypeOf(this, _DatabaseError.prototype);
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
var TimeoutError = class _TimeoutError extends DatabaseError {
|
|
38
|
-
constructor(message = "Query timed out", cause) {
|
|
39
|
-
super(message, "TIMEOUT", void 0, cause);
|
|
40
|
-
this.name = "TimeoutError";
|
|
41
|
-
Object.setPrototypeOf(this, _TimeoutError.prototype);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// src/protocol.ts
|
|
46
|
-
function encodeValue(value) {
|
|
47
|
-
if (value === null || value === void 0) {
|
|
48
|
-
return { type: "null" };
|
|
49
|
-
}
|
|
50
|
-
if (typeof value === "number") {
|
|
51
|
-
if (!Number.isFinite(value)) {
|
|
52
|
-
throw new Error("Only finite numbers (not Infinity or NaN) can be passed as arguments");
|
|
53
|
-
}
|
|
54
|
-
if (Number.isSafeInteger(value)) {
|
|
55
|
-
return { type: "integer", value: value.toString() };
|
|
56
|
-
}
|
|
57
|
-
return { type: "float", value };
|
|
58
|
-
}
|
|
59
|
-
if (typeof value === "bigint") {
|
|
60
|
-
return { type: "integer", value: value.toString() };
|
|
61
|
-
}
|
|
62
|
-
if (typeof value === "boolean") {
|
|
63
|
-
return { type: "integer", value: value ? "1" : "0" };
|
|
64
|
-
}
|
|
65
|
-
if (typeof value === "string") {
|
|
66
|
-
return { type: "text", value };
|
|
67
|
-
}
|
|
68
|
-
if (value instanceof ArrayBuffer || value instanceof Uint8Array) {
|
|
69
|
-
const base64 = btoa(String.fromCharCode(...new Uint8Array(value)));
|
|
70
|
-
return { type: "blob", base64 };
|
|
71
|
-
}
|
|
72
|
-
return { type: "text", value: String(value) };
|
|
73
|
-
}
|
|
74
|
-
function decodeValue(value, safeIntegers = false) {
|
|
75
|
-
switch (value.type) {
|
|
76
|
-
case "null":
|
|
77
|
-
return null;
|
|
78
|
-
case "integer":
|
|
79
|
-
if (safeIntegers) {
|
|
80
|
-
return BigInt(value.value);
|
|
81
|
-
}
|
|
82
|
-
return parseInt(value.value, 10);
|
|
83
|
-
case "float":
|
|
84
|
-
return value.value;
|
|
85
|
-
case "text":
|
|
86
|
-
return value.value;
|
|
87
|
-
case "blob":
|
|
88
|
-
if (value.base64 !== void 0 && value.base64 !== null) {
|
|
89
|
-
let b64 = value.base64;
|
|
90
|
-
while (b64.length % 4 !== 0) {
|
|
91
|
-
b64 += "=";
|
|
92
|
-
}
|
|
93
|
-
const binaryString = atob(b64);
|
|
94
|
-
const bytes = new Uint8Array(binaryString.length);
|
|
95
|
-
for (let i = 0; i < binaryString.length; i++) {
|
|
96
|
-
bytes[i] = binaryString.charCodeAt(i);
|
|
97
|
-
}
|
|
98
|
-
return Buffer.from(bytes);
|
|
99
|
-
}
|
|
100
|
-
return Buffer.alloc(0);
|
|
101
|
-
default:
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
var ENCRYPTION_KEY_HEADER = "x-turso-encryption-key";
|
|
106
|
-
function wrapAbortError(error) {
|
|
107
|
-
if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) {
|
|
108
|
-
throw new TimeoutError("Query timed out");
|
|
109
|
-
}
|
|
110
|
-
throw error;
|
|
111
|
-
}
|
|
112
|
-
async function executeCursor(url, authToken, request, remoteEncryptionKey, signal) {
|
|
113
|
-
const headers = {
|
|
114
|
-
"Content-Type": "application/json"
|
|
115
|
-
};
|
|
116
|
-
if (authToken) {
|
|
117
|
-
headers["Authorization"] = `Bearer ${authToken}`;
|
|
118
|
-
}
|
|
119
|
-
if (remoteEncryptionKey) {
|
|
120
|
-
headers[ENCRYPTION_KEY_HEADER] = remoteEncryptionKey;
|
|
121
|
-
}
|
|
122
|
-
let response;
|
|
123
|
-
try {
|
|
124
|
-
response = await fetch(`${url}/v3/cursor`, {
|
|
125
|
-
method: "POST",
|
|
126
|
-
headers,
|
|
127
|
-
body: JSON.stringify(request),
|
|
128
|
-
signal
|
|
129
|
-
});
|
|
130
|
-
} catch (error) {
|
|
131
|
-
wrapAbortError(error);
|
|
132
|
-
}
|
|
133
|
-
if (!response.ok) {
|
|
134
|
-
let errorMessage = `HTTP error! status: ${response.status}`;
|
|
135
|
-
try {
|
|
136
|
-
const errorBody = await response.text();
|
|
137
|
-
const errorData = JSON.parse(errorBody);
|
|
138
|
-
if (errorData.message) {
|
|
139
|
-
errorMessage = errorData.message;
|
|
140
|
-
}
|
|
141
|
-
} catch {
|
|
142
|
-
}
|
|
143
|
-
throw new DatabaseError(errorMessage);
|
|
144
|
-
}
|
|
145
|
-
const reader = response.body?.getReader();
|
|
146
|
-
if (!reader) {
|
|
147
|
-
throw new DatabaseError("No response body");
|
|
148
|
-
}
|
|
149
|
-
const decoder = new TextDecoder();
|
|
150
|
-
let buffer = "";
|
|
151
|
-
let cursorResponse;
|
|
152
|
-
try {
|
|
153
|
-
while (!cursorResponse) {
|
|
154
|
-
const { done, value } = await reader.read();
|
|
155
|
-
if (done) break;
|
|
156
|
-
buffer += decoder.decode(value, { stream: true });
|
|
157
|
-
const newlineIndex = buffer.indexOf("\n");
|
|
158
|
-
if (newlineIndex !== -1) {
|
|
159
|
-
const line = buffer.slice(0, newlineIndex).trim();
|
|
160
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
161
|
-
if (line) {
|
|
162
|
-
cursorResponse = JSON.parse(line);
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
} catch (error) {
|
|
168
|
-
reader.releaseLock();
|
|
169
|
-
wrapAbortError(error);
|
|
170
|
-
}
|
|
171
|
-
if (!cursorResponse) {
|
|
172
|
-
reader.releaseLock();
|
|
173
|
-
throw new DatabaseError("No cursor response received");
|
|
174
|
-
}
|
|
175
|
-
async function* parseEntries() {
|
|
176
|
-
try {
|
|
177
|
-
let newlineIndex;
|
|
178
|
-
while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
|
|
179
|
-
const line = buffer.slice(0, newlineIndex).trim();
|
|
180
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
181
|
-
if (line) {
|
|
182
|
-
yield JSON.parse(line);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
while (true) {
|
|
186
|
-
let readResult;
|
|
187
|
-
try {
|
|
188
|
-
readResult = await reader.read();
|
|
189
|
-
} catch (error) {
|
|
190
|
-
wrapAbortError(error);
|
|
191
|
-
}
|
|
192
|
-
if (readResult.done) break;
|
|
193
|
-
buffer += decoder.decode(readResult.value, { stream: true });
|
|
194
|
-
while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
|
|
195
|
-
const line = buffer.slice(0, newlineIndex).trim();
|
|
196
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
197
|
-
if (line) {
|
|
198
|
-
yield JSON.parse(line);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
if (buffer.trim()) {
|
|
203
|
-
yield JSON.parse(buffer.trim());
|
|
204
|
-
}
|
|
205
|
-
} finally {
|
|
206
|
-
reader.releaseLock();
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return { response: cursorResponse, entries: parseEntries() };
|
|
210
|
-
}
|
|
211
|
-
async function executePipeline(url, authToken, request, remoteEncryptionKey, signal) {
|
|
212
|
-
const headers = {
|
|
213
|
-
"Content-Type": "application/json"
|
|
214
|
-
};
|
|
215
|
-
if (authToken) {
|
|
216
|
-
headers["Authorization"] = `Bearer ${authToken}`;
|
|
217
|
-
}
|
|
218
|
-
if (remoteEncryptionKey) {
|
|
219
|
-
headers[ENCRYPTION_KEY_HEADER] = remoteEncryptionKey;
|
|
220
|
-
}
|
|
221
|
-
let response;
|
|
222
|
-
try {
|
|
223
|
-
response = await fetch(`${url}/v3/pipeline`, {
|
|
224
|
-
method: "POST",
|
|
225
|
-
headers,
|
|
226
|
-
body: JSON.stringify(request),
|
|
227
|
-
signal
|
|
228
|
-
});
|
|
229
|
-
} catch (error) {
|
|
230
|
-
wrapAbortError(error);
|
|
231
|
-
}
|
|
232
|
-
if (!response.ok) {
|
|
233
|
-
throw new DatabaseError(`HTTP error! status: ${response.status}`);
|
|
234
|
-
}
|
|
235
|
-
return response.json();
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// src/args.ts
|
|
239
|
-
function normalizeArgs(args) {
|
|
240
|
-
if (args === void 0) return [];
|
|
241
|
-
if (Array.isArray(args)) return args;
|
|
242
|
-
if (args !== null && typeof args === "object" && args.constructor === Object) {
|
|
243
|
-
return args;
|
|
244
|
-
}
|
|
245
|
-
return [args];
|
|
246
|
-
}
|
|
247
|
-
function isQueryOptions(value) {
|
|
248
|
-
return value != null && typeof value === "object" && !Array.isArray(value) && Object.prototype.hasOwnProperty.call(value, "queryTimeout");
|
|
249
|
-
}
|
|
250
|
-
function splitBindParameters(bindParameters) {
|
|
251
|
-
if (bindParameters.length === 0) {
|
|
252
|
-
return { params: void 0, queryOptions: void 0 };
|
|
253
|
-
}
|
|
254
|
-
if (isQueryOptions(bindParameters[bindParameters.length - 1])) {
|
|
255
|
-
if (bindParameters.length === 1) {
|
|
256
|
-
return { params: void 0, queryOptions: bindParameters[0] };
|
|
257
|
-
}
|
|
258
|
-
return {
|
|
259
|
-
params: bindParameters.length === 2 ? bindParameters[0] : bindParameters.slice(0, -1),
|
|
260
|
-
queryOptions: bindParameters[bindParameters.length - 1]
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
return {
|
|
264
|
-
params: bindParameters.length === 1 ? bindParameters[0] : bindParameters,
|
|
265
|
-
queryOptions: void 0
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
function encodeSqlArgs(args = []) {
|
|
269
|
-
let positionalArgs = [];
|
|
270
|
-
let namedArgs = [];
|
|
271
|
-
if (Array.isArray(args)) {
|
|
272
|
-
positionalArgs = args.map(encodeValue);
|
|
273
|
-
} else {
|
|
274
|
-
const keys = Object.keys(args);
|
|
275
|
-
const isNumericKeys = keys.length > 0 && keys.every((key) => /^\d+$/.test(key));
|
|
276
|
-
if (isNumericKeys) {
|
|
277
|
-
const sortedKeys = keys.sort((a, b) => parseInt(a, 10) - parseInt(b, 10));
|
|
278
|
-
const maxIndex = parseInt(sortedKeys[sortedKeys.length - 1], 10);
|
|
279
|
-
positionalArgs = new Array(maxIndex);
|
|
280
|
-
for (const key of sortedKeys) {
|
|
281
|
-
const index = parseInt(key, 10) - 1;
|
|
282
|
-
positionalArgs[index] = encodeValue(args[key]);
|
|
283
|
-
}
|
|
284
|
-
for (let i = 0; i < positionalArgs.length; i++) {
|
|
285
|
-
if (positionalArgs[i] === void 0) {
|
|
286
|
-
positionalArgs[i] = { type: "null" };
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
} else {
|
|
290
|
-
namedArgs = Object.entries(args).map(([name, value]) => ({
|
|
291
|
-
name,
|
|
292
|
-
value: encodeValue(value)
|
|
293
|
-
}));
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return { args: positionalArgs, namedArgs };
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// src/session.ts
|
|
300
|
-
function normalizeUrl(url) {
|
|
301
|
-
return url.replace(/^libsql:\/\//, "https://");
|
|
302
|
-
}
|
|
303
|
-
function isValidIdentifier(str) {
|
|
304
|
-
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(str);
|
|
305
|
-
}
|
|
306
|
-
var Session = class {
|
|
307
|
-
constructor(config) {
|
|
308
|
-
this.baton = null;
|
|
309
|
-
this.config = config;
|
|
310
|
-
this.baseUrl = normalizeUrl(config.url);
|
|
311
|
-
}
|
|
312
|
-
createAbortSignal(queryOptions) {
|
|
313
|
-
const timeout = queryOptions?.queryTimeout ?? this.config.defaultQueryTimeout;
|
|
314
|
-
if (timeout != null && timeout > 0) {
|
|
315
|
-
return AbortSignal.timeout(timeout);
|
|
316
|
-
}
|
|
317
|
-
return void 0;
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Describe a SQL statement to get its column metadata.
|
|
321
|
-
*
|
|
322
|
-
* @param sql - The SQL statement to describe
|
|
323
|
-
* @returns Promise resolving to the statement description
|
|
324
|
-
*/
|
|
325
|
-
async describe(sql, queryOptions) {
|
|
326
|
-
const request = {
|
|
327
|
-
baton: this.baton,
|
|
328
|
-
requests: [{
|
|
329
|
-
type: "describe",
|
|
330
|
-
sql
|
|
331
|
-
}]
|
|
332
|
-
};
|
|
333
|
-
let response;
|
|
334
|
-
try {
|
|
335
|
-
response = await executePipeline(this.baseUrl, this.config.authToken, request, this.config.remoteEncryptionKey, this.createAbortSignal(queryOptions));
|
|
336
|
-
} catch (e) {
|
|
337
|
-
this.baton = null;
|
|
338
|
-
throw e;
|
|
339
|
-
}
|
|
340
|
-
this.baton = response.baton;
|
|
341
|
-
if (response.base_url) {
|
|
342
|
-
this.baseUrl = response.base_url;
|
|
343
|
-
}
|
|
344
|
-
if (response.results && response.results[0]) {
|
|
345
|
-
const result = response.results[0];
|
|
346
|
-
if (result.type === "error") {
|
|
347
|
-
throw new DatabaseError(result.error?.message || "Describe execution failed", result.error?.code);
|
|
348
|
-
}
|
|
349
|
-
if (result.response?.type === "describe" && result.response.result) {
|
|
350
|
-
return result.response.result;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
throw new DatabaseError("Unexpected describe response");
|
|
354
|
-
}
|
|
355
|
-
/**
|
|
356
|
-
* Execute a SQL statement and return all results.
|
|
357
|
-
*
|
|
358
|
-
* @param sql - The SQL statement to execute
|
|
359
|
-
* @param args - Optional array of parameter values or object with named parameters
|
|
360
|
-
* @param safeIntegers - Whether to return integers as BigInt
|
|
361
|
-
* @returns Promise resolving to the complete result set
|
|
362
|
-
*/
|
|
363
|
-
async execute(sql, args = [], safeIntegers = false, queryOptions) {
|
|
364
|
-
const { response, entries } = await this.executeRaw(sql, args, queryOptions);
|
|
365
|
-
const result = await this.processCursorEntries(entries, safeIntegers);
|
|
366
|
-
return result;
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Execute a SQL statement and return the raw response and entries.
|
|
370
|
-
*
|
|
371
|
-
* @param sql - The SQL statement to execute
|
|
372
|
-
* @param args - Optional array of parameter values or object with named parameters
|
|
373
|
-
* @returns Promise resolving to the raw response and cursor entries
|
|
374
|
-
*/
|
|
375
|
-
async executeRaw(sql, args = [], queryOptions) {
|
|
376
|
-
const encodedArgs = encodeSqlArgs(args);
|
|
377
|
-
const request = {
|
|
378
|
-
baton: this.baton,
|
|
379
|
-
batch: {
|
|
380
|
-
steps: [{
|
|
381
|
-
stmt: {
|
|
382
|
-
sql,
|
|
383
|
-
args: encodedArgs.args,
|
|
384
|
-
named_args: encodedArgs.namedArgs,
|
|
385
|
-
want_rows: true
|
|
386
|
-
}
|
|
387
|
-
}]
|
|
388
|
-
}
|
|
389
|
-
};
|
|
390
|
-
let result;
|
|
391
|
-
try {
|
|
392
|
-
result = await executeCursor(this.baseUrl, this.config.authToken, request, this.config.remoteEncryptionKey, this.createAbortSignal(queryOptions));
|
|
393
|
-
} catch (e) {
|
|
394
|
-
this.baton = null;
|
|
395
|
-
throw e;
|
|
396
|
-
}
|
|
397
|
-
const { response, entries } = result;
|
|
398
|
-
this.baton = response.baton;
|
|
399
|
-
if (response.base_url) {
|
|
400
|
-
this.baseUrl = response.base_url;
|
|
401
|
-
}
|
|
402
|
-
return { response, entries };
|
|
403
|
-
}
|
|
404
|
-
/**
|
|
405
|
-
* Process cursor entries into a structured result.
|
|
406
|
-
*
|
|
407
|
-
* @param entries - Async generator of cursor entries
|
|
408
|
-
* @returns Promise resolving to the processed result
|
|
409
|
-
*/
|
|
410
|
-
async processCursorEntries(entries, safeIntegers = false) {
|
|
411
|
-
let columns = [];
|
|
412
|
-
let columnTypes = [];
|
|
413
|
-
let rows = [];
|
|
414
|
-
let rowsAffected = 0;
|
|
415
|
-
let lastInsertRowid;
|
|
416
|
-
for await (const entry of entries) {
|
|
417
|
-
switch (entry.type) {
|
|
418
|
-
case "step_begin":
|
|
419
|
-
if (entry.cols) {
|
|
420
|
-
columns = entry.cols.map((col) => col.name);
|
|
421
|
-
columnTypes = entry.cols.map((col) => col.decltype || "");
|
|
422
|
-
}
|
|
423
|
-
break;
|
|
424
|
-
case "row":
|
|
425
|
-
if (entry.row) {
|
|
426
|
-
const decodedRow = entry.row.map((value) => decodeValue(value, safeIntegers));
|
|
427
|
-
const rowObject = this.createRowObject(decodedRow, columns);
|
|
428
|
-
rows.push(rowObject);
|
|
429
|
-
}
|
|
430
|
-
break;
|
|
431
|
-
case "step_end":
|
|
432
|
-
if (entry.affected_row_count !== void 0) {
|
|
433
|
-
rowsAffected = entry.affected_row_count;
|
|
434
|
-
}
|
|
435
|
-
if (entry.last_insert_rowid !== void 0 && entry.last_insert_rowid !== null) {
|
|
436
|
-
lastInsertRowid = typeof entry.last_insert_rowid === "number" ? entry.last_insert_rowid : parseInt(entry.last_insert_rowid, 10);
|
|
437
|
-
}
|
|
438
|
-
break;
|
|
439
|
-
case "step_error":
|
|
440
|
-
case "error":
|
|
441
|
-
throw new DatabaseError(entry.error?.message || "SQL execution failed", entry.error?.code);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
return {
|
|
445
|
-
columns,
|
|
446
|
-
columnTypes,
|
|
447
|
-
rows,
|
|
448
|
-
rowsAffected,
|
|
449
|
-
lastInsertRowid
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
/**
|
|
453
|
-
* Create a row object with both array and named property access.
|
|
454
|
-
*
|
|
455
|
-
* @param values - Array of column values
|
|
456
|
-
* @param columns - Array of column names
|
|
457
|
-
* @returns Row object with dual access patterns
|
|
458
|
-
*/
|
|
459
|
-
createRowObject(values, columns) {
|
|
460
|
-
const row = [...values];
|
|
461
|
-
columns.forEach((column, index) => {
|
|
462
|
-
if (column && isValidIdentifier(column)) {
|
|
463
|
-
Object.defineProperty(row, column, {
|
|
464
|
-
value: values[index],
|
|
465
|
-
enumerable: false,
|
|
466
|
-
writable: false,
|
|
467
|
-
configurable: true
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
});
|
|
471
|
-
return row;
|
|
472
|
-
}
|
|
473
|
-
/**
|
|
474
|
-
* Execute multiple SQL statements in a batch.
|
|
475
|
-
*
|
|
476
|
-
* When `mode` is set, the batch is sent as a single Hrana request that
|
|
477
|
-
* also carries `BEGIN <mode>` / `COMMIT` / `ROLLBACK` steps using the
|
|
478
|
-
* server-side condition chain, giving atomic execution in one round-trip.
|
|
479
|
-
* When `mode` is omitted, the user statements are sent as-is and run
|
|
480
|
-
* under autocommit (or whatever transaction is already active on this
|
|
481
|
-
* stream).
|
|
482
|
-
*
|
|
483
|
-
* @param statements - Array of SQL statements to execute.
|
|
484
|
-
* @param mode - Optional locking mode; when set, the batch executes
|
|
485
|
-
* atomically. Accepts the same values as `Database.transaction(...)`
|
|
486
|
-
* variants: `"deferred"`, `"immediate"`, `"exclusive"`, `"concurrent"`.
|
|
487
|
-
* @returns Promise resolving to batch execution results.
|
|
488
|
-
*/
|
|
489
|
-
async batch(statements, mode, queryOptions) {
|
|
490
|
-
const userSteps = statements.map((statement) => {
|
|
491
|
-
if (typeof statement === "string") {
|
|
492
|
-
return {
|
|
493
|
-
stmt: { sql: statement, args: [], named_args: [], want_rows: false }
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
const encodedArgs = encodeSqlArgs(statement.args ?? []);
|
|
497
|
-
return {
|
|
498
|
-
stmt: {
|
|
499
|
-
sql: statement.sql,
|
|
500
|
-
args: encodedArgs.args,
|
|
501
|
-
named_args: encodedArgs.namedArgs,
|
|
502
|
-
want_rows: false
|
|
503
|
-
}
|
|
504
|
-
};
|
|
505
|
-
});
|
|
506
|
-
let steps;
|
|
507
|
-
let firstUserStepIdx = 0;
|
|
508
|
-
let lastUserStepIdx = userSteps.length - 1;
|
|
509
|
-
let beginIdx = -1;
|
|
510
|
-
let commitIdx = -1;
|
|
511
|
-
let rollbackIdx = -1;
|
|
512
|
-
if (mode === void 0) {
|
|
513
|
-
steps = userSteps;
|
|
514
|
-
} else {
|
|
515
|
-
beginIdx = 0;
|
|
516
|
-
firstUserStepIdx = 1;
|
|
517
|
-
lastUserStepIdx = userSteps.length;
|
|
518
|
-
commitIdx = lastUserStepIdx + 1;
|
|
519
|
-
rollbackIdx = commitIdx + 1;
|
|
520
|
-
steps = [
|
|
521
|
-
{ stmt: { sql: `BEGIN ${mode.toUpperCase()}`, args: [], named_args: [], want_rows: false } },
|
|
522
|
-
...userSteps.map((step, i) => ({
|
|
523
|
-
...step,
|
|
524
|
-
condition: { type: "ok", step: i === 0 ? beginIdx : firstUserStepIdx + i - 1 }
|
|
525
|
-
})),
|
|
526
|
-
{
|
|
527
|
-
stmt: { sql: "COMMIT", args: [], named_args: [], want_rows: false },
|
|
528
|
-
condition: { type: "ok", step: lastUserStepIdx }
|
|
529
|
-
},
|
|
530
|
-
{
|
|
531
|
-
stmt: { sql: "ROLLBACK", args: [], named_args: [], want_rows: false },
|
|
532
|
-
condition: {
|
|
533
|
-
type: "and",
|
|
534
|
-
conds: [
|
|
535
|
-
{ type: "ok", step: beginIdx },
|
|
536
|
-
{ type: "not", cond: { type: "ok", step: commitIdx } }
|
|
537
|
-
]
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
];
|
|
541
|
-
}
|
|
542
|
-
const request = {
|
|
543
|
-
baton: this.baton,
|
|
544
|
-
batch: { steps }
|
|
545
|
-
};
|
|
546
|
-
let batchResult;
|
|
547
|
-
try {
|
|
548
|
-
batchResult = await executeCursor(this.baseUrl, this.config.authToken, request, this.config.remoteEncryptionKey, this.createAbortSignal(queryOptions));
|
|
549
|
-
} catch (e) {
|
|
550
|
-
this.baton = null;
|
|
551
|
-
throw e;
|
|
552
|
-
}
|
|
553
|
-
const { response, entries } = batchResult;
|
|
554
|
-
this.baton = response.baton;
|
|
555
|
-
if (response.base_url) {
|
|
556
|
-
this.baseUrl = response.base_url;
|
|
557
|
-
}
|
|
558
|
-
let totalRowsAffected = 0;
|
|
559
|
-
let lastInsertRowid;
|
|
560
|
-
let deferredError = null;
|
|
561
|
-
let currentStep;
|
|
562
|
-
const isUserStep = (step) => {
|
|
563
|
-
if (mode === void 0) {
|
|
564
|
-
return true;
|
|
565
|
-
}
|
|
566
|
-
return step !== void 0 && step >= firstUserStepIdx && step <= lastUserStepIdx;
|
|
567
|
-
};
|
|
568
|
-
for await (const entry of entries) {
|
|
569
|
-
switch (entry.type) {
|
|
570
|
-
case "step_begin":
|
|
571
|
-
currentStep = entry.step;
|
|
572
|
-
break;
|
|
573
|
-
case "step_end":
|
|
574
|
-
if (isUserStep(currentStep)) {
|
|
575
|
-
if (entry.affected_row_count !== void 0) {
|
|
576
|
-
totalRowsAffected += entry.affected_row_count;
|
|
577
|
-
}
|
|
578
|
-
if (entry.last_insert_rowid !== void 0 && entry.last_insert_rowid !== null) {
|
|
579
|
-
lastInsertRowid = typeof entry.last_insert_rowid === "number" ? entry.last_insert_rowid : parseInt(entry.last_insert_rowid, 10);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
currentStep = void 0;
|
|
583
|
-
break;
|
|
584
|
-
case "step_error":
|
|
585
|
-
if (mode === void 0) {
|
|
586
|
-
throw new DatabaseError(entry.error?.message || "Batch execution failed", entry.error?.code);
|
|
587
|
-
}
|
|
588
|
-
if (deferredError === null && entry.step !== rollbackIdx) {
|
|
589
|
-
deferredError = new DatabaseError(entry.error?.message || "Batch execution failed", entry.error?.code);
|
|
590
|
-
}
|
|
591
|
-
currentStep = void 0;
|
|
592
|
-
break;
|
|
593
|
-
case "error":
|
|
594
|
-
throw new DatabaseError(entry.error?.message || "Batch execution failed", entry.error?.code);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
if (deferredError !== null) {
|
|
598
|
-
throw deferredError;
|
|
599
|
-
}
|
|
600
|
-
return {
|
|
601
|
-
rowsAffected: totalRowsAffected,
|
|
602
|
-
lastInsertRowid
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
/**
|
|
606
|
-
* Execute a sequence of SQL statements separated by semicolons.
|
|
607
|
-
*
|
|
608
|
-
* @param sql - SQL string containing multiple statements separated by semicolons
|
|
609
|
-
* @returns Promise resolving when all statements are executed
|
|
610
|
-
*/
|
|
611
|
-
async sequence(sql, queryOptions) {
|
|
612
|
-
const request = {
|
|
613
|
-
baton: this.baton,
|
|
614
|
-
requests: [{
|
|
615
|
-
type: "sequence",
|
|
616
|
-
sql
|
|
617
|
-
}]
|
|
618
|
-
};
|
|
619
|
-
let seqResponse;
|
|
620
|
-
try {
|
|
621
|
-
seqResponse = await executePipeline(this.baseUrl, this.config.authToken, request, this.config.remoteEncryptionKey, this.createAbortSignal(queryOptions));
|
|
622
|
-
} catch (e) {
|
|
623
|
-
this.baton = null;
|
|
624
|
-
throw e;
|
|
625
|
-
}
|
|
626
|
-
this.baton = seqResponse.baton;
|
|
627
|
-
if (seqResponse.base_url) {
|
|
628
|
-
this.baseUrl = seqResponse.base_url;
|
|
629
|
-
}
|
|
630
|
-
if (seqResponse.results && seqResponse.results[0]) {
|
|
631
|
-
const result = seqResponse.results[0];
|
|
632
|
-
if (result.type === "error") {
|
|
633
|
-
throw new DatabaseError(result.error?.message || "Sequence execution failed", result.error?.code);
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* Close the session.
|
|
639
|
-
*
|
|
640
|
-
* This sends a close request to the server to properly clean up the stream
|
|
641
|
-
* before resetting the local state.
|
|
642
|
-
*/
|
|
643
|
-
async close() {
|
|
644
|
-
if (this.baton) {
|
|
645
|
-
try {
|
|
646
|
-
const request = {
|
|
647
|
-
baton: this.baton,
|
|
648
|
-
requests: [{
|
|
649
|
-
type: "close"
|
|
650
|
-
}]
|
|
651
|
-
};
|
|
652
|
-
await executePipeline(this.baseUrl, this.config.authToken, request, this.config.remoteEncryptionKey);
|
|
653
|
-
} catch {
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
this.baton = null;
|
|
657
|
-
this.baseUrl = "";
|
|
658
|
-
}
|
|
659
|
-
};
|
|
660
|
-
|
|
661
|
-
// src/statement.ts
|
|
662
|
-
var Statement = class _Statement {
|
|
663
|
-
constructor(sessionConfig, sql, columns) {
|
|
664
|
-
this.presentationMode = "expanded";
|
|
665
|
-
this.safeIntegerMode = false;
|
|
666
|
-
this.session = new Session(sessionConfig);
|
|
667
|
-
this.sql = sql;
|
|
668
|
-
this.columnMetadata = columns || [];
|
|
669
|
-
}
|
|
670
|
-
/**
|
|
671
|
-
* Create a Statement that shares an existing session and serializes execution
|
|
672
|
-
* through the given lock. Used by Connection.prepare() so prepared statements
|
|
673
|
-
* participate in the connection's transaction scope.
|
|
674
|
-
*/
|
|
675
|
-
static fromSession(session, sql, columns, execLock) {
|
|
676
|
-
const stmt = Object.create(_Statement.prototype);
|
|
677
|
-
stmt.session = session;
|
|
678
|
-
stmt.sql = sql;
|
|
679
|
-
stmt.columnMetadata = columns || [];
|
|
680
|
-
stmt.presentationMode = "expanded";
|
|
681
|
-
stmt.safeIntegerMode = false;
|
|
682
|
-
stmt.execLock = execLock;
|
|
683
|
-
return stmt;
|
|
684
|
-
}
|
|
685
|
-
/**
|
|
686
|
-
* Whether the prepared statement returns data.
|
|
687
|
-
*
|
|
688
|
-
* This is `true` for SELECT queries and statements with RETURNING clause,
|
|
689
|
-
* and `false` for INSERT, UPDATE, DELETE statements without RETURNING.
|
|
690
|
-
*
|
|
691
|
-
* @example
|
|
692
|
-
* ```typescript
|
|
693
|
-
* const stmt = await conn.prepare(sql);
|
|
694
|
-
* if (stmt.reader) {
|
|
695
|
-
* return stmt.all(args); // SELECT-like query
|
|
696
|
-
* } else {
|
|
697
|
-
* return stmt.run(args); // INSERT/UPDATE/DELETE
|
|
698
|
-
* }
|
|
699
|
-
* ```
|
|
700
|
-
*/
|
|
701
|
-
get reader() {
|
|
702
|
-
return this.columnMetadata.length > 0;
|
|
703
|
-
}
|
|
704
|
-
/**
|
|
705
|
-
* Enable raw mode to return arrays instead of objects.
|
|
706
|
-
*
|
|
707
|
-
* @param raw Enable or disable raw mode. If you don't pass the parameter, raw mode is enabled.
|
|
708
|
-
* @returns This statement instance for chaining
|
|
709
|
-
*
|
|
710
|
-
* @example
|
|
711
|
-
* ```typescript
|
|
712
|
-
* const stmt = client.prepare("SELECT * FROM users WHERE id = ?");
|
|
713
|
-
* const row = await stmt.raw().get([1]);
|
|
714
|
-
* console.log(row); // [1, "Alice", "alice@example.org"]
|
|
715
|
-
* ```
|
|
716
|
-
*/
|
|
717
|
-
raw(raw) {
|
|
718
|
-
this.presentationMode = raw === false ? "expanded" : "raw";
|
|
719
|
-
return this;
|
|
720
|
-
}
|
|
721
|
-
/**
|
|
722
|
-
* Enable pluck mode to return only the first column value from each row.
|
|
723
|
-
*
|
|
724
|
-
* @param pluck Enable or disable pluck mode. If you don't pass the parameter, pluck mode is enabled.
|
|
725
|
-
* @returns This statement instance for chaining
|
|
726
|
-
*
|
|
727
|
-
* @example
|
|
728
|
-
* ```typescript
|
|
729
|
-
* const stmt = client.prepare("SELECT id FROM users");
|
|
730
|
-
* const ids = await stmt.pluck().all();
|
|
731
|
-
* console.log(ids); // [1, 2, 3, ...]
|
|
732
|
-
* ```
|
|
733
|
-
*/
|
|
734
|
-
pluck(pluck) {
|
|
735
|
-
this.presentationMode = pluck === false ? "expanded" : "pluck";
|
|
736
|
-
return this;
|
|
737
|
-
}
|
|
738
|
-
/**
|
|
739
|
-
* Sets safe integers mode for this statement.
|
|
740
|
-
*
|
|
741
|
-
* @param toggle Whether to use safe integers. If you don't pass the parameter, safe integers mode is enabled.
|
|
742
|
-
* @returns This statement instance for chaining
|
|
743
|
-
*/
|
|
744
|
-
safeIntegers(toggle) {
|
|
745
|
-
this.safeIntegerMode = toggle === false ? false : true;
|
|
746
|
-
return this;
|
|
747
|
-
}
|
|
748
|
-
/**
|
|
749
|
-
* Get column information for this statement.
|
|
750
|
-
*
|
|
751
|
-
* @returns Array of column metadata objects matching the native bindings format
|
|
752
|
-
*
|
|
753
|
-
* @example
|
|
754
|
-
* ```typescript
|
|
755
|
-
* const stmt = await client.prepare("SELECT id, name, email FROM users");
|
|
756
|
-
* const columns = stmt.columns();
|
|
757
|
-
* console.log(columns); // [{ name: 'id', type: 'INTEGER', column: null, database: null, table: null }, ...]
|
|
758
|
-
* ```
|
|
759
|
-
*/
|
|
760
|
-
columns() {
|
|
761
|
-
return this.columnMetadata.map((col) => ({
|
|
762
|
-
name: col.name,
|
|
763
|
-
type: col.decltype
|
|
764
|
-
}));
|
|
765
|
-
}
|
|
766
|
-
async withLock(fn) {
|
|
767
|
-
if (!this.execLock) {
|
|
768
|
-
return await fn();
|
|
769
|
-
}
|
|
770
|
-
await this.execLock.acquire();
|
|
771
|
-
try {
|
|
772
|
-
return await fn();
|
|
773
|
-
} finally {
|
|
774
|
-
this.execLock.release();
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
/**
|
|
778
|
-
* Executes the prepared statement.
|
|
779
|
-
*
|
|
780
|
-
* @param args - Optional array of parameter values or object with named parameters
|
|
781
|
-
* @returns Promise resolving to the result of the statement
|
|
782
|
-
*
|
|
783
|
-
* @example
|
|
784
|
-
* ```typescript
|
|
785
|
-
* const stmt = client.prepare("INSERT INTO users (name, email) VALUES (?, ?)");
|
|
786
|
-
* const result = await stmt.run(['John Doe', 'john.doe@example.com']);
|
|
787
|
-
* console.log(`Inserted user with ID ${result.lastInsertRowid}`);
|
|
788
|
-
* ```
|
|
789
|
-
*/
|
|
790
|
-
async run(args, queryOptions) {
|
|
791
|
-
return await this.withLock(async () => {
|
|
792
|
-
const normalizedArgs = normalizeArgs(args);
|
|
793
|
-
const result = await this.session.execute(this.sql, normalizedArgs, this.safeIntegerMode, queryOptions);
|
|
794
|
-
return { changes: result.rowsAffected, lastInsertRowid: result.lastInsertRowid };
|
|
795
|
-
});
|
|
796
|
-
}
|
|
797
|
-
/**
|
|
798
|
-
* Execute the statement and return the first row.
|
|
799
|
-
*
|
|
800
|
-
* @param args - Optional array of parameter values or object with named parameters
|
|
801
|
-
* @returns Promise resolving to the first row or undefined if no results
|
|
802
|
-
*
|
|
803
|
-
* @example
|
|
804
|
-
* ```typescript
|
|
805
|
-
* const stmt = client.prepare("SELECT * FROM users WHERE id = ?");
|
|
806
|
-
* const user = await stmt.get([123]);
|
|
807
|
-
* if (user) {
|
|
808
|
-
* console.log(user.name);
|
|
809
|
-
* }
|
|
810
|
-
* ```
|
|
811
|
-
*/
|
|
812
|
-
async get(args, queryOptions) {
|
|
813
|
-
return await this.withLock(async () => {
|
|
814
|
-
const normalizedArgs = normalizeArgs(args);
|
|
815
|
-
const result = await this.session.execute(this.sql, normalizedArgs, this.safeIntegerMode, queryOptions);
|
|
816
|
-
const row = result.rows[0];
|
|
817
|
-
if (!row) {
|
|
818
|
-
return void 0;
|
|
819
|
-
}
|
|
820
|
-
if (this.presentationMode === "pluck") {
|
|
821
|
-
return row[0];
|
|
822
|
-
}
|
|
823
|
-
if (this.presentationMode === "raw") {
|
|
824
|
-
return [...row];
|
|
825
|
-
}
|
|
826
|
-
const obj = {};
|
|
827
|
-
result.columns.forEach((col, i) => {
|
|
828
|
-
obj[col] = row[i];
|
|
829
|
-
});
|
|
830
|
-
return obj;
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
/**
|
|
834
|
-
* Execute the statement and return all rows.
|
|
835
|
-
*
|
|
836
|
-
* @param args - Optional array of parameter values or object with named parameters
|
|
837
|
-
* @returns Promise resolving to an array of all result rows
|
|
838
|
-
*
|
|
839
|
-
* @example
|
|
840
|
-
* ```typescript
|
|
841
|
-
* const stmt = client.prepare("SELECT * FROM users WHERE active = ?");
|
|
842
|
-
* const activeUsers = await stmt.all([true]);
|
|
843
|
-
* console.log(`Found ${activeUsers.length} active users`);
|
|
844
|
-
* ```
|
|
845
|
-
*/
|
|
846
|
-
async all(args, queryOptions) {
|
|
847
|
-
return await this.withLock(async () => {
|
|
848
|
-
const normalizedArgs = normalizeArgs(args);
|
|
849
|
-
const result = await this.session.execute(this.sql, normalizedArgs, this.safeIntegerMode, queryOptions);
|
|
850
|
-
if (this.presentationMode === "pluck") {
|
|
851
|
-
return result.rows.map((row) => row[0]);
|
|
852
|
-
}
|
|
853
|
-
if (this.presentationMode === "raw") {
|
|
854
|
-
return result.rows.map((row) => [...row]);
|
|
855
|
-
}
|
|
856
|
-
return result.rows.map((row) => {
|
|
857
|
-
const obj = {};
|
|
858
|
-
result.columns.forEach((col, i) => {
|
|
859
|
-
obj[col] = row[i];
|
|
860
|
-
});
|
|
861
|
-
return obj;
|
|
862
|
-
});
|
|
863
|
-
});
|
|
864
|
-
}
|
|
865
|
-
/**
|
|
866
|
-
* Execute the statement and return an async iterator for streaming results.
|
|
867
|
-
*
|
|
868
|
-
* This method provides memory-efficient processing of large result sets
|
|
869
|
-
* by streaming rows one at a time instead of loading everything into memory.
|
|
870
|
-
*
|
|
871
|
-
* @param args - Optional array of parameter values or object with named parameters
|
|
872
|
-
* @returns AsyncGenerator that yields individual rows
|
|
873
|
-
*
|
|
874
|
-
* @example
|
|
875
|
-
* ```typescript
|
|
876
|
-
* const stmt = client.prepare("SELECT * FROM large_table WHERE category = ?");
|
|
877
|
-
* for await (const row of stmt.iterate(['electronics'])) {
|
|
878
|
-
* // Process each row individually
|
|
879
|
-
* console.log(row.id, row.name);
|
|
880
|
-
* }
|
|
881
|
-
* ```
|
|
882
|
-
*/
|
|
883
|
-
async *iterate(args, queryOptions) {
|
|
884
|
-
if (this.execLock) {
|
|
885
|
-
const rows = await this.all(args, queryOptions);
|
|
886
|
-
for (const row of rows) {
|
|
887
|
-
yield row;
|
|
888
|
-
}
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
const normalizedArgs = normalizeArgs(args);
|
|
892
|
-
const { entries } = await this.session.executeRaw(this.sql, normalizedArgs, queryOptions);
|
|
893
|
-
let columns = [];
|
|
894
|
-
for await (const entry of entries) {
|
|
895
|
-
switch (entry.type) {
|
|
896
|
-
case "step_begin":
|
|
897
|
-
if (entry.cols) {
|
|
898
|
-
columns = entry.cols.map((col) => col.name);
|
|
899
|
-
}
|
|
900
|
-
break;
|
|
901
|
-
case "row":
|
|
902
|
-
if (entry.row) {
|
|
903
|
-
const decodedRow = entry.row.map((value) => decodeValue(value, this.safeIntegerMode));
|
|
904
|
-
if (this.presentationMode === "pluck") {
|
|
905
|
-
yield decodedRow[0];
|
|
906
|
-
} else if (this.presentationMode === "raw") {
|
|
907
|
-
yield decodedRow;
|
|
908
|
-
} else {
|
|
909
|
-
const obj = {};
|
|
910
|
-
columns.forEach((col, i) => {
|
|
911
|
-
obj[col] = decodedRow[i];
|
|
912
|
-
});
|
|
913
|
-
yield obj;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
break;
|
|
917
|
-
case "step_error":
|
|
918
|
-
case "error":
|
|
919
|
-
throw new DatabaseError(entry.error?.message || "SQL execution failed");
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
};
|
|
924
|
-
|
|
925
|
-
// src/connection.ts
|
|
926
|
-
var Connection = class {
|
|
927
|
-
constructor(config) {
|
|
928
|
-
this.isOpen = true;
|
|
929
|
-
this.defaultSafeIntegerMode = false;
|
|
930
|
-
this._inTransaction = false;
|
|
931
|
-
this.execLock = new AsyncLock();
|
|
932
|
-
if (!config.url) {
|
|
933
|
-
throw new Error("invalid config: url is required");
|
|
934
|
-
}
|
|
935
|
-
this.config = config;
|
|
936
|
-
this.session = new Session(config);
|
|
937
|
-
Object.defineProperty(this, "inTransaction", {
|
|
938
|
-
get: () => this._inTransaction,
|
|
939
|
-
enumerable: true
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
/**
|
|
943
|
-
* Whether the database is currently in a transaction.
|
|
944
|
-
*/
|
|
945
|
-
get inTransaction() {
|
|
946
|
-
return this._inTransaction;
|
|
947
|
-
}
|
|
948
|
-
/**
|
|
949
|
-
* Prepare a SQL statement for execution.
|
|
950
|
-
*
|
|
951
|
-
* Prepared statements created from a Connection use the same underlying session so transaction boundaries are preserved.
|
|
952
|
-
* This method fetches column metadata using the describe functionality.
|
|
953
|
-
*
|
|
954
|
-
* @param sql - The SQL statement to prepare
|
|
955
|
-
* @returns A Promise that resolves to a Statement object with column metadata
|
|
956
|
-
*
|
|
957
|
-
* @example
|
|
958
|
-
* ```typescript
|
|
959
|
-
* const stmt = await client.prepare("SELECT * FROM users WHERE id = ?");
|
|
960
|
-
* const columns = stmt.columns();
|
|
961
|
-
* const user = await stmt.get([123]);
|
|
962
|
-
* ```
|
|
963
|
-
*/
|
|
964
|
-
async prepare(sql) {
|
|
965
|
-
if (!this.isOpen) {
|
|
966
|
-
throw new TypeError("The database connection is not open");
|
|
967
|
-
}
|
|
968
|
-
await this.execLock.acquire();
|
|
969
|
-
let description;
|
|
970
|
-
try {
|
|
971
|
-
description = await this.session.describe(sql);
|
|
972
|
-
} finally {
|
|
973
|
-
this.execLock.release();
|
|
974
|
-
}
|
|
975
|
-
const stmt = Statement.fromSession(this.session, sql, description.cols, this.execLock);
|
|
976
|
-
if (this.defaultSafeIntegerMode) {
|
|
977
|
-
stmt.safeIntegers(true);
|
|
978
|
-
}
|
|
979
|
-
return stmt;
|
|
980
|
-
}
|
|
981
|
-
/**
|
|
982
|
-
* Like `prepare(sql).run(args)` but in a single round trip — skips `describe`
|
|
983
|
-
* since run() does not need column metadata.
|
|
984
|
-
*/
|
|
985
|
-
async run(sql, ...bindParameters) {
|
|
986
|
-
if (!this.isOpen) throw new TypeError("The database connection is not open");
|
|
987
|
-
const { params, queryOptions } = splitBindParameters(bindParameters);
|
|
988
|
-
await this.execLock.acquire();
|
|
989
|
-
try {
|
|
990
|
-
const result = await this.session.execute(sql, normalizeArgs(params), this.defaultSafeIntegerMode, queryOptions);
|
|
991
|
-
return { changes: result.rowsAffected, lastInsertRowid: result.lastInsertRowid };
|
|
992
|
-
} finally {
|
|
993
|
-
this.execLock.release();
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
/**
|
|
997
|
-
* Like `prepare(sql).get(args)` but in a single round trip.
|
|
998
|
-
*/
|
|
999
|
-
async get(sql, ...bindParameters) {
|
|
1000
|
-
if (!this.isOpen) throw new TypeError("The database connection is not open");
|
|
1001
|
-
const { params, queryOptions } = splitBindParameters(bindParameters);
|
|
1002
|
-
await this.execLock.acquire();
|
|
1003
|
-
try {
|
|
1004
|
-
const result = await this.session.execute(sql, normalizeArgs(params), this.defaultSafeIntegerMode, queryOptions);
|
|
1005
|
-
const row = result.rows[0];
|
|
1006
|
-
if (!row) return void 0;
|
|
1007
|
-
const obj = {};
|
|
1008
|
-
result.columns.forEach((col, i) => {
|
|
1009
|
-
obj[col] = row[i];
|
|
1010
|
-
});
|
|
1011
|
-
return obj;
|
|
1012
|
-
} finally {
|
|
1013
|
-
this.execLock.release();
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
/**
|
|
1017
|
-
* Like `prepare(sql).all(args)` but in a single round trip.
|
|
1018
|
-
*/
|
|
1019
|
-
async all(sql, ...bindParameters) {
|
|
1020
|
-
if (!this.isOpen) throw new TypeError("The database connection is not open");
|
|
1021
|
-
const { params, queryOptions } = splitBindParameters(bindParameters);
|
|
1022
|
-
await this.execLock.acquire();
|
|
1023
|
-
try {
|
|
1024
|
-
const result = await this.session.execute(sql, normalizeArgs(params), this.defaultSafeIntegerMode, queryOptions);
|
|
1025
|
-
return result.rows.map((row) => {
|
|
1026
|
-
const obj = {};
|
|
1027
|
-
result.columns.forEach((col, i) => {
|
|
1028
|
-
obj[col] = row[i];
|
|
1029
|
-
});
|
|
1030
|
-
return obj;
|
|
1031
|
-
});
|
|
1032
|
-
} finally {
|
|
1033
|
-
this.execLock.release();
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
/**
|
|
1037
|
-
* Like `prepare(sql).iterate(args)` but in a single round trip. Buffers the
|
|
1038
|
-
* result set — the connection lock cannot be held across `yield` points
|
|
1039
|
-
* without risking deadlock on nested calls.
|
|
1040
|
-
*/
|
|
1041
|
-
async *iterate(sql, ...bindParameters) {
|
|
1042
|
-
for (const row of await this.all(sql, ...bindParameters)) yield row;
|
|
1043
|
-
}
|
|
1044
|
-
/**
|
|
1045
|
-
* Execute a SQL statement and return all results.
|
|
1046
|
-
*
|
|
1047
|
-
* @param sql - The SQL statement to execute
|
|
1048
|
-
* @param args - Optional array of parameter values
|
|
1049
|
-
* @returns Promise resolving to the complete result set
|
|
1050
|
-
*
|
|
1051
|
-
* @example
|
|
1052
|
-
* ```typescript
|
|
1053
|
-
* const result = await client.execute("SELECT * FROM users WHERE id = ?", [123]);
|
|
1054
|
-
* console.log(result.rows);
|
|
1055
|
-
* ```
|
|
1056
|
-
*/
|
|
1057
|
-
async execute(sql, args, queryOptions) {
|
|
1058
|
-
if (!this.isOpen) {
|
|
1059
|
-
throw new TypeError("The database connection is not open");
|
|
1060
|
-
}
|
|
1061
|
-
await this.execLock.acquire();
|
|
1062
|
-
try {
|
|
1063
|
-
return await this.session.execute(sql, args || [], this.defaultSafeIntegerMode, queryOptions);
|
|
1064
|
-
} finally {
|
|
1065
|
-
this.execLock.release();
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
/**
|
|
1069
|
-
* Execute a SQL statement and return all results.
|
|
1070
|
-
*
|
|
1071
|
-
* @param sql - The SQL statement to execute
|
|
1072
|
-
* @returns Promise resolving to the complete result set
|
|
1073
|
-
*
|
|
1074
|
-
* @example
|
|
1075
|
-
* ```typescript
|
|
1076
|
-
* const result = await client.exec("SELECT * FROM users");
|
|
1077
|
-
* console.log(result.rows);
|
|
1078
|
-
* ```
|
|
1079
|
-
*/
|
|
1080
|
-
async exec(sql, queryOptions) {
|
|
1081
|
-
if (!this.isOpen) {
|
|
1082
|
-
throw new TypeError("The database connection is not open");
|
|
1083
|
-
}
|
|
1084
|
-
await this.execLock.acquire();
|
|
1085
|
-
try {
|
|
1086
|
-
return await this.session.sequence(sql, queryOptions);
|
|
1087
|
-
} finally {
|
|
1088
|
-
this.execLock.release();
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
/**
|
|
1092
|
-
* Executes a batch of SQL statements over this connection.
|
|
1093
|
-
*
|
|
1094
|
-
* By default, batch() is not transactional: each statement runs in its
|
|
1095
|
-
* own autocommit step, so a failure mid-batch leaves earlier successful
|
|
1096
|
-
* statements committed. Pass a `mode` to make the batch atomic — the
|
|
1097
|
-
* statements are wrapped in `BEGIN <mode>` / `COMMIT` (with `ROLLBACK`
|
|
1098
|
-
* on failure) and dispatched as a single Hrana request, so the whole
|
|
1099
|
-
* batch completes in one round-trip. When called from inside a
|
|
1100
|
-
* `connection.transaction(...)` callback the `mode` argument is ignored
|
|
1101
|
-
* and the surrounding transaction is reused.
|
|
1102
|
-
*
|
|
1103
|
-
* When `mode` is set, `batch()` owns the surrounding
|
|
1104
|
-
* `BEGIN`/`COMMIT`/`ROLLBACK`, so the `statements` array must not
|
|
1105
|
-
* contain its own transaction-control SQL (`BEGIN`, `COMMIT`,
|
|
1106
|
-
* `ROLLBACK`, `SAVEPOINT`, `RELEASE`). The input is not validated
|
|
1107
|
-
* for that — a user-supplied `COMMIT` will close the wrapper
|
|
1108
|
-
* transaction mid-batch and leave earlier statements committed,
|
|
1109
|
-
* defeating the all-or-nothing contract.
|
|
1110
|
-
*
|
|
1111
|
-
* @param statements - An array of SQL strings or `{ sql, args }` objects.
|
|
1112
|
-
* @param mode - When set, makes the batch atomic. Accepts the same
|
|
1113
|
-
* values as `connection.transaction(...)` variants: `"deferred"`,
|
|
1114
|
-
* `"immediate"`, `"exclusive"`, `"concurrent"`. Ignored when already
|
|
1115
|
-
* inside a transaction.
|
|
1116
|
-
* @returns An object with `rowsAffected` (sum of affected rows) and
|
|
1117
|
-
* `lastInsertRowid` (rowid of the last successful insert).
|
|
1118
|
-
*
|
|
1119
|
-
* @example
|
|
1120
|
-
* // Plain SQL strings (non-atomic).
|
|
1121
|
-
* await db.batch([
|
|
1122
|
-
* "INSERT INTO users(name) VALUES ('Alice')",
|
|
1123
|
-
* "INSERT INTO users(name) VALUES ('Bob')",
|
|
1124
|
-
* ]);
|
|
1125
|
-
*
|
|
1126
|
-
* @example
|
|
1127
|
-
* // Positional and named bind parameters.
|
|
1128
|
-
* await db.batch([
|
|
1129
|
-
* { sql: "INSERT INTO users(name, email) VALUES (?, ?)", args: ["Carol", "carol@example.net"] },
|
|
1130
|
-
* { sql: "INSERT INTO users(name, email) VALUES (:name, :email)", args: { name: "Dave", email: "dave@example.net" } },
|
|
1131
|
-
* ]);
|
|
1132
|
-
*
|
|
1133
|
-
* @example
|
|
1134
|
-
* // Atomic via the mode parameter.
|
|
1135
|
-
* await db.batch([
|
|
1136
|
-
* { sql: "INSERT INTO users(name) VALUES (?)", args: ["Eve"] },
|
|
1137
|
-
* { sql: "INSERT INTO users(name) VALUES (?)", args: ["Frank"] },
|
|
1138
|
-
* ], "immediate");
|
|
1139
|
-
*
|
|
1140
|
-
* @example
|
|
1141
|
-
* // Atomic via the transaction() API for mixed workloads.
|
|
1142
|
-
* const txn = db.transaction(async () => {
|
|
1143
|
-
* await db.batch([{ sql: "INSERT INTO users(name) VALUES (?)", args: ["Eve"] }]);
|
|
1144
|
-
* await db.execute("UPDATE counters SET n = n + 1");
|
|
1145
|
-
* });
|
|
1146
|
-
* await txn.immediate();
|
|
1147
|
-
*/
|
|
1148
|
-
async batch(statements, mode, queryOptions) {
|
|
1149
|
-
if (!this.isOpen) {
|
|
1150
|
-
throw new TypeError("The database connection is not open");
|
|
1151
|
-
}
|
|
1152
|
-
await this.execLock.acquire();
|
|
1153
|
-
try {
|
|
1154
|
-
const effectiveMode = this._inTransaction ? void 0 : mode;
|
|
1155
|
-
return await this.session.batch(statements, effectiveMode, queryOptions);
|
|
1156
|
-
} finally {
|
|
1157
|
-
this.execLock.release();
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
/**
|
|
1161
|
-
* Execute a pragma.
|
|
1162
|
-
*
|
|
1163
|
-
* @param pragma - The pragma to execute
|
|
1164
|
-
* @returns Promise resolving to the result of the pragma
|
|
1165
|
-
*/
|
|
1166
|
-
async pragma(pragma, queryOptions) {
|
|
1167
|
-
if (!this.isOpen) {
|
|
1168
|
-
throw new TypeError("The database connection is not open");
|
|
1169
|
-
}
|
|
1170
|
-
await this.execLock.acquire();
|
|
1171
|
-
try {
|
|
1172
|
-
const sql = `PRAGMA ${pragma}`;
|
|
1173
|
-
return await this.session.execute(sql, [], false, queryOptions);
|
|
1174
|
-
} finally {
|
|
1175
|
-
this.execLock.release();
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
/**
|
|
1179
|
-
* Sets the default safe integers mode for all statements from this connection.
|
|
1180
|
-
*
|
|
1181
|
-
* @param toggle - Whether to use safe integers by default.
|
|
1182
|
-
*/
|
|
1183
|
-
defaultSafeIntegers(toggle) {
|
|
1184
|
-
this.defaultSafeIntegerMode = toggle === false ? false : true;
|
|
1185
|
-
}
|
|
1186
|
-
/**
|
|
1187
|
-
* Returns a function that executes the given function in a transaction.
|
|
1188
|
-
*
|
|
1189
|
-
* @param fn - The function to wrap in a transaction
|
|
1190
|
-
* @returns A function that will execute fn within a transaction
|
|
1191
|
-
*
|
|
1192
|
-
* @example
|
|
1193
|
-
* ```typescript
|
|
1194
|
-
* const insert = await client.prepare("INSERT INTO users (name) VALUES (?)");
|
|
1195
|
-
* const insertMany = client.transaction((users) => {
|
|
1196
|
-
* for (const user of users) {
|
|
1197
|
-
* insert.run([user]);
|
|
1198
|
-
* }
|
|
1199
|
-
* });
|
|
1200
|
-
*
|
|
1201
|
-
* await insertMany(['Alice', 'Bob', 'Charlie']);
|
|
1202
|
-
* ```
|
|
1203
|
-
*/
|
|
1204
|
-
transaction(fn) {
|
|
1205
|
-
if (typeof fn !== "function") {
|
|
1206
|
-
throw new TypeError("Expected first argument to be a function");
|
|
1207
|
-
}
|
|
1208
|
-
const db = this;
|
|
1209
|
-
const wrapTxn = (mode) => {
|
|
1210
|
-
return async (...bindParameters) => {
|
|
1211
|
-
await db.exec("BEGIN " + mode);
|
|
1212
|
-
db._inTransaction = true;
|
|
1213
|
-
try {
|
|
1214
|
-
const result = await fn(...bindParameters);
|
|
1215
|
-
await db.exec("COMMIT");
|
|
1216
|
-
db._inTransaction = false;
|
|
1217
|
-
return result;
|
|
1218
|
-
} catch (err) {
|
|
1219
|
-
await db.exec("ROLLBACK");
|
|
1220
|
-
db._inTransaction = false;
|
|
1221
|
-
throw err;
|
|
1222
|
-
}
|
|
1223
|
-
};
|
|
1224
|
-
};
|
|
1225
|
-
const properties = {
|
|
1226
|
-
default: { value: wrapTxn("") },
|
|
1227
|
-
deferred: { value: wrapTxn("DEFERRED") },
|
|
1228
|
-
concurrent: { value: wrapTxn("CONCURRENT") },
|
|
1229
|
-
immediate: { value: wrapTxn("IMMEDIATE") },
|
|
1230
|
-
exclusive: { value: wrapTxn("EXCLUSIVE") },
|
|
1231
|
-
database: { value: this, enumerable: true }
|
|
1232
|
-
};
|
|
1233
|
-
Object.defineProperties(properties.default.value, properties);
|
|
1234
|
-
Object.defineProperties(properties.deferred.value, properties);
|
|
1235
|
-
Object.defineProperties(properties.concurrent.value, properties);
|
|
1236
|
-
Object.defineProperties(properties.immediate.value, properties);
|
|
1237
|
-
Object.defineProperties(properties.exclusive.value, properties);
|
|
1238
|
-
return properties.default.value;
|
|
1239
|
-
}
|
|
1240
|
-
/**
|
|
1241
|
-
* Close the connection.
|
|
1242
|
-
*
|
|
1243
|
-
* This sends a close request to the server to properly clean up the stream.
|
|
1244
|
-
*/
|
|
1245
|
-
async close() {
|
|
1246
|
-
this.isOpen = false;
|
|
1247
|
-
await this.session.close();
|
|
1248
|
-
}
|
|
1249
|
-
async reconnect() {
|
|
1250
|
-
try {
|
|
1251
|
-
if (this.isOpen) {
|
|
1252
|
-
await this.close();
|
|
1253
|
-
}
|
|
1254
|
-
} finally {
|
|
1255
|
-
this.session = new Session(this.config);
|
|
1256
|
-
this.isOpen = true;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
};
|
|
1260
|
-
function connect(config) {
|
|
1261
|
-
return new Connection(config);
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
export { Connection, DatabaseError, ENCRYPTION_KEY_HEADER, Session, Statement, TimeoutError, connect };
|
|
1
|
+
// Turso serverless driver entry point
|
|
2
|
+
export { Connection, connect } from './connection.js';
|
|
3
|
+
export { Statement } from './statement.js';
|
|
4
|
+
export { Session } from './session.js';
|
|
5
|
+
export { DatabaseError, TimeoutError } from './error.js';
|
|
6
|
+
export { ENCRYPTION_KEY_HEADER } from './protocol.js';
|