@oliphaunt/wasix-ts 0.1.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 (169) hide show
  1. package/ARCHITECTURE.md +655 -0
  2. package/CHANGELOG.md +33 -0
  3. package/LICENSE +21 -0
  4. package/README.md +404 -0
  5. package/THIRD_PARTY_NOTICES.md +20 -0
  6. package/lib/archive.d.ts +21 -0
  7. package/lib/archive.js +336 -0
  8. package/lib/asset-source.d.ts +4 -0
  9. package/lib/asset-source.js +15 -0
  10. package/lib/byte-channel.d.ts +27 -0
  11. package/lib/byte-channel.js +170 -0
  12. package/lib/client-common.d.ts +6 -0
  13. package/lib/client-common.js +36 -0
  14. package/lib/client.d.ts +7 -0
  15. package/lib/client.js +24 -0
  16. package/lib/database-root.d.ts +19 -0
  17. package/lib/database-root.js +139 -0
  18. package/lib/database.d.ts +135 -0
  19. package/lib/database.js +1039 -0
  20. package/lib/descriptor-validation.d.ts +10 -0
  21. package/lib/descriptor-validation.js +75 -0
  22. package/lib/direct-client-common.d.ts +50 -0
  23. package/lib/direct-client-common.js +717 -0
  24. package/lib/direct-client.d.ts +4 -0
  25. package/lib/direct-client.js +13 -0
  26. package/lib/direct.node.d.ts +2 -0
  27. package/lib/direct.node.js +2 -0
  28. package/lib/errors.d.ts +25 -0
  29. package/lib/errors.js +34 -0
  30. package/lib/extension-descriptor.d.ts +14 -0
  31. package/lib/extension-descriptor.js +419 -0
  32. package/lib/extensions.d.ts +68 -0
  33. package/lib/extensions.js +769 -0
  34. package/lib/host/LICENSE +21 -0
  35. package/lib/host/index.d.mts +123 -0
  36. package/lib/host/index.mjs +11 -0
  37. package/lib/host/provenance.json +16 -0
  38. package/lib/host/wasmer_js_bg.wasm +0 -0
  39. package/lib/host/worker.mjs +11 -0
  40. package/lib/host-runtime.d.ts +5 -0
  41. package/lib/host-runtime.js +13 -0
  42. package/lib/icu-descriptor.d.ts +3 -0
  43. package/lib/icu-descriptor.js +92 -0
  44. package/lib/index.bun.d.ts +2 -0
  45. package/lib/index.bun.js +2 -0
  46. package/lib/index.d.ts +2 -0
  47. package/lib/index.deno.d.ts +2 -0
  48. package/lib/index.deno.js +2 -0
  49. package/lib/index.js +2 -0
  50. package/lib/index.node.d.ts +2 -0
  51. package/lib/index.node.js +2 -0
  52. package/lib/internal-common.d.ts +12 -0
  53. package/lib/internal-common.js +274 -0
  54. package/lib/internal.d.ts +5 -0
  55. package/lib/internal.js +42 -0
  56. package/lib/internal.node.d.ts +5 -0
  57. package/lib/internal.node.js +8 -0
  58. package/lib/native-addon.d.ts +111 -0
  59. package/lib/native-addon.js +223 -0
  60. package/lib/native-server.d.ts +21 -0
  61. package/lib/native-server.js +109 -0
  62. package/lib/native-session.d.ts +60 -0
  63. package/lib/native-session.js +565 -0
  64. package/lib/node-actor.d.ts +7 -0
  65. package/lib/node-actor.js +10 -0
  66. package/lib/node-client-common.d.ts +9 -0
  67. package/lib/node-client-common.js +35 -0
  68. package/lib/node-client.d.ts +4 -0
  69. package/lib/node-client.js +13 -0
  70. package/lib/node-direct.d.ts +7 -0
  71. package/lib/node-direct.js +10 -0
  72. package/lib/node-worker-options.d.ts +5 -0
  73. package/lib/node-worker-options.js +65 -0
  74. package/lib/node-worker-port.d.ts +4 -0
  75. package/lib/node-worker-port.js +116 -0
  76. package/lib/node-worker.d.ts +1 -0
  77. package/lib/node-worker.js +38 -0
  78. package/lib/pgwire-connection.d.ts +60 -0
  79. package/lib/pgwire-connection.js +528 -0
  80. package/lib/pgwire.d.ts +3 -0
  81. package/lib/pgwire.js +105 -0
  82. package/lib/physical-archive.d.ts +29 -0
  83. package/lib/physical-archive.js +527 -0
  84. package/lib/protocol.d.ts +1 -0
  85. package/lib/protocol.js +1 -0
  86. package/lib/public.d.ts +4 -0
  87. package/lib/public.js +3 -0
  88. package/lib/query.d.ts +1 -0
  89. package/lib/query.js +1 -0
  90. package/lib/rpc.d.ts +203 -0
  91. package/lib/rpc.js +84 -0
  92. package/lib/runtime-descriptor.d.ts +3 -0
  93. package/lib/runtime-descriptor.js +79 -0
  94. package/lib/server.node.d.ts +1 -0
  95. package/lib/server.node.js +1 -0
  96. package/lib/startup-config.d.ts +2 -0
  97. package/lib/startup-config.js +19 -0
  98. package/lib/storage/bun.d.ts +6 -0
  99. package/lib/storage/bun.js +6 -0
  100. package/lib/storage/deno.d.ts +7 -0
  101. package/lib/storage/deno.js +7 -0
  102. package/lib/storage/incremental-storage.d.ts +25 -0
  103. package/lib/storage/incremental-storage.js +154 -0
  104. package/lib/storage/indexed-db-provider.d.ts +40 -0
  105. package/lib/storage/indexed-db-provider.js +259 -0
  106. package/lib/storage/indexed-db.d.ts +9 -0
  107. package/lib/storage/indexed-db.js +11 -0
  108. package/lib/storage/node.d.ts +10 -0
  109. package/lib/storage/node.js +13 -0
  110. package/lib/storage/opfs-pool.d.ts +31 -0
  111. package/lib/storage/opfs-pool.js +1271 -0
  112. package/lib/storage/opfs-provider.d.ts +4 -0
  113. package/lib/storage/opfs-provider.js +257 -0
  114. package/lib/storage/opfs.d.ts +8 -0
  115. package/lib/storage/opfs.js +10 -0
  116. package/lib/storage/restore-cleanup.d.ts +4 -0
  117. package/lib/storage/restore-cleanup.js +22 -0
  118. package/lib/storage/web-lock.d.ts +2 -0
  119. package/lib/storage/web-lock.js +64 -0
  120. package/lib/storage-provider.d.ts +47 -0
  121. package/lib/storage-provider.js +141 -0
  122. package/lib/storage-snapshot.d.ts +44 -0
  123. package/lib/storage-snapshot.js +274 -0
  124. package/lib/storage.d.ts +46 -0
  125. package/lib/storage.js +83 -0
  126. package/lib/tool-runtime.d.ts +43 -0
  127. package/lib/tool-runtime.js +93 -0
  128. package/lib/tool-worker-common.d.ts +48 -0
  129. package/lib/tool-worker-common.js +97 -0
  130. package/lib/tool-worker.d.ts +1 -0
  131. package/lib/tool-worker.js +10 -0
  132. package/lib/types.d.ts +230 -0
  133. package/lib/types.js +1 -0
  134. package/lib/wasix-runtime.d.ts +24 -0
  135. package/lib/wasix-runtime.js +186 -0
  136. package/lib/worker-client.d.ts +4 -0
  137. package/lib/worker-client.js +45 -0
  138. package/lib/worker-dispatch.d.ts +11 -0
  139. package/lib/worker-dispatch.js +174 -0
  140. package/lib/worker-entry.bun.d.ts +2 -0
  141. package/lib/worker-entry.bun.js +2 -0
  142. package/lib/worker-entry.d.ts +2 -0
  143. package/lib/worker-entry.deno.d.ts +2 -0
  144. package/lib/worker-entry.deno.js +2 -0
  145. package/lib/worker-entry.js +2 -0
  146. package/lib/worker-entry.node.d.ts +2 -0
  147. package/lib/worker-entry.node.js +2 -0
  148. package/lib/worker-node-client.d.ts +4 -0
  149. package/lib/worker-node-client.js +55 -0
  150. package/lib/worker-rpc.d.ts +36 -0
  151. package/lib/worker-rpc.js +422 -0
  152. package/lib/worker-transfer.d.ts +6 -0
  153. package/lib/worker-transfer.js +11 -0
  154. package/lib/worker.d.ts +1 -0
  155. package/lib/worker.js +21 -0
  156. package/lib/zstd.d.ts +4 -0
  157. package/lib/zstd.js +12 -0
  158. package/node_modules/@oliphaunt/js-core/README.md +7 -0
  159. package/node_modules/@oliphaunt/js-core/dist/commonjs/protocol.d.ts +1 -0
  160. package/node_modules/@oliphaunt/js-core/dist/commonjs/protocol.js +22 -0
  161. package/node_modules/@oliphaunt/js-core/dist/commonjs/query.d.ts +255 -0
  162. package/node_modules/@oliphaunt/js-core/dist/commonjs/query.js +2068 -0
  163. package/node_modules/@oliphaunt/js-core/dist/module/package.json +3 -0
  164. package/node_modules/@oliphaunt/js-core/dist/module/protocol.d.ts +1 -0
  165. package/node_modules/@oliphaunt/js-core/dist/module/protocol.js +19 -0
  166. package/node_modules/@oliphaunt/js-core/dist/module/query.d.ts +255 -0
  167. package/node_modules/@oliphaunt/js-core/dist/module/query.js +2039 -0
  168. package/node_modules/@oliphaunt/js-core/package.json +21 -0
  169. package/package.json +122 -0
@@ -0,0 +1,655 @@
1
+ # WASIX TypeScript binding architecture
2
+
3
+ ## Boundary
4
+
5
+ `src/bindings/wasix-ts` is one public TypeScript API over two host adapters.
6
+ The package export conditions, not a runtime option, select the adapter:
7
+
8
+ ```text
9
+ browser/default node/bun/deno/electron
10
+ | |
11
+ v v
12
+ patched Wasmer JavaScript host napi-rs, Node-API 8 addon
13
+ | |
14
+ portable liboliphaunt-wasix Rust actor, direct, Worker, server
15
+ `---------------------+----------------'
16
+ v
17
+ shared TypeScript database API
18
+ ```
19
+
20
+ The browser adapter owns the portable runtime/seed descriptors and dynamic
21
+ extension carrier installation. The server adapter owns no Wasmer JavaScript
22
+ fallback: it loads one exact, prebuilt platform carrier whose Rust dependency
23
+ embeds the runtime, AOT objects, cluster seed, tools, and supported extension
24
+ catalog. Both execute the canonical WASIX guest and preserve its physical
25
+ database and backup formats.
26
+
27
+ This boundary deliberately does not depend on `src/sdks/js`,
28
+ `liboliphaunt-native`, `node-direct`, or the broker. The N-API product wraps the
29
+ WASIX Rust binding; it is not a route into the native PostgreSQL SDK.
30
+
31
+ Protocol and typed-query helpers are exact mirrors of `src/shared/js-core`.
32
+ That is a shared semantic source, not a dependency on the native TypeScript
33
+ product.
34
+
35
+ The patched Wasmer host under `host/` is a browser-only implementation
36
+ dependency of this binding, not another Oliphaunt runtime product. Browser
37
+ PostgreSQL binaries, PGDATA, and the canonical runtime manifest remain owned by
38
+ `liboliphaunt-wasix`; each extension product owns its separately versioned
39
+ portable carrier envelope. The N-API release embeds the corresponding frozen
40
+ artifacts instead of resolving those bytes during application startup.
41
+
42
+ The canonical guest also owns the backend-only single-backend spinlock and
43
+ scalar-atomic specializations carried by PostgreSQL patches 0035 and 0036.
44
+ They follow the guest into the Rust binding's AOT artifacts and the portable
45
+ module used by the browser. They are not a TypeScript host optimization.
46
+ Frontends, PGXS side modules, and concurrent PostgreSQL builds retain the normal
47
+ atomic implementation. Each adapter asserts the shared
48
+ `OLIPHAUNT_WASIX_SINGLE_BACKEND=1` concurrency invariant and denies guest
49
+ process and thread creation under it. Browser root and `/worker` use the same
50
+ Oliphaunt export driver. Native-host root, `/direct`, `/worker`, and `/server` use the
51
+ same Rust WASIX semantics with different explicit owners. Placement changes
52
+ ownership and hop count, not the PostgreSQL protocol contract.
53
+
54
+ ## Browser lifecycle
55
+
56
+ `Oliphaunt.open()` from the root package uses the host driver in the importing
57
+ realm: setup is asynchronous, but PostgreSQL lifecycle and protocol exports run
58
+ in that realm and may monopolize its event loop while active. `Oliphaunt.open()` from
59
+ `@oliphaunt/wasix-ts/worker` creates one package-owned module Worker around the
60
+ same driver. There is no public placement option and neither entrypoint falls
61
+ back to the other. Both share one database state machine, mount
62
+ construction, PostgreSQL configuration, extension/role setup, and storage
63
+ contract. Immutable preparation and compiled modules are cached by verified
64
+ runtime identity, while writable `Directory` mounts and storage leases are
65
+ recreated for every open. Each handle remains one serialized PostgreSQL
66
+ session. Only the root entrypoint contends for its caller's event loop.
67
+
68
+ The pinned host currently instantiates dynamically loaded native side modules
69
+ synchronously. Chromium refuses Window-realm modules above 8 MiB, so the root
70
+ entrypoint fails early there for a selected carrier above that threshold. The
71
+ explicit `/worker` entrypoint and the root imported from a Dedicated Worker are
72
+ outside that Window restriction and apply descriptor-declared native
73
+ load order; a real Chrome canary loads PostGIS there and verifies recovery
74
+ across its large dependency module. The core guest uses the asynchronous path;
75
+ smaller qualified side modules remain supported in a direct Window.
76
+
77
+ 1. The root entrypoint imports the package-relative host lazily in the caller
78
+ realm and creates no Worker. `/worker` creates one module Web Worker per open
79
+ and a temporary Worker for restore.
80
+ 2. The binding resolves the default `@oliphaunt/liboliphaunt-wasix` descriptor
81
+ internally. The selected realm fetches or receives its canonical manifest, runtime,
82
+ and cluster-seed `.tar.zst` artifacts as one product/version identity. Each
83
+ uncached identity verifies descriptor sizes and hashes plus the manifest's
84
+ core/module and PostgreSQL/source identity; every open uses that exact
85
+ verified identity. Imported extension descriptors add their exact carrier
86
+ closure.
87
+ 3. The selected realm safely expands the core artifacts and overlays only each
88
+ extension carrier's install-contract files into separate `/bin`, `/lib`, `/share`,
89
+ writable `/base`, `/home`, and `/tmp` Wasmer memory mounts. Before `/base` is
90
+ materialized, a storage provider lease supplies either the packaged cluster
91
+ seed or an exact-compatible persistent PGDATA. The source-pinned
92
+ host adds ephemeral `/dev/shm` and a real Wasmer `RandomFile` at
93
+ `/dev/urandom`. Its narrow `Directory` mutation journal records successful
94
+ writes and truncates through already-open descriptors as well as file,
95
+ directory, remove, and rename paths for either execution surface and every provider.
96
+ Both execution surfaces pass the verified precompiled main module and its original
97
+ bytes to `instantiateOliphauntDirect`. The root keeps the resulting Store in
98
+ the caller realm; `/worker` keeps it in its package Worker.
99
+ 4. Both execution surfaces push protocol bytes through guest-owned reusable input
100
+ and output buffers. The host writes requests directly into canonical guest
101
+ memory and returns one owned JavaScript response copy, so PostgreSQL can
102
+ safely reuse or grow its memory after the call. Startup preserves an
103
+ `ErrorResponse` and its SQLSTATE even when startup terminates the guest.
104
+ 5. The direct export driver completes the exported startup transition before
105
+ exposing the session. Selected carriers contribute verified artifacts and
106
+ required startup/preload configuration only; database-local extension SQL is
107
+ application/ORM-owned. A requested non-default user is selected from existing
108
+ roles with `SET ROLE`; standalone bootstrap remains the fixed `postgres`
109
+ identity.
110
+ 6. The binding frames later responses through `ReadyForQuery` and exposes
111
+ serialized `query`, `execute`, buffered `execProtocolRaw`, callback
112
+ `execProtocolRawStream`, and callback-scoped `transaction` calls
113
+ through one database contract. The same contract supports explicit
114
+ `close()` and `await using` disposal.
115
+ Every successfully completed protocol operation reaches `ReadyForQuery`, then
116
+ asks a persistent provider to publish only journaled `/base` paths before the
117
+ Promise resolves. A callback transaction defers publication for `BEGIN`, its
118
+ body, and `COMMIT`/`ROLLBACK`, then publishes exactly once after the confirmed
119
+ final boundary. A new persistent synchronous-OPFS root uses a separate internal
120
+ full-publication boundary after initialization; it is not a public database
121
+ operation. PostgreSQL `CHECKPOINT` remains available through ordinary
122
+ `execute`. If a
123
+ PostgreSQL `ERROR` crosses the host boundary, the direct host
124
+ invokes `PostgresMainLongJmp`, sends and flushes readiness, and continues
125
+ through `PostgresMainLoopOnce`. Normal ErrorResponse returns receive the same
126
+ top-level cleanup as trapping errors.
127
+ 7. `close` establishes a terminal admission cutoff and lets already accepted
128
+ database work drain. The direct owner
129
+ sends PostgreSQL Terminate through the same direct bridge, deactivates the
130
+ embedded lifecycle, and runs its atexit exports synchronously in the owning
131
+ realm. A successful close completes the
132
+ provider's final persistence boundary. Every outcome attempts provider close,
133
+ exclusive-lease release, and entrypoint-owned host-resource release. The
134
+ browser `/worker` waits for that close reply and then terminates its already
135
+ quiescent Worker; a Node-compatible `/worker` closes its native handle, posts
136
+ the reply, and exits itself. The public
137
+ handle memoizes that single outcome and becomes closed after teardown settles;
138
+ a rejected close never advertises the destroyed owner or guest as reusable.
139
+ If the isolated owner terminates independently, shared session state makes the
140
+ public handle closed immediately and prevents later work from crossing the
141
+ dead transport. An explicit close still memoizes and reports that terminal
142
+ failure while completing package-owned resource cleanup.
143
+ 8. Each public database handle registers an opaque generation token for
144
+ best-effort forgotten-handle recovery. The finalizer holds no reference to
145
+ the public owner and only schedules work after returning. It atomically
146
+ claims the exact still-active generation, then schedules the same best-effort
147
+ close for that root, direct, or `/worker` generation. Explicit close
148
+ unregisters the generation before teardown, so queued stale finalizers are
149
+ harmless and cannot affect a later database.
150
+
151
+ Stock Wasmer's public browser API exposes streams and process completion, but
152
+ not arbitrary guest exports. The source-pinned host deliberately adds only the
153
+ narrow Oliphaunt export driver needed to match the Rust WASIX lifecycle; it is
154
+ not a general synchronous WASIX process API. Generic Wasmer process streams
155
+ remain upstream behavior and are not part of the TypeScript database surface.
156
+
157
+ ## Node, Bun, Deno, and Electron lifecycle
158
+
159
+ Native-host conditions load one Node-API 8 addon. The root constructs
160
+ `NativeWasixActorDatabase`, which directly owns the Rust `AsyncOliphaunt`
161
+ database actor. Bounded admission is synchronous, PostgreSQL runs on its one
162
+ Rust owner thread, and completion settles the existing Promise on the importing
163
+ JavaScript thread. This is the responsive default and adds one native queue hop.
164
+
165
+ The conditional `/direct` export constructs `NativeWasixDatabase` around
166
+ synchronous `oliphaunt_wasix::Oliphaunt` on the importing JavaScript thread. It
167
+ has the fewest hops and can block that event loop. The conditional `/worker`
168
+ export creates one real package-owned Node-compatible Worker, which loads the
169
+ same `/direct` implementation inside that Worker. It adds the requested
170
+ JavaScript RPC hop and realm isolation without a child process or a second Rust
171
+ owner thread. Native direct handles remain creator-thread-affine.
172
+
173
+ Close establishes one admission cutoff. The actor drains accepted work and
174
+ settles its terminal completion. Direct close runs on its owning thread. A
175
+ package Worker closes its native database at quiescence, posts the close reply,
176
+ then closes its parent port and exits itself; the parent does not terminate a
177
+ Worker across an active Node-API frame. An unexpected Worker exit rejects
178
+ pending work and leaves the public handle terminal.
179
+
180
+ Query serialization, close semantics, the memory default, storage identity,
181
+ and public errors remain TypeScript-owned. Descriptor validation also remains
182
+ shared, but native release addons resolve validated extension SQL names against
183
+ their compile-time catalog instead of expanding portable extension archives at
184
+ open.
185
+
186
+ IndexedDB and OPFS remain browser-only and are rejected before a native-host
187
+ actor, direct, or Worker session starts. Directory persistence is exposed through matching
188
+ `storage/node`, `storage/bun`, and `storage/deno` entrypoints. They preserve the
189
+ shared managed-root descriptor and exclusive path ownership while Rust owns
190
+ the database bytes and durability. No host falls back to native
191
+ `@oliphaunt/ts`. Direct and explicit `/worker` entrypoints may themselves
192
+ be imported from an application-owned worker thread. Rust holds one OS advisory
193
+ lock for the managed-root lifetime, shared with direct Rust owners. There is no
194
+ JavaScript marker lock to recover. Callers should still close before externally
195
+ terminating their own realm.
196
+
197
+ ## Protocol streams, tools, and local endpoints
198
+
199
+ The public callback stream reuses the guest's COPY-aware synchronous transport
200
+ and emits at most 64 KiB per callback. Browser root and native `/direct` invoke
201
+ the callback in their owning JavaScript realm. Browser and native-host
202
+ Workers block only their Worker with a shared-memory acknowledgement until the
203
+ importing-realm callback returns. The native actor uses a napi-rs thread-safe
204
+ function with queue size one and waits for each JavaScript acknowledgement.
205
+ Every path therefore preserves bounded backpressure and callback ordering.
206
+
207
+ Direct native requests borrow JavaScript input for the duration of their
208
+ synchronous call. Actor requests copy into owned Rust admission data before the
209
+ call returns. All native responses, backup archives, chunks, and tool output are
210
+ ordinary V8-owned typed arrays with predictable detach and lifetime behavior.
211
+ The Worker transport transfers eligible response `ArrayBuffer` values directly;
212
+ there is no external-buffer finalizer crossing an isolate or environment exit.
213
+
214
+ A callback returning a Promise or thenable is rejected: asynchronous
215
+ completion cannot acknowledge this synchronous backpressure contract, and the
216
+ PostgreSQL session is poisoned conservatively. The callback is also an
217
+ ownership boundary: it cannot queue work through the same database or
218
+ transaction while that database is waiting for the chunk acknowledgement. Such
219
+ reentry fails immediately instead of creating a hidden post-stream operation.
220
+
221
+ `@oliphaunt/wasix-tools` remains the optional public facade. In a browser it
222
+ resolves the separately published `@oliphaunt/liboliphaunt-wasix-tools` asset
223
+ carrier. `pg_dump` runs in the realm that already owns the database; `psql`
224
+ uses a separate persistent browser tool worker because COPY input is genuinely
225
+ full duplex. Its private pgwire connection has fixed, bounded shared-memory
226
+ rings.
227
+
228
+ Native release addons compile both frontends and the current extension catalog
229
+ into every platform binary. Node.js, Bun, Deno, and Electron route `pg_dump` and
230
+ `psql` through the existing Rust database owner on root, `/direct`, or `/worker` and do
231
+ not resolve portable tool bytes at invocation time. This intentionally trades
232
+ larger platform packages and a coordinated carrier release for fewer startup
233
+ reads, decompressions, compilation steps, and runtime compatibility edges.
234
+
235
+ The package export `@oliphaunt/wasix-ts/internal/tools` exists only so the
236
+ version-matched `@oliphaunt/wasix-tools` package can reach this bridge. It is not
237
+ an application API or part of the stable SDK surface, is undocumented for app
238
+ consumers, and may change only in lockstep with that companion package. Package
239
+ checks reject any other low-level query or protocol subpath exports.
240
+
241
+ The database session is exclusively serialized. It resets PostgreSQL with
242
+ `ROLLBACK`, `DISCARD ALL`, and the configured role before and after a tool, then
243
+ publishes storage once after the final safe cleanup boundary. An uncertain tool
244
+ transport outcome poisons the handle after making its stored state safe. The
245
+ tools remain outside the core public database surface on both adapters.
246
+
247
+ The host-only `/server` subpath uses conditions to export the same implementation
248
+ for Node, Bun, Deno, and Electron. It has no browser or default condition. The implementation
249
+ constructs the Rust `OliphauntServer` through the same addon rather than
250
+ adapting a JavaScript socket relay. It binds one loopback TCP or
251
+ PostgreSQL-named Unix listener and serves one active client. Another connection
252
+ may wait in the operating-system backlog, so consumers configure pools with a
253
+ maximum size of one. Each admitted connection receives a fresh embedded
254
+ backend. Server state, listener lifetime, and storage publication are
255
+ Rust-owned; the TypeScript facade retains the
256
+ existing Promise-shaped open/close and `closed` contract. The concurrent WASIX
257
+ postmaster remains a separate runtime product rather than a mode of this
258
+ single-backend SDK.
259
+
260
+ ## Browser storage boundary
261
+
262
+ Storage is a binding-owned provider/lease contract rather than runtime asset
263
+ configuration:
264
+
265
+ ```text
266
+ opaque storage descriptor
267
+ |
268
+ v
269
+ acquire provider lease ---- exact physical compatibility
270
+ |
271
+ +---- synchronous OPFS /base ---- exact-range file I/O
272
+ |
273
+ `---- portable /base ------ journaled publication
274
+ |
275
+ v
276
+ PostgreSQL boundary + release
277
+ ```
278
+
279
+ The main package owns the fresh-memory descriptor and default. IndexedDB and
280
+ OPFS are selective `./storage/indexed-db` and `./storage/opfs` entrypoints whose
281
+ implementations load only when an opaque descriptor reaches the owning
282
+ realm. Raw serialized descriptors are not accepted from consumers. The
283
+ internal lease exposes `state`, one initial PGDATA mount,
284
+ an optional synchronous PGDATA materializer, `sync(directory, boundary)`, and
285
+ `close(directory, outcome)`; it does not own runtime or extension assets.
286
+
287
+ The source-pinned Wasmer `Directory` exposes a compact current-state mutation
288
+ journal. Write-capable files are wrapped so a PostgreSQL descriptor retained
289
+ across multiple operations records every later write, not only its initial
290
+ open. The shared portable delta layer drains the journal only at
291
+ PostgreSQL-safe host boundaries, collapses overlapping paths, reads changed
292
+ files and subtrees, and expresses removals explicitly. A provider without that
293
+ host capability falls back to a full scan, so correctness does not depend on
294
+ the optimization. Synchronous OPFS mounts bypass mutation tracking and serve the
295
+ guest synchronously in its owning worker. Process-lifetime `postmaster.pid` and
296
+ `postmaster.opts` never enter persistent storage.
297
+
298
+ Each logical IndexedDB name owns a separate physical IndexedDB database with
299
+ fixed metadata and one row per PGDATA path. Each boundary applies upserts and
300
+ removals in one atomic read-write transaction using the browser's default
301
+ commitState policy; an aborted write leaves the preceding generation intact,
302
+ and distinct logical databases do not share an object-store transaction. OPFS
303
+ stores a strict logical namespace and physical identity over flat backing files.
304
+ `/worker`, and the root inside a Dedicated Worker, preopen synchronous access
305
+ handles and perform exact-range guest I/O without a mailbox or nested worker. A
306
+ direct Window uses the portable path. Guest file flushes are immediate;
307
+ operation boundaries drain WAL; internal full-publication, close, and namespace publication
308
+ flush WAL before ordinary files and `global/pg_control`. The portable path uses
309
+ copy-on-write backing files and atomically replaces namespace state last. OPFS
310
+ has PostgreSQL recovery ordering but no cross-file transaction, so a failed
311
+ publication reports unknown state instead of claiming that nothing changed.
312
+ The synchronous path keeps a bounded private reserve of preopened backing files for
313
+ the synchronous hot path. Overflow is staged only until the mandatory host
314
+ boundary, which allocates, writes, and flushes every staged file before
315
+ publishing namespace state. A failure leaves the previous namespace
316
+ authoritative and poisons the live handle. The reserve is replenished
317
+ best-effort after successful boundaries, and hosts that cannot establish its
318
+ initial capacity use the portable path. Its size is an implementation detail,
319
+ not a public database-capacity limit.
320
+
321
+ Compatibility uses the PostgreSQL major and versioned WASIX physical format.
322
+ Runtime hashes and source fingerprints still reject mixed runtime, cluster-seed,
323
+ AOT, and extension build outputs, while package and carrier changes do not
324
+ rewrite the managed-root descriptor or reject an unchanged physical format.
325
+ Safe extension upgrade or removal remains an explicit migration concern rather
326
+ than a reason to reject every change in the available carrier set.
327
+ Cross-binding root handoff is not a supported or qualified workflow.
328
+
329
+ Persistent databases use an origin-scoped exclusive Web Lock. This preserves
330
+ the single-owner invariant rather than suggesting that one single-user
331
+ PostgreSQL backend represents independent connections. There is no leader
332
+ proxy or multi-tab transaction ownership yet.
333
+
334
+ Provider acquisition and PGDATA materialization happen before PostgreSQL
335
+ starts. Provider boundaries happen only after pgwire recovery returns
336
+ `ReadyForQuery`, so ordinary PostgreSQL errors retain their existing
337
+ `PostgresError` identity. A host persistence failure is instead a typed storage
338
+ error and poisons the live handle: guest state may be ahead of confirmed durable
339
+ storage, so retrying the application operation is not known to be safe.
340
+
341
+ ## Selective extension descriptor contract
342
+
343
+ The consumer API accepts exact structural values rather than SQL strings:
344
+
345
+ ```ts
346
+ type WasixExtensionDescriptor = {
347
+ schema: 'oliphaunt-wasix-extension-v1';
348
+ runtime: 'wasix';
349
+ product: string;
350
+ version: string;
351
+ compatibility: {
352
+ extensionRuntimeContract: 'oliphaunt-extension-runtime-contract-v1';
353
+ postgresMajor: string;
354
+ wasixRuntimeProduct: 'liboliphaunt-wasix';
355
+ wasixRuntimeVersion: string;
356
+ };
357
+ sqlName: string;
358
+ carriers: readonly {
359
+ product: string;
360
+ version: string;
361
+ sqlName: string;
362
+ archive: string;
363
+ sha256: string;
364
+ size: number;
365
+ source: string | URL | ArrayBuffer | Uint8Array;
366
+ install: {
367
+ schema: 'oliphaunt-wasix-extension-install-v1';
368
+ dependencies: readonly string[];
369
+ coreExportsRequired: readonly string[];
370
+ // exact native-module, lifecycle, and installed-file projections
371
+ };
372
+ }[];
373
+ };
374
+ ```
375
+
376
+ This is structural rather than nominal so a generated extension package can be
377
+ dependency-free; it does not import the host binding merely to acquire a brand.
378
+ The literal `runtime: 'wasix'` still makes native descriptors statically
379
+ incompatible with non-WASIX extension descriptors, and the client
380
+ runtime-validates the complete shape.
381
+ The binding keeps an internal validation/freezing helper for fixtures. It is not
382
+ part of the consumer entrypoint and generated packages do not depend on it.
383
+
384
+ Generated leaf packages can point at their package-owned payload without any
385
+ host conditional:
386
+
387
+ ```ts
388
+ const carrier = {
389
+ source: new URL('./extensions/pgtap/extension.tar.zst', import.meta.url),
390
+ // product, version, SQL identity, archive key, hash, and size
391
+ } as const;
392
+ ```
393
+
394
+ The development Vite harness derives virtual package descriptors from the current
395
+ canonical target outputs and uses development route strings while serving those
396
+ exact artifacts directly.
397
+
398
+ Each descriptor selects only its root `sqlName`. Its carrier array is a
399
+ dependency-complete byte closure, not an alternate dependency declaration. The
400
+ client validates each root's exact dependency closure, unions closures in
401
+ deterministic SQL-name order, deduplicates shared rows only when their complete
402
+ identity/install/compatibility metadata agrees, and rejects repeated rows,
403
+ duplicate roots, or conflicts. The selected realm resolves dependencies solely from
404
+ the imported install contracts, treating only the stripped core manifest's
405
+ `runtime-support` entries as runtime-provided. Before reading extension bytes,
406
+ it gates every carrier on the selected WASIX runtime version, PostgreSQL major,
407
+ extension-runtime contract, and required names in `runtime.link.exports`. It
408
+ then verifies each archive's declared size/hash and overlays exactly its
409
+ carrier-owned installed-file inventory. The core manifest is required to have
410
+ `extensions: []` so it cannot quietly reclaim optional extension ownership.
411
+
412
+ That byte-closure processing is the browser implementation. Node.js, Bun, Deno,
413
+ and Electron retain the same public descriptor and perform its structural/runtime
414
+ validation, but pass only the validated, dependency-ordered SQL names across
415
+ the N-API boundary. The Rust runtime resolves those names against the exact
416
+ extension features compiled into the release carrier. Unknown names fail; the
417
+ addon never treats arbitrary descriptor bytes as native code. A new or upgraded
418
+ extension can ship independently for browsers, but it becomes available to
419
+ native-host consumers only after the N-API product is rebuilt and released
420
+ with that feature.
421
+
422
+ ## Host compatibility
423
+
424
+ The host is rebuilt from source rather than maintained as hand-edited generated
425
+ JavaScript/WASM. `host/source.toml` pins the Wasmer JS Git source and Cargo
426
+ crates; the adjacent patches are the reviewable compatibility delta. The build
427
+ lands first in `target/oliphaunt-wasix-ts/host`. Public package staging copies
428
+ the exact JS module, worker module, WebAssembly module, license, and provenance
429
+ into `lib/host`; the browser root imports the host in the caller realm, while
430
+ the browser `/worker` imports it in its package Worker. Node.js, Bun, Deno, and Electron
431
+ conditions do not import this module.
432
+
433
+ This is not a general backport of WASIX 0.702 to Wasmer 0.601. The authoritative
434
+ patch order is the `series` in `host/source.toml`; this document records the
435
+ resulting invariants instead of duplicating that filename inventory. Together,
436
+ the patches:
437
+
438
+ - honor configured args, environment, mounts, cwd, and stdio; preserve original
439
+ module bytes where the generic blocking worker needs them; and repair the
440
+ pinned npm/toolchain inputs without mutating their lock;
441
+ - provide only the 0.702 compatibility imports and runtime devices required by
442
+ the shipped guests, reject unavailable fork/context/thread/process behavior,
443
+ remove the retired Rust target, and recognize standard WebAssembly exception
444
+ reference types;
445
+ - make oversized main-module construction asynchronous through the builder and
446
+ linker while keeping the returned database driver synchronous and rejecting
447
+ unsupported oversized side modules before open;
448
+ - enforce the single-backend profile, use correct realtime and monotonic clocks,
449
+ amortize bounded pending-work checks, and avoid turning synchronous-file POSIX
450
+ close into an implicit fsync that bypasses PostgreSQL durability policy;
451
+ - expose the current-state mutation journal and the narrow caller-realm
452
+ synchronous filesystem bridge used by synchronous OPFS, without reviving the old
453
+ mailbox transport;
454
+ - provide the caller-realm PostgreSQL lifecycle and reusable-memory pgwire
455
+ driver, including COPY-aware callback streaming, top-level error recovery,
456
+ and a bounded 16 KiB failure-only stderr tail; and
457
+ - run only the packaged PostgreSQL frontend tools through a fresh caller-realm
458
+ WASIX process with captured stdio and synchronous pgwire callbacks. This path
459
+ uses neither the generic Wasmer scheduler worker nor a Web Streams pump.
460
+
461
+ The clock specialization is intentionally narrower than a general syscall
462
+ shortcut. Realtime uses the JavaScript epoch clock, while monotonic reads
463
+ calibrate the host's monotonic clock against the canonical Rust fallback epoch,
464
+ so fast and fallback reads cannot jump between domains. Process and thread CPU
465
+ clocks remain on the canonical fallback because wall time is not an equivalent
466
+ clock. Synthetic clock offsets remain honored by declining the direct import
467
+ for guests that import `clock_time_set`, and pending WASIX operations are
468
+ checked on a real-time bound. Invalid clock IDs, pointers, or host values use
469
+ the complete Rust syscall. Other WASIX programs retain the complete upstream
470
+ per-call path.
471
+
472
+ The exact pairing is qualified for the single-process direct Oliphaunt export
473
+ path in both execution surfaces, including repeated PostgreSQL `ERROR` recovery. The
474
+ direct driver treats every `PostgresMainLoopOnce` trap as the guest's exported
475
+ top-level recovery boundary and also cleans up non-trapping ErrorResponses.
476
+ Its JavaScript memory bridge is limited to the direct Oliphaunt driver: generic
477
+ WASIX streams keep their normal ownership and scheduling semantics. Copy failures
478
+ are caught before guest buffers are released, and protocol responses are copied
479
+ once into owned JavaScript storage rather than exposed as mutable guest views.
480
+ Browser qualification loads and calls PostGIS in a real worker and asserts that
481
+ its dependency side module exceeds Chromium's 8 MiB main-thread compilation
482
+ limit; the exemption is therefore attached to the worker realm, not to an
483
+ extension name or a benchmark payload size.
484
+ This remains an integration contract with the pinned Oliphaunt runtime rather
485
+ than a generic Wasmer guarantee.
486
+ Missing WASIX context switching is a broader compatibility gap, but is not part
487
+ of this PostgreSQL recovery path. Ordinary package resolution never selects
488
+ stock `@wasmer/sdk`; the published binding owns the source-pinned host. A larger
489
+ current-Wasmer JS port is outside this host's compatibility contract.
490
+
491
+ The version skew is upstream-owned rather than a loose Oliphaunt dependency.
492
+ The commit referenced by the latest npm `@wasmer/sdk` 0.10.0 release identifies
493
+ its checked-in source as 0.8.0 and embeds Wasmer 6.1 with the 0.601 Wasmer
494
+ support family. `wasmer-wasix` 0.702.1 embeds Wasmer 7.2.1 and
495
+ matching 0.702.1 virtual filesystem/network, package, configuration, backend,
496
+ and types contracts. A coordinated compile probe exposed incompatible
497
+ `FileSystem` mounting, `TaskWasm`, wasm-bindgen conversion, registry calls,
498
+ module hashing, and binary-package construction before the Oliphaunt runner and
499
+ recovery changes could be reapplied. Consequently 0.702.1 adoption is a full
500
+ source-host port plus browser qualification, not an isolated crate bump.
501
+ `host/source.toml` records the intentionally coherent 0.601 source family until
502
+ that port exists.
503
+
504
+ ## PGlite reference, not product inheritance
505
+
506
+ PGlite independently validates the recovery shape used here. Its Emscripten
507
+ guest turns the active PostgreSQL top-level `longjmp` into a known exit status;
508
+ the TypeScript host then calls `PostgresMainLongJmp`, sends readiness, flushes,
509
+ and resumes `PostgresMainLoopOnce`. Its public database error is separately
510
+ decoded from pgwire. See PGlite's
511
+ [runtime loop](https://github.com/electric-sql/pglite/blob/67872123b637ba132cceb8dbb3f739a09685ee87/packages/pglite/src/pglite.ts#L932-L965)
512
+ and [guest shim](https://github.com/electric-sql/postgres-pglite/blob/7b4ee5086055dc5e54ae1e13e487888249438e68/pglite/src/pglitec/pglitec.c#L52-L84).
513
+ Oliphaunt deliberately uses an environment-gated Wasmer exception discriminator
514
+ instead of Emscripten's numeric sentinel, but preserves the same separation
515
+ between control-flow recovery and the pgwire `PostgresError` seen by callers.
516
+ Lifecycle SQL for a selectively imported extension runs in the owning realm.
517
+ Isolated-host errors are serialized by PostgreSQL field and rebuilt in the caller;
518
+ direct errors retain the same `PostgresError` identity in place. Generic
519
+ transport errors retain their name, message, and owner-side stack. Neither path
520
+ collapses SQLSTATE and diagnostics into a generic error.
521
+
522
+ PGlite is also a useful ordering reference: it stages extension archives and
523
+ precompiles Emscripten side modules before PostgreSQL starts. Those
524
+ `MAIN_MODULE`/`SIDE_MODULE` binaries are not WASIX carriers, however, and its
525
+ filesystem persistence is coupled to Emscripten FS.
526
+
527
+ The browser benchmark also exposed a preparation asymmetry: PGlite reused
528
+ precompiled modules while each WASIX open recompiled the verified guest bytes.
529
+ Caller-realm execution now bounds and keys immutable preparation and compiled-module
530
+ caches by exact runtime/carrier/GUC identity. Mutable directories and storage
531
+ leases never enter those caches. The checked-in insert benchmark compares WAL
532
+ volume alongside timing; separate root-cause diagnostics compare buffer
533
+ activity and relation sizes. Both keep host/runtime overhead visible without
534
+ changing PostgreSQL work or commitState settings.
535
+
536
+ This binding keeps the following deliberate divergences:
537
+
538
+ - extension lifecycle and install metadata comes from each selectively imported
539
+ `-wasix` package; the stripped runtime manifest cannot override it;
540
+ - runtime/PGDATA/manifest hashes, carrier hashes, required core exports, exact
541
+ installed-file inventories, dependencies, and collisions are checked before
542
+ startup;
543
+ - selecting an extension stages its verified artifacts and startup configuration;
544
+ applications explicitly run ordinary `CREATE EXTENSION`, `LOAD`, schema, or
545
+ migration SQL, matching the ownership expected by ORMs; and
546
+ - IndexedDB and OPFS now use source-pinned dirty-path synchronization at each
547
+ completed protocol operation, matching PGlite's useful commitState boundary
548
+ without importing Emscripten FS. Oliphaunt keeps explicit provider-specific
549
+ atomicity and exclusive ownership; multi-tab leadership remains unsupported.
550
+
551
+ The host validates every native `load-order` entry against the carrier's exact
552
+ installed-file inventory but does not execute it as SQL. Applications explicitly
553
+ issue any required `LOAD`/`CREATE EXTENSION` lifecycle, after which PostgreSQL and
554
+ Wasmer's dynamic linker remain responsible for each module's declared
555
+ `dylink-needed` closure. `shared-memory-required` contracts remain rejected
556
+ because the single-backend runtime has not qualified that capability.
557
+
558
+ ## Asset ownership
559
+
560
+ The `@oliphaunt/wasix-ts` tarball does not contain PostgreSQL binaries. Browser
561
+ conditions import `@oliphaunt/liboliphaunt-wasix`, whose generated descriptor
562
+ points at package-owned runtime, PGDATA, and manifest assets. There is no public
563
+ raw runtime-source override. Development reads
564
+ `target/oliphaunt-wasix/assets`, produced by
565
+ `liboliphaunt-wasix:runtime-portable`, through the browser example's Vite
566
+ plugin, which models that generated carrier.
567
+
568
+ Node.js, Bun, Deno, and Electron also receive one target-filtered optional dependency.
569
+ The public carriers are
570
+ `@oliphaunt/wasix-napi-darwin-arm64`,
571
+ `@oliphaunt/wasix-napi-linux-arm64-gnu`,
572
+ `@oliphaunt/wasix-napi-linux-x64-gnu`, and
573
+ `@oliphaunt/wasix-napi-win32-x64-msvc`. Each has no install script and contains
574
+ one `oliphaunt_wasix_napi.node` binary with both standard and ICU profiles. The private
575
+ `@oliphaunt/wasix-napi` product coordinates the Rust build and carrier release;
576
+ applications never import it.
577
+
578
+ Linux carriers are GNU/glibc-only. The adapter identifies libc from the
579
+ runtime diagnostic report before resolving package-adjacent, optional, or
580
+ explicit addon paths; known musl and unknown libc identities fail closed.
581
+
582
+ Native release builds embed the runtime, seed, AOT objects, frontend tools, and
583
+ complete currently supported extension feature set. Optional extensions remain
584
+ exact, separately imported `-wasix` packages at the public TypeScript boundary,
585
+ but native hosts use their descriptor identity to select compiled-in artifacts
586
+ instead of copying the carrier bytes. Their availability is consequently a
587
+ release-time N-API contract.
588
+
589
+ The source workspace manifest deliberately does not resolve that generated
590
+ carrier from npm: the carrier exists only after same-candidate runtime assets
591
+ are frozen. SDK release staging injects the exact dependency recorded by
592
+ `oliphaunt.runtimeVersion`, validates it, and publishes only that staged
593
+ manifest. This keeps fresh frozen workspace installs independent of an
594
+ unpublished candidate while making the consumer tarball's browser runtime edge
595
+ exact. The same staging step rewrites every native optional dependency to the
596
+ exact N-API product version. The loader rejects a carrier whose package name,
597
+ version, target, WASIX runtime, addon ABI, Node-API level, or profile inventory do
598
+ not match the SDK metadata; the addon then self-reports its runtime and exact
599
+ supported profile inventory before open.
600
+
601
+ The release runtime carrier owns a stripped core manifest (`extensions: []`).
602
+ The development Vite plugin projects the same core-only bytes from the build
603
+ pipeline's qualification manifest and derives separate exact extension install
604
+ contracts from its extension rows. The binding rejects a nonempty core manifest,
605
+ so the runtime carrier cannot become the authority for independently versioned
606
+ extensions.
607
+
608
+ The first browser smoke selects the SQL-only `pgtap` carrier and explicitly runs
609
+ `CREATE EXTENSION`. That isolates manifest verification, dependency ordering, archive overlay, and lifecycle SQL
610
+ from dynamic linking. The separate `smoke-browser.mjs --pg-uuidv7` profile selects the
611
+ native carrier, calls `uuid_generate_v7()` before and after the two error
612
+ recovery cases, verifies both results are UUIDv7 values, and checks clean
613
+ process exit. That proves one exact `.so` against the pinned package-owned host; it
614
+ does not add or widen a canonical extension target claim. Generic native-module
615
+ support remains gated on a safer loader boundary and broader qualification.
616
+
617
+ The example's virtual Vite modules model the intended
618
+ `@oliphaunt/extension-pgtap-wasix` and
619
+ `@oliphaunt/extension-pg-uuidv7-wasix` package roots from current target
620
+ outputs. Its asset middleware and COOP/COEP headers are development-only.
621
+ Production hosting, cache policy, and asset integrity are application/carrier
622
+ concerns; the binding does not silently copy target-owned assets into its npm
623
+ bundle.
624
+
625
+ ## Public package and qualification
626
+
627
+ `@oliphaunt/wasix-ts` is a separately versioned public SDK product. It has its own
628
+ release metadata and changelog, declares an exact browser dependency on the
629
+ published `@oliphaunt/liboliphaunt-wasix` runtime carrier, and declares the four
630
+ exact native packages as optional dependencies. It publishes the patched host
631
+ under `lib/host` for browser/default conditions. Conditional package exports
632
+ choose browser, Node.js, Bun, Deno, or Electron adapters. Browser root remains
633
+ caller-owned; the native-host root uses the Rust actor, `/direct` is caller-owned, and
634
+ the conditional `/worker` subpath is owned by its isolated Worker.
635
+
636
+ The browser smoke proves the exact runtime/host pairing can start PostgreSQL,
637
+ explicitly activate `pgtap`, retain SQLSTATE across repeated PostgreSQL error recovery,
638
+ continue with `42` on the same handle, persist through IndexedDB operation
639
+ boundaries, run an explicit `CHECKPOINT` through `execute`, and close with a
640
+ successful zero exit status. Each Node.js, Bun, Deno, and Electron host smoke installs the
641
+ packed SDK and matching packed platform carrier into a fresh external project,
642
+ verifies conditional-export and profile selection, starts the embedded
643
+ WASIX Rust runtime, activates a compiled extension, recovers from an error, and
644
+ closes cleanly. Each carrier also runs a real actor Simple Query roundtrip and
645
+ proves its V8-owned response buffer is transferable; Node additionally proves
646
+ direct and local-server lifecycles. The Deno proof uses local `node_modules`
647
+ with explicit read, environment, and FFI permissions and qualifies the declared
648
+ Deno CLI range, not managed Deno Deploy. Electron additionally qualifies the
649
+ ASAR-unpacked native-addon layout. The opt-in native browser profile
650
+ additionally loads and calls the canonical `pg_uuidv7.so`; it remains a narrow
651
+ canary rather than a generic dynamic-extension claim.
652
+
653
+ The intentional host, persistence, extension, and Wasmer compatibility limits
654
+ remain listed in [README.md](./README.md). They are explicit product boundaries,
655
+ not compatibility aliases or fallbacks to a native SDK.