From 78500462f4141df0be7cca44d7c44becd186d119 Mon Sep 17 00:00:00 2001 From: Doug Mealing Date: Fri, 30 Oct 2026 23:57:33 -0400 Subject: [PATCH 1/4] docs(examples): head-to-head demo of MetaObjects against a DIY stack One small app (TypeScript API, Python prompt worker, Postgres) set up twice on the published 1.2.0 packages: a competent do-it-yourself baseline (TypeSpec, OpenAPI, openapi-typescript, datamodel-code-generator, Atlas, oasdiff, a StrictUndefined render test) and MetaObjects. `./run.sh` makes six changes to each, runs each setup's own pull-request gate, and writes SCORECARD.md from the exit codes. Layer consistency and the partner-contract check are scored separately, so the row MetaObjects has no check for is shown. Self-measured; the README says what it does not measure. --- examples/head-to-head/README.md | 145 + examples/head-to-head/SCORECARD.md | 218 ++ examples/head-to-head/baseline/.gitignore | 6 + examples/head-to-head/baseline/api/smoke.ts | 20 + examples/head-to-head/baseline/api/src/app.ts | 41 + .../head-to-head/baseline/api/tsconfig.json | 12 + examples/head-to-head/baseline/atlas.hcl | 11 + examples/head-to-head/baseline/atlas.sh | 4 + .../atlas/migrations/20261031030525_init.sql | 2 + .../baseline/atlas/migrations/atlas.sum | 2 + .../head-to-head/baseline/atlas/schema.sql | 7 + examples/head-to-head/baseline/ci.sh | 33 + .../baseline/contract/released/openapi.yaml | 119 + examples/head-to-head/baseline/db-apply.sh | 6 + examples/head-to-head/baseline/gen.sh | 10 + .../head-to-head/baseline/generated/api.d.ts | 150 + .../head-to-head/baseline/generated/models.py | 33 + .../baseline/generated/openapi.yaml | 119 + examples/head-to-head/baseline/live-drift.sh | 9 + examples/head-to-head/baseline/mypy.ini | 9 + .../head-to-head/baseline/package-lock.json | 2852 ++++++++++++++++ examples/head-to-head/baseline/package.json | 19 + .../baseline/prompts/welcome/greeting.jinja | 1 + .../head-to-head/baseline/requirements.in | 6 + .../head-to-head/baseline/requirements.txt | 85 + examples/head-to-head/baseline/smoke.sh | 13 + examples/head-to-head/baseline/spec/main.tsp | 49 + .../head-to-head/baseline/tests/__init__.py | 0 .../baseline/tests/test_prompts.py | 8 + examples/head-to-head/baseline/tspconfig.yaml | 8 + .../head-to-head/baseline/worker/__init__.py | 0 examples/head-to-head/baseline/worker/main.py | 27 + .../head-to-head/baseline/worker/prompts.py | 15 + examples/head-to-head/harness/lines.py | 108 + examples/head-to-head/harness/run.py | 768 +++++ examples/head-to-head/metaobjects/.gitignore | 8 + .../.metaobjects/.gen-state/.hashes.json | 7 + .../metaobjects/.metaobjects/config.json | 5 + .../migrations/.schema.postgres.json | 72 + .../migrations/20261031030631-init/down.sql | 1 + .../migrations/20261031030631-init/up.sql | 9 + .../head-to-head/metaobjects/api/smoke.ts | 20 + .../head-to-head/metaobjects/api/src/app.ts | 33 + .../metaobjects/api/src/generated/Customer.ts | 179 + .../api/src/generated/WelcomePayload.ts | 50 + .../metaobjects/api/tsconfig.json | 12 + examples/head-to-head/metaobjects/ci.sh | 21 + examples/head-to-head/metaobjects/db-apply.sh | 5 + .../head-to-head/metaobjects/live-drift.sh | 5 + .../metaobjects/metaobjects.config.ts | 8 + .../metaobjects/metaobjects.config.yaml | 6 + .../metaobjects/metaobjects/meta.crm.yaml | 26 + examples/head-to-head/metaobjects/mypy.ini | 9 + .../metaobjects/package-lock.json | 2873 +++++++++++++++++ .../head-to-head/metaobjects/package.json | 21 + .../prompts/welcome/greeting.jinja | 1 + .../head-to-head/metaobjects/requirements.in | 4 + .../head-to-head/metaobjects/requirements.txt | 39 + examples/head-to-head/metaobjects/smoke.sh | 13 + .../metaobjects/worker/__init__.py | 0 .../metaobjects/worker/generated/Customer.py | 34 + .../worker/generated/WelcomePayload.py | 22 + .../metaobjects/worker/generated/__init__.py | 2 + .../head-to-head/metaobjects/worker/main.py | 27 + .../metaobjects/worker/prompts.py | 14 + examples/head-to-head/run.sh | 7 + 66 files changed, 8448 insertions(+) create mode 100644 examples/head-to-head/README.md create mode 100644 examples/head-to-head/SCORECARD.md create mode 100644 examples/head-to-head/baseline/.gitignore create mode 100644 examples/head-to-head/baseline/api/smoke.ts create mode 100644 examples/head-to-head/baseline/api/src/app.ts create mode 100644 examples/head-to-head/baseline/api/tsconfig.json create mode 100644 examples/head-to-head/baseline/atlas.hcl create mode 100755 examples/head-to-head/baseline/atlas.sh create mode 100644 examples/head-to-head/baseline/atlas/migrations/20261031030525_init.sql create mode 100644 examples/head-to-head/baseline/atlas/migrations/atlas.sum create mode 100644 examples/head-to-head/baseline/atlas/schema.sql create mode 100755 examples/head-to-head/baseline/ci.sh create mode 100644 examples/head-to-head/baseline/contract/released/openapi.yaml create mode 100755 examples/head-to-head/baseline/db-apply.sh create mode 100755 examples/head-to-head/baseline/gen.sh create mode 100644 examples/head-to-head/baseline/generated/api.d.ts create mode 100644 examples/head-to-head/baseline/generated/models.py create mode 100644 examples/head-to-head/baseline/generated/openapi.yaml create mode 100755 examples/head-to-head/baseline/live-drift.sh create mode 100644 examples/head-to-head/baseline/mypy.ini create mode 100644 examples/head-to-head/baseline/package-lock.json create mode 100644 examples/head-to-head/baseline/package.json create mode 100644 examples/head-to-head/baseline/prompts/welcome/greeting.jinja create mode 100644 examples/head-to-head/baseline/requirements.in create mode 100644 examples/head-to-head/baseline/requirements.txt create mode 100755 examples/head-to-head/baseline/smoke.sh create mode 100644 examples/head-to-head/baseline/spec/main.tsp create mode 100644 examples/head-to-head/baseline/tests/__init__.py create mode 100644 examples/head-to-head/baseline/tests/test_prompts.py create mode 100644 examples/head-to-head/baseline/tspconfig.yaml create mode 100644 examples/head-to-head/baseline/worker/__init__.py create mode 100644 examples/head-to-head/baseline/worker/main.py create mode 100644 examples/head-to-head/baseline/worker/prompts.py create mode 100644 examples/head-to-head/harness/lines.py create mode 100644 examples/head-to-head/harness/run.py create mode 100644 examples/head-to-head/metaobjects/.gitignore create mode 100644 examples/head-to-head/metaobjects/.metaobjects/.gen-state/.hashes.json create mode 100644 examples/head-to-head/metaobjects/.metaobjects/config.json create mode 100644 examples/head-to-head/metaobjects/.metaobjects/migrations/.schema.postgres.json create mode 100644 examples/head-to-head/metaobjects/.metaobjects/migrations/20261031030631-init/down.sql create mode 100644 examples/head-to-head/metaobjects/.metaobjects/migrations/20261031030631-init/up.sql create mode 100644 examples/head-to-head/metaobjects/api/smoke.ts create mode 100644 examples/head-to-head/metaobjects/api/src/app.ts create mode 100644 examples/head-to-head/metaobjects/api/src/generated/Customer.ts create mode 100644 examples/head-to-head/metaobjects/api/src/generated/WelcomePayload.ts create mode 100644 examples/head-to-head/metaobjects/api/tsconfig.json create mode 100755 examples/head-to-head/metaobjects/ci.sh create mode 100755 examples/head-to-head/metaobjects/db-apply.sh create mode 100755 examples/head-to-head/metaobjects/live-drift.sh create mode 100644 examples/head-to-head/metaobjects/metaobjects.config.ts create mode 100644 examples/head-to-head/metaobjects/metaobjects.config.yaml create mode 100644 examples/head-to-head/metaobjects/metaobjects/meta.crm.yaml create mode 100644 examples/head-to-head/metaobjects/mypy.ini create mode 100644 examples/head-to-head/metaobjects/package-lock.json create mode 100644 examples/head-to-head/metaobjects/package.json create mode 100644 examples/head-to-head/metaobjects/prompts/welcome/greeting.jinja create mode 100644 examples/head-to-head/metaobjects/requirements.in create mode 100644 examples/head-to-head/metaobjects/requirements.txt create mode 100755 examples/head-to-head/metaobjects/smoke.sh create mode 100644 examples/head-to-head/metaobjects/worker/__init__.py create mode 100644 examples/head-to-head/metaobjects/worker/generated/Customer.py create mode 100644 examples/head-to-head/metaobjects/worker/generated/WelcomePayload.py create mode 100644 examples/head-to-head/metaobjects/worker/generated/__init__.py create mode 100644 examples/head-to-head/metaobjects/worker/main.py create mode 100644 examples/head-to-head/metaobjects/worker/prompts.py create mode 100755 examples/head-to-head/run.sh diff --git a/examples/head-to-head/README.md b/examples/head-to-head/README.md new file mode 100644 index 0000000000..0f44560711 --- /dev/null +++ b/examples/head-to-head/README.md @@ -0,0 +1,145 @@ +# Head-to-head: the same app, set up twice + +One small app, built two ways, and a script that makes the same six changes to each and records which +checks go red. The result is [SCORECARD.md](SCORECARD.md), written by `./run.sh` from the exit codes of +the checks that ran. + +**Who measured it.** The MetaObjects maintainers wrote both setups, the scenarios and the harness. The +numbers are self-measured. We tried to make the baseline competent, but we chose its tools, so read the +setup and change anything you disagree with; `./run.sh` reruns the lot. + +## The app + +A customer record, a TypeScript HTTP API over Postgres, and a Python worker that renders a welcome prompt +for a customer (the prompt is where an LLM call would go; this demo makes none). + +| | DIY baseline (`baseline/`) | MetaObjects (`metaobjects/`) | +|---|---|---| +| Source of truth | TypeSpec (`spec/main.tsp`) for the API and the prompt payload; plain SQL (`atlas/schema.sql`) for the database | One model (`metaobjects/meta.crm.yaml`) | +| TypeScript side | `openapi-typescript` types from the generated OpenAPI; hand-written Fastify handlers over raw `pg` SQL | `meta gen` entity and Zod schema; hand-written Fastify handlers over Drizzle | +| Python side | `datamodel-code-generator` Pydantic models; Jinja2 with `StrictUndefined` | `metaobjects gen` Pydantic models; `metaobjects.render` | +| Database | Atlas versioned migrations, diffed from `schema.sql` | `meta migrate` migrations, diffed from the model | +| Checks in the pull-request gate | regenerate and `git diff --exit-code`; `oasdiff` against the released contract; Atlas migration and lint checks; `tsc`; `mypy`; a pytest that renders the prompt | `meta verify`; `metaobjects verify`; migrations applied to a CI database and `meta verify --db`; `tsc`; `mypy` | + +Both use the published packages (npm and PyPI, versions pinned in `package-lock.json` and +`requirements.txt`; the scorecard lists every tool version it measured). Nothing in here needs a checkout +of the MetaObjects source. + +`oasdiff` is in the baseline so the partner-contract question has a competent answer on the baseline side. +It is not a MetaObjects feature, and the scorecard says so. + +## Run it + +You need Docker (Postgres 16, Atlas and oasdiff run as containers), Node.js 20 or newer, `uv`, `git` and +`python3`. The first run installs both setups' dependencies and pulls three images; later runs reuse them. + +```sh +./run.sh # every scenario, both setups, rewrites SCORECARD.md (about three minutes) +./run.sh S1 S6 # chosen scenarios (S0, the control, always runs) +./run.sh --keep /tmp/h2h-work # also keep each scenario's working copy and its .ci-logs/ +``` + +Each (setup, scenario) pair gets a throwaway copy of the setup in a fresh git repository: a base commit +of the clean setup, then the scenario's edit plus whatever generators that setup's developer would run, +then a "change" commit, then the setup's own `./ci.sh`. The repository is not touched. One Postgres +container is started for the run and removed at the end. + +## The scenarios + +Every scenario edits **the place that setup treats as its source of truth**, runs **the generators that +setup gives you**, and leaves anything else alone. That is the premise: it measures what happens when +someone forgets the rest, not how well anyone remembers it. S0 changes nothing and must be green on both +sides, otherwise a red cell elsewhere would be noise. + +| | Change | Why it is in the set | +|---|---|---| +| S1 | Rename `Customer.email` to `contactEmail` | one name in many layers | +| S2 | Add a required `country` field | a new column the API now demands | +| S3 | Change a prompt variable to one the payload does not have | prompt drift | +| S4 | Add a field and skip every generator | forgotten regeneration | +| S5 | `ALTER TABLE` a live database by hand | database drift (checked against the live database) | +| S6 | `name` becomes required on `POST /customers` | a change that breaks callers outside the project | + +The exact edits are in `harness/run.py` (functions `s1_baseline` … `s6_metaobjects`). + +## How to read the scorecard + +There are two questions and the scorecard asks them separately, because one tool answering one of them says +nothing about the other. + +1. **Do the layers stay consistent?** A model, generated code, a database, a prompt and some hand-written + code have to agree. *Red* means a check noticed they did not. *Green* means none did; it does not mean + the change was safe, which is why a happy-path **probe** (create a customer through the API, render its + prompt in the worker; the same files in both setups) is reported beside it. The probe is informational + and is not part of either gate. +2. **Does a breaking change for outside callers get flagged?** Only the baseline has a check for this + (`oasdiff`). MetaObjects 1.2.0 has no equivalent, so every row in that table reads "no such check" for + it. A change can be perfectly consistent across every layer and still break a partner; consistency checks + cannot see that, and this is the one place the baseline is simply ahead. + +### Our reading of the rows that need explaining + +These are interpretation, written by hand, not measured. Check them against the logs (`--keep`). + +- **S2, baseline.** The baseline's only red check is the partner-contract diff. Nothing ties the API model to + `atlas/schema.sql`, so the API now requires `country` while the database has no such column, and the raw-SQL + handler never reads it, so the probe passes. A team that edits `schema.sql` in the same change closes the + gap by hand; the scenario is "forgot to". +- **S2, MetaObjects.** Every generated layer moved together (the table, the Zod schema, the Pydantic model) + and the migration was proposed, so no layer check has anything to say. The probe fails because the client + in the probe does not send `country`, which is the partner-break row again, not layer drift. The + migration it proposed carries a warning that adding a required column fails on a table that has rows; + the harness prints it under *authoring*. +- **S3.** The baseline fails at render time (`StrictUndefined`), in a test. MetaObjects fails at + `verify` time with `ERR_VAR_NOT_ON_PAYLOAD`. At runtime MetaObjects' renderer is lenient and silently + drops the unknown variable, which is why the probe fails there with a prompt that has no name in it. + `verify` has to be in your pipeline for that to matter. +- **S6.** MetaObjects has no check for this. The change is legal everywhere in the project and breaks + only callers outside it. + +## Lines each setup needs + +The scorecard also counts what each setup makes you write. One rule for both: every committed file in the +setup's directory counts, except lock files, machine-generated output (`generated/`, the migrations, +`.metaobjects/.gen-state/` and so on, all listed in the scorecard) and the smoke probe, which is +byte-identical in both. Blank lines and comment-only lines are not counted. `harness/lines.py` fails if a +file fits no category, so nothing escapes the count. Generated lines are reported, not hidden. + +## What this does not measure + +- **Agent speed, agent cost or agent defect rates.** No LLM wrote any of this and none ran. It says nothing + about how fast or accurately a coding agent works against either setup. +- **Runtime behaviour beyond a happy path.** The probe creates one customer and renders one prompt. Neither + setup has the integration or contract tests a production service would; both would add some. +- **Anything outside these six changes.** Six scenarios on one small entity, two languages, one database. + Do not read the result as a statement about larger models, other stacks, other databases, or other + scenarios. +- **Authoring effort or skill.** The line counts are a rough size, not a time. + +## Known limits of the comparison + +- The baseline's handlers use raw SQL, which is what a team often writes when it wants no ORM. That makes + the database invisible to `tsc`, so a column change reaches the baseline's checks only through the Atlas + and OpenAPI paths. A baseline built on an ORM would be checked differently. +- The baseline developer edits `spec/main.tsp` as the source of truth and does not edit `atlas/schema.sql` + in the same change, in every scenario. A team that updates both by hand would see a different result in + S1 and S2. MetaObjects derives the table from the model, which is the point of the comparison, but it is + also why MetaObjects' API shape and table shape are coupled (S6: a field required on the API becomes + `NOT NULL` in the table). +- The baseline's handlers do not validate request bodies at run time; the OpenAPI types only exist at + compile time. MetaObjects' handlers validate with the generated Zod schema and answer 400. A baseline team + would add a validator, and that is not counted. +- Which tools make up "a competent baseline" is a judgment. TypeSpec, `openapi-typescript`, + `datamodel-code-generator`, Atlas, `oasdiff` and a `StrictUndefined` render test are real tools in real + use; a different team might pick Prisma, or Buf, or hand-write the schema. +- Both setups' gates run only the checks listed above. Each could carry more. + +## Layout + +``` +baseline/ the DIY setup: spec/, atlas/, api/, worker/, prompts/, tests/, ci.sh +metaobjects/ the MetaObjects setup: metaobjects/ (the model), api/, worker/, prompts/, ci.sh +harness/ run.py (scenarios, checks, scorecard) and lines.py (the line counter) +run.sh entry point +SCORECARD.md the output of the last run, committed +``` diff --git a/examples/head-to-head/SCORECARD.md b/examples/head-to-head/SCORECARD.md new file mode 100644 index 0000000000..2ffa35ea6d --- /dev/null +++ b/examples/head-to-head/SCORECARD.md @@ -0,0 +1,218 @@ +# Head-to-head scorecard + +> **Self-measured.** The MetaObjects maintainers wrote both setups and this harness. The baseline is built to be a competent do-it-yourself stack, but we chose its tools, its scenarios and its app, and a reader who disagrees with any of those should change them and rerun (`./run.sh`). Read [README.md](README.md) for what this does and does not measure. + +Generated by `./run.sh` on 2026-10-31; 7 scenarios × 2 setups took 232 s. Every result cell below is computed from the exit codes of the checks that ran; none is typed by hand. + +## Results + +Each scenario is one change a developer makes where the setup keeps its source of truth, followed by that setup's own generators. The pull-request gate (for S5, the live-database check) is then run, and each check's exit code is recorded. The **probe** is a happy-path run (create a customer through the API, render its prompt in the worker) that is identical in both setups; it is informational and is not part of either gate. Two different questions follow, scored separately. + +### Do the layers stay consistent? + +**red** means at least one check other than the partner-contract diff failed: the model, the generated code, the database, the prompt and the hand-written code disagreed and a check said so. **green** means every such check passed. It is not a claim that the change was safe; the probe column says whether the happy path still works. + +| # | Scenario | DIY baseline | MetaObjects | +|---|---|---|---| +| S0 | **Control: no change** | ✅ **no false alarm** · exit 0 · probe passed | ✅ **no false alarm** · exit 0 · probe passed | +| S1 | **Rename a field** | 🔴 **red**: `tsc`, `mypy`, `prompt-render` · probe failed | 🔴 **red**: `tsc` · probe failed | +| S2 | **Add a required field** | 🟢 **green** · probe passed | 🟢 **green** · probe failed | +| S3 | **Change a prompt variable** | 🔴 **red**: `prompt-render` · probe failed | 🔴 **red**: `meta-verify`, `metaobjects-verify` · probe failed | +| S4 | **Skip a regen** | 🔴 **red**: `generated-in-sync` · probe passed | 🔴 **red**: `meta-verify`, `metaobjects-verify`, `meta-verify-db` · probe passed | +| S5 | **Edit the database by hand** | 🔴 **red**: `live-drift` | 🔴 **red**: `live-drift` | +| S6 | **Change a field on the partner API** | 🟢 **green** · probe passed | 🟢 **green** · probe passed | + +### Does a breaking change for partners get flagged? + +The DIY baseline runs `oasdiff` against the OpenAPI document released to partners. MetaObjects 1.2.0 has no equivalent check, so it cannot flag a change that is legal everywhere in the project but breaks an outside caller. This is the row MetaObjects loses. + +| # | Scenario | DIY baseline | MetaObjects | +|---|---|---|---| +| S0 | **Control: no change** | 🟢 none flagged | ❌ **no such check** | +| S1 | **Rename a field** | ✅ **breaking change flagged** | ❌ **no such check** | +| S2 | **Add a required field** | ✅ **breaking change flagged** | ❌ **no such check** | +| S3 | **Change a prompt variable** | 🟢 none flagged | ❌ **no such check** | +| S4 | **Skip a regen** | 🟢 none flagged | ❌ **no such check** | +| S5 | **Edit the database by hand** | n/a | n/a | +| S6 | **Change a field on the partner API** | ✅ **breaking change flagged** | ❌ **no such check** | + +- **DIY baseline**: layer checks went red on 4 of 6 scenarios (S1, S3, S4, S5). +- **MetaObjects**: layer checks went red on 4 of 6 scenarios (S1, S3, S4, S5). +- **Partner-break check:** the baseline flagged 3 (S1, S2, S6); MetaObjects has none. +- **Where the baseline did better:** the partner-contract check (S1, S2, S6); layer checks red only on the baseline side: none. +- **Where MetaObjects did better:** layer checks red only on the MetaObjects side: none. + +### What each scenario does + +- **S0 Control: no change.** Nothing is edited. Every check must pass, so a red cell elsewhere is not noise from the setup itself. +- **S1 Rename a field.** `Customer.email` becomes `contactEmail` in the place each setup treats as its source of truth, then that setup's own generators run. The prompt payload, the database column and the hand-written code are left as they were. +- **S2 Add a required field.** `Customer` gains a required `country` (2 characters). Generators run; nothing else is touched. +- **S3 Change a prompt variable.** The prompt template is edited to use `{{ fullName }}`, which the payload does not have. Nothing is regenerated. +- **S4 Skip a regen.** An optional `nickname` field is added to the model and no generator is run, so the committed generated files are stale. +- **S5 Edit the database by hand.** Someone runs `ALTER TABLE customers ADD COLUMN vip boolean NOT NULL DEFAULT false` on a live database. The check is each setup's live-database drift command, the one a team runs on a schedule or at deploy time. +- **S6 Change a field on the partner API.** `name` becomes required on `POST /customers`: a request partners send today without it would be rejected. Generators run; the database follows where the setup derives it. + +### Which check said so + +**DIY baseline** + +| scenario | `generated-in-sync` | `api-compat` | `atlas-migrations` | `atlas-lint` | `tsc` | `mypy` | `prompt-render` | `live-drift` | +|---|---|---|---|---|---|---|---|---| +| S0 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | +| S1 | ✓ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | · | +| S2 | ✓ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | · | +| S3 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | · | +| S4 | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | +| S5 | · | · | · | · | · | · | · | ✗ | +| S6 | ✓ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | · | + +✓ passed, ✗ failed, · not run in that scenario. `generated-in-sync`: regenerate, then `git diff --exit-code`; `api-compat`: oasdiff against the released OpenAPI document; `atlas-migrations`: Atlas: migrations match `schema.sql`; `atlas-lint`: Atlas migration lint; `tsc`: TypeScript typecheck; `mypy`: mypy with the pydantic plugin; `prompt-render`: pytest render of the prompt with `StrictUndefined`. `live-drift`: `atlas schema diff` of the live database against the migrations, wrapped to exit 1 on a non-empty plan. + +**MetaObjects** + +| scenario | `meta-verify` | `metaobjects-verify` | `meta-verify-db` | `tsc` | `mypy` | `live-drift` | +|---|---|---|---|---|---|---| +| S0 | ✓ | ✓ | ✓ | ✓ | ✓ | · | +| S1 | ✓ | ✓ | ✓ | ✗ | ✓ | · | +| S2 | ✓ | ✓ | ✓ | ✓ | ✓ | · | +| S3 | ✗ | ✗ | ✓ | ✓ | ✓ | · | +| S4 | ✗ | ✗ | ✗ | ✓ | ✓ | · | +| S5 | · | · | · | · | · | ✗ | +| S6 | ✓ | ✓ | ✓ | ✓ | ✓ | · | + +✓ passed, ✗ failed, · not run in that scenario. `meta-verify`: `meta verify` (codegen, templates, requirement links); `metaobjects-verify`: `metaobjects verify` (Python codegen and templates); `meta-verify-db`: CI database from the migrations, then `meta verify --db`; `tsc`: TypeScript typecheck; `mypy`: mypy with the pydantic plugin. `live-drift`: `meta verify --db` against the live database. + +### What the failing checks printed + +The informative lines of each failing check's log, picked by a per-check extractor in `harness/run.py` (paths removed); full logs are written to `.ci-logs/` in the scenario's working directory (`--keep DIR`). *authoring* is what the setup's own generators said while the developer made the change, before CI. + +- **S1, DIY baseline:** + - `api-compat`: oasdiff: 5 changes: 4 error, 1 warning, 0 info; first: [response-required-property-removed] API GET /customers: removed the required property `items/email` from the response with the `200` status + - `tsc`: api/src/app.ts(32,13): error TS2339: Property 'email' does not exist on type '{ contactEmail: string; name?: string | undefined; plan: "free" | "pro"; }'. + - `mypy`: tests/test_prompts.py:7: error: Unexpected keyword argument "email" for "WelcomePayload" [call-arg] + - `prompt-render`: pydantic_core._pydantic_core.ValidationError: 1 validation error for WelcomePayload contactEmail + - smoke: pydantic_core._pydantic_core.ValidationError: 1 validation error for WelcomePayload contactEmail Field required [type=missing, input_value={'name': 'Ada Lovelace', ...lan': }, input_… +- **S1, MetaObjects:** + - `tsc`: api/src/app.ts(16,67): error TS2339: Property 'email' does not exist on type 'PgTableWithColumns<{ name: "customers"; schema: undefined; columns: { id: PgColumn<{ name: "id"; tableName: "customers"; … + - smoke: Error: POST /customers -> 400 {"error":"invalid_request","issues":[{"expected":"string","code":"invalid_type","path":["contactEmail"],"message":"Invalid input: expected string, received undefined"}]} + - authoring: `npx meta gen` printed: warnings[1]: "1 declared template.prompt (WelcomePrompt) generated no prompt code: no prompt generator is wired, so there is no render function to send one and nothing read a reply. Add promptRender() +- **S2, DIY baseline:** + - `api-compat`: oasdiff: 1 changes: 1 error, 0 warning, 0 info; first: [new-required-request-property] API POST /customers: added the new required request property `country` +- **S2, MetaObjects:** + - smoke: Error: POST /customers -> 400 {"error":"invalid_request","issues":[{"expected":"string","code":"invalid_type","path":["country"],"message":"Invalid input: expected string, received undefined"}]} + - authoring: `npx meta gen` printed: warnings[1]: "1 declared template.prompt (WelcomePrompt) generated no prompt code: no prompt generator is wired, so there is no render function to send one and nothing read a reply. Add promptRender() + - authoring: `npx meta migrate --slug add-country` printed: warnings[1]: "customers.country is added NOT NULL with no default — fails if customers has rows. Give the field a @default, or add it optional, backfill, then make it required." +- **S3, DIY baseline:** + - `prompt-render`: jinja2.exceptions.UndefinedError: 'fullName' is undefined + - smoke: jinja2.exceptions.UndefinedError: 'fullName' is undefined +- **S3, MetaObjects:** + - `meta-verify`: [WelcomePrompt] (prompt) ERR_VAR_NOT_ON_PAYLOAD: fullName (prompts/welcome/greeting.jinja:1); meta verify — 1 drift error(s) across 1 template(s). + - `metaobjects-verify`: error: [WelcomePrompt] ref 'welcome/greeting' — ERR_VAR_NOT_ON_PAYLOAD: fullName + - smoke: probe: the rendered prompt does not contain the customer's name: Write a short, friendly welcome note for , who just joined on the pro plan. +- **S4, DIY baseline:** + - `generated-in-sync`: a fresh regeneration differs from the committed `generated/api.d.ts`, `generated/models.py`, `generated/openapi.yaml` +- **S4, MetaObjects:** + - `meta-verify`: meta verify — codegen drift (1 file(s) differ from a fresh regen):; ~ api/src/generated/Customer.ts (committed content differs from a fresh regen) + - `metaobjects-verify`: error: [app] generated code is out of sync with metadata. drifted: Customer.py + - `meta-verify-db`: meta verify — schema drift vs postgresql://postgres:***@127.0.0.1:/me_s4_ci (1 change(s)):; + column customers.nickname +- **S5, DIY baseline:** + - `live-drift`: atlas would change the live database with: ALTER TABLE "customers" DROP COLUMN "vip"; +- **S5, MetaObjects:** + - `live-drift`: meta verify — schema drift vs postgresql://postgres:***@127.0.0.1:/me_s5_live (1 change(s)):; - column customers.vip +- **S6, DIY baseline:** + - `api-compat`: oasdiff: 1 changes: 1 error, 0 warning, 0 info; first: [request-property-became-required] API POST /customers: the request property `name` became required +- **S6, MetaObjects:** + - authoring: `npx meta gen` printed: warnings[1]: "1 declared template.prompt (WelcomePrompt) generated no prompt code: no prompt generator is wired, so there is no render function to send one and nothing read a reply. Add promptRender() + - authoring: `npx meta migrate --slug name-required --allow nullable-to-not-null` printed: warnings[1]: customers.name becomes NOT NULL — fails if any row holds NULL. Backfill it first. + +## Lines each setup needs + +One rule for both: every committed file in the setup's directory counts, minus lock files and machine-generated output (listed below), minus the smoke probe (identical in both). Blank lines and comment-only lines do not count. The numbers are from the files in this repository at the commit that generated this page. + +| Category | DIY baseline | MetaObjects | +|---|---:|---:| +| Model and schema (the sources of truth) | 43 | 23 | +| Tool configuration | 38 | 38 | +| Checks and glue scripts | 54 | 21 | +| Hand-written application code and prompt | 63 | 56 | +| Dependency manifests | 25 | 25 | +| **Model + configuration + checks** | **135** | **82** | +| **All counted lines** | **223** | **163** | + +Not counted, machine-generated: baseline 8 files / 425 lines (`generated/`, `atlas/migrations/`, the released OpenAPI copy); MetaObjects 11 files / 376 lines (`api/src/generated/`, `worker/generated/`, `.metaobjects/migrations/`, `.metaobjects/.gen-state/`). + +
Per-file counts + +**DIY baseline** + +- `atlas/schema.sql` (model): 7 +- `spec/main.tsp` (model): 36 +- `api/tsconfig.json` (config): 12 +- `atlas.hcl` (config): 10 +- `mypy.ini` (config): 8 +- `tspconfig.yaml` (config): 8 +- `atlas.sh` (checks): 2 +- `ci.sh` (checks): 28 +- `db-apply.sh` (checks): 4 +- `gen.sh` (checks): 8 +- `live-drift.sh` (checks): 7 +- `tests/__init__.py` (checks): 0 +- `tests/test_prompts.py` (checks): 5 +- `api/src/app.ts` (app): 34 +- `prompts/welcome/greeting.jinja` (app): 1 +- `worker/__init__.py` (app): 0 +- `worker/main.py` (app): 18 +- `worker/prompts.py` (app): 10 +- `package.json` (manifest): 19 +- `requirements.in` (manifest): 6 + +**MetaObjects** + +- `metaobjects/meta.crm.yaml` (model): 23 +- `.metaobjects/config.json` (config): 5 +- `api/tsconfig.json` (config): 12 +- `metaobjects.config.ts` (config): 7 +- `metaobjects.config.yaml` (config): 6 +- `mypy.ini` (config): 8 +- `ci.sh` (checks): 15 +- `db-apply.sh` (checks): 3 +- `live-drift.sh` (checks): 3 +- `api/src/app.ts` (app): 28 +- `prompts/welcome/greeting.jinja` (app): 1 +- `worker/__init__.py` (app): 0 +- `worker/main.py` (app): 18 +- `worker/prompts.py` (app): 9 +- `package.json` (manifest): 21 +- `requirements.in` (manifest): 4 + +
+ +## Versions + +| Scope | Tool | Version | +|---|---|---| +| both | Node.js | v24.22.1 | +| both | npm | 11.19.1 | +| both | Python (venv) | 3.12.12 | +| both | uv | 0.9.18 | +| both | Docker | 29.1.3 | +| both | PostgreSQL | 16.15 (Debian 16.15-1.pgdg13+2) (postgres:16) | +| both | TypeScript | 5.9.3 | +| both | Fastify | 5.12.7 | +| both | mypy | 2.4.1 | +| both | pydantic | 2.14.2 | +| baseline | TypeSpec compiler | 1.17.0 | +| baseline | @typespec/openapi3 | 1.17.0 | +| baseline | openapi-typescript | 7.13.0 | +| baseline | datamodel-code-generator | 0.87.0 | +| baseline | Atlas (community) | arigaio/atlas:1.3.6-community (atlas community version v1.3.6) | +| baseline | oasdiff | tufin/oasdiff:v1.35.0 (oasdiff version v1.35.0) | +| baseline | Jinja2 | 3.1.6 | +| baseline | pytest | 9.2.0 | +| metaobjects | @metaobjectsdev/cli | 1.2.0 | +| metaobjects | @metaobjectsdev/codegen-ts | 1.2.0 | +| metaobjects | metaobjects (PyPI) | 1.2.0 | +| metaobjects | Drizzle ORM | 0.45.4 | +| metaobjects | zod | 4.6.7 | + +Platform: Linux x86_64. diff --git a/examples/head-to-head/baseline/.gitignore b/examples/head-to-head/baseline/.gitignore new file mode 100644 index 0000000000..05f9fa76f0 --- /dev/null +++ b/examples/head-to-head/baseline/.gitignore @@ -0,0 +1,6 @@ +node_modules +.venv +__pycache__/ +.mypy_cache/ +.pytest_cache/ +.ci-logs/ diff --git a/examples/head-to-head/baseline/api/smoke.ts b/examples/head-to-head/baseline/api/smoke.ts new file mode 100644 index 0000000000..36bd5fe44a --- /dev/null +++ b/examples/head-to-head/baseline/api/smoke.ts @@ -0,0 +1,20 @@ +// End-to-end probe, identical in both setups: create a customer through the API, +// read it back, print its id. +import { buildApp } from "./src/app"; + +const app = buildApp(process.env.DATABASE_URL ?? ""); +const created = await app.inject({ + method: "POST", + url: "/customers", + payload: { email: "ada@example.test", name: "Ada Lovelace", plan: "pro" }, +}); +if (created.statusCode !== 201) { + throw new Error(`POST /customers -> ${created.statusCode} ${created.body}`); +} +const { id } = created.json<{ id: number }>(); +const read = await app.inject({ method: "GET", url: `/customers/${id}` }); +if (read.statusCode !== 200 || read.json<{ email: string }>().email !== "ada@example.test") { + throw new Error(`GET /customers/${id} -> ${read.statusCode} ${read.body}`); +} +await app.close(); +console.log(id); diff --git a/examples/head-to-head/baseline/api/src/app.ts b/examples/head-to-head/baseline/api/src/app.ts new file mode 100644 index 0000000000..6838baa0c2 --- /dev/null +++ b/examples/head-to-head/baseline/api/src/app.ts @@ -0,0 +1,41 @@ +import Fastify from "fastify"; +import pg from "pg"; +import type { components, operations } from "../../generated/api"; + +type Customer = components["schemas"]["Customer"]; +type CreateCustomer = components["schemas"]["CreateCustomer"]; +type ListQuery = NonNullable; + +const COLUMNS = `id, email, name, plan, created_at AS "createdAt"`; + +export function buildApp(databaseUrl: string) { + const pool = new pg.Pool({ connectionString: databaseUrl }); + const app = Fastify(); + app.addHook("onClose", async () => pool.end()); + + app.get<{ Querystring: ListQuery }>("/customers", async (req) => { + const { email } = req.query; + const sql = `SELECT ${COLUMNS} FROM customers WHERE ($1::text IS NULL OR email = $1) ORDER BY id`; + return (await pool.query(sql, [email ?? null])).rows; + }); + + app.get<{ Params: { id: string } }>("/customers/:id", async (req, reply) => { + const { rows } = await pool.query( + `SELECT ${COLUMNS} FROM customers WHERE id = $1`, + [req.params.id], + ); + if (rows.length === 0) return reply.code(404).send(); + return rows[0]; + }); + + app.post<{ Body: CreateCustomer }>("/customers", async (req, reply) => { + const { email, name, plan } = req.body; + const { rows } = await pool.query( + `INSERT INTO customers (email, name, plan) VALUES ($1, $2, $3) RETURNING ${COLUMNS}`, + [email, name ?? null, plan ?? "free"], + ); + return reply.code(201).send(rows[0]); + }); + + return app; +} diff --git a/examples/head-to-head/baseline/api/tsconfig.json b/examples/head-to-head/baseline/api/tsconfig.json new file mode 100644 index 0000000000..e27d029d61 --- /dev/null +++ b/examples/head-to-head/baseline/api/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["./**/*.ts", "../generated/*.d.ts"] +} diff --git a/examples/head-to-head/baseline/atlas.hcl b/examples/head-to-head/baseline/atlas.hcl new file mode 100644 index 0000000000..7e6331609d --- /dev/null +++ b/examples/head-to-head/baseline/atlas.hcl @@ -0,0 +1,11 @@ +variable "dev_url" { + type = string +} + +env "ci" { + src = "file://atlas/schema.sql" + dev = var.dev_url + migration { + dir = "file://atlas/migrations" + } +} diff --git a/examples/head-to-head/baseline/atlas.sh b/examples/head-to-head/baseline/atlas.sh new file mode 100755 index 0000000000..62273b3df3 --- /dev/null +++ b/examples/head-to-head/baseline/atlas.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +# Run the pinned Atlas image inside the Postgres container's network namespace. +exec docker run --rm --network "container:${H2H_PG_CONTAINER}" -v "$PWD":/w -w /w \ + --user "$(id -u):$(id -g)" arigaio/atlas:1.3.6-community "$@" diff --git a/examples/head-to-head/baseline/atlas/migrations/20261031030525_init.sql b/examples/head-to-head/baseline/atlas/migrations/20261031030525_init.sql new file mode 100644 index 0000000000..c3f4196729 --- /dev/null +++ b/examples/head-to-head/baseline/atlas/migrations/20261031030525_init.sql @@ -0,0 +1,2 @@ +-- Create "customers" table +CREATE TABLE "customers" ("id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "email" character varying(320) NOT NULL, "name" character varying(200) NULL, "plan" text NOT NULL DEFAULT 'free', "created_at" timestamptz NOT NULL DEFAULT now(), PRIMARY KEY ("id"), CONSTRAINT "customers_plan_check" CHECK (plan = ANY (ARRAY['free'::text, 'pro'::text]))); diff --git a/examples/head-to-head/baseline/atlas/migrations/atlas.sum b/examples/head-to-head/baseline/atlas/migrations/atlas.sum new file mode 100644 index 0000000000..8127b44768 --- /dev/null +++ b/examples/head-to-head/baseline/atlas/migrations/atlas.sum @@ -0,0 +1,2 @@ +h1:zDczpeh9tZrCDDT+jQkemaariQ8zFB3Svdgrht9PVdI= +20261031030525_init.sql h1:2zTREOrPnEWSsHJhy3JaIXH7b+LxfKTgICk0nqNapdw= diff --git a/examples/head-to-head/baseline/atlas/schema.sql b/examples/head-to-head/baseline/atlas/schema.sql new file mode 100644 index 0000000000..634bb04932 --- /dev/null +++ b/examples/head-to-head/baseline/atlas/schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE customers ( + id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + email varchar(320) NOT NULL, + name varchar(200), + plan text NOT NULL DEFAULT 'free' CHECK (plan IN ('free', 'pro')), + created_at timestamptz NOT NULL DEFAULT now() +); diff --git a/examples/head-to-head/baseline/ci.sh b/examples/head-to-head/baseline/ci.sh new file mode 100755 index 0000000000..a6c500b96c --- /dev/null +++ b/examples/head-to-head/baseline/ci.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# The pull-request gate: every check runs, one "CHECK " line each, +# and the script exits 1 if any failed. +cd "$(dirname "$0")" +export PATH="$PWD/.venv/bin:$PATH" PYTHONPATH="$PWD" +DEV_URL="postgres://postgres:h2h@127.0.0.1:5432/${H2H_DEV_DB}?sslmode=disable&search_path=public" +mkdir -p .ci-logs +failed=0 +check() { + local name=$1; shift + if "$@" >".ci-logs/$name.log" 2>&1; then echo "CHECK $name ok"; else echo "CHECK $name fail"; failed=1; fi +} + +generated_in_sync() { ./gen.sh && git diff --exit-code -- generated && [ -z "$(git status --porcelain -- generated)" ]; } +api_compat() { + docker run --rm -v "$PWD":/w -w /w tufin/oasdiff:v1.35.0 breaking \ + contract/released/openapi.yaml generated/openapi.yaml --fail-on ERR +} +atlas_in_sync() { + ./atlas.sh migrate validate --env ci --var dev_url="$DEV_URL" && + ./atlas.sh migrate diff ci_sync_check --env ci --var dev_url="$DEV_URL" && + [ -z "$(git status --porcelain -- atlas/migrations)" ] +} +atlas_lint() { ./atlas.sh migrate lint --env ci --latest 1 --var dev_url="$DEV_URL"; } + +check generated-in-sync generated_in_sync +check api-compat api_compat +check atlas-migrations atlas_in_sync +check atlas-lint atlas_lint +check tsc npx tsc -p api +check mypy mypy worker tests +check prompt-render pytest -q tests +exit $failed diff --git a/examples/head-to-head/baseline/contract/released/openapi.yaml b/examples/head-to-head/baseline/contract/released/openapi.yaml new file mode 100644 index 0000000000..fadf17504f --- /dev/null +++ b/examples/head-to-head/baseline/contract/released/openapi.yaml @@ -0,0 +1,119 @@ +openapi: 3.1.0 +info: + title: Customer API + version: 0.0.0 +tags: [] +paths: + /customers: + get: + operationId: Customers_list + parameters: + - name: email + in: query + required: false + schema: + type: string + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Customer' + post: + operationId: Customers_create + parameters: [] + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + $ref: '#/components/schemas/Customer' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateCustomer' + /customers/{id}: + get: + operationId: Customers_read + parameters: + - name: id + in: path + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Customer' + '404': + description: The server cannot find the requested resource. +components: + schemas: + CreateCustomer: + type: object + required: + - email + properties: + email: + type: string + minLength: 1 + maxLength: 320 + name: + type: string + maxLength: 200 + plan: + $ref: '#/components/schemas/Plan' + default: free + Customer: + type: object + required: + - id + - email + - plan + - createdAt + properties: + id: + type: integer + format: int64 + email: + type: string + maxLength: 320 + name: + type: string + maxLength: 200 + plan: + $ref: '#/components/schemas/Plan' + createdAt: + type: string + format: date-time + Plan: + type: string + enum: + - free + - pro + WelcomePayload: + type: object + required: + - email + - plan + properties: + email: + type: string + maxLength: 320 + name: + type: string + maxLength: 200 + plan: + $ref: '#/components/schemas/Plan' + description: The template for picking properties. diff --git a/examples/head-to-head/baseline/db-apply.sh b/examples/head-to-head/baseline/db-apply.sh new file mode 100755 index 0000000000..9a0c9a744e --- /dev/null +++ b/examples/head-to-head/baseline/db-apply.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# Apply the committed migrations to $H2H_DB. +set -euo pipefail +cd "$(dirname "$0")" +./atlas.sh migrate apply --dir file://atlas/migrations \ + --url "postgres://postgres:h2h@127.0.0.1:5432/${H2H_DB}?sslmode=disable&search_path=public" diff --git a/examples/head-to-head/baseline/gen.sh b/examples/head-to-head/baseline/gen.sh new file mode 100755 index 0000000000..3653aefbef --- /dev/null +++ b/examples/head-to-head/baseline/gen.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Regenerate everything derived from the TypeSpec model. +set -euo pipefail +cd "$(dirname "$0")" +export PATH="$PWD/.venv/bin:$PATH" +npx tsp compile spec/main.tsp +npx openapi-typescript generated/openapi.yaml -o generated/api.d.ts +datamodel-codegen --input generated/openapi.yaml --input-file-type openapi \ + --output-model-type pydantic_v2.BaseModel --target-python-version 3.12 \ + --disable-timestamp --formatters black isort --output generated/models.py diff --git a/examples/head-to-head/baseline/generated/api.d.ts b/examples/head-to-head/baseline/generated/api.d.ts new file mode 100644 index 0000000000..0d9c418df9 --- /dev/null +++ b/examples/head-to-head/baseline/generated/api.d.ts @@ -0,0 +1,150 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/customers": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["Customers_list"]; + put?: never; + post: operations["Customers_create"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/customers/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["Customers_read"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + CreateCustomer: { + email: string; + name?: string; + /** @default free */ + plan: components["schemas"]["Plan"]; + }; + Customer: { + /** Format: int64 */ + id: number; + email: string; + name?: string; + plan: components["schemas"]["Plan"]; + /** Format: date-time */ + createdAt: string; + }; + /** @enum {string} */ + Plan: "free" | "pro"; + /** @description The template for picking properties. */ + WelcomePayload: { + email: string; + name?: string; + plan: components["schemas"]["Plan"]; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + Customers_list: { + parameters: { + query?: { + email?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Customer"][]; + }; + }; + }; + }; + Customers_create: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateCustomer"]; + }; + }; + responses: { + /** @description The request has succeeded and a new resource has been created as a result. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Customer"]; + }; + }; + }; + }; + Customers_read: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Customer"]; + }; + }; + /** @description The server cannot find the requested resource. */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; +} diff --git a/examples/head-to-head/baseline/generated/models.py b/examples/head-to-head/baseline/generated/models.py new file mode 100644 index 0000000000..64774a689e --- /dev/null +++ b/examples/head-to-head/baseline/generated/models.py @@ -0,0 +1,33 @@ +# generated by datamodel-codegen: +# filename: openapi.yaml + +from __future__ import annotations + +from enum import StrEnum + +from pydantic import AwareDatetime, BaseModel, constr + + +class Plan(StrEnum): + free = 'free' + pro = 'pro' + + +class WelcomePayload(BaseModel): + email: constr(max_length=320) + name: constr(max_length=200) | None = None + plan: Plan + + +class CreateCustomer(BaseModel): + email: constr(min_length=1, max_length=320) + name: constr(max_length=200) | None = None + plan: Plan | None = 'free' + + +class Customer(BaseModel): + id: int + email: constr(max_length=320) + name: constr(max_length=200) | None = None + plan: Plan + createdAt: AwareDatetime diff --git a/examples/head-to-head/baseline/generated/openapi.yaml b/examples/head-to-head/baseline/generated/openapi.yaml new file mode 100644 index 0000000000..fadf17504f --- /dev/null +++ b/examples/head-to-head/baseline/generated/openapi.yaml @@ -0,0 +1,119 @@ +openapi: 3.1.0 +info: + title: Customer API + version: 0.0.0 +tags: [] +paths: + /customers: + get: + operationId: Customers_list + parameters: + - name: email + in: query + required: false + schema: + type: string + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Customer' + post: + operationId: Customers_create + parameters: [] + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + $ref: '#/components/schemas/Customer' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateCustomer' + /customers/{id}: + get: + operationId: Customers_read + parameters: + - name: id + in: path + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Customer' + '404': + description: The server cannot find the requested resource. +components: + schemas: + CreateCustomer: + type: object + required: + - email + properties: + email: + type: string + minLength: 1 + maxLength: 320 + name: + type: string + maxLength: 200 + plan: + $ref: '#/components/schemas/Plan' + default: free + Customer: + type: object + required: + - id + - email + - plan + - createdAt + properties: + id: + type: integer + format: int64 + email: + type: string + maxLength: 320 + name: + type: string + maxLength: 200 + plan: + $ref: '#/components/schemas/Plan' + createdAt: + type: string + format: date-time + Plan: + type: string + enum: + - free + - pro + WelcomePayload: + type: object + required: + - email + - plan + properties: + email: + type: string + maxLength: 320 + name: + type: string + maxLength: 200 + plan: + $ref: '#/components/schemas/Plan' + description: The template for picking properties. diff --git a/examples/head-to-head/baseline/live-drift.sh b/examples/head-to-head/baseline/live-drift.sh new file mode 100755 index 0000000000..e2928ce762 --- /dev/null +++ b/examples/head-to-head/baseline/live-drift.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Compare the live database $H2H_DB with the committed migrations; non-empty plan = drift. +set -euo pipefail +cd "$(dirname "$0")" +plan=$(./atlas.sh schema diff --exclude atlas_schema_revisions \ + --from "postgres://postgres:h2h@127.0.0.1:5432/${H2H_DB}?sslmode=disable&search_path=public" \ + --to file://atlas/migrations \ + --dev-url "postgres://postgres:h2h@127.0.0.1:5432/${H2H_DEV_DB}?sslmode=disable&search_path=public" 2>/dev/null) +case "$plan" in *"synced"*) exit 0 ;; *) echo "$plan"; exit 1 ;; esac diff --git a/examples/head-to-head/baseline/mypy.ini b/examples/head-to-head/baseline/mypy.ini new file mode 100644 index 0000000000..beca05a70f --- /dev/null +++ b/examples/head-to-head/baseline/mypy.ini @@ -0,0 +1,9 @@ +[mypy] +python_version = 3.12 +plugins = pydantic.mypy +disallow_untyped_defs = True +mypy_path = . + +[pydantic-mypy] +init_forbid_extra = True +init_typed = True diff --git a/examples/head-to-head/baseline/package-lock.json b/examples/head-to-head/baseline/package-lock.json new file mode 100644 index 0000000000..ccb251ce73 --- /dev/null +++ b/examples/head-to-head/baseline/package-lock.json @@ -0,0 +1,2852 @@ +{ + "name": "h2h-baseline", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "h2h-baseline", + "dependencies": { + "fastify": "5.12.7", + "pg": "8.23.1" + }, + "devDependencies": { + "@types/node": "22.20.5", + "@types/pg": "8.23.1", + "@typespec/compiler": "1.17.0", + "@typespec/http": "1.17.0", + "@typespec/openapi3": "1.17.0", + "openapi-typescript": "7.13.0", + "tsx": "4.23.15", + "typescript": "5.9.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.6.tgz", + "integrity": "sha512-uMHhvmDmwFiLuiozaoRYTZHqMFuu3fem/X9JY78lohAuLoh8LSGGC8JqUzo2cVuBsBxkjMZNk56CE7ock8Xxkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^8.0.6", + "js-tokens": "^10.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.6.tgz", + "integrity": "sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.7.tgz", + "integrity": "sha512-VP0yIfwlc1ohj733keYDqyHm1VWeP9VQxYomunhr90jkC4aNNibbkrRiaNgPgqj1ORxUenILR4qzA/ppYBNAtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^4.0.0" + } + }, + "node_modules/@fastify/ajv-compiler/node_modules/fast-uri": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.2.3.tgz", + "integrity": "sha512-I1PXwP5eAhlNlcdpjt9CgB59op2ZvZfxw+BkqGFVIUOgNpCVTcTqp3HhTX7brxj5vsip+nSY4nTvff+eIA76hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/@fastify/deepmerge": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-3.2.4.tgz", + "integrity": "sha512-kc8GQq5gX9RRhw0rov3DMrN8wS8yRlxJRdDzsYwwdBV/wazuVgc/1K7CKL90tlvu3RlHan6Y0R0aE3nJtBz2eQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/error": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.1.tgz", + "integrity": "sha512-MYBumuIwP+5MIpXSyKm6/o1Eigcoij186lWUssX3LH5U8KWqU0ktZhzC4CErIX9EJeQeRGI+zJZQWakBlFbUcg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.1.1.tgz", + "integrity": "sha512-pyxRKk7UpUnnrztP9pF4+ZYxusEQ4LifTyzRdiXmyql1QezNHxO02D25MVU2o4kMeAMYWAjG6RKRlojgkbiDAw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^7.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.1.2.tgz", + "integrity": "sha512-89WiRG+hW3VQSfCwg4Hb8b43PeyEX15K5+lEfsZ4EUth/FyOjS/lv8HsVuswUNXWiSnxMGNouE/fq/0w3VumHQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.2.tgz", + "integrity": "sha512-d/gx9sRbUCVgGRGzma46wLcwiY8noTqO2Nmjg/vbTARxHRE5bdh5PIL8s97Lt1X7ScRWRW52eVYxwIoPwIdVUA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.2.tgz", + "integrity": "sha512-uqB4QsgVoro0XDC+DIftIMGXHdBPn+lK6Ek3xXAIxPMCJfqxnbtyEoMCm2KPUVOuIosd7Cyw9HawEqMFM/UA+g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, + "node_modules/@inquirer/ansi": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.10.tgz", + "integrity": "sha512-dwrc+1aqVZpHNkNDswXGtn1typ+Pxpo5FHZRyBw1le7onEJUkPUkYOY1FIFC1LvFMYUqqLuWBwElhAmencL7MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.8.tgz", + "integrity": "sha512-NY/S1EalQhlonqhwwsarrcnkH7s9ByQuvjWYdk1jB2Gb9EPQJjKkhCTWjM3yQ0UpHaLm1XbIL89b+32G8rhO5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.10", + "@inquirer/core": "^12.0.6", + "@inquirer/figures": "^2.0.10", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.3.5.tgz", + "integrity": "sha512-BJ59BOyURiZiAxdr9AuRu38/k+rV+DcTIM2fUF/c/BXrab6H8zMZTTOl3LgkSJH96Xob2vOJxDiBkHiz32Xx2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "12.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-12.0.6.tgz", + "integrity": "sha512-5yxn2LsJ8Tu4ei2a+yNkmFRhM/cb+JMEsEs/AW6CCOnBnpMy4IjYiPMFyHtYq8weOmlTyqqCx40y0DsD7OsEPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.10", + "@inquirer/figures": "^2.0.10", + "@inquirer/type": "4.2.1", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.10.tgz", + "integrity": "sha512-51elrUJV3jZpvD0ygz8+U10Kc6+Lzqln+/tYmnbhy6jo5oXpE6hMCcv+OTBC0enwJXsb3JIuT3VtEinRNCSK2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + } + }, + "node_modules/@inquirer/input": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.1.9.tgz", + "integrity": "sha512-2Vd5T7lTxfgJMpAd3aOu6bQpbUnCU7tBgvTQIZkP/7juJzmRmTM2t5XfOCuirFduWLTSJI/WZp32Cq1E01TVeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.2.8.tgz", + "integrity": "sha512-BklkkAAOpjdCfuQw5fVGcDuhKxA7T1iBpn6b2JUvDDdkIsO3Ftlg+WnWUCVpkUBFagL2o036OFW+xL4lC9wIPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.10", + "@inquirer/core": "^12.0.6", + "@inquirer/figures": "^2.0.10", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.2.1.tgz", + "integrity": "sha512-lDkPsnv5M5CcYB2T1qTc3dobNTPkVr4DU3Ic2Q6SW8/qVRHgvzdRdmVZ6YqG9Ag1aJTeMsLU0s0WPRh3CZWDQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@redocly/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js-replace": "^1.0.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@redocly/config": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz", + "integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/openapi-core": { + "version": "1.34.22", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.22.tgz", + "integrity": "sha512-dEyx4uHh6Pc6ZkdU9Xe8rA4h12Odq6QKU/fqseBn07xwVwskhHiF3KHhqmw5BR+jAiFTvKTmwCafFql/k1pSzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/ajv": "8.11.2", + "@redocly/config": "0.22.0", + "colorette": "1.4.0", + "https-proxy-agent": "7.0.6", + "js-levenshtein": "1.1.6", + "js-yaml": "4.3.2", + "minimatch": "5.1.9", + "pluralize": "8.0.0", + "yaml-ast-parser": "0.0.43" + }, + "engines": { + "node": ">=18.17.0", + "npm": ">=9.5.0" + } + }, + "node_modules/@scalar/helpers": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@scalar/helpers/-/helpers-0.12.0.tgz", + "integrity": "sha512-rcX0rFLiWWc0VBr/E+HkatubL0I0ZzxASQkd3032NWEMAEfNd2TiJ9kH46HwbSAOOl696ghxymWegjpjjvxwzA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/json-magic": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/@scalar/json-magic/-/json-magic-0.13.5.tgz", + "integrity": "sha512-w5eENfKAkc4CYiLva3nGUhTaJfyi3szmZ3E7+EHlzahOUnbDmB9rAWyzt8GFCXbwxeTuR+1m7lO1PyV/QAid4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.12.0", + "pathe": "^2.0.3", + "undici": "7.24.4", + "yaml": "^2.9.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/json-schema-validator": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@scalar/json-schema-validator/-/json-schema-validator-0.1.17.tgz", + "integrity": "sha512-4Kmbvpng0V0kgPyqDWUZ1qstKjtDfz7seUCK1/inHjeQ+EJIcXPhoW47sOXf9yQBPbSjp42Wt8a7MZvizyoCPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.16.4", + "@scalar/types": "0.26.0", + "ajv": "^8.20.0", + "ajv-draft-04": "^1.0.0", + "ajv-formats": "^3.0.1", + "yaml": "^2.9.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/json-schema-validator/node_modules/@scalar/helpers": { + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/@scalar/helpers/-/helpers-0.16.4.tgz", + "integrity": "sha512-9eILWzUcAkHEuYMMvijpSIy69t+Vkq+NudLB/7lgARFTWqXfQpIxW1L3VH7ogKyRie5+E4CNZ3PSvlw6IIfLGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/json-schema-validator/node_modules/@scalar/types": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@scalar/types/-/types-0.26.0.tgz", + "integrity": "sha512-T78Dj1Q+W0E1RsMyPoydfR+L+p9IUIkntBCRYlWaRibM98jAxABRYGQoU/qDHX0PR1IAFdE+d7ZqslypZbJzcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.16.4", + "nanoid": "^5.1.16", + "type-fest": "^5.10.0", + "zod": "^4.6.7" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/openapi-parser": { + "version": "0.29.19", + "resolved": "https://registry.npmjs.org/@scalar/openapi-parser/-/openapi-parser-0.29.19.tgz", + "integrity": "sha512-ajtjKM9sQQOxEKP/VCtLbM9kQCAa0M4XWKsfadVnPvga1p7t1wPtIW+uDph8sHe+nKyydqwYxlZ8Q6RluxhbXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.16.4", + "@scalar/json-magic": "0.17.0", + "@scalar/openapi-types": "0.9.8", + "@scalar/openapi-upgrader": "0.4.6", + "@scalar/openapi-validator": "0.1.17", + "@scalar/types": "0.26.0", + "yaml": "^2.9.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/openapi-parser/node_modules/@scalar/helpers": { + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/@scalar/helpers/-/helpers-0.16.4.tgz", + "integrity": "sha512-9eILWzUcAkHEuYMMvijpSIy69t+Vkq+NudLB/7lgARFTWqXfQpIxW1L3VH7ogKyRie5+E4CNZ3PSvlw6IIfLGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/openapi-parser/node_modules/@scalar/json-magic": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@scalar/json-magic/-/json-magic-0.17.0.tgz", + "integrity": "sha512-YmJAO9NoGCcU/vx7P4Dy9DGv+Vntp+ny3Fh99slHOOh/IZxvbdK5hWeZdzQ3x+ezab9XJNscFoX8H/P6W+H8lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.16.4", + "pathe": "^2.0.3", + "undici": "8.12.3", + "yaml": "^2.9.1" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@scalar/openapi-parser/node_modules/@scalar/types": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@scalar/types/-/types-0.26.0.tgz", + "integrity": "sha512-T78Dj1Q+W0E1RsMyPoydfR+L+p9IUIkntBCRYlWaRibM98jAxABRYGQoU/qDHX0PR1IAFdE+d7ZqslypZbJzcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.16.4", + "nanoid": "^5.1.16", + "type-fest": "^5.10.0", + "zod": "^4.6.7" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/openapi-parser/node_modules/undici": { + "version": "8.12.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.12.3.tgz", + "integrity": "sha512-+fdndUTwBP86cVjihTp21hZHo7d6oj1c/47XPA3lL8mM0XHs+C4EkcjmgRsFnFk2f/UONGDy9ItBItmXsLOQHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@scalar/openapi-types": { + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@scalar/openapi-types/-/openapi-types-0.9.8.tgz", + "integrity": "sha512-pGcpkZObsGBC7nRKYte+6gstoe0C4hp7B2UWQYjg5To1wE68s3OHVvin+muL3tn3ZAoahXeJAkn1+aEcMi802w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/openapi-upgrader": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@scalar/openapi-upgrader/-/openapi-upgrader-0.4.6.tgz", + "integrity": "sha512-wkTbowGZAlDA5AhCAnBVft6y3veoE17P+okH/zrwM/BZWBSzPghGpa4BPTCg3+IzRmRP/8ZeaYO7LfxCDAEmcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.16.4", + "@scalar/openapi-types": "0.9.8" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/openapi-upgrader/node_modules/@scalar/helpers": { + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/@scalar/helpers/-/helpers-0.16.4.tgz", + "integrity": "sha512-9eILWzUcAkHEuYMMvijpSIy69t+Vkq+NudLB/7lgARFTWqXfQpIxW1L3VH7ogKyRie5+E4CNZ3PSvlw6IIfLGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/openapi-validator": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@scalar/openapi-validator/-/openapi-validator-0.1.17.tgz", + "integrity": "sha512-vBZZgD5k5mp5ii3kNdAti3bZZO0kEvUWYbr9E+Dkjdb67LAdgD5y4Ym0pNIik7prN6KUuUVUu1UMFGFvu1Udqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.16.4", + "@scalar/json-schema-validator": "0.1.17", + "@scalar/types": "0.26.0", + "yaml": "^2.9.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/openapi-validator/node_modules/@scalar/helpers": { + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/@scalar/helpers/-/helpers-0.16.4.tgz", + "integrity": "sha512-9eILWzUcAkHEuYMMvijpSIy69t+Vkq+NudLB/7lgARFTWqXfQpIxW1L3VH7ogKyRie5+E4CNZ3PSvlw6IIfLGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/openapi-validator/node_modules/@scalar/types": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@scalar/types/-/types-0.26.0.tgz", + "integrity": "sha512-T78Dj1Q+W0E1RsMyPoydfR+L+p9IUIkntBCRYlWaRibM98jAxABRYGQoU/qDHX0PR1IAFdE+d7ZqslypZbJzcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.16.4", + "nanoid": "^5.1.16", + "type-fest": "^5.10.0", + "zod": "^4.6.7" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/types": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@scalar/types/-/types-0.19.0.tgz", + "integrity": "sha512-EKeoWgUlP+uepbM/zEHKbsdpBNyOmSrw6DdU/WC0p53gz5uRzI7d/IEecIP9SQMUkResPR9DmWeWfFQftG7vqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.11.3", + "nanoid": "^5.1.6", + "type-fest": "^5.8.0", + "zod": "^4.4.3" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@scalar/types/node_modules/@scalar/helpers": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@scalar/helpers/-/helpers-0.11.3.tgz", + "integrity": "sha512-4zPzuNTXObDUtZS93xzAoK83ddHDgBGifv/vKFe6bHqEl5i+IMLTTtEHOaaOZK4dusPEXcXsU5TBSaY/I6Mi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@types/node": { + "version": "22.20.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz", + "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/pg": { + "version": "8.23.1", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.23.1.tgz", + "integrity": "sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@typespec/asset-emitter": { + "version": "0.79.3", + "resolved": "https://registry.npmjs.org/@typespec/asset-emitter/-/asset-emitter-0.79.3.tgz", + "integrity": "sha512-mqEcgz4wSLe+Y8epvOTJnoOar/ePLSA/xirIaCSPVUvdbZo+fKvPfnKNiPvwtSuhEYlDPewva3PLTPtOTe6GXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "@typespec/compiler": "^1.17.0" + } + }, + "node_modules/@typespec/compiler": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@typespec/compiler/-/compiler-1.17.0.tgz", + "integrity": "sha512-Prj9o2bjoWNS9wJ6sUk532jGm8h2AtpQ/lCFH6dhcywiP7UTj9cJtNlDAHptAS9Irh3YJM2g5D4/7S/u/vcG8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@inquirer/checkbox": "^5.2.2", + "@inquirer/confirm": "^6.2.0", + "@inquirer/input": "^5.1.3", + "@inquirer/select": "^5.2.2", + "ajv": "^8.20.0", + "change-case": "^5.4.4", + "env-paths": "^4.0.0", + "is-unicode-supported": "^2.1.0", + "mustache": "^4.2.0", + "picocolors": "^1.1.1", + "prettier": "^3.9.6", + "semver": "^7.8.5", + "tar": "^7.5.22", + "temporal-polyfill": "^1.0.4", + "vscode-languageserver": "^10.1.0", + "vscode-languageserver-textdocument": "^1.0.13", + "yaml": "^2.9.0", + "yargs": "^18.1.0" + }, + "bin": { + "tsp": "cmd/tsp.js", + "tsp-server": "cmd/tsp-server.js" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@typespec/http": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@typespec/http/-/http-1.17.0.tgz", + "integrity": "sha512-XLwL+NvkQ4hWhnIwZms2CNoxCULFt/jMa+QGaKg4hDwPFLvrXsUm7JWPHi1QOcwzGFG8Z1NglqRPzKl2der2ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "@typespec/compiler": "^1.17.0", + "@typespec/streams": "^0.87.0" + }, + "peerDependenciesMeta": { + "@typespec/streams": { + "optional": true + } + } + }, + "node_modules/@typespec/openapi": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@typespec/openapi/-/openapi-1.17.0.tgz", + "integrity": "sha512-0ScXsaovyFZWJv0nnokGI25vK5nRvJ8KfxiKokyXyQrwzK51eb61P/yZr/cZDGjk/w/qKHz2Vz1OvVC4ztdQdA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "@typespec/compiler": "^1.17.0", + "@typespec/http": "^1.17.0" + } + }, + "node_modules/@typespec/openapi3": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@typespec/openapi3/-/openapi3-1.17.0.tgz", + "integrity": "sha512-BC5zSemNfyFWIgJOj0TrwVA3KULIvCmlrDhqESMbzmN4h3byhXR/PENxPP4TlPKfu3M1KE44esnxSDQ5Py3WnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@scalar/json-magic": "^0.13.2", + "@scalar/openapi-parser": "^0.29.1", + "@scalar/openapi-types": "^0.9.5", + "@scalar/types": "^0.19.0", + "@typespec/asset-emitter": "^0.79.3", + "yaml": "^2.9.0" + }, + "bin": { + "tsp-openapi3": "cmd/tsp-openapi3.js" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "@typespec/compiler": "^1.17.0", + "@typespec/events": "^0.87.0", + "@typespec/http": "^1.17.0", + "@typespec/json-schema": "^1.17.0", + "@typespec/openapi": "^1.17.0", + "@typespec/sse": "^0.87.0", + "@typespec/streams": "^0.87.0", + "@typespec/versioning": "^0.87.0" + }, + "peerDependenciesMeta": { + "@typespec/events": { + "optional": true + }, + "@typespec/json-schema": { + "optional": true + }, + "@typespec/sse": { + "optional": true + }, + "@typespec/streams": { + "optional": true + }, + "@typespec/versioning": { + "optional": true + }, + "@typespec/xml": { + "optional": true + } + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.5.0.tgz", + "integrity": "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.3.1.tgz", + "integrity": "sha512-QyTKW61/qlba+cUQBm/sT9X8lri4UdomqSDwCnrnj1IvLVDRRiXRkz4CgBRWB+/cmfC7SK5WQc8T1vSdGvaUcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/env-paths": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-4.0.0.tgz", + "integrity": "sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-safe-filename": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-7.2.0.tgz", + "integrity": "sha512-q18imgm9fEdQVfvJbRJALE9VP/A5HGme0+NUnWcmFYM9rbQbLkZZk20FyIp1LQ8gDOauQKwqMc52YozqFaQyOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^4.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/fast-uri": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.2.3.tgz", + "integrity": "sha512-I1PXwP5eAhlNlcdpjt9CgB59op2ZvZfxw+BkqGFVIUOgNpCVTcTqp3HhTX7brxj5vsip+nSY4nTvff+eIA76hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-uri": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/fastify": { + "version": "5.12.7", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.12.7.tgz", + "integrity": "sha512-v4pEPsTRPuqlDIYiQFB+BHjVN4SdHX9u0ABk0ZDUKbbdY1vsNryUd1j4Z+jQEZajvYIVk047Kimva9/AJ79GYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.5", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^7.0.0", + "find-my-way": "^9.6.0", + "light-my-request": "^6.0.0", + "pino": "^9.14.0 || ^10.1.0", + "process-warning": "^5.1.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/find-my-way": { + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.10.0.tgz", + "integrity": "sha512-eru+uCPQ2g+nLBkf84MHP/KyNNMar0BDy0j+i8Fb72GF9/YHBZ58q7FODUtfVV7XU9nfRUHXSmaM8NmZcIbZ4Q==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ipaddr.js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz", + "integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-safe-filename": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-safe-filename/-/is-safe-filename-0.1.1.tgz", + "integrity": "sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-ref-resolver": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.1.tgz", + "integrity": "sha512-w+HwgljXpVwZ3IidoUR1qPZxSOQ+NRQWuVe6a5pe8XbHsNZE4UAwVPUvzZagn7yBKbJtt6UJnRFYbNTRPZI45Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/light-my-request": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.8.0.tgz", + "integrity": "sha512-5Q8PjoSEdtGIB8fikZsTem5vgRo4F2mR6vUKTDW/H1g6MtAt8e3duQbg/QxL5yzg6BZhor2KOUm3hhWMWLTZwg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "@fastify/deepmerge": "^3.1.0", + "cookie": "^2.0.0", + "process-warning": "^5.0.0", + "set-cookie-parser": "^3.0.1" + } + }, + "node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/mute-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/nanoid": { + "version": "5.1.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz", + "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/openapi-typescript": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.13.0.tgz", + "integrity": "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/openapi-core": "^1.34.6", + "ansi-colors": "^4.1.3", + "change-case": "^5.4.4", + "parse-json": "^8.3.0", + "supports-color": "^10.2.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "openapi-typescript": "bin/cli.js" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/parse-json/node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/parse-json/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-json/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.23.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.23.1.tgz", + "integrity": "sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.14.1", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.16.1", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.4.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.1.tgz", + "integrity": "sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.1.tgz", + "integrity": "sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.1.tgz", + "integrity": "sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/pino": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.4.0.tgz", + "integrity": "sha512-bk1ZMTwG/Vymx+zPoa28MhNzucFvXJWp3csrSuxuHPSMeUettaCoQ5Jvg07mtC4JGNpcfTwz41oVCnNgBK9+vA==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^1.0.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prettier": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz", + "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process-warning": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.1.tgz", + "integrity": "sha512-oCkNVj4OKnKEcocVBcDfiIugN1/cE6rgfT3nCtzeqZPA8+rIxMaQ0xXzIkGP8Gj2qUaP1UR7uhJ1VD8lUCpzCg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "license": "MIT" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ret": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.1.tgz", + "integrity": "sha512-Z/nyvABwcRnRD2mMP7Kz6G1dFZZqX1mNErOnxF42OCQKE07b9k9hK9SQxHL3LFEZUdzQaLM4dId1E/E6Vm4WhA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.2.tgz", + "integrity": "sha512-tAtCGFPPP02VroDVzp4daGi6bC7jWG+41D47GKf18FeH/ubvKQHT9VmhAkvtjNOmJwqtApzWeUpEhkyEYULvmw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.1.tgz", + "integrity": "sha512-lnd81MQEZKjhW/Gd2TQqdd4XP6QYNdDW/EP+I7nTrFHP9y2LmaGPIHk9xsnzcOiEr1c39c/e84xFgGviQKBTRw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.2.2.tgz", + "integrity": "sha512-df017qMeYuXo4emL09nlyMpi2i2MtEIKS/6jtXH3vmjFgIRsOi852/5RmhJ4EkvN3ZF8jXadM/EkTPXgc8yPIw==", + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/string-width": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/temporal-polyfill": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-1.0.6.tgz", + "integrity": "sha512-tYyEry1HLGUDbm2356W3iYD0OZdo7rlP7h98Zr/EnjqLZQwTAfcBDUvJnJbfA15v/vQad57hJZScG5O0W2oKEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "temporal-spec": "1.0.1", + "temporal-utils": "1.0.3" + } + }, + "node_modules/temporal-spec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-1.0.1.tgz", + "integrity": "sha512-wxVoanmDeavXie1vu2JaQ3WIc3JZnWAOYFBsJyATaVsXsycKYUflGsyBmrRSnoCpZJpwPyr38VpgSUlQ8CbFxg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/temporal-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/temporal-utils/-/temporal-utils-1.0.3.tgz", + "integrity": "sha512-9jbTSX3HvkOWvDpNpFzwX/d1mkCrgQgwLmVTOEiuocZGKHsrQJtttGZFmybJ2zYP5zCSZuU5ZyeS8+eOEPABiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/thread-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", + "license": "MIT", + "dependencies": { + "real-require": "^1.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/toad-cache": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.4.tgz", + "integrity": "sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/tsx": { + "version": "4.23.15", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz", + "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-fest": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.10.0.tgz", + "integrity": "sha512-NoSdpq/WEiAg5sjmBkmV/hfxv6HJH4NqPNrqjtSO5CwRmpsDfaf4begxW34KdJykH/l1yHtwBWQkCRdoXO8mPA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.4.tgz", + "integrity": "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js-replace": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.2.tgz", + "integrity": "sha512-VeBBt/nMxedczht3KDL2yBBMJqIxpCQLMZp3UJ1otcWwUHdQW4IiyHpJivExzSK+FdxTowRfGL4FmwDqH9+S8Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-jsonrpc": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.3.tgz", + "integrity": "sha512-qETYf9PCl1lVfwR+oBmMfc4vP2nqLNA35ABxorw3j06+xlKRgDVxbLgKm4Y/xPL0YWdeDjLoI4ImICIT3nB0zQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-10.1.2.tgz", + "integrity": "sha512-pXPWde3RytdeBVEs6PulOOT5OpeXC5GkWRpcT11f5rNn41jviQsAYPyAqJ/0oVGETQLKPJtYbHNiMPlfLb0NTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.18.4" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.18.4.tgz", + "integrity": "sha512-CCXbyr99dafRJzJH/0Ryk7ly5T8XPmSR0//OuF2w4m+9JiOin7zcAT5iET8GRE7Z9taFBejb3GbM0a0mbjdbrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "9.0.3", + "vscode-languageserver-types": "3.18.4" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.16.tgz", + "integrity": "sha512-qeNEuOi9a3qkqRf7Gfez2WL2VwlBTLCNekHBGO9kxPVUp9XuY15yfTJ9jwvOwKI0I2MhUUfZq95NSp+LK1CLQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.18.4.tgz", + "integrity": "sha512-nQsacoiijvl0fRf2q28uFDrAVwRhdCa3QveiF7/CX0r18mLR5FU/1WskSsCKOjmEbtteKUnY8x8Yzxop+k0zwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/yargs": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.2.0.tgz", + "integrity": "sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/zod": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/examples/head-to-head/baseline/package.json b/examples/head-to-head/baseline/package.json new file mode 100644 index 0000000000..b6bc928511 --- /dev/null +++ b/examples/head-to-head/baseline/package.json @@ -0,0 +1,19 @@ +{ + "name": "h2h-baseline", + "private": true, + "type": "module", + "dependencies": { + "fastify": "5.12.7", + "pg": "8.23.1" + }, + "devDependencies": { + "@types/node": "22.20.5", + "@types/pg": "8.23.1", + "@typespec/compiler": "1.17.0", + "@typespec/http": "1.17.0", + "@typespec/openapi3": "1.17.0", + "openapi-typescript": "7.13.0", + "tsx": "4.23.15", + "typescript": "5.9.3" + } +} diff --git a/examples/head-to-head/baseline/prompts/welcome/greeting.jinja b/examples/head-to-head/baseline/prompts/welcome/greeting.jinja new file mode 100644 index 0000000000..d924070c2c --- /dev/null +++ b/examples/head-to-head/baseline/prompts/welcome/greeting.jinja @@ -0,0 +1 @@ +Write a short, friendly welcome note for {{ name }} <{{ email }}>, who just joined on the {{ plan }} plan. diff --git a/examples/head-to-head/baseline/requirements.in b/examples/head-to-head/baseline/requirements.in new file mode 100644 index 0000000000..263c68626b --- /dev/null +++ b/examples/head-to-head/baseline/requirements.in @@ -0,0 +1,6 @@ +datamodel-code-generator[black,isort]==0.87.0 +jinja2 +mypy +psycopg[binary] +pydantic +pytest diff --git a/examples/head-to-head/baseline/requirements.txt b/examples/head-to-head/baseline/requirements.txt new file mode 100644 index 0000000000..1797e074c4 --- /dev/null +++ b/examples/head-to-head/baseline/requirements.txt @@ -0,0 +1,85 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile requirements.in --python-version 3.12 -o requirements.txt +annotated-types==0.8.0 + # via pydantic +argcomplete==3.7.2 + # via datamodel-code-generator +ast-serialize==0.13.1 + # via mypy +black==26.10.1 + # via datamodel-code-generator +click==8.5.0 + # via black +datamodel-code-generator==0.87.0 + # via -r requirements.in +genson==1.4.0 + # via datamodel-code-generator +inflect==7.6.0 + # via datamodel-code-generator +iniconfig==2.3.1 + # via pytest +isort==8.0.1 + # via datamodel-code-generator +jaraco-functools==4.6.0 + # via inflect +jinja2==3.1.6 + # via + # -r requirements.in + # datamodel-code-generator +librt==0.16.0 + # via mypy +markupsafe==3.0.4 + # via jinja2 +more-itertools==11.2.1 + # via + # inflect + # jaraco-functools +mypy==2.4.1 + # via -r requirements.in +mypy-extensions==1.1.0 + # via + # black + # mypy +packaging==26.3 + # via + # black + # pytest +pathspec==1.1.1 + # via + # black + # mypy +platformdirs==4.14.0 + # via black +pluggy==1.7.0 + # via pytest +psycopg==3.3.6 + # via -r requirements.in +psycopg-binary==3.3.6 + # via psycopg +pydantic==2.14.2 + # via + # -r requirements.in + # datamodel-code-generator +pydantic-core==2.50.2 + # via pydantic +pygments==2.21.0 + # via pytest +pytest==9.2.0 + # via -r requirements.in +pytokens==0.4.1 + # via black +pyyaml==6.0.3 + # via datamodel-code-generator +typeguard==4.6.0 + # via inflect +typing-extensions==4.16.0 + # via + # datamodel-code-generator + # mypy + # psycopg + # pydantic + # pydantic-core + # typeguard + # typing-inspection +typing-inspection==0.4.4 + # via pydantic diff --git a/examples/head-to-head/baseline/smoke.sh b/examples/head-to-head/baseline/smoke.sh new file mode 100755 index 0000000000..5860fb74c5 --- /dev/null +++ b/examples/head-to-head/baseline/smoke.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# End-to-end probe on a database that already has the committed migrations applied: +# create a customer through the TypeScript API, then render its prompt in the Python worker. +set -euo pipefail +cd "$(dirname "$0")" +export PATH="$PWD/.venv/bin:$PATH" PYTHONPATH="$PWD" +export DATABASE_URL="postgresql://postgres:h2h@127.0.0.1:${H2H_PG_PORT}/${H2H_DB}" +id=$(npx tsx api/smoke.ts) +prompt=$(python -m worker.main "$id") +case "$prompt" in + *"Ada Lovelace"*) ;; + *) echo "probe: the rendered prompt does not contain the customer's name: $prompt"; exit 1 ;; +esac diff --git a/examples/head-to-head/baseline/spec/main.tsp b/examples/head-to-head/baseline/spec/main.tsp new file mode 100644 index 0000000000..e10df49592 --- /dev/null +++ b/examples/head-to-head/baseline/spec/main.tsp @@ -0,0 +1,49 @@ +import "@typespec/http"; +import "@typespec/openapi3"; + +using TypeSpec.Http; + +@service(#{ title: "Customer API" }) +namespace CustomerApi; + +enum Plan { + free, + pro, +} + +model Customer { + id: int64; + + @maxLength(320) + email: string; + + @maxLength(200) + name?: string; + + plan: Plan; + createdAt: utcDateTime; +} + +model CreateCustomer { + @minLength(1) + @maxLength(320) + email: string; + + @maxLength(200) + name?: string; + + plan?: Plan = Plan.free; +} + +// The payload the LLM worker fills the welcome prompt with. +model WelcomePayload is PickProperties; + +@route("/customers") +interface Customers { + @get list(@query email?: string): Customer[]; + @get read(@path id: int64): Customer | NotFoundResponse; + @post create(@body body: CreateCustomer): { + @statusCode _: 201; + @body body: Customer; + }; +} diff --git a/examples/head-to-head/baseline/tests/__init__.py b/examples/head-to-head/baseline/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/head-to-head/baseline/tests/test_prompts.py b/examples/head-to-head/baseline/tests/test_prompts.py new file mode 100644 index 0000000000..4e289cf165 --- /dev/null +++ b/examples/head-to-head/baseline/tests/test_prompts.py @@ -0,0 +1,8 @@ +from generated.models import Plan, WelcomePayload +from worker.prompts import render_welcome + + +def test_welcome_greeting_renders_every_variable() -> None: + # StrictUndefined (see worker/prompts.py) raises on any variable the payload lacks. + out = render_welcome(WelcomePayload(name="Ada Lovelace", email="ada@example.test", plan=Plan.pro)) + assert "Ada Lovelace" in out and "ada@example.test" in out diff --git a/examples/head-to-head/baseline/tspconfig.yaml b/examples/head-to-head/baseline/tspconfig.yaml new file mode 100644 index 0000000000..2e0ea7037c --- /dev/null +++ b/examples/head-to-head/baseline/tspconfig.yaml @@ -0,0 +1,8 @@ +emit: + - "@typespec/openapi3" +options: + "@typespec/openapi3": + emitter-output-dir: "{project-root}/generated" + file-type: yaml + openapi-versions: + - 3.1.0 diff --git a/examples/head-to-head/baseline/worker/__init__.py b/examples/head-to-head/baseline/worker/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/head-to-head/baseline/worker/main.py b/examples/head-to-head/baseline/worker/main.py new file mode 100644 index 0000000000..078a90b859 --- /dev/null +++ b/examples/head-to-head/baseline/worker/main.py @@ -0,0 +1,27 @@ +import os +import sys + +import psycopg + +from generated.models import Plan, WelcomePayload +from worker.prompts import render_welcome + + +def load_payload(conn: psycopg.Connection, customer_id: int) -> WelcomePayload: + row = conn.execute( + "SELECT name, email, plan FROM customers WHERE id = %s", (customer_id,) + ).fetchone() + if row is None: + raise LookupError(f"no customer {customer_id}") + name, email, plan = row + return WelcomePayload(name=name, email=email, plan=Plan(plan)) + + +def main() -> None: + with psycopg.connect(os.environ["DATABASE_URL"]) as conn: + # A real worker would send this prompt to an LLM; the demo prints it. + print(render_welcome(load_payload(conn, int(sys.argv[1])))) + + +if __name__ == "__main__": + main() diff --git a/examples/head-to-head/baseline/worker/prompts.py b/examples/head-to-head/baseline/worker/prompts.py new file mode 100644 index 0000000000..33d34ca4aa --- /dev/null +++ b/examples/head-to-head/baseline/worker/prompts.py @@ -0,0 +1,15 @@ +from pathlib import Path + +import jinja2 + +from generated.models import WelcomePayload + +_env = jinja2.Environment( + loader=jinja2.FileSystemLoader(Path(__file__).parent.parent / "prompts"), + undefined=jinja2.StrictUndefined, + keep_trailing_newline=True, +) + + +def render_welcome(payload: WelcomePayload) -> str: + return _env.get_template("welcome/greeting.jinja").render(**payload.model_dump(mode="json")) diff --git a/examples/head-to-head/harness/lines.py b/examples/head-to-head/harness/lines.py new file mode 100644 index 0000000000..5c276c141d --- /dev/null +++ b/examples/head-to-head/harness/lines.py @@ -0,0 +1,108 @@ +"""Count the lines each setup makes you write, by one rule on both sides. + +The rule: every file under a setup directory is committed source unless it is a +dependency directory, a cache, a lock file or machine-generated output (the GENERATED +list below). Each remaining file falls in exactly one category; a file that matches no +category is an error, so nothing can escape the count. Blank lines and comment-only +lines are not counted. +""" +from __future__ import annotations + +import fnmatch +from pathlib import Path + +SKIP_DIRS = {"node_modules", ".venv", "__pycache__", ".mypy_cache", ".pytest_cache", ".ci-logs"} + +# Machine-written or machine-resolved: not counted, and listed so the reader can see them. +GENERATED = { + "baseline": [ + "generated/*", + "atlas/migrations/*", + "contract/released/openapi.yaml", + "package-lock.json", + "requirements.txt", + ], + "metaobjects": [ + "api/src/generated/*", + "worker/generated/*", + ".metaobjects/migrations/*", + ".metaobjects/.gen-state/*", + "package-lock.json", + "requirements.txt", + ], +} + +# Probe files are the same in both setups (the end-to-end smoke probe). +PROBE = ["smoke.sh", "api/smoke.ts"] + +CATEGORIES = { + "baseline": { + "model": ["spec/main.tsp", "atlas/schema.sql"], + "config": ["tspconfig.yaml", "atlas.hcl", "mypy.ini", "api/tsconfig.json"], + "checks": ["ci.sh", "gen.sh", "atlas.sh", "db-apply.sh", "live-drift.sh", "tests/*"], + "app": ["api/src/*", "worker/*", "prompts/*"], + "manifest": ["package.json", "requirements.in"], + "ignore": [".gitignore"], + }, + "metaobjects": { + "model": ["metaobjects/*"], + "config": [ + "metaobjects.config.ts", + "metaobjects.config.yaml", + ".metaobjects/config.json", + "mypy.ini", + "api/tsconfig.json", + ], + "checks": ["ci.sh", "db-apply.sh", "live-drift.sh"], + "app": ["api/src/*", "worker/*", "prompts/*"], + "manifest": ["package.json", "requirements.in"], + "ignore": [".gitignore"], + }, +} +LINE_COMMENT = {".sh": "#", ".py": "#", ".yaml": "#", ".yml": "#", ".hcl": "#", ".ini": "#", + ".sql": "--", ".ts": "//", ".tsp": "//"} + + +def _match(rel: str, patterns: list[str]) -> bool: + return any(fnmatch.fnmatch(rel, p) for p in patterns) + + +def count_file(path: Path) -> int: + marker = LINE_COMMENT.get(path.suffix) + n = 0 + for raw in path.read_text(encoding="utf-8").splitlines(): + line = raw.strip() + if not line or (marker and line.startswith(marker)): + continue + n += 1 + return n + + +def count_setup(root: Path, setup: str) -> dict: + cats = CATEGORIES[setup] + totals = {c: 0 for c in cats if c != "ignore"} + files: dict[str, list[tuple[str, int]]] = {c: [] for c in totals} + generated: list[str] = [] + probe: list[tuple[str, int]] = [] + for path in sorted(root.rglob("*")): + if not path.is_file() or any(part in SKIP_DIRS for part in path.relative_to(root).parts): + continue + rel = path.relative_to(root).as_posix() + if _match(rel, GENERATED[setup]): + generated.append(rel) + elif rel in PROBE: + probe.append((rel, count_file(path))) + else: + for cat, patterns in cats.items(): + if _match(rel, patterns): + if cat != "ignore": + n = count_file(path) + totals[cat] += n + files[cat].append((rel, n)) + break + else: + raise SystemExit(f"lines.py: {setup}/{rel} matches no category; add it to CATEGORIES or GENERATED") + gen_lines = sum(len((root / g).read_text(encoding="utf-8").splitlines()) for g in generated + if not g.endswith(("package-lock.json", "requirements.txt"))) + return {"totals": totals, "files": files, "generated_files": len(generated), + "generated_lines": gen_lines, "probe": probe} diff --git a/examples/head-to-head/harness/run.py b/examples/head-to-head/harness/run.py new file mode 100644 index 0000000000..73e04caa69 --- /dev/null +++ b/examples/head-to-head/harness/run.py @@ -0,0 +1,768 @@ +#!/usr/bin/env python3 +"""Run every drift scenario against both setups and write SCORECARD.md. + +Nothing in the scorecard's result cells is typed by hand: each one is computed from the +exit codes of the checks the scenario ran. Standard library only (Python 3.9+). +""" +from __future__ import annotations + +import argparse +import atexit +import json +import os +import platform +import re +import shutil +import subprocess +import sys +import tempfile +import time +import uuid +from dataclasses import dataclass, field +from datetime import datetime, timezone +from pathlib import Path +from typing import Callable + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +import lines # noqa: E402 + +ROOT = Path(__file__).resolve().parent.parent +SETUPS = ("baseline", "metaobjects") +LABEL = {"baseline": "DIY baseline", "metaobjects": "MetaObjects"} +PG_IMAGE = "postgres:16" +ATLAS_IMAGE = "arigaio/atlas:1.3.6-community" +OASDIFF_IMAGE = "tufin/oasdiff:v1.35.0" +TIMEOUT = 600 + + +def log(msg: str) -> None: + print(f"[h2h] {msg}", flush=True) + + +# --------------------------------------------------------------------------- shell + + +def sh(cmd: str | list[str], cwd: Path, env: dict[str, str] | None = None, + timeout: int = TIMEOUT) -> subprocess.CompletedProcess: + full_env = {**os.environ, **(env or {})} + return subprocess.run(cmd, cwd=cwd, env=full_env, shell=isinstance(cmd, str), text=True, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=timeout) + + +# --------------------------------------------------------------------------- postgres + + +class Postgres: + def __init__(self) -> None: + self.name = f"h2h-pg-{uuid.uuid4().hex[:8]}" + out = subprocess.run( + ["docker", "run", "-d", "--rm", "--name", self.name, "-e", "POSTGRES_PASSWORD=h2h", + "-p", "127.0.0.1::5432", PG_IMAGE], text=True, capture_output=True) + if out.returncode != 0: + raise SystemExit(f"could not start Postgres:\n{out.stderr}") + atexit.register(self.stop) + port = subprocess.run(["docker", "port", self.name, "5432/tcp"], text=True, capture_output=True) + self.port = port.stdout.strip().rsplit(":", 1)[1] + for _ in range(120): + logs = subprocess.run(["docker", "logs", self.name], text=True, capture_output=True) + ready = (logs.stdout + logs.stderr).count("ready to accept connections") + if ready >= 2 and self.psql("select 1").returncode == 0: + return + time.sleep(0.5) + raise SystemExit("Postgres did not become ready") + + def psql(self, sql: str, db: str = "postgres") -> subprocess.CompletedProcess: + return subprocess.run(["docker", "exec", self.name, "psql", "-U", "postgres", "-d", db, + "-v", "ON_ERROR_STOP=1", "-tAc", sql], text=True, capture_output=True) + + def fresh_db(self, name: str) -> str: + r = self.psql(f'CREATE DATABASE "{name}"') + if r.returncode != 0: + raise SystemExit(f"create database {name}: {r.stderr}") + return name + + def stop(self) -> None: + subprocess.run(["docker", "rm", "-f", self.name], capture_output=True) + + def env(self, **extra: str) -> dict[str, str]: + return {"H2H_PG_CONTAINER": self.name, "H2H_PG_PORT": self.port, **extra} + + +# --------------------------------------------------------------------------- workdir + + +class Work: + """A throwaway copy of one setup, committed as the base branch, where a scenario edits.""" + + def __init__(self, setup: str, scenario: str, pg: Postgres, tmp: Path) -> None: + self.setup, self.scenario, self.pg = setup, scenario, pg + self.path = tmp / f"{setup}-{scenario}" + src = ROOT / setup + shutil.copytree(src, self.path, symlinks=True, ignore=shutil.ignore_patterns( + "node_modules", ".venv", "__pycache__", ".mypy_cache", ".pytest_cache", ".ci-logs")) + for dep in ("node_modules", ".venv"): + (self.path / dep).symlink_to(src / dep) + git = ["git", "-c", "user.name=h2h", "-c", "user.email=h2h@example.test"] + self.git = git + subprocess.run(["git", "init", "-q", "-b", "main"], cwd=self.path, check=True) + subprocess.run([*git, "add", "-A"], cwd=self.path, check=True) + subprocess.run([*git, "commit", "-qm", "base"], cwd=self.path, check=True) + self.dev_log: list[dict] = [] + self.uid = f"{setup[:2]}_{scenario.lower()}" + + def env(self, **extra: str) -> dict[str, str]: + return self.pg.env(**extra) + + def edit(self, rel: str, old: str, new: str) -> None: + p = self.path / rel + text = p.read_text(encoding="utf-8") + if text.count(old) != 1: + raise SystemExit(f"{self.setup}/{rel}: expected exactly one match for {old!r}, found {text.count(old)}") + p.write_text(text.replace(old, new), encoding="utf-8") + + def dev(self, cmd: str) -> None: + """A command the developer runs while making the change (their own generators).""" + r = sh(cmd, self.path, self.env()) + self.dev_log.append({"cmd": cmd, "rc": r.returncode, "output": r.stdout}) + + def commit(self) -> None: + subprocess.run([*self.git, "add", "-A"], cwd=self.path, check=True) + subprocess.run([*self.git, "commit", "-qm", "change", "--allow-empty"], cwd=self.path, check=True) + + def db(self, tag: str) -> str: + return self.pg.fresh_db(f"{self.uid}_{tag}") + + +# --------------------------------------------------------------------------- scenarios + + +@dataclass +class Scenario: + id: str + title: str + what: str + kind: str # "pr" runs the pull-request gate; "live" runs the live-database drift check + change: dict[str, Callable[[Work], None]] = field(default_factory=dict) + + +def no_change(w: Work) -> None: + pass + + +def s1_baseline(w: Work) -> None: + w.edit("spec/main.tsp", " id: int64;\n\n @maxLength(320)\n email: string;", + " id: int64;\n\n @maxLength(320)\n contactEmail: string;") + w.edit("spec/main.tsp", " @minLength(1)\n @maxLength(320)\n email: string;", + " @minLength(1)\n @maxLength(320)\n contactEmail: string;") + w.edit("spec/main.tsp", '"name" | "email" | "plan"', '"name" | "contactEmail" | "plan"') + w.dev("./gen.sh") + + +def s1_metaobjects(w: Work) -> None: + w.edit("metaobjects/meta.crm.yaml", "name: email, maxLength: 320", "name: contactEmail, maxLength: 320") + w.edit("metaobjects/meta.crm.yaml", "extends: Customer.email", "extends: Customer.contactEmail") + w.dev("npx meta gen") + w.dev(".venv/bin/metaobjects gen") + w.dev("npx meta migrate --slug rename-email --rename-column customers.email=contact_email") + + +def s2_baseline(w: Work) -> None: + w.edit("spec/main.tsp", " plan: Plan;\n createdAt", " plan: Plan;\n\n @maxLength(2)\n country: string;\n\n createdAt") + w.edit("spec/main.tsp", " plan?: Plan = Plan.free;", " plan?: Plan = Plan.free;\n\n @maxLength(2)\n country: string;") + w.dev("./gen.sh") + + +def s2_metaobjects(w: Work) -> None: + w.edit("metaobjects/meta.crm.yaml", " - field.timestamp:", + " - field.string: { name: country, maxLength: 2, required: true }\n - field.timestamp:") + w.dev("npx meta gen") + w.dev(".venv/bin/metaobjects gen") + w.dev("npx meta migrate --slug add-country") + + +def s3(w: Work) -> None: + w.edit("prompts/welcome/greeting.jinja", "{{ name }}", "{{ fullName }}") + + +def s4_baseline(w: Work) -> None: + w.edit("spec/main.tsp", " plan: Plan;\n createdAt", " plan: Plan;\n\n @maxLength(100)\n nickname?: string;\n\n createdAt") + + +def s4_metaobjects(w: Work) -> None: + w.edit("metaobjects/meta.crm.yaml", " - field.timestamp:", + " - field.string: { name: nickname, maxLength: 100 }\n - field.timestamp:") + + +def s5(w: Work) -> None: # the edit happens in the database, see run_scenario + pass + + +def s6_baseline(w: Work) -> None: + w.edit("spec/main.tsp", " @maxLength(200)\n name?: string;\n\n plan?: Plan = Plan.free;", + " @maxLength(200)\n name: string;\n\n plan?: Plan = Plan.free;") + w.dev("./gen.sh") + + +def s6_metaobjects(w: Work) -> None: + w.edit("metaobjects/meta.crm.yaml", "{ name: name, maxLength: 200 }", "{ name: name, maxLength: 200, required: true }") + w.dev("npx meta gen") + w.dev(".venv/bin/metaobjects gen") + w.dev("npx meta migrate --slug name-required --allow nullable-to-not-null") + + +SCENARIOS = [ + Scenario("S0", "Control: no change", "Nothing is edited. Every check must pass, so a red cell elsewhere is not noise from the setup itself.", + "pr", {"baseline": no_change, "metaobjects": no_change}), + Scenario("S1", "Rename a field", "`Customer.email` becomes `contactEmail` in the place each setup treats as its source of truth, then that setup's own generators run. The prompt payload, the database column and the hand-written code are left as they were.", + "pr", {"baseline": s1_baseline, "metaobjects": s1_metaobjects}), + Scenario("S2", "Add a required field", "`Customer` gains a required `country` (2 characters). Generators run; nothing else is touched.", + "pr", {"baseline": s2_baseline, "metaobjects": s2_metaobjects}), + Scenario("S3", "Change a prompt variable", "The prompt template is edited to use `{{ fullName }}`, which the payload does not have. Nothing is regenerated.", + "pr", {"baseline": s3, "metaobjects": s3}), + Scenario("S4", "Skip a regen", "An optional `nickname` field is added to the model and no generator is run, so the committed generated files are stale.", + "pr", {"baseline": s4_baseline, "metaobjects": s4_metaobjects}), + Scenario("S5", "Edit the database by hand", "Someone runs `ALTER TABLE customers ADD COLUMN vip boolean NOT NULL DEFAULT false` on a live database. The check is each setup's live-database drift command, the one a team runs on a schedule or at deploy time.", + "live", {"baseline": s5, "metaobjects": s5}), + Scenario("S6", "Change a field on the partner API", "`name` becomes required on `POST /customers`: a request partners send today without it would be rejected. Generators run; the database follows where the setup derives it.", + "pr", {"baseline": s6_baseline, "metaobjects": s6_metaobjects}), +] + +# What a "check" is named in the scorecard grid, by setup, in run order. +CHECKS = { + "baseline": ["generated-in-sync", "api-compat", "atlas-migrations", "atlas-lint", "tsc", "mypy", "prompt-render"], + "metaobjects": ["meta-verify", "metaobjects-verify", "meta-verify-db", "tsc", "mypy"], +} +CHECK_NOTE = { + "generated-in-sync": "regenerate, then `git diff --exit-code`", + "api-compat": "oasdiff against the released OpenAPI document", + "atlas-migrations": "Atlas: migrations match `schema.sql`", + "atlas-lint": "Atlas migration lint", + "tsc": "TypeScript typecheck", + "mypy": "mypy with the pydantic plugin", + "prompt-render": "pytest render of the prompt with `StrictUndefined`", + "meta-verify": "`meta verify` (codegen, templates, requirement links)", + "metaobjects-verify": "`metaobjects verify` (Python codegen and templates)", + "meta-verify-db": "CI database from the migrations, then `meta verify --db`", +} + + +# --------------------------------------------------------------------------- results + + +# A check that asks "does this break callers?" rather than "do the layers agree?". The two are scored apart. +COMPAT_CHECKS = {"api-compat"} + + +@dataclass +class Outcome: + scenario: Scenario + setup: str + gate_rc: int | None = None + checks: dict[str, tuple[bool, str]] = field(default_factory=dict) # name -> (ok, evidence) + smoke: tuple[bool, str] | None = None + dev_notes: list[str] = field(default_factory=list) # what the setup's own generators said while the change was made + seconds: float = 0.0 + + @property + def layer_failed(self) -> list[str]: + return [n for n, (ok, _) in self.checks.items() if not ok and n not in COMPAT_CHECKS] + + @property + def compat_failed(self) -> bool: + return any(not ok for n, (ok, _) in self.checks.items() if n in COMPAT_CHECKS) + + @property + def has_compat_check(self) -> bool: + return any(n in COMPAT_CHECKS for n in CHECKS[self.setup]) + + @property + def clean_gate(self) -> bool: + return self.gate_rc in (0, None) + + +def clean(text: str, work: Path | None = None) -> str: + """Drop local paths from a log line so the scorecard carries none.""" + if work is not None: + text = text.replace(str(work), ".") + text = re.sub(r"/(?:home|Users|tmp|var)/[^\s:'\"]+", "", text) + text = re.sub(r"127\.0\.0\.1:\d+", "127.0.0.1:", text) + return re.sub(r"\s+", " ", text).strip() + + +NOISE_RE = re.compile(r"Notice:|Atlas edition|advanced database objects|install the non-community|curl -sSf|" + r"Or, visit|^notRepresented|FutureWarning|warn_deprecated|^\s*$|antiPatterns|status: ran|" + r"total: 0|rows: \[\]|^help\[|^exitCode|^summary|^verify\[|^\s+(templates|schema|codegen|deps|requirements|replay),|" + r"running --codegen|not run:|^command:|^dialect:|^displayUrl:|^dryRun:|^pending\[|^applied\[", re.IGNORECASE) +GENERIC_RE = re.compile(r"error|ERR_|fail|breaking|differ|drift|stale|undefined|not found|no longer|drop|would|mismatch|warn", + re.IGNORECASE) + + +def _lines(text: str) -> list[str]: + return [ln.rstrip() for ln in text.splitlines() if not NOISE_RE.search(ln)] + + +def _generic(text: str) -> str: + cand = _lines(text) + return next((ln for ln in cand if GENERIC_RE.search(ln)), cand[-1] if cand else "") + + +def _api_compat(text: str) -> str: + ls = [ln.strip() for ln in text.splitlines()] + head = next((ln for ln in ls if re.match(r"\d+ changes?:", ln)), "") + for i, ln in enumerate(ls): + m = re.match(r"error\s+\[(.+?)\]", ln) + if m: + where = ls[i + 1] if i + 1 < len(ls) else "" + what = ls[i + 2] if i + 2 < len(ls) else "" + return f"oasdiff: {head}; first: [{m.group(1)}] {where.removeprefix('in ')}: {what}" + return head or _generic(text) + + +def _in_sync(text: str) -> str: + files = re.findall(r"^diff --git a/(\S+) b/", text, re.MULTILINE) + return ("a fresh regeneration differs from the committed " + ", ".join(f"`{f}`" for f in files)) if files else _generic(text) + + +def _prompt_render(text: str) -> str: + errs = [ln[1:].strip() for ln in text.splitlines() if ln.startswith("E ")] + return " ".join(errs[:2]) if errs else _generic(text) + + +def _first_match(rx: str): + pat = re.compile(rx) + def f(text: str) -> str: + return next((ln for ln in text.splitlines() if pat.search(ln)), "") or _generic(text) + return f + + +def _meta_verify(text: str) -> str: + keep = [ln[6:].strip() for ln in text.splitlines() + if ln.startswith("meta:") and re.search(r"ERR_|drift|differ|~ |\+ |- ", ln) + and not re.search(r"not run|running --|compared against|no codegen drift|current", ln)] + return "; ".join(keep[:3]) if keep else _generic(text) + + +def _python_verify(text: str) -> str: + keep = [ln.strip() for ln in text.splitlines() if re.match(r"\s*(error:|drifted:)", ln)] + return " ".join(keep[:2]) if keep else _generic(text) + + +def _smoke(text: str) -> str: + ls = text.splitlines() + probe = next((ln for ln in ls if ln.startswith("probe:")), "") + if probe: + return probe + err = next((ln for ln in ls if re.match(r"(\w+\.)*\w*(Error|Exception)\b", ln) or ln.startswith("Error:")), "") + if err.startswith("pydantic"): # the field name is on the next lines + i = ls.index(err) + return " ".join(x.strip() for x in ls[i:i + 3] if x.strip()) + return err or _generic(text) + + +EXTRACT = { + "generated-in-sync": _in_sync, + "api-compat": _api_compat, + "tsc": _first_match(r"error TS"), + "mypy": _first_match(r"error:"), + "prompt-render": _prompt_render, + "meta-verify": _meta_verify, + "metaobjects-verify": _python_verify, + "meta-verify-db": _meta_verify, + "live-drift": lambda t: _meta_verify(t) if "meta:" in t else ( + "atlas would change the live database with: " + " ".join(ln.strip() for ln in t.splitlines() + if re.match(r"(ALTER|CREATE|DROP)", ln))), + "smoke": _smoke, +} + + +def evidence(name: str, text: str, work: Path) -> str: + out = EXTRACT.get(name, _generic)(text) + out = clean(out, work) + return out if len(out) <= 200 else out[:199] + "…" + + +def read_log(path: Path) -> str: + return path.read_text(encoding="utf-8", errors="replace") if path.exists() else "" + + +def run_scenario(sc: Scenario, setup: str, pg: Postgres, tmp: Path) -> Outcome: + t0 = time.time() + out = Outcome(sc, setup) + w = Work(setup, sc.id, pg, tmp) + sc.change[setup](w) + for d in w.dev_log: + warn = next((ln for ln in _lines(d["output"]) if re.search("warn", ln, re.I)), "") + note = evidence("dev", d["output"], w.path) if d["rc"] != 0 else clean(warn, w.path)[:200] + if d["rc"] != 0: + out.dev_notes.append(f"`{d['cmd']}` exited {d['rc']}: {note}") + elif note: + out.dev_notes.append(f"`{d['cmd']}` printed: {note}") + w.commit() + logs = w.path / ".ci-logs" + logs.mkdir(exist_ok=True) + for i, d in enumerate(w.dev_log): + (logs / f"authoring-{i + 1}.log").write_text(f"$ {d['cmd']}\n(exit {d['rc']})\n{d['output']}", encoding="utf-8") + + if sc.kind == "pr": + ci_db = w.db("ci") + r = sh("./ci.sh", w.path, w.env(H2H_DEV_DB=ci_db)) + out.gate_rc = r.returncode + for line in r.stdout.splitlines(): + m = re.match(r"CHECK (\S+) (ok|fail)", line) + if m: + name, ok = m.group(1), m.group(2) == "ok" + out.checks[name] = (ok, "" if ok else evidence(name, read_log(logs / f"{name}.log"), w.path)) + smoke_db = w.db("smoke") + app = sh("./db-apply.sh", w.path, w.env(H2H_DB=smoke_db, H2H_DEV_DB=smoke_db)) + (logs / "db-apply.log").write_text(app.stdout, encoding="utf-8") + if app.returncode != 0: + out.smoke = (False, "the committed migrations did not apply: " + evidence("dev", app.stdout, w.path)) + else: + s = sh("./smoke.sh", w.path, w.env(H2H_DB=smoke_db)) + (logs / "smoke.log").write_text(s.stdout, encoding="utf-8") + out.smoke = (s.returncode == 0, "" if s.returncode == 0 else evidence("smoke", s.stdout, w.path)) + else: # live-database drift + live_db = w.db("live") + app = sh("./db-apply.sh", w.path, w.env(H2H_DB=live_db, H2H_DEV_DB=w.db("dev"))) + if app.returncode != 0: + raise SystemExit(f"{setup}: could not provision the live database:\n{app.stdout}") + edit = pg.psql("ALTER TABLE customers ADD COLUMN vip boolean NOT NULL DEFAULT false", live_db) + if edit.returncode != 0: + raise SystemExit(f"hand edit failed: {edit.stderr}") + r = sh("./live-drift.sh", w.path, w.env(H2H_DB=live_db, H2H_DEV_DB=w.db("devcheck"))) + (logs / "live-drift.log").write_text(r.stdout, encoding="utf-8") + out.gate_rc = r.returncode + out.checks["live-drift"] = (r.returncode == 0, "" if r.returncode == 0 else evidence("live-drift", r.stdout, w.path)) + out.seconds = time.time() - t0 + return out + + +# --------------------------------------------------------------------------- versions + + +def first_line(cmd: str | list[str], cwd: Path) -> str: + try: + r = sh(cmd, cwd, timeout=120) + return next((ln.strip() for ln in r.stdout.splitlines() if ln.strip()), "?") + except Exception as exc: # noqa: BLE001 + return f"? ({exc.__class__.__name__})" + + +def npm_version(setup: str, pkg: str) -> str: + p = ROOT / setup / "node_modules" / pkg / "package.json" + import json as _json + return _json.loads(p.read_text())["version"] if p.exists() else "?" + + +def py_version(setup: str, dist: str) -> str: + return first_line([str(ROOT / setup / ".venv/bin/python"), "-c", + f"import importlib.metadata as m; print(m.version('{dist}'))"], ROOT) + + +def collect_versions(pg: Postgres) -> list[tuple[str, str, str]]: + rows: list[tuple[str, str, str]] = [] + add = lambda scope, tool, ver: rows.append((scope, tool, ver)) # noqa: E731 + add("both", "Node.js", first_line("node --version", ROOT)) + add("both", "npm", first_line("npm --version", ROOT)) + add("both", "Python (venv)", first_line([str(ROOT / "baseline/.venv/bin/python"), "--version"], ROOT).replace("Python ", "")) + add("both", "uv", first_line("uv --version", ROOT).replace("uv ", "")) + add("both", "Docker", first_line("docker --version", ROOT).replace("Docker version ", "").split(",")[0]) + add("both", "PostgreSQL", pg.psql("show server_version").stdout.strip() + f" ({PG_IMAGE})") + add("both", "TypeScript", npm_version("baseline", "typescript")) + add("both", "Fastify", npm_version("baseline", "fastify")) + add("both", "mypy", py_version("baseline", "mypy")) + add("both", "pydantic", py_version("baseline", "pydantic")) + add("baseline", "TypeSpec compiler", npm_version("baseline", "@typespec/compiler")) + add("baseline", "@typespec/openapi3", npm_version("baseline", "@typespec/openapi3")) + add("baseline", "openapi-typescript", npm_version("baseline", "openapi-typescript")) + add("baseline", "datamodel-code-generator", py_version("baseline", "datamodel-code-generator")) + add("baseline", "Atlas (community)", f"{ATLAS_IMAGE} ({first_line(['docker', 'run', '--rm', ATLAS_IMAGE, 'version'], ROOT)})") + add("baseline", "oasdiff", f"{OASDIFF_IMAGE} ({first_line(['docker', 'run', '--rm', OASDIFF_IMAGE, '--version'], ROOT)})") + add("baseline", "Jinja2", py_version("baseline", "jinja2")) + add("baseline", "pytest", py_version("baseline", "pytest")) + add("metaobjects", "@metaobjectsdev/cli", npm_version("metaobjects", "@metaobjectsdev/cli")) + add("metaobjects", "@metaobjectsdev/codegen-ts", npm_version("metaobjects", "@metaobjectsdev/codegen-ts")) + add("metaobjects", "metaobjects (PyPI)", py_version("metaobjects", "metaobjects")) + add("metaobjects", "Drizzle ORM", npm_version("metaobjects", "drizzle-orm")) + add("metaobjects", "zod", npm_version("metaobjects", "zod")) + return rows + + +# --------------------------------------------------------------------------- setup + + +def ensure_installed(setup: str) -> None: + d = ROOT / setup + if not (d / "node_modules").exists(): + log(f"{setup}: npm ci") + r = sh("npm ci --no-audit --no-fund --silent", d) + if r.returncode != 0: + raise SystemExit(r.stdout) + if not (d / ".venv").exists(): + log(f"{setup}: python venv + requirements.txt") + r = sh("uv venv --python 3.12 .venv -q && uv pip install -q --python .venv/bin/python -r requirements.txt", d) + if r.returncode != 0: + raise SystemExit(r.stdout) + + +def prefetch_images() -> None: + for image in (PG_IMAGE, ATLAS_IMAGE, OASDIFF_IMAGE): + if subprocess.run(["docker", "image", "inspect", image], capture_output=True).returncode != 0: + log(f"docker pull {image}") + subprocess.run(["docker", "pull", "-q", image], check=True, capture_output=True) + + +def check_shared_probe_files() -> None: + for rel in ("smoke.sh", "api/smoke.ts", "prompts/welcome/greeting.jinja"): + if (ROOT / "baseline" / rel).read_bytes() != (ROOT / "metaobjects" / rel).read_bytes(): + raise SystemExit(f"{rel} differs between the two setups; it must be identical") + + +# --------------------------------------------------------------------------- scorecard + + +def verdict(o: Outcome) -> str: + """One word for the log; the scorecard uses layers_cell/compat_cell.""" + if o.scenario.id == "S0": + return "no false alarm" if o.clean_gate and (o.smoke is None or o.smoke[0]) else "FALSE ALARM" + return "layers red" if o.layer_failed else ("partner break flagged" if o.compat_failed else "all green") + + +def probe(o: Outcome) -> str: + if o.smoke is None: + return "" + return "probe passed" if o.smoke[0] else "probe failed" + + +def layers_cell(o: Outcome) -> str: + if o.scenario.id == "S0": + return ("✅ **no false alarm**" if o.clean_gate and (o.smoke is None or o.smoke[0]) else "⚠️ **FALSE ALARM**") \ + + f" · exit {o.gate_rc} · {probe(o)}" + parts = ["🔴 **red**: " + ", ".join(f"`{n}`" for n in o.layer_failed)] if o.layer_failed else ["🟢 **green**"] + if o.smoke is not None: + parts.append(probe(o)) + return " · ".join(parts) + + +def compat_cell(o: Outcome) -> str: + if o.scenario.kind == "live": + return "n/a" + if not o.has_compat_check: + return "❌ **no such check**" + return "✅ **breaking change flagged**" if o.compat_failed else "🟢 none flagged" + + +def render_scorecard(outcomes: dict[tuple[str, str], Outcome], versions, counts, started: str, + scenarios: list[Scenario], seconds: float) -> str: + md: list[str] = [] + w = md.append + w("# Head-to-head scorecard") + w("") + w("> **Self-measured.** The MetaObjects maintainers wrote both setups and this harness. The baseline is built " + "to be a competent do-it-yourself stack, but we chose its tools, its scenarios and its app, and a reader " + "who disagrees with any of those should change them and rerun (`./run.sh`). Read " + "[README.md](README.md) for what this does and does not measure.") + w("") + w(f"Generated by `./run.sh` on {started}; {len(scenarios)} scenarios × 2 setups took {seconds:.0f} s. " + "Every result cell below is computed from the exit codes of the checks that ran; none is typed by hand.") + w("") + w("## Results") + w("") + w("Each scenario is one change a developer makes where the setup keeps its source of truth, followed by that " + "setup's own generators. The pull-request gate (for S5, the live-database check) is then run, and each check's " + "exit code is recorded. The **probe** is a happy-path run (create a customer through the API, render its prompt " + "in the worker) that is identical in both setups; it is informational and is not part of either gate. " + "Two different questions follow, scored separately.") + w("") + w("### Do the layers stay consistent?") + w("") + w("**red** means at least one check other than the partner-contract diff failed: the model, the generated code, " + "the database, the prompt and the hand-written code disagreed and a check said so. **green** means every such " + "check passed. It is not a claim that the change was safe; the probe column says whether the happy path still " + "works.") + w("") + w("| # | Scenario | DIY baseline | MetaObjects |") + w("|---|---|---|---|") + for sc in scenarios: + w(f"| {sc.id} | **{sc.title}** | {layers_cell(outcomes[(sc.id, 'baseline')])} | {layers_cell(outcomes[(sc.id, 'metaobjects')])} |") + w("") + w("### Does a breaking change for partners get flagged?") + w("") + w("The DIY baseline runs `oasdiff` against the OpenAPI document released to partners. MetaObjects 1.2.0 has no " + "equivalent check, so it cannot flag a change that is legal everywhere in the project but breaks an outside " + "caller. This is the row MetaObjects loses.") + w("") + w("| # | Scenario | DIY baseline | MetaObjects |") + w("|---|---|---|---|") + for sc in scenarios: + w(f"| {sc.id} | **{sc.title}** | {compat_cell(outcomes[(sc.id, 'baseline')])} | {compat_cell(outcomes[(sc.id, 'metaobjects')])} |") + w("") + graded = [s for s in scenarios if s.id != "S0"] + ids = lambda xs: ", ".join(s.id for s in xs) or "none" # noqa: E731 + for setup in SETUPS: + red = [s for s in graded if outcomes[(s.id, setup)].layer_failed] + w(f"- **{LABEL[setup]}**: layer checks went red on {len(red)} of {len(graded)} scenarios ({ids(red)}).") + flagged = [s for s in graded if outcomes[(s.id, "baseline")].compat_failed] + w(f"- **Partner-break check:** the baseline flagged {len(flagged)} ({ids(flagged)}); MetaObjects has none.") + base_only = [s for s in graded if outcomes[(s.id, "baseline")].layer_failed and not outcomes[(s.id, "metaobjects")].layer_failed] + mo_only = [s for s in graded if outcomes[(s.id, "metaobjects")].layer_failed and not outcomes[(s.id, "baseline")].layer_failed] + w(f"- **Where the baseline did better:** the partner-contract check ({ids(flagged)}); layer checks red only on the " + f"baseline side: {ids(base_only)}.") + w(f"- **Where MetaObjects did better:** layer checks red only on the MetaObjects side: {ids(mo_only)}.") + w("") + w("### What each scenario does") + w("") + for sc in scenarios: + w(f"- **{sc.id} {sc.title}.** {sc.what}") + w("") + w("### Which check said so") + w("") + for setup in SETUPS: + w(f"**{LABEL[setup]}**") + w("") + names = CHECKS[setup] + live = ["live-drift"] + w("| scenario | " + " | ".join(f"`{n}`" for n in names + live) + " |") + w("|---|" + "---|" * (len(names) + 1)) + for sc in scenarios: + o = outcomes[(sc.id, setup)] + row = [] + for n in names + live: + if n in o.checks: + row.append("✓" if o.checks[n][0] else "✗") + else: + row.append("·") + w(f"| {sc.id} | " + " | ".join(row) + " |") + w("") + w("✓ passed, ✗ failed, · not run in that scenario. " + "; ".join( + f"`{n}`: {CHECK_NOTE[n]}" for n in names) + ". `live-drift`: " + ( + "`atlas schema diff` of the live database against the migrations, wrapped to exit 1 on a non-empty plan" + if setup == "baseline" else "`meta verify --db` against the live database") + ".") + w("") + w("### What the failing checks printed") + w("") + w("The informative lines of each failing check's log, picked by a per-check extractor in `harness/run.py` " + "(paths removed); full logs are written to `.ci-logs/` in the scenario's working directory (`--keep DIR`). " + "*authoring* is what the setup's own generators said while the developer made the change, before CI.") + w("") + for sc in scenarios: + for setup in SETUPS: + o = outcomes[(sc.id, setup)] + items = [f"`{n}`: {ev or '(no output)'}" for n, (ok, ev) in o.checks.items() if not ok] + if o.smoke is not None and not o.smoke[0]: + items.append(f"smoke: {o.smoke[1] or '(no output)'}") + items += [f"authoring: {f}" for f in o.dev_notes] + if items: + w(f"- **{sc.id}, {LABEL[setup]}:**") + for it in items: + w(f" - {it}") + w("") + w("## Lines each setup needs") + w("") + w("One rule for both: every committed file in the setup's directory counts, minus lock files and " + "machine-generated output (listed below), minus the smoke probe (identical in both). Blank lines and " + "comment-only lines do not count. The numbers are from the files in this repository at the commit that " + "generated this page.") + w("") + w("| Category | DIY baseline | MetaObjects |") + w("|---|---:|---:|") + cats = [("model", "Model and schema (the sources of truth)"), ("config", "Tool configuration"), + ("checks", "Checks and glue scripts"), ("app", "Hand-written application code and prompt"), + ("manifest", "Dependency manifests")] + for key, label in cats: + w(f"| {label} | {counts['baseline']['totals'][key]} | {counts['metaobjects']['totals'][key]} |") + b = sum(counts['baseline']['totals'][k] for k in ("model", "config", "checks")) + m = sum(counts['metaobjects']['totals'][k] for k in ("model", "config", "checks")) + w(f"| **Model + configuration + checks** | **{b}** | **{m}** |") + b = sum(counts['baseline']['totals'].values()) + m = sum(counts['metaobjects']['totals'].values()) + w(f"| **All counted lines** | **{b}** | **{m}** |") + w("") + w(f"Not counted, machine-generated: baseline {counts['baseline']['generated_files']} files / " + f"{counts['baseline']['generated_lines']} lines (`generated/`, `atlas/migrations/`, the released OpenAPI copy); " + f"MetaObjects {counts['metaobjects']['generated_files']} files / {counts['metaobjects']['generated_lines']} lines " + "(`api/src/generated/`, `worker/generated/`, `.metaobjects/migrations/`, `.metaobjects/.gen-state/`).") + w("") + w("
Per-file counts") + w("") + for setup in SETUPS: + w(f"**{LABEL[setup]}**") + w("") + for key, label in cats: + for rel, n in counts[setup]["files"][key]: + w(f"- `{rel}` ({key}): {n}") + w("") + w("
") + w("") + w("## Versions") + w("") + w("| Scope | Tool | Version |") + w("|---|---|---|") + for scope, tool, ver in versions: + w(f"| {scope} | {tool} | {ver} |") + w("") + w(f"Platform: {platform.system()} {platform.machine()}.") + w("") + return "\n".join(md) + + +# --------------------------------------------------------------------------- main + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("scenarios", nargs="*", help="scenario ids to run (default: all), e.g. S1 S3") + ap.add_argument("--out", default=str(ROOT / "SCORECARD.md"), help="where to write the scorecard") + ap.add_argument("--json", help="also write raw results to this path") + ap.add_argument("--keep", help="run in this directory and keep every scenario's working copy and logs") + args = ap.parse_args() + + for tool in ("docker", "node", "npm", "uv", "git"): + if shutil.which(tool) is None: + raise SystemExit(f"missing prerequisite: {tool}") + if subprocess.run(["docker", "info"], capture_output=True).returncode != 0: + raise SystemExit("docker is installed but its daemon is not reachable") + wanted = [s for s in SCENARIOS if not args.scenarios or s.id in args.scenarios or s.id == "S0"] + + check_shared_probe_files() + for setup in SETUPS: + ensure_installed(setup) + prefetch_images() + counts = {s: lines.count_setup(ROOT / s, s) for s in SETUPS} + + t0 = time.time() + pg = Postgres() + log(f"Postgres {PG_IMAGE} up as {pg.name} on 127.0.0.1:{pg.port}") + versions = collect_versions(pg) + outcomes: dict[tuple[str, str], Outcome] = {} + keep = Path(args.keep).resolve() if args.keep else None + if keep: + keep.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory(prefix="h2h-") as tmpdir: + if keep: + tmpdir = str(keep) + for sc in wanted: + for setup in SETUPS: + log(f"{sc.id} {sc.title} / {setup}") + o = run_scenario(sc, setup, pg, Path(tmpdir)) + outcomes[(sc.id, setup)] = o + log(f" -> {verdict(o)} (gate exit {o.gate_rc}, red: {', '.join(o.layer_failed) or '-'}, partner break: {o.compat_failed}, " + f"smoke {'-' if o.smoke is None else ('ok' if o.smoke[0] else 'FAIL')}) {o.seconds:.0f}s") + card = render_scorecard(outcomes, versions, counts, + datetime.now(timezone.utc).strftime("%Y-%m-%d"), wanted, time.time() - t0) + Path(args.out).write_text(card, encoding="utf-8") + log(f"wrote {args.out}") + if args.json: + Path(args.json).write_text(json.dumps({f"{k[0]}/{k[1]}": { + "gate_rc": v.gate_rc, "checks": {n: ok for n, (ok, _) in v.checks.items()}, + "smoke": None if v.smoke is None else v.smoke[0], "dev_notes": v.dev_notes} + for k, v in outcomes.items()}, indent=2), encoding="utf-8") + control_ok = all(verdict(outcomes[("S0", s)]) == "no false alarm" for s in SETUPS) + if not control_ok: + log("the control scenario failed: a setup is red with no change, fix that before reading anything else") + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/examples/head-to-head/metaobjects/.gitignore b/examples/head-to-head/metaobjects/.gitignore new file mode 100644 index 0000000000..bcbcdfcb89 --- /dev/null +++ b/examples/head-to-head/metaobjects/.gitignore @@ -0,0 +1,8 @@ +node_modules +.venv +__pycache__/ +.mypy_cache/ +.pytest_cache/ +.ci-logs/ +.metaobjects/.gen-state/* +!.metaobjects/.gen-state/.hashes.json diff --git a/examples/head-to-head/metaobjects/.metaobjects/.gen-state/.hashes.json b/examples/head-to-head/metaobjects/.metaobjects/.gen-state/.hashes.json new file mode 100644 index 0000000000..3b426d7112 --- /dev/null +++ b/examples/head-to-head/metaobjects/.metaobjects/.gen-state/.hashes.json @@ -0,0 +1,7 @@ +{ + "api/src/generated/Customer.ts": "d9006b83967730e33605fca1f21440e2d854dcc8889e4bed47f33d606a957864", + "api/src/generated/WelcomePayload.ts": "791e8ce00f4737d5f9cea4ba3a53c801cfa873bff24cdb4a79e7a8f4f28ee7f6", + "worker/generated/Customer.py": "89650fb2d0fdd0d4f27611d31db55daf715de21b121cb36934646dbdccb033ee", + "worker/generated/WelcomePayload.py": "1a301352d76fac08c92f6515b95fd1a4c3bc5a7dec94d8a802d9ae358bafbfb0", + "worker/generated/__init__.py": "daa04cec586768866d0996c779f62073e6773fe63750ff668327991fac2fd0f5" +} diff --git a/examples/head-to-head/metaobjects/.metaobjects/config.json b/examples/head-to-head/metaobjects/.metaobjects/config.json new file mode 100644 index 0000000000..f57891137b --- /dev/null +++ b/examples/head-to-head/metaobjects/.metaobjects/config.json @@ -0,0 +1,5 @@ +{ + "schema_version": 1, + "sources": [], + "migrate": { "dialect": "postgres" } +} diff --git a/examples/head-to-head/metaobjects/.metaobjects/migrations/.schema.postgres.json b/examples/head-to-head/metaobjects/.metaobjects/migrations/.schema.postgres.json new file mode 100644 index 0000000000..167d841fa6 --- /dev/null +++ b/examples/head-to-head/metaobjects/.metaobjects/migrations/.schema.postgres.json @@ -0,0 +1,72 @@ +{ + "formatVersion": 4, + "snapshot": { + "tables": [ + { + "checks": [ + { + "expression": "\"plan\" IN ('free', 'pro')", + "name": "customers_plan_chk" + } + ], + "columns": [ + { + "default": { + "kind": "expr", + "value": "now()" + }, + "name": "created_at", + "nullable": false, + "sqlType": { + "kind": "timestamp", + "withTimezone": true + } + }, + { + "name": "email", + "nullable": false, + "sqlType": { + "kind": "text", + "maxLength": 320 + } + }, + { + "identity": "increment", + "name": "id", + "nullable": false, + "sqlType": { + "bits": 64, + "kind": "integer" + } + }, + { + "name": "name", + "nullable": true, + "sqlType": { + "kind": "text", + "maxLength": 200 + } + }, + { + "default": { + "kind": "literal", + "value": "free" + }, + "name": "plan", + "nullable": false, + "sqlType": { + "kind": "text" + } + } + ], + "foreignKeys": [], + "indexes": [], + "name": "customers", + "primaryKey": [ + "id" + ] + } + ], + "views": [] + } +} diff --git a/examples/head-to-head/metaobjects/.metaobjects/migrations/20261031030631-init/down.sql b/examples/head-to-head/metaobjects/.metaobjects/migrations/20261031030631-init/down.sql new file mode 100644 index 0000000000..f9fac6567a --- /dev/null +++ b/examples/head-to-head/metaobjects/.metaobjects/migrations/20261031030631-init/down.sql @@ -0,0 +1 @@ +DROP TABLE "customers"; diff --git a/examples/head-to-head/metaobjects/.metaobjects/migrations/20261031030631-init/up.sql b/examples/head-to-head/metaobjects/.metaobjects/migrations/20261031030631-init/up.sql new file mode 100644 index 0000000000..47dbb3328c --- /dev/null +++ b/examples/head-to-head/metaobjects/.metaobjects/migrations/20261031030631-init/up.sql @@ -0,0 +1,9 @@ +CREATE TABLE "customers" ( + "id" BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + "email" VARCHAR(320) NOT NULL, + "name" VARCHAR(200), + "plan" TEXT NOT NULL DEFAULT 'free', + "created_at" TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT "customers_pkey" PRIMARY KEY ("id"), + CONSTRAINT "customers_plan_chk" CHECK ((plan = ANY (ARRAY['free'::text, 'pro'::text]))) +); diff --git a/examples/head-to-head/metaobjects/api/smoke.ts b/examples/head-to-head/metaobjects/api/smoke.ts new file mode 100644 index 0000000000..36bd5fe44a --- /dev/null +++ b/examples/head-to-head/metaobjects/api/smoke.ts @@ -0,0 +1,20 @@ +// End-to-end probe, identical in both setups: create a customer through the API, +// read it back, print its id. +import { buildApp } from "./src/app"; + +const app = buildApp(process.env.DATABASE_URL ?? ""); +const created = await app.inject({ + method: "POST", + url: "/customers", + payload: { email: "ada@example.test", name: "Ada Lovelace", plan: "pro" }, +}); +if (created.statusCode !== 201) { + throw new Error(`POST /customers -> ${created.statusCode} ${created.body}`); +} +const { id } = created.json<{ id: number }>(); +const read = await app.inject({ method: "GET", url: `/customers/${id}` }); +if (read.statusCode !== 200 || read.json<{ email: string }>().email !== "ada@example.test") { + throw new Error(`GET /customers/${id} -> ${read.statusCode} ${read.body}`); +} +await app.close(); +console.log(id); diff --git a/examples/head-to-head/metaobjects/api/src/app.ts b/examples/head-to-head/metaobjects/api/src/app.ts new file mode 100644 index 0000000000..c50eebf8e3 --- /dev/null +++ b/examples/head-to-head/metaobjects/api/src/app.ts @@ -0,0 +1,33 @@ +import { eq } from "drizzle-orm"; +import { drizzle } from "drizzle-orm/node-postgres"; +import Fastify from "fastify"; +import pg from "pg"; +import { CustomerInsertSchema, customers } from "./generated/Customer"; + +export function buildApp(databaseUrl: string) { + const pool = new pg.Pool({ connectionString: databaseUrl }); + const db = drizzle(pool); + const app = Fastify(); + app.addHook("onClose", async () => pool.end()); + + app.get<{ Querystring: { email?: string } }>("/customers", async (req) => { + const { email } = req.query; + const query = db.select().from(customers).orderBy(customers.id); + return email === undefined ? query : query.where(eq(customers.email, email)); + }); + + app.get<{ Params: { id: string } }>("/customers/:id", async (req, reply) => { + const rows = await db.select().from(customers).where(eq(customers.id, Number(req.params.id))); + if (rows.length === 0) return reply.code(404).send(); + return rows[0]; + }); + + app.post("/customers", async (req, reply) => { + const parsed = CustomerInsertSchema.safeParse(req.body); + if (!parsed.success) return reply.code(400).send({ error: "invalid_request", issues: parsed.error.issues }); + const [created] = await db.insert(customers).values(parsed.data).returning(); + return reply.code(201).send(created); + }); + + return app; +} diff --git a/examples/head-to-head/metaobjects/api/src/generated/Customer.ts b/examples/head-to-head/metaobjects/api/src/generated/Customer.ts new file mode 100644 index 0000000000..cb2bcbf27a --- /dev/null +++ b/examples/head-to-head/metaobjects/api/src/generated/Customer.ts @@ -0,0 +1,179 @@ +// @generated by @metaobjectsdev/codegen-ts — hand edits are kept by a three-way merge on regeneration; meta verify --codegen checks only the generated parts. +// Source metadata: Customer (Customer) +// Extend in your own module (e.g. Customer.extra.ts) — nothing imports it for you. +import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; +import { + bigint, + check, + pgTable, + text, + timestamp, + varchar, +} from "drizzle-orm/pg-core"; +import { z } from "zod"; + +export const customers = pgTable( + "customers", + { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + email: varchar("email", { length: 320 }).notNull(), + name: varchar("name", { length: 200 }), + plan: text("plan", { enum: ["free", "pro"] as const }) + .notNull() + .default("free"), + createdAt: timestamp("created_at", { mode: "string", withTimezone: true }) + .notNull() + .$defaultFn(() => new Date().toISOString()), + }, + (table) => [check("customers_plan_chk", sql`plan IN ('free', 'pro')`)], +); +export type Customer = InferSelectModel; +export type CustomerInsert = InferInsertModel; +export type CustomerUpdate = Partial; +export type CustomerPlan = "free" | "pro"; +export const CustomerPlanEnum = z.enum(["free", "pro"]); +/** A field.timestamp instant in UTC (`YYYY-MM-DDTHH:MM:SS[.fff]Z`): a value already spelled so is kept + * as sent, an offset is applied, a zoneless value is UTC and a date alone is midnight UTC. + * SQLite/D1 compare timestamps as text, so instants are stored in UTC. Generated. */ +function utcIsoTimestamp(v: string): string { + if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/.test(v)) { + return v; + } + const m = + /^(\d{4}-\d{2}-\d{2})(?:[Tt ](\d{2}:\d{2})(?::(\d{2})(?:\.(\d+))?)?(?:([Zz])|([+-]\d{2}):?(\d{2})?)?)?$/.exec( + v, + ); + if (m === null) { + return v; + } + const t = Date.parse( + `${m[1]}T${m[2] ?? "00:00"}:${m[3] ?? "00"}.${(m[4] ?? "").slice(0, 3).padEnd(3, "0")}${ + m[6] === undefined ? "Z" : `${m[6]}:${m[7] ?? "00"}` + }`, + ); + return Number.isNaN(t) + ? v + : new Date(t).toISOString().replace(/\.?0+Z$/, "Z"); +} + +export const CustomerInsertSchema = z.object({ + email: z.string().min(1).max(320), + name: z.string().max(200).optional(), + plan: z.enum(["free", "pro"]).optional(), + createdAt: z + .string() + .optional() + .transform(() => new Date().toISOString()), +}); + +export const CustomerUpdateSchema = z.object({ + email: z.string().min(1).max(320).optional(), + name: z.string().max(200).optional().nullable(), + plan: z.enum(["free", "pro"]).optional(), +}); + +/** Typed create shape for Customer: the insert schema's INPUT (pre-transform) type. A + * renamed/dropped/misspelt field is a compile error at every `createCustomer` call site; + * the schema still validates at runtime. */ +export type CustomerCreate = z.input; + +/** Typed patch shape for Customer: every settable field, optional (FR-035 PATCH). A + * renamed/dropped field is a compile error at every `updateCustomer` call site. */ +export type CustomerPatch = z.input; + +/** Insert-shape for import / restore / replication of Customer: identical to + * CustomerInsertSchema, but the @autoSet timestamp columns are written VERBATIM + * (no create-time now() stamp) so the caller's original values are preserved. */ +export const CustomerInsertPreservingSchema = z.object({ + email: z.string().min(1).max(320), + name: z.string().max(200).optional(), + plan: z.enum(["free", "pro"]).optional(), + createdAt: z + .string() + .regex( + /^(?:\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|02-(?:0[1-9]|1\d|2[0-8]))|(?:\d{2}(?:0[48]|[2468][048]|[13579][26])|(?:[02468][048]|[13579][26])00)-02-29)(?:[Tt ](?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:[Zz]|[+-](?:[01]\d|2[0-3])(?::?[0-5]\d)?)?)?$/, + "must be an ISO 8601 timestamp (YYYY-MM-DD[THH:MM[:SS[.fff]]][Z|±HH:MM])", + ) + .transform(utcIsoTimestamp), +}); + +/** Typed input of `insertPreservingCustomer` — CustomerInsertPreservingSchema's pre-transform shape. */ +export type CustomerCreatePreserving = z.input< + typeof CustomerInsertPreservingSchema +>; +/** + * Metadata constants for Customer. + * + * Use these instead of magic strings so TS catches typos and refactors stay + * coherent. Each non-dollar-prefixed key is a per-field object carrying + * name, label, view, an htmlType where the field's view maps to one, and the + * RHF-shaped validation rules derived from the field's validator children. + * + * placeholder and helpText are NOT emitted — no provider registers an attribute + * for them, so nothing here could derive one. (htmlType is emitted, but derived + * from the view subtype; the @htmlType override was removed for the same reason.) + * useEntityForm still honours placeholder if you add it: this file is generated, + * hand edits inside it survive regeneration through the three-way merge, and the + * consumers read this const rather than the metadata. That is the intended way + * to set it. + * + * Typical usage with the metaobjects React form helper: + * + * import { useEntityForm } from '@metaobjectsdev/react'; + * const form = useEntityForm(Customer, CustomerInsertSchema); + * + */ +export const Customer = { + $entity: "Customer", + $table: "customers", + $path: "/customers", + id: { name: "id", label: "Id", view: "number", htmlType: "number" }, + email: { + name: "email", + label: "Email", + view: "text", + htmlType: "text", + rules: { + required: "Email is required", + maxLength: { value: 320, message: "Must be 320 characters or fewer" }, + }, + }, + name: { + name: "name", + label: "Name", + view: "text", + htmlType: "text", + rules: { + maxLength: { value: 200, message: "Must be 200 characters or fewer" }, + }, + }, + plan: { + name: "plan", + label: "Plan", + view: "dropdown", + rules: { required: "Plan is required" }, + options: ["free", "pro"] as const, + }, + createdAt: { + name: "createdAt", + label: "Created At", + view: "date", + htmlType: "datetime-local", + rules: { required: "Created At is required" }, + }, +} as const; +import type { FilterAllowlist } from "@metaobjectsdev/runtime-ts/drizzle-fastify"; + +export const CustomerFilterAllowlist = {} as const satisfies FilterAllowlist; +import type { SortAllowlist } from "@metaobjectsdev/runtime-ts/drizzle-fastify"; + +export const CustomerSortAllowlist = {} as const satisfies SortAllowlist; +export type CustomerFilter = { + limit?: number; + offset?: number; + sort?: string; + or?: CustomerFilter[]; + and?: CustomerFilter[]; +}; diff --git a/examples/head-to-head/metaobjects/api/src/generated/WelcomePayload.ts b/examples/head-to-head/metaobjects/api/src/generated/WelcomePayload.ts new file mode 100644 index 0000000000..0d2fc0da9a --- /dev/null +++ b/examples/head-to-head/metaobjects/api/src/generated/WelcomePayload.ts @@ -0,0 +1,50 @@ +// @generated by @metaobjectsdev/codegen-ts — hand edits are kept by a three-way merge on regeneration; meta verify --codegen checks only the generated parts. +// Source metadata: WelcomePayload (WelcomePayload) +// Extend in your own module (e.g. WelcomePayload.extra.ts) — nothing imports it for you. +import { z } from "zod"; + +export interface WelcomePayload { + name?: string | undefined; + email: string; + plan: Plan; +} +export type Plan = "free" | "pro"; +export const PlanEnum = z.enum(["free", "pro"]); +export const WelcomePayloadInsertSchema = z.object({ + name: z.string().max(200).optional(), + email: z.string().min(1).max(320), + plan: z.enum(["free", "pro"]).optional(), +}); +/** + * Metadata constants for the value object WelcomePayload: each field's name, label, view and the + * validation rules derived from its validator children (e.g. `WelcomePayload..rules.maxLength.value`). + */ +export const WelcomePayload = { + $entity: "WelcomePayload", + name: { + name: "name", + label: "Name", + view: "text", + htmlType: "text", + rules: { + maxLength: { value: 200, message: "Must be 200 characters or fewer" }, + }, + }, + email: { + name: "email", + label: "Email", + view: "text", + htmlType: "text", + rules: { + required: "Email is required", + maxLength: { value: 320, message: "Must be 320 characters or fewer" }, + }, + }, + plan: { + name: "plan", + label: "Plan", + view: "dropdown", + rules: { required: "Plan is required" }, + options: ["free", "pro"] as const, + }, +} as const; diff --git a/examples/head-to-head/metaobjects/api/tsconfig.json b/examples/head-to-head/metaobjects/api/tsconfig.json new file mode 100644 index 0000000000..e27d029d61 --- /dev/null +++ b/examples/head-to-head/metaobjects/api/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["./**/*.ts", "../generated/*.d.ts"] +} diff --git a/examples/head-to-head/metaobjects/ci.sh b/examples/head-to-head/metaobjects/ci.sh new file mode 100755 index 0000000000..b656ee2bfd --- /dev/null +++ b/examples/head-to-head/metaobjects/ci.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# The pull-request gate: every check runs, one "CHECK " line each, +# and the script exits 1 if any failed. +cd "$(dirname "$0")" +export PATH="$PWD/.venv/bin:$PATH" PYTHONPATH="$PWD" +CI_DB="postgresql://postgres:h2h@127.0.0.1:${H2H_PG_PORT}/${H2H_DEV_DB}" +mkdir -p .ci-logs +failed=0 +check() { + local name=$1; shift + if "$@" >".ci-logs/$name.log" 2>&1; then echo "CHECK $name ok"; else echo "CHECK $name fail"; failed=1; fi +} + +# Generated code, prompt templates and requirement links, TypeScript side then Python side. +check meta-verify npx meta verify +check metaobjects-verify metaobjects verify +# Provision a CI database from the committed migrations, then compare it with the model. +check meta-verify-db sh -c "npx meta migrate apply-pending --db '$CI_DB' && npx meta verify --db '$CI_DB'" +check tsc npx tsc -p api +check mypy mypy worker +exit $failed diff --git a/examples/head-to-head/metaobjects/db-apply.sh b/examples/head-to-head/metaobjects/db-apply.sh new file mode 100755 index 0000000000..175c6c2fe4 --- /dev/null +++ b/examples/head-to-head/metaobjects/db-apply.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# Apply the committed migrations to $H2H_DB. +set -euo pipefail +cd "$(dirname "$0")" +npx meta migrate apply-pending --db "postgresql://postgres:h2h@127.0.0.1:${H2H_PG_PORT}/${H2H_DB}" diff --git a/examples/head-to-head/metaobjects/live-drift.sh b/examples/head-to-head/metaobjects/live-drift.sh new file mode 100755 index 0000000000..1a48608bad --- /dev/null +++ b/examples/head-to-head/metaobjects/live-drift.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# Compare the live database $H2H_DB with the model. +set -euo pipefail +cd "$(dirname "$0")" +npx meta verify --db "postgresql://postgres:h2h@127.0.0.1:${H2H_PG_PORT}/${H2H_DB}" diff --git a/examples/head-to-head/metaobjects/metaobjects.config.ts b/examples/head-to-head/metaobjects/metaobjects.config.ts new file mode 100644 index 0000000000..35dd26b7cf --- /dev/null +++ b/examples/head-to-head/metaobjects/metaobjects.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "@metaobjectsdev/cli"; +import { entityFile } from "@metaobjectsdev/codegen-ts/generators"; + +export default defineConfig({ + outDir: "api/src/generated", + dialect: "postgres", + generators: [entityFile()], +}); diff --git a/examples/head-to-head/metaobjects/metaobjects.config.yaml b/examples/head-to-head/metaobjects/metaobjects.config.yaml new file mode 100644 index 0000000000..a950f5836f --- /dev/null +++ b/examples/head-to-head/metaobjects/metaobjects.config.yaml @@ -0,0 +1,6 @@ +metadata: metaobjects +templates: prompts +targets: + app: + outDir: worker/generated + generators: [entity] diff --git a/examples/head-to-head/metaobjects/metaobjects/meta.crm.yaml b/examples/head-to-head/metaobjects/metaobjects/meta.crm.yaml new file mode 100644 index 0000000000..8a18a78771 --- /dev/null +++ b/examples/head-to-head/metaobjects/metaobjects/meta.crm.yaml @@ -0,0 +1,26 @@ +metadata: + package: crm + children: + - object.entity: + name: Customer + children: + - source.rdb: { table: customers } + - field.long: { name: id } + - field.string: { name: email, maxLength: 320, required: true } + - field.string: { name: name, maxLength: 200 } + - field.enum: { name: plan, values: [free, pro], required: true, default: free } + - field.timestamp: { name: createdAt, required: true, autoSet: onCreate } + - identity.primary: { name: primary, fields: [id], generation: increment } + + # The payload the LLM worker fills the welcome prompt with: bound to Customer's fields. + - object.value: + name: WelcomePayload + children: + - field.string: { name: name, extends: Customer.name } + - field.string: { name: email, extends: Customer.email } + - field.enum: { name: plan, extends: Customer.plan } + + - template.prompt: + name: WelcomePrompt + payloadRef: WelcomePayload + textRef: welcome/greeting diff --git a/examples/head-to-head/metaobjects/mypy.ini b/examples/head-to-head/metaobjects/mypy.ini new file mode 100644 index 0000000000..beca05a70f --- /dev/null +++ b/examples/head-to-head/metaobjects/mypy.ini @@ -0,0 +1,9 @@ +[mypy] +python_version = 3.12 +plugins = pydantic.mypy +disallow_untyped_defs = True +mypy_path = . + +[pydantic-mypy] +init_forbid_extra = True +init_typed = True diff --git a/examples/head-to-head/metaobjects/package-lock.json b/examples/head-to-head/metaobjects/package-lock.json new file mode 100644 index 0000000000..9cdc772d72 --- /dev/null +++ b/examples/head-to-head/metaobjects/package-lock.json @@ -0,0 +1,2873 @@ +{ + "name": "h2h-metaobjects", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "h2h-metaobjects", + "dependencies": { + "@metaobjectsdev/runtime-ts": "1.2.0", + "drizzle-orm": "0.45.4", + "fastify": "5.12.7", + "pg": "8.23.1", + "zod": "4.6.7" + }, + "devDependencies": { + "@metaobjectsdev/cli": "1.2.0", + "@metaobjectsdev/codegen-ts": "1.2.0", + "@metaobjectsdev/metadata": "1.2.0", + "@types/node": "22.20.5", + "@types/pg": "8.23.1", + "tsx": "4.23.15", + "typescript": "5.9.3" + } + }, + "node_modules/@biomejs/biome": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.6.4.tgz", + "integrity": "sha512-8sKKwaNW4ot22awZ3lBjwWLHnUFXgXnrBPYS2gNlBAXRbDC++7kqjK2H7dR/FgCDtPvO1xfKpgEhJkGxAKYkxA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peer": true, + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-android-arm64": "2.6.4", + "@biomejs/cli-android-x64": "2.6.4", + "@biomejs/cli-darwin-arm64": "2.6.4", + "@biomejs/cli-darwin-x64": "2.6.4", + "@biomejs/cli-linux-arm64": "2.6.4", + "@biomejs/cli-linux-arm64-musl": "2.6.4", + "@biomejs/cli-linux-x64": "2.6.4", + "@biomejs/cli-linux-x64-musl": "2.6.4", + "@biomejs/cli-win32-arm64": "2.6.4", + "@biomejs/cli-win32-x64": "2.6.4" + } + }, + "node_modules/@biomejs/cli-android-arm64": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-android-arm64/-/cli-android-arm64-2.6.4.tgz", + "integrity": "sha512-jwrwigP4tBcHxzuV8izioX/BOUhJL/4e2rNSOE5nCjBkVC018LQAQUGiH+CNkFYvLtQ1dcZo5/xXERmaAGc7JQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-android-x64": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-android-x64/-/cli-android-x64-2.6.4.tgz", + "integrity": "sha512-HdGInPJ4nZFwHxzvEtcp2nmSBtUyJrurmtIK43upFckuS3+ZqVM05HtsxQWnqGsuy/pxckIAjyKWlEKRlBXUyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.6.4.tgz", + "integrity": "sha512-0VvcUz46wbg59Q4TZ59KkIjDJrxEyu4Eoe79+F4SChxoWe+W7AEyIp6F5r72C9+p6MsB6dIovnZt1D30lGM3XA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.6.4.tgz", + "integrity": "sha512-fxmvkF81srlS3DiMyfm+aG3J0eG+6/BjaxRyjy57KbLSafnq0PO+hHfVWmtH2ty0S7nm6yDXBRwySfFeaRJKRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.6.4.tgz", + "integrity": "sha512-6+bK1Y/evmIUiJr7auertuMbiaYVicJhqA1bZyL47lCMWugv8l0gFQCqvR/gN1vZMHYm+HwoTKgeMxdNFCoQTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.6.4.tgz", + "integrity": "sha512-31ZaH/MHqPX+O+evRwRrCHQ8W5/9adY+/eMytGGWZnRx1T41QsWIF09+/WujynLBhWZUxNGS5CtsWLwgOFTAyg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.6.4.tgz", + "integrity": "sha512-tbFZMdL6dP/ws4edXxYG9WhvaVa1ZDzL4G5DzahnWW7Psib9H+VTdyU4XXVxpJk5j/nYZroSbvSNyYlpW3yCIw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.6.4.tgz", + "integrity": "sha512-bomSa2XdQAGGj6Cwdm4M13qE+lN9+AOlW8KtI4hdyVyRRF/Z71qPAi95T4wtdZK0QpBbkTW8hgbZZCwPi4xzIw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.6.4.tgz", + "integrity": "sha512-eCsqt8Ma3eVap+oyJCDnJZH60SIU464B66n3y38EUTJdM1T/HfZol1FJC9vb+sHToX0ix8XzqPcwJnOzyAJz0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.6.4.tgz", + "integrity": "sha512-pYsMy5AThiHFOZAsngg8qN4PurM6nInKBhVX7sbj3eNpRQqo1Hb0MgCwPL7VqVMrmcEEMj9Ks3qbkallR65e8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/js-api": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@biomejs/js-api/-/js-api-0.7.1.tgz", + "integrity": "sha512-VFdgFFZWcyCQxZcAasyv8Enpexn4CblMdWmr6izLYHTLcbd+z9x/LuKU71qnmClABfnYqZjiY7c8DKTVri3Ajw==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "@biomejs/wasm-bundler": "^1.9.2", + "@biomejs/wasm-nodejs": "^1.9.2", + "@biomejs/wasm-web": "^1.9.2" + }, + "peerDependenciesMeta": { + "@biomejs/wasm-bundler": { + "optional": true + }, + "@biomejs/wasm-nodejs": { + "optional": true + }, + "@biomejs/wasm-web": { + "optional": true + } + } + }, + "node_modules/@biomejs/wasm-nodejs": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/wasm-nodejs/-/wasm-nodejs-1.9.4.tgz", + "integrity": "sha512-ZqNlhKcZW6MW1LxWIOfh9YVrBykvzyFad3bOh6JJFraDnNa3NXboRDiaI8dmrbb0ZHXCU1Tsq6WQsKV2Vpp5dw==", + "dev": true, + "license": "MIT OR Apache-2.0" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.7.tgz", + "integrity": "sha512-VP0yIfwlc1ohj733keYDqyHm1VWeP9VQxYomunhr90jkC4aNNibbkrRiaNgPgqj1ORxUenILR4qzA/ppYBNAtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^4.0.0" + } + }, + "node_modules/@fastify/deepmerge": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-3.2.4.tgz", + "integrity": "sha512-kc8GQq5gX9RRhw0rov3DMrN8wS8yRlxJRdDzsYwwdBV/wazuVgc/1K7CKL90tlvu3RlHan6Y0R0aE3nJtBz2eQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/error": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.1.tgz", + "integrity": "sha512-MYBumuIwP+5MIpXSyKm6/o1Eigcoij186lWUssX3LH5U8KWqU0ktZhzC4CErIX9EJeQeRGI+zJZQWakBlFbUcg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.1.1.tgz", + "integrity": "sha512-pyxRKk7UpUnnrztP9pF4+ZYxusEQ4LifTyzRdiXmyql1QezNHxO02D25MVU2o4kMeAMYWAjG6RKRlojgkbiDAw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^7.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.1.2.tgz", + "integrity": "sha512-89WiRG+hW3VQSfCwg4Hb8b43PeyEX15K5+lEfsZ4EUth/FyOjS/lv8HsVuswUNXWiSnxMGNouE/fq/0w3VumHQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.2.tgz", + "integrity": "sha512-d/gx9sRbUCVgGRGzma46wLcwiY8noTqO2Nmjg/vbTARxHRE5bdh5PIL8s97Lt1X7ScRWRW52eVYxwIoPwIdVUA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.2.tgz", + "integrity": "sha512-uqB4QsgVoro0XDC+DIftIMGXHdBPn+lK6Ek3xXAIxPMCJfqxnbtyEoMCm2KPUVOuIosd7Cyw9HawEqMFM/UA+g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", + "dev": true, + "license": "ISC" + }, + "node_modules/@libsql/client": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.18.0.tgz", + "integrity": "sha512-zMCCo58vv7w27j7+cnt/GW+X6/Rih6cHZ86PKi0AxCMOqMfrVgG9OZ5lW2bDMJhSeOjsv2szH2nJF3A9l36UmA==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@libsql/core": "^0.18.0", + "@libsql/hrana-client": "^0.10.0", + "js-base64": "^3.7.5", + "libsql": "^0.5.28", + "promise-limit": "^2.7.0" + } + }, + "node_modules/@libsql/core": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.18.0.tgz", + "integrity": "sha512-N8RR2CIpcgtbKZnXs2KVpw2lJQfinUj3I56e7qRDRpK18Hty0Rr8nr00VmFb4R/DsqOanPB/TaP3+QAQLZeXMg==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "js-base64": "^3.7.5" + } + }, + "node_modules/@libsql/darwin-arm64": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.5.29.tgz", + "integrity": "sha512-K+2RIB1OGFPYQbfay48GakLhqf3ArcbHqPFu7EZiaUcRgFcdw8RoltsMyvbj5ix2fY0HV3Q3Ioa/ByvQdaSM0A==", + "cpu": [ + "arm64" + ], + "extraneous": true, + "license": "MIT", + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/darwin-x64": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.5.29.tgz", + "integrity": "sha512-OtT+KFHsKFy1R5FVadr8FJ2Bb1mghtXTyJkxv0trocq7NuHntSki1eUbxpO5ezJesDvBlqFjnWaYYY516QNLhQ==", + "cpu": [ + "x64" + ], + "extraneous": true, + "license": "MIT", + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/hrana-client": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.10.0.tgz", + "integrity": "sha512-OoA4EMqRAC7kn7V2P6EQqRcpZf2W+AjsNIyCizBg339Tq/aMC7sRnzs3SklderhmQWAqEzvv8A2vhxVmWpkVvw==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@libsql/isomorphic-ws": "^0.1.5", + "js-base64": "^3.7.5" + } + }, + "node_modules/@libsql/isomorphic-fetch": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-fetch/-/isomorphic-fetch-0.2.5.tgz", + "integrity": "sha512-8s/B2TClEHms2yb+JGpsVRTPBfy1ih/Pq6h6gvyaNcYnMVJvgQRY7wAa8U2nD0dppbCuDU5evTNMEhrQ17ZKKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@libsql/isomorphic-ws": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", + "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/ws": "^8.5.4", + "ws": "^8.13.0" + } + }, + "node_modules/@libsql/kysely-libsql": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@libsql/kysely-libsql/-/kysely-libsql-0.4.1.tgz", + "integrity": "sha512-mCTa6OWgoME8LNu22COM6XjKBmcMAvNtIO6DYM10jSAFq779fVlrTKQEmXIB8TwJVU65dA5jGCpT8gkDdWS0HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@libsql/client": "^0.8.0" + }, + "peerDependencies": { + "kysely": "*" + } + }, + "node_modules/@libsql/kysely-libsql/node_modules/@libsql/client": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.8.1.tgz", + "integrity": "sha512-xGg0F4iTDFpeBZ0r4pA6icGsYa5rG6RAG+i/iLDnpCAnSuTqEWMDdPlVseiq4Z/91lWI9jvvKKiKpovqJ1kZWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@libsql/core": "^0.8.1", + "@libsql/hrana-client": "^0.6.2", + "js-base64": "^3.7.5", + "libsql": "^0.3.10", + "promise-limit": "^2.7.0" + } + }, + "node_modules/@libsql/kysely-libsql/node_modules/@libsql/core": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.8.1.tgz", + "integrity": "sha512-u6nrj6HZMTPsgJ9EBhLzO2uhqhlHQJQmVHV+0yFLvfGf3oSP8w7TjZCNUgu1G8jHISx6KFi7bmcrdXW9lRt++A==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-base64": "^3.7.5" + } + }, + "node_modules/@libsql/kysely-libsql/node_modules/@libsql/darwin-arm64": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.3.19.tgz", + "integrity": "sha512-rmOqsLcDI65zzxlUOoEiPJLhqmbFsZF6p4UJQ2kMqB+Kc0Rt5/A1OAdOZ/Wo8fQfJWjR1IbkbpEINFioyKf+nQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/kysely-libsql/node_modules/@libsql/darwin-x64": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.3.19.tgz", + "integrity": "sha512-q9O55B646zU+644SMmOQL3FIfpmEvdWpRpzubwFc2trsa+zoBlSkHuzU9v/C+UNoPHQVRMP7KQctJ455I/h/xw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@libsql/kysely-libsql/node_modules/@libsql/hrana-client": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.6.2.tgz", + "integrity": "sha512-MWxgD7mXLNf9FXXiM0bc90wCjZSpErWKr5mGza7ERy2FJNNMXd7JIOv+DepBA1FQTIfI8TFO4/QDYgaQC0goNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@libsql/isomorphic-fetch": "^0.2.1", + "@libsql/isomorphic-ws": "^0.1.5", + "js-base64": "^3.7.5", + "node-fetch": "^3.3.2" + } + }, + "node_modules/@libsql/kysely-libsql/node_modules/@libsql/linux-arm64-gnu": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.3.19.tgz", + "integrity": "sha512-mgeAUU1oqqh57k7I3cQyU6Trpdsdt607eFyEmH5QO7dv303ti+LjUvh1pp21QWV6WX7wZyjeJV1/VzEImB+jRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/kysely-libsql/node_modules/@libsql/linux-arm64-musl": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.3.19.tgz", + "integrity": "sha512-VEZtxghyK6zwGzU9PHohvNxthruSxBEnRrX7BSL5jQ62tN4n2JNepJ6SdzXp70pdzTfwroOj/eMwiPt94gkVRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/kysely-libsql/node_modules/@libsql/linux-x64-gnu": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.3.19.tgz", + "integrity": "sha512-2t/J7LD5w2f63wGihEO+0GxfTyYIyLGEvTFEsMO16XI5o7IS9vcSHrxsvAJs4w2Pf907uDjmc7fUfMg6L82BrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/kysely-libsql/node_modules/@libsql/linux-x64-musl": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.3.19.tgz", + "integrity": "sha512-BLsXyJaL8gZD8+3W2LU08lDEd9MIgGds0yPy5iNPp8tfhXx3pV/Fge2GErN0FC+nzt4DYQtjL+A9GUMglQefXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@libsql/kysely-libsql/node_modules/@libsql/win32-x64-msvc": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.3.19.tgz", + "integrity": "sha512-ay1X9AobE4BpzG0XPw1gplyLZPGHIgJOovvW23gUrukRegiUP62uzhpRbKNogLlUOynyXeq//prHgPXiebUfWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@libsql/kysely-libsql/node_modules/libsql": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.3.19.tgz", + "integrity": "sha512-Aj5cQ5uk/6fHdmeW0TiXK42FqUlwx7ytmMLPSaUQPin5HKKKuUPD62MAbN4OEweGBBI7q1BekoEN4gPUEL6MZA==", + "cpu": [ + "x64", + "arm64", + "wasm32" + ], + "dev": true, + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32" + ], + "dependencies": { + "@neon-rs/load": "^0.0.4", + "detect-libc": "2.0.2", + "libsql": "^0.3.15" + }, + "optionalDependencies": { + "@libsql/darwin-arm64": "0.3.19", + "@libsql/darwin-x64": "0.3.19", + "@libsql/linux-arm64-gnu": "0.3.19", + "@libsql/linux-arm64-musl": "0.3.19", + "@libsql/linux-x64-gnu": "0.3.19", + "@libsql/linux-x64-musl": "0.3.19", + "@libsql/win32-x64-msvc": "0.3.19" + } + }, + "node_modules/@libsql/linux-arm-gnueabihf": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm-gnueabihf/-/linux-arm-gnueabihf-0.5.29.tgz", + "integrity": "sha512-CD4n4zj7SJTHso4nf5cuMoWoMSS7asn5hHygsDuhRl8jjjCTT3yE+xdUvI4J7zsyb53VO5ISh4cwwOtf6k2UhQ==", + "cpu": [ + "arm" + ], + "extraneous": true, + "license": "MIT", + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm-musleabihf": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm-musleabihf/-/linux-arm-musleabihf-0.5.29.tgz", + "integrity": "sha512-2Z9qBVpEJV7OeflzIR3+l5yAd4uTOLxklScYTwpZnkm2vDSGlC1PRlueLaufc4EFITkLKXK2MWBpexuNJfMVcg==", + "cpu": [ + "arm" + ], + "extraneous": true, + "license": "MIT", + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm64-gnu": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.29.tgz", + "integrity": "sha512-gURBqaiXIGGwFNEaUj8Ldk7Hps4STtG+31aEidCk5evMMdtsdfL3HPCpvys+ZF/tkOs2MWlRWoSq7SOuCE9k3w==", + "cpu": [ + "arm64" + ], + "extraneous": true, + "license": "MIT", + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-arm64-musl": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.29.tgz", + "integrity": "sha512-fwgYZ0H8mUkyVqXZHF3mT/92iIh1N94Owi/f66cPVNsk9BdGKq5gVpoKO+7UxaNzuEH1roJp2QEwsCZMvBLpqg==", + "cpu": [ + "arm64" + ], + "extraneous": true, + "license": "MIT", + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-gnu": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.29.tgz", + "integrity": "sha512-y14V0vY0nmMC6G0pHeJcEarcnGU2H6cm21ZceRkacWHvQAEhAG0latQkCtoS2njFOXiYIg+JYPfAoWKbi82rkg==", + "cpu": [ + "x64" + ], + "extraneous": true, + "license": "MIT", + "os": [ + "linux" + ] + }, + "node_modules/@libsql/linux-x64-musl": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.29.tgz", + "integrity": "sha512-gquqwA/39tH4pFl+J9n3SOMSymjX+6kZ3kWgY3b94nXFTwac9bnFNMffIomgvlFaC4ArVqMnOZD3nuJ3H3VO1w==", + "cpu": [ + "x64" + ], + "extraneous": true, + "license": "MIT", + "os": [ + "linux" + ] + }, + "node_modules/@libsql/win32-x64-msvc": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.29.tgz", + "integrity": "sha512-4/0CvEdhi6+KjMxMaVbFM2n2Z44escBRoEYpR+gZg64DdetzGnYm8mcNLcoySaDJZNaBd6wz5DNdgRmcI4hXcg==", + "cpu": [ + "x64" + ], + "extraneous": true, + "license": "MIT", + "os": [ + "win32" + ] + }, + "node_modules/@metaobjectsdev/cli": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@metaobjectsdev/cli/-/cli-1.2.0.tgz", + "integrity": "sha512-nthlJc4orqbwEKUn+JHnFRaXD97I0qmdCeTZERLhYnw/Rxkkhj5Y0ZpmoYh4NcZWei5db7xwG26ktVdRKYFo8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@biomejs/js-api": "^0.7.0", + "@biomejs/wasm-nodejs": "^1.9.4", + "@libsql/kysely-libsql": "^0.4.0", + "@metaobjectsdev/codegen-ts": "1.2.0", + "@metaobjectsdev/codegen-ts-react": "1.2.0", + "@metaobjectsdev/codegen-ts-tanstack": "1.2.0", + "@metaobjectsdev/docs-site": "1.2.0", + "@metaobjectsdev/metadata": "1.2.0", + "@metaobjectsdev/migrate-ts": "1.2.0", + "@metaobjectsdev/render": "1.2.0", + "@metaobjectsdev/runtime-ts": "1.2.0", + "@metaobjectsdev/sdk": "1.2.0", + "@toon-format/toon": "^2.3.0", + "jiti": "^2.4.0" + }, + "bin": { + "meta": "dist/bin/meta.js" + }, + "peerDependencies": { + "kysely": ">=0.27.0 <0.30.0", + "pg": ">=8.0.0 <9.0.0" + }, + "peerDependenciesMeta": { + "pg": { + "optional": true + } + } + }, + "node_modules/@metaobjectsdev/codegen-ts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@metaobjectsdev/codegen-ts/-/codegen-ts-1.2.0.tgz", + "integrity": "sha512-Rt5WDY09S+DIGMHMhCUWwXHRvwDbLlmk8zy1UWGlI5UCxt4nBjMqO4H3Icp7gAJnwrVSdcUhN62zYc8HxnVGmg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@biomejs/js-api": "^0.7.0", + "@biomejs/wasm-nodejs": "^1.9.4", + "@metaobjectsdev/docs-site": "1.2.0", + "@metaobjectsdev/metadata": "1.2.0", + "@metaobjectsdev/migrate-ts": "1.2.0", + "@metaobjectsdev/render": "1.2.0", + "@toon-format/toon": "^2.3.0", + "ts-poet": "^6.10.0" + }, + "peerDependencies": { + "drizzle-orm": ">=0.38.0 <1.0.0", + "zod": ">=3.23.0 <5.0.0" + } + }, + "node_modules/@metaobjectsdev/codegen-ts-react": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@metaobjectsdev/codegen-ts-react/-/codegen-ts-react-1.2.0.tgz", + "integrity": "sha512-2/RY4uTYQJP6tANuOeYjw9+sJgwvUNB3by6PlqXscWMvAUk+RqXPkB5s5JxU3q39kWatAIVabQhDYQwQtpcZNw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@metaobjectsdev/codegen-ts": "1.2.0", + "@metaobjectsdev/metadata": "1.2.0", + "ts-poet": "^6.10.0" + }, + "peerDependencies": { + "@biomejs/biome": ">=1.9.0 <3.0.0" + } + }, + "node_modules/@metaobjectsdev/codegen-ts-tanstack": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@metaobjectsdev/codegen-ts-tanstack/-/codegen-ts-tanstack-1.2.0.tgz", + "integrity": "sha512-KOIXvsTlsyPWJDFEhgCmCMM8TYZj0lvMB8namb2vhNkaFlySpHLOYBGjhl/fYEjUoM77XVJ64X4ecmWojojmaQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@metaobjectsdev/codegen-ts": "1.2.0", + "@metaobjectsdev/metadata": "1.2.0", + "ts-poet": "^6.10.0" + }, + "peerDependencies": { + "@biomejs/biome": ">=1.9.0 <3.0.0" + } + }, + "node_modules/@metaobjectsdev/docs-site": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@metaobjectsdev/docs-site/-/docs-site-1.2.0.tgz", + "integrity": "sha512-+CX2OjtqY12Ob/JkBtPjk4Gq1ggvuLlJ8RFD9jVZZP+oIRUnNVVfUQNTE5Q6aPbY7aIR9VfOU86NsgVLeYRO4g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@metaobjectsdev/metadata": "1.2.0", + "@metaobjectsdev/render": "1.2.0", + "yaml": "^2.9.0" + } + }, + "node_modules/@metaobjectsdev/metadata": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@metaobjectsdev/metadata/-/metadata-1.2.0.tgz", + "integrity": "sha512-4lU3s48yjSx1JwX9UkFa71Un9vxPr6cEtI5IWjo9rSNZdwGg/NmEb66Cw1MzfZhe9n/ZtvJLhxY02jzb1CnaNA==", + "license": "Apache-2.0", + "dependencies": { + "yaml": "^2.9.0" + } + }, + "node_modules/@metaobjectsdev/migrate-ts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@metaobjectsdev/migrate-ts/-/migrate-ts-1.2.0.tgz", + "integrity": "sha512-jHb3DZjpTOFOyiIeZMC308giAGmvDiPr7lKgQW2JaahA2qqf3nGfCwsgsJSVq4LhjNpweBgS4hxrFXhnf1O7BA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@iarna/toml": "^2.2.5", + "@metaobjectsdev/metadata": "1.2.0" + }, + "peerDependencies": { + "@electric-sql/pglite": ">=0.3.0 <0.6.0", + "@libsql/kysely-libsql": ">=0.4.0 <0.5.0", + "kysely": ">=0.27.0 <0.30.0" + }, + "peerDependenciesMeta": { + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/kysely-libsql": { + "optional": true + } + } + }, + "node_modules/@metaobjectsdev/render": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@metaobjectsdev/render/-/render-1.2.0.tgz", + "integrity": "sha512-8LzWpb5e/jg1Ozk9th+u36O7t/wuCKMH+IGUMeBy/Adx5Z2Ap5lNg+ecWwNcD5UVTF42PV9ZJkqIJsCb9PYinw==", + "license": "Apache-2.0", + "dependencies": { + "mustache": "^4.2.0", + "nunjucks": "^3.2.4" + } + }, + "node_modules/@metaobjectsdev/runtime-ts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@metaobjectsdev/runtime-ts/-/runtime-ts-1.2.0.tgz", + "integrity": "sha512-g6LoefcxybPa/1Iu+94xSm4iKk4N5U2Hv6tc73jt3GFO0GrhtyhCo2GyLOD4DtoaDnpywrY2pfBroVPj0Br6QQ==", + "license": "Apache-2.0", + "dependencies": { + "@metaobjectsdev/metadata": "1.2.0", + "@metaobjectsdev/render": "1.2.0", + "qs": "^6.13.0" + }, + "peerDependencies": { + "drizzle-orm": ">=0.36.0 <1.0.0", + "fastify": ">=5.0.0 <6.0.0", + "hono": ">=4.0.0 <5.0.0", + "kysely": ">=0.27.0 <0.30.0", + "zod": ">=3.23.0 <5.0.0" + }, + "peerDependenciesMeta": { + "drizzle-orm": { + "optional": true + }, + "fastify": { + "optional": true + }, + "hono": { + "optional": true + }, + "kysely": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@metaobjectsdev/sdk": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@metaobjectsdev/sdk/-/sdk-1.2.0.tgz", + "integrity": "sha512-icDDCWKk6Zt3IVMUV5M2hylkvi8xf7P9LZ4XkNdO091/r4ucxJug41JciEO8RdPEbQrIPsSH6QNXjxC4TEaY+Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@metaobjectsdev/metadata": "1.2.0", + "yaml": "^2.9.0", + "zod": "^4.6.6" + } + }, + "node_modules/@neon-rs/load": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", + "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@toon-format/toon": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@toon-format/toon/-/toon-2.3.1.tgz", + "integrity": "sha512-sWqEMeAJGMda9qRrfPKrX3C4c33CO9SJuvJzzrcBEn5sbDB+k6pSLkDsfN0rVnLQwR97MV3sDgVbcxQNsq8xVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz", + "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/pg": { + "version": "8.23.1", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.23.1.tgz", + "integrity": "sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/a-sync-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", + "license": "MIT" + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv/node_modules/fast-uri": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.3.1.tgz", + "integrity": "sha512-QyTKW61/qlba+cUQBm/sT9X8lri4UdomqSDwCnrnj1IvLVDRRiXRkz4CgBRWB+/cmfC7SK5WQc8T1vSdGvaUcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cookie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dprint-node": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/dprint-node/-/dprint-node-1.0.8.tgz", + "integrity": "sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3" + } + }, + "node_modules/dprint-node/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/drizzle-orm": { + "version": "0.45.4", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.4.tgz", + "integrity": "sha512-NiM1YZpJ2UxyeE67DpDDZ+Tsw4YLX3trSlkNT7Tm9eKoUNb1LvaAaQzXgYQsWs+AQ9TUmmwMmBOCXuqqxUWKKQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@netlify/db": ">=0.4.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1.13", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/sql.js": "*", + "@upstash/redis": ">=1.34.7", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "gel": ">=2", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@netlify/db": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "gel": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-7.2.0.tgz", + "integrity": "sha512-q18imgm9fEdQVfvJbRJALE9VP/A5HGme0+NUnWcmFYM9rbQbLkZZk20FyIp1LQ8gDOauQKwqMc52YozqFaQyOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^4.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.2.3.tgz", + "integrity": "sha512-I1PXwP5eAhlNlcdpjt9CgB59op2ZvZfxw+BkqGFVIUOgNpCVTcTqp3HhTX7brxj5vsip+nSY4nTvff+eIA76hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastify": { + "version": "5.12.7", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.12.7.tgz", + "integrity": "sha512-v4pEPsTRPuqlDIYiQFB+BHjVN4SdHX9u0ABk0ZDUKbbdY1vsNryUd1j4Z+jQEZajvYIVk047Kimva9/AJ79GYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.5", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^7.0.0", + "find-my-way": "^9.6.0", + "light-my-request": "^6.0.0", + "pino": "^9.14.0 || ^10.1.0", + "process-warning": "^5.1.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/find-my-way": { + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.10.0.tgz", + "integrity": "sha512-eru+uCPQ2g+nLBkf84MHP/KyNNMar0BDy0j+i8Fb72GF9/YHBZ58q7FODUtfVV7XU9nfRUHXSmaM8NmZcIbZ4Q==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ipaddr.js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz", + "integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-base64": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.9.5.tgz", + "integrity": "sha512-u0CFDUca8BbBRkMkF/fDcuoLCx9nTNbXdVtbjVnBqySA9E221u0uYKbACiYGOEk1heY69qM4caeX3OwjSyxevw==", + "devOptional": true, + "license": "BSD-3-Clause" + }, + "node_modules/json-schema-ref-resolver": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.1.tgz", + "integrity": "sha512-w+HwgljXpVwZ3IidoUR1qPZxSOQ+NRQWuVe6a5pe8XbHsNZE4UAwVPUvzZagn7yBKbJtt6UJnRFYbNTRPZI45Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/kysely": { + "version": "0.29.7", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.29.7.tgz", + "integrity": "sha512-fmIq+7kv4WbFm2tcfznRzikqB4cuNpgZ40PSU2fAJ2/OO5XhojqnTPrmgrCjYSRJVwBln03XcRXJGnw9H099IQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/libsql": { + "version": "0.5.29", + "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.5.29.tgz", + "integrity": "sha512-8lMP8iMgiBzzoNbAPQ59qdVcj6UaE/Vnm+fiwX4doX4Narook0a4GPKWBEv+CR8a1OwbfkgL18uBfBjWdF0Fzg==", + "cpu": [ + "x64", + "arm64", + "wasm32", + "arm" + ], + "extraneous": true, + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32" + ], + "dependencies": { + "@neon-rs/load": "^0.0.4", + "detect-libc": "2.0.2" + }, + "optionalDependencies": { + "@libsql/darwin-arm64": "0.5.29", + "@libsql/darwin-x64": "0.5.29", + "@libsql/linux-arm-gnueabihf": "0.5.29", + "@libsql/linux-arm-musleabihf": "0.5.29", + "@libsql/linux-arm64-gnu": "0.5.29", + "@libsql/linux-arm64-musl": "0.5.29", + "@libsql/linux-x64-gnu": "0.5.29", + "@libsql/linux-x64-musl": "0.5.29", + "@libsql/win32-x64-msvc": "0.5.29" + } + }, + "node_modules/light-my-request": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.8.0.tgz", + "integrity": "sha512-5Q8PjoSEdtGIB8fikZsTem5vgRo4F2mR6vUKTDW/H1g6MtAt8e3duQbg/QxL5yzg6BZhor2KOUm3hhWMWLTZwg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "@fastify/deepmerge": "^3.1.0", + "cookie": "^2.0.0", + "process-warning": "^5.0.0", + "set-cookie-parser": "^3.0.1" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/nunjucks": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", + "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", + "license": "BSD-2-Clause", + "dependencies": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + }, + "bin": { + "nunjucks-precompile": "bin/precompile" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "chokidar": "^3.3.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/pg": { + "version": "8.23.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.23.1.tgz", + "integrity": "sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.14.1", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.16.1", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.4.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.1.tgz", + "integrity": "sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.1.tgz", + "integrity": "sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.1.tgz", + "integrity": "sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/pino": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.4.0.tgz", + "integrity": "sha512-bk1ZMTwG/Vymx+zPoa28MhNzucFvXJWp3csrSuxuHPSMeUettaCoQ5Jvg07mtC4JGNpcfTwz41oVCnNgBK9+vA==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^1.0.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process-warning": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.1.tgz", + "integrity": "sha512-oCkNVj4OKnKEcocVBcDfiIugN1/cE6rgfT3nCtzeqZPA8+rIxMaQ0xXzIkGP8Gj2qUaP1UR7uhJ1VD8lUCpzCg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/promise-limit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/qs": { + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.17.1.tgz", + "integrity": "sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "license": "MIT" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ret": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.1.tgz", + "integrity": "sha512-Z/nyvABwcRnRD2mMP7Kz6G1dFZZqX1mNErOnxF42OCQKE07b9k9hK9SQxHL3LFEZUdzQaLM4dId1E/E6Vm4WhA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.2.tgz", + "integrity": "sha512-tAtCGFPPP02VroDVzp4daGi6bC7jWG+41D47GKf18FeH/ubvKQHT9VmhAkvtjNOmJwqtApzWeUpEhkyEYULvmw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.1.tgz", + "integrity": "sha512-lnd81MQEZKjhW/Gd2TQqdd4XP6QYNdDW/EP+I7nTrFHP9y2LmaGPIHk9xsnzcOiEr1c39c/e84xFgGviQKBTRw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.2.2.tgz", + "integrity": "sha512-df017qMeYuXo4emL09nlyMpi2i2MtEIKS/6jtXH3vmjFgIRsOi852/5RmhJ4EkvN3ZF8jXadM/EkTPXgc8yPIw==", + "license": "MIT" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/thread-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", + "license": "MIT", + "dependencies": { + "real-require": "^1.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/toad-cache": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.4.tgz", + "integrity": "sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/ts-poet": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.12.0.tgz", + "integrity": "sha512-xo+iRNMWqyvXpFTaOAvLPA5QAWO6TZrSUs5s4Odaya3epqofBu/fMLHEWl8jPmjhA0s9sgj9sNvF1BmaQlmQkA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dprint-node": "^1.0.8" + } + }, + "node_modules/tsx": { + "version": "4.23.15", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz", + "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ws": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/zod": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/examples/head-to-head/metaobjects/package.json b/examples/head-to-head/metaobjects/package.json new file mode 100644 index 0000000000..b17e1b1fb3 --- /dev/null +++ b/examples/head-to-head/metaobjects/package.json @@ -0,0 +1,21 @@ +{ + "name": "h2h-metaobjects", + "private": true, + "type": "module", + "dependencies": { + "@metaobjectsdev/runtime-ts": "1.2.0", + "drizzle-orm": "0.45.4", + "fastify": "5.12.7", + "pg": "8.23.1", + "zod": "4.6.7" + }, + "devDependencies": { + "@metaobjectsdev/cli": "1.2.0", + "@metaobjectsdev/codegen-ts": "1.2.0", + "@metaobjectsdev/metadata": "1.2.0", + "@types/node": "22.20.5", + "@types/pg": "8.23.1", + "tsx": "4.23.15", + "typescript": "5.9.3" + } +} diff --git a/examples/head-to-head/metaobjects/prompts/welcome/greeting.jinja b/examples/head-to-head/metaobjects/prompts/welcome/greeting.jinja new file mode 100644 index 0000000000..d924070c2c --- /dev/null +++ b/examples/head-to-head/metaobjects/prompts/welcome/greeting.jinja @@ -0,0 +1 @@ +Write a short, friendly welcome note for {{ name }} <{{ email }}>, who just joined on the {{ plan }} plan. diff --git a/examples/head-to-head/metaobjects/requirements.in b/examples/head-to-head/metaobjects/requirements.in new file mode 100644 index 0000000000..a3e116c2f5 --- /dev/null +++ b/examples/head-to-head/metaobjects/requirements.in @@ -0,0 +1,4 @@ +metaobjects[jinja,pydantic]==1.2.0 +mypy +psycopg[binary] +pydantic diff --git a/examples/head-to-head/metaobjects/requirements.txt b/examples/head-to-head/metaobjects/requirements.txt new file mode 100644 index 0000000000..e600592d4e --- /dev/null +++ b/examples/head-to-head/metaobjects/requirements.txt @@ -0,0 +1,39 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile requirements.in --python-version 3.12 -o requirements.txt +annotated-types==0.8.0 + # via pydantic +ast-serialize==0.13.1 + # via mypy +jinja2==3.1.6 + # via metaobjects +librt==0.16.0 + # via mypy +markupsafe==3.0.4 + # via jinja2 +metaobjects==1.2.0 + # via -r requirements.in +mypy==2.4.1 + # via -r requirements.in +mypy-extensions==1.1.0 + # via mypy +pathspec==1.1.1 + # via mypy +psycopg==3.3.6 + # via -r requirements.in +psycopg-binary==3.3.6 + # via psycopg +pydantic==2.14.2 + # via -r requirements.in +pydantic-core==2.50.2 + # via pydantic +pyyaml==6.0.3 + # via metaobjects +typing-extensions==4.16.0 + # via + # mypy + # psycopg + # pydantic + # pydantic-core + # typing-inspection +typing-inspection==0.4.4 + # via pydantic diff --git a/examples/head-to-head/metaobjects/smoke.sh b/examples/head-to-head/metaobjects/smoke.sh new file mode 100755 index 0000000000..5860fb74c5 --- /dev/null +++ b/examples/head-to-head/metaobjects/smoke.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# End-to-end probe on a database that already has the committed migrations applied: +# create a customer through the TypeScript API, then render its prompt in the Python worker. +set -euo pipefail +cd "$(dirname "$0")" +export PATH="$PWD/.venv/bin:$PATH" PYTHONPATH="$PWD" +export DATABASE_URL="postgresql://postgres:h2h@127.0.0.1:${H2H_PG_PORT}/${H2H_DB}" +id=$(npx tsx api/smoke.ts) +prompt=$(python -m worker.main "$id") +case "$prompt" in + *"Ada Lovelace"*) ;; + *) echo "probe: the rendered prompt does not contain the customer's name: $prompt"; exit 1 ;; +esac diff --git a/examples/head-to-head/metaobjects/worker/__init__.py b/examples/head-to-head/metaobjects/worker/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/head-to-head/metaobjects/worker/generated/Customer.py b/examples/head-to-head/metaobjects/worker/generated/Customer.py new file mode 100644 index 0000000000..50a1165a58 --- /dev/null +++ b/examples/head-to-head/metaobjects/worker/generated/Customer.py @@ -0,0 +1,34 @@ +# @generated by metaobjects — DO NOT EDIT. +# Source metadata: Customer (crm::Customer) +# Extend in your own module (e.g. Customer_extra.py) — nothing imports it for you. + +from __future__ import annotations + +from typing import Literal +import datetime + +from pydantic import BaseModel, Field + + +class Customer(BaseModel): + id: int | None = None + email: str = Field(max_length=320) + name: str | None = Field(default=None, max_length=200) + plan: Literal["free", "pro"] = 'free' + createdAt: datetime.datetime + + +class CustomerCreate(BaseModel): + """GENERATED — CREATE input: auto-gen PK / @mutability readOnly omitted (writeOnce is settable here, once); @default/@autoSet optional; present values validated (FR-036).""" + email: str = Field(min_length=1, max_length=320) + name: str | None = Field(default=None, max_length=200) + plan: Literal["free", "pro"] = 'free' + createdAt: datetime.datetime | None = None + + +class CustomerPatch(BaseModel): + """GENERATED — PATCH input: all fields optional (PK, @mutability readOnly and writeOnce excluded); present values validated (FR-036).""" + email: str | None = Field(default=None, min_length=1, max_length=320) + name: str | None = Field(default=None, max_length=200) + plan: Literal["free", "pro"] | None = None + createdAt: datetime.datetime | None = None diff --git a/examples/head-to-head/metaobjects/worker/generated/WelcomePayload.py b/examples/head-to-head/metaobjects/worker/generated/WelcomePayload.py new file mode 100644 index 0000000000..18196b2953 --- /dev/null +++ b/examples/head-to-head/metaobjects/worker/generated/WelcomePayload.py @@ -0,0 +1,22 @@ +# @generated by metaobjects — DO NOT EDIT. +# Source metadata: WelcomePayload (crm::WelcomePayload) +# Extend in your own module (e.g. WelcomePayload_extra.py) — nothing imports it for you. + +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel, Field + + +class WelcomePayload(BaseModel): + name: str | None = Field(default=None, max_length=200) + email: str = Field(max_length=320) + plan: Literal["free", "pro"] = 'free' + + +class WelcomePayloadCreate(BaseModel): + """GENERATED — CREATE input: auto-gen PK / @mutability readOnly omitted (writeOnce is settable here, once); @default/@autoSet optional; present values validated (FR-036).""" + name: str | None = Field(default=None, max_length=200) + email: str = Field(min_length=1, max_length=320) + plan: Literal["free", "pro"] = 'free' diff --git a/examples/head-to-head/metaobjects/worker/generated/__init__.py b/examples/head-to-head/metaobjects/worker/generated/__init__.py new file mode 100644 index 0000000000..6a2d6b24cd --- /dev/null +++ b/examples/head-to-head/metaobjects/worker/generated/__init__.py @@ -0,0 +1,2 @@ +# @generated by metaobjects — DO NOT EDIT. +# Package marker so the generated output directory is importable. diff --git a/examples/head-to-head/metaobjects/worker/main.py b/examples/head-to-head/metaobjects/worker/main.py new file mode 100644 index 0000000000..1f51156e89 --- /dev/null +++ b/examples/head-to-head/metaobjects/worker/main.py @@ -0,0 +1,27 @@ +import os +import sys + +import psycopg + +from worker.generated.WelcomePayload import WelcomePayload +from worker.prompts import render_welcome + + +def load_payload(conn: psycopg.Connection, customer_id: int) -> WelcomePayload: + row = conn.execute( + "SELECT name, email, plan FROM customers WHERE id = %s", (customer_id,) + ).fetchone() + if row is None: + raise LookupError(f"no customer {customer_id}") + name, email, plan = row + return WelcomePayload(name=name, email=email, plan=plan) + + +def main() -> None: + with psycopg.connect(os.environ["DATABASE_URL"]) as conn: + # A real worker would send this prompt to an LLM; the demo prints it. + print(render_welcome(load_payload(conn, int(sys.argv[1])))) + + +if __name__ == "__main__": + main() diff --git a/examples/head-to-head/metaobjects/worker/prompts.py b/examples/head-to-head/metaobjects/worker/prompts.py new file mode 100644 index 0000000000..e50a790f32 --- /dev/null +++ b/examples/head-to-head/metaobjects/worker/prompts.py @@ -0,0 +1,14 @@ +from pathlib import Path + +from metaobjects.render import FilesystemProvider +from metaobjects.render.renderer import RenderRequest, render + +from worker.generated.WelcomePayload import WelcomePayload + +_provider = FilesystemProvider(str(Path(__file__).parent.parent / "prompts")) + + +def render_welcome(payload: WelcomePayload) -> str: + return render( + RenderRequest(payload=payload.model_dump(mode="json"), provider=_provider, ref="welcome/greeting") + ) diff --git a/examples/head-to-head/run.sh b/examples/head-to-head/run.sh new file mode 100755 index 0000000000..34291d477d --- /dev/null +++ b/examples/head-to-head/run.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Run every drift scenario against both setups and write SCORECARD.md. +# Needs Docker (Postgres, Atlas and oasdiff run as pinned images), Node.js 20+, uv and git. +# ./run.sh all scenarios +# ./run.sh S1 S6 only these (the S0 control always runs) +set -euo pipefail +exec python3 "$(dirname "$0")/harness/run.py" "$@" From 4fc04431c1e08568ef6de0a837f9deffcab17bf1 Mon Sep 17 00:00:00 2001 From: Doug Mealing Date: Sat, 31 Oct 2026 00:14:37 -0400 Subject: [PATCH 2/4] no-mistakes(review): apply reviewer inline fixes --- examples/head-to-head/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/head-to-head/README.md b/examples/head-to-head/README.md index 0f44560711..be260e0807 100644 --- a/examples/head-to-head/README.md +++ b/examples/head-to-head/README.md @@ -34,7 +34,7 @@ You need Docker (Postgres 16, Atlas and oasdiff run as containers), Node.js 20 o `python3`. The first run installs both setups' dependencies and pulls three images; later runs reuse them. ```sh -./run.sh # every scenario, both setups, rewrites SCORECARD.md (about three minutes) +./run.sh # every scenario, both setups, rewrites SCORECARD.md (about four minutes) ./run.sh S1 S6 # chosen scenarios (S0, the control, always runs) ./run.sh --keep /tmp/h2h-work # also keep each scenario's working copy and its .ci-logs/ ``` @@ -73,8 +73,9 @@ nothing about the other. prompt in the worker; the same files in both setups) is reported beside it. The probe is informational and is not part of either gate. 2. **Does a breaking change for outside callers get flagged?** Only the baseline has a check for this - (`oasdiff`). MetaObjects 1.2.0 has no equivalent, so every row in that table reads "no such check" for - it. A change can be perfectly consistent across every layer and still break a partner; consistency checks + (`oasdiff`). MetaObjects 1.2.0 has no equivalent, so every pull-request row in that table reads "no such + check" for it (S5 is `n/a` on both sides). A change can be perfectly consistent across every layer and + still break a partner; consistency checks cannot see that, and this is the one place the baseline is simply ahead. ### Our reading of the rows that need explaining From 537dbe8a77b4d4f3bffa24a9f7420f315ecd1aa2 Mon Sep 17 00:00:00 2001 From: Doug Mealing Date: Sat, 31 Oct 2026 00:21:39 -0400 Subject: [PATCH 3/4] no-mistakes(review): Fix head-to-head scorecard text, counts, control, flags --- examples/head-to-head/README.md | 6 +-- examples/head-to-head/SCORECARD.md | 20 ++++---- examples/head-to-head/baseline/api/src/app.ts | 2 + examples/head-to-head/harness/lines.py | 8 +-- examples/head-to-head/harness/run.py | 49 ++++++++++--------- examples/head-to-head/run.sh | 6 +-- 6 files changed, 47 insertions(+), 44 deletions(-) diff --git a/examples/head-to-head/README.md b/examples/head-to-head/README.md index be260e0807..4d0c1b9702 100644 --- a/examples/head-to-head/README.md +++ b/examples/head-to-head/README.md @@ -35,7 +35,7 @@ You need Docker (Postgres 16, Atlas and oasdiff run as containers), Node.js 20 o ```sh ./run.sh # every scenario, both setups, rewrites SCORECARD.md (about four minutes) -./run.sh S1 S6 # chosen scenarios (S0, the control, always runs) +./run.sh S1 S6 # chosen scenarios (S0, the control, always runs); prints the scorecard, does not write SCORECARD.md ./run.sh --keep /tmp/h2h-work # also keep each scenario's working copy and its .ci-logs/ ``` @@ -47,7 +47,7 @@ container is started for the run and removed at the end. ## The scenarios Every scenario edits **the place that setup treats as its source of truth**, runs **the generators that -setup gives you**, and leaves anything else alone. That is the premise: it measures what happens when +setup gives you** (for MetaObjects that includes `meta migrate`), and leaves the hand-written code and the prompt alone. S1's description in the scorecard spells out what it edits in each setup. That is the premise: it measures what happens when someone forgets the rest, not how well anyone remembers it. S0 changes nothing and must be green on both sides, otherwise a red cell elsewhere would be noise. @@ -101,7 +101,7 @@ These are interpretation, written by hand, not measured. Check them against the ## Lines each setup needs The scorecard also counts what each setup makes you write. One rule for both: every committed file in the -setup's directory counts, except lock files, machine-generated output (`generated/`, the migrations, +setup's directory counts, except lock files, `.gitignore`, machine-generated output (`generated/`, the migrations, `.metaobjects/.gen-state/` and so on, all listed in the scorecard) and the smoke probe, which is byte-identical in both. Blank lines and comment-only lines are not counted. `harness/lines.py` fails if a file fits no category, so nothing escapes the count. Generated lines are reported, not hidden. diff --git a/examples/head-to-head/SCORECARD.md b/examples/head-to-head/SCORECARD.md index 2ffa35ea6d..1a83d41a74 100644 --- a/examples/head-to-head/SCORECARD.md +++ b/examples/head-to-head/SCORECARD.md @@ -2,7 +2,7 @@ > **Self-measured.** The MetaObjects maintainers wrote both setups and this harness. The baseline is built to be a competent do-it-yourself stack, but we chose its tools, its scenarios and its app, and a reader who disagrees with any of those should change them and rerun (`./run.sh`). Read [README.md](README.md) for what this does and does not measure. -Generated by `./run.sh` on 2026-10-31; 7 scenarios × 2 setups took 232 s. Every result cell below is computed from the exit codes of the checks that ran; none is typed by hand. +Generated by `./run.sh` on 2026-10-31; 7 scenarios × 2 setups took 224 s. Every result cell below is computed from the exit codes of the checks that ran; none is typed by hand. ## Results @@ -44,12 +44,12 @@ The DIY baseline runs `oasdiff` against the OpenAPI document released to partner ### What each scenario does -- **S0 Control: no change.** Nothing is edited. Every check must pass, so a red cell elsewhere is not noise from the setup itself. -- **S1 Rename a field.** `Customer.email` becomes `contactEmail` in the place each setup treats as its source of truth, then that setup's own generators run. The prompt payload, the database column and the hand-written code are left as they were. +- **S0 Control: no change.** Nothing is edited. Every check must pass, so a red cell elsewhere is not noise from the setup itself. (The live-database check has its own control: S5 runs it on the unedited database first and aborts the run unless it passes.) +- **S1 Rename a field.** `Customer.email` becomes `contactEmail` in the place each setup treats as its source of truth, then that setup's own generators run. Baseline: the TypeSpec model is edited, including the payload's field list, so the payload field is renamed too; `./gen.sh` runs; `atlas/schema.sql`, the migrations, the prompt and the hand-written code are left as they were. MetaObjects: the field and the payload's `extends` are edited, so the payload field keeps the name `email`; `meta gen`, `metaobjects gen` and `meta migrate --rename-column customers.email=contact_email` run; the prompt and the hand-written code are left as they were. - **S2 Add a required field.** `Customer` gains a required `country` (2 characters). Generators run; nothing else is touched. - **S3 Change a prompt variable.** The prompt template is edited to use `{{ fullName }}`, which the payload does not have. Nothing is regenerated. - **S4 Skip a regen.** An optional `nickname` field is added to the model and no generator is run, so the committed generated files are stale. -- **S5 Edit the database by hand.** Someone runs `ALTER TABLE customers ADD COLUMN vip boolean NOT NULL DEFAULT false` on a live database. The check is each setup's live-database drift command, the one a team runs on a schedule or at deploy time. +- **S5 Edit the database by hand.** Someone runs `ALTER TABLE customers ADD COLUMN vip boolean NOT NULL DEFAULT false` on a live database. The check is each setup's live-database drift command, the one a team runs on a schedule or at deploy time. It is run first on the unedited database and must pass, so a red result after the edit is the edit, not a broken tool. - **S6 Change a field on the partner API.** `name` becomes required on `POST /customers`: a request partners send today without it would be rejected. Generators run; the database follows where the setup derives it. ### Which check said so @@ -88,7 +88,7 @@ The informative lines of each failing check's log, picked by a per-check extract - **S1, DIY baseline:** - `api-compat`: oasdiff: 5 changes: 4 error, 1 warning, 0 info; first: [response-required-property-removed] API GET /customers: removed the required property `items/email` from the response with the `200` status - - `tsc`: api/src/app.ts(32,13): error TS2339: Property 'email' does not exist on type '{ contactEmail: string; name?: string | undefined; plan: "free" | "pro"; }'. + - `tsc`: api/src/app.ts(34,13): error TS2339: Property 'email' does not exist on type '{ contactEmail: string; name?: string | undefined; plan: "free" | "pro"; }'. - `mypy`: tests/test_prompts.py:7: error: Unexpected keyword argument "email" for "WelcomePayload" [call-arg] - `prompt-render`: pydantic_core._pydantic_core.ValidationError: 1 validation error for WelcomePayload contactEmail - smoke: pydantic_core._pydantic_core.ValidationError: 1 validation error for WelcomePayload contactEmail Field required [type=missing, input_value={'name': 'Ada Lovelace', ...lan': }, input_… @@ -127,19 +127,19 @@ The informative lines of each failing check's log, picked by a per-check extract ## Lines each setup needs -One rule for both: every committed file in the setup's directory counts, minus lock files and machine-generated output (listed below), minus the smoke probe (identical in both). Blank lines and comment-only lines do not count. The numbers are from the files in this repository at the commit that generated this page. +One rule for both: every committed file in the setup's directory counts, minus lock files, machine-generated output (listed below), the smoke probe (identical in both) and `.gitignore`. Blank lines and comment-only lines do not count. The numbers are from the files in this repository at the commit that generated this page. | Category | DIY baseline | MetaObjects | |---|---:|---:| | Model and schema (the sources of truth) | 43 | 23 | | Tool configuration | 38 | 38 | | Checks and glue scripts | 54 | 21 | -| Hand-written application code and prompt | 63 | 56 | +| Hand-written application code and prompt | 64 | 56 | | Dependency manifests | 25 | 25 | | **Model + configuration + checks** | **135** | **82** | -| **All counted lines** | **223** | **163** | +| **All counted lines** | **224** | **163** | -Not counted, machine-generated: baseline 8 files / 425 lines (`generated/`, `atlas/migrations/`, the released OpenAPI copy); MetaObjects 11 files / 376 lines (`api/src/generated/`, `worker/generated/`, `.metaobjects/migrations/`, `.metaobjects/.gen-state/`). +Not counted, machine-generated: baseline 6 files / 425 lines (`generated/`, `atlas/migrations/`, the released OpenAPI copy); MetaObjects 9 files / 376 lines (`api/src/generated/`, `worker/generated/`, `.metaobjects/migrations/`, `.metaobjects/.gen-state/`).
Per-file counts @@ -158,7 +158,7 @@ Not counted, machine-generated: baseline 8 files / 425 lines (`generated/`, `atl - `live-drift.sh` (checks): 7 - `tests/__init__.py` (checks): 0 - `tests/test_prompts.py` (checks): 5 -- `api/src/app.ts` (app): 34 +- `api/src/app.ts` (app): 35 - `prompts/welcome/greeting.jinja` (app): 1 - `worker/__init__.py` (app): 0 - `worker/main.py` (app): 18 diff --git a/examples/head-to-head/baseline/api/src/app.ts b/examples/head-to-head/baseline/api/src/app.ts index 6838baa0c2..18c3f44d77 100644 --- a/examples/head-to-head/baseline/api/src/app.ts +++ b/examples/head-to-head/baseline/api/src/app.ts @@ -2,6 +2,8 @@ import Fastify from "fastify"; import pg from "pg"; import type { components, operations } from "../../generated/api"; +pg.types.setTypeParser(pg.types.builtins.INT8, Number); + type Customer = components["schemas"]["Customer"]; type CreateCustomer = components["schemas"]["CreateCustomer"]; type ListQuery = NonNullable; diff --git a/examples/head-to-head/harness/lines.py b/examples/head-to-head/harness/lines.py index 5c276c141d..31218f11e3 100644 --- a/examples/head-to-head/harness/lines.py +++ b/examples/head-to-head/harness/lines.py @@ -2,7 +2,7 @@ The rule: every file under a setup directory is committed source unless it is a dependency directory, a cache, a lock file or machine-generated output (the GENERATED -list below). Each remaining file falls in exactly one category; a file that matches no +list below), or a .gitignore (counted in no category). Each remaining file falls in exactly one category; a file that matches no category is an error, so nothing can escape the count. Blank lines and comment-only lines are not counted. """ @@ -102,7 +102,7 @@ def count_setup(root: Path, setup: str) -> dict: break else: raise SystemExit(f"lines.py: {setup}/{rel} matches no category; add it to CATEGORIES or GENERATED") - gen_lines = sum(len((root / g).read_text(encoding="utf-8").splitlines()) for g in generated - if not g.endswith(("package-lock.json", "requirements.txt"))) - return {"totals": totals, "files": files, "generated_files": len(generated), + shown = [g for g in generated if not g.endswith(("package-lock.json", "requirements.txt"))] + gen_lines = sum(len((root / g).read_text(encoding="utf-8").splitlines()) for g in shown) + return {"totals": totals, "files": files, "generated_files": len(shown), "generated_lines": gen_lines, "probe": probe} diff --git a/examples/head-to-head/harness/run.py b/examples/head-to-head/harness/run.py index 73e04caa69..5c24be4bb5 100644 --- a/examples/head-to-head/harness/run.py +++ b/examples/head-to-head/harness/run.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Run every drift scenario against both setups and write SCORECARD.md. +"""Run the drift scenarios against both setups; a full run writes SCORECARD.md, a subset prints it. Nothing in the scorecard's result cells is typed by hand: each one is computed from the exit codes of the checks the scenario ran. Standard library only (Python 3.9+). @@ -211,9 +211,9 @@ def s6_metaobjects(w: Work) -> None: SCENARIOS = [ - Scenario("S0", "Control: no change", "Nothing is edited. Every check must pass, so a red cell elsewhere is not noise from the setup itself.", + Scenario("S0", "Control: no change", "Nothing is edited. Every check must pass, so a red cell elsewhere is not noise from the setup itself. (The live-database check has its own control: S5 runs it on the unedited database first and aborts the run unless it passes.)", "pr", {"baseline": no_change, "metaobjects": no_change}), - Scenario("S1", "Rename a field", "`Customer.email` becomes `contactEmail` in the place each setup treats as its source of truth, then that setup's own generators run. The prompt payload, the database column and the hand-written code are left as they were.", + Scenario("S1", "Rename a field", "`Customer.email` becomes `contactEmail` in the place each setup treats as its source of truth, then that setup's own generators run. Baseline: the TypeSpec model is edited, including the payload's field list, so the payload field is renamed too; `./gen.sh` runs; `atlas/schema.sql`, the migrations, the prompt and the hand-written code are left as they were. MetaObjects: the field and the payload's `extends` are edited, so the payload field keeps the name `email`; `meta gen`, `metaobjects gen` and `meta migrate --rename-column customers.email=contact_email` run; the prompt and the hand-written code are left as they were.", "pr", {"baseline": s1_baseline, "metaobjects": s1_metaobjects}), Scenario("S2", "Add a required field", "`Customer` gains a required `country` (2 characters). Generators run; nothing else is touched.", "pr", {"baseline": s2_baseline, "metaobjects": s2_metaobjects}), @@ -221,7 +221,7 @@ def s6_metaobjects(w: Work) -> None: "pr", {"baseline": s3, "metaobjects": s3}), Scenario("S4", "Skip a regen", "An optional `nickname` field is added to the model and no generator is run, so the committed generated files are stale.", "pr", {"baseline": s4_baseline, "metaobjects": s4_metaobjects}), - Scenario("S5", "Edit the database by hand", "Someone runs `ALTER TABLE customers ADD COLUMN vip boolean NOT NULL DEFAULT false` on a live database. The check is each setup's live-database drift command, the one a team runs on a schedule or at deploy time.", + Scenario("S5", "Edit the database by hand", "Someone runs `ALTER TABLE customers ADD COLUMN vip boolean NOT NULL DEFAULT false` on a live database. The check is each setup's live-database drift command, the one a team runs on a schedule or at deploy time. It is run first on the unedited database and must pass, so a red result after the edit is the edit, not a broken tool.", "live", {"baseline": s5, "metaobjects": s5}), Scenario("S6", "Change a field on the partner API", "`name` becomes required on `POST /customers`: a request partners send today without it would be rejected. Generators run; the database follows where the setup derives it.", "pr", {"baseline": s6_baseline, "metaobjects": s6_metaobjects}), @@ -275,9 +275,6 @@ def compat_failed(self) -> bool: def has_compat_check(self) -> bool: return any(n in COMPAT_CHECKS for n in CHECKS[self.setup]) - @property - def clean_gate(self) -> bool: - return self.gate_rc in (0, None) def clean(text: str, work: Path | None = None) -> str: @@ -426,6 +423,9 @@ def run_scenario(sc: Scenario, setup: str, pg: Postgres, tmp: Path) -> Outcome: app = sh("./db-apply.sh", w.path, w.env(H2H_DB=live_db, H2H_DEV_DB=w.db("dev"))) if app.returncode != 0: raise SystemExit(f"{setup}: could not provision the live database:\n{app.stdout}") + control = sh("./live-drift.sh", w.path, w.env(H2H_DB=live_db, H2H_DEV_DB=w.db("devcontrol"))) + if control.returncode != 0: + raise SystemExit(f"{setup}: live-drift reports drift on an unedited database:\n{control.stdout}") edit = pg.psql("ALTER TABLE customers ADD COLUMN vip boolean NOT NULL DEFAULT false", live_db) if edit.returncode != 0: raise SystemExit(f"hand edit failed: {edit.stderr}") @@ -450,8 +450,7 @@ def first_line(cmd: str | list[str], cwd: Path) -> str: def npm_version(setup: str, pkg: str) -> str: p = ROOT / setup / "node_modules" / pkg / "package.json" - import json as _json - return _json.loads(p.read_text())["version"] if p.exists() else "?" + return json.loads(p.read_text())["version"] if p.exists() else "?" def py_version(setup: str, dist: str) -> str: @@ -521,10 +520,14 @@ def check_shared_probe_files() -> None: # --------------------------------------------------------------------------- scorecard +def control_ok(o: Outcome) -> bool: + return o.gate_rc == 0 and (o.smoke is None or o.smoke[0]) + + def verdict(o: Outcome) -> str: """One word for the log; the scorecard uses layers_cell/compat_cell.""" if o.scenario.id == "S0": - return "no false alarm" if o.clean_gate and (o.smoke is None or o.smoke[0]) else "FALSE ALARM" + return "no false alarm" if control_ok(o) else "FALSE ALARM" return "layers red" if o.layer_failed else ("partner break flagged" if o.compat_failed else "all green") @@ -536,7 +539,7 @@ def probe(o: Outcome) -> str: def layers_cell(o: Outcome) -> str: if o.scenario.id == "S0": - return ("✅ **no false alarm**" if o.clean_gate and (o.smoke is None or o.smoke[0]) else "⚠️ **FALSE ALARM**") \ + return ("✅ **no false alarm**" if control_ok(o) else "⚠️ **FALSE ALARM**") \ + f" · exit {o.gate_rc} · {probe(o)}" parts = ["🔴 **red**: " + ", ".join(f"`{n}`" for n in o.layer_failed)] if o.layer_failed else ["🟢 **green**"] if o.smoke is not None: @@ -659,8 +662,8 @@ def render_scorecard(outcomes: dict[tuple[str, str], Outcome], versions, counts, w("") w("## Lines each setup needs") w("") - w("One rule for both: every committed file in the setup's directory counts, minus lock files and " - "machine-generated output (listed below), minus the smoke probe (identical in both). Blank lines and " + w("One rule for both: every committed file in the setup's directory counts, minus lock files, " + "machine-generated output (listed below), the smoke probe (identical in both) and `.gitignore`. Blank lines and " "comment-only lines do not count. The numbers are from the files in this repository at the commit that " "generated this page.") w("") @@ -712,8 +715,6 @@ def render_scorecard(outcomes: dict[tuple[str, str], Outcome], versions, counts, def main() -> int: ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("scenarios", nargs="*", help="scenario ids to run (default: all), e.g. S1 S3") - ap.add_argument("--out", default=str(ROOT / "SCORECARD.md"), help="where to write the scorecard") - ap.add_argument("--json", help="also write raw results to this path") ap.add_argument("--keep", help="run in this directory and keep every scenario's working copy and logs") args = ap.parse_args() @@ -722,6 +723,9 @@ def main() -> int: raise SystemExit(f"missing prerequisite: {tool}") if subprocess.run(["docker", "info"], capture_output=True).returncode != 0: raise SystemExit("docker is installed but its daemon is not reachable") + unknown = sorted(set(args.scenarios) - {s.id for s in SCENARIOS}) + if unknown: + ap.error(f"unknown scenario id(s): {', '.join(unknown)}; choose from {', '.join(s.id for s in SCENARIOS)}") wanted = [s for s in SCENARIOS if not args.scenarios or s.id in args.scenarios or s.id == "S0"] check_shared_probe_files() @@ -750,15 +754,12 @@ def main() -> int: f"smoke {'-' if o.smoke is None else ('ok' if o.smoke[0] else 'FAIL')}) {o.seconds:.0f}s") card = render_scorecard(outcomes, versions, counts, datetime.now(timezone.utc).strftime("%Y-%m-%d"), wanted, time.time() - t0) - Path(args.out).write_text(card, encoding="utf-8") - log(f"wrote {args.out}") - if args.json: - Path(args.json).write_text(json.dumps({f"{k[0]}/{k[1]}": { - "gate_rc": v.gate_rc, "checks": {n: ok for n, (ok, _) in v.checks.items()}, - "smoke": None if v.smoke is None else v.smoke[0], "dev_notes": v.dev_notes} - for k, v in outcomes.items()}, indent=2), encoding="utf-8") - control_ok = all(verdict(outcomes[("S0", s)]) == "no false alarm" for s in SETUPS) - if not control_ok: + if args.scenarios: + print(card) + else: + (ROOT / "SCORECARD.md").write_text(card, encoding="utf-8") + log("wrote SCORECARD.md") + if not all(control_ok(outcomes[("S0", s)]) for s in SETUPS): log("the control scenario failed: a setup is red with no change, fix that before reading anything else") return 1 return 0 diff --git a/examples/head-to-head/run.sh b/examples/head-to-head/run.sh index 34291d477d..0a177320c3 100755 --- a/examples/head-to-head/run.sh +++ b/examples/head-to-head/run.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -# Run every drift scenario against both setups and write SCORECARD.md. +# Run the drift scenarios against both setups. A full run writes SCORECARD.md. # Needs Docker (Postgres, Atlas and oasdiff run as pinned images), Node.js 20+, uv and git. -# ./run.sh all scenarios -# ./run.sh S1 S6 only these (the S0 control always runs) +# ./run.sh all scenarios, rewrites SCORECARD.md +# ./run.sh S1 S6 only these (the S0 control always runs); prints, does not write SCORECARD.md set -euo pipefail exec python3 "$(dirname "$0")/harness/run.py" "$@" From 1e187a24b348f3fef2e7462a5fc2558b79378e8b Mon Sep 17 00:00:00 2001 From: Doug Mealing Date: Sat, 31 Oct 2026 02:18:08 -0400 Subject: [PATCH 4/4] no-mistakes(document): Link head-to-head demo from root README and docs index --- README.md | 3 ++- docs/README.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d700d62a7f..71aed63937 100644 --- a/README.md +++ b/README.md @@ -311,7 +311,8 @@ metaobjects/ │ ├── superpowers/specs/ # design specs │ └── RELEASING.md # npm publish procedure ├── examples/ -│ └── advanced-modeling/ # a worked, runnable non-toy model +│ ├── advanced-modeling/ # a worked, runnable non-toy model +│ └── head-to-head/ # the same app set up twice (DIY stack vs MetaObjects), self-measured scorecard ├── library/ # shipped libraries, opt-in per project via `libraries: [...]` │ ├── ai/ # the LLM-call trace envelope (stable) │ └── iam/ # users, groups, roles, grants (preview) diff --git a/docs/README.md b/docs/README.md index 1f3a9dc2c5..372048eaec 100644 --- a/docs/README.md +++ b/docs/README.md @@ -82,6 +82,7 @@ this tree is documentation, not the source of truth. | Know which feature is supported in which port today | the capability matrix in the root [`README.md`](../README.md) or the per-port "Capability snapshot" table | | See which conformance fixtures gate each feature (and which port passes which) | [`CONFORMANCE.md`](CONFORMANCE.md) | | See a worked, non-toy model end to end (projections, value objects, TPH, prompt payloads on one runnable spine) | [`../examples/advanced-modeling/`](../examples/advanced-modeling/) | +| Compare MetaObjects with a do-it-yourself stack on the same app (drift scenarios, self-measured scorecard) | [`../examples/head-to-head/`](../examples/head-to-head/) | | Read the canonical spec (target-agnostic) | [`../spec/`](../spec/) | | Find a release recipe (Cloudflare D1, etc.) | [`recipes/`](recipes/) | | Keep drizzle-kit migrations while MetaObjects owns the model | [`recipes/drizzle-kit-migrations.md`](recipes/drizzle-kit-migrations.md) |