@mason-data/cli 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -73
- package/dist/src/api/error.js +3 -0
- package/dist/src/api/postgres/catalog-worker.js +49 -2
- package/dist/src/api/postgres/catalog.js +368 -25
- package/dist/src/api/postgres/encode.js +3 -1
- package/dist/src/api/postgres/explain.js +3 -2
- package/dist/src/api/postgres/listener.js +11 -3
- package/dist/src/api/postgres/protocol.js +39 -23
- package/dist/src/api/postgres/proxy.js +283 -0
- package/dist/src/api/postgres/schema.js +6 -0
- package/dist/src/api/postgres/server.js +1617 -245
- package/dist/src/api/postgres/translate.js +127 -10
- package/dist/src/api/request-log.js +18 -5
- package/dist/src/cdc/poll.js +5 -5
- package/dist/src/cli/background.js +47 -25
- package/dist/src/cli/bridge-stdin.js +101 -0
- package/dist/src/cli/cancel.js +75 -0
- package/dist/src/cli/command.js +170 -11
- package/dist/src/cli/commands.js +804 -209
- package/dist/src/cli/control.js +59 -0
- package/dist/src/cli/credentials.js +182 -0
- package/dist/src/cli/demo.js +20 -11
- package/dist/src/cli/deploy.js +574 -0
- package/dist/src/cli/dev.js +491 -130
- package/dist/src/cli/doctor.js +131 -38
- package/dist/src/cli/generated-files.js +143 -0
- package/dist/src/cli/guides.js +175 -36
- package/dist/src/cli/hosted.js +850 -0
- package/dist/src/cli/init.js +162 -65
- package/dist/src/cli/login.js +289 -0
- package/dist/src/cli/machine.js +13 -12
- package/dist/src/cli/main.js +11 -5
- package/dist/src/cli/mason.cjs +17 -0
- package/dist/src/cli/mcp.js +31 -22
- package/dist/src/cli/metrics.js +79 -0
- package/dist/src/cli/output-gate.js +176 -0
- package/dist/src/cli/page-share.js +96 -0
- package/dist/src/cli/pg-tls.js +14 -4
- package/dist/src/cli/pg-url.js +4 -0
- package/dist/src/cli/pin.js +19 -14
- package/dist/src/cli/pool-size.cjs +85 -0
- package/dist/src/cli/ports.js +2 -0
- package/dist/src/cli/preflight.js +138 -46
- package/dist/src/cli/project.js +142 -4
- package/dist/src/cli/readiness.js +76 -6
- package/dist/src/cli/registry.js +65 -38
- package/dist/src/cli/replica-hosted.js +665 -0
- package/dist/src/cli/replica.js +76 -0
- package/dist/src/cli/replication-checks.js +7 -5
- package/dist/src/cli/replication-mode.js +105 -26
- package/dist/src/cli/request-filter.js +134 -0
- package/dist/src/cli/roles.js +585 -0
- package/dist/src/cli/run-server.js +637 -48
- package/dist/src/cli/runs.js +1 -1
- package/dist/src/cli/security.js +67 -18
- package/dist/src/cli/serve.js +204 -46
- package/dist/src/cli/share-run.js +329 -0
- package/dist/src/cli/share.js +286 -0
- package/dist/src/cli/source-edit.js +75 -17
- package/dist/src/cli/source-role.js +344 -0
- package/dist/src/cli/sources.js +306 -47
- package/dist/src/cli/style.js +2 -1
- package/dist/src/cli/supervise.js +27 -24
- package/dist/src/cli/sync.js +55 -25
- package/dist/src/cli/token-hosted.js +337 -0
- package/dist/src/cli/tunnel.js +200 -0
- package/dist/src/cli/ui-ssh.js +460 -0
- package/dist/src/cli/ui.js +124 -17
- package/dist/src/cli/usage.js +128 -0
- package/dist/src/cli/user-hosted.js +299 -0
- package/dist/src/cli/user.js +573 -65
- package/dist/src/cli/version.js +9 -7
- package/dist/src/demo/seed.js +29 -0
- package/dist/src/duck/analyze.js +36 -20
- package/dist/src/duck/cancel.js +10 -1
- package/dist/src/duck/collation.js +266 -0
- package/dist/src/duck/constant-key.js +94 -0
- package/dist/src/duck/distinct-key.js +283 -26
- package/dist/src/duck/events.js +165 -0
- package/dist/src/duck/executor-worker.js +20 -11
- package/dist/src/duck/executor.js +47 -33
- package/dist/src/duck/explain.js +60 -46
- package/dist/src/duck/extensions.js +160 -0
- package/dist/src/duck/functions.js +461 -63
- package/dist/src/duck/gaps.js +48 -21
- package/dist/src/duck/generated-plan.js +1070 -0
- package/dist/src/duck/generated.js +906 -0
- package/dist/src/duck/grants.js +176 -0
- package/dist/src/duck/held-types.js +64 -0
- package/dist/src/duck/history.js +127 -0
- package/dist/src/duck/jsonb-order.js +9 -12
- package/dist/src/duck/kept-route.js +6 -5
- package/dist/src/duck/kept.js +15 -13
- package/dist/src/duck/late.js +1469 -40
- package/dist/src/duck/libc-bound.js +341 -0
- package/dist/src/duck/libc-order.js +892 -126
- package/dist/src/duck/pg-eval-worker.js +7 -1
- package/dist/src/duck/pg-eval.js +6 -4
- package/dist/src/duck/pgtree.js +774 -23
- package/dist/src/duck/plan.js +369 -47
- package/dist/src/duck/publication.js +20 -6
- package/dist/src/duck/requests.js +269 -93
- package/dist/src/duck/resolved.js +117 -2
- package/dist/src/duck/rewrite.js +4584 -580
- package/dist/src/duck/row-filter.js +655 -0
- package/dist/src/duck/statement.js +19 -0
- package/dist/src/duck/store.js +439 -177
- package/dist/src/duck/stream.js +19 -10
- package/dist/src/duck/sync.js +1411 -368
- package/dist/src/duck/token.js +4 -1
- package/dist/src/duck/types.js +69 -12
- package/dist/src/duck/usage.js +326 -0
- package/dist/src/duck/users.js +69 -15
- package/dist/src/duck/verified.js +161 -1
- package/dist/src/duck/views.js +5 -5
- package/dist/src/lib/database-name.js +62 -0
- package/dist/src/lib/database-url.js +55 -10
- package/dist/src/lib/dotenv.js +17 -4
- package/dist/src/lib/instant.js +50 -0
- package/dist/src/lib/log-buffer.js +77 -0
- package/dist/src/lib/log-read.js +196 -0
- package/dist/src/lib/pg-numeric.js +390 -49
- package/dist/src/lib/project-version.js +23 -23
- package/dist/src/lib/secret-mask.js +142 -0
- package/dist/src/lib/table-sync.js +282 -0
- package/dist/src/web/app.css +706 -66
- package/dist/src/web/app.js +331 -409
- package/dist/src/web/charts.js +229 -0
- package/dist/src/web/index.html +82 -18
- package/dist/src/web/pages/queries.js +192 -0
- package/dist/src/web/pages/status.js +611 -0
- package/dist/src/web/pages/syncs.js +155 -0
- package/dist/src/web/pages/tables.js +439 -0
- package/dist/src/web/pages/usage.js +397 -0
- package/dist/src/web/pages/users.js +219 -0
- package/dist/src/web/shared.js +584 -0
- package/dist/src/web/vendor/README.md +25 -0
- package/dist/src/web/vendor/geist-1.7.2/Geist-Variable.woff2 +0 -0
- package/dist/src/web/vendor/geist-1.7.2/GeistMono-Variable.woff2 +0 -0
- package/dist/src/web/vendor/geist-1.7.2/LICENSE.txt +92 -0
- package/dist/src/web/vendor/lucide-1.56.0/LICENSE.txt +43 -0
- package/dist/src/web/vendor/lucide-1.56.0/chart-column.mjs +29 -0
- package/dist/src/web/vendor/lucide-1.56.0/layout-dashboard.mjs +29 -0
- package/dist/src/web/vendor/lucide-1.56.0/logs.mjs +34 -0
- package/dist/src/web/vendor/lucide-1.56.0/menu.mjs +28 -0
- package/dist/src/web/vendor/lucide-1.56.0/square-terminal.mjs +28 -0
- package/dist/src/web/vendor/lucide-1.56.0/table.mjs +29 -0
- package/dist/src/web/vendor/lucide-1.56.0/users.mjs +29 -0
- package/dist/src/web/vendor/lucide-1.56.0/x.mjs +27 -0
- package/dist/src/web/vendor/uplot-1.6.32/LICENSE.txt +21 -0
- package/dist/src/web/vendor/uplot-1.6.32/uPlot.esm.js +6140 -0
- package/dist/src/web/vendor/uplot-1.6.32/uPlot.min.css +1 -0
- package/dist/src/web/vendor/zilla-slab-5.3.0/LICENSE.txt +93 -0
- package/dist/src/web/vendor/zilla-slab-5.3.0/zilla-slab-latin-700-normal.woff2 +0 -0
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -1,60 +1,66 @@
|
|
|
1
1
|
# @mason-data/cli
|
|
2
2
|
|
|
3
|
-
The `mason` command points [Mason](https://
|
|
4
|
-
add, keeps
|
|
3
|
+
The `mason` command points [Mason](https://github.com/embeddable-hq/mason-data) at the Postgres your app already uses, syncs the tables you
|
|
4
|
+
add, keeps them up to date, and serves them to BI tools and your app over the Postgres protocol. Every command works the
|
|
5
5
|
same for you and for a coding agent (Claude Code, Codex, …): it prints for a person, or with `--json` as data, and its
|
|
6
6
|
exit code says how it went ([for coding agents](#for-coding-agents)).
|
|
7
7
|
|
|
8
8
|
There is nothing to clone or build: install this package and run `mason`. It needs Node 22.13 or later and nothing
|
|
9
|
-
else — no Docker, no database of its own
|
|
9
|
+
else — no Docker, no database of its own, and nothing downloaded after `npm install`: Mason's postgres and inet
|
|
10
|
+
extensions come with it, as an optional dependency for your platform (`@mason-data/extensions-<platform>`, 17–31 MiB
|
|
11
|
+
to download; installing with `--omit=optional` leaves them out, and the first sync then downloads them).
|
|
12
|
+
`mason doctor` checks this machine and says what is missing. On a terminal
|
|
10
13
|
its output is coloured and wrapped to the window, and a command that takes a moment shows a spinner; piped,
|
|
11
14
|
redirected, with `--json` or with `NO_COLOR` set, it is plain text.
|
|
12
15
|
|
|
13
16
|
**A Mason project is your app's folder.** `mason init` makes it one: its sources file in `mason/sources/` — the
|
|
14
|
-
database and the tables to
|
|
17
|
+
database and the tables to sync — yours to commit; this machine's synced data and Mason's state in `.mason/`,
|
|
15
18
|
both rebuilt from that file. Every other command works
|
|
16
|
-
anywhere in the folder, as git does. `mason destroy` removes the
|
|
19
|
+
anywhere in the folder, as git does. `mason destroy` removes the synced data and the state. ([Cleaning up](#cleaning-up))
|
|
17
20
|
|
|
18
21
|
```sh
|
|
19
22
|
npm i -g @mason-data/cli
|
|
20
23
|
cd my-app
|
|
21
24
|
mason doctor # Node, memory, ports: what is missing, and what to do
|
|
22
|
-
mason init # this folder becomes a project, on the database
|
|
25
|
+
mason init # this folder becomes a project, on the database .env names (checked first)
|
|
23
26
|
mason tables # the tables: rows, keys, added; a table's columns with `mason tables orders`
|
|
24
27
|
mason add orders customers # add them to the sources file (--columns id,status: those only)
|
|
25
|
-
mason start # Mason in the background,
|
|
26
|
-
mason wait # until every added table is
|
|
28
|
+
mason start # Mason in the background, syncing them, then returns
|
|
29
|
+
mason wait # until every added table is synced and served
|
|
27
30
|
mason user add metabase # a user for a BI tool: its password, printed once, and the URL to paste
|
|
28
|
-
mason ui # the status
|
|
31
|
+
mason ui # the status, tables, users and statements, in your browser
|
|
29
32
|
```
|
|
30
33
|
|
|
31
34
|
`mason init` takes the database's URL as an argument too (`mason init postgres://user:password@localhost:5432/your_app`);
|
|
32
|
-
without one it reads `DATABASE_URL
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
database
|
|
35
|
+
without one it reads `MASON_DATABASE_URL`, else `DATABASE_URL`, each from the folder's `.env.local`, `.env` or the
|
|
36
|
+
shell — set `MASON_DATABASE_URL` to aim Mason at a database other than the one your app's `DATABASE_URL` names. The
|
|
37
|
+
sources file it writes names the variable it read, `url: ${DATABASE_URL}` or `url: ${MASON_DATABASE_URL}` (given a URL
|
|
38
|
+
as an argument, `MASON_DATABASE_URL` when your app's `DATABASE_URL` names another database), and the URL itself stays
|
|
39
|
+
in `.env` (init writes the line there when you gave the URL as an argument or it was only in the shell, and gitignores
|
|
40
|
+
that file): change it there when the password changes. The database is required.
|
|
36
41
|
|
|
37
42
|
**No database at hand?** `mason init demo && mason start`, in a new folder, makes a project on a demo database Mason
|
|
38
43
|
hosts itself — a small shop (`customers`, `products`, `orders`, `order_items`; the first three added, `order_items`
|
|
39
44
|
left for your first `mason add order_items` after `mason start`), kept in `.mason/demo/`, served on a port of this machine, with a new order every few seconds (`MASON_DEMO_CHANGE_MS`, default 5000) so
|
|
40
|
-
`mason status` shows the
|
|
41
|
-
`mason destroy` removes it with the
|
|
45
|
+
`mason status` shows the synced data keeping up. Nothing to install. It is read by polling only (it has no change log), and
|
|
46
|
+
`mason destroy` removes it with the synced data.
|
|
42
47
|
|
|
43
48
|
## The project: its sources file, in your app
|
|
44
49
|
|
|
45
50
|
```
|
|
46
51
|
my-app/
|
|
47
|
-
.env the database's URL (DATABASE_URL
|
|
48
|
-
.mason/ this machine's: Mason's key, the
|
|
49
|
-
mason/sources/app.yml the database and the tables Mason
|
|
52
|
+
.env the database's URL (DATABASE_URL, as your app has it, or MASON_DATABASE_URL) (gitignored)
|
|
53
|
+
.mason/ this machine's: Mason's key, the synced data, its state and logs (gitignored)
|
|
54
|
+
mason/sources/app.yml the database and the tables Mason syncs — commit it
|
|
50
55
|
mason/mason.yml the Mason version the project is written for — commit it: production runs that version
|
|
51
56
|
```
|
|
52
57
|
|
|
53
|
-
The sources file is named for the database (`app.yml` above) and lists what Mason
|
|
58
|
+
The sources file is named for the database (`app.yml` above) and lists what Mason syncs, one entry per table:
|
|
54
59
|
|
|
55
60
|
```yaml
|
|
56
61
|
kind: postgres
|
|
57
62
|
url: ${DATABASE_URL} # a variable from .env.local, .env or the shell; a literal URL is allowed for local dev
|
|
63
|
+
database: production # optional: the database name a tool connects with (none: mason)
|
|
58
64
|
tables:
|
|
59
65
|
orders: # a bare name is in `public`; `billing.invoices` names its schema
|
|
60
66
|
columns: [id, customer_id, status, amount, created_at, payload]
|
|
@@ -62,12 +68,14 @@ tables:
|
|
|
62
68
|
events:
|
|
63
69
|
exclude: [raw_body] # every column but these
|
|
64
70
|
sessions:
|
|
65
|
-
key: [tenant_id, id] # only for a table with no primary key: the unique columns to
|
|
71
|
+
key: [tenant_id, id] # only for a table with no primary key: the unique columns to key it by
|
|
66
72
|
```
|
|
67
73
|
|
|
68
|
-
- An entry is `{}` (every column), `columns:` (those; the primary key is always
|
|
74
|
+
- An entry is `{}` (every column), `columns:` (those; the primary key is always synced), `exclude:` (every column but
|
|
69
75
|
those) — never both — and, for a table with no primary key, `key:`: the columns that tell its rows apart, which it is
|
|
70
|
-
|
|
76
|
+
keyed by (polling only: logical replication cannot apply a change by them). A table with a primary key refuses `key:`.
|
|
77
|
+
- A tool connects with the database name `mason`. A tool moving from your Postgres changes its host, port and database
|
|
78
|
+
name, or `database:` keeps the name it has, so it changes only its host and port.
|
|
71
79
|
- Column types are not in the file: Mason reads them from the database at every start.
|
|
72
80
|
- `url:` is `${NAME}`, read from `.env.local`, `.env` or the shell — or a literal URL, for a database on your machine.
|
|
73
81
|
- One source per project for now, and `postgres` is its only kind.
|
|
@@ -77,11 +85,11 @@ tables:
|
|
|
77
85
|
mistakes on one report, each with its key and the way out (`did you mean …?`).
|
|
78
86
|
|
|
79
87
|
`mason init --folder <dir>` puts `sources/` elsewhere in the folder (`--folder .` for the folder itself). Keep it in
|
|
80
|
-
your app's repo, so a schema change and the tables that
|
|
88
|
+
your app's repo, so a schema change and the tables that sync it travel together. A teammate who clones the repo runs
|
|
81
89
|
`mason init` once: the sources file is kept, and `.mason/` is per machine.
|
|
82
90
|
|
|
83
91
|
**A project an older Mason made** (its tables listed in `mason/models/`) is refused by `mason start`; nothing is
|
|
84
|
-
migrated. `mason init` in it writes the sources file alone — its `.mason/` and
|
|
92
|
+
migrated. `mason init` in it writes the sources file alone — its `.mason/` and synced data kept — then `mason add` each table
|
|
85
93
|
again and remove `mason/models/`.
|
|
86
94
|
|
|
87
95
|
## Commands
|
|
@@ -89,22 +97,27 @@ again and remove `mason/models/`.
|
|
|
89
97
|
| Command | What it does |
|
|
90
98
|
|---|---|
|
|
91
99
|
| `mason help [<command> \| <guide>]` | The commands by step, one command's usage, or one of the guides, offline: `mason help agent` (driving Mason from a coding agent: the commands in order, `--json` and its shapes, exit codes, failures) and `mason help production` (the road from a laptop to production: the image, its settings, Docker Compose, logical replication, upgrades). |
|
|
92
|
-
| `mason doctor` | Checks what Mason needs here — Node 22.13+, memory, Mason's ports — and in a project's folder its database; with Mason running for it, the run itself: whether every added table is
|
|
93
|
-
| `mason init [<postgres url>]` | Makes this folder a project: checks it can read the database (nothing is written when it cannot; `--skip-check` writes anyway), writes `.mason/config.json` (Mason's key, never the URL) and `mason/sources/<database>.yml` (keeping a sources file already there), adds the URL to `.env` when it was not there, and adds `.mason/` and that `.env` to `.gitignore`. The URL from the argument, else `DATABASE_URL
|
|
94
|
-
| `mason start` | Starts Mason for this folder's project in the background and returns once it is up. Run it again any time: it says so when Mason is already running.
|
|
100
|
+
| `mason doctor` | Checks what Mason needs here — Node 22.13+, memory, Mason's ports — and in a project's folder its database; with Mason running for it, the run itself: whether every added table is synced, the statements refused or slow in the last day. Every failing line says what to do; it ends with the next command. |
|
|
101
|
+
| `mason init [<postgres url>]` | Makes this folder a project: checks it can read the database (nothing is written when it cannot; `--skip-check` writes anyway), writes `.mason/config.json` (Mason's key, never the URL) and `mason/sources/<database>.yml` (keeping a sources file already there), adds the URL to `.env` when it was not there, and adds `.mason/` and that `.env` to `.gitignore`. The URL from the argument, else `MASON_DATABASE_URL`, else `DATABASE_URL`, each in `.env.local`, `.env` or the shell (the sources file names the one read). `--name` sets the project's name (default: the folder's), which its replication slot is named for; `--database` the database name a tool connects with (default `mason`, none written); `--folder` where its files go, `--remote` allows a database on another machine. Refuses a folder already in a project, but for one with no sources file (an older Mason's), whose sources file alone it writes. |
|
|
102
|
+
| `mason start` | Starts Mason for this folder's project in the background and returns once it is up. Run it again any time: it says so when Mason is already running. Each table is kept current as its `sync:` in the sources file says (`off`, `poll <interval>` or `replication`). `--self-signed-tls` serves the Postgres API over TLS; `--attach` stays in the foreground. |
|
|
95
103
|
| `mason tables [<table>…] [--all]` | The tables this user can read against the sources file, one line each: rows, primary key, whether added (and with which columns), and why a table cannot be added yet (no primary key: the `--key` to add it by). Name a table (or pass `--all` for every one) for its columns: each one's type, what it references, and whether `mason add` would leave it out as a secret. It reads the project's database itself, so Mason need not be running. |
|
|
96
|
-
| `mason add <table>… [--columns …] [--include …] [--key …]` |
|
|
97
|
-
| `mason remove <table>…` | Takes tables out of the sources file — each one's entry, every other byte as it was — and Mason drops their
|
|
98
|
-
| `mason wait [--timeout <s>]` | Waits until every added table is
|
|
104
|
+
| `mason add <table>… [--columns …] [--include …] [--key …] [--sync …]` | Syncs tables into Mason, each one an entry of the sources file: every column (`orders: {}`), or `--columns id,status,created_at` for those (one table at a time; the key is written with them). Columns that look like secrets (`password`, `token`, `api_key`, `private_key`, …) are left out, named, and written as `exclude:`; `--include password_hash` adds one. `--key tenant_id,id` adds a table with no primary key, keyed by those columns. `--sync "poll 30s"` (or `off`) writes how it is kept current after its first sync on its entry; on a table already added, `--sync` alone changes that line and nothing else. Without it the entry follows the file's `sync:`, but a table over 1,000,000 rows under a file that polls is written `sync: off` and said, and a frequent poll of tens of millions of rows is warned of with an estimate of its cost. Adding a table again with other columns changes its entry. Mason need not be running: the entry is checked against the database as the run checks it, and the first sync begins at `mason start` (with Mason running, at once). |
|
|
105
|
+
| `mason remove <table>…` | Takes tables out of the sources file — each one's entry, every other byte as it was — and Mason drops their synced data: at once when it is running, else at the next `mason start`. Mason need not be running. |
|
|
106
|
+
| `mason wait [--timeout <s>]` | Waits until every added table is synced and served, showing every added table on a line of its own: 100% for the finished ones, in the order they finished, and a bar for each still syncing (rows, %, rate, time left) at the bottom. Exit 0 ready, 1 a first sync failed (printed, with what to do), 124 timed out (default 600 s). |
|
|
99
107
|
| `mason user add\|list\|remove <name>` | The users of the Postgres API: a name and a password that does not expire, as a BI tool's datasource form asks for them or an app's `DATABASE_URL` carries them. `add` prints the password once with the URL; `remove` refuses the next connection at once and ends the sessions it holds at their next statement, without a restart. |
|
|
100
|
-
| `mason mcp [--url <url>]` | An MCP server on stdio for Claude Code, Claude Desktop and other agents (below): the tables Mason serves with each
|
|
101
|
-
| `mason ui` | Opens the running Mason's web page in your browser: what `mason status` says,
|
|
102
|
-
| `mason status` | What Mason is doing: the database and how its changes are read, the project folder, the
|
|
103
|
-
| `mason sync <table>…` |
|
|
104
|
-
| `mason logs [-n <lines>] [-f] [--start] [--requests [--failed]]` | Mason's log: every
|
|
105
|
-
| `mason
|
|
106
|
-
| `mason
|
|
107
|
-
| `mason
|
|
108
|
+
| `mason mcp [--url <url>]` | An MCP server on stdio for Claude Code, Claude Desktop and other agents (below): the tables Mason serves with each one's rows and how current it is, a table's columns, a statement's rows with their Postgres types or Mason's refusal with its way out, and EXPLAIN — connected as a `mason user` (`MASON_URL`, else `--url`), beside `mason start` or a `mason serve` elsewhere. |
|
|
109
|
+
| `mason ui` | Opens the running Mason's web page in your browser: the Overview (what `mason status` says), the Tables (add, remove or sync one), the Users of `mason user` (add one, its password shown once; disable, enable or remove one) and Observability (the statements, usage over time, the sync's events), refreshed while the tab is open. It prints the URL with the run's token, which the page trades for a cookie on the first visit; `--no-open` prints it only, `--json` is `{ url, port }`. `mason start`'s alone: `mason serve` serves no page. |
|
|
110
|
+
| `mason status` | What Mason is doing: the database and how its changes are read, the project folder, the synced data's file, the Postgres API and how it listens, each synced table, the statements of the last day and week (each outcome, the completed ones' average, p50 and p95 time, the rows and bytes sent), the ones answered slowly in the last day and the ones refused or failed, each with its reason. In replication mode, where the stream is. |
|
|
111
|
+
| `mason sync <table>…` | Brings these synced tables up to date now, by one poll each: a table whose `sync:` is `off` is kept current only so (from cron, before a report), a polled one is polled now as well as at its interval. In replication mode the stream keeps every table current, and it says where the stream is. |
|
|
112
|
+
| `mason logs [-n <lines>] [-f] [--start] [--requests [<filters>]] [--events [--failed]] [--running]` | Mason's log: every sync, poll and statement, with warnings and errors and their reasons; `-f` follows it, `--start` shows what `mason start` printed instead. `--requests` is the request log: every statement a tool sent, when, the outcome (ok, refused, failed, cancelled) and SQLSTATE, how long, the rows and bytes sent, the tool and the user; `--failed` only the ones not answered, `--outcome`, `--since` / `--until` (`2h`, `7d`, an ISO time), `--user` and `--client` narrow it, and `--before` reads the page before. `--events` is the sync's events: each first sync, poll, catch-up or stream that failed, the database out of reach, a table current again, each sync asked by hand (mason sync, the page's Sync now), and each restart of the run (`--failed` only the failures). `--running` lists the statements running now, by session. |
|
|
113
|
+
| `mason cancel <session>` | Stops the statement a session of the Postgres API is running, as the client's own cancel would: the client is told 57014, its session goes on, and the request log keeps it as cancelled. `mason logs --running` lists the sessions. |
|
|
114
|
+
| `mason usage [--since 7d] [--step 1h]` | What Mason did over time, from a sample a minute kept `MASON_HISTORY_DAYS` days: the statements and their p95, the rows and bytes sent, the synced data's size and rows, the stalest table, the stream's lag, the memory; then each synced table's own size. |
|
|
115
|
+
| `mason stop` | Stops Mason for this project. The synced data and the state stay: the next `mason start` resumes in seconds. |
|
|
116
|
+
| `mason destroy` | Removes this project's synced data (its file) and Mason's state in `.mason/`, stopping Mason first if it runs — and, when a table was replicated, its slot (and its publication, when Mason made it) on the database. Your files, `.mason/config.json` and the users stay: the next `mason start` syncs the tables again. |
|
|
117
|
+
| `mason serve` | Mason for a server or a container: `mason start --attach` configured by the environment alone, its state in one folder (a volume), its log on stdout, `GET /healthz`, `GET /metrics` (Prometheus, numbers only), a clean stop on SIGTERM. `mason serve --help` lists every setting with its default; `MASON_PROJECT_READONLY=1`, for a project folder deployed as committed, refuses every edit of its files there (`mason add`, `mason remove`, the page's Add and Remove), with "edit the files and deploy them". |
|
|
118
|
+
| `mason login` | Signs this machine in to a hosted Mason service (`MASON_CLOUD_URL`, else `https://app.masondata.ai`): it prints a page and a code (on stderr, at once, so a coding agent can pass them on), opens the page in a browser on a terminal, waits until the code is approved, then says who is signed in and their organisations. The login is kept in `~/.config/mason/credentials.json` (0600), never in a project; refused while `MASON_TOKEN` is set, which every command uses first. [`docs/HOSTED-API.md`](../docs/HOSTED-API.md) is the API. |
|
|
119
|
+
| `mason logout` | Revokes this machine's login to that service and removes it. When the service cannot be reached, the login is still removed, and it says it was NOT revoked and how to revoke it. |
|
|
120
|
+
| `mason deploy [--replica <org>/<name>] [--dry-run] [--timeout 10m]` | Sends this project's files (the YAML under `mason/`: never `.env` or `.mason/`) to a replica on the hosted service (`--replica`, else `MASON_REPLICA`), after checking the project as `mason start` does, without connecting to any database, then waits until the replica runs it (exit 0), Mason there refuses it (its failure printed as it came, exit 1), or the timeout (exit 124: the deploy goes on, its id said). `--dry-run` prints each file, its size and its sha256, and sends nothing. The login is `MASON_TOKEN`, else `mason login`'s. |
|
|
108
121
|
|
|
109
122
|
Every command documents itself: `mason --help` lists them, and `mason <command> --help` (or `mason help <command>`) says
|
|
110
123
|
how to use one, with examples. Every command but `help` and `mcp` (whose stdout is the MCP protocol's) takes `--json`. A command means the project of the folder it
|
|
@@ -112,47 +125,56 @@ runs in, or the nearest one above it.
|
|
|
112
125
|
|
|
113
126
|
## What `mason start` does
|
|
114
127
|
|
|
115
|
-
1. **It only reads the database** — the one the sources file names
|
|
116
|
-
|
|
117
|
-
them. When you stop it, nothing is left behind on the database.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
is
|
|
128
|
+
1. **It only reads the database** — the one the sources file names, with `SELECT` statements. It does not need
|
|
129
|
+
`wal_level = logical`, a replication slot, a publication or the `REPLICATION` attribute, and it creates none of
|
|
130
|
+
them. When you stop it, nothing is left behind on the database. After its first sync each table is kept current as
|
|
131
|
+
the sources file's `sync:` says — at the top for every table, on an entry for that one: `off` (the default, and what
|
|
132
|
+
`mason init` writes) reads it again only at `mason sync <table>`; `poll 30s` polls it every 30 seconds, each poll a
|
|
133
|
+
scan of the table, the next one at least four times the last one's time later. Changing a table's `sync:` keeps its
|
|
134
|
+
synced data and catches it up by one poll. `mason status` says each table's sync and when it was last synced, on its line.
|
|
135
|
+
(`sync: replication` is the exception: that table is read from the change log, as production does, through one slot
|
|
136
|
+
Mason makes and a publication carrying the replicated tables alone — its own, or the one `publication:` at the top
|
|
137
|
+
of the file names, never altered. The slot is dropped, verified gone, once no table is replicated.)
|
|
138
|
+
2. **It syncs each added table** into a DuckDB file in `.mason/`, a range of pages at a time, a few in parallel, one
|
|
139
|
+
Postgres backend each, smallest table first; a first sync that stops continues with the ranges it did not commit. The table
|
|
140
|
+
is served the moment its first sync lands; it runs beside the polls, never instead of them.
|
|
123
141
|
3. **It serves the Postgres API** on 5433, or on the next port free on every address when something holds 5433 (often a
|
|
124
142
|
second Postgres; `mason start` says what held it) — and records it: every later start listens on the same port, so a
|
|
125
143
|
tool's saved connection keeps working, and refuses, naming what holds it, rather than move. `MASON_PG_PORT=55433` in
|
|
126
144
|
your shell names the port, or moves the project to it (said). It listens on 127.0.0.1 unless `MASON_PG_HOST` names
|
|
127
145
|
another address; off this machine it needs TLS (`--self-signed-tls`, or `MASON_PG_TLS_CERT` / `MASON_PG_TLS_KEY`).
|
|
128
|
-
4. **It runs in the background**,
|
|
146
|
+
4. **It runs in the background**, syncing what the sources file names: `mason add` and `mason remove` change it at once,
|
|
129
147
|
a hand edit (or an add or remove made while Mason was stopped) at the next `mason start`.
|
|
130
148
|
|
|
131
|
-
Mason's own settings (`
|
|
132
|
-
|
|
149
|
+
Mason's own settings (`MASON_DUCKDB_MEMORY`, `MASON_COPY_PARALLEL`, …) set in your shell reach Mason. How often a
|
|
150
|
+
table is polled is its `sync:`, never a setting: `MASON_POLL_MS` set refuses the start, naming the line to write.
|
|
151
|
+
A first sync reads `MASON_COPY_PARALLEL` chunks at once (default 3), each sized to take about `MASON_COPY_CHUNK_SECONDS` to
|
|
133
152
|
read (default 12) at the rate it measures on your connection, so `mason wait` moves every few seconds even over a slow
|
|
134
|
-
link, and a
|
|
135
|
-
A JSON value your queries keep reading (`payload ->> 'customer_id'`) is stored as a column of the
|
|
153
|
+
link, and a first sync that stops reads at most those chunks again.
|
|
154
|
+
A JSON value your queries keep reading (`payload ->> 'customer_id'`) is stored as a column of the synced table and read from
|
|
136
155
|
there (`mason status` lists them); `MASON_STORE_JSON_PATHS=off` turns that off.
|
|
137
156
|
|
|
138
157
|
## `mason start` options
|
|
139
158
|
|
|
140
159
|
| Option | What it does |
|
|
141
160
|
|---|---|
|
|
142
|
-
| `--replication` | Read the database's changes from its change log (logical replication), as production does, instead of polling. It needs `wal_level = logical` and a user that may replicate, and makes its own publication and slot (`mason_dev_<project>_<source>`, the source the sources file's name; past 63 bytes, a prefix and a hash) on the database. The project keeps the mode until `--poll`; a switch keeps the copies and catches each table up by one poll. `mason stop` leaves the slot (it holds WAL until the next start); `mason destroy` and `--poll` drop it. |
|
|
143
|
-
| `--publication <name>` | With `--replication`: a publication the database owner made (else `PG_PUBLICATION`), read as it is, verified to carry every added table and column, never created, altered or dropped. A read replica needs one. |
|
|
144
|
-
| `--poll` | Read them with `SELECT` again (the default). |
|
|
145
161
|
| `--self-signed-tls` | The Postgres API requires TLS, from a certificate made for this project in `.mason/` (served again by every later start with the flag); clients connect with `sslmode=require`. |
|
|
146
162
|
| `--port <n>` | The port the `mason` commands reach it on (default 4039, or the next free one). |
|
|
147
163
|
| `--attach` | Stay in the foreground: the terminal shows Mason's progress, and Ctrl-C stops it. |
|
|
148
164
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
165
|
+
`--replication`, `--poll` and `--publication` are gone: each answers with the line to write at the top of the sources
|
|
166
|
+
file (`sync: replication`, `sync: poll 5m`, `publication: <name>`) and exit 2, and starts nothing. A project that
|
|
167
|
+
streams, with no `sync:` anywhere in its file, is refused with its slot kept: `sync: replication` keeps streaming,
|
|
168
|
+
`sync: off` or `sync: poll <interval>` confirms the drop. One with a recorded publication needs `publication: <name>`,
|
|
169
|
+
or `publication: null` for Mason's own.
|
|
152
170
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
171
|
+
A database on another machine needs `mason init --remote`, which records that you mean it: the synced data leaves the
|
|
172
|
+
database's network, and each polled table is read whole at its `sync:` interval (`sync: replication` reads only its
|
|
173
|
+
changes). Point it at a read replica, never the primary your app writes to.
|
|
174
|
+
|
|
175
|
+
**When the database moves.** Mason records the Postgres server it synced from. The same database on a new server (a
|
|
176
|
+
restore, a move to another host) is read again at the next `mason start`: every row is compared with the synced data, and only
|
|
177
|
+
what changed is written. A different database is refused: `mason destroy`, then `mason start`, syncs it.
|
|
156
178
|
|
|
157
179
|
## For coding agents
|
|
158
180
|
|
|
@@ -188,10 +210,10 @@ and says `mason user add claude`.
|
|
|
188
210
|
|
|
189
211
|
| Tool | Arguments | What it answers |
|
|
190
212
|
|---|---|---|
|
|
191
|
-
| `tables` | none | each table Mason serves, named as a statement writes it (`public.orders`, `public."Clients"`): `rows_about` (the
|
|
213
|
+
| `tables` | none | each table Mason serves, named as a statement writes it (`public.orders`, `public."Clients"`): `rows_about` (the synced table's estimated row count), `kept` (how it is kept current, as `mason status` says it) and `kept_current_by` (`polling`, `logical replication`, `copying again`, or null when nothing keeps it current now) |
|
|
192
214
|
| `describe` | `table` (as `tables` names it, or a bare name in `public`) | its columns with their Postgres types and NOT NULL, its primary key, its foreign keys, comments, and the same rows and `kept` |
|
|
193
215
|
| `query` | `sql`, `max_rows` (200, at most 10,000), `timeout_ms` (30,000, at most 300,000) | `columns` (name, Postgres type), `rows` as Postgres's text, `truncated` when `max_rows` cut it; a refusal is a tool error with the SQLSTATE, message, DETAIL and HINT (the way out) |
|
|
194
|
-
| `explain` | `sql`, `analyze`, `timeout_ms` | Mason's EXPLAIN: each
|
|
216
|
+
| `explain` | `sql`, `analyze`, `timeout_ms` | Mason's EXPLAIN: each synced table read with its rows and how current it is, the SQL it runs, the plan |
|
|
195
217
|
|
|
196
218
|
Its resources are `mason://help/agent` and `mason://help/mcp` (the two pages `mason help` prints). Every statement it
|
|
197
219
|
runs is in the request log under `application_name` `mason-mcp/<the client's name>` (`mason logs --requests`), a query
|
|
@@ -203,9 +225,9 @@ comes before `--env`, which takes every value after it):
|
|
|
203
225
|
|
|
204
226
|
```sh
|
|
205
227
|
mason user add claude # the URL, printed once
|
|
206
|
-
claude mcp add mason --env MASON_URL="postgres://claude:<password>@127.0.0.1:5433/
|
|
228
|
+
claude mcp add mason --env MASON_URL="postgres://claude:<password>@127.0.0.1:5433/mason" -- mason mcp
|
|
207
229
|
# or on its command line, where ps shows it to this machine's users and claude mcp add prints it
|
|
208
|
-
claude mcp add mason -- mason mcp --url "postgres://claude:<password>@127.0.0.1:5433/
|
|
230
|
+
claude mcp add mason -- mason mcp --url "postgres://claude:<password>@127.0.0.1:5433/mason"
|
|
209
231
|
```
|
|
210
232
|
|
|
211
233
|
Claude Desktop, in `claude_desktop_config.json`. The app often starts a server without your shell's `PATH`, where
|
|
@@ -218,7 +240,7 @@ neither `mason` nor the `node` its first line asks for is found: name both by th
|
|
|
218
240
|
"mason": {
|
|
219
241
|
"command": "/absolute/path/to/node",
|
|
220
242
|
"args": ["/absolute/path/to/mason", "mcp"],
|
|
221
|
-
"env": { "MASON_URL": "postgres://claude:<password>@127.0.0.1:5433/
|
|
243
|
+
"env": { "MASON_URL": "postgres://claude:<password>@127.0.0.1:5433/mason" }
|
|
222
244
|
}
|
|
223
245
|
}
|
|
224
246
|
}
|
|
@@ -226,17 +248,17 @@ neither `mason` nor the `node` its first line asks for is found: name both by th
|
|
|
226
248
|
|
|
227
249
|
## Cleaning up
|
|
228
250
|
|
|
229
|
-
Three things exist once a project has run: your **files** (the sources file, in your app), the **
|
|
251
|
+
Three things exist once a project has run: your **files** (the sources file, in your app), the **synced data** (a
|
|
230
252
|
file in `.mason/`) and **Mason's state** (`.mason/`: its settings, logs and the request log). Each way of cleaning up
|
|
231
253
|
keeps a different set:
|
|
232
254
|
|
|
233
255
|
| You want | Run | Keeps |
|
|
234
256
|
|---|---|---|
|
|
235
257
|
| Pause | `mason stop` | the data, the state, your files: the next `mason start` resumes in seconds |
|
|
236
|
-
|
|
|
258
|
+
| Sync the data again, or free its disk | `mason destroy` | your files, `.mason/config.json` and the users: the next `mason start` syncs everything again |
|
|
237
259
|
|
|
238
260
|
Restarting the machine is a `mason stop` that nobody ran: the next `mason start` resumes. In a demo project
|
|
239
|
-
(`mason init demo`) the demo database is state too: `mason destroy` removes it with the
|
|
261
|
+
(`mason init demo`) the demo database is state too: `mason destroy` removes it with the synced data, and the next `mason start`
|
|
240
262
|
seeds it again.
|
|
241
263
|
|
|
242
264
|
## Requirements
|
|
@@ -244,9 +266,15 @@ seeds it again.
|
|
|
244
266
|
`mason doctor` checks all of this and says what is missing.
|
|
245
267
|
|
|
246
268
|
- Node 22.13 or later. The SQL parser (`libpg-query`), the catalog (`PGlite`) and the engine (DuckDB) ship as
|
|
247
|
-
WebAssembly or prebuilt binaries: no native build, no Python or compiler needed.
|
|
248
|
-
|
|
249
|
-
|
|
269
|
+
WebAssembly or prebuilt binaries: no native build, no Python or compiler needed. Mason's CI and the `mason-data`
|
|
270
|
+
image run Node 26.
|
|
271
|
+
- On Node 24 Mason runs, but a bug in Node 24's V8 (nodejs/node#66366, fixed in Node 26) can now and then abort it
|
|
272
|
+
with `Check failed: jit_page_->allocations_.erase(addr) == 1`, as a process exits or when the catalog's worker stops
|
|
273
|
+
after idling. `mason start` starts a run that died after it was ready again (not under `--attach`), and `mason status`
|
|
274
|
+
shows the restart. To avoid it, use Node 26, or a 22.x release from 22.13.
|
|
275
|
+
- Memory: by default the engine may use half the container's memory limit, else of the machine's RAM, or what leaves
|
|
276
|
+
2.5 GiB beside it when that is less, never under 128 MiB (`MASON_DUCKDB_MEMORY=4GB` sets it; `mason doctor` says
|
|
277
|
+
it). Under 3 GiB, with it unset, Mason starts and warns that the system may stop it. Sizing a server:
|
|
250
278
|
[deploy/mason-data/README.md#memory](https://github.com/embeddable-hq/mason-data/blob/main/deploy/mason-data/README.md#memory).
|
|
251
279
|
- Postgres 13 or later. Each table needs a primary key, or a `key:` in the sources file (polling only).
|
|
252
280
|
|
|
@@ -256,14 +284,14 @@ seeds it again.
|
|
|
256
284
|
project's `.mason/config.json`, which a browser cannot send — or, for its web page (`mason ui`), the cookie the run's token is traded for on the first visit (HttpOnly, SameSite=Strict, its Host and every change's Origin checked). The Postgres API takes a user's password (`mason user`): a
|
|
257
285
|
MAC over its name keyed by the project's API key, so rotating the key removes every user at once. Off loopback the Postgres API is TLS or refused.
|
|
258
286
|
|
|
259
|
-
What a project keeps on this machine (the database URL, the API key, the run's token, the
|
|
287
|
+
What a project keeps on this machine (the database URL, the API key, the run's token, the synced data, its log and request
|
|
260
288
|
log) is in its `.mason/`, readable by your user only and gitignored by `mason init`.
|
|
261
289
|
|
|
262
290
|
## In production
|
|
263
291
|
|
|
264
292
|
Only the project folder travels — `mason/`, with `mason.yml` naming the Mason version it is written for. Production runs
|
|
265
293
|
it as one container, `ghcr.io/embeddable-hq/mason-data`, with `mason serve`: the same run, configured by the environment,
|
|
266
|
-
its
|
|
294
|
+
its synced data on one volume, each table read from Postgres's change log or polled as its `sync:` says. `mason help
|
|
267
295
|
production` is the road, offline; `deploy/mason-data/` in the repo is the Compose file and its walkthrough.
|
|
268
296
|
|
|
269
297
|
Apache-2.0.
|
package/dist/src/api/error.js
CHANGED
|
@@ -16,3 +16,6 @@ export class ApiError extends Error {
|
|
|
16
16
|
this.code = code;
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
/** The code of a DISABLED `mason user`'s refused login or session (src/duck/users.ts): the listener answers it 28000, as
|
|
20
|
+
* Postgres answers a role without LOGIN, never 28P01 — its password was right. */
|
|
21
|
+
export const USER_DISABLED = "user_disabled";
|
|
@@ -7,18 +7,65 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Messages in: `{ id, op: "init", statements, optional? }` (start PGlite and run the DDL; the reply names the refused
|
|
9
9
|
* statement, or the optional ones refused),
|
|
10
|
-
* `{ id, op: "raw", bytes }` (frontend protocol bytes in, backend bytes out
|
|
10
|
+
* `{ id, op: "raw", bytes, at? }` (frontend protocol bytes in, backend bytes out; `at`: the instant the FIRST message
|
|
11
|
+
* runs at, in microseconds since the epoch, `pinnedClock`), `{ id, op: "query", sql }` (rows as
|
|
11
12
|
* objects, for the server's own lookups and the tests), `{ id, op: "close" }`. Every reply carries the id:
|
|
12
13
|
* `{ id, ok: true, bytes | rows }` or `{ id, ok: false, message }`.
|
|
13
14
|
*/
|
|
14
15
|
import { parentPort } from "node:worker_threads";
|
|
15
16
|
import { PGlite } from "@electric-sql/pglite";
|
|
16
17
|
import { citext } from "@electric-sql/pglite/contrib/citext";
|
|
18
|
+
import { instantWallMs, nowMicros } from "../../lib/instant.js";
|
|
17
19
|
const port = parentPort;
|
|
18
20
|
if (port === null)
|
|
19
21
|
throw new Error("catalog-worker.ts runs on a worker thread only");
|
|
20
22
|
const errorMessage = (e) => (e instanceof Error ? e.message : String(e));
|
|
21
23
|
let db = null;
|
|
24
|
+
/**
|
|
25
|
+
* THE CATALOG'S CLOCK IS THE SESSION'S TRANSACTION'S (GitHub issue 253, review r3 of PR 333): `bytes`' first message
|
|
26
|
+
* (`asSession`'s, which begins the request's transaction) runs with the wall clock PGlite reads held at `at`, the
|
|
27
|
+
* session's instant, and the rest with the real one. Postgres's transaction start is the instant its first message
|
|
28
|
+
* arrived (`xactStartTimestamp`), and every reader of it — now(), CURRENT_TIMESTAMP and the other keywords, a
|
|
29
|
+
* relative word (`'now'`, `'today'`) however it is spelt, computed or bound — reads that one value, so the catalog's
|
|
30
|
+
* transaction instant IS the session's and no statement is rewritten for it. `statement_timestamp()` and
|
|
31
|
+
* `clock_timestamp()` read the real clock, as on Postgres. PGlite's wall clock is `Date.now()`
|
|
32
|
+
* (emscripten's `clock_time_get(CLOCK_REALTIME)`), on this thread alone; the requests are one at a time.
|
|
33
|
+
* The instant is MICROSECONDS, as Postgres's (src/lib/instant.ts, GitHub issues 372, 363): `at` is handed to PGlite
|
|
34
|
+
* as the value it reads back as exactly that microsecond (`instantWallMs`), and the real clock after it is this
|
|
35
|
+
* thread's microsecond clock (`nowMicros`), floored at `at`: the two threads' clocks may disagree inside a
|
|
36
|
+
* millisecond, and `statement_timestamp()` must never read earlier than the transaction's start, as on Postgres.
|
|
37
|
+
*/
|
|
38
|
+
async function pinnedClock(instance, bytes, at) {
|
|
39
|
+
// a frame: its type byte, then its length (its own four bytes included)
|
|
40
|
+
const first = 1 + new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(1);
|
|
41
|
+
const wall = Date.now;
|
|
42
|
+
const pinned = instantWallMs(at);
|
|
43
|
+
let head, tail;
|
|
44
|
+
try {
|
|
45
|
+
Date.now = () => pinned;
|
|
46
|
+
head = new Uint8Array(await instance.execProtocolRaw(bytes.subarray(0, first))); // a copy: PGlite may reuse its buffer
|
|
47
|
+
Date.now = () => instantWallMs(Math.max(at, nowMicros(wall)));
|
|
48
|
+
tail = await instance.execProtocolRaw(bytes.subarray(first));
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
Date.now = wall;
|
|
52
|
+
}
|
|
53
|
+
const out = new Uint8Array(head.length + tail.length);
|
|
54
|
+
out.set(head, 0);
|
|
55
|
+
out.set(tail, head.length);
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
/** a request run at no instant of a session's (the DDL, a parameter's input function): the real clock, in microseconds */
|
|
59
|
+
async function microsClock(instance, bytes) {
|
|
60
|
+
const wall = Date.now;
|
|
61
|
+
try {
|
|
62
|
+
Date.now = () => instantWallMs(nowMicros(wall));
|
|
63
|
+
return await instance.execProtocolRaw(bytes);
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
Date.now = wall;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
22
69
|
port.on("message", (m) => {
|
|
23
70
|
void (async () => {
|
|
24
71
|
try {
|
|
@@ -63,7 +110,7 @@ port.on("message", (m) => {
|
|
|
63
110
|
case "raw": {
|
|
64
111
|
if (db === null)
|
|
65
112
|
throw new Error("the catalog is not started");
|
|
66
|
-
const bytes = await db.
|
|
113
|
+
const bytes = m.at === undefined ? await microsClock(db, m.bytes) : await pinnedClock(db, m.bytes, m.at);
|
|
67
114
|
const copy = new Uint8Array(bytes); // PGlite may reuse its buffer: a copy of our own is transferred
|
|
68
115
|
port.postMessage({ id: m.id, ok: true, bytes: copy }, [copy.buffer]);
|
|
69
116
|
return;
|