flatsql 3.11.1 → 3.12.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.
- package/README.md +61 -795
- package/bin/flatsql.mjs +76 -0
- package/cpp/README.md +18 -0
- package/cpp/include/flatsql/flatsql.h +162 -0
- package/cpp/include/flatsql/flatsql_io.h +159 -0
- package/docs/API-3.12.md +114 -0
- package/package.json +28 -123
- package/wasm/flatsql.wasm +0 -0
- package/wasm/index.d.ts +61 -548
- package/wasm/index.js +280 -1281
- package/wasm/integrity.json +6 -26
- package/wasm/runtime/LICENSE.sdk.txt +190 -0
- package/wasm/runtime/NOTICE.txt +2 -0
- package/wasm/runtime/browser-guest.mjs +1 -0
- package/wasm/runtime/browser-host.mjs +1 -0
- package/wasm/runtime/node-guest.mjs +2 -0
- package/wasm/runtime/node-host.mjs +5 -0
- package/wasm/runtime/opfsIoWorker.mjs +1 -0
- package/wasm/runtime/wasiThreadBrowserWorker.mjs +1 -0
- package/dist/artifacts/index.d.ts +0 -3
- package/dist/artifacts/index.d.ts.map +0 -1
- package/dist/artifacts/index.js +0 -3
- package/dist/artifacts/index.js.map +0 -1
- package/dist/artifacts/transport.d.ts +0 -5
- package/dist/artifacts/transport.d.ts.map +0 -1
- package/dist/artifacts/transport.js +0 -45
- package/dist/artifacts/transport.js.map +0 -1
- package/dist/artifacts/types.d.ts +0 -34
- package/dist/artifacts/types.d.ts.map +0 -1
- package/dist/artifacts/types.js +0 -2
- package/dist/artifacts/types.js.map +0 -1
- package/dist/btree/btree.d.ts +0 -34
- package/dist/btree/btree.d.ts.map +0 -1
- package/dist/btree/btree.js +0 -234
- package/dist/btree/btree.js.map +0 -1
- package/dist/btree/index.d.ts +0 -3
- package/dist/btree/index.d.ts.map +0 -1
- package/dist/btree/index.js +0 -3
- package/dist/btree/index.js.map +0 -1
- package/dist/btree/types.d.ts +0 -36
- package/dist/btree/types.d.ts.map +0 -1
- package/dist/btree/types.js +0 -55
- package/dist/btree/types.js.map +0 -1
- package/dist/cluster/index.d.ts +0 -11
- package/dist/cluster/index.d.ts.map +0 -1
- package/dist/cluster/index.js +0 -59
- package/dist/cluster/index.js.map +0 -1
- package/dist/response/artifact.d.ts +0 -4
- package/dist/response/artifact.d.ts.map +0 -1
- package/dist/response/artifact.js +0 -102
- package/dist/response/artifact.js.map +0 -1
- package/dist/response/cache-key.d.ts +0 -6
- package/dist/response/cache-key.d.ts.map +0 -1
- package/dist/response/cache-key.js +0 -114
- package/dist/response/cache-key.js.map +0 -1
- package/dist/response/index.d.ts +0 -5
- package/dist/response/index.d.ts.map +0 -1
- package/dist/response/index.js +0 -5
- package/dist/response/index.js.map +0 -1
- package/dist/response/memory-cache.d.ts +0 -17
- package/dist/response/memory-cache.d.ts.map +0 -1
- package/dist/response/memory-cache.js +0 -76
- package/dist/response/memory-cache.js.map +0 -1
- package/dist/response/types.d.ts +0 -57
- package/dist/response/types.d.ts.map +0 -1
- package/dist/response/types.js +0 -2
- package/dist/response/types.js.map +0 -1
- package/dist/schema/index.d.ts +0 -3
- package/dist/schema/index.d.ts.map +0 -1
- package/dist/schema/index.js +0 -3
- package/dist/schema/index.js.map +0 -1
- package/dist/schema/parser.d.ts +0 -25
- package/dist/schema/parser.d.ts.map +0 -1
- package/dist/schema/parser.js +0 -474
- package/dist/schema/parser.js.map +0 -1
- package/dist/schema/types.d.ts +0 -33
- package/dist/schema/types.d.ts.map +0 -1
- package/dist/schema/types.js +0 -29
- package/dist/schema/types.js.map +0 -1
- package/dist/standalone/index.d.ts +0 -3
- package/dist/standalone/index.d.ts.map +0 -1
- package/dist/standalone/index.js +0 -3
- package/dist/standalone/index.js.map +0 -1
- package/dist/standalone/process-client.d.ts +0 -85
- package/dist/standalone/process-client.d.ts.map +0 -1
- package/dist/standalone/process-client.js +0 -537
- package/dist/standalone/process-client.js.map +0 -1
- package/dist/standalone/wasmedge-runner.d.ts +0 -19
- package/dist/standalone/wasmedge-runner.d.ts.map +0 -1
- package/dist/standalone/wasmedge-runner.js +0 -108
- package/dist/standalone/wasmedge-runner.js.map +0 -1
- package/dist/storage/index.d.ts +0 -3
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js +0 -3
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/stacked-flatbuffers.d.ts +0 -38
- package/dist/storage/stacked-flatbuffers.d.ts.map +0 -1
- package/dist/storage/stacked-flatbuffers.js +0 -253
- package/dist/storage/stacked-flatbuffers.js.map +0 -1
- package/dist/storage/types.d.ts +0 -23
- package/dist/storage/types.d.ts.map +0 -1
- package/dist/storage/types.js +0 -27
- package/dist/storage/types.js.map +0 -1
- package/dist/stress/aggregate.d.ts +0 -3
- package/dist/stress/aggregate.d.ts.map +0 -1
- package/dist/stress/aggregate.js +0 -61
- package/dist/stress/aggregate.js.map +0 -1
- package/dist/stress/docker-compose.d.ts +0 -17
- package/dist/stress/docker-compose.d.ts.map +0 -1
- package/dist/stress/docker-compose.js +0 -156
- package/dist/stress/docker-compose.js.map +0 -1
- package/dist/stress/index.d.ts +0 -10
- package/dist/stress/index.d.ts.map +0 -1
- package/dist/stress/index.js +0 -10
- package/dist/stress/index.js.map +0 -1
- package/dist/stress/metrics.d.ts +0 -3
- package/dist/stress/metrics.d.ts.map +0 -1
- package/dist/stress/metrics.js +0 -83
- package/dist/stress/metrics.js.map +0 -1
- package/dist/stress/native-publish-event-workload.d.ts +0 -3
- package/dist/stress/native-publish-event-workload.d.ts.map +0 -1
- package/dist/stress/native-publish-event-workload.js +0 -465
- package/dist/stress/native-publish-event-workload.js.map +0 -1
- package/dist/stress/production-gate.d.ts +0 -4
- package/dist/stress/production-gate.d.ts.map +0 -1
- package/dist/stress/production-gate.js +0 -12
- package/dist/stress/production-gate.js.map +0 -1
- package/dist/stress/report.d.ts +0 -3
- package/dist/stress/report.d.ts.map +0 -1
- package/dist/stress/report.js +0 -174
- package/dist/stress/report.js.map +0 -1
- package/dist/stress/sds-discovery.d.ts +0 -4
- package/dist/stress/sds-discovery.d.ts.map +0 -1
- package/dist/stress/sds-discovery.js +0 -40
- package/dist/stress/sds-discovery.js.map +0 -1
- package/dist/stress/smoke-runner.d.ts +0 -3
- package/dist/stress/smoke-runner.d.ts.map +0 -1
- package/dist/stress/smoke-runner.js +0 -64
- package/dist/stress/smoke-runner.js.map +0 -1
- package/dist/stress/types.d.ts +0 -219
- package/dist/stress/types.d.ts.map +0 -1
- package/dist/stress/types.js +0 -2
- package/dist/stress/types.js.map +0 -1
- package/dist/stress/workload.d.ts +0 -6
- package/dist/stress/workload.d.ts.map +0 -1
- package/dist/stress/workload.js +0 -144
- package/dist/stress/workload.js.map +0 -1
- package/docs/PARTITION-STORE-WASM.md +0 -338
- package/docs/PARTITION-STORE.md +0 -2380
- package/docs/agents/response-artifacts.md +0 -65
- package/docs/response-artifacts.md +0 -86
- package/scripts/stress-docker.sh +0 -279
- package/sdm/flatsql-spatial.wasm +0 -0
- package/sdm/index.d.ts +0 -88
- package/sdm/index.js +0 -253
- package/sdm/manifest.json +0 -402
- package/stress/docker/Dockerfile +0 -11
- package/stress/docker/docker-compose.yml +0 -51
- package/stress/sds/aggregate.mjs +0 -97
- package/stress/sds/run.mjs +0 -105
- package/stress/sds/write-compose.mjs +0 -96
- package/wasm/flatsql-client.js +0 -280
- package/wasm/flatsql-io.d.ts +0 -97
- package/wasm/flatsql-io.js +0 -625
- package/wasm/flatsql-p4-threads.wasm +0 -0
- package/wasm/flatsql-ps-threads.wasm +0 -0
- package/wasm/flatsql-wasi-noeh.wasm +0 -0
- package/wasm/flatsql-wasi.wasm +0 -0
- package/wasm/flatsql.js +0 -2
- package/wasm/flatsql.worker.js +0 -209
- package/wasm/native/flatsql_wasmedge_runner.cpp +0 -858
- package/wasm/ps-node-fault.d.mts +0 -23
- package/wasm/ps-node-fault.mjs +0 -381
- package/wasm/ps-node-guest.mjs +0 -173
- package/wasm/ps-node-host.d.mts +0 -95
- package/wasm/ps-node-host.mjs +0 -476
- package/wasm/ps-node-imports.mjs +0 -847
- package/wasm/ps.d.ts +0 -21
- package/wasm/ps.js +0 -75
- package/wasm/wasi.d.ts +0 -11
- package/wasm/wasi.js +0 -51
package/wasm/index.js
CHANGED
|
@@ -1,1291 +1,290 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
32
|
-
if
|
|
33
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
441
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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;
|