From e042498dfdfd4d5384b5ac9320df9b5a9a84e697 Mon Sep 17 00:00:00 2001 From: sdairs Date: Mon, 5 Oct 2026 11:32:59 +0100 Subject: [PATCH] Add PostGraphile and Apollo workspace notebook example --- .github/workflows/project-graphql.yml | 60 + applications/project-graphql/.gitignore | 7 + applications/project-graphql/.node-version | 1 + applications/project-graphql/README.md | 181 + .../project-graphql/docs/project-graphql.png | Bin 0 -> 87959 bytes applications/project-graphql/index.html | 1 + .../project-graphql/package-lock.json | 4184 +++++++++++++++++ applications/project-graphql/package.json | 37 + .../project-graphql/playwright.config.ts | 10 + applications/project-graphql/schema.graphql | 730 +++ .../project-graphql/scripts/export-schema.ts | 10 + .../project-graphql/scripts/seed-users.ts | 28 + applications/project-graphql/sql/roles.sql | 8 + applications/project-graphql/sql/schema.sql | 40 + applications/project-graphql/sql/seed.sql | 14 + .../project-graphql/src/client/main.tsx | 510 ++ .../project-graphql/src/client/operations.ts | 117 + .../project-graphql/src/client/style.css | 357 ++ .../project-graphql/src/client/vite-env.d.ts | 1 + .../project-graphql/src/server/database.ts | 38 + .../project-graphql/src/server/index.ts | 166 + .../project-graphql/src/server/limits.ts | 71 + .../project-graphql/src/server/preset.ts | 40 + .../project-graphql/src/server/sessions.ts | 74 + .../project-graphql/tests/api.test.ts | 306 ++ .../tests/browser/workspace.spec.ts | 136 + .../project-graphql/tests/session.test.ts | 59 + applications/project-graphql/tsconfig.json | 16 + applications/project-graphql/vite.config.ts | 3 + 29 files changed, 7205 insertions(+) create mode 100644 .github/workflows/project-graphql.yml create mode 100644 applications/project-graphql/.gitignore create mode 100644 applications/project-graphql/.node-version create mode 100644 applications/project-graphql/README.md create mode 100644 applications/project-graphql/docs/project-graphql.png create mode 100644 applications/project-graphql/index.html create mode 100644 applications/project-graphql/package-lock.json create mode 100644 applications/project-graphql/package.json create mode 100644 applications/project-graphql/playwright.config.ts create mode 100644 applications/project-graphql/schema.graphql create mode 100644 applications/project-graphql/scripts/export-schema.ts create mode 100644 applications/project-graphql/scripts/seed-users.ts create mode 100644 applications/project-graphql/sql/roles.sql create mode 100644 applications/project-graphql/sql/schema.sql create mode 100644 applications/project-graphql/sql/seed.sql create mode 100644 applications/project-graphql/src/client/main.tsx create mode 100644 applications/project-graphql/src/client/operations.ts create mode 100644 applications/project-graphql/src/client/style.css create mode 100644 applications/project-graphql/src/client/vite-env.d.ts create mode 100644 applications/project-graphql/src/server/database.ts create mode 100644 applications/project-graphql/src/server/index.ts create mode 100644 applications/project-graphql/src/server/limits.ts create mode 100644 applications/project-graphql/src/server/preset.ts create mode 100644 applications/project-graphql/src/server/sessions.ts create mode 100644 applications/project-graphql/tests/api.test.ts create mode 100644 applications/project-graphql/tests/browser/workspace.spec.ts create mode 100644 applications/project-graphql/tests/session.test.ts create mode 100644 applications/project-graphql/tsconfig.json create mode 100644 applications/project-graphql/vite.config.ts diff --git a/.github/workflows/project-graphql.yml b/.github/workflows/project-graphql.yml new file mode 100644 index 00000000..7a231a1d --- /dev/null +++ b/.github/workflows/project-graphql.yml @@ -0,0 +1,60 @@ +name: Project GraphQL +on: + push: + paths: ['applications/project-graphql/**', '.github/workflows/project-graphql.yml'] + pull_request: + paths: ['applications/project-graphql/**', '.github/workflows/project-graphql.yml'] +permissions: + contents: read +jobs: + validate: + runs-on: ubuntu-latest + defaults: + run: + working-directory: applications/project-graphql + services: + postgres: + image: postgres:18 + env: + POSTGRES_PASSWORD: ci-admin-not-for-use + ports: ['5432:5432'] + options: >- + --health-cmd pg_isready --health-interval 5s --health-timeout 5s --health-retries 12 + env: + PGHOST: 127.0.0.1 + PGPORT: 5432 + PGDATABASE: postgres + PGUSER: postgres + PGPASSWORD: ci-admin-not-for-use + PGSSLMODE: disable + PG_MIGRATION_PASSWORD: ci-migration-not-for-use + PG_APP_PASSWORD: ci-runtime-not-for-use + DATABASE_LOCAL: 'true' + DATABASE_URL: postgresql://tracker_api:ci-runtime-not-for-use@127.0.0.1:5432/postgres + MIGRATION_DATABASE_URL: postgresql://tracker_migration:ci-migration-not-for-use@127.0.0.1:5432/postgres + APP_ORIGIN: http://127.0.0.1:3002 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '24.21.0' + cache: npm + cache-dependency-path: applications/project-graphql/package-lock.json + - run: npm ci + - name: Apply dedicated local roles and schema + run: | + psql -X -v ON_ERROR_STOP=1 -f sql/roles.sql + PGUSER=tracker_migration PGPASSWORD="$PG_MIGRATION_PASSWORD" psql -X -v ON_ERROR_STOP=1 -f sql/schema.sql + PGUSER=tracker_migration PGPASSWORD="$PG_MIGRATION_PASSWORD" psql -X -v ON_ERROR_STOP=1 -f sql/seed.sql + node scripts/seed-users.ts + touch .env + - run: npm run schema && git diff --exit-code -- schema.graphql + - run: npm run typecheck && npm run build && npm test + - name: Exercise SQL-derived API and real restricted roles + run: | + npm start > /tmp/project-graphql-server.log 2>&1 & + for attempt in $(seq 1 20); do + if curl --fail --silent http://127.0.0.1:3002/api/me >/dev/null; then break; fi + sleep 1 + done + npm run test:integration diff --git a/applications/project-graphql/.gitignore b/applications/project-graphql/.gitignore new file mode 100644 index 00000000..803294a9 --- /dev/null +++ b/applications/project-graphql/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +dist/ +.env +.deployment/ +.clickhouse/ +test-results/ +playwright-report/ diff --git a/applications/project-graphql/.node-version b/applications/project-graphql/.node-version new file mode 100644 index 00000000..df6ae337 --- /dev/null +++ b/applications/project-graphql/.node-version @@ -0,0 +1 @@ +24.21.0 diff --git a/applications/project-graphql/README.md b/applications/project-graphql/README.md new file mode 100644 index 00000000..02e86d1a --- /dev/null +++ b/applications/project-graphql/README.md @@ -0,0 +1,181 @@ +# Common Ground: a database-first project notebook + +A two-workspace project and task app using **PostGraphile 5**, **Grafast** and **Apollo Client**, backed by ClickHouse Managed Postgres. SQL foreign keys generate the nested GraphQL API; verified synthetic-account sessions select the database role and workspace. Postgres row-level security enforces the boundary. + +The browser creates projects, adds tasks, updates checkboxes and pages through real cursor connections. Signing into another workspace replaces its Apollo client and normalized cache. The server executes the generated schema with Grafast; there are no handwritten project/task resolvers. + +![Actual managed Postgres notebook after an app restart](docs/project-graphql.png) + +## Tools and source + +Use an isolated Linux VM for dependencies. On macOS, [OrbStack isolated machines](https://docs.orbstack.dev/machines/isolated) provide a convenient option: + +```sh +orbctl create ubuntu:noble ground-dev --isolated --arch arm64 --memory 4G --cpus 2 +orbctl run -m ground-dev bash +``` + +Inside Linux, install Docker, curl, git, jq, OpenSSL and a Postgres client. Install [Node.js 24 LTS](https://nodejs.org/en/download) and confirm `node --version` reports `v24.21.0`; its native TypeScript support runs the server without a transpiler. Install clickhousectl: + +```sh +curl -fsSL https://clickhouse.com/cli | sh +export PATH="$HOME/.local/bin:$PATH" +git clone https://github.com/ClickHouse/examples.git +cd examples/applications/project-graphql +npm ci +``` + +The lockfile pins PostGraphile 5.1.5, Grafast 1.1.4 and Apollo Client 4.3.1. Cloud lifecycle commands can use an existing host clickhousectl; copy only database credentials and the CA into the VM, keeping Cloud API credentials on the host. + +## Choose a fresh database + +### Local Postgres + +Docker must be running and accessible to your Linux user. The local receipt contains a password: + +```sh +umask 077 +mkdir -p .deployment +clickhousectl local postgres start --name common-ground --version 18 --json \ + > .deployment/local-create.json +export PGHOST=127.0.0.1 +export PGPORT="$(jq -er '.port' .deployment/local-create.json)" +export PGDATABASE="$(jq -er '.database' .deployment/local-create.json)" +export PGADMIN="$(jq -er '.user' .deployment/local-create.json)" +export PGPASSWORD="$(jq -er '.password' .deployment/local-create.json)" +export PGSSLMODE=disable +export DATABASE_LOCAL=true +``` + +### ClickHouse Managed Postgres + +Create an Admin API key using the [Cloud API guide](https://clickhouse.com/docs/cloud/manage/openapi), then authenticate interactively: + +```sh +clickhousectl cloud auth login --interactive +clickhousectl cloud auth status +clickhousectl cloud org list +umask 077 +mkdir -p .deployment +``` + +Save your organization UUID, available region and size as `CH_ORG_ID`, `CLOUD_REGION` and `PG_SIZE` in `.deployment/resources.env`. Review [pricing](https://clickhouse.com/pricing); the managed service continues to incur charges when the app or VM stops. + +```sh +source .deployment/resources.env +clickhousectl cloud postgres create --org-id "$CH_ORG_ID" \ + --name project-graphql-example --provider aws --region "$CLOUD_REGION" \ + --size "$PG_SIZE" --pg-version 18 --ha-type none \ + --tag project=project-graphql --json > .deployment/postgres-create.json +PG_SERVICE_ID="$(jq -er '.id' .deployment/postgres-create.json)" +``` + +Save the ID in the private resources file. Preserve the create receipt: its initial password is returned once. If creation is interrupted, inspect the organization service list before retrying. Repeat inspection until the service is running: + +```sh +clickhousectl cloud postgres get "$PG_SERVICE_ID" --org-id "$CH_ORG_ID" --json \ + > .deployment/postgres-status.json +jq '{id,state,hostname,username,postgresVersion,size}' .deployment/postgres-status.json +clickhousectl cloud postgres certs get "$PG_SERVICE_ID" --org-id "$CH_ORG_ID" \ + --output .deployment/postgres-ca.pem +export PGHOST="$(jq -er '.hostname' .deployment/postgres-status.json)" +export PGPORT=5432 PGDATABASE=postgres +export PGADMIN="$(jq -er '.username' .deployment/postgres-status.json)" +export PGPASSWORD="$(jq -er '.password' .deployment/postgres-create.json)" +export PGSSLMODE=verify-full +export PGSSLROOTCERT="$PWD/.deployment/postgres-ca.pem" +export PG_CA_CERT_PATH="$PGSSLROOTCERT" +unset DATABASE_LOCAL +``` + +These are direct-service port/database defaults; the actual service response is authoritative. The runtime connector requires the supplied CA and hostname verification. It rejects TLS settings embedded in URL query parameters. + +## Roles, schema and synthetic accounts + +Apply once to your dedicated fresh database. Passwords stay in ignored files and environment variables rather than command arguments: + +```sh +cat > .deployment/passwords.env < .env <> .env +else + printf 'PG_CA_CERT_PATH=%s\n' "$PG_CA_CERT_PATH" >> .env +fi +chmod 600 .env +unset PG_MIGRATION_PASSWORD PG_APP_PASSWORD +``` + +`tracker_migration` owns the schemas. `tracker_api` is a restricted `NOINHERIT` login; it can validate hashed passwords and opaque sessions in `tracker_private`, but does not inherit project access. GraphQL transactions assume `tracker_member` and set `app.workspace_id` from the verified session. Settings are transaction-local. The exposed `tracker` tables all have RLS; tasks also have a composite project/workspace foreign key. + +Synthetic accounts: **alice@example.test / river-demo** uses Riverside Studio; **bob@example.test / summit-demo** uses Summit Team. These published demo credentials are for a trusted local demonstration, not a production identity service. Passwords use salted scrypt verifiers; the database stores only hashes of random session tokens. Cookies are HttpOnly and SameSite=Strict; the app binds loopback and enforces its configured Host and Origin. There is no GraphiQL route or public schema introspection endpoint. + +## Run and explore + +```sh +npm run schema +npm run typecheck +npm run build +npm start +``` + +Open `http://127.0.0.1:3002` inside Linux or directly from the host using +[OrbStack's automatic port forwarding](https://docs.orbstack.dev/machines/network#running-servers). +Use a unique `APP_ORIGIN` port when running several VMs; the app binds that +loopback port and validates the same Host/Origin. A same-port SSH tunnel can +conflict with OrbStack's forwarding. + +Sign in, create a project, add a task and complete it. Switch workspaces and confirm the other team's rows appear. Switch back and inspect the saved task. Projects load 20 at a time and nested tasks load 50; the UI shows loaded/total counts and offers **Load more**. Timestamp indexes expose generated `CREATED_AT_DESC` ordering; a new row appears on the refreshed first page. The selected project has its own detail query, independent of list pagination. + +`schema.graphql` is generated by the installed PostGraphile version using member-role introspection. `src/client/operations.ts` contains typed Apollo query/mutation documents. `src/server/preset.ts` connects SQL-derived schema construction to verified per-request Grafast context; `src/server/index.ts` uses Grafast's documented schema execution API. The request layer limits body size, operation length, expanded depth/field count and connection page sizes, including fragments. + +## Validate + +With the app running against a dedicated test database: + +```sh +npm test +npm run test:integration +npx playwright install --with-deps chromium +npm run test:browser +``` + +Integration tests add pagination fixtures and saved rows. They exercise real HTTP sessions, nested workspace isolation, forged scope headers, cross-workspace writes, actual member-role privilege denials, pooled context reset, connection bounds and pages beyond 20 projects/50 tasks. Browser tests use actual Apollo queries/mutations and cache replacement. A separate browser control delays an old session response and simulates network failure to verify UI ordering/error handling; it does not claim server-side rejection of those simulated failures. + +Verified against local Postgres 18 and ClickHouse Managed Postgres 18.6: typecheck/build, three unit checks, seven API/role checks and two browser tests passed. A separate real app process restart preserved the verified session, project, three tasks and completed checkbox. Managed TLS was verified with the supplied CA; unrelated CA and a reachable server under an incorrect URL hostname both failed certificate validation. + +To inspect persistence, create a task, complete it, stop the app and restart `npm start` with the same `.env`. The session, project and task should remain saved; no data lives solely in the Apollo cache. + +## Cleanup + +Stop the application first. For the local fixture: + +```sh +clickhousectl local postgres stop --name common-ground +``` + +For your owned disposable managed service: + +```sh +source .deployment/resources.env +clickhousectl cloud postgres delete "$PG_SERVICE_ID" --org-id "$CH_ORG_ID" --json +clickhousectl cloud postgres list --org-id "$CH_ORG_ID" --json > .deployment/postgres-after.json +jq --arg id "$PG_SERVICE_ID" '[.[] | select(.id == $id)]' .deployment/postgres-after.json +``` + +Confirm the list contains no service with that ID. Delete only the resource you created for this example; retain private receipts until cleanup is verified. diff --git a/applications/project-graphql/docs/project-graphql.png b/applications/project-graphql/docs/project-graphql.png new file mode 100644 index 0000000000000000000000000000000000000000..e38a83b557ca16b73f3ce0d5a530b45159802499 GIT binary patch literal 87959 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

q}PWPbJ*^yL5RZ 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 z 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 MnU-Sd)0gl62NjZ5P5=M^ literal 0 HcmV?d00001 diff --git a/applications/project-graphql/index.html b/applications/project-graphql/index.html new file mode 100644 index 00000000..eedcf089 --- /dev/null +++ b/applications/project-graphql/index.html @@ -0,0 +1 @@ +Common Ground — workspace projects

diff --git a/applications/project-graphql/package-lock.json b/applications/project-graphql/package-lock.json new file mode 100644 index 00000000..40ce708a --- /dev/null +++ b/applications/project-graphql/package-lock.json @@ -0,0 +1,4184 @@ +{ + "name": "project-graphql", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "project-graphql", + "dependencies": { + "@apollo/client": "4.3.1", + "grafast": "1.1.4", + "graphql": "16.12.0", + "pg": "8.23.1", + "postgraphile": "5.1.5", + "react": "19.3.0", + "react-dom": "19.3.0", + "rxjs": "7.8.2" + }, + "devDependencies": { + "@playwright/test": "1.63.0", + "@types/node": "24.10.1", + "@types/pg": "8.23.1", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "@vitejs/plugin-react": "6.1.1", + "typescript": "7.0.2", + "vite": "8.3.2" + }, + "engines": { + "node": ">=24 <25" + } + }, + "node_modules/@apollo/client": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-4.3.1.tgz", + "integrity": "sha512-MFHzUfHxS+A6AzH+cS4896jHuSTRw5ACUANijz1gyesWZDLsbgZ17oCb/KfRAOUuoUr8AzCaBE+xJPi7R7ZOCA==", + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@wry/caches": "^1.0.0", + "@wry/equality": "^0.5.6", + "@wry/trie": "^0.5.0", + "graphql-tag": "^2.12.6", + "optimism": "^0.18.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "graphql": "^16.0.0 || ^17.0.0", + "graphql-ws": "^5.5.5 || ^6.0.3", + "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc", + "react-dom": "^17.0.0 || ^18.0.0 || >=19.0.0-rc", + "rxjs": "^7.3.0", + "subscriptions-transport-ws": "^0.9.0 || ^0.11.0" + }, + "peerDependenciesMeta": { + "graphql-ws": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "subscriptions-transport-ws": { + "optional": true + } + } + }, + "node_modules/@dataplan/json": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@dataplan/json/-/json-1.0.1.tgz", + "integrity": "sha512-wE+FK5rravDZMCd5xQNk5gZtfoVooVHREQWQ15oRJzOPbypZHDvRIMilWbKzgW8uJy3mGfVSP6xWZqxm/3651A==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "grafast": "^1.0.0-rc.8" + } + }, + "node_modules/@dataplan/pg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@dataplan/pg/-/pg-1.1.2.tgz", + "integrity": "sha512-LJgq83lFksruzF+z0fnUP23CR6vhuCvIUomAgyTSSBSiDHcNJMkSajAibJtM8CAsDnx8CX+LsWLj0EMNH3THOw==", + "license": "MIT", + "dependencies": { + "@dataplan/json": "^1.0.1", + "@graphile/lru": "^5.0.0", + "@types/node": "^22.19.1", + "chalk": "^4.1.2", + "debug": "^4.4.3", + "eventemitter3": "^5.0.1", + "grafast": "^1.1.3", + "pg-sql2": "^5.0.1", + "postgres-array": "~3.0.4", + "postgres-range": "^1.1.4", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@dataplan/json": "^1.0.1", + "grafast": "^1.1.3", + "graphile-config": "^1.0.0-rc.5", + "graphql": "^16.9.0", + "pg": "^8.7.1", + "pg-sql2": "^5.0.1" + }, + "peerDependenciesMeta": { + "pg": { + "optional": true + } + } + }, + "node_modules/@dataplan/pg/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==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@dataplan/pg/node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/@dataplan/pg/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==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.8.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@graphile/lru": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@graphile/lru/-/lru-5.0.0.tgz", + "integrity": "sha512-NeRBDdUd/l4H284HrYL2/wNHv/FmW5stAMPFAiBZanLHwq9J3suZTtyN5CwTxUFA/vgqzu0B1/9XtIEaJYEKig==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@graphiql/plugin-doc-explorer": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@graphiql/plugin-doc-explorer/-/plugin-doc-explorer-0.4.4.tgz", + "integrity": "sha512-N+AVh4KFjX8ooZUXj3jBPmz8rshXM0kIl+vV6TiyQgMZtGSpPohIn2NdSYc8ukRmZEfMzYT9gqLw4446SXz1lw==", + "license": "MIT", + "dependencies": { + "@headlessui/react": "^2.2", + "react-compiler-runtime": "19.1.0-rc.1", + "zustand": "^5" + }, + "peerDependencies": { + "@graphiql/react": "^0.39.0", + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0", + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, + "node_modules/@graphiql/plugin-history": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@graphiql/plugin-history/-/plugin-history-0.4.4.tgz", + "integrity": "sha512-vbUq8mKmUsEnhLupY+mYerWbRS7FoCsFjRiST+QmJmIGzR6VmsIrmeF2vLKtwx+ztOCqcFGYjZ31oITORBk+aA==", + "license": "MIT", + "dependencies": { + "@graphiql/toolkit": "^0.12.0", + "react-compiler-runtime": "19.1.0-rc.1", + "zustand": "^5" + }, + "peerDependencies": { + "@graphiql/react": "^0.39.0", + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, + "node_modules/@graphiql/plugin-history/node_modules/@graphiql/toolkit": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.12.1.tgz", + "integrity": "sha512-OuHVUIvPL7nz3uAdG9eyZ5cZx87JH+XMOHiXgv3foYZWBPW8FNMGUyFflxP70jhv7bX9UPyYmRsTIJ+GSlK0PQ==", + "license": "MIT", + "dependencies": { + "@n1ru4l/push-pull-async-iterable-iterator": "^3.1.0", + "meros": "^1.1.4" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0", + "graphql-ws": ">= 4.5.0" + }, + "peerDependenciesMeta": { + "graphql-ws": { + "optional": true + } + } + }, + "node_modules/@graphiql/react": { + "version": "0.39.1", + "resolved": "https://registry.npmjs.org/@graphiql/react/-/react-0.39.1.tgz", + "integrity": "sha512-htD3WW8+EhXeFEfUcBsY+t+UT0Abqe3V4AqA+qSAFSxL2AcH37Id1Wwgt0ULFDYRwvlf77xNhhDrWwjebeSiSA==", + "license": "MIT", + "dependencies": { + "@graphiql/toolkit": "^0.12.1", + "@radix-ui/react-dialog": "^1.1", + "@radix-ui/react-dropdown-menu": "^2.1", + "@radix-ui/react-tooltip": "^1.2", + "@radix-ui/react-visually-hidden": "^1.2", + "clsx": "^1.2.1", + "framer-motion": "^13.2", + "get-value": "^4.1.0", + "graphql-language-service": "^5.7.0", + "jsonc-parser": "^3.3.1", + "markdown-it": "^15.0.1", + "monaco-editor": "0.52.2", + "monaco-graphql": "^1.9.0", + "prettier": "^3.5.3", + "react-compiler-runtime": "19.1.0-rc.3", + "set-value": "^4.1.0", + "zustand": "^5" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0", + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, + "node_modules/@graphiql/react/node_modules/@graphiql/toolkit": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.12.1.tgz", + "integrity": "sha512-OuHVUIvPL7nz3uAdG9eyZ5cZx87JH+XMOHiXgv3foYZWBPW8FNMGUyFflxP70jhv7bX9UPyYmRsTIJ+GSlK0PQ==", + "license": "MIT", + "dependencies": { + "@n1ru4l/push-pull-async-iterable-iterator": "^3.1.0", + "meros": "^1.1.4" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0", + "graphql-ws": ">= 4.5.0" + }, + "peerDependenciesMeta": { + "graphql-ws": { + "optional": true + } + } + }, + "node_modules/@graphiql/react/node_modules/react-compiler-runtime": { + "version": "19.1.0-rc.3", + "resolved": "https://registry.npmjs.org/react-compiler-runtime/-/react-compiler-runtime-19.1.0-rc.3.tgz", + "integrity": "sha512-Cssogys2XZu6SqxRdX2xd8cQAf57BBvFbLEBlIa77161lninbKUn/EqbecCe7W3eqDQfg3rIoOwzExzgCh7h/g==", + "license": "MIT", + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^0.0.0-experimental" + } + }, + "node_modules/@graphiql/toolkit": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.11.3.tgz", + "integrity": "sha512-Glf0fK1cdHLNq52UWPzfSrYIJuNxy8h4451Pw1ZVpJ7dtU+tm7GVVC64UjEDQ/v2j3fnG4cX8jvR75IvfL6nzQ==", + "license": "MIT", + "dependencies": { + "@n1ru4l/push-pull-async-iterable-iterator": "^3.1.0", + "meros": "^1.1.4" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0", + "graphql-ws": ">= 4.5.0" + }, + "peerDependenciesMeta": { + "graphql-ws": { + "optional": true + } + } + }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "license": "MIT", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@headlessui/react": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.10.tgz", + "integrity": "sha512-5pVLNK9wlpxTUTy9GpgbX/SdcRh+HBnPktjM2wbiLTH4p+2EPHBO1aoSryUCuKUIItdDWO9ITlhUL8UnUN/oIA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.16", + "@react-aria/focus": "^3.20.2", + "@react-aria/interactions": "^3.25.0", + "@tanstack/react-virtual": "^3.13.9", + "use-sync-external-store": "^1.5.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@internationalized/date": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.4.tgz", + "integrity": "sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.8", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.8.tgz", + "integrity": "sha512-8UmMFia46DUt+k97zKd9fKWXcWHR+k8ae3eYzILETuT2KbIvLyOfac7zesw+sJdRAAZ7Q9pM1Mk22aXp2LD0Ig==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/string": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.10.tgz", + "integrity": "sha512-PDx6//vHSpRnHfxqMqto11zQvhsaU74O3mKv2F/0eicGZcl9NLjQmGlbHz/LsJh5tLKp4A4L7ZVTzN1/MmMTvA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@n1ru4l/push-pull-async-iterable-iterator": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-3.2.0.tgz", + "integrity": "sha512-3fkKj25kEjsfObL6IlKPAlHYPq/oYwUkkQ03zsTTiDjD7vg/RxjdiLeCydqtxHZP0JgsXL3D/X5oAkMGzuUp/Q==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.152.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", + "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@playwright/test": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", + "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", + "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", + "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", + "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-effect-event": "0.0.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.24.tgz", + "integrity": "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.24.tgz", + "integrity": "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-rect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", + "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.16.tgz", + "integrity": "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", + "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", + "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", + "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", + "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", + "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", + "license": "MIT" + }, + "node_modules/@react-aria/focus": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.22.1.tgz", + "integrity": "sha512-CPxtkyrBi/HYY5P3lE/57sQ6qfa0lN8E55TOm89H0kNGv0lKt+/0zP7lWERzBjRr5IxBVrQX4gFEowBN52LPaA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.28.1", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.28.1.tgz", + "integrity": "sha512-Bqb+HrD5I5MHS2SKBhISYqo2SW8Y2dfzgF/Y1lIJq7xqLxheo9vzxPGEHhz+XzkgGfoqEJx8A6a3C7uiqS3HWA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.34.0", + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/shared": { + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.36.1.tgz", + "integrity": "sha512-AzsuD9OfxTOZMMvTRhlN3oHBwOmFN7tDh27LzqmHt4+uOgPhJT7ZM7/kVs/8/o0WxayMUIk3hBmCFRHv1FUoag==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", + "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", + "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", + "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", + "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", + "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", + "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", + "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", + "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", + "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", + "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", + "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", + "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", + "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", + "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", + "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.14.13", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.13.tgz", + "integrity": "sha512-JbDTAwtzZ99aOeCrAfW5EsE5KSq5RWh6Af2dtFwyLIIk48Ja7vm6n4axu/43T3vfjFnEGJalxJ1wyYjSQD6bSg==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.17.11" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.17.11", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.11.tgz", + "integrity": "sha512-+ILjvtHup6Y2hzQ6YzwMgX1Q+oQpxEGOXCEsCNaPoIP0VxMbizIBTmYTDtkerkIQS8/CbP1BRuyt8V/8BCsy1g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.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==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/pluralize": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.33.tgz", + "integrity": "sha512-JOqsl+ZoCpP4e8TDke9W79FDcSgPAR0l6pixx2JHkhnRjvShyYiAYw2LVsnA7K08Y6DeOnaU6ujmENO4os/cYg==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.3.0" + } + }, + "node_modules/@types/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ==", + "license": "MIT" + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", + "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.145.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { + "optional": true + } + } + }, + "node_modules/@wry/caches": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@wry/caches/-/caches-1.0.1.tgz", + "integrity": "sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wry/context": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.4.tgz", + "integrity": "sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wry/equality": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.7.tgz", + "integrity": "sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wry/trie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.5.0.tgz", + "integrity": "sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-3.0.2.tgz", + "integrity": "sha512-mFdDM6WqWKraGLsVb+C9CahPnzTXOefAOLq3jYcca2YZ8bEWpr++Tzj+zSaKW9+X9L5uSxcm1AZ3Y6aZJ09OhQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "PSF-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debounce-promise": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz", + "integrity": "sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.1.0.tgz", + "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz", + "integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/framer-motion": { + "version": "13.5.1", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-13.5.1.tgz", + "integrity": "sha512-HeWOaeHD3kyTSIEMT6+nw7fBn4saIfxxabZyAITbycAl4xr1kzGJKIvGt/OWghSlvIA9qTnZ1zTGelHAe6S8DA==", + "license": "MIT", + "dependencies": { + "motion-dom": "13.5.1", + "motion-utils": "13.5.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "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==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-value": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-4.1.0.tgz", + "integrity": "sha512-mKjrL73VxCUixi0Qe9OuerDVkm+475FeNwM5zZpjK6TK/a7fqv/dGI7vkRgvWkyw1xQ9Fl+wKuOUgPkE6aYB3w==", + "license": "MIT" + }, + "node_modules/grafast": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/grafast/-/grafast-1.1.4.tgz", + "integrity": "sha512-UIVQl8ygG/2nMJxPd6m/l+wpRpptjXSqOKCcqs65eIovjHNKFuXzr24xoX5v1eZcdeIF4BVXDRA1e2YQuYI/KQ==", + "license": "MIT", + "dependencies": { + "@graphile/lru": "^5.0.0", + "chalk": "^4.1.2", + "debug": "^4.4.3", + "eventemitter3": "^5.0.1", + "graphile-config": "^1.1.1", + "graphql": "^16.9.0", + "iterall": "^1.3.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@envelop/core": "^5.0.0", + "graphql": "^16.9.0" + }, + "peerDependenciesMeta": { + "@envelop/core": { + "optional": true + } + } + }, + "node_modules/grafserv": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/grafserv/-/grafserv-1.0.2.tgz", + "integrity": "sha512-tWtj1TUNjVmNLzTiXqigYWUFGkQquI2fcZ1X/ij+q3iXsLLxUlMyodd0d3JxcQTe3rGCE2D42e5glIqbHF/rGA==", + "license": "MIT", + "dependencies": { + "@graphile/lru": "^5.0.0", + "debug": "^4.4.3", + "eventemitter3": "^5.0.1", + "graphile-config": "^1.1.1", + "graphql-ws": "^6.0.5", + "ruru": "^2.0.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@envelop/core": "^5.0.0", + "@whatwg-node/server": "^0.9.64", + "grafast": "^1.0.0-rc.8", + "graphile-config": "^1.1.1", + "graphql": "^16.9.0", + "h3": "^1.13.0", + "hono": "^4.6.15", + "ws": "^8.12.1" + }, + "peerDependenciesMeta": { + "@envelop/core": { + "optional": true + }, + "@whatwg-node/server": { + "optional": true + }, + "h3": { + "optional": true + }, + "hono": { + "optional": true + }, + "ws": { + "optional": true + } + } + }, + "node_modules/graphile-build": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/graphile-build/-/graphile-build-5.1.1.tgz", + "integrity": "sha512-L+UvIp56hE7UZjS9HY21xeype4D94L5yk6lshxcK1BDOeNsXp3cizP4N0ev5kTjeDo/vGpHkBOgzwiCoa1qx9A==", + "license": "MIT", + "dependencies": { + "@types/node": "^22.19.1", + "@types/pluralize": "^0.0.33", + "@types/semver": "^7.7.1", + "chalk": "^4.1.2", + "debug": "^4.4.3", + "graphile-config": "^1.1.0", + "graphql": "^16.9.0", + "lodash": "^4.18.1", + "pluralize": "^7.0.0", + "semver": "^7.7.3", + "tamedevil": "^0.1.1", + "transliteration": "^2.3.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "grafast": "^1.0.0-rc.8", + "graphile-config": "^1.0.0-rc.5", + "graphql": "^16.9.0" + } + }, + "node_modules/graphile-build-pg": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/graphile-build-pg/-/graphile-build-pg-5.1.5.tgz", + "integrity": "sha512-SUHA5hz5nnFIkjj9pFQtQpIE29O8syx8uAeIt/oRIjB+zOw6phXUm19VHX+d2HwhzRMgJmiszMDLgNddXmjHNA==", + "license": "MIT", + "dependencies": { + "@types/node": "^22.19.1", + "debug": "^4.4.3", + "graphile-config": "^1.1.1", + "jsonwebtoken": "^9.0.2", + "pg-introspection": "^1.0.1", + "pg-sql2": "^5.0.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@dataplan/pg": "^1.0.0-rc.7", + "grafast": "^1.0.0-rc.8", + "graphile-build": "^5.0.0-rc.5", + "graphile-config": "^1.0.0-rc.5", + "graphql": "^16.9.0", + "pg": "^8.7.1", + "pg-sql2": "^5.0.0-rc.4", + "tamedevil": "^0.1.0-rc.5" + }, + "peerDependenciesMeta": { + "pg": { + "optional": true + } + } + }, + "node_modules/graphile-build-pg/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==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/graphile-build-pg/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==", + "license": "MIT" + }, + "node_modules/graphile-build/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==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/graphile-build/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==", + "license": "MIT" + }, + "node_modules/graphile-config": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/graphile-config/-/graphile-config-1.1.1.tgz", + "integrity": "sha512-gRDzLguvWZIrYG58Kx+gJbGTExrJ98mM8wZ8l3x5lB9B1e+S3WopgvcTFVWOO2pSDVaZiZAR8zE/Z0d+E+MG2Q==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "debug": "^4.4.3", + "interpret": "^3.1.1", + "semver": "^7.7.3", + "tslib": "^2.8.1", + "yargs": "^17.7.2" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/graphile-utils": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/graphile-utils/-/graphile-utils-5.0.3.tgz", + "integrity": "sha512-imT1voKDD6qGtlYVLfye9Wo33zgV8LzT7hPxF4SZMekx+iyEH8PmR4gWMqScMPfo69cL49zGwy+0hETAr9kXrg==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "json5": "^2.2.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@dataplan/pg": "^1.0.0-rc.7", + "grafast": "^1.0.0-rc.8", + "graphile-build": "^5.0.0-rc.5", + "graphile-build-pg": "^5.0.0-rc.7", + "graphile-config": "^1.0.0-rc.5", + "graphql": "^16.9.0", + "tamedevil": "^0.1.0-rc.5" + }, + "peerDependenciesMeta": { + "graphile-build-pg": { + "optional": true + } + } + }, + "node_modules/graphiql": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/graphiql/-/graphiql-5.4.0.tgz", + "integrity": "sha512-8wmv8T1KqvKWm77ws3hkncio/dSkWlgrslee6GBgiN2h/x0LQNDn4jgG2OGPWiss/Gl5GHyH74kfeqDjcCXWRg==", + "license": "MIT", + "dependencies": { + "@graphiql/plugin-doc-explorer": "^0.4.4", + "@graphiql/plugin-history": "^0.4.4", + "@graphiql/react": "^0.39.0", + "react-compiler-runtime": "19.1.0-rc.1" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0", + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, + "node_modules/graphql": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", + "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-language-service": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-5.7.0.tgz", + "integrity": "sha512-5mwDb9HyHxMcO9RCLLieg6rC1u9XKJKWhFuvaZFJi3broQZDz1fcFNlm96UCMrIgI6MSsb9RwwqKKp2sZOZ9Gw==", + "license": "MIT", + "dependencies": { + "debounce-promise": "^3.1.2", + "nullthrows": "^1.0.0", + "vscode-languageserver-types": "^3.17.1" + }, + "bin": { + "graphql": "dist/temp-bin.js" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/graphql-tag": { + "version": "2.12.7", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.7.tgz", + "integrity": "sha512-xnE/NFzy+0eIesvAsREJZ284zTl/wYuBAvpsFSDhRGRdRHdnE90M21Q3xAWyYInb0J756c6x0pIQ62+vtvOs1Q==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/graphql-ws": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-6.3.0.tgz", + "integrity": "sha512-qBAXPtFjWHF3QypDyNcBp8Rycd+rmofxUSJsu3bGI9QMMq7pImKBSLSg9hZ2ojIMbJ54T/LPjKBIeOUoJbNBnQ==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@fastify/websocket": "^10 || ^11", + "crossws": "~0.3 || ~0.4", + "graphql": "^15.10.1 || ^16 || ^17", + "ws": "^8" + }, + "peerDependenciesMeta": { + "@fastify/websocket": { + "optional": true + }, + "crossws": { + "optional": true + }, + "ws": { + "optional": true + } + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-primitive": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz", + "integrity": "sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/iterall": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz", + "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/linkify-it": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-6.1.0.tgz", + "integrity": "sha512-wJ/TwpSDTLepCrQoYWYIExIKg5Zchex2Nn5yk2mFnB+6PtdkHtyLx742md9csRjjOnGkKIS/RrbY7l8D6gT9Vw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^3.0.0" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/markdown-it": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-15.0.2.tgz", + "integrity": "sha512-q4IGxMv56jCqT4OCRCADBoDP3LO4MhmTXjFbphHPXs4g3j9Xg5RDnxqN8IF/3vIWEU+VCnUq+7JUg/cfy2E6Qw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^3.0.0", + "entities": "^8.0.0", + "linkify-it": "^6.0.0", + "mdurl": "^2.1.0", + "punycode.js": "^2.3.1", + "uc.micro": "^3.0.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/mdurl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.1.0.tgz", + "integrity": "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==", + "license": "MIT" + }, + "node_modules/meros": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/meros/-/meros-1.3.2.tgz", + "integrity": "sha512-Q3mobPbvEx7XbwhnC1J1r60+5H6EZyNccdzSz0eGexJRwouUtTZxPVRGdqKtxlpD84ScK4+tIGldkqDtCKdI0A==", + "license": "MIT", + "engines": { + "node": ">=13" + }, + "peerDependencies": { + "@types/node": ">=13" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/monaco-editor": { + "version": "0.52.2", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", + "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==", + "license": "MIT" + }, + "node_modules/monaco-graphql": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/monaco-graphql/-/monaco-graphql-1.9.0.tgz", + "integrity": "sha512-zIK4Abg7KpJ6E0nis75wktpQ6vgDTKOEMwtrqa2mMIv7D+Q2n42cr75LCuWfO144xqbTjxyav7MbI2ViIy6q3w==", + "license": "MIT", + "dependencies": { + "graphql-language-service": "^5.7.0", + "picomatch-browser": "^2.2.6" + }, + "peerDependencies": { + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0", + "monaco-editor": ">= 0.20.0 < 0.53", + "prettier": "^2.8.0 || ^3.0.0" + } + }, + "node_modules/motion-dom": { + "version": "13.5.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-13.5.1.tgz", + "integrity": "sha512-/7CVCD20Mz1KobktorpdgvowV0V41J7UxqE+qWS3GkSJqrRr5ft+2RloOYtEKv+wvBp3JaSFBclhxt4ja8ovHg==", + "license": "MIT", + "dependencies": { + "motion-utils": "13.5.0" + } + }, + "node_modules/motion-utils": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-13.5.0.tgz", + "integrity": "sha512-Hqe/sqjIKliPZg8sVjWAkdySDPJNIeKH+euVkDOG8ri3DUUU7ZjndmHsGqEPz2iHpiyVaV5I22iguCMHU2he+Q==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "license": "MIT" + }, + "node_modules/optimism": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.18.1.tgz", + "integrity": "sha512-mLXNwWPa9dgFyDqkNi54sjDyNJ9/fTI6WGBLgnXku1vdKY/jovHfZT5r+aiVeFFLOz+foPNOm5YJ4mqgld2GBQ==", + "license": "MIT", + "dependencies": { + "@wry/caches": "^1.0.0", + "@wry/context": "^0.7.0", + "@wry/trie": "^0.5.0", + "tslib": "^2.3.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-introspection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-introspection/-/pg-introspection-1.0.1.tgz", + "integrity": "sha512-HwxpCEWygpRPfvFf7IVtEPchtjl1Fw0TxzCYXJIQdTEFio/AcGnp2XI5x+LpowbyEa3XgB9L5gvw2D0Jqji4eA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=22" + } + }, + "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-sql2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pg-sql2/-/pg-sql2-5.0.1.tgz", + "integrity": "sha512-DdOZNUBhuBuGcq3UgUNsYE9FPdPzw9YA1K/WQxutNhC17DA/CImapyamv6lliVvdAVNZ+CWB1z+p7SmSJmkezw==", + "license": "MIT", + "dependencies": { + "@graphile/lru": "^5.0.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=22" + } + }, + "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/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/picomatch-browser": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/picomatch-browser/-/picomatch-browser-2.2.6.tgz", + "integrity": "sha512-0ypsOQt9D4e3hziV8O4elD9uN0z/jtUEfxVRtNaAAtXIyUx9m/SzlO020i8YNL2aL/E6blOvvHQcin6HZlFy/w==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.19", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postgraphile": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/postgraphile/-/postgraphile-5.1.5.tgz", + "integrity": "sha512-2sT6pyzlkJJURcrGbVng4hEDVDHq2YuySbJVnEFABs6Nr6WGLOtxsNZs1i8iYroPkI2+OL3jArEt7A6tu+b0MA==", + "license": "MIT", + "dependencies": { + "@dataplan/json": "^1.0.1", + "@dataplan/pg": "^1.1.2", + "@graphile/lru": "^5.0.0", + "@types/node": "^22.19.1", + "@types/pg": "^8.15.6", + "debug": "^4.4.3", + "grafast": "^1.1.3", + "grafserv": "^1.0.2", + "graphile-build": "^5.1.1", + "graphile-build-pg": "^5.1.4", + "graphile-config": "^1.1.1", + "graphile-utils": "^5.0.3", + "graphql": "^16.9.0", + "iterall": "^1.3.0", + "jsonwebtoken": "^9.0.2", + "pg": "^8.16.3", + "pg-sql2": "^5.0.1", + "tamedevil": "^0.1.1", + "tslib": "^2.8.1", + "ws": "^8.18.3" + }, + "bin": { + "postgraphile": "dist/cli-run.js" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@dataplan/json": "^1.0.1", + "@dataplan/pg": "^1.1.2", + "@envelop/core": "^5.0.0", + "grafast": "^1.1.3", + "grafserv": "^1.0.2", + "graphile-build": "^5.1.1", + "graphile-build-pg": "^5.1.4", + "graphile-config": "^1.1.1", + "graphql": "^16.9.0", + "pg": "^8.7.1", + "pg-sql2": "^5.0.1", + "tamedevil": "^0.1.1" + }, + "peerDependenciesMeta": { + "@envelop/core": { + "optional": true + } + } + }, + "node_modules/postgraphile/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==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/postgraphile/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==", + "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/postgres-range": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz", + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==", + "license": "MIT" + }, + "node_modules/prettier": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz", + "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-aria": { + "version": "3.52.1", + "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.52.1.tgz", + "integrity": "sha512-fdZZruC9/x/joCg0mhKGs5aHpwrXLCSZ4GOJmhhYyiE0ffyEsk9MLFt9LCOCF9tn8ErTD+UDQP4oDUSlZkmpCg==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.4", + "@internationalized/number": "^3.6.8", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.36.1", + "@swc/helpers": "^0.5.0", + "aria-hidden": "^1.2.3", + "clsx": "^2.0.0", + "react-stately": "3.50.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/react-aria/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react-compiler-runtime": { + "version": "19.1.0-rc.1", + "resolved": "https://registry.npmjs.org/react-compiler-runtime/-/react-compiler-runtime-19.1.0-rc.1.tgz", + "integrity": "sha512-wCt6g+cRh8g32QT18/9blfQHywGjYu+4FlEc3CW1mx3pPxYzZZl1y+VtqxRgnKKBCFLIGUYxog4j4rs5YS86hw==", + "license": "MIT", + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^0.0.0-experimental" + } + }, + "node_modules/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.28.0" + }, + "peerDependencies": { + "react": "^19.3.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-stately": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.50.0.tgz", + "integrity": "sha512-TnckvpDQGU0672wEaLZqdzvhuSeXWjgW6vijMWxiKOxc8CosQUfPGISdcZyfHqjX3XMjEtRxj4w9HumvX4h4mw==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.4", + "@internationalized/number": "^3.6.8", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.36.1", + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/rolldown": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", + "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.152.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.12", + "@rolldown/binding-android-arm64": "1.2.12", + "@rolldown/binding-darwin-arm64": "1.2.12", + "@rolldown/binding-darwin-x64": "1.2.12", + "@rolldown/binding-freebsd-x64": "1.2.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", + "@rolldown/binding-linux-arm64-gnu": "1.2.12", + "@rolldown/binding-linux-arm64-musl": "1.2.12", + "@rolldown/binding-linux-ppc64-gnu": "1.2.12", + "@rolldown/binding-linux-s390x-gnu": "1.2.12", + "@rolldown/binding-linux-x64-gnu": "1.2.12", + "@rolldown/binding-linux-x64-musl": "1.2.12", + "@rolldown/binding-openharmony-arm64": "1.2.12", + "@rolldown/binding-win32-arm64-msvc": "1.2.12", + "@rolldown/binding-win32-x64-msvc": "1.2.12" + } + }, + "node_modules/ruru": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ruru/-/ruru-2.0.2.tgz", + "integrity": "sha512-WYTeVEdEcz9vZQCjXsH4wVlbRyv6lik4IjFtktk6xrkQR4dVmzJYQ3NpFkM1/+349v/RjRcZ6nB6mwyc83lNKA==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "^1.4.0", + "graphile-config": "^1.1.1", + "graphql": "^16.9.0", + "http-proxy": "^1.18.1", + "ruru-types": "^2.0.0", + "tslib": "^2.8.1", + "yargs": "^17.7.2" + }, + "bin": { + "ruru": "dist/cli-run.js" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "graphile-config": "^1.0.0-rc.5", + "graphql": "^16.9.0", + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/ruru-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ruru-types/-/ruru-types-2.0.0.tgz", + "integrity": "sha512-7dBZHeU8Pnj0V+tLiPzr8RhpdsNuAwu5yhZqcolu6pzpItLG/LKKzN+gKAiCp17z6Lfpdu7bXs+9JS39PO+VxA==", + "license": "MIT", + "dependencies": { + "@graphiql/toolkit": "^0.11.3", + "graphiql": "^5.2.1" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "graphql": "^16.9.0", + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT" + }, + "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-value": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-4.1.0.tgz", + "integrity": "sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw==", + "funding": [ + "https://github.com/sponsors/jonschlinkert", + "https://paypal.me/jonathanschlinkert", + "https://jonschlinkert.dev/sponsor" + ], + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "is-primitive": "^3.0.1" + }, + "engines": { + "node": ">=11.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.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": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tabbable": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", + "license": "MIT" + }, + "node_modules/tamedevil": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/tamedevil/-/tamedevil-0.1.1.tgz", + "integrity": "sha512-YH5/T/FXUYrsfFSsCdLqJwUGAlbTBrK2V78dftXnOIgnOnM9aYBi3C+uUg9pevezjE2ENPyOxHqnXJrTG9WPFQ==", + "license": "MIT", + "dependencies": { + "@graphile/lru": "^5.0.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/transliteration": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/transliteration/-/transliteration-2.6.1.tgz", + "integrity": "sha512-hJ9BhrQAOnNTbpOr1MxsNjZISkn7ppvF5TKUeFmTE1mG4ZPD/XVxF0L0LUoIUCWmQyxH0gJpVtfYLAWf298U9w==", + "license": "MIT", + "bin": { + "slugify": "dist/bin/slugify", + "transliterate": "dist/bin/transliterate" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/uc.micro": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-3.0.0.tgz", + "integrity": "sha512-U3PppEkleoTnIfi8BozMx3yju3qc/L6SwqWo2Sw+54PX+PX0q9I+r1Um5HCmqD7n9VDX5/v3vQH/AjA6deDdtw==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz", + "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", + "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.11", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "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==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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==", + "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/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/zustand": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.15.tgz", + "integrity": "sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/applications/project-graphql/package.json b/applications/project-graphql/package.json new file mode 100644 index 00000000..86d99f21 --- /dev/null +++ b/applications/project-graphql/package.json @@ -0,0 +1,37 @@ +{ + "name": "project-graphql", + "private": true, + "type": "module", + "engines": { + "node": ">=24 <25" + }, + "scripts": { + "typecheck": "tsc --noEmit", + "build": "vite build", + "start": "node --env-file=.env src/server/index.ts", + "test": "node --test tests/session.test.ts", + "test:integration": "node --env-file=.env --test tests/api.test.ts", + "test:browser": "playwright test", + "schema": "node --env-file=.env scripts/export-schema.ts" + }, + "dependencies": { + "postgraphile": "5.1.5", + "grafast": "1.1.4", + "@apollo/client": "4.3.1", + "graphql": "16.12.0", + "rxjs": "7.8.2", + "pg": "8.23.1", + "react": "19.3.0", + "react-dom": "19.3.0" + }, + "devDependencies": { + "@types/node": "24.10.1", + "@types/pg": "8.23.1", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "@vitejs/plugin-react": "6.1.1", + "vite": "8.3.2", + "typescript": "7.0.2", + "@playwright/test": "1.63.0" + } +} diff --git a/applications/project-graphql/playwright.config.ts b/applications/project-graphql/playwright.config.ts new file mode 100644 index 00000000..738e1018 --- /dev/null +++ b/applications/project-graphql/playwright.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "@playwright/test"; +export default defineConfig({ + testDir: "tests/browser", + workers: 1, + use: { + baseURL: process.env.APP_ORIGIN ?? "http://127.0.0.1:3002", + viewport: { width: 1440, height: 1080 }, + }, + reporter: "list", +}); diff --git a/applications/project-graphql/schema.graphql b/applications/project-graphql/schema.graphql new file mode 100644 index 00000000..3a509137 --- /dev/null +++ b/applications/project-graphql/schema.graphql @@ -0,0 +1,730 @@ +"""The root query type which gives access points into the data universe.""" +type Query implements Node { + """ + Exposes the root query type nested one level down. This is helpful for Relay 1 + which can only query top level fields if they are in a particular form. + """ + query: Query! + + """ + The root query type must be a `Node` to work well with Relay 1 mutations. This just resolves to `query`. + """ + id: ID! + + """Fetches an object given its globally unique `ID`.""" + node( + """The globally unique `ID`.""" + id: ID! + ): Node + + """Get a single `Workspace`.""" + workspaceByRowId(rowId: UUID!): Workspace + + """Get a single `Project`.""" + projectByRowId(rowId: UUID!): Project + + """Get a single `Project`.""" + projectByRowIdAndWorkspaceId(rowId: UUID!, workspaceId: UUID!): Project + + """Get a single `Task`.""" + taskByRowId(rowId: UUID!): Task + + """Reads a single `Workspace` using its globally unique `ID`.""" + workspace( + """The globally unique `ID` to be used in selecting a single `Workspace`.""" + id: ID! + ): Workspace + + """Reads a single `Project` using its globally unique `ID`.""" + project( + """The globally unique `ID` to be used in selecting a single `Project`.""" + id: ID! + ): Project + + """Reads a single `Task` using its globally unique `ID`.""" + task( + """The globally unique `ID` to be used in selecting a single `Task`.""" + id: ID! + ): Task + + """Reads and enables pagination through a set of `Workspace`.""" + allWorkspaces( + """Only read the first `n` values of the set.""" + first: Int + + """Only read the last `n` values of the set.""" + last: Int + + """ + Skip the first `n` values from our `after` cursor, an alternative to cursor + based pagination. May not be used with `last`. + """ + offset: Int + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: WorkspaceCondition + + """The method to use when ordering `Workspace`.""" + orderBy: [WorkspaceOrderBy!] = [PRIMARY_KEY_ASC] + ): WorkspaceConnection + + """Reads and enables pagination through a set of `Project`.""" + allProjects( + """Only read the first `n` values of the set.""" + first: Int + + """Only read the last `n` values of the set.""" + last: Int + + """ + Skip the first `n` values from our `after` cursor, an alternative to cursor + based pagination. May not be used with `last`. + """ + offset: Int + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: ProjectCondition + + """The method to use when ordering `Project`.""" + orderBy: [ProjectOrderBy!] = [PRIMARY_KEY_ASC] + ): ProjectConnection + + """Reads and enables pagination through a set of `Task`.""" + allTasks( + """Only read the first `n` values of the set.""" + first: Int + + """Only read the last `n` values of the set.""" + last: Int + + """ + Skip the first `n` values from our `after` cursor, an alternative to cursor + based pagination. May not be used with `last`. + """ + offset: Int + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TaskCondition + + """The method to use when ordering `Task`.""" + orderBy: [TaskOrderBy!] = [PRIMARY_KEY_ASC] + ): TaskConnection +} + +"""An object with a globally unique `ID`.""" +interface Node { + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + id: ID! +} + +type Workspace implements Node { + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + id: ID! + rowId: UUID! + name: String! + + """Reads and enables pagination through a set of `Project`.""" + projectsByWorkspaceId( + """Only read the first `n` values of the set.""" + first: Int + + """Only read the last `n` values of the set.""" + last: Int + + """ + Skip the first `n` values from our `after` cursor, an alternative to cursor + based pagination. May not be used with `last`. + """ + offset: Int + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: ProjectCondition + + """The method to use when ordering `Project`.""" + orderBy: [ProjectOrderBy!] = [PRIMARY_KEY_ASC] + ): ProjectConnection! +} + +""" +A universally unique identifier as defined by [RFC 4122](https://tools.ietf.org/html/rfc4122). +""" +scalar UUID + +"""A connection to a list of `Project` values.""" +type ProjectConnection { + """A list of `Project` objects.""" + nodes: [Project]! + + """ + A list of edges which contains the `Project` and cursor to aid in pagination. + """ + edges: [ProjectEdge]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """The count of *all* `Project` you could get from the connection.""" + totalCount: Int! +} + +type Project implements Node { + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + id: ID! + rowId: UUID! + workspaceId: UUID! + name: String! + createdAt: Datetime! + description: String! + + """Reads a single `Workspace` that is related to this `Project`.""" + workspaceByWorkspaceId: Workspace + + """Reads and enables pagination through a set of `Task`.""" + tasksByProjectIdAndWorkspaceId( + """Only read the first `n` values of the set.""" + first: Int + + """Only read the last `n` values of the set.""" + last: Int + + """ + Skip the first `n` values from our `after` cursor, an alternative to cursor + based pagination. May not be used with `last`. + """ + offset: Int + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """ + A condition to be used in determining which values should be returned by the collection. + """ + condition: TaskCondition + + """The method to use when ordering `Task`.""" + orderBy: [TaskOrderBy!] = [PRIMARY_KEY_ASC] + ): TaskConnection! +} + +""" +A point in time as described by the [ISO +8601](https://en.wikipedia.org/wiki/ISO_8601) and, if it has a timezone, [RFC +3339](https://datatracker.ietf.org/doc/html/rfc3339) standards. Input values +that do not conform to both ISO 8601 and RFC 3339 may be coerced, which may lead +to unexpected results. +""" +scalar Datetime + +"""A connection to a list of `Task` values.""" +type TaskConnection { + """A list of `Task` objects.""" + nodes: [Task]! + + """ + A list of edges which contains the `Task` and cursor to aid in pagination. + """ + edges: [TaskEdge]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """The count of *all* `Task` you could get from the connection.""" + totalCount: Int! +} + +type Task implements Node { + """ + A globally unique identifier. Can be used in various places throughout the system to identify this single value. + """ + id: ID! + rowId: UUID! + workspaceId: UUID! + projectId: UUID! + title: String! + done: Boolean! + createdAt: Datetime! + + """Reads a single `Project` that is related to this `Task`.""" + projectByProjectIdAndWorkspaceId: Project +} + +"""A `Task` edge in the connection.""" +type TaskEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The `Task` at the end of the edge.""" + node: Task +} + +"""A location in a connection that can be used for resuming pagination.""" +scalar Cursor + +"""Information about pagination in a connection.""" +type PageInfo { + """When paginating forwards, are there more items?""" + hasNextPage: Boolean! + + """When paginating backwards, are there more items?""" + hasPreviousPage: Boolean! + + """When paginating backwards, the cursor to continue.""" + startCursor: Cursor + + """When paginating forwards, the cursor to continue.""" + endCursor: Cursor +} + +""" +A condition to be used against `Task` object types. All fields are tested for equality and combined with a logical ‘and.’ +""" +input TaskCondition { + """Checks for equality with the object’s `rowId` field.""" + rowId: UUID + + """Checks for equality with the object’s `workspaceId` field.""" + workspaceId: UUID + + """Checks for equality with the object’s `projectId` field.""" + projectId: UUID + + """Checks for equality with the object’s `createdAt` field.""" + createdAt: Datetime +} + +"""Methods to use when ordering `Task`.""" +enum TaskOrderBy { + NATURAL + PRIMARY_KEY_ASC + PRIMARY_KEY_DESC + ROW_ID_ASC + ROW_ID_DESC + WORKSPACE_ID_ASC + WORKSPACE_ID_DESC + PROJECT_ID_ASC + PROJECT_ID_DESC + CREATED_AT_ASC + CREATED_AT_DESC +} + +"""A `Project` edge in the connection.""" +type ProjectEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The `Project` at the end of the edge.""" + node: Project +} + +""" +A condition to be used against `Project` object types. All fields are tested for equality and combined with a logical ‘and.’ +""" +input ProjectCondition { + """Checks for equality with the object’s `rowId` field.""" + rowId: UUID + + """Checks for equality with the object’s `workspaceId` field.""" + workspaceId: UUID + + """Checks for equality with the object’s `createdAt` field.""" + createdAt: Datetime +} + +"""Methods to use when ordering `Project`.""" +enum ProjectOrderBy { + NATURAL + PRIMARY_KEY_ASC + PRIMARY_KEY_DESC + ROW_ID_ASC + ROW_ID_DESC + WORKSPACE_ID_ASC + WORKSPACE_ID_DESC + CREATED_AT_ASC + CREATED_AT_DESC +} + +"""A connection to a list of `Workspace` values.""" +type WorkspaceConnection { + """A list of `Workspace` objects.""" + nodes: [Workspace]! + + """ + A list of edges which contains the `Workspace` and cursor to aid in pagination. + """ + edges: [WorkspaceEdge]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """The count of *all* `Workspace` you could get from the connection.""" + totalCount: Int! +} + +"""A `Workspace` edge in the connection.""" +type WorkspaceEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The `Workspace` at the end of the edge.""" + node: Workspace +} + +""" +A condition to be used against `Workspace` object types. All fields are tested +for equality and combined with a logical ‘and.’ +""" +input WorkspaceCondition { + """Checks for equality with the object’s `rowId` field.""" + rowId: UUID +} + +"""Methods to use when ordering `Workspace`.""" +enum WorkspaceOrderBy { + NATURAL + PRIMARY_KEY_ASC + PRIMARY_KEY_DESC + ROW_ID_ASC + ROW_ID_DESC +} + +""" +The root mutation type which contains root level fields which mutate data. +""" +type Mutation { + """Creates a single `Project`.""" + createProject( + """ + The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. + """ + input: CreateProjectInput! + ): CreateProjectPayload + + """Creates a single `Task`.""" + createTask( + """ + The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. + """ + input: CreateTaskInput! + ): CreateTaskPayload + + """Updates a single `Project` using its globally unique id and a patch.""" + updateProject( + """ + The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. + """ + input: UpdateProjectInput! + ): UpdateProjectPayload + + """Updates a single `Project` using a unique key and a patch.""" + updateProjectByRowId( + """ + The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. + """ + input: UpdateProjectByRowIdInput! + ): UpdateProjectPayload + + """Updates a single `Project` using a unique key and a patch.""" + updateProjectByRowIdAndWorkspaceId( + """ + The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. + """ + input: UpdateProjectByRowIdAndWorkspaceIdInput! + ): UpdateProjectPayload + + """Updates a single `Task` using its globally unique id and a patch.""" + updateTask( + """ + The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. + """ + input: UpdateTaskInput! + ): UpdateTaskPayload + + """Updates a single `Task` using a unique key and a patch.""" + updateTaskByRowId( + """ + The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. + """ + input: UpdateTaskByRowIdInput! + ): UpdateTaskPayload +} + +"""The output of our create `Project` mutation.""" +type CreateProjectPayload { + """ + The exact same `clientMutationId` that was provided in the mutation input, + unchanged and unused. May be used by a client to track mutations. + """ + clientMutationId: String + + """The `Project` that was created by this mutation.""" + project: Project + + """ + Our root query field type. Allows us to run any query from our mutation payload. + """ + query: Query + + """An edge for our `Project`. May be used by Relay 1.""" + projectEdge( + """The method to use when ordering `Project`.""" + orderBy: [ProjectOrderBy!]! = [PRIMARY_KEY_ASC] + ): ProjectEdge +} + +"""All input for the create `Project` mutation.""" +input CreateProjectInput { + """ + An arbitrary string value with no semantic meaning. Will be included in the + payload verbatim. May be used to track mutations by the client. + """ + clientMutationId: String + + """The `Project` to be created by this mutation.""" + project: ProjectInput! +} + +"""An input for mutations affecting `Project`""" +input ProjectInput { + rowId: UUID + workspaceId: UUID + name: String! + createdAt: Datetime + description: String +} + +"""The output of our create `Task` mutation.""" +type CreateTaskPayload { + """ + The exact same `clientMutationId` that was provided in the mutation input, + unchanged and unused. May be used by a client to track mutations. + """ + clientMutationId: String + + """The `Task` that was created by this mutation.""" + task: Task + + """ + Our root query field type. Allows us to run any query from our mutation payload. + """ + query: Query + + """An edge for our `Task`. May be used by Relay 1.""" + taskEdge( + """The method to use when ordering `Task`.""" + orderBy: [TaskOrderBy!]! = [PRIMARY_KEY_ASC] + ): TaskEdge +} + +"""All input for the create `Task` mutation.""" +input CreateTaskInput { + """ + An arbitrary string value with no semantic meaning. Will be included in the + payload verbatim. May be used to track mutations by the client. + """ + clientMutationId: String + + """The `Task` to be created by this mutation.""" + task: TaskInput! +} + +"""An input for mutations affecting `Task`""" +input TaskInput { + rowId: UUID + workspaceId: UUID + projectId: UUID! + title: String! + done: Boolean + createdAt: Datetime +} + +"""The output of our update `Project` mutation.""" +type UpdateProjectPayload { + """ + The exact same `clientMutationId` that was provided in the mutation input, + unchanged and unused. May be used by a client to track mutations. + """ + clientMutationId: String + + """The `Project` that was updated by this mutation.""" + project: Project + + """ + Our root query field type. Allows us to run any query from our mutation payload. + """ + query: Query + + """An edge for our `Project`. May be used by Relay 1.""" + projectEdge( + """The method to use when ordering `Project`.""" + orderBy: [ProjectOrderBy!]! = [PRIMARY_KEY_ASC] + ): ProjectEdge +} + +"""All input for the `updateProject` mutation.""" +input UpdateProjectInput { + """ + An arbitrary string value with no semantic meaning. Will be included in the + payload verbatim. May be used to track mutations by the client. + """ + clientMutationId: String + + """ + The globally unique `ID` which will identify a single `Project` to be updated. + """ + id: ID! + + """ + An object where the defined keys will be set on the `Project` being updated. + """ + projectPatch: ProjectPatch! +} + +""" +Represents an update to a `Project`. Fields that are set will be updated. +""" +input ProjectPatch { + rowId: UUID + workspaceId: UUID + name: String + createdAt: Datetime + description: String +} + +"""All input for the `updateProjectByRowId` mutation.""" +input UpdateProjectByRowIdInput { + """ + An arbitrary string value with no semantic meaning. Will be included in the + payload verbatim. May be used to track mutations by the client. + """ + clientMutationId: String + rowId: UUID! + + """ + An object where the defined keys will be set on the `Project` being updated. + """ + projectPatch: ProjectPatch! +} + +"""All input for the `updateProjectByRowIdAndWorkspaceId` mutation.""" +input UpdateProjectByRowIdAndWorkspaceIdInput { + """ + An arbitrary string value with no semantic meaning. Will be included in the + payload verbatim. May be used to track mutations by the client. + """ + clientMutationId: String + rowId: UUID! + workspaceId: UUID! + + """ + An object where the defined keys will be set on the `Project` being updated. + """ + projectPatch: ProjectPatch! +} + +"""The output of our update `Task` mutation.""" +type UpdateTaskPayload { + """ + The exact same `clientMutationId` that was provided in the mutation input, + unchanged and unused. May be used by a client to track mutations. + """ + clientMutationId: String + + """The `Task` that was updated by this mutation.""" + task: Task + + """ + Our root query field type. Allows us to run any query from our mutation payload. + """ + query: Query + + """An edge for our `Task`. May be used by Relay 1.""" + taskEdge( + """The method to use when ordering `Task`.""" + orderBy: [TaskOrderBy!]! = [PRIMARY_KEY_ASC] + ): TaskEdge +} + +"""All input for the `updateTask` mutation.""" +input UpdateTaskInput { + """ + An arbitrary string value with no semantic meaning. Will be included in the + payload verbatim. May be used to track mutations by the client. + """ + clientMutationId: String + + """ + The globally unique `ID` which will identify a single `Task` to be updated. + """ + id: ID! + + """ + An object where the defined keys will be set on the `Task` being updated. + """ + taskPatch: TaskPatch! +} + +"""Represents an update to a `Task`. Fields that are set will be updated.""" +input TaskPatch { + rowId: UUID + workspaceId: UUID + projectId: UUID + title: String + done: Boolean + createdAt: Datetime +} + +"""All input for the `updateTaskByRowId` mutation.""" +input UpdateTaskByRowIdInput { + """ + An arbitrary string value with no semantic meaning. Will be included in the + payload verbatim. May be used to track mutations by the client. + """ + clientMutationId: String + rowId: UUID! + + """ + An object where the defined keys will be set on the `Task` being updated. + """ + taskPatch: TaskPatch! +} diff --git a/applications/project-graphql/scripts/export-schema.ts b/applications/project-graphql/scripts/export-schema.ts new file mode 100644 index 00000000..9a8e9ca7 --- /dev/null +++ b/applications/project-graphql/scripts/export-schema.ts @@ -0,0 +1,10 @@ +import { writeFile } from "node:fs/promises"; +import { printSchema } from "graphql"; +import { connectDatabase } from "../src/server/database.ts"; +import { createGraphile } from "../src/server/preset.ts"; +const pool = await connectDatabase(); +const pgl = createGraphile(pool); +const { schema } = await pgl.getSchemaResult(); +await writeFile("schema.graphql", printSchema(schema) + "\n"); +await pgl.release(); +await pool.end(); diff --git a/applications/project-graphql/scripts/seed-users.ts b/applications/project-graphql/scripts/seed-users.ts new file mode 100644 index 00000000..15e095a4 --- /dev/null +++ b/applications/project-graphql/scripts/seed-users.ts @@ -0,0 +1,28 @@ +import { randomBytes } from "node:crypto"; +import { connectDatabase } from "../src/server/database.ts"; +import { passwordHash } from "../src/server/sessions.ts"; +const pool = await connectDatabase(process.env.MIGRATION_DATABASE_URL); +for (const [id, email, workspace, name, password] of [ + [ + "40000000-0000-4000-8000-000000000001", + "alice@example.test", + "10000000-0000-4000-8000-000000000001", + "Riverside Studio", + "river-demo", + ], + [ + "40000000-0000-4000-8000-000000000002", + "bob@example.test", + "10000000-0000-4000-8000-000000000002", + "Summit Team", + "summit-demo", + ], +]) { + const salt = randomBytes(16).toString("hex"); + await pool.query( + "INSERT INTO tracker_private.accounts(id,email,workspace_id,workspace_name,salt,password_hash) VALUES($1,$2,$3,$4,$5,$6)", + [id, email, workspace, name, salt, passwordHash(password!, salt)], + ); +} +await pool.end(); +console.log("Two synthetic demo accounts seeded with salted scrypt hashes."); diff --git a/applications/project-graphql/sql/roles.sql b/applications/project-graphql/sql/roles.sql new file mode 100644 index 00000000..0f0391e5 --- /dev/null +++ b/applications/project-graphql/sql/roles.sql @@ -0,0 +1,8 @@ +\getenv migration_password PG_MIGRATION_PASSWORD +\getenv app_password PG_APP_PASSWORD +CREATE ROLE tracker_migration LOGIN PASSWORD :'migration_password'; +CREATE ROLE tracker_member NOLOGIN NOSUPERUSER NOBYPASSRLS; +CREATE ROLE tracker_api LOGIN NOINHERIT NOSUPERUSER NOBYPASSRLS PASSWORD :'app_password'; +GRANT tracker_member TO tracker_api; +GRANT CONNECT ON DATABASE postgres TO tracker_api,tracker_migration; +GRANT CREATE ON DATABASE postgres TO tracker_migration; diff --git a/applications/project-graphql/sql/schema.sql b/applications/project-graphql/sql/schema.sql new file mode 100644 index 00000000..110bdbbb --- /dev/null +++ b/applications/project-graphql/sql/schema.sql @@ -0,0 +1,40 @@ +BEGIN; +CREATE SCHEMA tracker; +CREATE SCHEMA tracker_private; +CREATE TABLE tracker.workspaces(id uuid PRIMARY KEY,name text NOT NULL); +CREATE TABLE tracker.projects( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + workspace_id uuid NOT NULL DEFAULT nullif(current_setting('app.workspace_id',true),'')::uuid REFERENCES tracker.workspaces(id), + name text NOT NULL CHECK(length(trim(name)) BETWEEN 1 AND 80), + created_at timestamptz NOT NULL DEFAULT now(), + description text NOT NULL DEFAULT '' CHECK(length(description)<=500), + UNIQUE(id,workspace_id) +); +CREATE TABLE tracker.tasks( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + workspace_id uuid NOT NULL DEFAULT nullif(current_setting('app.workspace_id',true),'')::uuid, + project_id uuid NOT NULL, + title text NOT NULL CHECK(length(trim(title)) BETWEEN 1 AND 120), + done boolean NOT NULL DEFAULT false, + created_at timestamptz NOT NULL DEFAULT now(), + FOREIGN KEY(project_id,workspace_id) REFERENCES tracker.projects(id,workspace_id) +); +CREATE INDEX projects_recent ON tracker.projects(created_at,id); +CREATE INDEX tasks_recent ON tracker.tasks(created_at,id); +CREATE INDEX projects_workspace ON tracker.projects(workspace_id); +CREATE INDEX tasks_project ON tracker.tasks(project_id,workspace_id); +ALTER TABLE tracker.workspaces ENABLE ROW LEVEL SECURITY; +ALTER TABLE tracker.projects ENABLE ROW LEVEL SECURITY; +ALTER TABLE tracker.tasks ENABLE ROW LEVEL SECURITY; +CREATE POLICY workspace_access ON tracker.workspaces TO tracker_member USING(id=nullif(current_setting('app.workspace_id',true),'')::uuid); +CREATE POLICY project_access ON tracker.projects TO tracker_member USING(workspace_id=nullif(current_setting('app.workspace_id',true),'')::uuid) WITH CHECK(workspace_id=nullif(current_setting('app.workspace_id',true),'')::uuid); +CREATE POLICY task_access ON tracker.tasks TO tracker_member USING(workspace_id=nullif(current_setting('app.workspace_id',true),'')::uuid) WITH CHECK(workspace_id=nullif(current_setting('app.workspace_id',true),'')::uuid); +CREATE TABLE tracker_private.accounts(id uuid PRIMARY KEY,email text UNIQUE NOT NULL,workspace_id uuid NOT NULL REFERENCES tracker.workspaces(id),workspace_name text NOT NULL,salt text NOT NULL,password_hash text NOT NULL); +CREATE TABLE tracker_private.sessions(token_hash text PRIMARY KEY,account_id uuid NOT NULL REFERENCES tracker_private.accounts(id),expires_at timestamptz NOT NULL); +GRANT USAGE ON SCHEMA tracker TO tracker_member; +GRANT SELECT ON tracker.workspaces TO tracker_member; +GRANT SELECT,INSERT,UPDATE ON tracker.projects,tracker.tasks TO tracker_member; +GRANT USAGE ON SCHEMA tracker_private TO tracker_api; +GRANT SELECT ON tracker_private.accounts TO tracker_api; +GRANT SELECT,INSERT,DELETE ON tracker_private.sessions TO tracker_api; +COMMIT; diff --git a/applications/project-graphql/sql/seed.sql b/applications/project-graphql/sql/seed.sql new file mode 100644 index 00000000..93c8678c --- /dev/null +++ b/applications/project-graphql/sql/seed.sql @@ -0,0 +1,14 @@ +BEGIN; +INSERT INTO tracker.workspaces VALUES('10000000-0000-4000-8000-000000000001','Riverside Studio'),('10000000-0000-4000-8000-000000000002','Summit Team'); +INSERT INTO tracker.projects(id,workspace_id,name,description) VALUES +('20000000-0000-4000-8000-000000000001','10000000-0000-4000-8000-000000000001','Website refresh','Make the next release easier to explore.'), +('20000000-0000-4000-8000-000000000002','10000000-0000-4000-8000-000000000001','Customer research','Turn conversations into practical improvements.'), +('20000000-0000-4000-8000-000000000003','10000000-0000-4000-8000-000000000002','Spring programme','Plan the team’s next community programme.'), +('20000000-0000-4000-8000-000000000004','10000000-0000-4000-8000-000000000002','Partner outreach','Prepare a useful partner briefing.'); +INSERT INTO tracker.tasks(id,workspace_id,project_id,title,done) VALUES +('30000000-0000-4000-8000-000000000001','10000000-0000-4000-8000-000000000001','20000000-0000-4000-8000-000000000001','Sketch the new homepage',true), +('30000000-0000-4000-8000-000000000002','10000000-0000-4000-8000-000000000001','20000000-0000-4000-8000-000000000001','Review the navigation labels',false), +('30000000-0000-4000-8000-000000000003','10000000-0000-4000-8000-000000000001','20000000-0000-4000-8000-000000000002','Summarize interview themes',false), +('30000000-0000-4000-8000-000000000004','10000000-0000-4000-8000-000000000002','20000000-0000-4000-8000-000000000003','Confirm the workshop dates',true), +('30000000-0000-4000-8000-000000000005','10000000-0000-4000-8000-000000000002','20000000-0000-4000-8000-000000000004','Draft the partner welcome',false); +COMMIT; diff --git a/applications/project-graphql/src/client/main.tsx b/applications/project-graphql/src/client/main.tsx new file mode 100644 index 00000000..87a8c978 --- /dev/null +++ b/applications/project-graphql/src/client/main.tsx @@ -0,0 +1,510 @@ +import React, { useEffect, useMemo, useState, useRef } from "react"; +import { createRoot } from "react-dom/client"; +import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client"; +import { ApolloProvider, useQuery, useMutation } from "@apollo/client/react"; +import { + BoardDocument, + ProjectDocument, + CreateProjectDocument, + CreateTaskDocument, + UpdateTaskDocument, +} from "./operations.ts"; +import "./style.css"; +type Session = { + userId: string; + email: string; + workspaceId: string; + workspaceName: string; +}; +function App() { + const [session, setSession] = useState(null); + const [checking, setChecking] = useState(true); + const [epoch, setEpoch] = useState(0); + const [authError, setAuthError] = useState(""); + const bundle = useMemo(() => { + const controller = new AbortController(); + return { + controller, + client: new ApolloClient({ + link: new HttpLink({ + uri: "/graphql", + credentials: "same-origin", + fetch: (input, init) => + fetch(input, { ...init, signal: controller.signal }), + }), + cache: new InMemoryCache(), + }), + }; + }, [epoch]); + const channel = useMemo( + () => new BroadcastChannel("common-ground-session"), + [], + ); + const generation = useRef(0); + const currentBundle = useRef(bundle); + currentBundle.current = bundle; + async function refreshSession() { + const request = ++generation.current; + setChecking(true); + const previous = currentBundle.current; + previous.controller.abort(); + previous.client.stop(); + await previous.client.clearStore(); + try { + const response = await fetch("/api/me"); + if (!response.ok) throw new Error("Session request failed"); + const value = await response.json(); + if (request !== generation.current) return; + setSession(value.session); + setEpoch((e) => e + 1); + } catch { + if (request !== generation.current) return; + setAuthError("Could not read your session. Reload to try again."); + setSession(null); + } finally { + if (request === generation.current) setChecking(false); + } + } + useEffect(() => { + void refreshSession(); + return () => { + generation.current++; + channel.close(); + }; + }, []); + useEffect(() => { + channel.onmessage = () => void refreshSession(); + return () => { + channel.onmessage = null; + }; + }, []); + useEffect( + () => () => { + bundle.controller.abort(); + bundle.client.stop(); + }, + [bundle], + ); + async function signIn(email: string, password: string) { + setAuthError(""); + try { + const response = await fetch("/api/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, password }), + }); + if (!response.ok) { + setAuthError("Email or password did not match."); + return; + } + await refreshSession(); + channel.postMessage("changed"); + } catch { + setAuthError("Could not sign in. Check the connection and try again."); + } + } + async function signOut() { + setAuthError(""); + try { + const response = await fetch("/api/logout", { method: "POST" }); + if (!response.ok) throw new Error("Logout failed"); + await refreshSession(); + channel.postMessage("changed"); + } catch { + setAuthError("Could not sign out. Check the connection and try again."); + } + } + return ( +
+
+ + ◌ COMMON GROUND + + WORKSPACE JOURNAL / 01 +
+ {checking ? ( +
+

Checking your session…

+
+ ) : session ? ( + + + {authError &&

{authError}

} +
+ ) : ( + + )} +
+ A local synthetic-account demo · SQL-derived GraphQL · Postgres + row-level security +
+
+ ); +} +function Login({ + error, + signIn, +}: { + error: string; + signIn: (email: string, password: string) => Promise; +}) { + const [email, setEmail] = useState("alice@example.test"); + const [password, setPassword] = useState(""); + const [busy, setBusy] = useState(false); + return ( +
+

A place for each team’s work

+

+ Different workspaces. +
+ A common ground. +

+

+ Sign in with a synthetic demo account. Its verified session determines + which rows Postgres allows you to see. +

+
{ + e.preventDefault(); + setBusy(true); + try { + await signIn(email, password); + } finally { + setBusy(false); + } + }} + > + + + +
+

+ Demo passwords: Alice river-demo · Bob{" "} + summit-demo +

+ {error &&

{error}

} +
+ ); +} +function Board({ + session, + signOut, +}: { + session: Session; + signOut: () => Promise; +}) { + const { data, loading, error, fetchMore } = useQuery(BoardDocument); + const [selected, setSelected] = useState(null); + const [taskTitle, setTaskTitle] = useState(""); + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [message, setMessage] = useState(""); + const [createProject, { loading: creating }] = useMutation( + CreateProjectDocument, + { refetchQueries: [BoardDocument], awaitRefetchQueries: true }, + ); + const [createTask, { loading: adding }] = useMutation(CreateTaskDocument, { + refetchQueries: ["ProjectNotebook"], + awaitRefetchQueries: true, + }); + const [updateTask, { loading: updating }] = useMutation(UpdateTaskDocument); + const rows = data?.allProjects.nodes ?? []; + const selectedId = selected ?? rows[0]?.rowId; + const detail = useQuery(ProjectDocument, { + variables: { rowId: selectedId ?? "" }, + skip: !selectedId, + }); + const project = detail.data?.projectByRowId; + const tasks = project?.tasksByProjectIdAndWorkspaceId; + const busy = creating || adding || updating; + async function moreProjects() { + try { + await fetchMore({ + variables: { after: data?.allProjects.pageInfo.endCursor }, + updateQuery: (previous, { fetchMoreResult }) => ({ + allProjects: { + ...fetchMoreResult.allProjects, + nodes: [ + ...previous.allProjects.nodes, + ...fetchMoreResult.allProjects.nodes.filter( + (p) => + !previous.allProjects.nodes.some((old) => old.id === p.id), + ), + ], + }, + }), + }); + } catch { + setMessage("Could not load more projects. Try again."); + } + } + async function moreTasks() { + try { + await detail.fetchMore({ + variables: { after: tasks?.pageInfo.endCursor }, + updateQuery: (previous, { fetchMoreResult }) => { + const old = previous.projectByRowId; + const next = fetchMoreResult.projectByRowId; + if (!old || !next) return fetchMoreResult; + return { + projectByRowId: { + ...next, + tasksByProjectIdAndWorkspaceId: { + ...next.tasksByProjectIdAndWorkspaceId, + nodes: [ + ...old.tasksByProjectIdAndWorkspaceId.nodes, + ...next.tasksByProjectIdAndWorkspaceId.nodes.filter( + (t) => + !old.tasksByProjectIdAndWorkspaceId.nodes.some( + (o) => o.id === t.id, + ), + ), + ], + }, + }, + }; + }, + }); + } catch { + setMessage("Could not load more tasks. Try again."); + } + } + async function operation(fn: () => Promise, success: string) { + setMessage(""); + try { + await fn(); + setMessage(success); + return true; + } catch { + setMessage( + "The operation could not be completed. Review the fields and try again.", + ); + return false; + } + } + return ( + <> +
+
+

Your team’s working notebook

+

{session.workspaceName}

+

+ Keep projects clear, and the next step close at hand. +

+
+ +
+ + {error && ( +

+ Could not load the workspace. Sign in again to retry. +

+ )} + {loading ? ( +

Loading projects…

+ ) : ( +
+ +
+ {detail.loading && !project ? ( +

Loading notebook…

+ ) : detail.error ? ( +

Could not load this project.

+ ) : project ? ( + <> +

Project notebook

+

{project.name}

+

+ {project.description || + "A fresh space for the team’s next steps."} +

+
+ Next steps + + {tasks?.nodes.length} of {tasks?.totalCount} tasks ·{" "} + {tasks?.nodes.filter((t) => t.done).length} loaded complete + +
+ {project.tasksByProjectIdAndWorkspaceId.nodes.map((task) => ( + + ))} + {project.tasksByProjectIdAndWorkspaceId.nodes.length === 0 && ( +

+ No tasks yet. Add the first next step below. +

+ )} + {tasks?.pageInfo.hasNextPage && ( + + )} +
{ + e.preventDefault(); + if ( + await operation( + () => + createTask({ + variables: { + projectId: project.rowId, + title: taskTitle, + }, + }), + "Task saved", + ) + ) + setTaskTitle(""); + }} + > + + +
+ + ) : ( + <> +

A clear starting point.

+

Create a project to begin this workspace.

+ + )} +
+
+ )} +

+ {message || + "Only this session’s workspace is visible. Switching sessions replaces the Apollo cache."} +

+ + ); +} +createRoot(document.getElementById("root")!).render(); diff --git a/applications/project-graphql/src/client/operations.ts b/applications/project-graphql/src/client/operations.ts new file mode 100644 index 00000000..e9eec564 --- /dev/null +++ b/applications/project-graphql/src/client/operations.ts @@ -0,0 +1,117 @@ +import { gql, type TypedDocumentNode } from "@apollo/client"; +export type Task = { id: string; rowId: string; title: string; done: boolean }; +export type Project = { + id: string; + rowId: string; + name: string; + description: string; +}; +export type Connection = { + nodes: T[]; + totalCount: number; + pageInfo: { hasNextPage: boolean; endCursor: string | null }; +}; +export type ProjectDetail = Project & { + tasksByProjectIdAndWorkspaceId: Connection; +}; +export const BoardDocument = gql` + query Board($after: Cursor) { + allProjects( + first: 20 + after: $after + orderBy: [CREATED_AT_DESC, PRIMARY_KEY_DESC] + ) { + totalCount + pageInfo { + hasNextPage + endCursor + } + nodes { + id + rowId + name + description + } + } + } +` as TypedDocumentNode< + { allProjects: Connection }, + { after?: string | null } +>; +export const ProjectDocument = gql` + query ProjectNotebook($rowId: UUID!, $after: Cursor) { + projectByRowId(rowId: $rowId) { + id + rowId + name + description + tasksByProjectIdAndWorkspaceId( + first: 50 + after: $after + orderBy: [CREATED_AT_DESC, PRIMARY_KEY_DESC] + ) { + totalCount + pageInfo { + hasNextPage + endCursor + } + nodes { + id + rowId + title + done + } + } + } + } +` as TypedDocumentNode< + { projectByRowId: ProjectDetail | null }, + { rowId: string; after?: string | null } +>; +export const CreateProjectDocument = gql` + mutation CreateProject($name: String!, $description: String!) { + createProject( + input: { project: { name: $name, description: $description } } + ) { + project { + id + rowId + name + description + } + } + } +` as TypedDocumentNode< + { createProject: { project: Project } }, + { name: string; description: string } +>; +export const CreateTaskDocument = gql` + mutation CreateTask($projectId: UUID!, $title: String!) { + createTask(input: { task: { projectId: $projectId, title: $title } }) { + task { + id + rowId + title + done + } + } + } +` as TypedDocumentNode< + { createTask: { task: Task } }, + { projectId: string; title: string } +>; +export const UpdateTaskDocument = gql` + mutation UpdateTask($rowId: UUID!, $done: Boolean!) { + updateTaskByRowId(input: { rowId: $rowId, taskPatch: { done: $done } }) { + task { + id + rowId + title + done + } + } + } +` as TypedDocumentNode< + { updateTaskByRowId: { task: Task } }, + { rowId: string; done: boolean } +>; diff --git a/applications/project-graphql/src/client/style.css b/applications/project-graphql/src/client/style.css new file mode 100644 index 00000000..01dc51f1 --- /dev/null +++ b/applications/project-graphql/src/client/style.css @@ -0,0 +1,357 @@ +:root { + font-family: system-ui, sans-serif; + color: #353c40; + background: #f4f2ed; + font-synthesis: none; +} +* { + box-sizing: border-box; +} +body { + margin: 0; +} +main { + max-width: 1240px; + margin: auto; + padding: 38px 44px; +} +header { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #d3d4ce; + padding-bottom: 25px; +} +.brand { + color: #344b53; + font-size: 17px; + letter-spacing: 2px; + text-decoration: none; + font-weight: 700; +} +.edition, +.eyebrow { + font-size: 10px; + letter-spacing: 1.8px; + color: #758188; + text-transform: uppercase; +} +.intro { + display: flex; + justify-content: space-between; + align-items: center; + padding: 50px 0 35px; +} +h1 { + font-family: Georgia, serif; + font-size: 46px; + font-weight: 400; + line-height: 1.12; + margin: 13px 0 18px; +} +h1 em { + color: #648089; +} +.lede { + font-size: 14px; + color: #7e888c; + max-width: 620px; + line-height: 1.7; +} +.intro aside { + background: #e9eef0; + border: 1px solid #d6dee1; + padding: 21px 25px; + border-radius: 7px; + display: flex; + flex-direction: column; + align-items: start; + font-size: 12px; + color: #6d7e88; +} +.intro aside p { + font-size: 16px; + margin-bottom: 0; + color: #4a6068; +} +button, +select, +input { + font-family: inherit; + font-size: 12px; +} +button { + padding: 11px 15px; + border: 0; + background: #45646e; + color: white; + border-radius: 4px; + cursor: pointer; +} +.quiet { + background: transparent; + color: #4a6a76; + padding: 12px 0 0; +} +.scope { + border-top: 1px solid #d3d4ce; + border-bottom: 1px solid #d3d4ce; + padding: 17px 0; + display: flex; + align-items: center; + font-size: 11px; + color: #72818a; +} +.scope > span:last-child { + margin-left: auto; + color: #8a979c; +} +.dot { + height: 6px; + width: 6px; + display: inline-block; + border-radius: 50%; + background: #779498; + margin-right: 10px; +} +.workspace { + display: grid; + grid-template-columns: 295px 1fr; + gap: 30px; + margin: 28px 0; +} +nav { + border: 1px solid #dbded6; + background: #eeefea; + border-radius: 7px; + padding: 22px; +} +.eyebrow b { + float: right; + font-weight: 400; +} +.project { + width: 100%; + text-align: left; + background: transparent; + color: #64727a; + border: 1px solid transparent; + display: flex; + flex-direction: column; + gap: 8px; + padding: 16px 13px; + margin: 7px 0; +} +.project.active { + background: #fff; + border-color: #d5dfe0; + color: #37545e; + box-shadow: 0 2px 3px #384d5704; +} +.project strong { + font-size: 13px; + font-weight: 500; +} +.project span { + font-size: 10px; + color: #859398; +} +.project-form { + border-top: 1px solid #d5dbd2; + margin-top: 26px; + padding-top: 18px; + display: grid; + gap: 15px; +} +h3 { + font-family: Georgia, serif; + font-size: 19px; + font-weight: 400; + margin: 0; +} +.project-form button { + justify-self: start; +} +label { + font-size: 10px; + color: #7b898d; + display: flex; + flex-direction: column; + gap: 7px; +} +input, +select { + border: 1px solid #d5dfdc; + border-radius: 4px; + padding: 10px; + color: #39535a; + background: #fff; +} +input:focus { + outline: 2px solid #a8c3c6; + outline-offset: 1px; +} +.detail { + background: #fff; + border: 1px solid #dbe0d8; + border-radius: 7px; + padding: 34px 38px; +} +.detail h2 { + font-family: Georgia, serif; + font-size: 30px; + font-weight: 400; + margin: 15px 0; +} +.description { + font-size: 13px; + color: #879094; + line-height: 1.6; +} +.task-header { + display: flex; + justify-content: space-between; + border-top: 1px solid #e4e7df; + margin-top: 32px; + padding: 18px 0; + font-size: 11px; + color: #7e8c90; +} +.task { + border: 1px solid #e2e7df; + background: #fafbf8; + border-radius: 5px; + padding: 19px 17px; + margin-bottom: 10px; + display: flex; + flex-direction: row; + align-items: center; + gap: 12px; + font-size: 13px; + color: #455c62; +} +.task input { + appearance: auto; + width: 16px; + height: 16px; + accent-color: #63848c; +} +.task small { + font-size: 10px; + margin-left: auto; + color: #8e999a; +} +.task.complete span { + text-decoration: line-through; + color: #9aa7a4; +} +.task-form { + display: flex; + align-items: end; + gap: 13px; + margin-top: 23px; +} +.task-form label { + flex: 1; +} +.api-note { + background: #f2f5f5; + border-left: 2px solid #b2c6ca; + padding: 19px 22px; + margin-top: 35px; +} +.api-note .eyebrow { + margin-top: 0; +} +.api-note code { + font-size: 12px; + color: #577780; +} +.api-note > p:last-child { + font-size: 11px; + line-height: 1.7; + color: #85969b; + max-width: 470px; + margin-bottom: 0; +} +.notice { + color: #758789; + font-size: 12px; + min-height: 20px; + margin: 15px 0 28px; +} +.footnote { + border-top: 1px solid #d4d9d0; + font-size: 10px; + color: #909b9f; + padding-top: 20px; +} +.login { + padding: 65px 0 70px; +} +.login form { + display: flex; + align-items: end; + gap: 20px; + max-width: 760px; + margin: 35px 0 20px; +} +.login label { + min-width: 250px; +} +.demo { + font-size: 11px; + color: #8a959a; +} +.demo code { + color: #557780; +} +.empty { + font-size: 12px; + color: #9aa4a5; + padding: 25px 0; +} +button:disabled, +input:disabled { + opacity: 0.5; + cursor: wait; +} +p[role="alert"] { + font-size: 13px; + color: #a35e4e; +} +@media (max-width: 800px) { + main { + padding: 24px; + } + .intro { + display: block; + } + h1 { + font-size: 36px; + } + .intro aside { + margin-top: 25px; + } + .scope > span:last-child { + display: none; + } + .workspace { + grid-template-columns: 1fr; + } + .detail { + padding: 25px; + } + .login form { + display: block; + } + .login label { + margin: 18px 0; + } + .task-form { + flex-wrap: wrap; + } + .task-form label { + min-width: 65%; + } +} diff --git a/applications/project-graphql/src/client/vite-env.d.ts b/applications/project-graphql/src/client/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/applications/project-graphql/src/client/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/applications/project-graphql/src/server/database.ts b/applications/project-graphql/src/server/database.ts new file mode 100644 index 00000000..d6e16da9 --- /dev/null +++ b/applications/project-graphql/src/server/database.ts @@ -0,0 +1,38 @@ +import { Pool } from "pg"; +import { readFileSync } from "node:fs"; +export async function connectDatabase( + urlText = process.env.DATABASE_URL, + caPath = process.env.PG_CA_CERT_PATH, +) { + if (!urlText) throw new Error("DATABASE_URL required"); + const url = new URL(urlText); + if ( + !["postgres:", "postgresql:"].includes(url.protocol) || + url.search || + url.hash + ) + throw new Error("Use a plain Postgres URL without query overrides"); + const local = process.env.DATABASE_LOCAL === "true"; + if (local && !["127.0.0.1", "localhost", "[::1]"].includes(url.hostname)) + throw new Error("Local mode requires loopback"); + if (!local && !caPath) throw new Error("Supplied CA required"); + const pool = new Pool({ + connectionString: url.toString(), + max: 5, + connectionTimeoutMillis: 10000, + ssl: local + ? false + : { + ca: readFileSync(caPath!, "utf8"), + servername: url.hostname, + rejectUnauthorized: true, + }, + }); + try { + await pool.query("SELECT 1"); + return pool; + } catch (error) { + await pool.end(); + throw error; + } +} diff --git a/applications/project-graphql/src/server/index.ts b/applications/project-graphql/src/server/index.ts new file mode 100644 index 00000000..36d8cf03 --- /dev/null +++ b/applications/project-graphql/src/server/index.ts @@ -0,0 +1,166 @@ +import { + createServer, + type IncomingMessage, + type ServerResponse, +} from "node:http"; +import { readFile } from "node:fs/promises"; +import { resolve, extname } from "node:path"; +import { grafast } from "postgraphile/grafast"; +import { connectDatabase } from "./database.ts"; +import { createGraphile, verifiedSessions } from "./preset.ts"; +import { login, getSession, logout } from "./sessions.ts"; +import { validateOperation } from "./limits.ts"; +const origin = process.env.APP_ORIGIN ?? "http://127.0.0.1:3002"; +const target = new URL(origin); +if ( + target.protocol !== "http:" || + target.hostname !== "127.0.0.1" || + target.pathname !== "/" +) + throw new Error("APP_ORIGIN must be an HTTP loopback origin"); +const pool = await connectDatabase(); +const pgl = createGraphile(pool); +await pgl.getSchemaResult(); +function json(res: ServerResponse, status: number, value: unknown) { + res.writeHead(status, { + "Content-Type": "application/json", + "Cache-Control": "no-store", + }); + res.end(JSON.stringify(value)); +} +async function body(req: IncomingMessage) { + let value = ""; + for await (const chunk of req) { + value += chunk; + if (Buffer.byteLength(value) > 16384) + throw new Error("Request is too large"); + } + return JSON.parse(value); +} +const server = createServer(async (req, res) => { + try { + if ( + req.headers.host !== target.host || + (req.headers.origin && req.headers.origin !== origin) || + (req.method === "POST" && req.headers.origin !== origin) + ) { + json(res, 403, { error: "Unexpected host or origin" }); + return; + } + const path = new URL(req.url ?? "/", origin).pathname; + if (path === "/api/login" && req.method === "POST") { + const value = await body(req); + if ( + typeof value.email !== "string" || + typeof value.password !== "string" || + value.email.length > 120 || + value.password.length > 120 + ) { + json(res, 400, { error: "Email and password required" }); + return; + } + const token = await login(pool, value.email, value.password); + if (!token) { + json(res, 401, { error: "Email or password did not match" }); + return; + } + res.setHeader( + "Set-Cookie", + `ground_session=${token}; HttpOnly; SameSite=Strict; Path=/; Max-Age=7200`, + ); + json(res, 200, { ok: true }); + return; + } + if (path === "/api/logout" && req.method === "POST") { + await logout(pool, req.headers.cookie); + res.setHeader( + "Set-Cookie", + "ground_session=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0", + ); + json(res, 200, { ok: true }); + return; + } + if (path === "/api/me" && req.method === "GET") { + json(res, 200, { session: await getSession(pool, req.headers.cookie) }); + return; + } + if (path === "/graphql" && req.method === "POST") { + const session = await getSession(pool, req.headers.cookie); + if (!session) { + json(res, 401, { errors: [{ message: "Sign in required" }] }); + return; + } + const value = await body(req); + const variables = value.variables ?? {}; + if (typeof variables !== "object" || Array.isArray(variables)) + throw new Error("Variables must be an object"); + validateOperation(value.query, variables, value.operationName); + verifiedSessions.set(req, session); + const { schema, resolvedPreset } = await pgl.getSchemaResult(); + const result = await grafast({ + schema, + resolvedPreset, + requestContext: { node: { req, res } }, + source: value.query, + variableValues: variables, + operationName: value.operationName, + }); + if (Symbol.asyncIterator in result) + throw new Error("Streaming operations are not enabled"); + json(res, 200, { + ...result, + errors: result.errors?.map((error) => ({ + message: + error.extensions?.code === "UNAUTHENTICATED" + ? "Sign in required" + : "The operation could not be completed", + path: error.path, + })), + }); + return; + } + if (req.method !== "GET") { + json(res, 404, { error: "Not found" }); + return; + } + const file = resolve("dist", path === "/" ? "index.html" : "." + path); + if (!file.startsWith(resolve("dist") + "/")) { + json(res, 404, { error: "Not found" }); + return; + } + try { + const data = await readFile(file); + res.writeHead(200, { + "Content-Type": + ( + { + ".html": "text/html", + ".js": "text/javascript", + ".css": "text/css", + ".png": "image/png", + } as Record + )[extname(file)] ?? "application/octet-stream", + }); + res.end(data); + } catch { + json(res, 404, { error: "Not found" }); + } + } catch (error) { + console.error( + "Request failed", + error instanceof Error ? error.name : "Unknown", + ); + json(res, 400, { error: "Invalid or unsupported request" }); + } +}); +server.listen(Number(target.port || 80), "127.0.0.1", () => + console.log(`Common Ground on ${origin}`), +); +for (const signal of ["SIGINT", "SIGTERM"] as const) + process.on(signal, () => { + server.close(async () => { + await pgl.release(); + await pool.end(); + process.exit(0); + }); + }); diff --git a/applications/project-graphql/src/server/limits.ts b/applications/project-graphql/src/server/limits.ts new file mode 100644 index 00000000..8134d36f --- /dev/null +++ b/applications/project-graphql/src/server/limits.ts @@ -0,0 +1,71 @@ +import { + parse, + getOperationAST, + Kind, + valueFromASTUntyped, + type SelectionSetNode, + type FragmentDefinitionNode, +} from "graphql"; +export function validateOperation( + source: unknown, + variables: Record, + name?: string, +) { + if (typeof source !== "string" || !source.trim() || source.length > 8192) + throw new Error("Query text must be 1..8192 characters"); + const document = parse(source); + const operation = getOperationAST(document, name); + if (!operation || operation.operation === "subscription") + throw new Error("Choose one query or mutation"); + const fragments = new Map( + document.definitions + .filter((d) => d.kind === Kind.FRAGMENT_DEFINITION) + .map((d) => [d.name.value, d as FragmentDefinitionNode]), + ); + let fields = 0; + function connectionRows( + set: SelectionSetNode | undefined, + stack = new Set(), + ): boolean { + if (!set) return false; + return set.selections.some((s) => { + if (s.kind === Kind.FIELD) + return ["nodes", "edges"].includes(s.name.value); + if (s.kind === Kind.INLINE_FRAGMENT) + return connectionRows(s.selectionSet, stack); + const key = s.name.value; + if (stack.has(key)) throw new Error("Cyclic query fragment"); + const fragment = fragments.get(key); + if (!fragment) throw new Error("Unknown fragment"); + return connectionRows(fragment.selectionSet, new Set([...stack, key])); + }); + } + function walk(set: SelectionSetNode, depth: number, stack: Set) { + if (depth > 8) throw new Error("Query nesting exceeds eight levels"); + for (const s of set.selections) { + if (s.kind === Kind.FIELD) { + if (++fields > 100) throw new Error("Query contains too many fields"); + if (["__schema", "__type"].includes(s.name.value)) + throw new Error("Use the checked-in schema for introspection"); + if (connectionRows(s.selectionSet)) { + const page = s.arguments?.find( + (a) => a.name.value === "first" || a.name.value === "last", + ); + const n = page && valueFromASTUntyped(page.value, variables); + if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 50) + throw new Error("Connections require first or last between1 and50"); + } + if (s.selectionSet) walk(s.selectionSet, depth + 1, stack); + } else if (s.kind === Kind.INLINE_FRAGMENT) + walk(s.selectionSet, depth, stack); + else { + const key = s.name.value; + if (stack.has(key)) throw new Error("Cyclic query fragment"); + const f = fragments.get(key); + if (!f) throw new Error("Unknown fragment"); + walk(f.selectionSet, depth, new Set([...stack, key])); + } + } + } + walk(operation.selectionSet, 1, new Set()); +} diff --git a/applications/project-graphql/src/server/preset.ts b/applications/project-graphql/src/server/preset.ts new file mode 100644 index 00000000..67afacc9 --- /dev/null +++ b/applications/project-graphql/src/server/preset.ts @@ -0,0 +1,40 @@ +import { postgraphile } from "postgraphile"; +import { makePgService } from "postgraphile/adaptors/pg"; +import { PostGraphileAmberPreset } from "postgraphile/presets/amber"; +import type {} from "postgraphile/grafserv/node"; +import { GraphQLError } from "graphql"; +import type { Pool } from "pg"; +import type { IncomingMessage } from "node:http"; +import type { Session } from "./sessions.ts"; +export const verifiedSessions = new WeakMap(); +export function createGraphile(pool: Pool) { + const preset = { + extends: [PostGraphileAmberPreset], + pgServices: [ + makePgService({ + pool, + schemas: ["tracker"], + pubsub: false, + pgSettingsForIntrospection: { role: "tracker_member" }, + }), + ], + grafast: { + context(request) { + const req = request.node?.req; + const session = req && verifiedSessions.get(req); + if (!session) + throw new GraphQLError("Sign in required", { + extensions: { code: "UNAUTHENTICATED" }, + }); + return { + pgSettings: { + role: "tracker_member", + "app.workspace_id": session.workspaceId, + statement_timeout: "5000", + }, + }; + }, + }, + } satisfies GraphileConfig.Preset; + return postgraphile(preset); +} diff --git a/applications/project-graphql/src/server/sessions.ts b/applications/project-graphql/src/server/sessions.ts new file mode 100644 index 00000000..3e7f95b2 --- /dev/null +++ b/applications/project-graphql/src/server/sessions.ts @@ -0,0 +1,74 @@ +import { + randomBytes, + scryptSync, + timingSafeEqual, + createHash, +} from "node:crypto"; +import type { Pool } from "pg"; +export function passwordHash(password: string, salt: string) { + return scryptSync(password, salt, 64).toString("hex"); +} +export function passwordMatches(password: string, salt: string, hash: string) { + if (!/^[a-f0-9]{128}$/.test(hash)) return false; + return timingSafeEqual( + Buffer.from(passwordHash(password, salt), "hex"), + Buffer.from(hash, "hex"), + ); +} +export function tokenHash(token: string) { + return createHash("sha256").update(token).digest("hex"); +} +export type Session = { + userId: string; + email: string; + workspaceId: string; + workspaceName: string; +}; +export async function login(pool: Pool, email: string, password: string) { + const { rows } = await pool.query( + "SELECT * FROM tracker_private.accounts WHERE email=$1", + [email], + ); + const account = rows[0]; + if ( + !account || + !passwordMatches(password, account.salt, account.password_hash) + ) + return null; + const token = randomBytes(32).toString("hex"); + await pool.query( + "INSERT INTO tracker_private.sessions(token_hash,account_id,expires_at) VALUES($1,$2,now()+interval '2 hours')", + [tokenHash(token), account.id], + ); + return token; +} +export function cookieToken(cookie: string | undefined) { + const matches = + cookie + ?.split(";") + .map((s) => s.trim()) + .filter((s) => s.startsWith("ground_session=")) ?? []; + if (matches.length !== 1) return null; + const value = matches[0]!.slice("ground_session=".length); + return /^[a-f0-9]{64}$/.test(value) ? value : null; +} +export async function getSession( + pool: Pool, + cookie: string | undefined, +): Promise { + const token = cookieToken(cookie); + if (!token) return null; + const { rows } = await pool.query( + `SELECT a.id AS "userId",a.email,a.workspace_id AS "workspaceId",a.workspace_name AS "workspaceName" FROM tracker_private.sessions s JOIN tracker_private.accounts a ON a.id=s.account_id WHERE s.token_hash=$1 AND s.expires_at>now()`, + [tokenHash(token)], + ); + return rows[0] ?? null; +} +export async function logout(pool: Pool, cookie: string | undefined) { + const token = cookieToken(cookie); + if (token) + await pool.query( + "DELETE FROM tracker_private.sessions WHERE token_hash=$1", + [tokenHash(token)], + ); +} diff --git a/applications/project-graphql/tests/api.test.ts b/applications/project-graphql/tests/api.test.ts new file mode 100644 index 00000000..9997d4e4 --- /dev/null +++ b/applications/project-graphql/tests/api.test.ts @@ -0,0 +1,306 @@ +import type { IncomingMessage, ServerResponse } from "node:http"; +import { grafast } from "postgraphile/grafast"; +import { createGraphile, verifiedSessions } from "../src/server/preset.ts"; +import { test, after } from "node:test"; +import assert from "node:assert/strict"; +import { connectDatabase } from "../src/server/database.ts"; +const origin = process.env.APP_ORIGIN ?? "http://127.0.0.1:3002"; +const A = "10000000-0000-4000-8000-000000000001", + B = "10000000-0000-4000-8000-000000000002"; +const AP = "20000000-0000-4000-8000-000000000001", + BP = "20000000-0000-4000-8000-000000000003"; +const pagination = "20000000-0000-4000-8000-000000000010"; +const pool = await connectDatabase(); +after(() => pool.end()); +async function login(email: string, password: string) { + const r = await fetch(origin + "/api/login", { + method: "POST", + headers: { Origin: origin, "Content-Type": "application/json" }, + body: JSON.stringify({ email, password }), + }); + assert.equal(r.status, 200); + return r.headers.get("set-cookie")!.split(";")[0]!; +} +const alice = await login("alice@example.test", "river-demo"), + bob = await login("bob@example.test", "summit-demo"); +async function gql( + cookie: string, + query: string, + variables: Record = {}, + headers: Record = {}, +) { + const r = await fetch(origin + "/graphql", { + method: "POST", + headers: { + Origin: origin, + Cookie: cookie, + "Content-Type": "application/json", + ...headers, + }, + body: JSON.stringify({ query, variables }), + }); + return { status: r.status, body: await r.json() }; +} +async function scoped( + workspace: string, + action: (client: import("pg").PoolClient) => Promise, +) { + const c = await pool.connect(); + try { + await c.query("BEGIN"); + await c.query("SET LOCAL ROLE tracker_member"); + await c.query("SELECT set_config('app.workspace_id',$1,true)", [workspace]); + await action(c); + await c.query("COMMIT"); + } catch (e) { + await c.query("ROLLBACK"); + throw e; + } finally { + c.release(); + } +} +test("all effective member RLS policies, transaction-local context and privilege denials", async () => { + await scoped(A, async (c) => { + for (const table of ["workspaces", "projects", "tasks"]) { + const r = await c.query(`SELECT * FROM tracker.${table}`); + assert.ok(r.rows.length > 0); + assert.ok(r.rows.every((row) => (row.workspace_id ?? row.id) === A)); + } + assert.equal( + (await c.query("SELECT current_user AS role")).rows[0].role, + "tracker_member", + ); + }); + const c = await pool.connect(); + try { + const state = await c.query( + "SELECT current_user AS role,current_setting('app.workspace_id',true) AS workspace", + ); + assert.equal(state.rows[0].role, "tracker_api"); + assert.ok(!state.rows[0].workspace); + await assert.rejects(c.query("SELECT * FROM tracker.projects"), { + code: "42501", + }); + for (const sql of [ + "SELECT * FROM tracker_private.accounts", + "SELECT * FROM tracker_private.sessions", + "CREATE TABLE tracker.intruder(id int)", + "CREATE SCHEMA intruder", + "SET ROLE tracker_migration", + ]) { + await c.query("BEGIN"); + await c.query("SET LOCAL ROLE tracker_member"); + await assert.rejects(c.query(sql), { code: "42501" }); + await c.query("ROLLBACK"); + } + } finally { + c.release(); + } +}); +test("actual Grafast execution resets role and workspace settings on its own pooled connections", async () => { + const graphile = createGraphile(pool); + try { + const { schema, resolvedPreset } = await graphile.getSchemaResult(); + for (const workspace of [A, B, A]) { + const req = {} as IncomingMessage; + verifiedSessions.set(req, { + userId: "test", + email: "test@example.test", + workspaceId: workspace, + workspaceName: "test", + }); + const result = await grafast({ + schema, + resolvedPreset, + requestContext: { node: { req, res: {} as ServerResponse } }, + source: "{allWorkspaces(first:10){nodes{rowId}}}", + }); + assert.ok(!(Symbol.asyncIterator in result)); + assert.equal(result.errors, undefined); + assert.deepEqual( + (result.data as any).allWorkspaces.nodes.map((w: any) => w.rowId), + [workspace], + ); + } + const clients = await Promise.all( + Array.from({ length: pool.totalCount }, () => pool.connect()), + ); + try { + for (const c of clients) { + const state = ( + await c.query( + "SELECT current_user AS role,current_setting('app.workspace_id',true) AS workspace", + ) + ).rows[0]; + assert.equal(state.role, "tracker_api"); + assert.ok(!state.workspace); + } + } finally { + clients.forEach((c) => c.release()); + } + } finally { + await graphile.release(); + } +}); +test("verified sessions isolate generated nested relations and ignore forged tenant headers", async () => { + const query = + "{ allWorkspaces(first:10){nodes{rowId name}} allProjects(first:50){nodes{rowId workspaceId tasksByProjectIdAndWorkspaceId(first:50){nodes{workspaceId title}} workspaceByWorkspaceId{rowId}}} }"; + for (const [cookie, workspace] of [ + [alice, A], + [bob, B], + ]) { + const { body, status } = await gql( + cookie!, + query, + {}, + { + "X-Workspace-Id": workspace === A ? B : A, + "X-Pg-Settings": JSON.stringify({ + role: "tracker_migration", + "app.workspace_id": B, + }), + }, + ); + assert.equal(status, 200); + assert.equal(body.errors, undefined); + assert.deepEqual( + body.data.allWorkspaces.nodes.map((r: any) => r.rowId), + [workspace], + ); + assert.ok( + body.data.allProjects.nodes.every( + (p: any) => + p.workspaceId === workspace && + p.workspaceByWorkspaceId.rowId === workspace && + p.tasksByProjectIdAndWorkspaceId.nodes.every( + (t: any) => t.workspaceId === workspace, + ), + ), + ); + } + const other = await gql( + alice, + "query($id:UUID!){projectByRowId(rowId:$id){rowId tasksByProjectIdAndWorkspaceId(first:10){nodes{title}}}}", + { id: BP }, + ); + assert.equal(other.body.data.projectByRowId, null); + const forged = await gql( + "ground_session=" + "0".repeat(64), + "{allProjects(first:1){nodes{rowId}}}", + ); + assert.equal(forged.status, 401); +}); +test("actual HTTP mutations enforce workspace checks and composite foreign keys", async () => { + const mutation = + 'mutation($project:UUID!,$workspace:UUID!){createTask(input:{task:{projectId:$project,workspaceId:$workspace,title:"Must never appear"}}){task{rowId}}}'; + for (const [project, workspace] of [ + [AP, B], + [BP, A], + ]) { + const result = await gql(alice, mutation, { project, workspace }); + assert.ok(result.body.errors?.length); + assert.equal(result.body.data.createTask, null); + } + await scoped(A, async (c) => + assert.equal( + ( + await c.query( + "SELECT count(*)::int AS n FROM tracker.tasks WHERE title='Must never appear'", + ) + ).rows[0].n, + 0, + ), + ); + const result = await gql( + alice, + 'mutation{createProject(input:{project:{name:"HTTP saved project",description:"Persisted through generated mutation"}}){project{rowId workspaceId}}}', + ); + assert.equal(result.body.errors, undefined); + assert.equal(result.body.data.createProject.project.workspaceId, A); + const id = result.body.data.createProject.project.rowId; + const read = await gql( + alice, + "query($id:UUID!){projectByRowId(rowId:$id){name}}", + { id }, + ); + assert.equal(read.body.data.projectByRowId.name, "HTTP saved project"); +}); +test("a generated cross-workspace UPDATE cannot change the other team's stored task", async () => { + const task = "30000000-0000-4000-8000-000000000004"; + const query = "query($id:UUID!){taskByRowId(rowId:$id){rowId done}}"; + const before = (await gql(bob, query, { id: task })).body.data.taskByRowId; + assert.ok(before); + const mutation = + "mutation($id:UUID!,$done:Boolean!){updateTaskByRowId(input:{rowId:$id,taskPatch:{done:$done}}){task{rowId done}}}"; + const result = await gql(alice, mutation, { id: task, done: !before.done }); + assert.ok( + result.body.errors?.length || + result.body.data.updateTaskByRowId === null || + result.body.data.updateTaskByRowId?.task === null, + ); + const after = (await gql(bob, query, { id: task })).body.data.taskByRowId; + assert.deepEqual(after, before); +}); +test("fragment pagination limits and request boundary controls operate over HTTP", async () => { + for (const query of [ + "{allProjects{...Rows}} fragment Rows on ProjectConnection{nodes{rowId}}", + "{allProjects{... on ProjectConnection{nodes{rowId}}}}", + "{__schema{queryType{name}}}", + ]) { + assert.equal((await gql(alice, query)).status, 400); + } + const denied = await fetch(origin + "/graphql", { + method: "POST", + headers: { + Origin: "http://foreign.test", + Cookie: alice, + "Content-Type": "application/json", + }, + body: JSON.stringify({ query: "{allProjects(first:1){nodes{rowId}}}" }), + }); + assert.equal(denied.status, 403); + const tooLarge = await gql(alice, " ".repeat(9000)); + assert.equal(tooLarge.status, 400); +}); +test("generated cursor pagination covers more than20projects and50tasks", async () => { + await scoped(A, async (c) => { + await c.query( + "INSERT INTO tracker.projects(id,name,created_at) VALUES($1,'Pagination notebook',now()+interval '1 minute') ON CONFLICT(id) DO NOTHING", + [pagination], + ); + await c.query( + "INSERT INTO tracker.projects(id,name) SELECT ('21000000-0000-4000-8000-'||lpad(g::text,12,'0'))::uuid,'Pagination project '||g FROM generate_series(1,21) g ON CONFLICT(id) DO NOTHING", + ); + await c.query( + "INSERT INTO tracker.tasks(id,project_id,title,created_at) SELECT ('31000000-0000-4000-8000-'||lpad(g::text,12,'0'))::uuid,$1,'Pagination step '||lpad(g::text,2,'0'),now()-g*interval '1 second' FROM generate_series(1,51)g ON CONFLICT(id) DO NOTHING", + [pagination], + ); + }); + const q = + "query($after:Cursor){allProjects(first:20,after:$after,orderBy:[CREATED_AT_DESC,PRIMARY_KEY_DESC]){totalCount pageInfo{hasNextPage endCursor} nodes{rowId}}}"; + const response = await gql(alice, q); + assert.equal(response.status, 200, JSON.stringify(response.body)); + assert.equal(response.body.errors, undefined, JSON.stringify(response.body)); + const first = response.body.data.allProjects; + assert.ok(first.totalCount > 20); + assert.equal(first.nodes.length, 20); + assert.equal(first.pageInfo.hasNextPage, true); + const second = (await gql(alice, q, { after: first.pageInfo.endCursor })).body + .data.allProjects; + assert.equal( + new Set([...first.nodes, ...second.nodes].map((p: any) => p.rowId)).size, + first.totalCount, + ); + const tq = + "query($id:UUID!,$after:Cursor){projectByRowId(rowId:$id){tasksByProjectIdAndWorkspaceId(first:50,after:$after,orderBy:[CREATED_AT_DESC,PRIMARY_KEY_DESC]){totalCount pageInfo{hasNextPage endCursor} nodes{rowId}}}}"; + const tasks = (await gql(alice, tq, { id: pagination })).body.data + .projectByRowId.tasksByProjectIdAndWorkspaceId; + assert.equal(tasks.totalCount, 51); + assert.equal(tasks.nodes.length, 50); + const next = ( + await gql(alice, tq, { id: pagination, after: tasks.pageInfo.endCursor }) + ).body.data.projectByRowId.tasksByProjectIdAndWorkspaceId; + assert.equal(next.nodes.length, 1); + assert.equal(next.pageInfo.hasNextPage, false); + assert.ok(!tasks.nodes.some((t: any) => t.rowId === next.nodes[0].rowId)); +}); diff --git a/applications/project-graphql/tests/browser/workspace.spec.ts b/applications/project-graphql/tests/browser/workspace.spec.ts new file mode 100644 index 00000000..89176dbb --- /dev/null +++ b/applications/project-graphql/tests/browser/workspace.spec.ts @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; +test("real Apollo pages, generated writes and cache replacement across verified sessions", async ({ + page, +}) => { + await page.goto("/"); + await page.getByLabel("Password", { exact: true }).fill("river-demo"); + await page.getByRole("button", { name: "Sign in" }).click(); + await expect( + page.getByRole("heading", { name: "Riverside Studio" }), + ).toBeVisible(); + await page.getByRole("button", { name: "Pagination notebook" }).click(); + await expect( + page.getByText("50 of 51 tasks", { exact: false }), + ).toBeVisible(); + await page.getByRole("button", { name: "Load more tasks" }).click(); + await expect( + page.getByText("51 of 51 tasks", { exact: false }), + ).toBeVisible(); + await expect( + page.getByText("Pagination step 51", { exact: true }), + ).toBeVisible(); + await page.getByRole("button", { name: "Load more projects" }).click(); + await expect( + page.getByRole("button", { name: "Load more projects" }), + ).toHaveCount(0); + const name = "Launch notebook " + Date.now(); + await page.getByLabel("New project name").fill(name); + await page + .getByLabel("New project description") + .fill("A small launch checklist for the team"); + await page.getByRole("button", { name: "Create project" }).click(); + await expect(page.getByRole("heading", { name })).toBeVisible(); + await expect(page.getByRole("button", { name, exact: false })).toBeVisible(); + await page.getByLabel("New task title").fill("Review the launch notes"); + await page.getByRole("button", { name: "Add task" }).click(); + await expect( + page.getByText("Review the launch notes", { exact: true }), + ).toBeVisible(); + await page + .getByRole("checkbox", { name: "Complete Review the launch notes" }) + .click(); + await expect( + page.getByRole("checkbox", { name: "Complete Review the launch notes" }), + ).toBeChecked(); + await page.screenshot({ + path: ".deployment/workspace-browser.png", + fullPage: true, + }); + await page.getByRole("button", { name: "Switch workspace" }).click(); + await page + .getByLabel("Account", { exact: true }) + .selectOption("bob@example.test"); + await page.getByLabel("Password", { exact: true }).fill("summit-demo"); + await page.getByRole("button", { name: "Sign in" }).click(); + await expect( + page.getByRole("heading", { name: "Summit Team" }), + ).toBeVisible(); + await expect(page.getByText(name, { exact: true })).toHaveCount(0); + await expect( + page.getByText("Review the launch notes", { exact: true }), + ).toHaveCount(0); + await expect( + page.getByRole("button", { name: "Pagination notebook" }), + ).toHaveCount(0); + await page.getByRole("button", { name: "Switch workspace" }).click(); + await page.getByLabel("Password", { exact: true }).fill("river-demo"); + await page.getByRole("button", { name: "Sign in" }).click(); + await page.getByRole("button", { name, exact: false }).click(); + await expect( + page.getByRole("checkbox", { name: "Complete Review the launch notes" }), + ).toBeChecked(); +}); + +test("a late session response cannot restore an old workspace; transport failures are visible", async ({ + page, + context, +}) => { + const origin = process.env.APP_ORIGIN ?? "http://127.0.0.1:3002"; + async function account(email: string, password: string) { + const response = await context.request.post(origin + "/api/login", { + headers: { Origin: origin }, + data: { email, password }, + }); + expect(response.status()).toBe(200); + } + await account("alice@example.test", "river-demo"); + let release!: () => void; + let captured!: () => void; + const gate = new Promise((resolve) => (release = resolve)); + const ready = new Promise((resolve) => (captured = resolve)); + let first = true; + await page.route("**/api/me", async (route) => { + if (!first) { + await route.continue(); + return; + } + first = false; + const response = await route.fetch(); + const body = await response.body(); + captured(); + await gate; + await route.fulfill({ response, body }); + }); + await page.goto("/"); + await ready; + await account("bob@example.test", "summit-demo"); + const broadcaster = await context.newPage(); + await broadcaster.goto("/"); + await broadcaster.evaluate(() => { + const c = new BroadcastChannel("common-ground-session"); + c.postMessage("changed"); + c.close(); + }); + await expect( + page.getByRole("heading", { name: "Summit Team" }), + ).toBeVisible(); + release(); + await expect( + page.getByRole("heading", { name: "Riverside Studio" }), + ).toHaveCount(0); + await expect( + page.getByRole("heading", { name: "Summit Team" }), + ).toBeVisible(); + await page.route("**/api/logout", (route) => route.abort()); + await page.getByRole("button", { name: "Switch workspace" }).click(); + await expect(page.getByRole("alert")).toContainText("Could not sign out"); + await expect( + page.getByRole("heading", { name: "Summit Team" }), + ).toBeVisible(); + await page.unroute("**/api/logout"); + await page.getByRole("button", { name: "Switch workspace" }).click(); + await page.route("**/api/login", (route) => route.abort()); + await page.getByLabel("Password", { exact: true }).fill("river-demo"); + await page.getByRole("button", { name: "Sign in" }).click(); + await expect(page.getByRole("alert")).toContainText("Could not sign in"); +}); diff --git a/applications/project-graphql/tests/session.test.ts b/applications/project-graphql/tests/session.test.ts new file mode 100644 index 00000000..b90c59cb --- /dev/null +++ b/applications/project-graphql/tests/session.test.ts @@ -0,0 +1,59 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { + passwordHash, + passwordMatches, + cookieToken, +} from "../src/server/sessions.ts"; +import { validateOperation } from "../src/server/limits.ts"; +test("stored password verifier refuses mismatches and cookies reject ambiguous tokens", () => { + const hash = passwordHash("correct", "salt"); + assert.equal(passwordMatches("wrong", "salt", hash), false); + assert.equal(passwordMatches("correct", "salt", hash), true); + assert.equal( + cookieToken( + "ground_session=" + "a".repeat(64) + "; ground_session=" + "b".repeat(64), + ), + null, + ); + assert.equal(cookieToken("ground_session=forged"), null); +}); +test("real GraphQL AST bounds expanded fragments and connection variables", () => { + validateOperation( + "query Q($n:Int!){allProjects(first:$n){nodes{id}}}", + { n: 20 }, + "Q", + ); + for (const n of [0, 51, null]) + assert.throws(() => + validateOperation( + "query Q($n:Int!){allProjects(first:$n){nodes{id}}}", + { n }, + "Q", + ), + ); + assert.throws(() => validateOperation("{__schema{queryType{name}}}", {})); + assert.throws(() => + validateOperation("query{a{...X}} fragment X on X{b{...X}}", {}), + ); + assert.throws(() => validateOperation("{allProjects{nodes{id}}}", {})); +}); + +test("pagination checks expand named and inline fragments at connection level", () => { + assert.throws(() => + validateOperation( + "query{allProjects{...Rows}} fragment Rows on ProjectsConnection {nodes{id}}", + {}, + ), + ); + assert.throws(() => + validateOperation( + "{allProjects{... on ProjectsConnection {nodes{id}}}}", + {}, + ), + ); + validateOperation( + "query{allProjects(first:20){...Rows}} fragment Rows on ProjectsConnection {nodes{id}}", + {}, + ); +}); diff --git a/applications/project-graphql/tsconfig.json b/applications/project-graphql/tsconfig.json new file mode 100644 index 00000000..ef194fd7 --- /dev/null +++ b/applications/project-graphql/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "types": ["node"], + "noEmit": true, + "allowImportingTsExtensions": true, + "erasableSyntaxOnly": true + }, + "include": ["src", "scripts", "tests", "*.ts"] +} diff --git a/applications/project-graphql/vite.config.ts b/applications/project-graphql/vite.config.ts new file mode 100644 index 00000000..1826ab6a --- /dev/null +++ b/applications/project-graphql/vite.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +export default defineConfig({ plugins: [react()] });