@sqlrooms/duckdb-core 0.26.1-rc.5

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.
Files changed (55) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +387 -0
  3. package/dist/BaseDuckDbConnector.d.ts +20 -0
  4. package/dist/BaseDuckDbConnector.d.ts.map +1 -0
  5. package/dist/BaseDuckDbConnector.js +122 -0
  6. package/dist/BaseDuckDbConnector.js.map +1 -0
  7. package/dist/DuckDbConnector.d.ts +312 -0
  8. package/dist/DuckDbConnector.d.ts.map +1 -0
  9. package/dist/DuckDbConnector.js +2 -0
  10. package/dist/DuckDbConnector.js.map +1 -0
  11. package/dist/arrow-utils.d.ts +8 -0
  12. package/dist/arrow-utils.d.ts.map +1 -0
  13. package/dist/arrow-utils.js +28 -0
  14. package/dist/arrow-utils.js.map +1 -0
  15. package/dist/duckdb-utils.d.ts +140 -0
  16. package/dist/duckdb-utils.d.ts.map +1 -0
  17. package/dist/duckdb-utils.js +290 -0
  18. package/dist/duckdb-utils.js.map +1 -0
  19. package/dist/index.d.ts +12 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +8 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/load/create.d.ts +33 -0
  24. package/dist/load/create.d.ts.map +1 -0
  25. package/dist/load/create.js +33 -0
  26. package/dist/load/create.js.map +1 -0
  27. package/dist/load/load.d.ts +57 -0
  28. package/dist/load/load.d.ts.map +1 -0
  29. package/dist/load/load.js +153 -0
  30. package/dist/load/load.js.map +1 -0
  31. package/dist/load/sql-from.d.ts +18 -0
  32. package/dist/load/sql-from.d.ts.map +1 -0
  33. package/dist/load/sql-from.js +69 -0
  34. package/dist/load/sql-from.js.map +1 -0
  35. package/dist/schema-tree/schemaTree.d.ts +9 -0
  36. package/dist/schema-tree/schemaTree.d.ts.map +1 -0
  37. package/dist/schema-tree/schemaTree.js +75 -0
  38. package/dist/schema-tree/schemaTree.js.map +1 -0
  39. package/dist/schema-tree/typeCategories.d.ts +16 -0
  40. package/dist/schema-tree/typeCategories.d.ts.map +1 -0
  41. package/dist/schema-tree/typeCategories.js +72 -0
  42. package/dist/schema-tree/typeCategories.js.map +1 -0
  43. package/dist/schema-tree/types.d.ts +28 -0
  44. package/dist/schema-tree/types.d.ts.map +1 -0
  45. package/dist/schema-tree/types.js +2 -0
  46. package/dist/schema-tree/types.js.map +1 -0
  47. package/dist/typedRowAccessor.d.ts +19 -0
  48. package/dist/typedRowAccessor.d.ts.map +1 -0
  49. package/dist/typedRowAccessor.js +45 -0
  50. package/dist/typedRowAccessor.js.map +1 -0
  51. package/dist/types.d.ts +21 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +2 -0
  54. package/dist/types.js.map +1 -0
  55. package/package.json +42 -0
package/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright 2025 Ilya Boyandin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,387 @@
1
+ A powerful wrapper around DuckDB-WASM that provides React hooks and utilities for working with DuckDB in browser environments.
2
+
3
+ ## Features
4
+
5
+ ### React Integration & Type Safety
6
+
7
+ - **React Hooks**: Seamless integration with React applications via `useSql`
8
+ - **Runtime Validation**: Optional Zod schema validation for query results with type transformations
9
+ - **Typed Row Accessors**: Type-safe row access with validation and multiple iteration methods
10
+
11
+ ### Data Management
12
+
13
+ - **File Operations**: Import data from various file formats (CSV, JSON, Parquet) with auto-detection
14
+ - **Arrow Integration**: Work directly with Apache Arrow tables for efficient columnar data processing
15
+ - **Schema Management**: Comprehensive database, schema, and table discovery and management
16
+ - **Qualified Table Names**: Full support for `database.schema.table` naming convention
17
+
18
+ ### Performance & Operations
19
+
20
+ - **Query Deduplication**: Automatic deduplication of identical running queries to prevent duplicate execution
21
+ - **Query Cancellation**: Cancel running queries with full composability support via `QueryHandle` interface ([learn more](https://sqlrooms.org/query-cancellation))
22
+ - **Data Export**: Export query results to CSV files with pagination for large datasets
23
+ - **Batch Processing**: Handle large datasets efficiently with built-in pagination support
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install @sqlrooms/duckdb
29
+ ```
30
+
31
+ ## Basic Usage
32
+
33
+ ### Using the SQL Hook
34
+
35
+ ```tsx
36
+ import {useSql} from '@sqlrooms/duckdb';
37
+
38
+ function UserList() {
39
+ // Basic usage with TypeScript types
40
+ const {data, isLoading, error} = useSql<{id: number; name: string}>({
41
+ query: 'SELECT id, name FROM users',
42
+ });
43
+
44
+ if (isLoading) return <div>Loading...</div>;
45
+ if (error) return <div>Error: {error.message}</div>;
46
+ if (!data) return null;
47
+
48
+ return (
49
+ <ul>
50
+ {Array.from(data.rows()).map((user) => (
51
+ <li key={user.id}>{user.name}</li>
52
+ ))}
53
+ </ul>
54
+ );
55
+ }
56
+ ```
57
+
58
+ For more information and examples on using the `useSql` hook, see the [useSql API documentation](/api/duckdb/functions/useSql).
59
+
60
+ ### Using Zod for Runtime Validation
61
+
62
+ ```tsx
63
+ import {useSql} from '@sqlrooms/duckdb';
64
+ import {z} from 'zod';
65
+
66
+ const userSchema = z.object({
67
+ id: z.number(),
68
+ name: z.string(),
69
+ email: z.string().email(),
70
+ created_at: z.string().transform((str) => new Date(str)),
71
+ });
72
+
73
+ function ValidatedUserList() {
74
+ const {data, isLoading, error} = useSql(userSchema, {
75
+ query: 'SELECT id, name, email, created_at FROM users',
76
+ });
77
+
78
+ if (isLoading) return <div>Loading...</div>;
79
+ if (error) {
80
+ if (error instanceof z.ZodError) {
81
+ return <div>Validation Error: {error.errors[0].message}</div>;
82
+ }
83
+ return <div>Error: {error.message}</div>;
84
+ }
85
+ if (!data) return null;
86
+
87
+ return (
88
+ <ul>
89
+ {data.toArray().map((user) => (
90
+ <li key={user.id}>
91
+ {user.name} ({user.email}) - Joined:{' '}
92
+ {user.created_at.toLocaleDateString()}
93
+ </li>
94
+ ))}
95
+ </ul>
96
+ );
97
+ }
98
+ ```
99
+
100
+ ### Accessing the Underlying Arrow Table and Schema
101
+
102
+ You can access the underlying Arrow table and schema of a `useSql()` query result. This is especially useful if you want to pass the data to a library that expect an Apache Arrow Table as input without additional data transformation:
103
+
104
+ ```tsx
105
+ import {useSql} from '@sqlrooms/duckdb';
106
+
107
+ function ArrowTableSchemaExample() {
108
+ const {data, isLoading, error} = useSql({
109
+ query: 'SELECT id, name FROM users',
110
+ });
111
+
112
+ if (isLoading) return <div>Loading...</div>;
113
+ if (error) return <div>Error: {error.message}</div>;
114
+ if (!data || !data.arrowTable) return null;
115
+
116
+ const {arrowTable} = data;
117
+ const fields = arrowTable.schema.fields;
118
+ const numRows = arrowTable.numRows;
119
+
120
+ return (
121
+ <table>
122
+ <thead>
123
+ <tr>
124
+ {fields.map((field) => (
125
+ <th key={field.name}>{field.name}</th>
126
+ ))}
127
+ </tr>
128
+ </thead>
129
+ <tbody>
130
+ {Array.from({length: numRows}).map((_, rowIdx) => (
131
+ <tr key={rowIdx}>
132
+ {fields.map((field, colIdx) => (
133
+ <td key={field.name}>
134
+ {String(arrowTable.getChildAt(colIdx)?.get(rowIdx) ?? '')}
135
+ </td>
136
+ ))}
137
+ </tr>
138
+ ))}
139
+ </tbody>
140
+ </table>
141
+ );
142
+ }
143
+ ```
144
+
145
+ ## Working with Tables
146
+
147
+ ### Using the Store for Direct Database Operations
148
+
149
+ ```tsx
150
+ function DatabaseManager() {
151
+ const createTableFromQuery = useRoomStore(
152
+ (state) => state.db.createTableFromQuery,
153
+ );
154
+ const addTable = useRoomStore((state) => state.db.addTable);
155
+ const dropTable = useRoomStore((state) => state.db.dropTable);
156
+ const tables = useRoomStore((state) => state.db.tables);
157
+ const refreshTableSchemas = useRoomStore(
158
+ (state) => state.db.refreshTableSchemas,
159
+ );
160
+
161
+ // Create a table from a query
162
+ const handleCreateTable = async () => {
163
+ const result = await createTableFromQuery(
164
+ 'filtered_users',
165
+ 'SELECT * FROM users WHERE active = true',
166
+ );
167
+ console.log(`Created table with ${result.rowCount} rows`);
168
+ };
169
+
170
+ // Add a table from JavaScript objects
171
+ const handleAddTable = async () => {
172
+ const users = [
173
+ {id: 1, name: 'Alice', email: 'alice@example.com'},
174
+ {id: 2, name: 'Bob', email: 'bob@example.com'},
175
+ ];
176
+ await addTable('new_users', users);
177
+ };
178
+
179
+ // Drop a table
180
+ const handleDropTable = async () => {
181
+ await dropTable('old_table');
182
+ };
183
+
184
+ return (
185
+ <div>
186
+ <button onClick={handleCreateTable}>Create Filtered Users Table</button>
187
+ <button onClick={handleAddTable}>Add New Users Table</button>
188
+ <button onClick={handleDropTable}>Drop Old Table</button>
189
+ <button onClick={refreshTableSchemas}>Refresh Schemas</button>
190
+
191
+ <h3>Available Tables:</h3>
192
+ <ul>
193
+ {tables.map((table) => (
194
+ <li key={table.table.toString()}>
195
+ {table.table.toString()} ({table.columns.length} columns)
196
+ </li>
197
+ ))}
198
+ </ul>
199
+ </div>
200
+ );
201
+ }
202
+ ```
203
+
204
+ ### Working with Qualified Table Names
205
+
206
+ ```tsx
207
+ import {makeQualifiedTableName} from '@sqlrooms/duckdb';
208
+
209
+ // Support for database.schema.table naming
210
+ const qualifiedTable = makeQualifiedTableName({
211
+ database: 'mydb',
212
+ schema: 'public',
213
+ table: 'users',
214
+ });
215
+
216
+ // Use with table operations
217
+ await createTableFromQuery(qualifiedTable, 'SELECT * FROM source_table');
218
+ await dropTable(qualifiedTable);
219
+ const tableExists = await checkTableExists(qualifiedTable);
220
+ ```
221
+
222
+ ## Loading Data from Files
223
+
224
+ ### Using Load Functions Directly
225
+
226
+ ```tsx
227
+ import {loadCSV, loadJSON, loadParquet, loadObjects} from '@sqlrooms/duckdb';
228
+
229
+ function DataLoader() {
230
+ const getConnector = useRoomStore((state) => state.db.getConnector);
231
+
232
+ const handleLoadCSV = async (file: File) => {
233
+ const connector = await getConnector();
234
+
235
+ // Generate SQL to load CSV file
236
+ const sql = loadCSV('my_table', file.name, {
237
+ auto_detect: true,
238
+ replace: true,
239
+ });
240
+
241
+ // Execute the load operation
242
+ await connector.query(sql).result;
243
+ };
244
+
245
+ const handleLoadObjects = async () => {
246
+ const connector = await getConnector();
247
+ const data = [
248
+ {id: 1, name: 'Alice'},
249
+ {id: 2, name: 'Bob'},
250
+ ];
251
+
252
+ // Generate SQL to load objects
253
+ const sql = loadObjects('users', data, {replace: true});
254
+ await connector.query(sql).result;
255
+ };
256
+
257
+ return (
258
+ <div>
259
+ <input
260
+ type="file"
261
+ accept=".csv"
262
+ onChange={(e) => {
263
+ if (e.target.files?.[0]) handleLoadCSV(e.target.files[0]);
264
+ }}
265
+ />
266
+ <button onClick={handleLoadObjects}>Load Sample Data</button>
267
+ </div>
268
+ );
269
+ }
270
+ ```
271
+
272
+ ### Using the Connector Directly
273
+
274
+ ```tsx
275
+ function AdvancedDataLoader() {
276
+ const connector = useRoomStore((state) => state.db.connector);
277
+
278
+ const handleFileUpload = async (file: File) => {
279
+ // Load file directly using the connector
280
+ await connector.loadFile(file, 'uploaded_data', {
281
+ method: 'auto', // Auto-detect file type
282
+ replace: true,
283
+ temp: false,
284
+ });
285
+ };
286
+
287
+ const handleLoadArrowTable = async (arrowTable: arrow.Table) => {
288
+ // Load Arrow table directly
289
+ await connector.loadArrow(arrowTable, 'arrow_data');
290
+ };
291
+
292
+ return (
293
+ <input
294
+ type="file"
295
+ accept=".csv,.json,.parquet"
296
+ onChange={(e) => {
297
+ if (e.target.files?.[0]) handleFileUpload(e.target.files[0]);
298
+ }}
299
+ />
300
+ );
301
+ }
302
+ ```
303
+
304
+ ## Exporting Data to CSV
305
+
306
+ ```tsx
307
+ import {useExportToCsv} from '@sqlrooms/duckdb';
308
+
309
+ function ExportButton() {
310
+ const {exportToCsv} = useExportToCsv();
311
+
312
+ const handleExport = async () => {
313
+ await exportToCsv('SELECT * FROM users ORDER BY name', 'users_export.csv');
314
+ };
315
+
316
+ return <button onClick={handleExport}>Export to CSV</button>;
317
+ }
318
+ ```
319
+
320
+ ## Low-Level DuckDB Access
321
+
322
+ ### Basic direct usage
323
+
324
+ ```tsx
325
+ async function executeCustomQuery() {
326
+ // Grab the connector directly (no React hook necessary inside plain TS)
327
+ const connector = useRoomStore((state) => state.db.connector);
328
+
329
+ // QueryHandle is promise-like – await it directly
330
+ const result = await connector.query('SELECT COUNT(*) AS count FROM users');
331
+
332
+ // Inspect Arrow table
333
+ const count = result.getChildAt(0)?.get(0);
334
+ console.log(`Total users: ${count}`);
335
+ }
336
+ ```
337
+
338
+ ### Cancellation examples
339
+
340
+ ```tsx
341
+ async function cancelExample() {
342
+ const connector = useRoomStore((state) => state.db.connector);
343
+
344
+ // 1. Manual cancel via the handle
345
+ const query = connector.query('SELECT * FROM large_table');
346
+ setTimeout(() => h.cancel(), 2000); // cancel after 2 s
347
+ await query; // throws if cancelled
348
+
349
+ // 2. Composable cancellation – many queries, one controller
350
+ const controller = new AbortController();
351
+ const q1 = connector.query('SELECT 1', {signal: controller.signal});
352
+ const q2 = connector.query('SELECT 2', {signal: controller.signal});
353
+ controller.abort(); // cancels q1 & q2
354
+ await Promise.allSettled([q1, q2]);
355
+ }
356
+ ```
357
+
358
+ ### Advanced operations with the Zustand store
359
+
360
+ ```tsx
361
+ function AdvancedOperations() {
362
+ const executeSql = useRoomStore((s) => s.db.executeSql);
363
+ const sqlSelectToJson = useRoomStore((s) => s.db.sqlSelectToJson);
364
+ const checkTableExists = useRoomStore((s) => s.db.checkTableExists);
365
+
366
+ const handleAdvancedQuery = async () => {
367
+ // Cached execution with deduplication
368
+ const query = await executeSql('SELECT * FROM users LIMIT 10');
369
+ if (query) {
370
+ const rows = await query; // await handle directly
371
+ console.log('Query result:', rows);
372
+ }
373
+
374
+ // Parse SQL to JSON (analysis tool)
375
+ const parsed = await sqlSelectToJson('SELECT id, name FROM users');
376
+ console.log('Parsed query:', parsed);
377
+
378
+ // Safety check before destructive operations
379
+ const exists = await checkTableExists('users');
380
+ console.log('Table exists:', exists);
381
+ };
382
+
383
+ return <button onClick={handleAdvancedQuery}>Run Advanced Operations</button>;
384
+ }
385
+ ```
386
+
387
+ For more information, visit the SQLRooms documentation.
@@ -0,0 +1,20 @@
1
+ import { LoadFileOptions, StandardLoadOptions } from '@sqlrooms/room-config';
2
+ import * as arrow from 'apache-arrow';
3
+ import { DuckDbConnector } from './DuckDbConnector';
4
+ export interface BaseDuckDbConnectorOptions {
5
+ dbPath?: string;
6
+ initializationQuery?: string;
7
+ }
8
+ export interface BaseDuckDbConnectorImpl {
9
+ initializeInternal?(): Promise<void>;
10
+ destroyInternal?(): Promise<void>;
11
+ executeQueryInternal<T extends arrow.TypeMap = any>(query: string, signal: AbortSignal, queryId?: string): Promise<arrow.Table<T>>;
12
+ cancelQueryInternal?(queryId: string): Promise<void>;
13
+ loadArrowInternal?(file: arrow.Table | Uint8Array, tableName: string, opts?: {
14
+ schema?: string;
15
+ }): Promise<void>;
16
+ loadObjectsInternal?(file: Record<string, unknown>[], tableName: string, opts?: StandardLoadOptions): Promise<void>;
17
+ loadFileInternal?(file: string | File, tableName: string, opts?: LoadFileOptions): Promise<void>;
18
+ }
19
+ export declare function createBaseDuckDbConnector({ dbPath, initializationQuery, }: BaseDuckDbConnectorOptions | undefined, impl: BaseDuckDbConnectorImpl): DuckDbConnector;
20
+ //# sourceMappingURL=BaseDuckDbConnector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseDuckDbConnector.d.ts","sourceRoot":"","sources":["../src/BaseDuckDbConnector.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EACf,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAC,eAAe,EAA4B,MAAM,mBAAmB,CAAC;AAI7E,MAAM,WAAW,0BAA0B;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,kBAAkB,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,eAAe,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,oBAAoB,CAAC,CAAC,SAAS,KAAK,CAAC,OAAO,GAAG,GAAG,EAChD,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,WAAW,EACnB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,mBAAmB,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,iBAAiB,CAAC,CAChB,IAAI,EAAE,KAAK,CAAC,KAAK,GAAG,UAAU,EAC9B,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAC,GACvB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,mBAAmB,CAAC,CAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC/B,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,mBAAmB,GACzB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,gBAAgB,CAAC,CACf,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,eAAe,GACrB,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,wBAAgB,yBAAyB,CACvC,EACE,MAAmB,EACnB,mBAAwB,GACzB,EAAE,0BAA0B,YAAK,EAClC,IAAI,EAAE,uBAAuB,GAC5B,eAAe,CAoKjB"}
@@ -0,0 +1,122 @@
1
+ import { isSpatialLoadFileOptions, } from '@sqlrooms/room-config';
2
+ import { load, loadObjects as loadObjectsSql, loadSpatial } from './load/load';
3
+ import { createTypedRowAccessor } from './typedRowAccessor';
4
+ export function createBaseDuckDbConnector({ dbPath = ':memory:', initializationQuery = '', } = {}, impl) {
5
+ const state = {
6
+ dbPath,
7
+ initializationQuery,
8
+ initialized: false,
9
+ initializing: null,
10
+ activeQueries: new Map(),
11
+ };
12
+ const ensureInitialized = async () => {
13
+ if (!state.initialized && state.initializing) {
14
+ await state.initializing;
15
+ }
16
+ };
17
+ const initialize = async () => {
18
+ if (state.initialized) {
19
+ return;
20
+ }
21
+ if (state.initializing) {
22
+ return state.initializing;
23
+ }
24
+ state.initializing = (async () => {
25
+ await impl.initializeInternal?.();
26
+ if (initializationQuery) {
27
+ await impl.executeQueryInternal(initializationQuery, new AbortController().signal);
28
+ }
29
+ state.initialized = true;
30
+ state.initializing = null;
31
+ })().catch((err) => {
32
+ state.initialized = false;
33
+ state.initializing = null;
34
+ throw err;
35
+ });
36
+ return state.initializing;
37
+ };
38
+ const destroy = async () => {
39
+ await impl.destroyInternal?.();
40
+ state.initialized = false;
41
+ state.initializing = null;
42
+ };
43
+ const generateQueryId = () => `q_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
44
+ const cancelQuery = async (queryId) => {
45
+ const abortController = state.activeQueries.get(queryId);
46
+ if (abortController) {
47
+ abortController.abort();
48
+ state.activeQueries.delete(queryId);
49
+ }
50
+ await impl.cancelQueryInternal?.(queryId);
51
+ };
52
+ const createQueryHandle = (queryPromiseFactory, options) => {
53
+ const abortController = new AbortController();
54
+ const queryId = generateQueryId();
55
+ if (options?.signal) {
56
+ const userSignal = options.signal;
57
+ if (userSignal.aborted)
58
+ abortController.abort();
59
+ else
60
+ userSignal.addEventListener('abort', () => abortController.abort());
61
+ }
62
+ state.activeQueries.set(queryId, abortController);
63
+ const resultPromise = queryPromiseFactory(abortController.signal, queryId).finally(() => {
64
+ state.activeQueries.delete(queryId);
65
+ });
66
+ const handle = {
67
+ result: resultPromise,
68
+ signal: abortController.signal,
69
+ cancel: async () => cancelQuery(queryId),
70
+ then: resultPromise.then.bind(resultPromise),
71
+ catch: resultPromise.catch.bind(resultPromise),
72
+ finally: resultPromise.finally?.bind(resultPromise),
73
+ };
74
+ return handle;
75
+ };
76
+ const execute = (sql, options) => createQueryHandle((signal, id) => impl.executeQueryInternal(sql, signal, id), options);
77
+ const query = (queryStr, options) => createQueryHandle((signal, id) => impl.executeQueryInternal(queryStr, signal, id), options);
78
+ const queryJson = (queryStr, options) => createQueryHandle(async (signal, id) => {
79
+ const table = await impl.executeQueryInternal(queryStr, signal, id);
80
+ return createTypedRowAccessor({ arrowTable: table });
81
+ }, options);
82
+ const loadFile = async (file, tableName, opts) => {
83
+ if (impl.loadFileInternal) {
84
+ return impl.loadFileInternal(file, tableName, opts);
85
+ }
86
+ if (file instanceof File) {
87
+ throw new Error('Not implemented', { cause: { file, tableName, opts } });
88
+ }
89
+ const fileName = file;
90
+ await ensureInitialized();
91
+ if (opts && isSpatialLoadFileOptions(opts)) {
92
+ await query(loadSpatial(tableName, fileName, opts));
93
+ }
94
+ else {
95
+ await query(load(opts?.method ?? 'auto', tableName, fileName, opts));
96
+ }
97
+ };
98
+ const loadArrow = async (file, tableName, opts) => {
99
+ if (impl.loadArrowInternal) {
100
+ return impl.loadArrowInternal(file, tableName, opts);
101
+ }
102
+ throw new Error('Not implemented');
103
+ };
104
+ const loadObjects = async (file, tableName, opts) => {
105
+ if (impl.loadObjectsInternal) {
106
+ return impl.loadObjectsInternal(file, tableName, opts);
107
+ }
108
+ await ensureInitialized();
109
+ await query(loadObjectsSql(tableName, file, opts));
110
+ };
111
+ return {
112
+ initialize,
113
+ destroy,
114
+ execute,
115
+ query,
116
+ queryJson,
117
+ loadFile,
118
+ loadArrow,
119
+ loadObjects,
120
+ };
121
+ }
122
+ //# sourceMappingURL=BaseDuckDbConnector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseDuckDbConnector.js","sourceRoot":"","sources":["../src/BaseDuckDbConnector.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n isSpatialLoadFileOptions,\n LoadFileOptions,\n StandardLoadOptions,\n} from '@sqlrooms/room-config';\nimport * as arrow from 'apache-arrow';\nimport {DuckDbConnector, QueryHandle, QueryOptions} from './DuckDbConnector';\nimport {load, loadObjects as loadObjectsSql, loadSpatial} from './load/load';\nimport {createTypedRowAccessor} from './typedRowAccessor';\n\nexport interface BaseDuckDbConnectorOptions {\n dbPath?: string;\n initializationQuery?: string;\n}\n\nexport interface BaseDuckDbConnectorImpl {\n initializeInternal?(): Promise<void>;\n destroyInternal?(): Promise<void>;\n executeQueryInternal<T extends arrow.TypeMap = any>(\n query: string,\n signal: AbortSignal,\n queryId?: string,\n ): Promise<arrow.Table<T>>;\n cancelQueryInternal?(queryId: string): Promise<void>;\n loadArrowInternal?(\n file: arrow.Table | Uint8Array,\n tableName: string,\n opts?: {schema?: string},\n ): Promise<void>;\n loadObjectsInternal?(\n file: Record<string, unknown>[],\n tableName: string,\n opts?: StandardLoadOptions,\n ): Promise<void>;\n loadFileInternal?(\n file: string | File,\n tableName: string,\n opts?: LoadFileOptions,\n ): Promise<void>;\n}\n\nexport function createBaseDuckDbConnector(\n {\n dbPath = ':memory:',\n initializationQuery = '',\n }: BaseDuckDbConnectorOptions = {},\n impl: BaseDuckDbConnectorImpl,\n): DuckDbConnector {\n const state = {\n dbPath,\n initializationQuery,\n initialized: false,\n initializing: null as Promise<void> | null,\n activeQueries: new Map<string, AbortController>(),\n };\n\n const ensureInitialized = async () => {\n if (!state.initialized && state.initializing) {\n await state.initializing;\n }\n };\n\n const initialize = async () => {\n if (state.initialized) {\n return;\n }\n if (state.initializing) {\n return state.initializing;\n }\n state.initializing = (async () => {\n await impl.initializeInternal?.();\n if (initializationQuery) {\n await impl.executeQueryInternal(\n initializationQuery,\n new AbortController().signal,\n );\n }\n state.initialized = true;\n state.initializing = null;\n })().catch((err) => {\n state.initialized = false;\n state.initializing = null;\n throw err;\n });\n return state.initializing;\n };\n\n const destroy = async () => {\n await impl.destroyInternal?.();\n state.initialized = false;\n state.initializing = null;\n };\n\n const generateQueryId = () =>\n `q_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n\n const cancelQuery = async (queryId: string) => {\n const abortController = state.activeQueries.get(queryId);\n if (abortController) {\n abortController.abort();\n state.activeQueries.delete(queryId);\n }\n await impl.cancelQueryInternal?.(queryId);\n };\n\n const createQueryHandle = <T>(\n queryPromiseFactory: (signal: AbortSignal, queryId: string) => Promise<T>,\n options?: QueryOptions,\n ): QueryHandle<T> => {\n const abortController = new AbortController();\n const queryId = generateQueryId();\n if (options?.signal) {\n const userSignal = options.signal;\n if (userSignal.aborted) abortController.abort();\n else userSignal.addEventListener('abort', () => abortController.abort());\n }\n state.activeQueries.set(queryId, abortController);\n const resultPromise = queryPromiseFactory(\n abortController.signal,\n queryId,\n ).finally(() => {\n state.activeQueries.delete(queryId);\n });\n const handle: QueryHandle<T> = {\n result: resultPromise,\n signal: abortController.signal,\n cancel: async () => cancelQuery(queryId),\n then: resultPromise.then.bind(resultPromise),\n catch: resultPromise.catch.bind(resultPromise),\n finally: resultPromise.finally?.bind(resultPromise),\n } as unknown as QueryHandle<T>;\n return handle;\n };\n\n const execute = (sql: string, options?: QueryOptions): QueryHandle =>\n createQueryHandle(\n (signal, id) => impl.executeQueryInternal(sql, signal, id),\n options,\n );\n\n const query = <T extends arrow.TypeMap = any>(\n queryStr: string,\n options?: QueryOptions,\n ): QueryHandle<arrow.Table<T>> =>\n createQueryHandle(\n (signal, id) => impl.executeQueryInternal<T>(queryStr, signal, id),\n options,\n );\n\n const queryJson = <T = Record<string, any>>(\n queryStr: string,\n options?: QueryOptions,\n ): QueryHandle<Iterable<T>> =>\n createQueryHandle(async (signal, id) => {\n const table = await impl.executeQueryInternal(queryStr, signal, id);\n return createTypedRowAccessor({arrowTable: table});\n }, options);\n\n const loadFile = async (\n file: string | File,\n tableName: string,\n opts?: LoadFileOptions,\n ) => {\n if (impl.loadFileInternal) {\n return impl.loadFileInternal(file, tableName, opts);\n }\n if (file instanceof File) {\n throw new Error('Not implemented', {cause: {file, tableName, opts}});\n }\n const fileName = file;\n await ensureInitialized();\n if (opts && isSpatialLoadFileOptions(opts)) {\n await query(loadSpatial(tableName, fileName, opts));\n } else {\n await query(load(opts?.method ?? 'auto', tableName, fileName, opts));\n }\n };\n\n const loadArrow = async (\n file: arrow.Table | Uint8Array,\n tableName: string,\n opts?: {schema?: string},\n ): Promise<void> => {\n if (impl.loadArrowInternal) {\n return impl.loadArrowInternal(file, tableName, opts);\n }\n throw new Error('Not implemented');\n };\n\n const loadObjects = async (\n file: Record<string, unknown>[],\n tableName: string,\n opts?: StandardLoadOptions,\n ) => {\n if (impl.loadObjectsInternal) {\n return impl.loadObjectsInternal(file, tableName, opts);\n }\n await ensureInitialized();\n await query(loadObjectsSql(tableName, file, opts));\n };\n\n return {\n initialize,\n destroy,\n execute,\n query,\n queryJson,\n loadFile,\n loadArrow,\n loadObjects,\n };\n}\n"]}