firebird-wasm 0.7.0 → 0.9.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 (99) hide show
  1. package/CHANGELOG.md +266 -0
  2. package/README.md +31 -15
  3. package/dist/.tsbuildinfo.build +1 -1
  4. package/dist/browser/engine-transport.d.ts +2 -0
  5. package/dist/browser/engine-transport.d.ts.map +1 -1
  6. package/dist/browser/engine-transport.js +43 -11
  7. package/dist/browser/engine-transport.js.map +1 -1
  8. package/dist/browser/firebird-browser.d.ts.map +1 -1
  9. package/dist/browser/firebird-browser.js +59 -20
  10. package/dist/browser/firebird-browser.js.map +1 -1
  11. package/dist/browser/index.d.ts +3 -3
  12. package/dist/browser/index.d.ts.map +1 -1
  13. package/dist/browser/index.js +6 -3
  14. package/dist/browser/index.js.map +1 -1
  15. package/dist/browser/params.d.ts +8 -2
  16. package/dist/browser/params.d.ts.map +1 -1
  17. package/dist/browser/params.js +14 -5
  18. package/dist/browser/params.js.map +1 -1
  19. package/dist/browser/serve-engine.d.ts +30 -0
  20. package/dist/browser/serve-engine.d.ts.map +1 -0
  21. package/dist/browser/serve-engine.js +93 -0
  22. package/dist/browser/serve-engine.js.map +1 -0
  23. package/dist/browser/worker-entry.d.ts +8 -22
  24. package/dist/browser/worker-entry.d.ts.map +1 -1
  25. package/dist/browser/worker-entry.js +18 -79
  26. package/dist/browser/worker-entry.js.map +1 -1
  27. package/dist/electric/apply.d.ts +53 -0
  28. package/dist/electric/apply.d.ts.map +1 -0
  29. package/dist/electric/apply.js +88 -0
  30. package/dist/electric/apply.js.map +1 -0
  31. package/dist/electric/index.d.ts +133 -0
  32. package/dist/electric/index.d.ts.map +1 -0
  33. package/dist/electric/index.js +378 -0
  34. package/dist/electric/index.js.map +1 -0
  35. package/dist/electric/serial.d.ts +20 -0
  36. package/dist/electric/serial.d.ts.map +1 -0
  37. package/dist/electric/serial.js +29 -0
  38. package/dist/electric/serial.js.map +1 -0
  39. package/dist/electric/values.d.ts +65 -0
  40. package/dist/electric/values.d.ts.map +1 -0
  41. package/dist/electric/values.js +257 -0
  42. package/dist/electric/values.js.map +1 -0
  43. package/dist/electric/writes.d.ts +99 -0
  44. package/dist/electric/writes.d.ts.map +1 -0
  45. package/dist/electric/writes.js +282 -0
  46. package/dist/electric/writes.js.map +1 -0
  47. package/dist/electric-server/capture.d.ts +51 -0
  48. package/dist/electric-server/capture.d.ts.map +1 -0
  49. package/dist/electric-server/capture.js +224 -0
  50. package/dist/electric-server/capture.js.map +1 -0
  51. package/dist/electric-server/catalog.d.ts +61 -0
  52. package/dist/electric-server/catalog.d.ts.map +1 -0
  53. package/dist/electric-server/catalog.js +137 -0
  54. package/dist/electric-server/catalog.js.map +1 -0
  55. package/dist/electric-server/index.d.ts +85 -0
  56. package/dist/electric-server/index.d.ts.map +1 -0
  57. package/dist/electric-server/index.js +528 -0
  58. package/dist/electric-server/index.js.map +1 -0
  59. package/dist/electric-server/where.d.ts +30 -0
  60. package/dist/electric-server/where.d.ts.map +1 -0
  61. package/dist/electric-server/where.js +243 -0
  62. package/dist/electric-server/where.js.map +1 -0
  63. package/dist/firebird.d.ts +3 -1
  64. package/dist/firebird.d.ts.map +1 -1
  65. package/dist/firebird.js +259 -22
  66. package/dist/firebird.js.map +1 -1
  67. package/dist/frameworks/query-store.d.ts +99 -0
  68. package/dist/frameworks/query-store.d.ts.map +1 -0
  69. package/dist/frameworks/query-store.js +166 -0
  70. package/dist/frameworks/query-store.js.map +1 -0
  71. package/dist/index.d.ts +1 -1
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +2 -1
  74. package/dist/index.js.map +1 -1
  75. package/dist/react/index.d.ts +68 -0
  76. package/dist/react/index.d.ts.map +1 -0
  77. package/dist/react/index.js +92 -0
  78. package/dist/react/index.js.map +1 -0
  79. package/dist/repl/index.d.ts +76 -0
  80. package/dist/repl/index.d.ts.map +1 -0
  81. package/dist/repl/index.js +328 -0
  82. package/dist/repl/index.js.map +1 -0
  83. package/dist/types.d.ts +2 -1
  84. package/dist/types.d.ts.map +1 -1
  85. package/dist/version.d.ts +1 -1
  86. package/dist/version.js +1 -1
  87. package/dist/vue/index.d.ts +68 -0
  88. package/dist/vue/index.d.ts.map +1 -0
  89. package/dist/vue/index.js +107 -0
  90. package/dist/vue/index.js.map +1 -0
  91. package/dist/wasm/firebird-embedded-st.js +1 -1
  92. package/dist/wasm/firebird-embedded-st.wasm +0 -0
  93. package/dist/wasm/firebird-embedded.js +1 -1
  94. package/dist/wasm/firebird-embedded.wasm +0 -0
  95. package/dist/wasm-loader.d.ts +57 -7
  96. package/dist/wasm-loader.d.ts.map +1 -1
  97. package/dist/wasm-loader.js +106 -21
  98. package/dist/wasm-loader.js.map +1 -1
  99. package/package.json +50 -3
package/CHANGELOG.md CHANGED
@@ -4,6 +4,272 @@ All notable changes to `firebird-wasm`. This project follows
4
4
  [semantic versioning](https://semver.org/); while the major version is 0 the
5
5
  API may still move between minor releases.
6
6
 
7
+ ## 0.9.0 — 2026-10-19
8
+
9
+ ### Changed
10
+
11
+ - **Closing the last database frees the engine.** A module used to stay for
12
+ the life of the page or process; now the transports count the databases
13
+ that use one, and the last `close()` disposes it, unless whoever called
14
+ `loadFirebirdWasm()` still owns it. The next database loads a new module
15
+ (0.33–0.43 s without threads, about 1 s threaded); to keep one loaded
16
+ across closes, call `loadFirebirdWasm()` once, which owns it. A `memory://` database
17
+ went with its `close()` already; nothing else kept state in the module. An
18
+ `opfs://` database's access handle is now closed on `close()` too, so the
19
+ same Worker can open it again.
20
+ - **The build without threads is the default.** `wasmGlueFile()`,
21
+ `loadFirebirdWasm()`, `DirectTransport` and `cdnArtifacts()` now pick
22
+ `firebird-embedded-st` unless asked for threads, in Node and in a cross-origin
23
+ isolated page too; until now `'auto'` picked the threaded build wherever it
24
+ could run. The build without threads needs no `SharedArrayBuffer` and no
25
+ Worker, starts in about a third of the time (0.33–0.43 s against
26
+ 0.87–1.06 s), and was not slower in anything measured; `firebird-quake2`
27
+ plays the same on it (a 5 ms tic on demo1 either way). What it gives up is
28
+ statement and idle timeouts. To keep the threaded build, ask for it:
29
+ `wasmGlueFile(true)` (or `wasmGlueFile(self.crossOriginIsolated)` for
30
+ threads where the page allows them), `loadFirebirdWasm({ threads: true })`,
31
+ `new DirectTransport({ threads: true })`, `cdnArtifacts({ threads: true })`.
32
+
33
+ ### Added
34
+
35
+ - **`disposeFirebirdWasm(mod)`** ends a module and lets it go: every database
36
+ detached, the engine shut down (`_fb_dispose`, which calls `fb_shutdown`),
37
+ the threaded build's workers terminated, and the module out of the cache.
38
+ Two things held a dropped threaded module: its 32 workers (about 400 MB in
39
+ Node) and the main thread's `Atomics.waitAsync` on its mailbox. Over ten
40
+ load/dispose cycles in Node, RSS levels off at about 200 MB without threads
41
+ and 300 MB threaded. Needs a rebuilt artifact.
42
+ - **Bytes as parameters.** A `Uint8Array` or `ArrayBuffer` binds as its own
43
+ bytes into a `BLOB` or a `BINARY` / `VARBINARY` (`OCTETS`) column, on both
44
+ backends and by every route (`exec`, `query`, transactions, `execBatch`).
45
+ The browser engine marks the bytes as bytes, so any other column refuses
46
+ them instead of reading them as text; `FirebirdLite` hands them to the
47
+ driver as a `Buffer`. They used to throw.
48
+ - **`bytea` syncs.** `firebird-wasm/electric` decodes Electric's `\x…` hex
49
+ into bytes, and `tableFromSchema` maps `bytea` to `BLOB SUB_TYPE BINARY`
50
+ (`VARCHAR(255) CHARACTER SET OCTETS` in a key). The shape server serves a
51
+ binary `BLOB`, `BINARY` and `VARBINARY` as `bytea`, byte for byte.
52
+ - **`syncShapesToTables`: several tables, one transaction per batch.** The
53
+ shapes are followed together and each batch is applied to every table in
54
+ one Firebird transaction with every shape's position, so a Postgres
55
+ transaction that wrote to two tables never shows in one of them alone. A
56
+ batch waits until every shape has reached up-to-date after its last change,
57
+ and until all are at the same `global_last_seen_lsn` where the server sends
58
+ it; a shape behind is refreshed. The shape server now sends
59
+ `global_last_seen_lsn` (its log position) as Electric does.
60
+ `syncShapeToTable` is the one-shape case of the same code.
61
+ - **A write path: `createWriteQueue`.** `insert`, `update` and `delete`
62
+ apply to the synced table at once and are recorded in `ELECTRIC_WRITES`
63
+ in the same transaction (so they survive a reload and an offline spell),
64
+ then go to the app's API through `send`, in order, retried on failure. A
65
+ sync given `writes` applies the pending writes again after every batch, so
66
+ the source's older state never shows over them, and drops each write once
67
+ its change comes back: by the transaction id `send` returned (Electric's
68
+ `txids`, which the shape server now sends too), by the row changing, or
69
+ after `confirmTimeoutMs`. A `WriteRejected` rolls the write back by
70
+ fetching the table's shape again. Syncs and the queue now run their
71
+ transactions on a database one at a time.
72
+ - **The shape server takes `where`, pages its snapshots, prunes its log,
73
+ wakes on events, and runs beside others.**
74
+ - `where` (and Electric's `params[n]`) is parsed and written as Firebird
75
+ SQL with every value bound; functions, casts and subqueries are refused
76
+ with a 400. Each `where` and `columns` is a shape with its own handle,
77
+ and a row that stops matching leaves as a delete.
78
+ - Snapshots are served `snapshotPageRows` (10 000) rows a response, by
79
+ key.
80
+ - The log is pruned of changes older than `retainLogMs` (7 days), or by
81
+ `prune(before)`; a client further behind is sent back to a snapshot.
82
+ - Commits wake live requests through the `ELECTRIC_CHANGES` event, with
83
+ polling (now every 1 s) as the fallback.
84
+ - Numbering takes a lock, so several servers can serve one database.
85
+ - **An example app that syncs from two servers:
86
+ [`examples/electric`](./examples/electric).** Two lists of notes, each a
87
+ table in the page's Firebird: one follows Postgres through Electric, the
88
+ other a Firebird server through `firebird-wasm/electric-server`. Writes go
89
+ to the source and come back down the shape; `db.live()` draws the lists;
90
+ `<firebird-repl>` queries the local copy. A Node server and a
91
+ `docker-compose.yml` run the sources. CI runs it in Chromium against real
92
+ containers, with both engine builds.
93
+
94
+ ### Fixed
95
+
96
+ - **The statement cache sees DDL it did not run.** It emptied itself before
97
+ DDL the bridge ran, but not for DDL inside `EXECUTE STATEMENT`, a
98
+ procedure, or another attachment: a kept `SELECT` of a table dropped that
99
+ way went on returning the dropped table's rows, and a kept call of a
100
+ procedure altered that way ran its old body. The engine now counts each
101
+ committed metadata change (patch 0007), and a database's kept statements go
102
+ when the count has moved. Reads and writes never move it, so the cache
103
+ works as before for them; `_fb_metadata_commits()` reads the count.
104
+ Calling `_fb_statement_cache()` after such DDL is no longer needed. Needs a
105
+ rebuilt artifact.
106
+ - **`EXECUTE STATEMENT` runs in the browser engine.** It did nothing, without
107
+ an error: an `INSERT` through it inserted no row, `INTO` gave `NULL`, and
108
+ `FOR EXECUTE STATEMENT … DO` gave no rows. It runs through Firebird's
109
+ External Data Sources (`jrd/extds`) even without `ON EXTERNAL`, and the
110
+ build replaced that directory with no-op stubs. It is compiled now, so
111
+ parameters, `WITH AUTONOMOUS TRANSACTION` and the caller's transaction work
112
+ as in Firebird, and its errors are reported.
113
+ Needs a rebuilt artifact: 170–200 KB larger, 54–55 KB gzipped (+2 %).
114
+ - **The browser engine reads `BINARY` and `VARBINARY` (`OCTETS`) values as
115
+ bytes**, as it reads a binary `BLOB`: base64 by default, a `Uint8Array`
116
+ with `types: { binary: true }`. They were read as text, which turned bytes
117
+ that are not UTF-8 into replacement characters (`GEN_UUID()` among them).
118
+ - **Two `syncShapeToTable` calls started together on a new database no
119
+ longer stop one of them.** Both found no `ELECTRIC_SHAPES` table and both
120
+ created it; the second creation failed ("unsuccessful metadata update")
121
+ and that sync stopped before it began. The loser now looks again and goes
122
+ on with the table the other made. Found by the example, which starts its
123
+ two syncs at once.
124
+ - **Closing a shape server while a live request waits no longer crashes the
125
+ process.** `close()` disconnected at once, and a waiting live request's next
126
+ poll started a transaction on the closed connection, where the native
127
+ client segfaults rather than throwing. `close()` now waits for whatever is
128
+ running on the connection, and the requests still waiting end with a 503.
129
+ Found as an intermittent crash of `browser-firebird-shapes.spec.ts`, which
130
+ closes its server while the browser is still following it.
131
+
132
+ ## 0.8.0 — 2026-10-18
133
+
134
+ ### Security
135
+
136
+ - **Importing the library no longer starts an engine server, and a page's
137
+ window is never served.** `firebird-wasm/browser` re-exported
138
+ `serveEngine` from the Worker entry, which started serving as soon as it
139
+ was loaded wherever `self.postMessage` existed, and a page's `window` has
140
+ one. So every page that imported the library listened for engine requests
141
+ on its window. With the engine on the page thread (the build without
142
+ threads, new in 0.7.0) that listener shared the page's engine, and any
143
+ window able to `postMessage` to the page, a frame or an opener of another
144
+ origin, could run SQL against its databases. With the engine in a Worker it
145
+ had no engine to reach. Now `serveEngine` lives in a module without side
146
+ effects, the Worker entry starts only in a dedicated Worker, and
147
+ `serveEngine` refuses a scope that has a `document`. Upgrade from 0.7.0 if
148
+ you run the engine on the page thread.
149
+
150
+ ### Added
151
+
152
+ - **`firebird-wasm/electric-server`: a Firebird server's tables over
153
+ Electric's protocol.** `createShapeServer({ database, tables })` returns a
154
+ Node HTTP handler that `syncShapeToTable` syncs from as it would from
155
+ Electric, so a table on a Firebird server reaches Firebird in the browser
156
+ with no Postgres. Triggers log each changed row's key; the server numbers
157
+ the log in commit order before every read, so a transaction that wrote
158
+ first and committed last is never skipped, and a connection opened before
159
+ the capture was installed is captured too. Tested on Firebird 3, 4 and 5,
160
+ and end to end into the engine in Chromium.
161
+ - **`firebird-wasm/electric`: sync a Postgres table through Electric.**
162
+ `syncShapeToTable(db, { shape, table, shapeKey })` keeps a Firebird table in
163
+ step with an Electric shape: each batch Electric marks up-to-date applied in
164
+ one transaction with the sync position, a resume from that position after a
165
+ reload, a full reload in one transaction on must-refetch, Postgres text
166
+ converted exactly per column type (and unmapped types refused by name), and
167
+ an event per batch for `live()` and the hooks. `tableFromSchema()` writes a
168
+ `CREATE TABLE` from a shape's schema to review. Read-only, as Electric is.
169
+ `@electric-sql/client` is an optional peer dependency. Tested against a real
170
+ Postgres and Electric in CI. A Postgres `int8` is bound as its text, which
171
+ converts into `BIGINT` exactly and which the Node driver can bind.
172
+ - **`firebird-wasm/repl`: a SQL console as a custom element.**
173
+ `defineFirebirdRepl()` registers `<firebird-repl>`; give it a database
174
+ through its `db` property. Ctrl+Enter runs a script statement by statement
175
+ (`SET TERM` included) and stops at the first error; rows show as a table,
176
+ other statements with the rows they changed; ArrowUp and ArrowDown walk the
177
+ history. Values are drawn as text, never as HTML. A `db` set before the
178
+ element was defined, as frameworks do, is kept.
179
+ - **`firebird-wasm/react` and `firebird-wasm/vue`.** `useQuery` runs a
180
+ statement and re-runs it when its SQL or parameters change by value;
181
+ `useLiveQuery` re-runs it each time one of its events fires (`live()`
182
+ under a hook). Both give `rows`, `error`, `loading` and `refresh()`, keep
183
+ the previous rows while the next ones load, drop results that arrive after
184
+ the component moved on, and unsubscribe on unmount, StrictMode's double
185
+ mount included. React through `FirebirdProvider`, Vue through
186
+ `provideFirebird(db)` or `app.provide(FIREBIRD, db)`; the statement and
187
+ parameters may be refs or getters in Vue. React 18+ and Vue 3.3+ are
188
+ optional peer dependencies.
189
+ - **`examples/vite` and `examples/webpack`.** Minimal apps built by their own
190
+ bundlers from the package, run by CI in Chromium with and without
191
+ cross-origin isolation.
192
+
193
+ ### Fixed
194
+
195
+ - **`FirebirdLite` binds a string into a DATE, TIME or TIMESTAMP parameter,
196
+ exactly.** The native driver takes a JS `Date` there and failed on a string
197
+ (`date.getFullYear is not a function`), so `firebird-wasm/electric`'s
198
+ `syncShapeToTable`, which sends dates and times as text, could not write to
199
+ a `FirebirdLite` database. Such a string is now sent as text and Firebird
200
+ converts it, as the browser engine does: to 1/10000 s, with no detour
201
+ through milliseconds or the process's time zone, and `WITH TIME ZONE` too
202
+ (with a Firebird 4 or later client library). A `Date` binds as before.
203
+ - **`FirebirdLite` binds a string into an integer or NUMERIC parameter digit
204
+ for digit.** The driver binds those as doubles: `'9007199254740993'` into
205
+ a BIGINT arrived as 9007199254740992, and `syncShapeToTable` sends `int8`
206
+ and `numeric` as text. Such a string is now sent as text too; a JS number
207
+ is unchanged, and `execBatch` declares a block's parameter as the column's
208
+ own type rather than DOUBLE PRECISION.
209
+ - **A bad parameter beside a BLOB no longer kills the process.** The driver
210
+ wrote a BLOB parameter while encoding the others, under a `Promise.all`
211
+ that rejected as soon as one failed, and the transaction was rolled back
212
+ under the blob's native calls: a date as a string, or a string too long for
213
+ its VARCHAR, beside a BLOB ended Node with a segmentation fault (seen in
214
+ Jest syncing a shape into a `FirebirdLite` database). `FirebirdLite` now
215
+ writes the blob before the driver encodes anything, so the failure is the
216
+ error it should be.
217
+ - **An event posted right after subscribing is no longer lost.** A
218
+ subscription's first delivery from the engine is a baseline, not news, and
219
+ the bridge absorbed whatever it said. With threads that delivery comes from
220
+ the engine's watcher thread some time after the subscription returns, so a
221
+ `POST_EVENT` that committed first was folded into it and never reported: a
222
+ `live()` query missed its first change in about one browser run in three.
223
+ Subscribing now waits for the baseline before returning (the build without
224
+ threads delivered it in time already).
225
+ - **`live()` no longer drops an event heard during a refresh.** It ran one
226
+ refresh at a time by ignoring events while one ran, but that refresh may
227
+ have read before the change committed, so the rows stayed stale until some
228
+ later event. An event during a refresh now earns exactly one more. `live()`
229
+ also subscribes before its first query rather than after, so a change
230
+ committed between the two is no longer in neither, and releases the
231
+ subscription if that query fails.
232
+ - **`FirebirdLite` binds a string into a BLOB parameter.** The native driver
233
+ takes a `Buffer` there and refused a string ("Unrecognized type used as
234
+ BLOB"); a string bound to a BLOB parameter is now passed as its UTF-8
235
+ bytes, in `exec`, `query`, transactions and `execBatch`. The browser engine
236
+ had the same gap, fixed above.
237
+ - **A string binds into a BLOB parameter.** `INSERT INTO notes (body) VALUES
238
+ (?)` with a `BLOB SUB_TYPE TEXT` column failed in the browser engine ("BLOB
239
+ and array data types are not supported for move operation"): a BLOB
240
+ parameter takes a blob's id, and the bridge only converted values. It now
241
+ writes the text as a blob in the statement's transaction and binds its id,
242
+ through `exec`, `query`, transactions and `execBatch` alike, with no size
243
+ limit of its own (text parameters were capped at 64 KB). A batch with a
244
+ BLOB parameter runs as one execute per row, since Firebird's batch takes no
245
+ blob written outside it.
246
+ - **`firebird-wasm/browser/worker-entry` can be imported.** The package's
247
+ `exports` did not list it, so the import the integration guide showed failed
248
+ in any bundler that honours `exports` (Vite and webpack both). It is now
249
+ exported, and so is `./package.json`.
250
+ - **`firebird-wasm/browser/worker-entry` imported with a bundler.** In a
251
+ Worker that imported the library before setting
252
+ `FIREBIRD_WORKER_OPTIONS`, the side effect above started the engine without
253
+ them, so its `.wasm` was looked for next to the Worker script.
254
+ - **A `CHAR(n)` in UTF-8 reads back as n characters.** Firebird pads a
255
+ `CHAR` to its length in bytes, four a character in UTF-8, and the browser
256
+ engine returned all of them: a `CHAR(36)` holding a uuid came back as the
257
+ uuid and 108 spaces. It is now cut to n characters, its own padding kept
258
+ (`'ab'` in a `CHAR(5)` is `'ab '`, `'žš'` is `'žš '`), as other
259
+ Firebird drivers return it. `OCTETS` and `NONE` columns, one byte a
260
+ character, are unchanged.
261
+
262
+ ### Changed
263
+
264
+ - **A quoted column name keeps its case in results.** `query` upper-cased
265
+ every column name, so `SELECT title AS "title"` gave `TITLE`, unlike what
266
+ the integration guide said, and `SELECT "camelCase" FROM t` gave
267
+ `CAMELCASE`. Names are now as Firebird reports them, in the browser engine
268
+ and in `FirebirdLite` alike: unquoted names are still upper case (Firebird
269
+ folds them), so only quoted names change. Code that read a quoted name by
270
+ its upper-cased key (`row.CAMELCASE`) must use the name as written
271
+ (`row.camelCase`), or drop the quotes from the alias.
272
+
7
273
  ## 0.7.0 — 2026-10-18
8
274
 
9
275
  ### Fixed
package/README.md CHANGED
@@ -51,11 +51,13 @@ const { rows } = await db.query('SELECT * FROM notes');
51
51
  // [{ ID: 1, TITLE: 'Hello' }]
52
52
  ```
53
53
 
54
- Two constraints are structural for the default, threaded build: the page must
54
+ The default build, `firebird-embedded-st.js`, has no threads: it needs no
55
+ `SharedArrayBuffer`, so no cross-origin isolation, and can run without a
56
+ Worker. The threaded build (`wasmGlueFile(true)`, `threads: true`) adds
57
+ statement and idle timeouts, and has two structural constraints: the page must
55
58
  be [cross-origin isolated](../../docs/integration.md#2-the-two-hard-requirements-and-the-build-without-them),
56
59
  and the engine must run in a Worker — it blocks on mutexes, and a browser main
57
- thread may not block. `firebird-embedded-st.js`, the build without threads,
58
- has neither constraint and gives up statement and idle timeouts.
60
+ thread may not block. Until 0.9.0 the threaded build was the default.
59
61
 
60
62
  ## Quick start
61
63
 
@@ -114,7 +116,7 @@ Execute a query and return a `QueryResult<T>`:
114
116
 
115
117
  ```ts
116
118
  interface QueryResult<T> {
117
- rows: T[]; // result rows keyed by UPPER-CASE column name
119
+ rows: T[]; // result rows keyed by column name (upper case unless quoted)
118
120
  fields: FieldInfo[]; // column metadata
119
121
  }
120
122
  ```
@@ -155,8 +157,7 @@ FIREBIRD_PASSWORD=masterkey npm test
155
157
 
156
158
  ## Roadmap
157
159
 
158
- Shipped in 0.1.0 — every item the previous roadmap listed as pending, except
159
- the last:
160
+ Shipped in 0.1.0:
160
161
 
161
162
  - [x] True WASM build — Firebird 6.0 compiled with Emscripten
162
163
  - [x] Browser support via the WASM bundle, with the engine in a Web Worker
@@ -167,19 +168,34 @@ the last:
167
168
  letting it overwrite the first
168
169
  - [x] The prebuilt engine on npm, so using it needs no Emscripten
169
170
 
171
+ Shipped since:
172
+
173
+ - [x] Live queries built on Firebird's `POST_EVENT` (`live()`, `listen()`,
174
+ `notify()`)
175
+ - [x] Multi-tab *sharing* (`multiTab: 'shared'`): one tab runs the engine,
176
+ the others are served by it
177
+ - [x] OPFS backend (`opfs://name`), with no image copy and no persist step
178
+ - [x] A build without `SharedArrayBuffer` (`firebird-embedded-st`), for pages
179
+ served without COOP/COEP headers
180
+ - [x] React and Vue hooks (`firebird-wasm/react`, `firebird-wasm/vue`) and a
181
+ REPL element (`firebird-wasm/repl`)
182
+ - [x] ElectricSQL sync, read path: a shape from Postgres through Electric
183
+ (`firebird-wasm/electric`), or from a Firebird server
184
+ (`firebird-wasm/electric-server`), with an example app syncing from
185
+ both ([`examples/electric`](../../examples/electric)), `bytea`
186
+ included, and several tables in one transaction
187
+ (`syncShapesToTables`)
188
+ - [x] ElectricSQL writes: `createWriteQueue` applies a write at once, sends
189
+ it to the app's API, and keeps it over the sync until it comes back
190
+ - [x] Module disposal: `disposeFirebirdWasm()`, and closing the last
191
+ database frees the engine's memory
192
+
170
193
  Next:
171
194
 
172
- - [ ] Live queries built on Firebird's `POST_EVENT`
173
- - [ ] Multi-tab *sharing* — a `SharedWorker` leader, so a second tab is served
174
- rather than refused
175
- - [ ] OPFS backend — a better match for page-oriented I/O than IndexedDB
176
195
  - [ ] A typed binary result ABI, so exact numerics need not travel as strings
177
- - [ ] Module disposal — `close()` does not currently release the WASM heap
178
- - [ ] Loadable character sets. `src/intl` compiles under Emscripten and costs
179
- +245 KB gzipped, measured; what is missing is a way for `IntlManager`
180
- to reach it without `dlopen`. See
196
+ - [ ] UNICODE collations (case- and accent-insensitive comparison), which
197
+ need ICU collation data in the build. See
181
198
  [docs/roadmap.md](../../docs/roadmap.md)
182
- - [ ] ElectricSQL sync
183
199
 
184
200
  **"Firebird 4 & 5 support" is gone from this list because it was never the
185
201
  gap.** The build tracks Firebird `master` and the shipped engine reports