@tursodatabase/serverless 0.1.1 → 0.1.3
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 +41 -7
- package/dist/compat.js +27 -7
- package/dist/connection.d.ts +15 -2
- package/dist/connection.js +34 -3
- package/dist/error.d.ts +3 -0
- package/dist/error.js +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/protocol.d.ts +16 -4
- package/dist/protocol.js +39 -24
- package/dist/session.d.ts +18 -4
- package/dist/session.js +98 -6
- package/dist/statement.d.ts +29 -9
- package/dist/statement.js +85 -18
- package/dist/transaction.d.ts +131 -0
- package/dist/transaction.js +207 -0
- package/package.json +1 -1
- package/dist/client.d.ts +0 -31
- package/dist/client.js +0 -184
package/README.md
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">Turso Serverless Driver for JavaScript</h1>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a title="JavaScript" target="_blank" href="https://www.npmjs.com/package/@tursodatabase/serverless"><img alt="npm" src="https://img.shields.io/npm/v/@tursodatabase/serverless"></a>
|
|
7
|
+
<a title="MIT" target="_blank" href="https://github.com/tursodatabase/turso/blob/main/LICENSE.md"><img src="http://img.shields.io/badge/license-MIT-orange.svg?style=flat-square"></a>
|
|
8
|
+
</p>
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a title="Users Discord" target="_blank" href="https://tur.so/discord"><img alt="Chat with other users of Turso on Discord" src="https://img.shields.io/discord/933071162680958986?label=Discord&logo=Discord&style=social"></a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## About
|
|
2
16
|
|
|
3
17
|
A serverless database driver for Turso Cloud, using only `fetch()`. Connect to your database from serverless and edge functions, such as Cloudflare Workers and Vercel.
|
|
4
18
|
|
|
5
|
-
>
|
|
6
|
-
> This driver is experimental and, therefore, subject to change at any time.
|
|
19
|
+
> **📝 Note:** This driver is experimental and, therefore, subject to change at any time.
|
|
7
20
|
|
|
8
21
|
## Installation
|
|
9
22
|
|
|
@@ -11,7 +24,9 @@ A serverless database driver for Turso Cloud, using only `fetch()`. Connect to y
|
|
|
11
24
|
npm install @tursodatabase/serverless
|
|
12
25
|
```
|
|
13
26
|
|
|
14
|
-
##
|
|
27
|
+
## Getting Started
|
|
28
|
+
|
|
29
|
+
### Basic Usage
|
|
15
30
|
|
|
16
31
|
```javascript
|
|
17
32
|
import { connect } from "@tursodatabase/serverless";
|
|
@@ -36,7 +51,11 @@ console.log(rows);
|
|
|
36
51
|
for await (const row of stmt.iterate([123])) {
|
|
37
52
|
console.log(row);
|
|
38
53
|
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Batch Operations
|
|
39
57
|
|
|
58
|
+
```javascript
|
|
40
59
|
// Execute multiple statements in a batch
|
|
41
60
|
await conn.batch([
|
|
42
61
|
"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, email TEXT)",
|
|
@@ -45,9 +64,9 @@ await conn.batch([
|
|
|
45
64
|
]);
|
|
46
65
|
```
|
|
47
66
|
|
|
48
|
-
### Compatibility
|
|
67
|
+
### libSQL Compatibility Layer
|
|
49
68
|
|
|
50
|
-
|
|
69
|
+
For existing libSQL applications, use the compatibility layer:
|
|
51
70
|
|
|
52
71
|
```javascript
|
|
53
72
|
import { createClient } from "@tursodatabase/serverless/compat";
|
|
@@ -73,6 +92,21 @@ await client.batch([
|
|
|
73
92
|
|
|
74
93
|
Check out the `examples/` directory for complete usage examples.
|
|
75
94
|
|
|
95
|
+
## API Reference
|
|
96
|
+
|
|
97
|
+
For complete API documentation, see [JavaScript API Reference](../../docs/javascript-api-reference.md).
|
|
98
|
+
|
|
99
|
+
## Related Packages
|
|
100
|
+
|
|
101
|
+
* The [@tursodatabase/database](https://www.npmjs.com/package/@tursodatabase/database) package provides the Turso in-memory database, compatible with SQLite.
|
|
102
|
+
* The [@tursodatabase/sync](https://www.npmjs.com/package/@tursodatabase/sync) package provides bidirectional sync between a local Turso database and Turso Cloud.
|
|
103
|
+
|
|
76
104
|
## License
|
|
77
105
|
|
|
78
|
-
MIT
|
|
106
|
+
This project is licensed under the [MIT license](../../LICENSE.md).
|
|
107
|
+
|
|
108
|
+
## Support
|
|
109
|
+
|
|
110
|
+
- [GitHub Issues](https://github.com/tursodatabase/turso/issues)
|
|
111
|
+
- [Documentation](https://docs.turso.tech)
|
|
112
|
+
- [Discord Community](https://tur.so/discord)
|
package/dist/compat.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Session } from './session.js';
|
|
2
2
|
/**
|
|
3
3
|
* libSQL-compatible error class with error codes.
|
|
4
4
|
*/
|
|
@@ -14,11 +14,11 @@ class LibSQLClient {
|
|
|
14
14
|
constructor(config) {
|
|
15
15
|
this._closed = false;
|
|
16
16
|
this.validateConfig(config);
|
|
17
|
-
const
|
|
17
|
+
const sessionConfig = {
|
|
18
18
|
url: config.url,
|
|
19
19
|
authToken: config.authToken || ''
|
|
20
20
|
};
|
|
21
|
-
this.
|
|
21
|
+
this.session = new Session(sessionConfig);
|
|
22
22
|
}
|
|
23
23
|
validateConfig(config) {
|
|
24
24
|
// Check for unsupported config options
|
|
@@ -108,8 +108,15 @@ class LibSQLClient {
|
|
|
108
108
|
else {
|
|
109
109
|
normalizedStmt = this.normalizeStatement(stmtOrSql);
|
|
110
110
|
}
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
await this.session.sequence(normalizedStmt.sql);
|
|
112
|
+
// Return empty result set for sequence execution
|
|
113
|
+
return this.convertResult({
|
|
114
|
+
columns: [],
|
|
115
|
+
columnTypes: [],
|
|
116
|
+
rows: [],
|
|
117
|
+
rowsAffected: 0,
|
|
118
|
+
lastInsertRowid: undefined
|
|
119
|
+
});
|
|
113
120
|
}
|
|
114
121
|
catch (error) {
|
|
115
122
|
throw new LibsqlError(error.message, "EXECUTE_ERROR");
|
|
@@ -124,7 +131,7 @@ class LibSQLClient {
|
|
|
124
131
|
const normalized = this.normalizeStatement(stmt);
|
|
125
132
|
return normalized.sql; // For now, ignore args in batch
|
|
126
133
|
});
|
|
127
|
-
const result = await this.
|
|
134
|
+
const result = await this.session.batch(sqlStatements);
|
|
128
135
|
// Return array of result sets (simplified - actual implementation would be more complex)
|
|
129
136
|
return [this.convertResult(result)];
|
|
130
137
|
}
|
|
@@ -140,13 +147,26 @@ class LibSQLClient {
|
|
|
140
147
|
throw new LibsqlError("Transactions not implemented", "NOT_IMPLEMENTED");
|
|
141
148
|
}
|
|
142
149
|
async executeMultiple(sql) {
|
|
143
|
-
|
|
150
|
+
try {
|
|
151
|
+
if (this._closed) {
|
|
152
|
+
throw new LibsqlError("Client is closed", "CLIENT_CLOSED");
|
|
153
|
+
}
|
|
154
|
+
await this.session.sequence(sql);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
throw new LibsqlError(error.message, "EXECUTE_MULTIPLE_ERROR");
|
|
158
|
+
}
|
|
144
159
|
}
|
|
145
160
|
async sync() {
|
|
146
161
|
throw new LibsqlError("Sync not supported for remote databases", "NOT_SUPPORTED");
|
|
147
162
|
}
|
|
148
163
|
close() {
|
|
149
164
|
this._closed = true;
|
|
165
|
+
// Note: The libSQL client interface expects synchronous close,
|
|
166
|
+
// but our underlying session needs async close. We'll fire and forget.
|
|
167
|
+
this.session.close().catch(error => {
|
|
168
|
+
console.error('Error closing session:', error);
|
|
169
|
+
});
|
|
150
170
|
}
|
|
151
171
|
}
|
|
152
172
|
/**
|
package/dist/connection.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface Config extends SessionConfig {
|
|
|
14
14
|
export declare class Connection {
|
|
15
15
|
private config;
|
|
16
16
|
private session;
|
|
17
|
+
private isOpen;
|
|
17
18
|
constructor(config: Config);
|
|
18
19
|
/**
|
|
19
20
|
* Prepare a SQL statement for execution.
|
|
@@ -35,7 +36,6 @@ export declare class Connection {
|
|
|
35
36
|
* Execute a SQL statement and return all results.
|
|
36
37
|
*
|
|
37
38
|
* @param sql - The SQL statement to execute
|
|
38
|
-
* @param args - Optional array of parameter values
|
|
39
39
|
* @returns Promise resolving to the complete result set
|
|
40
40
|
*
|
|
41
41
|
* @example
|
|
@@ -44,7 +44,7 @@ export declare class Connection {
|
|
|
44
44
|
* console.log(result.rows);
|
|
45
45
|
* ```
|
|
46
46
|
*/
|
|
47
|
-
|
|
47
|
+
exec(sql: string): Promise<any>;
|
|
48
48
|
/**
|
|
49
49
|
* Execute multiple SQL statements in a batch.
|
|
50
50
|
*
|
|
@@ -62,6 +62,19 @@ export declare class Connection {
|
|
|
62
62
|
* ```
|
|
63
63
|
*/
|
|
64
64
|
batch(statements: string[], mode?: string): Promise<any>;
|
|
65
|
+
/**
|
|
66
|
+
* Execute a pragma.
|
|
67
|
+
*
|
|
68
|
+
* @param pragma - The pragma to execute
|
|
69
|
+
* @returns Promise resolving to the result of the pragma
|
|
70
|
+
*/
|
|
71
|
+
pragma(pragma: string): Promise<any>;
|
|
72
|
+
/**
|
|
73
|
+
* Close the connection.
|
|
74
|
+
*
|
|
75
|
+
* This sends a close request to the server to properly clean up the stream.
|
|
76
|
+
*/
|
|
77
|
+
close(): Promise<void>;
|
|
65
78
|
}
|
|
66
79
|
/**
|
|
67
80
|
* Create a new connection to a Turso database.
|
package/dist/connection.js
CHANGED
|
@@ -8,6 +8,10 @@ import { Statement } from './statement.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export class Connection {
|
|
10
10
|
constructor(config) {
|
|
11
|
+
this.isOpen = true;
|
|
12
|
+
if (!config.url) {
|
|
13
|
+
throw new Error("invalid config: url is required");
|
|
14
|
+
}
|
|
11
15
|
this.config = config;
|
|
12
16
|
this.session = new Session(config);
|
|
13
17
|
}
|
|
@@ -27,13 +31,15 @@ export class Connection {
|
|
|
27
31
|
* ```
|
|
28
32
|
*/
|
|
29
33
|
prepare(sql) {
|
|
34
|
+
if (!this.isOpen) {
|
|
35
|
+
throw new TypeError("The database connection is not open");
|
|
36
|
+
}
|
|
30
37
|
return new Statement(this.config, sql);
|
|
31
38
|
}
|
|
32
39
|
/**
|
|
33
40
|
* Execute a SQL statement and return all results.
|
|
34
41
|
*
|
|
35
42
|
* @param sql - The SQL statement to execute
|
|
36
|
-
* @param args - Optional array of parameter values
|
|
37
43
|
* @returns Promise resolving to the complete result set
|
|
38
44
|
*
|
|
39
45
|
* @example
|
|
@@ -42,8 +48,11 @@ export class Connection {
|
|
|
42
48
|
* console.log(result.rows);
|
|
43
49
|
* ```
|
|
44
50
|
*/
|
|
45
|
-
async
|
|
46
|
-
|
|
51
|
+
async exec(sql) {
|
|
52
|
+
if (!this.isOpen) {
|
|
53
|
+
throw new TypeError("The database connection is not open");
|
|
54
|
+
}
|
|
55
|
+
return this.session.sequence(sql);
|
|
47
56
|
}
|
|
48
57
|
/**
|
|
49
58
|
* Execute multiple SQL statements in a batch.
|
|
@@ -64,6 +73,28 @@ export class Connection {
|
|
|
64
73
|
async batch(statements, mode) {
|
|
65
74
|
return this.session.batch(statements);
|
|
66
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Execute a pragma.
|
|
78
|
+
*
|
|
79
|
+
* @param pragma - The pragma to execute
|
|
80
|
+
* @returns Promise resolving to the result of the pragma
|
|
81
|
+
*/
|
|
82
|
+
async pragma(pragma) {
|
|
83
|
+
if (!this.isOpen) {
|
|
84
|
+
throw new TypeError("The database connection is not open");
|
|
85
|
+
}
|
|
86
|
+
const sql = `PRAGMA ${pragma}`;
|
|
87
|
+
return this.session.execute(sql);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Close the connection.
|
|
91
|
+
*
|
|
92
|
+
* This sends a close request to the server to properly clean up the stream.
|
|
93
|
+
*/
|
|
94
|
+
async close() {
|
|
95
|
+
this.isOpen = false;
|
|
96
|
+
await this.session.close();
|
|
97
|
+
}
|
|
67
98
|
}
|
|
68
99
|
/**
|
|
69
100
|
* Create a new connection to a Turso database.
|
package/dist/error.d.ts
ADDED
package/dist/error.js
ADDED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/protocol.d.ts
CHANGED
|
@@ -13,12 +13,16 @@ export interface ExecuteResult {
|
|
|
13
13
|
affected_row_count: number;
|
|
14
14
|
last_insert_rowid?: string;
|
|
15
15
|
}
|
|
16
|
+
export interface NamedArg {
|
|
17
|
+
name: string;
|
|
18
|
+
value: Value;
|
|
19
|
+
}
|
|
16
20
|
export interface ExecuteRequest {
|
|
17
21
|
type: 'execute';
|
|
18
22
|
stmt: {
|
|
19
23
|
sql: string;
|
|
20
24
|
args: Value[];
|
|
21
|
-
named_args:
|
|
25
|
+
named_args: NamedArg[];
|
|
22
26
|
want_rows: boolean;
|
|
23
27
|
};
|
|
24
28
|
}
|
|
@@ -26,6 +30,7 @@ export interface BatchStep {
|
|
|
26
30
|
stmt: {
|
|
27
31
|
sql: string;
|
|
28
32
|
args: Value[];
|
|
33
|
+
named_args?: NamedArg[];
|
|
29
34
|
want_rows: boolean;
|
|
30
35
|
};
|
|
31
36
|
condition?: {
|
|
@@ -39,9 +44,16 @@ export interface BatchRequest {
|
|
|
39
44
|
steps: BatchStep[];
|
|
40
45
|
};
|
|
41
46
|
}
|
|
47
|
+
export interface SequenceRequest {
|
|
48
|
+
type: 'sequence';
|
|
49
|
+
sql: string;
|
|
50
|
+
}
|
|
51
|
+
export interface CloseRequest {
|
|
52
|
+
type: 'close';
|
|
53
|
+
}
|
|
42
54
|
export interface PipelineRequest {
|
|
43
55
|
baton: string | null;
|
|
44
|
-
requests: (ExecuteRequest | BatchRequest)[];
|
|
56
|
+
requests: (ExecuteRequest | BatchRequest | SequenceRequest | CloseRequest)[];
|
|
45
57
|
}
|
|
46
58
|
export interface PipelineResponse {
|
|
47
59
|
baton: string | null;
|
|
@@ -49,8 +61,8 @@ export interface PipelineResponse {
|
|
|
49
61
|
results: Array<{
|
|
50
62
|
type: 'ok' | 'error';
|
|
51
63
|
response?: {
|
|
52
|
-
type: 'execute' | 'batch';
|
|
53
|
-
result
|
|
64
|
+
type: 'execute' | 'batch' | 'sequence' | 'close';
|
|
65
|
+
result?: ExecuteResult;
|
|
54
66
|
};
|
|
55
67
|
error?: {
|
|
56
68
|
message: string;
|
package/dist/protocol.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DatabaseError } from './error.js';
|
|
1
2
|
export function encodeValue(value) {
|
|
2
3
|
if (value === null || value === undefined) {
|
|
3
4
|
return { type: 'null' };
|
|
@@ -62,55 +63,69 @@ export async function executeCursor(url, authToken, request) {
|
|
|
62
63
|
catch {
|
|
63
64
|
// If we can't parse the error body, use the default HTTP error message
|
|
64
65
|
}
|
|
65
|
-
throw new
|
|
66
|
+
throw new DatabaseError(errorMessage);
|
|
66
67
|
}
|
|
67
68
|
const reader = response.body?.getReader();
|
|
68
69
|
if (!reader) {
|
|
69
|
-
throw new
|
|
70
|
+
throw new DatabaseError('No response body');
|
|
70
71
|
}
|
|
71
72
|
const decoder = new TextDecoder();
|
|
72
73
|
let buffer = '';
|
|
73
|
-
let isFirstLine = true;
|
|
74
74
|
let cursorResponse;
|
|
75
|
+
// First, read until we get the cursor response (first line)
|
|
76
|
+
while (!cursorResponse) {
|
|
77
|
+
const { done, value } = await reader.read();
|
|
78
|
+
if (done)
|
|
79
|
+
break;
|
|
80
|
+
buffer += decoder.decode(value, { stream: true });
|
|
81
|
+
const newlineIndex = buffer.indexOf('\n');
|
|
82
|
+
if (newlineIndex !== -1) {
|
|
83
|
+
const line = buffer.slice(0, newlineIndex).trim();
|
|
84
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
85
|
+
if (line) {
|
|
86
|
+
cursorResponse = JSON.parse(line);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!cursorResponse) {
|
|
92
|
+
throw new DatabaseError('No cursor response received');
|
|
93
|
+
}
|
|
75
94
|
async function* parseEntries() {
|
|
76
95
|
try {
|
|
96
|
+
// Process any remaining data in the buffer
|
|
97
|
+
let newlineIndex;
|
|
98
|
+
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
|
|
99
|
+
const line = buffer.slice(0, newlineIndex).trim();
|
|
100
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
101
|
+
if (line) {
|
|
102
|
+
yield JSON.parse(line);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Continue reading from the stream
|
|
77
106
|
while (true) {
|
|
78
107
|
const { done, value } = await reader.read();
|
|
79
108
|
if (done)
|
|
80
109
|
break;
|
|
81
110
|
buffer += decoder.decode(value, { stream: true });
|
|
82
|
-
let newlineIndex;
|
|
83
111
|
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
|
|
84
112
|
const line = buffer.slice(0, newlineIndex).trim();
|
|
85
113
|
buffer = buffer.slice(newlineIndex + 1);
|
|
86
114
|
if (line) {
|
|
87
|
-
|
|
88
|
-
cursorResponse = JSON.parse(line);
|
|
89
|
-
isFirstLine = false;
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
yield JSON.parse(line);
|
|
93
|
-
}
|
|
115
|
+
yield JSON.parse(line);
|
|
94
116
|
}
|
|
95
117
|
}
|
|
96
118
|
}
|
|
119
|
+
// Process any remaining data in the buffer
|
|
120
|
+
if (buffer.trim()) {
|
|
121
|
+
yield JSON.parse(buffer.trim());
|
|
122
|
+
}
|
|
97
123
|
}
|
|
98
124
|
finally {
|
|
99
125
|
reader.releaseLock();
|
|
100
126
|
}
|
|
101
127
|
}
|
|
102
|
-
|
|
103
|
-
// Get the first entry to parse the cursor response
|
|
104
|
-
const firstEntry = await entries.next();
|
|
105
|
-
if (!firstEntry.done) {
|
|
106
|
-
// Put the first entry back
|
|
107
|
-
const generator = (async function* () {
|
|
108
|
-
yield firstEntry.value;
|
|
109
|
-
yield* entries;
|
|
110
|
-
})();
|
|
111
|
-
return { response: cursorResponse, entries: generator };
|
|
112
|
-
}
|
|
113
|
-
return { response: cursorResponse, entries };
|
|
128
|
+
return { response: cursorResponse, entries: parseEntries() };
|
|
114
129
|
}
|
|
115
130
|
export async function executePipeline(url, authToken, request) {
|
|
116
131
|
const response = await fetch(`${url}/v3/pipeline`, {
|
|
@@ -122,7 +137,7 @@ export async function executePipeline(url, authToken, request) {
|
|
|
122
137
|
body: JSON.stringify(request),
|
|
123
138
|
});
|
|
124
139
|
if (!response.ok) {
|
|
125
|
-
throw new
|
|
140
|
+
throw new DatabaseError(`HTTP error! status: ${response.status}`);
|
|
126
141
|
}
|
|
127
142
|
return response.json();
|
|
128
143
|
}
|
package/dist/session.d.ts
CHANGED
|
@@ -23,18 +23,18 @@ export declare class Session {
|
|
|
23
23
|
* Execute a SQL statement and return all results.
|
|
24
24
|
*
|
|
25
25
|
* @param sql - The SQL statement to execute
|
|
26
|
-
* @param args - Optional array of parameter values
|
|
26
|
+
* @param args - Optional array of parameter values or object with named parameters
|
|
27
27
|
* @returns Promise resolving to the complete result set
|
|
28
28
|
*/
|
|
29
|
-
execute(sql: string, args?: any[]): Promise<any>;
|
|
29
|
+
execute(sql: string, args?: any[] | Record<string, any>): Promise<any>;
|
|
30
30
|
/**
|
|
31
31
|
* Execute a SQL statement and return the raw response and entries.
|
|
32
32
|
*
|
|
33
33
|
* @param sql - The SQL statement to execute
|
|
34
|
-
* @param args - Optional array of parameter values
|
|
34
|
+
* @param args - Optional array of parameter values or object with named parameters
|
|
35
35
|
* @returns Promise resolving to the raw response and cursor entries
|
|
36
36
|
*/
|
|
37
|
-
executeRaw(sql: string, args?: any[]): Promise<{
|
|
37
|
+
executeRaw(sql: string, args?: any[] | Record<string, any>): Promise<{
|
|
38
38
|
response: CursorResponse;
|
|
39
39
|
entries: AsyncGenerator<CursorEntry>;
|
|
40
40
|
}>;
|
|
@@ -60,4 +60,18 @@ export declare class Session {
|
|
|
60
60
|
* @returns Promise resolving to batch execution results
|
|
61
61
|
*/
|
|
62
62
|
batch(statements: string[]): Promise<any>;
|
|
63
|
+
/**
|
|
64
|
+
* Execute a sequence of SQL statements separated by semicolons.
|
|
65
|
+
*
|
|
66
|
+
* @param sql - SQL string containing multiple statements separated by semicolons
|
|
67
|
+
* @returns Promise resolving when all statements are executed
|
|
68
|
+
*/
|
|
69
|
+
sequence(sql: string): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Close the session.
|
|
72
|
+
*
|
|
73
|
+
* This sends a close request to the server to properly clean up the stream
|
|
74
|
+
* before resetting the local state.
|
|
75
|
+
*/
|
|
76
|
+
close(): Promise<void>;
|
|
63
77
|
}
|
package/dist/session.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { executeCursor, encodeValue, decodeValue } from './protocol.js';
|
|
1
|
+
import { executeCursor, executePipeline, encodeValue, decodeValue } from './protocol.js';
|
|
2
|
+
import { DatabaseError } from './error.js';
|
|
2
3
|
function normalizeUrl(url) {
|
|
3
4
|
return url.replace(/^libsql:\/\//, 'https://');
|
|
4
5
|
}
|
|
@@ -21,7 +22,7 @@ export class Session {
|
|
|
21
22
|
* Execute a SQL statement and return all results.
|
|
22
23
|
*
|
|
23
24
|
* @param sql - The SQL statement to execute
|
|
24
|
-
* @param args - Optional array of parameter values
|
|
25
|
+
* @param args - Optional array of parameter values or object with named parameters
|
|
25
26
|
* @returns Promise resolving to the complete result set
|
|
26
27
|
*/
|
|
27
28
|
async execute(sql, args = []) {
|
|
@@ -33,17 +34,53 @@ export class Session {
|
|
|
33
34
|
* Execute a SQL statement and return the raw response and entries.
|
|
34
35
|
*
|
|
35
36
|
* @param sql - The SQL statement to execute
|
|
36
|
-
* @param args - Optional array of parameter values
|
|
37
|
+
* @param args - Optional array of parameter values or object with named parameters
|
|
37
38
|
* @returns Promise resolving to the raw response and cursor entries
|
|
38
39
|
*/
|
|
39
40
|
async executeRaw(sql, args = []) {
|
|
41
|
+
let positionalArgs = [];
|
|
42
|
+
let namedArgs = [];
|
|
43
|
+
if (Array.isArray(args)) {
|
|
44
|
+
positionalArgs = args.map(encodeValue);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Check if this is an object with numeric keys (for ?1, ?2 style parameters)
|
|
48
|
+
const keys = Object.keys(args);
|
|
49
|
+
const isNumericKeys = keys.length > 0 && keys.every(key => /^\d+$/.test(key));
|
|
50
|
+
if (isNumericKeys) {
|
|
51
|
+
// Convert numeric-keyed object to positional args
|
|
52
|
+
// Sort keys numerically to ensure correct order
|
|
53
|
+
const sortedKeys = keys.sort((a, b) => parseInt(a) - parseInt(b));
|
|
54
|
+
const maxIndex = parseInt(sortedKeys[sortedKeys.length - 1]);
|
|
55
|
+
// Create array with undefined for missing indices
|
|
56
|
+
positionalArgs = new Array(maxIndex);
|
|
57
|
+
for (const key of sortedKeys) {
|
|
58
|
+
const index = parseInt(key) - 1; // Convert to 0-based index
|
|
59
|
+
positionalArgs[index] = encodeValue(args[key]);
|
|
60
|
+
}
|
|
61
|
+
// Fill any undefined values with null
|
|
62
|
+
for (let i = 0; i < positionalArgs.length; i++) {
|
|
63
|
+
if (positionalArgs[i] === undefined) {
|
|
64
|
+
positionalArgs[i] = { type: 'null' };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Convert object with named parameters to NamedArg array
|
|
70
|
+
namedArgs = Object.entries(args).map(([name, value]) => ({
|
|
71
|
+
name,
|
|
72
|
+
value: encodeValue(value)
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
40
76
|
const request = {
|
|
41
77
|
baton: this.baton,
|
|
42
78
|
batch: {
|
|
43
79
|
steps: [{
|
|
44
80
|
stmt: {
|
|
45
81
|
sql,
|
|
46
|
-
args:
|
|
82
|
+
args: positionalArgs,
|
|
83
|
+
named_args: namedArgs,
|
|
47
84
|
want_rows: true
|
|
48
85
|
}
|
|
49
86
|
}]
|
|
@@ -93,7 +130,7 @@ export class Session {
|
|
|
93
130
|
break;
|
|
94
131
|
case 'step_error':
|
|
95
132
|
case 'error':
|
|
96
|
-
throw new
|
|
133
|
+
throw new DatabaseError(entry.error?.message || 'SQL execution failed');
|
|
97
134
|
}
|
|
98
135
|
}
|
|
99
136
|
return {
|
|
@@ -141,6 +178,7 @@ export class Session {
|
|
|
141
178
|
stmt: {
|
|
142
179
|
sql,
|
|
143
180
|
args: [],
|
|
181
|
+
named_args: [],
|
|
144
182
|
want_rows: false
|
|
145
183
|
}
|
|
146
184
|
}))
|
|
@@ -165,7 +203,7 @@ export class Session {
|
|
|
165
203
|
break;
|
|
166
204
|
case 'step_error':
|
|
167
205
|
case 'error':
|
|
168
|
-
throw new
|
|
206
|
+
throw new DatabaseError(entry.error?.message || 'Batch execution failed');
|
|
169
207
|
}
|
|
170
208
|
}
|
|
171
209
|
return {
|
|
@@ -173,4 +211,58 @@ export class Session {
|
|
|
173
211
|
lastInsertRowid
|
|
174
212
|
};
|
|
175
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Execute a sequence of SQL statements separated by semicolons.
|
|
216
|
+
*
|
|
217
|
+
* @param sql - SQL string containing multiple statements separated by semicolons
|
|
218
|
+
* @returns Promise resolving when all statements are executed
|
|
219
|
+
*/
|
|
220
|
+
async sequence(sql) {
|
|
221
|
+
const request = {
|
|
222
|
+
baton: this.baton,
|
|
223
|
+
requests: [{
|
|
224
|
+
type: "sequence",
|
|
225
|
+
sql: sql
|
|
226
|
+
}]
|
|
227
|
+
};
|
|
228
|
+
const response = await executePipeline(this.baseUrl, this.config.authToken, request);
|
|
229
|
+
this.baton = response.baton;
|
|
230
|
+
if (response.base_url) {
|
|
231
|
+
this.baseUrl = response.base_url;
|
|
232
|
+
}
|
|
233
|
+
// Check for errors in the response
|
|
234
|
+
if (response.results && response.results[0]) {
|
|
235
|
+
const result = response.results[0];
|
|
236
|
+
if (result.type === "error") {
|
|
237
|
+
throw new DatabaseError(result.error?.message || 'Sequence execution failed');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Close the session.
|
|
243
|
+
*
|
|
244
|
+
* This sends a close request to the server to properly clean up the stream
|
|
245
|
+
* before resetting the local state.
|
|
246
|
+
*/
|
|
247
|
+
async close() {
|
|
248
|
+
// Only send close request if we have an active baton
|
|
249
|
+
if (this.baton) {
|
|
250
|
+
try {
|
|
251
|
+
const request = {
|
|
252
|
+
baton: this.baton,
|
|
253
|
+
requests: [{
|
|
254
|
+
type: "close"
|
|
255
|
+
}]
|
|
256
|
+
};
|
|
257
|
+
await executePipeline(this.baseUrl, this.config.authToken, request);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
// Ignore errors during close, as the connection might already be closed
|
|
261
|
+
console.error('Error closing session:', error);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Reset local state
|
|
265
|
+
this.baton = null;
|
|
266
|
+
this.baseUrl = '';
|
|
267
|
+
}
|
|
176
268
|
}
|