@tursodatabase/serverless 1.2.0-pre.1 → 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,120 @@
1
+ export interface Value {
2
+ type: 'null' | 'integer' | 'float' | 'text' | 'blob';
3
+ value?: string | number;
4
+ base64?: string;
5
+ }
6
+ export interface Column {
7
+ name: string;
8
+ decltype: string;
9
+ }
10
+ export interface ExecuteResult {
11
+ cols: Column[];
12
+ rows: Value[][];
13
+ affected_row_count: number;
14
+ last_insert_rowid?: string;
15
+ }
16
+ export interface NamedArg {
17
+ name: string;
18
+ value: Value;
19
+ }
20
+ export interface ExecuteRequest {
21
+ type: 'execute';
22
+ stmt: {
23
+ sql: string;
24
+ args: Value[];
25
+ named_args: NamedArg[];
26
+ want_rows: boolean;
27
+ };
28
+ }
29
+ export interface BatchStep {
30
+ stmt: {
31
+ sql: string;
32
+ args: Value[];
33
+ named_args?: NamedArg[];
34
+ want_rows: boolean;
35
+ };
36
+ condition?: {
37
+ type: 'ok';
38
+ step: number;
39
+ };
40
+ }
41
+ export interface BatchRequest {
42
+ type: 'batch';
43
+ batch: {
44
+ steps: BatchStep[];
45
+ };
46
+ }
47
+ export interface SequenceRequest {
48
+ type: 'sequence';
49
+ sql: string;
50
+ }
51
+ export interface CloseRequest {
52
+ type: 'close';
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
+ }
66
+ export interface PipelineRequest {
67
+ baton: string | null;
68
+ requests: (ExecuteRequest | BatchRequest | SequenceRequest | CloseRequest | DescribeRequest)[];
69
+ }
70
+ export interface PipelineResponse {
71
+ baton: string | null;
72
+ base_url: string | null;
73
+ results: Array<{
74
+ type: 'ok' | 'error';
75
+ response?: {
76
+ type: 'execute' | 'batch' | 'sequence' | 'close' | 'describe';
77
+ result?: ExecuteResult | DescribeResult;
78
+ };
79
+ error?: {
80
+ message: string;
81
+ code: string;
82
+ };
83
+ }>;
84
+ }
85
+ export declare function encodeValue(value: any): Value;
86
+ export declare function decodeValue(value: Value, safeIntegers?: boolean): any;
87
+ export interface CursorRequest {
88
+ baton: string | null;
89
+ batch: {
90
+ steps: BatchStep[];
91
+ };
92
+ }
93
+ export interface CursorResponse {
94
+ baton: string | null;
95
+ base_url: string | null;
96
+ }
97
+ export interface CursorEntry {
98
+ type: 'step_begin' | 'step_end' | 'step_error' | 'row' | 'error';
99
+ step?: number;
100
+ cols?: Column[];
101
+ row?: Value[];
102
+ affected_row_count?: number;
103
+ last_insert_rowid?: string;
104
+ error?: {
105
+ message: string;
106
+ code: string;
107
+ };
108
+ }
109
+ /** HTTP header key for the encryption key */
110
+ export declare const ENCRYPTION_KEY_HEADER = "x-turso-encryption-key";
111
+ /** Per-query timeout options. Overrides defaultQueryTimeout for this call. */
112
+ export interface QueryOptions {
113
+ /** Per-query timeout in milliseconds. Overrides defaultQueryTimeout for this call. */
114
+ queryTimeout?: number;
115
+ }
116
+ export declare function executeCursor(url: string, authToken: string | undefined, request: CursorRequest, remoteEncryptionKey?: string, signal?: AbortSignal): Promise<{
117
+ response: CursorResponse;
118
+ entries: AsyncGenerator<CursorEntry>;
119
+ }>;
120
+ export declare function executePipeline(url: string, authToken: string | undefined, request: PipelineRequest, remoteEncryptionKey?: string, signal?: AbortSignal): Promise<PipelineResponse>;
@@ -0,0 +1,199 @@
1
+ import { DatabaseError, TimeoutError } from './error.js';
2
+ export function encodeValue(value) {
3
+ if (value === null || value === undefined) {
4
+ return { type: 'null' };
5
+ }
6
+ if (typeof value === 'number') {
7
+ if (!Number.isFinite(value)) {
8
+ throw new Error("Only finite numbers (not Infinity or NaN) can be passed as arguments");
9
+ }
10
+ return { type: 'float', value };
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
+ }
18
+ if (typeof value === 'string') {
19
+ return { type: 'text', value };
20
+ }
21
+ if (value instanceof ArrayBuffer || value instanceof Uint8Array) {
22
+ const base64 = btoa(String.fromCharCode(...new Uint8Array(value)));
23
+ return { type: 'blob', base64 };
24
+ }
25
+ return { type: 'text', value: String(value) };
26
+ }
27
+ export function decodeValue(value, safeIntegers = false) {
28
+ switch (value.type) {
29
+ case 'null':
30
+ return null;
31
+ case 'integer':
32
+ if (safeIntegers) {
33
+ return BigInt(value.value);
34
+ }
35
+ return parseInt(value.value, 10);
36
+ case 'float':
37
+ return value.value;
38
+ case 'text':
39
+ return value.value;
40
+ case 'blob':
41
+ if (value.base64) {
42
+ const binaryString = atob(value.base64);
43
+ const bytes = new Uint8Array(binaryString.length);
44
+ for (let i = 0; i < binaryString.length; i++) {
45
+ bytes[i] = binaryString.charCodeAt(i);
46
+ }
47
+ return Buffer.from(bytes);
48
+ }
49
+ return null;
50
+ default:
51
+ return null;
52
+ }
53
+ }
54
+ /** HTTP header key for the encryption key */
55
+ export const ENCRYPTION_KEY_HEADER = 'x-turso-encryption-key';
56
+ function wrapAbortError(error) {
57
+ if (error instanceof Error && (error.name === 'AbortError' || error.name === 'TimeoutError')) {
58
+ throw new TimeoutError('Query timed out');
59
+ }
60
+ throw error;
61
+ }
62
+ export async function executeCursor(url, authToken, request, remoteEncryptionKey, signal) {
63
+ const headers = {
64
+ 'Content-Type': 'application/json',
65
+ };
66
+ if (authToken) {
67
+ headers['Authorization'] = `Bearer ${authToken}`;
68
+ }
69
+ if (remoteEncryptionKey) {
70
+ headers[ENCRYPTION_KEY_HEADER] = remoteEncryptionKey;
71
+ }
72
+ let response;
73
+ try {
74
+ response = await fetch(`${url}/v3/cursor`, {
75
+ method: 'POST',
76
+ headers,
77
+ body: JSON.stringify(request),
78
+ signal,
79
+ });
80
+ }
81
+ catch (error) {
82
+ wrapAbortError(error);
83
+ }
84
+ if (!response.ok) {
85
+ let errorMessage = `HTTP error! status: ${response.status}`;
86
+ try {
87
+ const errorBody = await response.text();
88
+ const errorData = JSON.parse(errorBody);
89
+ if (errorData.message) {
90
+ errorMessage = errorData.message;
91
+ }
92
+ }
93
+ catch {
94
+ // If we can't parse the error body, use the default HTTP error message
95
+ }
96
+ throw new DatabaseError(errorMessage);
97
+ }
98
+ const reader = response.body?.getReader();
99
+ if (!reader) {
100
+ throw new DatabaseError('No response body');
101
+ }
102
+ const decoder = new TextDecoder();
103
+ let buffer = '';
104
+ let cursorResponse;
105
+ // First, read until we get the cursor response (first line)
106
+ try {
107
+ while (!cursorResponse) {
108
+ const { done, value } = await reader.read();
109
+ if (done)
110
+ break;
111
+ buffer += decoder.decode(value, { stream: true });
112
+ const newlineIndex = buffer.indexOf('\n');
113
+ if (newlineIndex !== -1) {
114
+ const line = buffer.slice(0, newlineIndex).trim();
115
+ buffer = buffer.slice(newlineIndex + 1);
116
+ if (line) {
117
+ cursorResponse = JSON.parse(line);
118
+ break;
119
+ }
120
+ }
121
+ }
122
+ }
123
+ catch (error) {
124
+ reader.releaseLock();
125
+ wrapAbortError(error);
126
+ }
127
+ if (!cursorResponse) {
128
+ reader.releaseLock();
129
+ throw new DatabaseError('No cursor response received');
130
+ }
131
+ async function* parseEntries() {
132
+ try {
133
+ // Process any remaining data in the buffer
134
+ let newlineIndex;
135
+ while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
136
+ const line = buffer.slice(0, newlineIndex).trim();
137
+ buffer = buffer.slice(newlineIndex + 1);
138
+ if (line) {
139
+ yield JSON.parse(line);
140
+ }
141
+ }
142
+ // Continue reading from the stream
143
+ while (true) {
144
+ let readResult;
145
+ try {
146
+ readResult = await reader.read();
147
+ }
148
+ catch (error) {
149
+ wrapAbortError(error);
150
+ }
151
+ if (readResult.done)
152
+ break;
153
+ buffer += decoder.decode(readResult.value, { stream: true });
154
+ while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
155
+ const line = buffer.slice(0, newlineIndex).trim();
156
+ buffer = buffer.slice(newlineIndex + 1);
157
+ if (line) {
158
+ yield JSON.parse(line);
159
+ }
160
+ }
161
+ }
162
+ // Process any remaining data in the buffer
163
+ if (buffer.trim()) {
164
+ yield JSON.parse(buffer.trim());
165
+ }
166
+ }
167
+ finally {
168
+ reader.releaseLock();
169
+ }
170
+ }
171
+ return { response: cursorResponse, entries: parseEntries() };
172
+ }
173
+ export async function executePipeline(url, authToken, request, remoteEncryptionKey, signal) {
174
+ const headers = {
175
+ 'Content-Type': 'application/json',
176
+ };
177
+ if (authToken) {
178
+ headers['Authorization'] = `Bearer ${authToken}`;
179
+ }
180
+ if (remoteEncryptionKey) {
181
+ headers[ENCRYPTION_KEY_HEADER] = remoteEncryptionKey;
182
+ }
183
+ let response;
184
+ try {
185
+ response = await fetch(`${url}/v3/pipeline`, {
186
+ method: 'POST',
187
+ headers,
188
+ body: JSON.stringify(request),
189
+ signal,
190
+ });
191
+ }
192
+ catch (error) {
193
+ wrapAbortError(error);
194
+ }
195
+ if (!response.ok) {
196
+ throw new DatabaseError(`HTTP error! status: ${response.status}`);
197
+ }
198
+ return response.json();
199
+ }
@@ -0,0 +1,93 @@
1
+ import { type CursorResponse, type CursorEntry, type DescribeResult, type QueryOptions } from './protocol.js';
2
+ /**
3
+ * Configuration options for a session.
4
+ */
5
+ export interface SessionConfig {
6
+ /** Database URL */
7
+ url: string;
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;
15
+ /** Default maximum query execution time in milliseconds before interruption. */
16
+ defaultQueryTimeout?: number;
17
+ }
18
+ /**
19
+ * A database session that manages the connection state and baton.
20
+ *
21
+ * Each session maintains its own connection state and can execute SQL statements
22
+ * independently without interfering with other sessions.
23
+ */
24
+ export declare class Session {
25
+ private config;
26
+ private baton;
27
+ private baseUrl;
28
+ constructor(config: SessionConfig);
29
+ private createAbortSignal;
30
+ /**
31
+ * Describe a SQL statement to get its column metadata.
32
+ *
33
+ * @param sql - The SQL statement to describe
34
+ * @returns Promise resolving to the statement description
35
+ */
36
+ describe(sql: string, queryOptions?: QueryOptions): Promise<DescribeResult>;
37
+ /**
38
+ * Execute a SQL statement and return all results.
39
+ *
40
+ * @param sql - The SQL statement to execute
41
+ * @param args - Optional array of parameter values or object with named parameters
42
+ * @param safeIntegers - Whether to return integers as BigInt
43
+ * @returns Promise resolving to the complete result set
44
+ */
45
+ execute(sql: string, args?: any[] | Record<string, any>, safeIntegers?: boolean, queryOptions?: QueryOptions): Promise<any>;
46
+ /**
47
+ * Execute a SQL statement and return the raw response and entries.
48
+ *
49
+ * @param sql - The SQL statement to execute
50
+ * @param args - Optional array of parameter values or object with named parameters
51
+ * @returns Promise resolving to the raw response and cursor entries
52
+ */
53
+ executeRaw(sql: string, args?: any[] | Record<string, any>, queryOptions?: QueryOptions): Promise<{
54
+ response: CursorResponse;
55
+ entries: AsyncGenerator<CursorEntry>;
56
+ }>;
57
+ /**
58
+ * Process cursor entries into a structured result.
59
+ *
60
+ * @param entries - Async generator of cursor entries
61
+ * @returns Promise resolving to the processed result
62
+ */
63
+ processCursorEntries(entries: AsyncGenerator<CursorEntry>, safeIntegers?: boolean): Promise<any>;
64
+ /**
65
+ * Create a row object with both array and named property access.
66
+ *
67
+ * @param values - Array of column values
68
+ * @param columns - Array of column names
69
+ * @returns Row object with dual access patterns
70
+ */
71
+ createRowObject(values: any[], columns: string[]): any;
72
+ /**
73
+ * Execute multiple SQL statements in a batch.
74
+ *
75
+ * @param statements - Array of SQL statements to execute
76
+ * @returns Promise resolving to batch execution results
77
+ */
78
+ batch(statements: string[], queryOptions?: QueryOptions): Promise<any>;
79
+ /**
80
+ * Execute a sequence of SQL statements separated by semicolons.
81
+ *
82
+ * @param sql - SQL string containing multiple statements separated by semicolons
83
+ * @returns Promise resolving when all statements are executed
84
+ */
85
+ sequence(sql: string, queryOptions?: QueryOptions): Promise<void>;
86
+ /**
87
+ * Close the session.
88
+ *
89
+ * This sends a close request to the server to properly clean up the stream
90
+ * before resetting the local state.
91
+ */
92
+ close(): Promise<void>;
93
+ }