From 3e5e1b72555e54c8b23b654ca5ff3a0acd6d7db2 Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:00:26 -0500 Subject: [PATCH 1/8] updates --- templates/keynote-2/.dockerignore | 6 + templates/keynote-2/.env.example | 63 + templates/keynote-2/.gitignore | 39 + templates/keynote-2/.prettierrc | 5 + templates/keynote-2/DEVELOP.md | 221 ++ templates/keynote-2/Dockerfile.bench | 17 + templates/keynote-2/Dockerfile.bun | 14 + templates/keynote-2/Dockerfile.rpc | 14 + templates/keynote-2/Dockerfile.sqlite-seed | 24 + templates/keynote-2/bun/bun-server.ts | 266 ++ templates/keynote-2/bun/bun.lock | 668 ++++ templates/keynote-2/convex-app/.gitignore | 2 + .../keynote-2/convex-app/convex/README.md | 90 + .../keynote-2/convex-app/convex/accounts.ts | 33 + .../keynote-2/convex-app/convex/balances.ts | 12 + .../convex-app/convex/convex.config.ts | 8 + templates/keynote-2/convex-app/convex/http.ts | 28 + .../keynote-2/convex-app/convex/schema.ts | 10 + templates/keynote-2/convex-app/convex/seed.ts | 28 + .../keynote-2/convex-app/convex/transfer.ts | 72 + .../keynote-2/convex-app/convex/tsconfig.json | 25 + .../keynote-2/convex-app/package-lock.json | 546 +++ templates/keynote-2/convex-app/package.json | 13 + templates/keynote-2/convex-app/pnpm-lock.yaml | 344 ++ .../crdb-nodes/docker-compose-crdb-node-1.yml | 27 + .../crdb-nodes/docker-compose-crdb-node-2.yml | 19 + .../crdb-nodes/docker-compose-crdb-node-3.yml | 19 + .../docker-compose-crdb-loadbalancer.yml | 7 + .../docker-compose-crdb-rpc-server.yml | 14 + .../docker-compose-linux-raid-crdb.yml | 210 + .../keynote-2/docker-compose-linux-raid.yml | 331 ++ templates/keynote-2/docker-compose.yml | 298 ++ .../modules/test-1/server/.cargo/config.toml | 11 + .../modules/test-1/server/.gitignore | 17 + .../modules/test-1/server/Cargo.toml | 13 + .../modules/test-1/server/src/lib.rs | 83 + templates/keynote-2/nginx-crdb-local.conf | 23 + templates/keynote-2/nginx-crdb.conf | 34 + templates/keynote-2/package-lock.json | 3431 +++++++++++++++++ templates/keynote-2/package.json | 42 + templates/keynote-2/pnpm-lock.yaml | 2510 ++++++++++++ templates/keynote-2/src/cli.ts | 295 ++ templates/keynote-2/src/connectors/bun.ts | 120 + templates/keynote-2/src/connectors/convex.ts | 203 + .../src/connectors/direct/cockroach.ts | 228 ++ .../src/connectors/direct/postgres.ts | 202 + .../keynote-2/src/connectors/direct/sqlite.ts | 227 ++ .../connectors/drizzle/cockroach_drizzle.ts | 278 ++ .../connectors/drizzle/postgres_drizzle.ts | 283 ++ .../src/connectors/drizzle/sqlite_drizzle.ts | 316 ++ templates/keynote-2/src/connectors/index.ts | 34 + .../src/connectors/rpc/cockroach_rpc.ts | 133 + .../src/connectors/rpc/planetscale_pg_rpc.ts | 82 + .../src/connectors/rpc/postgres_rpc.ts | 97 + .../src/connectors/rpc/rpc_common.ts | 8 + .../src/connectors/rpc/sqlite_rpc.ts | 97 + .../src/connectors/rpc/supabase_rpc.ts | 97 + .../keynote-2/src/connectors/spacetimedb.ts | 316 ++ .../keynote-2/src/connectors/sqlite_common.ts | 113 + .../keynote-2/src/connectors/supabase.ts | 223 ++ .../keynote-2/src/core/collision_tracker.ts | 39 + templates/keynote-2/src/core/connectors.ts | 27 + templates/keynote-2/src/core/runner.ts | 356 ++ templates/keynote-2/src/core/runner_1.ts | 302 ++ .../keynote-2/src/core/spacetimeMetrics.ts | 76 + templates/keynote-2/src/core/types.ts | 11 + templates/keynote-2/src/core/zipf.ts | 185 + templates/keynote-2/src/drizzle/schema.ts | 17 + templates/keynote-2/src/helpers.ts | 24 + templates/keynote-2/src/init/init-all.ts | 94 + templates/keynote-2/src/init/init_bun.ts | 112 + templates/keynote-2/src/init/init_convex.ts | 81 + templates/keynote-2/src/init/init_pglike.ts | 38 + .../keynote-2/src/init/init_rpc_servers.ts | 82 + .../keynote-2/src/init/init_spacetime.ts | 65 + templates/keynote-2/src/init/init_sqlite.ts | 46 + .../src/init/init_sqlite_seed_in_docker.ts | 83 + templates/keynote-2/src/init/init_supabase.ts | 252 ++ templates/keynote-2/src/init/utils.ts | 67 + .../src/rpc-servers/cockroach-rpc-server.ts | 272 ++ .../src/rpc-servers/postgres-rpc-server.ts | 257 ++ .../src/rpc-servers/sqlite-rpc-server.ts | 277 ++ .../src/rpc-servers/supabase-rpc-server.ts | 272 ++ .../src/scenario_recipes/reducer_single.ts | 16 + .../src/scenario_recipes/rpc_single_call.ts | 35 + .../scenario_recipes/sql_single_statement.ts | 61 + templates/keynote-2/src/tests/test-1/bun.ts | 7 + .../keynote-2/src/tests/test-1/cockroach.ts | 7 + .../src/tests/test-1/cockroach_drizzle.ts | 7 + .../src/tests/test-1/cockroach_rpc.ts | 7 + .../keynote-2/src/tests/test-1/convex.ts | 7 + .../src/tests/test-1/planetscale_pg_rpc.ts | 7 + .../keynote-2/src/tests/test-1/postgres.ts | 7 + .../src/tests/test-1/postgres_drizzle.ts | 7 + .../src/tests/test-1/postgres_rpc.ts | 7 + .../keynote-2/src/tests/test-1/spacetimedb.ts | 7 + .../keynote-2/src/tests/test-1/sqlite.ts | 9 + .../src/tests/test-1/sqlite_drizzle.ts | 7 + .../keynote-2/src/tests/test-1/sqlite_rpc.ts | 7 + .../keynote-2/src/tests/test-1/supabase.ts | 7 + .../src/tests/test-1/supabase_rpc.ts | 7 + templates/keynote-2/src/tests/types.ts | 13 + templates/keynote-2/supabase/.gitignore | 8 + templates/keynote-2/supabase/config.toml | 362 ++ templates/keynote-2/tools/benchmarks.html | 598 +++ templates/keynote-2/tsconfig.json | 23 + 106 files changed, 17247 insertions(+) create mode 100644 templates/keynote-2/.dockerignore create mode 100644 templates/keynote-2/.env.example create mode 100644 templates/keynote-2/.gitignore create mode 100644 templates/keynote-2/.prettierrc create mode 100644 templates/keynote-2/DEVELOP.md create mode 100644 templates/keynote-2/Dockerfile.bench create mode 100644 templates/keynote-2/Dockerfile.bun create mode 100644 templates/keynote-2/Dockerfile.rpc create mode 100644 templates/keynote-2/Dockerfile.sqlite-seed create mode 100644 templates/keynote-2/bun/bun-server.ts create mode 100644 templates/keynote-2/bun/bun.lock create mode 100644 templates/keynote-2/convex-app/.gitignore create mode 100644 templates/keynote-2/convex-app/convex/README.md create mode 100644 templates/keynote-2/convex-app/convex/accounts.ts create mode 100644 templates/keynote-2/convex-app/convex/balances.ts create mode 100644 templates/keynote-2/convex-app/convex/convex.config.ts create mode 100644 templates/keynote-2/convex-app/convex/http.ts create mode 100644 templates/keynote-2/convex-app/convex/schema.ts create mode 100644 templates/keynote-2/convex-app/convex/seed.ts create mode 100644 templates/keynote-2/convex-app/convex/transfer.ts create mode 100644 templates/keynote-2/convex-app/convex/tsconfig.json create mode 100644 templates/keynote-2/convex-app/package-lock.json create mode 100644 templates/keynote-2/convex-app/package.json create mode 100644 templates/keynote-2/convex-app/pnpm-lock.yaml create mode 100644 templates/keynote-2/crdb-nodes/docker-compose-crdb-node-1.yml create mode 100644 templates/keynote-2/crdb-nodes/docker-compose-crdb-node-2.yml create mode 100644 templates/keynote-2/crdb-nodes/docker-compose-crdb-node-3.yml create mode 100644 templates/keynote-2/docker-compose-crdb-loadbalancer.yml create mode 100644 templates/keynote-2/docker-compose-crdb-rpc-server.yml create mode 100644 templates/keynote-2/docker-compose-linux-raid-crdb.yml create mode 100644 templates/keynote-2/docker-compose-linux-raid.yml create mode 100644 templates/keynote-2/docker-compose.yml create mode 100644 templates/keynote-2/modules/test-1/server/.cargo/config.toml create mode 100644 templates/keynote-2/modules/test-1/server/.gitignore create mode 100644 templates/keynote-2/modules/test-1/server/Cargo.toml create mode 100644 templates/keynote-2/modules/test-1/server/src/lib.rs create mode 100644 templates/keynote-2/nginx-crdb-local.conf create mode 100644 templates/keynote-2/nginx-crdb.conf create mode 100644 templates/keynote-2/package-lock.json create mode 100644 templates/keynote-2/package.json create mode 100644 templates/keynote-2/pnpm-lock.yaml create mode 100644 templates/keynote-2/src/cli.ts create mode 100644 templates/keynote-2/src/connectors/bun.ts create mode 100644 templates/keynote-2/src/connectors/convex.ts create mode 100644 templates/keynote-2/src/connectors/direct/cockroach.ts create mode 100644 templates/keynote-2/src/connectors/direct/postgres.ts create mode 100644 templates/keynote-2/src/connectors/direct/sqlite.ts create mode 100644 templates/keynote-2/src/connectors/drizzle/cockroach_drizzle.ts create mode 100644 templates/keynote-2/src/connectors/drizzle/postgres_drizzle.ts create mode 100644 templates/keynote-2/src/connectors/drizzle/sqlite_drizzle.ts create mode 100644 templates/keynote-2/src/connectors/index.ts create mode 100644 templates/keynote-2/src/connectors/rpc/cockroach_rpc.ts create mode 100644 templates/keynote-2/src/connectors/rpc/planetscale_pg_rpc.ts create mode 100644 templates/keynote-2/src/connectors/rpc/postgres_rpc.ts create mode 100644 templates/keynote-2/src/connectors/rpc/rpc_common.ts create mode 100644 templates/keynote-2/src/connectors/rpc/sqlite_rpc.ts create mode 100644 templates/keynote-2/src/connectors/rpc/supabase_rpc.ts create mode 100644 templates/keynote-2/src/connectors/spacetimedb.ts create mode 100644 templates/keynote-2/src/connectors/sqlite_common.ts create mode 100644 templates/keynote-2/src/connectors/supabase.ts create mode 100644 templates/keynote-2/src/core/collision_tracker.ts create mode 100644 templates/keynote-2/src/core/connectors.ts create mode 100644 templates/keynote-2/src/core/runner.ts create mode 100644 templates/keynote-2/src/core/runner_1.ts create mode 100644 templates/keynote-2/src/core/spacetimeMetrics.ts create mode 100644 templates/keynote-2/src/core/types.ts create mode 100644 templates/keynote-2/src/core/zipf.ts create mode 100644 templates/keynote-2/src/drizzle/schema.ts create mode 100644 templates/keynote-2/src/helpers.ts create mode 100644 templates/keynote-2/src/init/init-all.ts create mode 100644 templates/keynote-2/src/init/init_bun.ts create mode 100644 templates/keynote-2/src/init/init_convex.ts create mode 100644 templates/keynote-2/src/init/init_pglike.ts create mode 100644 templates/keynote-2/src/init/init_rpc_servers.ts create mode 100644 templates/keynote-2/src/init/init_spacetime.ts create mode 100644 templates/keynote-2/src/init/init_sqlite.ts create mode 100644 templates/keynote-2/src/init/init_sqlite_seed_in_docker.ts create mode 100644 templates/keynote-2/src/init/init_supabase.ts create mode 100644 templates/keynote-2/src/init/utils.ts create mode 100644 templates/keynote-2/src/rpc-servers/cockroach-rpc-server.ts create mode 100644 templates/keynote-2/src/rpc-servers/postgres-rpc-server.ts create mode 100644 templates/keynote-2/src/rpc-servers/sqlite-rpc-server.ts create mode 100644 templates/keynote-2/src/rpc-servers/supabase-rpc-server.ts create mode 100644 templates/keynote-2/src/scenario_recipes/reducer_single.ts create mode 100644 templates/keynote-2/src/scenario_recipes/rpc_single_call.ts create mode 100644 templates/keynote-2/src/scenario_recipes/sql_single_statement.ts create mode 100644 templates/keynote-2/src/tests/test-1/bun.ts create mode 100644 templates/keynote-2/src/tests/test-1/cockroach.ts create mode 100644 templates/keynote-2/src/tests/test-1/cockroach_drizzle.ts create mode 100644 templates/keynote-2/src/tests/test-1/cockroach_rpc.ts create mode 100644 templates/keynote-2/src/tests/test-1/convex.ts create mode 100644 templates/keynote-2/src/tests/test-1/planetscale_pg_rpc.ts create mode 100644 templates/keynote-2/src/tests/test-1/postgres.ts create mode 100644 templates/keynote-2/src/tests/test-1/postgres_drizzle.ts create mode 100644 templates/keynote-2/src/tests/test-1/postgres_rpc.ts create mode 100644 templates/keynote-2/src/tests/test-1/spacetimedb.ts create mode 100644 templates/keynote-2/src/tests/test-1/sqlite.ts create mode 100644 templates/keynote-2/src/tests/test-1/sqlite_drizzle.ts create mode 100644 templates/keynote-2/src/tests/test-1/sqlite_rpc.ts create mode 100644 templates/keynote-2/src/tests/test-1/supabase.ts create mode 100644 templates/keynote-2/src/tests/test-1/supabase_rpc.ts create mode 100644 templates/keynote-2/src/tests/types.ts create mode 100644 templates/keynote-2/supabase/.gitignore create mode 100644 templates/keynote-2/supabase/config.toml create mode 100644 templates/keynote-2/tools/benchmarks.html create mode 100644 templates/keynote-2/tsconfig.json diff --git a/templates/keynote-2/.dockerignore b/templates/keynote-2/.dockerignore new file mode 100644 index 00000000000..b3157866cf7 --- /dev/null +++ b/templates/keynote-2/.dockerignore @@ -0,0 +1,6 @@ +node_modules +**/node_modules +.git +dist +build +.tmp \ No newline at end of file diff --git a/templates/keynote-2/.env.example b/templates/keynote-2/.env.example new file mode 100644 index 00000000000..2a4e11af809 --- /dev/null +++ b/templates/keynote-2/.env.example @@ -0,0 +1,63 @@ +# ===== Runtime toggles ===== +USE_DOCKER=1 # 1 = run docker compose up for pg/crdb; 0 = skip +#SKIP_PG=1 # 1 = don't init Postgres in prep +#SKIP_CRDB=1 # 1 = don't init Cockroach in prep +#SKIP_SQLITE=1 # 1 = don't init SQLite in prep +#SKIP_SUPABASE=1 # 1 = don't init Supabase in prep +SKIP_CONVEX=1 # 1 = don't init Convex in prep +USE_SPACETIME_METRICS_ENDPOINT=0 + +# ===== PostgreSQL ===== +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_DB=postgres +PG_URL=postgres://postgres:postgres@127.0.0.1:5432/postgres + +# ===== CockroachDB ===== +CRDB_URL=postgresql://root@127.0.0.1:26257/defaultdb?sslmode=disable +# Example managed CockroachDB URL (fill in your own values): +#CRDB_URL=postgresql://username:password@your-cluster.region.cockroachlabs.cloud:26257/defaultdb?sslmode=verify-full + +# ===== SQLite ===== +SQLITE_FILE=./.data/accounts.sqlite +SQLITE_MODE=default + +# ===== SpacetimeDB ===== +STDB_URL=ws://127.0.0.1:3000 +STDB_MODULE=test-1 +STDB_MODULE_PATH=./modules/test-1/server +STDB_METRICS_URL=http://127.0.0.1:3000/v1/metrics + +# ===== Supabase ===== +# Replace with your actual Supabase project ref +SUPABASE_URL=http://127.0.0.1:54321 +SUPABASE_ANON_KEY=your_supabase_anon_key_here +SUPABASE_DB_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres + +# ===== Convex ===== +#CONVEX_URL=https://your-deployment.convex.cloud +CONVEX_URL=http://127.0.0.1:3210 +CONVEX_SITE_URL=http://127.0.0.1:3210 +CLEAR_CONVEX_ON_PREP=0 +CONVEX_USE_SHARDED_COUNTER=1 + +# ===== Bun ===== +BUN_URL=http://127.0.0.1:4001 +BUN_PG_URL=postgres://postgres:postgres@pg_bun:5432/bun_bench + +# ===== RPC Servers ===== +PG_RPC_URL=http://127.0.0.1:4101 +CRDB_RPC_URL=http://127.0.0.1:4102 +SQLITE_RPC_URL=http://127.0.0.1:4103 +SUPABASE_RPC_URL=http://127.0.0.1:4106 + +# ===== PlanetScale (optional) ===== +#PLANETSCALE_PG_URL=postgresql://user:password@your-planetscale-host:5432/database +#PLANETSCALE_RPC_URL=http://127.0.0.1:4104 + +# ===== Seeding knobs ===== +SEED_ACCOUNTS=100000 +SEED_INITIAL_BALANCE=10000000 + +VERIFY=0 +ENABLE_RPC_SERVERS=0 diff --git a/templates/keynote-2/.gitignore b/templates/keynote-2/.gitignore new file mode 100644 index 00000000000..8c693918910 --- /dev/null +++ b/templates/keynote-2/.gitignore @@ -0,0 +1,39 @@ +# Environment files +.env +.env.* +!.env.example + +# Data files +.data/ + +# Auto-generated files +convex-app/convex/_generated/ +modules/test-1/module_bindings/ + +# SpacetimeDB +.spacetime/ + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/ +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +/runs diff --git a/templates/keynote-2/.prettierrc b/templates/keynote-2/.prettierrc new file mode 100644 index 00000000000..80eaa73302f --- /dev/null +++ b/templates/keynote-2/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "printWidth": 80, + "tabWidth": 2 +} diff --git a/templates/keynote-2/DEVELOP.md b/templates/keynote-2/DEVELOP.md new file mode 100644 index 00000000000..0f3629882f7 --- /dev/null +++ b/templates/keynote-2/DEVELOP.md @@ -0,0 +1,221 @@ +## Overview + +This app runs repeatable load tests against multiple connectors (Bun+Postgres, CockroachDB, SQLite, Supabase, Convex, SpacetimeDB, etc.). + +Each run: + +- Loads a scenario from `src/tests//` +- Runs it against one or more connectors +- Writes a JSON report into `./runs/` with TPS and latency stats + +--- + +## Prerequisites + +- **Node.js** ≥ 20.x +- **pnpm** installed globally +- **Docker** for local Postgres / Cockroach / Supabase +- Local/Cloud Convex + +From a fresh clone: + +```bash +pnpm install +```` + +--- + +## Configuration (`.env`) + +Copy `.env.example` to `.env` and adjust. + +**Seeding / verification:** + +* `SEED_ACCOUNTS` – number of accounts to seed (if unset, code defaults to `100_000`) +* `SEED_INITIAL_BALANCE` – starting balance per account +* `VERIFY` – enable extra verification passes when non-zero +* `ENABLE_RPC_SERVERS` – flag used by scripts that start the RPC benchmark servers + +**Runtime toggles:** + +* `USE_DOCKER` – `1` = run Docker Compose for Postgres / CockroachDB; `0` = skip +* `SKIP_PG` – `1` = don't init Postgres in prep +* `SKIP_CRDB` – `1` = don't init CockroachDB in prep +* `SKIP_SQLITE` – `1` = don't init SQLite in prep +* `SKIP_SUPABASE` – `1` = don't init Supabase in prep +* `SKIP_CONVEX` – `1` = don't init Convex in prep +* `USE_SPACETIME_METRICS_ENDPOINT` – `1` = read committed transfer counts from the SpacetimeDB metrics endpoint; otherwise only local counters are used + +**PostgreSQL / CockroachDB:** + +* `PG_URL` – Postgres connection string +* `CRDB_URL` – CockroachDB connection string + +**SQLite:** + +* `SQLITE_FILE` – path to the SQLite file +* `SQLITE_MODE` – tuning preset for the SQLite connector + +**SpacetimeDB:** + +* `STDB_URL` – WebSocket URL for SpacetimeDB +* `STDB_MODULE` – module name to load (e.g. `test-1`) +* `STDB_MODULE_PATH` – filesystem path to the module source (for local dev) +* `STDB_METRICS_URL` – HTTP URL for the SpacetimeDB metrics endpoint + +**Supabase:** + +* `SUPABASE_URL` – Supabase project URL +* `SUPABASE_ANON_KEY` – Supabase anon/public key +* `SUPABASE_DB_URL` – Postgres connection string for the Supabase database + +**Convex:** + +* `CONVEX_URL` – Convex deployment URL +* `CONVEX_SITE_URL` – Convex site URL +* `CLEAR_CONVEX_ON_PREP` – Convex prep flag (clears data when enabled) +* `CONVEX_USE_SHARDED_COUNTER` – flag for using the sharded-counter implementation + +**Bun / RPC helpers:** + +* `BUN_URL` – Bun HTTP benchmark server URL +* `BUN_PG_URL` – Postgres connection string for the Bun benchmark service + +**RPC benchmark servers:** + +* `PG_RPC_URL` – HTTP URL for the Postgres RPC server +* `CRDB_RPC_URL` – HTTP URL for the CockroachDB RPC server +* `SQLITE_RPC_URL` – HTTP URL for the SQLite RPC server + +--- + +## Setup + +### Generate bindings (first time after clone) + +**SpacetimeDB module bindings:** +```bash +cd modules/test-1/server +spacetimedb generate --lang typescript --out-dir ../module_bindings +cd ../../.. +``` + +**Convex generated files:** +```bash +cd convex-app +npx convex dev +# Wait for it to generate files, then Ctrl+C +cd .. +``` + +### Start services + +1. Start SpacetimeDB (`spacetimedb start`) +2. Start Convex (inside convex-app run `npx convex dev`) +3. Init Supabase (run `supabase init`) inside project root. +4. `npm run prep` to seed the databases. +5. `npm run bench` to run the test against all connectors. + +## Commands & Examples + +### 1. Run a test + +```bash +npm run bench [test-name] [--seconds N] [--concurrency N] [--alpha A] [--connectors list] +``` + +Examples: + +```bash +# Default test (test-1), default args (note: only 1 test right now, and it's embedded) +npm run bench + +# Explicit test name +npm run bench test-1 + +# Short run, 100 concurrent workers +npm run bench test-1 --seconds 10 --concurrency 100 + +# Heavier skew on hot accounts +npm run bench test-1 --alpha 2.0 + +# Only run selected connectors +npm run bench test-1 --connectors spacetimedb,sqlite +``` + +--- + +## CLI Arguments + +From `src/cli.ts`: + +* **`test-name`** (positional) + + * Name of the test folder under `src/tests/` + * Default: `test-1` + +* **`--seconds N`** + + * Duration of the benchmark in seconds + * Default: `1` + +* **`--concurrency N`** + + * Number of workers / in-flight operations + * Default: `10` + +* **`--alpha A`** + + * Zipf α parameter for account selection (hot vs cold distribution) + * Default: `0.5` + +* **`--connectors list`** + + * Optional, comma-separated list of connector `system` names + * Example: + + ```bash + --connectors spacetimedb,sqlite,postgres + ``` + * If omitted, all connectors for that test are run + * The valid names come from `tc.system` in the test modules and the keys in `CONNECTORS` + +* **`--contention-tests startAlpha endAlpha step concurrency`** + + * Runs a sweep over Zipf α values for a single connector + * Uses `startAlpha`, `endAlpha`, and `step` to choose the α values + * Uses the provided `concurrency` for all runs + +* **`--concurrency-tests startConc endConc step alpha`** + + * Runs a sweep over concurrency levels for a single connector + * Uses `startConc`, `endConc`, and `step` to choose the concurrency values + * Uses the provided `alpha` for all runs + +--- + +### Running in Docker + +You can also run the benchmark via Docker instead of Node directly: + +```bash +docker compose run --rm bench \ + --seconds 5 \ + --concurrency 50 \ + --alpha 1 \ + --connectors convex +``` + +If using Docker, make sure to set `USE_DOCKER=1` in `.env`, verify docker-compose env variables, verify you've run supabase init, and run `npm prep` before running bench. + +## Output + +Every run writes a JSON file into `./runs/`: + +* Directory: `./runs/` +* Filename: `-.json` + + * Example: `test-1-2025-11-17T16-45-12-345Z.json` + + +Point your visualizations / CSV exports at `./runs/` and you’re good. diff --git a/templates/keynote-2/Dockerfile.bench b/templates/keynote-2/Dockerfile.bench new file mode 100644 index 00000000000..1fabf18f0a6 --- /dev/null +++ b/templates/keynote-2/Dockerfile.bench @@ -0,0 +1,17 @@ +# Dockerfile.bench +FROM node:20.18.1-bookworm-slim + +WORKDIR /app +RUN npm install -g pnpm@9.15.3 + +# Install build tools for better-sqlite3 +RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/* + +COPY package.json pnpm-lock.yaml* tsconfig.json* ./ +RUN pnpm install --frozen-lockfile || pnpm install + +COPY src ./src +COPY modules ./modules + +# Correct: use bench script with passed args +ENTRYPOINT ["pnpm", "tsx", "src/cli.ts"] \ No newline at end of file diff --git a/templates/keynote-2/Dockerfile.bun b/templates/keynote-2/Dockerfile.bun new file mode 100644 index 00000000000..8db39b5fbf0 --- /dev/null +++ b/templates/keynote-2/Dockerfile.bun @@ -0,0 +1,14 @@ +FROM oven/bun:latest + +WORKDIR /app + +COPY package.json bun.lockb* tsconfig.json ./ + +RUN bun install --ignore-scripts + +# Copy source +COPY bun ./bun + +ENV PORT=4001 + +CMD ["bun", "bun/bun-server.ts"] diff --git a/templates/keynote-2/Dockerfile.rpc b/templates/keynote-2/Dockerfile.rpc new file mode 100644 index 00000000000..833bbd7ed7a --- /dev/null +++ b/templates/keynote-2/Dockerfile.rpc @@ -0,0 +1,14 @@ +# Dockerfile.rpc +FROM node:20.18.1-bookworm-slim + +WORKDIR /app +RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/* + +RUN npm install -g pnpm@9.15.3 + +COPY package.json pnpm-lock.yaml* tsconfig.json* ./ +RUN pnpm install --frozen-lockfile || pnpm install + +COPY src ./src + +CMD ["pnpm", "tsx", "src/rpc-servers/postgres-rpc-server.ts"] \ No newline at end of file diff --git a/templates/keynote-2/Dockerfile.sqlite-seed b/templates/keynote-2/Dockerfile.sqlite-seed new file mode 100644 index 00000000000..8e814589c79 --- /dev/null +++ b/templates/keynote-2/Dockerfile.sqlite-seed @@ -0,0 +1,24 @@ +# Dockerfile.sqlite-seed +FROM node:20.18.1-bookworm-slim + +WORKDIR /app + +# Use the same pnpm version as the rest of the repo +RUN npm install -g pnpm@9.15.3 + +# Build tools for better-sqlite3 +RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/* + +# Install deps +COPY package.json pnpm-lock.yaml* tsconfig.json* ./ +RUN pnpm install --frozen-lockfile || pnpm install + +# App source +COPY src ./src +COPY modules ./modules + +# Default location for the named volume inside the container +ENV SQLITE_FILE=/data/accounts.sqlite + +# Seed script that runs entirely inside Docker, using the named volume +CMD ["pnpm", "tsx", "src/init/init_sqlite_seed_in_docker.ts"] diff --git a/templates/keynote-2/bun/bun-server.ts b/templates/keynote-2/bun/bun-server.ts new file mode 100644 index 00000000000..309491088a7 --- /dev/null +++ b/templates/keynote-2/bun/bun-server.ts @@ -0,0 +1,266 @@ +import { Pool } from 'pg'; +import { drizzle } from 'drizzle-orm/node-postgres'; +import { pgTable, integer, bigint as pgBigint } from 'drizzle-orm/pg-core'; +import { eq, inArray, sql } from 'drizzle-orm'; +import { RpcRequest, RpcResponse } from '../src/connectors/rpc/rpc_common'; +// import { poolMaxFromEnv } from '../src/helpers'; + +const DB_URL = process.env.BUN_PG_URL ?? process.env.PG_URL; +if (!DB_URL) throw new Error('BUN_PG_URL or PG_URL not set'); + +const pool = new Pool({ + connectionString: DB_URL, + application_name: 'bun-rpc-drizzle', + // max: poolMaxFromEnv() +}); + +const accounts = pgTable('accounts', { + id: integer('id').primaryKey(), + balance: pgBigint('balance', { mode: 'bigint' }).notNull(), +}); + +const db = drizzle(pool, { schema: { accounts } }); + +async function handleRpc(body: RpcRequest): Promise { + const name = body?.name; + const args = body?.args ?? {}; + + if (!name) return { ok: false, error: 'missing name' }; + + try { + switch (name) { + case 'health': + return { ok: true, result: { status: 'ok' } }; + + case 'transfer': + await rpcTransfer(args); + return { ok: true }; + + case 'getAccount': + return { ok: true, result: await rpcGetAccount(args) }; + + case 'verify': + return { ok: true, result: await rpcVerify() }; + + case 'seed': + await rpcSeed(args); + return { ok: true }; + + default: + return { ok: false, error: `unknown method: ${name}` }; + } + } catch (err: any) { + console.error('[bun] rpc error:', err); + return { ok: false, error: String(err?.message ?? err) }; + } +} + +async function rpcSeed(args: Record) { + const count = Number(args.accounts ?? process.env.SEED_ACCOUNTS ?? '0'); + const rawInitial = + (args.initialBalance as string | number | undefined) ?? + process.env.SEED_INITIAL_BALANCE; + + if (!Number.isInteger(count) || count <= 0) { + throw new Error('invalid accounts for seed'); + } + if (rawInitial === undefined || rawInitial === null) { + throw new Error('missing initialBalance for seed'); + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + throw new Error(`invalid initialBalance=${rawInitial}`); + } + + await db.transaction(async (tx) => { + // Create table if needed + await tx.execute(sql` + CREATE TABLE IF NOT EXISTS "accounts" ( + "id" integer PRIMARY KEY, + "balance" bigint NOT NULL + ) + `); + + await tx.delete(accounts); + + const batchSize = 10_000; + for (let start = 0; start < count; start += batchSize) { + const end = Math.min(start + batchSize, count); + const values = []; + + for (let id = start; id < end; id++) { + values.push({ id, balance: initial }); + } + + await tx.insert(accounts).values(values); + } + }); + + console.log( + `[bun] seeded accounts: count=${count} initial=${initial.toString()}`, + ); +} + +async function rpcTransfer(args: Record) { + const fromId = Number(args.from_id ?? args.from); + const toId = Number(args.to_id ?? args.to); + const amount = Number(args.amount); + + if (!Number.isInteger(fromId) || !Number.isInteger(toId) || !Number.isFinite(amount)) { + throw new Error('invalid transfer args'); + } + if (fromId === toId || amount <= 0) return; + + const delta = BigInt(amount); + + await db.transaction(async (tx) => { + // Lock both rows in a deterministic order to avoid deadlocks + const rows = await tx + .select() + .from(accounts) + .where(inArray(accounts.id, [fromId, toId])) + .for('update') + .orderBy(accounts.id); + + if (rows.length !== 2) { + throw new Error('account_missing'); + } + + const [first, second] = rows; + const fromRow = first.id === fromId ? first : second; + const toRow = first.id === fromId ? second : first; + + if (fromRow.balance < delta) { + return; // not enough funds, do nothing (same as other backends) + } + + await tx + .update(accounts) + .set({ balance: fromRow.balance - delta }) + .where(eq(accounts.id, fromId)); + + await tx + .update(accounts) + .set({ balance: toRow.balance + delta }) + .where(eq(accounts.id, toId)); + }); +} + +async function rpcGetAccount(args: Record) { + const id = Number(args.id); + if (!Number.isInteger(id)) throw new Error('invalid id'); + + const rows = await db + .select() + .from(accounts) + .where(eq(accounts.id, id)) + .limit(1); + + if (rows.length === 0) return null; + + const row = rows[0]!; + const balance = row.balance; + if (balance == null) { + throw new Error('account balance is null'); + } + + return { + id: row.id, + balance: balance.toString(), + }; +} + + +async function rpcVerify() { + const rawInitial = process.env.SEED_INITIAL_BALANCE; + if (!rawInitial) { + console.warn('[bun] SEED_INITIAL_BALANCE not set; skipping verification'); + return { skipped: true }; + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + throw new Error(`invalid SEED_INITIAL_BALANCE=${rawInitial}`); + } + + const result = await db.execute( + sql` + SELECT + COUNT(*)::bigint AS count, + COALESCE(SUM(balance), 0)::bigint AS total, + SUM( + CASE WHEN balance != ${initial}::bigint THEN 1 ELSE 0 END + )::bigint AS changed + FROM ${accounts} + `, + ); + + const row = (result as any).rows[0] as { + count: string | number | bigint; + total: string | number | bigint; + changed: string | number | bigint; + }; + + const count = BigInt(row.count); + const total = BigInt(row.total); + const changed = BigInt(row.changed); + const expected = initial * count; + + if (count === 0n) throw new Error('verify failed: accounts=0'); + if (total !== expected) { + throw new Error( + `verify failed: accounts=${count} total=${total} expected=${expected}`, + ); + } + if (changed === 0n) { + throw new Error( + 'verify failed: total preserved but no account balances changed', + ); + } + + return { + accounts: count.toString(), + total: total.toString(), + changed: changed.toString(), + }; +} + +const port = Number(process.env.PORT ?? 4001); + +Bun.serve({ + port, + async fetch(req) { + const url = new URL(req.url); + + if (req.method === 'POST' && url.pathname === '/rpc') { + let body: RpcRequest; + try { + body = (await req.json()) as RpcRequest; + } catch { + return new Response( + JSON.stringify({ ok: false, error: 'invalid json' }), + { status: 400, headers: { 'content-type': 'application/json' } }, + ); + } + + const rsp = await handleRpc(body); + return new Response(JSON.stringify(rsp), { + status: rsp.ok ? 200 : 500, + headers: { 'content-type': 'application/json' }, + }); + } + + if (req.method === 'GET' && url.pathname === '/') { + return new Response('bun drizzle rpc server', { status: 200 }); + } + + return new Response('not found', { status: 404 }); + }, +}); + +console.log(`bun drizzle rpc server listening on http://localhost:${port}`); diff --git a/templates/keynote-2/bun/bun.lock b/templates/keynote-2/bun/bun.lock new file mode 100644 index 00000000000..59217e4e27b --- /dev/null +++ b/templates/keynote-2/bun/bun.lock @@ -0,0 +1,668 @@ +{ + "lockfileVersion": 1, + "configVersion": 0, + "workspaces": { + "": { + "name": "spacetime-simulations", + "dependencies": { + "@supabase/supabase-js": "^2.80.0", + "better-sqlite3": "^12.4.1", + "bun": "^1.3.2", + "express": "^5.1.0", + "hdr-histogram-js": "^3.0.1", + "spacetimedb": "^1.x", + "sql.js": "^1.13.0", + "undici": "^6.19.2", + }, + "devDependencies": { + "@types/better-sqlite3": "^7.6.13", + "@types/express": "^5.0.5", + "@types/pg": "^8.15.6", + "@types/sql.js": "^1.4.9", + "bun-types": "^1.3.2", + "convex": "^1.29.0", + "dotenv": "^17.2.3", + "node-gyp": "^12.1.0", + "pg": "^8.16.3", + "prettier": "^3.3.1", + "tsx": "^4.20.6", + "typescript": "^5.9.3", + }, + }, + }, + "packages": { + "@assemblyscript/loader": ["@assemblyscript/loader@0.19.23", "", {}, "sha512-ulkCYfFbYj01ie1MDOyxv2F6SpRN1TOj7fQxbP07D6HmeR+gr2JLSmINKjga2emB+b1L2KGrFKBTc+e00p54nw=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], + + "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], + + "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + + "@npmcli/agent": ["@npmcli/agent@4.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA=="], + + "@npmcli/fs": ["@npmcli/fs@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q=="], + + "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-licBDIbbLP5L5/S0+bwtJynso94XD3KyqSP48K59Sq7Mude6C7dR5ZujZm4Ut4BwZqUFfNOfYNMWBU5nlL7t1A=="], + + "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-hn8lLzsYyyh6ULo2E8v2SqtrWOkdQKJwapeVy1rDw7juTTeHY3KDudGWf4mVYteC9riZU6HD88Fn3nGwyX0eIg=="], + + "@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-UHxdtbyxdtNJUNcXtIrjx3Lmq8ji3KywlXtIHV/0vn9A8W5mulqOcryqUWMFVH9JTIIzmNn6Q/qVmXHTME63Ww=="], + + "@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5uZzxzvHU/z+3cZwN/A0H8G+enQ+9FkeJVZkE2fwK2XhiJZFUGAuWajCpy7GepvOWlqV7VjPaKi2+Qmr4IX7nQ=="], + + "@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-OD9DYkjes7WXieBn4zQZGXWhRVZhIEWMDGCetZ3H4vxIuweZ++iul/CNX5jdpNXaJ17myb1ROMvmRbrqW44j3w=="], + + "@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EoEuRP9bxAxVKuvi6tZ0ZENjueP4lvjz0mKsMzdG0kwg/2apGKiirH1l0RIcdmvfDGGuDmNiv/XBpkoXq1x8ug=="], + + "@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-m9Ov9YH8KjRLui87eNtQQFKVnjGsNk3xgbrR9c8d2FS3NfZSxmVjSeBvEsDjzNf1TXLDriHb/NYOlpiMf/QzDg=="], + + "@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-3TuOsRVoG8K+soQWRo+Cp5ACpRs6rTFSu5tAqc/6WrqwbNWmqjov/eWJPTgz3gPXnC7uNKVG7RxxAmV8r2EYTQ=="], + + "@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-q8Hto8hcpofPJjvuvjuwyYvhOaAzPw1F5vRUUeOJDmDwZ4lZhANFM0rUwchMzfWUJCD6jg8/EVQ8MiixnZWU0A=="], + + "@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-nZJUa5NprPYQ4Ii4cMwtP9PzlJJTp1XhxJ+A9eSn1Jfr6YygVWyN2KLjenyI93IcuBouBAaepDAVZZjH2lFBhg=="], + + "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-s00T99MjB+xLOWq+t+wVaVBrry+oBOZNiTJijt+bmkp/MJptYS3FGvs7a+nkjLNzoNDoWQcXgKew6AaHES37Bg=="], + + "@supabase/auth-js": ["@supabase/auth-js@2.80.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-q2LyCVJGN4p7d92cOI7scWOoNwxJhZuFRwiimSUGJGI5zX7ubf1WUPznwOmYEn8WVo3Io+MyMinA7era6j5KPw=="], + + "@supabase/functions-js": ["@supabase/functions-js@2.80.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-0S/k8LRtoblrbzy4ir9m4WuvU/XTkb1EwL/33/oJexCUHCXtsqaPJ3eKfr1GWtNqTa1zryv6sXs3Fpv7lKCsMQ=="], + + "@supabase/postgrest-js": ["@supabase/postgrest-js@2.80.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-yKzehXlRbDoXIQefdRQnvaI9BEogoWIp/7+y/m5enZDKW2IP9aAgq5tU72sThcwftDJvknnIpEHAABG3qviEng=="], + + "@supabase/realtime-js": ["@supabase/realtime-js@2.80.0", "", { "dependencies": { "@types/phoenix": "^1.6.6", "@types/ws": "^8.18.1", "tslib": "2.8.1", "ws": "^8.18.2" } }, "sha512-cXK6Gs4UDylN8oz40omi01QK0cSCBVj0efXC1WodpENTuDnrkUs28W8/eslEnAtlawaVtikC1Q92mpz9+o85Mg=="], + + "@supabase/storage-js": ["@supabase/storage-js@2.80.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-Iepod83h2WoMCaLC9pGb3QOT67Kn3RlUdbXpo3uvbDKfPU8EgytS4RVaPmDjhqDjj8AGaiz9mk/ppd2Q2WS+gw=="], + + "@supabase/supabase-js": ["@supabase/supabase-js@2.80.0", "", { "dependencies": { "@supabase/auth-js": "2.80.0", "@supabase/functions-js": "2.80.0", "@supabase/postgrest-js": "2.80.0", "@supabase/realtime-js": "2.80.0", "@supabase/storage-js": "2.80.0" } }, "sha512-n8pkXQxuo5zCWXX5cbSNZj1vuWS8IVNGWTmP1m31Iq1k0e8lPZ07PF08TRV79HHq3mEPP/Ko//BQuflHvY2o8w=="], + + "@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="], + + "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="], + + "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], + + "@types/emscripten": ["@types/emscripten@1.41.5", "", {}, "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q=="], + + "@types/express": ["@types/express@5.0.5", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "^1" } }, "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ=="], + + "@types/express-serve-static-core": ["@types/express-serve-static-core@5.1.0", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA=="], + + "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="], + + "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="], + + "@types/node": ["@types/node@24.10.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A=="], + + "@types/pg": ["@types/pg@8.15.6", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ=="], + + "@types/phoenix": ["@types/phoenix@1.6.6", "", {}, "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A=="], + + "@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="], + + "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], + + "@types/react": ["@types/react@19.2.6", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w=="], + + "@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="], + + "@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="], + + "@types/sql.js": ["@types/sql.js@1.4.9", "", { "dependencies": { "@types/emscripten": "*", "@types/node": "*" } }, "sha512-ep8b36RKHlgWPqjNG9ToUrPiwkhwh0AEzy883mO5Xnd+cL6VBH1EvSjBAAuxLUFF2Vn/moE3Me6v9E1Lo+48GQ=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "abbrev": ["abbrev@4.0.0", "", {}, "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA=="], + + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "better-sqlite3": ["better-sqlite3@12.4.1", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-3yVdyZhklTiNrtg+4WqHpJpFDd+WHTg2oM7UcR80GqL05AOV0xEJzc6qNvFYoEtE+hRp1n9MpN6/+4yhlGkDXQ=="], + + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "bun": ["bun@1.3.2", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.2", "@oven/bun-darwin-x64": "1.3.2", "@oven/bun-darwin-x64-baseline": "1.3.2", "@oven/bun-linux-aarch64": "1.3.2", "@oven/bun-linux-aarch64-musl": "1.3.2", "@oven/bun-linux-x64": "1.3.2", "@oven/bun-linux-x64-baseline": "1.3.2", "@oven/bun-linux-x64-musl": "1.3.2", "@oven/bun-linux-x64-musl-baseline": "1.3.2", "@oven/bun-windows-x64": "1.3.2", "@oven/bun-windows-x64-baseline": "1.3.2" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-x75mPJiEfhO1j4Tfc65+PtW6ZyrAB6yTZInydnjDZXF9u9PRAnr6OK3v0Q9dpDl0dxRHkXlYvJ8tteJxc8t4Sw=="], + + "bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="], + + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + + "cacache": ["cacache@20.0.1", "", { "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^11.0.3", "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "unique-filename": "^4.0.0" } }, "sha512-+7LYcYGBYoNqTp1Rv7Ny1YjUo5E0/ftkQtraH3vkfAGgVHc+ouWdC8okAwQgQR7EVIdW6JTzTmhKFwzb+4okAQ=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="], + + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + + "convex": ["convex@1.29.0", "", { "dependencies": { "esbuild": "0.25.4", "prettier": "^3.0.0" }, "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "optionalPeers": ["@auth0/auth0-react", "@clerk/clerk-react", "react"], "bin": "bin/main.js" }, "sha512-uoIPXRKIp2eLCkkR9WJ2vc9NtgQtx8Pml59WPUahwbrd5EuW2WLI/cf2E7XrUzOSifdQC3kJZepisk4wJNTJaA=="], + + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + + "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": "bin/esbuild" }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], + + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + + "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], + + "express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="], + + "fast-text-encoding": ["fast-text-encoding@1.0.6", "", {}, "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], + + "finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + + "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], + + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + + "glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": "dist/esm/bin.mjs" }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "hdr-histogram-js": ["hdr-histogram-js@3.0.1", "", { "dependencies": { "@assemblyscript/loader": "^0.19.21", "base64-js": "^1.2.0", "pako": "^1.0.3" } }, "sha512-l3GSdZL1Jr1C0kyb461tUjEdrRPZr8Qry7jByltf5JGrA0xvqOSrxRBfcrJqqV/AMEtqqhHhC6w8HW0gn76tRQ=="], + + "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], + + "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + + "isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], + + "jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="], + + "lru-cache": ["lru-cache@11.2.2", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="], + + "make-fetch-happen": ["make-fetch-happen@15.0.2", "", { "dependencies": { "@npmcli/agent": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" } }, "sha512-sI1NY4lWlXBAfjmCtVWIIpBypbBdhHtcjnwnv+gtCnsaOffyFil3aidszGC8hgzJe+fT1qix05sWxmD/Bmf/oQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + + "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], + + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], + + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + + "minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + + "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], + + "minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="], + + "minipass-flush": ["minipass-flush@1.0.5", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw=="], + + "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], + + "minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "node-abi": ["node-abi@3.80.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-LyPuZJcI9HVwzXK1GPxWNzrr+vr8Hp/3UqlmWxxh8p54U1ZbclOqbSog9lWHaCX+dBaiGi6n/hIX+mKu74GmPA=="], + + "node-gyp": ["node-gyp@12.1.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^15.0.0", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.2", "tinyglobby": "^0.2.12", "which": "^6.0.0" }, "bin": "bin/node-gyp.js" }, "sha512-W+RYA8jBnhSr2vrTtlPYPc1K+CSjGpVDRZxcqJcERZ8ND3A1ThWPHRwctTx3qC3oW99jt726jhdz3Y6ky87J4g=="], + + "nopt": ["nopt@9.0.0", "", { "dependencies": { "abbrev": "^4.0.0" }, "bin": "bin/nopt.js" }, "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw=="], + + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="], + + "path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], + + "pg": ["pg@8.16.3", "", { "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", "pg-protocol": "^1.10.3", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.2.7" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw=="], + + "pg-cloudflare": ["pg-cloudflare@1.2.7", "", {}, "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg=="], + + "pg-connection-string": ["pg-connection-string@2.9.1", "", {}, "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="], + + "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], + + "pg-pool": ["pg-pool@3.10.1", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg=="], + + "pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="], + + "pg-types": ["pg-types@2.2.0", "", { "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" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + + "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + + "postgres-bytea": ["postgres-bytea@1.0.0", "", {}, "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="], + + "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": "bin.js" }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + + "prettier": ["prettier@3.6.2", "", { "bin": "bin/prettier.cjs" }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], + + "proc-log": ["proc-log@6.0.0", "", {}, "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA=="], + + "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], + + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + + "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], + + "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], + + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], + + "raw-body": ["raw-body@3.0.1", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.7.0", "unpipe": "1.0.0" } }, "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA=="], + + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": "cli.js" }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + + "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="], + + "serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="], + + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + + "spacetimedb": ["spacetimedb@1.8.0", "", { "dependencies": { "base64-js": "^1.5.1", "fast-text-encoding": "^1.0.0", "prettier": "^3.3.3" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0", "undici": "^6.19.2" }, "optionalPeers": ["react"] }, "sha512-f0dw91qR+U3PWemUgV4Ar1x4g5p6xZqc3XGASIyueVvcgKXhtzVaxOHFrkZWis4SQ34UF//CIFMHAG8CiTSNqA=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "sql.js": ["sql.js@1.13.0", "", {}, "sha512-RJbVP1HRDlUUXahJ7VMTcu9Rm1Nzw+EBpoPr94vnbD4LwR715F3CcxE2G2k45PewcaZ57pjetYa+LoSJLAASgA=="], + + "ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], + + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + + "tar": ["tar@7.5.2", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg=="], + + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tsx": ["tsx@4.20.6", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": "dist/cli.mjs" }, "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg=="], + + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + + "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici": ["undici@6.22.0", "", {}, "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "unique-filename": ["unique-filename@4.0.0", "", { "dependencies": { "unique-slug": "^5.0.0" } }, "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ=="], + + "unique-slug": ["unique-slug@5.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg=="], + + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + + "which": ["which@6.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg=="], + + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + + "@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="], + + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], + + "make-fetch-happen/proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="], + + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "raw-body/iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], + + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "tsx/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": "bin/esbuild" }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "minipass-sized/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + } +} diff --git a/templates/keynote-2/convex-app/.gitignore b/templates/keynote-2/convex-app/.gitignore new file mode 100644 index 00000000000..8c5fbb9ced5 --- /dev/null +++ b/templates/keynote-2/convex-app/.gitignore @@ -0,0 +1,2 @@ + +.env.local diff --git a/templates/keynote-2/convex-app/convex/README.md b/templates/keynote-2/convex-app/convex/README.md new file mode 100644 index 00000000000..7fda0c3d680 --- /dev/null +++ b/templates/keynote-2/convex-app/convex/README.md @@ -0,0 +1,90 @@ +# Welcome to your Convex functions directory! + +Write your Convex functions here. +See https://docs.convex.dev/functions for more. + +A query function that takes two arguments looks like: + +```ts +// convex/myFunctions.ts +import { query } from "./_generated/server"; +import { v } from "convex/values"; + +export const myQueryFunction = query({ + // Validators for arguments. + args: { + first: v.number(), + second: v.string(), + }, + + // Function implementation. + handler: async (ctx, args) => { + // Read the database as many times as you need here. + // See https://docs.convex.dev/database/reading-data. + const documents = await ctx.db.query("tablename").collect(); + + // Arguments passed from the client are properties of the args object. + console.log(args.first, args.second); + + // Write arbitrary JavaScript here: filter, aggregate, build derived data, + // remove non-public properties, or create new objects. + return documents; + }, +}); +``` + +Using this query function in a React component looks like: + +```ts +const data = useQuery(api.myFunctions.myQueryFunction, { + first: 10, + second: "hello", +}); +``` + +A mutation function looks like: + +```ts +// convex/myFunctions.ts +import { mutation } from "./_generated/server"; +import { v } from "convex/values"; + +export const myMutationFunction = mutation({ + // Validators for arguments. + args: { + first: v.string(), + second: v.string(), + }, + + // Function implementation. + handler: async (ctx, args) => { + // Insert or modify documents in the database here. + // Mutations can also read from the database like queries. + // See https://docs.convex.dev/database/writing-data. + const message = { body: args.first, author: args.second }; + const id = await ctx.db.insert("messages", message); + + // Optionally, return a value from your mutation. + return await ctx.db.get(id); + }, +}); +``` + +Using this mutation function in a React component looks like: + +```ts +const mutation = useMutation(api.myFunctions.myMutationFunction); +function handleButtonPress() { + // fire and forget, the most common way to use mutations + mutation({ first: "Hello!", second: "me" }); + // OR + // use the result once the mutation has completed + mutation({ first: "Hello!", second: "me" }).then((result) => + console.log(result), + ); +} +``` + +Use the Convex CLI to push your functions to a deployment. See everything +the Convex CLI can do by running `npx convex -h` in your project root +directory. To learn more, launch the docs with `npx convex docs`. diff --git a/templates/keynote-2/convex-app/convex/accounts.ts b/templates/keynote-2/convex-app/convex/accounts.ts new file mode 100644 index 00000000000..095ada1e5d2 --- /dev/null +++ b/templates/keynote-2/convex-app/convex/accounts.ts @@ -0,0 +1,33 @@ +import { query } from "./_generated/server"; +import { v } from 'convex/values'; + +export const get_account = query(async ({ db }, { id }: { id: number }) => { + const row = await db + .query("accounts") + .withIndex("by_account_id", (q) => q.eq("id", id)) + .unique(); + + if (!row) return null; + return { id: row.id, balance: row.balance }; +}); + +export const get_stats = query({ + args: { + initialBalance: v.number(), + }, + handler: async (ctx, { initialBalance }) => { + const rows = await ctx.db.query("accounts").collect(); + + let count = 0; + let total = 0; + let changed = 0; + + for (const row of rows) { + count++; + total += row.balance; + if (row.balance !== initialBalance) changed++; + } + + return { count, total, changed }; + }, +}); \ No newline at end of file diff --git a/templates/keynote-2/convex-app/convex/balances.ts b/templates/keynote-2/convex-app/convex/balances.ts new file mode 100644 index 00000000000..983ecc7a49c --- /dev/null +++ b/templates/keynote-2/convex-app/convex/balances.ts @@ -0,0 +1,12 @@ +// convex/balances.ts +import { ShardedCounter } from '@convex-dev/sharded-counter'; +import { components } from './_generated/api'; + +export const accountBalances = new ShardedCounter( + components.shardedCounter, + { + defaultShards: 10000, + }, +); + +export const accountKey = (id: number) => `account:${id}`; diff --git a/templates/keynote-2/convex-app/convex/convex.config.ts b/templates/keynote-2/convex-app/convex/convex.config.ts new file mode 100644 index 00000000000..929455ecb1d --- /dev/null +++ b/templates/keynote-2/convex-app/convex/convex.config.ts @@ -0,0 +1,8 @@ +import { defineApp } from "convex/server"; +import shardedCounter from "@convex-dev/sharded-counter/convex.config.js"; + +const app = defineApp(); + +app.use(shardedCounter); + +export default app; \ No newline at end of file diff --git a/templates/keynote-2/convex-app/convex/http.ts b/templates/keynote-2/convex-app/convex/http.ts new file mode 100644 index 00000000000..3bd5b33a912 --- /dev/null +++ b/templates/keynote-2/convex-app/convex/http.ts @@ -0,0 +1,28 @@ +import { httpRouter } from "convex/server"; +import { httpAction } from "./_generated/server"; +import { api } from "./_generated/api"; +import { seed_range } from './seed'; + +const http = httpRouter(); + +http.route({ + path: "/seed", + method: "POST", + handler: httpAction(async (ctx, req) => { + const { start, count, initial } = await req.json(); + await ctx.runMutation(api.seed.seed_range, { start: Number(start), count: Number(count), initial: Number(initial) }); + return new Response("ok"); + }), +}); + +http.route({ + path: "/transfer", + method: "POST", + handler: httpAction(async (ctx, req) => { + const { from, to, amount } = await req.json(); + await ctx.runMutation(api.transfer.transfer, { from_id: Number(from), to_id: Number(to), amount: Number(amount) }); + return new Response("ok"); + }), +}); + +export default http; diff --git a/templates/keynote-2/convex-app/convex/schema.ts b/templates/keynote-2/convex-app/convex/schema.ts new file mode 100644 index 00000000000..4362c414c2e --- /dev/null +++ b/templates/keynote-2/convex-app/convex/schema.ts @@ -0,0 +1,10 @@ +import { defineSchema, defineTable } from "convex/server"; +import { v } from "convex/values"; + +export default defineSchema({ + accounts: defineTable({ + id: v.number(), + balance: v.number(), + }) + .index("by_account_id", ["id"]), +}); diff --git a/templates/keynote-2/convex-app/convex/seed.ts b/templates/keynote-2/convex-app/convex/seed.ts new file mode 100644 index 00000000000..25b3cad0d9f --- /dev/null +++ b/templates/keynote-2/convex-app/convex/seed.ts @@ -0,0 +1,28 @@ +import { v } from "convex/values"; +import { mutation } from "./_generated/server"; + +export const clear_accounts = mutation({ + args: {}, + handler: async ({ db }) => { + const BATCH = 4_000; //limit is 4k + const docs = await db.query("accounts").take(BATCH); + for (const doc of docs) { + await db.delete(doc._id); + } + return docs.length; + }, +}); + +export const seed_range = mutation({ + args: { + start: v.number(), + count: v.number(), + initial: v.number(), + }, + handler: async ({ db }, { start, count, initial }) => { + const end = start + count; + for (let i = start; i < end; i++) { + await db.insert("accounts", { id: i, balance: initial }); + } + }, +}); diff --git a/templates/keynote-2/convex-app/convex/transfer.ts b/templates/keynote-2/convex-app/convex/transfer.ts new file mode 100644 index 00000000000..e929ca7c665 --- /dev/null +++ b/templates/keynote-2/convex-app/convex/transfer.ts @@ -0,0 +1,72 @@ +import { mutation } from "./_generated/server"; +import { v } from "convex/values"; +import { accountBalances, accountKey } from './balances'; + +export const transfer = mutation({ + args: { + amount: v.number(), + from_id: v.number(), + to_id: v.number(), + }, + handler: async (ctx, { amount, from_id, to_id }) => { + if (from_id === to_id || amount <= 0) return; + + const from = await ctx.db + .query("accounts") + .withIndex("by_account_id", q => q.eq("id", from_id)) + .first(); + + const to = await ctx.db + .query("accounts") + .withIndex("by_account_id", q => q.eq("id", to_id)) + .first(); + + if (!from || !to) return; + + const fromBalance = from.balance ?? 0; + const toBalance = to.balance ?? 0; + + // prevent negative balances + if (fromBalance < amount) { + return; + } + + await ctx.db.patch(from._id, { balance: fromBalance - amount }); + await ctx.db.patch(to._id, { balance: toBalance + amount }); + }, +}); + + +export const transfer_sharded = mutation({ + args: { + amount: v.number(), + from_id: v.number(), + to_id: v.number(), + }, + handler: async (ctx, { amount, from_id, to_id }) => { + if (from_id === to_id || amount <= 0) return; + + const from = await ctx.db + .query("accounts") + .withIndex("by_account_id", q => q.eq("id", from_id)) + .first(); + + const to = await ctx.db + .query("accounts") + .withIndex("by_account_id", q => q.eq("id", to_id)) + .first(); + + if (!from || !to) return; + + const fromCounter = accountBalances.for(accountKey(from_id)); + const toCounter = accountBalances.for(accountKey(to_id)); + + const fromBalance = await fromCounter.count(ctx); + if (fromBalance < amount) return; + + await Promise.all([ + fromCounter.subtract(ctx, amount), + toCounter.add(ctx, amount), + ]); + }, +}); \ No newline at end of file diff --git a/templates/keynote-2/convex-app/convex/tsconfig.json b/templates/keynote-2/convex-app/convex/tsconfig.json new file mode 100644 index 00000000000..73741270b01 --- /dev/null +++ b/templates/keynote-2/convex-app/convex/tsconfig.json @@ -0,0 +1,25 @@ +{ + /* This TypeScript project config describes the environment that + * Convex functions run in and is used to typecheck them. + * You can modify it, but some settings are required to use Convex. + */ + "compilerOptions": { + /* These settings are not required by Convex and can be modified. */ + "allowJs": true, + "strict": true, + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + + /* These compiler options are required by Convex */ + "target": "ESNext", + "lib": ["ES2021", "dom"], + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "isolatedModules": true, + "noEmit": true + }, + "include": ["./**/*"], + "exclude": ["./_generated"] +} diff --git a/templates/keynote-2/convex-app/package-lock.json b/templates/keynote-2/convex-app/package-lock.json new file mode 100644 index 00000000000..7393eaef9b3 --- /dev/null +++ b/templates/keynote-2/convex-app/package-lock.json @@ -0,0 +1,546 @@ +{ + "name": "convex-app", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "convex-app", + "dependencies": { + "@convex-dev/sharded-counter": "^0.2.0", + "convex": "^1.29.3", + "convex-helpers": "^0.1.0" + } + }, + "node_modules/@convex-dev/sharded-counter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@convex-dev/sharded-counter/-/sharded-counter-0.2.0.tgz", + "integrity": "sha512-Qv1TvFXyQ60rADS1fWH01mnMDNSPfmKKowXva6WxIXGIC2ta5xvohe9TfUhXp4AT1vtOXoFgse3Z9mJB9fJlSQ==", + "license": "Apache-2.0", + "peerDependencies": { + "convex": "^1.24.8" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/convex": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/convex/-/convex-1.29.3.tgz", + "integrity": "sha512-tg5TXzMjpNk9m50YRtdp6US+t7ckxE4E+7DNKUCjJ2MupQs2RBSPF/z5SNN4GUmQLSfg0eMILDySzdAvjTrhnw==", + "license": "Apache-2.0", + "dependencies": { + "esbuild": "0.25.4", + "prettier": "^3.0.0" + }, + "bin": { + "convex": "bin/main.js" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=7.0.0" + }, + "peerDependencies": { + "@auth0/auth0-react": "^2.0.1", + "@clerk/clerk-react": "^4.12.8 || ^5.0.0", + "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@auth0/auth0-react": { + "optional": true + }, + "@clerk/clerk-react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/convex-helpers": { + "version": "0.1.104", + "resolved": "https://registry.npmjs.org/convex-helpers/-/convex-helpers-0.1.104.tgz", + "integrity": "sha512-7CYvx7T3K6n+McDTK4ZQaQNNGBzq5aWezpjzsKbOxPXx7oNcTP9wrpef3JxeXWFzkByJv5hRCjseh9B7eNJ7Ig==", + "license": "Apache-2.0", + "bin": { + "convex-helpers": "bin.cjs" + }, + "peerDependencies": { + "@standard-schema/spec": "^1.0.0", + "convex": "^1.24.0", + "hono": "^4.0.5", + "react": "^17.0.2 || ^18.0.0 || ^19.0.0", + "typescript": "^5.5", + "zod": "^3.22.4 || ^4.0.15" + }, + "peerDependenciesMeta": { + "@standard-schema/spec": { + "optional": true + }, + "hono": { + "optional": true + }, + "react": { + "optional": true + }, + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/esbuild": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + } + } +} diff --git a/templates/keynote-2/convex-app/package.json b/templates/keynote-2/convex-app/package.json new file mode 100644 index 00000000000..eebdd65afc6 --- /dev/null +++ b/templates/keynote-2/convex-app/package.json @@ -0,0 +1,13 @@ +{ + "name": "convex-app", + "private": true, + "type": "module", + "scripts": { + "dev": "convex dev" + }, + "dependencies": { + "@convex-dev/sharded-counter": "^0.2.0", + "convex": "^1.29.3", + "convex-helpers": "^0.1.0" + } +} diff --git a/templates/keynote-2/convex-app/pnpm-lock.yaml b/templates/keynote-2/convex-app/pnpm-lock.yaml new file mode 100644 index 00000000000..4e46ef3b110 --- /dev/null +++ b/templates/keynote-2/convex-app/pnpm-lock.yaml @@ -0,0 +1,344 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@convex-dev/sharded-counter': + specifier: ^0.2.0 + version: 0.2.0(convex@1.29.3) + convex: + specifier: ^1.29.3 + version: 1.29.3 + convex-helpers: + specifier: ^0.1.0 + version: 0.1.105(convex@1.29.3) + +packages: + + '@convex-dev/sharded-counter@0.2.0': + resolution: {integrity: sha512-Qv1TvFXyQ60rADS1fWH01mnMDNSPfmKKowXva6WxIXGIC2ta5xvohe9TfUhXp4AT1vtOXoFgse3Z9mJB9fJlSQ==} + peerDependencies: + convex: ^1.24.8 + + '@esbuild/aix-ppc64@0.25.4': + resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.4': + resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.4': + resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.4': + resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.4': + resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.4': + resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.4': + resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.4': + resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.4': + resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.4': + resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.4': + resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.4': + resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.4': + resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.4': + resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.4': + resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.4': + resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.4': + resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.4': + resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.4': + resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.4': + resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.4': + resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.4': + resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.4': + resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.4': + resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.4': + resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + convex-helpers@0.1.105: + resolution: {integrity: sha512-B6EUQsBpLFcXpa1g/9Zsc7EaFHdaeMlPLCa/snSD+4hRBFMWKJjT589dZ/0Z40taBOQWC2tb81ciTNr4ctJb2w==} + hasBin: true + peerDependencies: + '@standard-schema/spec': ^1.0.0 + convex: ^1.24.0 + hono: ^4.0.5 + react: ^17.0.2 || ^18.0.0 || ^19.0.0 + typescript: ^5.5 + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + '@standard-schema/spec': + optional: true + hono: + optional: true + react: + optional: true + typescript: + optional: true + zod: + optional: true + + convex@1.29.3: + resolution: {integrity: sha512-tg5TXzMjpNk9m50YRtdp6US+t7ckxE4E+7DNKUCjJ2MupQs2RBSPF/z5SNN4GUmQLSfg0eMILDySzdAvjTrhnw==} + engines: {node: '>=18.0.0', npm: '>=7.0.0'} + hasBin: true + peerDependencies: + '@auth0/auth0-react': ^2.0.1 + '@clerk/clerk-react': ^4.12.8 || ^5.0.0 + react: ^18.0.0 || ^19.0.0-0 || ^19.0.0 + peerDependenciesMeta: + '@auth0/auth0-react': + optional: true + '@clerk/clerk-react': + optional: true + react: + optional: true + + esbuild@0.25.4: + resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==} + engines: {node: '>=18'} + hasBin: true + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + +snapshots: + + '@convex-dev/sharded-counter@0.2.0(convex@1.29.3)': + dependencies: + convex: 1.29.3 + + '@esbuild/aix-ppc64@0.25.4': + optional: true + + '@esbuild/android-arm64@0.25.4': + optional: true + + '@esbuild/android-arm@0.25.4': + optional: true + + '@esbuild/android-x64@0.25.4': + optional: true + + '@esbuild/darwin-arm64@0.25.4': + optional: true + + '@esbuild/darwin-x64@0.25.4': + optional: true + + '@esbuild/freebsd-arm64@0.25.4': + optional: true + + '@esbuild/freebsd-x64@0.25.4': + optional: true + + '@esbuild/linux-arm64@0.25.4': + optional: true + + '@esbuild/linux-arm@0.25.4': + optional: true + + '@esbuild/linux-ia32@0.25.4': + optional: true + + '@esbuild/linux-loong64@0.25.4': + optional: true + + '@esbuild/linux-mips64el@0.25.4': + optional: true + + '@esbuild/linux-ppc64@0.25.4': + optional: true + + '@esbuild/linux-riscv64@0.25.4': + optional: true + + '@esbuild/linux-s390x@0.25.4': + optional: true + + '@esbuild/linux-x64@0.25.4': + optional: true + + '@esbuild/netbsd-arm64@0.25.4': + optional: true + + '@esbuild/netbsd-x64@0.25.4': + optional: true + + '@esbuild/openbsd-arm64@0.25.4': + optional: true + + '@esbuild/openbsd-x64@0.25.4': + optional: true + + '@esbuild/sunos-x64@0.25.4': + optional: true + + '@esbuild/win32-arm64@0.25.4': + optional: true + + '@esbuild/win32-ia32@0.25.4': + optional: true + + '@esbuild/win32-x64@0.25.4': + optional: true + + convex-helpers@0.1.105(convex@1.29.3): + dependencies: + convex: 1.29.3 + + convex@1.29.3: + dependencies: + esbuild: 0.25.4 + prettier: 3.6.2 + + esbuild@0.25.4: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.4 + '@esbuild/android-arm': 0.25.4 + '@esbuild/android-arm64': 0.25.4 + '@esbuild/android-x64': 0.25.4 + '@esbuild/darwin-arm64': 0.25.4 + '@esbuild/darwin-x64': 0.25.4 + '@esbuild/freebsd-arm64': 0.25.4 + '@esbuild/freebsd-x64': 0.25.4 + '@esbuild/linux-arm': 0.25.4 + '@esbuild/linux-arm64': 0.25.4 + '@esbuild/linux-ia32': 0.25.4 + '@esbuild/linux-loong64': 0.25.4 + '@esbuild/linux-mips64el': 0.25.4 + '@esbuild/linux-ppc64': 0.25.4 + '@esbuild/linux-riscv64': 0.25.4 + '@esbuild/linux-s390x': 0.25.4 + '@esbuild/linux-x64': 0.25.4 + '@esbuild/netbsd-arm64': 0.25.4 + '@esbuild/netbsd-x64': 0.25.4 + '@esbuild/openbsd-arm64': 0.25.4 + '@esbuild/openbsd-x64': 0.25.4 + '@esbuild/sunos-x64': 0.25.4 + '@esbuild/win32-arm64': 0.25.4 + '@esbuild/win32-ia32': 0.25.4 + '@esbuild/win32-x64': 0.25.4 + + prettier@3.6.2: {} diff --git a/templates/keynote-2/crdb-nodes/docker-compose-crdb-node-1.yml b/templates/keynote-2/crdb-nodes/docker-compose-crdb-node-1.yml new file mode 100644 index 00000000000..5ffc79b8120 --- /dev/null +++ b/templates/keynote-2/crdb-nodes/docker-compose-crdb-node-1.yml @@ -0,0 +1,27 @@ +services: + crdb: + image: cockroachdb/cockroach:latest + command: + - start + - --insecure + - --max-sql-memory=.25 + - --cache=.3 + - --listen-addr=:26257 + - --http-addr=:8080 + - --join=10.128.0.14:26257,10.128.0.15:26257 + volumes: + - ./crdb_data:/cockroach/cockroach-data + network_mode: host + + crdb-init: + image: cockroachdb/cockroach:latest + depends_on: + crdb: + condition: service_started + network_mode: host + restart: "no" + entrypoint: ["/bin/sh", "-c"] + command: | + /cockroach/cockroach init --insecure --host=localhost:26257 || true + /cockroach/cockroach sql --insecure --host=localhost:26257 \ + -e 'CREATE DATABASE IF NOT EXISTS bench;' diff --git a/templates/keynote-2/crdb-nodes/docker-compose-crdb-node-2.yml b/templates/keynote-2/crdb-nodes/docker-compose-crdb-node-2.yml new file mode 100644 index 00000000000..e552e6366c6 --- /dev/null +++ b/templates/keynote-2/crdb-nodes/docker-compose-crdb-node-2.yml @@ -0,0 +1,19 @@ +services: + crdb-node: + image: cockroachdb/cockroach:latest + command: + - start + - --insecure + - --max-sql-memory=.25 + - --cache=.3 + - --listen-addr=:26257 + - --http-addr=:8080 + - --join=10.128.0.14:26257,10.128.0.15:26257 + healthcheck: + test: ["CMD", "/cockroach/cockroach", "node", "status", "--insecure"] + interval: 2s + timeout: 2s + retries: 15 + volumes: + - ./crdb_data:/cockroach/cockroach-data + network_mode: host diff --git a/templates/keynote-2/crdb-nodes/docker-compose-crdb-node-3.yml b/templates/keynote-2/crdb-nodes/docker-compose-crdb-node-3.yml new file mode 100644 index 00000000000..d56bb1cbf1f --- /dev/null +++ b/templates/keynote-2/crdb-nodes/docker-compose-crdb-node-3.yml @@ -0,0 +1,19 @@ +services: + crdb-node: + image: cockroachdb/cockroach:latest + command: + - start + - --insecure + - --max-sql-memory=.25 + - --cache=.3 + - --listen-addr=:26257 + - --http-addr=:8080 + - --join=10.128.0.14:26257,10.128.0.15:26257,10.128.0.16:26257 + healthcheck: + test: ["CMD", "/cockroach/cockroach", "node", "status", "--insecure"] + interval: 2s + timeout: 2s + retries: 15 + volumes: + - ./crdb_data:/cockroach/cockroach-data + network_mode: host diff --git a/templates/keynote-2/docker-compose-crdb-loadbalancer.yml b/templates/keynote-2/docker-compose-crdb-loadbalancer.yml new file mode 100644 index 00000000000..26c5cfbdcbb --- /dev/null +++ b/templates/keynote-2/docker-compose-crdb-loadbalancer.yml @@ -0,0 +1,7 @@ +services: + crdb-rpc-lb: + image: nginx:stable + ports: + - "4102:4102" + volumes: + - ./nginx-crdb.conf:/etc/nginx/nginx.conf:ro diff --git a/templates/keynote-2/docker-compose-crdb-rpc-server.yml b/templates/keynote-2/docker-compose-crdb-rpc-server.yml new file mode 100644 index 00000000000..6b78dd7ae83 --- /dev/null +++ b/templates/keynote-2/docker-compose-crdb-rpc-server.yml @@ -0,0 +1,14 @@ +services: + crdb-rpc: + build: + context: . + dockerfile: Dockerfile.rpc + command: ["pnpm", "tsx", "src/rpc-servers/cockroach-rpc-server.ts"] + ports: + - "5001:5001" + environment: + CRDB_URL: ${CRDB_URL} # Point to remote CRDB cluster + CRDB_RPC_PORT: "5001" + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + diff --git a/templates/keynote-2/docker-compose-linux-raid-crdb.yml b/templates/keynote-2/docker-compose-linux-raid-crdb.yml new file mode 100644 index 00000000000..3e0053aed6e --- /dev/null +++ b/templates/keynote-2/docker-compose-linux-raid-crdb.yml @@ -0,0 +1,210 @@ +services: + pg: + image: postgres:16 + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + ports: + - "5432:5432" + command: > # faster setup + -c fsync=on + -c synchronous_commit=off + -c shared_buffers=8GB + -c work_mem=64MB + -c max_connections=10000 + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 2s + timeout: 2s + retries: 15 + volumes: + - /mnt/local-ssd/pg_data:/var/lib/postgresql/data + network_mode: host + + pg-rpc: + build: + context: . + dockerfile: Dockerfile.rpc + command: ["pnpm", "tsx", "src/rpc-servers/postgres-rpc-server.ts"] + ports: + - "4101:4101" + environment: + PG_URL: ${PG_URL} + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + depends_on: + pg: + condition: service_healthy + network_mode: host + + # crdb-rpc-1: + # build: + # context: . + # dockerfile: Dockerfile.rpc + # command: ["pnpm", "tsx", "src/rpc-servers/cockroach-rpc-server.ts"] + # environment: + # CRDB_URL: ${CRDB_URL} + # CRDB_RPC_PORT: "5001" + # SEED_ACCOUNTS: ${SEED_ACCOUNTS} + # SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + # network_mode: host + # + # crdb-rpc-2: + # build: + # context: . + # dockerfile: Dockerfile.rpc + # command: ["pnpm", "tsx", "src/rpc-servers/cockroach-rpc-server.ts"] + # environment: + # CRDB_URL: ${CRDB_URL} + # CRDB_RPC_PORT: "5002" + # SEED_ACCOUNTS: ${SEED_ACCOUNTS} + # SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + # network_mode: host + # + # crdb-rpc-3: + # build: + # context: . + # dockerfile: Dockerfile.rpc + # command: ["pnpm", "tsx", "src/rpc-servers/cockroach-rpc-server.ts"] + # environment: + # CRDB_URL: ${CRDB_URL} + # CRDB_RPC_PORT: "5003" + # SEED_ACCOUNTS: ${SEED_ACCOUNTS} + # SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + # network_mode: host + # + # crdb-rpc-lb: + # image: nginx:stable + # volumes: + # - ./nginx-crdb.conf:/etc/nginx/nginx.conf:ro + # depends_on: + # crdb-rpc-1: + # condition: service_started + # crdb-rpc-2: + # condition: service_started + # crdb-rpc-3: + # condition: service_started + # network_mode: host + + sqlite-rpc: + build: + context: . + dockerfile: Dockerfile.rpc + command: ["pnpm", "tsx", "src/rpc-servers/sqlite-rpc-server.ts"] + ports: + - "4103:4103" + environment: + SQLITE_FILE: /data/accounts.sqlite + SQLITE_MODE: ${SQLITE_MODE} + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + volumes: + - /mnt/local-ssd/sqlite_data:/data + network_mode: host + + bun-rpc: + build: + context: . + dockerfile: Dockerfile.bun + ports: + - "4001:4001" + environment: + BUN_PG_URL: ${BUN_PG_URL} + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + depends_on: + pg: + condition: service_healthy + network_mode: host + + supabase-rpc: + build: + context: . + dockerfile: Dockerfile.rpc + command: [ "pnpm", "tsx", "src/rpc-servers/supabase-rpc-server.ts" ] + ports: + - "4106:4106" + environment: + SUPABASE_DB_URL: ${SUPABASE_DB_URL} + PG_RPC_PORT: "4106" + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + network_mode: host + + planetscale-pg-rpc: + build: + context: . + dockerfile: Dockerfile.rpc + command: [ "pnpm", "tsx", "src/rpc-servers/postgres-rpc-server.ts" ] + ports: + - "4104:4104" + environment: + PG_URL: ${PLANETSCALE_PG_URL} + PG_RPC_PORT: "4104" + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + network_mode: host + + spacetime: + image: clockworklabs/spacetime:latest + command: start + ports: + - "3000:3000" + volumes: + - /mnt/local-ssd/spacetime_data:/data + network_mode: host + + bench: + build: + context: . + dockerfile: Dockerfile.bench + depends_on: + spacetime: + condition: service_started + pg-rpc: + condition: service_started + sqlite-rpc: + condition: service_started + environment: + USE_DOCKER: ${USE_DOCKER} + PG_URL: ${PG_URL} + CRDB_URL: ${CRDB_URL} + CONVEX_URL: ${CONVEX_URL} + STDB_URL: ${STDB_URL} + STDB_MODULE: ${STDB_MODULE} + STDB_MODULE_PATH: ${STDB_MODULE_PATH} + STDB_METRICS_URL: ${STDB_METRICS_URL} + BUN_URL: ${BUN_URL} + SQLITE_FILE: /data/accounts.sqlite + SQLITE_MODE: ${SQLITE_MODE} + SUPABASE_URL: ${SUPABASE_URL} + SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY} + SUPABASE_DB_URL: ${SUPABASE_DB_URL} + PG_RPC_URL: ${PG_RPC_URL} + CRDB_RPC_URL: ${CRDB_RPC_URL} + SQLITE_RPC_URL: ${SQLITE_RPC_URL} + SUPABASE_RPC_URL: ${SUPABASE_RPC_URL} + PLANETSCALE_RPC_URL: ${PLANETSCALE_RPC_URL} + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + USE_SPACETIME_METRICS_ENDPOINT: ${USE_SPACETIME_METRICS_ENDPOINT} + CONVEX_USE_SHARDED_COUNTER: ${CONVEX_USE_SHARDED_COUNTER} + VERIFY: ${VERIFY} + volumes: + - /mnt/local-ssd/sqlite_data:/data + - ./runs:/app/runs + command: ["--seconds", "5", "--concurrency", "50", "--alpha", "1", "--connectors", "sqlite"] + network_mode: host + + sqlite-seed: + build: + context: . + dockerfile: Dockerfile.sqlite-seed + environment: + SQLITE_FILE: /data/accounts.sqlite + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + volumes: + - /mnt/local-ssd/sqlite_data:/data + network_mode: host diff --git a/templates/keynote-2/docker-compose-linux-raid.yml b/templates/keynote-2/docker-compose-linux-raid.yml new file mode 100644 index 00000000000..f67ad919078 --- /dev/null +++ b/templates/keynote-2/docker-compose-linux-raid.yml @@ -0,0 +1,331 @@ +services: + pg: + image: postgres:16 + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + ports: + - "5432:5432" + # command: > + # -c fsync=on + # -c synchronous_commit=on + # -c shared_buffers=2GB + # -c work_mem=64MB + # -c max_connections=1000 + command: > # faster setup + -c fsync=on + -c synchronous_commit=off + -c shared_buffers=8GB + -c work_mem=64MB + -c max_connections=10000 + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 2s + timeout: 2s + retries: 15 + volumes: + - /mnt/local-ssd/pg_data:/var/lib/postgresql/data + network_mode: host + + crdb: + image: cockroachdb/cockroach:latest + command: start-single-node --insecure --max-sql-memory=.25 --cache=.5 + ports: + - "26257:26257" + - "8082:8080" + healthcheck: + test: ["CMD", "/cockroach/cockroach", "node", "status", "--insecure"] + interval: 2s + timeout: 2s + retries: 15 + volumes: + - /mnt/local-ssd/crdb_data:/cockroach/cockroach-data + network_mode: host + +# crdb: +# image: cockroachdb/cockroach:latest +# command: > +# start --insecure +# --max-sql-memory=.25 --cache=.3 +# --listen-addr=0.0.0.0:36257 +# --http-addr=0.0.0.0:18080 +# --advertise-addr=127.0.0.1:36257 +# --join=127.0.0.1:36257,127.0.0.1:36258,127.0.0.1:36259 +# healthcheck: +# test: ["CMD-SHELL", "/cockroach/cockroach sql --insecure --host=127.0.0.1:36257 -e 'SELECT 1' >/dev/null 2>&1"] +# interval: 2s +# timeout: 2s +# retries: 60 +# volumes: +# - /mnt/local-ssd/crdb_data:/cockroach/cockroach-data +# network_mode: host +# +# crdb-node2: +# image: cockroachdb/cockroach:latest +# command: > +# start --insecure +# --max-sql-memory=.25 --cache=.3 +# --listen-addr=0.0.0.0:36258 +# --http-addr=0.0.0.0:18081 +# --advertise-addr=127.0.0.1:36258 +# --join=127.0.0.1:36257,127.0.0.1:36258,127.0.0.1:36259 +# volumes: +# - /mnt/local-ssd/crdb_data2:/cockroach/cockroach-data +# network_mode: host +# +# crdb-node3: +# image: cockroachdb/cockroach:latest +# command: > +# start --insecure +# --max-sql-memory=.25 --cache=.3 +# --listen-addr=0.0.0.0:36259 +# --http-addr=0.0.0.0:18082 +# --advertise-addr=127.0.0.1:36259 +# --join=127.0.0.1:36257,127.0.0.1:36258,127.0.0.1:36259 +# volumes: +# - /mnt/local-ssd/crdb_data3:/cockroach/cockroach-data +# network_mode: host +# +# crdb-init: +# image: cockroachdb/cockroach:latest +# depends_on: +# crdb: +# condition: service_healthy +# crdb-node2: +# condition: service_started +# crdb-node3: +# condition: service_started +# network_mode: host +# restart: "no" +# entrypoint: [ "sh", "-c" ] +# command: > +# cockroach init --insecure --host=127.0.0.1:36257 || true && +# cockroach sql --insecure --host=127.0.0.1:36257 -e 'CREATE DATABASE IF NOT EXISTS bench;' + + pg-rpc: + build: + context: . + dockerfile: Dockerfile.rpc + command: ["pnpm", "tsx", "src/rpc-servers/postgres-rpc-server.ts"] + ports: + - "4101:4101" + environment: + PG_URL: ${PG_URL} + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + depends_on: + pg: + condition: service_healthy + network_mode: host + +# crdb-rpc-1: +# build: +# context: . +# dockerfile: Dockerfile.rpc +# command: ["pnpm", "tsx", "src/rpc-servers/cockroach-rpc-server.ts"] +# environment: +# CRDB_URL: ${CRDB_URL} +# CRDB_RPC_PORT: "5001" +# SEED_ACCOUNTS: ${SEED_ACCOUNTS} +# SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} +# network_mode: host +# +# crdb-rpc-2: +# build: +# context: . +# dockerfile: Dockerfile.rpc +# command: ["pnpm", "tsx", "src/rpc-servers/cockroach-rpc-server.ts"] +# environment: +# CRDB_URL: ${CRDB_URL} +# CRDB_RPC_PORT: "5002" +# SEED_ACCOUNTS: ${SEED_ACCOUNTS} +# SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} +# network_mode: host +# +# crdb-rpc-3: +# build: +# context: . +# dockerfile: Dockerfile.rpc +# command: ["pnpm", "tsx", "src/rpc-servers/cockroach-rpc-server.ts"] +# environment: +# CRDB_URL: ${CRDB_URL} +# CRDB_RPC_PORT: "5003" +# SEED_ACCOUNTS: ${SEED_ACCOUNTS} +# SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} +# network_mode: host +# +# crdb-rpc-lb: +# image: nginx:stable +# volumes: +# - ./nginx-crdb.conf:/etc/nginx/nginx.conf:ro +# depends_on: +# crdb-rpc-1: +# condition: service_started +# crdb-rpc-2: +# condition: service_started +# crdb-rpc-3: +# condition: service_started +# network_mode: host + + crdb-rpc: + build: + context: . + dockerfile: Dockerfile.rpc + command: ["pnpm", "tsx", "src/rpc-servers/cockroach-rpc-server.ts"] + ports: + - "4102:4102" + environment: + CRDB_URL: ${CRDB_URL} + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + depends_on: + crdb: + condition: service_healthy +# crdb-init: +# condition: service_completed_successfully + network_mode: host + + sqlite-rpc: + build: + context: . + dockerfile: Dockerfile.rpc + command: ["pnpm", "tsx", "src/rpc-servers/sqlite-rpc-server.ts"] + ports: + - "4103:4103" + environment: + SQLITE_FILE: /data/accounts.sqlite + SQLITE_MODE: ${SQLITE_MODE} + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + volumes: + - /mnt/local-ssd/sqlite_data:/data + network_mode: host + + bun-rpc: + build: + context: . + dockerfile: Dockerfile.bun + ports: + - "4001:4001" + environment: + BUN_PG_URL: ${BUN_PG_URL} + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + depends_on: + pg: + condition: service_healthy + network_mode: host + + supabase-rpc: + build: + context: . + dockerfile: Dockerfile.rpc + command: [ "pnpm", "tsx", "src/rpc-servers/supabase-rpc-server.ts" ] + ports: + - "4106:4106" + environment: + SUPABASE_DB_URL: ${SUPABASE_DB_URL} + PG_RPC_PORT: "4106" + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + network_mode: host + + planetscale-pg-rpc: + build: + context: . + dockerfile: Dockerfile.rpc + command: [ "pnpm", "tsx", "src/rpc-servers/postgres-rpc-server.ts" ] + ports: + - "4104:4104" + environment: + PG_URL: ${PLANETSCALE_PG_URL} + PG_RPC_PORT: "4104" + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + network_mode: host + + spacetime: + image: clockworklabs/spacetime:latest + command: start + ports: + - "3000:3000" + volumes: + - /mnt/local-ssd/spacetime_data:/data + network_mode: host + # healthcheck: + # test: ["CMD-SHELL", "curl -fsS http://localhost:3000/health || exit 1"] + # interval: 2s + # timeout: 2s + # retries: 15 + + bench: + build: + context: . + dockerfile: Dockerfile.bench + depends_on: + pg: + condition: service_healthy + crdb: + condition: service_healthy +# crdb-init: +# condition: service_completed_successfully + spacetime: + condition: service_started + pg-rpc: + condition: service_started +# crdb-rpc-lb: +# condition: service_started +# crdb-rpc-1: +# condition: service_started +# crdb-rpc-2: +# condition: service_started +# crdb-rpc-3: +# condition: service_started + sqlite-rpc: + condition: service_started + environment: + USE_DOCKER: ${USE_DOCKER} + PG_URL: ${PG_URL} + CRDB_URL: ${CRDB_URL} + CONVEX_URL: ${CONVEX_URL} + STDB_URL: ${STDB_URL} + STDB_MODULE: ${STDB_MODULE} + STDB_MODULE_PATH: ${STDB_MODULE_PATH} + STDB_METRICS_URL: ${STDB_METRICS_URL} + BUN_URL: ${BUN_URL} + SQLITE_FILE: /data/accounts.sqlite + SQLITE_MODE: ${SQLITE_MODE} + SUPABASE_URL: ${SUPABASE_URL} + SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY} + SUPABASE_DB_URL: ${SUPABASE_DB_URL} + PG_RPC_URL: ${PG_RPC_URL} + CRDB_RPC_URL: ${CRDB_RPC_URL} + SQLITE_RPC_URL: ${SQLITE_RPC_URL} + SUPABASE_RPC_URL: ${SUPABASE_RPC_URL} + PLANETSCALE_RPC_URL: ${PLANETSCALE_RPC_URL} + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + USE_SPACETIME_METRICS_ENDPOINT: ${USE_SPACETIME_METRICS_ENDPOINT} + CONVEX_USE_SHARDED_COUNTER: ${CONVEX_USE_SHARDED_COUNTER} + VERIFY: ${VERIFY} + BENCH_PIPELINED: ${BENCH_PIPELINED} + MAX_INFLIGHT_PER_WORKER: ${MAX_INFLIGHT_PER_WORKER} + ENABLE_RPC_SERVERS: ${ENABLE_RPC_SERVERS} + volumes: + - /mnt/local-ssd/sqlite_data:/data + - ./runs:/app/runs + command: ["--seconds", "5", "--concurrency", "50", "--alpha", "1", "--connectors", "sqlite"] + network_mode: host + + sqlite-seed: + build: + context: . + dockerfile: Dockerfile.sqlite-seed + environment: + SQLITE_FILE: /data/accounts.sqlite + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + volumes: + - /mnt/local-ssd/sqlite_data:/data + network_mode: host diff --git a/templates/keynote-2/docker-compose.yml b/templates/keynote-2/docker-compose.yml new file mode 100644 index 00000000000..a20879ed977 --- /dev/null +++ b/templates/keynote-2/docker-compose.yml @@ -0,0 +1,298 @@ +services: + pg: + image: postgres:16 + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + ports: + - "5432:5432" + # command: > + # -c fsync=on + # -c synchronous_commit=on + # -c shared_buffers=2GB + # -c work_mem=64MB + # -c max_connections=1000 + command: > # faster setup + -c fsync=on + -c synchronous_commit=off + -c shared_buffers=8GB + -c work_mem=64MB + -c max_connections=10000 + -c default_transaction_isolation=serializable + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 2s + timeout: 2s + retries: 15 + volumes: + - pg_data:/var/lib/postgresql/data + network_mode: host + + crdb: + image: cockroachdb/cockroach:latest + command: start-single-node --insecure --max-sql-memory=.25 --cache=.5 + ports: + - "26257:26257" + - "8082:8080" + healthcheck: + test: ["CMD", "/cockroach/cockroach", "node", "status", "--insecure"] + interval: 2s + timeout: 2s + retries: 15 + volumes: + - crdb_data:/cockroach/cockroach-data + network_mode: host + + pg-rpc: + build: + context: . + dockerfile: Dockerfile.rpc + command: ["pnpm", "tsx", "src/rpc-servers/postgres-rpc-server.ts"] + ports: + - "4101:4101" + environment: + PG_URL: ${PG_URL} + PG_RPC_PORT: "4101" + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + depends_on: + pg: + condition: service_healthy + network_mode: host + +# crdb-rpc-lb: +# image: nginx:stable +# volumes: +# - ./nginx-crdb.conf:/etc/nginx/conf.d/crdb.conf:ro +# depends_on: +# crdb-rpc-1: +# condition: service_healthy +# crdb-rpc-2: +# condition: service_healthy +# crdb-rpc-3: +# condition: service_healthy +# network_mode: host +# +# crdb-rpc-1: +# build: +# context: . +# dockerfile: Dockerfile.rpc +# command: [ "pnpm", "tsx", "src/rpc-servers/cockroach-rpc-server.ts" ] +# environment: +# CRDB_URL: ${CRDB_URL} +# CRDB_RPC_PORT: "5001" +# SEED_ACCOUNTS: ${SEED_ACCOUNTS} +# SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} +# depends_on: +# crdb-init: +# condition: service_completed_successfully +# healthcheck: +# test: [ "CMD", "curl", "-f", "http://127.0.0.1:${CRDB_RPC_PORT}/health" ] +# interval: 2s +# timeout: 2s +# retries: 15 +# network_mode: host +# +# crdb-rpc-2: +# build: +# context: . +# dockerfile: Dockerfile.rpc +# command: [ "pnpm", "tsx", "src/rpc-servers/cockroach-rpc-server.ts" ] +# environment: +# CRDB_URL: ${CRDB_URL} +# CRDB_RPC_PORT: "5002" +# SEED_ACCOUNTS: ${SEED_ACCOUNTS} +# SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} +# depends_on: +# crdb-init: +# condition: service_completed_successfully +# healthcheck: +# test: [ "CMD", "curl", "-f", "http://127.0.0.1:${CRDB_RPC_PORT}/health" ] +# interval: 2s +# timeout: 2s +# retries: 15 +# network_mode: host +# +# crdb-rpc-3: +# build: +# context: . +# dockerfile: Dockerfile.rpc +# command: [ "pnpm", "tsx", "src/rpc-servers/cockroach-rpc-server.ts" ] +# environment: +# CRDB_URL: ${CRDB_URL} +# CRDB_RPC_PORT: "5003" +# SEED_ACCOUNTS: ${SEED_ACCOUNTS} +# SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} +# depends_on: +# crdb-init: +# condition: service_completed_successfully +# healthcheck: +# test: [ "CMD", "curl", "-f", "http://127.0.0.1:${CRDB_RPC_PORT}/health" ] +# interval: 2s +# timeout: 2s +# retries: 15 +# network_mode: host + + crdb-rpc: + build: + context: . + dockerfile: Dockerfile.rpc + command: ["pnpm", "tsx", "src/rpc-servers/cockroach-rpc-server.ts"] + ports: + - "4102:4102" + environment: + CRDB_URL: ${CRDB_URL} + CRDB_RPC_PORT: "4102" + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + depends_on: + crdb: + condition: service_healthy + network_mode: host + + sqlite-rpc: + build: + context: . + dockerfile: Dockerfile.rpc + command: ["pnpm", "tsx", "src/rpc-servers/sqlite-rpc-server.ts"] + ports: + - "4103:4103" + environment: + SQLITE_FILE: /data/accounts.sqlite + SQLITE_MODE: ${SQLITE_MODE} + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + volumes: + - sqlite_data:/data + network_mode: host + + bun-rpc: + build: + context: . + dockerfile: Dockerfile.bun + ports: + - "4001:4001" + environment: + BUN_PG_URL: ${BUN_PG_URL} + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + depends_on: + pg: + condition: service_healthy + network_mode: host + + supabase-rpc: + build: + context: . + dockerfile: Dockerfile.rpc + command: ["pnpm", "tsx", "src/rpc-servers/supabase-rpc-server.ts"] + environment: + SUPABASE_DB_URL: ${SUPABASE_DB_URL} + PG_RPC_PORT: "4106" + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + network_mode: host + + planetscale-pg-rpc: + build: + context: . + dockerfile: Dockerfile.rpc + command: ["pnpm", "tsx", "src/rpc-servers/postgres-rpc-server.ts"] + ports: + - "4104:4104" + environment: + PG_URL: ${PLANETSCALE_PG_URL} + PG_RPC_PORT: "4104" + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + network_mode: host + + spacetime: + image: clockworklabs/spacetime:latest + command: start + ports: + - "3000:3000" + volumes: + - spacetime_data:/data + network_mode: host + # healthcheck: + # test: ["CMD-SHELL", "curl -fsS http://localhost:3000/health || exit 1"] + # interval: 2s + # timeout: 2s + # retries: 15 + + bench: + build: + context: . + dockerfile: Dockerfile.bench + depends_on: + pg: + condition: service_healthy + crdb: + condition: service_healthy + crdb-rpc: + condition: service_started +# crdb-init: +# condition: service_completed_successfully + spacetime: + condition: service_started + pg-rpc: + condition: service_started +# crdb-rpc-lb: +# condition: service_started +# crdb-rpc-1: +# condition: service_started +# crdb-rpc-2: +# condition: service_started +# crdb-rpc-3: +# condition: service_started + sqlite-rpc: + condition: service_started + environment: + USE_DOCKER: ${USE_DOCKER} + PG_URL: ${PG_URL} + CRDB_URL: ${CRDB_URL} + CONVEX_URL: ${CONVEX_URL} + STDB_URL: ${STDB_URL} + STDB_MODULE: ${STDB_MODULE} + STDB_MODULE_PATH: ${STDB_MODULE_PATH} + STDB_METRICS_URL: ${STDB_METRICS_URL} + BUN_URL: ${BUN_URL} + SQLITE_FILE: ${SQLITE_FILE} + SQLITE_MODE: ${SQLITE_MODE} + SUPABASE_URL: ${SUPABASE_URL} + SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY} + PG_RPC_URL: ${PG_RPC_URL} + CRDB_RPC_URL: ${CRDB_RPC_URL} + SQLITE_RPC_URL: ${SQLITE_RPC_URL} + SUPABASE_RPC_URL: ${SUPABASE_RPC_URL} + PLANETSCALE_RPC_URL: ${PLANETSCALE_RPC_URL} + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + USE_SPACETIME_METRICS_ENDPOINT: ${USE_SPACETIME_METRICS_ENDPOINT} + CONVEX_USE_SHARDED_COUNTER: ${CONVEX_USE_SHARDED_COUNTER} + VERIFY: ${VERIFY} + volumes: + - sqlite_data:/data + - ./runs:/app/runs + command: ["--seconds", "5", "--concurrency", "50", "--alpha", "1", "--connectors", "sqlite"] + network_mode: host + + sqlite-seed: + build: + context: . + dockerfile: Dockerfile.sqlite-seed + environment: + SQLITE_FILE: /data/accounts.sqlite + SEED_ACCOUNTS: ${SEED_ACCOUNTS} + SEED_INITIAL_BALANCE: ${SEED_INITIAL_BALANCE} + volumes: + - sqlite_data:/data + network_mode: host + +volumes: + pg_data: + crdb_data: + sqlite_data: + spacetime_data: diff --git a/templates/keynote-2/modules/test-1/server/.cargo/config.toml b/templates/keynote-2/modules/test-1/server/.cargo/config.toml new file mode 100644 index 00000000000..f2bdad558ae --- /dev/null +++ b/templates/keynote-2/modules/test-1/server/.cargo/config.toml @@ -0,0 +1,11 @@ +# .cargo/config.toml +[build] +rustflags = ["-C","debuginfo=0","-C","split-debuginfo=off"] + +[target.x86_64-pc-windows-msvc] +# Avoid MSVC linker -> fixes .exp/.pdb handle issues +linker = "rust-lld.exe" + +[target.wasm32-unknown-unknown] +# wasm already uses wasm-ld; keep output lean +rustflags = ["-C","debuginfo=0","-C","panic=abort"] \ No newline at end of file diff --git a/templates/keynote-2/modules/test-1/server/.gitignore b/templates/keynote-2/modules/test-1/server/.gitignore new file mode 100644 index 00000000000..78a346dcac8 --- /dev/null +++ b/templates/keynote-2/modules/test-1/server/.gitignore @@ -0,0 +1,17 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Spacetime ignore +/.spacetime \ No newline at end of file diff --git a/templates/keynote-2/modules/test-1/server/Cargo.toml b/templates/keynote-2/modules/test-1/server/Cargo.toml new file mode 100644 index 00000000000..d54c7f016f7 --- /dev/null +++ b/templates/keynote-2/modules/test-1/server/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] + +[package] +name = "spacetime-module" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb = "=1.9.0" +log = "0.4" \ No newline at end of file diff --git a/templates/keynote-2/modules/test-1/server/src/lib.rs b/templates/keynote-2/modules/test-1/server/src/lib.rs new file mode 100644 index 00000000000..3dcd9f1aef7 --- /dev/null +++ b/templates/keynote-2/modules/test-1/server/src/lib.rs @@ -0,0 +1,83 @@ +use log::info; +use spacetimedb::{reducer, ReducerContext, Table}; + +#[spacetimedb::table(name = accounts, public)] +#[derive(Debug, Clone)] +pub struct Accounts { + #[primary_key] + pub id: u32, + pub balance: i64, +} + +#[reducer] +pub fn seed(ctx: &ReducerContext, n: u32, initial_balance: i64) -> Result<(), String> { + let accounts = ctx.db.accounts(); + + //reset + for row in accounts.iter() { + accounts.delete(row); + } + + //seed + for id in 0..n { + accounts.insert(Accounts { + id, + balance: initial_balance, + }); + } + + info!("seeded {} accounts with balance {}", n, initial_balance); + Ok(()) +} + +#[reducer] +pub fn create_account(ctx: &ReducerContext, id: u32, balance: i64) -> Result<(), String> { + let accounts = ctx.db.accounts(); + let by_id = accounts.id(); + + if let Some(mut row) = by_id.find(&id) { + row.balance = balance; + by_id.update(row); + } else { + accounts.insert(Accounts { id, balance }); + } + Ok(()) +} + +#[reducer] +pub fn transfer( + ctx: &ReducerContext, + from: u32, + to: u32, + amount: i64, + _client_txn_id: u64, +) -> Result<(), String> { + if from == to { + return Err("same_account".into()); + } + if amount <= 0 { + return Err("non_positive_amount".into()); + } + + let accounts = ctx.db.accounts(); + let by_id = accounts.id(); + + let from_row = by_id.find(&from).ok_or("account_missing")?; + let to_row = by_id.find(&to).ok_or("account_missing")?; + + if from_row.balance < amount { + return Err("insufficient_funds".into()); + } + + by_id.update(Accounts { + id: from, + balance: from_row.balance - amount, + }); + + by_id.update(Accounts { + id: to, + balance: to_row.balance + amount, + }); + + Ok(()) +} diff --git a/templates/keynote-2/nginx-crdb-local.conf b/templates/keynote-2/nginx-crdb-local.conf new file mode 100644 index 00000000000..b8fdbd98b94 --- /dev/null +++ b/templates/keynote-2/nginx-crdb-local.conf @@ -0,0 +1,23 @@ +worker_processes auto; + +events { + worker_connections 1024; +} + +http { + upstream crdb_rpc_backend { + server 127.0.0.1:5001; + server 127.0.0.1:5002; + server 127.0.0.1:5003; + } + + server { + listen 4102; + + location / { + proxy_pass http://crdb_rpc_backend; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } + } +} diff --git a/templates/keynote-2/nginx-crdb.conf b/templates/keynote-2/nginx-crdb.conf new file mode 100644 index 00000000000..41b3ad19166 --- /dev/null +++ b/templates/keynote-2/nginx-crdb.conf @@ -0,0 +1,34 @@ +worker_processes auto; +worker_rlimit_nofile 100000; + +events { + worker_connections 8192; + # multi_accept on; +} + +http { + log_format crdb_upstream + '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + 'upstream=$upstream_addr upstream_status=$upstream_status ' + 'rt=$request_time urt=$upstream_response_time'; + + access_log /var/log/nginx/access.log crdb_upstream; + + upstream crdb_rpc_backend { + server 10.128.0.8:5001; + server 10.128.0.10:5001; + server 10.128.0.12:5001; + } + + server { + listen 4102; + + location / { + proxy_pass http://crdb_rpc_backend; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } + } +} \ No newline at end of file diff --git a/templates/keynote-2/package-lock.json b/templates/keynote-2/package-lock.json new file mode 100644 index 00000000000..86ec04ce8d7 --- /dev/null +++ b/templates/keynote-2/package-lock.json @@ -0,0 +1,3431 @@ +{ + "name": "spacetime-simulations", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "spacetime-simulations", + "version": "0.0.0", + "dependencies": { + "@convex-dev/sharded-counter": "^0.2.0", + "@supabase/supabase-js": "^2.80.0", + "better-sqlite3": "^12.4.1", + "bun": "^1.3.2", + "drizzle-orm": "^0.44.7", + "express": "^5.1.0", + "hdr-histogram-js": "^3.0.1", + "spacetimedb": "1.9.0", + "sql.js": "^1.13.0", + "undici": "^6.19.2" + }, + "devDependencies": { + "@types/better-sqlite3": "^7.6.13", + "@types/bun": "^1.3.2", + "@types/express": "^5.0.5", + "@types/pg": "^8.15.6", + "@types/sql.js": "^1.4.9", + "bun-types": "^1.3.2", + "convex": "^1.29.0", + "dotenv": "^17.2.3", + "node-gyp": "^12.1.0", + "pg": "^8.16.3", + "prettier": "^3.3.1", + "tsx": "^4.20.6", + "typescript": "^5.9.3" + } + }, + "node_modules/@assemblyscript/loader": { + "version": "0.19.23", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.19.23.tgz", + "integrity": "sha512-ulkCYfFbYj01ie1MDOyxv2F6SpRN1TOj7fQxbP07D6HmeR+gr2JLSmINKjga2emB+b1L2KGrFKBTc+e00p54nw==", + "license": "Apache-2.0" + }, + "node_modules/@convex-dev/sharded-counter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@convex-dev/sharded-counter/-/sharded-counter-0.2.0.tgz", + "integrity": "sha512-Qv1TvFXyQ60rADS1fWH01mnMDNSPfmKKowXva6WxIXGIC2ta5xvohe9TfUhXp4AT1vtOXoFgse3Z9mJB9fJlSQ==", + "license": "Apache-2.0", + "peerDependencies": { + "convex": "^1.24.8" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@oven/bun-darwin-aarch64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.3.3.tgz", + "integrity": "sha512-eJopQrUk0WR7jViYDC29+Rp50xGvs4GtWOXBeqCoFMzutkkO3CZvHehA4JqnjfWMTSS8toqvRhCSOpOz62Wf9w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.3.3.tgz", + "integrity": "sha512-xGDePueVFrNgkS+iN0QdEFeRrx2MQ5hQ9ipRFu7N73rgoSSJsFlOKKt2uGZzunczedViIfjYl0ii0K4E9aZ0Ow==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64-baseline": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.3.3.tgz", + "integrity": "sha512-1ij4wQ9ECLFf1XFry+IFUN+28if40ozDqq6+QtuyOhIwraKzXOlAUbILhRMGvM3ED3yBex2mTwlKpA4Vja/V2g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-linux-aarch64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.3.3.tgz", + "integrity": "sha512-DabZ3Mt1XcJneWdEEug8l7bCPVvDBRBpjUIpNnRnMFWFnzr8KBEpMcaWTwYOghjXyJdhB4MPKb19MwqyQ+FHAw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-aarch64-musl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64-musl/-/bun-linux-aarch64-musl-1.3.3.tgz", + "integrity": "sha512-XWQ3tV/gtZj0wn2AdSUq/tEOKWT4OY+Uww70EbODgrrq00jxuTfq5nnYP6rkLD0M/T5BHJdQRSfQYdIni9vldw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.3.3.tgz", + "integrity": "sha512-7eIARtKZKZDtah1aCpQUj/1/zT/zHRR063J6oAxZP9AuA547j5B9OM2D/vi/F4En7Gjk9FPjgPGTSYeqpQDzJw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-baseline": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.3.3.tgz", + "integrity": "sha512-IU8pxhIf845psOv55LqJyL+tSUc6HHMfs6FGhuJcAnyi92j+B1HjOhnFQh9MW4vjoo7do5F8AerXlvk59RGH2w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-musl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl/-/bun-linux-x64-musl-1.3.3.tgz", + "integrity": "sha512-xNSDRPn1yyObKteS8fyQogwsS4eCECswHHgaKM+/d4wy/omZQrXn8ZyGm/ZF9B73UfQytUfbhE7nEnrFq03f0w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-musl-baseline": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl-baseline/-/bun-linux-x64-musl-baseline-1.3.3.tgz", + "integrity": "sha512-JoRTPdAXRkNYouUlJqEncMWUKn/3DiWP03A7weBbtbsKr787gcdNna2YeyQKCb1lIXE4v1k18RM3gaOpQobGIQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-windows-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.3.3.tgz", + "integrity": "sha512-kWqa1LKvDdAIzyfHxo3zGz3HFWbFHDlrNK77hKjUN42ycikvZJ+SHSX76+1OW4G8wmLETX4Jj+4BM1y01DQRIQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oven/bun-windows-x64-baseline": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64-baseline/-/bun-windows-x64-baseline-1.3.3.tgz", + "integrity": "sha512-u5eZHKq6TPJSE282KyBOicGQ2trkFml0RoUfqkPOJVo7TXGrsGYYzdsugZRnVQY/WEmnxGtBy4T3PAaPqgQViA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@supabase/auth-js": { + "version": "2.84.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.84.0.tgz", + "integrity": "sha512-J6XKbqqg1HQPMfYkAT9BrC8anPpAiifl7qoVLsYhQq5B/dnu/lxab1pabnxtJEsvYG5rwI5HEVEGXMjoQ6Wz2Q==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.84.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.84.0.tgz", + "integrity": "sha512-2oY5QBV4py/s64zMlhPEz+4RTdlwxzmfhM1k2xftD2v1DruRZKfoe7Yn9DCz1VondxX8evcvpc2udEIGzHI+VA==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.84.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.84.0.tgz", + "integrity": "sha512-oplc/3jfJeVW4F0J8wqywHkjIZvOVHtqzF0RESijepDAv5Dn/LThlGW1ftysoP4+PXVIrnghAbzPHo88fNomPQ==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.84.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.84.0.tgz", + "integrity": "sha512-ThqjxiCwWiZAroHnYPmnNl6tZk6jxGcG2a7Hp/3kcolPcMj89kWjUTA3cHmhdIWYsP84fHp8MAQjYWMLf7HEUg==", + "license": "MIT", + "dependencies": { + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.84.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.84.0.tgz", + "integrity": "sha512-vXvAJ1euCuhryOhC6j60dG8ky+lk0V06ubNo+CbhuoUv+sl39PyY0lc+k+qpQhTk/VcI6SiM0OECLN83+nyJ5A==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.84.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.84.0.tgz", + "integrity": "sha512-byMqYBvb91sx2jcZsdp0qLpmd4Dioe80e4OU/UexXftCkpTcgrkoENXHf5dO8FCSai8SgNeq16BKg10QiDI6xg==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.84.0", + "@supabase/functions-js": "2.84.0", + "@supabase/postgrest-js": "2.84.0", + "@supabase/realtime-js": "2.84.0", + "@supabase/storage-js": "2.84.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bun": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.3.tgz", + "integrity": "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bun-types": "1.3.3" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/emscripten": { + "version": "1.41.5", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz", + "integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", + "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "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.15.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz", + "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/sql.js": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@types/sql.js/-/sql.js-1.4.9.tgz", + "integrity": "sha512-ep8b36RKHlgWPqjNG9ToUrPiwkhwh0AEzy883mO5Xnd+cL6VBH1EvSjBAAuxLUFF2Vn/moE3Me6v9E1Lo+48GQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/emscripten": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/better-sqlite3": { + "version": "12.4.6", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.4.6.tgz", + "integrity": "sha512-gaYt9yqTbQ1iOxLpJA8FPR5PiaHP+jlg8I5EX0Rs2KFwNzhBsF40KzMZS5FwelY7RG0wzaucWdqSAJM3uNCPCg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bun": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/bun/-/bun-1.3.3.tgz", + "integrity": "sha512-2hJ4ocTZ634/Ptph4lysvO+LbbRZq8fzRvMwX0/CqaLBxrF2UB5D1LdMB8qGcdtCer4/VR9Bx5ORub0yn+yzmw==", + "cpu": [ + "arm64", + "x64" + ], + "hasInstallScript": true, + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32" + ], + "bin": { + "bun": "bin/bun.exe", + "bunx": "bin/bunx.exe" + }, + "optionalDependencies": { + "@oven/bun-darwin-aarch64": "1.3.3", + "@oven/bun-darwin-x64": "1.3.3", + "@oven/bun-darwin-x64-baseline": "1.3.3", + "@oven/bun-linux-aarch64": "1.3.3", + "@oven/bun-linux-aarch64-musl": "1.3.3", + "@oven/bun-linux-x64": "1.3.3", + "@oven/bun-linux-x64-baseline": "1.3.3", + "@oven/bun-linux-x64-musl": "1.3.3", + "@oven/bun-linux-x64-musl-baseline": "1.3.3", + "@oven/bun-windows-x64": "1.3.3", + "@oven/bun-windows-x64-baseline": "1.3.3" + } + }, + "node_modules/bun-types": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.3.tgz", + "integrity": "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", + "integrity": "sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0", + "unique-filename": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convex": { + "version": "1.29.3", + "resolved": "https://registry.npmjs.org/convex/-/convex-1.29.3.tgz", + "integrity": "sha512-tg5TXzMjpNk9m50YRtdp6US+t7ckxE4E+7DNKUCjJ2MupQs2RBSPF/z5SNN4GUmQLSfg0eMILDySzdAvjTrhnw==", + "license": "Apache-2.0", + "dependencies": { + "esbuild": "0.25.4", + "prettier": "^3.0.0" + }, + "bin": { + "convex": "bin/main.js" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=7.0.0" + }, + "peerDependencies": { + "@auth0/auth0-react": "^2.0.1", + "@clerk/clerk-react": "^4.12.8 || ^5.0.0", + "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@auth0/auth0-react": { + "optional": true + }, + "@clerk/clerk-react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "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/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/drizzle-orm": { + "version": "0.44.7", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.44.7.tgz", + "integrity": "sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1.13", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/sql.js": "*", + "@upstash/redis": ">=1.34.7", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "gel": ">=2", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "gel": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", + "license": "Apache-2.0" + }, + "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/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hdr-histogram-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-3.0.1.tgz", + "integrity": "sha512-l3GSdZL1Jr1C0kyb461tUjEdrRPZr8Qry7jByltf5JGrA0xvqOSrxRBfcrJqqV/AMEtqqhHhC6w8HW0gn76tRQ==", + "license": "BSD-2-Clause", + "dependencies": { + "@assemblyscript/loader": "^0.19.21", + "base64-js": "^1.2.0", + "pako": "^1.0.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/make-fetch-happen": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.3.tgz", + "integrity": "sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.0.tgz", + "integrity": "sha512-fiCdUALipqgPWrOVTz9fw0XhcazULXOSU6ie40DDbX1F49p1dBrSRBuswndTx1x3vEb/g0FT7vC4c4C2u/mh3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "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/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.1.0.tgz", + "integrity": "sha512-W+RYA8jBnhSr2vrTtlPYPc1K+CSjGpVDRZxcqJcERZ8ND3A1ThWPHRwctTx3qC3oW99jt726jhdz3Y6ky87J4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.2", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "devOptional": true, + "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==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "devOptional": true, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "devOptional": true, + "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==", + "devOptional": true, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proc-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz", + "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "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/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "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", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/spacetimedb": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/spacetimedb/-/spacetimedb-1.9.0.tgz", + "integrity": "sha512-cl/HeO50A1z6JgUZpA7pJcTspBE4IfH1SJJU8f1DpZ4hYHvQesbbYFHPPIC181s8zexdVAj5NnvFOPwQnSrcXg==", + "license": "ISC", + "dependencies": { + "base64-js": "^1.5.1", + "fast-text-encoding": "^1.0.0", + "prettier": "^3.3.3" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0", + "undici": "^6.19.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "undici": { + "optional": true + } + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sql.js": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.13.0.tgz", + "integrity": "sha512-RJbVP1HRDlUUXahJ7VMTcu9Rm1Nzw+EBpoPr94vnbD4LwR715F3CcxE2G2k45PewcaZ57pjetYa+LoSJLAASgA==", + "license": "MIT" + }, + "node_modules/ssri": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.0.tgz", + "integrity": "sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", + "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "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/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", + "integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "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/unique-filename": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-5.0.0.tgz", + "integrity": "sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/unique-slug": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-6.0.0.tgz", + "integrity": "sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + } + } +} diff --git a/templates/keynote-2/package.json b/templates/keynote-2/package.json new file mode 100644 index 00000000000..c517a3bf312 --- /dev/null +++ b/templates/keynote-2/package.json @@ -0,0 +1,42 @@ +{ + "name": "spacetime-simulations", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "prep": "tsx src/init/init-all.ts", + "down": "docker compose down || exit 0", + "test-1": "tsx src/cli.ts test-1", + "bench": "tsx src/cli.ts" + }, + "devDependencies": { + "@types/better-sqlite3": "^7.6.13", + "@types/bun": "^1.3.2", + "@types/express": "^5.0.5", + "@types/pg": "^8.15.6", + "@types/sql.js": "^1.4.9", + "bun-types": "^1.3.2", + "convex": "^1.29.0", + "dotenv": "^17.2.3", + "node-gyp": "^12.1.0", + "pg": "^8.16.3", + "prettier": "^3.3.1", + "tsx": "^4.20.6", + "typescript": "^5.9.3" + }, + "dependencies": { + "@convex-dev/sharded-counter": "^0.2.0", + "@supabase/supabase-js": "^2.80.0", + "better-sqlite3": "^12.4.1", + "bun": "^1.3.2", + "drizzle-orm": "^0.44.7", + "express": "^5.1.0", + "hdr-histogram-js": "^3.0.1", + "spacetimedb": "1.9.0", + "sql.js": "^1.13.0", + "undici": "^6.19.2" + } +} diff --git a/templates/keynote-2/pnpm-lock.yaml b/templates/keynote-2/pnpm-lock.yaml new file mode 100644 index 00000000000..d71b5eec5d9 --- /dev/null +++ b/templates/keynote-2/pnpm-lock.yaml @@ -0,0 +1,2510 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@convex-dev/sharded-counter': + specifier: ^0.2.0 + version: 0.2.0(convex@1.29.0) + '@supabase/supabase-js': + specifier: ^2.80.0 + version: 2.81.0 + better-sqlite3: + specifier: ^12.4.1 + version: 12.4.1 + bun: + specifier: ^1.3.2 + version: 1.3.2 + drizzle-orm: + specifier: ^0.44.7 + version: 0.44.7(@types/better-sqlite3@7.6.13)(@types/pg@8.15.6)(@types/sql.js@1.4.9)(better-sqlite3@12.4.1)(bun-types@1.3.2(@types/react@19.2.6))(pg@8.16.3)(sql.js@1.13.0) + express: + specifier: ^5.1.0 + version: 5.1.0 + hdr-histogram-js: + specifier: ^3.0.1 + version: 3.0.1 + spacetimedb: + specifier: 1.9.0 + version: 1.9.0(undici@6.22.0) + sql.js: + specifier: ^1.13.0 + version: 1.13.0 + undici: + specifier: ^6.19.2 + version: 6.22.0 + devDependencies: + '@types/better-sqlite3': + specifier: ^7.6.13 + version: 7.6.13 + '@types/bun': + specifier: ^1.3.2 + version: 1.3.2(@types/react@19.2.6) + '@types/express': + specifier: ^5.0.5 + version: 5.0.5 + '@types/pg': + specifier: ^8.15.6 + version: 8.15.6 + '@types/sql.js': + specifier: ^1.4.9 + version: 1.4.9 + bun-types: + specifier: ^1.3.2 + version: 1.3.2(@types/react@19.2.6) + convex: + specifier: ^1.29.0 + version: 1.29.0 + dotenv: + specifier: ^17.2.3 + version: 17.2.3 + node-gyp: + specifier: ^12.1.0 + version: 12.1.0 + pg: + specifier: ^8.16.3 + version: 8.16.3 + prettier: + specifier: ^3.3.1 + version: 3.6.2 + tsx: + specifier: ^4.20.6 + version: 4.20.6 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + +packages: + + '@assemblyscript/loader@0.19.23': + resolution: {integrity: sha512-ulkCYfFbYj01ie1MDOyxv2F6SpRN1TOj7fQxbP07D6HmeR+gr2JLSmINKjga2emB+b1L2KGrFKBTc+e00p54nw==} + + '@convex-dev/sharded-counter@0.2.0': + resolution: {integrity: sha512-Qv1TvFXyQ60rADS1fWH01mnMDNSPfmKKowXva6WxIXGIC2ta5xvohe9TfUhXp4AT1vtOXoFgse3Z9mJB9fJlSQ==} + peerDependencies: + convex: ^1.24.8 + + '@esbuild/aix-ppc64@0.25.4': + resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.4': + resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.4': + resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.4': + resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.4': + resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.4': + resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.4': + resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.4': + resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.4': + resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.4': + resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.4': + resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.4': + resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.4': + resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.4': + resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.4': + resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.4': + resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.4': + resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.4': + resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.4': + resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.4': + resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.4': + resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.4': + resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.4': + resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.4': + resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.4': + resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@npmcli/agent@4.0.0': + resolution: {integrity: sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/fs@4.0.0': + resolution: {integrity: sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@oven/bun-darwin-aarch64@1.3.2': + resolution: {integrity: sha512-licBDIbbLP5L5/S0+bwtJynso94XD3KyqSP48K59Sq7Mude6C7dR5ZujZm4Ut4BwZqUFfNOfYNMWBU5nlL7t1A==} + cpu: [arm64] + os: [darwin] + + '@oven/bun-darwin-x64-baseline@1.3.2': + resolution: {integrity: sha512-UHxdtbyxdtNJUNcXtIrjx3Lmq8ji3KywlXtIHV/0vn9A8W5mulqOcryqUWMFVH9JTIIzmNn6Q/qVmXHTME63Ww==} + cpu: [x64] + os: [darwin] + + '@oven/bun-darwin-x64@1.3.2': + resolution: {integrity: sha512-hn8lLzsYyyh6ULo2E8v2SqtrWOkdQKJwapeVy1rDw7juTTeHY3KDudGWf4mVYteC9riZU6HD88Fn3nGwyX0eIg==} + cpu: [x64] + os: [darwin] + + '@oven/bun-linux-aarch64-musl@1.3.2': + resolution: {integrity: sha512-OD9DYkjes7WXieBn4zQZGXWhRVZhIEWMDGCetZ3H4vxIuweZ++iul/CNX5jdpNXaJ17myb1ROMvmRbrqW44j3w==} + cpu: [arm64] + os: [linux] + + '@oven/bun-linux-aarch64@1.3.2': + resolution: {integrity: sha512-5uZzxzvHU/z+3cZwN/A0H8G+enQ+9FkeJVZkE2fwK2XhiJZFUGAuWajCpy7GepvOWlqV7VjPaKi2+Qmr4IX7nQ==} + cpu: [arm64] + os: [linux] + + '@oven/bun-linux-x64-baseline@1.3.2': + resolution: {integrity: sha512-m9Ov9YH8KjRLui87eNtQQFKVnjGsNk3xgbrR9c8d2FS3NfZSxmVjSeBvEsDjzNf1TXLDriHb/NYOlpiMf/QzDg==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64-musl-baseline@1.3.2': + resolution: {integrity: sha512-q8Hto8hcpofPJjvuvjuwyYvhOaAzPw1F5vRUUeOJDmDwZ4lZhANFM0rUwchMzfWUJCD6jg8/EVQ8MiixnZWU0A==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64-musl@1.3.2': + resolution: {integrity: sha512-3TuOsRVoG8K+soQWRo+Cp5ACpRs6rTFSu5tAqc/6WrqwbNWmqjov/eWJPTgz3gPXnC7uNKVG7RxxAmV8r2EYTQ==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64@1.3.2': + resolution: {integrity: sha512-EoEuRP9bxAxVKuvi6tZ0ZENjueP4lvjz0mKsMzdG0kwg/2apGKiirH1l0RIcdmvfDGGuDmNiv/XBpkoXq1x8ug==} + cpu: [x64] + os: [linux] + + '@oven/bun-windows-x64-baseline@1.3.2': + resolution: {integrity: sha512-s00T99MjB+xLOWq+t+wVaVBrry+oBOZNiTJijt+bmkp/MJptYS3FGvs7a+nkjLNzoNDoWQcXgKew6AaHES37Bg==} + cpu: [x64] + os: [win32] + + '@oven/bun-windows-x64@1.3.2': + resolution: {integrity: sha512-nZJUa5NprPYQ4Ii4cMwtP9PzlJJTp1XhxJ+A9eSn1Jfr6YygVWyN2KLjenyI93IcuBouBAaepDAVZZjH2lFBhg==} + cpu: [x64] + os: [win32] + + '@supabase/auth-js@2.81.0': + resolution: {integrity: sha512-mWyRPO+XUo19MHNBFg5qdH8cMIyxRNj9HXhwkwToxDHYRZWru96hWZFCVb7trOrTpPVe4TgLer2yy3KMvYBMPw==} + engines: {node: '>=20.0.0'} + + '@supabase/functions-js@2.81.0': + resolution: {integrity: sha512-yxxIGbXm1TtRpP5VwXKEZIdQMd2XUrWS1xt3zPF3jMItX5dXfdpbz5YRPY3IfebR8gXB113d/APWvYLiNuzI1Q==} + engines: {node: '>=20.0.0'} + + '@supabase/postgrest-js@2.81.0': + resolution: {integrity: sha512-HdybTRf5Sy+gBxzgwkag+WkvV8QqMXhnKQ383YG51lCbm8p82CuCcUTzGy2xFHiA2ZXnnlkSzrfw8uKFAiAiog==} + engines: {node: '>=20.0.0'} + + '@supabase/realtime-js@2.81.0': + resolution: {integrity: sha512-WCL9kMbmHQNGAG4ep+jfU22+h9OiQVv7bbkOmLy4gwlqtE+SJszkAtRp3l3xthqYkbxHbIqGc/BlHv3Dh79cXg==} + engines: {node: '>=20.0.0'} + + '@supabase/storage-js@2.81.0': + resolution: {integrity: sha512-gj9u+EyEVLgDA9jW8JOsAgEc8H79zg01STK5KLv9EU45kf5Qh7kAoCmG090Jkp/YEGvSiaR/Ta7Xs/gUTLqflw==} + engines: {node: '>=20.0.0'} + + '@supabase/supabase-js@2.81.0': + resolution: {integrity: sha512-FkiqUYCzsT92V/mfvoFueszkQrPqSTHgXhN9ADqeMpY5j0tUqeAZu8g2ptLYiDmx1pBbh4xoiqxWAf3UDIv4Bw==} + engines: {node: '>=20.0.0'} + + '@types/better-sqlite3@7.6.13': + resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/bun@1.3.2': + resolution: {integrity: sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/emscripten@1.41.5': + resolution: {integrity: sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==} + + '@types/express-serve-static-core@5.1.0': + resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==} + + '@types/express@5.0.5': + resolution: {integrity: sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/node@24.10.0': + resolution: {integrity: sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==} + + '@types/pg@8.15.6': + resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==} + + '@types/phoenix@1.6.6': + resolution: {integrity: sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/react@19.2.6': + resolution: {integrity: sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==} + + '@types/send@0.17.6': + resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@1.15.10': + resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} + + '@types/sql.js@1.4.9': + resolution: {integrity: sha512-ep8b36RKHlgWPqjNG9ToUrPiwkhwh0AEzy883mO5Xnd+cL6VBH1EvSjBAAuxLUFF2Vn/moE3Me6v9E1Lo+48GQ==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + abbrev@4.0.0: + resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} + engines: {node: ^20.17.0 || >=22.9.0} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + better-sqlite3@12.4.1: + resolution: {integrity: sha512-3yVdyZhklTiNrtg+4WqHpJpFDd+WHTg2oM7UcR80GqL05AOV0xEJzc6qNvFYoEtE+hRp1n9MpN6/+4yhlGkDXQ==} + engines: {node: 20.x || 22.x || 23.x || 24.x} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + body-parser@2.2.0: + resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + engines: {node: '>=18'} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + bun-types@1.3.2: + resolution: {integrity: sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg==} + peerDependencies: + '@types/react': ^19 + + bun@1.3.2: + resolution: {integrity: sha512-x75mPJiEfhO1j4Tfc65+PtW6ZyrAB6yTZInydnjDZXF9u9PRAnr6OK3v0Q9dpDl0dxRHkXlYvJ8tteJxc8t4Sw==} + cpu: [arm64, x64] + os: [darwin, linux, win32] + hasBin: true + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cacache@20.0.1: + resolution: {integrity: sha512-+7LYcYGBYoNqTp1Rv7Ny1YjUo5E0/ftkQtraH3vkfAGgVHc+ouWdC8okAwQgQR7EVIdW6JTzTmhKFwzb+4okAQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + content-disposition@1.0.0: + resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convex@1.29.0: + resolution: {integrity: sha512-uoIPXRKIp2eLCkkR9WJ2vc9NtgQtx8Pml59WPUahwbrd5EuW2WLI/cf2E7XrUzOSifdQC3kJZepisk4wJNTJaA==} + engines: {node: '>=18.0.0', npm: '>=7.0.0'} + hasBin: true + peerDependencies: + '@auth0/auth0-react': ^2.0.1 + '@clerk/clerk-react': ^4.12.8 || ^5.0.0 + react: ^18.0.0 || ^19.0.0-0 || ^19.0.0 + peerDependenciesMeta: + '@auth0/auth0-react': + optional: true + '@clerk/clerk-react': + optional: true + react: + optional: true + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + engines: {node: '>=12'} + + drizzle-orm@0.44.7: + resolution: {integrity: sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1.13' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=14.0.0' + gel: '>=2' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + prisma: '*' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@upstash/redis': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + gel: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + esbuild@0.25.4: + resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==} + engines: {node: '>=18'} + hasBin: true + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + + express@5.1.0: + resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} + engines: {node: '>= 18'} + + fast-text-encoding@1.0.6: + resolution: {integrity: sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + finalhandler@2.1.0: + resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} + engines: {node: '>= 0.8'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs-minipass@3.0.3: + resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + glob@11.0.3: + resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} + engines: {node: 20 || >=22} + hasBin: true + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hdr-histogram-js@3.0.1: + resolution: {integrity: sha512-l3GSdZL1Jr1C0kyb461tUjEdrRPZr8Qry7jByltf5JGrA0xvqOSrxRBfcrJqqV/AMEtqqhHhC6w8HW0gn76tRQ==} + engines: {node: '>=14'} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.7.0: + resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} + engines: {node: 20 || >=22} + + lru-cache@11.2.2: + resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} + engines: {node: 20 || >=22} + + make-fetch-happen@15.0.2: + resolution: {integrity: sha512-sI1NY4lWlXBAfjmCtVWIIpBypbBdhHtcjnwnv+gtCnsaOffyFil3aidszGC8hgzJe+fT1qix05sWxmD/Bmf/oQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} + engines: {node: '>= 0.6'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + engines: {node: 20 || >=22} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass-collect@2.0.1: + resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass-fetch@4.0.1: + resolution: {integrity: sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + node-abi@3.80.0: + resolution: {integrity: sha512-LyPuZJcI9HVwzXK1GPxWNzrr+vr8Hp/3UqlmWxxh8p54U1ZbclOqbSog9lWHaCX+dBaiGi6n/hIX+mKu74GmPA==} + engines: {node: '>=10'} + + node-gyp@12.1.0: + resolution: {integrity: sha512-W+RYA8jBnhSr2vrTtlPYPc1K+CSjGpVDRZxcqJcERZ8ND3A1ThWPHRwctTx3qC3oW99jt726jhdz3Y6ky87J4g==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + nopt@9.0.0: + resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + p-map@7.0.4: + resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} + engines: {node: '>=18'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@2.0.1: + resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} + engines: {node: 20 || >=22} + + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + + pg-cloudflare@1.2.7: + resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} + + pg-connection-string@2.9.1: + resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.10.1: + resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.10.3: + resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.16.3: + resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + proc-log@5.0.0: + resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + proc-log@6.0.0: + resolution: {integrity: sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==} + engines: {node: ^20.17.0 || >=22.9.0} + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.1: + resolution: {integrity: sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==} + engines: {node: '>= 0.10'} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.0: + resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} + engines: {node: '>= 18'} + + serve-static@2.2.0: + resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + spacetimedb@1.9.0: + resolution: {integrity: sha512-cl/HeO50A1z6JgUZpA7pJcTspBE4IfH1SJJU8f1DpZ4hYHvQesbbYFHPPIC181s8zexdVAj5NnvFOPwQnSrcXg==} + peerDependencies: + react: ^18.0.0 || ^19.0.0-0 || ^19.0.0 + undici: ^6.19.2 + peerDependenciesMeta: + react: + optional: true + undici: + optional: true + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + sql.js@1.13.0: + resolution: {integrity: sha512-RJbVP1HRDlUUXahJ7VMTcu9Rm1Nzw+EBpoPr94vnbD4LwR715F3CcxE2G2k45PewcaZ57pjetYa+LoSJLAASgA==} + + ssri@12.0.0: + resolution: {integrity: sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar@7.5.2: + resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.20.6: + resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==} + engines: {node: '>=18.0.0'} + hasBin: true + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + undici@6.22.0: + resolution: {integrity: sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==} + engines: {node: '>=18.17'} + + unique-filename@4.0.0: + resolution: {integrity: sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + unique-slug@5.0.0: + resolution: {integrity: sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==} + engines: {node: ^18.17.0 || >=20.5.0} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@6.0.0: + resolution: {integrity: sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + 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 + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + +snapshots: + + '@assemblyscript/loader@0.19.23': {} + + '@convex-dev/sharded-counter@0.2.0(convex@1.29.0)': + dependencies: + convex: 1.29.0 + + '@esbuild/aix-ppc64@0.25.4': + optional: true + + '@esbuild/android-arm64@0.25.4': + optional: true + + '@esbuild/android-arm@0.25.4': + optional: true + + '@esbuild/android-x64@0.25.4': + optional: true + + '@esbuild/darwin-arm64@0.25.4': + optional: true + + '@esbuild/darwin-x64@0.25.4': + optional: true + + '@esbuild/freebsd-arm64@0.25.4': + optional: true + + '@esbuild/freebsd-x64@0.25.4': + optional: true + + '@esbuild/linux-arm64@0.25.4': + optional: true + + '@esbuild/linux-arm@0.25.4': + optional: true + + '@esbuild/linux-ia32@0.25.4': + optional: true + + '@esbuild/linux-loong64@0.25.4': + optional: true + + '@esbuild/linux-mips64el@0.25.4': + optional: true + + '@esbuild/linux-ppc64@0.25.4': + optional: true + + '@esbuild/linux-riscv64@0.25.4': + optional: true + + '@esbuild/linux-s390x@0.25.4': + optional: true + + '@esbuild/linux-x64@0.25.4': + optional: true + + '@esbuild/netbsd-arm64@0.25.4': + optional: true + + '@esbuild/netbsd-x64@0.25.4': + optional: true + + '@esbuild/openbsd-arm64@0.25.4': + optional: true + + '@esbuild/openbsd-x64@0.25.4': + optional: true + + '@esbuild/sunos-x64@0.25.4': + optional: true + + '@esbuild/win32-arm64@0.25.4': + optional: true + + '@esbuild/win32-ia32@0.25.4': + optional: true + + '@esbuild/win32-x64@0.25.4': + optional: true + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@npmcli/agent@4.0.0': + dependencies: + agent-base: 7.1.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 11.2.2 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + '@npmcli/fs@4.0.0': + dependencies: + semver: 7.7.3 + + '@oven/bun-darwin-aarch64@1.3.2': + optional: true + + '@oven/bun-darwin-x64-baseline@1.3.2': + optional: true + + '@oven/bun-darwin-x64@1.3.2': + optional: true + + '@oven/bun-linux-aarch64-musl@1.3.2': + optional: true + + '@oven/bun-linux-aarch64@1.3.2': + optional: true + + '@oven/bun-linux-x64-baseline@1.3.2': + optional: true + + '@oven/bun-linux-x64-musl-baseline@1.3.2': + optional: true + + '@oven/bun-linux-x64-musl@1.3.2': + optional: true + + '@oven/bun-linux-x64@1.3.2': + optional: true + + '@oven/bun-windows-x64-baseline@1.3.2': + optional: true + + '@oven/bun-windows-x64@1.3.2': + optional: true + + '@supabase/auth-js@2.81.0': + dependencies: + tslib: 2.8.1 + + '@supabase/functions-js@2.81.0': + dependencies: + tslib: 2.8.1 + + '@supabase/postgrest-js@2.81.0': + dependencies: + tslib: 2.8.1 + + '@supabase/realtime-js@2.81.0': + dependencies: + '@types/phoenix': 1.6.6 + '@types/ws': 8.18.1 + tslib: 2.8.1 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@supabase/storage-js@2.81.0': + dependencies: + tslib: 2.8.1 + + '@supabase/supabase-js@2.81.0': + dependencies: + '@supabase/auth-js': 2.81.0 + '@supabase/functions-js': 2.81.0 + '@supabase/postgrest-js': 2.81.0 + '@supabase/realtime-js': 2.81.0 + '@supabase/storage-js': 2.81.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@types/better-sqlite3@7.6.13': + dependencies: + '@types/node': 24.10.0 + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 24.10.0 + + '@types/bun@1.3.2(@types/react@19.2.6)': + dependencies: + bun-types: 1.3.2(@types/react@19.2.6) + transitivePeerDependencies: + - '@types/react' + + '@types/connect@3.4.38': + dependencies: + '@types/node': 24.10.0 + + '@types/emscripten@1.41.5': {} + + '@types/express-serve-static-core@5.1.0': + dependencies: + '@types/node': 24.10.0 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.5': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.0 + '@types/serve-static': 1.15.10 + + '@types/http-errors@2.0.5': {} + + '@types/mime@1.3.5': {} + + '@types/node@24.10.0': + dependencies: + undici-types: 7.16.0 + + '@types/pg@8.15.6': + dependencies: + '@types/node': 24.10.0 + pg-protocol: 1.10.3 + pg-types: 2.2.0 + + '@types/phoenix@1.6.6': {} + + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/react@19.2.6': + dependencies: + csstype: 3.2.3 + + '@types/send@0.17.6': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 24.10.0 + + '@types/send@1.2.1': + dependencies: + '@types/node': 24.10.0 + + '@types/serve-static@1.15.10': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.10.0 + '@types/send': 0.17.6 + + '@types/sql.js@1.4.9': + dependencies: + '@types/emscripten': 1.41.5 + '@types/node': 24.10.0 + + '@types/ws@8.18.1': + dependencies: + '@types/node': 24.10.0 + + abbrev@4.0.0: {} + + accepts@2.0.0: + dependencies: + mime-types: 3.0.1 + negotiator: 1.0.0 + + agent-base@7.1.4: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + base64-js@1.5.1: {} + + better-sqlite3@12.4.1: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + body-parser@2.2.0: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + on-finished: 2.4.1 + qs: 6.14.0 + raw-body: 3.0.1 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bun-types@1.3.2(@types/react@19.2.6): + dependencies: + '@types/node': 24.10.0 + '@types/react': 19.2.6 + + bun@1.3.2: + optionalDependencies: + '@oven/bun-darwin-aarch64': 1.3.2 + '@oven/bun-darwin-x64': 1.3.2 + '@oven/bun-darwin-x64-baseline': 1.3.2 + '@oven/bun-linux-aarch64': 1.3.2 + '@oven/bun-linux-aarch64-musl': 1.3.2 + '@oven/bun-linux-x64': 1.3.2 + '@oven/bun-linux-x64-baseline': 1.3.2 + '@oven/bun-linux-x64-musl': 1.3.2 + '@oven/bun-linux-x64-musl-baseline': 1.3.2 + '@oven/bun-windows-x64': 1.3.2 + '@oven/bun-windows-x64-baseline': 1.3.2 + + bytes@3.1.2: {} + + cacache@20.0.1: + dependencies: + '@npmcli/fs': 4.0.0 + fs-minipass: 3.0.3 + glob: 11.0.3 + lru-cache: 11.2.2 + minipass: 7.1.2 + minipass-collect: 2.0.1 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + p-map: 7.0.4 + ssri: 12.0.0 + unique-filename: 4.0.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + chownr@1.1.4: {} + + chownr@3.0.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + content-disposition@1.0.0: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + convex@1.29.0: + dependencies: + esbuild: 0.25.4 + prettier: 3.6.2 + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + + depd@2.0.0: {} + + detect-libc@2.1.2: {} + + dotenv@17.2.3: {} + + drizzle-orm@0.44.7(@types/better-sqlite3@7.6.13)(@types/pg@8.15.6)(@types/sql.js@1.4.9)(better-sqlite3@12.4.1)(bun-types@1.3.2(@types/react@19.2.6))(pg@8.16.3)(sql.js@1.13.0): + optionalDependencies: + '@types/better-sqlite3': 7.6.13 + '@types/pg': 8.15.6 + '@types/sql.js': 1.4.9 + better-sqlite3: 12.4.1 + bun-types: 1.3.2(@types/react@19.2.6) + pg: 8.16.3 + sql.js: 1.13.0 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ee-first@1.1.1: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@2.0.0: {} + + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + optional: true + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + env-paths@2.2.1: {} + + err-code@2.0.3: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + esbuild@0.25.4: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.4 + '@esbuild/android-arm': 0.25.4 + '@esbuild/android-arm64': 0.25.4 + '@esbuild/android-x64': 0.25.4 + '@esbuild/darwin-arm64': 0.25.4 + '@esbuild/darwin-x64': 0.25.4 + '@esbuild/freebsd-arm64': 0.25.4 + '@esbuild/freebsd-x64': 0.25.4 + '@esbuild/linux-arm': 0.25.4 + '@esbuild/linux-arm64': 0.25.4 + '@esbuild/linux-ia32': 0.25.4 + '@esbuild/linux-loong64': 0.25.4 + '@esbuild/linux-mips64el': 0.25.4 + '@esbuild/linux-ppc64': 0.25.4 + '@esbuild/linux-riscv64': 0.25.4 + '@esbuild/linux-s390x': 0.25.4 + '@esbuild/linux-x64': 0.25.4 + '@esbuild/netbsd-arm64': 0.25.4 + '@esbuild/netbsd-x64': 0.25.4 + '@esbuild/openbsd-arm64': 0.25.4 + '@esbuild/openbsd-x64': 0.25.4 + '@esbuild/sunos-x64': 0.25.4 + '@esbuild/win32-arm64': 0.25.4 + '@esbuild/win32-ia32': 0.25.4 + '@esbuild/win32-x64': 0.25.4 + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + expand-template@2.0.3: {} + + exponential-backoff@3.1.3: {} + + express@5.1.0: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.0 + content-disposition: 1.0.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.0 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + mime-types: 3.0.1 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.0 + serve-static: 2.2.0 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-text-encoding@1.0.6: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-uri-to-path@1.0.0: {} + + finalhandler@2.1.0: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs-constants@1.0.0: {} + + fs-minipass@3.0.3: + dependencies: + minipass: 7.1.2 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + github-from-package@0.0.0: {} + + glob@11.0.3: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.1.1 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.1 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hdr-histogram-js@3.0.1: + dependencies: + '@assemblyscript/loader': 0.19.23 + base64-js: 1.5.1 + pako: 1.0.11 + + http-cache-semantics@4.2.0: {} + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.7.0: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + imurmurhash@0.1.4: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + ip-address@10.1.0: {} + + ipaddr.js@1.9.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-promise@4.0.0: {} + + isexe@2.0.0: {} + + isexe@3.1.1: {} + + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + + lru-cache@11.2.2: {} + + make-fetch-happen@15.0.2: + dependencies: + '@npmcli/agent': 4.0.0 + cacache: 20.0.1 + http-cache-semantics: 4.2.0 + minipass: 7.1.2 + minipass-fetch: 4.0.1 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 1.0.0 + proc-log: 5.0.0 + promise-retry: 2.0.1 + ssri: 12.0.0 + transitivePeerDependencies: + - supports-color + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.54.0: {} + + mime-types@3.0.1: + dependencies: + mime-db: 1.54.0 + + mimic-response@3.1.0: {} + + minimatch@10.1.1: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + + minimist@1.2.8: {} + + minipass-collect@2.0.1: + dependencies: + minipass: 7.1.2 + + minipass-fetch@4.0.1: + dependencies: + minipass: 7.1.2 + minipass-sized: 1.0.3 + minizlib: 3.1.0 + optionalDependencies: + encoding: 0.1.13 + + minipass-flush@1.0.5: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass-sized@1.0.3: + dependencies: + minipass: 3.3.6 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@7.1.2: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.2 + + mkdirp-classic@0.5.3: {} + + ms@2.1.3: {} + + napi-build-utils@2.0.0: {} + + negotiator@1.0.0: {} + + node-abi@3.80.0: + dependencies: + semver: 7.7.3 + + node-gyp@12.1.0: + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.3 + graceful-fs: 4.2.11 + make-fetch-happen: 15.0.2 + nopt: 9.0.0 + proc-log: 6.0.0 + semver: 7.7.3 + tar: 7.5.2 + tinyglobby: 0.2.15 + which: 6.0.0 + transitivePeerDependencies: + - supports-color + + nopt@9.0.0: + dependencies: + abbrev: 4.0.0 + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + p-map@7.0.4: {} + + package-json-from-dist@1.0.1: {} + + pako@1.0.11: {} + + parseurl@1.3.3: {} + + path-key@3.1.1: {} + + path-scurry@2.0.1: + dependencies: + lru-cache: 11.2.2 + minipass: 7.1.2 + + path-to-regexp@8.3.0: {} + + pg-cloudflare@1.2.7: + optional: true + + pg-connection-string@2.9.1: {} + + pg-int8@1.0.1: {} + + pg-pool@3.10.1(pg@8.16.3): + dependencies: + pg: 8.16.3 + + pg-protocol@1.10.3: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.16.3: + dependencies: + pg-connection-string: 2.9.1 + pg-pool: 3.10.1(pg@8.16.3) + pg-protocol: 1.10.3 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.2.7 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + + picomatch@4.0.3: {} + + postgres-array@2.0.0: {} + + postgres-bytea@1.0.0: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.80.0 + pump: 3.0.3 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + + prettier@3.6.2: {} + + proc-log@5.0.0: {} + + proc-log@6.0.0: {} + + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + + range-parser@1.2.1: {} + + raw-body@3.0.1: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.7.0 + unpipe: 1.0.0 + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + resolve-pkg-maps@1.0.0: {} + + retry@0.12.0: {} + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + semver@7.7.3: {} + + send@1.2.0: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.0 + mime-types: 3.0.1 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.0: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.0 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + smart-buffer@4.2.0: {} + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + socks: 2.8.7 + transitivePeerDependencies: + - supports-color + + socks@2.8.7: + dependencies: + ip-address: 10.1.0 + smart-buffer: 4.2.0 + + spacetimedb@1.9.0(undici@6.22.0): + dependencies: + base64-js: 1.5.1 + fast-text-encoding: 1.0.6 + prettier: 3.6.2 + optionalDependencies: + undici: 6.22.0 + + split2@4.2.0: {} + + sql.js@1.13.0: {} + + ssri@12.0.0: + dependencies: + minipass: 7.1.2 + + statuses@2.0.1: {} + + statuses@2.0.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-json-comments@2.0.1: {} + + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar@7.5.2: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + toidentifier@1.0.1: {} + + tslib@2.8.1: {} + + tsx@4.20.6: + dependencies: + esbuild: 0.25.4 + get-tsconfig: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.1 + + typescript@5.9.3: {} + + undici-types@7.16.0: {} + + undici@6.22.0: {} + + unique-filename@4.0.0: + dependencies: + unique-slug: 5.0.0 + + unique-slug@5.0.0: + dependencies: + imurmurhash: 0.1.4 + + unpipe@1.0.0: {} + + util-deprecate@1.0.2: {} + + vary@1.1.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@6.0.0: + dependencies: + isexe: 3.1.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + wrappy@1.0.2: {} + + ws@8.18.3: {} + + xtend@4.0.2: {} + + yallist@4.0.0: {} + + yallist@5.0.0: {} diff --git a/templates/keynote-2/src/cli.ts b/templates/keynote-2/src/cli.ts new file mode 100644 index 00000000000..3b1a26ec936 --- /dev/null +++ b/templates/keynote-2/src/cli.ts @@ -0,0 +1,295 @@ +import 'dotenv/config'; +import { readdir, mkdir, writeFile } from 'node:fs/promises'; +import { CONNECTORS } from './connectors'; +import { runOne } from './core/runner'; +import type { TestCaseModule } from './tests/types'; +import { fileURLToPath } from 'node:url'; +import { join } from 'node:path'; +import { RunResult } from './core/types.ts'; + +const args = process.argv.slice(2); + +let testName = 'test-1'; +let posArgs = args; + +if (args.length > 0 && !args[0].startsWith('--')) { + testName = args[0]; + posArgs = args.slice(1); +} + +let seconds = 1, + concurrency = 10, + accounts = process.env.SEED_ACCOUNTS + ? Number(process.env.SEED_ACCOUNTS) + : 100_000, + alpha = 0.5, + connectors: string[] | null = null, + contentionTests: { + startAlpha: number; + endAlpha: number; + step: number; + concurrency: number; + } | null = null, + concurrencyTests: { + startConc: number; + endConc: number; + step: number; + alpha: number; + } | null = null; + +for (let i = 0; i < posArgs.length; ) { + const arg = posArgs[i]; + if (!arg.startsWith('--')) { + i++; + continue; + } + const key = arg.slice(2); + const val = posArgs[i + 1]; + if (!val || val.startsWith('--')) { + i++; + continue; + } + + switch (key) { + case 'seconds': + seconds = Number(val); + i += 2; + break; + case 'concurrency': + concurrency = Number(val); + i += 2; + break; + case 'alpha': + alpha = Number(val); + i += 2; + break; + case 'connectors': + connectors = val + .split(',') + .map((s) => s.trim()) + .filter(Boolean); + i += 2; + break; + case 'contention-tests': + contentionTests = { + startAlpha: Number(posArgs[i + 1]), + endAlpha: Number(posArgs[i + 2]), + step: Number(posArgs[i + 3]), + concurrency: Number(posArgs[i + 4]), + }; + concurrency = Number(posArgs[i + 4]); + + i += 5; + break; + case 'concurrency-tests': + concurrencyTests = { + startConc: Number(posArgs[i + 1]), + endConc: Number(posArgs[i + 2]), + step: Number(posArgs[i + 3]), + alpha: Number(posArgs[i + 4]), + }; + alpha = Number(posArgs[i + 4]); + + i += 5; + break; + } +} + +interface BenchmarkConfig { + connector: any; + scenario: any; + seconds: number; + accounts: number; +} + +class BenchmarkTester { + private config: BenchmarkConfig; + + constructor(config: BenchmarkConfig) { + this.config = config; + } + + private async runAvg( + concurrency: number, + alpha: number, + runs: number = 3, + ): Promise { + let totals = { + tps: 0, + samples: 0, + committed_txns: 0, + p50_ms: 0, + p95_ms: 0, + p99_ms: 0, + collision_ops: 0, + collision_count: 0, + collision_rate: 0, + }; + for (let i = 0; i < runs; i++) { + const result = await runOne({ ...this.config, concurrency, alpha }); + totals.tps += result.tps; + totals.samples += result.samples; + totals.committed_txns += result.committed_txns ?? 0; + totals.p50_ms += result.p50_ms; + totals.p95_ms += result.p95_ms; + totals.p99_ms += result.p99_ms; + totals.collision_ops += result.collision_ops; + totals.collision_count += result.collision_count; + totals.collision_rate += result.collision_rate; + + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + const avg = { + tps: totals.tps / runs, + samples: totals.samples / runs, + committed_txns: totals.committed_txns / runs, + p50_ms: totals.p50_ms / runs, + p95_ms: totals.p95_ms / runs, + p99_ms: totals.p99_ms / runs, + collision_ops: totals.collision_ops / runs, + collision_count: totals.collision_count / runs, + collision_rate: totals.collision_rate / runs, + }; + return avg; + } + + async contentionTests( + startAlpha: number = 1, + endAlpha: number = 100, + step: number = 1, + concurrency: number = 50, + ) { + const results: { alpha: number; avgResult: RunResult }[] = []; + for (let alpha = startAlpha; alpha <= endAlpha; alpha += step) { + const avgResult = await this.runAvg(concurrency, alpha); + results.push({ alpha, avgResult }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + return results; + } + + async concurrencyTests( + startConc: number = 1, + endConc: number = 100, + step: number = 1, + alpha: number = 1, + ) { + const results: { concurrency: number; avgResult: RunResult }[] = []; + for (let conc = startConc; conc <= endConc; conc += step) { + const avgResult = await this.runAvg(conc, alpha); + results.push({ concurrency: conc, avgResult }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + return results; + } + + async concurrencyTestsMutiply( + startConc: number = 1, + endConc: number = 100, + factor: number = 2, + alpha: number = 1, + ) { + if (factor <= 1) { + throw new Error('factor must be > 1 to avoid infinite loop'); + } + + const results: { concurrency: number; avgResult: RunResult }[] = []; + + for (let conc = startConc; conc <= endConc; conc *= factor) { + const avgResult = await this.runAvg(conc, alpha); + results.push({ concurrency: conc, avgResult }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + return results; + } +} + +const testDirUrl = new URL(`./tests/${testName}/`, import.meta.url); +const testDirPath = fileURLToPath(testDirUrl); + +(async () => { + const files = (await readdir(testDirPath)).filter( + (f) => (f.endsWith('.ts') || f.endsWith('.js')) && !f.endsWith('.d.ts'), + ); + + const results: any[] = []; + + for (const file of files) { + const mod = (await import( + new URL(`./tests/${testName}/${file}`, import.meta.url).href + )) as TestCaseModule; + const tc = mod.default; + + if (connectors && !connectors.includes(tc.system)) continue; + + const makeConnector = (CONNECTORS as any)[tc.system]; + if (!makeConnector) throw new Error(`Unknown connector ${tc.system}`); + + const connector = makeConnector(); + + let res: any; + + const config = { connector, scenario: tc.run, seconds, accounts }; + + const tester = new BenchmarkTester(config); + + if (contentionTests) { + res = await tester.contentionTests( + contentionTests.startAlpha, + contentionTests.endAlpha, + contentionTests.step, + contentionTests.concurrency, + ); + } else if (concurrencyTests) { + res = await tester.concurrencyTestsMutiply( + concurrencyTests.startConc, + concurrencyTests.endConc, + concurrencyTests.step, + concurrencyTests.alpha, + ); + } else { + res = await runOne({ + connector, + scenario: tc.run, + seconds, + concurrency, + accounts, + alpha, + }); + } + + results.push({ + system: connector.name, + label: tc.label ?? file, + file, + seconds, + concurrency, + accounts, + alpha, + res, + }); + console.log(`${file}:`, res); + } + + const runData = { + test: testName, + seconds, + concurrency, + accounts, + alpha, + results, + }; + const runsDir = fileURLToPath(new URL('../runs/', import.meta.url)); + await mkdir(runsDir, { recursive: true }); + const outFile = join( + runsDir, + `${testName}-${new Date().toISOString().replace(/[:.]/g, '-')}.json`, + ); + await writeFile(outFile, JSON.stringify(runData, null, 2)); + + console.log(`Wrote results to ${outFile}`); +})(); diff --git a/templates/keynote-2/src/connectors/bun.ts b/templates/keynote-2/src/connectors/bun.ts new file mode 100644 index 00000000000..9f0570bfa16 --- /dev/null +++ b/templates/keynote-2/src/connectors/bun.ts @@ -0,0 +1,120 @@ +import type { RpcConnector } from '../core/connectors.ts'; + +export default function bun( + url = process.env.BUN_URL || 'http://127.0.0.1:4000', +): RpcConnector { + if (!url) throw new Error('BUN_URL not set'); + + const baseUrl = url.replace(/\/+$/, ''); + + async function httpCall( + name: string, + args?: Record, + ): Promise { + const res = await fetch(`${baseUrl}/rpc`, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ name, args: args ?? {} }), + }); + + if (!res.ok) { + const text = await res.text().catch(() => ''); + throw new Error( + `[bun] RPC ${name} HTTP ${res.status} ${res.statusText}: ${text.slice( + 0, + 200, + )}`, + ); + } + + const json = (await res.json()) as any; + + if (json && typeof json === 'object' && typeof json.ok === 'boolean') { + if (!json.ok) { + throw new Error( + `[bun] RPC ${name} failed: ${String(json.error ?? 'unknown error')}`, + ); + } + return json.result; + } + + return json; + } + + const root: RpcConnector = { + name: 'bun', + + async open() { + try { + await httpCall('health').catch(() => {}); + } catch (err) { + console.warn('[bun] health check error (continuing anyway):', err); + } + }, + + async close() {}, + + async call(name: string, args?: Record): Promise { + return httpCall(name, args); + }, + + async getAccount(id: number) { + const r = (await root.call('getAccount', { id })) as { + id: number; + balance: bigint; + } | null; + + if (!r) return null; + + return { + id: r.id, + balance: r.balance, + }; + }, + + async verify() { + await root.call('verify'); + }, + + async createWorker() { + const workerConnector: RpcConnector = { + name: 'bun', + + async open() {}, + + async close() {}, + + async call( + name: string, + args?: Record, + ): Promise { + return httpCall(name, args); + }, + + async getAccount(id: number) { + const r = (await workerConnector.call('getAccount', { id })) as { + id: number; + balance: bigint; + } | null; + + if (!r) return null; + + return { + id: r.id, + balance: r.balance, + }; + }, + + async verify() { + throw new Error( + 'verify() not supported on bun worker connector; call verify() on the root connector instead', + ); + }, + }; + + return workerConnector; + }, + }; + + return root; +} diff --git a/templates/keynote-2/src/connectors/convex.ts b/templates/keynote-2/src/connectors/convex.ts new file mode 100644 index 00000000000..f60643aa1ac --- /dev/null +++ b/templates/keynote-2/src/connectors/convex.ts @@ -0,0 +1,203 @@ +import type { RpcConnector } from '../core/connectors.ts'; + +export default function convex( + url = process.env.CONVEX_URL || 'http://127.0.0.1:3210', +): RpcConnector { + if (!url) throw new Error('CONVEX_URL not set'); + + function isWriteConflict(msg: unknown): boolean { + if (typeof msg !== 'string') return false; + return ( + msg.includes('Documents read from or written to the') && + msg.includes('while this mutation was being run') + ); + } + + async function queryConvex(path: string, args: any) { + const res = await fetch(`${url}/api/query?format=json`, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ path, args }), + }); + + let json: any = {}; + try { + json = await res.json(); + } catch {} + + if (res.ok && json.status === 'success') { + return json.value; + } + + const msg = + json?.errorMessage ?? + json?.message ?? + `HTTP ${res.status} ${res.statusText}`; + + throw new Error(`convex query ${path} failed: ${msg}`); + } + + async function mutationConvex(path: string, args: any) { + const MAX_RETRIES = 32; + const BASE_DELAY_MS = 0.1; + const MAX_DELAY_MS = 100; + + for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) { + const res = await fetch(`${url}/api/mutation?format=json`, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ path, args }), + }); + + let json: any = {}; + try { + json = await res.json(); + } catch {} + + const ok = res.ok && json.status === 'success'; + const msgRaw = + json?.errorMessage ?? + json?.message ?? + `HTTP ${res.status} ${res.statusText}`; + const msg = String(msgRaw); + const writeConflict = isWriteConflict(msg); + + if (ok) { + return json.value; + } + + if (writeConflict && attempt < MAX_RETRIES) { + const base = BASE_DELAY_MS * 2 ** attempt; + const delay = + Math.min(MAX_DELAY_MS, base) + Math.floor(Math.random() * 10); + await new Promise((r) => setTimeout(r, delay)); + continue; + } + + throw new Error(`convex mutation ${path} failed: ${msg}`); + } + + throw new Error( + `convex mutation ${path} failed after ${MAX_RETRIES} retries due to write conflicts`, + ); + } + + const root: RpcConnector = { + name: 'convex', + + async open() {}, + async close() {}, + + async call(method: string, args?: Record) { + return mutationConvex(method, args ?? {}); + }, + + async getAccount(id: number) { + const row = await queryConvex('accounts:get_account', { id }); + if (!row) return null; + return { + id: Number(row.id), + balance: BigInt(row.balance), + }; + }, + + async verify() { + const rawInitial = process.env.SEED_INITIAL_BALANCE; + if (!rawInitial) { + console.warn( + '[convex] SEED_INITIAL_BALANCE not set; skipping verification', + ); + return; + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + console.error( + `[convex] invalid SEED_INITIAL_BALANCE=${rawInitial}; expected integer`, + ); + return; + } + + const BATCH = 64; + + let count = 0n; + let total = 0n; + let changed = 0n; + let nextId = 0; + + for (;;) { + const ids = Array.from({ length: BATCH }, (_, i) => nextId + i); + + const rows = await Promise.all( + ids.map((id) => queryConvex('accounts:get_account', { id })), + ); + + let hitHole = false; + let sawAny = false; + + for (const acc of rows) { + if (!acc) { + hitHole = true; + break; + } + + sawAny = true; + count++; + + const bal = BigInt(acc.balance); + total += bal; + if (bal !== initial) changed++; + } + + if (!sawAny || hitHole) break; + + nextId += BATCH; + } + + if (count === 0n) { + console.error('[convex] verify failed: no accounts found'); + throw new Error('convex verification failed: no accounts found'); + } + + const expected = initial * count; + + // 1) total must be conserved + if (total !== expected) { + console.error( + `[convex] verify failed: accounts=${count} total_balance=${total} expected=${expected}`, + ); + throw new Error('convex verification failed: total_balance mismatch'); + } + + // 2) at least one row must have changed + if (changed === 0n) { + console.error( + '[convex] verify failed: total preserved but no balances changed', + ); + throw new Error( + 'convex verification failed: no account balances changed', + ); + } + + console.log( + `[convex] verify ok: accounts=${count} total_balance=${total} changed=${changed}`, + ); + }, + + async createWorker(): Promise { + const worker: any = convex(url); + await worker.open(); + worker.verify = async () => { + throw new Error( + 'verify() not supported on convex worker connector; call verify() on the root connector instead', + ); + }; + delete worker.createWorker; + return worker as RpcConnector; + }, + }; + + return root; +} diff --git a/templates/keynote-2/src/connectors/direct/cockroach.ts b/templates/keynote-2/src/connectors/direct/cockroach.ts new file mode 100644 index 00000000000..8fd7a2fc4eb --- /dev/null +++ b/templates/keynote-2/src/connectors/direct/cockroach.ts @@ -0,0 +1,228 @@ +import { Pool, PoolClient } from 'pg'; +import type { SqlConnector } from '../../core/connectors.ts'; +import { poolMax } from '../../helpers.ts'; + +export default function cockroach(url = process.env.CRDB_URL!): SqlConnector { + let pool: Pool | undefined; + + async function ensurePool(workers?: number): Promise { + if (!url) throw new Error('CRDB_URL not set'); + if (!pool) { + pool = new Pool({ + connectionString: url, + application_name: 'rtt-cli', + max: poolMax(workers, 'MAX_POOL', 1000), + }); + } + return pool; + } + + async function rootExec(sql: string, params?: unknown[]) { + const p = await ensurePool(); + const res = await p.query(sql, params as any[]); + return res.rows as any; + } + + const root: SqlConnector = { + name: 'cockroach', + + async open(workers?: number) { + const p = await ensurePool(workers); + const client = await p.connect(); + try { + await client.query('SELECT 1'); + } finally { + client.release(); + } + }, + + async close() { + if (pool) { + await pool.end(); + pool = undefined; + } + }, + + exec: rootExec, + + async begin() { + throw new Error( + 'Transactions not supported on root cockroach connector; use worker connections (createWorker) instead', + ); + }, + + async commit() { + throw new Error( + 'Transactions not supported on root cockroach connector; use worker connections (createWorker) instead', + ); + }, + + async rollback() { + throw new Error( + 'Transactions not supported on root cockroach connector; use worker connections (createWorker) instead', + ); + }, + + async getAccount(id: number) { + const rows = await rootExec( + 'SELECT id, balance FROM accounts WHERE id = $1', + [id], + ); + if (!rows || rows.length === 0) return null; + return { + id: Number(rows[0].id), + balance: BigInt(rows[0].balance), + }; + }, + + async verify() { + const p = await ensurePool(); + + const rawInitial = process.env.SEED_INITIAL_BALANCE; + if (!rawInitial) { + console.warn( + '[cockroach] SEED_INITIAL_BALANCE not set; skipping verification', + ); + return; + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + console.error( + `[cockroach] invalid SEED_INITIAL_BALANCE=${rawInitial}; expected integer`, + ); + return; + } + + const r = await p.query( + ` + SELECT + COUNT(*)::bigint AS count, + COALESCE(SUM(balance), 0)::bigint AS total, + SUM(CASE WHEN balance != $1::bigint THEN 1 ELSE 0 END)::bigint AS changed + FROM accounts + `, + [initial.toString()], + ); + + const row = r.rows[0] as { + count: string | number | bigint; + total: string | number | bigint; + changed: string | number | bigint; + }; + + const count = BigInt(row.count); + const total = BigInt(row.total); + const changed = BigInt(row.changed); + const expected = initial * count; + + if (count === 0n) { + console.error('[cockroach] verify failed: accounts=0'); + throw new Error('cockroach verification failed: no accounts'); + } + + if (total !== expected) { + console.error( + `[cockroach] verify failed: accounts=${count} total_balance=${total} expected=${expected}`, + ); + throw new Error( + 'cockroach verification failed: total_balance mismatch', + ); + } + + if (changed === 0n) { + console.error( + `[cockroach] verify failed: all ${count} accounts still at initial balance=${initial}; workload may not have executed`, + ); + throw new Error('cockroach verification failed: no balances changed'); + } + + console.log( + `[cockroach] verify ok: accounts=${count} total_balance=${total} changed=${changed}`, + ); + }, + + async createWorker() { + const p = await ensurePool(); + + // This worker keeps track of a client only for the lifetime of a txn. + let client: PoolClient | null = null; + + async function ensureClient(): Promise { + if (!client) { + client = await p.connect(); + } + return client; + } + + const worker: SqlConnector = { + name: 'cockroach', + + async open() { + // no-op; lazy client fetch in ensureClient() + }, + + async close() { + // If a client is currently held (e.g. txn never committed/rolled back), + // release it. + if (client) { + try { + client.release(); + } catch {} + client = null; + } + }, + + async exec(sql: string, params?: unknown[]) { + const c = await ensureClient(); + const res = await c.query(sql, params as any[]); + return res.rows as any; + }, + + async begin() { + const c = await ensureClient(); + await c.query('BEGIN'); + }, + + async commit() { + if (!client) return; + await client.query('COMMIT'); + client.release(); + client = null; // free the connection back to the pool + }, + + async rollback() { + if (!client) return; + await client.query('ROLLBACK'); + client.release(); + client = null; + }, + + async getAccount(id: number) { + const c = await ensureClient(); + const r = await c.query( + 'SELECT id, balance FROM accounts WHERE id = $1', + [id], + ); + if (r.rows.length === 0) return null; + return { + id: Number(r.rows[0].id), + balance: BigInt(r.rows[0].balance), + }; + }, + + async verify() { + throw new Error( + 'verify() not supported on cockroach worker; call verify() on the root connector instead', + ); + }, + }; + + return worker; + }, + }; + + return root; +} diff --git a/templates/keynote-2/src/connectors/direct/postgres.ts b/templates/keynote-2/src/connectors/direct/postgres.ts new file mode 100644 index 00000000000..81609ec62a2 --- /dev/null +++ b/templates/keynote-2/src/connectors/direct/postgres.ts @@ -0,0 +1,202 @@ +import { Pool, PoolClient } from 'pg'; +import type { SqlConnector } from '../../core/connectors.ts'; +import { poolMax } from '../../helpers.ts'; + +export default function postgres(url = process.env.PG_URL!): SqlConnector { + let pool: Pool | undefined; + + const root: SqlConnector = { + name: 'postgres', + + async open(workers?: number) { + if (!url) throw new Error('PG_URL not set'); + if (pool) return; // idempotent + + pool = new Pool({ + connectionString: url, + application_name: 'rtt-cli', + max: poolMax(workers, 'MAX_POOL', 1000), + }); + + // Simple connectivity check + await pool.query('SELECT 1'); + }, + + async close() { + if (pool) { + await pool.end(); + pool = undefined; + } + }, + + async exec(sql: string, params?: unknown[]) { + if (!pool) throw new Error('Postgres pool not initialized'); + const res = await pool.query(sql, params as any[]); + return res.rows as any; + }, + + async begin() { + throw new Error( + 'postgres.begin on the root connector is not supported in pooled mode; use worker connectors instead', + ); + }, + + async commit() { + throw new Error( + 'postgres.commit on the root connector is not supported in pooled mode; use worker connectors instead', + ); + }, + + async rollback() { + throw new Error( + 'postgres.rollback on the root connector is not supported in pooled mode; use worker connectors instead', + ); + }, + + async getAccount(id: number) { + if (!pool) throw new Error('Postgres pool not initialized'); + const r = await pool.query( + 'SELECT id, balance FROM accounts WHERE id = $1', + [id], + ); + if (r.rows.length === 0) return null; + return { + id: Number(r.rows[0].id), + balance: BigInt(r.rows[0].balance), + }; + }, + + async verify() { + if (!pool) throw new Error('Postgres pool not initialized'); + + const rawInitial = process.env.SEED_INITIAL_BALANCE; + if (!rawInitial) { + console.warn( + '[postgres] SEED_INITIAL_BALANCE not set; skipping verification', + ); + return; + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + console.error( + `[postgres] invalid SEED_INITIAL_BALANCE=${rawInitial}; expected integer`, + ); + return; + } + + const r = await pool.query( + ` + SELECT + COUNT(*)::bigint AS count, + COALESCE(SUM(balance), 0)::bigint AS total, + SUM(CASE WHEN balance != $1::bigint THEN 1 ELSE 0 END)::bigint AS changed + FROM accounts + `, + [initial.toString()], + ); + + const row = r.rows[0] as { + count: string | number | bigint; + total: string | number | bigint; + changed: string | number | bigint; + }; + + const count = BigInt(row.count); + const total = BigInt(row.total); + const changed = BigInt(row.changed); + const expected = initial * count; + + if (count === 0n) { + console.error('[postgres] verify failed: accounts=0'); + throw new Error('postgres verification failed: no accounts'); + } + + // 1) total must be conserved + if (total !== expected) { + console.error( + `[postgres] verify failed: accounts=${count} total_balance=${total} expected=${expected}`, + ); + throw new Error('postgres verification failed: total_balance mismatch'); + } + + // 2) at least one row must have changed + if (changed === 0n) { + console.error( + '[postgres] verify failed: total preserved but no balances changed', + ); + throw new Error( + 'postgres verification failed: no account balances changed', + ); + } + + console.log( + `[postgres] verify ok: accounts=${count} total_balance=${total} changed=${changed}`, + ); + }, + }; + + root.createWorker = async (): Promise => { + if (!pool) throw new Error('Postgres pool not initialized'); + + const client: PoolClient = await pool.connect(); + + const exec: SqlConnector['exec'] = async ( + sql: string, + params?: unknown[], + ) => { + const res = await client.query(sql, params as any[]); + return res.rows as any; + }; + + const workerConnector: SqlConnector = { + name: 'postgres', + + // No-op: worker is ready once created. + async open() {}, + + async close() { + client.release(); + }, + + exec, + + async begin() { + await client.query('BEGIN'); + }, + + async commit() { + await client.query('COMMIT'); + }, + + async rollback() { + await client.query('ROLLBACK'); + }, + + async getAccount(id: number) { + const r = await client.query( + 'SELECT id, balance FROM accounts WHERE id = $1', + [id], + ); + if (r.rows.length === 0) return null; + return { + id: Number(r.rows[0].id), + balance: BigInt(r.rows[0].balance), + }; + }, + + async verify() { + // Should never be called; verification is done on the root connector. + throw new Error( + 'postgres worker connector does not support verify(); call verify() on the root connector', + ); + }, + }; + + return workerConnector; + }; + + return root; +} diff --git a/templates/keynote-2/src/connectors/direct/sqlite.ts b/templates/keynote-2/src/connectors/direct/sqlite.ts new file mode 100644 index 00000000000..f81ca58f625 --- /dev/null +++ b/templates/keynote-2/src/connectors/direct/sqlite.ts @@ -0,0 +1,227 @@ +import Database from 'better-sqlite3'; +import type { + Database as BetterSqlite3Database, + Statement, +} from 'better-sqlite3'; +import type { SqlConnector } from '../../core/connectors.ts'; +import { + applySqlitePragmas, + ensureSqliteDirExists, + getSqliteMode, + sqliteModeRank, +} from '../sqlite_common.ts'; + +export default function sqlite( + file = process.env.SQLITE_FILE || './.data/sqlite/accounts.sqlite', +): SqlConnector { + let db: BetterSqlite3Database | undefined; + const mode = getSqliteMode(); + console.log(`[sqlite] mode=${mode}`); + + const useStmtCache = + sqliteModeRank(mode) >= sqliteModeRank('realistic_cached'); + + const stmtCache = new Map(); + + function assertDb(): BetterSqlite3Database { + if (!db) throw new Error('SQLite (better-sqlite3) not connected'); + return db; + } + + function getStmt(sql: string): Statement { + const d = assertDb(); + + if (!useStmtCache) { + // no caching: always prepare a fresh statement + return d.prepare(sql); + } + + const key = sql; + const cached = stmtCache.get(key); + if (cached) return cached; + + const stmt = d.prepare(sql); + stmtCache.set(key, stmt); + return stmt; + } + + const root: SqlConnector = { + name: 'sqlite', + + async open() { + if (db) return; + + console.log(`[sqlite] opening file=${file} mode=${mode}`); + + await ensureSqliteDirExists(file, mode); + db = mode === 'fastest' ? new Database(':memory:') : new Database(file); + + applySqlitePragmas(db, mode); + + db.exec(` + CREATE TABLE IF NOT EXISTS accounts ( + id INTEGER PRIMARY KEY, + balance INTEGER NOT NULL + ); + `); + + const row = db.prepare('SELECT COUNT(*) AS c FROM accounts').get() as { c: number }; + console.log('[sqlite] accounts count=', row.c); + }, + + async close() { + stmtCache.clear(); + if (db) { + db.close(); + db = undefined; + } + }, + + async exec(sql: string, params?: unknown[]): Promise { + const text = sql.trim(); + const upper = text.toUpperCase(); + const args = (params ?? []) as any[]; + + const stmt = getStmt(text); + + const bind: Record = {}; + for (let i = 0; i < args.length; i++) { + bind[String(i + 1)] = args[i]; + } + + if ( + upper.startsWith('BEGIN') || + upper.startsWith('COMMIT') || + upper.startsWith('ROLLBACK') + ) { + stmt.run(bind); + return []; + } + + const rows = stmt.all(bind); + return rows as unknown[]; + }, + + async begin() { + assertDb().exec('BEGIN'); + }, + + async commit() { + assertDb().exec('COMMIT'); + }, + + async rollback() { + assertDb().exec('ROLLBACK'); + }, + + async getAccount(id: number) { + const d = assertDb(); + const stmt = d.prepare('SELECT id, balance FROM accounts WHERE id = ?'); + const row = stmt.get(id) as { id: number; balance: number } | undefined; + if (!row) return null; + + return { + id: row.id, + balance: BigInt(row.balance), + }; + }, + + async verify() { + const db = assertDb(); + + const rawInitial = process.env.SEED_INITIAL_BALANCE; + if (!rawInitial) { + console.warn( + '[sqlite] SEED_INITIAL_BALANCE not set; skipping verification', + ); + return; + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + console.error( + `[sqlite] invalid SEED_INITIAL_BALANCE=${rawInitial}; expected integer`, + ); + return; + } + + const row = db + .prepare( + ` + SELECT + COUNT(*) AS count, + COALESCE(SUM(balance), 0) AS total, + SUM(CASE WHEN balance != ? THEN 1 ELSE 0 END) AS changed + FROM accounts + `, + ) + .get(initial.toString()) as + | { count: number; total: number; changed: number } + | undefined; + + const count = BigInt(row?.count ?? 0); + const total = BigInt(row?.total ?? 0); + const changed = BigInt(row?.changed ?? 0); + const expected = initial * count; + + if (count === 0n) { + console.error('[sqlite] verify failed: accounts=0'); + throw new Error('sqlite verification failed: no accounts'); + } + + // 1) total must be conserved + if (total !== expected) { + console.error( + `[sqlite] verify failed: accounts=${count} total_balance=${total} expected=${expected}`, + ); + throw new Error('sqlite verification failed: total_balance mismatch'); + } + + // 2) at least one row must have changed + if (changed === 0n) { + console.error( + `[sqlite] verify failed: all ${count} accounts still at initial balance=${initial}; workload may not have executed`, + ); + throw new Error('sqlite verification failed: no balances changed'); + } + + console.log( + `[sqlite] verify ok: accounts=${count} total_balance=${total} changed=${changed}`, + ); + }, + }; + + (root as any).createWorker = async (): Promise => { + await root.open(); // ensure db exists once + + const worker: SqlConnector = { + name: 'sqlite', + + async open() { + // no-op; root.open() already created the db + }, + + async close() { + // no-op; root.close() will actually close the db + }, + + exec: root.exec, + begin: root.begin, + commit: root.commit, + rollback: root.rollback, + getAccount: root.getAccount, + + async verify() { + throw new Error( + 'sqlite worker connector does not support verify(); call verify() on the root connector instead', + ); + }, + }; + + return worker; + }; + + return root; +} diff --git a/templates/keynote-2/src/connectors/drizzle/cockroach_drizzle.ts b/templates/keynote-2/src/connectors/drizzle/cockroach_drizzle.ts new file mode 100644 index 00000000000..ba27003e6b3 --- /dev/null +++ b/templates/keynote-2/src/connectors/drizzle/cockroach_drizzle.ts @@ -0,0 +1,278 @@ +import { Pool } from 'pg'; +import { drizzle } from 'drizzle-orm/node-postgres'; +import { eq, inArray, sql } from 'drizzle-orm'; +import type { RpcConnector } from '../../core/connectors.ts'; +import { crdbAccounts as accounts } from '../../drizzle/schema.ts'; +import { poolMax } from '../../helpers.ts'; + +type Db = ReturnType; + +export function cockroach_drizzle(url = process.env.CRDB_URL!): RpcConnector { + let pool: Pool | null = null; + let db: Db | null = null; + + function requireDb(): Db { + if (!db) throw new Error('[cockroach_drizzle] not connected'); + return db; + } + + async function open(workers?: number) { + if (!url) throw new Error('CRDB_URL not set'); + + // Only create the pool once; subsequent open() calls are no-ops. + if (pool) return; + + pool = new Pool({ + connectionString: url, + application_name: 'rtt-crdb-drizzle', + max: poolMax(workers, 'MAX_POOL', 1000), + }); + + db = drizzle(pool, { schema: { accounts } }); + await pool.query('SELECT 1'); + } + + async function close() { + if (pool) { + await pool.end(); + pool = null; + db = null; + } + } + + async function transfer(args: Record) { + const fromId = Number(args.from_id ?? args.from); + const toId = Number(args.to_id ?? args.to); + const amount = Number(args.amount); + + if (fromId === toId || amount <= 0) return; + + const delta = BigInt(amount); + const dbi = requireDb(); + + await dbi.transaction(async (tx) => { + const rows = await tx + .select() + .from(accounts) + .where(inArray(accounts.id, [fromId, toId])) + .for('update') + .orderBy(accounts.id); + + if (rows.length !== 2) { + throw new Error('[cockroach_drizzle] account_missing'); + } + + const [first, second] = rows; + const fromRow = first.id === fromId ? first : second; + const toRow = first.id === fromId ? second : first; + + if (fromRow.balance < delta) { + return; + } + + await tx + .update(accounts) + .set({ balance: fromRow.balance - delta }) + .where(eq(accounts.id, fromId)); + + await tx + .update(accounts) + .set({ balance: toRow.balance + delta }) + .where(eq(accounts.id, toId)); + }); + } + + async function getAccount(args: Record) { + const id = Number(args.id); + if (!Number.isInteger(id)) + throw new Error('[cockroach_drizzle] invalid id'); + + const dbi = requireDb(); + const rows = await dbi + .select() + .from(accounts) + .where(eq(accounts.id, id)) + .limit(1); + + if (rows.length === 0) return null; + const row = rows[0]!; + return { + id: row.id, + balance: BigInt(row.balance), + }; + } + + async function verify() { + const dbi = requireDb(); + + const rawInitial = process.env.SEED_INITIAL_BALANCE; + if (!rawInitial) { + console.warn( + '[cockroach_drizzle] SEED_INITIAL_BALANCE not set; skipping verify', + ); + return { skipped: true }; + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + throw new Error( + `[cockroach_drizzle] invalid SEED_INITIAL_BALANCE=${rawInitial}`, + ); + } + + const result = await dbi.execute( + sql` + SELECT COUNT(*) ::bigint AS count, + COALESCE(SUM("balance"), 0)::bigint AS total, + SUM( + CASE WHEN "balance" != ${initial}::bigint THEN 1 ELSE 0 END + )::bigint AS changed + FROM ${accounts} + `, + ); + + const row = (result as any).rows[0] as { + count: string | number | bigint; + total: string | number | bigint; + changed: string | number | bigint; + }; + + const count = BigInt(row.count); + const total = BigInt(row.total); + const changed = BigInt(row.changed); + const expected = initial * count; + + if (count === 0n) { + throw new Error('[cockroach_drizzle] verify failed: accounts=0'); + } + if (total !== expected) { + throw new Error( + `[cockroach_drizzle] verify failed: accounts=${count} total=${total} expected=${expected}`, + ); + } + if (changed === 0n) { + throw new Error( + '[cockroach_drizzle] verify failed: total preserved but no balances changed', + ); + } + + return { + accounts: count.toString(), + total: total.toString(), + changed: changed.toString(), + }; + } + + async function seed() { + const dbi = requireDb(); + + const count = Number(process.env.SEED_ACCOUNTS); + const rawInitial = process.env.SEED_INITIAL_BALANCE; + if (!rawInitial) { + console.warn( + '[cockroach_drizzle] SEED_INITIAL_BALANCE not set; skipping verification', + ); + return; + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + console.error( + `[cockroach_drizzle] invalid SEED_INITIAL_BALANCE=${rawInitial}; expected integer`, + ); + return; + } + await dbi.transaction(async (tx) => { + await tx.execute(sql`DELETE + FROM ${accounts}`); + + const batchSize = 10_000; + for (let start = 0; start < count; start += batchSize) { + const end = Math.min(start + batchSize, count); + const values: { id: number; balance: bigint }[] = []; + + for (let id = start; id < end; id++) { + values.push({ id, balance: initial }); + } + + await tx.insert(accounts).values(values as any); + } + }); + + console.log( + `[cockroach_drizzle] seeded accounts: count=${count} initial=${initial.toString()}`, + ); + } + + const root: RpcConnector = { + name: 'cockroach_drizzle', + + async open(workers?: number) { + await open(workers); + }, + + async close() { + await close(); + }, + + async getAccount(id: number) { + return getAccount({ id }); + }, + + async verify() { + await verify(); + }, + + async call(name: string, args?: Record): Promise { + const a = args ?? {}; + switch (name) { + case 'transfer': + return transfer(a); + case 'getAccount': + return getAccount(a); + case 'verify': + return verify(); + case 'seed': + return seed(); + default: + throw new Error(`[cockroach_drizzle] unknown RPC method: ${name}`); + } + }, + + async createWorker(): Promise { + await open(); // no-op if already open + + const worker: RpcConnector = { + name: 'cockroach_drizzle_worker', + + async open() {}, + + async call(name: string, args?: Record) { + return root.call(name, args); + }, + + async getAccount(id: number) { + return root.getAccount(id); + }, + + async verify() { + throw new Error( + 'verify() not supported on cockroach_drizzle worker connector; call verify() on the root connector instead', + ); + }, + + async close() { + // no-op; root.close() will shut down the pool + }, + }; + + return worker; + }, + }; + + return root; +} diff --git a/templates/keynote-2/src/connectors/drizzle/postgres_drizzle.ts b/templates/keynote-2/src/connectors/drizzle/postgres_drizzle.ts new file mode 100644 index 00000000000..db348df5740 --- /dev/null +++ b/templates/keynote-2/src/connectors/drizzle/postgres_drizzle.ts @@ -0,0 +1,283 @@ +import { Pool } from 'pg'; +import { drizzle } from 'drizzle-orm/node-postgres'; +import { eq, inArray } from 'drizzle-orm'; +import { pgAccounts as accounts } from '../../drizzle/schema.ts'; +import { RpcConnector } from '../../core/connectors.ts'; +import { poolMax } from '../../helpers.ts'; + +type Db = ReturnType; + +type PostgresConnector = RpcConnector & { + createWorker?(opts: { index: number; total: number }): Promise; +}; + +export default function postgres_drizzle( + url = process.env.PG_URL!, +): PostgresConnector { + let pool: Pool | null = null; + let db: Db | null = null; + + async function getDb(): Promise { + if (!db) throw new Error('[postgres_drizzle] not connected'); + return db; + } + + async function open(workers?: number) { + if (pool) return; // already created + + if (!url) throw new Error('PG_URL not set'); + + pool = new Pool({ + connectionString: url, + application_name: 'rtt-pg-drizzle', + max: poolMax(workers, 'MAX_POOL', 1000), + }); + + db = drizzle(pool, { schema: { accounts } }); + } + + async function close() { + await pool?.end(); + pool = null; + db = null; + } + + async function transfer(args: Record) { + const fromId = Number(args.from_id ?? args.from); + const toId = Number(args.to_id ?? args.to); + const amount = Number(args.amount); + + if (fromId === toId || amount <= 0) return; + + const delta = BigInt(amount); + const dbi = await getDb(); + + await dbi.transaction(async (tx) => { + const rows = await tx + .select() + .from(accounts) + .where(inArray(accounts.id, [fromId, toId])) + .for('update') + .orderBy(accounts.id); + + if (rows.length !== 2) { + throw new Error('account_missing'); + } + + const [first, second] = rows; + const fromRow = first.id === fromId ? first : second; + const toRow = first.id === fromId ? second : first; + + if (fromRow.balance < delta) { + return; + } + + await tx + .update(accounts) + .set({ balance: fromRow.balance - delta }) + .where(eq(accounts.id, fromId)); + + await tx + .update(accounts) + .set({ balance: toRow.balance + delta }) + .where(eq(accounts.id, toId)); + }); + } + + async function getAccount(args: Record) { + const id = Number(args.id); + if (!Number.isInteger(id)) throw new Error('invalid id'); + + const dbi = await getDb(); + const rows = await dbi + .select() + .from(accounts) + .where(eq(accounts.id, id)) + .limit(1); + + if (rows.length === 0) return null; + const row = rows[0]!; + return { + id: row.id, + balance: BigInt(row.balance), + }; + } + + async function verify() { + const dbi = await getDb(); + + const rawInitial = process.env.SEED_INITIAL_BALANCE; + if (!rawInitial) { + console.warn( + '[postgres_drizzle] SEED_INITIAL_BALANCE not set; skipping verify', + ); + return { skipped: true }; + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + throw new Error( + `[postgres_drizzle] invalid SEED_INITIAL_BALANCE=${rawInitial}`, + ); + } + + const rows = await dbi.select().from(accounts); + if (rows.length === 0) { + throw new Error('[postgres_drizzle] verify failed: accounts=0'); + } + + let total = 0n; + let changed = 0n; + + for (const row of rows) { + const bal = BigInt(row.balance); + total += bal; + if (bal !== initial) changed++; + } + + const count = BigInt(rows.length); + const expected = initial * count; + + if (total !== expected) { + throw new Error( + `[postgres_drizzle] verify failed: accounts=${count} total=${total} expected=${expected}`, + ); + } + + if (changed === 0n) { + throw new Error( + '[postgres_drizzle] verify failed: total preserved but no balances changed', + ); + } + + return { + accounts: count.toString(), + total: total.toString(), + changed: changed.toString(), + }; + } + + async function seed(args: Record) { + const dbi = await getDb(); + + const count = Number(args.accounts ?? process.env.SEED_ACCOUNTS ?? '0'); + const rawInitial = + (args.initialBalance as string | number | undefined) ?? + process.env.SEED_INITIAL_BALANCE; + + if (!Number.isInteger(count) || count <= 0) { + throw new Error('[postgres_drizzle] invalid accounts for seed'); + } + if (rawInitial === undefined || rawInitial === null) { + throw new Error('[postgres_drizzle] missing initialBalance for seed'); + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + throw new Error( + `[postgres_drizzle] invalid initialBalance=${rawInitial}`, + ); + } + + await dbi.transaction(async (tx) => { + await tx.delete(accounts); + + const values = []; + for (let id = 0; id < count; id++) { + values.push({ id, balance: initial }); + } + + await tx.insert(accounts).values(values); + }); + + console.log( + `[postgres_drizzle] seeded accounts: count=${count} initial=${initial.toString()}`, + ); + } + + const root: PostgresConnector = { + name: 'postgres_drizzle', + + async open() { + await open(); + }, + + async close() { + await close(); + }, + + async getAccount(id: number) { + return getAccount({ id }); + }, + + async verify() { + await verify(); + }, + + async call(name: string, args?: Record): Promise { + switch (name) { + case 'transfer': + return transfer(args ?? {}); + case 'getAccount': + return getAccount(args ?? {}); + case 'verify': + return verify(); + case 'seed': + return seed(args ?? {}); + default: + throw new Error(`[postgres_drizzle] unknown RPC method: ${name}`); + } + }, + + async createWorker(): Promise { + await root.open(); + + const worker: RpcConnector = { + name: 'postgres_drizzle', + + async open() { + // no-op; uses shared pool/db + }, + + async close() { + // no-op; root owns the pool + }, + + async getAccount(id: number) { + return getAccount({ id }); + }, + + async verify() { + throw new Error( + 'verify() not supported on postgres_drizzle worker connector; call verify() on the root connector instead', + ); + }, + + async call( + name: string, + args?: Record, + ): Promise { + switch (name) { + case 'transfer': + return transfer(args ?? {}); + case 'getAccount': + return getAccount(args ?? {}); + // seed/verify kept on root only + default: + throw new Error( + `[postgres_drizzle worker] unsupported RPC method: ${name}`, + ); + } + }, + }; + + return worker; + }, + }; + + return root; +} diff --git a/templates/keynote-2/src/connectors/drizzle/sqlite_drizzle.ts b/templates/keynote-2/src/connectors/drizzle/sqlite_drizzle.ts new file mode 100644 index 00000000000..dbfcfea35ae --- /dev/null +++ b/templates/keynote-2/src/connectors/drizzle/sqlite_drizzle.ts @@ -0,0 +1,316 @@ +import Database from 'better-sqlite3'; +import type { Database as BetterSqlite3Database } from 'better-sqlite3'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import { eq, inArray } from 'drizzle-orm'; +import type { RpcConnector } from '../../core/connectors.ts'; +import { sqliteAccounts as accounts } from '../../drizzle/schema.ts'; +import { + applySqlitePragmas, + ensureSqliteDirExists, + getSqliteMode, +} from '../sqlite_common.ts'; + +type Db = ReturnType; +const SQLITE_FILE = process.env.SQLITE_FILE || './.data/accounts.sqlite'; + +type SqliteConnector = RpcConnector & { + createWorker?(opts: { index: number; total: number }): Promise; +}; + +export default function sqlite_drizzle( + file: string = SQLITE_FILE, +): SqliteConnector { + const mode = getSqliteMode(); + console.log(`[sqlite_drizzle] mode=${mode}`); + + let dbFile: BetterSqlite3Database | null = null; + let db: Db | null = null; + + function assertDb(): Db { + if (!db) throw new Error('[sqlite_drizzle] not connected'); + return db; + } + + function assertDbFile(): BetterSqlite3Database { + if (!dbFile) throw new Error('[sqlite_drizzle] db file not open'); + return dbFile; + } + + async function openInternal() { + if (db && dbFile) return; // idempotent + + await ensureSqliteDirExists(file, mode); + + dbFile = mode === 'fastest' ? new Database(':memory:') : new Database(file); + + applySqlitePragmas(dbFile, mode); + + // Same table shape as direct sqlite connector + dbFile.exec(` + CREATE TABLE IF NOT EXISTS accounts ( + id INTEGER PRIMARY KEY, + balance INTEGER NOT NULL + ); + `); + + db = drizzle(dbFile, { schema: { accounts } }); + } + + async function closeInternal() { + if (dbFile) { + dbFile.close(); + dbFile = null; + } + db = null; + } + + // -------- core RPC methods (transfer / getAccount / verify / seed) -------- + + async function transfer(args: Record) { + const fromId = Number(args.from_id ?? args.from); + const toId = Number(args.to_id ?? args.to); + const amount = Number(args.amount); + + if (fromId === toId || amount <= 0) return; + + const delta = amount; + const dbi = assertDb(); + + dbi.transaction((tx) => { + const rows = tx + .select() + .from(accounts) + .where(inArray(accounts.id, [fromId, toId])) + .all(); + + if (rows.length !== 2) { + throw new Error('account_missing'); + } + + const [first, second] = rows; + const fromRow = first.id === fromId ? first : second; + const toRow = first.id === fromId ? second : first; + + if (fromRow.balance < delta) { + return; + } + + tx.update(accounts) + .set({ balance: fromRow.balance - delta }) + .where(eq(accounts.id, fromId)); + + tx.update(accounts) + .set({ balance: toRow.balance + delta }) + .where(eq(accounts.id, toId)); + }); + } + + async function getAccount(args: Record) { + const id = Number(args.id); + if (!Number.isInteger(id)) throw new Error('invalid id'); + + const dbi = assertDb(); + + const row = dbi + .select() + .from(accounts) + .where(eq(accounts.id, id)) + .limit(1) + .get(); + + if (!row) return null; + + return { + id: row.id, + balance: BigInt(row.balance), + }; + } + + async function verify() { + const dbf = assertDbFile(); + + const rawInitial = process.env.SEED_INITIAL_BALANCE; + if (!rawInitial) { + console.warn( + '[sqlite_drizzle] SEED_INITIAL_BALANCE not set; skipping verification', + ); + return; + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + console.error( + `[sqlite_drizzle] invalid SEED_INITIAL_BALANCE=${rawInitial}; expected integer`, + ); + return; + } + + // Use the same aggregate query as direct sqlite connector + const row = dbf + .prepare( + ` + SELECT + COUNT(*) AS count, + COALESCE(SUM(balance), 0) AS total, + SUM(CASE WHEN balance != ? THEN 1 ELSE 0 END) AS changed + FROM accounts + `, + ) + .get(initial.toString()) as + | { count: number; total: number; changed: number } + | undefined; + + const count = BigInt(row?.count ?? 0); + const total = BigInt(row?.total ?? 0); + const changed = BigInt(row?.changed ?? 0); + const expected = initial * count; + + if (count === 0n) { + console.error('[sqlite_drizzle] verify failed: accounts=0'); + throw new Error('sqlite_drizzle verification failed: no accounts'); + } + + if (total !== expected) { + console.error( + `[sqlite_drizzle] verify failed: accounts=${count} total_balance=${total} expected=${expected}`, + ); + throw new Error( + 'sqlite_drizzle verification failed: total_balance mismatch', + ); + } + + if (changed === 0n) { + console.error( + `[sqlite_drizzle] verify failed: all ${count} accounts still at initial balance=${initial}; workload may not have executed`, + ); + throw new Error( + 'sqlite_drizzle verification failed: no balances changed', + ); + } + + console.log( + `[sqlite_drizzle] verify ok: accounts=${count} total_balance=${total} changed=${changed}`, + ); + } + + async function seed(args: Record) { + const dbf = assertDbFile(); + + const rawCount = args.accounts ?? process.env.SEED_ACCOUNTS ?? '0'; + const count = Number(rawCount); + const rawInitial = + (args.initialBalance as string | number | undefined) ?? + process.env.SEED_INITIAL_BALANCE; + + if (!Number.isInteger(count) || count <= 0) { + throw new Error('[sqlite_drizzle] invalid accounts for seed'); + } + if (rawInitial === undefined || rawInitial === null) { + throw new Error('[sqlite_drizzle] missing initialBalance for seed'); + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + throw new Error( + `[sqlite_drizzle] invalid initialBalance=${String(rawInitial)}`, + ); + } + + // Seed like a realistic app: simple delete + batched inserts + dbf.exec('DELETE FROM accounts'); + + const insert = dbf.prepare( + 'INSERT INTO accounts (id, balance) VALUES (?, ?)', + ); + + const batchSize = 10_000; + for (let start = 0; start < count; start += batchSize) { + const end = Math.min(start + batchSize, count); + dbf.transaction(() => { + for (let id = start; id < end; id++) { + insert.run(id, initial.toString()); + } + })(); + } + + console.log( + `[sqlite_drizzle] seeded accounts: count=${count} initial=${initial.toString()}`, + ); + } + + // ---------- RpcConnector implementation ---------------------------- + + const root: SqliteConnector = { + name: 'sqlite_drizzle', + + async open() { + await openInternal(); + }, + + async close() { + await closeInternal(); + }, + + async getAccount(id: number) { + return getAccount({ id }); + }, + + async verify() { + await verify(); + }, + + async call(name: string, args?: Record): Promise { + const a = args ?? {}; + switch (name) { + case 'transfer': + return transfer(a); + case 'getAccount': + return getAccount(a); + case 'verify': + return verify(); + case 'seed': + return seed(a); + default: + throw new Error(`[sqlite_drizzle] unknown RPC method: ${name}`); + } + }, + + async createWorker(): Promise { + await root.open(); + + const worker: RpcConnector = { + name: 'sqlite_drizzle_worker', + + async open() { + // no-op; root.open() already initialized DB + }, + + async close() { + // no-op; root.close() tears it down + }, + + async call(name: string, args?: Record) { + return root.call(name, args); + }, + + async getAccount(id: number) { + return root.getAccount(id); + }, + + async verify() { + throw new Error( + 'verify() not supported on sqlite_drizzle worker connector; call verify() on the root connector instead', + ); + }, + }; + + return worker; + }, + }; + + return root; +} diff --git a/templates/keynote-2/src/connectors/index.ts b/templates/keynote-2/src/connectors/index.ts new file mode 100644 index 00000000000..373ca1599aa --- /dev/null +++ b/templates/keynote-2/src/connectors/index.ts @@ -0,0 +1,34 @@ +import postgres from './direct/postgres.ts'; +import cockroach from './direct/cockroach.ts'; +import sqlite from './direct/sqlite.ts'; +import supabase from './supabase.ts'; +import convex from './convex.ts'; +import { spacetimedb } from './spacetimedb.ts'; +import bun from './bun.ts'; +import postgres_drizzle from './drizzle/postgres_drizzle.ts'; +import { cockroach_drizzle } from './drizzle/cockroach_drizzle.ts'; +import sqlite_drizzle from './drizzle/sqlite_drizzle.ts'; +import postgres_rpc from './rpc/postgres_rpc.ts'; +import cockroach_rpc from './rpc/cockroach_rpc.ts'; +import sqlite_rpc from './rpc/sqlite_rpc.ts'; +import supabase_rpc from './rpc/supabase_rpc.ts'; +import planetscale_pg_rpc from './rpc/planetscale_pg_rpc.ts'; + +export const CONNECTORS = { + postgres, + cockroach, + sqlite, + supabase, + convex, + spacetimedb, + bun, + postgres_drizzle, + cockroach_drizzle, + sqlite_drizzle, + postgres_rpc, + cockroach_rpc, + sqlite_rpc, + supabase_rpc, + planetscale_pg_rpc, +}; +export type ConnectorKey = keyof typeof CONNECTORS; diff --git a/templates/keynote-2/src/connectors/rpc/cockroach_rpc.ts b/templates/keynote-2/src/connectors/rpc/cockroach_rpc.ts new file mode 100644 index 00000000000..fd706e3a4f9 --- /dev/null +++ b/templates/keynote-2/src/connectors/rpc/cockroach_rpc.ts @@ -0,0 +1,133 @@ +import type { RpcConnector } from '../../core/connectors.ts'; +import { RpcRequest, RpcResponse } from './rpc_common.ts'; + +export default function cockroach_rpc( + url = process.env.CRDB_RPC_URL || 'http://127.0.0.1:4102', +): RpcConnector { + let rpcUrl: URL | null = null; + + function ensureUrl() { + if (!url) throw new Error('CRDB_RPC_URL not set'); + if (!rpcUrl) rpcUrl = new URL('/rpc', new URL(url)); + } + + async function httpCall(name: string, args?: Record) { + ensureUrl(); + const body: RpcRequest = { name, args }; + + const res = await fetch(rpcUrl!, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(body), + }); + + const text = await res.text(); + let json: RpcResponse; + try { + json = JSON.parse(text) as RpcResponse; + } catch { + throw new Error( + `[cockroach_rpc] invalid JSON: HTTP ${res.status} ${res.statusText} body=${text.slice( + 0, + 200, + )}`, + ); + } + + if (!res.ok || !json.ok) { + throw new Error( + `[cockroach_rpc] RPC ${name} failed: HTTP ${res.status} ${res.statusText} body=${text.slice( + 0, + 200, + )}`, + ); + } + + return json.result; + } + + async function callWithRetry( + name: string, + args?: Record, + maxRetries: number = 5, + ) { + let attempts = 0; + while (attempts < maxRetries) { + try { + return await httpCall(name, args); + } catch (err: unknown) { + let errMsg = 'Unknown error'; + if (err instanceof Error) { + errMsg = err.message; + } else if (typeof err === 'string') { + errMsg = err; + } + if ( + errMsg.includes('serialization') || + errMsg.includes('restart transaction') || + errMsg.includes('40001') + ) { + attempts++; + if (attempts >= maxRetries) throw err; + continue; + } + throw err; + } + } + throw new Error('Max retries exceeded'); + } + + const root: RpcConnector = { + name: 'cockroach_rpc', + + // Only a single health check on the root. + async open() { + await httpCall('health'); + }, + + async close() { + // no-op + }, + + async getAccount(id: number) { + const result = (await httpCall('getAccount', { id })) as { + id: number; + balance: bigint; + } | null; + + if (!result) return null; + return { + id: result.id, + balance: result.balance, + }; + }, + + async verify() { + await httpCall('verify'); + }, + + async call(name: string, args?: Record) { + if (name === 'transfer') { + return callWithRetry(name, args); + } + return httpCall(name, args); + }, + + async createWorker(): Promise { + const worker = cockroach_rpc(url); + + worker.open = async () => {}; + + worker.verify = async () => { + throw new Error( + 'verify() not supported on cockroach_rpc worker connector; call verify() on the root connector instead', + ); + }; + + delete worker.createWorker; + return worker as RpcConnector; + }, + }; + + return root; +} diff --git a/templates/keynote-2/src/connectors/rpc/planetscale_pg_rpc.ts b/templates/keynote-2/src/connectors/rpc/planetscale_pg_rpc.ts new file mode 100644 index 00000000000..b39436a25f6 --- /dev/null +++ b/templates/keynote-2/src/connectors/rpc/planetscale_pg_rpc.ts @@ -0,0 +1,82 @@ +import { RpcConnector } from '../../core/connectors.ts'; +import { RpcRequest, RpcResponse } from './rpc_common.ts'; + +export default function planetscale_pg_rpc( + url = process.env.PLANETSCALE_RPC_URL || 'http://127.0.0.1:4104', +): RpcConnector { + let rpcUrl: URL | null = null; + + function ensureUrl() { + if (!url) throw new Error('PLANETSCALE_RPC_URL not set'); + if (!rpcUrl) rpcUrl = new URL('/rpc', new URL(url)); + } + + async function httpCall(name: string, args?: Record) { + ensureUrl(); + const body: RpcRequest = { name, args }; + + const res = await fetch(rpcUrl!, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(body), + }); + + if (!res.ok) { + const text = await res.text().catch(() => ''); + throw new Error( + `planetscale_pg_rpc HTTP ${res.status} ${res.statusText}${ + text ? ` - ${text}` : '' + }`, + ); + } + + const json = (await res.json()) as RpcResponse; + if (!json.ok) { + throw new Error(json.error || 'Unknown planetscale_pg_rpc error'); + } + return json.result; + } + + const root: RpcConnector = { + name: 'planetscale_pg_rpc', + + async open() { + ensureUrl(); + }, + + async close() {}, + + async verify() { + await httpCall('verify'); + }, + + async call(name: string, args?: Record) { + return httpCall(name, args); + }, + + async createWorker() { + const worker: any = planetscale_pg_rpc(url); + await worker.open(); + worker.verify = async () => { + throw new Error('verify() only on root connector'); + }; + delete worker.createWorker; + return worker as RpcConnector; + }, + + async getAccount(id: number) { + const result = (await httpCall('getAccount', { id })) as { + id: number; + balance: bigint; + } | null; + + if (!result) return null; + return { + id: result.id, + balance: result.balance, + }; + }, + }; + + return root; +} diff --git a/templates/keynote-2/src/connectors/rpc/postgres_rpc.ts b/templates/keynote-2/src/connectors/rpc/postgres_rpc.ts new file mode 100644 index 00000000000..055c374bd8d --- /dev/null +++ b/templates/keynote-2/src/connectors/rpc/postgres_rpc.ts @@ -0,0 +1,97 @@ +import type { RpcConnector } from '../../core/connectors.ts'; +import { RpcRequest, RpcResponse } from './rpc_common.ts'; + +export default function postgres_rpc( + url = process.env.PG_RPC_URL || 'http://127.0.0.1:4101', +): RpcConnector { + let rpcUrl: URL | null = null; + + function ensureUrl() { + if (!url) throw new Error('PG_RPC_URL not set'); + if (!rpcUrl) rpcUrl = new URL('/rpc', new URL(url)); + } + + async function httpCall(name: string, args?: Record) { + ensureUrl(); + const body: RpcRequest = { name, args }; + + const res = await fetch(rpcUrl!, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(body), + }); + + const text = await res.text(); + let json: RpcResponse; + try { + json = JSON.parse(text) as RpcResponse; + } catch { + throw new Error( + `[postgres_rpc] invalid JSON: HTTP ${res.status} ${res.statusText} body=${text.slice( + 0, + 200, + )}`, + ); + } + + if (!res.ok || !json.ok) { + throw new Error( + `[postgres_rpc] RPC ${name} failed: HTTP ${res.status} ${res.statusText} body=${text.slice( + 0, + 200, + )}`, + ); + } + + return json.result; + } + + const root: RpcConnector = { + name: 'postgres_rpc', + + async open() { + await httpCall('health'); + }, + + async close() { + // no-op + }, + + async getAccount(id: number) { + const result = (await httpCall('getAccount', { id })) as { + id: number; + balance: bigint; + } | null; + + if (!result) return null; + return { + id: result.id, + balance: result.balance, + }; + }, + + async verify() { + await httpCall('verify'); + }, + + async call(name: string, args?: Record) { + return httpCall(name, args); + }, + + async createWorker(): Promise { + const worker = postgres_rpc(url); + + //no need to open + + worker.verify = async () => { + throw new Error( + 'verify() not supported on postgres_rpc worker connector; call verify() on the root connector instead', + ); + }; + delete worker.createWorker; + return worker as RpcConnector; + }, + }; + + return root; +} diff --git a/templates/keynote-2/src/connectors/rpc/rpc_common.ts b/templates/keynote-2/src/connectors/rpc/rpc_common.ts new file mode 100644 index 00000000000..c6143e05e7e --- /dev/null +++ b/templates/keynote-2/src/connectors/rpc/rpc_common.ts @@ -0,0 +1,8 @@ +export type RpcRequest = { + name?: string; + args?: Record; +}; + +export type RpcResponse = + | { ok: true; result?: unknown } + | { ok: false; error: string }; diff --git a/templates/keynote-2/src/connectors/rpc/sqlite_rpc.ts b/templates/keynote-2/src/connectors/rpc/sqlite_rpc.ts new file mode 100644 index 00000000000..d39a6805b57 --- /dev/null +++ b/templates/keynote-2/src/connectors/rpc/sqlite_rpc.ts @@ -0,0 +1,97 @@ +import type { RpcConnector } from '../../core/connectors.ts'; +import { RpcRequest, RpcResponse } from './rpc_common.ts'; + +export default function sqlite_rpc( + url = process.env.SQLITE_RPC_URL || 'http://127.0.0.1:4103', +): RpcConnector { + let rpcUrl: URL | null = null; + + function ensureUrl() { + if (!url) { + throw new Error('SQLITE_RPC_URL not set'); + } + if (!rpcUrl) { + rpcUrl = new URL('/rpc', new URL(url)); + } + } + + async function httpCall(name: string, args?: Record) { + ensureUrl(); + const body: RpcRequest = { name, args }; + + let res: Response; + try { + res = await fetch(rpcUrl!, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(body), + }); + } catch (err) { + throw new Error(`[sqlite_rpc] RPC ${name} network error: ${String(err)}`); + } + + const text = await res.text(); + let json: RpcResponse; + try { + json = JSON.parse(text) as RpcResponse; + } catch { + throw new Error( + `[sqlite_rpc] RPC ${name} invalid JSON: HTTP ${res.status} ${ + res.statusText + } body=${text.slice(0, 200)}`, + ); + } + + if (!res.ok || !json.ok) { + const msg = + !json.ok && json.error + ? json.error + : `HTTP ${res.status} ${res.statusText}`; + throw new Error( + `[sqlite_rpc] RPC ${name} failed: ${msg} body=${text.slice(0, 200)}`, + ); + } + + return json.result; + } + + const connector: RpcConnector = { + name: 'sqlite_rpc', + + async open() { + // Root health check; runner only calls this on the root connector. + await httpCall('health'); + }, + + async close() { + // no-op; RPC server lifetime is managed outside this process + }, + + async getAccount(id: number) { + const result = (await httpCall('getAccount', { id })) as { + id: number; + balance: bigint; + } | null; + + if (!result) return null; + return { + id: result.id, + balance: result.balance, + }; + }, + + async verify() { + await httpCall('verify'); + }, + + async call(name: string, args?: Record) { + return httpCall(name, args); + }, + + async createWorker() { + return sqlite_rpc(url); + }, + }; + + return connector; +} diff --git a/templates/keynote-2/src/connectors/rpc/supabase_rpc.ts b/templates/keynote-2/src/connectors/rpc/supabase_rpc.ts new file mode 100644 index 00000000000..cf9f24fedcd --- /dev/null +++ b/templates/keynote-2/src/connectors/rpc/supabase_rpc.ts @@ -0,0 +1,97 @@ +import { RpcConnector } from '../../core/connectors.ts'; +import { RpcRequest, RpcResponse } from './rpc_common.ts'; + +export default function supabase_rpc( + url = process.env.SUPABASE_RPC_URL || 'http://127.0.0.1:4106', +): RpcConnector { + let rpcUrl: URL | null = null; + + function ensureUrl() { + if (!url) throw new Error('SUPABASE_RPC_URL not set'); + if (!rpcUrl) rpcUrl = new URL('/rpc', new URL(url)); + } + + async function httpCall(name: string, args?: Record) { + ensureUrl(); + + const body: RpcRequest = { name, args }; + + const res = await fetch(rpcUrl!, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(body), + }); + + if (!res.ok) { + const text = await res.text().catch(() => ''); + throw new Error( + `supabase_rpc HTTP ${res.status} ${res.statusText}${ + text ? ` - ${text}` : '' + }`, + ); + } + + let json: RpcResponse; + try { + json = (await res.json()) as RpcResponse; + } catch (e: any) { + throw new Error( + `supabase_rpc returned invalid JSON: ${e?.message ?? String(e)}`, + ); + } + + if (!json.ok) { + throw new Error(json.error || 'Unknown supabase_rpc error'); + } + + return json.result; + } + + const root: RpcConnector = { + name: 'supabase_rpc', + + async open() { + // Just validate URL; real connections happen on the server. + ensureUrl(); + }, + + async close() { + // No persistent client state on the bench side. + }, + + async verify() { + await httpCall('verify'); + }, + + async call(name: string, args?: Record): Promise { + return httpCall(name, args); + }, + + async getAccount( + id: number, + ): Promise<{ id: number; balance: bigint } | null> { + const result = await httpCall('getAccount', { id }); + if (!result) return null; + + const r = result as { id: number; balance: bigint }; + + return { id: r.id, balance: r.balance }; + }, + + async createWorker() { + const worker: any = supabase_rpc(url); + await worker.open(); + + worker.verify = async () => { + throw new Error( + 'verify() not supported on supabase_rpc worker; call verify() on the root connector instead', + ); + }; + + delete worker.createWorker; + return worker as RpcConnector; + }, + }; + + return root; +} diff --git a/templates/keynote-2/src/connectors/spacetimedb.ts b/templates/keynote-2/src/connectors/spacetimedb.ts new file mode 100644 index 00000000000..bf6018f891e --- /dev/null +++ b/templates/keynote-2/src/connectors/spacetimedb.ts @@ -0,0 +1,316 @@ +import type { ReducerConnector } from '../core/connectors'; +import * as mod from '../../modules/test-1/module_bindings'; + +export function spacetimedb( + url = process.env.STDB_URL!, + moduleName = process.env.STDB_MODULE!, +): ReducerConnector { + let ready!: Promise; + let conn: mod.DbConnection; + let resolveReady!: () => void; + let rejectReady!: (e: unknown) => void; + + function armReady() { + ready = new Promise((res, rej) => { + resolveReady = res; + rejectReady = rej; + }); + } + + // --- reducer completion tracking ------------------------ + const transferWaiters = new Map< + bigint, + { resolve: () => void; reject: (e: unknown) => void } + >(); + + let nextTransferId = 1n; + let transferHooked = false; + + async function connectWithBindings() { + if (!url) throw new Error('STDB_URL not set'); + if (!moduleName) throw new Error('STDB_MODULE not set'); + + const Db = mod.DbConnection; + + armReady(); + + const subscriptions: string[] = []; + if (process.env.VERIFY === '1') { + console.log('[spacetimedb] subscribing to accounts'); + subscriptions.push('SELECT * FROM accounts'); + } + let subscribed = subscriptions.length === 0; + + const builder = Db.builder() + .withUri(url) + .withModuleName(moduleName) + .withConfirmedReads(process.env.STDB_CONFIRMED_READS === '1') + .onConnect((ctx) => { + console.log('[stdb] connected'); + const conn = ctx; + + const reducers = conn.reducers; + + if ( + process.env.USE_SPACETIME_METRICS_ENDPOINT === '0' && + !transferHooked + ) { + transferHooked = true; + console.log('[stdb] hooking onTransfer'); + (reducers as any).onTransfer( + ( + eventCtx: any, + _from: any, + _to: bigint, + _amount: bigint, + clientTxnId: bigint, + ) => { + // console.log('[stdb] onTransfer fired', { from, to, amount: amount.toString(), clientTxnId: clientTxnId.toString(), status: eventCtx?.event?.status }); + + const waiter = transferWaiters.get(clientTxnId); + if (!waiter) { + console.warn( + '[stdb] no waiter for clientTxnId', + clientTxnId.toString(), + ); + return; + } + + transferWaiters.delete(clientTxnId); + + const status = eventCtx?.event?.status; + + if (status?.tag === 'Committed') { + waiter.resolve(); + } else if (status?.tag === 'Failed') { + waiter.reject(new Error(status?.value ?? 'transfer failed')); + } else if (status?.tag === 'OutOfEnergy') { + waiter.reject(new Error('transfer out of energy')); + } else { + waiter.reject(new Error('unknown transfer status')); + } + }, + ); + } + + resolveReady(); + + if (subscriptions.length > 0) { + conn + .subscriptionBuilder() + .onApplied((_sCtx) => { + subscribed = true; + }) + .onError((ctx) => { + console.error('[stdb] subscription failed', ctx.event?.message); + }) + .subscribe(subscriptions); + } + }) + .onConnectError((_ctx, err: any) => { + console.error('[stdb] onConnectError', err); + + if (err instanceof Error) { + rejectReady(err); + } else if (err && typeof err.message === 'string') { + rejectReady(new Error(err.message)); + } else { + rejectReady( + new Error(`Spacetime connection error: ${JSON.stringify(err)}`), + ); + } + }) + .onDisconnect((_ctx, _err) => {}); + + conn = builder.build(); + + while (!subscribed) { + await new Promise((res) => setTimeout(res, 25)); + } + } + + return { + name: 'spacetimedb', + + async open() { + try { + await connectWithBindings(); + await ready; + } catch (err) { + console.error('[spacetimedb] open() failed', err); + throw err; + } + }, + + async close() { + const err = new Error('SpacetimeDB connection closed'); + + // Fail any in-flight transfers + for (const waiter of transferWaiters.values()) { + try { + waiter.reject(err); + } catch { + /* ignore */ + } + } + transferWaiters.clear(); + transferHooked = false; + + try { + conn.disconnect(); + } catch (e) { + console.error('[spacetimedb] close() failed', e); + } + }, + + async createWorker(): Promise { + const worker = spacetimedb(url, moduleName); + await worker.open(); + worker.verify = async () => { + throw new Error( + 'verify() not supported on spacetimedb worker connector; call verify() on the root connector instead', + ); + }; + delete worker.createWorker; + return worker; + }, + + async reducer(fn: string, args: Record) { + await ready; + + switch (fn) { + case 'seed': { + conn.reducers.seed({ + n: args.n, + initialBalance: args.initial_balance, + }); + return; + } + + case 'createAccount': { + conn.reducers.createAccount({ id: args.id, balance: args.balance }); + return; + } + + case 'transfer': { + const clientTxnId = nextTransferId++; + + if (process.env.USE_SPACETIME_METRICS_ENDPOINT === '0') { + return new Promise((resolve, reject) => { + const waiter = { resolve, reject }; + transferWaiters.set(clientTxnId, waiter); + + try { + conn.reducers.transfer({ + from: args.from, + to: args.to, + amount: args.amount, + clientTxnId, + }); + } catch (err) { + console.log(`ERROR ${err}`); + transferWaiters.delete(clientTxnId); + reject(err); + } + }); + } else { + return conn.reducers.transfer({ + from: args.from, + to: args.to, + amount: args.amount, + clientTxnId, + }); + } + } + + default: + throw new Error(`Unknown reducer: ${fn}`); + } + }, + + async getAccount(id: number) { + if (!conn) { + throw new Error('SpacetimeDB not connected'); + } + + const accounts = conn.db?.accounts; + if (!accounts) { + throw new Error('SpacetimeDB not connected or accounts table missing'); + } + + const acc = accounts.id.find(id); + if (!acc) return null; + + return { + id: acc.id, + balance: acc.balance, + }; + }, + + async verify() { + if (!conn) throw new Error('SpacetimeDB not connected'); + + const rawInitial = process.env.SEED_INITIAL_BALANCE; + if (!rawInitial) { + console.warn( + '[spacetimedb] SEED_INITIAL_BALANCE not set; skipping verification', + ); + return; + } + + let initial = BigInt(rawInitial); + + const accounts = conn.db?.accounts; + if (!accounts) { + console.error( + '[spacetimedb] verify failed: accounts table missing/invalid', + ); + throw new Error( + 'spacetimedb verification failed: accounts table missing/invalid', + ); + } + + let count = 0; + let total = 0n; + let changed = 0n; + + console.log(`[spacetimedb] verifying ${accounts.count()} accounts...`); + for (const row of accounts.iter()) { + count++; + const bal = row.balance; + total += bal; + if (bal !== initial) changed++; + } + + const countBig = BigInt(count); + if (countBig === 0n) { + console.error('[spacetimedb] verify failed: accounts=0'); + throw new Error('spacetimedb verification failed: no accounts'); + } + + const expected = initial * countBig; + + if (total !== expected) { + console.error( + `[spacetimedb] verify failed: accounts=${countBig} total_balance=${total} expected=${expected}`, + ); + throw new Error( + 'spacetimedb verification failed: total_balance mismatch', + ); + } + + if (changed === 0n) { + console.error( + '[spacetimedb] verify failed: total preserved but no balances changed', + ); + throw new Error( + 'spacetimedb verification failed: no account balances changed', + ); + } + + console.log( + `[spacetimedb] verify ok: accounts=${countBig} total_balance=${total} changed=${changed}`, + ); + }, + }; +} diff --git a/templates/keynote-2/src/connectors/sqlite_common.ts b/templates/keynote-2/src/connectors/sqlite_common.ts new file mode 100644 index 00000000000..c3f3660d31c --- /dev/null +++ b/templates/keynote-2/src/connectors/sqlite_common.ts @@ -0,0 +1,113 @@ +import type { Database as BetterSqlite3Database } from 'better-sqlite3'; +import path from 'path'; +import fs from 'fs'; + +export type SqliteMode = + | 'fastest' + | 'realistic_cached' + | 'realistic_fast' + | 'realistic' + | 'default' + | 'baseline'; + +export function getSqliteMode(): SqliteMode { + switch ((process.env.SQLITE_MODE ?? 'realistic_cached').toLowerCase()) { + case 'fastest': + return 'fastest'; + case 'realistic_cached': + return 'realistic_cached'; + case 'realistic_fast': + return 'realistic_fast'; + case 'realistic': + return 'realistic'; + case 'default': + return 'default'; + case 'baseline': + return 'baseline'; + default: + return 'realistic_cached'; + } +} + +export function applySqlitePragmas( + db: BetterSqlite3Database, + mode: SqliteMode, +): void { + switch (mode) { + case 'fastest': + db.pragma('journal_mode = MEMORY'); + db.pragma('synchronous = OFF'); + db.pragma('temp_store = MEMORY'); + db.pragma('cache_size = -64000'); + db.pragma('foreign_keys = OFF'); + break; + case 'realistic_cached': + db.pragma('journal_mode = WAL'); + db.pragma('synchronous = MEMORY'); + db.pragma('temp_store = MEMORY'); + db.pragma('cache_size = -64000'); + break; + case 'realistic_fast': + db.pragma('journal_mode = WAL'); + db.pragma('synchronous = NORMAL'); + db.pragma('temp_store = MEMORY'); + db.pragma('cache_size = -64000'); + break; + case 'realistic': + db.pragma('journal_mode = WAL'); + db.pragma('synchronous = FULL'); + break; + case 'default': + // leave engine defaults + /* + [sqlite] defaults before pragmas: { + journal_mode: 'wal', + synchronous: 1, + temp_store: 0, + locking_mode: 'normal', + cache_size: -16000, + foreign_keys: 1 + } + */ + break; + case 'baseline': + db.pragma('journal_mode = DELETE'); + db.pragma('synchronous = FULL'); + break; + } +} + +export function sqliteModeRank(mode: SqliteMode): number { + switch (mode) { + case 'baseline': + return 0; + case 'default': + return 1; + case 'realistic': + return 2; + case 'realistic_fast': + return 3; + case 'realistic_cached': + return 4; + case 'fastest': + return 5; + } +} + +export async function ensureSqliteDirExists( + file: string, + mode: SqliteMode, +): Promise { + if (mode === 'fastest') return; + const dir = path.dirname(file); + await fs.promises.mkdir(dir, { recursive: true }); +} + +export function ensureSqliteDirExistsSync( + file: string, + mode: SqliteMode, +): void { + if (mode === 'fastest') return; + const dir = path.dirname(file); + fs.mkdirSync(dir, { recursive: true }); +} diff --git a/templates/keynote-2/src/connectors/supabase.ts b/templates/keynote-2/src/connectors/supabase.ts new file mode 100644 index 00000000000..03671a7e13e --- /dev/null +++ b/templates/keynote-2/src/connectors/supabase.ts @@ -0,0 +1,223 @@ +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { RpcConnector } from '../core/connectors.ts'; + +function formatSupabaseError(err: any): string { + if (!err) return 'Unknown Supabase error'; + const parts = [ + err.message ?? '', + err.details ?? '', + err.hint ? `hint: ${err.hint}` : '', + err.code ? `code: ${err.code}` : '', + ] + .map((s) => String(s).trim()) + .filter(Boolean); + return parts.join(' | '); +} + +export default function supabase( + url = process.env.SUPABASE_URL!, + anon = process.env.SUPABASE_ANON_KEY!, +): RpcConnector { + let rootClient: SupabaseClient | undefined; + + function createSupabaseClient(): SupabaseClient { + if (!url || !anon) { + throw new Error('SUPABASE_URL / SUPABASE_ANON_KEY not set'); + } + return createClient(url, anon, { auth: { persistSession: false } }); + } + + function ensureRootClient(): SupabaseClient { + if (!rootClient) { + rootClient = createSupabaseClient(); + } + return rootClient; + } + + const connector: RpcConnector = { + name: 'supabase', + + async open() { + ensureRootClient(); + }, + + async close() { + /* noop */ + }, + + async call(method: string, args?: Record) { + const sb = ensureRootClient(); + + try { + const { data, error } = await sb.rpc(method, (args ?? {}) as any); + if (error) { + throw new Error(formatSupabaseError(error)); + } + return data as any; + } catch (err: any) { + if (err instanceof Error) throw err; + throw new Error(`Supabase RPC failed: ${JSON.stringify(err)}`); + } + }, + + async getAccount(id: number) { + const sb = ensureRootClient(); + + const { data, error } = await sb + .from('accounts') + .select('id,balance') + .eq('id', id) + .maybeSingle(); + + if (error) { + throw new Error(formatSupabaseError(error)); + } + if (!data) return null; + + return { + id: Number(data.id), + balance: BigInt(data.balance), + }; + }, + + async verify() { + const sb = ensureRootClient(); + + const rawInitial = process.env.SEED_INITIAL_BALANCE; + if (!rawInitial) { + console.warn( + '[supabase] SEED_INITIAL_BALANCE not set; skipping verification', + ); + return; + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + console.error( + `[supabase] invalid SEED_INITIAL_BALANCE=${rawInitial}; expected integer`, + ); + return; + } + + const PAGE_SIZE = 1000; + + let offset = 0; + let count = 0n; + let total = 0n; + let changed = 0n; + + for (;;) { + const { data, error } = await sb + .from('accounts') + .select('balance') + .range(offset, offset + PAGE_SIZE - 1); + + if (error) { + throw new Error(formatSupabaseError(error)); + } + + const rows = (data ?? []) as { balance: bigint }[]; + + if (rows.length === 0) break; + + for (const row of rows) { + const bal = row.balance; + total += bal; + count++; + if (bal !== initial) changed++; + } + + if (rows.length < PAGE_SIZE) break; + offset += rows.length; + } + + if (count === 0n) { + console.error('[supabase] verify failed: accounts=0'); + throw new Error('supabase verification failed: no accounts'); + } + + const expected = initial * count; + + // 1) total must be conserved + if (total !== expected) { + console.error( + `[supabase] verify failed: accounts=${count} total_balance=${total} expected=${expected}`, + ); + throw new Error('supabase verification failed: total_balance mismatch'); + } + + // 2) at least one row must have changed + if (changed === 0n) { + console.error( + '[supabase] verify failed: total preserved but no balances changed', + ); + throw new Error( + 'supabase verification failed: no account balances changed', + ); + } + + console.log( + `[supabase] verify ok: accounts=${count} total_balance=${total} changed=${changed}`, + ); + }, + + async createWorker() { + const sb = createSupabaseClient(); + + const worker: RpcConnector = { + name: 'supabase', + + async open() { + /* noop */ + }, + + async close() { + /* noop */ + }, + + async call(method: string, args?: Record) { + try { + const { data, error } = await sb.rpc(method, (args ?? {}) as any); + if (error) { + throw new Error(formatSupabaseError(error)); + } + return data as any; + } catch (err: any) { + if (err instanceof Error) throw err; + throw new Error(`Supabase RPC failed: ${JSON.stringify(err)}`); + } + }, + + async getAccount(id: number) { + const { data, error } = await sb + .from('accounts') + .select('id,balance') + .eq('id', id) + .maybeSingle(); + + if (error) { + throw new Error(formatSupabaseError(error)); + } + if (!data) return null; + + return { + id: Number(data.id), + balance: BigInt(data.balance as any), + }; + }, + + async verify() { + throw new Error( + 'verify() not supported on supabase worker; call verify() on the root connector instead', + ); + }, + }; + + return worker; + }, + }; + + return connector; +} diff --git a/templates/keynote-2/src/core/collision_tracker.ts b/templates/keynote-2/src/core/collision_tracker.ts new file mode 100644 index 00000000000..9124693f3c7 --- /dev/null +++ b/templates/keynote-2/src/core/collision_tracker.ts @@ -0,0 +1,39 @@ +export interface CollisionStats { + total: number; // total begin() calls + collisions: number; // how many times begin() found inflight > 0 + collisionRate: number; // collisions / total +} + +export function makeCollisionTracker() { + // how many workers are currently using each key + const inflight = new Map(); + + let total = 0; + let collisions = 0; + + function begin(id: number) { + total++; + const prev = inflight.get(id) ?? 0; + if (prev > 0) collisions++; + inflight.set(id, prev + 1); + } + + function end(id: number) { + const prev = inflight.get(id); + if (prev === undefined) return; + + const next = prev - 1; + if (next <= 0) inflight.delete(id); + else inflight.set(id, next); + } + + function stats(): CollisionStats { + return { + total, + collisions, + collisionRate: total === 0 ? 0 : collisions / total, + }; + } + + return { begin, end, stats }; +} diff --git a/templates/keynote-2/src/core/connectors.ts b/templates/keynote-2/src/core/connectors.ts new file mode 100644 index 00000000000..a6767474b45 --- /dev/null +++ b/templates/keynote-2/src/core/connectors.ts @@ -0,0 +1,27 @@ +export interface BaseConnector { + name: string; + open(workers?: number): Promise; + close(): Promise; + getAccount(id: number): Promise<{ + id: number; + balance: bigint; + } | null>; + verify(): Promise; + + createWorker?(opts: { index: number; total: number }): Promise; +} + +export interface SqlConnector extends BaseConnector { + exec(sql: string, params?: unknown[]): Promise; + begin(): Promise; + commit(): Promise; + rollback(): Promise; +} + +export interface ReducerConnector extends BaseConnector { + reducer(name: string, args?: Record): Promise; +} + +export interface RpcConnector extends BaseConnector { + call(name: string, args?: Record): Promise; +} diff --git a/templates/keynote-2/src/core/runner.ts b/templates/keynote-2/src/core/runner.ts new file mode 100644 index 00000000000..82f306f964c --- /dev/null +++ b/templates/keynote-2/src/core/runner.ts @@ -0,0 +1,356 @@ +import hdr from 'hdr-histogram-js'; +import { performance } from 'node:perf_hooks'; +import { pickTwoDistinct, zipfSampler } from './zipf.ts'; +import { getSpacetimeCommittedTransfers } from './spacetimeMetrics.ts'; +import { makeCollisionTracker } from './collision_tracker.ts'; +import { RunResult } from './types.ts'; + +const OP_TIMEOUT_MS = Number(process.env.BENCH_OP_TIMEOUT_MS ?? '15000'); +const MIN_OP_TIMEOUT_MS = Number(process.env.MIN_OP_TIMEOUT_MS ?? '250'); +const TAIL_SLACK_MS = Number(process.env.TAIL_SLACK_MS ?? '1000'); + +async function withOpTimeout( + promise: Promise, + label: string, + timeoutOverrideMs?: number, +): Promise { + const timeoutMs = timeoutOverrideMs ?? OP_TIMEOUT_MS; + let timer: NodeJS.Timeout | undefined; + + const timeoutPromise = new Promise((_, reject) => { + timer = setTimeout(() => { + reject(new Error(`[runOne] ${label} timed out after ${timeoutMs}ms`)); + }, timeoutMs); + }); + + try { + return (await Promise.race([promise, timeoutPromise])) as T; + } finally { + if (timer) clearTimeout(timer); + } +} + +export async function runOne({ + connector, + scenario, + seconds, + concurrency, + accounts, + alpha, +}: { + connector: { + name: string; + open(workers?: number): Promise; + close: () => Promise; + verify: () => Promise; + createWorker?: (opts?: { + index: number; + total: number; + }) => Promise; + } & Record; + scenario: ( + conn: unknown, + from: number, + to: number, + amount: number, + ) => Promise; + seconds: number; + concurrency: number; + accounts: number; + alpha: number; +}): Promise { + console.log( + `[${connector.name}] Running ${seconds}s with ${concurrency} workers, ${accounts} accounts, alpha=${alpha}`, + ); + + const collisionTracker = makeCollisionTracker(); + + const hist = hdr.build({ + lowestDiscernibleValue: 1, + highestTrackableValue: 10_000_000_000, + numberOfSignificantValueDigits: 3, + }); + + const hasWorkerFactory = + typeof (connector as any).createWorker === 'function'; + + const workers: unknown[] = []; + + if (hasWorkerFactory) { + await connector.open(concurrency); + + const createWorker = (connector as any).createWorker as (opts?: { + index: number; + total: number; + }) => Promise; + + for (let i = 0; i < concurrency; i++) { + const workerConn = await createWorker({ index: i, total: concurrency }); + workers.push(workerConn); + } + } else { + await connector.open(); + for (let i = 0; i < concurrency; i++) { + workers.push(connector); + } + } + + const useSpacetimeMetrics = + process.env.USE_SPACETIME_METRICS_ENDPOINT === '1' && + connector.name === 'spacetimedb'; + let beforeTransfers: bigint | null = null; + + if (useSpacetimeMetrics) { + try { + beforeTransfers = await getSpacetimeCommittedTransfers(); + if (beforeTransfers !== null) { + console.log( + `[spacetimedb] metrics before run: committed transfer txns = ${beforeTransfers.toString()}`, + ); + } else { + beforeTransfers = BigInt(0); + console.warn( + '[spacetimedb] spacetime_num_txns_total (transfer) not found before run; falling back to client call count', + ); + } + } catch (err) { + console.warn( + '[spacetimedb] failed to read metrics before run; falling back to client call count:', + err, + ); + } + } + + const pick = zipfSampler(accounts, alpha); + const start = performance.now(); + const endAt = start + seconds * 1000; + + let completedWithinWindow = 0; + let completedTotal = 0; + + const PIPELINED = process.env.BENCH_PIPELINED === '1'; + const MAX_INFLIGHT_ENV = process.env.MAX_INFLIGHT_PER_WORKER; + const MAX_INFLIGHT_PER_WORKER = + MAX_INFLIGHT_ENV === '0' ? Infinity : Number(MAX_INFLIGHT_ENV ?? '8'); + + console.log( + `[${connector.name}] max inflight per worker: ${MAX_INFLIGHT_PER_WORKER}`, + ); + + async function worker(workerIndex: number) { + const conn = workers[workerIndex]; + + // non-pipelined + if (!PIPELINED) { + while (true) { + const now = performance.now(); + if (now >= endAt) break; + + const timeLeft = endAt - now; + const dynamicTimeout = Math.max( + MIN_OP_TIMEOUT_MS, + Math.min(OP_TIMEOUT_MS, timeLeft + TAIL_SLACK_MS), + ); + + const [from, to] = pickTwoDistinct(pick); + + collisionTracker.begin(from); + collisionTracker.begin(to); + + const t0 = performance.now(); + let ok = false; + try { + await withOpTimeout( + scenario(conn as unknown, from, to, 1), + `${connector.name} scenario ${from}->${to}`, + dynamicTimeout, + ); + ok = true; + } catch (err) { + if (process.env.LOG_ERRORS === '1') { + const msg = + err instanceof Error + ? `${err.name}: ${err.message}` + : String(err); + console.warn( + `[${connector.name}] Scenario failed for ${from} -> ${to}: ${msg}`, + ); + } + } finally { + collisionTracker.end(from); + collisionTracker.end(to); + } + + const t1 = performance.now(); + if (ok) { + completedTotal++; + if (t1 <= endAt) { + completedWithinWindow++; + hist.recordValue(Math.max(1, Math.round((t1 - t0) * 1e3))); + } + } + } + return; + } + + // pipelined + const inflight = new Set>(); + const unlimitedInflight = !Number.isFinite(MAX_INFLIGHT_PER_WORKER); + + const launchOp = (dynamicTimeout: number) => { + const [from, to] = pickTwoDistinct(pick); + + collisionTracker.begin(from); + collisionTracker.begin(to); + + const p = (async () => { + const t0 = performance.now(); + try { + await withOpTimeout( + scenario(conn as unknown, from, to, 1), + `${connector.name} scenario ${from}->${to}`, + dynamicTimeout, + ); + const t1 = performance.now(); + completedTotal++; + if (t1 <= endAt) { + completedWithinWindow++; + hist.recordValue(Math.max(1, Math.round((t1 - t0) * 1e3))); + } + } catch (err) { + if (process.env.LOG_ERRORS === '1') { + const msg = + err instanceof Error + ? `${err instanceof Error ? err.message : String(err)}` + : String(err); + console.warn( + `[${connector.name}] Scenario failed for ${from} -> ${to}: ${msg}`, + ); + } + } finally { + collisionTracker.end(from); + collisionTracker.end(to); + } + })(); + + inflight.add(p); + p.finally(() => { + inflight.delete(p); + }); + }; + + while (true) { + const now = performance.now(); + if (now >= endAt) break; + + const timeLeft = endAt - now; + const dynamicTimeout = Math.max( + MIN_OP_TIMEOUT_MS, + Math.min(OP_TIMEOUT_MS, timeLeft + TAIL_SLACK_MS), + ); + + if (unlimitedInflight || inflight.size < MAX_INFLIGHT_PER_WORKER) { + launchOp(dynamicTimeout); + } else { + await new Promise((resolve) => setTimeout(resolve, 0)); + } + } + + await Promise.all(inflight); + } + + console.log(`[${connector.name}] Starting workers for ${seconds}s run...`); + + await Promise.all(Array.from({ length: concurrency }, (_, i) => worker(i))); + + console.log( + `[${connector.name}] All workers finished; collecting metrics...`, + ); + + let committedDelta: number | null = null; + + if (useSpacetimeMetrics && beforeTransfers !== null) { + try { + const afterTransfers = await getSpacetimeCommittedTransfers(); + if (afterTransfers !== null && afterTransfers >= beforeTransfers) { + const deltaBig = afterTransfers - beforeTransfers; + const maxSafe = BigInt(Number.MAX_SAFE_INTEGER); + committedDelta = + deltaBig <= maxSafe ? Number(deltaBig) : Number(maxSafe); + + console.log( + `[spacetimedb] metrics after run: committed transfer txns = ${afterTransfers.toString()} (delta = ${deltaBig.toString()})`, + ); + } else { + console.warn( + '[spacetimedb] metrics after run missing or decreased; ignoring metrics delta', + ); + } + } catch (err) { + console.warn( + '[spacetimedb] failed to read metrics after run; ignoring metrics delta:', + err, + ); + } + } + + if (process.env.VERIFY === '1') { + console.log(`[${connector.name}] Running verification pass...`); + try { + await withOpTimeout(connector.verify(), `${connector.name} verify()`); + } catch (err) { + console.error(`[${connector.name}] Verification failed:`, err); + } + } + + if (hasWorkerFactory) { + for (const w of workers) { + const c = w as { close?: () => Promise }; + if (typeof c.close === 'function') { + try { + await withOpTimeout(c.close(), `${connector.name} worker close`); + } catch (err) { + console.warn( + `[${connector.name}] Worker close failed: ${ + err instanceof Error ? err.message : String(err) + }`, + ); + } + } + } + } + + await withOpTimeout(connector.close(), `${connector.name} root close`); + + const q = (p: number) => hist.getValueAtPercentile(p) / 1000; + + const committedOrCompleted = committedDelta ?? completedWithinWindow; + + const c = collisionTracker.stats(); + const elapsedSeconds = (performance.now() - start) / 1000; + + console.log( + '[runOne]', + 'completed within window =', + completedWithinWindow, + 'total completed =', + completedTotal, + 'window =', + seconds, + 's, actual elapsed =', + elapsedSeconds, + 's', + ); + + return { + tps: committedOrCompleted / seconds, + samples: completedWithinWindow, + committed_txns: committedDelta, + p50_ms: q(50), + p95_ms: q(95), + p99_ms: q(99), + collision_ops: c.total, + collision_count: c.collisions, + collision_rate: c.collisionRate, + }; +} diff --git a/templates/keynote-2/src/core/runner_1.ts b/templates/keynote-2/src/core/runner_1.ts new file mode 100644 index 00000000000..d2a35523225 --- /dev/null +++ b/templates/keynote-2/src/core/runner_1.ts @@ -0,0 +1,302 @@ +import hdr from 'hdr-histogram-js'; +import { performance } from 'node:perf_hooks'; +import { pickTwoDistinct, zipfSampler } from './zipf.ts'; +import { getSpacetimeCommittedTransfers } from './spacetimeMetrics.ts'; +import { makeCollisionTracker } from './collision_tracker.ts'; +import { RunResult } from './types.ts'; + +const OP_TIMEOUT_MS = Number(process.env.BENCH_OP_TIMEOUT_MS ?? '15000'); + +async function withOpTimeout( + promise: Promise, + label: string, +): Promise { + const timeoutMs = OP_TIMEOUT_MS; + let timer: NodeJS.Timeout | undefined; + + const timeoutPromise = new Promise((_, reject) => { + timer = setTimeout(() => { + reject(new Error(`[runOne] ${label} timed out after ${timeoutMs}ms`)); + }, timeoutMs); + }); + + try { + return (await Promise.race([promise, timeoutPromise])) as T; + } finally { + if (timer) clearTimeout(timer); + } +} + +export async function runOne({ + connector, + scenario, + seconds, + concurrency, + accounts, + alpha, +}: { + connector: { + name: string; + open(workers?: number): Promise; + close: () => Promise; + verify: () => Promise; + createWorker?: (opts?: { + index: number; + total: number; + }) => Promise; + } & Record; + scenario: ( + conn: unknown, + from: number, + to: number, + amount: number, + ) => Promise; + seconds: number; + concurrency: number; + accounts: number; + alpha: number; +}): Promise { + console.log( + `[${connector.name}] Running ${seconds}s with ${concurrency} workers, ${accounts} accounts, alpha=${alpha}`, + ); + + const collisionTracker = makeCollisionTracker(); + + const hist = hdr.build({ + lowestDiscernibleValue: 1, + highestTrackableValue: 10_000_000_000, + numberOfSignificantValueDigits: 3, + }); + + const hasWorkerFactory = + typeof (connector as any).createWorker === 'function'; + + const workers: unknown[] = []; + + if (hasWorkerFactory) { + await connector.open(concurrency); + + const createWorker = (connector as any).createWorker as (opts?: { + index: number; + total: number; + }) => Promise; + + for (let i = 0; i < concurrency; i++) { + const workerConn = await createWorker({ index: i, total: concurrency }); + workers.push(workerConn); + } + } else { + await connector.open(); + for (let i = 0; i < concurrency; i++) { + workers.push(connector); + } + } + + const useSpacetimeMetrics = + process.env.USE_SPACETIME_METRICS_ENDPOINT === '1' && + connector.name === 'spacetimedb'; + let beforeTransfers: bigint | null = null; + + if (useSpacetimeMetrics) { + try { + beforeTransfers = await getSpacetimeCommittedTransfers(); + if (beforeTransfers !== null) { + console.log( + `[spacetimedb] metrics before run: committed transfer txns = ${beforeTransfers.toString()}`, + ); + } else { + beforeTransfers = BigInt(0); + console.warn( + '[spacetimedb] spacetime_num_txns_total (transfer) not found before run; falling back to client call count', + ); + } + } catch (err) { + console.warn( + '[spacetimedb] failed to read metrics before run; falling back to client call count:', + err, + ); + } + } + + const pick = zipfSampler(accounts, alpha); + const start = performance.now(); + const endAt = start + seconds * 1000; + let completed = 0; + + const PIPELINED = process.env.BENCH_PIPELINED === '1'; + const MAX_INFLIGHT_ENV = process.env.MAX_INFLIGHT_PER_WORKER; + const MAX_INFLIGHT_PER_WORKER = + MAX_INFLIGHT_ENV === '0' ? Infinity : Number(MAX_INFLIGHT_ENV ?? '8'); + + async function worker(workerIndex: number) { + const conn = workers[workerIndex]; + + if (!PIPELINED) { + while (performance.now() < endAt) { + const [from, to] = pickTwoDistinct(pick); + + collisionTracker.begin(from); + collisionTracker.begin(to); + + const t0 = performance.now(); + try { + await withOpTimeout( + scenario(conn as unknown, from, to, 1), + `${connector.name} scenario ${from}->${to}`, + ); + } catch (err) { + const msg = + err instanceof Error ? `${err.name}: ${err.message}` : String(err); + console.warn( + `[${connector.name}] Scenario failed for ${from} -> ${to}: ${msg}`, + ); + } finally { + collisionTracker.end(from); + collisionTracker.end(to); + } + + const t1 = performance.now(); + hist.recordValue(Math.max(1, Math.round((t1 - t0) * 1e3))); + completed++; + } + return; + } + + const inflight = new Set>(); + const unlimitedInflight = !Number.isFinite(MAX_INFLIGHT_PER_WORKER); + + const launchOp = () => { + const [from, to] = pickTwoDistinct(pick); + + collisionTracker.begin(from); + collisionTracker.begin(to); + + const t0 = performance.now(); + + const p = (async () => { + try { + await withOpTimeout( + scenario(conn as unknown, from, to, 1), + `${connector.name} scenario ${from}->${to}`, + ); + } catch (err) { + const msg = + err instanceof Error ? `${err.name}: ${err.message}` : String(err); + console.warn( + `[${connector.name}] Scenario failed for ${from} -> ${to}: ${msg}`, + ); + } finally { + collisionTracker.end(from); + collisionTracker.end(to); + + const t1 = performance.now(); + hist.recordValue(Math.max(1, Math.round((t1 - t0) * 1e3))); + completed++; + } + })(); + + inflight.add(p); + p.finally(() => { + inflight.delete(p); + }); + }; + + while (performance.now() < endAt) { + if (unlimitedInflight || inflight.size < MAX_INFLIGHT_PER_WORKER) { + // unlimited: this is always true → just keep launching + launchOp(); + } else { + // bounded: yield to let existing inflight ops progress, but do NOT + // wait for any specific one to finish + await new Promise((resolve) => setTimeout(resolve, 0)); + } + } + + // after the time window, wait for remaining ops to settle so stats are sane + await Promise.all(inflight); + } + + console.log(`[${connector.name}] Starting workers for ${seconds}s run...`); + + await Promise.all(Array.from({ length: concurrency }, (_, i) => worker(i))); + + console.log( + `[${connector.name}] All workers finished; collecting metrics...`, + ); + + let committedDelta: number | null = null; + + if (useSpacetimeMetrics && beforeTransfers !== null) { + try { + const afterTransfers = await getSpacetimeCommittedTransfers(); + if (afterTransfers !== null && afterTransfers >= beforeTransfers) { + const deltaBig = afterTransfers - beforeTransfers; + const maxSafe = BigInt(Number.MAX_SAFE_INTEGER); + committedDelta = + deltaBig <= maxSafe ? Number(deltaBig) : Number(maxSafe); + + console.log( + `[spacetimedb] metrics after run: committed transfer txns = ${afterTransfers.toString()} (delta = ${deltaBig.toString()})`, + ); + } else { + console.warn( + '[spacetimedb] metrics after run missing or decreased; ignoring metrics delta', + ); + } + } catch (err) { + console.warn( + '[spacetimedb] failed to read metrics after run; ignoring metrics delta:', + err, + ); + } + } + + if (process.env.VERIFY === '1') { + console.log(`[${connector.name}] Running verification pass...`); + try { + await withOpTimeout(connector.verify(), `${connector.name} verify()`); + } catch (err) { + console.error(`[${connector.name}] Verification failed:`, err); + } + } + + if (hasWorkerFactory) { + for (const w of workers) { + const c = w as { close?: () => Promise }; + if (typeof c.close === 'function') { + try { + await withOpTimeout(c.close(), `${connector.name} worker close`); + } catch (err) { + console.warn( + `[${connector.name}] Worker close failed: ${ + err instanceof Error ? err.message : String(err) + }`, + ); + } + } + } + } + + await withOpTimeout(connector.close(), `${connector.name} root close`); + + const q = (p: number) => hist.getValueAtPercentile(p) / 1000; + + const committedOrCompleted = committedDelta ?? completed; + + const c = collisionTracker.stats(); + + return { + tps: committedOrCompleted / seconds, + samples: completed, + committed_txns: committedDelta, + p50_ms: q(50), + p95_ms: q(95), + p99_ms: q(99), + collision_ops: c.total, + collision_count: c.collisions, + collision_rate: c.collisionRate, + }; +} + + + diff --git a/templates/keynote-2/src/core/spacetimeMetrics.ts b/templates/keynote-2/src/core/spacetimeMetrics.ts new file mode 100644 index 00000000000..113d04ed58d --- /dev/null +++ b/templates/keynote-2/src/core/spacetimeMetrics.ts @@ -0,0 +1,76 @@ +type LabelFilter = Record; + +export async function fetchMetrics(url: string): Promise { + const res = await fetch(url); + if (!res.ok) { + throw new Error( + `metrics GET ${url} failed: ${res.status} ${res.statusText}`, + ); + } + return await res.text(); +} + +export function parseMetricCounter( + body: string, + metricName: string, + labels: LabelFilter, +): bigint | null { + const lines = body.split('\n'); + + for (const line of lines) { + if (!line.startsWith(metricName)) continue; + + // spacetime_num_txns_total{committed="true",reducer="transfer",txn_type="Reducer"} 619241 + const m = line.match(/^([^{\s]+)(\{([^}]*)\})?\s+([0-9.eE+-]+)$/); + if (!m) continue; + + const [, , , rawLabelStr = '', rawValue] = m; + const labelStr = rawLabelStr.trim(); + + const parsedLabels: Record = {}; + if (labelStr.length > 0) { + for (const kv of labelStr.split(',')) { + const trimmed = kv.trim(); + if (!trimmed) continue; + const idx = trimmed.indexOf('='); + if (idx === -1) continue; + const key = trimmed.slice(0, idx).trim(); + const rawVal = trimmed.slice(idx + 1).trim(); + const value = rawVal.replace(/^"|"$/g, ''); + parsedLabels[key] = value; + } + } + + let match = true; + for (const [k, v] of Object.entries(labels)) { + if (parsedLabels[k] !== v) { + match = false; + break; + } + } + if (!match) continue; + + const integerPart = rawValue.split('.')[0]; + try { + return BigInt(integerPart); + } catch { + continue; + } + } + + return null; +} + +export async function getSpacetimeCommittedTransfers(): Promise { + const url = + process.env.STDB_METRICS_URL ?? 'http://127.0.0.1:3000/v1/metrics'; + + const labels: LabelFilter = { + committed: 'true', + reducer: 'transfer', + txn_type: 'Reducer', + }; + + const body = await fetchMetrics(url); + return parseMetricCounter(body, 'spacetime_num_txns_total', labels); +} diff --git a/templates/keynote-2/src/core/types.ts b/templates/keynote-2/src/core/types.ts new file mode 100644 index 00000000000..5d3f4c9a7a2 --- /dev/null +++ b/templates/keynote-2/src/core/types.ts @@ -0,0 +1,11 @@ +export type RunResult = { + tps: number; + samples: number; + committed_txns: number | null; + p50_ms: number; + p95_ms: number; + p99_ms: number; + collision_ops: number; + collision_count: number; + collision_rate: number; +}; diff --git a/templates/keynote-2/src/core/zipf.ts b/templates/keynote-2/src/core/zipf.ts new file mode 100644 index 00000000000..d52ba431729 --- /dev/null +++ b/templates/keynote-2/src/core/zipf.ts @@ -0,0 +1,185 @@ +/** Fast, deterministic PRNG (mulberry32). */ +function mulberry32(seed: number) { + let t = seed >>> 0; + return { + next(): number { + t += 0x6d2b79f5; + let r = Math.imul(t ^ (t >>> 15), 1 | t); + r ^= r + Math.imul(r ^ (r >>> 7), 61 | r); + return ((r ^ (r >>> 14)) >>> 0) / 4294967296; + }, + }; +} + +export type Rng = { next(): number }; + +export function makeRng(seed = 0x12345678): Rng { + return mulberry32(seed); +} + +/** + * Pareto sampler (continuous) returning a function that yields samples in [xm, ∞). + * CDF: 1 - (xm/x)^alpha for x >= xm + */ +export function pareto(xm: number, alpha: number, seed?: number) { + if (!(xm > 0)) throw new Error('xm must be > 0'); + if (!(alpha > 0)) throw new Error('alpha must be > 0'); + const rng = makeRng(seed); + return () => { + let u = 1 - rng.next(); // (0,1] + if (u <= 0) u = Number.MIN_VALUE; + return xm / Math.pow(u, 1 / alpha); + }; +} + +/** + * Zipf sampler with binary search. + * + * For very large N (>100k), consider using rejection sampling or + * approximation methods instead. + */ +export function zipfSampler(N: number, s: number, seed?: number) { + if (s <= 0) { + // uniform for s <= 0 + const rng = makeRng(seed); + return () => Math.floor(rng.next() * N); + } + + const rng = makeRng(seed); + + // For small N, use precomputed CDF with binary search + if (N <= 100_000) { + return zipfCDF(N, s, rng); + } + + // For very large N, use rejection sampling (more memory efficient) + return zipfRejection(N, s, rng); +} + +/** + * CDF-based Zipf sampler with binary search. + * Good for N up to ~100k. + */ +function zipfCDF(N: number, s: number, rng: Rng) { + // Precompute CDF using Kahan summation for better numerical stability + const cdf = new Float64Array(N); + let sum = 0; + let c = 0; // Kahan compensation + + for (let i = 0; i < N; i++) { + const p = 1 / Math.pow(i + 1, s); + // Kahan summation + const y = p - c; + const t = sum + y; + c = t - sum - y; + sum = t; + } + + // Normalize to CDF + let cumulative = 0; + let comp = 0; + for (let i = 0; i < N; i++) { + const p = 1 / Math.pow(i + 1, s) / sum; + const y = p - comp; + const t = cumulative + y; + comp = t - cumulative - y; + cumulative = t; + cdf[i] = cumulative; + } + cdf[N - 1] = 1.0; // ensure last value is exactly 1 + + // Return sampler using binary search + return () => { + const u = rng.next(); + + // Binary search for the first index where cdf[i] >= u + let left = 0; + let right = N - 1; + + while (left < right) { + const mid = (left + right) >>> 1; + if (cdf[mid] < u) { + left = mid + 1; + } else { + right = mid; + } + } + + return left; + }; +} + +/** + * Rejection sampling for Zipf distribution. + * More memory efficient for very large N, but slightly slower per sample. + * + * Uses the rejection method with a power-law proposal distribution. + */ +function zipfRejection(N: number, s: number, rng: Rng) { + // Zeta function approximation for normalization + const zeta = (() => { + if (s === 1) { + return Math.log(N) + 0.5772156649; // Euler-Mascheroni constant + } + let sum = 0; + // Compute exact sum for first 1000 terms, approximate the rest + const exact = Math.min(N, 1000); + for (let i = 1; i <= exact; i++) { + sum += 1 / Math.pow(i, s); + } + if (N > exact) { + // Integral approximation for tail + const a = exact; + const b = N; + sum += (Math.pow(a, 1 - s) - Math.pow(b, 1 - s)) / (s - 1); + } + return sum; + })(); + + // Proposal distribution parameter (power law with exponent s) + const alpha = s; + const xmin = 1; + + return () => { + while (true) { + // Sample from power law proposal: x^(-alpha) for x in [1, N] + const u = rng.next(); + let x: number; + + if (alpha === 1) { + x = Math.exp(u * Math.log(N)); + } else { + const term = Math.pow(N, 1 - alpha) * u + (1 - u); + x = Math.pow(term, 1 / (1 - alpha)); + } + + const k = Math.floor(x); + if (k < 1 || k > N) continue; + + // Acceptance probability + const px = 1 / (Math.pow(k, s) * zeta); + const proposal = + (alpha / (Math.pow(xmin, -alpha) - Math.pow(N + 1, -alpha))) * + Math.pow(k, -alpha - 1); + const M = Math.pow(N, s - alpha); // envelope constant + + const acceptProb = px / (M * proposal); + + if (rng.next() <= acceptProb) { + return k - 1; // return 0-indexed + } + } + }; +} + +/** Pick two distinct integers from a sampler. */ +export function pickTwoDistinct( + pick: () => number, + maxSpins = 32, +): [number, number] { + const a = pick(); + let b = pick(); + let spins = 0; + while (b === a && spins++ < maxSpins) b = pick(); + return [a, b]; +} diff --git a/templates/keynote-2/src/drizzle/schema.ts b/templates/keynote-2/src/drizzle/schema.ts new file mode 100644 index 00000000000..35d55249553 --- /dev/null +++ b/templates/keynote-2/src/drizzle/schema.ts @@ -0,0 +1,17 @@ +import { pgTable, integer, bigint as pgBigint } from 'drizzle-orm/pg-core'; +import { sqliteTable, integer as sqliteInt } from 'drizzle-orm/sqlite-core'; + +export const pgAccounts = pgTable('accounts', { + id: integer('id').primaryKey(), + balance: pgBigint('balance', { mode: 'bigint' }).notNull(), +}); + +export const crdbAccounts = pgTable('accounts', { + id: integer('id').primaryKey(), + balance: pgBigint('balance', { mode: 'bigint' }).notNull(), +}); + +export const sqliteAccounts = sqliteTable('accounts', { + id: sqliteInt('id').primaryKey(), + balance: sqliteInt('balance').notNull(), +}); diff --git a/templates/keynote-2/src/helpers.ts b/templates/keynote-2/src/helpers.ts new file mode 100644 index 00000000000..a077c1f3a94 --- /dev/null +++ b/templates/keynote-2/src/helpers.ts @@ -0,0 +1,24 @@ +export function poolMax( + workers: number | undefined, + envName: string, + fallbackEnvMax: number, + defaultWorkers = 1, +): number { + const requested = + workers && Number.isFinite(workers) && workers > 0 + ? workers + : defaultWorkers; + + const raw = process.env[envName]; + const parsed = raw !== undefined ? Number(raw) : NaN; + const envMax = + Number.isFinite(parsed) && parsed > 0 ? parsed : fallbackEnvMax; + + return Math.min(requested, envMax); +} + +export function poolMaxFromEnv(fallback = 1000): number { + const raw = process.env.MAX_POOL; + const n = raw !== undefined ? Number(raw) : NaN; + return Number.isFinite(n) && n > 0 ? n : fallback; +} diff --git a/templates/keynote-2/src/init/init-all.ts b/templates/keynote-2/src/init/init-all.ts new file mode 100644 index 00000000000..78bb51f47d8 --- /dev/null +++ b/templates/keynote-2/src/init/init-all.ts @@ -0,0 +1,94 @@ +import 'dotenv/config'; +import { init_supabase } from './init_supabase.ts'; +import { ACC, BAL, has, sh } from './utils.ts'; +import { initSpacetime } from './init_spacetime.ts'; +import { initConvex } from './init_convex.ts'; +import { initPgLike } from './init_pglike.ts'; +import { initSqlite } from './init_sqlite.ts'; +import { initBun } from './init_bun.ts'; +import { initRpcServers } from './init_rpc_servers.ts'; + +async function main() { + // 1) Bring up docker services + const useDocker = process.env.USE_DOCKER === '1'; + if (useDocker) { + console.log('\n[docker] compose up -d --build'); + await sh('docker', ['compose', 'up', '-d', '--build', '--force-recreate', "--remove-orphans"]); + + // Seed the SQLite named volume inside Docker + console.log('[sqlite] seeding named volume via sqlite-seed...'); + await sh('docker', ['compose', 'run', '--rm', 'sqlite-seed']); + } else { + console.log('\n[docker] skipped (set USE_DOCKER=1 to enable)'); + } + + // 2) Init PG/CRDB/PlanetScale if URLs set + if (has(process.env.PG_URL) && process.env.SKIP_PG !== '1') { + await initPgLike(process.env.PG_URL!, 'postgres'); + } else { + console.log('[postgres] skipped (set SKIP_PG=0 to enable)'); + } + + if (has(process.env.CRDB_URL) && process.env.SKIP_CRDB !== '1') { + await initPgLike(process.env.CRDB_URL!, 'cockroach'); + } else { + console.log('[cockroach] skipped (set SKIP_CRDB=0 to enable)'); + } + + if ( + has(process.env.PLANETSCALE_PG_URL) && + process.env.SKIP_PLANETSCALE_PG !== '1' + ) { + await initPgLike(process.env.PLANETSCALE_PG_URL!, 'planetscale'); + } else { + console.log( + '[planetscale_pg] skipped (set PLANETSCALE_PG_URL and SKIP_PLANETSCALE_PG=0 to enable)', + ); + } + + // 3) Init SQLite if path set + if (has(process.env.SQLITE_FILE) && process.env.SKIP_SQLITE !== '1') + initSqlite(process.env.SQLITE_FILE!); + + // 4) Supabase + if (has(process.env.SUPABASE_URL) && process.env.SKIP_SUPABASE !== '1') { + console.log( + '[supabase] detected env; run your SQL in the Supabase SQL editor once (see README).', + ); + await init_supabase({ + dbUrl: process.env.SUPABASE_DB_URL, + supabaseUrl: process.env.SUPABASE_URL, + supabaseAnonKey: process.env.SUPABASE_ANON_KEY, + accountCount: ACC, + initialBalance: BAL, + }); + } + + // 5) Bun + if (process.env.BUN_URL && process.env.SKIP_BUN !== '1') { + await initBun(process.env.BUN_URL); + } else { + console.log('[bun] skipped (set BUN_URL and SKIP_BUN=0 to enable)'); + } + + // 6) Convex + if (has(process.env.CONVEX_URL) && process.env.SKIP_CONVEX !== '1') { + console.log( + '[convex] detected env; ensure debit/credit mutations + seed exist (see README).', + ); + await initConvex(); + } + + // 7) SpacetimeDB publish/generate/seed + await initSpacetime(); + + // 8) start rpc + await initRpcServers(); + + console.log('\n[prep] All set ✅'); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/templates/keynote-2/src/init/init_bun.ts b/templates/keynote-2/src/init/init_bun.ts new file mode 100644 index 00000000000..397c218bfcf --- /dev/null +++ b/templates/keynote-2/src/init/init_bun.ts @@ -0,0 +1,112 @@ +import 'dotenv/config'; + +export async function initBun(url?: string) { + const bunUrl = url ?? process.env.BUN_URL; + + if (!bunUrl || !bunUrl.trim()) { + console.log('[bun] missing BUN_URL; skipping'); + return; + } + + let base: URL; + try { + base = new URL(bunUrl); + } catch { + console.error(`[bun] invalid BUN_URL=${bunUrl}`); + throw new Error('invalid BUN_URL'); + } + + const rpcUrl = new URL('/rpc', base); + console.log(`[bun] init @ ${rpcUrl.href}`); + + // 1) health + let res: Response; + try { + res = await fetch(rpcUrl, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ name: 'health', args: {} }), + }); + } catch (err) { + console.error('[bun] request failed:', err); + throw err; + } + + if (!res.ok) { + const body = await res.text().catch(() => ''); + console.error( + `[bun] HTTP ${res.status} ${res.statusText}: ${body.slice(0, 200)}`, + ); + throw new Error('bun health check failed'); + } + + let json: any; + try { + json = await res.json(); + } catch { + console.error('[bun] invalid JSON response from Bun RPC server'); + throw new Error('bun health check returned invalid JSON'); + } + + if (!json || typeof json !== 'object' || json.ok !== true) { + console.error( + '[bun] health check failed:', + typeof json === 'object' ? JSON.stringify(json) : String(json), + ); + throw new Error('bun health check failed'); + } + + console.log('[bun] health ok'); + + // 2) seed via RPC + const seedAccounts = Number(process.env.SEED_ACCOUNTS ?? '0'); + const seedInitial = process.env.SEED_INITIAL_BALANCE; + + if (!Number.isInteger(seedAccounts) || seedAccounts <= 0 || !seedInitial) { + console.log( + '[bun] missing SEED_ACCOUNTS or SEED_INITIAL_BALANCE; skipping seed', + ); + return; + } + + console.log( + `[bun] seeding ${seedAccounts} accounts with initial_balance=${seedInitial}`, + ); + + const seedRes = await fetch(rpcUrl, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + name: 'seed', + args: { + accounts: seedAccounts, + initialBalance: seedInitial, + }, + }), + }); + + if (!seedRes.ok) { + const body = await seedRes.text().catch(() => ''); + console.error( + `[bun] seed HTTP ${seedRes.status} ${seedRes.statusText}: ${body.slice( + 0, + 200, + )}`, + ); + throw new Error('bun seed failed'); + } + + const seedJson: any = await seedRes.json().catch(() => null); + + if (!seedJson || typeof seedJson !== 'object' || seedJson.ok !== true) { + console.error( + '[bun] seed failed:', + typeof seedJson === 'object' + ? JSON.stringify(seedJson) + : String(seedJson), + ); + throw new Error('bun seed failed'); + } + + console.log('[bun] ready'); +} diff --git a/templates/keynote-2/src/init/init_convex.ts b/templates/keynote-2/src/init/init_convex.ts new file mode 100644 index 00000000000..76bd46563ba --- /dev/null +++ b/templates/keynote-2/src/init/init_convex.ts @@ -0,0 +1,81 @@ +const DEFAULT_CONVEX_URL = 'http://127.0.0.1:3210'; + +async function callConvexMutation(url: string, pathName: string, args: any) { + const res = await fetch(`${url}/api/mutation?format=json`, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ path: pathName, args }), + }); + + const json = await res.json().catch(() => ({}) as any); + if (!res.ok || json.status !== 'success') { + const msg = + json?.errorMessage ?? + json?.message ?? + `HTTP ${res.status} ${res.statusText}`; + throw new Error(`convex mutation ${pathName} failed: ${msg}`); + } + return json.value; +} + +export async function initConvex() { + if (process.env.SKIP_CONVEX === '1') { + console.log('[convex] skipped (set SKIP_CONVEX=0 to enable)'); + return; + } + console.log('\n[convex] scaffold'); + + const dir = process.env.CONVEX_DIR || './convex-app'; + + const ACC = Number(process.env.SEED_ACCOUNTS ?? 100_000); + const BAL = Number(process.env.SEED_INITIAL_BALANCE ?? 1_000_000_000); + const url = process.env.CONVEX_URL || DEFAULT_CONVEX_URL; + + console.log( + `[convex] expecting dev server at ${url} (start with: cd ${dir} && pnpm dev)`, + ); + + try { + if (process.env.CLEAR_CONVEX_ON_PREP === '1') { + console.log('[convex] clearing accounts…'); + for (;;) { + const deleted: number = await callConvexMutation( + url, + 'seed:clear_accounts', + {}, + ); + console.log(`[convex] → deleted batch of ${deleted}`); + if (!deleted) break; + } + } else { + console.log( + '[convex] skipping account clearing (set CLEAR_CONVEX_ON_PREP=1 to enable)', + ); + } + + // Max ~16k writes per function; keep a safety margin + const CHUNK = 10_000; + console.log( + `[convex] seeding ${ACC} accounts in chunks of ${CHUNK} (initial=${BAL})`, + ); + + for (let start = 0; start < ACC; start += CHUNK) { + const count = Math.min(CHUNK, ACC - start); + console.log( + `[convex] → seed:seed_range { start: ${start}, count: ${count} }`, + ); + await callConvexMutation(url, 'seed:seed_range', { + start, + count, + initial: BAL, + }); + } + + console.log('[convex] seed complete.'); + } catch (err) { + console.warn( + `[convex] seed failed: ${(err as Error).message}\n` + + `Make sure "convex dev" is running and that "seed:clear_accounts" / "seed:seed_range" exist.`, + ); + } +} diff --git a/templates/keynote-2/src/init/init_pglike.ts b/templates/keynote-2/src/init/init_pglike.ts new file mode 100644 index 00000000000..00699ae9117 --- /dev/null +++ b/templates/keynote-2/src/init/init_pglike.ts @@ -0,0 +1,38 @@ +import { ACC, BAL, waitFor } from './utils.ts'; +import pg from 'pg'; + +export async function initPgLike(url: string, label: string) { + const isPlanetScale = url.includes('psdb.cloud'); + + console.log(`\n[${label}] init @ ${url}`); + await waitFor(url); + + const client = new pg.Client({ + connectionString: url, + ssl: isPlanetScale ? { rejectUnauthorized: false } : undefined, + }); + + await client.connect(); + + await client.query(` + CREATE TABLE IF NOT EXISTS accounts ( + id INT PRIMARY KEY, + balance BIGINT NOT NULL + ); + `); + + // reset + await client.query(`TRUNCATE TABLE accounts;`); + + // seed + await client.query( + ` + INSERT INTO accounts(id, balance) + SELECT g, $1 FROM generate_series(0, $2) AS g; + `, + [BAL, ACC - 1], + ); + + await client.end(); + console.log(`[${label}] ready`); +} diff --git a/templates/keynote-2/src/init/init_rpc_servers.ts b/templates/keynote-2/src/init/init_rpc_servers.ts new file mode 100644 index 00000000000..7894b98eab2 --- /dev/null +++ b/templates/keynote-2/src/init/init_rpc_servers.ts @@ -0,0 +1,82 @@ +import { spawnServer } from './utils.ts'; + +type RpcServerConfig = { + name: string; + urlEnv: string; + defaultUrl: string; + command: string; +}; + +async function rpcHealthCheck(baseUrl: string): Promise { + try { + const url = new URL('/rpc', new URL(baseUrl)); + const res = await fetch(url, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ name: 'health', args: {} }), + }); + + if (!res.ok) return false; + + const json = await res.json().catch(() => null); + return !!json && typeof json === 'object' && json.ok === true; + } catch { + return false; + } +} + +async function ensureRpcServer(cfg: RpcServerConfig) { + const url = process.env[cfg.urlEnv] || cfg.defaultUrl; + const healthy = await rpcHealthCheck(url); + + if (healthy) { + console.log(`[rpc] ${cfg.name} already running @ ${url}, skipping spawn`); + return; + } + + console.log(`[rpc] ${cfg.name} not responding @ ${url}, starting...`); + spawnServer(cfg.command, cfg.name); +} + +export async function initRpcServers() { + const enabled = process.env.ENABLE_RPC_SERVERS !== '0'; + if (!enabled) { + console.log('\n[rpc] skipped (ENABLE_RPC_SERVERS=0)'); + return; + } + + console.log('\n[rpc] starting Node RPC servers (if not already running)'); + + await Promise.all([ + ensureRpcServer({ + name: 'postgres_rpc', + urlEnv: 'PG_RPC_URL', + defaultUrl: 'http://127.0.0.1:4101', + command: 'pnpm tsx src/rpc-servers/postgres-rpc-server.ts', + }), + ensureRpcServer({ + name: 'cockroach_rpc', + urlEnv: 'CRDB_RPC_URL', + defaultUrl: 'http://127.0.0.1:4102', + command: 'pnpm tsx src/rpc-servers/cockroach-rpc-server.ts', + }), + ensureRpcServer({ + name: 'sqlite_rpc', + urlEnv: 'SQLITE_RPC_URL', + defaultUrl: 'http://127.0.0.1:4103', + command: 'pnpm tsx src/rpc-servers/sqlite-rpc-server.ts', + }), + ensureRpcServer({ + name: 'supabase_rpc', + urlEnv: 'SUPABASE_RPC_URL', + defaultUrl: 'http://127.0.0.1:4106', + command: 'pnpm tsx src/rpc-servers/supabase-rpc-server.ts', + }), + ensureRpcServer({ + name: 'planetscale_pg_rpc', + urlEnv: 'PLANETSCALE_RPC_URL', + defaultUrl: 'http://127.0.0.1:4104', + command: 'pnpm tsx src/rpc-servers/postgres-rpc-server.ts', + }), + ]); +} diff --git a/templates/keynote-2/src/init/init_spacetime.ts b/templates/keynote-2/src/init/init_spacetime.ts new file mode 100644 index 00000000000..192798ecb44 --- /dev/null +++ b/templates/keynote-2/src/init/init_spacetime.ts @@ -0,0 +1,65 @@ +import { join } from 'node:path'; +import { ACC, BAL, sh } from './utils.ts'; + +export async function initSpacetime() { + const useMaincloud = process.env.STDB_MAINCLOUD === '1'; + const server = useMaincloud + ? 'maincloud' + : process.env.STDB_SERVER || 'local'; + + const dbName = process.env.STDB_MODULE!; + const modulePath = process.env.STDB_MODULE_PATH!; + + if (!dbName || !modulePath) { + console.log('[spacetimedb] missing STDB_MODULE/STDB_MODULE_PATH; skipping'); + return; + } + + // 1) Publish + console.log( + `[spacetimedb] publish "${dbName}" from ${modulePath} (server=${server})`, + ); + try { + await sh('spacetime', [ + 'publish', + '-c', + '-y', + '--server', + server, + '--project-path', + modulePath, + dbName, + ]); + } catch (e: any) { + console.warn('[spacetimedb] publish likely already done:', e.message); + } + + // 2) Generate TS bindings + const outDir = join(process.cwd(), 'modules', 'test-1', 'module_bindings'); + console.log(`[spacetimedb] generate bindings → ${outDir}`); + await sh('spacetime', [ + 'generate', + '--lang', + 'typescript', + '--out-dir', + outDir, + '--project-path', + modulePath, + ]); + + // 3) Seed + console.log(`[spacetimedb] seed: n=${ACC} bal=${BAL}`); + try { + await sh('spacetime', [ + 'call', + '--server', + server, + dbName, + 'seed', + String(ACC), + String(BAL), + ]); + } catch (e: any) { + console.warn('[spacetimedb] seed reducer failed/missing:', e.message); + } +} diff --git a/templates/keynote-2/src/init/init_sqlite.ts b/templates/keynote-2/src/init/init_sqlite.ts new file mode 100644 index 00000000000..5f80359eca9 --- /dev/null +++ b/templates/keynote-2/src/init/init_sqlite.ts @@ -0,0 +1,46 @@ +import Database from 'better-sqlite3'; +import path from 'path'; +import fs from 'fs'; +import { ACC, BAL } from './utils.ts'; + +export function initSqlite(file: string) { + console.log(`\n[sqlite] init @ ${file}`); + + try { + fs.mkdirSync(path.dirname(path.resolve(file)), { recursive: true }); + + const db = new Database(file); + + db.exec(`BEGIN TRANSACTION;`); + + db.exec(` + CREATE TABLE IF NOT EXISTS accounts( + id INTEGER PRIMARY KEY, + balance INTEGER NOT NULL + ); + + -- 🔥 Hard reset + DELETE FROM accounts; + + -- Efficiently insert test data + WITH RECURSIVE c(x) AS ( + SELECT 0 + UNION ALL + SELECT x + 1 FROM c LIMIT ${ACC} + ) + INSERT INTO accounts(id, balance) + SELECT x, ${BAL} FROM c; + `); + + db.exec(`COMMIT;`); + db.close(); + + console.log(`[sqlite] ready`); + } catch (error) { + console.error( + `[sqlite] ERROR during initialization for file: ${file}`, + error, + ); + throw error; + } +} diff --git a/templates/keynote-2/src/init/init_sqlite_seed_in_docker.ts b/templates/keynote-2/src/init/init_sqlite_seed_in_docker.ts new file mode 100644 index 00000000000..a1805a11920 --- /dev/null +++ b/templates/keynote-2/src/init/init_sqlite_seed_in_docker.ts @@ -0,0 +1,83 @@ +// src/init/init_sqlite_seed_in_docker.ts +import 'dotenv/config'; +import Database from 'better-sqlite3'; +import { + applySqlitePragmas, + ensureSqliteDirExists, + getSqliteMode, +} from '../connectors/sqlite_common.ts'; + +async function main() { + const file = process.env.SQLITE_FILE || '/data/accounts.sqlite'; + const rawCount = process.env.SEED_ACCOUNTS ?? '0'; + const rawInitial = process.env.SEED_INITIAL_BALANCE; + + const count = Number(rawCount); + if (!Number.isInteger(count) || count <= 0) { + throw new Error(`[sqlite-seed] invalid SEED_ACCOUNTS=${rawCount}`); + } + if (rawInitial == null) { + throw new Error('[sqlite-seed] SEED_INITIAL_BALANCE not set'); + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + throw new Error( + `[sqlite-seed] invalid SEED_INITIAL_BALANCE=${String(rawInitial)}`, + ); + } + + const mode = getSqliteMode(); + console.log( + `[sqlite-seed] file=${file} accounts=${count} initial=${initial.toString()} mode=${mode}`, + ); + + await ensureSqliteDirExists(file, mode); + + const db = new Database(file); + try { + applySqlitePragmas(db, mode); + + db.exec(` + CREATE TABLE IF NOT EXISTS accounts ( + id INTEGER PRIMARY KEY, + balance INTEGER NOT NULL + ); + `); + + db.exec('DELETE FROM accounts'); + + const insert = db.prepare( + 'INSERT INTO accounts (id, balance) VALUES (?, ?)', + ); + + const batchSize = 10_000; + for (let start = 0; start < count; start += batchSize) { + const end = Math.min(start + batchSize, count); + db.transaction(() => { + for (let id = start; id < end; id++) { + insert.run(id, initial.toString()); + } + })(); + } + + const row = db + .prepare('SELECT COUNT(*) AS n, SUM(balance) AS total FROM accounts') + .get() as { n: number; total: number | null }; + + console.log( + `[sqlite-seed] done: rows=${row.n} total=${row.total ?? 0} expected=${ + initial * BigInt(count) + }`, + ); + } finally { + db.close(); + } +} + +main().catch((err) => { + console.error('[sqlite-seed] failed:', err); + process.exit(1); +}); diff --git a/templates/keynote-2/src/init/init_supabase.ts b/templates/keynote-2/src/init/init_supabase.ts new file mode 100644 index 00000000000..c33ed6a59da --- /dev/null +++ b/templates/keynote-2/src/init/init_supabase.ts @@ -0,0 +1,252 @@ +import pg, { Client as Pg } from 'pg'; +import { setTimeout as sleep } from 'node:timers/promises'; + +type PrepOpts = { + dbUrl?: string; + supabaseUrl?: string; + supabaseAnonKey?: string; + accountCount?: number; + initialBalance?: number; + maxReloadWaitMs?: number; +}; + +export async function init_supabase(opts: PrepOpts = {}) { + const dbUrl = + opts.dbUrl ?? + process.env.SUPABASE_DB_URL ?? + 'postgresql://postgres:postgres@127.0.0.1:54322/postgres'; + + const supabaseUrl = + opts.supabaseUrl ?? process.env.SUPABASE_URL ?? 'http://127.0.0.1:54321'; + + const anon = opts.supabaseAnonKey ?? process.env.SUPABASE_ANON_KEY ?? ''; + + const N = Number.isFinite(opts.accountCount) + ? (opts.accountCount as number) + : 2; + const initial = opts.initialBalance ?? 1_000_000; + const maxReloadWaitMs = opts.maxReloadWaitMs ?? 10_000; + + // Ensure REST is up before touching DB through PostgREST later. + await waitForRest(`${supabaseUrl}/health`); + + const dbName = await ensureSchema(dbUrl); + console.log(`[supabase] ensured schema in db=${dbName}`); + + const inserted = await ensureAccountsRange(dbUrl, N, initial); + console.log(`[supabase] seeded [0..${N - 1}] inserted=${inserted}`); + + await notifyReload(dbUrl); + await waitForRpcVisible({ supabaseUrl, anon }, maxReloadWaitMs); +} + +/* -------------------- internals -------------------- */ +async function ensureSchema(dbUrl: string): Promise { + const sql = ` + create table if not exists public.accounts( + id bigint primary key, + balance bigint not null + ); + + create or replace function public.seed_accounts(n int, initial bigint) + returns bigint + language plpgsql + security definer + as $$ + declare + seeded bigint; + begin + delete from public.accounts; + + insert into public.accounts(id, balance) + select i, initial + from generate_series(0, n - 1) as g(i); + + get diagnostics seeded = row_count; + return seeded; + end; + $$; + + create or replace function public.transfer( + amount bigint, + from_id bigint, + to_id bigint + ) + returns void + language plpgsql + security definer + as $$ + declare + a bigint; + b bigint; + from_bal bigint; + begin + if amount is null or from_id is null or to_id is null then + raise exception 'arg_null amount=%, from_id=%, to_id=%', amount, from_id, to_id; + end if; + + if amount <= 0 then + raise exception 'non_positive_amount'; + end if; + + if from_id = to_id then + raise exception 'same_account'; + end if; + + a := least(from_id, to_id); + b := greatest(from_id, to_id); + + perform 1 + from public.accounts + where id = a + for update; + + if not found then + raise exception 'account_not_found id=%', a; + end if; + + perform 1 + from public.accounts + where id = b + for update; + + if not found then + raise exception 'account_not_found id=%', b; + end if; + + select balance + into from_bal + from public.accounts + where id = from_id; + + if from_bal < amount then + raise exception 'insufficient_funds balance=%, amount=%', from_bal, amount; + end if; + + update public.accounts + set balance = balance + + case + when id = from_id then -amount + when id = to_id then amount + else 0 + end + where id in (from_id, to_id); + end; + $$; + + grant usage on schema public to anon, authenticated, service_role; + grant execute on function public.transfer(bigint,bigint,bigint), public.seed_accounts(int,bigint) + to anon, authenticated, service_role; + + select pg_notify('pgrst','reload schema'); + `; + + const useSsl = /supabase\.co/.test(dbUrl); + + const client = new pg.Client({ + connectionString: dbUrl, + ssl: useSsl ? { rejectUnauthorized: false } : false, + }); + + await client.connect(); + try { + const { rows } = await client.query<{ db: string }>( + 'select current_database() as db;', + ); + const dbName = rows[0].db; + + await client.query('begin'); + await client.query(sql); + await client.query('commit'); + + return dbName; + } catch (e) { + try { + await client.query('rollback'); + } catch { + // ignore rollback errors + } + throw e; + } finally { + await client.end(); + } +} + +async function ensureAccountsRange( + dbUrl: string, + count: number, + initial: number, +): Promise { + if (!(count > 0)) return 0; + const c = new Pg({ connectionString: dbUrl, ssl: false }); + await c.connect(); + try { + await c.query('SET synchronous_commit = on'); + await c.query("SET work_mem = '64MB'"); + + await c.query('begin'); + const r = await c.query<{ seeded: string }>( + `select public.seed_accounts($1::int, $2::bigint) as seeded;`, + [count, initial], + ); + await c.query('commit'); + return Number(r.rows[0].seeded); + } catch (e) { + await c.query('rollback'); + throw e; + } finally { + await c.end(); + } +} + +async function notifyReload(dbUrl: string) { + const c = new Pg({ connectionString: dbUrl, ssl: false }); + await c.connect(); + try { + await c.query(`select pg_notify('pgrst','reload schema');`); + } finally { + await c.end(); + } +} + +async function waitForRpcVisible( + opts: { supabaseUrl: string; anon: string }, + timeoutMs: number, +) { + const deadline = Date.now() + timeoutMs; + for (;;) { + const r = await fetch(`${opts.supabaseUrl}/rest/v1/rpc/transfer`, { + method: 'POST', + headers: { + apikey: opts.anon, + authorization: `Bearer ${opts.anon}`, + 'content-type': 'application/json', + }, + body: JSON.stringify({ amount: 0, from_id: 0, to_id: 0 }), + }); + if (r.status !== 404) return; + if (Date.now() > deadline) { + const body = await r.text(); + throw new Error( + `RPC not visible after reload window. Last body: ${body}`, + ); + } + await sleep(250); + } +} + +async function waitForRest(url: string, timeoutMs = 20_000) { + const start = Date.now(); + for (;;) { + try { + const r = await fetch(url, { method: 'GET' }); + if (r.status >= 200 && r.status < 500) return; + } catch {} + if (Date.now() - start > timeoutMs) { + throw new Error( + `Supabase REST not reachable at ${url} within ${timeoutMs}ms`, + ); + } + await sleep(300); + } +} diff --git a/templates/keynote-2/src/init/utils.ts b/templates/keynote-2/src/init/utils.ts new file mode 100644 index 00000000000..fbecfaccc35 --- /dev/null +++ b/templates/keynote-2/src/init/utils.ts @@ -0,0 +1,67 @@ +import { spawn } from 'node:child_process'; +import pg from 'pg'; +import { setTimeout as sleep } from 'timers/promises'; + +export const ACC = Number(process.env.SEED_ACCOUNTS ?? 100_000); +export const BAL = Number(process.env.SEED_INITIAL_BALANCE ?? 1_000_000); + +export function has(v?: string) { + return v && v.trim().length > 0; +} + +export function sh( + cmd: string, + args: string[], + opts: import('node:child_process').SpawnOptions = {}, +): Promise { + return new Promise((resolve, reject) => { + const child = spawn(cmd, args, { + stdio: 'inherit', + ...opts, + }); + + child.on('error', reject); + + child.on('close', (code, signal) => { + if (code === 0) return resolve(); + reject( + new Error( + `${cmd} ${args.join(' ')} failed` + + (code !== null ? ` with exit code ${code}` : '') + + (signal ? ` (signal ${signal})` : ''), + ), + ); + }); + }); +} + +export async function waitFor(host: string, tries = 30) { + for (let i = 0; i < tries; i++) { + try { + const url = new URL(host); + if (url.protocol.startsWith('postgres')) { + const c = new pg.Client({ connectionString: host }); + await c.connect(); + await c.end(); + return; + } + } catch {} + await sleep(1000); + } + throw new Error(`Timed out waiting for ${host}`); +} + +export function spawnServer(command: string, name: string) { + const child = spawn(command, { + stdio: 'inherit', + shell: true, + env: process.env, + }); + + console.log(`[rpc] started ${name} (pid=${child.pid})`); + child.on('error', (err) => { + console.error(`[rpc] ${name} error:`, err); + }); + + // child.unref(); +} diff --git a/templates/keynote-2/src/rpc-servers/cockroach-rpc-server.ts b/templates/keynote-2/src/rpc-servers/cockroach-rpc-server.ts new file mode 100644 index 00000000000..0c5c8d1b522 --- /dev/null +++ b/templates/keynote-2/src/rpc-servers/cockroach-rpc-server.ts @@ -0,0 +1,272 @@ +import 'dotenv/config'; +import http from 'node:http'; +import { Pool } from 'pg'; +import { drizzle } from 'drizzle-orm/node-postgres'; +import { pgTable, integer, bigint as pgBigint } from 'drizzle-orm/pg-core'; +import { eq, inArray, sql } from 'drizzle-orm'; +import { RpcRequest, RpcResponse } from '../connectors/rpc/rpc_common.ts'; +import { poolMaxFromEnv } from '../helpers.ts'; + +const CRDB_URL = process.env.CRDB_URL; +if (!CRDB_URL) { + throw new Error('CRDB_URL not set'); +} + +const accounts = pgTable('accounts', { + id: integer('id').primaryKey(), + balance: pgBigint('balance', { mode: 'bigint' }).notNull(), +}); + +const pool = new Pool({ + connectionString: CRDB_URL, + application_name: 'crdb-rpc-drizzle', + max: poolMaxFromEnv(), +}); + +const db = drizzle(pool, { schema: { accounts } }); + +async function rpcTransfer(args: Record) { + const fromId = Number(args.from_id ?? args.from); + const toId = Number(args.to_id ?? args.to); + const amount = Number(args.amount); + + if ( + !Number.isInteger(fromId) || + !Number.isInteger(toId) || + !Number.isFinite(amount) + ) { + throw new Error('invalid transfer args'); + } + if (fromId === toId || amount <= 0) return; + + const delta = BigInt(amount); + + await db.transaction(async (tx) => { + const rows = await tx + .select() + .from(accounts) + .where(inArray(accounts.id, [fromId, toId])) + .for('update') + .orderBy(accounts.id); + + if (rows.length !== 2) { + throw new Error('account_missing'); + } + + const [first, second] = rows; + const fromRow = first.id === fromId ? first : second; + const toRow = first.id === fromId ? second : first; + + if (fromRow.balance < delta) { + return; + } + + await tx + .update(accounts) + .set({ balance: fromRow.balance - delta }) + .where(eq(accounts.id, fromId)); + + await tx + .update(accounts) + .set({ balance: toRow.balance + delta }) + .where(eq(accounts.id, toId)); + }); +} + +async function rpcGetAccount(args: Record) { + const id = Number(args.id); + if (!Number.isInteger(id)) throw new Error('invalid id'); + + const rows = await db + .select() + .from(accounts) + .where(eq(accounts.id, id)) + .limit(1); + + if (rows.length === 0) return null; + const row = rows[0]!; + return { + id: row.id, + balance: row.balance.toString(), + }; +} + +async function rpcVerify() { + const rawInitial = process.env.SEED_INITIAL_BALANCE; + if (!rawInitial) { + console.warn('[crdb-rpc] SEED_INITIAL_BALANCE not set; skipping verify'); + return { skipped: true }; + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + throw new Error(`invalid SEED_INITIAL_BALANCE=${rawInitial}`); + } + + const result = await db.execute( + sql` + SELECT + COUNT(*)::bigint AS count, + COALESCE(SUM("balance"), 0)::bigint AS total, + SUM( + CASE WHEN "balance" != ${initial}::bigint THEN 1 ELSE 0 END + )::bigint AS changed + FROM ${accounts} + `, + ); + + const row = (result as any).rows[0] as { + count: string | number | bigint; + total: string | number | bigint; + changed: string | number | bigint; + }; + + const count = BigInt(row.count); + const total = BigInt(row.total); + const changed = BigInt(row.changed); + const expected = initial * count; + + if (count === 0n) throw new Error('verify failed: accounts=0'); + if (total !== expected) { + throw new Error( + `verify failed: accounts=${count} total=${total} expected=${expected}`, + ); + } + if (changed === 0n) { + throw new Error('verify failed: total preserved but no balances changed'); + } + + return { + accounts: count.toString(), + total: total.toString(), + changed: changed.toString(), + }; +} + +async function rpcSeed(args: Record) { + const count = Number(args.accounts ?? process.env.SEED_ACCOUNTS ?? '0'); + const rawInitial = + (args.initialBalance as string | number | undefined) ?? + process.env.SEED_INITIAL_BALANCE; + + if (!Number.isInteger(count) || count <= 0) { + throw new Error('[crdb-rpc] invalid accounts for seed'); + } + if (rawInitial === undefined || rawInitial === null) { + throw new Error('[crdb-rpc] missing initialBalance for seed'); + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + throw new Error(`[crdb-rpc] invalid initialBalance=${rawInitial}`); + } + + await db.transaction(async (tx) => { + await tx.execute(sql`DELETE FROM ${accounts}`); + + const batchSize = 10_000; + for (let start = 0; start < count; start += batchSize) { + const end = Math.min(start + batchSize, count); + const values: { id: number; balance: bigint }[] = []; + + for (let id = start; id < end; id++) { + values.push({ id, balance: initial }); + } + + await tx.insert(accounts).values(values as any); + } + }); + + console.log( + `[crdb-rpc] seeded accounts: count=${count} initial=${initial.toString()}`, + ); +} + +async function handleRpc(body: RpcRequest): Promise { + const name = body?.name; + const args = body?.args ?? {}; + + if (!name) return { ok: false, error: 'missing name' }; + + try { + switch (name) { + case 'health': + return { ok: true, result: { status: 'ok' } }; + case 'transfer': + await rpcTransfer(args); + return { ok: true }; + case 'getAccount': + return { ok: true, result: await rpcGetAccount(args) }; + case 'verify': + return { ok: true, result: await rpcVerify() }; + case 'seed': + return { ok: true, result: await rpcSeed(args) }; + default: + return { ok: false, error: `unknown method: ${name}` }; + } + } catch (err: any) { + console.error('[crdb-rpc] rpc error:', name, err); + return { ok: false, error: String(err?.message ?? err) }; + } +} + +const port = Number(process.env.CRDB_RPC_PORT ?? 4102); + +const server = http.createServer((req, res) => { + const url = new URL(req.url ?? '/', `http://${req.headers.host}`); + + if (req.method === 'POST' && url.pathname === '/rpc') { + let buf = ''; + req.on('data', (chunk) => { + buf += chunk; + }); + req.on('end', async () => { + let body: RpcRequest; + try { + body = JSON.parse(buf) as RpcRequest; + } catch { + res.statusCode = 400; + res.setHeader('content-type', 'application/json'); + res.end(JSON.stringify({ ok: false, error: 'invalid json' })); + return; + } + + const rsp = await handleRpc(body); + res.statusCode = rsp.ok ? 200 : 500; + res.setHeader('content-type', 'application/json'); + res.end(JSON.stringify(rsp)); + }); + return; + } + + if (req.method === 'GET' && url.pathname === '/') { + res.statusCode = 200; + res.end('cockroach drizzle rpc server'); + return; + } + + res.statusCode = 404; + res.end('not found'); +}); + +server.listen(port, () => { + console.log( + `cockroach drizzle rpc server listening on http://localhost:${port}`, + ); +}); + +server.on('error', (err) => { + console.error('[crdb-rpc] server error:', err); +}); + +process.on('unhandledRejection', (reason) => { + console.error('[crdb-rpc] unhandledRejection:', reason); +}); + +process.on('uncaughtException', (err) => { + console.error('[crdb-rpc] uncaughtException:', err); +}); diff --git a/templates/keynote-2/src/rpc-servers/postgres-rpc-server.ts b/templates/keynote-2/src/rpc-servers/postgres-rpc-server.ts new file mode 100644 index 00000000000..ff94146a5aa --- /dev/null +++ b/templates/keynote-2/src/rpc-servers/postgres-rpc-server.ts @@ -0,0 +1,257 @@ +import 'dotenv/config'; +import http from 'node:http'; +import { Pool } from 'pg'; +import { drizzle } from 'drizzle-orm/node-postgres'; +import { pgTable, integer, bigint as pgBigint } from 'drizzle-orm/pg-core'; +import { eq, inArray, sql } from 'drizzle-orm'; +import { RpcRequest, RpcResponse } from '../connectors/rpc/rpc_common.ts'; +import { poolMaxFromEnv } from '../helpers.ts'; + +const PG_URL = process.env.PG_URL; +if (!PG_URL) { + throw new Error('PG_URL not set'); +} + +const accounts = pgTable('accounts', { + id: integer('id').primaryKey(), + balance: pgBigint('balance', { mode: 'bigint' }).notNull(), +}); + +const pool = new Pool({ + connectionString: PG_URL, + application_name: 'pg-rpc-drizzle', + max: poolMaxFromEnv(), +}); + +const db = drizzle(pool, { schema: { accounts } }); + +async function rpcTransfer(args: Record) { + const fromId = Number(args.from_id ?? args.from); + const toId = Number(args.to_id ?? args.to); + const amount = Number(args.amount); + + if ( + !Number.isInteger(fromId) || + !Number.isInteger(toId) || + !Number.isFinite(amount) + ) { + throw new Error('invalid transfer args'); + } + if (fromId === toId || amount <= 0) return; + + const delta = BigInt(amount); + + await db.transaction(async (tx) => { + const rows = await tx + .select() + .from(accounts) + .where(inArray(accounts.id, [fromId, toId])) + .for('update') + .orderBy(accounts.id); + + if (rows.length !== 2) { + throw new Error('account_missing'); + } + + const [first, second] = rows; + const fromRow = first.id === fromId ? first : second; + const toRow = first.id === fromId ? second : first; + + if (fromRow.balance < delta) { + return; + } + + await tx + .update(accounts) + .set({ balance: fromRow.balance - delta }) + .where(eq(accounts.id, fromId)); + + await tx + .update(accounts) + .set({ balance: toRow.balance + delta }) + .where(eq(accounts.id, toId)); + }); +} + +async function rpcGetAccount(args: Record) { + const id = Number(args.id); + if (!Number.isInteger(id)) throw new Error('invalid id'); + + const rows = await db + .select() + .from(accounts) + .where(eq(accounts.id, id)) + .limit(1); + + if (rows.length === 0) return null; + const row = rows[0]!; + return { + id: row.id, + balance: row.balance.toString(), + }; +} + +async function rpcVerify() { + const rawInitial = process.env.SEED_INITIAL_BALANCE; + if (!rawInitial) { + console.warn('[pg-rpc] SEED_INITIAL_BALANCE not set; skipping verify'); + return { skipped: true }; + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + throw new Error(`invalid SEED_INITIAL_BALANCE=${rawInitial}`); + } + + const result = await db.execute( + sql` + SELECT + COUNT(*)::bigint AS count, + COALESCE(SUM("balance"), 0)::bigint AS total, + SUM( + CASE WHEN "balance" != ${initial}::bigint THEN 1 ELSE 0 END + )::bigint AS changed + FROM ${accounts} + `, + ); + + const row = (result as any).rows[0] as { + count: string | number | bigint; + total: string | number | bigint; + changed: string | number | bigint; + }; + + const count = BigInt(row.count); + const total = BigInt(row.total); + const changed = BigInt(row.changed); + const expected = initial * count; + + if (count === 0n) throw new Error('verify failed: accounts=0'); + if (total !== expected) { + throw new Error( + `verify failed: accounts=${count} total=${total} expected=${expected}`, + ); + } + if (changed === 0n) { + throw new Error('verify failed: total preserved but no balances changed'); + } + + return { + accounts: count.toString(), + total: total.toString(), + changed: changed.toString(), + }; +} + +async function rpcSeed(args: Record) { + const count = Number(args.accounts ?? process.env.SEED_ACCOUNTS ?? '0'); + const rawInitial = + (args.initialBalance as string | number | undefined) ?? + process.env.SEED_INITIAL_BALANCE; + + if (!Number.isInteger(count) || count <= 0) { + throw new Error('[pg-rpc] invalid accounts for seed'); + } + if (rawInitial === undefined || rawInitial === null) { + throw new Error('[pg-rpc] missing initialBalance for seed'); + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + throw new Error(`[pg-rpc] invalid initialBalance=${rawInitial}`); + } + + await db.transaction(async (tx) => { + await tx.execute(sql`DELETE FROM ${accounts}`); + + const batchSize = 10_000; + for (let start = 0; start < count; start += batchSize) { + const end = Math.min(start + batchSize, count); + const values: { id: number; balance: bigint }[] = []; + + for (let id = start; id < end; id++) { + values.push({ id, balance: initial }); + } + + await tx.insert(accounts).values(values as any); + } + }); + + console.log( + `[pg-rpc] seeded accounts: count=${count} initial=${initial.toString()}`, + ); +} + +async function handleRpc(body: RpcRequest): Promise { + const name = body?.name; + const args = body?.args ?? {}; + + if (!name) return { ok: false, error: 'missing name' }; + + try { + switch (name) { + case 'health': + return { ok: true, result: { status: 'ok' } }; + case 'transfer': + await rpcTransfer(args); + return { ok: true }; + case 'getAccount': + return { ok: true, result: await rpcGetAccount(args) }; + case 'verify': + return { ok: true, result: await rpcVerify() }; + case 'seed': + return { ok: true, result: await rpcSeed(args) }; + default: + return { ok: false, error: `unknown method: ${name}` }; + } + } catch (err: any) { + return { ok: false, error: String(err?.message ?? err) }; + } +} + +const port = Number(process.env.PG_RPC_PORT ?? 4101); + +const server = http.createServer((req, res) => { + const url = new URL(req.url ?? '/', `http://${req.headers.host}`); + + if (req.method === 'POST' && url.pathname === '/rpc') { + let buf = ''; + req.on('data', (chunk) => { + buf += chunk; + }); + req.on('end', async () => { + let body: RpcRequest; + try { + body = JSON.parse(buf) as RpcRequest; + } catch { + res.statusCode = 400; + res.setHeader('content-type', 'application/json'); + res.end(JSON.stringify({ ok: false, error: 'invalid json' })); + return; + } + + const rsp = await handleRpc(body); + res.statusCode = rsp.ok ? 200 : 500; + res.setHeader('content-type', 'application/json'); + res.end(JSON.stringify(rsp)); + }); + return; + } + + if (req.method === 'GET' && url.pathname === '/') { + res.statusCode = 200; + res.end('pg drizzle rpc server'); + return; + } + + res.statusCode = 404; + res.end('not found'); +}); + +server.listen(port, () => { + console.log(`pg drizzle rpc server listening on http://localhost:${port}`); +}); diff --git a/templates/keynote-2/src/rpc-servers/sqlite-rpc-server.ts b/templates/keynote-2/src/rpc-servers/sqlite-rpc-server.ts new file mode 100644 index 00000000000..7a5a1c1b789 --- /dev/null +++ b/templates/keynote-2/src/rpc-servers/sqlite-rpc-server.ts @@ -0,0 +1,277 @@ +import 'dotenv/config'; +import http from 'node:http'; +import Database from 'better-sqlite3'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import { sqliteTable, integer } from 'drizzle-orm/sqlite-core'; +import { eq, inArray } from 'drizzle-orm'; +import { + getSqliteMode, + applySqlitePragmas, + ensureSqliteDirExistsSync, + type SqliteMode, +} from '../connectors/sqlite_common.ts'; +import { RpcRequest, RpcResponse } from '../connectors/rpc/rpc_common.ts'; + +const SQLITE_FILE = process.env.SQLITE_FILE ?? './.data/accounts.sqlite'; +const mode: SqliteMode = getSqliteMode(); + +ensureSqliteDirExistsSync(SQLITE_FILE, mode); + +const dbFile = new Database(mode === 'fastest' ? ':memory:' : SQLITE_FILE); +applySqlitePragmas(dbFile, mode); + +const accounts = sqliteTable('accounts', { + id: integer('id').primaryKey(), + balance: integer('balance').notNull(), +}); + +const db = drizzle(dbFile, { schema: { accounts } }); + +function ensureSchema() { + dbFile + .prepare( + `CREATE TABLE IF NOT EXISTS accounts ( + id INTEGER PRIMARY KEY, + balance INTEGER NOT NULL + )`, + ) + .run(); +} + +ensureSchema(); + +async function rpcTransfer(args: Record) { + const fromId = Number(args.from_id ?? args.from); + const toId = Number(args.to_id ?? args.to); + const amount = Number(args.amount); + + if ( + !Number.isInteger(fromId) || + !Number.isInteger(toId) || + !Number.isFinite(amount) + ) { + throw new Error('invalid transfer args'); + } + if (fromId === toId || amount <= 0) return; + + const delta = BigInt(amount); + + db.transaction((tx) => { + const rows = tx + .select() + .from(accounts) + .where(inArray(accounts.id, [fromId, toId])) + .all(); + + if (rows.length !== 2) { + throw new Error('account_missing'); + } + + const [first, second] = rows; + const fromRow = first.id === fromId ? first : second; + const toRow = first.id === fromId ? second : first; + + const fromBal = BigInt(fromRow.balance); + const toBal = BigInt(toRow.balance); + + if (fromBal < delta) return; + + const newFrom = fromBal - delta; + const newTo = toBal + delta; + + tx.update(accounts) + .set({ balance: Number(newFrom) }) + .where(eq(accounts.id, fromId)); + + tx.update(accounts) + .set({ balance: Number(newTo) }) + .where(eq(accounts.id, toId)); + }); +} + +async function rpcGetAccount(args: Record) { + const id = Number(args.id); + if (!Number.isInteger(id)) throw new Error('invalid id'); + + const row = db + .select() + .from(accounts) + .where(eq(accounts.id, id)) + .limit(1) + .get(); + + if (!row) return null; + + return { + id: row.id, + balance: BigInt(row.balance).toString(), + }; +} + +async function rpcVerify() { + const rawInitial = process.env.SEED_INITIAL_BALANCE; + if (!rawInitial) { + console.warn('[sqlite-rpc] SEED_INITIAL_BALANCE not set; skipping verify'); + return { skipped: true }; + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + throw new Error(`invalid SEED_INITIAL_BALANCE=${rawInitial}`); + } + + // reuse the same aggregate pattern as direct sqlite connector + const row = dbFile + .prepare( + ` + SELECT + COUNT(*) AS count, + COALESCE(SUM(balance), 0) AS total, + SUM(CASE WHEN balance != ? THEN 1 ELSE 0 END) AS changed + FROM accounts + `, + ) + .get(initial.toString()) as + | { count: number; total: number; changed: number } + | undefined; + + const count = BigInt(row?.count ?? 0); + const total = BigInt(row?.total ?? 0); + const changed = BigInt(row?.changed ?? 0); + const expected = initial * count; + + if (count === 0n) { + throw new Error('verify failed: accounts=0'); + } + if (total !== expected) { + throw new Error( + `verify failed: accounts=${count} total=${total} expected=${expected}`, + ); + } + if (changed === 0n) { + throw new Error('verify failed: total preserved but no balances changed'); + } + + return { + accounts: count.toString(), + total: total.toString(), + changed: changed.toString(), + }; +} + +async function rpcSeed(args: Record) { + const count = Number(args.accounts ?? process.env.SEED_ACCOUNTS ?? '0'); + const rawInitial = + (args.initialBalance as string | number | undefined) ?? + process.env.SEED_INITIAL_BALANCE; + + if (!Number.isInteger(count) || count <= 0) { + throw new Error('[sqlite-rpc] invalid accounts for seed'); + } + if (rawInitial === undefined || rawInitial === null) { + throw new Error('[sqlite-rpc] missing initialBalance for seed'); + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + throw new Error(`[sqlite-rpc] invalid initialBalance=${rawInitial}`); + } + + const seedTx = dbFile.transaction(() => { + dbFile.prepare('DELETE FROM accounts').run(); + + const insert = dbFile.prepare( + 'INSERT INTO accounts (id, balance) VALUES (?, ?)', + ); + + const batchSize = 10_000; + for (let start = 0; start < count; start += batchSize) { + const end = Math.min(start + batchSize, count); + for (let id = start; id < end; id++) { + insert.run(id, Number(initial)); + } + } + }); + + seedTx(); + + console.log( + `[sqlite-rpc] seeded accounts: count=${count} initial=${initial.toString()}`, + ); +} + +async function handleRpc(body: RpcRequest): Promise { + const name = body?.name; + const args = body?.args ?? {}; + + if (!name) return { ok: false, error: 'missing name' }; + + try { + switch (name) { + case 'health': + return { ok: true, result: { status: 'ok' } }; + case 'transfer': + await rpcTransfer(args); + return { ok: true }; + case 'getAccount': + return { ok: true, result: await rpcGetAccount(args) }; + case 'verify': + return { ok: true, result: await rpcVerify() }; + case 'seed': + return { ok: true, result: await rpcSeed(args) }; + default: + return { ok: false, error: `unknown method: ${name}` }; + } + } catch (err: any) { + return { ok: false, error: String(err?.message ?? err) }; + } +} + +const port = Number(process.env.SQLITE_RPC_PORT ?? 4103); + +const server = http.createServer((req, res) => { + const url = new URL(req.url ?? '/', `http://${req.headers.host}`); + + if (req.method === 'POST' && url.pathname === '/rpc') { + let buf = ''; + req.on('data', (chunk) => { + buf += chunk; + }); + req.on('end', async () => { + let body: RpcRequest; + try { + body = JSON.parse(buf) as RpcRequest; + } catch { + res.statusCode = 400; + res.setHeader('content-type', 'application/json'); + res.end(JSON.stringify({ ok: false, error: 'invalid json' })); + return; + } + + const rsp = await handleRpc(body); + res.statusCode = rsp.ok ? 200 : 500; + res.setHeader('content-type', 'application/json'); + res.end(JSON.stringify(rsp)); + }); + return; + } + + if (req.method === 'GET' && url.pathname === '/') { + res.statusCode = 200; + res.end('sqlite drizzle rpc server'); + return; + } + + res.statusCode = 404; + res.end('not found'); +}); + +server.listen(port, () => { + console.log( + `sqlite drizzle rpc server listening on http://localhost:${port}`, + ); +}); diff --git a/templates/keynote-2/src/rpc-servers/supabase-rpc-server.ts b/templates/keynote-2/src/rpc-servers/supabase-rpc-server.ts new file mode 100644 index 00000000000..354eeb364d1 --- /dev/null +++ b/templates/keynote-2/src/rpc-servers/supabase-rpc-server.ts @@ -0,0 +1,272 @@ +import 'dotenv/config'; +import http from 'node:http'; +import { Pool } from 'pg'; +import { drizzle } from 'drizzle-orm/node-postgres'; +import { pgTable, integer, bigint as pgBigint } from 'drizzle-orm/pg-core'; +import { eq, inArray, sql } from 'drizzle-orm'; +import type { RpcRequest, RpcResponse } from '../connectors/rpc/rpc_common.ts'; +import { poolMaxFromEnv } from '../helpers.ts'; + +const DB_URL = process.env.SUPABASE_DB_URL ?? process.env.PG_URL; +if (!DB_URL) { + throw new Error('SUPABASE_DB_URL / PG_URL not set'); +} + +const accounts = pgTable('accounts', { + id: integer('id').primaryKey(), + balance: pgBigint('balance', { mode: 'bigint' }).notNull(), +}); + +const pool = new Pool({ + connectionString: DB_URL, + application_name: 'supabase-rpc-drizzle', + max: poolMaxFromEnv(), +}); + +const db = drizzle(pool, { schema: { accounts } }); + +async function rpcTransfer(args: Record) { + const fromId = Number(args.from_id ?? args.from); + const toId = Number(args.to_id ?? args.to); + const amount = Number(args.amount); + + if ( + !Number.isInteger(fromId) || + !Number.isInteger(toId) || + !Number.isFinite(amount) + ) { + throw new Error('invalid transfer args'); + } + if (fromId === toId || amount <= 0) return; + + const delta = BigInt(amount); + + await db.transaction(async (tx) => { + const rows = await tx + .select() + .from(accounts) + .where(inArray(accounts.id, [fromId, toId])) + .for('update') + .orderBy(accounts.id); + + if (rows.length !== 2) { + throw new Error('account_missing'); + } + + const [first, second] = rows; + const fromRow = first.id === fromId ? first : second; + const toRow = first.id === fromId ? second : first; + + if (fromRow.balance < delta) { + // Not enough balance; just skip + return; + } + + await tx + .update(accounts) + .set({ balance: fromRow.balance - delta }) + .where(eq(accounts.id, fromId)); + + await tx + .update(accounts) + .set({ balance: toRow.balance + delta }) + .where(eq(accounts.id, toId)); + }); +} + +async function rpcGetAccount(args: Record) { + const id = Number(args.id); + if (!Number.isInteger(id)) throw new Error('invalid id'); + + const rows = await db + .select() + .from(accounts) + .where(eq(accounts.id, id)) + .limit(1); + + if (rows.length === 0) return null; + const row = rows[0]!; + return { + id: row.id, + balance: row.balance.toString(), + }; +} + +async function rpcVerify() { + const rawInitial = process.env.SEED_INITIAL_BALANCE; + if (!rawInitial) { + console.warn( + '[supabase-rpc] SEED_INITIAL_BALANCE not set; skipping verify', + ); + return { skipped: true }; + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + throw new Error(`invalid SEED_INITIAL_BALANCE=${rawInitial}`); + } + + const result = await db.execute( + sql` + SELECT + COUNT(*)::bigint AS count, + COALESCE(SUM("balance"), 0)::bigint AS total, + SUM( + CASE WHEN "balance" != ${initial}::bigint THEN 1 ELSE 0 END + )::bigint AS changed + FROM ${accounts} + `, + ); + + const row = (result as any).rows[0] as { + count: string | number | bigint; + total: string | number | bigint; + changed: string | number | bigint; + }; + + const count = BigInt(row.count); + const total = BigInt(row.total); + const changed = BigInt(row.changed); + const expected = initial * count; + + if (count === 0n) throw new Error('verify failed: accounts=0'); + if (total !== expected) { + throw new Error( + `verify failed: accounts=${count} total=${total} expected=${expected}`, + ); + } + if (changed === 0n) { + throw new Error('verify failed: total preserved but no balances changed'); + } + + return { + accounts: count.toString(), + total: total.toString(), + changed: changed.toString(), + }; +} + +async function rpcSeed(args: Record) { + const count = Number(args.accounts ?? process.env.SEED_ACCOUNTS ?? '0'); + const rawInitial = + (args.initialBalance as string | number | undefined) ?? + process.env.SEED_INITIAL_BALANCE; + + if (!Number.isInteger(count) || count <= 0) { + throw new Error('[supabase-rpc] invalid accounts for seed'); + } + if (rawInitial === undefined || rawInitial === null) { + throw new Error('[supabase-rpc] missing initialBalance for seed'); + } + + let initial: bigint; + try { + initial = BigInt(rawInitial); + } catch { + throw new Error(`[supabase-rpc] invalid initialBalance=${rawInitial}`); + } + + await db.transaction(async (tx) => { + // Keep schema, just wipe rows + await tx.execute(sql`DELETE FROM ${accounts}`); + + const batchSize = 10_000; + for (let start = 0; start < count; start += batchSize) { + const end = Math.min(start + batchSize, count); + const values: { id: number; balance: bigint }[] = []; + + for (let id = start; id < end; id++) { + values.push({ id, balance: initial }); + } + + await tx.insert(accounts).values(values as any); + } + }); + + console.log( + `[supabase-rpc] seeded accounts: count=${count} initial=${initial.toString()}`, + ); +} + +async function handleRpc(body: RpcRequest): Promise { + const name = body?.name; + const args = body?.args ?? {}; + + if (!name) return { ok: false, error: 'missing name' }; + + try { + switch (name) { + case 'health': + return { ok: true, result: { status: 'ok' } }; + case 'transfer': + await rpcTransfer(args); + return { ok: true }; + case 'getAccount': + return { ok: true, result: await rpcGetAccount(args) }; + case 'verify': + return { ok: true, result: await rpcVerify() }; + case 'seed': + return { ok: true, result: await rpcSeed(args) }; + default: + return { ok: false, error: `unknown method: ${name}` }; + } + } catch (err: any) { + return { ok: false, error: String(err?.message ?? err) }; + } +} + +const port = Number( + process.env.SUPABASE_RPC_PORT ?? process.env.PG_RPC_PORT ?? 4106, +); + +const server = http.createServer((req, res) => { + const url = new URL(req.url ?? '/', `http://${req.headers.host}`); + + if (req.method === 'POST' && url.pathname === '/rpc') { + let buf = ''; + req.on('data', (chunk) => { + buf += chunk; + }); + req.on('end', async () => { + let body: RpcRequest; + try { + body = JSON.parse(buf) as RpcRequest; + } catch { + res.statusCode = 400; + res.setHeader('content-type', 'application/json'); + res.end(JSON.stringify({ ok: false, error: 'invalid json' })); + return; + } + + const rsp = await handleRpc(body); + res.statusCode = rsp.ok ? 200 : 500; + res.setHeader('content-type', 'application/json'); + res.end(JSON.stringify(rsp)); + }); + return; + } + + if (req.method === 'GET' && url.pathname === '/') { + res.statusCode = 200; + res.end('supabase drizzle rpc server'); + return; + } + + res.statusCode = 404; + res.end('not found'); +}); + +server.listen(port, () => { + console.log( + `supabase drizzle rpc server listening on http://localhost:${port}`, + ); +}); + +for (const sig of ['SIGINT', 'SIGTERM'] as const) { + process.on(sig, async () => { + await pool.end(); + process.exit(0); + }); +} diff --git a/templates/keynote-2/src/scenario_recipes/reducer_single.ts b/templates/keynote-2/src/scenario_recipes/reducer_single.ts new file mode 100644 index 00000000000..15a0bc34e9c --- /dev/null +++ b/templates/keynote-2/src/scenario_recipes/reducer_single.ts @@ -0,0 +1,16 @@ +import type { ReducerConnector } from '../core/connectors'; + +export async function reducer_single( + conn: unknown, + from: number, + to: number, + amount: number, +): Promise { + if (from === to || amount <= 0) return; + + await (conn as ReducerConnector).reducer('transfer', { + from, + to, + amount: BigInt(amount), + }); +} diff --git a/templates/keynote-2/src/scenario_recipes/rpc_single_call.ts b/templates/keynote-2/src/scenario_recipes/rpc_single_call.ts new file mode 100644 index 00000000000..37b133fb002 --- /dev/null +++ b/templates/keynote-2/src/scenario_recipes/rpc_single_call.ts @@ -0,0 +1,35 @@ +import type { RpcConnector } from '../core/connectors'; + +export async function rpc_single_call( + conn: unknown, + from: number, + to: number, + amount: number, +): Promise { + if (from === to || amount <= 0) return; + + const api = conn as RpcConnector; + + // If this env var is "1", call the sharded Convex transfer + const useShardedConvex = process.env.CONVEX_USE_SHARDED_COUNTER === '1'; + + const fn = + api.name === 'convex' + ? useShardedConvex + ? 'transfer:transfer_sharded' + : 'transfer:transfer' + : 'transfer'; + + for (let attempts = 0; attempts < 3; attempts++) { + try { + await api.call(fn, { amount, from_id: from, to_id: to }); + return; + } catch (e: any) { + const msg = String(e?.message ?? ''); + const retriable = /429|502|503|504/.test(msg); + if (!retriable || attempts === 2) throw e; + + await new Promise((r) => setTimeout(r, 50 * (attempts + 1))); + } + } +} diff --git a/templates/keynote-2/src/scenario_recipes/sql_single_statement.ts b/templates/keynote-2/src/scenario_recipes/sql_single_statement.ts new file mode 100644 index 00000000000..e7f2709a29f --- /dev/null +++ b/templates/keynote-2/src/scenario_recipes/sql_single_statement.ts @@ -0,0 +1,61 @@ +export async function sql_single_statement( + conn: unknown, + from: number, + to: number, + amount: number, +): Promise { + if (from === to || amount <= 0) return; + + const db = conn as any; + const isSqlite = !!db?.prepare || db?.name === 'sqlite'; + if (!db?.exec) + throw new Error('sql_single_statement requires a SqlConnector'); + + const pgSql = ` + WITH + locks AS ( + SELECT id, balance + FROM accounts + WHERE id IN ($1, $2) + ORDER BY id + FOR UPDATE + ), + ok AS ( + SELECT 1 + WHERE (SELECT balance FROM locks WHERE id = $1) >= $3 + AND EXISTS (SELECT 1 FROM locks WHERE id = $2) + ) + UPDATE accounts + SET balance = CASE + WHEN id = $1 THEN balance - $3 + WHEN id = $2 THEN balance + $3 + END + WHERE id IN ($1, $2) + AND EXISTS (SELECT 1 FROM ok) + RETURNING id; + `; + + const sqliteSql = ` + UPDATE accounts + SET balance = CASE + WHEN id = ?1 THEN balance - ?3 + WHEN id = ?2 THEN balance + ?3 + END + WHERE id IN (?1, ?2) + AND EXISTS ( + SELECT 1 + FROM accounts AS from_acct + JOIN accounts AS to_acct ON to_acct.id = ?2 + WHERE from_acct.id = ?1 + AND from_acct.balance >= ?3 + ) + RETURNING id; + `; + + const sql = isSqlite ? sqliteSql : pgSql; + const params = [from, to, amount]; + + const result = await db.exec(sql, params); + + if (result.length === 0) return; +} diff --git a/templates/keynote-2/src/tests/test-1/bun.ts b/templates/keynote-2/src/tests/test-1/bun.ts new file mode 100644 index 00000000000..c0fe1f757cc --- /dev/null +++ b/templates/keynote-2/src/tests/test-1/bun.ts @@ -0,0 +1,7 @@ +import { rpc_single_call } from '../../scenario_recipes/rpc_single_call.ts'; + +export default { + system: 'bun', + label: 'bun', + run: rpc_single_call, +}; diff --git a/templates/keynote-2/src/tests/test-1/cockroach.ts b/templates/keynote-2/src/tests/test-1/cockroach.ts new file mode 100644 index 00000000000..f2e996b84b9 --- /dev/null +++ b/templates/keynote-2/src/tests/test-1/cockroach.ts @@ -0,0 +1,7 @@ +import { sql_single_statement } from '../../scenario_recipes/sql_single_statement.ts'; + +export default { + system: 'cockroach', + label: 'cockroach:single_statement', + run: sql_single_statement, +}; diff --git a/templates/keynote-2/src/tests/test-1/cockroach_drizzle.ts b/templates/keynote-2/src/tests/test-1/cockroach_drizzle.ts new file mode 100644 index 00000000000..3ce14cc8236 --- /dev/null +++ b/templates/keynote-2/src/tests/test-1/cockroach_drizzle.ts @@ -0,0 +1,7 @@ +import { rpc_single_call } from '../../scenario_recipes/rpc_single_call.ts'; + +export default { + system: 'cockroach_drizzle', + label: 'node+drizzle:rpc_single_call (cockroach)', + run: rpc_single_call, +}; diff --git a/templates/keynote-2/src/tests/test-1/cockroach_rpc.ts b/templates/keynote-2/src/tests/test-1/cockroach_rpc.ts new file mode 100644 index 00000000000..5bb81f58b11 --- /dev/null +++ b/templates/keynote-2/src/tests/test-1/cockroach_rpc.ts @@ -0,0 +1,7 @@ +import { rpc_single_call } from '../../scenario_recipes/rpc_single_call.ts'; + +export default { + system: 'cockroach_rpc', + label: 'cockroach_rpc:rpc_single_call', + run: rpc_single_call, +}; diff --git a/templates/keynote-2/src/tests/test-1/convex.ts b/templates/keynote-2/src/tests/test-1/convex.ts new file mode 100644 index 00000000000..3fd1aa0e040 --- /dev/null +++ b/templates/keynote-2/src/tests/test-1/convex.ts @@ -0,0 +1,7 @@ +import { rpc_single_call } from '../../scenario_recipes/rpc_single_call.ts'; + +export default { + system: 'convex', + label: 'convex rpc_single', + run: rpc_single_call, +}; diff --git a/templates/keynote-2/src/tests/test-1/planetscale_pg_rpc.ts b/templates/keynote-2/src/tests/test-1/planetscale_pg_rpc.ts new file mode 100644 index 00000000000..fb06cfdecd4 --- /dev/null +++ b/templates/keynote-2/src/tests/test-1/planetscale_pg_rpc.ts @@ -0,0 +1,7 @@ +import { rpc_single_call } from '../../scenario_recipes/rpc_single_call.ts'; + +export default { + system: 'planetscale_pg_rpc', + label: 'planetscale_pg_rpc:rpc_single_call', + run: rpc_single_call, +}; diff --git a/templates/keynote-2/src/tests/test-1/postgres.ts b/templates/keynote-2/src/tests/test-1/postgres.ts new file mode 100644 index 00000000000..f66c3fb48de --- /dev/null +++ b/templates/keynote-2/src/tests/test-1/postgres.ts @@ -0,0 +1,7 @@ +import { sql_single_statement } from '../../scenario_recipes/sql_single_statement.ts'; + +export default { + system: 'postgres', + label: 'postgres / node+postgres:single_statement', + run: sql_single_statement, +}; diff --git a/templates/keynote-2/src/tests/test-1/postgres_drizzle.ts b/templates/keynote-2/src/tests/test-1/postgres_drizzle.ts new file mode 100644 index 00000000000..c773a414178 --- /dev/null +++ b/templates/keynote-2/src/tests/test-1/postgres_drizzle.ts @@ -0,0 +1,7 @@ +import { rpc_single_call } from '../../scenario_recipes/rpc_single_call.ts'; + +export default { + system: 'postgres_drizzle', + label: 'node+drizzle:rpc_single_call (postgres)', + run: rpc_single_call, +}; diff --git a/templates/keynote-2/src/tests/test-1/postgres_rpc.ts b/templates/keynote-2/src/tests/test-1/postgres_rpc.ts new file mode 100644 index 00000000000..ce78170d08d --- /dev/null +++ b/templates/keynote-2/src/tests/test-1/postgres_rpc.ts @@ -0,0 +1,7 @@ +import { rpc_single_call } from '../../scenario_recipes/rpc_single_call.ts'; + +export default { + system: 'postgres_rpc', + label: 'postgres_rpc:rpc_single_call', + run: rpc_single_call, +}; diff --git a/templates/keynote-2/src/tests/test-1/spacetimedb.ts b/templates/keynote-2/src/tests/test-1/spacetimedb.ts new file mode 100644 index 00000000000..2329af10d4a --- /dev/null +++ b/templates/keynote-2/src/tests/test-1/spacetimedb.ts @@ -0,0 +1,7 @@ +import { reducer_single } from '../../scenario_recipes/reducer_single.ts'; + +export default { + system: 'spacetimedb', + label: 'spacetimedb:reducer_single', + run: reducer_single, +}; diff --git a/templates/keynote-2/src/tests/test-1/sqlite.ts b/templates/keynote-2/src/tests/test-1/sqlite.ts new file mode 100644 index 00000000000..839b96f6cf8 --- /dev/null +++ b/templates/keynote-2/src/tests/test-1/sqlite.ts @@ -0,0 +1,9 @@ +import { sql_single_statement } from '../../scenario_recipes/sql_single_statement.ts'; +import sqlite from '../../connectors/direct/sqlite.ts'; + +export default { + system: 'sqlite', + label: 'sqlite:single_statement', + connector: sqlite, + run: sql_single_statement, +}; diff --git a/templates/keynote-2/src/tests/test-1/sqlite_drizzle.ts b/templates/keynote-2/src/tests/test-1/sqlite_drizzle.ts new file mode 100644 index 00000000000..4782e05a6ce --- /dev/null +++ b/templates/keynote-2/src/tests/test-1/sqlite_drizzle.ts @@ -0,0 +1,7 @@ +import { rpc_single_call } from '../../scenario_recipes/rpc_single_call.ts'; + +export default { + system: 'sqlite_drizzle', + label: 'node+drizzle:rpc_single_call (sqlite)', + run: rpc_single_call, +}; diff --git a/templates/keynote-2/src/tests/test-1/sqlite_rpc.ts b/templates/keynote-2/src/tests/test-1/sqlite_rpc.ts new file mode 100644 index 00000000000..f6c61625db8 --- /dev/null +++ b/templates/keynote-2/src/tests/test-1/sqlite_rpc.ts @@ -0,0 +1,7 @@ +import { rpc_single_call } from '../../scenario_recipes/rpc_single_call.ts'; + +export default { + system: 'sqlite_rpc', + label: 'sqlite_rpc:rpc_single_call', + run: rpc_single_call, +}; diff --git a/templates/keynote-2/src/tests/test-1/supabase.ts b/templates/keynote-2/src/tests/test-1/supabase.ts new file mode 100644 index 00000000000..cbe64a8a71c --- /dev/null +++ b/templates/keynote-2/src/tests/test-1/supabase.ts @@ -0,0 +1,7 @@ +import { rpc_single_call } from '../../scenario_recipes/rpc_single_call.ts'; + +export default { + system: 'supabase', + label: 'vercel+supabase:single_rpc_call', + run: rpc_single_call, +}; diff --git a/templates/keynote-2/src/tests/test-1/supabase_rpc.ts b/templates/keynote-2/src/tests/test-1/supabase_rpc.ts new file mode 100644 index 00000000000..b639525fef1 --- /dev/null +++ b/templates/keynote-2/src/tests/test-1/supabase_rpc.ts @@ -0,0 +1,7 @@ +import { rpc_single_call } from '../../scenario_recipes/rpc_single_call.ts'; + +export default { + system: 'supabase_rpc', + label: 'supabase_rpc:rpc_single_call', + run: rpc_single_call, +}; diff --git a/templates/keynote-2/src/tests/types.ts b/templates/keynote-2/src/tests/types.ts new file mode 100644 index 00000000000..b6082e3a750 --- /dev/null +++ b/templates/keynote-2/src/tests/types.ts @@ -0,0 +1,13 @@ +import type { ConnectorKey } from '../connectors'; + +export type TestCase = { + system: ConnectorKey; + label?: string; + run: ( + conn: unknown, + from: number, + to: number, + amount: number, + ) => Promise; +}; +export type TestCaseModule = { default: TestCase }; diff --git a/templates/keynote-2/supabase/.gitignore b/templates/keynote-2/supabase/.gitignore new file mode 100644 index 00000000000..ad9264f0b14 --- /dev/null +++ b/templates/keynote-2/supabase/.gitignore @@ -0,0 +1,8 @@ +# Supabase +.branches +.temp + +# dotenvx +.env.keys +.env.local +.env.*.local diff --git a/templates/keynote-2/supabase/config.toml b/templates/keynote-2/supabase/config.toml new file mode 100644 index 00000000000..835e51313b4 --- /dev/null +++ b/templates/keynote-2/supabase/config.toml @@ -0,0 +1,362 @@ +# For detailed configuration reference documentation, visit: +# https://supabase.com/docs/guides/local-development/cli/config +# A string used to distinguish different Supabase projects on the same host. Defaults to the +# working directory name when running `supabase init`. +project_id = "spacetime-simulations" + +[api] +enabled = true +# Port to use for the API URL. +port = 54321 +# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API +# endpoints. `public` and `graphql_public` schemas are included by default. +schemas = ["public", "graphql_public"] +# Extra schemas to add to the search_path of every request. +extra_search_path = ["public", "extensions"] +# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size +# for accidental or malicious requests. +max_rows = 1000 + +[api.tls] +# Enable HTTPS endpoints locally using a self-signed certificate. +enabled = false +# Paths to self-signed certificate pair. +# cert_path = "../certs/my-cert.pem" +# key_path = "../certs/my-key.pem" + +[db] +# Port to use for the local database URL. +port = 54322 +# Port used by db diff command to initialize the shadow database. +shadow_port = 54320 +# The database major version to use. This has to be the same as your remote database's. Run `SHOW +# server_version;` on the remote database to check. +major_version = 17 + +[db.pooler] +enabled = true +# Port to use for the local connection pooler. +port = 54329 +# Specifies when a server connection can be reused by other clients. +# Configure one of the supported pooler modes: `transaction`, `session`. +pool_mode = "transaction" +# How many server connections to allow per user/database pair. +default_pool_size = 1000 +# Maximum number of client connections allowed. +max_client_conn = 10000 + +# [db.vault] +# secret_key = "env(SECRET_VALUE)" + +[db.migrations] +# If disabled, migrations will be skipped during a db push or reset. +enabled = true +# Specifies an ordered list of schema files that describe your database. +# Supports glob patterns relative to supabase directory: "./schemas/*.sql" +schema_paths = [] + +[db.seed] +# If enabled, seeds the database after migrations during a db reset. +enabled = true +# Specifies an ordered list of seed files to load during db reset. +# Supports glob patterns relative to supabase directory: "./seeds/*.sql" +sql_paths = ["./seed.sql"] + +[db.network_restrictions] +# Enable management of network restrictions. +enabled = false +# List of IPv4 CIDR blocks allowed to connect to the database. +# Defaults to allow all IPv4 connections. Set empty array to block all IPs. +allowed_cidrs = ["0.0.0.0/0"] +# List of IPv6 CIDR blocks allowed to connect to the database. +# Defaults to allow all IPv6 connections. Set empty array to block all IPs. +allowed_cidrs_v6 = ["::/0"] + +[realtime] +enabled = true +# Bind realtime via either IPv4 or IPv6. (default: IPv4) +# ip_version = "IPv6" +# The maximum length in bytes of HTTP request headers. (default: 4096) +# max_header_length = 4096 + +[studio] +enabled = true +# Port to use for Supabase Studio. +port = 54323 +# External URL of the API server that frontend connects to. +api_url = "http://127.0.0.1" +# OpenAI API Key to use for Supabase AI in the Supabase Studio. +openai_api_key = "env(OPENAI_API_KEY)" + +# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they +# are monitored, and you can view the emails that would have been sent from the web interface. +[inbucket] +enabled = true +# Port to use for the email testing server web interface. +port = 54324 +# Uncomment to expose additional ports for testing user applications that send emails. +# smtp_port = 54325 +# pop3_port = 54326 +# admin_email = "admin@email.com" +# sender_name = "Admin" + +[storage] +enabled = true +# The maximum file size allowed (e.g. "5MB", "500KB"). +file_size_limit = "50MiB" + +# Image transformation API is available to Supabase Pro plan. +# [storage.image_transformation] +# enabled = true + +# Uncomment to configure local storage buckets +# [storage.buckets.images] +# public = false +# file_size_limit = "50MiB" +# allowed_mime_types = ["image/png", "image/jpeg"] +# objects_path = "./images" + +[auth] +enabled = true +# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used +# in emails. +site_url = "http://127.0.0.1:3000" +# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. +additional_redirect_urls = ["https://127.0.0.1:3000"] +# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). +jwt_expiry = 3600 +# JWT issuer URL. If not set, defaults to the local API URL (http://127.0.0.1:/auth/v1). +# jwt_issuer = "" +# Path to JWT signing key. DO NOT commit your signing keys file to git. +# signing_keys_path = "./signing_keys.json" +# If disabled, the refresh token will never expire. +enable_refresh_token_rotation = true +# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. +# Requires enable_refresh_token_rotation = true. +refresh_token_reuse_interval = 10 +# Allow/disallow new user signups to your project. +enable_signup = true +# Allow/disallow anonymous sign-ins to your project. +enable_anonymous_sign_ins = false +# Allow/disallow testing manual linking of accounts +enable_manual_linking = false +# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more. +minimum_password_length = 6 +# Passwords that do not meet the following requirements will be rejected as weak. Supported values +# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols` +password_requirements = "" + +[auth.rate_limit] +# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled. +email_sent = 2 +# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled. +sms_sent = 30 +# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true. +anonymous_users = 30 +# Number of sessions that can be refreshed in a 5 minute interval per IP address. +token_refresh = 150 +# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users). +sign_in_sign_ups = 30 +# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address. +token_verifications = 30 +# Number of Web3 logins that can be made in a 5 minute interval per IP address. +web3 = 30 + +# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`. +# [auth.captcha] +# enabled = true +# provider = "hcaptcha" +# secret = "" + +[auth.email] +# Allow/disallow new user signups via email to your project. +enable_signup = true +# If enabled, a user will be required to confirm any email change on both the old, and new email +# addresses. If disabled, only the new email is required to confirm. +double_confirm_changes = true +# If enabled, users need to confirm their email address before signing in. +enable_confirmations = false +# If enabled, users will need to reauthenticate or have logged in recently to change their password. +secure_password_change = false +# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. +max_frequency = "1s" +# Number of characters used in the email OTP. +otp_length = 6 +# Number of seconds before the email OTP expires (defaults to 1 hour). +otp_expiry = 3600 + +# Use a production-ready SMTP server +# [auth.email.smtp] +# enabled = true +# host = "smtp.sendgrid.net" +# port = 587 +# user = "apikey" +# pass = "env(SENDGRID_API_KEY)" +# admin_email = "admin@email.com" +# sender_name = "Admin" + +# Uncomment to customize email template +# [auth.email.template.invite] +# subject = "You have been invited" +# content_path = "./supabase/templates/invite.html" + +# Uncomment to customize notification email template +# [auth.email.notification.password_changed] +# enabled = true +# subject = "Your password has been changed" +# content_path = "./templates/password_changed_notification.html" + +[auth.sms] +# Allow/disallow new user signups via SMS to your project. +enable_signup = false +# If enabled, users need to confirm their phone number before signing in. +enable_confirmations = false +# Template for sending OTP to users +template = "Your code is {{ .Code }}" +# Controls the minimum amount of time that must pass before sending another sms otp. +max_frequency = "5s" + +# Use pre-defined map of phone number to OTP for testing. +# [auth.sms.test_otp] +# 4152127777 = "123456" + +# Configure logged in session timeouts. +# [auth.sessions] +# Force log out after the specified duration. +# timebox = "24h" +# Force log out if the user has been inactive longer than the specified duration. +# inactivity_timeout = "8h" + +# This hook runs before a new user is created and allows developers to reject the request based on the incoming user object. +# [auth.hook.before_user_created] +# enabled = true +# uri = "pg-functions://postgres/auth/before-user-created-hook" + +# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used. +# [auth.hook.custom_access_token] +# enabled = true +# uri = "pg-functions:////" + +# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`. +[auth.sms.twilio] +enabled = false +account_sid = "" +message_service_sid = "" +# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead: +auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)" + +# Multi-factor-authentication is available to Supabase Pro plan. +[auth.mfa] +# Control how many MFA factors can be enrolled at once per user. +max_enrolled_factors = 10 + +# Control MFA via App Authenticator (TOTP) +[auth.mfa.totp] +enroll_enabled = false +verify_enabled = false + +# Configure MFA via Phone Messaging +[auth.mfa.phone] +enroll_enabled = false +verify_enabled = false +otp_length = 6 +template = "Your code is {{ .Code }}" +max_frequency = "5s" + +# Configure MFA via WebAuthn +# [auth.mfa.web_authn] +# enroll_enabled = true +# verify_enabled = true + +# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, +# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, +# `twitter`, `slack`, `spotify`, `workos`, `zoom`. +[auth.external.apple] +enabled = false +client_id = "" +# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: +secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" +# Overrides the default auth redirectUrl. +redirect_uri = "" +# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, +# or any other third-party OIDC providers. +url = "" +# If enabled, the nonce check will be skipped. Required for local sign in with Google auth. +skip_nonce_check = false +# If enabled, it will allow the user to successfully authenticate when the provider does not return an email address. +email_optional = false + +# Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard. +# You can configure "web3" rate limit in the [auth.rate_limit] section and set up [auth.captcha] if self-hosting. +[auth.web3.solana] +enabled = false + +# Use Firebase Auth as a third-party provider alongside Supabase Auth. +[auth.third_party.firebase] +enabled = false +# project_id = "my-firebase-project" + +# Use Auth0 as a third-party provider alongside Supabase Auth. +[auth.third_party.auth0] +enabled = false +# tenant = "my-auth0-tenant" +# tenant_region = "us" + +# Use AWS Cognito (Amplify) as a third-party provider alongside Supabase Auth. +[auth.third_party.aws_cognito] +enabled = false +# user_pool_id = "my-user-pool-id" +# user_pool_region = "us-east-1" + +# Use Clerk as a third-party provider alongside Supabase Auth. +[auth.third_party.clerk] +enabled = false +# Obtain from https://clerk.com/setup/supabase +# domain = "example.clerk.accounts.dev" + +# OAuth server configuration +[auth.oauth_server] +# Enable OAuth server functionality +enabled = false +# Path for OAuth consent flow UI +authorization_url_path = "/oauth/consent" +# Allow dynamic client registration +allow_dynamic_registration = false + +[edge_runtime] +enabled = true +# Supported request policies: `oneshot`, `per_worker`. +# `per_worker` (default) — enables hot reload during local development. +# `oneshot` — fallback mode if hot reload causes issues (e.g. in large repos or with symlinks). +policy = "per_worker" +# Port to attach the Chrome inspector for debugging edge functions. +inspector_port = 8083 +# The Deno major version to use. +deno_version = 2 + +# [edge_runtime.secrets] +# secret_key = "env(SECRET_VALUE)" + +[analytics] +enabled = true +port = 54327 +# Configure one of the supported backends: `postgres`, `bigquery`. +backend = "postgres" + +# Experimental features may be deprecated any time +[experimental] +# Configures Postgres storage engine to use OrioleDB (S3) +orioledb_version = "" +# Configures S3 bucket URL, eg. .s3-.amazonaws.com +s3_host = "env(S3_HOST)" +# Configures S3 bucket region, eg. us-east-1 +s3_region = "env(S3_REGION)" +# Configures AWS_ACCESS_KEY_ID for S3 bucket +s3_access_key = "env(S3_ACCESS_KEY)" +# Configures AWS_SECRET_ACCESS_KEY for S3 bucket +s3_secret_key = "env(S3_SECRET_KEY)" + +[db.settings] +shared_buffers = "2GB" +work_mem = "16MB" +max_connections= "10000" diff --git a/templates/keynote-2/tools/benchmarks.html b/templates/keynote-2/tools/benchmarks.html new file mode 100644 index 00000000000..41be794770c --- /dev/null +++ b/templates/keynote-2/tools/benchmarks.html @@ -0,0 +1,598 @@ + + + + + SpacetimeDB Benchmarks + + + +
+
+ SpacetimeDB logo + Transactional, relational, multiplayer computing. +
+

Benchmark Streams

+

+ Drop in a JSON benchmark file and watch each connector stream transactions in real time. + Dot density reflects throughput (TPS) and + dot speed reflects median latency (p50). Right side shows TPS, p50, and sample count. +

+
+ Drag & drop a JSON file or paste raw JSON anywhere in this window. + +
+
+ +
+ + + + diff --git a/templates/keynote-2/tsconfig.json b/templates/keynote-2/tsconfig.json new file mode 100644 index 00000000000..75abdef2659 --- /dev/null +++ b/templates/keynote-2/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} From 776c323b1c4c475635557821ae0c5fafcd2f744b Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Wed, 21 Jan 2026 09:03:42 -0500 Subject: [PATCH 2/8] Potential fix for code scanning alert no. 249: Client-side cross-site scripting Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: bradleyshep <148254416+bradleyshep@users.noreply.github.com> --- templates/keynote-2/tools/benchmarks.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/templates/keynote-2/tools/benchmarks.html b/templates/keynote-2/tools/benchmarks.html index 41be794770c..ab504d545a5 100644 --- a/templates/keynote-2/tools/benchmarks.html +++ b/templates/keynote-2/tools/benchmarks.html @@ -432,9 +432,11 @@

Benchmark Streams

const nameBlock = document.createElement("div"); nameBlock.className = "lane-name-block"; - nameBlock.innerHTML = ` -
${c.name}
- `; + + const primaryLabel = document.createElement("div"); + primaryLabel.className = "lane-label-primary"; + primaryLabel.textContent = c.name || ""; + nameBlock.appendChild(primaryLabel); label.appendChild(logoWrap); label.appendChild(nameBlock); From 5d81eecec110819aeae57bb386b4a98f436cce57 Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Wed, 21 Jan 2026 09:04:13 -0500 Subject: [PATCH 3/8] Potential fix for code scanning alert no. 248: Clear-text logging of sensitive information Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: bradleyshep <148254416+bradleyshep@users.noreply.github.com> --- templates/keynote-2/src/init/init_sqlite_seed_in_docker.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/keynote-2/src/init/init_sqlite_seed_in_docker.ts b/templates/keynote-2/src/init/init_sqlite_seed_in_docker.ts index a1805a11920..cee6f61be30 100644 --- a/templates/keynote-2/src/init/init_sqlite_seed_in_docker.ts +++ b/templates/keynote-2/src/init/init_sqlite_seed_in_docker.ts @@ -78,6 +78,8 @@ async function main() { } main().catch((err) => { - console.error('[sqlite-seed] failed:', err); + console.error( + '[sqlite-seed] failed: an error occurred during sqlite seeding (see secure logs for details)', + ); process.exit(1); }); From 528edcc156a4da79e2d14ef2a193e7dc45f82841 Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Wed, 21 Jan 2026 09:04:25 -0500 Subject: [PATCH 4/8] Potential fix for code scanning alert no. 251: Information exposure through a stack trace Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: bradleyshep <148254416+bradleyshep@users.noreply.github.com> --- templates/keynote-2/src/rpc-servers/cockroach-rpc-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/keynote-2/src/rpc-servers/cockroach-rpc-server.ts b/templates/keynote-2/src/rpc-servers/cockroach-rpc-server.ts index 0c5c8d1b522..05fad190c08 100644 --- a/templates/keynote-2/src/rpc-servers/cockroach-rpc-server.ts +++ b/templates/keynote-2/src/rpc-servers/cockroach-rpc-server.ts @@ -210,7 +210,7 @@ async function handleRpc(body: RpcRequest): Promise { } } catch (err: any) { console.error('[crdb-rpc] rpc error:', name, err); - return { ok: false, error: String(err?.message ?? err) }; + return { ok: false, error: 'internal server error' }; } } From 6e2771626583cc725524ef6d85e6bf9b3bfb5d64 Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Wed, 21 Jan 2026 09:04:37 -0500 Subject: [PATCH 5/8] Potential fix for code scanning alert no. 253: Information exposure through a stack trace Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: bradleyshep <148254416+bradleyshep@users.noreply.github.com> --- templates/keynote-2/src/rpc-servers/postgres-rpc-server.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/keynote-2/src/rpc-servers/postgres-rpc-server.ts b/templates/keynote-2/src/rpc-servers/postgres-rpc-server.ts index ff94146a5aa..17542e01c85 100644 --- a/templates/keynote-2/src/rpc-servers/postgres-rpc-server.ts +++ b/templates/keynote-2/src/rpc-servers/postgres-rpc-server.ts @@ -209,7 +209,8 @@ async function handleRpc(body: RpcRequest): Promise { return { ok: false, error: `unknown method: ${name}` }; } } catch (err: any) { - return { ok: false, error: String(err?.message ?? err) }; + console.error('Error while handling RPC request:', err); + return { ok: false, error: 'internal server error' }; } } From 7810894258c92e6fc848042f85336d2f46cbc53a Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Wed, 21 Jan 2026 09:04:56 -0500 Subject: [PATCH 6/8] Potential fix for code scanning alert no. 254: Information exposure through a stack trace Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: bradleyshep <148254416+bradleyshep@users.noreply.github.com> --- templates/keynote-2/src/rpc-servers/supabase-rpc-server.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/keynote-2/src/rpc-servers/supabase-rpc-server.ts b/templates/keynote-2/src/rpc-servers/supabase-rpc-server.ts index 354eeb364d1..893b5d9557b 100644 --- a/templates/keynote-2/src/rpc-servers/supabase-rpc-server.ts +++ b/templates/keynote-2/src/rpc-servers/supabase-rpc-server.ts @@ -213,7 +213,8 @@ async function handleRpc(body: RpcRequest): Promise { return { ok: false, error: `unknown method: ${name}` }; } } catch (err: any) { - return { ok: false, error: String(err?.message ?? err) }; + console.error('Error handling RPC request:', err); + return { ok: false, error: 'internal error' }; } } From d0f33cc0e5ab3f41052e3b5b1289530b9f16ce92 Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Wed, 21 Jan 2026 09:05:02 -0500 Subject: [PATCH 7/8] Potential fix for code scanning alert no. 252: Information exposure through a stack trace Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: bradleyshep <148254416+bradleyshep@users.noreply.github.com> --- templates/keynote-2/src/rpc-servers/sqlite-rpc-server.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/keynote-2/src/rpc-servers/sqlite-rpc-server.ts b/templates/keynote-2/src/rpc-servers/sqlite-rpc-server.ts index 7a5a1c1b789..8640d9da16e 100644 --- a/templates/keynote-2/src/rpc-servers/sqlite-rpc-server.ts +++ b/templates/keynote-2/src/rpc-servers/sqlite-rpc-server.ts @@ -227,7 +227,9 @@ async function handleRpc(body: RpcRequest): Promise { return { ok: false, error: `unknown method: ${name}` }; } } catch (err: any) { - return { ok: false, error: String(err?.message ?? err) }; + // Log full error details on the server, but return a generic message to the client. + console.error('Unhandled error in handleRpc:', err); + return { ok: false, error: 'internal error' }; } } From 1ed919db11c15d946422eef8b6d2ea28db49ddea Mon Sep 17 00:00:00 2001 From: bradleyshep <148254416+bradleyshep@users.noreply.github.com> Date: Wed, 21 Jan 2026 09:05:11 -0500 Subject: [PATCH 8/8] Potential fix for code scanning alert no. 250: Information exposure through a stack trace Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: bradleyshep <148254416+bradleyshep@users.noreply.github.com> --- templates/keynote-2/bun/bun-server.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/keynote-2/bun/bun-server.ts b/templates/keynote-2/bun/bun-server.ts index 309491088a7..ffc75a607ad 100644 --- a/templates/keynote-2/bun/bun-server.ts +++ b/templates/keynote-2/bun/bun-server.ts @@ -51,7 +51,8 @@ async function handleRpc(body: RpcRequest): Promise { } } catch (err: any) { console.error('[bun] rpc error:', err); - return { ok: false, error: String(err?.message ?? err) }; + // Return a generic error message to avoid exposing internal details or stack traces. + return { ok: false, error: 'internal error' }; } }