@di-framework/pglite-component 6.0.16 → 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 +106 -9
- package/dist/BUILD-INFO.txt +2 -2
- package/dist/SHA256SUMS +3 -3
- 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 +11 -2
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ The pglite component is published to GitHub Container Registry (GHCR) as an OCI
|
|
|
130
130
|
|
|
131
131
|
The workflow has four jobs:
|
|
132
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.
|
|
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 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.
|
|
134
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>`.
|
|
135
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.
|
|
136
136
|
|
|
@@ -139,7 +139,7 @@ Tags are movable. `0.2.0` always points to the latest `main` publish for WIT pac
|
|
|
139
139
|
### Artifact location
|
|
140
140
|
|
|
141
141
|
The digest is in **Details** > **Record component digest** > **Step summary**, and in the `pglite-publish-report` artifact (`publish-report.json`).
|
|
142
|
-
Published digest: `ghcr.io/di-framework/pglite-component@sha256:
|
|
142
|
+
Published digest: `ghcr.io/di-framework/pglite-component@sha256:9d5b7c96aa4ad13c1c4de8d0285cf731b9926a47e0a7c680237708d26b439da3`
|
|
143
143
|
|
|
144
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.
|
|
145
145
|
|
|
@@ -177,7 +177,7 @@ wasmtime run --dir ./data::/data path/to/app.composed.wasm
|
|
|
177
177
|
To use the published OCI component from GHCR instead (pin by digest for reproducibility):
|
|
178
178
|
|
|
179
179
|
```sh
|
|
180
|
-
export PGLITE_REF='ghcr.io/di-framework/pglite-component@sha256:
|
|
180
|
+
export PGLITE_REF='ghcr.io/di-framework/pglite-component@sha256:9d5b7c96aa4ad13c1c4de8d0285cf731b9926a47e0a7c680237708d26b439da3'
|
|
181
181
|
wash oci pull "${PGLITE_REF}" pglite.wasm
|
|
182
182
|
wac plug --plug pglite.wasm path/to/app.wasm -o path/to/app.composed.wasm
|
|
183
183
|
mkdir -p ./data
|
|
@@ -196,8 +196,8 @@ savepoints, `rows-affected`, `close`, and `server-version`. PostgreSQL parameter
|
|
|
196
196
|
use `$1`, `$2`, etc. Text parameters are sent with OID 0 (unknown type), allowing
|
|
197
197
|
PostgreSQL to infer the type from context; comparisons against `uuid`, `timestamptz`,
|
|
198
198
|
and `jsonb` columns work correctly (see Typed values). `server-version()` reports the bundled engine without
|
|
199
|
-
opening a database
|
|
200
|
-
|
|
199
|
+
opening a database (see [Application database](#application-database) for
|
|
200
|
+
`open-options.database`). Requests must fit the engine's
|
|
201
201
|
input buffer; each serialized result is limited to 64 MiB and stays in memory.
|
|
202
202
|
Larger results return a PostgreSQL program-limit error. Signal-based statement
|
|
203
203
|
timeouts are unavailable in this WASI engine: nonzero `statement-timeout-ms`
|
|
@@ -211,6 +211,23 @@ and migrations run on embedded PostgreSQL. Compose your component with this
|
|
|
211
211
|
provider (see above). The adapter is portable: it uses only
|
|
212
212
|
`node:async_hooks`, which the framework's component toolchain shims.
|
|
213
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
|
+
|
|
214
231
|
```ts
|
|
215
232
|
import type { SqlDatabase } from '@di-framework/repo';
|
|
216
233
|
import { createWasmPgliteDatabase } from '@di-framework/pglite-component';
|
|
@@ -233,8 +250,8 @@ handle and share it: every call goes through one async mutex.
|
|
|
233
250
|
|
|
234
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.
|
|
235
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.
|
|
236
|
-
- **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
|
|
237
|
-
- **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.
|
|
238
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.
|
|
239
256
|
|
|
240
257
|
Pass `module` or `loadModule` to supply the interface yourself (for tests), and
|
|
@@ -360,11 +377,90 @@ libraries per engine instance. pgcrypto resolves before that table and never
|
|
|
360
377
|
uses a slot. A `LOAD` of a new library after the table is full fails with
|
|
361
378
|
`dlopen table is full` and leaves the session usable.
|
|
362
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
|
+
|
|
363
457
|
## Persistence and ownership
|
|
364
458
|
|
|
365
459
|
For an `open("/data/orders", ...)` call:
|
|
366
460
|
|
|
367
|
-
- `/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)).
|
|
368
464
|
- `/data/orders/runtime/` contains bundled initialization/support files.
|
|
369
465
|
- Bootstrap and shared-memory scratch files also stay beneath `/data/orders`.
|
|
370
466
|
|
|
@@ -440,7 +536,7 @@ for both the export and the import. PostgreSQL 18 is tracked in #401.
|
|
|
440
536
|
- `js/wit.d.ts`: generated TypeScript view of the `database` interface.
|
|
441
537
|
- `src/lib.rs`: public WIT resources and connection lifecycle.
|
|
442
538
|
- `src/pgwire.rs`: PostgreSQL message encoding and typed rows, over memory.
|
|
443
|
-
- `src/engine.rs`: persistent filesystem setup
|
|
539
|
+
- `src/engine.rs`: persistent filesystem setup, exclusive engine access, and the application database choice (marker, crash windows).
|
|
444
540
|
- `engine/bridge.wat`: private canonical ABI bridge to the core engine.
|
|
445
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.
|
|
446
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.
|
|
@@ -448,5 +544,6 @@ for both the export and the import. PostgreSQL 18 is tracked in #401.
|
|
|
448
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.
|
|
449
545
|
- `scripts/prepare-engine.ts`: pinned engine preparation and componentization (all build scripts are Bun TypeScript; see `scripts/`).
|
|
450
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.
|
|
451
548
|
|
|
452
549
|
Engine provenance and modifications are described in `engine/NOTICE`.
|
package/dist/BUILD-INFO.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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)
|
|
@@ -13,4 +13,4 @@ engine-patch: engine/patches/postgresql-17.11-wasi.patch (sha256 6beb64a561ca
|
|
|
13
13
|
engine-recipe: https://github.com/moznion/wasipg/tree/fa290be67946c0d3c4540bd1400b11e193d874e1
|
|
14
14
|
adapter-sha256: e51dd61669a20daca34befe7fdadf1ac84e4795a2c84a8400818ee1de5373ccc
|
|
15
15
|
engine-sha256: eb374344cff24c4cedacdad05a636a3b75b5eb1c4e55aa1a3ff9930be2ebc328
|
|
16
|
-
size:
|
|
16
|
+
size: 20176453 bytes
|
package/dist/SHA256SUMS
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
04cea641db9ac90a954fefd7783100175ce15bb2cc2e6fc47660fb4090eb2663 di-framework-pglite.wasm
|
|
2
|
+
fcce3740d3bc8501368fa6c4686eb626a1e6d4d15d017817de273dfd77afcab9 di-framework-pglite.wit
|
|
3
|
+
c3744f0613035d64fc6120bb9404a56e5759761e7cd64c55feeadd74b9727ad0 BUILD-INFO.txt
|
|
4
4
|
443fd294671eb64972556656d3ee871e48448ad1169c012d18aa868568f628c1 THIRD-PARTY-NOTICES.txt
|
|
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.
|
|
@@ -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
|
}
|