@visactor/vquery 0.1.45 → 0.1.47

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,16 @@
1
+ import { DataSourceType, DataSourceValue } from '../types';
2
+ export declare class DataSourceBuilder {
3
+ private type;
4
+ private value;
5
+ constructor(type: DataSourceType, value: DataSourceValue);
6
+ static from(type: DataSourceType, value: DataSourceValue): DataSourceBuilder;
7
+ build(): Promise<{
8
+ type: DataSourceType;
9
+ blob: Blob;
10
+ }>;
11
+ /**
12
+ * 将不同类型的数据转换为Blob
13
+ */
14
+ private static convertToBlob;
15
+ private static fetchBlob;
16
+ }
@@ -0,0 +1 @@
1
+ export { DataSourceBuilder } from './dataSourceBuilder';
@@ -0,0 +1,21 @@
1
+ import { DatasetColumn } from '../types';
2
+ import { DuckDB } from '../db/duckDb';
3
+ import { IndexedDB } from '../db/indexedDb';
4
+ export declare class Dataset {
5
+ private duckDB;
6
+ private indexedDB;
7
+ private _datasetId;
8
+ constructor(duckDB: DuckDB, indexedDB: IndexedDB, datasetId: string);
9
+ init(temporaryStructs?: DatasetColumn[]): Promise<void>;
10
+ queryBySQL(sql: string): Promise<{
11
+ performance: {
12
+ startAt: string;
13
+ endAt: string;
14
+ duration: number;
15
+ };
16
+ dataset: any[];
17
+ table: any;
18
+ }>;
19
+ disconnect(): Promise<void>;
20
+ get datasetId(): string;
21
+ }
@@ -0,0 +1 @@
1
+ export { Dataset } from './dataset';
@@ -1,4 +1,4 @@
1
- import { QueryResult } from '../types';
1
+ import { QueryResult } from '../types/DataSet';
2
2
  export declare class DuckDB {
3
3
  private db;
4
4
  private connection;
@@ -16,7 +16,7 @@ export declare class DuckDB {
16
16
  * @param fileName 文件名
17
17
  * @param source 文件内容
18
18
  */
19
- writeFile: <T extends string | ArrayBuffer | Uint8Array | Blob>(fileName: string, source: T) => Promise<void>;
19
+ writeFile: <T extends Blob>(fileName: string, source: T) => Promise<void>;
20
20
  /**
21
21
  * @description 执行 SQL 查询
22
22
  * @param sql SQL 语句
@@ -26,11 +26,6 @@ export declare class DuckDB {
26
26
  dataset: any[];
27
27
  table: any;
28
28
  }>;
29
- /**
30
- * 确保一个文件存在,如果不存在,则根据同名文件创建临时表
31
- * @param fileName 文件名
32
- */
33
- private ensureSchema;
34
29
  /**
35
30
  * @description 获取文件的 Schema
36
31
  * @param fileName 文件名
@@ -1,11 +1,21 @@
1
+ import { DataSource } from '../types';
2
+ import { DatasetSchema } from '../types/DataSet';
1
3
  export declare class IndexedDB {
2
4
  private db;
3
5
  private dbName;
4
- private storeName;
6
+ private datasetStoreName;
5
7
  constructor(dbName: string);
6
8
  open: () => Promise<void>;
7
9
  close: () => void;
8
- writeFile: (fileName: string, data: Blob) => Promise<void>;
9
- readFile: (fileName: string) => Promise<Blob | null>;
10
- listFiles: () => Promise<string[]>;
10
+ writeDataset: (datasetId: string, dataSource: DataSource, datasetSchema: DatasetSchema) => Promise<void>;
11
+ readDataset: (datasetId: string) => Promise<{
12
+ dataSource: DataSource;
13
+ datasetSchema: DatasetSchema;
14
+ } | null>;
15
+ deleteDataset: (datasetId: string) => Promise<void>;
16
+ listDatasets: () => Promise<{
17
+ datasetId: string;
18
+ dataSource: DataSource;
19
+ datasetSchema: DatasetSchema;
20
+ }[]>;
11
21
  }
package/dist/index.cjs CHANGED
@@ -27,8 +27,68 @@ var __webpack_require__ = {};
27
27
  var __webpack_exports__ = {};
28
28
  __webpack_require__.r(__webpack_exports__);
29
29
  __webpack_require__.d(__webpack_exports__, {
30
+ DataSourceBuilder: ()=>DataSourceBuilder,
31
+ isHttpUrl: ()=>isHttpUrl,
32
+ isBase64Url: ()=>isBase64Url,
33
+ isUrl: ()=>isUrl,
30
34
  VQuery: ()=>VQuery
31
35
  });
36
+ class Dataset {
37
+ duckDB;
38
+ indexedDB;
39
+ _datasetId;
40
+ constructor(duckDB, indexedDB1, datasetId){
41
+ this.duckDB = duckDB;
42
+ this.indexedDB = indexedDB1;
43
+ this._datasetId = datasetId;
44
+ }
45
+ async init(temporaryStructs) {
46
+ const readFunctionMap = {
47
+ csv: 'read_csv_auto',
48
+ json: 'read_json_auto',
49
+ xlsx: 'read_excel',
50
+ parquet: 'read_parquet'
51
+ };
52
+ const dataTypeMap = {
53
+ number: 'DOUBLE',
54
+ string: 'VARCHAR',
55
+ date: 'DATE',
56
+ datetime: 'TIMESTAMP',
57
+ timestamp: 'TIMESTAMP'
58
+ };
59
+ const datasetInfo = await this.indexedDB.readDataset(this._datasetId);
60
+ if (!datasetInfo) throw new Error(`Dataset ${this._datasetId} not found`);
61
+ const { dataSource } = datasetInfo;
62
+ const datasetSchema = datasetInfo.datasetSchema;
63
+ const columns = temporaryStructs || datasetSchema.columns;
64
+ const readFunction = readFunctionMap[dataSource.type];
65
+ if (!readFunction) throw new Error(`Unsupported dataSource type: ${dataSource.type}`);
66
+ await this.duckDB.writeFile(this._datasetId, dataSource.blob);
67
+ const columnsStruct = `{${columns.map((c)=>`'${c.name}': '${dataTypeMap[c.type] || 'VARCHAR'}'`).join(', ')}}`;
68
+ const columnNames = columns.map((c)=>`"${c.name}"`).join(', ');
69
+ const createViewSql = `CREATE OR REPLACE VIEW "${this._datasetId}" AS SELECT ${columnNames} FROM ${readFunction}('${this._datasetId}', columns=${columnsStruct})`;
70
+ await this.duckDB.query(createViewSql);
71
+ }
72
+ async queryBySQL(sql) {
73
+ const start = performance?.now?.()?.toFixed(3) ?? Date.now().toFixed(3);
74
+ const result = await this.duckDB.query(sql);
75
+ const end = performance?.now?.()?.toFixed(3) ?? Date.now().toFixed(3);
76
+ return {
77
+ ...result,
78
+ performance: {
79
+ startAt: start,
80
+ endAt: end,
81
+ duration: Number(end) - Number(start)
82
+ }
83
+ };
84
+ }
85
+ async disconnect() {
86
+ await this.duckDB.query(`DROP VIEW IF EXISTS "${this._datasetId}"`);
87
+ }
88
+ get datasetId() {
89
+ return this._datasetId;
90
+ }
91
+ }
32
92
  const duckdb_wasm_namespaceObject = require("@duckdb/duckdb-wasm");
33
93
  class DuckDB {
34
94
  db = null;
@@ -71,16 +131,10 @@ class DuckDB {
71
131
  writeFile = async (fileName, source)=>{
72
132
  if (!this.db) throw new Error('db is null');
73
133
  let uint8Array;
74
- if ('string' == typeof source) {
75
- const response = await fetch(source);
76
- const buffer = await response.arrayBuffer();
77
- uint8Array = new Uint8Array(buffer);
78
- } else if (source instanceof Blob) {
134
+ if (source instanceof Blob) {
79
135
  const buffer = await source.arrayBuffer();
80
136
  uint8Array = new Uint8Array(buffer);
81
- } else if (source instanceof ArrayBuffer) uint8Array = new Uint8Array(source);
82
- else if (source instanceof Uint8Array) uint8Array = source;
83
- else throw new Error('Unsupported source type');
137
+ } else throw new Error('Unsupported source type');
84
138
  await this.db.registerFileBuffer(fileName, uint8Array);
85
139
  };
86
140
  query = async (sql)=>{
@@ -92,13 +146,8 @@ class DuckDB {
92
146
  table
93
147
  };
94
148
  };
95
- ensureSchema = async (fileName)=>{
96
- if (!this.connection) throw new Error('connection is null');
97
- await this.connection.query(`CREATE TEMP TABLE IF NOT EXISTS "${fileName}" AS SELECT * FROM read_csv_auto('${fileName}')`);
98
- };
99
149
  getSchema = async (fileName)=>{
100
150
  if (!this.connection) throw new Error('connection is null');
101
- await this.ensureSchema(fileName);
102
151
  const result = await this.connection.query(`PRAGMA table_info('${fileName}')`);
103
152
  return result.toArray().map((row)=>row.toJSON());
104
153
  };
@@ -106,16 +155,16 @@ class DuckDB {
106
155
  class IndexedDB {
107
156
  db = null;
108
157
  dbName;
109
- storeName = 'vqueryFiles';
158
+ datasetStoreName = 'vqueryDatasets';
110
159
  constructor(dbName){
111
160
  this.dbName = dbName;
112
161
  }
113
162
  open = ()=>new Promise((resolve, reject)=>{
114
- const request = indexedDB.open(this.dbName, 1);
163
+ const request = indexedDB.open(this.dbName, 2);
115
164
  request.onupgradeneeded = (event)=>{
116
165
  const db = event.target.result;
117
- if (!db.objectStoreNames.contains(this.storeName)) db.createObjectStore(this.storeName, {
118
- keyPath: 'name'
166
+ if (!db.objectStoreNames.contains(this.datasetStoreName)) db.createObjectStore(this.datasetStoreName, {
167
+ keyPath: 'datasetId'
119
168
  });
120
169
  };
121
170
  request.onsuccess = (event)=>{
@@ -132,15 +181,16 @@ class IndexedDB {
132
181
  this.db = null;
133
182
  }
134
183
  };
135
- writeFile = (fileName, data)=>new Promise((resolve, reject)=>{
184
+ writeDataset = (datasetId, dataSource, datasetSchema)=>new Promise((resolve, reject)=>{
136
185
  if (!this.db) return reject('DB is not open');
137
186
  const transaction = this.db.transaction([
138
- this.storeName
187
+ this.datasetStoreName
139
188
  ], 'readwrite');
140
- const store = transaction.objectStore(this.storeName);
189
+ const store = transaction.objectStore(this.datasetStoreName);
141
190
  const request = store.put({
142
- name: fileName,
143
- data
191
+ datasetId,
192
+ dataSource,
193
+ datasetSchema
144
194
  });
145
195
  request.onsuccess = ()=>{
146
196
  resolve();
@@ -149,92 +199,206 @@ class IndexedDB {
149
199
  reject(event.target.error);
150
200
  };
151
201
  });
152
- readFile = (fileName)=>new Promise((resolve, reject)=>{
202
+ readDataset = (datasetId)=>new Promise((resolve, reject)=>{
153
203
  if (!this.db) return reject('DB is not open');
154
204
  const transaction = this.db.transaction([
155
- this.storeName
205
+ this.datasetStoreName
156
206
  ], 'readonly');
157
- const store = transaction.objectStore(this.storeName);
158
- const request = store.get(fileName);
207
+ const store = transaction.objectStore(this.datasetStoreName);
208
+ const request = store.get(datasetId);
159
209
  request.onsuccess = (event)=>{
160
210
  const result = event.target.result;
161
- result ? resolve(result.data) : resolve(null);
211
+ resolve(result || null);
162
212
  };
163
213
  request.onerror = (event)=>{
164
214
  reject(event.target.error);
165
215
  };
166
216
  });
167
- listFiles = ()=>new Promise((resolve, reject)=>{
217
+ deleteDataset = (datasetId)=>new Promise((resolve, reject)=>{
168
218
  if (!this.db) return reject('DB is not open');
169
219
  const transaction = this.db.transaction([
170
- this.storeName
220
+ this.datasetStoreName
221
+ ], 'readwrite');
222
+ const store = transaction.objectStore(this.datasetStoreName);
223
+ const request = store.delete(datasetId);
224
+ request.onsuccess = ()=>{
225
+ resolve();
226
+ };
227
+ request.onerror = (event)=>{
228
+ reject(event.target.error);
229
+ };
230
+ });
231
+ listDatasets = ()=>new Promise((resolve, reject)=>{
232
+ if (!this.db) return reject('DB is not open');
233
+ const transaction = this.db.transaction([
234
+ this.datasetStoreName
171
235
  ], 'readonly');
172
- const store = transaction.objectStore(this.storeName);
173
- const request = store.getAllKeys();
236
+ const store = transaction.objectStore(this.datasetStoreName);
237
+ const request = store.getAll();
174
238
  request.onsuccess = (event)=>{
175
- const keys = event.target.result;
176
- resolve(keys);
239
+ const result = event.target.result;
240
+ resolve(result);
177
241
  };
178
242
  request.onerror = (event)=>{
179
243
  reject(event.target.error);
180
244
  };
181
245
  });
182
246
  }
247
+ const isUrl = (url)=>isHttpUrl(url) || isBase64Url(url);
248
+ const isHttpUrl = (url)=>url.startsWith('http://') || url.startsWith('https://');
249
+ const isBase64Url = (url)=>url.startsWith('data:');
250
+ class DataSourceBuilder {
251
+ type;
252
+ value;
253
+ constructor(type, value){
254
+ this.type = type;
255
+ this.value = value;
256
+ }
257
+ static from(type, value) {
258
+ return new DataSourceBuilder(type, value);
259
+ }
260
+ async build() {
261
+ const blob = await DataSourceBuilder.convertToBlob(this.type, this.value);
262
+ return {
263
+ type: this.type,
264
+ blob: blob
265
+ };
266
+ }
267
+ static async convertToBlob(type, value) {
268
+ if (value instanceof Blob) return value;
269
+ const convertCsvToBlob = (csvSource)=>{
270
+ if (csvSource instanceof ArrayBuffer) return new Blob([
271
+ csvSource
272
+ ], {
273
+ type: 'text/csv'
274
+ });
275
+ if ('string' == typeof csvSource && isUrl(csvSource)) return DataSourceBuilder.fetchBlob(csvSource);
276
+ return new Blob([
277
+ JSON.stringify(csvSource)
278
+ ], {
279
+ type: 'text/csv'
280
+ });
281
+ };
282
+ const convertJsonToBlob = (jsonSource)=>{
283
+ if (jsonSource instanceof ArrayBuffer) return new Blob([
284
+ jsonSource
285
+ ], {
286
+ type: 'application/json'
287
+ });
288
+ if ('string' == typeof jsonSource && isUrl(jsonSource)) return DataSourceBuilder.fetchBlob(jsonSource);
289
+ return new Blob([
290
+ JSON.stringify(jsonSource)
291
+ ], {
292
+ type: 'application/json'
293
+ });
294
+ };
295
+ const convertParquetToBlob = (parquetSource)=>{
296
+ if (parquetSource instanceof ArrayBuffer) return new Blob([
297
+ parquetSource
298
+ ], {
299
+ type: 'application/parquet'
300
+ });
301
+ if ('string' == typeof parquetSource && isUrl(parquetSource)) return DataSourceBuilder.fetchBlob(parquetSource);
302
+ return new Blob([
303
+ parquetSource
304
+ ], {
305
+ type: 'application/parquet'
306
+ });
307
+ };
308
+ const convertXlsxToBlob = (xlsxSource)=>{
309
+ if (xlsxSource instanceof ArrayBuffer) return new Blob([
310
+ xlsxSource
311
+ ], {
312
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
313
+ });
314
+ if ('string' == typeof xlsxSource && isUrl(xlsxSource)) return DataSourceBuilder.fetchBlob(xlsxSource);
315
+ return new Blob([
316
+ xlsxSource
317
+ ], {
318
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
319
+ });
320
+ };
321
+ switch(type){
322
+ case 'csv':
323
+ return convertCsvToBlob(value);
324
+ case 'json':
325
+ return convertJsonToBlob(value);
326
+ case 'xlsx':
327
+ return convertXlsxToBlob(value);
328
+ case 'parquet':
329
+ return convertParquetToBlob(value);
330
+ default:
331
+ return new Blob([
332
+ value
333
+ ]);
334
+ }
335
+ }
336
+ static async fetchBlob(url) {
337
+ const response = await fetch(url);
338
+ return await response.blob();
339
+ }
340
+ }
183
341
  class VQuery {
184
342
  duckDB;
185
343
  indexedDB;
344
+ isInitialized = false;
186
345
  constructor(dbName = 'vquery'){
187
346
  this.duckDB = new DuckDB();
188
347
  this.indexedDB = new IndexedDB(dbName);
189
348
  }
190
- init = async ()=>{
191
- await this.duckDB.init();
192
- await this.indexedDB.open();
193
- };
194
- close = async ()=>{
349
+ async ensureInitialized() {
350
+ if (!this.isInitialized) {
351
+ await this.duckDB.init();
352
+ await this.indexedDB.open();
353
+ this.isInitialized = true;
354
+ }
355
+ }
356
+ async createDataset(datasetId, data, type, datasetSchema) {
357
+ await this.ensureInitialized();
358
+ const dataSource = await DataSourceBuilder.from(type, data).build();
359
+ await this.indexedDB.writeDataset(datasetId, dataSource, datasetSchema);
360
+ }
361
+ async updateDataset(datasetId, data, type, datasetSchema) {
362
+ await this.ensureInitialized();
363
+ const dataSource = await DataSourceBuilder.from(type, data).build();
364
+ await this.indexedDB.writeDataset(datasetId, dataSource, datasetSchema);
365
+ }
366
+ async deleteDataset(datasetId) {
367
+ await this.ensureInitialized();
368
+ await this.indexedDB.deleteDataset(datasetId);
369
+ }
370
+ async listDatasets() {
371
+ await this.ensureInitialized();
372
+ return this.indexedDB.listDatasets();
373
+ }
374
+ async connectDataset(datasetId) {
375
+ await this.ensureInitialized();
376
+ const dataset = new Dataset(this.duckDB, this.indexedDB, datasetId);
377
+ await dataset.init();
378
+ return dataset;
379
+ }
380
+ async connectTemporaryDataset(datasetId, temporaryDatasetSchema) {
381
+ await this.ensureInitialized();
382
+ const dataset = new Dataset(this.duckDB, this.indexedDB, datasetId);
383
+ await dataset.init(temporaryDatasetSchema);
384
+ return dataset;
385
+ }
386
+ async close() {
387
+ await this.ensureInitialized();
195
388
  await this.duckDB.close();
196
- this.indexedDB.close();
197
- };
198
- writeFile = async (fileName, source)=>{
199
- let blob;
200
- if ('string' == typeof source) {
201
- const response = await fetch(source);
202
- blob = await response.blob();
203
- } else if (source instanceof ArrayBuffer) blob = new Blob([
204
- source
205
- ]);
206
- else if (source instanceof Uint8Array) blob = new Blob([
207
- source.slice()
208
- ]);
209
- else if (source instanceof Blob) blob = source;
210
- else throw new Error('Unsupported source type');
211
- await this.indexedDB.writeFile(fileName, blob);
212
- await this.duckDB.writeFile(fileName, blob);
213
- };
214
- readFile = async (fileName)=>{
215
- const blob = await this.indexedDB.readFile(fileName);
216
- if (blob) await this.duckDB.writeFile(fileName, blob);
217
- else throw new Error(`File ${fileName} not found in IndexedDB`);
218
- };
219
- listFiles = ()=>this.indexedDB.listFiles();
220
- query = async (sql)=>{
221
- const start = performance?.now?.()?.toFixed(3) ?? Date.now().toFixed(3);
222
- const result = await this.duckDB.query(sql);
223
- const end = performance?.now?.()?.toFixed(3) ?? Date.now().toFixed(3);
224
- return {
225
- ...result,
226
- performance: {
227
- startAt: start,
228
- endAt: end,
229
- duration: Number(end) - Number(start)
230
- }
231
- };
232
- };
233
- getSchema = async (fileName)=>this.duckDB.getSchema(fileName);
389
+ }
234
390
  }
391
+ exports.DataSourceBuilder = __webpack_exports__.DataSourceBuilder;
235
392
  exports.VQuery = __webpack_exports__.VQuery;
393
+ exports.isBase64Url = __webpack_exports__.isBase64Url;
394
+ exports.isHttpUrl = __webpack_exports__.isHttpUrl;
395
+ exports.isUrl = __webpack_exports__.isUrl;
236
396
  for(var __webpack_i__ in __webpack_exports__)if (-1 === [
237
- "VQuery"
397
+ "DataSourceBuilder",
398
+ "VQuery",
399
+ "isBase64Url",
400
+ "isHttpUrl",
401
+ "isUrl"
238
402
  ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
239
403
  Object.defineProperty(exports, '__esModule', {
240
404
  value: true
package/dist/index.d.ts CHANGED
@@ -1 +1,3 @@
1
1
  export { VQuery } from './vquery';
2
+ export { DataSourceBuilder } from './dataSourceBuilder/dataSourceBuilder';
3
+ export * from './utils';
package/dist/index.js CHANGED
@@ -1,4 +1,60 @@
1
1
  import { AsyncDuckDB, ConsoleLogger, selectBundle } from "@duckdb/duckdb-wasm";
2
+ class Dataset {
3
+ duckDB;
4
+ indexedDB;
5
+ _datasetId;
6
+ constructor(duckDB, indexedDB1, datasetId){
7
+ this.duckDB = duckDB;
8
+ this.indexedDB = indexedDB1;
9
+ this._datasetId = datasetId;
10
+ }
11
+ async init(temporaryStructs) {
12
+ const readFunctionMap = {
13
+ csv: 'read_csv_auto',
14
+ json: 'read_json_auto',
15
+ xlsx: 'read_excel',
16
+ parquet: 'read_parquet'
17
+ };
18
+ const dataTypeMap = {
19
+ number: 'DOUBLE',
20
+ string: 'VARCHAR',
21
+ date: 'DATE',
22
+ datetime: 'TIMESTAMP',
23
+ timestamp: 'TIMESTAMP'
24
+ };
25
+ const datasetInfo = await this.indexedDB.readDataset(this._datasetId);
26
+ if (!datasetInfo) throw new Error(`Dataset ${this._datasetId} not found`);
27
+ const { dataSource } = datasetInfo;
28
+ const datasetSchema = datasetInfo.datasetSchema;
29
+ const columns = temporaryStructs || datasetSchema.columns;
30
+ const readFunction = readFunctionMap[dataSource.type];
31
+ if (!readFunction) throw new Error(`Unsupported dataSource type: ${dataSource.type}`);
32
+ await this.duckDB.writeFile(this._datasetId, dataSource.blob);
33
+ const columnsStruct = `{${columns.map((c)=>`'${c.name}': '${dataTypeMap[c.type] || 'VARCHAR'}'`).join(', ')}}`;
34
+ const columnNames = columns.map((c)=>`"${c.name}"`).join(', ');
35
+ const createViewSql = `CREATE OR REPLACE VIEW "${this._datasetId}" AS SELECT ${columnNames} FROM ${readFunction}('${this._datasetId}', columns=${columnsStruct})`;
36
+ await this.duckDB.query(createViewSql);
37
+ }
38
+ async queryBySQL(sql) {
39
+ const start = performance?.now?.()?.toFixed(3) ?? Date.now().toFixed(3);
40
+ const result = await this.duckDB.query(sql);
41
+ const end = performance?.now?.()?.toFixed(3) ?? Date.now().toFixed(3);
42
+ return {
43
+ ...result,
44
+ performance: {
45
+ startAt: start,
46
+ endAt: end,
47
+ duration: Number(end) - Number(start)
48
+ }
49
+ };
50
+ }
51
+ async disconnect() {
52
+ await this.duckDB.query(`DROP VIEW IF EXISTS "${this._datasetId}"`);
53
+ }
54
+ get datasetId() {
55
+ return this._datasetId;
56
+ }
57
+ }
2
58
  class DuckDB {
3
59
  db = null;
4
60
  connection = null;
@@ -40,16 +96,10 @@ class DuckDB {
40
96
  writeFile = async (fileName, source)=>{
41
97
  if (!this.db) throw new Error('db is null');
42
98
  let uint8Array;
43
- if ('string' == typeof source) {
44
- const response = await fetch(source);
45
- const buffer = await response.arrayBuffer();
46
- uint8Array = new Uint8Array(buffer);
47
- } else if (source instanceof Blob) {
99
+ if (source instanceof Blob) {
48
100
  const buffer = await source.arrayBuffer();
49
101
  uint8Array = new Uint8Array(buffer);
50
- } else if (source instanceof ArrayBuffer) uint8Array = new Uint8Array(source);
51
- else if (source instanceof Uint8Array) uint8Array = source;
52
- else throw new Error('Unsupported source type');
102
+ } else throw new Error('Unsupported source type');
53
103
  await this.db.registerFileBuffer(fileName, uint8Array);
54
104
  };
55
105
  query = async (sql)=>{
@@ -61,13 +111,8 @@ class DuckDB {
61
111
  table
62
112
  };
63
113
  };
64
- ensureSchema = async (fileName)=>{
65
- if (!this.connection) throw new Error('connection is null');
66
- await this.connection.query(`CREATE TEMP TABLE IF NOT EXISTS "${fileName}" AS SELECT * FROM read_csv_auto('${fileName}')`);
67
- };
68
114
  getSchema = async (fileName)=>{
69
115
  if (!this.connection) throw new Error('connection is null');
70
- await this.ensureSchema(fileName);
71
116
  const result = await this.connection.query(`PRAGMA table_info('${fileName}')`);
72
117
  return result.toArray().map((row)=>row.toJSON());
73
118
  };
@@ -75,16 +120,16 @@ class DuckDB {
75
120
  class IndexedDB {
76
121
  db = null;
77
122
  dbName;
78
- storeName = 'vqueryFiles';
123
+ datasetStoreName = 'vqueryDatasets';
79
124
  constructor(dbName){
80
125
  this.dbName = dbName;
81
126
  }
82
127
  open = ()=>new Promise((resolve, reject)=>{
83
- const request = indexedDB.open(this.dbName, 1);
128
+ const request = indexedDB.open(this.dbName, 2);
84
129
  request.onupgradeneeded = (event)=>{
85
130
  const db = event.target.result;
86
- if (!db.objectStoreNames.contains(this.storeName)) db.createObjectStore(this.storeName, {
87
- keyPath: 'name'
131
+ if (!db.objectStoreNames.contains(this.datasetStoreName)) db.createObjectStore(this.datasetStoreName, {
132
+ keyPath: 'datasetId'
88
133
  });
89
134
  };
90
135
  request.onsuccess = (event)=>{
@@ -101,15 +146,16 @@ class IndexedDB {
101
146
  this.db = null;
102
147
  }
103
148
  };
104
- writeFile = (fileName, data)=>new Promise((resolve, reject)=>{
149
+ writeDataset = (datasetId, dataSource, datasetSchema)=>new Promise((resolve, reject)=>{
105
150
  if (!this.db) return reject('DB is not open');
106
151
  const transaction = this.db.transaction([
107
- this.storeName
152
+ this.datasetStoreName
108
153
  ], 'readwrite');
109
- const store = transaction.objectStore(this.storeName);
154
+ const store = transaction.objectStore(this.datasetStoreName);
110
155
  const request = store.put({
111
- name: fileName,
112
- data
156
+ datasetId,
157
+ dataSource,
158
+ datasetSchema
113
159
  });
114
160
  request.onsuccess = ()=>{
115
161
  resolve();
@@ -118,87 +164,193 @@ class IndexedDB {
118
164
  reject(event.target.error);
119
165
  };
120
166
  });
121
- readFile = (fileName)=>new Promise((resolve, reject)=>{
167
+ readDataset = (datasetId)=>new Promise((resolve, reject)=>{
122
168
  if (!this.db) return reject('DB is not open');
123
169
  const transaction = this.db.transaction([
124
- this.storeName
170
+ this.datasetStoreName
125
171
  ], 'readonly');
126
- const store = transaction.objectStore(this.storeName);
127
- const request = store.get(fileName);
172
+ const store = transaction.objectStore(this.datasetStoreName);
173
+ const request = store.get(datasetId);
128
174
  request.onsuccess = (event)=>{
129
175
  const result = event.target.result;
130
- result ? resolve(result.data) : resolve(null);
176
+ resolve(result || null);
131
177
  };
132
178
  request.onerror = (event)=>{
133
179
  reject(event.target.error);
134
180
  };
135
181
  });
136
- listFiles = ()=>new Promise((resolve, reject)=>{
182
+ deleteDataset = (datasetId)=>new Promise((resolve, reject)=>{
137
183
  if (!this.db) return reject('DB is not open');
138
184
  const transaction = this.db.transaction([
139
- this.storeName
185
+ this.datasetStoreName
186
+ ], 'readwrite');
187
+ const store = transaction.objectStore(this.datasetStoreName);
188
+ const request = store.delete(datasetId);
189
+ request.onsuccess = ()=>{
190
+ resolve();
191
+ };
192
+ request.onerror = (event)=>{
193
+ reject(event.target.error);
194
+ };
195
+ });
196
+ listDatasets = ()=>new Promise((resolve, reject)=>{
197
+ if (!this.db) return reject('DB is not open');
198
+ const transaction = this.db.transaction([
199
+ this.datasetStoreName
140
200
  ], 'readonly');
141
- const store = transaction.objectStore(this.storeName);
142
- const request = store.getAllKeys();
201
+ const store = transaction.objectStore(this.datasetStoreName);
202
+ const request = store.getAll();
143
203
  request.onsuccess = (event)=>{
144
- const keys = event.target.result;
145
- resolve(keys);
204
+ const result = event.target.result;
205
+ resolve(result);
146
206
  };
147
207
  request.onerror = (event)=>{
148
208
  reject(event.target.error);
149
209
  };
150
210
  });
151
211
  }
212
+ const isUrl = (url)=>isHttpUrl(url) || isBase64Url(url);
213
+ const isHttpUrl = (url)=>url.startsWith('http://') || url.startsWith('https://');
214
+ const isBase64Url = (url)=>url.startsWith('data:');
215
+ class DataSourceBuilder {
216
+ type;
217
+ value;
218
+ constructor(type, value){
219
+ this.type = type;
220
+ this.value = value;
221
+ }
222
+ static from(type, value) {
223
+ return new DataSourceBuilder(type, value);
224
+ }
225
+ async build() {
226
+ const blob = await DataSourceBuilder.convertToBlob(this.type, this.value);
227
+ return {
228
+ type: this.type,
229
+ blob: blob
230
+ };
231
+ }
232
+ static async convertToBlob(type, value) {
233
+ if (value instanceof Blob) return value;
234
+ const convertCsvToBlob = (csvSource)=>{
235
+ if (csvSource instanceof ArrayBuffer) return new Blob([
236
+ csvSource
237
+ ], {
238
+ type: 'text/csv'
239
+ });
240
+ if ('string' == typeof csvSource && isUrl(csvSource)) return DataSourceBuilder.fetchBlob(csvSource);
241
+ return new Blob([
242
+ JSON.stringify(csvSource)
243
+ ], {
244
+ type: 'text/csv'
245
+ });
246
+ };
247
+ const convertJsonToBlob = (jsonSource)=>{
248
+ if (jsonSource instanceof ArrayBuffer) return new Blob([
249
+ jsonSource
250
+ ], {
251
+ type: 'application/json'
252
+ });
253
+ if ('string' == typeof jsonSource && isUrl(jsonSource)) return DataSourceBuilder.fetchBlob(jsonSource);
254
+ return new Blob([
255
+ JSON.stringify(jsonSource)
256
+ ], {
257
+ type: 'application/json'
258
+ });
259
+ };
260
+ const convertParquetToBlob = (parquetSource)=>{
261
+ if (parquetSource instanceof ArrayBuffer) return new Blob([
262
+ parquetSource
263
+ ], {
264
+ type: 'application/parquet'
265
+ });
266
+ if ('string' == typeof parquetSource && isUrl(parquetSource)) return DataSourceBuilder.fetchBlob(parquetSource);
267
+ return new Blob([
268
+ parquetSource
269
+ ], {
270
+ type: 'application/parquet'
271
+ });
272
+ };
273
+ const convertXlsxToBlob = (xlsxSource)=>{
274
+ if (xlsxSource instanceof ArrayBuffer) return new Blob([
275
+ xlsxSource
276
+ ], {
277
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
278
+ });
279
+ if ('string' == typeof xlsxSource && isUrl(xlsxSource)) return DataSourceBuilder.fetchBlob(xlsxSource);
280
+ return new Blob([
281
+ xlsxSource
282
+ ], {
283
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
284
+ });
285
+ };
286
+ switch(type){
287
+ case 'csv':
288
+ return convertCsvToBlob(value);
289
+ case 'json':
290
+ return convertJsonToBlob(value);
291
+ case 'xlsx':
292
+ return convertXlsxToBlob(value);
293
+ case 'parquet':
294
+ return convertParquetToBlob(value);
295
+ default:
296
+ return new Blob([
297
+ value
298
+ ]);
299
+ }
300
+ }
301
+ static async fetchBlob(url) {
302
+ const response = await fetch(url);
303
+ return await response.blob();
304
+ }
305
+ }
152
306
  class VQuery {
153
307
  duckDB;
154
308
  indexedDB;
309
+ isInitialized = false;
155
310
  constructor(dbName = 'vquery'){
156
311
  this.duckDB = new DuckDB();
157
312
  this.indexedDB = new IndexedDB(dbName);
158
313
  }
159
- init = async ()=>{
160
- await this.duckDB.init();
161
- await this.indexedDB.open();
162
- };
163
- close = async ()=>{
314
+ async ensureInitialized() {
315
+ if (!this.isInitialized) {
316
+ await this.duckDB.init();
317
+ await this.indexedDB.open();
318
+ this.isInitialized = true;
319
+ }
320
+ }
321
+ async createDataset(datasetId, data, type, datasetSchema) {
322
+ await this.ensureInitialized();
323
+ const dataSource = await DataSourceBuilder.from(type, data).build();
324
+ await this.indexedDB.writeDataset(datasetId, dataSource, datasetSchema);
325
+ }
326
+ async updateDataset(datasetId, data, type, datasetSchema) {
327
+ await this.ensureInitialized();
328
+ const dataSource = await DataSourceBuilder.from(type, data).build();
329
+ await this.indexedDB.writeDataset(datasetId, dataSource, datasetSchema);
330
+ }
331
+ async deleteDataset(datasetId) {
332
+ await this.ensureInitialized();
333
+ await this.indexedDB.deleteDataset(datasetId);
334
+ }
335
+ async listDatasets() {
336
+ await this.ensureInitialized();
337
+ return this.indexedDB.listDatasets();
338
+ }
339
+ async connectDataset(datasetId) {
340
+ await this.ensureInitialized();
341
+ const dataset = new Dataset(this.duckDB, this.indexedDB, datasetId);
342
+ await dataset.init();
343
+ return dataset;
344
+ }
345
+ async connectTemporaryDataset(datasetId, temporaryDatasetSchema) {
346
+ await this.ensureInitialized();
347
+ const dataset = new Dataset(this.duckDB, this.indexedDB, datasetId);
348
+ await dataset.init(temporaryDatasetSchema);
349
+ return dataset;
350
+ }
351
+ async close() {
352
+ await this.ensureInitialized();
164
353
  await this.duckDB.close();
165
- this.indexedDB.close();
166
- };
167
- writeFile = async (fileName, source)=>{
168
- let blob;
169
- if ('string' == typeof source) {
170
- const response = await fetch(source);
171
- blob = await response.blob();
172
- } else if (source instanceof ArrayBuffer) blob = new Blob([
173
- source
174
- ]);
175
- else if (source instanceof Uint8Array) blob = new Blob([
176
- source.slice()
177
- ]);
178
- else if (source instanceof Blob) blob = source;
179
- else throw new Error('Unsupported source type');
180
- await this.indexedDB.writeFile(fileName, blob);
181
- await this.duckDB.writeFile(fileName, blob);
182
- };
183
- readFile = async (fileName)=>{
184
- const blob = await this.indexedDB.readFile(fileName);
185
- if (blob) await this.duckDB.writeFile(fileName, blob);
186
- else throw new Error(`File ${fileName} not found in IndexedDB`);
187
- };
188
- listFiles = ()=>this.indexedDB.listFiles();
189
- query = async (sql)=>{
190
- const start = performance?.now?.()?.toFixed(3) ?? Date.now().toFixed(3);
191
- const result = await this.duckDB.query(sql);
192
- const end = performance?.now?.()?.toFixed(3) ?? Date.now().toFixed(3);
193
- return {
194
- ...result,
195
- performance: {
196
- startAt: start,
197
- endAt: end,
198
- duration: Number(end) - Number(start)
199
- }
200
- };
201
- };
202
- getSchema = async (fileName)=>this.duckDB.getSchema(fileName);
354
+ }
203
355
  }
204
- export { VQuery };
356
+ export { DataSourceBuilder, VQuery, isBase64Url, isHttpUrl, isUrl };
@@ -0,0 +1,11 @@
1
+ export type { QueryResult } from './QueryResult';
2
+ export type DataType = 'number' | 'string' | 'date' | 'datetime' | 'timestamp';
3
+ export interface DatasetColumn {
4
+ type: DataType;
5
+ name: string;
6
+ }
7
+ export interface DatasetSchema {
8
+ datasetId: string;
9
+ datasetAlias: string;
10
+ columns: DatasetColumn[];
11
+ }
@@ -0,0 +1,7 @@
1
+ export type TidyDatum = Record<string, number | string | null | boolean | undefined>;
2
+ export type DataSourceType = 'csv' | 'json' | 'xlsx' | 'parquet';
3
+ export type DataSourceValue = string | ArrayBuffer | Blob | TidyDatum[];
4
+ export interface DataSource {
5
+ type: DataSourceType;
6
+ blob: Blob;
7
+ }
@@ -1 +1,3 @@
1
- export type { QueryResult } from './QueryResult';
1
+ export * from './DataSet';
2
+ export * from './DataSource';
3
+ export * from './QueryResult';
@@ -0,0 +1 @@
1
+ export { isUrl, isHttpUrl, isBase64Url } from './url';
@@ -0,0 +1,3 @@
1
+ export declare const isUrl: (url: string) => boolean;
2
+ export declare const isHttpUrl: (url: string) => boolean;
3
+ export declare const isBase64Url: (url: string) => boolean;
package/dist/vquery.d.ts CHANGED
@@ -1,48 +1,43 @@
1
- import { QueryResult } from './types';
1
+ import { Dataset } from './dataset/dataset';
2
+ import { DatasetSchema, TidyDatum, DataSourceType, DatasetColumn } from './types';
2
3
  export declare class VQuery {
3
4
  private duckDB;
4
5
  private indexedDB;
6
+ private isInitialized;
5
7
  constructor(dbName?: string);
8
+ private ensureInitialized;
6
9
  /**
7
- * @description 初始化数据库
10
+ * 创建数据集,根据表结构和数据,存储信息到indexedDB
8
11
  */
9
- init: () => Promise<void>;
12
+ createDataset(datasetId: string, data: string | ArrayBuffer | Blob | TidyDatum[], type: DataSourceType, datasetSchema: DatasetSchema): Promise<void>;
10
13
  /**
11
- * @description 关闭数据库
14
+ * 修改数据集,更新信息到indexedDB内
12
15
  */
13
- close: () => Promise<void>;
16
+ updateDataset(datasetId: string, data: string | ArrayBuffer | Blob | TidyDatum[], type: DataSourceType, datasetSchema: DatasetSchema): Promise<void>;
14
17
  /**
15
- * @description 注册文件
16
- * @param fileName 文件名
17
- * @param source 文件内容
18
+ * 删除数据集,从indexdb移除数据集
18
19
  */
19
- writeFile: (fileName: string, source: string | ArrayBuffer | Uint8Array | Blob) => Promise<void>;
20
+ deleteDataset(datasetId: string): Promise<void>;
20
21
  /**
21
- * @description 从 IndexedDB 读取文件并注册到 DuckDB
22
- * @param fileName 文件名
22
+ * 获取所有可用数据集
23
23
  */
24
- readFile: (fileName: string) => Promise<void>;
24
+ listDatasets(): Promise<{
25
+ datasetId: string;
26
+ dataSource: import("./types").DataSource;
27
+ datasetSchema: DatasetSchema;
28
+ }[]>;
25
29
  /**
26
- * @description 列出 IndexedDB 中的所有文件
30
+ * 连接数据集,返回数据集信息,从indexedDB获取表结构,使用DuckDB在内存中创建表
27
31
  */
28
- listFiles: () => Promise<string[]>;
32
+ connectDataset(datasetId: string): Promise<Dataset>;
29
33
  /**
30
- * @description 执行 SQL 查询
31
- * @param sql SQL 语句
34
+ * 连接临时数据集,返回数据集信息,从indexedDB获取表结构,使用DuckDB在内存中创建表
35
+ * @param datasetId
36
+ * @returns
32
37
  */
33
- query: (sql: string) => Promise<{
34
- performance: {
35
- startAt: string;
36
- endAt: string;
37
- duration: number;
38
- };
39
- dataset: any[];
40
- table: any;
41
- }>;
38
+ connectTemporaryDataset(datasetId: string, temporaryDatasetSchema?: DatasetColumn[]): Promise<Dataset>;
42
39
  /**
43
- * @description 获取文件的 Schema
44
- * @param fileName 文件名
45
- * @returns 文件的 Schema
40
+ * 关闭所有数据集连接,释放DuckDB资源
46
41
  */
47
- getSchema: (fileName: string) => Promise<QueryResult>;
42
+ close(): Promise<void>;
48
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visactor/vquery",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {