flatsql 3.11.0 → 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.
Files changed (181) hide show
  1. package/README.md +61 -795
  2. package/bin/flatsql.mjs +76 -0
  3. package/cpp/README.md +18 -0
  4. package/cpp/include/flatsql/flatsql.h +162 -0
  5. package/cpp/include/flatsql/flatsql_io.h +159 -0
  6. package/docs/API-3.12.md +114 -0
  7. package/package.json +28 -123
  8. package/wasm/flatsql.wasm +0 -0
  9. package/wasm/index.d.ts +61 -548
  10. package/wasm/index.js +280 -1281
  11. package/wasm/integrity.json +6 -26
  12. package/wasm/runtime/LICENSE.sdk.txt +190 -0
  13. package/wasm/runtime/NOTICE.txt +2 -0
  14. package/wasm/runtime/browser-guest.mjs +1 -0
  15. package/wasm/runtime/browser-host.mjs +1 -0
  16. package/wasm/runtime/node-guest.mjs +2 -0
  17. package/wasm/runtime/node-host.mjs +5 -0
  18. package/wasm/runtime/opfsIoWorker.mjs +1 -0
  19. package/wasm/runtime/wasiThreadBrowserWorker.mjs +1 -0
  20. package/dist/artifacts/index.d.ts +0 -3
  21. package/dist/artifacts/index.d.ts.map +0 -1
  22. package/dist/artifacts/index.js +0 -3
  23. package/dist/artifacts/index.js.map +0 -1
  24. package/dist/artifacts/transport.d.ts +0 -5
  25. package/dist/artifacts/transport.d.ts.map +0 -1
  26. package/dist/artifacts/transport.js +0 -45
  27. package/dist/artifacts/transport.js.map +0 -1
  28. package/dist/artifacts/types.d.ts +0 -34
  29. package/dist/artifacts/types.d.ts.map +0 -1
  30. package/dist/artifacts/types.js +0 -2
  31. package/dist/artifacts/types.js.map +0 -1
  32. package/dist/btree/btree.d.ts +0 -34
  33. package/dist/btree/btree.d.ts.map +0 -1
  34. package/dist/btree/btree.js +0 -234
  35. package/dist/btree/btree.js.map +0 -1
  36. package/dist/btree/index.d.ts +0 -3
  37. package/dist/btree/index.d.ts.map +0 -1
  38. package/dist/btree/index.js +0 -3
  39. package/dist/btree/index.js.map +0 -1
  40. package/dist/btree/types.d.ts +0 -36
  41. package/dist/btree/types.d.ts.map +0 -1
  42. package/dist/btree/types.js +0 -55
  43. package/dist/btree/types.js.map +0 -1
  44. package/dist/cluster/index.d.ts +0 -11
  45. package/dist/cluster/index.d.ts.map +0 -1
  46. package/dist/cluster/index.js +0 -59
  47. package/dist/cluster/index.js.map +0 -1
  48. package/dist/response/artifact.d.ts +0 -4
  49. package/dist/response/artifact.d.ts.map +0 -1
  50. package/dist/response/artifact.js +0 -102
  51. package/dist/response/artifact.js.map +0 -1
  52. package/dist/response/cache-key.d.ts +0 -6
  53. package/dist/response/cache-key.d.ts.map +0 -1
  54. package/dist/response/cache-key.js +0 -114
  55. package/dist/response/cache-key.js.map +0 -1
  56. package/dist/response/index.d.ts +0 -5
  57. package/dist/response/index.d.ts.map +0 -1
  58. package/dist/response/index.js +0 -5
  59. package/dist/response/index.js.map +0 -1
  60. package/dist/response/memory-cache.d.ts +0 -17
  61. package/dist/response/memory-cache.d.ts.map +0 -1
  62. package/dist/response/memory-cache.js +0 -76
  63. package/dist/response/memory-cache.js.map +0 -1
  64. package/dist/response/types.d.ts +0 -57
  65. package/dist/response/types.d.ts.map +0 -1
  66. package/dist/response/types.js +0 -2
  67. package/dist/response/types.js.map +0 -1
  68. package/dist/schema/index.d.ts +0 -3
  69. package/dist/schema/index.d.ts.map +0 -1
  70. package/dist/schema/index.js +0 -3
  71. package/dist/schema/index.js.map +0 -1
  72. package/dist/schema/parser.d.ts +0 -25
  73. package/dist/schema/parser.d.ts.map +0 -1
  74. package/dist/schema/parser.js +0 -474
  75. package/dist/schema/parser.js.map +0 -1
  76. package/dist/schema/types.d.ts +0 -33
  77. package/dist/schema/types.d.ts.map +0 -1
  78. package/dist/schema/types.js +0 -29
  79. package/dist/schema/types.js.map +0 -1
  80. package/dist/standalone/index.d.ts +0 -3
  81. package/dist/standalone/index.d.ts.map +0 -1
  82. package/dist/standalone/index.js +0 -3
  83. package/dist/standalone/index.js.map +0 -1
  84. package/dist/standalone/process-client.d.ts +0 -85
  85. package/dist/standalone/process-client.d.ts.map +0 -1
  86. package/dist/standalone/process-client.js +0 -537
  87. package/dist/standalone/process-client.js.map +0 -1
  88. package/dist/standalone/wasmedge-runner.d.ts +0 -19
  89. package/dist/standalone/wasmedge-runner.d.ts.map +0 -1
  90. package/dist/standalone/wasmedge-runner.js +0 -108
  91. package/dist/standalone/wasmedge-runner.js.map +0 -1
  92. package/dist/storage/index.d.ts +0 -3
  93. package/dist/storage/index.d.ts.map +0 -1
  94. package/dist/storage/index.js +0 -3
  95. package/dist/storage/index.js.map +0 -1
  96. package/dist/storage/stacked-flatbuffers.d.ts +0 -38
  97. package/dist/storage/stacked-flatbuffers.d.ts.map +0 -1
  98. package/dist/storage/stacked-flatbuffers.js +0 -253
  99. package/dist/storage/stacked-flatbuffers.js.map +0 -1
  100. package/dist/storage/types.d.ts +0 -23
  101. package/dist/storage/types.d.ts.map +0 -1
  102. package/dist/storage/types.js +0 -27
  103. package/dist/storage/types.js.map +0 -1
  104. package/dist/stress/aggregate.d.ts +0 -3
  105. package/dist/stress/aggregate.d.ts.map +0 -1
  106. package/dist/stress/aggregate.js +0 -61
  107. package/dist/stress/aggregate.js.map +0 -1
  108. package/dist/stress/docker-compose.d.ts +0 -17
  109. package/dist/stress/docker-compose.d.ts.map +0 -1
  110. package/dist/stress/docker-compose.js +0 -156
  111. package/dist/stress/docker-compose.js.map +0 -1
  112. package/dist/stress/index.d.ts +0 -10
  113. package/dist/stress/index.d.ts.map +0 -1
  114. package/dist/stress/index.js +0 -10
  115. package/dist/stress/index.js.map +0 -1
  116. package/dist/stress/metrics.d.ts +0 -3
  117. package/dist/stress/metrics.d.ts.map +0 -1
  118. package/dist/stress/metrics.js +0 -83
  119. package/dist/stress/metrics.js.map +0 -1
  120. package/dist/stress/native-publish-event-workload.d.ts +0 -3
  121. package/dist/stress/native-publish-event-workload.d.ts.map +0 -1
  122. package/dist/stress/native-publish-event-workload.js +0 -465
  123. package/dist/stress/native-publish-event-workload.js.map +0 -1
  124. package/dist/stress/production-gate.d.ts +0 -4
  125. package/dist/stress/production-gate.d.ts.map +0 -1
  126. package/dist/stress/production-gate.js +0 -12
  127. package/dist/stress/production-gate.js.map +0 -1
  128. package/dist/stress/report.d.ts +0 -3
  129. package/dist/stress/report.d.ts.map +0 -1
  130. package/dist/stress/report.js +0 -174
  131. package/dist/stress/report.js.map +0 -1
  132. package/dist/stress/sds-discovery.d.ts +0 -4
  133. package/dist/stress/sds-discovery.d.ts.map +0 -1
  134. package/dist/stress/sds-discovery.js +0 -40
  135. package/dist/stress/sds-discovery.js.map +0 -1
  136. package/dist/stress/smoke-runner.d.ts +0 -3
  137. package/dist/stress/smoke-runner.d.ts.map +0 -1
  138. package/dist/stress/smoke-runner.js +0 -64
  139. package/dist/stress/smoke-runner.js.map +0 -1
  140. package/dist/stress/types.d.ts +0 -219
  141. package/dist/stress/types.d.ts.map +0 -1
  142. package/dist/stress/types.js +0 -2
  143. package/dist/stress/types.js.map +0 -1
  144. package/dist/stress/workload.d.ts +0 -6
  145. package/dist/stress/workload.d.ts.map +0 -1
  146. package/dist/stress/workload.js +0 -144
  147. package/dist/stress/workload.js.map +0 -1
  148. package/docs/PARTITION-STORE-WASM.md +0 -338
  149. package/docs/PARTITION-STORE.md +0 -2380
  150. package/docs/agents/response-artifacts.md +0 -65
  151. package/docs/response-artifacts.md +0 -86
  152. package/scripts/stress-docker.sh +0 -279
  153. package/sdm/flatsql-spatial.wasm +0 -0
  154. package/sdm/index.d.ts +0 -88
  155. package/sdm/index.js +0 -253
  156. package/sdm/manifest.json +0 -402
  157. package/stress/docker/Dockerfile +0 -11
  158. package/stress/docker/docker-compose.yml +0 -51
  159. package/stress/sds/aggregate.mjs +0 -97
  160. package/stress/sds/run.mjs +0 -105
  161. package/stress/sds/write-compose.mjs +0 -96
  162. package/wasm/flatsql-client.js +0 -280
  163. package/wasm/flatsql-io.d.ts +0 -97
  164. package/wasm/flatsql-io.js +0 -621
  165. package/wasm/flatsql-p4-threads.wasm +0 -0
  166. package/wasm/flatsql-ps-threads.wasm +0 -0
  167. package/wasm/flatsql-wasi-noeh.wasm +0 -0
  168. package/wasm/flatsql-wasi.wasm +0 -0
  169. package/wasm/flatsql.js +0 -2
  170. package/wasm/flatsql.worker.js +0 -209
  171. package/wasm/native/flatsql_wasmedge_runner.cpp +0 -858
  172. package/wasm/ps-node-fault.d.mts +0 -23
  173. package/wasm/ps-node-fault.mjs +0 -381
  174. package/wasm/ps-node-guest.mjs +0 -173
  175. package/wasm/ps-node-host.d.mts +0 -95
  176. package/wasm/ps-node-host.mjs +0 -476
  177. package/wasm/ps-node-imports.mjs +0 -847
  178. package/wasm/ps.d.ts +0 -21
  179. package/wasm/ps.js +0 -75
  180. package/wasm/wasi.d.ts +0 -11
  181. package/wasm/wasi.js +0 -51
package/README.md CHANGED
@@ -1,822 +1,88 @@
1
1
  # FlatSQL
2
2
 
3
- [![CI](https://github.com/DigitalArsenal/flatsql/actions/workflows/ci.yml/badge.svg)](https://github.com/DigitalArsenal/flatsql/actions/workflows/ci.yml)
4
- [![npm version](https://img.shields.io/npm/v/flatsql.svg)](https://www.npmjs.com/package/flatsql)
5
- [![License](https://img.shields.io/badge/License-PolyForm%20Noncommercial%201.0.0-blue.svg)](https://polyformproject.org/licenses/noncommercial/1.0.0/)
3
+ [![CI](https://github.com/DigitalArsenal/flatsql/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/DigitalArsenal/flatsql/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/flatsql)](https://www.npmjs.com/package/flatsql)
6
5
 
7
- **SQL queries over raw FlatBuffer storage** — A streaming query engine that keeps data in native FlatBuffer format while providing SQL access via SQLite virtual tables.
6
+ SQL over persistent FlatBuffer records. FlatBuffers in, FlatBuffers out.
8
7
 
9
- ## Live Demo
8
+ FlatSQL 3.12 provides one API and one `wasm32-wasip1-threads` engine for Node,
9
+ browsers, and WASI hosts. The optimized store, SQL lanes, and geospatial
10
+ functions share that engine. There are no separate PS/P4 entry points.
10
11
 
11
- Try FlatSQL in your browser: **[https://digitalarsenal.github.io/flatsql/](https://digitalarsenal.github.io/flatsql/)**
12
-
13
- ## Installation
14
-
15
- ```bash
12
+ ```sh
16
13
  npm install flatsql
17
14
  ```
18
15
 
19
- ### 3.0: the partition store
20
-
21
- `flatsql` 3.x ships the multithreaded partition store as one
22
- `wasm32-wasip1-threads` artifact, `flatsql/ps-threads.wasm`: one writer per
23
- (producer, SDS type) partition on a pinned pool of writer threads, durable
24
- acks, and reader lanes that read committed snapshots and never wait on writers.
25
- Hosts instantiate it over a shared memory and provide WASI preview1,
26
- `wasi.thread-spawn` and the seven `flatsql_io_*` imports; its sha256 is in
27
- `flatsql/integrity.json`.
28
-
29
- ```javascript
30
- import { loadFlatSQLPsThreads, getFlatSQLPsThreadsURL } from 'flatsql';
31
- import { createPsNodeInstance } from 'flatsql/ps/node-host'; // Node; needs space-data-module-sdk >= 0.8.24
32
-
33
- const bytes = await loadFlatSQLPsThreads(); // checked against integrity.json
34
- const writer = await createPsNodeInstance(bytes, { root: './store-root' });
35
- ```
36
-
37
- See [docs/PARTITION-STORE-WASM.md](docs/PARTITION-STORE-WASM.md) (artifact,
38
- Node host, acceptance) and [docs/PARTITION-STORE.md](docs/PARTITION-STORE.md)
39
- (the engine). 3.0.0 removed the TypeScript engine (`FlatSQLDatabase` from
40
- `src/`, `DirectAccessor`, `FlatcAccessor`), the `node:sqlite` artifact builder,
41
- `flatsql/standalone`, `flatsql/artifacts/standalone` and the `sql.js` and
42
- `flatbuffers` dependencies; the package root is the wasm bindings.
43
-
44
- ## Quick Start
45
-
46
- ```javascript
47
- import { initFlatSQL } from 'flatsql/wasm';
48
-
49
- // Initialize
50
- const flatsql = await initFlatSQL();
51
-
52
- // Create database with schema
53
- const db = flatsql.createDatabase(`
54
- table User {
55
- id: int (id);
56
- name: string;
57
- email: string (key);
58
- age: int;
59
- }
60
- `, 'myapp');
61
-
62
- // Register file identifier for routing
63
- db.registerFileId('USER', 'User');
64
-
65
- // Ingest FlatBuffer data (streaming)
66
- db.ingest(flatbufferStream);
67
-
68
- // Query with SQL
69
- const result = db.query('SELECT * FROM User WHERE age > 25');
70
- console.log(result.columns, result.rows);
71
- ```
72
-
73
- ## What is FlatSQL?
74
-
75
- FlatSQL bridges two technologies:
76
-
77
- - **[FlatBuffers](https://github.com/digitalarsenal/flatbuffers)** — Google's efficient cross-platform serialization library. Data is stored in binary format with zero-copy access (no parsing/unpacking needed).
78
- - **SQLite** — The most widely deployed SQL database engine, used here only for SQL parsing and query execution.
79
-
80
- The key insight: instead of converting FlatBuffers to SQLite rows (expensive), FlatSQL uses [SQLite virtual tables](https://sqlite.org/vtab.html) to query FlatBuffer data directly. Your data stays in portable FlatBuffer format, readable by any FlatBuffer tooling, while you get SQL query capabilities.
81
-
82
- ## Why FlatSQL?
83
-
84
- Traditional approach:
85
- ```
86
- FlatBuffer → Deserialize → SQLite rows → Query → Serialize → FlatBuffer
87
- ```
88
-
89
- FlatSQL approach:
90
- ```
91
- FlatBuffer → Query (via virtual table) → FlatBuffer
92
- ```
93
-
94
- **Benefits:**
95
- - **Zero conversion overhead** — Data stays in FlatBuffer format
96
- - **Streaming ingestion** — Indexes built during data arrival, not after
97
- - **Portable output** — Exported data is standard FlatBuffers, readable by any tooling
98
- - **Multi-source federation** — Query across multiple FlatBuffer sources with automatic source tagging
99
-
100
- ## Runtime Choices & Performance Gates
101
-
102
- ### Complete-record full-text search
103
-
104
- `flatsql_record_text(table, record, binary_schema)` extracts search text using a
105
- canonical FlatBuffers binary schema (`.bfbs`). This includes fields after vectors,
106
- nested tables and unions, even when the SQL table exposes only an initial set of
107
- columns. Records remain in their original binary format. The two-argument form
108
- continues to extract the registered SQL columns.
109
-
110
- Generate binary schemas from the same published IDL version used to produce the
111
- records, using `flatc -b --schema --bfbs-builtins`. The schema and record must match
112
- the registered table's file identifier. FlatSQL validates both inputs and indexes
113
- public scalar values, strings and nested content. It excludes encrypted fields,
114
- internal fields beginning with `_`, and opaque byte vectors.
115
-
116
- Extraction rejects unsupported layouts and reports bounds failures instead of
117
- returning partial text. Limits are 1 MiB per binary schema, 16 MiB per record and
118
- output, 100,000 visited values/objects and 64 nesting levels. Schema metadata with
119
- 64-bit offsets or unknown advanced features is rejected. Consumers must include
120
- the binary schema and engine versions in their derived-index fingerprint so
121
- upgrades rebuild previously incomplete indexes. See the
122
- [FlatBuffers reflection documentation](https://flatbuffers.dev/languages/cpp/).
123
-
124
- ### SDN flow runtime contract
125
-
126
- Within an SDN module flow, FlatSQL always runs as a signed, pluggable WASM node.
127
- The exact same artifact bytes and hash run under the browser/JavaScript harness
128
- and WasmEdge. The pure TypeScript implementation is a library fallback and
129
- must not substitute for the FlatSQL node in an isomorphic flow.
130
-
131
- FlatSQL remains independently instantiated and addressable by its signed
132
- manifest and artifact hash. A flow bundle may carry those bytes, but a compiler
133
- must not statically fold FlatSQL into the consuming application module.
134
-
135
- The FlatSQL node owns SQL, tables, rows, indexes, compaction, snapshots, and
136
- reload semantics. Hosts provide only generic opaque byte persistence,
137
- shared-arena bounds checking, clocks/wakeups, network byte streams, and module
138
- lifecycle. They must not expose a FlatSQL service, `storage_engine_link`, or
139
- schema-aware row/query API.
140
-
141
- Every FlatSQL module input and output port supports both canonical SDS
142
- FlatBuffer and aligned-binary representations for the same schema identity.
143
- `PIV`/`TAB.WIRE_FORMAT` selects per frame. Aligned frames are transient
144
- shared-memory views; durable, network, and fallback records remain canonical
145
- size-prefixed FlatBuffers.
146
-
147
- - The SQLite-backed WASM path is `initFlatSQL` → `FlatSQL` → `createDatabase` (the package root and `flatsql/wasm`).
148
- - Standalone deployments use the C++ WASI reactor at `flatsql/wasi.wasm`; Node/WasmEdge hosts use a resident runner from `flatsql/standalone/wasmedge`. The partition store is `flatsql/ps-threads.wasm` (Node host: `flatsql/ps/node-host`).
149
- - Artifact-builder cache behavior belongs to C++: query templates, result-cache keys, invalidation generation, hit/miss counters, SQL execution, and raw FlatBuffer lookup all live in the WASM database instance. JavaScript only marshals bytes and fills host capability gaps.
150
- - Use `db.ingestBuffers([...])` on the WASM path when your input is already a set of raw FlatBuffers. It feeds the native bulk-stream ingest path instead of looping through `ingestOne(...)`.
151
- - Run `npm run bench` (alias `npm run bench:perf`) for the WASM ingest benchmark (medians and records/s). Use `npm run bench:perf:profile` to print the ingest phase breakdown (`pack`, `decode`, `append`, `index`, `verify`); profiling is diagnostic and slower by design.
152
- - Run `npm run test:cluster` for the native cluster validation workload, or `npm run test:cluster:smoke` for the short preflight run. See [docs/performance.md](docs/performance.md).
153
-
154
- ## SDS Stress Harness
155
-
156
- The Space Data Standards stress harness exercises every discoverable `main.fbs` under `../spacedatastandards.org/schema`, records bandwidth and storage-fill metrics, and keeps canonical schemas database-neutral. Results are written under `stress/results/<run-id>/`.
157
-
158
- ```bash
159
- # Local smoke path, no Docker required.
160
- npm run stress:sds:smoke -- --run-id sds-smoke
161
-
162
- # Existing single-controller Docker smoke path.
163
- npm run stress:sds:docker-smoke -- --run-id sds-docker-smoke
164
-
165
- # Container-per-node fleet path for small probes, e.g. 10 isolated nodes.
166
- npm run stress:sds:docker-fleet -- --run-id sds-10-node --nodes 10 --storage-gb 1 --records-per-node 100000
167
-
168
- # Generate and validate the 100-node / 1 GB-per-node production compose file without running it.
169
- npm run stress:sds:full-config -- --run-id sds-full
170
- docker compose -f stress/results/sds-full/docker-compose.full.yml config
171
-
172
- # Run the full production gate. Requires Docker and enough local storage.
173
- CONFIRM_FULL_STRESS=1 npm run stress:sds:full -- --run-id sds-full
174
- ```
175
-
176
- The production gate only passes when the aggregate report shows container isolation, Docker transport, at least 100 nodes, no metric errors, measured native/standalone/WasmEdge events, complete SDS schema-sweep coverage, at least 95% storage fill against the 1 GB/node target on every node, and bulk-ingest throughput at or above 75 fps on every node.
177
-
178
- Full mode defaults to 20,000 records per node, or 2 million records across the 100-node fleet. In full mode the generated PublishEventRecord payload size scales from the configured storage budget so the run targets the requested per-node storage fill without requiring hundreds of millions of tiny records.
179
-
180
- ## Agent Runtime Notes
181
-
182
- Use these entrypoints when wiring agents or deployments:
183
-
184
- | Import | Runtime | Use for |
185
- |--------|---------|---------|
186
- | `flatsql`, `flatsql/wasm` | Emscripten JS wrapper + C++ WASM | Browser/Node SQLite-backed engine |
187
- | `flatsql/ps-threads.wasm`, `flatsql/ps` | The partition store artifact and its locator | Multithreaded partition store hosts |
188
- | `flatsql/ps/node-host` | Node wasi-threads host (thread pool, WASI, flatsql_io, fault overlay) | Tests and Node processes |
189
- | `flatsql/response` | Response artifact bytes | ETags, chunks, and raw byte artifacts keyed by native runtime cache keys |
190
- | `flatsql/standalone/wasmedge` | Native WasmEdge runner builder/client | Persistent standalone runtime in Node |
191
-
192
- For repeated FILE_ID-style traffic, register a cacheable query template once and call `queryTemplate(...)` with positional params. Identical query IDs and params hit the C++ result cache; ingest, load, template changes, and explicit `clearQueryCache()` invalidate stale entries by generation.
193
-
194
- ```javascript
195
- import { initFlatSQL } from 'flatsql';
196
-
197
- const builder = (await initFlatSQL()).createDatabase(schema, 'users');
198
- await builder.registerFileId('USER', 'User');
199
- await builder.enableDemoExtractors();
200
- await builder.ingestBuffers(buffers);
201
- await builder.configureQueryCache({ maxEntries: 4096, maxRows: 5000 });
202
- await builder.registerQueryTemplate('userByEmail', 'SELECT id FROM User WHERE email = ?', true);
203
-
204
- await builder.queryTemplate('userByEmail', ['alice@example.com']); // miss
205
- await builder.queryTemplate('userByEmail', ['alice@example.com']); // hit
206
- console.log(await builder.getQueryCacheStats());
207
- ```
208
-
209
- The native cache holds up to 1024 results within a 64 MiB budget, evicting the least recently used result to stay under it; a result bigger than the whole budget is not cached. `maxRows` caps any single result (default 1,000,000 rows). Tune `maxEntries` and `maxRows` for production FILE_ID traffic instead of recompiling the C++ core.
210
-
211
- For WasmEdge, build the small host runner and open a database through its process runtime. The runner keeps one WASI module instance resident so cache state survives across requests.
212
-
213
- ```javascript
214
- import { buildFlatSQLWasmEdgeRunner, createFlatSQLWasmEdgeProcessRuntime } from 'flatsql/standalone/wasmedge';
215
-
216
- const runner = await buildFlatSQLWasmEdgeRunner({
217
- outputPath: './build/flatsql-wasmedge-runner',
218
- });
219
-
220
- const runtime = createFlatSQLWasmEdgeProcessRuntime({ runnerPath: runner.outputPath });
221
- const builder = await runtime.createDatabase(schema, 'users');
222
- ```
16
+ ```js
17
+ import { FlatSQL } from 'flatsql';
223
18
 
224
- WasmEdge must support WebAssembly exception handling for this artifact. The test runner uses `wasmedge --enable-exception-handling --reactor wasm/flatsql-wasi.wasm _initialize` for the smoke check and the native C API runner enables the same proposal programmatically. Some Node 24 builds require `node --experimental-wasm-exnref` when directly instantiating the standalone WASI artifact.
225
-
226
- For high-volume repeated FILE_ID responses, build the cache key through the C++ runtime, wrap query results as response artifacts, and let edge/browser/WasmEdge host caches serve the immutable bytes by ETag or content hash. FlatSQL creates deterministic native cache keys and chunk metadata; deployment code owns CDN placement, routing, authorization, and global invalidation.
227
-
228
- ```javascript
229
- import { createQueryResponseArtifact } from 'flatsql/response';
230
-
231
- const sql = 'SELECT _data FROM PublishEventRecord WHERE FILE_ID = ?';
232
- const cacheKey = await builder.buildResponseArtifactCacheKey('PublishEventRecord', '1', sql, {
233
- format: 'raw-flatbuffer-stream',
234
- publishEventKey: 'publish-1',
235
- projection: ['_data'],
236
- params: ['publish-1'],
237
- });
238
- const result = await builder.query(sql, ['publish-1']);
239
- const artifact = createQueryResponseArtifact(result, {
240
- schemaName: 'PublishEventRecord',
241
- schemaVersion: 1,
242
- sql,
243
- params: ['publish-1'],
244
- format: 'raw-flatbuffer-stream',
245
- publishEventKey: 'publish-1',
246
- projection: ['_data'],
247
- cacheKey,
248
- });
249
-
250
- console.log(artifact.metadata.cacheKey, artifact.metadata.etag);
251
- ```
252
-
253
- See `docs/response-artifacts.md` and `docs/agents/response-artifacts.md` for production and agent guidance.
254
-
255
-
256
- ## Source Code
257
-
258
- | Repository | Description |
259
- |------------|-------------|
260
- | [digitalarsenal/flatsql](https://github.com/digitalarsenal/flatsql) | This project — FlatSQL query engine |
261
- | [digitalarsenal/flatbuffers](https://github.com/digitalarsenal/flatbuffers) | Fork of Google FlatBuffers with WASM support |
262
- | [flatc-wasm](https://digitalarsenal.github.io/flatbuffers/) | FlatBuffer compiler running in WebAssembly |
263
-
264
- ## Usage
265
-
266
- ### WASM (Browser/Node.js)
267
-
268
- The C++ engine compiles to WebAssembly for cross-platform deployment:
269
-
270
- ```javascript
271
- import { initFlatSQL } from 'flatsql/wasm';
272
-
273
- const flatsql = await initFlatSQL();
274
-
275
- // Create database with schema
276
- const db = flatsql.createDatabase(`
277
- table User {
278
- id: int (id);
279
- name: string;
280
- email: string (key);
281
- age: int;
282
- }
283
- `, 'myapp');
284
-
285
- // Register file identifier routing
286
- db.registerFileId('USER', 'User');
287
-
288
- // Enable demo field extractors (for testing)
289
- db.enableDemoExtractors();
290
-
291
- // Ingest FlatBuffer stream
292
- // Format: [4-byte size LE][FlatBuffer][4-byte size LE][FlatBuffer]...
293
- db.ingest(streamData);
294
-
295
- // Or ingest pre-built FlatBuffers directly through the bulk path.
296
- db.ingestBuffers([
297
- flatsql.createTestUser(1, 'Alice', 'alice@example.com', 30),
298
- flatsql.createTestUser(2, 'Bob', 'bob@example.com', 25),
299
- ]);
300
-
301
- // Query with SQL
302
- const result = db.query('SELECT id, name, email FROM User WHERE age > 25');
303
- console.log(result.columns); // ['id', 'name', 'email']
304
- console.log(result.rows); // [[1, 'Alice', 'alice@example.com'], ...]
305
-
306
- // Export database
307
- const exported = db.exportData();
308
-
309
- // Cleanup
310
- db.destroy();
311
- ```
312
-
313
- ### TypeScript engine (removed)
314
-
315
- The pure TypeScript engine (`FlatSQLDatabase` with `DirectAccessor` /
316
- `FlatcAccessor`) was removed in 3.0.0; `flatsql` 2.x still carries it. The
317
- BTree, stacked store and schema parser remain as `flatsql/btree`,
318
- `flatsql/storage` and `flatsql/schema`.
319
-
320
- ### Cluster Runtime Detection
321
-
322
- Use the runtime guard helpers before attempting browser cluster mode:
323
-
324
- ```typescript
325
- import { detectClusterEnvironment, isClusterModeSupported } from 'flatsql/cluster';
326
-
327
- const env = detectClusterEnvironment();
328
- if (!isClusterModeSupported(env)) {
329
- throw new Error('Cluster mode unsupported on this runtime');
19
+ const db = await FlatSQL.open({ root: './customer-data' });
20
+ await db.registerSchema({ name: 'GJN', fileIdentifier: '$GJN', bfbs });
21
+ await db.ingest('GJN', sizePrefixedRecords);
22
+ for await (const chunk of db.query(qrpRequest)) {
23
+ consumeBinaryResults(chunk);
330
24
  }
25
+ await db.close();
331
26
  ```
332
27
 
333
- ### Native C++ (Embedded)
334
-
335
- For performance-critical applications, link the C++ library directly:
336
-
337
- ```cpp
338
- #include <flatsql/database.h>
339
-
340
- auto db = flatsql::FlatSQLDatabase::fromSchema(schema, "mydb");
341
-
342
- // Register file ID routing
343
- db.registerFileId("USER", "User");
344
-
345
- // Set field extractor
346
- db.setFieldExtractor("User", extractUserField);
347
-
348
- // Ingest streaming data
349
- size_t recordsIngested = 0;
350
- db.ingest(data, length, &recordsIngested);
351
-
352
- // Query
353
- auto result = db.query("SELECT * FROM User WHERE id = 5");
354
- for (size_t i = 0; i < result.rowCount(); i++) {
355
- std::cout << result.getString(i, "name") << std::endl;
356
- }
357
- ```
358
-
359
- ## Architecture
360
-
361
- ```
362
- ┌─────────────────────────────────────────────────────────────┐
363
- │ FlatSQLDatabase │
364
- ├─────────────────────────────────────────────────────────────┤
365
- │ SchemaParser │ SQLiteEngine │
366
- │ (FlatBuffers IDL) │ (Virtual Tables) │
367
- ├─────────────────────────────────────────────────────────────┤
368
- │ TableStore (per table) │
369
- │ ┌────────────────────────────────────────────────────────┐ │
370
- │ │ SQLite Indexes Field Extractors │ │
371
- │ │ (id, email, timestamp) (getField callbacks) │ │
372
- │ └────────────────────────────────────────────────────────┘ │
373
- ├─────────────────────────────────────────────────────────────┤
374
- │ StackedFlatBufferStore (append-only) │
375
- │ ┌──────────────────────────────────────────────────────┐ │
376
- │ │ [Header][FB₁][FB₂][FB₃]... │ │
377
- │ └──────────────────────────────────────────────────────┘ │
378
- └─────────────────────────────────────────────────────────────┘
379
- ```
380
-
381
- ## Schema Joins & References
382
-
383
- - JSON schemas that reference other definitions now produce explicit ``From_Target_join`` tables with `{FromRowId, TargetRowId}` columns.
384
- - This keeps referenced schemas in the same normalized storage layer so queries can `JOIN` the virtual tables instead of embedding opaque JSON blobs.
385
-
386
- ### Stream Format
387
-
388
- FlatSQL ingests size-prefixed FlatBuffer streams:
389
-
390
- ```
391
- [4-byte size LE][FlatBuffer with file_id][4-byte size LE][FlatBuffer]...
392
- ```
393
-
394
- The 4-byte file identifier in each FlatBuffer determines which table receives the record.
395
-
396
- ## Spatial Extensions
397
-
398
- FlatSQL includes a full spatial computation engine with SpatiaLite-compatible functions, polygon boolean operations, Voronoi/Delaunay tessellation, and coordinate transforms.
399
-
400
- ### Spatial SQL Functions
401
-
402
- | Category | Functions |
403
- |----------|-----------|
404
- | **Point ops** | `geo_distance`, `geo_bearing`, `geo_destination`, `geo_midpoint`, `geo_area_bbox`, `geo_within_radius`, `geo_bbox_contains` |
405
- | **Geohash** | `geo_geohash_encode`, `geo_geohash_decode_lat`, `geo_geohash_decode_lon` |
406
- | **Geometry I/O** | `geo_from_text` (WKT→blob), `geo_as_text` (blob→WKT), `geo_from_geojson`, `geo_as_geojson` |
407
- | **Predicates** | `geo_contains` (point-in-polygon, ray casting with hole support) |
408
- | **Boolean ops** | `geo_intersection`, `geo_union`, `geo_difference`, `geo_sym_difference`, `geo_buffer` |
409
- | **Analysis** | `geo_area_geom`, `geo_centroid`, `geo_length_geom`, `geo_envelope`, `geo_convex_hull` |
410
- | **Voronoi/Delaunay** | `geo_voronoi`, `geo_delaunay` |
411
- | **Coord transforms** | `geo_to_ecef` (WGS84→ECEF), `geo_from_ecef` (ECEF→WGS84) |
412
-
413
- ### Examples
414
-
415
- ```sql
416
- -- Distance between NYC and DC (~328 km)
417
- SELECT geo_distance(40.7128, -74.0060, 38.9072, -77.0369);
418
-
419
- -- Bearing from NYC to LA
420
- SELECT geo_bearing(40.7128, -74.0060, 34.0522, -118.2437);
421
-
422
- -- Geohash encode/decode
423
- SELECT geo_geohash_encode(42.6, -5.6, 5); -- 'ezs42'
424
-
425
- -- Point-in-polygon
426
- SELECT geo_contains('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))', 5, 5); -- 1
427
-
428
- -- Polygon intersection (accepts WKT or blob)
429
- SELECT geo_as_text(geo_intersection(
430
- 'POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))',
431
- 'POLYGON((5 5, 15 5, 15 15, 5 15, 5 5))'
432
- ));
433
-
434
- -- WGS84 to ECEF coordinate transform
435
- SELECT geo_to_ecef(48.8566, 2.3522, 35); -- '4200952.xxx,172458.xxx,4780111.xxx'
436
-
437
- -- Voronoi diagram
438
- SELECT geo_as_text(geo_voronoi(
439
- 'MULTIPOINT((0 0), (10 0), (10 10), (0 10))',
440
- 'POLYGON((-5 -5, 15 -5, 15 15, -5 15, -5 -5))'
441
- ));
442
- ```
443
-
444
- ### R-Tree Spatial Indexing
445
-
446
- FlatSQL automatically creates R-Tree shadow tables for spatial columns during ingest. Spatial columns are detected by:
447
- - **Convention**: columns named `latitude`/`lat` + `longitude`/`lon`/`lng`
448
- - **Schema attribute**: fields marked with `(spatial)` in FlatBuffers IDL
449
-
450
- ```sql
451
- -- R-Tree pre-filter + precise radius check (72x faster than full scan)
452
- SELECT p.* FROM points p
453
- INNER JOIN _rtree_points r ON p.rowid = r.id
454
- WHERE r.minLat >= 36.2 AND r.maxLat <= 45.2
455
- AND r.minLon >= -80 AND r.maxLon <= -68
456
- AND geo_within_radius(40.7, -74.0, p.lat, p.lon, 500) = 1;
457
- ```
458
-
459
- ### Spatial Performance
460
-
461
- | Operation | Throughput | Latency |
462
- |-----------|-----------|---------|
463
- | Point operations (distance, bearing) | 733K–1.07M ops/sec | ~1 µs |
464
- | Geohash encode/decode | 631K–746K ops/sec | ~1.4 µs |
465
- | Point-in-polygon | 127K–255K ops/sec | ~4 µs |
466
- | Polygon intersection | 81K ops/sec | ~12 µs |
467
- | Voronoi (4 sites) | 34K ops/sec | ~29 µs |
468
- | Coordinate transforms | 556K–609K ops/sec | ~1.7 µs |
469
- | R-Tree bbox query (10K records) | 77K ops/sec | ~13 µs |
470
- | R-Tree vs full scan speedup | **58–72x** | |
471
-
472
- ### Space Data Module (SDM)
473
-
474
- FlatSQL Spatial is also available as a standalone WASM module compliant with the [Space Data Module SDK](https://github.com/DigitalArsenal/space-data-module-sdk):
475
-
476
- ```javascript
477
- import { initSpatialSDM } from 'flatsql/sdm';
478
-
479
- const sdm = await initSpatialSDM();
480
-
481
- // Read the embedded manifest
482
- console.log(sdm.getManifest().pluginId);
483
- // → 'com.digitalarsenal.flatsql.spatial'
484
-
485
- // Compute distance
486
- const km = sdm.computeDistance(40.7128, -74.006, 38.9072, -77.0369);
487
-
488
- // Point-in-polygon
489
- const inside = sdm.pointInPolygon('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))', 5, 5);
490
-
491
- // Coordinate transform
492
- const ecef = sdm.toECEF(48.8566, 2.3522, 35);
493
- const back = sdm.fromECEF(ecef.x, ecef.y, ecef.z);
494
- ```
495
-
496
- ## SQL Support
497
-
498
- The WASM engines (`initFlatSQL()`, the partition store's reader lanes) embed
499
- SQLite. (The pure-JS engine and its SQL subset were removed in 3.0.0.)
500
-
501
- ### Fail-closed guarantee
502
-
503
- The engines reject what they cannot execute: a caller that asks for a subset
504
- gets that subset or an error, never the whole table with no indication that
505
- the filter was dropped.
506
-
507
- ### Supported
508
-
509
- - `SELECT` with column selection
510
- - `WHERE` with `=`, `!=`/`<>`, `<`, `>`, `<=`, `>=`, `BETWEEN`, `IN`, `LIKE`,
511
- `IS NULL`, and `AND` / `OR` / `NOT` / parentheses
512
- - `LIKE` with `%` and `_` wildcards, ASCII case-insensitive (the SQLite default)
513
- - `ORDER BY` (ASC/DESC, multiple terms)
514
- - `LIMIT` and `OFFSET`
515
- - `COUNT(*)` aggregate (WASM engine)
516
- - Index-accelerated queries on `(id)` and `(key)` columns, including as one
517
- conjunct of a larger `AND` predicate
518
-
519
- ### Not Supported
520
-
521
- - `JOIN` (query one table at a time)
522
- - `GROUP BY`, `HAVING`, most aggregates
523
- - `INSERT`, `UPDATE`, `DELETE` (use API methods instead)
524
- - Subqueries, CTEs, window functions
525
-
526
- ## Performance
527
-
528
- FlatSQL outperforms traditional SQLite on query operations:
529
-
530
- | Operation | FlatSQL | SQLite | Speedup |
531
- |-----------|---------|--------|---------|
532
- | Point query (by id) | 3.50 ms | 3.93 ms | 1.1x |
533
- | Point query (by key) | 5.23 ms | 6.94 ms | 1.3x |
534
- | Direct index lookup | 1.56 ms | 3.93 ms | 2.5x |
535
- | Full scan | 0.84 ms | 1.25 ms | 1.5x |
536
- | Direct iteration | 0.05 ms | 1.25 ms | 25x |
537
-
538
- *Benchmarks: 10,000 records, 10,000 query iterations, Apple M3 Ultra*
539
-
540
- ## Performance Trade-offs
541
-
542
- FlatSQL uses SQLite's virtual table (VTable) API to expose FlatBuffer data as queryable tables. This architecture enables SQL queries over raw binary data, but comes with fundamental trade-offs that affect performance characteristics.
28
+ `bfbs` is a binary reflection schema. Records are concatenated size-prefixed
29
+ FlatBuffers. `qrpRequest` is an SDS QRP FlatBuffer containing SQL and typed
30
+ parameters. Results are a stream of size-prefixed SDS QRP pages. Request
31
+ `{ records: true }` with `SELECT _data` to return the original record frames.
32
+ Stream chunks can split frames; consume the four-byte lengths when framing.
543
33
 
544
- ### Access Paths
34
+ Use the canonical [SDS GJN schema](https://github.com/DigitalArsenal/spacedatastandards.org)
35
+ for geometry. For example, a QRP request containing
36
+ `SELECT _data FROM GJN WHERE geo_contains(_data, 40.75, -73.98)` filters binary
37
+ records. Geometry projections such as `geo_envelope(_data)` return GJN bytes
38
+ inside QRP values. Scalar distance and radius queries remain available.
545
39
 
546
- FlatSQL provides three access paths with different performance profiles:
40
+ Text conversion belongs in a separate program such as
41
+ [flatc-wasm](https://digitalarsenal.github.io/flatc-wasm/). The engine has no
42
+ JSON, GeoJSON, WKT, or schema-text conversion and no SQLite JSON extension.
43
+ It has no runtime npm dependencies. Generated SDS bindings can construct and
44
+ read the binary requests/results in your application.
547
45
 
548
- | Access Path | Latency | Throughput | Use Case |
549
- |-------------|---------|------------|----------|
550
- | **Zero-Copy API** | 1.7 µs | 580K ops/sec | Direct index lookup, returns raw FlatBuffer pointer |
551
- | **VTable SQL** | 12.9 µs | 78K ops/sec | Full SQL queries via SQLite |
552
- | **Pure SQLite** | 2.5 µs | 400K ops/sec | Baseline comparison |
46
+ ## Binary CLI
553
47
 
554
- ### Why VTable Queries Are Slower Than Pure SQLite
555
-
556
- SQLite's VTable API has fundamental limitations that prevent FlatSQL from matching pure SQLite query performance:
557
-
558
- 1. **Per-Column Extraction** — The `xColumn()` callback is invoked once per column per row. There is no batch API to extract multiple fields at once, meaning each field access has function call overhead.
559
-
560
- 2. **Mandatory Value Conversion** — All values must be converted to SQLite's internal format via `sqlite3_result_*()` functions. Even though FlatBuffers already store data in an efficient binary format, we must convert strings to SQLite strings, integers to SQLite integers, etc.
561
-
562
- 3. **Row-by-Row Processing** — The `xNext()` callback advances one row at a time with no vectorized or batch iteration. This prevents SIMD optimizations or processing multiple records per call.
563
-
564
- 4. **No Direct Memory Access** — SQLite cannot read FlatBuffer memory directly; all data must flow through the VTable callback interface, adding overhead for every field access.
565
-
566
- ### When to Use Each Access Path
567
-
568
- **Use the Zero-Copy API when:**
569
- - You need maximum throughput for point lookups
570
- - You're building hot paths that query by indexed keys
571
- - You can work directly with FlatBuffer data structures
572
-
573
- ```cpp
574
- // Zero-copy: returns pointer to raw FlatBuffer (1.7 µs)
575
- const uint8_t* data = db.findRawByIndex("User", "email", email, &len);
576
- auto user = GetUser(data); // Direct FlatBuffer access
48
+ ```sh
49
+ # Feed the binary output of your converter through stdin.
50
+ flatsql ingest --root ./store --schema GJN.bfbs --type GJN --id '$GJN' < records.fb > receipts.fb
51
+ flatsql query --root ./store --request query.qrp > results.qrp
52
+ flatsql query --root ./store --records < records-query.qrp > records.fb
577
53
  ```
578
54
 
579
- **Use VTable SQL when:**
580
- - You need complex queries (filtering, sorting, aggregation)
581
- - Query flexibility is more important than raw speed
582
- - You're doing ad-hoc exploration of the data
583
-
584
- ```cpp
585
- // VTable SQL: full query capability (12.9 µs)
586
- auto result = db.query("SELECT * FROM User WHERE age > 25 ORDER BY name");
587
- ```
588
-
589
- ### Architecture: SQLite-Backed Indexes
590
-
591
- FlatSQL uses SQLite's highly optimized B-tree for indexing (not a custom implementation). This provides:
592
-
593
- - **Battle-tested performance** — SQLite's B-tree is used by billions of devices
594
- - **Consistent behavior** — Same indexing code path as pure SQLite
595
- - **Fast path optimization** — Type-specific lookups bypass `std::variant` overhead
596
-
597
- The index stores `(key, sequence) → (offset, length)` mappings, allowing O(log n) lookups that return pointers directly into the FlatBuffer storage.
598
-
599
- ### Trade-off Summary
600
-
601
- | Aspect | FlatSQL | Pure SQLite |
602
- |--------|---------|-------------|
603
- | Point lookup (indexed) | **1.4x faster** (zero-copy API) | Baseline |
604
- | SQL queries | 5-7x slower (VTable overhead) | Baseline |
605
- | Storage format | FlatBuffers (portable, zero-copy) | SQLite pages |
606
- | Data conversion | None (zero-copy) or on-demand | Always required |
607
- | Streaming ingest | Append-only, real-time indexing | Row-by-row inserts |
55
+ CLI ingest batches are bounded to 4 MiB, including each record's size prefix.
56
+ The JavaScript request capacity is configurable. Errors go to stderr and produce
57
+ a nonzero exit status; stdout contains binary results only.
608
58
 
609
- **Bottom line:** FlatSQL excels when you need streaming ingestion of FlatBuffer data with SQL query capability. Use the zero-copy API for performance-critical lookups; use SQL for complex queries where flexibility matters more than speed.
59
+ ## Runtime requirements
610
60
 
611
- ## Building from Source
61
+ Node 24 or later uses the bundled generic thread/filesystem adapter. With an
62
+ explicit `root`, data survives `close()` and later opens. Without one, Node uses
63
+ a temporary store. Browsers use OPFS and require cross-origin isolation
64
+ (`Cross-Origin-Opener-Policy: same-origin` and
65
+ `Cross-Origin-Embedder-Policy: require-corp` or `credentialless`). Serve the
66
+ package's runtime workers and WASM file as static assets. The
67
+ [interactive demo](https://digitalarsenal.github.io/flatsql/) shows this setup.
612
68
 
613
- ### Prerequisites
69
+ The same artifact imports WASI preview 1, shared memory, `wasi.thread-spawn`, and
70
+ seven filesystem operations. Native builds expose the same `flatsql_*` ABI.
71
+ See [the complete API and geometry contract](docs/API-3.12.md),
72
+ [the native build](cpp/README.md), and [CI evidence](docs/CI.md).
614
73
 
615
- - Node.js 24+
616
- - CMake 3.20+ (for WASM builds)
617
- - Emscripten (for WASM builds)
74
+ ## Development
618
75
 
619
- ### TypeScript Build
620
-
621
- ```bash
622
- npm install
76
+ ```sh
77
+ npm ci --ignore-scripts
623
78
  npm run build
624
- npm test
625
- ```
626
-
627
- ### WASM Build
628
-
629
- ```bash
630
- # Clone DA-FlatBuffers (required dependency)
631
- git clone https://github.com/DigitalArsenal/flatbuffers.git ../flatbuffers
632
-
633
- # Build browser, standalone WASI, and SDM artifacts
634
- npm run build:wasm
635
-
636
- # Verify standalone and package surfaces
637
- npm run test:wasi
638
- npm run test:wasmedge
639
- npm test -- --runInBand --runTestsByPath test/package-exports.test.ts
79
+ npm run test:e2e # set FLATBUFFERS_DIR to the pinned checkout for native checks
80
+ npm run test:browser # installed Chromium headless shell required
640
81
  ```
641
82
 
642
- Output: `wasm/flatsql.js`, `wasm/flatsql.wasm`, `wasm/flatsql-wasi.wasm`, `sdm/flatsql-spatial.wasm`, and docs copies for GitHub Pages.
643
-
644
- The partition store artifact `wasm/flatsql-ps-threads.wasm` needs wasi-sdk 30 (`WASI_SDK_PATH`) and builds with `npm run build:wasm:ps` (`npm run build:wasm:ps-tests` adds the wasm test commands); see docs/PARTITION-STORE-WASM.md.
645
-
646
- ### Run Demo Locally
647
-
648
- ```bash
649
- npm run serve
650
- # Open http://localhost:8081
651
- ```
652
-
653
- ## API Reference
654
-
655
- ### initFlatSQL()
656
-
657
- ```typescript
658
- import { initFlatSQL } from 'flatsql/wasm';
659
-
660
- const flatsql = await initFlatSQL();
661
- ```
662
-
663
- ### createDatabase(schema, name)
664
-
665
- ```typescript
666
- const db = flatsql.createDatabase(schemaString, 'dbname');
667
- ```
668
-
669
- ### db.registerFileId(fileId, tableName)
670
-
671
- ```typescript
672
- db.registerFileId('USER', 'User'); // Route "USER" FlatBuffers to User table
673
- ```
674
-
675
- ### db.ingest(data, source?)
676
-
677
- ```typescript
678
- const bytesConsumed = db.ingest(uint8ArrayStream);
679
- // Or with source tagging (requires registerSource first):
680
- const bytesConsumed = db.ingest(uint8ArrayStream, 'satellite-1');
681
- ```
682
-
683
- ### db.query(sql)
684
-
685
- ```typescript
686
- const result = db.query('SELECT * FROM User WHERE age > 25');
687
- // result.columns: string[]
688
- // result.rows: any[][]
689
- ```
690
-
691
- ### db.exportData()
692
-
693
- ```typescript
694
- const data = db.exportData(); // Returns Uint8Array
695
- ```
696
-
697
- ### db.registerSource(sourceName)
698
-
699
- ```typescript
700
- db.registerSource('satellite-1'); // Creates User@satellite-1, Post@satellite-1, etc.
701
- ```
702
-
703
- ### db.createUnifiedViews()
704
-
705
- ```typescript
706
- db.createUnifiedViews(); // Creates unified views with _source column
707
- ```
708
-
709
- ### db.listSources()
710
-
711
- ```typescript
712
- const sources = db.listSources(); // ['satellite-1', 'satellite-2', ...]
713
- ```
714
-
715
- ## Multi-Source Queries
716
-
717
- FlatSQL supports federating multiple data sources with the same schema. Each source gets its own set of tables, and you can query them individually or across all sources.
718
-
719
- ### Use Case
720
-
721
- Imagine you have multiple satellites streaming telemetry data with the same schema:
722
-
723
- ```javascript
724
- // Register sources
725
- db.registerSource('satellite-1');
726
- db.registerSource('satellite-2');
727
- db.registerSource('ground-station');
728
-
729
- // Register file IDs and extractors (must be done after registerSource)
730
- db.registerFileId('TELE', 'Telemetry');
731
- db.enableDemoExtractors();
732
-
733
- // Create unified views (call once after all sources registered)
734
- db.createUnifiedViews();
735
-
736
- // Ingest from different sources
737
- db.ingest(satellite1Stream, 'satellite-1');
738
- db.ingest(satellite2Stream, 'satellite-2');
739
- db.ingest(groundStream, 'ground-station');
740
-
741
- // Query a specific source
742
- db.query('SELECT * FROM "Telemetry@satellite-1" WHERE signal > 50');
743
-
744
- // Query across all sources (unified view)
745
- db.query('SELECT * FROM Telemetry WHERE timestamp > 1000');
746
- ```
747
-
748
- ### Table Naming Convention
749
-
750
- - **Source-specific tables**: `TableName@sourceName` (e.g., `User@siteA`, `Telemetry@satellite-1`)
751
- - **Unified views**: `TableName` (e.g., `User`, `Telemetry`) - combines all source tables with a `_source` column
752
-
753
- ### The _source Column
754
-
755
- Unified views include a `_source` column that identifies which source each row came from:
756
-
757
- ```sql
758
- -- See source for each record
759
- SELECT _source, id, name FROM User LIMIT 10;
760
-
761
- -- Count records by source
762
- SELECT _source, COUNT(*) as count FROM User GROUP BY _source;
763
-
764
- -- Filter by source in unified view
765
- SELECT * FROM User WHERE _source = 'satellite-1';
766
- ```
767
-
768
- ## Database-key encryption
769
-
770
- Fields marked `(encrypted)` in the schema are stored encrypted under the database key and decrypted when SQL reads them. The format is FlatBuffers field-encryption format 3: each record gets its own key, derived from the database key and the record's index, and each encrypted value gets its own IV, its position in the record. No two records and no two values share a key stream.
771
-
772
- The record index is the record's sequence, its `rowid`. Records are numbered 1, 2, 3, ... in stream order, so the stream carries the index: an exported stream loaded into an empty database (`loadAndRebuild`) gives every record the index it was encrypted with. Load an encrypted stream into an empty database only.
773
-
774
- ```javascript
775
- db.registerFileId('SECR', 'Secret');
776
- db.setEncryptionKey(key); // 32 bytes
777
- const seq = db.ingestOneEncrypted(record, bfbs); // encrypted as record `seq`
778
- db.query('SELECT code FROM Secret'); // plaintext
779
- ```
780
-
781
- - `bfbs` is the table's binary schema with builtin attributes (`flatc -b --schema --bfbs-builtins`). It must mark the same fields `(encrypted)` as the database schema, or the call is refused.
782
- - `ingestOneEncrypted(record, bfbs, source?)` assigns the sequence and encrypts under it in one step. `encryptBuffer(record, bfbs, recordIndex)` and `decryptBuffer(record, bfbs, recordIndex)` take the index explicitly.
783
- - `(encrypted)` columns are never indexed. A predicate on one scans and decrypts.
784
- - A database that already holds records in tables with `(encrypted)` columns needs their format declared: `setEncryptionKey(key, { format: 3 })`. Nothing in the stream marks the format.
785
-
786
- **Crypto backend.** Format 3 derives keys with HKDF-SHA256 and encrypts with AES-256-CTR, and `computeHMAC`/`verifyHMAC` are HMAC-SHA256 under the database key. All three come from the FlatBuffers crypto backend. Native builds use OpenSSL when CMake finds it. The wasm builds (`flatsql.wasm`, `flatsql-wasi.wasm`, `flatsql-wasi-noeh.wasm`) and native builds without OpenSSL compile the FlatBuffers fallback backend, which implements them from FlatBuffers 8af3053e on and derives the same keys as OpenSSL: the same key and record give the same stored bytes in every build. Built against an older FlatBuffers, the fallback's key derivation is not HKDF-SHA256 (it had 256 outputs, so records would share key streams); such a build refuses a key for a database with `(encrypted)` columns, with an error that names HKDF-SHA256. `FlatSQLDatabase::recordEncryptionAvailable()` checks the backend against an HKDF-SHA256 known answer.
787
-
788
- **Format 2 (flatsql 3.2.0 and earlier).** Those versions encrypted every record under the same field keys, those of record 0, derived by the fallback backend. This version refuses format 2: `setEncryptionKey(key, { format: 2 })` throws, and a database that holds records must declare format 3. To migrate, use flatsql 3.2.0 and the old key to read each record of `exportData()` back as plaintext with `decryptBuffer(record, bfbs)`. Then ingest the plaintext records into a new database with `ingestOneEncrypted`.
789
-
790
- ## License
791
-
792
- [PolyForm Noncommercial License 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0/) — source-available, free for any noncommercial purpose. Commercial use requires a separate license from Edgesource Corporation See [LICENSE](LICENSE).
793
-
794
- ## Contributing
795
-
796
- Contributions welcome. Please open an issue first to discuss significant changes.
797
-
798
- ## Contact
799
-
800
- For questions, licensing inquiries, or commercial support: [tj@edgesource.com](mailto:tj@edgesource.com)
801
-
802
- ---
803
-
804
- Built on [DA-FlatBuffers](https://digitalarsenal.github.io/flatbuffers/) and [SQLite](https://sqlite.org/).
805
-
806
- ### Full-text indexing of record archives
807
-
808
- The bundled SQLite engine includes FTS5 on native, browser and WASI targets.
809
- `SELECT flatsql_record_text('CAT', ?)` extracts searchable scalar/text fields
810
- from a bound FlatBuffer blob using the registered table schema. Both raw and
811
- size-prefixed records are accepted; the file identifier must match the table.
812
- Encrypted columns, opaque byte vectors and internal columns are excluded.
813
- Scalar defaults retain their declared type. Enums use their declared integer
814
- width when their definition is present; unresolved named types and non-byte
815
- vectors stay opaque instead of being treated as strings.
816
- Unknown schemas and malformed supported fields return SQL errors.
83
+ CI runs short end-to-end flows against the packed npm artifact and native
84
+ engine. It does not run unit suites. Release CI reproduces the committed WASM
85
+ bytes with wasi-sdk 30 before npm trusted publishing.
817
86
 
818
- A host can persist that text in an FTS5 table keyed by its durable record ID,
819
- including records outside the engine's active record window. Index lifecycle,
820
- source permissions, coverage and cursor identity remain the host's responsibility.
821
- The function covers the fields declared in the registered schema; it does not
822
- recursively decode nested tables or invent a text representation for binary data.
87
+ Licensed under [PolyForm Noncommercial 1.0.0](LICENSE). Commercial licensing:
88
+ [tj@edgesource.com](mailto:tj@edgesource.com).