flatsql 2.0.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +76 -64
  3. package/dist/artifacts/index.d.ts +1 -3
  4. package/dist/artifacts/index.d.ts.map +1 -1
  5. package/dist/artifacts/index.js +1 -3
  6. package/dist/artifacts/index.js.map +1 -1
  7. package/dist/response/artifact.d.ts +1 -2
  8. package/dist/response/artifact.d.ts.map +1 -1
  9. package/dist/response/artifact.js.map +1 -1
  10. package/dist/response/types.d.ts +6 -0
  11. package/dist/response/types.d.ts.map +1 -1
  12. package/dist/stress/index.d.ts +0 -1
  13. package/dist/stress/index.d.ts.map +1 -1
  14. package/dist/stress/index.js +0 -1
  15. package/dist/stress/index.js.map +1 -1
  16. package/dist/stress/native-publish-event-workload.d.ts.map +1 -1
  17. package/dist/stress/native-publish-event-workload.js +14 -4
  18. package/dist/stress/native-publish-event-workload.js.map +1 -1
  19. package/dist/stress/smoke-runner.d.ts.map +1 -1
  20. package/dist/stress/smoke-runner.js +6 -5
  21. package/dist/stress/smoke-runner.js.map +1 -1
  22. package/docs/PARTITION-STORE-WASM.md +241 -0
  23. package/docs/PARTITION-STORE.md +635 -0
  24. package/package.json +61 -22
  25. package/wasm/flatsql-io.js +4 -1
  26. package/wasm/flatsql-ps-threads.wasm +0 -0
  27. package/wasm/flatsql-wasi-noeh.wasm +0 -0
  28. package/wasm/flatsql-wasi.wasm +0 -0
  29. package/wasm/flatsql.js +1 -1
  30. package/wasm/flatsql.wasm +0 -0
  31. package/wasm/index.d.ts +2 -0
  32. package/wasm/index.js +16 -1
  33. package/wasm/integrity.json +28 -0
  34. package/wasm/ps-node-fault.d.mts +23 -0
  35. package/wasm/ps-node-fault.mjs +381 -0
  36. package/wasm/ps-node-guest.mjs +156 -0
  37. package/wasm/ps-node-host.d.mts +86 -0
  38. package/wasm/ps-node-host.mjs +431 -0
  39. package/wasm/ps-node-imports.mjs +847 -0
  40. package/wasm/ps.d.ts +21 -0
  41. package/wasm/ps.js +75 -0
  42. package/dist/artifacts/builder.d.ts +0 -31
  43. package/dist/artifacts/builder.d.ts.map +0 -1
  44. package/dist/artifacts/builder.js +0 -514
  45. package/dist/artifacts/builder.js.map +0 -1
  46. package/dist/artifacts/demo-extractors.d.ts +0 -15
  47. package/dist/artifacts/demo-extractors.d.ts.map +0 -1
  48. package/dist/artifacts/demo-extractors.js +0 -238
  49. package/dist/artifacts/demo-extractors.js.map +0 -1
  50. package/dist/artifacts/standalone-builder.d.ts +0 -62
  51. package/dist/artifacts/standalone-builder.d.ts.map +0 -1
  52. package/dist/artifacts/standalone-builder.js +0 -144
  53. package/dist/artifacts/standalone-builder.js.map +0 -1
  54. package/dist/artifacts/worker-client.d.ts +0 -32
  55. package/dist/artifacts/worker-client.d.ts.map +0 -1
  56. package/dist/artifacts/worker-client.js +0 -415
  57. package/dist/artifacts/worker-client.js.map +0 -1
  58. package/dist/core/database.d.ts +0 -86
  59. package/dist/core/database.d.ts.map +0 -1
  60. package/dist/core/database.js +0 -900
  61. package/dist/core/database.js.map +0 -1
  62. package/dist/core/index.d.ts +0 -3
  63. package/dist/core/index.d.ts.map +0 -1
  64. package/dist/core/index.js +0 -3
  65. package/dist/core/index.js.map +0 -1
  66. package/dist/core/table-store.d.ts +0 -30
  67. package/dist/core/table-store.d.ts.map +0 -1
  68. package/dist/core/table-store.js +0 -187
  69. package/dist/core/table-store.js.map +0 -1
  70. package/dist/flatbuffer-accessor.d.ts +0 -35
  71. package/dist/flatbuffer-accessor.d.ts.map +0 -1
  72. package/dist/flatbuffer-accessor.js +0 -114
  73. package/dist/flatbuffer-accessor.js.map +0 -1
  74. package/dist/index.d.ts +0 -15
  75. package/dist/index.d.ts.map +0 -1
  76. package/dist/index.js +0 -22
  77. package/dist/index.js.map +0 -1
  78. package/dist/stress/publish-event-workload.d.ts +0 -3
  79. package/dist/stress/publish-event-workload.d.ts.map +0 -1
  80. package/dist/stress/publish-event-workload.js +0 -467
  81. package/dist/stress/publish-event-workload.js.map +0 -1
  82. package/wasm/flatsql-artifact.worker.js +0 -847
  83. package/wasm/standalone.d.ts +0 -122
  84. package/wasm/standalone.js +0 -914
@@ -1,847 +0,0 @@
1
- import { availableParallelism } from 'node:os';
2
- import { parentPort } from 'node:worker_threads';
3
- import { DatabaseSync } from 'node:sqlite';
4
-
5
- const decoder = new TextDecoder();
6
- const builders = new Map();
7
- const INSERT_BATCH_SIZE = 64;
8
- const DEFAULT_PAGE_SIZE = 32768;
9
- const DEFAULT_MMAP_SIZE = 268435456;
10
- const DEFAULT_CACHE_SIZE = -131072;
11
- const DEFAULT_THREAD_COUNT = Math.min(4, Math.max(2, availableParallelism()));
12
- const MAX_QUERY_STATEMENT_CACHE_ENTRIES = 256;
13
-
14
- function isCacheableQuerySql(sql) {
15
- const trimmed = sql.trimStart();
16
- if (trimmed.length === 0) {
17
- return false;
18
- }
19
-
20
- const upper = trimmed.toUpperCase();
21
- if (upper.startsWith('SELECT') || upper.startsWith('WITH')) {
22
- return true;
23
- }
24
-
25
- return upper.startsWith('PRAGMA') && !trimmed.includes('=');
26
- }
27
-
28
- function executeStatementAll(statement, params) {
29
- if (params === undefined) {
30
- return statement.all();
31
- }
32
-
33
- if (Array.isArray(params)) {
34
- switch (params.length) {
35
- case 0:
36
- return statement.all();
37
- case 1:
38
- return statement.all(params[0]);
39
- case 2:
40
- return statement.all(params[0], params[1]);
41
- case 3:
42
- return statement.all(params[0], params[1], params[2]);
43
- case 4:
44
- return statement.all(params[0], params[1], params[2], params[3]);
45
- default:
46
- return statement.all(...params);
47
- }
48
- }
49
-
50
- return statement.all(params);
51
- }
52
-
53
- function getCachedStatement(cache, key) {
54
- const cached = cache.get(key);
55
- if (!cached) {
56
- return undefined;
57
- }
58
- cache.delete(key);
59
- cache.set(key, cached);
60
- return cached;
61
- }
62
-
63
- function setCachedStatement(cache, key, value) {
64
- cache.set(key, value);
65
- while (cache.size > MAX_QUERY_STATEMENT_CACHE_ENTRIES) {
66
- const oldest = cache.keys().next().value;
67
- if (oldest === undefined) {
68
- break;
69
- }
70
- cache.delete(oldest);
71
- }
72
- }
73
-
74
- function runQuery(builder, sql, params) {
75
- const cacheable = isCacheableQuerySql(sql);
76
- let cached = cacheable ? getCachedStatement(builder.queryCache, sql) : undefined;
77
-
78
- if (!cached) {
79
- const statement = builder.db.prepare(sql);
80
- const arrayMode = typeof statement.setReturnArrays === 'function';
81
- if (arrayMode) {
82
- statement.setReturnArrays(true);
83
- }
84
- const columns = statement.columns().map((column) => column.name);
85
- cached = { statement, columns, arrayMode };
86
- if (cacheable) {
87
- setCachedStatement(builder.queryCache, sql, cached);
88
- }
89
- }
90
-
91
- const rawRows = executeStatementAll(cached.statement, params);
92
- const rows = cached.arrayMode ? rawRows : normalizeRows(rawRows, cached.columns);
93
- return { columns: [...cached.columns], rows, rowCount: rows.length };
94
- }
95
-
96
- function sqliteType(column) {
97
- return column.sqlType ?? 'BLOB';
98
- }
99
-
100
- function createState(data) {
101
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
102
- const root = view.getUint32(0, true);
103
- const vtable = root - view.getInt32(root, true);
104
- return {
105
- data,
106
- view,
107
- root,
108
- vtable,
109
- vtableSize: view.getUint16(vtable, true),
110
- };
111
- }
112
-
113
- function getFieldOffset(view, vtable, vtableSize, fieldIndex) {
114
- const entryOffset = vtable + 4 + fieldIndex * 2;
115
- if (entryOffset + 2 > vtable + vtableSize) {
116
- return 0;
117
- }
118
- return view.getUint16(entryOffset, true);
119
- }
120
-
121
- function readInt32Field(view, root, vtable, vtableSize, fieldIndex) {
122
- const fieldOffset = getFieldOffset(view, vtable, vtableSize, fieldIndex);
123
- return fieldOffset === 0 ? 0 : view.getInt32(root + fieldOffset, true);
124
- }
125
-
126
- function readStringField(data, view, root, vtable, vtableSize, fieldIndex) {
127
- const fieldOffset = getFieldOffset(view, vtable, vtableSize, fieldIndex);
128
- if (fieldOffset === 0) {
129
- return '';
130
- }
131
- const relative = view.getUint32(root + fieldOffset, true);
132
- const stringStart = root + fieldOffset + relative;
133
- const stringLength = view.getUint32(stringStart, true);
134
- return decoder.decode(data.subarray(stringStart + 4, stringStart + 4 + stringLength));
135
- }
136
-
137
- function readDescriptorValue(state, descriptor) {
138
- if (descriptor.kind === 'int32') {
139
- return readInt32Field(state.view, state.root, state.vtable, state.vtableSize, descriptor.index);
140
- }
141
-
142
- return readStringField(state.data, state.view, state.root, state.vtable, state.vtableSize, descriptor.index);
143
- }
144
-
145
- function readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor) {
146
- if (descriptor.kind === 'int32') {
147
- return readInt32Field(view, root, vtable, vtableSize, descriptor.index);
148
- }
149
-
150
- return readStringField(data, view, root, vtable, vtableSize, descriptor.index);
151
- }
152
-
153
- function createMappedExtractor(fieldDescriptors) {
154
- return {
155
- getField(data, fieldName) {
156
- const descriptor = fieldDescriptors[fieldName];
157
- if (!descriptor) {
158
- return null;
159
- }
160
- return readDescriptorValue(createState(data), descriptor);
161
- },
162
- compileFieldAppender(fieldNames) {
163
- const descriptors = fieldNames.map((fieldName) => fieldDescriptors[fieldName] ?? null);
164
-
165
- switch (descriptors.length) {
166
- case 1: {
167
- const [descriptor0] = descriptors;
168
- return (pendingArgs, data) => {
169
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
170
- const root = view.getUint32(0, true);
171
- const vtable = root - view.getInt32(root, true);
172
- const vtableSize = view.getUint16(vtable, true);
173
- pendingArgs.push(
174
- descriptor0 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor0) : null
175
- );
176
- };
177
- }
178
- case 2: {
179
- const [descriptor0, descriptor1] = descriptors;
180
- return (pendingArgs, data) => {
181
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
182
- const root = view.getUint32(0, true);
183
- const vtable = root - view.getInt32(root, true);
184
- const vtableSize = view.getUint16(vtable, true);
185
- pendingArgs.push(
186
- descriptor0 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor0) : null,
187
- descriptor1 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor1) : null,
188
- );
189
- };
190
- }
191
- case 3: {
192
- const [descriptor0, descriptor1, descriptor2] = descriptors;
193
- return (pendingArgs, data) => {
194
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
195
- const root = view.getUint32(0, true);
196
- const vtable = root - view.getInt32(root, true);
197
- const vtableSize = view.getUint16(vtable, true);
198
- pendingArgs.push(
199
- descriptor0 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor0) : null,
200
- descriptor1 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor1) : null,
201
- descriptor2 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor2) : null,
202
- );
203
- };
204
- }
205
- case 4: {
206
- const [descriptor0, descriptor1, descriptor2, descriptor3] = descriptors;
207
- return (pendingArgs, data) => {
208
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
209
- const root = view.getUint32(0, true);
210
- const vtable = root - view.getInt32(root, true);
211
- const vtableSize = view.getUint16(vtable, true);
212
- pendingArgs.push(
213
- descriptor0 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor0) : null,
214
- descriptor1 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor1) : null,
215
- descriptor2 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor2) : null,
216
- descriptor3 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor3) : null,
217
- );
218
- };
219
- }
220
- default:
221
- return (pendingArgs, data) => {
222
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
223
- const root = view.getUint32(0, true);
224
- const vtable = root - view.getInt32(root, true);
225
- const vtableSize = view.getUint16(vtable, true);
226
- for (const descriptor of descriptors) {
227
- pendingArgs.push(
228
- descriptor ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor) : null
229
- );
230
- }
231
- };
232
- }
233
- },
234
- compileFieldValues(fieldNames) {
235
- const descriptors = fieldNames.map((fieldName) => fieldDescriptors[fieldName] ?? null);
236
-
237
- switch (descriptors.length) {
238
- case 1: {
239
- const [descriptor0] = descriptors;
240
- return (data) => {
241
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
242
- const root = view.getUint32(0, true);
243
- const vtable = root - view.getInt32(root, true);
244
- const vtableSize = view.getUint16(vtable, true);
245
- return [
246
- descriptor0 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor0) : null
247
- ];
248
- };
249
- }
250
- case 2: {
251
- const [descriptor0, descriptor1] = descriptors;
252
- return (data) => {
253
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
254
- const root = view.getUint32(0, true);
255
- const vtable = root - view.getInt32(root, true);
256
- const vtableSize = view.getUint16(vtable, true);
257
- return [
258
- descriptor0 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor0) : null,
259
- descriptor1 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor1) : null,
260
- ];
261
- };
262
- }
263
- case 3: {
264
- const [descriptor0, descriptor1, descriptor2] = descriptors;
265
- return (data) => {
266
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
267
- const root = view.getUint32(0, true);
268
- const vtable = root - view.getInt32(root, true);
269
- const vtableSize = view.getUint16(vtable, true);
270
- return [
271
- descriptor0 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor0) : null,
272
- descriptor1 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor1) : null,
273
- descriptor2 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor2) : null,
274
- ];
275
- };
276
- }
277
- case 4: {
278
- const [descriptor0, descriptor1, descriptor2, descriptor3] = descriptors;
279
- return (data) => {
280
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
281
- const root = view.getUint32(0, true);
282
- const vtable = root - view.getInt32(root, true);
283
- const vtableSize = view.getUint16(vtable, true);
284
- return [
285
- descriptor0 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor0) : null,
286
- descriptor1 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor1) : null,
287
- descriptor2 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor2) : null,
288
- descriptor3 ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor3) : null,
289
- ];
290
- };
291
- }
292
- default:
293
- return (data) => {
294
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
295
- const root = view.getUint32(0, true);
296
- const vtable = root - view.getInt32(root, true);
297
- const vtableSize = view.getUint16(vtable, true);
298
- return descriptors.map((descriptor) =>
299
- descriptor ? readDescriptorValueDirect(data, view, root, vtable, vtableSize, descriptor) : null
300
- );
301
- };
302
- }
303
- },
304
- getFields(data, fieldNames) {
305
- const state = createState(data);
306
- return Object.fromEntries(
307
- fieldNames.map((fieldName) => {
308
- const descriptor = fieldDescriptors[fieldName];
309
- return [fieldName, descriptor ? readDescriptorValue(state, descriptor) : null];
310
- })
311
- );
312
- },
313
- getFieldValues(data, fieldNames) {
314
- const state = createState(data);
315
- return fieldNames.map((fieldName) => {
316
- const descriptor = fieldDescriptors[fieldName];
317
- return descriptor ? readDescriptorValue(state, descriptor) : null;
318
- });
319
- },
320
- };
321
- }
322
-
323
- function extractFields(extractor, data, fieldNames) {
324
- if (extractor.getFields) {
325
- return extractor.getFields(data, fieldNames);
326
- }
327
-
328
- return Object.fromEntries(fieldNames.map((fieldName) => [fieldName, extractor.getField(data, fieldName)]));
329
- }
330
-
331
- function extractFieldValues(extractor, data, fieldNames) {
332
- if (extractor.compileFieldValues) {
333
- return extractor.compileFieldValues(fieldNames)(data);
334
- }
335
-
336
- if (extractor.getFieldValues) {
337
- return extractor.getFieldValues(data, fieldNames);
338
- }
339
-
340
- return fieldNames.map((fieldName) => extractor.getField(data, fieldName));
341
- }
342
-
343
- function createFieldValueReader(extractor, fieldNames) {
344
- if (extractor.compileFieldValues) {
345
- return extractor.compileFieldValues(fieldNames);
346
- }
347
-
348
- return (data) => extractFieldValues(extractor, data, fieldNames);
349
- }
350
-
351
- function createFieldAppender(extractor, fieldNames) {
352
- if (extractor.compileFieldAppender) {
353
- return extractor.compileFieldAppender(fieldNames);
354
- }
355
-
356
- return null;
357
- }
358
-
359
- const demoExtractors = {
360
- User: createMappedExtractor({
361
- id: { kind: 'int32', index: 0 },
362
- name: { kind: 'string', index: 1 },
363
- email: { kind: 'string', index: 2 },
364
- age: { kind: 'int32', index: 3 },
365
- }),
366
- Post: createMappedExtractor({
367
- id: { kind: 'int32', index: 0 },
368
- user_id: { kind: 'int32', index: 1 },
369
- title: { kind: 'string', index: 2 },
370
- }),
371
- };
372
-
373
- function readFileId(data) {
374
- if (data.length < 8) {
375
- throw new Error('FlatBuffer payload is too short to contain a file identifier');
376
- }
377
- return decoder.decode(data.subarray(4, 8));
378
- }
379
-
380
- function readFileIdCode(data) {
381
- if (data.length < 8) {
382
- throw new Error('FlatBuffer payload is too short to contain a file identifier');
383
- }
384
- return (
385
- data[4] |
386
- (data[5] << 8) |
387
- (data[6] << 16) |
388
- (data[7] << 24)
389
- ) >>> 0;
390
- }
391
-
392
- function encodeFileId(fileId) {
393
- if (fileId.length !== 4) {
394
- throw new Error('FlatBuffer file identifiers must be four characters');
395
- }
396
-
397
- return (
398
- fileId.charCodeAt(0) |
399
- (fileId.charCodeAt(1) << 8) |
400
- (fileId.charCodeAt(2) << 16) |
401
- (fileId.charCodeAt(3) << 24)
402
- ) >>> 0;
403
- }
404
-
405
- function indexTableName(tableName, columnName) {
406
- return `_idx_${tableName}_${columnName}`;
407
- }
408
-
409
- function recordTableName(tableName) {
410
- return `_rows_${tableName}`;
411
- }
412
-
413
- function coveringIndexName(tableName, columnName) {
414
- return `_cov_${tableName}_${columnName}`;
415
- }
416
-
417
- function withTransaction(db, beginSql, operation) {
418
- let started = false;
419
-
420
- try {
421
- db.exec(beginSql);
422
- started = true;
423
- const result = operation();
424
- db.exec('COMMIT');
425
- started = false;
426
- return result;
427
- } catch (error) {
428
- if (started) {
429
- db.exec('ROLLBACK');
430
- }
431
- throw error;
432
- }
433
- }
434
-
435
- function readPragmaText(db, name) {
436
- const row = db.prepare(`PRAGMA ${name}`).get();
437
- const value = row ? Object.values(row)[0] : undefined;
438
- return typeof value === 'string' ? value.toLowerCase() : String(value ?? '').toLowerCase();
439
- }
440
-
441
- function applyPerformanceProfile(db, profile) {
442
- db.exec(`PRAGMA page_size = ${DEFAULT_PAGE_SIZE}`);
443
- db.exec(`PRAGMA threads = ${DEFAULT_THREAD_COUNT}`);
444
- db.exec(`PRAGMA mmap_size = ${DEFAULT_MMAP_SIZE}`);
445
-
446
- if (profile === 'safe') {
447
- db.exec('PRAGMA journal_mode = DELETE');
448
- db.exec('PRAGMA synchronous = FULL');
449
- return 'BEGIN IMMEDIATE';
450
- }
451
-
452
- db.exec('PRAGMA journal_mode = OFF');
453
- if (readPragmaText(db, 'journal_mode') !== 'off') {
454
- db.exec('PRAGMA journal_mode = MEMORY');
455
- }
456
- db.exec('PRAGMA synchronous = OFF');
457
- db.exec('PRAGMA locking_mode = EXCLUSIVE');
458
- db.exec('PRAGMA temp_store = MEMORY');
459
- db.exec(`PRAGMA cache_size = ${DEFAULT_CACHE_SIZE}`);
460
- return 'BEGIN EXCLUSIVE';
461
- }
462
-
463
- function createArgumentAppender(fieldCount) {
464
- switch (fieldCount) {
465
- case 1:
466
- return (pendingArgs, fieldValues, recordOffset, recordLength, sequence) =>
467
- pendingArgs.push(fieldValues[0], recordOffset, recordLength, sequence);
468
- case 2:
469
- return (pendingArgs, fieldValues, recordOffset, recordLength, sequence) =>
470
- pendingArgs.push(fieldValues[0], fieldValues[1], recordOffset, recordLength, sequence);
471
- case 3:
472
- return (pendingArgs, fieldValues, recordOffset, recordLength, sequence) =>
473
- pendingArgs.push(fieldValues[0], fieldValues[1], fieldValues[2], recordOffset, recordLength, sequence);
474
- case 4:
475
- return (pendingArgs, fieldValues, recordOffset, recordLength, sequence) =>
476
- pendingArgs.push(fieldValues[0], fieldValues[1], fieldValues[2], fieldValues[3], recordOffset, recordLength, sequence);
477
- default:
478
- return (pendingArgs, fieldValues, recordOffset, recordLength, sequence) => {
479
- for (let index = 0; index < fieldValues.length; index += 1) {
480
- pendingArgs.push(fieldValues[index]);
481
- }
482
- pendingArgs.push(recordOffset, recordLength, sequence);
483
- };
484
- }
485
- }
486
-
487
- function buildInsertSql(tableName, fieldNames, rowCount) {
488
- const columnNames = [...fieldNames.map((fieldName) => `"${fieldName}"`), 'data_offset', 'data_length', 'sequence'].join(', ');
489
- const valueTuple = `(${Array.from({ length: fieldNames.length + 3 }, () => '?').join(', ')})`;
490
- return `INSERT INTO "${recordTableName(tableName)}" (${columnNames}) VALUES ${Array.from({ length: rowCount }, () => valueTuple).join(', ')}`;
491
- }
492
-
493
- function createBatchedRowWriter(db, tableName, fieldNames) {
494
- const fullStatement = db.prepare(buildInsertSql(tableName, fieldNames, INSERT_BATCH_SIZE));
495
- const partialStatements = new Map();
496
- const appendArgs = createArgumentAppender(fieldNames.length);
497
- let pendingArgs = [];
498
- let pendingRowCount = 0;
499
-
500
- function statementFor(rowCount) {
501
- if (rowCount === INSERT_BATCH_SIZE) {
502
- return fullStatement;
503
- }
504
-
505
- let statement = partialStatements.get(rowCount);
506
- if (!statement) {
507
- statement = db.prepare(buildInsertSql(tableName, fieldNames, rowCount));
508
- partialStatements.set(rowCount, statement);
509
- }
510
- return statement;
511
- }
512
-
513
- function flushRows() {
514
- if (pendingRowCount === 0) {
515
- return;
516
- }
517
-
518
- statementFor(pendingRowCount).run(...pendingArgs);
519
- pendingArgs = [];
520
- pendingRowCount = 0;
521
- }
522
-
523
- return {
524
- writeRow(fieldValues, recordOffset, recordLength, sequence) {
525
- appendArgs(pendingArgs, fieldValues, recordOffset, recordLength, sequence);
526
- pendingRowCount += 1;
527
-
528
- if (pendingRowCount === INSERT_BATCH_SIZE) {
529
- flushRows();
530
- }
531
- },
532
- writeCompiledRow(appendFieldValues, data, recordOffset, recordLength, sequence) {
533
- appendFieldValues(pendingArgs, data);
534
- pendingArgs.push(recordOffset, recordLength, sequence);
535
- pendingRowCount += 1;
536
-
537
- if (pendingRowCount === INSERT_BATCH_SIZE) {
538
- flushRows();
539
- }
540
- },
541
- flushRows,
542
- };
543
- }
544
-
545
- function createBuilder({ builderId, schema, performanceProfile = 'fast', sqlitePath }) {
546
- const db = new DatabaseSync(sqlitePath);
547
- const state = {
548
- schema,
549
- db,
550
- beginTransactionSql: applyPerformanceProfile(db, performanceProfile),
551
- tableByName: new Map(schema.tables.map((table) => [table.name, table])),
552
- fileIdToTable: new Map(),
553
- extractors: new Map(),
554
- queryCache: new Map(),
555
- sequence: 1,
556
- };
557
-
558
- for (const table of schema.tables) {
559
- const indexedColumns = table.columns.filter((column) => column.isIndexed && !column.name.startsWith('_'));
560
- if (indexedColumns.length === 0) {
561
- continue;
562
- }
563
-
564
- db.exec(
565
- `CREATE TABLE IF NOT EXISTS "${recordTableName(table.name)}" (
566
- ${indexedColumns.map((column) => `"${column.name}" ${sqliteType(column)} NOT NULL`).join(',\n ')},
567
- data_offset INTEGER NOT NULL,
568
- data_length INTEGER NOT NULL,
569
- sequence INTEGER NOT NULL
570
- )`
571
- );
572
-
573
- for (const column of indexedColumns) {
574
- db.exec(
575
- `CREATE VIEW IF NOT EXISTS "${indexTableName(table.name, column.name)}" AS
576
- SELECT "${column.name}" AS key, data_offset, data_length, sequence
577
- FROM "${recordTableName(table.name)}"`
578
- );
579
- }
580
- }
581
-
582
- builders.set(builderId, state);
583
- return { builderId };
584
- }
585
-
586
- function getBuilder(builderId) {
587
- const builder = builders.get(builderId);
588
- if (!builder) {
589
- throw new Error(`Artifact builder not found: ${builderId}`);
590
- }
591
- return builder;
592
- }
593
-
594
- function ingestRecords(state, buffers, options, transportMode) {
595
- let currentOffset = options?.startOffset ?? 0;
596
- const plans = new Map();
597
- state.queryCache.clear();
598
-
599
- withTransaction(state.db, state.beginTransactionSql, () => {
600
- for (let index = 0; index < buffers.length; index++) {
601
- const buffer = buffers[index];
602
- const fileIdCode = readFileIdCode(buffer);
603
- const tableName = state.fileIdToTable.get(fileIdCode);
604
- if (!tableName) {
605
- const fileId = readFileId(buffer);
606
- throw new Error(`No table registered for file identifier ${fileId}`);
607
- }
608
-
609
- let plan = plans.get(tableName);
610
- if (!plan) {
611
- const extractor = state.extractors.get(tableName);
612
- if (!extractor) {
613
- throw new Error(`No field extractor registered for table ${tableName}`);
614
- }
615
-
616
- const table = state.tableByName.get(tableName);
617
- if (!table) {
618
- throw new Error(`Table ${tableName} is not present in the parsed schema`);
619
- }
620
-
621
- plan = {
622
- table,
623
- fieldNames: table.columns
624
- .filter((column) => column.isIndexed && !column.name.startsWith('_'))
625
- .map((column) => column.name),
626
- writeRecord: null,
627
- ...createBatchedRowWriter(
628
- state.db,
629
- table.name,
630
- table.columns
631
- .filter((column) => column.isIndexed && !column.name.startsWith('_'))
632
- .map((column) => column.name)
633
- ),
634
- droppedIndexes: false,
635
- };
636
- const compiledAppender = createFieldAppender(extractor, plan.fieldNames);
637
- const extractRowValues = compiledAppender ? null : createFieldValueReader(extractor, plan.fieldNames);
638
- plan.writeRecord = compiledAppender
639
- ? (data, recordOffset, recordLength, sequence) =>
640
- plan.writeCompiledRow(compiledAppender, data, recordOffset, recordLength, sequence)
641
- : (data, recordOffset, recordLength, sequence) =>
642
- plan.writeRow(extractRowValues(data), recordOffset, recordLength, sequence);
643
- plans.set(tableName, plan);
644
- }
645
-
646
- if (!plan.droppedIndexes) {
647
- for (const column of plan.table.columns) {
648
- if (!column.isIndexed || column.name.startsWith('_')) {
649
- continue;
650
- }
651
- state.db.exec(`DROP INDEX IF EXISTS "${coveringIndexName(plan.table.name, column.name)}"`);
652
- }
653
- plan.droppedIndexes = true;
654
- }
655
-
656
- const recordOffset = options?.offsets?.[index] ?? currentOffset;
657
- plan.writeRecord(buffer, recordOffset, buffer.length, state.sequence);
658
-
659
- state.sequence += 1;
660
- currentOffset = recordOffset + buffer.length;
661
- }
662
-
663
- for (const plan of plans.values()) {
664
- plan.flushRows();
665
- for (const column of plan.table.columns) {
666
- if (!column.isIndexed || column.name.startsWith('_')) {
667
- continue;
668
- }
669
-
670
- state.db.exec(
671
- `CREATE INDEX "${coveringIndexName(plan.table.name, column.name)}"
672
- ON "${recordTableName(plan.table.name)}" ("${column.name}", sequence, data_offset, data_length)`
673
- );
674
- }
675
- }
676
- });
677
-
678
- return {
679
- recordCount: buffers.length,
680
- transportMode,
681
- };
682
- }
683
-
684
- function forEachSizePrefixedBuffer(stream, visitor) {
685
- const view = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
686
- let offset = 0;
687
- let index = 0;
688
-
689
- while (offset < stream.byteLength) {
690
- if (offset + 4 > stream.byteLength) {
691
- throw new Error(`Invalid size-prefixed stream: truncated frame header at offset ${offset}`);
692
- }
693
- const size = view.getUint32(offset, true);
694
- offset += 4;
695
- if (offset + size > stream.byteLength) {
696
- throw new Error(`Invalid size-prefixed stream: truncated frame at index ${index}`);
697
- }
698
- visitor(stream.subarray(offset, offset + size), index);
699
- offset += size;
700
- index += 1;
701
- }
702
-
703
- return index;
704
- }
705
-
706
- function normalizeRows(rows, columns) {
707
- return rows.map((row) => columns.map((column) => row[column]));
708
- }
709
-
710
- const methods = {
711
- createBuilder,
712
- registerFileId({ builderId, fileId, tableName }) {
713
- getBuilder(builderId).fileIdToTable.set(encodeFileId(fileId), tableName);
714
- return { ok: true };
715
- },
716
- enableDemoExtractors({ builderId }) {
717
- const builder = getBuilder(builderId);
718
- for (const [tableName, extractor] of Object.entries(demoExtractors)) {
719
- builder.extractors.set(tableName, extractor);
720
- }
721
- return { ok: true };
722
- },
723
- ingestClone({ builderId, buffers, options }) {
724
- const builder = getBuilder(builderId);
725
- return ingestRecords(builder, buffers.map((buffer) => Uint8Array.from(buffer)), options, 'clone');
726
- },
727
- ingestShared({ builderId, sharedBuffer, byteLength, options }) {
728
- const builder = getBuilder(builderId);
729
- let currentOffset = options?.startOffset ?? 0;
730
- const plans = new Map();
731
- const stream = new Uint8Array(sharedBuffer, 0, byteLength);
732
- let recordCount = 0;
733
-
734
- builder.queryCache.clear();
735
-
736
- withTransaction(builder.db, builder.beginTransactionSql, () => {
737
- forEachSizePrefixedBuffer(stream, (buffer, index) => {
738
- const fileIdCode = readFileIdCode(buffer);
739
- const tableName = builder.fileIdToTable.get(fileIdCode);
740
- if (!tableName) {
741
- const fileId = readFileId(buffer);
742
- throw new Error(`No table registered for file identifier ${fileId}`);
743
- }
744
-
745
- let plan = plans.get(tableName);
746
- if (!plan) {
747
- const extractor = builder.extractors.get(tableName);
748
- if (!extractor) {
749
- throw new Error(`No field extractor registered for table ${tableName}`);
750
- }
751
-
752
- const table = builder.tableByName.get(tableName);
753
- if (!table) {
754
- throw new Error(`Table ${tableName} is not present in the parsed schema`);
755
- }
756
-
757
- plan = {
758
- table,
759
- fieldNames: table.columns
760
- .filter((column) => column.isIndexed && !column.name.startsWith('_'))
761
- .map((column) => column.name),
762
- writeRecord: null,
763
- ...createBatchedRowWriter(
764
- builder.db,
765
- table.name,
766
- table.columns
767
- .filter((column) => column.isIndexed && !column.name.startsWith('_'))
768
- .map((column) => column.name)
769
- ),
770
- droppedIndexes: false,
771
- };
772
- const compiledAppender = createFieldAppender(extractor, plan.fieldNames);
773
- const extractRowValues = compiledAppender ? null : createFieldValueReader(extractor, plan.fieldNames);
774
- plan.writeRecord = compiledAppender
775
- ? (data, recordOffset, recordLength, sequence) =>
776
- plan.writeCompiledRow(compiledAppender, data, recordOffset, recordLength, sequence)
777
- : (data, recordOffset, recordLength, sequence) =>
778
- plan.writeRow(extractRowValues(data), recordOffset, recordLength, sequence);
779
- plans.set(tableName, plan);
780
- }
781
-
782
- if (!plan.droppedIndexes) {
783
- for (const column of plan.table.columns) {
784
- if (!column.isIndexed || column.name.startsWith('_')) {
785
- continue;
786
- }
787
- builder.db.exec(`DROP INDEX IF EXISTS "${coveringIndexName(plan.table.name, column.name)}"`);
788
- }
789
- plan.droppedIndexes = true;
790
- }
791
-
792
- const recordOffset = options?.offsets?.[index] ?? currentOffset;
793
- plan.writeRecord(buffer, recordOffset, buffer.length, builder.sequence);
794
-
795
- builder.sequence += 1;
796
- currentOffset = recordOffset + buffer.length;
797
- recordCount = index + 1;
798
- });
799
-
800
- for (const plan of plans.values()) {
801
- plan.flushRows();
802
- for (const column of plan.table.columns) {
803
- if (!column.isIndexed || column.name.startsWith('_')) {
804
- continue;
805
- }
806
-
807
- builder.db.exec(
808
- `CREATE INDEX "${coveringIndexName(plan.table.name, column.name)}"
809
- ON "${recordTableName(plan.table.name)}" ("${column.name}", sequence, data_offset, data_length)`
810
- );
811
- }
812
- }
813
- });
814
-
815
- return {
816
- recordCount,
817
- transportMode: 'shared-array-buffer',
818
- };
819
- },
820
- query({ builderId, sql, params }) {
821
- const builder = getBuilder(builderId);
822
- return runQuery(builder, sql, params);
823
- },
824
- queryMany({ builderId, queries }) {
825
- const builder = getBuilder(builderId);
826
- return queries.map(({ sql, params }) => runQuery(builder, sql, params));
827
- },
828
- closeBuilder({ builderId }) {
829
- const builder = getBuilder(builderId);
830
- builder.queryCache.clear();
831
- builder.db.close();
832
- builders.delete(builderId);
833
- return { ok: true };
834
- },
835
- };
836
-
837
- parentPort.on('message', async (message) => {
838
- const { id, method, params } = message;
839
- try {
840
- const result = await methods[method](params);
841
- parentPort.postMessage({ id, success: true, result });
842
- } catch (error) {
843
- parentPort.postMessage({ id, success: false, error: error instanceof Error ? error.message : String(error) });
844
- }
845
- });
846
-
847
- parentPort.postMessage({ type: 'ready' });