flatsql 3.11.1 → 3.12.1

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 (181) hide show
  1. package/README.md +61 -795
  2. package/bin/flatsql.mjs +76 -0
  3. package/cpp/README.md +18 -0
  4. package/cpp/include/flatsql/flatsql.h +162 -0
  5. package/cpp/include/flatsql/flatsql_io.h +159 -0
  6. package/docs/API-3.12.md +126 -0
  7. package/package.json +28 -123
  8. package/wasm/flatsql.wasm +0 -0
  9. package/wasm/index.d.ts +61 -548
  10. package/wasm/index.js +280 -1281
  11. package/wasm/integrity.json +6 -26
  12. package/wasm/runtime/LICENSE.sdk.txt +190 -0
  13. package/wasm/runtime/NOTICE.txt +2 -0
  14. package/wasm/runtime/browser-guest.mjs +1 -0
  15. package/wasm/runtime/browser-host.mjs +1 -0
  16. package/wasm/runtime/node-guest.mjs +2 -0
  17. package/wasm/runtime/node-host.mjs +5 -0
  18. package/wasm/runtime/opfsIoWorker.mjs +1 -0
  19. package/wasm/runtime/wasiThreadBrowserWorker.mjs +1 -0
  20. package/dist/artifacts/index.d.ts +0 -3
  21. package/dist/artifacts/index.d.ts.map +0 -1
  22. package/dist/artifacts/index.js +0 -3
  23. package/dist/artifacts/index.js.map +0 -1
  24. package/dist/artifacts/transport.d.ts +0 -5
  25. package/dist/artifacts/transport.d.ts.map +0 -1
  26. package/dist/artifacts/transport.js +0 -45
  27. package/dist/artifacts/transport.js.map +0 -1
  28. package/dist/artifacts/types.d.ts +0 -34
  29. package/dist/artifacts/types.d.ts.map +0 -1
  30. package/dist/artifacts/types.js +0 -2
  31. package/dist/artifacts/types.js.map +0 -1
  32. package/dist/btree/btree.d.ts +0 -34
  33. package/dist/btree/btree.d.ts.map +0 -1
  34. package/dist/btree/btree.js +0 -234
  35. package/dist/btree/btree.js.map +0 -1
  36. package/dist/btree/index.d.ts +0 -3
  37. package/dist/btree/index.d.ts.map +0 -1
  38. package/dist/btree/index.js +0 -3
  39. package/dist/btree/index.js.map +0 -1
  40. package/dist/btree/types.d.ts +0 -36
  41. package/dist/btree/types.d.ts.map +0 -1
  42. package/dist/btree/types.js +0 -55
  43. package/dist/btree/types.js.map +0 -1
  44. package/dist/cluster/index.d.ts +0 -11
  45. package/dist/cluster/index.d.ts.map +0 -1
  46. package/dist/cluster/index.js +0 -59
  47. package/dist/cluster/index.js.map +0 -1
  48. package/dist/response/artifact.d.ts +0 -4
  49. package/dist/response/artifact.d.ts.map +0 -1
  50. package/dist/response/artifact.js +0 -102
  51. package/dist/response/artifact.js.map +0 -1
  52. package/dist/response/cache-key.d.ts +0 -6
  53. package/dist/response/cache-key.d.ts.map +0 -1
  54. package/dist/response/cache-key.js +0 -114
  55. package/dist/response/cache-key.js.map +0 -1
  56. package/dist/response/index.d.ts +0 -5
  57. package/dist/response/index.d.ts.map +0 -1
  58. package/dist/response/index.js +0 -5
  59. package/dist/response/index.js.map +0 -1
  60. package/dist/response/memory-cache.d.ts +0 -17
  61. package/dist/response/memory-cache.d.ts.map +0 -1
  62. package/dist/response/memory-cache.js +0 -76
  63. package/dist/response/memory-cache.js.map +0 -1
  64. package/dist/response/types.d.ts +0 -57
  65. package/dist/response/types.d.ts.map +0 -1
  66. package/dist/response/types.js +0 -2
  67. package/dist/response/types.js.map +0 -1
  68. package/dist/schema/index.d.ts +0 -3
  69. package/dist/schema/index.d.ts.map +0 -1
  70. package/dist/schema/index.js +0 -3
  71. package/dist/schema/index.js.map +0 -1
  72. package/dist/schema/parser.d.ts +0 -25
  73. package/dist/schema/parser.d.ts.map +0 -1
  74. package/dist/schema/parser.js +0 -474
  75. package/dist/schema/parser.js.map +0 -1
  76. package/dist/schema/types.d.ts +0 -33
  77. package/dist/schema/types.d.ts.map +0 -1
  78. package/dist/schema/types.js +0 -29
  79. package/dist/schema/types.js.map +0 -1
  80. package/dist/standalone/index.d.ts +0 -3
  81. package/dist/standalone/index.d.ts.map +0 -1
  82. package/dist/standalone/index.js +0 -3
  83. package/dist/standalone/index.js.map +0 -1
  84. package/dist/standalone/process-client.d.ts +0 -85
  85. package/dist/standalone/process-client.d.ts.map +0 -1
  86. package/dist/standalone/process-client.js +0 -537
  87. package/dist/standalone/process-client.js.map +0 -1
  88. package/dist/standalone/wasmedge-runner.d.ts +0 -19
  89. package/dist/standalone/wasmedge-runner.d.ts.map +0 -1
  90. package/dist/standalone/wasmedge-runner.js +0 -108
  91. package/dist/standalone/wasmedge-runner.js.map +0 -1
  92. package/dist/storage/index.d.ts +0 -3
  93. package/dist/storage/index.d.ts.map +0 -1
  94. package/dist/storage/index.js +0 -3
  95. package/dist/storage/index.js.map +0 -1
  96. package/dist/storage/stacked-flatbuffers.d.ts +0 -38
  97. package/dist/storage/stacked-flatbuffers.d.ts.map +0 -1
  98. package/dist/storage/stacked-flatbuffers.js +0 -253
  99. package/dist/storage/stacked-flatbuffers.js.map +0 -1
  100. package/dist/storage/types.d.ts +0 -23
  101. package/dist/storage/types.d.ts.map +0 -1
  102. package/dist/storage/types.js +0 -27
  103. package/dist/storage/types.js.map +0 -1
  104. package/dist/stress/aggregate.d.ts +0 -3
  105. package/dist/stress/aggregate.d.ts.map +0 -1
  106. package/dist/stress/aggregate.js +0 -61
  107. package/dist/stress/aggregate.js.map +0 -1
  108. package/dist/stress/docker-compose.d.ts +0 -17
  109. package/dist/stress/docker-compose.d.ts.map +0 -1
  110. package/dist/stress/docker-compose.js +0 -156
  111. package/dist/stress/docker-compose.js.map +0 -1
  112. package/dist/stress/index.d.ts +0 -10
  113. package/dist/stress/index.d.ts.map +0 -1
  114. package/dist/stress/index.js +0 -10
  115. package/dist/stress/index.js.map +0 -1
  116. package/dist/stress/metrics.d.ts +0 -3
  117. package/dist/stress/metrics.d.ts.map +0 -1
  118. package/dist/stress/metrics.js +0 -83
  119. package/dist/stress/metrics.js.map +0 -1
  120. package/dist/stress/native-publish-event-workload.d.ts +0 -3
  121. package/dist/stress/native-publish-event-workload.d.ts.map +0 -1
  122. package/dist/stress/native-publish-event-workload.js +0 -465
  123. package/dist/stress/native-publish-event-workload.js.map +0 -1
  124. package/dist/stress/production-gate.d.ts +0 -4
  125. package/dist/stress/production-gate.d.ts.map +0 -1
  126. package/dist/stress/production-gate.js +0 -12
  127. package/dist/stress/production-gate.js.map +0 -1
  128. package/dist/stress/report.d.ts +0 -3
  129. package/dist/stress/report.d.ts.map +0 -1
  130. package/dist/stress/report.js +0 -174
  131. package/dist/stress/report.js.map +0 -1
  132. package/dist/stress/sds-discovery.d.ts +0 -4
  133. package/dist/stress/sds-discovery.d.ts.map +0 -1
  134. package/dist/stress/sds-discovery.js +0 -40
  135. package/dist/stress/sds-discovery.js.map +0 -1
  136. package/dist/stress/smoke-runner.d.ts +0 -3
  137. package/dist/stress/smoke-runner.d.ts.map +0 -1
  138. package/dist/stress/smoke-runner.js +0 -64
  139. package/dist/stress/smoke-runner.js.map +0 -1
  140. package/dist/stress/types.d.ts +0 -219
  141. package/dist/stress/types.d.ts.map +0 -1
  142. package/dist/stress/types.js +0 -2
  143. package/dist/stress/types.js.map +0 -1
  144. package/dist/stress/workload.d.ts +0 -6
  145. package/dist/stress/workload.d.ts.map +0 -1
  146. package/dist/stress/workload.js +0 -144
  147. package/dist/stress/workload.js.map +0 -1
  148. package/docs/PARTITION-STORE-WASM.md +0 -338
  149. package/docs/PARTITION-STORE.md +0 -2380
  150. package/docs/agents/response-artifacts.md +0 -65
  151. package/docs/response-artifacts.md +0 -86
  152. package/scripts/stress-docker.sh +0 -279
  153. package/sdm/flatsql-spatial.wasm +0 -0
  154. package/sdm/index.d.ts +0 -88
  155. package/sdm/index.js +0 -253
  156. package/sdm/manifest.json +0 -402
  157. package/stress/docker/Dockerfile +0 -11
  158. package/stress/docker/docker-compose.yml +0 -51
  159. package/stress/sds/aggregate.mjs +0 -97
  160. package/stress/sds/run.mjs +0 -105
  161. package/stress/sds/write-compose.mjs +0 -96
  162. package/wasm/flatsql-client.js +0 -280
  163. package/wasm/flatsql-io.d.ts +0 -97
  164. package/wasm/flatsql-io.js +0 -625
  165. package/wasm/flatsql-p4-threads.wasm +0 -0
  166. package/wasm/flatsql-ps-threads.wasm +0 -0
  167. package/wasm/flatsql-wasi-noeh.wasm +0 -0
  168. package/wasm/flatsql-wasi.wasm +0 -0
  169. package/wasm/flatsql.js +0 -2
  170. package/wasm/flatsql.worker.js +0 -209
  171. package/wasm/native/flatsql_wasmedge_runner.cpp +0 -858
  172. package/wasm/ps-node-fault.d.mts +0 -23
  173. package/wasm/ps-node-fault.mjs +0 -381
  174. package/wasm/ps-node-guest.mjs +0 -173
  175. package/wasm/ps-node-host.d.mts +0 -95
  176. package/wasm/ps-node-host.mjs +0 -476
  177. package/wasm/ps-node-imports.mjs +0 -847
  178. package/wasm/ps.d.ts +0 -21
  179. package/wasm/ps.js +0 -75
  180. package/wasm/wasi.d.ts +0 -11
  181. package/wasm/wasi.js +0 -51
package/wasm/index.js CHANGED
@@ -1,1291 +1,290 @@
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
- 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;
16
-
17
- // Security: Track if integrity was verified
18
- let integrityVerified = false;
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);
1
+ // One engine, one data API: binary schemas and records in, SDS QRP/records out.
2
+ const encoder = new TextEncoder();
3
+ const decoder = new TextDecoder();
4
+ const pause = () => new Promise(resolve => setTimeout(resolve, 1));
5
+ const bytes = value => {
6
+ if (!(value instanceof Uint8Array)) throw new TypeError('Expected Uint8Array FlatBuffer bytes');
7
+ return value;
8
+ };
9
+ function integer(value, name, minimum=0, maximum=Number.MAX_SAFE_INTEGER) {
10
+ if(!Number.isSafeInteger(value) || value<minimum || value>maximum)
11
+ throw new RangeError(`${name} must be an integer from ${minimum} to ${maximum}`);
12
+ return value;
29
13
  }
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');
14
+ function queryOptions(options) {
15
+ for(const key of ['timeoutMs','maxRows','maxRowsExamined','maxBytesRead','maxResultBytes'])
16
+ if(options[key]!==undefined)integer(options[key],key);
17
+ if(options.sandbox && options.bulk)throw new TypeError('Choose either sandbox or bulk');
18
+ return options;
45
19
  }
46
-
47
- function hasNodeProcess() {
48
- return typeof process !== 'undefined' &&
49
- process.versions !== undefined &&
50
- typeof process.versions.node === 'string';
20
+ function join(parts) {
21
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
22
+ let at = 0;
23
+ for (const p of parts) { out.set(p, at); at += p.length; }
24
+ return out;
51
25
  }
52
-
53
- /**
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.
57
- * @param {ArrayBuffer} data
58
- * @param {(data: ArrayBuffer) => Promise<string|Uint8Array|ArrayBuffer>|string|Uint8Array|ArrayBuffer} [provider]
59
- * @returns {Promise<string>}
60
- */
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');
67
- const hash = cryptoModule.createHash('sha384');
68
- hash.update(Buffer.from(data));
69
- return hash.digest('base64');
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
- );
26
+ // Private engine control encoding; no record fields are decoded by the host.
27
+ function control(entries) {
28
+ return join(entries.map(([tag, value, width]) => {
29
+ let data;
30
+ if (typeof value === 'string') data = encoder.encode(value);
31
+ else if (value instanceof Uint8Array) data = value;
32
+ else {
33
+ data = new Uint8Array(width ?? 4);
34
+ const view = new DataView(data.buffer);
35
+ if (data.length === 8) view.setBigUint64(0, BigInt(value), true);
36
+ else if (data.length === 1) view.setUint8(0, value);
37
+ else view.setUint32(0, value, true);
38
+ }
39
+ const h = new Uint8Array(6);
40
+ const v = new DataView(h.buffer);
41
+ v.setUint16(0, tag, true); v.setUint32(2, data.length, true);
42
+ return join([h, data]);
43
+ }));
74
44
  }
75
-
76
- /**
77
- * Verify WASM binary integrity
78
- * @param {ArrayBuffer} wasmBinary - The WASM binary data
79
- * @param {string} expectedHash - Expected SHA-384 hash (base64)
80
- * @returns {Promise<boolean>}
81
- */
82
- async function verifyWASMIntegrity(wasmBinary, expectedHash, options = {}) {
83
- const computedHash = await computeSHA384(wasmBinary, options.computeSHA384);
84
- return computedHash === expectedHash;
45
+ export class FlatSQLError extends Error {
46
+ constructor(status, message) { super(message || `FlatSQL status ${status}`); this.name = 'FlatSQLError'; this.status = status; }
85
47
  }
86
-
87
- /**
88
- * Load integrity.json if available
89
- * @param {string} [basePath] - Base path for integrity.json
90
- * @returns {Promise<{hash: string, sri: string, size: number} | null>}
91
- */
92
- async function loadIntegrityFile(basePath = '') {
93
- try {
94
- if (typeof fetch !== 'undefined') {
95
- // Browser or Node.js with fetch
96
- const url = basePath ? `${basePath}/integrity.json` : new URL('./integrity.json', import.meta.url).href;
97
- const response = await fetch(url);
98
- if (response.ok) {
99
- return await response.json();
100
- }
101
- } else {
102
- // Node.js fallback
103
- const fs = await import('fs');
104
- const path = await import('path');
105
- const url = await import('url');
106
- const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
107
- const integrityPath = path.join(__dirname, 'integrity.json');
108
- if (fs.existsSync(integrityPath)) {
109
- return JSON.parse(fs.readFileSync(integrityPath, 'utf8'));
110
- }
111
- }
112
- } catch (e) {
113
- // Integrity file not available - this is OK in development
114
- }
115
- return null;
116
- }
117
-
118
- /**
119
- * @typedef {Object} InitOptions
120
- * @property {string} [integrity] - Expected SHA-384 hash for WASM verification (base64)
121
- * @property {string} [wasmPath] - Custom path to WASM files directory
122
- * @property {boolean} [skipIntegrityCheck] - Skip integrity verification (not recommended for production)
123
- * @property {boolean} [requireIntegrity] - Fail if integrity cannot be verified (default: false)
124
- * @property {function} [moduleFactory] - Custom Emscripten module factory
125
- */
126
-
127
- /**
128
- * Initialize the FlatSQL WASM module with optional integrity verification
129
- * @param {function|InitOptions} [moduleFactoryOrOptions] - Module factory or initialization options
130
- * @returns {Promise<FlatSQL>}
131
- *
132
- * @example
133
- * // Basic usage (auto-loads integrity.json if available)
134
- * const flatsql = await initFlatSQL();
135
- *
136
- * @example
137
- * // With explicit integrity hash
138
- * const flatsql = await initFlatSQL({
139
- * integrity: 'base64-hash-here',
140
- * requireIntegrity: true
141
- * });
142
- *
143
- * @example
144
- * // Skip integrity check (development only)
145
- * const flatsql = await initFlatSQL({ skipIntegrityCheck: true });
146
- */
147
- let ioBackend = null;
148
-
149
- /** The I/O backend this runtime was initialised with (null when ephemeral). */
150
- export function getFlatSqlIoBackend() {
151
- return ioBackend;
48
+ function check(status) { if (status < 0) throw new FlatSQLError(status); return status; }
49
+ async function load(url, integrity) {
50
+ if (url.protocol === 'file:') {
51
+ const { readFile } = await import('node:fs/promises');
52
+ return new Uint8Array(await readFile(url));
53
+ }
54
+ const response = await fetch(url, integrity ? { integrity } : undefined);
55
+ if (!response.ok) throw new Error(`FlatSQL artifact: HTTP ${response.status}`);
56
+ return new Uint8Array(await response.arrayBuffer());
152
57
  }
153
-
154
- export async function initFlatSQL(moduleFactoryOrOptions) {
155
- let moduleFactory = FlatSQLModule;
156
- let options = {};
157
-
158
- // Parse arguments - support both legacy and new API
159
- if (moduleFactoryOrOptions) {
160
- if (typeof moduleFactoryOrOptions === 'function') {
161
- // Legacy: moduleFactory passed directly
162
- moduleFactory = moduleFactoryOrOptions;
163
- } else if (typeof moduleFactoryOrOptions === 'object') {
164
- // New: options object
165
- options = moduleFactoryOrOptions;
166
- if (options.moduleFactory) {
167
- moduleFactory = options.moduleFactory;
168
- }
169
- }
170
- }
171
-
172
- // Determine expected integrity hash
173
- let expectedIntegrity = options.integrity || null;
174
-
175
- // Load integrity file if no explicit integrity provided
176
- if (!expectedIntegrity && !options.skipIntegrityCheck) {
177
- const integrityData = await loadIntegrityFile(options.wasmPath);
178
- if (integrityData) {
179
- expectedIntegrity = integrityData.hash;
180
- }
181
- }
182
-
183
- // Check if integrity is required but not available
184
- if (options.requireIntegrity && !expectedIntegrity) {
185
- throw new Error(
186
- 'WASM integrity verification required but no integrity hash available. ' +
187
- 'Ensure integrity.json exists or pass integrity option.'
188
- );
189
- }
190
-
191
- // Create module configuration
192
- const moduleConfig = {};
193
-
194
- // The seven-import host I/O contract (cpp/include/flatsql/flatsql_io.h).
195
- // cpp/js/flatsql_io_library.js reads this off the Module and translates
196
- // pointers into typed arrays; everything else about storage lives in the
197
- // backend. With no backend, disk-backed opens fail closed with an error
198
- // value — they never quietly succeed against RAM.
199
- ioBackend = options.io ?? null;
200
- if (ioBackend) {
201
- moduleConfig.flatsqlIO = ioBackend;
202
- }
203
-
204
- // If we have an expected hash, use custom WASM instantiation with verification
205
- if (expectedIntegrity && !options.skipIntegrityCheck) {
206
- moduleConfig.instantiateWasm = async (imports, successCallback) => {
207
- try {
208
- // Determine WASM path
209
- let wasmUrl;
210
- if (options.wasmPath) {
211
- wasmUrl = `${options.wasmPath}/flatsql.wasm`;
212
- } else {
213
- wasmUrl = new URL('./flatsql.wasm', import.meta.url).href;
214
- }
215
-
216
- // Fetch WASM binary
217
- let wasmBinary;
218
- if (typeof fetch !== 'undefined') {
219
- const response = await fetch(wasmUrl);
220
- if (!response.ok) {
221
- throw new Error(`Failed to fetch WASM: ${response.status}`);
222
- }
223
- wasmBinary = await response.arrayBuffer();
224
- } else {
225
- // Node.js fallback
226
- const fs = await import('fs');
227
- const path = await import('path');
228
- const url = await import('url');
229
- const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
230
- const wasmPath = path.join(__dirname, 'flatsql.wasm');
231
- wasmBinary = fs.readFileSync(wasmPath).buffer;
232
- }
233
-
234
- // Verify integrity
235
- const isValid = await verifyWASMIntegrity(wasmBinary, expectedIntegrity, options);
236
- if (!isValid) {
237
- throw new Error(
238
- 'WASM integrity check failed: hash mismatch. ' +
239
- 'The WASM binary may have been tampered with or corrupted.'
240
- );
241
- }
242
-
243
- // Mark as verified
244
- integrityVerified = true;
245
-
246
- // Instantiate verified WASM
247
- const result = await WebAssembly.instantiate(wasmBinary, imports);
248
- successCallback(result.instance);
249
- return result.instance.exports;
250
- } catch (error) {
251
- throw new Error(`WASM integrity verification failed: ${error.message}`);
252
- }
253
- };
254
- }
255
-
256
- // Initialize module
257
- Module = await moduleFactory(moduleConfig);
258
-
259
- // Wrap C functions using cwrap
260
- api = {
261
- // Database lifecycle
262
- createDb: Module.cwrap('flatsql_create_db', 'number', ['string', 'string']),
263
- openDb: Module.cwrap('flatsql_open_db', 'number', ['string', 'string', 'string', 'number']),
264
- isDiskBacked: Module.cwrap('flatsql_is_disk_backed', 'number', ['number']),
265
- openState: Module.cwrap('flatsql_open_state', 'number', ['number']),
266
- reindexAll: Module.cwrap('flatsql_reindex_all', 'number', ['number']),
267
- reindexStep: Module.cwrap('flatsql_reindex_step', 'number', ['number', 'number']),
268
- flushIndex: Module.cwrap('flatsql_flush_index', 'number', ['number']),
269
- flushedOffset: Module.cwrap('flatsql_flushed_offset', 'number', ['number']),
270
- streamPath: Module.cwrap('flatsql_stream_path', 'string', ['number']),
271
- destroyDb: Module.cwrap('flatsql_destroy_db', null, ['number']),
272
- registerFileId: Module.cwrap('flatsql_register_file_id', null, ['number', 'string', 'string']),
273
- enableDemoExtractors: Module.cwrap('flatsql_enable_demo_extractors', null, ['number']),
274
-
275
- // Data ingestion
276
- ingest: Module.cwrap('flatsql_ingest', 'number', ['number', 'number', 'number']),
277
- ingestOne: Module.cwrap('flatsql_ingest_one', 'number', ['number', 'number', 'number']),
278
-
279
- // Source-aware ingestion
280
- registerSource: Module.cwrap('flatsql_register_source', null, ['number', 'string']),
281
- createUnifiedViews: Module.cwrap('flatsql_create_unified_views', null, ['number']),
282
- ingestWithSource: Module.cwrap('flatsql_ingest_with_source', 'number', ['number', 'number', 'number', 'string']),
283
- ingestOneWithSource: Module.cwrap('flatsql_ingest_one_with_source', 'number', ['number', 'number', 'number', 'string']),
284
- getSourcesCount: Module.cwrap('flatsql_get_sources_count', 'number', ['number']),
285
- getSourceName: Module.cwrap('flatsql_get_source_name', 'string', ['number']),
286
-
287
- // Query execution
288
- query: Module.cwrap('flatsql_query', 'number', ['number', 'string']),
289
- queryParams: Module.cwrap('flatsql_query_params', 'number', ['number', 'string', 'number', 'number', 'number']),
290
- queryMany: Module.cwrap('flatsql_query_many', 'number', ['number', 'number', 'number', 'number']),
291
- buildQueryCacheKey: Module.cwrap('flatsql_build_query_cache_key', 'string', ['string', 'string', 'string', 'number', 'number', 'number']),
292
- buildResponseArtifactCacheKey: Module.cwrap('flatsql_build_response_artifact_cache_key', 'string', ['string', 'string', 'string', 'string', 'string', 'string', 'number', 'number', 'number']),
293
- registerQueryTemplate: Module.cwrap('flatsql_register_query_template', 'number', ['number', 'string', 'string', 'number']),
294
- queryTemplate: Module.cwrap('flatsql_query_template', 'number', ['number', 'string', 'number', 'number', 'number']),
295
- clearQueryCache: Module.cwrap('flatsql_clear_query_cache', null, ['number']),
296
- configureQueryCache: Module.cwrap('flatsql_configure_query_cache', 'number', ['number', 'number', 'number']),
297
- queryCacheHits: Module.cwrap('flatsql_query_cache_hits', 'number', ['number']),
298
- queryCacheMisses: Module.cwrap('flatsql_query_cache_misses', 'number', ['number']),
299
- queryCacheSize: Module.cwrap('flatsql_query_cache_size', 'number', ['number']),
300
- queryCacheGeneration: Module.cwrap('flatsql_query_cache_generation', 'number', ['number']),
301
- queryCacheMaxEntries: Module.cwrap('flatsql_query_cache_max_entries', 'number', ['number']),
302
- queryCacheMaxRows: Module.cwrap('flatsql_query_cache_max_rows', 'number', ['number']),
303
- batchResultCount: Module.cwrap('flatsql_batch_result_count', 'number', []),
304
- selectBatchResult: Module.cwrap('flatsql_select_batch_result', 'number', ['number']),
305
- getError: Module.cwrap('flatsql_get_error', 'string', []),
306
-
307
- // Result access
308
- resultColumnCount: Module.cwrap('flatsql_result_column_count', 'number', []),
309
- resultRowCount: Module.cwrap('flatsql_result_row_count', 'number', []),
310
- resultColumnName: Module.cwrap('flatsql_result_column_name', 'string', ['number']),
311
- resultCellType: Module.cwrap('flatsql_result_cell_type', 'number', ['number', 'number']),
312
- resultCellNumber: Module.cwrap('flatsql_result_cell_number', 'number', ['number', 'number']),
313
- resultCellString: Module.cwrap('flatsql_result_cell_string', 'string', ['number', 'number']),
314
- resultCellBlob: Module.cwrap('flatsql_result_cell_blob', 'number', ['number', 'number']),
315
- resultCellBlobSize: Module.cwrap('flatsql_result_cell_blob_size', 'number', ['number', 'number']),
316
- queryRawFlatBufferStream: Module.cwrap('flatsql_query_raw_flatbuffer_stream', 'number', ['number', 'string', 'number', 'number', 'number']),
317
- querySandboxed: Module.cwrap('flatsql_query_sandboxed', 'number', ['number', 'string', 'number', 'number', 'number', 'number', 'number', 'number', 'number']),
318
- responseArtifactData: Module.cwrap('flatsql_response_artifact_data', 'number', []),
319
- responseArtifactSize: Module.cwrap('flatsql_response_artifact_size', 'number', []),
320
- responseArtifactRowCount: Module.cwrap('flatsql_response_artifact_row_count', 'number', []),
321
- responseArtifactColumnCount: Module.cwrap('flatsql_response_artifact_column_count', 'number', []),
322
- responseArtifactCacheHit: Module.cwrap('flatsql_response_artifact_cache_hit', 'number', []),
323
- configureRawStreamCache: Module.cwrap('flatsql_configure_raw_stream_cache', 'number', ['number', 'number', 'number']),
324
- rawStreamCacheHits: Module.cwrap('flatsql_raw_stream_cache_hits', 'number', ['number']),
325
- rawStreamCacheMisses: Module.cwrap('flatsql_raw_stream_cache_misses', 'number', ['number']),
326
- rawStreamCacheSize: Module.cwrap('flatsql_raw_stream_cache_size', 'number', ['number']),
327
- rawStreamCacheTotalBytes: Module.cwrap('flatsql_raw_stream_cache_total_bytes', 'number', ['number']),
328
-
329
- // Export/Import
330
- exportData: Module.cwrap('flatsql_export_data', 'number', ['number']),
331
- exportSize: Module.cwrap('flatsql_export_size', 'number', []),
332
- loadAndRebuild: Module.cwrap('flatsql_load_and_rebuild', null, ['number', 'number', 'number']),
333
- reserveStorage: Module.cwrap('flatsql_reserve_storage', null, ['number', 'number']),
334
- loadFromDb: Module.cwrap('flatsql_load_from_db', null, ['number', 'number']),
335
-
336
- // Test helpers
337
- createTestUser: Module.cwrap('flatsql_create_test_user', 'number', ['number', 'string', 'string', 'number']),
338
- createTestPost: Module.cwrap('flatsql_create_test_post', 'number', ['number', 'number', 'string']),
339
- createTestMPE: Module.cwrap('flatsql_create_test_mpe', 'number', ['string', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number']),
340
- createTestTelemetry: Module.cwrap('flatsql_create_test_telemetry', 'number', ['number', 'string', 'string', 'string', 'number', 'number', 'number']),
341
- createTestPublishEvent: Module.cwrap('flatsql_create_test_publish_event', 'number', ['string', 'string', 'number', 'number']),
342
- testBufferSize: Module.cwrap('flatsql_test_buffer_size', 'number', []),
343
-
344
- // Stats
345
- getStatsCount: Module.cwrap('flatsql_get_stats_count', 'number', ['number']),
346
- getStatTableName: Module.cwrap('flatsql_get_stat_table_name', 'string', ['number']),
347
- getStatFileId: Module.cwrap('flatsql_get_stat_file_id', 'string', ['number']),
348
- getStatRecordCount: Module.cwrap('flatsql_get_stat_record_count', 'number', ['number']),
349
- resetIngestProfile: Module.cwrap('flatsql_reset_ingest_profile', null, ['number']),
350
- getIngestProfileRecordCount: Module.cwrap('flatsql_get_ingest_profile_record_count', 'number', ['number']),
351
- getIngestProfileByteCount: Module.cwrap('flatsql_get_ingest_profile_byte_count', 'number', ['number']),
352
- getIngestProfileDecodeNanos: Module.cwrap('flatsql_get_ingest_profile_decode_nanos', 'number', ['number']),
353
- getIngestProfileAppendNanos: Module.cwrap('flatsql_get_ingest_profile_append_nanos', 'number', ['number']),
354
- getIngestProfileIndexNanos: Module.cwrap('flatsql_get_ingest_profile_index_nanos', 'number', ['number']),
355
-
356
- // Delete support
357
- markDeleted: Module.cwrap('flatsql_mark_deleted', null, ['number', 'string', 'number']),
358
- getDeletedCount: Module.cwrap('flatsql_get_deleted_count', 'number', ['number', 'string']),
359
- clearTombstones: Module.cwrap('flatsql_clear_tombstones', null, ['number', 'string']),
360
-
361
- // Raw FlatBuffer access
362
- getFlatBufferById: Module.cwrap('flatsql_get_flatbuffer_by_id', 'number', ['number', 'string', 'number']),
363
- getFlatBufferByEmail: Module.cwrap('flatsql_get_flatbuffer_by_email', 'number', ['number', 'string', 'string']),
364
- getFlatBufferByIndex: Module.cwrap('flatsql_get_flatbuffer_by_index', 'number', ['number', 'string', 'string', 'number', 'number', 'number']),
365
- getRawFlatBufferSize: Module.cwrap('flatsql_get_raw_flatbuffer_size', 'number', []),
366
- getRawFlatBufferSequence: Module.cwrap('flatsql_get_raw_flatbuffer_sequence', 'number', []),
367
- getStorageBuffer: Module.cwrap('flatsql_get_storage_buffer', 'number', ['number']),
368
- getStorageSize: Module.cwrap('flatsql_get_storage_size', 'number', ['number']),
369
-
370
- // Encryption (field-encryption format 3; the record index is the sequence)
371
- setEncryptionKey: Module.cwrap('flatsql_set_encryption_key', 'number', ['number', 'number', 'number', 'number']),
372
- isEncrypted: Module.cwrap('flatsql_is_encrypted', 'number', ['number']),
373
- encryptBuffer: Module.cwrap('flatsql_encrypt_buffer', 'number', ['number', 'number', 'number', 'number', 'number', 'number']),
374
- decryptBuffer: Module.cwrap('flatsql_decrypt_buffer', 'number', ['number', 'number', 'number', 'number', 'number', 'number']),
375
- ingestOneEncrypted: Module.cwrap('flatsql_ingest_one_encrypted', 'number', ['number', 'number', 'number', 'number', 'number', 'string']),
376
-
377
- // HMAC Authentication
378
- setHMACVerification: Module.cwrap('flatsql_set_hmac_verification', 'number', ['number', 'number']),
379
- isHMACEnabled: Module.cwrap('flatsql_is_hmac_enabled', 'number', ['number']),
380
- computeHMAC: Module.cwrap('flatsql_compute_hmac', 'number', ['number', 'number', 'number', 'number']),
381
- verifyHMAC: Module.cwrap('flatsql_verify_hmac', 'number', ['number', 'number', 'number', 'number']),
58
+ export class FlatSQL {
59
+ #host; #layout; #memory; #u8; #i32; #u64; #view; #closed = false; #active = new Set();
60
+ static async open(options = {}) {
61
+ integer(options.maximumPages??8192,'maximumPages',8192,32768);
62
+ integer(options.requestBytes??8*1024*1024,'requestBytes',1024,16*1024*1024);
63
+ integer(options.writers??1,'writers',1,8);integer(options.readers??1,'readers',1,16);
64
+ if(options.backend && !['opfs','memory'].includes(options.backend))throw new TypeError('Unknown browser storage backend');
65
+ const url = new URL(options.wasmUrl ?? './flatsql.wasm', import.meta.url);
66
+ let artifact;
67
+ if (options.wasmBytes) {
68
+ if (!options.verify) throw new TypeError('Supplied WASM bytes require an artifact verification callback');
69
+ artifact = bytes(options.wasmBytes);
70
+ } else {
71
+ const manifest = JSON.parse(decoder.decode(await load(new URL('./integrity.json', url))));
72
+ const expected = manifest.files?.['flatsql.wasm'];
73
+ if (!expected?.sri || !expected?.sha256) throw new Error('Missing FlatSQL artifact integrity');
74
+ artifact = await load(url, expected.sri);
75
+ if (artifact.length !== expected.size) throw new Error('FlatSQL artifact size mismatch');
76
+ if (globalThis.process?.versions?.node) {
77
+ const { createHash } = await import('node:crypto');
78
+ if (createHash('sha256').update(artifact).digest('hex') !== expected.sha256) throw new Error('FlatSQL artifact digest mismatch');
79
+ }
80
+ }
81
+ if (options.verify) await options.verify(artifact);
82
+ const hostOptions = { ...options, maximumPages: options.maximumPages ?? 8192 };
83
+ const host = globalThis.process?.versions?.node
84
+ ? await (await import('./runtime/node-host.mjs')).createNodeInstance(artifact, hostOptions)
85
+ : await (await import('./runtime/browser-host.mjs')).createBrowserInstance(artifact, hostOptions);
86
+ const db = new FlatSQL();
87
+ db.#host = host;
88
+ db.#memory = host.memory;
89
+ db.#u8 = new Uint8Array(host.memory.buffer);
90
+ db.#i32 = new Int32Array(host.memory.buffer);
91
+ db.#u64 = new BigUint64Array(host.memory.buffer);
92
+ db.#view = new DataView(host.memory.buffer);
93
+ try {
94
+ const requestBytes = options.requestBytes ?? 8 * 1024 * 1024;
95
+ const cfg = control([[1, options.path ?? '/store'], [2, 1, 1], [3, options.writers ?? 1],
96
+ [4, options.readers ?? 1], [5, 1], [6, 1], [7, 2], [8, 4], [9, requestBytes], [10, requestBytes],
97
+ [11, 256 * 1024], [20, 384 * 1024 * 1024, 8], [21, 8], [23, 16], [25, 16 * 1024 * 1024, 8],
98
+ [26, 128 * 1024 * 1024, 8], [27, 192 * 1024 * 1024, 8], [48, 32 * 1024 * 1024, 8]]);
99
+ await db.#withBytes(cfg, async p => check(await host.call('flatsql_init', p, cfg.length)));
100
+ const p = await host.call('flatsql_alloc', 640);
101
+ if (!p) throw new FlatSQLError(-2);
102
+ try {
103
+ if (check(await host.call('flatsql_layout', p)) !== 640) throw new Error('Invalid FlatSQL mailbox layout');
104
+ db.#layout = new DataView((await host.read(p, 640)).buffer);
105
+ if (db.#layout.getUint32(0, true) !== 1 || db.#layout.getUint32(8, true) !== 448) throw new Error('Invalid FlatSQL mailbox layout');
106
+ } finally { await host.call('flatsql_free', p); }
107
+ check(await host.call('flatsql_start'));
108
+ return db;
109
+ } catch (error) { await host.close(); throw error; }
110
+ }
111
+ async #withBytes(data, fn) {
112
+ const p = await this.#host.write(data);
113
+ try { return await fn(p); } finally { await this.#host.call('flatsql_free', p); }
114
+ }
115
+ #assertOpen() { if (this.#closed || this.#host.faulted()) throw new FlatSQLError(-11, 'FlatSQL is closed or its guest failed'); }
116
+ async registerSchema({ name, fileIdentifier, bfbs, indexRules = '', maxRecordBytes = 16 * 1024 * 1024, maxRows = 400000, fullText = false }) {
117
+ this.#assertOpen(); bytes(bfbs);
118
+ integer(maxRecordBytes,'maxRecordBytes',8,128*1024*1024);integer(maxRows,'maxRows',1,0xffffffff);
119
+ if(typeof fileIdentifier!=='string')throw new TypeError('A four-byte file identifier is required');
120
+ if (!/^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(name) || encoder.encode(fileIdentifier).length !== 4) throw new TypeError('Schema name and four-byte file identifier are required');
121
+ const spec = control([[1, name], [2, fileIdentifier], [3, bfbs], [4, indexRules], [5, maxRecordBytes, 8],
122
+ [6, 4 * 1024 * 1024, 8], [7, 3], [8, 4096], [10, maxRows], [12, fullText ? 1 : 0, 1]]);
123
+ await this.#withBytes(spec, async p => check(await this.#host.call('flatsql_register_type', p, spec.length)));
124
+ }
125
+ ingest(type, frames, options = {}) { return this.#records(1, type, frames, options); }
126
+ remove(type, frames, options = {}) { return this.#records(3, type, frames, options); }
127
+ async #records(opcode, type, frames, options) {
128
+ this.#assertOpen(); bytes(frames);queryOptions(options);
129
+ if(options.timestamp!==undefined)integer(options.timestamp,'timestamp');
130
+ const tags = options.source ? [[51, control([[1, options.provider ?? 'local'], [2, options.source],
131
+ ...(options.batch ? [[4, options.batch]] : [])])]] : [];
132
+ const signatures = [];
133
+ if (options.signatures !== undefined) {
134
+ if (opcode !== 1 || !Array.isArray(options.signatures)) throw new TypeError('Signatures require an ingest array');
135
+ const header = new Uint8Array(4); new DataView(header.buffer).setUint32(0, options.signatures.length, true);
136
+ const parts = [header];
137
+ for (const signature of options.signatures) {
138
+ bytes(signature); integer(signature.length, 'signature size', 0, 65535);
139
+ const length = new Uint8Array(2); new DataView(length.buffer).setUint16(0, signature.length, true);
140
+ parts.push(length, signature);
141
+ }
142
+ signatures.push([55, join(parts)]);
143
+ }
144
+ const meta = control([[1, type], [50, options.producer ?? 'local'], ...tags, ...signatures, [54, options.timestamp ?? Math.floor(Date.now() / 1000), 8]]);
145
+ const request = await this.#withBytes(frames, f => this.#withBytes(meta, async m => {
146
+ const out = await this.#host.call('flatsql_alloc', 8);
147
+ if (!out) throw new FlatSQLError(-2);
148
+ let prepared = 0;
149
+ try {
150
+ check(await this.#host.call('flatsql_record_request', opcode, f, frames.length, m, meta.length, out, out + 4));
151
+ const view = new DataView((await this.#host.read(out, 8)).buffer);
152
+ prepared = view.getUint32(0, true);
153
+ return await this.#host.read(prepared, view.getUint32(4, true));
154
+ } finally {
155
+ if (prepared) await this.#host.call('flatsql_free', prepared);
156
+ await this.#host.call('flatsql_free', out);
157
+ }
158
+ }));
159
+ return this.#collect(this.#stream(opcode, request, options), options.maxResultBytes);
160
+ }
161
+ query(request, options = {}) { bytes(request); return this.#stream(30, request, options); }
162
+ async execute(request, options = {}) { return this.#collect(this.query(request, options), options.maxResultBytes); }
163
+ async surface(options = {}) { return this.#collect(this.#stream(31, new Uint8Array(), options), options.maxResultBytes); }
164
+ async setQuota(bytes) { integer(bytes,'quota'); this.#assertOpen(); check(await this.#host.call('flatsql_set_quota', bytes)); }
165
+ async #collect(stream, maxBytes = 16 * 1024 * 1024) {
166
+ const parts = []; let size = 0;
167
+ for await (const part of stream) {
168
+ size += part.length;
169
+ if (size > maxBytes + 65536) throw new FlatSQLError(-8, 'Collected result exceeds the byte budget');
170
+ parts.push(part);
171
+ }
172
+ return join(parts);
173
+ }
174
+ #get(offset) { return this.#layout.getUint32(offset, true); }
175
+ #wake(cls) {
176
+ for (let i = 0; i < this.#get(120); ++i) if (this.#get(128 + i * 4) === cls) {
177
+ const bell = this.#get(384 + i * 4) >>> 2;
178
+ Atomics.add(this.#i32, bell, 1); Atomics.notify(this.#i32, bell);
179
+ }
180
+ }
181
+ #enqueue(cls, slot) {
182
+ const i = cls - 1, enq = this.#get(88 + i * 4) >>> 3;
183
+ const mask = BigInt(this.#get(72 + i * 4));
184
+ let pos = Atomics.load(this.#u64, enq);
185
+ for (;;) {
186
+ const cell = this.#get(56 + i * 4) + Number(pos & mask) * 16;
187
+ const seq = Atomics.load(this.#u64, cell >>> 3);
188
+ if (seq < pos) return false;
189
+ if (seq === pos && Atomics.compareExchange(this.#u64, enq, pos, pos + 1n) === pos) {
190
+ this.#view.setUint32(cell + 8, slot, true);
191
+ Atomics.store(this.#u64, cell >>> 3, pos + 1n);
192
+ return true;
193
+ }
194
+ pos = Atomics.load(this.#u64, enq);
195
+ }
196
+ }
197
+ #stream(op, request, options) {
198
+ queryOptions(options);
199
+ const handle = {};
200
+ handle.iterator = this.#run(op, request, options, handle);
201
+ return handle.iterator;
202
+ }
203
+ async *#run(op, request, options, handle) {
204
+ this.#assertOpen();
205
+ const cls = op < 10 ? 1 : options.sandbox ? 4 : options.bulk ? 3 : 2;
206
+ const pool = cls === 1 ? 0 : 1, first = pool ? this.#get(16) : 0;
207
+ const count = this.#get(16 + pool * 4), base = this.#get(24 + pool * 4), stride = this.#get(32 + pool * 4);
208
+ const capacity = this.#get(40 + pool * 4), ringSize = this.#get(48 + pool * 4);
209
+ if (request.length > capacity) throw new FlatSQLError(-8, 'Request exceeds the configured mailbox capacity');
210
+ let address = 0, slot = 0, queued = false, finished = false;
211
+ const started = performance.now(), timeout = options.timeoutMs ?? 30000;
212
+ const expired = () => options.signal?.aborted || (timeout > 0 && performance.now() - started >= timeout);
213
+ for (;;) {
214
+ this.#assertOpen();
215
+ if (expired()) throw new FlatSQLError(-7, 'Request cancelled before submission');
216
+ for (let i = 0; i < count; ++i) {
217
+ const a = base + i * stride;
218
+ if (Atomics.compareExchange(this.#i32, a >>> 2, 0, 1) === 0) { address = a; slot = first + i; break; }
219
+ }
220
+ if (address) break;
221
+ await pause();
222
+ }
223
+ const h = address, ring = h + 448 + capacity, state = h >>> 2;
224
+ let done;
225
+ const completion = new Promise(resolve => { done = resolve; });
226
+ const active = { cancel: () => { Atomics.store(this.#i32, (h + 4) >>> 2, 1); this.#wake(cls); }, completion, finish: () => handle.iterator.return() };
227
+ this.#active.add(active);
228
+ const read = () => {
229
+ const head = Atomics.load(this.#u64, (h + 72) >>> 3), tail = Atomics.load(this.#u64, (h + 80) >>> 3);
230
+ if (head === tail) return null;
231
+ const n = Number(tail - head), at = Number(head & BigInt(ringSize - 1));
232
+ if (n < 0 || n > ringSize) throw new Error('Invalid FlatSQL ring counters');
233
+ const take = Math.min(n, 65536), a = Math.min(take, ringSize - at), out = new Uint8Array(take);
234
+ out.set(this.#u8.subarray(ring + at, ring + at + a));
235
+ if (a < take) out.set(this.#u8.subarray(ring, ring + take - a), a);
236
+ Atomics.store(this.#u64, (h + 72) >>> 3, head + BigInt(take));
237
+ Atomics.add(this.#i32, (h + 12) >>> 2, 1); this.#wake(cls);
238
+ return out;
382
239
  };
383
-
384
- return new FlatSQL();
385
- }
386
-
387
- /**
388
- * Check if WASM was loaded with integrity verification
389
- * @returns {boolean}
390
- */
391
- export function wasIntegrityVerified() {
392
- return integrityVerified;
393
- }
394
-
395
- function wasmPointerToByteOffset(ptr) {
396
- const numeric = Number(ptr);
397
- if (!Number.isFinite(numeric)) {
398
- throw new TypeError(`Invalid WASM pointer: ${ptr}`);
399
- }
400
- return numeric >>> 0;
401
- }
402
-
403
- export const __testWasmPointerToByteOffset = wasmPointerToByteOffset;
404
-
405
- function heapBytes(ptr, size) {
406
- return new Uint8Array(Module.HEAPU8.buffer, wasmPointerToByteOffset(ptr), size);
407
- }
408
-
409
- function copyHeapBytes(ptr, size) {
410
- return heapBytes(ptr, size).slice();
411
- }
412
-
413
- // BLOB cells are plain number arrays (the result contract). An indexed copy
414
- // into a preallocated array builds the same array ~14x faster than
415
- // Array.from(typedArray), which dominated 90k-row blob results (~430 ms).
416
- function blobToArray(bytes) {
417
- const out = new Array(bytes.length);
418
- for (let index = 0; index < bytes.length; index++) {
419
- out[index] = bytes[index];
420
- }
421
- return out;
422
- }
423
-
424
- function setHeapBytes(data, ptr) {
425
- Module.HEAPU8.set(data, wasmPointerToByteOffset(ptr));
426
- }
427
-
428
- // A record index is the record's sequence (rowid): 1 to 4294967295.
429
- function requireRecordIndex(method, recordIndex) {
430
- if (!Number.isInteger(recordIndex) || recordIndex < 1 || recordIndex > 0xFFFFFFFF) {
431
- throw new TypeError(
432
- `${method}: recordIndex must be the record's sequence, an integer from 1 to 4294967295 ` +
433
- '(field-encryption format 2, without a record index, is not supported)');
434
- }
435
- }
436
-
437
- function withHeapBytes(data, callback) {
438
- const ptr = Module._malloc(data.length);
439
240
  try {
440
- setHeapBytes(data, ptr);
441
- return callback(ptr);
241
+ this.#u8.fill(0, h + 4, h + 448);
242
+ this.#u8.set(request, h + 448);
243
+ this.#view.setUint32(h + 16, op, true); this.#view.setUint32(h + 20, cls, true);
244
+ this.#view.setUint32(h + 24, (options.records ? 1 : 0) | (options.sandbox ? 2 : 0), true);
245
+ this.#view.setUint32(h + 28, request.length, true);
246
+ for (const [off, value] of [[40, options.maxRowsExamined ?? 0], [48, options.maxBytesRead ?? 0],
247
+ [56, options.maxRows ?? 0], [64, options.maxResultBytes ?? 16 * 1024 * 1024]])
248
+ this.#view.setBigUint64(h + off, BigInt(value), true);
249
+ Atomics.store(this.#i32, state, 2);
250
+ while (!this.#enqueue(cls, slot)) {
251
+ if (expired()) throw new FlatSQLError(-7, 'Request cancelled before enqueue');
252
+ await pause();
253
+ }
254
+ queued = true; this.#wake(cls);
255
+ for (;;) {
256
+ if (expired()) active.cancel();
257
+ if (this.#host.faulted()) throw new FlatSQLError(-14, 'FlatSQL guest failed');
258
+ const part = read();
259
+ if (part) { yield part; continue; }
260
+ if (Atomics.load(this.#i32, state) === 5) {
261
+ finished = true;
262
+ const status = this.#view.getInt32(h + 88, true);
263
+ // Browser TextDecoder rejects SharedArrayBuffer views. Copy only the
264
+ // bounded diagnostic; the record/result transport stays binary.
265
+ if (status < 0) throw new FlatSQLError(status, decoder.decode(this.#u8.slice(h + 144, h + 144 + Math.min(256, this.#view.getUint32(h + 92, true)))));
266
+ break;
267
+ }
268
+ await pause();
269
+ }
442
270
  } finally {
443
- Module._free(ptr);
444
- }
445
- }
446
-
447
- function encodeQueryParams(params) {
448
- const parts = [];
449
- let total = 0;
450
-
451
- for (const value of params) {
452
- let tag;
453
- let payload;
454
-
455
- if (value === null) {
456
- tag = PARAM_NULL;
457
- payload = new Uint8Array(0);
458
- } else if (typeof value === 'boolean') {
459
- tag = PARAM_BOOL;
460
- payload = Uint8Array.of(value ? 1 : 0);
461
- } else if (typeof value === 'number' && Number.isInteger(value)) {
462
- if (!Number.isSafeInteger(value)) {
463
- throw new TypeError('Integer query parameters must be safe integers');
464
- }
465
- tag = PARAM_INT64;
466
- payload = new Uint8Array(8);
467
- new DataView(payload.buffer, payload.byteOffset, payload.byteLength).setBigInt64(0, BigInt(value), true);
468
- } else if (typeof value === 'number') {
469
- tag = PARAM_FLOAT64;
470
- payload = new Uint8Array(8);
471
- new DataView(payload.buffer, payload.byteOffset, payload.byteLength).setFloat64(0, value, true);
472
- } else if (typeof value === 'string') {
473
- tag = PARAM_STRING;
474
- payload = textEncoder.encode(value);
475
- } else if (value instanceof Uint8Array) {
476
- tag = PARAM_BYTES;
477
- payload = value;
478
- } else {
479
- throw new TypeError(`Unsupported query parameter type: ${typeof value}`);
480
- }
481
-
482
- const header = new Uint8Array(5);
483
- const view = new DataView(header.buffer);
484
- view.setUint8(0, tag);
485
- view.setUint32(1, payload.length, true);
486
-
487
- parts.push(header, payload);
488
- total += header.length + payload.length;
489
- }
490
-
491
- const encoded = new Uint8Array(total);
492
- let offset = 0;
493
- for (const part of parts) {
494
- encoded.set(part, offset);
495
- offset += part.length;
496
- }
497
-
498
- return encoded;
499
- }
500
-
501
- function encodeQueryRequests(queries) {
502
- const parts = [];
503
- let total = 0;
504
-
505
- for (const query of queries) {
506
- const sqlBytes = textEncoder.encode(query.sql);
507
- const paramList = query.params ?? [];
508
- const paramBytes = encodeQueryParams(paramList);
509
- const header = new Uint8Array(12);
510
- const view = new DataView(header.buffer);
511
- view.setUint32(0, sqlBytes.length, true);
512
- view.setUint32(4, paramList.length, true);
513
- view.setUint32(8, paramBytes.length, true);
514
-
515
- parts.push(header, sqlBytes, paramBytes);
516
- total += header.length + sqlBytes.length + paramBytes.length;
517
- }
518
-
519
- const encoded = new Uint8Array(total);
520
- let offset = 0;
521
- for (const part of parts) {
522
- encoded.set(part, offset);
523
- offset += part.length;
524
- }
525
-
526
- return encoded;
527
- }
528
-
529
- function readCellValue(row, col) {
530
- const type = api.resultCellType(row, col);
531
- switch (type) {
532
- case 0:
533
- return null;
534
- case 1:
535
- return api.resultCellNumber(row, col) !== 0;
536
- case 2:
537
- case 3:
538
- case 4:
539
- return api.resultCellNumber(row, col);
540
- case 5:
541
- return api.resultCellString(row, col);
542
- case 6: {
543
- const blobPtr = api.resultCellBlob(row, col);
544
- const blobSize = api.resultCellBlobSize(row, col);
545
- return blobPtr && blobSize > 0
546
- ? blobToArray(heapBytes(blobPtr, blobSize))
547
- : [];
548
- }
549
- default:
550
- return null;
551
- }
552
- }
553
-
554
- function readQueryResult() {
555
- const colCount = api.resultColumnCount();
556
- const rowCount = api.resultRowCount();
557
-
558
- const columns = [];
559
- for (let i = 0; i < colCount; i++) {
560
- columns.push(api.resultColumnName(i));
561
- }
562
-
563
- const rows = [];
564
- for (let r = 0; r < rowCount; r++) {
565
- const row = [];
566
- for (let c = 0; c < colCount; c++) {
567
- row.push(readCellValue(r, c));
568
- }
569
- rows.push(row);
570
- }
571
-
572
- return { columns, rows };
573
- }
574
-
575
- function buildSizePrefixedStream(buffers) {
576
- let total = 0;
577
- for (const buffer of buffers) {
578
- total += 4 + buffer.length;
579
- }
580
-
581
- const stream = new Uint8Array(total);
582
- let offset = 0;
583
- for (const buffer of buffers) {
584
- new DataView(stream.buffer, offset, 4).setUint32(0, buffer.length, true);
585
- offset += 4;
586
- stream.set(buffer, offset);
587
- offset += buffer.length;
588
- }
589
- return stream;
590
- }
591
-
592
- // High-level FlatSQL API class
593
- export class FlatSQL {
594
- createDatabase(schema, dbName = 'default') {
595
- const handle = api.createDb(schema, dbName);
596
- return new FlatSQLDatabase(handle);
597
- }
598
-
599
- /**
600
- * Disk-backed open through the I/O backend passed to initFlatSQL.
601
- *
602
- * `path` is a name in the BACKEND's namespace — a real path under a WASI
603
- * preopen on the server, a key prefix in a browser store. The module never
604
- * learns which, which is what makes one artifact work in both.
605
- *
606
- * journalMode: 0 DELETE, 1 WAL (not available on wasm — no xShmMap in
607
- * either lane), 2 TRUNCATE (the wasm default, crash-safe under the single
608
- * writer the one-daemon-per-box law guarantees), 3 MEMORY.
609
- *
610
- * Async backends must be hydrated BEFORE this call and flushed after
611
- * flushIndex(); see wasm/flatsql-io.js.
612
- */
613
- openDatabase(schema, dbName = 'default', path = '', journalMode = 2) {
614
- const handle = api.openDb(schema, dbName, path ?? '', journalMode);
615
- if (!handle) {
616
- throw new Error(
617
- `Failed to open FlatSQL database at "${path}": ${api.getError() || 'unknown error'}`
618
- );
619
- }
620
- return new FlatSQLDatabase(handle);
621
- }
622
-
623
- // Create test FlatBuffers
624
- createTestUser(id, name, email, age) {
625
- const ptr = api.createTestUser(id, name, email, age);
626
- const size = api.testBufferSize();
627
- return copyHeapBytes(ptr, size);
628
- }
629
-
630
- createTestPost(id, userId, title) {
631
- const ptr = api.createTestPost(id, userId, title);
632
- const size = api.testBufferSize();
633
- return copyHeapBytes(ptr, size);
634
- }
635
-
636
- createTestPublishEvent(fileId, recordId, eventIndex, payloadSize) {
637
- const ptr = api.createTestPublishEvent(fileId, recordId, eventIndex, payloadSize);
638
- const size = api.testBufferSize();
639
- return copyHeapBytes(ptr, size);
640
- }
641
-
642
- createTestMPE(entityId, epoch, meanMotion, eccentricity, inclination,
643
- raOfAscNode, argOfPericenter, meanAnomaly, bstar, meanElementTheory = 0) {
644
- const ptr = api.createTestMPE(
645
- entityId,
646
- epoch,
647
- meanMotion,
648
- eccentricity,
649
- inclination,
650
- raOfAscNode,
651
- argOfPericenter,
652
- meanAnomaly,
653
- bstar,
654
- meanElementTheory
655
- );
656
- const size = api.testBufferSize();
657
- return copyHeapBytes(ptr, size);
658
- }
659
-
660
- createTestTelemetry(packetId, spacecraft, subsystem, mode, temperatureC, signalDb, timestampS) {
661
- const ptr = api.createTestTelemetry(packetId, spacecraft, subsystem, mode, temperatureC, signalDb, timestampS);
662
- const size = api.testBufferSize();
663
- return copyHeapBytes(ptr, size);
664
- }
665
-
666
- /**
667
- * Check if WASM was loaded with integrity verification
668
- * @returns {boolean}
669
- */
670
- wasIntegrityVerified() {
671
- return integrityVerified;
672
- }
673
-
674
- buildQueryCacheKey(dataset, artifactVersion, queryId, params = []) {
675
- const encodedParams = encodeQueryParams(params);
676
- const key = encodedParams.length === 0
677
- ? api.buildQueryCacheKey(dataset, artifactVersion, queryId, 0, 0, 0)
678
- : withHeapBytes(
679
- encodedParams,
680
- (ptr) => api.buildQueryCacheKey(dataset, artifactVersion, queryId, ptr, encodedParams.length, params.length)
681
- );
682
- if (!key) {
683
- throw new Error(api.getError());
684
- }
685
- return key;
686
- }
687
-
688
- buildResponseArtifactCacheKey(schemaName, schemaVersion, sql, options = {}) {
689
- const format = options.format ?? 'json';
690
- const publishEventKey = options.publishEventKey ?? '';
691
- const projectionList = (options.projection ?? []).join('\n');
692
- const params = options.params ?? [];
693
- const encodedParams = encodeQueryParams(params);
694
- const key = encodedParams.length === 0
695
- ? api.buildResponseArtifactCacheKey(
696
- schemaName,
697
- schemaVersion,
698
- sql,
699
- format,
700
- publishEventKey,
701
- projectionList,
702
- 0,
703
- 0,
704
- 0
705
- )
706
- : withHeapBytes(
707
- encodedParams,
708
- (ptr) => api.buildResponseArtifactCacheKey(
709
- schemaName,
710
- schemaVersion,
711
- sql,
712
- format,
713
- publishEventKey,
714
- projectionList,
715
- ptr,
716
- encodedParams.length,
717
- params.length
718
- )
719
- );
720
- if (!key) {
721
- throw new Error(api.getError());
722
- }
723
- return key;
724
- }
725
- }
726
-
727
- // Database wrapper class
728
- export class FlatSQLDatabase {
729
- constructor(handle) {
730
- this._handle = handle;
731
- }
732
-
733
- // ---- Durable state (docs/STORAGE-DURABILITY.md §3.3) ------------------
734
- // Codes are VALUES, never throws:
735
- // >=0 record count / OK -1 absent -2 format or schema mismatch
736
- // -3 corrupt -4 torn pair (stream shorter than the mark)
737
- // -5 no filesystem
738
- // Every negative is recoverable with reindexAll(); none means data loss.
739
-
740
- isDiskBacked() {
741
- return api.isDiskBacked(this._handle) === 1;
742
- }
743
-
744
- /** Boot: verify persisted state, replay only the stream tail. */
745
- openState() {
746
- return api.openState(this._handle);
747
- }
748
-
749
- /** Full re-derivation from the stream. Always available. */
750
- reindexAll() {
751
- return api.reindexAll(this._handle);
752
- }
753
-
754
- reindexStep(maxRecords = 4096) {
755
- if (!Number.isSafeInteger(maxRecords) || maxRecords <= 0 || maxRecords > 0x7fffffff) {
756
- throw new RangeError('maxRecords must be a positive 32-bit integer');
757
- }
758
- return api.reindexStep(this._handle, maxRecords);
759
- }
760
-
761
- /** Append new stream bytes, sync, then commit the index + high-water mark. */
762
- flushIndex() {
763
- return api.flushIndex(this._handle);
764
- }
765
-
766
- /** High-water mark a host may resume its own journal from. */
767
- flushedOffset() {
768
- return api.flushedOffset(this._handle);
769
- }
770
-
771
- /** Path of the SDS FlatBuffer stream backing this handle. */
772
- streamPath() {
773
- return api.streamPath(this._handle);
774
- }
775
-
776
- destroy() {
777
- if (this._handle) {
778
- api.destroyDb(this._handle);
779
- this._handle = null;
780
- }
781
- }
782
-
783
- registerFileId(fileId, tableName) {
784
- api.registerFileId(this._handle, fileId, tableName);
785
- }
786
-
787
- enableDemoExtractors() {
788
- api.enableDemoExtractors(this._handle);
789
- }
790
-
791
- // Ingest data from Uint8Array (routes to base tables or source tables)
792
- ingest(data, source = null) {
793
- const result = withHeapBytes(data, (ptr) => {
794
- if (source) {
795
- return api.ingestWithSource(this._handle, ptr, data.length, source);
796
- }
797
- return api.ingest(this._handle, ptr, data.length);
798
- });
799
- if (result < 0) throw new Error(api.getError());
800
- return result;
801
- }
802
-
803
- // Ingest many FlatBuffers via the native bulk stream path.
804
- ingestBuffers(buffers, source = null) {
805
- return this.ingest(buildSizePrefixedStream(buffers), source);
806
- }
807
-
808
- ingestOne(data, source = null) {
809
- const result = withHeapBytes(data, (ptr) => {
810
- if (source) {
811
- return api.ingestOneWithSource(this._handle, ptr, data.length, source);
812
- }
813
- return api.ingestOne(this._handle, ptr, data.length);
814
- });
815
- if (result < 0) throw new Error(api.getError());
816
- return result;
817
- }
818
-
819
- // Register a named data source for source-aware ingestion
820
- // Creates source-specific tables: User@siteA, Post@siteA, etc.
821
- registerSource(sourceName) {
822
- api.registerSource(this._handle, sourceName);
823
- }
824
-
825
- // Create unified views for cross-source queries
826
- // Must be called after registering all sources and file IDs
827
- createUnifiedViews() {
828
- api.createUnifiedViews(this._handle);
829
- }
830
-
831
- // List registered sources
832
- listSources() {
833
- const count = api.getSourcesCount(this._handle);
834
- const sources = [];
835
- for (let i = 0; i < count; i++) {
836
- sources.push(api.getSourceName(i));
837
- }
838
- return sources;
839
- }
840
-
841
- query(sql, params = undefined) {
842
- const success = params === undefined
843
- ? api.query(this._handle, sql)
844
- : (() => {
845
- const encodedParams = encodeQueryParams(params);
846
- if (encodedParams.length === 0) {
847
- return api.queryParams(this._handle, sql, 0, 0, 0);
848
- }
849
- return withHeapBytes(
850
- encodedParams,
851
- (ptr) => api.queryParams(this._handle, sql, ptr, encodedParams.length, params.length)
852
- );
853
- })();
854
- if (!success) {
855
- throw new Error(api.getError());
856
- }
857
- return readQueryResult();
858
- }
859
-
860
- registerQueryTemplate(queryId, sql, cacheable = true) {
861
- const success = api.registerQueryTemplate(this._handle, queryId, sql, cacheable ? 1 : 0);
862
- if (!success) {
863
- throw new Error(api.getError());
864
- }
865
- }
866
-
867
- queryTemplate(queryId, params = []) {
868
- const encodedParams = encodeQueryParams(params);
869
- const success = encodedParams.length === 0
870
- ? api.queryTemplate(this._handle, queryId, 0, 0, 0)
871
- : withHeapBytes(
872
- encodedParams,
873
- (ptr) => api.queryTemplate(this._handle, queryId, ptr, encodedParams.length, params.length)
874
- );
875
- if (!success) {
876
- throw new Error(api.getError());
877
- }
878
- return readQueryResult();
879
- }
880
-
881
- clearQueryCache() {
882
- api.clearQueryCache(this._handle);
883
- }
884
-
885
- configureQueryCache({ maxEntries, maxRows }) {
886
- if (!Number.isSafeInteger(maxEntries) || maxEntries <= 0) {
887
- throw new TypeError(`maxEntries must be a positive safe integer, received: ${maxEntries}`);
888
- }
889
- if (!Number.isSafeInteger(maxRows) || maxRows <= 0) {
890
- throw new TypeError(`maxRows must be a positive safe integer, received: ${maxRows}`);
891
- }
892
- const success = api.configureQueryCache(this._handle, maxEntries, maxRows);
893
- if (!success) {
894
- throw new Error(api.getError());
895
- }
896
- }
897
-
898
- getQueryCacheStats() {
899
- return {
900
- hits: api.queryCacheHits(this._handle),
901
- misses: api.queryCacheMisses(this._handle),
902
- size: api.queryCacheSize(this._handle),
903
- generation: api.queryCacheGeneration(this._handle),
904
- maxEntries: api.queryCacheMaxEntries(this._handle),
905
- maxRows: api.queryCacheMaxRows(this._handle)
906
- };
907
- }
908
-
909
- queryMany(queries) {
910
- if (queries.length === 0) {
911
- return [];
912
- }
913
-
914
- const encodedRequests = encodeQueryRequests(queries);
915
- const success = withHeapBytes(
916
- encodedRequests,
917
- (ptr) => api.queryMany(this._handle, ptr, encodedRequests.length, queries.length)
918
- );
919
- if (!success) {
920
- throw new Error(api.getError());
921
- }
922
-
923
- const resultCount = api.batchResultCount();
924
- const results = [];
925
- for (let index = 0; index < resultCount; index++) {
926
- if (!api.selectBatchResult(index)) {
927
- throw new Error(`Failed to select batch result ${index}`);
928
- }
929
- results.push(readQueryResult());
930
- }
931
- return results;
932
- }
933
-
934
- queryRawFlatBufferStream(sql, params = []) {
935
- const encodedParams = encodeQueryParams(params);
936
- const success = encodedParams.length === 0
937
- ? api.queryRawFlatBufferStream(this._handle, sql, 0, 0, 0)
938
- : withHeapBytes(
939
- encodedParams,
940
- (ptr) => api.queryRawFlatBufferStream(this._handle, sql, ptr, encodedParams.length, params.length)
941
- );
942
- if (!success) {
943
- throw new Error(api.getError());
944
- }
945
-
946
- const ptr = api.responseArtifactData();
947
- const size = api.responseArtifactSize();
948
- return ptr && size > 0 ? copyHeapBytes(ptr, size) : new Uint8Array();
949
- }
950
-
951
- // Sandboxed public query (gateway loop G.5): one read-only SELECT under
952
- // the engine authorizer (record tables / shadow tables / unified views
953
- // only), single-statement, statement timeout, row/byte caps. options:
954
- // { mode: "stream" | "json", maxRows, maxBytes, timeoutMs }. Returns
955
- // { payload: Uint8Array, rows, columns } — payload is the aligned frame
956
- // stream (mode "stream") or UTF-8 bare-array JSON bytes (mode "json").
957
- // Sandbox rejections throw with the engine's "sandbox: <code>: ..." text.
958
- querySandboxed(sql, params = [], options = {}) {
959
- const encodedParams = encodeQueryParams(params);
960
- const mode = options.mode === 'json' ? 1 : 0;
961
- const maxRows = options.maxRows > 0 ? options.maxRows : 0;
962
- const maxBytes = options.maxBytes > 0 ? options.maxBytes : 0;
963
- const timeoutMs = options.timeoutMs > 0 ? options.timeoutMs : 0;
964
- const success = encodedParams.length === 0
965
- ? api.querySandboxed(this._handle, sql, 0, 0, 0, mode, maxRows, maxBytes, timeoutMs)
966
- : withHeapBytes(
967
- encodedParams,
968
- (ptr) => api.querySandboxed(this._handle, sql, ptr, encodedParams.length, params.length, mode, maxRows, maxBytes, timeoutMs)
969
- );
970
- if (!success) {
971
- throw new Error(api.getError());
972
- }
973
-
974
- const ptr = api.responseArtifactData();
975
- const size = api.responseArtifactSize();
976
- return {
977
- payload: ptr && size > 0 ? copyHeapBytes(ptr, size) : new Uint8Array(),
978
- rows: api.responseArtifactRowCount(),
979
- columns: api.responseArtifactColumnCount(),
980
- };
981
- }
982
-
983
- // True when the last queryRawFlatBufferStream call was served from the
984
- // engine's response-artifact cache (no SQL re-execution).
985
- lastRawStreamCacheHit() {
986
- return api.responseArtifactCacheHit() !== 0;
987
- }
988
-
989
- configureRawStreamCache(maxEntries, maxTotalBytes) {
990
- if (!api.configureRawStreamCache(this._handle, maxEntries, maxTotalBytes)) {
991
- throw new Error(api.getError());
992
- }
993
- }
994
-
995
- getRawStreamCacheStats() {
996
- return {
997
- hits: api.rawStreamCacheHits(this._handle),
998
- misses: api.rawStreamCacheMisses(this._handle),
999
- entries: api.rawStreamCacheSize(this._handle),
1000
- totalBytes: api.rawStreamCacheTotalBytes(this._handle)
1001
- };
1002
- }
1003
-
1004
- exportData() {
1005
- const ptr = api.exportData(this._handle);
1006
- const error = api.getError();
1007
- if (error) throw new Error(error);
1008
- const size = api.exportSize();
1009
- return copyHeapBytes(ptr, size);
1010
- }
1011
-
1012
- loadAndRebuild(data) {
1013
- const ptr = Module._malloc(data.length);
1014
- setHeapBytes(data, ptr);
1015
- api.loadAndRebuild(this._handle, ptr, data.length);
1016
- Module._free(ptr);
1017
- }
1018
-
1019
- reserveStorageBytes(bytes) {
1020
- api.reserveStorage(this._handle, bytes);
1021
- }
1022
-
1023
- loadAndRebuildFrom(sourceDb) {
1024
- if (!sourceDb?._handle) {
1025
- throw new TypeError('loadAndRebuildFrom requires a FlatSQL WASM database.');
1026
- }
1027
- api.loadFromDb(this._handle, sourceDb._handle);
1028
- }
1029
-
1030
- getStats() {
1031
- const count = api.getStatsCount(this._handle);
1032
- const stats = [];
1033
- for (let i = 0; i < count; i++) {
1034
- stats.push({
1035
- tableName: api.getStatTableName(i),
1036
- fileId: api.getStatFileId(i),
1037
- recordCount: api.getStatRecordCount(i)
1038
- });
1039
- }
1040
- return stats;
1041
- }
1042
-
1043
- resetIngestProfile() {
1044
- api.resetIngestProfile(this._handle);
1045
- }
1046
-
1047
- getIngestProfile() {
1048
- return {
1049
- recordCount: api.getIngestProfileRecordCount(this._handle),
1050
- byteCount: api.getIngestProfileByteCount(this._handle),
1051
- decodeNanos: api.getIngestProfileDecodeNanos(this._handle),
1052
- appendNanos: api.getIngestProfileAppendNanos(this._handle),
1053
- indexNanos: api.getIngestProfileIndexNanos(this._handle),
1054
- };
1055
- }
1056
-
1057
- markDeleted(tableName, sequence) {
1058
- api.markDeleted(this._handle, tableName, sequence);
1059
- }
1060
-
1061
- getDeletedCount(tableName) {
1062
- return api.getDeletedCount(this._handle, tableName);
1063
- }
1064
-
1065
- clearTombstones(tableName) {
1066
- api.clearTombstones(this._handle, tableName);
1067
- }
1068
-
1069
- getFlatBufferById(tableName, id) {
1070
- const ptr = api.getFlatBufferById(this._handle, tableName, id);
1071
- if (!ptr) {
1072
- return null;
1073
- }
1074
- return {
1075
- ptr,
1076
- size: api.getRawFlatBufferSize(),
1077
- sequence: api.getRawFlatBufferSequence()
1078
- };
1079
- }
1080
-
1081
- getFlatBufferByEmail(tableName, email) {
1082
- const ptr = api.getFlatBufferByEmail(this._handle, tableName, email);
1083
- if (!ptr) {
1084
- return null;
1085
- }
1086
- return {
1087
- ptr,
1088
- size: api.getRawFlatBufferSize(),
1089
- sequence: api.getRawFlatBufferSequence()
1090
- };
1091
- }
1092
-
1093
- getFlatBufferByIndex(tableName, columnName, value) {
1094
- const encodedParams = encodeQueryParams([value]);
1095
- const ptr = withHeapBytes(
1096
- encodedParams,
1097
- (paramPtr) => api.getFlatBufferByIndex(
1098
- this._handle,
1099
- tableName,
1100
- columnName,
1101
- paramPtr,
1102
- encodedParams.length,
1103
- 1
1104
- )
1105
- );
1106
- if (!ptr) {
1107
- const error = api.getError();
1108
- if (error) {
1109
- throw new Error(error);
1110
- }
1111
- return null;
1112
- }
1113
- return {
1114
- ptr,
1115
- size: api.getRawFlatBufferSize(),
1116
- sequence: api.getRawFlatBufferSequence()
1117
- };
1118
- }
1119
-
1120
- getFlatBufferDataById(tableName, id) {
1121
- const record = this.getFlatBufferById(tableName, id);
1122
- if (!record) {
1123
- return null;
1124
- }
1125
- return copyHeapBytes(record.ptr, record.size);
1126
- }
1127
-
1128
- getFlatBufferDataByIndex(tableName, columnName, value) {
1129
- const record = this.getFlatBufferByIndex(tableName, columnName, value);
1130
- if (!record) {
1131
- return null;
1132
- }
1133
- return copyHeapBytes(record.ptr, record.size);
1134
- }
1135
-
1136
- getStorageInfo() {
1137
- return {
1138
- ptr: api.getStorageBuffer(this._handle),
1139
- size: api.getStorageSize(this._handle)
1140
- };
1141
- }
1142
-
1143
- // ==================== Encryption API ====================
1144
- //
1145
- // The (encrypted) fields of a record are encrypted under FlatBuffers field
1146
- // encryption format 3: each record under its own key, with its sequence
1147
- // (rowid) as the record index, so no two records share a key stream.
1148
-
1149
- /**
1150
- * Set the database key. SQL decrypts the (encrypted) columns with it.
1151
- * @param {Uint8Array} key - 32-byte AES-256 key
1152
- * @param {{format?: number}} [options] - format: the field-encryption format
1153
- * of the records the database already holds in tables with (encrypted)
1154
- * columns. Required (3) when it holds any. Format 2 (records encrypted by
1155
- * flatsql 3.2.0 or earlier) is refused: see README "Database-key encryption".
1156
- */
1157
- setEncryptionKey(key, options = {}) {
1158
- const format = options && options.format !== undefined ? options.format : 0;
1159
- if (!Number.isInteger(format) || format < 0 || format > 255) {
1160
- throw new TypeError('setEncryptionKey: options.format must be 3');
1161
- }
1162
- const result = withHeapBytes(key, (ptr) =>
1163
- api.setEncryptionKey(this._handle, ptr, key.length, format));
1164
- if (!result) throw new Error(api.getError());
1165
- }
1166
-
1167
- /**
1168
- * Check if encryption is enabled.
1169
- * @returns {boolean}
1170
- */
1171
- isEncrypted() {
1172
- return api.isEncrypted(this._handle) !== 0;
1173
- }
1174
-
1175
- /**
1176
- * Encrypt the (encrypted) fields of a FlatBuffer as the record with index
1177
- * `recordIndex`: the sequence it has, or gets, in this database. To write
1178
- * encrypted records use ingestOneEncrypted, which assigns and uses the
1179
- * sequence together.
1180
- * @param {Uint8Array} buffer - FlatBuffer data (no size prefix)
1181
- * @param {Uint8Array} schema - Binary schema (.bfbs) with builtin attributes (flatc --bfbs-builtins)
1182
- * @param {number} recordIndex - The record's sequence (1 to 4294967295)
1183
- * @returns {Uint8Array} Encrypted buffer (copy)
1184
- */
1185
- encryptBuffer(buffer, schema, recordIndex) {
1186
- requireRecordIndex('encryptBuffer', recordIndex);
1187
- return this._cipherBuffer(api.encryptBuffer, buffer, schema, recordIndex);
1188
- }
1189
-
1190
- /**
1191
- * Decrypt the (encrypted) fields of a stored FlatBuffer.
1192
- * @param {Uint8Array} buffer - Encrypted FlatBuffer data (no size prefix)
1193
- * @param {Uint8Array} schema - Binary schema (.bfbs) with builtin attributes (flatc --bfbs-builtins)
1194
- * @param {number} recordIndex - The record's sequence (1 to 4294967295)
1195
- * @returns {Uint8Array} Decrypted buffer (copy)
1196
- */
1197
- decryptBuffer(buffer, schema, recordIndex) {
1198
- requireRecordIndex('decryptBuffer', recordIndex);
1199
- return this._cipherBuffer(api.decryptBuffer, buffer, schema, recordIndex);
1200
- }
1201
-
1202
- _cipherBuffer(cipher, buffer, schema, recordIndex) {
1203
- const bufPtr = Module._malloc(buffer.length);
1204
- const schemaPtr = Module._malloc(schema.length);
1205
- try {
1206
- setHeapBytes(buffer, bufPtr);
1207
- setHeapBytes(schema, schemaPtr);
1208
- const result = cipher(this._handle, bufPtr, buffer.length, schemaPtr, schema.length, recordIndex);
1209
- if (!result) throw new Error(api.getError());
1210
- return copyHeapBytes(bufPtr, buffer.length);
1211
- } finally {
1212
- Module._free(bufPtr);
1213
- Module._free(schemaPtr);
1214
- }
1215
- }
1216
-
1217
- /**
1218
- * Encrypt a plaintext FlatBuffer under the sequence it gets and ingest it.
1219
- * @param {Uint8Array} data - FlatBuffer data (no size prefix)
1220
- * @param {Uint8Array} schema - Binary schema (.bfbs) with builtin attributes (flatc --bfbs-builtins)
1221
- * @param {string|null} [source] - Source name, as for ingestOne
1222
- * @returns {number} The record's sequence
1223
- */
1224
- ingestOneEncrypted(data, schema, source = null) {
1225
- const result = withHeapBytes(data, (dataPtr) =>
1226
- withHeapBytes(schema, (schemaPtr) =>
1227
- api.ingestOneEncrypted(this._handle, dataPtr, data.length, schemaPtr, schema.length, source)));
1228
- if (result < 0) throw new Error(api.getError());
1229
- return result;
1230
- }
1231
-
1232
- // ==================== HMAC Authentication API ====================
1233
-
1234
- /**
1235
- * Enable or disable HMAC verification on ingest.
1236
- * Requires an encryption key to be set first.
1237
- * @param {boolean} enabled
1238
- */
1239
- setHMACVerification(enabled) {
1240
- const result = api.setHMACVerification(this._handle, enabled ? 1 : 0);
1241
- if (!result) throw new Error(api.getError());
1242
- }
1243
-
1244
- /**
1245
- * Check if HMAC verification is enabled.
1246
- * @returns {boolean}
1247
- */
1248
- isHMACEnabled() {
1249
- return api.isHMACEnabled(this._handle) !== 0;
1250
- }
1251
-
1252
- /**
1253
- * Compute HMAC-SHA256 for a FlatBuffer.
1254
- * @param {Uint8Array} buffer - FlatBuffer data
1255
- * @returns {Uint8Array} 32-byte HMAC
1256
- */
1257
- computeHMAC(buffer) {
1258
- const bufPtr = Module._malloc(buffer.length);
1259
- setHeapBytes(buffer, bufPtr);
1260
- const macPtr = Module._malloc(32);
1261
- const result = api.computeHMAC(this._handle, bufPtr, buffer.length, macPtr);
1262
- const mac = copyHeapBytes(macPtr, 32);
1263
- Module._free(bufPtr);
1264
- Module._free(macPtr);
1265
- if (!result) throw new Error('HMAC computation failed - is encryption key set?');
1266
- return mac;
1267
- }
1268
-
1269
- /**
1270
- * Verify HMAC-SHA256 for a FlatBuffer.
1271
- * @param {Uint8Array} buffer - FlatBuffer data
1272
- * @param {Uint8Array} mac - 32-byte HMAC to verify
1273
- * @returns {boolean} true if valid
1274
- */
1275
- verifyHMAC(buffer, mac) {
1276
- const bufPtr = Module._malloc(buffer.length);
1277
- setHeapBytes(buffer, bufPtr);
1278
- const macPtr = Module._malloc(32);
1279
- setHeapBytes(mac, macPtr);
1280
- const result = api.verifyHMAC(this._handle, bufPtr, buffer.length, macPtr);
1281
- Module._free(bufPtr);
1282
- Module._free(macPtr);
1283
- return result !== 0;
1284
- }
271
+ if (queued && !finished && !this.#host.faulted()) {
272
+ active.cancel();
273
+ // A consumer that breaks early must release ring space until the lane
274
+ // acknowledges cancellation before this slot can be reused.
275
+ while (Atomics.load(this.#i32, state) !== 5 && !this.#host.faulted()) { read(); await pause(); }
276
+ }
277
+ if (!this.#host.faulted()) Atomics.store(this.#i32, state, 0);
278
+ this.#active.delete(active); done();
279
+ }
280
+ }
281
+ async close() {
282
+ if (this.#closed) return;
283
+ this.#closed = true;
284
+ for (const operation of this.#active) operation.cancel();
285
+ await Promise.allSettled([...this.#active].map(operation => operation.finish()));
286
+ await Promise.all([...this.#active].map(operation => operation.completion));
287
+ try { if (!this.#host.faulted()) check(await this.#host.call('flatsql_stop', 5000)); }
288
+ finally { await this.#host.close(); }
289
+ }
1285
290
  }
1286
-
1287
- // The partition store artifact (flatsql-ps-threads.wasm): its URL, bytes and
1288
- // integrity (docs/PARTITION-STORE-WASM.md).
1289
- export * from './ps.js';
1290
-
1291
- export default initFlatSQL;