@tursodatabase/serverless 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/compat.js CHANGED
@@ -1,4 +1,4 @@
1
- import { connect } from './connection.js';
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 tursoConfig = {
17
+ const sessionConfig = {
18
18
  url: config.url,
19
19
  authToken: config.authToken || ''
20
20
  };
21
- this.connection = connect(tursoConfig);
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
- const result = await this.connection.execute(normalizedStmt.sql, normalizedStmt.args);
112
- return this.convertResult(result);
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.connection.batch(sqlStatements, mode);
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
- throw new LibsqlError("Execute multiple not implemented", "NOT_IMPLEMENTED");
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
  /**
@@ -35,7 +35,6 @@ export declare class Connection {
35
35
  * Execute a SQL statement and return all results.
36
36
  *
37
37
  * @param sql - The SQL statement to execute
38
- * @param args - Optional array of parameter values
39
38
  * @returns Promise resolving to the complete result set
40
39
  *
41
40
  * @example
@@ -44,7 +43,7 @@ export declare class Connection {
44
43
  * console.log(result.rows);
45
44
  * ```
46
45
  */
47
- execute(sql: string, args?: any[]): Promise<any>;
46
+ exec(sql: string): Promise<any>;
48
47
  /**
49
48
  * Execute multiple SQL statements in a batch.
50
49
  *
@@ -62,6 +61,19 @@ export declare class Connection {
62
61
  * ```
63
62
  */
64
63
  batch(statements: string[], mode?: string): Promise<any>;
64
+ /**
65
+ * Execute a pragma.
66
+ *
67
+ * @param pragma - The pragma to execute
68
+ * @returns Promise resolving to the result of the pragma
69
+ */
70
+ pragma(pragma: string): Promise<any>;
71
+ /**
72
+ * Close the connection.
73
+ *
74
+ * This sends a close request to the server to properly clean up the stream.
75
+ */
76
+ close(): Promise<void>;
65
77
  }
66
78
  /**
67
79
  * Create a new connection to a Turso database.
@@ -8,6 +8,9 @@ import { Statement } from './statement.js';
8
8
  */
9
9
  export class Connection {
10
10
  constructor(config) {
11
+ if (!config.url) {
12
+ throw new Error("invalid config: url is required");
13
+ }
11
14
  this.config = config;
12
15
  this.session = new Session(config);
13
16
  }
@@ -33,7 +36,6 @@ export class Connection {
33
36
  * Execute a SQL statement and return all results.
34
37
  *
35
38
  * @param sql - The SQL statement to execute
36
- * @param args - Optional array of parameter values
37
39
  * @returns Promise resolving to the complete result set
38
40
  *
39
41
  * @example
@@ -42,8 +44,8 @@ export class Connection {
42
44
  * console.log(result.rows);
43
45
  * ```
44
46
  */
45
- async execute(sql, args = []) {
46
- return this.session.execute(sql, args);
47
+ async exec(sql) {
48
+ return this.session.sequence(sql);
47
49
  }
48
50
  /**
49
51
  * Execute multiple SQL statements in a batch.
@@ -64,6 +66,24 @@ export class Connection {
64
66
  async batch(statements, mode) {
65
67
  return this.session.batch(statements);
66
68
  }
69
+ /**
70
+ * Execute a pragma.
71
+ *
72
+ * @param pragma - The pragma to execute
73
+ * @returns Promise resolving to the result of the pragma
74
+ */
75
+ async pragma(pragma) {
76
+ const sql = `PRAGMA ${pragma}`;
77
+ return this.session.execute(sql);
78
+ }
79
+ /**
80
+ * Close the connection.
81
+ *
82
+ * This sends a close request to the server to properly clean up the stream.
83
+ */
84
+ async close() {
85
+ await this.session.close();
86
+ }
67
87
  }
68
88
  /**
69
89
  * Create a new connection to a Turso database.
@@ -0,0 +1,3 @@
1
+ export declare class DatabaseError extends Error {
2
+ constructor(message: string);
3
+ }
package/dist/error.js ADDED
@@ -0,0 +1,7 @@
1
+ export class DatabaseError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'DatabaseError';
5
+ Object.setPrototypeOf(this, DatabaseError.prototype);
6
+ }
7
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { Connection, connect, type Config } from './connection.js';
2
2
  export { Statement } from './statement.js';
3
+ export { DatabaseError } from './error.js';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  // Turso serverless driver entry point
2
2
  export { Connection, connect } from './connection.js';
3
3
  export { Statement } from './statement.js';
4
+ export { DatabaseError } from './error.js';
@@ -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: Value[];
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: ExecuteResult;
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 Error(errorMessage);
66
+ throw new DatabaseError(errorMessage);
66
67
  }
67
68
  const reader = response.body?.getReader();
68
69
  if (!reader) {
69
- throw new Error('No response body');
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
- if (isFirstLine) {
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
- const entries = parseEntries();
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 Error(`HTTP error! status: ${response.status}`);
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: args.map(encodeValue),
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 Error(entry.error?.message || 'SQL execution failed');
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 Error(entry.error?.message || 'Batch execution failed');
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
  }
@@ -11,12 +11,41 @@ import { type SessionConfig } from './session.js';
11
11
  export declare class Statement {
12
12
  private session;
13
13
  private sql;
14
+ private presentationMode;
14
15
  constructor(sessionConfig: SessionConfig, sql: string);
16
+ /**
17
+ * Enable raw mode to return arrays instead of objects.
18
+ *
19
+ * @param raw Enable or disable raw mode. If you don't pass the parameter, raw mode is enabled.
20
+ * @returns This statement instance for chaining
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const stmt = client.prepare("SELECT * FROM users WHERE id = ?");
25
+ * const row = await stmt.raw().get([1]);
26
+ * console.log(row); // [1, "Alice", "alice@example.org"]
27
+ * ```
28
+ */
29
+ raw(raw?: boolean): Statement;
30
+ /**
31
+ * Executes the prepared statement.
32
+ *
33
+ * @param args - Optional array of parameter values or object with named parameters
34
+ * @returns Promise resolving to the result of the statement
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const stmt = client.prepare("INSERT INTO users (name, email) VALUES (?, ?)");
39
+ * const result = await stmt.run(['John Doe', 'john.doe@example.com']);
40
+ * console.log(`Inserted user with ID ${result.lastInsertRowid}`);
41
+ * ```
42
+ */
43
+ run(args?: any): Promise<any>;
15
44
  /**
16
45
  * Execute the statement and return the first row.
17
46
  *
18
- * @param args - Optional array of parameter values for the SQL statement
19
- * @returns Promise resolving to the first row or null if no results
47
+ * @param args - Optional array of parameter values or object with named parameters
48
+ * @returns Promise resolving to the first row or undefined if no results
20
49
  *
21
50
  * @example
22
51
  * ```typescript
@@ -27,11 +56,11 @@ export declare class Statement {
27
56
  * }
28
57
  * ```
29
58
  */
30
- get(args?: any[]): Promise<any>;
59
+ get(args?: any): Promise<any>;
31
60
  /**
32
61
  * Execute the statement and return all rows.
33
62
  *
34
- * @param args - Optional array of parameter values for the SQL statement
63
+ * @param args - Optional array of parameter values or object with named parameters
35
64
  * @returns Promise resolving to an array of all result rows
36
65
  *
37
66
  * @example
@@ -41,14 +70,14 @@ export declare class Statement {
41
70
  * console.log(`Found ${activeUsers.length} active users`);
42
71
  * ```
43
72
  */
44
- all(args?: any[]): Promise<any[]>;
73
+ all(args?: any): Promise<any[]>;
45
74
  /**
46
75
  * Execute the statement and return an async iterator for streaming results.
47
76
  *
48
77
  * This method provides memory-efficient processing of large result sets
49
78
  * by streaming rows one at a time instead of loading everything into memory.
50
79
  *
51
- * @param args - Optional array of parameter values for the SQL statement
80
+ * @param args - Optional array of parameter values or object with named parameters
52
81
  * @returns AsyncGenerator that yields individual rows
53
82
  *
54
83
  * @example
@@ -60,5 +89,10 @@ export declare class Statement {
60
89
  * }
61
90
  * ```
62
91
  */
63
- iterate(args?: any[]): AsyncGenerator<any>;
92
+ iterate(args?: any): AsyncGenerator<any>;
93
+ /**
94
+ * Normalize arguments to handle both single values and arrays.
95
+ * Matches the behavior of the native bindings.
96
+ */
97
+ private normalizeArgs;
64
98
  }