@tursodatabase/serverless 1.2.0-pre.2 → 1.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.
@@ -0,0 +1,308 @@
1
+ import { decodeValue } from './protocol.js';
2
+ import { Session } from './session.js';
3
+ import { DatabaseError } from './error.js';
4
+ /**
5
+ * A prepared SQL statement that can be executed in multiple ways.
6
+ *
7
+ * Statements may either own a dedicated session or share a connection session to preserve transaction boundaries.
8
+ * Provides three execution modes:
9
+ * - `get(args?)`: Returns the first row or null
10
+ * - `all(args?)`: Returns all rows as an array
11
+ * - `iterate(args?)`: Returns an async iterator for streaming results
12
+ */
13
+ export class Statement {
14
+ constructor(sessionConfig, sql, columns) {
15
+ this.presentationMode = 'expanded';
16
+ this.safeIntegerMode = false;
17
+ this.session = new Session(sessionConfig);
18
+ this.sql = sql;
19
+ this.columnMetadata = columns || [];
20
+ }
21
+ /**
22
+ * Create a Statement that shares an existing session and serializes execution
23
+ * through the given lock. Used by Connection.prepare() so prepared statements
24
+ * participate in the connection's transaction scope.
25
+ */
26
+ static fromSession(session, sql, columns, execLock) {
27
+ const stmt = Object.create(Statement.prototype);
28
+ stmt.session = session;
29
+ stmt.sql = sql;
30
+ stmt.columnMetadata = columns || [];
31
+ stmt.presentationMode = 'expanded';
32
+ stmt.safeIntegerMode = false;
33
+ stmt.execLock = execLock;
34
+ return stmt;
35
+ }
36
+ /**
37
+ * Whether the prepared statement returns data.
38
+ *
39
+ * This is `true` for SELECT queries and statements with RETURNING clause,
40
+ * and `false` for INSERT, UPDATE, DELETE statements without RETURNING.
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * const stmt = await conn.prepare(sql);
45
+ * if (stmt.reader) {
46
+ * return stmt.all(args); // SELECT-like query
47
+ * } else {
48
+ * return stmt.run(args); // INSERT/UPDATE/DELETE
49
+ * }
50
+ * ```
51
+ */
52
+ get reader() {
53
+ return this.columnMetadata.length > 0;
54
+ }
55
+ /**
56
+ * Enable raw mode to return arrays instead of objects.
57
+ *
58
+ * @param raw Enable or disable raw mode. If you don't pass the parameter, raw mode is enabled.
59
+ * @returns This statement instance for chaining
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const stmt = client.prepare("SELECT * FROM users WHERE id = ?");
64
+ * const row = await stmt.raw().get([1]);
65
+ * console.log(row); // [1, "Alice", "alice@example.org"]
66
+ * ```
67
+ */
68
+ raw(raw) {
69
+ this.presentationMode = raw === false ? 'expanded' : 'raw';
70
+ return this;
71
+ }
72
+ /**
73
+ * Enable pluck mode to return only the first column value from each row.
74
+ *
75
+ * @param pluck Enable or disable pluck mode. If you don't pass the parameter, pluck mode is enabled.
76
+ * @returns This statement instance for chaining
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * const stmt = client.prepare("SELECT id FROM users");
81
+ * const ids = await stmt.pluck().all();
82
+ * console.log(ids); // [1, 2, 3, ...]
83
+ * ```
84
+ */
85
+ pluck(pluck) {
86
+ this.presentationMode = pluck === false ? 'expanded' : 'pluck';
87
+ return this;
88
+ }
89
+ /**
90
+ * Sets safe integers mode for this statement.
91
+ *
92
+ * @param toggle Whether to use safe integers. If you don't pass the parameter, safe integers mode is enabled.
93
+ * @returns This statement instance for chaining
94
+ */
95
+ safeIntegers(toggle) {
96
+ this.safeIntegerMode = toggle === false ? false : true;
97
+ return this;
98
+ }
99
+ /**
100
+ * Get column information for this statement.
101
+ *
102
+ * @returns Array of column metadata objects matching the native bindings format
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * const stmt = await client.prepare("SELECT id, name, email FROM users");
107
+ * const columns = stmt.columns();
108
+ * console.log(columns); // [{ name: 'id', type: 'INTEGER', column: null, database: null, table: null }, ...]
109
+ * ```
110
+ */
111
+ columns() {
112
+ return this.columnMetadata.map(col => ({
113
+ name: col.name,
114
+ type: col.decltype
115
+ }));
116
+ }
117
+ async withLock(fn) {
118
+ if (!this.execLock) {
119
+ return await fn();
120
+ }
121
+ await this.execLock.acquire();
122
+ try {
123
+ return await fn();
124
+ }
125
+ finally {
126
+ this.execLock.release();
127
+ }
128
+ }
129
+ /**
130
+ * Executes the prepared statement.
131
+ *
132
+ * @param args - Optional array of parameter values or object with named parameters
133
+ * @returns Promise resolving to the result of the statement
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * const stmt = client.prepare("INSERT INTO users (name, email) VALUES (?, ?)");
138
+ * const result = await stmt.run(['John Doe', 'john.doe@example.com']);
139
+ * console.log(`Inserted user with ID ${result.lastInsertRowid}`);
140
+ * ```
141
+ */
142
+ async run(args, queryOptions) {
143
+ return await this.withLock(async () => {
144
+ const normalizedArgs = this.normalizeArgs(args);
145
+ const result = await this.session.execute(this.sql, normalizedArgs, this.safeIntegerMode, queryOptions);
146
+ return { changes: result.rowsAffected, lastInsertRowid: result.lastInsertRowid };
147
+ });
148
+ }
149
+ /**
150
+ * Execute the statement and return the first row.
151
+ *
152
+ * @param args - Optional array of parameter values or object with named parameters
153
+ * @returns Promise resolving to the first row or undefined if no results
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * const stmt = client.prepare("SELECT * FROM users WHERE id = ?");
158
+ * const user = await stmt.get([123]);
159
+ * if (user) {
160
+ * console.log(user.name);
161
+ * }
162
+ * ```
163
+ */
164
+ async get(args, queryOptions) {
165
+ return await this.withLock(async () => {
166
+ const normalizedArgs = this.normalizeArgs(args);
167
+ const result = await this.session.execute(this.sql, normalizedArgs, this.safeIntegerMode, queryOptions);
168
+ const row = result.rows[0];
169
+ if (!row) {
170
+ return undefined;
171
+ }
172
+ if (this.presentationMode === 'pluck') {
173
+ // In pluck mode, return only the first column value
174
+ return row[0];
175
+ }
176
+ if (this.presentationMode === 'raw') {
177
+ // In raw mode, return the row as a plain array (it already is one)
178
+ // The row object is already an array with column properties added
179
+ return [...row];
180
+ }
181
+ // In expanded mode, convert to plain object with named properties
182
+ const obj = {};
183
+ result.columns.forEach((col, i) => {
184
+ obj[col] = row[i];
185
+ });
186
+ return obj;
187
+ });
188
+ }
189
+ /**
190
+ * Execute the statement and return all rows.
191
+ *
192
+ * @param args - Optional array of parameter values or object with named parameters
193
+ * @returns Promise resolving to an array of all result rows
194
+ *
195
+ * @example
196
+ * ```typescript
197
+ * const stmt = client.prepare("SELECT * FROM users WHERE active = ?");
198
+ * const activeUsers = await stmt.all([true]);
199
+ * console.log(`Found ${activeUsers.length} active users`);
200
+ * ```
201
+ */
202
+ async all(args, queryOptions) {
203
+ return await this.withLock(async () => {
204
+ const normalizedArgs = this.normalizeArgs(args);
205
+ const result = await this.session.execute(this.sql, normalizedArgs, this.safeIntegerMode, queryOptions);
206
+ if (this.presentationMode === 'pluck') {
207
+ // In pluck mode, return only the first column value from each row
208
+ return result.rows.map((row) => row[0]);
209
+ }
210
+ if (this.presentationMode === 'raw') {
211
+ return result.rows.map((row) => [...row]);
212
+ }
213
+ // In expanded mode, convert rows to plain objects with named properties
214
+ return result.rows.map((row) => {
215
+ const obj = {};
216
+ result.columns.forEach((col, i) => {
217
+ obj[col] = row[i];
218
+ });
219
+ return obj;
220
+ });
221
+ });
222
+ }
223
+ /**
224
+ * Execute the statement and return an async iterator for streaming results.
225
+ *
226
+ * This method provides memory-efficient processing of large result sets
227
+ * by streaming rows one at a time instead of loading everything into memory.
228
+ *
229
+ * @param args - Optional array of parameter values or object with named parameters
230
+ * @returns AsyncGenerator that yields individual rows
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * const stmt = client.prepare("SELECT * FROM large_table WHERE category = ?");
235
+ * for await (const row of stmt.iterate(['electronics'])) {
236
+ * // Process each row individually
237
+ * console.log(row.id, row.name);
238
+ * }
239
+ * ```
240
+ */
241
+ async *iterate(args, queryOptions) {
242
+ // Shared-connection statements must not hold the connection lock across
243
+ // `yield` points, or nested queries in the loop body can deadlock.
244
+ if (this.execLock) {
245
+ const rows = await this.all(args, queryOptions);
246
+ for (const row of rows) {
247
+ yield row;
248
+ }
249
+ return;
250
+ }
251
+ const normalizedArgs = this.normalizeArgs(args);
252
+ const { entries } = await this.session.executeRaw(this.sql, normalizedArgs, queryOptions);
253
+ let columns = [];
254
+ for await (const entry of entries) {
255
+ switch (entry.type) {
256
+ case 'step_begin':
257
+ if (entry.cols) {
258
+ columns = entry.cols.map(col => col.name);
259
+ }
260
+ break;
261
+ case 'row':
262
+ if (entry.row) {
263
+ const decodedRow = entry.row.map(value => decodeValue(value, this.safeIntegerMode));
264
+ if (this.presentationMode === 'pluck') {
265
+ // In pluck mode, yield only the first column value
266
+ yield decodedRow[0];
267
+ }
268
+ else if (this.presentationMode === 'raw') {
269
+ // In raw mode, yield arrays of values
270
+ yield decodedRow;
271
+ }
272
+ else {
273
+ // In expanded mode, yield plain objects with named properties (consistent with all())
274
+ const obj = {};
275
+ columns.forEach((col, i) => {
276
+ obj[col] = decodedRow[i];
277
+ });
278
+ yield obj;
279
+ }
280
+ }
281
+ break;
282
+ case 'step_error':
283
+ case 'error':
284
+ throw new DatabaseError(entry.error?.message || 'SQL execution failed');
285
+ }
286
+ }
287
+ }
288
+ /**
289
+ * Normalize arguments to handle both single values and arrays.
290
+ * Matches the behavior of the native bindings.
291
+ */
292
+ normalizeArgs(args) {
293
+ // No arguments provided
294
+ if (args === undefined) {
295
+ return [];
296
+ }
297
+ // If it's an array, return as-is
298
+ if (Array.isArray(args)) {
299
+ return args;
300
+ }
301
+ // Check if it's a plain object (for named parameters)
302
+ if (args !== null && typeof args === 'object' && args.constructor === Object) {
303
+ return args;
304
+ }
305
+ // Single value - wrap in array
306
+ return [args];
307
+ }
308
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tursodatabase/serverless",
3
- "version": "1.2.0-pre.2",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",