flatsql 0.4.2 → 1.0.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 (143) hide show
  1. package/README.md +116 -6
  2. package/dist/artifacts/builder.d.ts +31 -0
  3. package/dist/artifacts/builder.d.ts.map +1 -0
  4. package/dist/artifacts/builder.js +514 -0
  5. package/dist/artifacts/builder.js.map +1 -0
  6. package/dist/artifacts/demo-extractors.d.ts +15 -0
  7. package/dist/artifacts/demo-extractors.d.ts.map +1 -0
  8. package/dist/artifacts/demo-extractors.js +238 -0
  9. package/dist/artifacts/demo-extractors.js.map +1 -0
  10. package/dist/artifacts/index.d.ts +5 -0
  11. package/dist/artifacts/index.d.ts.map +1 -0
  12. package/dist/artifacts/index.js +5 -0
  13. package/dist/artifacts/index.js.map +1 -0
  14. package/dist/artifacts/standalone-builder.d.ts +62 -0
  15. package/dist/artifacts/standalone-builder.d.ts.map +1 -0
  16. package/dist/artifacts/standalone-builder.js +144 -0
  17. package/dist/artifacts/standalone-builder.js.map +1 -0
  18. package/dist/artifacts/transport.d.ts +5 -0
  19. package/dist/artifacts/transport.d.ts.map +1 -0
  20. package/dist/artifacts/transport.js +45 -0
  21. package/dist/artifacts/transport.js.map +1 -0
  22. package/dist/artifacts/types.d.ts +34 -0
  23. package/dist/artifacts/types.d.ts.map +1 -0
  24. package/dist/artifacts/types.js +2 -0
  25. package/dist/artifacts/types.js.map +1 -0
  26. package/dist/artifacts/worker-client.d.ts +32 -0
  27. package/dist/artifacts/worker-client.d.ts.map +1 -0
  28. package/dist/artifacts/worker-client.js +415 -0
  29. package/dist/artifacts/worker-client.js.map +1 -0
  30. package/dist/btree/btree.d.ts.map +1 -1
  31. package/dist/btree/btree.js +13 -14
  32. package/dist/btree/btree.js.map +1 -1
  33. package/dist/core/database.d.ts +2 -0
  34. package/dist/core/database.d.ts.map +1 -1
  35. package/dist/core/database.js +18 -0
  36. package/dist/core/database.js.map +1 -1
  37. package/dist/core/table-store.d.ts +1 -0
  38. package/dist/core/table-store.d.ts.map +1 -1
  39. package/dist/core/table-store.js +31 -0
  40. package/dist/core/table-store.js.map +1 -1
  41. package/dist/index.d.ts +3 -0
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +3 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/response/artifact.d.ts +5 -0
  46. package/dist/response/artifact.d.ts.map +1 -0
  47. package/dist/response/artifact.js +102 -0
  48. package/dist/response/artifact.js.map +1 -0
  49. package/dist/response/cache-key.d.ts +6 -0
  50. package/dist/response/cache-key.d.ts.map +1 -0
  51. package/dist/response/cache-key.js +114 -0
  52. package/dist/response/cache-key.js.map +1 -0
  53. package/dist/response/index.d.ts +5 -0
  54. package/dist/response/index.d.ts.map +1 -0
  55. package/dist/response/index.js +5 -0
  56. package/dist/response/index.js.map +1 -0
  57. package/dist/response/memory-cache.d.ts +17 -0
  58. package/dist/response/memory-cache.d.ts.map +1 -0
  59. package/dist/response/memory-cache.js +76 -0
  60. package/dist/response/memory-cache.js.map +1 -0
  61. package/dist/response/types.d.ts +51 -0
  62. package/dist/response/types.d.ts.map +1 -0
  63. package/dist/response/types.js +2 -0
  64. package/dist/response/types.js.map +1 -0
  65. package/dist/standalone/index.d.ts +3 -0
  66. package/dist/standalone/index.d.ts.map +1 -0
  67. package/dist/standalone/index.js +3 -0
  68. package/dist/standalone/index.js.map +1 -0
  69. package/dist/standalone/process-client.d.ts +85 -0
  70. package/dist/standalone/process-client.d.ts.map +1 -0
  71. package/dist/standalone/process-client.js +537 -0
  72. package/dist/standalone/process-client.js.map +1 -0
  73. package/dist/standalone/wasmedge-runner.d.ts +19 -0
  74. package/dist/standalone/wasmedge-runner.d.ts.map +1 -0
  75. package/dist/standalone/wasmedge-runner.js +108 -0
  76. package/dist/standalone/wasmedge-runner.js.map +1 -0
  77. package/dist/stress/aggregate.d.ts +3 -0
  78. package/dist/stress/aggregate.d.ts.map +1 -0
  79. package/dist/stress/aggregate.js +61 -0
  80. package/dist/stress/aggregate.js.map +1 -0
  81. package/dist/stress/docker-compose.d.ts +17 -0
  82. package/dist/stress/docker-compose.d.ts.map +1 -0
  83. package/dist/stress/docker-compose.js +156 -0
  84. package/dist/stress/docker-compose.js.map +1 -0
  85. package/dist/stress/index.d.ts +11 -0
  86. package/dist/stress/index.d.ts.map +1 -0
  87. package/dist/stress/index.js +11 -0
  88. package/dist/stress/index.js.map +1 -0
  89. package/dist/stress/metrics.d.ts +3 -0
  90. package/dist/stress/metrics.d.ts.map +1 -0
  91. package/dist/stress/metrics.js +83 -0
  92. package/dist/stress/metrics.js.map +1 -0
  93. package/dist/stress/native-publish-event-workload.d.ts +3 -0
  94. package/dist/stress/native-publish-event-workload.d.ts.map +1 -0
  95. package/dist/stress/native-publish-event-workload.js +455 -0
  96. package/dist/stress/native-publish-event-workload.js.map +1 -0
  97. package/dist/stress/production-gate.d.ts +4 -0
  98. package/dist/stress/production-gate.d.ts.map +1 -0
  99. package/dist/stress/production-gate.js +12 -0
  100. package/dist/stress/production-gate.js.map +1 -0
  101. package/dist/stress/publish-event-workload.d.ts +3 -0
  102. package/dist/stress/publish-event-workload.d.ts.map +1 -0
  103. package/dist/stress/publish-event-workload.js +467 -0
  104. package/dist/stress/publish-event-workload.js.map +1 -0
  105. package/dist/stress/report.d.ts +3 -0
  106. package/dist/stress/report.d.ts.map +1 -0
  107. package/dist/stress/report.js +174 -0
  108. package/dist/stress/report.js.map +1 -0
  109. package/dist/stress/sds-discovery.d.ts +4 -0
  110. package/dist/stress/sds-discovery.d.ts.map +1 -0
  111. package/dist/stress/sds-discovery.js +40 -0
  112. package/dist/stress/sds-discovery.js.map +1 -0
  113. package/dist/stress/smoke-runner.d.ts +3 -0
  114. package/dist/stress/smoke-runner.d.ts.map +1 -0
  115. package/dist/stress/smoke-runner.js +63 -0
  116. package/dist/stress/smoke-runner.js.map +1 -0
  117. package/dist/stress/types.d.ts +219 -0
  118. package/dist/stress/types.d.ts.map +1 -0
  119. package/dist/stress/types.js +2 -0
  120. package/dist/stress/types.js.map +1 -0
  121. package/dist/stress/workload.d.ts +6 -0
  122. package/dist/stress/workload.d.ts.map +1 -0
  123. package/dist/stress/workload.js +144 -0
  124. package/dist/stress/workload.js.map +1 -0
  125. package/docs/agents/response-artifacts.md +65 -0
  126. package/docs/response-artifacts.md +86 -0
  127. package/package.json +37 -7
  128. package/scripts/stress-docker.sh +279 -0
  129. package/sdm/flatsql-spatial.wasm +0 -0
  130. package/stress/docker/Dockerfile +11 -0
  131. package/stress/docker/docker-compose.yml +51 -0
  132. package/stress/sds/aggregate.mjs +97 -0
  133. package/stress/sds/run.mjs +105 -0
  134. package/stress/sds/write-compose.mjs +96 -0
  135. package/wasm/flatsql-artifact.worker.js +847 -0
  136. package/wasm/flatsql-wasi.wasm +0 -0
  137. package/wasm/flatsql.js +2 -16
  138. package/wasm/flatsql.wasm +0 -0
  139. package/wasm/index.d.ts +127 -2
  140. package/wasm/index.js +459 -61
  141. package/wasm/native/flatsql_wasmedge_runner.cpp +858 -0
  142. package/wasm/standalone.d.ts +91 -0
  143. package/wasm/standalone.js +740 -0
package/wasm/index.js CHANGED
@@ -5,34 +5,72 @@ import FlatSQLModule from './flatsql.js';
5
5
 
6
6
  let Module = null;
7
7
  let api = null;
8
+ const textEncoder = new TextEncoder();
9
+
10
+ const PARAM_NULL = 0;
11
+ const PARAM_BOOL = 1;
12
+ const PARAM_INT64 = 2;
13
+ const PARAM_FLOAT64 = 3;
14
+ const PARAM_STRING = 4;
15
+ const PARAM_BYTES = 5;
8
16
 
9
17
  // Security: Track if integrity was verified
10
18
  let integrityVerified = false;
11
19
 
20
+ function toBase64(bytes) {
21
+ if (typeof Buffer !== 'undefined') {
22
+ return Buffer.from(bytes).toString('base64');
23
+ }
24
+ let binary = '';
25
+ for (let offset = 0; offset < bytes.length; offset += 0x8000) {
26
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));
27
+ }
28
+ return btoa(binary);
29
+ }
30
+
31
+ function normalizeHashResult(value) {
32
+ if (typeof value === 'string') {
33
+ return value;
34
+ }
35
+ if (value instanceof Uint8Array) {
36
+ return toBase64(value);
37
+ }
38
+ if (ArrayBuffer.isView(value)) {
39
+ return toBase64(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
40
+ }
41
+ if (value instanceof ArrayBuffer) {
42
+ return toBase64(new Uint8Array(value));
43
+ }
44
+ throw new Error('SHA-384 provider must return a base64 string or binary digest');
45
+ }
46
+
47
+ function hasNodeProcess() {
48
+ return typeof process !== 'undefined' &&
49
+ process.versions !== undefined &&
50
+ typeof process.versions.node === 'string';
51
+ }
52
+
12
53
  /**
13
- * Compute SHA-384 hash of data and return as base64
14
- * Works in both Node.js and browser environments
54
+ * Compute SHA-384 hash of data and return as base64.
55
+ * Browser WebCrypto is intentionally not used; browser callers that require
56
+ * integrity verification must pass computeSHA384 backed by WASM/native crypto.
15
57
  * @param {ArrayBuffer} data
58
+ * @param {(data: ArrayBuffer) => Promise<string|Uint8Array|ArrayBuffer>|string|Uint8Array|ArrayBuffer} [provider]
16
59
  * @returns {Promise<string>}
17
60
  */
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');
61
+ async function computeSHA384(data, provider) {
62
+ if (typeof provider === 'function') {
63
+ return normalizeHashResult(await provider(data));
64
+ }
65
+ if (hasNodeProcess()) {
66
+ const cryptoModule = await import('node:crypto');
32
67
  const hash = cryptoModule.createHash('sha384');
33
68
  hash.update(Buffer.from(data));
34
69
  return hash.digest('base64');
35
70
  }
71
+ throw new Error(
72
+ 'WASM integrity verification requires a computeSHA384 option backed by WASM/native crypto; browser WebCrypto is intentionally not used.'
73
+ );
36
74
  }
37
75
 
38
76
  /**
@@ -41,8 +79,8 @@ async function computeSHA384(data) {
41
79
  * @param {string} expectedHash - Expected SHA-384 hash (base64)
42
80
  * @returns {Promise<boolean>}
43
81
  */
44
- async function verifyWASMIntegrity(wasmBinary, expectedHash) {
45
- const computedHash = await computeSHA384(wasmBinary);
82
+ async function verifyWASMIntegrity(wasmBinary, expectedHash, options = {}) {
83
+ const computedHash = await computeSHA384(wasmBinary, options.computeSHA384);
46
84
  return computedHash === expectedHash;
47
85
  }
48
86
 
@@ -177,7 +215,7 @@ export async function initFlatSQL(moduleFactoryOrOptions) {
177
215
  }
178
216
 
179
217
  // Verify integrity
180
- const isValid = await verifyWASMIntegrity(wasmBinary, expectedIntegrity);
218
+ const isValid = await verifyWASMIntegrity(wasmBinary, expectedIntegrity, options);
181
219
  if (!isValid) {
182
220
  throw new Error(
183
221
  'WASM integrity check failed: hash mismatch. ' +
@@ -223,6 +261,22 @@ export async function initFlatSQL(moduleFactoryOrOptions) {
223
261
 
224
262
  // Query execution
225
263
  query: Module.cwrap('flatsql_query', 'number', ['number', 'string']),
264
+ queryParams: Module.cwrap('flatsql_query_params', 'number', ['number', 'string', 'number', 'number', 'number']),
265
+ queryMany: Module.cwrap('flatsql_query_many', 'number', ['number', 'number', 'number', 'number']),
266
+ buildQueryCacheKey: Module.cwrap('flatsql_build_query_cache_key', 'string', ['string', 'string', 'string', 'number', 'number', 'number']),
267
+ buildResponseArtifactCacheKey: Module.cwrap('flatsql_build_response_artifact_cache_key', 'string', ['string', 'string', 'string', 'string', 'string', 'string', 'number', 'number', 'number']),
268
+ registerQueryTemplate: Module.cwrap('flatsql_register_query_template', 'number', ['number', 'string', 'string', 'number']),
269
+ queryTemplate: Module.cwrap('flatsql_query_template', 'number', ['number', 'string', 'number', 'number', 'number']),
270
+ clearQueryCache: Module.cwrap('flatsql_clear_query_cache', null, ['number']),
271
+ configureQueryCache: Module.cwrap('flatsql_configure_query_cache', 'number', ['number', 'number', 'number']),
272
+ queryCacheHits: Module.cwrap('flatsql_query_cache_hits', 'number', ['number']),
273
+ queryCacheMisses: Module.cwrap('flatsql_query_cache_misses', 'number', ['number']),
274
+ queryCacheSize: Module.cwrap('flatsql_query_cache_size', 'number', ['number']),
275
+ queryCacheGeneration: Module.cwrap('flatsql_query_cache_generation', 'number', ['number']),
276
+ queryCacheMaxEntries: Module.cwrap('flatsql_query_cache_max_entries', 'number', ['number']),
277
+ queryCacheMaxRows: Module.cwrap('flatsql_query_cache_max_rows', 'number', ['number']),
278
+ batchResultCount: Module.cwrap('flatsql_batch_result_count', 'number', []),
279
+ selectBatchResult: Module.cwrap('flatsql_select_batch_result', 'number', ['number']),
226
280
  getError: Module.cwrap('flatsql_get_error', 'string', []),
227
281
 
228
282
  // Result access
@@ -234,17 +288,25 @@ export async function initFlatSQL(moduleFactoryOrOptions) {
234
288
  resultCellString: Module.cwrap('flatsql_result_cell_string', 'string', ['number', 'number']),
235
289
  resultCellBlob: Module.cwrap('flatsql_result_cell_blob', 'number', ['number', 'number']),
236
290
  resultCellBlobSize: Module.cwrap('flatsql_result_cell_blob_size', 'number', ['number', 'number']),
291
+ queryRawFlatBufferStream: Module.cwrap('flatsql_query_raw_flatbuffer_stream', 'number', ['number', 'string', 'number', 'number', 'number']),
292
+ responseArtifactData: Module.cwrap('flatsql_response_artifact_data', 'number', []),
293
+ responseArtifactSize: Module.cwrap('flatsql_response_artifact_size', 'number', []),
294
+ responseArtifactRowCount: Module.cwrap('flatsql_response_artifact_row_count', 'number', []),
295
+ responseArtifactColumnCount: Module.cwrap('flatsql_response_artifact_column_count', 'number', []),
237
296
 
238
297
  // Export/Import
239
298
  exportData: Module.cwrap('flatsql_export_data', 'number', ['number']),
240
299
  exportSize: Module.cwrap('flatsql_export_size', 'number', []),
241
300
  loadAndRebuild: Module.cwrap('flatsql_load_and_rebuild', null, ['number', 'number', 'number']),
301
+ reserveStorage: Module.cwrap('flatsql_reserve_storage', null, ['number', 'number']),
302
+ loadFromDb: Module.cwrap('flatsql_load_from_db', null, ['number', 'number']),
242
303
 
243
304
  // Test helpers
244
305
  createTestUser: Module.cwrap('flatsql_create_test_user', 'number', ['number', 'string', 'string', 'number']),
245
306
  createTestPost: Module.cwrap('flatsql_create_test_post', 'number', ['number', 'number', 'string']),
246
307
  createTestMPE: Module.cwrap('flatsql_create_test_mpe', 'number', ['string', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number']),
247
308
  createTestTelemetry: Module.cwrap('flatsql_create_test_telemetry', 'number', ['number', 'string', 'string', 'string', 'number', 'number', 'number']),
309
+ createTestPublishEvent: Module.cwrap('flatsql_create_test_publish_event', 'number', ['string', 'string', 'number', 'number']),
248
310
  testBufferSize: Module.cwrap('flatsql_test_buffer_size', 'number', []),
249
311
 
250
312
  // Stats
@@ -264,6 +326,15 @@ export async function initFlatSQL(moduleFactoryOrOptions) {
264
326
  getDeletedCount: Module.cwrap('flatsql_get_deleted_count', 'number', ['number', 'string']),
265
327
  clearTombstones: Module.cwrap('flatsql_clear_tombstones', null, ['number', 'string']),
266
328
 
329
+ // Raw FlatBuffer access
330
+ getFlatBufferById: Module.cwrap('flatsql_get_flatbuffer_by_id', 'number', ['number', 'string', 'number']),
331
+ getFlatBufferByEmail: Module.cwrap('flatsql_get_flatbuffer_by_email', 'number', ['number', 'string', 'string']),
332
+ getFlatBufferByIndex: Module.cwrap('flatsql_get_flatbuffer_by_index', 'number', ['number', 'string', 'string', 'number', 'number', 'number']),
333
+ getRawFlatBufferSize: Module.cwrap('flatsql_get_raw_flatbuffer_size', 'number', []),
334
+ getRawFlatBufferSequence: Module.cwrap('flatsql_get_raw_flatbuffer_sequence', 'number', []),
335
+ getStorageBuffer: Module.cwrap('flatsql_get_storage_buffer', 'number', ['number']),
336
+ getStorageSize: Module.cwrap('flatsql_get_storage_size', 'number', ['number']),
337
+
267
338
  // Encryption
268
339
  setEncryptionKey: Module.cwrap('flatsql_set_encryption_key', 'number', ['number', 'number', 'number']),
269
340
  isEncrypted: Module.cwrap('flatsql_is_encrypted', 'number', ['number']),
@@ -298,6 +369,134 @@ function withHeapBytes(data, callback) {
298
369
  }
299
370
  }
300
371
 
372
+ function encodeQueryParams(params) {
373
+ const parts = [];
374
+ let total = 0;
375
+
376
+ for (const value of params) {
377
+ let tag;
378
+ let payload;
379
+
380
+ if (value === null) {
381
+ tag = PARAM_NULL;
382
+ payload = new Uint8Array(0);
383
+ } else if (typeof value === 'boolean') {
384
+ tag = PARAM_BOOL;
385
+ payload = Uint8Array.of(value ? 1 : 0);
386
+ } else if (typeof value === 'number' && Number.isInteger(value)) {
387
+ if (!Number.isSafeInteger(value)) {
388
+ throw new TypeError('Integer query parameters must be safe integers');
389
+ }
390
+ tag = PARAM_INT64;
391
+ payload = new Uint8Array(8);
392
+ new DataView(payload.buffer, payload.byteOffset, payload.byteLength).setBigInt64(0, BigInt(value), true);
393
+ } else if (typeof value === 'number') {
394
+ tag = PARAM_FLOAT64;
395
+ payload = new Uint8Array(8);
396
+ new DataView(payload.buffer, payload.byteOffset, payload.byteLength).setFloat64(0, value, true);
397
+ } else if (typeof value === 'string') {
398
+ tag = PARAM_STRING;
399
+ payload = textEncoder.encode(value);
400
+ } else if (value instanceof Uint8Array) {
401
+ tag = PARAM_BYTES;
402
+ payload = value;
403
+ } else {
404
+ throw new TypeError(`Unsupported query parameter type: ${typeof value}`);
405
+ }
406
+
407
+ const header = new Uint8Array(5);
408
+ const view = new DataView(header.buffer);
409
+ view.setUint8(0, tag);
410
+ view.setUint32(1, payload.length, true);
411
+
412
+ parts.push(header, payload);
413
+ total += header.length + payload.length;
414
+ }
415
+
416
+ const encoded = new Uint8Array(total);
417
+ let offset = 0;
418
+ for (const part of parts) {
419
+ encoded.set(part, offset);
420
+ offset += part.length;
421
+ }
422
+
423
+ return encoded;
424
+ }
425
+
426
+ function encodeQueryRequests(queries) {
427
+ const parts = [];
428
+ let total = 0;
429
+
430
+ for (const query of queries) {
431
+ const sqlBytes = textEncoder.encode(query.sql);
432
+ const paramList = query.params ?? [];
433
+ const paramBytes = encodeQueryParams(paramList);
434
+ const header = new Uint8Array(12);
435
+ const view = new DataView(header.buffer);
436
+ view.setUint32(0, sqlBytes.length, true);
437
+ view.setUint32(4, paramList.length, true);
438
+ view.setUint32(8, paramBytes.length, true);
439
+
440
+ parts.push(header, sqlBytes, paramBytes);
441
+ total += header.length + sqlBytes.length + paramBytes.length;
442
+ }
443
+
444
+ const encoded = new Uint8Array(total);
445
+ let offset = 0;
446
+ for (const part of parts) {
447
+ encoded.set(part, offset);
448
+ offset += part.length;
449
+ }
450
+
451
+ return encoded;
452
+ }
453
+
454
+ function readCellValue(row, col) {
455
+ const type = api.resultCellType(row, col);
456
+ switch (type) {
457
+ case 0:
458
+ return null;
459
+ case 1:
460
+ return api.resultCellNumber(row, col) !== 0;
461
+ case 2:
462
+ case 3:
463
+ case 4:
464
+ return api.resultCellNumber(row, col);
465
+ case 5:
466
+ return api.resultCellString(row, col);
467
+ case 6: {
468
+ const blobPtr = api.resultCellBlob(row, col);
469
+ const blobSize = api.resultCellBlobSize(row, col);
470
+ return blobPtr && blobSize > 0
471
+ ? Array.from(new Uint8Array(Module.HEAPU8.buffer, blobPtr, blobSize))
472
+ : [];
473
+ }
474
+ default:
475
+ return null;
476
+ }
477
+ }
478
+
479
+ function readQueryResult() {
480
+ const colCount = api.resultColumnCount();
481
+ const rowCount = api.resultRowCount();
482
+
483
+ const columns = [];
484
+ for (let i = 0; i < colCount; i++) {
485
+ columns.push(api.resultColumnName(i));
486
+ }
487
+
488
+ const rows = [];
489
+ for (let r = 0; r < rowCount; r++) {
490
+ const row = [];
491
+ for (let c = 0; c < colCount; c++) {
492
+ row.push(readCellValue(r, c));
493
+ }
494
+ rows.push(row);
495
+ }
496
+
497
+ return { columns, rows };
498
+ }
499
+
301
500
  function buildSizePrefixedStream(buffers) {
302
501
  let total = 0;
303
502
  for (const buffer of buffers) {
@@ -335,6 +534,12 @@ export class FlatSQL {
335
534
  return new Uint8Array(Module.HEAPU8.buffer, ptr, size).slice();
336
535
  }
337
536
 
537
+ createTestPublishEvent(fileId, recordId, eventIndex, payloadSize) {
538
+ const ptr = api.createTestPublishEvent(fileId, recordId, eventIndex, payloadSize);
539
+ const size = api.testBufferSize();
540
+ return new Uint8Array(Module.HEAPU8.buffer, ptr, size).slice();
541
+ }
542
+
338
543
  createTestMPE(entityId, epoch, meanMotion, eccentricity, inclination,
339
544
  raOfAscNode, argOfPericenter, meanAnomaly, bstar, meanElementTheory = 0) {
340
545
  const ptr = api.createTestMPE(
@@ -366,6 +571,58 @@ export class FlatSQL {
366
571
  wasIntegrityVerified() {
367
572
  return integrityVerified;
368
573
  }
574
+
575
+ buildQueryCacheKey(dataset, artifactVersion, queryId, params = []) {
576
+ const encodedParams = encodeQueryParams(params);
577
+ const key = encodedParams.length === 0
578
+ ? api.buildQueryCacheKey(dataset, artifactVersion, queryId, 0, 0, 0)
579
+ : withHeapBytes(
580
+ encodedParams,
581
+ (ptr) => api.buildQueryCacheKey(dataset, artifactVersion, queryId, ptr, encodedParams.length, params.length)
582
+ );
583
+ if (!key) {
584
+ throw new Error(api.getError());
585
+ }
586
+ return key;
587
+ }
588
+
589
+ buildResponseArtifactCacheKey(schemaName, schemaVersion, sql, options = {}) {
590
+ const format = options.format ?? 'json';
591
+ const publishEventKey = options.publishEventKey ?? '';
592
+ const projectionList = (options.projection ?? []).join('\n');
593
+ const params = options.params ?? [];
594
+ const encodedParams = encodeQueryParams(params);
595
+ const key = encodedParams.length === 0
596
+ ? api.buildResponseArtifactCacheKey(
597
+ schemaName,
598
+ schemaVersion,
599
+ sql,
600
+ format,
601
+ publishEventKey,
602
+ projectionList,
603
+ 0,
604
+ 0,
605
+ 0
606
+ )
607
+ : withHeapBytes(
608
+ encodedParams,
609
+ (ptr) => api.buildResponseArtifactCacheKey(
610
+ schemaName,
611
+ schemaVersion,
612
+ sql,
613
+ format,
614
+ publishEventKey,
615
+ projectionList,
616
+ ptr,
617
+ encodedParams.length,
618
+ params.length
619
+ )
620
+ );
621
+ if (!key) {
622
+ throw new Error(api.getError());
623
+ }
624
+ return key;
625
+ }
369
626
  }
370
627
 
371
628
  // Database wrapper class
@@ -435,58 +692,114 @@ export class FlatSQLDatabase {
435
692
  return sources;
436
693
  }
437
694
 
438
- query(sql) {
439
- const success = api.query(this._handle, sql);
695
+ query(sql, params = undefined) {
696
+ const success = params === undefined
697
+ ? api.query(this._handle, sql)
698
+ : (() => {
699
+ const encodedParams = encodeQueryParams(params);
700
+ if (encodedParams.length === 0) {
701
+ return api.queryParams(this._handle, sql, 0, 0, 0);
702
+ }
703
+ return withHeapBytes(
704
+ encodedParams,
705
+ (ptr) => api.queryParams(this._handle, sql, ptr, encodedParams.length, params.length)
706
+ );
707
+ })();
440
708
  if (!success) {
441
709
  throw new Error(api.getError());
442
710
  }
711
+ return readQueryResult();
712
+ }
443
713
 
444
- // Read results
445
- const colCount = api.resultColumnCount();
446
- const rowCount = api.resultRowCount();
714
+ registerQueryTemplate(queryId, sql, cacheable = true) {
715
+ const success = api.registerQueryTemplate(this._handle, queryId, sql, cacheable ? 1 : 0);
716
+ if (!success) {
717
+ throw new Error(api.getError());
718
+ }
719
+ }
447
720
 
448
- const columns = [];
449
- for (let i = 0; i < colCount; i++) {
450
- columns.push(api.resultColumnName(i));
721
+ queryTemplate(queryId, params = []) {
722
+ const encodedParams = encodeQueryParams(params);
723
+ const success = encodedParams.length === 0
724
+ ? api.queryTemplate(this._handle, queryId, 0, 0, 0)
725
+ : withHeapBytes(
726
+ encodedParams,
727
+ (ptr) => api.queryTemplate(this._handle, queryId, ptr, encodedParams.length, params.length)
728
+ );
729
+ if (!success) {
730
+ throw new Error(api.getError());
451
731
  }
732
+ return readQueryResult();
733
+ }
452
734
 
453
- const rows = [];
454
- for (let r = 0; r < rowCount; r++) {
455
- const row = [];
456
- for (let c = 0; c < colCount; c++) {
457
- const type = api.resultCellType(r, c);
458
- switch (type) {
459
- case 0: // null
460
- row.push(null);
461
- break;
462
- case 1: // bool
463
- row.push(api.resultCellNumber(r, c) !== 0);
464
- break;
465
- case 2: // int32
466
- case 3: // int64
467
- case 4: // double
468
- row.push(api.resultCellNumber(r, c));
469
- break;
470
- case 5: // string
471
- row.push(api.resultCellString(r, c));
472
- break;
473
- case 6: // blob
474
- const blobPtr = api.resultCellBlob(r, c);
475
- const blobSize = api.resultCellBlobSize(r, c);
476
- if (blobPtr && blobSize > 0) {
477
- row.push(Array.from(new Uint8Array(Module.HEAPU8.buffer, blobPtr, blobSize)));
478
- } else {
479
- row.push([]);
480
- }
481
- break;
482
- default:
483
- row.push(null);
484
- }
735
+ clearQueryCache() {
736
+ api.clearQueryCache(this._handle);
737
+ }
738
+
739
+ configureQueryCache({ maxEntries, maxRows }) {
740
+ if (!Number.isSafeInteger(maxEntries) || maxEntries <= 0) {
741
+ throw new TypeError(`maxEntries must be a positive safe integer, received: ${maxEntries}`);
742
+ }
743
+ if (!Number.isSafeInteger(maxRows) || maxRows <= 0) {
744
+ throw new TypeError(`maxRows must be a positive safe integer, received: ${maxRows}`);
745
+ }
746
+ const success = api.configureQueryCache(this._handle, maxEntries, maxRows);
747
+ if (!success) {
748
+ throw new Error(api.getError());
749
+ }
750
+ }
751
+
752
+ getQueryCacheStats() {
753
+ return {
754
+ hits: api.queryCacheHits(this._handle),
755
+ misses: api.queryCacheMisses(this._handle),
756
+ size: api.queryCacheSize(this._handle),
757
+ generation: api.queryCacheGeneration(this._handle),
758
+ maxEntries: api.queryCacheMaxEntries(this._handle),
759
+ maxRows: api.queryCacheMaxRows(this._handle)
760
+ };
761
+ }
762
+
763
+ queryMany(queries) {
764
+ if (queries.length === 0) {
765
+ return [];
766
+ }
767
+
768
+ const encodedRequests = encodeQueryRequests(queries);
769
+ const success = withHeapBytes(
770
+ encodedRequests,
771
+ (ptr) => api.queryMany(this._handle, ptr, encodedRequests.length, queries.length)
772
+ );
773
+ if (!success) {
774
+ throw new Error(api.getError());
775
+ }
776
+
777
+ const resultCount = api.batchResultCount();
778
+ const results = [];
779
+ for (let index = 0; index < resultCount; index++) {
780
+ if (!api.selectBatchResult(index)) {
781
+ throw new Error(`Failed to select batch result ${index}`);
485
782
  }
486
- rows.push(row);
783
+ results.push(readQueryResult());
784
+ }
785
+ return results;
786
+ }
787
+
788
+ queryRawFlatBufferStream(sql, params = []) {
789
+ const encodedParams = encodeQueryParams(params);
790
+ const success = encodedParams.length === 0
791
+ ? api.queryRawFlatBufferStream(this._handle, sql, 0, 0, 0)
792
+ : withHeapBytes(
793
+ encodedParams,
794
+ (ptr) => api.queryRawFlatBufferStream(this._handle, sql, ptr, encodedParams.length, params.length)
795
+ );
796
+ if (!success) {
797
+ throw new Error(api.getError());
487
798
  }
488
799
 
489
- return { columns, rows };
800
+ const ptr = api.responseArtifactData();
801
+ const size = api.responseArtifactSize();
802
+ return ptr && size > 0 ? new Uint8Array(Module.HEAPU8.buffer, ptr, size).slice() : new Uint8Array();
490
803
  }
491
804
 
492
805
  exportData() {
@@ -502,6 +815,17 @@ export class FlatSQLDatabase {
502
815
  Module._free(ptr);
503
816
  }
504
817
 
818
+ reserveStorageBytes(bytes) {
819
+ api.reserveStorage(this._handle, bytes);
820
+ }
821
+
822
+ loadAndRebuildFrom(sourceDb) {
823
+ if (!sourceDb?._handle) {
824
+ throw new TypeError('loadAndRebuildFrom requires a FlatSQL WASM database.');
825
+ }
826
+ api.loadFromDb(this._handle, sourceDb._handle);
827
+ }
828
+
505
829
  getStats() {
506
830
  const count = api.getStatsCount(this._handle);
507
831
  const stats = [];
@@ -541,6 +865,80 @@ export class FlatSQLDatabase {
541
865
  api.clearTombstones(this._handle, tableName);
542
866
  }
543
867
 
868
+ getFlatBufferById(tableName, id) {
869
+ const ptr = api.getFlatBufferById(this._handle, tableName, id);
870
+ if (!ptr) {
871
+ return null;
872
+ }
873
+ return {
874
+ ptr,
875
+ size: api.getRawFlatBufferSize(),
876
+ sequence: api.getRawFlatBufferSequence()
877
+ };
878
+ }
879
+
880
+ getFlatBufferByEmail(tableName, email) {
881
+ const ptr = api.getFlatBufferByEmail(this._handle, tableName, email);
882
+ if (!ptr) {
883
+ return null;
884
+ }
885
+ return {
886
+ ptr,
887
+ size: api.getRawFlatBufferSize(),
888
+ sequence: api.getRawFlatBufferSequence()
889
+ };
890
+ }
891
+
892
+ getFlatBufferByIndex(tableName, columnName, value) {
893
+ const encodedParams = encodeQueryParams([value]);
894
+ const ptr = withHeapBytes(
895
+ encodedParams,
896
+ (paramPtr) => api.getFlatBufferByIndex(
897
+ this._handle,
898
+ tableName,
899
+ columnName,
900
+ paramPtr,
901
+ encodedParams.length,
902
+ 1
903
+ )
904
+ );
905
+ if (!ptr) {
906
+ const error = api.getError();
907
+ if (error) {
908
+ throw new Error(error);
909
+ }
910
+ return null;
911
+ }
912
+ return {
913
+ ptr,
914
+ size: api.getRawFlatBufferSize(),
915
+ sequence: api.getRawFlatBufferSequence()
916
+ };
917
+ }
918
+
919
+ getFlatBufferDataById(tableName, id) {
920
+ const record = this.getFlatBufferById(tableName, id);
921
+ if (!record) {
922
+ return null;
923
+ }
924
+ return new Uint8Array(Module.HEAPU8.buffer, record.ptr, record.size).slice();
925
+ }
926
+
927
+ getFlatBufferDataByIndex(tableName, columnName, value) {
928
+ const record = this.getFlatBufferByIndex(tableName, columnName, value);
929
+ if (!record) {
930
+ return null;
931
+ }
932
+ return new Uint8Array(Module.HEAPU8.buffer, record.ptr, record.size).slice();
933
+ }
934
+
935
+ getStorageInfo() {
936
+ return {
937
+ ptr: api.getStorageBuffer(this._handle),
938
+ size: api.getStorageSize(this._handle)
939
+ };
940
+ }
941
+
544
942
  // ==================== Encryption API ====================
545
943
 
546
944
  /**