@di-framework/pglite-component 6.0.15 → 6.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -12
- package/dist/BUILD-INFO.txt +7 -5
- package/dist/SHA256SUMS +4 -4
- package/dist/THIRD-PARTY-NOTICES.txt +29 -13
- package/dist/di-framework-pglite.wasm +0 -0
- package/dist/di-framework-pglite.wit +46 -46
- package/dist/js/adapter.d.ts +18 -4
- package/dist/js/adapter.js +21 -5
- package/package.json +1 -1
- package/wit/deps/pglite-engine-bootstrap/bootstrap.wit +22 -0
- package/wit/world.wit +12 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# di-framework:pglite — embedded PostgreSQL over WIT
|
|
2
2
|
|
|
3
|
-
The provider bundles
|
|
3
|
+
The provider bundles a PostgreSQL 17.11 engine, built for WASI, into a WebAssembly
|
|
4
4
|
component. SQL calls cross WIT and WebAssembly memory. There is no TCP listener,
|
|
5
5
|
network client, native sidecar, or separate database service.
|
|
6
6
|
|
|
@@ -59,6 +59,24 @@ is cached under `target/engine`. Containers are build tools only; deployment
|
|
|
59
59
|
runs the resulting `.wasm` directly. For a remote Podman daemon, set the task's
|
|
60
60
|
`DOCKER_CONTEXT=podman`.
|
|
61
61
|
|
|
62
|
+
### Engine source
|
|
63
|
+
|
|
64
|
+
The engine is upstream PostgreSQL `REL_17_11` with ElectricSQL's PGlite WASI
|
|
65
|
+
changes replayed onto it. Electric maintains no WASI branch past 17.5 (its newer
|
|
66
|
+
branches are Emscripten-only), so the 17.5 WASI changes are carried as
|
|
67
|
+
[`engine/patches/postgresql-17.11-wasi.patch`](engine/patches/README.md)
|
|
68
|
+
(PostgreSQL License). `scripts/build-engine.ts` fetches the tag pinned in
|
|
69
|
+
`scripts/tool-versions.env` (`POSTGRES_SOURCE_TAG`), refuses it unless it
|
|
70
|
+
resolves to `POSTGRES_SOURCE_COMMIT`, and applies that patch and then the
|
|
71
|
+
[wasipg](https://github.com/moznion/wasipg) recipe's sjlj patches, checking each
|
|
72
|
+
before applying it. `scripts/patch-engine-source.ts` adds the di-framework
|
|
73
|
+
changes described in `engine/NOTICE`.
|
|
74
|
+
|
|
75
|
+
`make check-engine-patch` runs all of those steps without the container build,
|
|
76
|
+
in about 30 seconds. Run it first after bumping the PostgreSQL release: a patch
|
|
77
|
+
that no longer applies fails with its name, the base, and git's output. Then
|
|
78
|
+
follow [engine/patches/README.md](engine/patches/README.md).
|
|
79
|
+
|
|
62
80
|
The build embeds the support filesystem, adapts the engine's WASI Preview 1
|
|
63
81
|
imports to Preview 2, and composes the engine with the Rust database provider.
|
|
64
82
|
It fails if the final artifact imports sockets or leaves the engine unresolved.
|
|
@@ -98,13 +116,21 @@ and persistence across separate runtime processes. It also exits a runtime
|
|
|
98
116
|
with an open transaction and verifies that committed rows survive and the
|
|
99
117
|
uncommitted row is absent after restart. No scripted database server is used.
|
|
100
118
|
|
|
119
|
+
`make smoke` also checks upgrades from the previous release. It downloads the
|
|
120
|
+
published `@di-framework/pglite-component` tarball pinned as
|
|
121
|
+
`PGLITE_UPGRADE_FROM_*` in `scripts/tool-versions.env` (6.0.15, PostgreSQL
|
|
122
|
+
17.5) and checks its sha256. Its provider writes a data directory with a table,
|
|
123
|
+
pgcrypto and PGP ciphertext. The new build must then open that directory,
|
|
124
|
+
report 17.11, read the rows, decrypt the old ciphertext, run pgcrypto on new
|
|
125
|
+
values, and write to it.
|
|
126
|
+
|
|
101
127
|
## Publishing to GHCR
|
|
102
128
|
|
|
103
129
|
The pglite component is published to GitHub Container Registry (GHCR) as an OCI wasm component. [The workflow](https://github.com/di-framework/platform/actions/workflows/pglite-component-publish.yml) runs by itself on each push to `main` that changes one of its input paths (`on.push.paths`). `workflow_dispatch` stays available for reruns and always builds.
|
|
104
130
|
|
|
105
131
|
The workflow has four jobs:
|
|
106
132
|
- `check-source` (`packages: read`) hashes the committed input files (`scripts/source-check.ts`). On a push it skips the build when `ghcr.io/di-framework/pglite-component:src-<hash>` already exists, so commits that do not change the component publish nothing.
|
|
107
|
-
- `build` (no write token) runs the same targets as `ci.yml`: `make tools-rust`, `make unit smoke` (which runs `make build`, composing the provider with the engine via `wac
|
|
133
|
+
- `build` (no write token) runs the same targets as `ci.yml`: `make tools-rust`, `make unit smoke` (which runs `make build`, composing the provider with two instances of the engine via `wac compose`), then the publisher crates' clippy and tests.
|
|
108
134
|
- `publish` (`packages: write`) downloads the tested `dist/` and runs `pglite-component-publisher`. It never rebuilds or modifies `dist/`. It checks `SHA256SUMS`, requires the export `di-framework:pglite/database@0.2.0` and rejects any import that is not `wasi:*`, then pushes the layer, config and manifest with `oras` under `ghcr.io/di-framework/pglite-component:<commit sha>` only, pulls it back with `wash` by the manifest digest to compare bytes, and only then points the version tag (`0.2.0`, the WIT package version) at that same manifest with `oras tag` and asserts both tags resolve to one digest. A failed check leaves the version tag where it was. The manifest and config carry a fixed `created` time (the publish step clears `SOURCE_DATE_EPOCH`, so `1970-01-01T00:00:00Z`), so publishing an unchanged `di-framework-pglite.wasm` gives the same digest and the pin job has nothing to pin. The `build` job passes the tested wasm sha256 to `publish`, which verifies it before pushing. It also tags the manifest `src-<hash>`.
|
|
109
135
|
- `pin` (`contents: write`, `pull-requests: write`) opens or updates the PR `bot/pin-pglite-component`, "chore: pin pglite-component to <digest>", which rewrites the digests in this README with `scripts/pin-artifact.ts`. GitHub does not run workflows on a PR that `GITHUB_TOKEN` opened, so a maintainer has to start its checks (close and reopen it) before merging. It also runs when a push's source is already published (a revert to an older source, or a pin PR closed unmerged), so `main`'s pin follows the current source; it opens no PR when `main` already has the digest. On that path the `realign` job (`packages: write`) points the moving version tag back at that source's digest with `scripts/retag.sh`, which leaves a tag that is already there alone. Every tag move is behind the same source guard, so a stale run never retags.
|
|
110
136
|
|
|
@@ -113,7 +139,7 @@ Tags are movable. `0.2.0` always points to the latest `main` publish for WIT pac
|
|
|
113
139
|
### Artifact location
|
|
114
140
|
|
|
115
141
|
The digest is in **Details** > **Record component digest** > **Step summary**, and in the `pglite-publish-report` artifact (`publish-report.json`).
|
|
116
|
-
Published digest: `ghcr.io/di-framework/pglite-component@sha256:
|
|
142
|
+
Published digest: `ghcr.io/di-framework/pglite-component@sha256:9d5b7c96aa4ad13c1c4de8d0285cf731b9926a47e0a7c680237708d26b439da3`
|
|
117
143
|
|
|
118
144
|
**Note:** The GHCR package must be made public by an org admin after the first publish. Until then, only members with access to di-framework/platform can pull it.
|
|
119
145
|
|
|
@@ -151,7 +177,7 @@ wasmtime run --dir ./data::/data path/to/app.composed.wasm
|
|
|
151
177
|
To use the published OCI component from GHCR instead (pin by digest for reproducibility):
|
|
152
178
|
|
|
153
179
|
```sh
|
|
154
|
-
export PGLITE_REF='ghcr.io/di-framework/pglite-component@sha256:
|
|
180
|
+
export PGLITE_REF='ghcr.io/di-framework/pglite-component@sha256:9d5b7c96aa4ad13c1c4de8d0285cf731b9926a47e0a7c680237708d26b439da3'
|
|
155
181
|
wash oci pull "${PGLITE_REF}" pglite.wasm
|
|
156
182
|
wac plug --plug pglite.wasm path/to/app.wasm -o path/to/app.composed.wasm
|
|
157
183
|
mkdir -p ./data
|
|
@@ -170,8 +196,8 @@ savepoints, `rows-affected`, `close`, and `server-version`. PostgreSQL parameter
|
|
|
170
196
|
use `$1`, `$2`, etc. Text parameters are sent with OID 0 (unknown type), allowing
|
|
171
197
|
PostgreSQL to infer the type from context; comparisons against `uuid`, `timestamptz`,
|
|
172
198
|
and `jsonb` columns work correctly (see Typed values). `server-version()` reports the bundled engine without
|
|
173
|
-
opening a database
|
|
174
|
-
|
|
199
|
+
opening a database (see [Application database](#application-database) for
|
|
200
|
+
`open-options.database`). Requests must fit the engine's
|
|
175
201
|
input buffer; each serialized result is limited to 64 MiB and stays in memory.
|
|
176
202
|
Larger results return a PostgreSQL program-limit error. Signal-based statement
|
|
177
203
|
timeouts are unavailable in this WASI engine: nonzero `statement-timeout-ms`
|
|
@@ -185,6 +211,23 @@ and migrations run on embedded PostgreSQL. Compose your component with this
|
|
|
185
211
|
provider (see above). The adapter is portable: it uses only
|
|
186
212
|
`node:async_hooks`, which the framework's component toolchain shims.
|
|
187
213
|
|
|
214
|
+
**Toolchain requirement for `int8` parameters past 2^53.** An exact `bigint`
|
|
215
|
+
parameter beyond ±(2^53-1) needs componentize-qjs `0.4.4-di.5` or later, which
|
|
216
|
+
`@di-framework/cli-plugin-platform` 6.2.0 or later installs. Use the default
|
|
217
|
+
runtime, without `--sync` or `--opt-size`. When you install the compiler
|
|
218
|
+
yourself, use the `di` dist-tag (`bun add @di-framework/componentize-qjs@di`):
|
|
219
|
+
npm's `latest` tag of `@di-framework/componentize-qjs` still points at
|
|
220
|
+
`0.4.4-di.1`. Older bindings, and the `--sync` / `--opt-size` runtimes, lower
|
|
221
|
+
WIT `s64` only from Numbers, and such a parameter traps the guest instance
|
|
222
|
+
instead of throwing a catchable error. Safe integers (`number` or `bigint`)
|
|
223
|
+
are sent as Numbers and work on every binding.
|
|
224
|
+
|
|
225
|
+
> **Upgrading.** Before this release, a `bigint` past 2^53 was sent as decimal
|
|
226
|
+
> text. It is now sent as an exact `int8` integer. If your guest binds such
|
|
227
|
+
> values, update the CLI plugin to 6.2.0 or later (componentize-qjs
|
|
228
|
+
> `0.4.4-di.5`) before you update this package. Guests that bind only safe
|
|
229
|
+
> integers need no change.
|
|
230
|
+
|
|
188
231
|
```ts
|
|
189
232
|
import type { SqlDatabase } from '@di-framework/repo';
|
|
190
233
|
import { createWasmPgliteDatabase } from '@di-framework/pglite-component';
|
|
@@ -207,8 +250,8 @@ handle and share it: every call goes through one async mutex.
|
|
|
207
250
|
|
|
208
251
|
- **Transactions.** `transaction(fn)` runs the provider's `begin` and `commit`, and `rollback` when `fn` throws. Statements issued from inside `fn`, through the callback view or the outer handle, join the transaction; other tasks wait for it. Inside `fn`, prefer the callback view: while the transaction is open it joins from any async context, including a worker loop or `AsyncResource` created earlier, and Wasm-guest code that the `node:async_hooks` shim does not instrument (native async generators). The outer handle joins only from `fn`'s own async context. From any other context it waits for the mutex, so awaiting it from `fn` deadlocks. A nested `transaction()` opens a savepoint. When it throws, only its own work rolls back, and the outer transaction can continue. Its work commits only with the outer transaction. Sibling savepoints started concurrently run one at a time. A plain statement issued through a view while a sibling savepoint is open joins that savepoint and rolls back with it: in `Promise.all([tx.transaction(failing), (async () => { await work(); await tx.run(...); })()])`, a `run` issued while the savepoint is open lands inside it, so await the savepoint first when the statement must survive it. Statements issued concurrently inside one transaction reach the connection one at a time, also with bindings that return promises. `transaction()` on a view, called from another async context while a sibling savepoint is open, waits for that sibling to finish; if the sibling awaits that call, it waits forever. Joining lasts only while a transaction is open. A savepoint's callback view kept after the savepoint returns joins the nearest open ancestor while the root transaction is still open, and its work commits or rolls back with that ancestor. Once the root transaction has ended, a retained callback view, like a timer scheduled inside `fn`, waits for the mutex like any other caller and never runs inside another task's transaction.
|
|
209
252
|
- **Placeholders.** `?` placeholders become `$1`, `$2`, ... in order (`toPgPlaceholders`). The rewrite skips `'...'` and `E'...'` strings, `"..."` identifiers, `$tag$...$tag$` bodies, `--` and `/* */` comments, and the jsonb operators `?|` and `?&`. Because of that, `?||` reads as `?|` followed by `|`: put a space in the concatenation (`? || 'x'`). The bare jsonb `?` operator looks like a placeholder: use `jsonb_exists(col, ?)` in a statement with parameters. A statement run without parameters is not rewritten. You can write `$n` yourself, but do not mix `$n` with `?` in one statement.
|
|
210
|
-
- **Parameters.** `string` is sent as text, and PostgreSQL infers its type from context, so a UUID or timestamp string works against `uuid` and `timestamptz` columns. A safe integer is `int8`, another number is `float8`, a `boolean` is `bool`, and `Uint8Array`, `ArrayBuffer` and typed arrays are `bytea`. A `Date` is sent as an RFC 3339 timestamp. Plain objects and arrays are sent as JSON, so a JavaScript array is not a PostgreSQL array: pass a literal such as `'{a,b}'`. A `bigint` outside the
|
|
211
|
-
- **Results.** Values match the `wasmcloud:postgres` codec in `@di-framework/repo`, so repositories behave the same on both backends. `json` and `jsonb` are parsed. `timestamptz` is an ISO string with milliseconds (`2024-01-15T10:30:00.123Z`, microseconds truncated); `infinity` and BC dates are returned as PostgreSQL prints them. An `int8` outside the safe integer range is
|
|
253
|
+
- **Parameters.** `string` is sent as text, and PostgreSQL infers its type from context, so a UUID or timestamp string works against `uuid` and `timestamptz` columns. A safe integer (`number` or `bigint`) is `int8`, sent as a JavaScript number, another number is `float8`, a `boolean` is `bool`, and `Uint8Array`, `ArrayBuffer` and typed arrays are `bytea`. A `Date` is sent as an RFC 3339 timestamp. Plain objects and arrays are sent as JSON, so a JavaScript array is not a PostgreSQL array: pass a literal such as `'{a,b}'`. A `bigint` past 2^53 that fits `int8` is sent as an exact `int8` with a `BigInt` payload, which needs componentize-qjs `0.4.4-di.5` or later (see the requirement above); one outside the `int8` range is sent as decimal text, so a `numeric` column receives it exactly. A `number` that is an integer outside the safe range throws a `TypeError`, because it has already lost precision: pass a `bigint` or a decimal string for `int8`. NaN and Infinity are valid `float8` values and are sent as `real`.
|
|
254
|
+
- **Results.** Values match the `wasmcloud:postgres` codec in `@di-framework/repo`, so repositories behave the same on both backends. `json` and `jsonb` are parsed. `timestamptz` is an ISO string with milliseconds (`2024-01-15T10:30:00.123Z`, microseconds truncated); `infinity` and BC dates are returned as PostgreSQL prints them. An `int8` outside the safe integer range is an exact decimal string when the binding lifts WIT `s64` as a `bigint` (componentize-qjs `0.4.4-di.5` or later with the default runtime, or jco). Older componentize-qjs releases and the `--sync` / `--opt-size` runtimes lift `s64` as a Number, which has already rounded it, so cast large `int8` columns to `text` (`id::text`) on those. `bool` is a boolean, `bytea` is a `Uint8Array`, and `timestamp` without zone, `date`, `uuid`, `numeric` and arrays are strings.
|
|
212
255
|
- **Errors.** Failures throw `WasmPgliteError` with the WIT `tag`. A PostgreSQL error keeps its SQLSTATE in `error.code` (`'23505'` for a unique violation), and the message starts with `PostgreSQL <code>`, so `isUniqueViolation` from `@di-framework/repo` works too.
|
|
213
256
|
|
|
214
257
|
Pass `module` or `loadModule` to supply the interface yourself (for tests), and
|
|
@@ -319,7 +362,12 @@ Blowfish (`bf`), CAST5 (`cast5`) and single DES (`des`) are unavailable in
|
|
|
319
362
|
this build, because the OpenSSL legacy provider is not built or loaded.
|
|
320
363
|
pgcrypto refuses them with `Cipher cannot be initialized`: in `encrypt()` and
|
|
321
364
|
`decrypt()`, in the PGP functions, and when decrypting PGP messages that use
|
|
322
|
-
them.
|
|
365
|
+
them. For PGP this is upstream PostgreSQL's own check (17.11, CVE-2026-14663);
|
|
366
|
+
pgcrypto is otherwise unmodified. Provider releases before the #355 fix wrote
|
|
367
|
+
Blowfish and CAST5 PGP messages that are not actually encrypted. To recover such
|
|
368
|
+
a message, decrypt it once with upstream's `ignore-cipher-failure=1` option
|
|
369
|
+
(`pgp_sym_decrypt`, `pgp_pub_decrypt`; decryption only), then re-encrypt it with
|
|
370
|
+
AES. Plaintext recovered that way is not authenticated by the key. `3des` (DES-EDE3) is in OpenSSL's default provider and still works, for
|
|
323
371
|
legacy data only. Use `aes` (`aes128`, `aes192`, `aes256` for PGP). Password hashing with
|
|
324
372
|
`crypt()` and `gen_salt('bf')`, `'md5'`, `'xdes'` or `'des'` is unaffected,
|
|
325
373
|
because pgcrypto implements those itself.
|
|
@@ -329,17 +377,97 @@ libraries per engine instance. pgcrypto resolves before that table and never
|
|
|
329
377
|
uses a slot. A `LOAD` of a new library after the table is full fails with
|
|
330
378
|
`dlopen table is full` and leaves the session usable.
|
|
331
379
|
|
|
380
|
+
### Application database
|
|
381
|
+
|
|
382
|
+
Application data lives in the `postgres` database on data directories this
|
|
383
|
+
provider initializes. Directories created before this release keep it in
|
|
384
|
+
`template1` (#212). With `open-options.database` unset:
|
|
385
|
+
|
|
386
|
+
| Data directory | Default database |
|
|
387
|
+
| --- | --- |
|
|
388
|
+
| Initialized by this provider: `<root>/data/di-framework-database` contains `postgres` | `postgres` |
|
|
389
|
+
| No marker: created before this release (6.0.16 and earlier) | `template1`, unchanged |
|
|
390
|
+
|
|
391
|
+
Upgrading the provider never moves data: an existing directory keeps opening
|
|
392
|
+
`template1`, and it never gets the marker. Fresh directories open `postgres`
|
|
393
|
+
from their first session on.
|
|
394
|
+
|
|
395
|
+
**Do not roll back below this release for a directory it created.** Providers
|
|
396
|
+
from before this release (6.0.16 and earlier) ignore the marker and open
|
|
397
|
+
`template1`. On a directory created by this release, that database is empty:
|
|
398
|
+
the application sees no data, may re-run its migrations, and writes into
|
|
399
|
+
`template1`. Upgrading again brings the `postgres` data back, and everything
|
|
400
|
+
written during the rollback stays stranded in `template1`. To roll back,
|
|
401
|
+
either roll back to a build that reads the marker, or restore the backup of
|
|
402
|
+
the root taken before the upgrade.
|
|
403
|
+
|
|
404
|
+
Explicit `open-options.database` values:
|
|
405
|
+
|
|
406
|
+
- `postgres` and `template1` are accepted. initdb creates both, so they exist
|
|
407
|
+
in every complete directory; on an older directory, `postgres` exists but is
|
|
408
|
+
empty. Before the engine starts, the provider checks that the database's
|
|
409
|
+
fixed-OID directory exists (`base/1` for `template1`, `base/5` for
|
|
410
|
+
`postgres`). If it does not, `open` fails with `open-failed`
|
|
411
|
+
(`database "postgres" has no data directory at base/5 ...`) and the same
|
|
412
|
+
component instance can still open `template1`. This is a filesystem check,
|
|
413
|
+
not a catalog lookup, so it does not catch every case:
|
|
414
|
+
- If initdb crashed while creating `postgres`, after copying `base/5` but
|
|
415
|
+
before committing the database, the check passes and an explicit
|
|
416
|
+
`postgres` open still stops the engine with
|
|
417
|
+
`FATAL: database "postgres" does not exist`. Only an initdb that crashed
|
|
418
|
+
partway through leaves a directory in this state, and with or without the
|
|
419
|
+
clean error such a directory is unfinished: re-create it (remove the root)
|
|
420
|
+
or restore it from a backup, do not use it.
|
|
421
|
+
- A `postgres` database that was dropped and re-created (new OID) or moved
|
|
422
|
+
with `ALTER DATABASE ... SET TABLESPACE` has no `base/5`, so the provider
|
|
423
|
+
refuses to open it although it exists.
|
|
424
|
+
- `template0` is always rejected: the single-user engine would not enforce
|
|
425
|
+
`datallowconn`, so opening it would make the pristine template writable.
|
|
426
|
+
- Any other name is rejected. The list is fixed because the engine picks its
|
|
427
|
+
database before its backend starts, so it cannot look the name up first.
|
|
428
|
+
|
|
429
|
+
The embedded backend stays connected to the database its first `open` chose.
|
|
430
|
+
A later `open` in the same component instance that resolves to a different
|
|
431
|
+
database fails with `open-failed`; restart the component to switch.
|
|
432
|
+
|
|
433
|
+
The marker is part of the data directory. Back it up and restore it with the
|
|
434
|
+
rest of PGDATA. Without it, a directory falls back to `template1`, and an
|
|
435
|
+
application whose tables are in `postgres` finds them missing; the provider
|
|
436
|
+
cannot detect this. Do not add the marker by hand to a directory whose data
|
|
437
|
+
is in `template1`. To move a legacy directory to `postgres`, export the data
|
|
438
|
+
through the application and import it into a fresh directory, as described
|
|
439
|
+
in [Upgrading across PostgreSQL major versions](#upgrading-across-postgresql-major-versions)
|
|
440
|
+
(no `pg_dump` is bundled).
|
|
441
|
+
|
|
442
|
+
Initialization runs in a separate, single-use engine instance bundled into
|
|
443
|
+
the provider: initdb's post-bootstrap step runs in `template1` and the
|
|
444
|
+
single-user backend cannot change databases afterwards. That instance creates
|
|
445
|
+
the cluster and the marker. The serving instance then boots on the finished
|
|
446
|
+
cluster in the selected database and verifies it with `current_database()`
|
|
447
|
+
before the first session is handed out.
|
|
448
|
+
|
|
449
|
+
Cost: the bootstrap instance runs only when a directory is created, but the
|
|
450
|
+
component model cannot drop an instance, so its linear memory and its open
|
|
451
|
+
files stay held until the process exits. The first start on a new directory
|
|
452
|
+
peaks at about 690 MB RSS (about 106 MB more than a single engine) and holds
|
|
453
|
+
about 43 more host file handles under the root. Later starts on an existing
|
|
454
|
+
directory cost the same as before (about 264 MB in the smoke consumer).
|
|
455
|
+
Restart the component after creating a directory if that memory matters.
|
|
456
|
+
|
|
332
457
|
## Persistence and ownership
|
|
333
458
|
|
|
334
459
|
For an `open("/data/orders", ...)` call:
|
|
335
460
|
|
|
336
|
-
- `/data/orders/data/` is PGDATA (catalogs, tables, WAL and configuration
|
|
461
|
+
- `/data/orders/data/` is PGDATA (catalogs, tables, WAL and configuration,
|
|
462
|
+
plus the `di-framework-database` marker on directories this provider
|
|
463
|
+
initialized; see [Application database](#application-database)).
|
|
337
464
|
- `/data/orders/runtime/` contains bundled initialization/support files.
|
|
338
465
|
- Bootstrap and shared-memory scratch files also stay beneath `/data/orders`.
|
|
339
466
|
|
|
340
467
|
Mount the entire root on persistent storage and reuse it after restarts.
|
|
341
468
|
Initialization creates the cluster once; later starts reuse it. Incomplete
|
|
342
|
-
clusters
|
|
469
|
+
clusters are rejected, and so is a data directory from another PostgreSQL major
|
|
470
|
+
version (see [below](#upgrading-across-postgresql-major-versions)).
|
|
343
471
|
`fsync`, `synchronous_commit`, and `full_page_writes` are enabled before the
|
|
344
472
|
backend starts. The host filesystem must honor WASI sync operations; a local
|
|
345
473
|
restart test is not a power-loss durability certification.
|
|
@@ -370,17 +498,52 @@ its components the matching WASI preopen. Use a stable workload identity on
|
|
|
370
498
|
redeploy and configure exclusive execution for this database. These
|
|
371
499
|
annotations provide storage; they do not enforce database ownership.
|
|
372
500
|
|
|
501
|
+
## Upgrading across PostgreSQL major versions
|
|
502
|
+
|
|
503
|
+
A data directory belongs to one PostgreSQL major version, recorded in
|
|
504
|
+
`<root>/data/PG_VERSION`. Minor releases share the on-disk format: a directory
|
|
505
|
+
written by the 17.5 engine (provider 6.0.15 and earlier) opens in place under
|
|
506
|
+
17.11, and `make smoke` checks this.
|
|
507
|
+
|
|
508
|
+
The engine opens only major version 17. For any other `PG_VERSION`, `open`
|
|
509
|
+
fails with `open-failed` and a message such as:
|
|
510
|
+
|
|
511
|
+
```text
|
|
512
|
+
incompatible PostgreSQL data directory: PG_VERSION is 16, but this provider bundles
|
|
513
|
+
PostgreSQL 17.11 and opens only major version 17 data directories; see
|
|
514
|
+
platform/pglite-component/README.md#upgrading-across-postgresql-major-versions
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
The provider does not convert directories between major versions; it bundles
|
|
518
|
+
neither a second engine nor `pg_upgrade`. To move a database across a major
|
|
519
|
+
version:
|
|
520
|
+
|
|
521
|
+
1. Stop every writer. Keep the provider version that wrote the directory,
|
|
522
|
+
pinned by its digest or npm version, and back up the whole root.
|
|
523
|
+
2. With that provider, export the data through the application, for example
|
|
524
|
+
with a migration step that reads each table through the `database`
|
|
525
|
+
interface into a portable format.
|
|
526
|
+
3. Deploy the new provider on a new, empty root, and recreate the schema with
|
|
527
|
+
the application's migrations.
|
|
528
|
+
4. Import the exported data, check it, then retire the old root.
|
|
529
|
+
|
|
530
|
+
If the root is encrypted with `pglite-encfs`, compose encfs with the same key
|
|
531
|
+
for both the export and the import. PostgreSQL 18 is tracked in #401.
|
|
532
|
+
|
|
373
533
|
## Implementation
|
|
374
534
|
|
|
375
535
|
- `js/adapter.ts`: the `SqlDatabase` adapter published on npm (`bun run build:adapter`).
|
|
376
536
|
- `js/wit.d.ts`: generated TypeScript view of the `database` interface.
|
|
377
537
|
- `src/lib.rs`: public WIT resources and connection lifecycle.
|
|
378
538
|
- `src/pgwire.rs`: PostgreSQL message encoding and typed rows, over memory.
|
|
379
|
-
- `src/engine.rs`: persistent filesystem setup
|
|
539
|
+
- `src/engine.rs`: persistent filesystem setup, exclusive engine access, and the application database choice (marker, crash windows).
|
|
380
540
|
- `engine/bridge.wat`: private canonical ABI bridge to the core engine.
|
|
381
541
|
- `engine/build-pgcrypto.sh`: builds OpenSSL libcrypto and pgcrypto in the engine container and generates pgcrypto's static `dlsym` table; `scripts/patch-engine-source.ts` links both into the engine. The libcrypto build is cached under a key of the OpenSSL source tarball's sha256, the Configure options and `SOURCE_DATE_EPOCH`, so an OpenSSL version bump always rebuilds it.
|
|
382
542
|
- `scripts/patch-engine-source.ts`: also replaces the upstream `dlopen` emulation's unbounded table (`dltab`) with the bounded one described under [Extensions](#extensions), and keeps pgcrypto's hook ahead of it.
|
|
543
|
+
- `engine/patches/postgresql-17.11-wasi.patch`: Electric's 17.5 WASI changes replayed onto upstream `REL_17_11`; see [engine/patches/README.md](engine/patches/README.md).
|
|
544
|
+
- `scripts/build-engine.ts`: builds the engine source tree (pinned tag, checked commit, checked patches) and runs the wasipg container build; `scripts/check-engine-patch.ts` (`make check-engine-patch`) runs the same source steps without the build.
|
|
383
545
|
- `scripts/prepare-engine.ts`: pinned engine preparation and componentization (all build scripts are Bun TypeScript; see `scripts/`).
|
|
384
546
|
- `wit/deps/pglite-engine/engine.wit`: internal interface, composed away at build.
|
|
547
|
+
- `wit/deps/pglite-engine-bootstrap/bootstrap.wit`: internal interface of the single-use initdb engine instance; `scripts/build.ts` (`providerWac`) feeds it from a second instance of the same engine component.
|
|
385
548
|
|
|
386
549
|
Engine provenance and modifications are described in `engine/NOTICE`.
|
package/dist/BUILD-INFO.txt
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
component: di-framework:pglite@0.2.0
|
|
2
2
|
profile: release
|
|
3
|
-
built: 2026-10-
|
|
3
|
+
built: 2026-10-17T04:21:44Z
|
|
4
4
|
rustc: rustc 1.97.1 (8bab26f4f 2026-07-14)
|
|
5
5
|
target: wasm32-wasip2
|
|
6
6
|
wasm-tools: wasm-tools 1.258.0 (5c6d31c78 2026-08-24)
|
|
7
7
|
wit-bindgen: 0.61.1
|
|
8
8
|
md5: 0.7.0
|
|
9
|
-
engine: PostgreSQL 17.
|
|
9
|
+
engine: PostgreSQL 17.11, persistent WASI filesystem
|
|
10
10
|
extensions: plpgsql, pgcrypto (OpenSSL 3.5.9 libcrypto)
|
|
11
|
-
engine-source: https://github.com/
|
|
11
|
+
engine-source: https://github.com/postgres/postgres/tree/083ac033419f690758508e08c1736089384bbee8 (REL_17_11)
|
|
12
|
+
engine-patch: engine/patches/postgresql-17.11-wasi.patch (sha256 6beb64a561caa80e3669733f9e24e344edb0445d2623c17eee79d48ec28fac65)
|
|
13
|
+
engine-recipe: https://github.com/moznion/wasipg/tree/fa290be67946c0d3c4540bd1400b11e193d874e1
|
|
12
14
|
adapter-sha256: e51dd61669a20daca34befe7fdadf1ac84e4795a2c84a8400818ee1de5373ccc
|
|
13
|
-
engine-sha256:
|
|
14
|
-
size:
|
|
15
|
+
engine-sha256: eb374344cff24c4cedacdad05a636a3b75b5eb1c4e55aa1a3ff9930be2ebc328
|
|
16
|
+
size: 20176453 bytes
|
package/dist/SHA256SUMS
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
04cea641db9ac90a954fefd7783100175ce15bb2cc2e6fc47660fb4090eb2663 di-framework-pglite.wasm
|
|
2
|
+
fcce3740d3bc8501368fa6c4686eb626a1e6d4d15d017817de273dfd77afcab9 di-framework-pglite.wit
|
|
3
|
+
c3744f0613035d64fc6120bb9404a56e5759761e7cd64c55feeadd74b9727ad0 BUILD-INFO.txt
|
|
4
|
+
443fd294671eb64972556656d3ee871e48448ad1169c012d18aa868568f628c1 THIRD-PARTY-NOTICES.txt
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
di-framework embedded PGlite component
|
|
2
2
|
|
|
3
|
-
The provider bundles PostgreSQL 17.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
The provider bundles PostgreSQL 17.11 built for WASI. The engine source is
|
|
4
|
+
upstream PostgreSQL REL_17_11 (commit 083ac033419f690758508e08c1736089384bbee8)
|
|
5
|
+
with ElectricSQL's PGlite WASI changes from postgres-pglite branch
|
|
6
|
+
REL_17_5_WASM-pglite (commit 2194acf356e9b7bf7cff29c4ed4bd6b82b93895d) replayed
|
|
7
|
+
onto it; that replay is engine/patches/postgresql-17.11-wasi.patch in the
|
|
8
|
+
di-framework source. PostgreSQL, postgres-pglite and the replay patch are
|
|
9
|
+
distributed under the PostgreSQL License, reproduced in POSTGRESQL-COPYRIGHT.
|
|
10
|
+
The engine is built with the wasipg recipe and its sjlj patches (Apache-2.0;
|
|
11
|
+
notice below). The upstream bindings license and WASI adapter license are
|
|
12
|
+
reproduced in PGLITE-BINDINGS-LICENSE and WASMTIME-LICENSE. The engine
|
|
13
|
+
statically links the pgcrypto extension and OpenSSL libcrypto; their notices
|
|
14
|
+
are in PGCRYPTO-NOTICES and the OpenSSL license (Apache-2.0) in
|
|
15
|
+
OPENSSL-LICENSE.
|
|
9
16
|
|
|
10
17
|
Sources:
|
|
11
|
-
https://github.com/
|
|
18
|
+
https://github.com/postgres/postgres/tree/REL_17_11
|
|
19
|
+
https://github.com/electric-sql/postgres-pglite/tree/REL_17_5_WASM-pglite
|
|
12
20
|
https://github.com/electric-sql/pglite-bindings
|
|
13
21
|
https://github.com/moznion/wasipg
|
|
14
22
|
https://github.com/bytecodealliance/wasmtime/tree/v29.0.1
|
|
@@ -21,9 +29,17 @@ bootstrap files within the database's persistent filesystem mount. A growable,
|
|
|
21
29
|
bounded memory buffer replaces socket-file spillover for query replies. Backend
|
|
22
30
|
startup restores application session defaults after bootstrap and enables
|
|
23
31
|
durable writes. The emulated dlopen keeps loaded libraries in a bounded table
|
|
24
|
-
instead of writing past the end of an unsized array.
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
instead of writing past the end of an unsized array. Two small changes to the
|
|
33
|
+
WASI glue let it compile against 17.11: initdb's frontend SplitGUCList
|
|
34
|
+
declaration is renamed where initdb is compiled into the backend, and the
|
|
35
|
+
frontend allocator stubs gain the overflow-checked pg_malloc_mul and
|
|
36
|
+
pg_realloc_mul. pgcrypto is unmodified: PostgreSQL 17.11 itself refuses
|
|
37
|
+
ciphers OpenSSL cannot run (CVE-2026-14663), which in this build includes
|
|
38
|
+
Blowfish and CAST5.
|
|
39
|
+
|
|
40
|
+
The wasipg notice below describes wasipg's own release artifacts, which were
|
|
41
|
+
built from REL_17_5_WASM-pglite; this provider uses wasipg's build recipe and
|
|
42
|
+
patches with the 17.11 source tree above.
|
|
27
43
|
|
|
28
44
|
The engine's core module, support files, adapter, and the provider are
|
|
29
45
|
composed into one deployable artifact. Engine code executes in the Wasm
|
|
@@ -86,9 +102,9 @@ wasi-libc from wasi-sdk 33, distributed under
|
|
|
86
102
|
"Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT". The shadow headers
|
|
87
103
|
under build/hotfix/ are derived from wasi-libc headers.
|
|
88
104
|
PostgreSQL Database Management System
|
|
89
|
-
(
|
|
105
|
+
(also known as Postgres, formerly known as Postgres95)
|
|
90
106
|
|
|
91
|
-
Portions Copyright (c) 1996-
|
|
107
|
+
Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
|
|
92
108
|
|
|
93
109
|
Portions Copyright (c) 1994, The Regents of the University of California
|
|
94
110
|
|
|
@@ -110,7 +126,7 @@ ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO
|
|
|
110
126
|
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
|
111
127
|
|
|
112
128
|
--------------------------------------------------------------------------
|
|
113
|
-
pgcrypto (PostgreSQL 17.
|
|
129
|
+
pgcrypto (PostgreSQL 17.11 contrib/pgcrypto)
|
|
114
130
|
--------------------------------------------------------------------------
|
|
115
131
|
|
|
116
132
|
pgcrypto is part of PostgreSQL and is distributed under the PostgreSQL
|
|
Binary file
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package root:component;
|
|
2
2
|
|
|
3
3
|
world root {
|
|
4
|
+
import wasi:random/random@0.2.3;
|
|
4
5
|
import wasi:io/poll@0.2.9;
|
|
5
6
|
import wasi:clocks/monotonic-clock@0.2.9;
|
|
6
7
|
import wasi:clocks/wall-clock@0.2.9;
|
|
@@ -18,11 +19,17 @@ world root {
|
|
|
18
19
|
import wasi:cli/terminal-stderr@0.2.9;
|
|
19
20
|
import wasi:filesystem/types@0.2.9;
|
|
20
21
|
import wasi:filesystem/preopens@0.2.9;
|
|
21
|
-
import wasi:random/random@0.2.3;
|
|
22
22
|
|
|
23
23
|
export di-framework:pglite/types@0.2.0;
|
|
24
24
|
export di-framework:pglite/database@0.2.0;
|
|
25
25
|
}
|
|
26
|
+
package wasi:random@0.2.3 {
|
|
27
|
+
interface random {
|
|
28
|
+
get-random-bytes: func(len: u64) -> list<u8>;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
26
33
|
package wasi:io@0.2.9 {
|
|
27
34
|
interface poll {
|
|
28
35
|
resource pollable {
|
|
@@ -40,16 +47,16 @@ package wasi:io@0.2.9 {
|
|
|
40
47
|
|
|
41
48
|
resource input-stream {
|
|
42
49
|
read: func(len: u64) -> result<list<u8>, stream-error>;
|
|
43
|
-
subscribe: func() -> pollable;
|
|
44
50
|
blocking-read: func(len: u64) -> result<list<u8>, stream-error>;
|
|
51
|
+
subscribe: func() -> pollable;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
resource output-stream {
|
|
48
55
|
check-write: func() -> result<u64, stream-error>;
|
|
49
56
|
write: func(contents: list<u8>) -> result<_, stream-error>;
|
|
57
|
+
blocking-write-and-flush: func(contents: list<u8>) -> result<_, stream-error>;
|
|
50
58
|
blocking-flush: func() -> result<_, stream-error>;
|
|
51
59
|
subscribe: func() -> pollable;
|
|
52
|
-
blocking-write-and-flush: func(contents: list<u8>) -> result<_, stream-error>;
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
variant stream-error {
|
|
@@ -64,15 +71,15 @@ package wasi:clocks@0.2.9 {
|
|
|
64
71
|
interface monotonic-clock {
|
|
65
72
|
use wasi:io/poll@0.2.9.{pollable};
|
|
66
73
|
|
|
67
|
-
type duration = u64;
|
|
68
|
-
|
|
69
74
|
type instant = u64;
|
|
70
75
|
|
|
71
|
-
|
|
76
|
+
type duration = u64;
|
|
72
77
|
|
|
73
78
|
now: func() -> instant;
|
|
74
79
|
|
|
75
80
|
subscribe-instant: func(when: instant) -> pollable;
|
|
81
|
+
|
|
82
|
+
subscribe-duration: func(when: duration) -> pollable;
|
|
76
83
|
}
|
|
77
84
|
interface wall-clock {
|
|
78
85
|
record datetime {
|
|
@@ -135,42 +142,47 @@ package wasi:cli@0.2.9 {
|
|
|
135
142
|
|
|
136
143
|
package wasi:filesystem@0.2.9 {
|
|
137
144
|
interface types {
|
|
138
|
-
use wasi:io/streams@0.2.9.{input-stream, output-stream};
|
|
139
|
-
use wasi:clocks/wall-clock@0.2.9.{datetime};
|
|
140
145
|
use wasi:io/error@0.2.9.{error};
|
|
146
|
+
use wasi:clocks/wall-clock@0.2.9.{datetime};
|
|
147
|
+
use wasi:io/streams@0.2.9.{input-stream, output-stream};
|
|
141
148
|
|
|
142
149
|
resource descriptor {
|
|
143
150
|
read-via-stream: func(offset: filesize) -> result<input-stream, error-code>;
|
|
144
151
|
write-via-stream: func(offset: filesize) -> result<output-stream, error-code>;
|
|
145
152
|
append-via-stream: func() -> result<output-stream, error-code>;
|
|
153
|
+
advise: func(offset: filesize, length: filesize, advice: advice) -> result<_, error-code>;
|
|
154
|
+
sync-data: func() -> result<_, error-code>;
|
|
146
155
|
get-flags: func() -> result<descriptor-flags, error-code>;
|
|
156
|
+
get-type: func() -> result<descriptor-type, error-code>;
|
|
157
|
+
set-size: func(size: filesize) -> result<_, error-code>;
|
|
158
|
+
read: func(length: filesize, offset: filesize) -> result<tuple<list<u8>, bool>, error-code>;
|
|
159
|
+
write: func(buffer: list<u8>, offset: filesize) -> result<filesize, error-code>;
|
|
147
160
|
read-directory: func() -> result<directory-entry-stream, error-code>;
|
|
148
161
|
sync: func() -> result<_, error-code>;
|
|
149
162
|
create-directory-at: func(path: string) -> result<_, error-code>;
|
|
150
163
|
stat: func() -> result<descriptor-stat, error-code>;
|
|
151
164
|
stat-at: func(path-flags: path-flags, path: string) -> result<descriptor-stat, error-code>;
|
|
152
165
|
open-at: func(path-flags: path-flags, path: string, open-flags: open-flags, %flags: descriptor-flags) -> result<descriptor, error-code>;
|
|
153
|
-
rename-at: func(old-path: string, new-descriptor: borrow<descriptor>, new-path: string) -> result<_, error-code>;
|
|
154
|
-
metadata-hash: func() -> result<metadata-hash-value, error-code>;
|
|
155
|
-
metadata-hash-at: func(path-flags: path-flags, path: string) -> result<metadata-hash-value, error-code>;
|
|
156
|
-
advise: func(offset: filesize, length: filesize, advice: advice) -> result<_, error-code>;
|
|
157
|
-
sync-data: func() -> result<_, error-code>;
|
|
158
|
-
get-type: func() -> result<descriptor-type, error-code>;
|
|
159
|
-
set-size: func(size: filesize) -> result<_, error-code>;
|
|
160
|
-
read: func(length: filesize, offset: filesize) -> result<tuple<list<u8>, bool>, error-code>;
|
|
161
|
-
write: func(buffer: list<u8>, offset: filesize) -> result<filesize, error-code>;
|
|
162
166
|
readlink-at: func(path: string) -> result<string, error-code>;
|
|
163
167
|
remove-directory-at: func(path: string) -> result<_, error-code>;
|
|
168
|
+
rename-at: func(old-path: string, new-descriptor: borrow<descriptor>, new-path: string) -> result<_, error-code>;
|
|
164
169
|
symlink-at: func(old-path: string, new-path: string) -> result<_, error-code>;
|
|
165
170
|
unlink-file-at: func(path: string) -> result<_, error-code>;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
resource directory-entry-stream {
|
|
169
|
-
read-directory-entry: func() -> result<option<directory-entry>, error-code>;
|
|
171
|
+
metadata-hash: func() -> result<metadata-hash-value, error-code>;
|
|
172
|
+
metadata-hash-at: func(path-flags: path-flags, path: string) -> result<metadata-hash-value, error-code>;
|
|
170
173
|
}
|
|
171
174
|
|
|
172
175
|
type filesize = u64;
|
|
173
176
|
|
|
177
|
+
enum advice {
|
|
178
|
+
normal,
|
|
179
|
+
sequential,
|
|
180
|
+
random,
|
|
181
|
+
will-need,
|
|
182
|
+
dont-need,
|
|
183
|
+
no-reuse,
|
|
184
|
+
}
|
|
185
|
+
|
|
174
186
|
enum error-code {
|
|
175
187
|
access,
|
|
176
188
|
would-block,
|
|
@@ -211,6 +223,10 @@ package wasi:filesystem@0.2.9 {
|
|
|
211
223
|
cross-device,
|
|
212
224
|
}
|
|
213
225
|
|
|
226
|
+
resource directory-entry-stream {
|
|
227
|
+
read-directory-entry: func() -> result<option<directory-entry>, error-code>;
|
|
228
|
+
}
|
|
229
|
+
|
|
214
230
|
flags descriptor-flags {
|
|
215
231
|
read,
|
|
216
232
|
write,
|
|
@@ -231,6 +247,15 @@ package wasi:filesystem@0.2.9 {
|
|
|
231
247
|
socket,
|
|
232
248
|
}
|
|
233
249
|
|
|
250
|
+
record directory-entry {
|
|
251
|
+
%type: descriptor-type,
|
|
252
|
+
name: string,
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
flags path-flags {
|
|
256
|
+
symlink-follow,
|
|
257
|
+
}
|
|
258
|
+
|
|
234
259
|
type link-count = u64;
|
|
235
260
|
|
|
236
261
|
record descriptor-stat {
|
|
@@ -242,10 +267,6 @@ package wasi:filesystem@0.2.9 {
|
|
|
242
267
|
status-change-timestamp: option<datetime>,
|
|
243
268
|
}
|
|
244
269
|
|
|
245
|
-
flags path-flags {
|
|
246
|
-
symlink-follow,
|
|
247
|
-
}
|
|
248
|
-
|
|
249
270
|
flags open-flags {
|
|
250
271
|
create,
|
|
251
272
|
directory,
|
|
@@ -258,20 +279,6 @@ package wasi:filesystem@0.2.9 {
|
|
|
258
279
|
upper: u64,
|
|
259
280
|
}
|
|
260
281
|
|
|
261
|
-
record directory-entry {
|
|
262
|
-
%type: descriptor-type,
|
|
263
|
-
name: string,
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
enum advice {
|
|
267
|
-
normal,
|
|
268
|
-
sequential,
|
|
269
|
-
random,
|
|
270
|
-
will-need,
|
|
271
|
-
dont-need,
|
|
272
|
-
no-reuse,
|
|
273
|
-
}
|
|
274
|
-
|
|
275
282
|
filesystem-error-code: func(err: borrow<error>) -> option<error-code>;
|
|
276
283
|
}
|
|
277
284
|
interface preopens {
|
|
@@ -282,13 +289,6 @@ package wasi:filesystem@0.2.9 {
|
|
|
282
289
|
}
|
|
283
290
|
|
|
284
291
|
|
|
285
|
-
package wasi:random@0.2.3 {
|
|
286
|
-
interface random {
|
|
287
|
-
get-random-bytes: func(len: u64) -> list<u8>;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
292
|
package di-framework:pglite@0.2.0 {
|
|
293
293
|
interface types {
|
|
294
294
|
record pg-error {
|
package/dist/js/adapter.d.ts
CHANGED
|
@@ -51,7 +51,13 @@ export interface WasmPgliteConnection {
|
|
|
51
51
|
}
|
|
52
52
|
/** The `open-options` record. Every field is optional. */
|
|
53
53
|
export interface WasmPgliteOpenOptions {
|
|
54
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Session database: `postgres` or `template1`. Unset selects the data
|
|
56
|
+
* directory's default: `postgres` for a directory the provider initialized
|
|
57
|
+
* (it writes a `di-framework-database` marker in PGDATA), `template1` for
|
|
58
|
+
* one created by an earlier release. The provider rejects `template0` and
|
|
59
|
+
* any other name.
|
|
60
|
+
*/
|
|
55
61
|
database?: string;
|
|
56
62
|
/** Reserved by the provider: only `0` (or unset) is accepted. */
|
|
57
63
|
statementTimeoutMs?: number;
|
|
@@ -87,9 +93,17 @@ export declare function unwrapWasmPgliteResult<T>(value: T | WasmResult<T>): T;
|
|
|
87
93
|
/**
|
|
88
94
|
* Converts a bound JavaScript parameter into a `value` variant.
|
|
89
95
|
*
|
|
90
|
-
* -
|
|
91
|
-
*
|
|
92
|
-
*
|
|
96
|
+
* - Integers are sent in three bands:
|
|
97
|
+
* - A safe integer (`number` or `bigint`, |v| <= 2^53-1) becomes `integer`
|
|
98
|
+
* with a `number` payload. Every componentize-qjs release, including the
|
|
99
|
+
* `--sync` / `--opt-size` runtimes, and jco lower WIT `s64` from it.
|
|
100
|
+
* - A `bigint` past 2^53 that fits `int8` becomes `integer` with a `bigint`
|
|
101
|
+
* payload, so it reaches PostgreSQL exact. That needs a binding that lowers
|
|
102
|
+
* `s64` from `BigInt`: componentize-qjs 0.4.4-di.5 or later (default
|
|
103
|
+
* runtime) or jco. Older componentize-qjs bindings trap on it.
|
|
104
|
+
* - A `bigint` outside the `int8` range is sent as `text` (OID 0), so
|
|
105
|
+
* PostgreSQL parses it for a `numeric` column instead of the binding
|
|
106
|
+
* wrapping it modulo 2^64.
|
|
93
107
|
* - A `number` that is an integer outside the safe range throws a `TypeError`
|
|
94
108
|
* (pass a `bigint` or string for `int8`); other non-safe numbers, including
|
|
95
109
|
* NaN and Infinity, are `real` (`float8`).
|
package/dist/js/adapter.js
CHANGED
|
@@ -84,12 +84,22 @@ export function unwrapWasmPgliteResult(value) {
|
|
|
84
84
|
}
|
|
85
85
|
const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
|
|
86
86
|
const MIN_SAFE = BigInt(Number.MIN_SAFE_INTEGER);
|
|
87
|
+
const MAX_INT8 = 2n ** 63n - 1n;
|
|
88
|
+
const MIN_INT8 = -(2n ** 63n);
|
|
87
89
|
/**
|
|
88
90
|
* Converts a bound JavaScript parameter into a `value` variant.
|
|
89
91
|
*
|
|
90
|
-
* -
|
|
91
|
-
*
|
|
92
|
-
*
|
|
92
|
+
* - Integers are sent in three bands:
|
|
93
|
+
* - A safe integer (`number` or `bigint`, |v| <= 2^53-1) becomes `integer`
|
|
94
|
+
* with a `number` payload. Every componentize-qjs release, including the
|
|
95
|
+
* `--sync` / `--opt-size` runtimes, and jco lower WIT `s64` from it.
|
|
96
|
+
* - A `bigint` past 2^53 that fits `int8` becomes `integer` with a `bigint`
|
|
97
|
+
* payload, so it reaches PostgreSQL exact. That needs a binding that lowers
|
|
98
|
+
* `s64` from `BigInt`: componentize-qjs 0.4.4-di.5 or later (default
|
|
99
|
+
* runtime) or jco. Older componentize-qjs bindings trap on it.
|
|
100
|
+
* - A `bigint` outside the `int8` range is sent as `text` (OID 0), so
|
|
101
|
+
* PostgreSQL parses it for a `numeric` column instead of the binding
|
|
102
|
+
* wrapping it modulo 2^64.
|
|
93
103
|
* - A `number` that is an integer outside the safe range throws a `TypeError`
|
|
94
104
|
* (pass a `bigint` or string for `int8`); other non-safe numbers, including
|
|
95
105
|
* NaN and Infinity, are `real` (`float8`).
|
|
@@ -101,14 +111,20 @@ export function toWasmPgValue(value) {
|
|
|
101
111
|
return { tag: 'null' };
|
|
102
112
|
switch (typeof value) {
|
|
103
113
|
case 'bigint':
|
|
104
|
-
//
|
|
114
|
+
// Safe values go as Numbers so bindings that lower s64 only from Numbers
|
|
115
|
+
// (componentize-qjs before 0.4.4-di.5, --sync, --opt-size) keep working.
|
|
105
116
|
if (value >= MIN_SAFE && value <= MAX_SAFE) {
|
|
106
117
|
return { tag: 'integer', val: Number(value) };
|
|
107
118
|
}
|
|
119
|
+
// Past 2^53 only a BigInt is exact (componentize-qjs 0.4.4-di.5+, jco).
|
|
120
|
+
if (value >= MIN_INT8 && value <= MAX_INT8) {
|
|
121
|
+
return { tag: 'integer', val: value };
|
|
122
|
+
}
|
|
108
123
|
return { tag: 'text', val: value.toString() };
|
|
109
124
|
case 'number':
|
|
110
125
|
if (Number.isSafeInteger(value)) {
|
|
111
|
-
|
|
126
|
+
// `+ 0` turns -0 into 0, which is the int8 PostgreSQL stores anyway.
|
|
127
|
+
return { tag: 'integer', val: value + 0 };
|
|
112
128
|
}
|
|
113
129
|
if (Number.isInteger(value)) {
|
|
114
130
|
// An integral double past 2^53 has already lost its low bits, and the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@di-framework/pglite-component",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Embedded PostgreSQL component with persistent WASI storage and no TCP, plus its SqlDatabase adapter for @di-framework/repo.",
|
|
6
6
|
"license": "(MIT OR Apache-2.0)",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package di-framework:pglite-engine-bootstrap@0.1.0;
|
|
2
|
+
|
|
3
|
+
/// Private: a second, single-use instance of the bundled engine that runs
|
|
4
|
+
/// initdb on an empty data directory (#212).
|
|
5
|
+
///
|
|
6
|
+
/// The functions match `di-framework:pglite-engine/engine@0.1.0`; the build
|
|
7
|
+
/// composes this import from a second instance of the same engine component
|
|
8
|
+
/// (see `scripts/build.ts`). Like the engine import, it is never a host import
|
|
9
|
+
/// in the final artifact.
|
|
10
|
+
///
|
|
11
|
+
/// Why a second instance: the engine's first backend session after initdb
|
|
12
|
+
/// runs initdb's post-bootstrap SQL, which creates the `postgres` database
|
|
13
|
+
/// from a `template1` session, and a single-user backend cannot change
|
|
14
|
+
/// databases afterwards. The provider therefore initializes the cluster here
|
|
15
|
+
/// and serves every session, the first included, from the main engine
|
|
16
|
+
/// instance booted on the existing cluster with the selected database.
|
|
17
|
+
interface bootstrap {
|
|
18
|
+
set-env: func(name: string, value: string) -> s32;
|
|
19
|
+
start: func(arguments: list<string>) -> s32;
|
|
20
|
+
initdb: func() -> s32;
|
|
21
|
+
backend: func();
|
|
22
|
+
}
|
package/wit/world.wit
CHANGED
|
@@ -91,7 +91,15 @@ interface types {
|
|
|
91
91
|
/// Options accepted by `open`. Every field is optional; `none` means the
|
|
92
92
|
/// documented default.
|
|
93
93
|
record open-options {
|
|
94
|
-
/// Database name for the session
|
|
94
|
+
/// Database name for the session: `postgres` or `template1`.
|
|
95
|
+
///
|
|
96
|
+
/// `none` selects the data directory's default. A directory this
|
|
97
|
+
/// provider initializes keeps application data in `postgres` and records
|
|
98
|
+
/// that with a `di-framework-database` marker in PGDATA. A directory
|
|
99
|
+
/// without the marker (one created by an earlier release) keeps using
|
|
100
|
+
/// `template1`, unchanged. `template0` and every other name fail `open`.
|
|
101
|
+
/// The engine stays in the database its first `open` selected until the
|
|
102
|
+
/// component restarts; a later `open` naming another one fails.
|
|
95
103
|
database: option<string>,
|
|
96
104
|
/// Reserved: WASI lacks the signal timer used for query cancellation.
|
|
97
105
|
/// Only none or zero is supported; nonzero values fail open.
|
|
@@ -114,7 +122,7 @@ interface database {
|
|
|
114
122
|
/// are supported. Close/drop releases the connection for reuse at that root.
|
|
115
123
|
open: func(path: string, options: option<open-options>) -> result<connection, error>;
|
|
116
124
|
|
|
117
|
-
/// The bundled engine version string, `17.
|
|
125
|
+
/// The bundled engine version string, `17.11`; does not open a database.
|
|
118
126
|
server-version: func() -> result<string, error>;
|
|
119
127
|
|
|
120
128
|
/// An exclusive handle to the embedded session. Closing or dropping it
|
|
@@ -174,10 +182,11 @@ interface database {
|
|
|
174
182
|
/// Kept symmetric with the sqlite-component: `types` is exported explicitly
|
|
175
183
|
/// so the component is self-contained for `wac plug`.
|
|
176
184
|
///
|
|
177
|
-
/// The private engine
|
|
185
|
+
/// The private engine imports are resolved during the provider build. The
|
|
178
186
|
/// shipped artifact has only WASI imports, including filesystem and randomness.
|
|
179
187
|
world pglite-provider {
|
|
180
188
|
import di-framework:pglite-engine/engine@0.1.0;
|
|
189
|
+
import di-framework:pglite-engine-bootstrap/bootstrap@0.1.0;
|
|
181
190
|
export types;
|
|
182
191
|
export database;
|
|
183
192
|
}
|