flatsql 0.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.
Files changed (65) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +489 -0
  3. package/dist/btree/btree.d.ts +34 -0
  4. package/dist/btree/btree.d.ts.map +1 -0
  5. package/dist/btree/btree.js +235 -0
  6. package/dist/btree/btree.js.map +1 -0
  7. package/dist/btree/index.d.ts +3 -0
  8. package/dist/btree/index.d.ts.map +1 -0
  9. package/dist/btree/index.js +3 -0
  10. package/dist/btree/index.js.map +1 -0
  11. package/dist/btree/types.d.ts +36 -0
  12. package/dist/btree/types.d.ts.map +1 -0
  13. package/dist/btree/types.js +55 -0
  14. package/dist/btree/types.js.map +1 -0
  15. package/dist/core/database.d.ts +45 -0
  16. package/dist/core/database.d.ts.map +1 -0
  17. package/dist/core/database.js +248 -0
  18. package/dist/core/database.js.map +1 -0
  19. package/dist/core/index.d.ts +3 -0
  20. package/dist/core/index.d.ts.map +1 -0
  21. package/dist/core/index.js +3 -0
  22. package/dist/core/index.js.map +1 -0
  23. package/dist/core/table-store.d.ts +28 -0
  24. package/dist/core/table-store.d.ts.map +1 -0
  25. package/dist/core/table-store.js +153 -0
  26. package/dist/core/table-store.js.map +1 -0
  27. package/dist/flatbuffer-accessor.d.ts +35 -0
  28. package/dist/flatbuffer-accessor.d.ts.map +1 -0
  29. package/dist/flatbuffer-accessor.js +114 -0
  30. package/dist/flatbuffer-accessor.js.map +1 -0
  31. package/dist/index.d.ts +12 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +15 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/schema/index.d.ts +3 -0
  36. package/dist/schema/index.d.ts.map +1 -0
  37. package/dist/schema/index.js +3 -0
  38. package/dist/schema/index.js.map +1 -0
  39. package/dist/schema/parser.d.ts +25 -0
  40. package/dist/schema/parser.d.ts.map +1 -0
  41. package/dist/schema/parser.js +394 -0
  42. package/dist/schema/parser.js.map +1 -0
  43. package/dist/schema/types.d.ts +33 -0
  44. package/dist/schema/types.d.ts.map +1 -0
  45. package/dist/schema/types.js +29 -0
  46. package/dist/schema/types.js.map +1 -0
  47. package/dist/storage/index.d.ts +3 -0
  48. package/dist/storage/index.d.ts.map +1 -0
  49. package/dist/storage/index.js +3 -0
  50. package/dist/storage/index.js.map +1 -0
  51. package/dist/storage/stacked-flatbuffers.d.ts +38 -0
  52. package/dist/storage/stacked-flatbuffers.d.ts.map +1 -0
  53. package/dist/storage/stacked-flatbuffers.js +253 -0
  54. package/dist/storage/stacked-flatbuffers.js.map +1 -0
  55. package/dist/storage/types.d.ts +23 -0
  56. package/dist/storage/types.d.ts.map +1 -0
  57. package/dist/storage/types.js +27 -0
  58. package/dist/storage/types.js.map +1 -0
  59. package/package.json +79 -0
  60. package/wasm/flatsql-client.js +280 -0
  61. package/wasm/flatsql.js +2 -0
  62. package/wasm/flatsql.wasm +0 -0
  63. package/wasm/flatsql.worker.js +209 -0
  64. package/wasm/index.d.ts +234 -0
  65. package/wasm/index.js +463 -0
package/wasm/index.js ADDED
@@ -0,0 +1,463 @@
1
+ // FlatSQL JavaScript API - Uses C exports (worker-compatible)
2
+ // This wrapper uses cwrap/ccall instead of embind to avoid worker issues
3
+
4
+ import FlatSQLModule from './flatsql.js';
5
+
6
+ let Module = null;
7
+ let api = null;
8
+
9
+ // Security: Track if integrity was verified
10
+ let integrityVerified = false;
11
+
12
+ /**
13
+ * Compute SHA-384 hash of data and return as base64
14
+ * Works in both Node.js and browser environments
15
+ * @param {ArrayBuffer} data
16
+ * @returns {Promise<string>}
17
+ */
18
+ async function computeSHA384(data) {
19
+ if (typeof crypto !== 'undefined' && crypto.subtle) {
20
+ // Browser or Node.js 18+
21
+ const hashBuffer = await crypto.subtle.digest('SHA-384', data);
22
+ const hashArray = new Uint8Array(hashBuffer);
23
+ // Convert to base64
24
+ if (typeof btoa !== 'undefined') {
25
+ return btoa(String.fromCharCode(...hashArray));
26
+ } else {
27
+ return Buffer.from(hashArray).toString('base64');
28
+ }
29
+ } else {
30
+ // Node.js fallback using dynamic import
31
+ const cryptoModule = await import('crypto');
32
+ const hash = cryptoModule.createHash('sha384');
33
+ hash.update(Buffer.from(data));
34
+ return hash.digest('base64');
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Verify WASM binary integrity
40
+ * @param {ArrayBuffer} wasmBinary - The WASM binary data
41
+ * @param {string} expectedHash - Expected SHA-384 hash (base64)
42
+ * @returns {Promise<boolean>}
43
+ */
44
+ async function verifyWASMIntegrity(wasmBinary, expectedHash) {
45
+ const computedHash = await computeSHA384(wasmBinary);
46
+ return computedHash === expectedHash;
47
+ }
48
+
49
+ /**
50
+ * Load integrity.json if available
51
+ * @param {string} [basePath] - Base path for integrity.json
52
+ * @returns {Promise<{hash: string, sri: string, size: number} | null>}
53
+ */
54
+ async function loadIntegrityFile(basePath = '') {
55
+ try {
56
+ if (typeof fetch !== 'undefined') {
57
+ // Browser or Node.js with fetch
58
+ const url = basePath ? `${basePath}/integrity.json` : new URL('./integrity.json', import.meta.url).href;
59
+ const response = await fetch(url);
60
+ if (response.ok) {
61
+ return await response.json();
62
+ }
63
+ } else {
64
+ // Node.js fallback
65
+ const fs = await import('fs');
66
+ const path = await import('path');
67
+ const url = await import('url');
68
+ const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
69
+ const integrityPath = path.join(__dirname, 'integrity.json');
70
+ if (fs.existsSync(integrityPath)) {
71
+ return JSON.parse(fs.readFileSync(integrityPath, 'utf8'));
72
+ }
73
+ }
74
+ } catch (e) {
75
+ // Integrity file not available - this is OK in development
76
+ }
77
+ return null;
78
+ }
79
+
80
+ /**
81
+ * @typedef {Object} InitOptions
82
+ * @property {string} [integrity] - Expected SHA-384 hash for WASM verification (base64)
83
+ * @property {string} [wasmPath] - Custom path to WASM files directory
84
+ * @property {boolean} [skipIntegrityCheck] - Skip integrity verification (not recommended for production)
85
+ * @property {boolean} [requireIntegrity] - Fail if integrity cannot be verified (default: false)
86
+ * @property {function} [moduleFactory] - Custom Emscripten module factory
87
+ */
88
+
89
+ /**
90
+ * Initialize the FlatSQL WASM module with optional integrity verification
91
+ * @param {function|InitOptions} [moduleFactoryOrOptions] - Module factory or initialization options
92
+ * @returns {Promise<FlatSQL>}
93
+ *
94
+ * @example
95
+ * // Basic usage (auto-loads integrity.json if available)
96
+ * const flatsql = await initFlatSQL();
97
+ *
98
+ * @example
99
+ * // With explicit integrity hash
100
+ * const flatsql = await initFlatSQL({
101
+ * integrity: 'base64-hash-here',
102
+ * requireIntegrity: true
103
+ * });
104
+ *
105
+ * @example
106
+ * // Skip integrity check (development only)
107
+ * const flatsql = await initFlatSQL({ skipIntegrityCheck: true });
108
+ */
109
+ export async function initFlatSQL(moduleFactoryOrOptions) {
110
+ let moduleFactory = FlatSQLModule;
111
+ let options = {};
112
+
113
+ // Parse arguments - support both legacy and new API
114
+ if (moduleFactoryOrOptions) {
115
+ if (typeof moduleFactoryOrOptions === 'function') {
116
+ // Legacy: moduleFactory passed directly
117
+ moduleFactory = moduleFactoryOrOptions;
118
+ } else if (typeof moduleFactoryOrOptions === 'object') {
119
+ // New: options object
120
+ options = moduleFactoryOrOptions;
121
+ if (options.moduleFactory) {
122
+ moduleFactory = options.moduleFactory;
123
+ }
124
+ }
125
+ }
126
+
127
+ // Determine expected integrity hash
128
+ let expectedIntegrity = options.integrity || null;
129
+
130
+ // Load integrity file if no explicit integrity provided
131
+ if (!expectedIntegrity && !options.skipIntegrityCheck) {
132
+ const integrityData = await loadIntegrityFile(options.wasmPath);
133
+ if (integrityData) {
134
+ expectedIntegrity = integrityData.hash;
135
+ }
136
+ }
137
+
138
+ // Check if integrity is required but not available
139
+ if (options.requireIntegrity && !expectedIntegrity) {
140
+ throw new Error(
141
+ 'WASM integrity verification required but no integrity hash available. ' +
142
+ 'Ensure integrity.json exists or pass integrity option.'
143
+ );
144
+ }
145
+
146
+ // Create module configuration
147
+ const moduleConfig = {};
148
+
149
+ // If we have an expected hash, use custom WASM instantiation with verification
150
+ if (expectedIntegrity && !options.skipIntegrityCheck) {
151
+ moduleConfig.instantiateWasm = async (imports, successCallback) => {
152
+ try {
153
+ // Determine WASM path
154
+ let wasmUrl;
155
+ if (options.wasmPath) {
156
+ wasmUrl = `${options.wasmPath}/flatsql.wasm`;
157
+ } else {
158
+ wasmUrl = new URL('./flatsql.wasm', import.meta.url).href;
159
+ }
160
+
161
+ // Fetch WASM binary
162
+ let wasmBinary;
163
+ if (typeof fetch !== 'undefined') {
164
+ const response = await fetch(wasmUrl);
165
+ if (!response.ok) {
166
+ throw new Error(`Failed to fetch WASM: ${response.status}`);
167
+ }
168
+ wasmBinary = await response.arrayBuffer();
169
+ } else {
170
+ // Node.js fallback
171
+ const fs = await import('fs');
172
+ const path = await import('path');
173
+ const url = await import('url');
174
+ const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
175
+ const wasmPath = path.join(__dirname, 'flatsql.wasm');
176
+ wasmBinary = fs.readFileSync(wasmPath).buffer;
177
+ }
178
+
179
+ // Verify integrity
180
+ const isValid = await verifyWASMIntegrity(wasmBinary, expectedIntegrity);
181
+ if (!isValid) {
182
+ throw new Error(
183
+ 'WASM integrity check failed: hash mismatch. ' +
184
+ 'The WASM binary may have been tampered with or corrupted.'
185
+ );
186
+ }
187
+
188
+ // Mark as verified
189
+ integrityVerified = true;
190
+
191
+ // Instantiate verified WASM
192
+ const result = await WebAssembly.instantiate(wasmBinary, imports);
193
+ successCallback(result.instance);
194
+ return result.instance.exports;
195
+ } catch (error) {
196
+ throw new Error(`WASM integrity verification failed: ${error.message}`);
197
+ }
198
+ };
199
+ }
200
+
201
+ // Initialize module
202
+ Module = await moduleFactory(moduleConfig);
203
+
204
+ // Wrap C functions using cwrap
205
+ api = {
206
+ // Database lifecycle
207
+ createDb: Module.cwrap('flatsql_create_db', 'number', ['string', 'string']),
208
+ destroyDb: Module.cwrap('flatsql_destroy_db', null, ['number']),
209
+ registerFileId: Module.cwrap('flatsql_register_file_id', null, ['number', 'string', 'string']),
210
+ enableDemoExtractors: Module.cwrap('flatsql_enable_demo_extractors', null, ['number']),
211
+
212
+ // Data ingestion
213
+ ingest: Module.cwrap('flatsql_ingest', 'number', ['number', 'number', 'number']),
214
+ ingestOne: Module.cwrap('flatsql_ingest_one', 'number', ['number', 'number', 'number']),
215
+
216
+ // Source-aware ingestion
217
+ registerSource: Module.cwrap('flatsql_register_source', null, ['number', 'string']),
218
+ createUnifiedViews: Module.cwrap('flatsql_create_unified_views', null, ['number']),
219
+ ingestWithSource: Module.cwrap('flatsql_ingest_with_source', 'number', ['number', 'number', 'number', 'string']),
220
+ ingestOneWithSource: Module.cwrap('flatsql_ingest_one_with_source', 'number', ['number', 'number', 'number', 'string']),
221
+ getSourcesCount: Module.cwrap('flatsql_get_sources_count', 'number', ['number']),
222
+ getSourceName: Module.cwrap('flatsql_get_source_name', 'string', ['number']),
223
+
224
+ // Query execution
225
+ query: Module.cwrap('flatsql_query', 'number', ['number', 'string']),
226
+ getError: Module.cwrap('flatsql_get_error', 'string', []),
227
+
228
+ // Result access
229
+ resultColumnCount: Module.cwrap('flatsql_result_column_count', 'number', []),
230
+ resultRowCount: Module.cwrap('flatsql_result_row_count', 'number', []),
231
+ resultColumnName: Module.cwrap('flatsql_result_column_name', 'string', ['number']),
232
+ resultCellType: Module.cwrap('flatsql_result_cell_type', 'number', ['number', 'number']),
233
+ resultCellNumber: Module.cwrap('flatsql_result_cell_number', 'number', ['number', 'number']),
234
+ resultCellString: Module.cwrap('flatsql_result_cell_string', 'string', ['number', 'number']),
235
+ resultCellBlob: Module.cwrap('flatsql_result_cell_blob', 'number', ['number', 'number']),
236
+ resultCellBlobSize: Module.cwrap('flatsql_result_cell_blob_size', 'number', ['number', 'number']),
237
+
238
+ // Export/Import
239
+ exportData: Module.cwrap('flatsql_export_data', 'number', ['number']),
240
+ exportSize: Module.cwrap('flatsql_export_size', 'number', []),
241
+ loadAndRebuild: Module.cwrap('flatsql_load_and_rebuild', null, ['number', 'number', 'number']),
242
+
243
+ // Test helpers
244
+ createTestUser: Module.cwrap('flatsql_create_test_user', 'number', ['number', 'string', 'string', 'number']),
245
+ createTestPost: Module.cwrap('flatsql_create_test_post', 'number', ['number', 'number', 'string']),
246
+ testBufferSize: Module.cwrap('flatsql_test_buffer_size', 'number', []),
247
+
248
+ // Stats
249
+ getStatsCount: Module.cwrap('flatsql_get_stats_count', 'number', ['number']),
250
+ getStatTableName: Module.cwrap('flatsql_get_stat_table_name', 'string', ['number']),
251
+ getStatFileId: Module.cwrap('flatsql_get_stat_file_id', 'string', ['number']),
252
+ getStatRecordCount: Module.cwrap('flatsql_get_stat_record_count', 'number', ['number']),
253
+
254
+ // Delete support
255
+ markDeleted: Module.cwrap('flatsql_mark_deleted', null, ['number', 'string', 'number']),
256
+ getDeletedCount: Module.cwrap('flatsql_get_deleted_count', 'number', ['number', 'string']),
257
+ clearTombstones: Module.cwrap('flatsql_clear_tombstones', null, ['number', 'string']),
258
+ };
259
+
260
+ return new FlatSQL();
261
+ }
262
+
263
+ /**
264
+ * Check if WASM was loaded with integrity verification
265
+ * @returns {boolean}
266
+ */
267
+ export function wasIntegrityVerified() {
268
+ return integrityVerified;
269
+ }
270
+
271
+ // High-level FlatSQL API class
272
+ export class FlatSQL {
273
+ createDatabase(schema, dbName = 'default') {
274
+ const handle = api.createDb(schema, dbName);
275
+ return new FlatSQLDatabase(handle);
276
+ }
277
+
278
+ // Create test FlatBuffers
279
+ createTestUser(id, name, email, age) {
280
+ const ptr = api.createTestUser(id, name, email, age);
281
+ const size = api.testBufferSize();
282
+ return new Uint8Array(Module.HEAPU8.buffer, ptr, size).slice();
283
+ }
284
+
285
+ createTestPost(id, userId, title) {
286
+ const ptr = api.createTestPost(id, userId, title);
287
+ const size = api.testBufferSize();
288
+ return new Uint8Array(Module.HEAPU8.buffer, ptr, size).slice();
289
+ }
290
+
291
+ /**
292
+ * Check if WASM was loaded with integrity verification
293
+ * @returns {boolean}
294
+ */
295
+ wasIntegrityVerified() {
296
+ return integrityVerified;
297
+ }
298
+ }
299
+
300
+ // Database wrapper class
301
+ export class FlatSQLDatabase {
302
+ constructor(handle) {
303
+ this._handle = handle;
304
+ }
305
+
306
+ destroy() {
307
+ if (this._handle) {
308
+ api.destroyDb(this._handle);
309
+ this._handle = null;
310
+ }
311
+ }
312
+
313
+ registerFileId(fileId, tableName) {
314
+ api.registerFileId(this._handle, fileId, tableName);
315
+ }
316
+
317
+ enableDemoExtractors() {
318
+ api.enableDemoExtractors(this._handle);
319
+ }
320
+
321
+ // Ingest data from Uint8Array (routes to base tables or source tables)
322
+ ingest(data, source = null) {
323
+ const ptr = Module._malloc(data.length);
324
+ Module.HEAPU8.set(data, ptr);
325
+ let count;
326
+ if (source) {
327
+ count = api.ingestWithSource(this._handle, ptr, data.length, source);
328
+ } else {
329
+ count = api.ingest(this._handle, ptr, data.length);
330
+ }
331
+ Module._free(ptr);
332
+ return count;
333
+ }
334
+
335
+ ingestOne(data, source = null) {
336
+ const ptr = Module._malloc(data.length);
337
+ Module.HEAPU8.set(data, ptr);
338
+ let count;
339
+ if (source) {
340
+ count = api.ingestOneWithSource(this._handle, ptr, data.length, source);
341
+ } else {
342
+ count = api.ingestOne(this._handle, ptr, data.length);
343
+ }
344
+ Module._free(ptr);
345
+ return count;
346
+ }
347
+
348
+ // Register a named data source for source-aware ingestion
349
+ // Creates source-specific tables: User@siteA, Post@siteA, etc.
350
+ registerSource(sourceName) {
351
+ api.registerSource(this._handle, sourceName);
352
+ }
353
+
354
+ // Create unified views for cross-source queries
355
+ // Must be called after registering all sources and file IDs
356
+ createUnifiedViews() {
357
+ api.createUnifiedViews(this._handle);
358
+ }
359
+
360
+ // List registered sources
361
+ listSources() {
362
+ const count = api.getSourcesCount(this._handle);
363
+ const sources = [];
364
+ for (let i = 0; i < count; i++) {
365
+ sources.push(api.getSourceName(i));
366
+ }
367
+ return sources;
368
+ }
369
+
370
+ query(sql) {
371
+ const success = api.query(this._handle, sql);
372
+ if (!success) {
373
+ throw new Error(api.getError());
374
+ }
375
+
376
+ // Read results
377
+ const colCount = api.resultColumnCount();
378
+ const rowCount = api.resultRowCount();
379
+
380
+ const columns = [];
381
+ for (let i = 0; i < colCount; i++) {
382
+ columns.push(api.resultColumnName(i));
383
+ }
384
+
385
+ const rows = [];
386
+ for (let r = 0; r < rowCount; r++) {
387
+ const row = [];
388
+ for (let c = 0; c < colCount; c++) {
389
+ const type = api.resultCellType(r, c);
390
+ switch (type) {
391
+ case 0: // null
392
+ row.push(null);
393
+ break;
394
+ case 1: // bool
395
+ row.push(api.resultCellNumber(r, c) !== 0);
396
+ break;
397
+ case 2: // int32
398
+ case 3: // int64
399
+ case 4: // double
400
+ row.push(api.resultCellNumber(r, c));
401
+ break;
402
+ case 5: // string
403
+ row.push(api.resultCellString(r, c));
404
+ break;
405
+ case 6: // blob
406
+ const blobPtr = api.resultCellBlob(r, c);
407
+ const blobSize = api.resultCellBlobSize(r, c);
408
+ if (blobPtr && blobSize > 0) {
409
+ row.push(Array.from(new Uint8Array(Module.HEAPU8.buffer, blobPtr, blobSize)));
410
+ } else {
411
+ row.push([]);
412
+ }
413
+ break;
414
+ default:
415
+ row.push(null);
416
+ }
417
+ }
418
+ rows.push(row);
419
+ }
420
+
421
+ return { columns, rows };
422
+ }
423
+
424
+ exportData() {
425
+ const ptr = api.exportData(this._handle);
426
+ const size = api.exportSize();
427
+ return new Uint8Array(Module.HEAPU8.buffer, ptr, size).slice();
428
+ }
429
+
430
+ loadAndRebuild(data) {
431
+ const ptr = Module._malloc(data.length);
432
+ Module.HEAPU8.set(data, ptr);
433
+ api.loadAndRebuild(this._handle, ptr, data.length);
434
+ Module._free(ptr);
435
+ }
436
+
437
+ getStats() {
438
+ const count = api.getStatsCount(this._handle);
439
+ const stats = [];
440
+ for (let i = 0; i < count; i++) {
441
+ stats.push({
442
+ tableName: api.getStatTableName(i),
443
+ fileId: api.getStatFileId(i),
444
+ recordCount: api.getStatRecordCount(i)
445
+ });
446
+ }
447
+ return stats;
448
+ }
449
+
450
+ markDeleted(tableName, sequence) {
451
+ api.markDeleted(this._handle, tableName, sequence);
452
+ }
453
+
454
+ getDeletedCount(tableName) {
455
+ return api.getDeletedCount(this._handle, tableName);
456
+ }
457
+
458
+ clearTombstones(tableName) {
459
+ api.clearTombstones(this._handle, tableName);
460
+ }
461
+ }
462
+
463
+ export default initFlatSQL;