@tursodatabase/serverless 0.2.2 → 0.2.4
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.js +14 -0
- package/dist/connection.d.ts +62 -8
- package/dist/connection.js +95 -13
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class AsyncLock {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.locked = false;
|
|
4
|
+
this.queue = [];
|
|
5
|
+
}
|
|
6
|
+
async acquire() {
|
|
7
|
+
if (!this.locked) {
|
|
8
|
+
this.locked = true;
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
return new Promise(resolve => { this.queue.push(resolve); });
|
|
12
|
+
}
|
|
13
|
+
release() {
|
|
14
|
+
const next = this.queue.shift();
|
|
15
|
+
if (next) {
|
|
16
|
+
next();
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
this.locked = false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
package/dist/compat.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Session } from './session.js';
|
|
2
|
+
import { AsyncLock } from './async-lock.js';
|
|
2
3
|
import { DatabaseError } from './error.js';
|
|
3
4
|
/**
|
|
4
5
|
* libSQL-compatible error class with error codes.
|
|
@@ -15,6 +16,7 @@ export class LibsqlError extends Error {
|
|
|
15
16
|
}
|
|
16
17
|
class LibSQLClient {
|
|
17
18
|
constructor(config) {
|
|
19
|
+
this.execLock = new AsyncLock();
|
|
18
20
|
this._closed = false;
|
|
19
21
|
this._defaultSafeIntegers = false;
|
|
20
22
|
this.validateConfig(config);
|
|
@@ -101,6 +103,7 @@ class LibSQLClient {
|
|
|
101
103
|
return resultSet;
|
|
102
104
|
}
|
|
103
105
|
async execute(stmtOrSql, args) {
|
|
106
|
+
await this.execLock.acquire();
|
|
104
107
|
try {
|
|
105
108
|
if (this._closed) {
|
|
106
109
|
throw new LibsqlError("Client is closed", "CLIENT_CLOSED");
|
|
@@ -122,8 +125,12 @@ class LibSQLClient {
|
|
|
122
125
|
}
|
|
123
126
|
throw mapDatabaseError(error, "EXECUTE_ERROR");
|
|
124
127
|
}
|
|
128
|
+
finally {
|
|
129
|
+
this.execLock.release();
|
|
130
|
+
}
|
|
125
131
|
}
|
|
126
132
|
async batch(stmts, mode) {
|
|
133
|
+
await this.execLock.acquire();
|
|
127
134
|
try {
|
|
128
135
|
if (this._closed) {
|
|
129
136
|
throw new LibsqlError("Client is closed", "CLIENT_CLOSED");
|
|
@@ -142,6 +149,9 @@ class LibSQLClient {
|
|
|
142
149
|
}
|
|
143
150
|
throw mapDatabaseError(error, "BATCH_ERROR");
|
|
144
151
|
}
|
|
152
|
+
finally {
|
|
153
|
+
this.execLock.release();
|
|
154
|
+
}
|
|
145
155
|
}
|
|
146
156
|
async migrate(stmts) {
|
|
147
157
|
// For now, just call batch - in a real implementation this would disable foreign keys
|
|
@@ -151,6 +161,7 @@ class LibSQLClient {
|
|
|
151
161
|
throw new LibsqlError("Transactions not implemented", "NOT_IMPLEMENTED");
|
|
152
162
|
}
|
|
153
163
|
async executeMultiple(sql) {
|
|
164
|
+
await this.execLock.acquire();
|
|
154
165
|
try {
|
|
155
166
|
if (this._closed) {
|
|
156
167
|
throw new LibsqlError("Client is closed", "CLIENT_CLOSED");
|
|
@@ -163,6 +174,9 @@ class LibSQLClient {
|
|
|
163
174
|
}
|
|
164
175
|
throw mapDatabaseError(error, "EXECUTE_MULTIPLE_ERROR");
|
|
165
176
|
}
|
|
177
|
+
finally {
|
|
178
|
+
this.execLock.release();
|
|
179
|
+
}
|
|
166
180
|
}
|
|
167
181
|
async sync() {
|
|
168
182
|
throw new LibsqlError("Sync not supported for remote databases", "NOT_SUPPORTED");
|
package/dist/connection.d.ts
CHANGED
|
@@ -9,7 +9,45 @@ export interface Config extends SessionConfig {
|
|
|
9
9
|
* A connection to a Turso database.
|
|
10
10
|
*
|
|
11
11
|
* Provides methods for executing SQL statements and managing prepared statements.
|
|
12
|
-
* Uses the SQL over HTTP protocol with streaming cursor support
|
|
12
|
+
* Uses the SQL over HTTP protocol with streaming cursor support.
|
|
13
|
+
*
|
|
14
|
+
* ## Concurrency model
|
|
15
|
+
*
|
|
16
|
+
* A Connection is **single-stream**: it can only run one statement at a time.
|
|
17
|
+
* This is not an implementation quirk — it follows from the SQL over HTTP protocol,
|
|
18
|
+
* where each request carries a baton from the previous response to sequence operations
|
|
19
|
+
* on the server. Concurrent calls on the same connection would race on that baton
|
|
20
|
+
* and corrupt the stream. This is the same model as SQLite itself (one execution
|
|
21
|
+
* at a time per connection).
|
|
22
|
+
*
|
|
23
|
+
* If you call `execute()` while another is in flight, the call automatically
|
|
24
|
+
* waits for the previous one to finish — just like the native
|
|
25
|
+
* `@tursodatabase/database` binding.
|
|
26
|
+
*
|
|
27
|
+
* ## Parallel queries
|
|
28
|
+
*
|
|
29
|
+
* For parallelism, create multiple connections. `connect()` is cheap — it just
|
|
30
|
+
* allocates a config object. No TCP connection is opened until the first `execute()`,
|
|
31
|
+
* and the underlying `fetch()` runtime automatically pools and reuses TCP/TLS
|
|
32
|
+
* connections to the same origin.
|
|
33
|
+
*
|
|
34
|
+
* ```typescript
|
|
35
|
+
* import { connect } from "@tursodatabase/serverless";
|
|
36
|
+
*
|
|
37
|
+
* const config = { url: process.env.TURSO_URL, authToken: process.env.TURSO_TOKEN };
|
|
38
|
+
*
|
|
39
|
+
* // Option 1: one connection per parallel query
|
|
40
|
+
* const [users, orders] = await Promise.all([
|
|
41
|
+
* connect(config).execute("SELECT * FROM users WHERE active = 1"),
|
|
42
|
+
* connect(config).execute("SELECT * FROM orders WHERE status = 'pending'"),
|
|
43
|
+
* ]);
|
|
44
|
+
*
|
|
45
|
+
* // Option 2: reusable pool for repeated parallel work
|
|
46
|
+
* const pool = Array.from({ length: 4 }, () => connect(config));
|
|
47
|
+
* const results = await Promise.all(
|
|
48
|
+
* queries.map((sql, i) => pool[i % pool.length].execute(sql))
|
|
49
|
+
* );
|
|
50
|
+
* ```
|
|
13
51
|
*/
|
|
14
52
|
export declare class Connection {
|
|
15
53
|
private config;
|
|
@@ -17,6 +55,7 @@ export declare class Connection {
|
|
|
17
55
|
private isOpen;
|
|
18
56
|
private defaultSafeIntegerMode;
|
|
19
57
|
private _inTransaction;
|
|
58
|
+
private execLock;
|
|
20
59
|
constructor(config: Config);
|
|
21
60
|
/**
|
|
22
61
|
* Whether the database is currently in a transaction.
|
|
@@ -126,17 +165,32 @@ export declare class Connection {
|
|
|
126
165
|
/**
|
|
127
166
|
* Create a new connection to a Turso database.
|
|
128
167
|
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
168
|
+
* This is a lightweight operation — it only allocates a config object. No network
|
|
169
|
+
* I/O happens until the first query. The underlying `fetch()` implementation
|
|
170
|
+
* automatically pools TCP/TLS connections to the same origin, so creating many
|
|
171
|
+
* connections is cheap.
|
|
172
|
+
*
|
|
173
|
+
* Each connection is single-stream: concurrent calls on the same connection are
|
|
174
|
+
* automatically serialized. For true parallelism, create multiple connections:
|
|
131
175
|
*
|
|
132
|
-
* @example
|
|
133
176
|
* ```typescript
|
|
134
177
|
* import { connect } from "@tursodatabase/serverless";
|
|
135
178
|
*
|
|
136
|
-
* const
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
179
|
+
* const config = { url: process.env.TURSO_URL, authToken: process.env.TURSO_TOKEN };
|
|
180
|
+
*
|
|
181
|
+
* // Sequential (single connection is fine)
|
|
182
|
+
* const conn = connect(config);
|
|
183
|
+
* const a = await conn.execute("SELECT 1");
|
|
184
|
+
* const b = await conn.execute("SELECT 2");
|
|
185
|
+
*
|
|
186
|
+
* // Parallel (use separate connections)
|
|
187
|
+
* const [x, y] = await Promise.all([
|
|
188
|
+
* connect(config).execute("SELECT 1"),
|
|
189
|
+
* connect(config).execute("SELECT 2"),
|
|
190
|
+
* ]);
|
|
140
191
|
* ```
|
|
192
|
+
*
|
|
193
|
+
* @param config - Configuration object with database URL and auth token
|
|
194
|
+
* @returns A new Connection instance
|
|
141
195
|
*/
|
|
142
196
|
export declare function connect(config: Config): Connection;
|
package/dist/connection.js
CHANGED
|
@@ -1,16 +1,56 @@
|
|
|
1
|
+
import { AsyncLock } from './async-lock.js';
|
|
1
2
|
import { Session } from './session.js';
|
|
2
3
|
import { Statement } from './statement.js';
|
|
3
4
|
/**
|
|
4
5
|
* A connection to a Turso database.
|
|
5
6
|
*
|
|
6
7
|
* Provides methods for executing SQL statements and managing prepared statements.
|
|
7
|
-
* Uses the SQL over HTTP protocol with streaming cursor support
|
|
8
|
+
* Uses the SQL over HTTP protocol with streaming cursor support.
|
|
9
|
+
*
|
|
10
|
+
* ## Concurrency model
|
|
11
|
+
*
|
|
12
|
+
* A Connection is **single-stream**: it can only run one statement at a time.
|
|
13
|
+
* This is not an implementation quirk — it follows from the SQL over HTTP protocol,
|
|
14
|
+
* where each request carries a baton from the previous response to sequence operations
|
|
15
|
+
* on the server. Concurrent calls on the same connection would race on that baton
|
|
16
|
+
* and corrupt the stream. This is the same model as SQLite itself (one execution
|
|
17
|
+
* at a time per connection).
|
|
18
|
+
*
|
|
19
|
+
* If you call `execute()` while another is in flight, the call automatically
|
|
20
|
+
* waits for the previous one to finish — just like the native
|
|
21
|
+
* `@tursodatabase/database` binding.
|
|
22
|
+
*
|
|
23
|
+
* ## Parallel queries
|
|
24
|
+
*
|
|
25
|
+
* For parallelism, create multiple connections. `connect()` is cheap — it just
|
|
26
|
+
* allocates a config object. No TCP connection is opened until the first `execute()`,
|
|
27
|
+
* and the underlying `fetch()` runtime automatically pools and reuses TCP/TLS
|
|
28
|
+
* connections to the same origin.
|
|
29
|
+
*
|
|
30
|
+
* ```typescript
|
|
31
|
+
* import { connect } from "@tursodatabase/serverless";
|
|
32
|
+
*
|
|
33
|
+
* const config = { url: process.env.TURSO_URL, authToken: process.env.TURSO_TOKEN };
|
|
34
|
+
*
|
|
35
|
+
* // Option 1: one connection per parallel query
|
|
36
|
+
* const [users, orders] = await Promise.all([
|
|
37
|
+
* connect(config).execute("SELECT * FROM users WHERE active = 1"),
|
|
38
|
+
* connect(config).execute("SELECT * FROM orders WHERE status = 'pending'"),
|
|
39
|
+
* ]);
|
|
40
|
+
*
|
|
41
|
+
* // Option 2: reusable pool for repeated parallel work
|
|
42
|
+
* const pool = Array.from({ length: 4 }, () => connect(config));
|
|
43
|
+
* const results = await Promise.all(
|
|
44
|
+
* queries.map((sql, i) => pool[i % pool.length].execute(sql))
|
|
45
|
+
* );
|
|
46
|
+
* ```
|
|
8
47
|
*/
|
|
9
48
|
export class Connection {
|
|
10
49
|
constructor(config) {
|
|
11
50
|
this.isOpen = true;
|
|
12
51
|
this.defaultSafeIntegerMode = false;
|
|
13
52
|
this._inTransaction = false;
|
|
53
|
+
this.execLock = new AsyncLock();
|
|
14
54
|
if (!config.url) {
|
|
15
55
|
throw new Error("invalid config: url is required");
|
|
16
56
|
}
|
|
@@ -75,7 +115,13 @@ export class Connection {
|
|
|
75
115
|
if (!this.isOpen) {
|
|
76
116
|
throw new TypeError("The database connection is not open");
|
|
77
117
|
}
|
|
78
|
-
|
|
118
|
+
await this.execLock.acquire();
|
|
119
|
+
try {
|
|
120
|
+
return await this.session.execute(sql, args || [], this.defaultSafeIntegerMode);
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
this.execLock.release();
|
|
124
|
+
}
|
|
79
125
|
}
|
|
80
126
|
/**
|
|
81
127
|
* Execute a SQL statement and return all results.
|
|
@@ -93,7 +139,13 @@ export class Connection {
|
|
|
93
139
|
if (!this.isOpen) {
|
|
94
140
|
throw new TypeError("The database connection is not open");
|
|
95
141
|
}
|
|
96
|
-
|
|
142
|
+
await this.execLock.acquire();
|
|
143
|
+
try {
|
|
144
|
+
return await this.session.sequence(sql);
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
this.execLock.release();
|
|
148
|
+
}
|
|
97
149
|
}
|
|
98
150
|
/**
|
|
99
151
|
* Execute multiple SQL statements in a batch.
|
|
@@ -112,7 +164,16 @@ export class Connection {
|
|
|
112
164
|
* ```
|
|
113
165
|
*/
|
|
114
166
|
async batch(statements, mode) {
|
|
115
|
-
|
|
167
|
+
if (!this.isOpen) {
|
|
168
|
+
throw new TypeError("The database connection is not open");
|
|
169
|
+
}
|
|
170
|
+
await this.execLock.acquire();
|
|
171
|
+
try {
|
|
172
|
+
return await this.session.batch(statements);
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
this.execLock.release();
|
|
176
|
+
}
|
|
116
177
|
}
|
|
117
178
|
/**
|
|
118
179
|
* Execute a pragma.
|
|
@@ -124,8 +185,14 @@ export class Connection {
|
|
|
124
185
|
if (!this.isOpen) {
|
|
125
186
|
throw new TypeError("The database connection is not open");
|
|
126
187
|
}
|
|
127
|
-
|
|
128
|
-
|
|
188
|
+
await this.execLock.acquire();
|
|
189
|
+
try {
|
|
190
|
+
const sql = `PRAGMA ${pragma}`;
|
|
191
|
+
return await this.session.execute(sql);
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
this.execLock.release();
|
|
195
|
+
}
|
|
129
196
|
}
|
|
130
197
|
/**
|
|
131
198
|
* Sets the default safe integers mode for all statements from this connection.
|
|
@@ -212,18 +279,33 @@ export class Connection {
|
|
|
212
279
|
/**
|
|
213
280
|
* Create a new connection to a Turso database.
|
|
214
281
|
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
282
|
+
* This is a lightweight operation — it only allocates a config object. No network
|
|
283
|
+
* I/O happens until the first query. The underlying `fetch()` implementation
|
|
284
|
+
* automatically pools TCP/TLS connections to the same origin, so creating many
|
|
285
|
+
* connections is cheap.
|
|
286
|
+
*
|
|
287
|
+
* Each connection is single-stream: concurrent calls on the same connection are
|
|
288
|
+
* automatically serialized. For true parallelism, create multiple connections:
|
|
217
289
|
*
|
|
218
|
-
* @example
|
|
219
290
|
* ```typescript
|
|
220
291
|
* import { connect } from "@tursodatabase/serverless";
|
|
221
292
|
*
|
|
222
|
-
* const
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
293
|
+
* const config = { url: process.env.TURSO_URL, authToken: process.env.TURSO_TOKEN };
|
|
294
|
+
*
|
|
295
|
+
* // Sequential (single connection is fine)
|
|
296
|
+
* const conn = connect(config);
|
|
297
|
+
* const a = await conn.execute("SELECT 1");
|
|
298
|
+
* const b = await conn.execute("SELECT 2");
|
|
299
|
+
*
|
|
300
|
+
* // Parallel (use separate connections)
|
|
301
|
+
* const [x, y] = await Promise.all([
|
|
302
|
+
* connect(config).execute("SELECT 1"),
|
|
303
|
+
* connect(config).execute("SELECT 2"),
|
|
304
|
+
* ]);
|
|
226
305
|
* ```
|
|
306
|
+
*
|
|
307
|
+
* @param config - Configuration object with database URL and auth token
|
|
308
|
+
* @returns A new Connection instance
|
|
227
309
|
*/
|
|
228
310
|
export function connect(config) {
|
|
229
311
|
return new Connection(config);
|