@tursodatabase/serverless 1.1.0-pre.1 → 1.1.0-pre.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compat.d.ts +2 -3
- package/dist/compat.js +118 -1
- package/dist/connection.d.ts +1 -1
- package/dist/connection.js +2 -2
- package/dist/statement.d.ts +11 -2
- package/dist/statement.js +82 -40
- package/package.json +1 -1
package/dist/compat.d.ts
CHANGED
|
@@ -87,11 +87,10 @@ export declare class LibsqlError extends Error {
|
|
|
87
87
|
constructor(message: string, code: string, extendedCode?: string, rawCode?: number, cause?: Error);
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
90
|
-
* Interactive transaction interface
|
|
90
|
+
* Interactive transaction interface.
|
|
91
91
|
*
|
|
92
92
|
* @remarks
|
|
93
|
-
*
|
|
94
|
-
* Calling transaction() will throw a LibsqlError.
|
|
93
|
+
* A transaction keeps a dedicated session open until commit/rollback/close.
|
|
95
94
|
*/
|
|
96
95
|
export interface Transaction {
|
|
97
96
|
execute(stmt: InStatement): Promise<ResultSet>;
|
package/dist/compat.js
CHANGED
|
@@ -25,6 +25,7 @@ class LibSQLClient {
|
|
|
25
25
|
authToken: config.authToken || '',
|
|
26
26
|
remoteEncryptionKey: config.remoteEncryptionKey
|
|
27
27
|
};
|
|
28
|
+
this.sessionConfig = sessionConfig;
|
|
28
29
|
this.session = new Session(sessionConfig);
|
|
29
30
|
}
|
|
30
31
|
validateConfig(config) {
|
|
@@ -157,8 +158,124 @@ class LibSQLClient {
|
|
|
157
158
|
// For now, just call batch - in a real implementation this would disable foreign keys
|
|
158
159
|
return this.batch(stmts, "write");
|
|
159
160
|
}
|
|
161
|
+
modeToBeginSql(mode) {
|
|
162
|
+
switch (mode) {
|
|
163
|
+
case "write":
|
|
164
|
+
return "BEGIN IMMEDIATE";
|
|
165
|
+
case "deferred":
|
|
166
|
+
return "BEGIN DEFERRED";
|
|
167
|
+
case "read":
|
|
168
|
+
default:
|
|
169
|
+
return "BEGIN";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
160
172
|
async transaction(mode) {
|
|
161
|
-
|
|
173
|
+
await this.execLock.acquire();
|
|
174
|
+
if (this._closed) {
|
|
175
|
+
this.execLock.release();
|
|
176
|
+
throw new LibsqlError("Client is closed", "CLIENT_CLOSED");
|
|
177
|
+
}
|
|
178
|
+
const txSession = new Session(this.sessionConfig);
|
|
179
|
+
let txClosed = false;
|
|
180
|
+
let cleanupStarted = false;
|
|
181
|
+
const ensureOpen = () => {
|
|
182
|
+
if (txClosed) {
|
|
183
|
+
throw new LibsqlError("Transaction is closed", "TRANSACTION_CLOSED");
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
const closeTx = async () => {
|
|
187
|
+
if (cleanupStarted)
|
|
188
|
+
return;
|
|
189
|
+
cleanupStarted = true;
|
|
190
|
+
txClosed = true;
|
|
191
|
+
try {
|
|
192
|
+
await txSession.close();
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
this.execLock.release();
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const executeInTx = async (stmt) => {
|
|
199
|
+
ensureOpen();
|
|
200
|
+
const normalized = this.normalizeStatement(stmt);
|
|
201
|
+
try {
|
|
202
|
+
const result = await txSession.execute(normalized.sql, normalized.args, this._defaultSafeIntegers);
|
|
203
|
+
return this.convertResult(result);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
throw mapDatabaseError(error, "EXECUTE_ERROR");
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
try {
|
|
210
|
+
await txSession.sequence(this.modeToBeginSql(mode));
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
await closeTx();
|
|
214
|
+
throw mapDatabaseError(error, "BEGIN_ERROR");
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
execute: async (stmtOrSql, args) => {
|
|
218
|
+
if (typeof stmtOrSql === "string") {
|
|
219
|
+
const normalizedArgs = args ? (Array.isArray(args) ? args : Object.values(args)) : [];
|
|
220
|
+
return executeInTx({ sql: stmtOrSql, args: normalizedArgs });
|
|
221
|
+
}
|
|
222
|
+
return executeInTx(stmtOrSql);
|
|
223
|
+
},
|
|
224
|
+
batch: async (stmts) => {
|
|
225
|
+
ensureOpen();
|
|
226
|
+
const results = [];
|
|
227
|
+
for (const stmt of stmts) {
|
|
228
|
+
results.push(await executeInTx(stmt));
|
|
229
|
+
}
|
|
230
|
+
return results;
|
|
231
|
+
},
|
|
232
|
+
executeMultiple: async (sql) => {
|
|
233
|
+
ensureOpen();
|
|
234
|
+
try {
|
|
235
|
+
await txSession.sequence(sql);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
throw mapDatabaseError(error, "EXECUTE_MULTIPLE_ERROR");
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
commit: async () => {
|
|
242
|
+
ensureOpen();
|
|
243
|
+
try {
|
|
244
|
+
await txSession.sequence("COMMIT");
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
throw mapDatabaseError(error, "COMMIT_ERROR");
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
await closeTx();
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
rollback: async () => {
|
|
254
|
+
ensureOpen();
|
|
255
|
+
try {
|
|
256
|
+
await txSession.sequence("ROLLBACK");
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
throw mapDatabaseError(error, "ROLLBACK_ERROR");
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
await closeTx();
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
close: () => {
|
|
266
|
+
if (txClosed)
|
|
267
|
+
return;
|
|
268
|
+
txClosed = true;
|
|
269
|
+
void txSession.sequence("ROLLBACK")
|
|
270
|
+
.catch(() => undefined)
|
|
271
|
+
.finally(() => {
|
|
272
|
+
void closeTx();
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
get closed() {
|
|
276
|
+
return txClosed;
|
|
277
|
+
},
|
|
278
|
+
};
|
|
162
279
|
}
|
|
163
280
|
async executeMultiple(sql) {
|
|
164
281
|
await this.execLock.acquire();
|
package/dist/connection.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ export declare class Connection {
|
|
|
65
65
|
/**
|
|
66
66
|
* Prepare a SQL statement for execution.
|
|
67
67
|
*
|
|
68
|
-
*
|
|
68
|
+
* Prepared statements created from a Connection use the same underlying session so transaction boundaries are preserved.
|
|
69
69
|
* This method fetches column metadata using the describe functionality.
|
|
70
70
|
*
|
|
71
71
|
* @param sql - The SQL statement to prepare
|
package/dist/connection.js
CHANGED
|
@@ -71,7 +71,7 @@ export class Connection {
|
|
|
71
71
|
/**
|
|
72
72
|
* Prepare a SQL statement for execution.
|
|
73
73
|
*
|
|
74
|
-
*
|
|
74
|
+
* Prepared statements created from a Connection use the same underlying session so transaction boundaries are preserved.
|
|
75
75
|
* This method fetches column metadata using the describe functionality.
|
|
76
76
|
*
|
|
77
77
|
* @param sql - The SQL statement to prepare
|
|
@@ -92,7 +92,7 @@ export class Connection {
|
|
|
92
92
|
const session = new Session(this.config);
|
|
93
93
|
const description = await session.describe(sql);
|
|
94
94
|
await session.close();
|
|
95
|
-
const stmt =
|
|
95
|
+
const stmt = Statement.fromSession(this.session, sql, description.cols, this.execLock);
|
|
96
96
|
if (this.defaultSafeIntegerMode) {
|
|
97
97
|
stmt.safeIntegers(true);
|
|
98
98
|
}
|
package/dist/statement.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type Column, type QueryOptions } from './protocol.js';
|
|
2
|
-
import { type SessionConfig } from './session.js';
|
|
2
|
+
import { Session, type SessionConfig } from './session.js';
|
|
3
|
+
import { type AsyncLock } from './async-lock.js';
|
|
3
4
|
/**
|
|
4
5
|
* A prepared SQL statement that can be executed in multiple ways.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
+
* Statements may either own a dedicated session or share a connection session to preserve transaction boundaries.
|
|
7
8
|
* Provides three execution modes:
|
|
8
9
|
* - `get(args?)`: Returns the first row or null
|
|
9
10
|
* - `all(args?)`: Returns all rows as an array
|
|
@@ -15,7 +16,14 @@ export declare class Statement {
|
|
|
15
16
|
private presentationMode;
|
|
16
17
|
private safeIntegerMode;
|
|
17
18
|
private columnMetadata;
|
|
19
|
+
private execLock?;
|
|
18
20
|
constructor(sessionConfig: SessionConfig, sql: string, columns?: Column[]);
|
|
21
|
+
/**
|
|
22
|
+
* Create a Statement that shares an existing session and serializes execution
|
|
23
|
+
* through the given lock. Used by Connection.prepare() so prepared statements
|
|
24
|
+
* participate in the connection's transaction scope.
|
|
25
|
+
*/
|
|
26
|
+
static fromSession(session: Session, sql: string, columns: Column[] | undefined, execLock: AsyncLock): Statement;
|
|
19
27
|
/**
|
|
20
28
|
* Whether the prepared statement returns data.
|
|
21
29
|
*
|
|
@@ -81,6 +89,7 @@ export declare class Statement {
|
|
|
81
89
|
* ```
|
|
82
90
|
*/
|
|
83
91
|
columns(): any[];
|
|
92
|
+
private withLock;
|
|
84
93
|
/**
|
|
85
94
|
* Executes the prepared statement.
|
|
86
95
|
*
|
package/dist/statement.js
CHANGED
|
@@ -4,7 +4,7 @@ import { DatabaseError } from './error.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* A prepared SQL statement that can be executed in multiple ways.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Statements may either own a dedicated session or share a connection session to preserve transaction boundaries.
|
|
8
8
|
* Provides three execution modes:
|
|
9
9
|
* - `get(args?)`: Returns the first row or null
|
|
10
10
|
* - `all(args?)`: Returns all rows as an array
|
|
@@ -18,6 +18,21 @@ export class Statement {
|
|
|
18
18
|
this.sql = sql;
|
|
19
19
|
this.columnMetadata = columns || [];
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Create a Statement that shares an existing session and serializes execution
|
|
23
|
+
* through the given lock. Used by Connection.prepare() so prepared statements
|
|
24
|
+
* participate in the connection's transaction scope.
|
|
25
|
+
*/
|
|
26
|
+
static fromSession(session, sql, columns, execLock) {
|
|
27
|
+
const stmt = Object.create(Statement.prototype);
|
|
28
|
+
stmt.session = session;
|
|
29
|
+
stmt.sql = sql;
|
|
30
|
+
stmt.columnMetadata = columns || [];
|
|
31
|
+
stmt.presentationMode = 'expanded';
|
|
32
|
+
stmt.safeIntegerMode = false;
|
|
33
|
+
stmt.execLock = execLock;
|
|
34
|
+
return stmt;
|
|
35
|
+
}
|
|
21
36
|
/**
|
|
22
37
|
* Whether the prepared statement returns data.
|
|
23
38
|
*
|
|
@@ -99,6 +114,18 @@ export class Statement {
|
|
|
99
114
|
type: col.decltype
|
|
100
115
|
}));
|
|
101
116
|
}
|
|
117
|
+
async withLock(fn) {
|
|
118
|
+
if (!this.execLock) {
|
|
119
|
+
return await fn();
|
|
120
|
+
}
|
|
121
|
+
await this.execLock.acquire();
|
|
122
|
+
try {
|
|
123
|
+
return await fn();
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
this.execLock.release();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
102
129
|
/**
|
|
103
130
|
* Executes the prepared statement.
|
|
104
131
|
*
|
|
@@ -113,9 +140,11 @@ export class Statement {
|
|
|
113
140
|
* ```
|
|
114
141
|
*/
|
|
115
142
|
async run(args, queryOptions) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
143
|
+
return await this.withLock(async () => {
|
|
144
|
+
const normalizedArgs = this.normalizeArgs(args);
|
|
145
|
+
const result = await this.session.execute(this.sql, normalizedArgs, this.safeIntegerMode, queryOptions);
|
|
146
|
+
return { changes: result.rowsAffected, lastInsertRowid: result.lastInsertRowid };
|
|
147
|
+
});
|
|
119
148
|
}
|
|
120
149
|
/**
|
|
121
150
|
* Execute the statement and return the first row.
|
|
@@ -133,27 +162,29 @@ export class Statement {
|
|
|
133
162
|
* ```
|
|
134
163
|
*/
|
|
135
164
|
async get(args, queryOptions) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
165
|
+
return await this.withLock(async () => {
|
|
166
|
+
const normalizedArgs = this.normalizeArgs(args);
|
|
167
|
+
const result = await this.session.execute(this.sql, normalizedArgs, this.safeIntegerMode, queryOptions);
|
|
168
|
+
const row = result.rows[0];
|
|
169
|
+
if (!row) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
if (this.presentationMode === 'pluck') {
|
|
173
|
+
// In pluck mode, return only the first column value
|
|
174
|
+
return row[0];
|
|
175
|
+
}
|
|
176
|
+
if (this.presentationMode === 'raw') {
|
|
177
|
+
// In raw mode, return the row as a plain array (it already is one)
|
|
178
|
+
// The row object is already an array with column properties added
|
|
179
|
+
return [...row];
|
|
180
|
+
}
|
|
181
|
+
// In expanded mode, convert to plain object with named properties
|
|
182
|
+
const obj = {};
|
|
183
|
+
result.columns.forEach((col, i) => {
|
|
184
|
+
obj[col] = row[i];
|
|
185
|
+
});
|
|
186
|
+
return obj;
|
|
155
187
|
});
|
|
156
|
-
return obj;
|
|
157
188
|
}
|
|
158
189
|
/**
|
|
159
190
|
* Execute the statement and return all rows.
|
|
@@ -169,22 +200,24 @@ export class Statement {
|
|
|
169
200
|
* ```
|
|
170
201
|
*/
|
|
171
202
|
async all(args, queryOptions) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
203
|
+
return await this.withLock(async () => {
|
|
204
|
+
const normalizedArgs = this.normalizeArgs(args);
|
|
205
|
+
const result = await this.session.execute(this.sql, normalizedArgs, this.safeIntegerMode, queryOptions);
|
|
206
|
+
if (this.presentationMode === 'pluck') {
|
|
207
|
+
// In pluck mode, return only the first column value from each row
|
|
208
|
+
return result.rows.map((row) => row[0]);
|
|
209
|
+
}
|
|
210
|
+
if (this.presentationMode === 'raw') {
|
|
211
|
+
return result.rows.map((row) => [...row]);
|
|
212
|
+
}
|
|
213
|
+
// In expanded mode, convert rows to plain objects with named properties
|
|
214
|
+
return result.rows.map((row) => {
|
|
215
|
+
const obj = {};
|
|
216
|
+
result.columns.forEach((col, i) => {
|
|
217
|
+
obj[col] = row[i];
|
|
218
|
+
});
|
|
219
|
+
return obj;
|
|
186
220
|
});
|
|
187
|
-
return obj;
|
|
188
221
|
});
|
|
189
222
|
}
|
|
190
223
|
/**
|
|
@@ -206,8 +239,17 @@ export class Statement {
|
|
|
206
239
|
* ```
|
|
207
240
|
*/
|
|
208
241
|
async *iterate(args, queryOptions) {
|
|
242
|
+
// Shared-connection statements must not hold the connection lock across
|
|
243
|
+
// `yield` points, or nested queries in the loop body can deadlock.
|
|
244
|
+
if (this.execLock) {
|
|
245
|
+
const rows = await this.all(args, queryOptions);
|
|
246
|
+
for (const row of rows) {
|
|
247
|
+
yield row;
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
209
251
|
const normalizedArgs = this.normalizeArgs(args);
|
|
210
|
-
const {
|
|
252
|
+
const { entries } = await this.session.executeRaw(this.sql, normalizedArgs, queryOptions);
|
|
211
253
|
let columns = [];
|
|
212
254
|
for await (const entry of entries) {
|
|
213
255
|
switch (entry.type) {
|