@tursodatabase/serverless 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -7
- package/dist/compat.d.ts +9 -3
- package/dist/compat.js +6 -11
- package/dist/connection.d.ts +53 -5
- package/dist/connection.js +130 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/protocol.d.ts +20 -6
- package/dist/protocol.js +37 -14
- package/dist/session.d.ts +18 -5
- package/dist/session.js +40 -8
- package/dist/statement.d.ts +55 -1
- package/dist/statement.js +104 -12
- package/package.json +1 -1
- package/dist/transaction.d.ts +0 -131
- package/dist/transaction.js +0 -207
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
|
+
> **⚠️ Warning:** This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.
|
|
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.d.ts
CHANGED
|
@@ -2,16 +2,22 @@
|
|
|
2
2
|
* Configuration options for creating a libSQL-compatible client.
|
|
3
3
|
*
|
|
4
4
|
* @remarks
|
|
5
|
-
* This interface matches the libSQL client configuration
|
|
6
|
-
* are supported in the serverless compatibility layer.
|
|
5
|
+
* This interface matches the libSQL client configuration. The `url`, `authToken`, and
|
|
6
|
+
* `remoteEncryptionKey` options are supported in the serverless compatibility layer.
|
|
7
|
+
* Other options will throw validation errors.
|
|
7
8
|
*/
|
|
8
9
|
export interface Config {
|
|
9
10
|
/** Database URL (required) */
|
|
10
11
|
url: string;
|
|
11
12
|
/** Authentication token for the database */
|
|
12
13
|
authToken?: string;
|
|
13
|
-
/** @deprecated
|
|
14
|
+
/** @deprecated Local database encryption key - not supported in serverless mode */
|
|
14
15
|
encryptionKey?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Encryption key for the remote database (base64 encoded)
|
|
18
|
+
* to enable access to encrypted Turso Cloud databases.
|
|
19
|
+
*/
|
|
20
|
+
remoteEncryptionKey?: string;
|
|
15
21
|
/** @deprecated Sync server URL - not supported in serverless mode */
|
|
16
22
|
syncUrl?: string;
|
|
17
23
|
/** @deprecated Sync frequency in seconds - not supported in serverless mode */
|
package/dist/compat.js
CHANGED
|
@@ -13,10 +13,12 @@ export class LibsqlError extends Error {
|
|
|
13
13
|
class LibSQLClient {
|
|
14
14
|
constructor(config) {
|
|
15
15
|
this._closed = false;
|
|
16
|
+
this._defaultSafeIntegers = false;
|
|
16
17
|
this.validateConfig(config);
|
|
17
18
|
const sessionConfig = {
|
|
18
19
|
url: config.url,
|
|
19
|
-
authToken: config.authToken || ''
|
|
20
|
+
authToken: config.authToken || '',
|
|
21
|
+
remoteEncryptionKey: config.remoteEncryptionKey
|
|
20
22
|
};
|
|
21
23
|
this.session = new Session(sessionConfig);
|
|
22
24
|
}
|
|
@@ -52,7 +54,7 @@ class LibSQLClient {
|
|
|
52
54
|
}
|
|
53
55
|
if (unsupportedOptions.length > 0) {
|
|
54
56
|
const optionsList = unsupportedOptions.map(opt => `'${opt.key}'`).join(', ');
|
|
55
|
-
throw new LibsqlError(`Unsupported configuration options: ${optionsList}. Only 'url' and '
|
|
57
|
+
throw new LibsqlError(`Unsupported configuration options: ${optionsList}. Only 'url', 'authToken', and 'remoteEncryptionKey' are supported in the serverless compatibility layer.`, "UNSUPPORTED_CONFIG");
|
|
56
58
|
}
|
|
57
59
|
// Validate required options
|
|
58
60
|
if (!config.url) {
|
|
@@ -108,15 +110,8 @@ class LibSQLClient {
|
|
|
108
110
|
else {
|
|
109
111
|
normalizedStmt = this.normalizeStatement(stmtOrSql);
|
|
110
112
|
}
|
|
111
|
-
await this.session.
|
|
112
|
-
|
|
113
|
-
return this.convertResult({
|
|
114
|
-
columns: [],
|
|
115
|
-
columnTypes: [],
|
|
116
|
-
rows: [],
|
|
117
|
-
rowsAffected: 0,
|
|
118
|
-
lastInsertRowid: undefined
|
|
119
|
-
});
|
|
113
|
+
const result = await this.session.execute(normalizedStmt.sql, normalizedStmt.args, this._defaultSafeIntegers);
|
|
114
|
+
return this.convertResult(result);
|
|
120
115
|
}
|
|
121
116
|
catch (error) {
|
|
122
117
|
throw new LibsqlError(error.message, "EXECUTE_ERROR");
|
package/dist/connection.d.ts
CHANGED
|
@@ -14,23 +14,45 @@ export interface Config extends SessionConfig {
|
|
|
14
14
|
export declare class Connection {
|
|
15
15
|
private config;
|
|
16
16
|
private session;
|
|
17
|
+
private isOpen;
|
|
18
|
+
private defaultSafeIntegerMode;
|
|
19
|
+
private _inTransaction;
|
|
17
20
|
constructor(config: Config);
|
|
21
|
+
/**
|
|
22
|
+
* Whether the database is currently in a transaction.
|
|
23
|
+
*/
|
|
24
|
+
get inTransaction(): boolean;
|
|
18
25
|
/**
|
|
19
26
|
* Prepare a SQL statement for execution.
|
|
20
27
|
*
|
|
21
28
|
* Each prepared statement gets its own session to avoid conflicts during concurrent execution.
|
|
29
|
+
* This method fetches column metadata using the describe functionality.
|
|
22
30
|
*
|
|
23
31
|
* @param sql - The SQL statement to prepare
|
|
24
|
-
* @returns A
|
|
32
|
+
* @returns A Promise that resolves to a Statement object with column metadata
|
|
25
33
|
*
|
|
26
34
|
* @example
|
|
27
35
|
* ```typescript
|
|
28
|
-
* const stmt = client.prepare("SELECT * FROM users WHERE id = ?");
|
|
36
|
+
* const stmt = await client.prepare("SELECT * FROM users WHERE id = ?");
|
|
37
|
+
* const columns = stmt.columns();
|
|
29
38
|
* const user = await stmt.get([123]);
|
|
30
|
-
* const allUsers = await stmt.all();
|
|
31
39
|
* ```
|
|
32
40
|
*/
|
|
33
|
-
prepare(sql: string): Statement
|
|
41
|
+
prepare(sql: string): Promise<Statement>;
|
|
42
|
+
/**
|
|
43
|
+
* Execute a SQL statement and return all results.
|
|
44
|
+
*
|
|
45
|
+
* @param sql - The SQL statement to execute
|
|
46
|
+
* @param args - Optional array of parameter values
|
|
47
|
+
* @returns Promise resolving to the complete result set
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const result = await client.execute("SELECT * FROM users WHERE id = ?", [123]);
|
|
52
|
+
* console.log(result.rows);
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
execute(sql: string, args?: any[]): Promise<any>;
|
|
34
56
|
/**
|
|
35
57
|
* Execute a SQL statement and return all results.
|
|
36
58
|
*
|
|
@@ -39,7 +61,7 @@ export declare class Connection {
|
|
|
39
61
|
*
|
|
40
62
|
* @example
|
|
41
63
|
* ```typescript
|
|
42
|
-
* const result = await client.
|
|
64
|
+
* const result = await client.exec("SELECT * FROM users");
|
|
43
65
|
* console.log(result.rows);
|
|
44
66
|
* ```
|
|
45
67
|
*/
|
|
@@ -68,12 +90,38 @@ export declare class Connection {
|
|
|
68
90
|
* @returns Promise resolving to the result of the pragma
|
|
69
91
|
*/
|
|
70
92
|
pragma(pragma: string): Promise<any>;
|
|
93
|
+
/**
|
|
94
|
+
* Sets the default safe integers mode for all statements from this connection.
|
|
95
|
+
*
|
|
96
|
+
* @param toggle - Whether to use safe integers by default.
|
|
97
|
+
*/
|
|
98
|
+
defaultSafeIntegers(toggle?: boolean): void;
|
|
99
|
+
/**
|
|
100
|
+
* Returns a function that executes the given function in a transaction.
|
|
101
|
+
*
|
|
102
|
+
* @param fn - The function to wrap in a transaction
|
|
103
|
+
* @returns A function that will execute fn within a transaction
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* const insert = await client.prepare("INSERT INTO users (name) VALUES (?)");
|
|
108
|
+
* const insertMany = client.transaction((users) => {
|
|
109
|
+
* for (const user of users) {
|
|
110
|
+
* insert.run([user]);
|
|
111
|
+
* }
|
|
112
|
+
* });
|
|
113
|
+
*
|
|
114
|
+
* await insertMany(['Alice', 'Bob', 'Charlie']);
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
transaction(fn: (...args: any[]) => any): any;
|
|
71
118
|
/**
|
|
72
119
|
* Close the connection.
|
|
73
120
|
*
|
|
74
121
|
* This sends a close request to the server to properly clean up the stream.
|
|
75
122
|
*/
|
|
76
123
|
close(): Promise<void>;
|
|
124
|
+
reconnect(): Promise<void>;
|
|
77
125
|
}
|
|
78
126
|
/**
|
|
79
127
|
* Create a new connection to a Turso database.
|
package/dist/connection.js
CHANGED
|
@@ -8,43 +8,91 @@ import { Statement } from './statement.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export class Connection {
|
|
10
10
|
constructor(config) {
|
|
11
|
+
this.isOpen = true;
|
|
12
|
+
this.defaultSafeIntegerMode = false;
|
|
13
|
+
this._inTransaction = false;
|
|
11
14
|
if (!config.url) {
|
|
12
15
|
throw new Error("invalid config: url is required");
|
|
13
16
|
}
|
|
14
17
|
this.config = config;
|
|
15
18
|
this.session = new Session(config);
|
|
19
|
+
// Define inTransaction property
|
|
20
|
+
Object.defineProperty(this, 'inTransaction', {
|
|
21
|
+
get: () => this._inTransaction,
|
|
22
|
+
enumerable: true
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Whether the database is currently in a transaction.
|
|
27
|
+
*/
|
|
28
|
+
get inTransaction() {
|
|
29
|
+
return this._inTransaction;
|
|
16
30
|
}
|
|
17
31
|
/**
|
|
18
32
|
* Prepare a SQL statement for execution.
|
|
19
33
|
*
|
|
20
34
|
* Each prepared statement gets its own session to avoid conflicts during concurrent execution.
|
|
35
|
+
* This method fetches column metadata using the describe functionality.
|
|
21
36
|
*
|
|
22
37
|
* @param sql - The SQL statement to prepare
|
|
23
|
-
* @returns A
|
|
38
|
+
* @returns A Promise that resolves to a Statement object with column metadata
|
|
24
39
|
*
|
|
25
40
|
* @example
|
|
26
41
|
* ```typescript
|
|
27
|
-
* const stmt = client.prepare("SELECT * FROM users WHERE id = ?");
|
|
42
|
+
* const stmt = await client.prepare("SELECT * FROM users WHERE id = ?");
|
|
43
|
+
* const columns = stmt.columns();
|
|
28
44
|
* const user = await stmt.get([123]);
|
|
29
|
-
* const allUsers = await stmt.all();
|
|
30
45
|
* ```
|
|
31
46
|
*/
|
|
32
|
-
prepare(sql) {
|
|
33
|
-
|
|
47
|
+
async prepare(sql) {
|
|
48
|
+
if (!this.isOpen) {
|
|
49
|
+
throw new TypeError("The database connection is not open");
|
|
50
|
+
}
|
|
51
|
+
// Create a session to get column metadata via describe
|
|
52
|
+
const session = new Session(this.config);
|
|
53
|
+
const description = await session.describe(sql);
|
|
54
|
+
await session.close();
|
|
55
|
+
const stmt = new Statement(this.config, sql, description.cols);
|
|
56
|
+
if (this.defaultSafeIntegerMode) {
|
|
57
|
+
stmt.safeIntegers(true);
|
|
58
|
+
}
|
|
59
|
+
return stmt;
|
|
34
60
|
}
|
|
35
61
|
/**
|
|
36
62
|
* Execute a SQL statement and return all results.
|
|
37
63
|
*
|
|
38
64
|
* @param sql - The SQL statement to execute
|
|
65
|
+
* @param args - Optional array of parameter values
|
|
39
66
|
* @returns Promise resolving to the complete result set
|
|
40
67
|
*
|
|
41
68
|
* @example
|
|
42
69
|
* ```typescript
|
|
43
|
-
* const result = await client.execute("SELECT * FROM users");
|
|
70
|
+
* const result = await client.execute("SELECT * FROM users WHERE id = ?", [123]);
|
|
71
|
+
* console.log(result.rows);
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
async execute(sql, args) {
|
|
75
|
+
if (!this.isOpen) {
|
|
76
|
+
throw new TypeError("The database connection is not open");
|
|
77
|
+
}
|
|
78
|
+
return this.session.execute(sql, args || [], this.defaultSafeIntegerMode);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Execute a SQL statement and return all results.
|
|
82
|
+
*
|
|
83
|
+
* @param sql - The SQL statement to execute
|
|
84
|
+
* @returns Promise resolving to the complete result set
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* const result = await client.exec("SELECT * FROM users");
|
|
44
89
|
* console.log(result.rows);
|
|
45
90
|
* ```
|
|
46
91
|
*/
|
|
47
92
|
async exec(sql) {
|
|
93
|
+
if (!this.isOpen) {
|
|
94
|
+
throw new TypeError("The database connection is not open");
|
|
95
|
+
}
|
|
48
96
|
return this.session.sequence(sql);
|
|
49
97
|
}
|
|
50
98
|
/**
|
|
@@ -73,17 +121,93 @@ export class Connection {
|
|
|
73
121
|
* @returns Promise resolving to the result of the pragma
|
|
74
122
|
*/
|
|
75
123
|
async pragma(pragma) {
|
|
124
|
+
if (!this.isOpen) {
|
|
125
|
+
throw new TypeError("The database connection is not open");
|
|
126
|
+
}
|
|
76
127
|
const sql = `PRAGMA ${pragma}`;
|
|
77
128
|
return this.session.execute(sql);
|
|
78
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Sets the default safe integers mode for all statements from this connection.
|
|
132
|
+
*
|
|
133
|
+
* @param toggle - Whether to use safe integers by default.
|
|
134
|
+
*/
|
|
135
|
+
defaultSafeIntegers(toggle) {
|
|
136
|
+
this.defaultSafeIntegerMode = toggle === false ? false : true;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Returns a function that executes the given function in a transaction.
|
|
140
|
+
*
|
|
141
|
+
* @param fn - The function to wrap in a transaction
|
|
142
|
+
* @returns A function that will execute fn within a transaction
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* const insert = await client.prepare("INSERT INTO users (name) VALUES (?)");
|
|
147
|
+
* const insertMany = client.transaction((users) => {
|
|
148
|
+
* for (const user of users) {
|
|
149
|
+
* insert.run([user]);
|
|
150
|
+
* }
|
|
151
|
+
* });
|
|
152
|
+
*
|
|
153
|
+
* await insertMany(['Alice', 'Bob', 'Charlie']);
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
transaction(fn) {
|
|
157
|
+
if (typeof fn !== "function") {
|
|
158
|
+
throw new TypeError("Expected first argument to be a function");
|
|
159
|
+
}
|
|
160
|
+
const db = this;
|
|
161
|
+
const wrapTxn = (mode) => {
|
|
162
|
+
return async (...bindParameters) => {
|
|
163
|
+
await db.exec("BEGIN " + mode);
|
|
164
|
+
db._inTransaction = true;
|
|
165
|
+
try {
|
|
166
|
+
const result = await fn(...bindParameters);
|
|
167
|
+
await db.exec("COMMIT");
|
|
168
|
+
db._inTransaction = false;
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
await db.exec("ROLLBACK");
|
|
173
|
+
db._inTransaction = false;
|
|
174
|
+
throw err;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
const properties = {
|
|
179
|
+
default: { value: wrapTxn("") },
|
|
180
|
+
deferred: { value: wrapTxn("DEFERRED") },
|
|
181
|
+
immediate: { value: wrapTxn("IMMEDIATE") },
|
|
182
|
+
exclusive: { value: wrapTxn("EXCLUSIVE") },
|
|
183
|
+
database: { value: this, enumerable: true },
|
|
184
|
+
};
|
|
185
|
+
Object.defineProperties(properties.default.value, properties);
|
|
186
|
+
Object.defineProperties(properties.deferred.value, properties);
|
|
187
|
+
Object.defineProperties(properties.immediate.value, properties);
|
|
188
|
+
Object.defineProperties(properties.exclusive.value, properties);
|
|
189
|
+
return properties.default.value;
|
|
190
|
+
}
|
|
79
191
|
/**
|
|
80
192
|
* Close the connection.
|
|
81
193
|
*
|
|
82
194
|
* This sends a close request to the server to properly clean up the stream.
|
|
83
195
|
*/
|
|
84
196
|
async close() {
|
|
197
|
+
this.isOpen = false;
|
|
85
198
|
await this.session.close();
|
|
86
199
|
}
|
|
200
|
+
async reconnect() {
|
|
201
|
+
try {
|
|
202
|
+
if (this.isOpen) {
|
|
203
|
+
await this.close();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
this.session = new Session(this.config);
|
|
208
|
+
this.isOpen = true;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
87
211
|
}
|
|
88
212
|
/**
|
|
89
213
|
* Create a new connection to a Turso database.
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/protocol.d.ts
CHANGED
|
@@ -51,9 +51,21 @@ export interface SequenceRequest {
|
|
|
51
51
|
export interface CloseRequest {
|
|
52
52
|
type: 'close';
|
|
53
53
|
}
|
|
54
|
+
export interface DescribeRequest {
|
|
55
|
+
type: 'describe';
|
|
56
|
+
sql: string;
|
|
57
|
+
}
|
|
58
|
+
export interface DescribeResult {
|
|
59
|
+
params: Array<{
|
|
60
|
+
name?: string;
|
|
61
|
+
}>;
|
|
62
|
+
cols: Column[];
|
|
63
|
+
is_explain: boolean;
|
|
64
|
+
is_readonly: boolean;
|
|
65
|
+
}
|
|
54
66
|
export interface PipelineRequest {
|
|
55
67
|
baton: string | null;
|
|
56
|
-
requests: (ExecuteRequest | BatchRequest | SequenceRequest | CloseRequest)[];
|
|
68
|
+
requests: (ExecuteRequest | BatchRequest | SequenceRequest | CloseRequest | DescribeRequest)[];
|
|
57
69
|
}
|
|
58
70
|
export interface PipelineResponse {
|
|
59
71
|
baton: string | null;
|
|
@@ -61,8 +73,8 @@ export interface PipelineResponse {
|
|
|
61
73
|
results: Array<{
|
|
62
74
|
type: 'ok' | 'error';
|
|
63
75
|
response?: {
|
|
64
|
-
type: 'execute' | 'batch' | 'sequence' | 'close';
|
|
65
|
-
result?: ExecuteResult;
|
|
76
|
+
type: 'execute' | 'batch' | 'sequence' | 'close' | 'describe';
|
|
77
|
+
result?: ExecuteResult | DescribeResult;
|
|
66
78
|
};
|
|
67
79
|
error?: {
|
|
68
80
|
message: string;
|
|
@@ -71,7 +83,7 @@ export interface PipelineResponse {
|
|
|
71
83
|
}>;
|
|
72
84
|
}
|
|
73
85
|
export declare function encodeValue(value: any): Value;
|
|
74
|
-
export declare function decodeValue(value: Value): any;
|
|
86
|
+
export declare function decodeValue(value: Value, safeIntegers?: boolean): any;
|
|
75
87
|
export interface CursorRequest {
|
|
76
88
|
baton: string | null;
|
|
77
89
|
batch: {
|
|
@@ -94,8 +106,10 @@ export interface CursorEntry {
|
|
|
94
106
|
code: string;
|
|
95
107
|
};
|
|
96
108
|
}
|
|
97
|
-
|
|
109
|
+
/** HTTP header key for the encryption key */
|
|
110
|
+
export declare const ENCRYPTION_KEY_HEADER = "x-turso-encryption-key";
|
|
111
|
+
export declare function executeCursor(url: string, authToken: string | undefined, request: CursorRequest, remoteEncryptionKey?: string): Promise<{
|
|
98
112
|
response: CursorResponse;
|
|
99
113
|
entries: AsyncGenerator<CursorEntry>;
|
|
100
114
|
}>;
|
|
101
|
-
export declare function executePipeline(url: string, authToken: string, request: PipelineRequest): Promise<PipelineResponse>;
|
|
115
|
+
export declare function executePipeline(url: string, authToken: string | undefined, request: PipelineRequest, remoteEncryptionKey?: string): Promise<PipelineResponse>;
|
package/dist/protocol.js
CHANGED
|
@@ -4,11 +4,17 @@ export function encodeValue(value) {
|
|
|
4
4
|
return { type: 'null' };
|
|
5
5
|
}
|
|
6
6
|
if (typeof value === 'number') {
|
|
7
|
-
if (Number.
|
|
8
|
-
|
|
7
|
+
if (!Number.isFinite(value)) {
|
|
8
|
+
throw new Error("Only finite numbers (not Infinity or NaN) can be passed as arguments");
|
|
9
9
|
}
|
|
10
10
|
return { type: 'float', value };
|
|
11
11
|
}
|
|
12
|
+
if (typeof value === 'bigint') {
|
|
13
|
+
return { type: 'integer', value: value.toString() };
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === 'boolean') {
|
|
16
|
+
return { type: 'integer', value: value ? '1' : '0' };
|
|
17
|
+
}
|
|
12
18
|
if (typeof value === 'string') {
|
|
13
19
|
return { type: 'text', value };
|
|
14
20
|
}
|
|
@@ -18,11 +24,14 @@ export function encodeValue(value) {
|
|
|
18
24
|
}
|
|
19
25
|
return { type: 'text', value: String(value) };
|
|
20
26
|
}
|
|
21
|
-
export function decodeValue(value) {
|
|
27
|
+
export function decodeValue(value, safeIntegers = false) {
|
|
22
28
|
switch (value.type) {
|
|
23
29
|
case 'null':
|
|
24
30
|
return null;
|
|
25
31
|
case 'integer':
|
|
32
|
+
if (safeIntegers) {
|
|
33
|
+
return BigInt(value.value);
|
|
34
|
+
}
|
|
26
35
|
return parseInt(value.value, 10);
|
|
27
36
|
case 'float':
|
|
28
37
|
return value.value;
|
|
@@ -35,20 +44,28 @@ export function decodeValue(value) {
|
|
|
35
44
|
for (let i = 0; i < binaryString.length; i++) {
|
|
36
45
|
bytes[i] = binaryString.charCodeAt(i);
|
|
37
46
|
}
|
|
38
|
-
return bytes;
|
|
47
|
+
return Buffer.from(bytes);
|
|
39
48
|
}
|
|
40
49
|
return null;
|
|
41
50
|
default:
|
|
42
51
|
return null;
|
|
43
52
|
}
|
|
44
53
|
}
|
|
45
|
-
|
|
54
|
+
/** HTTP header key for the encryption key */
|
|
55
|
+
export const ENCRYPTION_KEY_HEADER = 'x-turso-encryption-key';
|
|
56
|
+
export async function executeCursor(url, authToken, request, remoteEncryptionKey) {
|
|
57
|
+
const headers = {
|
|
58
|
+
'Content-Type': 'application/json',
|
|
59
|
+
};
|
|
60
|
+
if (authToken) {
|
|
61
|
+
headers['Authorization'] = `Bearer ${authToken}`;
|
|
62
|
+
}
|
|
63
|
+
if (remoteEncryptionKey) {
|
|
64
|
+
headers[ENCRYPTION_KEY_HEADER] = remoteEncryptionKey;
|
|
65
|
+
}
|
|
46
66
|
const response = await fetch(`${url}/v3/cursor`, {
|
|
47
67
|
method: 'POST',
|
|
48
|
-
headers
|
|
49
|
-
'Content-Type': 'application/json',
|
|
50
|
-
'Authorization': `Bearer ${authToken}`,
|
|
51
|
-
},
|
|
68
|
+
headers,
|
|
52
69
|
body: JSON.stringify(request),
|
|
53
70
|
});
|
|
54
71
|
if (!response.ok) {
|
|
@@ -127,13 +144,19 @@ export async function executeCursor(url, authToken, request) {
|
|
|
127
144
|
}
|
|
128
145
|
return { response: cursorResponse, entries: parseEntries() };
|
|
129
146
|
}
|
|
130
|
-
export async function executePipeline(url, authToken, request) {
|
|
147
|
+
export async function executePipeline(url, authToken, request, remoteEncryptionKey) {
|
|
148
|
+
const headers = {
|
|
149
|
+
'Content-Type': 'application/json',
|
|
150
|
+
};
|
|
151
|
+
if (authToken) {
|
|
152
|
+
headers['Authorization'] = `Bearer ${authToken}`;
|
|
153
|
+
}
|
|
154
|
+
if (remoteEncryptionKey) {
|
|
155
|
+
headers[ENCRYPTION_KEY_HEADER] = remoteEncryptionKey;
|
|
156
|
+
}
|
|
131
157
|
const response = await fetch(`${url}/v3/pipeline`, {
|
|
132
158
|
method: 'POST',
|
|
133
|
-
headers
|
|
134
|
-
'Content-Type': 'application/json',
|
|
135
|
-
'Authorization': `Bearer ${authToken}`,
|
|
136
|
-
},
|
|
159
|
+
headers,
|
|
137
160
|
body: JSON.stringify(request),
|
|
138
161
|
});
|
|
139
162
|
if (!response.ok) {
|
package/dist/session.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { type CursorResponse, type CursorEntry } from './protocol.js';
|
|
1
|
+
import { type CursorResponse, type CursorEntry, type DescribeResult } from './protocol.js';
|
|
2
2
|
/**
|
|
3
3
|
* Configuration options for a session.
|
|
4
4
|
*/
|
|
5
5
|
export interface SessionConfig {
|
|
6
6
|
/** Database URL */
|
|
7
7
|
url: string;
|
|
8
|
-
/** Authentication token */
|
|
9
|
-
authToken
|
|
8
|
+
/** Authentication token (optional for local development with turso dev) */
|
|
9
|
+
authToken?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Encryption key for the remote database (base64 encoded)
|
|
12
|
+
* to enable access to encrypted Turso Cloud databases.
|
|
13
|
+
*/
|
|
14
|
+
remoteEncryptionKey?: string;
|
|
10
15
|
}
|
|
11
16
|
/**
|
|
12
17
|
* A database session that manages the connection state and baton.
|
|
@@ -19,14 +24,22 @@ export declare class Session {
|
|
|
19
24
|
private baton;
|
|
20
25
|
private baseUrl;
|
|
21
26
|
constructor(config: SessionConfig);
|
|
27
|
+
/**
|
|
28
|
+
* Describe a SQL statement to get its column metadata.
|
|
29
|
+
*
|
|
30
|
+
* @param sql - The SQL statement to describe
|
|
31
|
+
* @returns Promise resolving to the statement description
|
|
32
|
+
*/
|
|
33
|
+
describe(sql: string): Promise<DescribeResult>;
|
|
22
34
|
/**
|
|
23
35
|
* Execute a SQL statement and return all results.
|
|
24
36
|
*
|
|
25
37
|
* @param sql - The SQL statement to execute
|
|
26
38
|
* @param args - Optional array of parameter values or object with named parameters
|
|
39
|
+
* @param safeIntegers - Whether to return integers as BigInt
|
|
27
40
|
* @returns Promise resolving to the complete result set
|
|
28
41
|
*/
|
|
29
|
-
execute(sql: string, args?: any[] | Record<string, any
|
|
42
|
+
execute(sql: string, args?: any[] | Record<string, any>, safeIntegers?: boolean): Promise<any>;
|
|
30
43
|
/**
|
|
31
44
|
* Execute a SQL statement and return the raw response and entries.
|
|
32
45
|
*
|
|
@@ -44,7 +57,7 @@ export declare class Session {
|
|
|
44
57
|
* @param entries - Async generator of cursor entries
|
|
45
58
|
* @returns Promise resolving to the processed result
|
|
46
59
|
*/
|
|
47
|
-
processCursorEntries(entries: AsyncGenerator<CursorEntry
|
|
60
|
+
processCursorEntries(entries: AsyncGenerator<CursorEntry>, safeIntegers?: boolean): Promise<any>;
|
|
48
61
|
/**
|
|
49
62
|
* Create a row object with both array and named property access.
|
|
50
63
|
*
|
package/dist/session.js
CHANGED
|
@@ -18,16 +18,48 @@ export class Session {
|
|
|
18
18
|
this.config = config;
|
|
19
19
|
this.baseUrl = normalizeUrl(config.url);
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Describe a SQL statement to get its column metadata.
|
|
23
|
+
*
|
|
24
|
+
* @param sql - The SQL statement to describe
|
|
25
|
+
* @returns Promise resolving to the statement description
|
|
26
|
+
*/
|
|
27
|
+
async describe(sql) {
|
|
28
|
+
const request = {
|
|
29
|
+
baton: this.baton,
|
|
30
|
+
requests: [{
|
|
31
|
+
type: "describe",
|
|
32
|
+
sql: sql
|
|
33
|
+
}]
|
|
34
|
+
};
|
|
35
|
+
const response = await executePipeline(this.baseUrl, this.config.authToken, request, this.config.remoteEncryptionKey);
|
|
36
|
+
this.baton = response.baton;
|
|
37
|
+
if (response.base_url) {
|
|
38
|
+
this.baseUrl = response.base_url;
|
|
39
|
+
}
|
|
40
|
+
// Check for errors in the response
|
|
41
|
+
if (response.results && response.results[0]) {
|
|
42
|
+
const result = response.results[0];
|
|
43
|
+
if (result.type === "error") {
|
|
44
|
+
throw new DatabaseError(result.error?.message || 'Describe execution failed');
|
|
45
|
+
}
|
|
46
|
+
if (result.response?.type === "describe" && result.response.result) {
|
|
47
|
+
return result.response.result;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
throw new DatabaseError('Unexpected describe response');
|
|
51
|
+
}
|
|
21
52
|
/**
|
|
22
53
|
* Execute a SQL statement and return all results.
|
|
23
54
|
*
|
|
24
55
|
* @param sql - The SQL statement to execute
|
|
25
56
|
* @param args - Optional array of parameter values or object with named parameters
|
|
57
|
+
* @param safeIntegers - Whether to return integers as BigInt
|
|
26
58
|
* @returns Promise resolving to the complete result set
|
|
27
59
|
*/
|
|
28
|
-
async execute(sql, args = []) {
|
|
60
|
+
async execute(sql, args = [], safeIntegers = false) {
|
|
29
61
|
const { response, entries } = await this.executeRaw(sql, args);
|
|
30
|
-
const result = await this.processCursorEntries(entries);
|
|
62
|
+
const result = await this.processCursorEntries(entries, safeIntegers);
|
|
31
63
|
return result;
|
|
32
64
|
}
|
|
33
65
|
/**
|
|
@@ -86,7 +118,7 @@ export class Session {
|
|
|
86
118
|
}]
|
|
87
119
|
}
|
|
88
120
|
};
|
|
89
|
-
const { response, entries } = await executeCursor(this.baseUrl, this.config.authToken, request);
|
|
121
|
+
const { response, entries } = await executeCursor(this.baseUrl, this.config.authToken, request, this.config.remoteEncryptionKey);
|
|
90
122
|
this.baton = response.baton;
|
|
91
123
|
if (response.base_url) {
|
|
92
124
|
this.baseUrl = response.base_url;
|
|
@@ -99,7 +131,7 @@ export class Session {
|
|
|
99
131
|
* @param entries - Async generator of cursor entries
|
|
100
132
|
* @returns Promise resolving to the processed result
|
|
101
133
|
*/
|
|
102
|
-
async processCursorEntries(entries) {
|
|
134
|
+
async processCursorEntries(entries, safeIntegers = false) {
|
|
103
135
|
let columns = [];
|
|
104
136
|
let columnTypes = [];
|
|
105
137
|
let rows = [];
|
|
@@ -115,7 +147,7 @@ export class Session {
|
|
|
115
147
|
break;
|
|
116
148
|
case 'row':
|
|
117
149
|
if (entry.row) {
|
|
118
|
-
const decodedRow = entry.row.map(decodeValue);
|
|
150
|
+
const decodedRow = entry.row.map(value => decodeValue(value, safeIntegers));
|
|
119
151
|
const rowObject = this.createRowObject(decodedRow, columns);
|
|
120
152
|
rows.push(rowObject);
|
|
121
153
|
}
|
|
@@ -184,7 +216,7 @@ export class Session {
|
|
|
184
216
|
}))
|
|
185
217
|
}
|
|
186
218
|
};
|
|
187
|
-
const { response, entries } = await executeCursor(this.baseUrl, this.config.authToken, request);
|
|
219
|
+
const { response, entries } = await executeCursor(this.baseUrl, this.config.authToken, request, this.config.remoteEncryptionKey);
|
|
188
220
|
this.baton = response.baton;
|
|
189
221
|
if (response.base_url) {
|
|
190
222
|
this.baseUrl = response.base_url;
|
|
@@ -225,7 +257,7 @@ export class Session {
|
|
|
225
257
|
sql: sql
|
|
226
258
|
}]
|
|
227
259
|
};
|
|
228
|
-
const response = await executePipeline(this.baseUrl, this.config.authToken, request);
|
|
260
|
+
const response = await executePipeline(this.baseUrl, this.config.authToken, request, this.config.remoteEncryptionKey);
|
|
229
261
|
this.baton = response.baton;
|
|
230
262
|
if (response.base_url) {
|
|
231
263
|
this.baseUrl = response.base_url;
|
|
@@ -254,7 +286,7 @@ export class Session {
|
|
|
254
286
|
type: "close"
|
|
255
287
|
}]
|
|
256
288
|
};
|
|
257
|
-
await executePipeline(this.baseUrl, this.config.authToken, request);
|
|
289
|
+
await executePipeline(this.baseUrl, this.config.authToken, request, this.config.remoteEncryptionKey);
|
|
258
290
|
}
|
|
259
291
|
catch (error) {
|
|
260
292
|
// Ignore errors during close, as the connection might already be closed
|
package/dist/statement.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Column } from './protocol.js';
|
|
1
2
|
import { type SessionConfig } from './session.js';
|
|
2
3
|
/**
|
|
3
4
|
* A prepared SQL statement that can be executed in multiple ways.
|
|
@@ -12,7 +13,26 @@ export declare class Statement {
|
|
|
12
13
|
private session;
|
|
13
14
|
private sql;
|
|
14
15
|
private presentationMode;
|
|
15
|
-
|
|
16
|
+
private safeIntegerMode;
|
|
17
|
+
private columnMetadata;
|
|
18
|
+
constructor(sessionConfig: SessionConfig, sql: string, columns?: Column[]);
|
|
19
|
+
/**
|
|
20
|
+
* Whether the prepared statement returns data.
|
|
21
|
+
*
|
|
22
|
+
* This is `true` for SELECT queries and statements with RETURNING clause,
|
|
23
|
+
* and `false` for INSERT, UPDATE, DELETE statements without RETURNING.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const stmt = await conn.prepare(sql);
|
|
28
|
+
* if (stmt.reader) {
|
|
29
|
+
* return stmt.all(args); // SELECT-like query
|
|
30
|
+
* } else {
|
|
31
|
+
* return stmt.run(args); // INSERT/UPDATE/DELETE
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
get reader(): boolean;
|
|
16
36
|
/**
|
|
17
37
|
* Enable raw mode to return arrays instead of objects.
|
|
18
38
|
*
|
|
@@ -27,6 +47,40 @@ export declare class Statement {
|
|
|
27
47
|
* ```
|
|
28
48
|
*/
|
|
29
49
|
raw(raw?: boolean): Statement;
|
|
50
|
+
/**
|
|
51
|
+
* Enable pluck mode to return only the first column value from each row.
|
|
52
|
+
*
|
|
53
|
+
* @param pluck Enable or disable pluck mode. If you don't pass the parameter, pluck mode is enabled.
|
|
54
|
+
* @returns This statement instance for chaining
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const stmt = client.prepare("SELECT id FROM users");
|
|
59
|
+
* const ids = await stmt.pluck().all();
|
|
60
|
+
* console.log(ids); // [1, 2, 3, ...]
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
pluck(pluck?: boolean): Statement;
|
|
64
|
+
/**
|
|
65
|
+
* Sets safe integers mode for this statement.
|
|
66
|
+
*
|
|
67
|
+
* @param toggle Whether to use safe integers. If you don't pass the parameter, safe integers mode is enabled.
|
|
68
|
+
* @returns This statement instance for chaining
|
|
69
|
+
*/
|
|
70
|
+
safeIntegers(toggle?: boolean): Statement;
|
|
71
|
+
/**
|
|
72
|
+
* Get column information for this statement.
|
|
73
|
+
*
|
|
74
|
+
* @returns Array of column metadata objects matching the native bindings format
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const stmt = await client.prepare("SELECT id, name, email FROM users");
|
|
79
|
+
* const columns = stmt.columns();
|
|
80
|
+
* console.log(columns); // [{ name: 'id', type: 'INTEGER', column: null, database: null, table: null }, ...]
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
columns(): any[];
|
|
30
84
|
/**
|
|
31
85
|
* Executes the prepared statement.
|
|
32
86
|
*
|
package/dist/statement.js
CHANGED
|
@@ -11,10 +11,31 @@ import { DatabaseError } from './error.js';
|
|
|
11
11
|
* - `iterate(args?)`: Returns an async iterator for streaming results
|
|
12
12
|
*/
|
|
13
13
|
export class Statement {
|
|
14
|
-
constructor(sessionConfig, sql) {
|
|
14
|
+
constructor(sessionConfig, sql, columns) {
|
|
15
15
|
this.presentationMode = 'expanded';
|
|
16
|
+
this.safeIntegerMode = false;
|
|
16
17
|
this.session = new Session(sessionConfig);
|
|
17
18
|
this.sql = sql;
|
|
19
|
+
this.columnMetadata = columns || [];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Whether the prepared statement returns data.
|
|
23
|
+
*
|
|
24
|
+
* This is `true` for SELECT queries and statements with RETURNING clause,
|
|
25
|
+
* and `false` for INSERT, UPDATE, DELETE statements without RETURNING.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const stmt = await conn.prepare(sql);
|
|
30
|
+
* if (stmt.reader) {
|
|
31
|
+
* return stmt.all(args); // SELECT-like query
|
|
32
|
+
* } else {
|
|
33
|
+
* return stmt.run(args); // INSERT/UPDATE/DELETE
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
get reader() {
|
|
38
|
+
return this.columnMetadata.length > 0;
|
|
18
39
|
}
|
|
19
40
|
/**
|
|
20
41
|
* Enable raw mode to return arrays instead of objects.
|
|
@@ -33,6 +54,51 @@ export class Statement {
|
|
|
33
54
|
this.presentationMode = raw === false ? 'expanded' : 'raw';
|
|
34
55
|
return this;
|
|
35
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Enable pluck mode to return only the first column value from each row.
|
|
59
|
+
*
|
|
60
|
+
* @param pluck Enable or disable pluck mode. If you don't pass the parameter, pluck mode is enabled.
|
|
61
|
+
* @returns This statement instance for chaining
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* const stmt = client.prepare("SELECT id FROM users");
|
|
66
|
+
* const ids = await stmt.pluck().all();
|
|
67
|
+
* console.log(ids); // [1, 2, 3, ...]
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
pluck(pluck) {
|
|
71
|
+
this.presentationMode = pluck === false ? 'expanded' : 'pluck';
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Sets safe integers mode for this statement.
|
|
76
|
+
*
|
|
77
|
+
* @param toggle Whether to use safe integers. If you don't pass the parameter, safe integers mode is enabled.
|
|
78
|
+
* @returns This statement instance for chaining
|
|
79
|
+
*/
|
|
80
|
+
safeIntegers(toggle) {
|
|
81
|
+
this.safeIntegerMode = toggle === false ? false : true;
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get column information for this statement.
|
|
86
|
+
*
|
|
87
|
+
* @returns Array of column metadata objects matching the native bindings format
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* const stmt = await client.prepare("SELECT id, name, email FROM users");
|
|
92
|
+
* const columns = stmt.columns();
|
|
93
|
+
* console.log(columns); // [{ name: 'id', type: 'INTEGER', column: null, database: null, table: null }, ...]
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
columns() {
|
|
97
|
+
return this.columnMetadata.map(col => ({
|
|
98
|
+
name: col.name,
|
|
99
|
+
type: col.decltype
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
36
102
|
/**
|
|
37
103
|
* Executes the prepared statement.
|
|
38
104
|
*
|
|
@@ -48,7 +114,7 @@ export class Statement {
|
|
|
48
114
|
*/
|
|
49
115
|
async run(args) {
|
|
50
116
|
const normalizedArgs = this.normalizeArgs(args);
|
|
51
|
-
const result = await this.session.execute(this.sql, normalizedArgs);
|
|
117
|
+
const result = await this.session.execute(this.sql, normalizedArgs, this.safeIntegerMode);
|
|
52
118
|
return { changes: result.rowsAffected, lastInsertRowid: result.lastInsertRowid };
|
|
53
119
|
}
|
|
54
120
|
/**
|
|
@@ -68,17 +134,26 @@ export class Statement {
|
|
|
68
134
|
*/
|
|
69
135
|
async get(args) {
|
|
70
136
|
const normalizedArgs = this.normalizeArgs(args);
|
|
71
|
-
const result = await this.session.execute(this.sql, normalizedArgs);
|
|
137
|
+
const result = await this.session.execute(this.sql, normalizedArgs, this.safeIntegerMode);
|
|
72
138
|
const row = result.rows[0];
|
|
73
139
|
if (!row) {
|
|
74
140
|
return undefined;
|
|
75
141
|
}
|
|
142
|
+
if (this.presentationMode === 'pluck') {
|
|
143
|
+
// In pluck mode, return only the first column value
|
|
144
|
+
return row[0];
|
|
145
|
+
}
|
|
76
146
|
if (this.presentationMode === 'raw') {
|
|
77
147
|
// In raw mode, return the row as a plain array (it already is one)
|
|
78
148
|
// The row object is already an array with column properties added
|
|
79
149
|
return [...row];
|
|
80
150
|
}
|
|
81
|
-
|
|
151
|
+
// In expanded mode, convert to plain object with named properties
|
|
152
|
+
const obj = {};
|
|
153
|
+
result.columns.forEach((col, i) => {
|
|
154
|
+
obj[col] = row[i];
|
|
155
|
+
});
|
|
156
|
+
return obj;
|
|
82
157
|
}
|
|
83
158
|
/**
|
|
84
159
|
* Execute the statement and return all rows.
|
|
@@ -95,13 +170,22 @@ export class Statement {
|
|
|
95
170
|
*/
|
|
96
171
|
async all(args) {
|
|
97
172
|
const normalizedArgs = this.normalizeArgs(args);
|
|
98
|
-
const result = await this.session.execute(this.sql, normalizedArgs);
|
|
173
|
+
const result = await this.session.execute(this.sql, normalizedArgs, this.safeIntegerMode);
|
|
174
|
+
if (this.presentationMode === 'pluck') {
|
|
175
|
+
// In pluck mode, return only the first column value from each row
|
|
176
|
+
return result.rows.map((row) => row[0]);
|
|
177
|
+
}
|
|
99
178
|
if (this.presentationMode === 'raw') {
|
|
100
|
-
// In raw mode, return arrays of values
|
|
101
|
-
// Each row is already an array with column properties added
|
|
102
179
|
return result.rows.map((row) => [...row]);
|
|
103
180
|
}
|
|
104
|
-
|
|
181
|
+
// In expanded mode, convert rows to plain objects with named properties
|
|
182
|
+
return result.rows.map((row) => {
|
|
183
|
+
const obj = {};
|
|
184
|
+
result.columns.forEach((col, i) => {
|
|
185
|
+
obj[col] = row[i];
|
|
186
|
+
});
|
|
187
|
+
return obj;
|
|
188
|
+
});
|
|
105
189
|
}
|
|
106
190
|
/**
|
|
107
191
|
* Execute the statement and return an async iterator for streaming results.
|
|
@@ -134,14 +218,22 @@ export class Statement {
|
|
|
134
218
|
break;
|
|
135
219
|
case 'row':
|
|
136
220
|
if (entry.row) {
|
|
137
|
-
const decodedRow = entry.row.map(decodeValue);
|
|
138
|
-
if (this.presentationMode === '
|
|
221
|
+
const decodedRow = entry.row.map(value => decodeValue(value, this.safeIntegerMode));
|
|
222
|
+
if (this.presentationMode === 'pluck') {
|
|
223
|
+
// In pluck mode, yield only the first column value
|
|
224
|
+
yield decodedRow[0];
|
|
225
|
+
}
|
|
226
|
+
else if (this.presentationMode === 'raw') {
|
|
139
227
|
// In raw mode, yield arrays of values
|
|
140
228
|
yield decodedRow;
|
|
141
229
|
}
|
|
142
230
|
else {
|
|
143
|
-
|
|
144
|
-
|
|
231
|
+
// In expanded mode, yield plain objects with named properties (consistent with all())
|
|
232
|
+
const obj = {};
|
|
233
|
+
columns.forEach((col, i) => {
|
|
234
|
+
obj[col] = decodedRow[i];
|
|
235
|
+
});
|
|
236
|
+
yield obj;
|
|
145
237
|
}
|
|
146
238
|
}
|
|
147
239
|
break;
|
package/package.json
CHANGED
package/dist/transaction.d.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { type SessionConfig } from "./session.js";
|
|
2
|
-
/**
|
|
3
|
-
* Transaction mode for controlling transaction behavior.
|
|
4
|
-
*/
|
|
5
|
-
export type TransactionMode = "write" | "read" | "deferred";
|
|
6
|
-
/**
|
|
7
|
-
* Transactions use a dedicated session to maintain state across multiple operations.
|
|
8
|
-
* All operations within a transaction are executed atomically - they either all
|
|
9
|
-
* succeed or all fail.
|
|
10
|
-
*/
|
|
11
|
-
export declare class Transaction {
|
|
12
|
-
private session;
|
|
13
|
-
private _closed;
|
|
14
|
-
private _committed;
|
|
15
|
-
private _rolledBack;
|
|
16
|
-
private constructor();
|
|
17
|
-
/**
|
|
18
|
-
* Create a new transaction instance.
|
|
19
|
-
*
|
|
20
|
-
* @param sessionConfig - Session configuration
|
|
21
|
-
* @param mode - Transaction mode
|
|
22
|
-
* @returns Promise resolving to a new Transaction instance
|
|
23
|
-
*/
|
|
24
|
-
static create(sessionConfig: SessionConfig, mode?: TransactionMode): Promise<Transaction>;
|
|
25
|
-
private initializeTransaction;
|
|
26
|
-
/**
|
|
27
|
-
* Execute a SQL statement within the transaction.
|
|
28
|
-
*
|
|
29
|
-
* @param sql - The SQL statement to execute
|
|
30
|
-
* @param args - Optional array of parameter values
|
|
31
|
-
* @returns Promise resolving to the complete result set
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```typescript
|
|
35
|
-
* const tx = await client.transaction();
|
|
36
|
-
* const result = await tx.execute("INSERT INTO users (name) VALUES (?)", ["Alice"]);
|
|
37
|
-
* await tx.commit();
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
execute(sql: string, args?: any[]): Promise<any>;
|
|
41
|
-
/**
|
|
42
|
-
* Execute multiple SQL statements as a batch within the transaction.
|
|
43
|
-
*
|
|
44
|
-
* @param statements - Array of SQL statements to execute
|
|
45
|
-
* @returns Promise resolving to batch execution results
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```typescript
|
|
49
|
-
* const tx = await client.transaction();
|
|
50
|
-
* await tx.batch([
|
|
51
|
-
* "INSERT INTO users (name) VALUES ('Alice')",
|
|
52
|
-
* "INSERT INTO users (name) VALUES ('Bob')"
|
|
53
|
-
* ]);
|
|
54
|
-
* await tx.commit();
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
batch(statements: string[]): Promise<any>;
|
|
58
|
-
/**
|
|
59
|
-
* Execute a SQL statement and return the raw response and entries.
|
|
60
|
-
*
|
|
61
|
-
* @param sql - The SQL statement to execute
|
|
62
|
-
* @param args - Optional array of parameter values
|
|
63
|
-
* @returns Promise resolving to the raw response and cursor entries
|
|
64
|
-
*/
|
|
65
|
-
executeRaw(sql: string, args?: any[]): Promise<{
|
|
66
|
-
response: any;
|
|
67
|
-
entries: AsyncGenerator<any>;
|
|
68
|
-
}>;
|
|
69
|
-
/**
|
|
70
|
-
* Commit the transaction, making all changes permanent.
|
|
71
|
-
*
|
|
72
|
-
* @returns Promise that resolves when the transaction is committed
|
|
73
|
-
*
|
|
74
|
-
* @example
|
|
75
|
-
* ```typescript
|
|
76
|
-
* const tx = await client.transaction();
|
|
77
|
-
* await tx.execute("INSERT INTO users (name) VALUES (?)", ["Alice"]);
|
|
78
|
-
* await tx.commit(); // Changes are now permanent
|
|
79
|
-
* ```
|
|
80
|
-
*/
|
|
81
|
-
commit(): Promise<void>;
|
|
82
|
-
/**
|
|
83
|
-
* Rollback the transaction, undoing all changes.
|
|
84
|
-
*
|
|
85
|
-
* @returns Promise that resolves when the transaction is rolled back
|
|
86
|
-
*
|
|
87
|
-
* @example
|
|
88
|
-
* ```typescript
|
|
89
|
-
* const tx = await client.transaction();
|
|
90
|
-
* try {
|
|
91
|
-
* await tx.execute("INSERT INTO users (name) VALUES (?)", ["Alice"]);
|
|
92
|
-
* await tx.execute("INSERT INTO invalid_table VALUES (1)"); // This will fail
|
|
93
|
-
* await tx.commit();
|
|
94
|
-
* } catch (error) {
|
|
95
|
-
* await tx.rollback(); // Undo the first INSERT
|
|
96
|
-
* }
|
|
97
|
-
* ```
|
|
98
|
-
*/
|
|
99
|
-
rollback(): Promise<void>;
|
|
100
|
-
/**
|
|
101
|
-
* Close the transaction without committing or rolling back.
|
|
102
|
-
*
|
|
103
|
-
* This will automatically rollback any uncommitted changes.
|
|
104
|
-
* It's safe to call this method multiple times.
|
|
105
|
-
*/
|
|
106
|
-
close(): void;
|
|
107
|
-
/**
|
|
108
|
-
* Check if the transaction is closed.
|
|
109
|
-
*
|
|
110
|
-
* @returns True if the transaction has been committed, rolled back, or closed
|
|
111
|
-
*/
|
|
112
|
-
get closed(): boolean;
|
|
113
|
-
/**
|
|
114
|
-
* Check if the transaction has been committed.
|
|
115
|
-
*
|
|
116
|
-
* @returns True if the transaction has been successfully committed
|
|
117
|
-
*/
|
|
118
|
-
get committed(): boolean;
|
|
119
|
-
/**
|
|
120
|
-
* Check if the transaction has been rolled back.
|
|
121
|
-
*
|
|
122
|
-
* @returns True if the transaction has been rolled back
|
|
123
|
-
*/
|
|
124
|
-
get rolledBack(): boolean;
|
|
125
|
-
/**
|
|
126
|
-
* Check transaction state and throw if it's not valid for operations.
|
|
127
|
-
*
|
|
128
|
-
* @throws Error if the transaction is closed
|
|
129
|
-
*/
|
|
130
|
-
private checkState;
|
|
131
|
-
}
|
package/dist/transaction.js
DELETED
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { Session } from "./session.js";
|
|
2
|
-
import { DatabaseError } from "./error.js";
|
|
3
|
-
/**
|
|
4
|
-
* Transactions use a dedicated session to maintain state across multiple operations.
|
|
5
|
-
* All operations within a transaction are executed atomically - they either all
|
|
6
|
-
* succeed or all fail.
|
|
7
|
-
*/
|
|
8
|
-
export class Transaction {
|
|
9
|
-
constructor(sessionConfig, mode = "deferred") {
|
|
10
|
-
this._closed = false;
|
|
11
|
-
this._committed = false;
|
|
12
|
-
this._rolledBack = false;
|
|
13
|
-
this.session = new Session(sessionConfig);
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Create a new transaction instance.
|
|
17
|
-
*
|
|
18
|
-
* @param sessionConfig - Session configuration
|
|
19
|
-
* @param mode - Transaction mode
|
|
20
|
-
* @returns Promise resolving to a new Transaction instance
|
|
21
|
-
*/
|
|
22
|
-
static async create(sessionConfig, mode = "deferred") {
|
|
23
|
-
const transaction = new Transaction(sessionConfig, mode);
|
|
24
|
-
await transaction.initializeTransaction(mode);
|
|
25
|
-
return transaction;
|
|
26
|
-
}
|
|
27
|
-
async initializeTransaction(mode) {
|
|
28
|
-
let beginStatement;
|
|
29
|
-
switch (mode) {
|
|
30
|
-
case "write":
|
|
31
|
-
beginStatement = "BEGIN IMMEDIATE";
|
|
32
|
-
break;
|
|
33
|
-
case "read":
|
|
34
|
-
beginStatement = "BEGIN";
|
|
35
|
-
break;
|
|
36
|
-
case "deferred":
|
|
37
|
-
default:
|
|
38
|
-
beginStatement = "BEGIN DEFERRED";
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
await this.session.execute(beginStatement);
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Execute a SQL statement within the transaction.
|
|
45
|
-
*
|
|
46
|
-
* @param sql - The SQL statement to execute
|
|
47
|
-
* @param args - Optional array of parameter values
|
|
48
|
-
* @returns Promise resolving to the complete result set
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* ```typescript
|
|
52
|
-
* const tx = await client.transaction();
|
|
53
|
-
* const result = await tx.execute("INSERT INTO users (name) VALUES (?)", ["Alice"]);
|
|
54
|
-
* await tx.commit();
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
async execute(sql, args = []) {
|
|
58
|
-
this.checkState();
|
|
59
|
-
return this.session.execute(sql, args);
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Execute multiple SQL statements as a batch within the transaction.
|
|
63
|
-
*
|
|
64
|
-
* @param statements - Array of SQL statements to execute
|
|
65
|
-
* @returns Promise resolving to batch execution results
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* ```typescript
|
|
69
|
-
* const tx = await client.transaction();
|
|
70
|
-
* await tx.batch([
|
|
71
|
-
* "INSERT INTO users (name) VALUES ('Alice')",
|
|
72
|
-
* "INSERT INTO users (name) VALUES ('Bob')"
|
|
73
|
-
* ]);
|
|
74
|
-
* await tx.commit();
|
|
75
|
-
* ```
|
|
76
|
-
*/
|
|
77
|
-
async batch(statements) {
|
|
78
|
-
this.checkState();
|
|
79
|
-
return this.session.batch(statements);
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Execute a SQL statement and return the raw response and entries.
|
|
83
|
-
*
|
|
84
|
-
* @param sql - The SQL statement to execute
|
|
85
|
-
* @param args - Optional array of parameter values
|
|
86
|
-
* @returns Promise resolving to the raw response and cursor entries
|
|
87
|
-
*/
|
|
88
|
-
async executeRaw(sql, args = []) {
|
|
89
|
-
this.checkState();
|
|
90
|
-
return this.session.executeRaw(sql, args);
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Commit the transaction, making all changes permanent.
|
|
94
|
-
*
|
|
95
|
-
* @returns Promise that resolves when the transaction is committed
|
|
96
|
-
*
|
|
97
|
-
* @example
|
|
98
|
-
* ```typescript
|
|
99
|
-
* const tx = await client.transaction();
|
|
100
|
-
* await tx.execute("INSERT INTO users (name) VALUES (?)", ["Alice"]);
|
|
101
|
-
* await tx.commit(); // Changes are now permanent
|
|
102
|
-
* ```
|
|
103
|
-
*/
|
|
104
|
-
async commit() {
|
|
105
|
-
this.checkState();
|
|
106
|
-
try {
|
|
107
|
-
await this.session.execute("COMMIT");
|
|
108
|
-
this._committed = true;
|
|
109
|
-
this._closed = true;
|
|
110
|
-
}
|
|
111
|
-
catch (error) {
|
|
112
|
-
// If commit fails, the transaction is still open
|
|
113
|
-
if (error instanceof Error) {
|
|
114
|
-
throw new DatabaseError(error.message);
|
|
115
|
-
}
|
|
116
|
-
throw new DatabaseError('Transaction commit failed');
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Rollback the transaction, undoing all changes.
|
|
121
|
-
*
|
|
122
|
-
* @returns Promise that resolves when the transaction is rolled back
|
|
123
|
-
*
|
|
124
|
-
* @example
|
|
125
|
-
* ```typescript
|
|
126
|
-
* const tx = await client.transaction();
|
|
127
|
-
* try {
|
|
128
|
-
* await tx.execute("INSERT INTO users (name) VALUES (?)", ["Alice"]);
|
|
129
|
-
* await tx.execute("INSERT INTO invalid_table VALUES (1)"); // This will fail
|
|
130
|
-
* await tx.commit();
|
|
131
|
-
* } catch (error) {
|
|
132
|
-
* await tx.rollback(); // Undo the first INSERT
|
|
133
|
-
* }
|
|
134
|
-
* ```
|
|
135
|
-
*/
|
|
136
|
-
async rollback() {
|
|
137
|
-
if (this._closed) {
|
|
138
|
-
return; // Already closed, nothing to rollback
|
|
139
|
-
}
|
|
140
|
-
try {
|
|
141
|
-
await this.session.execute("ROLLBACK");
|
|
142
|
-
}
|
|
143
|
-
catch (error) {
|
|
144
|
-
// Rollback errors are generally not critical - the transaction is abandoned anyway
|
|
145
|
-
}
|
|
146
|
-
finally {
|
|
147
|
-
this._rolledBack = true;
|
|
148
|
-
this._closed = true;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Close the transaction without committing or rolling back.
|
|
153
|
-
*
|
|
154
|
-
* This will automatically rollback any uncommitted changes.
|
|
155
|
-
* It's safe to call this method multiple times.
|
|
156
|
-
*/
|
|
157
|
-
close() {
|
|
158
|
-
if (!this._closed) {
|
|
159
|
-
// Async rollback - don't wait for it to complete
|
|
160
|
-
this.rollback().catch(() => {
|
|
161
|
-
// Ignore rollback errors on close
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Check if the transaction is closed.
|
|
167
|
-
*
|
|
168
|
-
* @returns True if the transaction has been committed, rolled back, or closed
|
|
169
|
-
*/
|
|
170
|
-
get closed() {
|
|
171
|
-
return this._closed;
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Check if the transaction has been committed.
|
|
175
|
-
*
|
|
176
|
-
* @returns True if the transaction has been successfully committed
|
|
177
|
-
*/
|
|
178
|
-
get committed() {
|
|
179
|
-
return this._committed;
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Check if the transaction has been rolled back.
|
|
183
|
-
*
|
|
184
|
-
* @returns True if the transaction has been rolled back
|
|
185
|
-
*/
|
|
186
|
-
get rolledBack() {
|
|
187
|
-
return this._rolledBack;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Check transaction state and throw if it's not valid for operations.
|
|
191
|
-
*
|
|
192
|
-
* @throws Error if the transaction is closed
|
|
193
|
-
*/
|
|
194
|
-
checkState() {
|
|
195
|
-
if (this._closed) {
|
|
196
|
-
if (this._committed) {
|
|
197
|
-
throw new DatabaseError("Transaction has already been committed");
|
|
198
|
-
}
|
|
199
|
-
else if (this._rolledBack) {
|
|
200
|
-
throw new DatabaseError("Transaction has already been rolled back");
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
throw new DatabaseError("Transaction has been closed");
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|