diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 359d30d..58c2660 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -14,10 +14,10 @@ A second copy of the truth is a copy that goes stale, so it is gone. | Question | Read | |---|---| | What is Solon, what are the models and routes? | `README.md` | -| How do I work in the repo — commands, CI, Prisma, gotchas? | `AGENTS.md` | +| How do I work in the repo — commands, CI, Drizzle, gotchas? | `AGENTS.md` | | What are the design rules? | `docs/development/ui-guidelines.md` | | What are the tokens? | `@fleet/design-tokens` — one package, shared by all three products | -| What is the schema? | `prisma/schema.prisma` (9 models) | +| What is the schema? | `src/lib/db/schema.ts` (9 models) | | Which env vars exist? | `.env.example` | ## The three that matter most diff --git a/.claude/settings.json b/.claude/settings.json index cc37893..8333aa1 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -7,7 +7,7 @@ "Bash(npm run typecheck)", "Bash(npm run lint)", "Bash(npm run test)", - "Bash(npm run prisma:generate)", + "Bash(npm run db:generate)", "mcp__github__actions_list", "mcp__github__pull_request_read" ] diff --git a/.cursor/rules/code-quality.mdc b/.cursor/rules/code-quality.mdc index eae1e3f..0365b3d 100644 --- a/.cursor/rules/code-quality.mdc +++ b/.cursor/rules/code-quality.mdc @@ -11,7 +11,7 @@ Next.js 16 Bitcoin governance platform: - `src/app/` - Pages and API routes - `src/components/` - UI components - `src/lib/` - Business logic, Bitcoin integration -- `prisma/` - Database schema +- `src/lib/db/` - Database schema (Drizzle), client, enums ## Core Principles @@ -25,7 +25,7 @@ Next.js 16 Bitcoin governance platform: |------|----------| | Navigation | `lib/site-config.ts` | | Design Tokens | Tailwind config | -| Database | `prisma/schema.prisma` | +| Database | `src/lib/db/schema.ts` | | Bitcoin Config | Environment variables | ### 3. Security First @@ -53,10 +53,10 @@ className="bg-slate-800 shadow-lg" // Wrong ## TypeScript Standards -Derive types from the Prisma schema — never redeclare a model by hand: +Derive types from the Drizzle schema — never redeclare a model by hand: ```typescript -import type { Vote, Proposal, TreasurySource } from '@prisma/client'; +import type { Vote, Proposal, TreasurySource } from '@/lib/db/schema'; ``` There is **no transaction model**. The treasury is watch-only: a diff --git a/.env.example b/.env.example index 1a4c8c7..89475a3 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ # --- Required --------------------------------------------------------------- -# PostgreSQL. `npm run prisma:push` creates the schema against this. +# PostgreSQL. `npm run db:migrate` creates the schema against this. DATABASE_URL="postgresql://solon:solon@localhost:5432/solon" # NextAuth v5. Generate with: openssl rand -base64 32 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a90d16e..d0a2d28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,11 @@ # Golden CI floor — see bitbaum/fleet templates/ci/README.md. -# solon: npm + Prisma. Floor = verify (lint + typecheck + design:check + test) + build. +# solon: npm + Drizzle. Floor = verify (lint + typecheck + design:check + test) + build. # Lint is `eslint .` — Next 16 removed `next lint`, and ESLint 9 wants flat # config, so the rules live in eslint.config.mjs (eslint-config-next already # exports a flat array). `.eslintrc.json` is gone. -# `next build` is hermetic: the server components that touch Prisma catch DB -# errors and render demo fallbacks, so no live DB is needed. Prisma has no -# postinstall, so the client is generated before typecheck/build need its types. +# `next build` is hermetic: the server components that touch the database catch +# errors and render demo fallbacks, so no live DB is needed. Drizzle has no +# codegen at all — types flow from src/lib/db/schema.ts at typecheck time. # The Playwright e2e smoke tests stay deferred (need a running app). name: CI @@ -37,14 +37,11 @@ jobs: - name: Install run: npm ci - - name: Prisma generate (types for typecheck) - run: npx prisma generate - # SSOT: lint + typecheck, defined in package.json `verify`. - name: Verify run: npm run verify - # Build gate — hermetic (Prisma calls fall back without a live DB, see header). + # Build gate — hermetic (DB calls fall back without a live database, see header). - name: Build run: npm run build @@ -81,11 +78,8 @@ jobs: - name: Install run: npm ci - - name: Prisma generate - run: npx prisma generate - - name: Migration replay (baseline + seed on a fresh database) - run: npx prisma migrate deploy + run: npx drizzle-kit migrate - name: Vote spine integration spec run: INTEGRATION=1 npx vitest run src/lib/domain/__tests__/vote-spine.integration.test.ts diff --git a/.prettierignore b/.prettierignore index ef97778..7b5da53 100644 --- a/.prettierignore +++ b/.prettierignore @@ -39,3 +39,7 @@ yarn.lock # is where it is most opinionated and least useful, and it would bury the real # diff. Remove this line when you want docs formatted too. *.md + +# drizzle-kit owns these files and rewrites them on every generate; formatting +# them creates a fight between two writers over generated output. +drizzle/meta diff --git a/AGENTS.md b/AGENTS.md index d5267af..f1b66ec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,7 @@ Read `README.md` for what the product is. This file is how to work in the repo. - **Framework**: Next.js 16.3 (App Router, `output: 'standalone'` for the Hetzner deploy) - **Language**: TypeScript 5.5 (strict) -- **Database**: PostgreSQL via Prisma 5.17 +- **Database**: PostgreSQL via Drizzle ORM (`drizzle-orm/node-postgres` + `pg` Pool) - **Auth**: NextAuth v5 (beta) — sign in with OrangeCat (OAuth) - **Styling**: Tailwind CSS 3.4, tokens in `src/app/globals.css` - **Bitcoin**: `@noble/*`, `bs58check` (message signing / signature verification) @@ -20,21 +20,26 @@ Read `README.md` for what the product is. This file is how to work in the repo. ```bash npm run dev # next dev (localhost:3000) -npm run build # next build (standalone) — run `prisma:generate` first (no postinstall) +npm run build # next build (standalone) — no codegen step, Drizzle types come from the schema npm run verify # lint + typecheck + design:check + test — run before every commit ``` `npm run verify` is the single source of truth for "is this change clean?" CI calls it verbatim. Green `verify` locally ⇒ green CI. -## Prisma / database +## Drizzle / database -- Schema SSOT: `prisma/schema.prisma` (9 models). Types flow from it via `@prisma/client`. -- **`prisma generate` is NOT automatic** — no postinstall hook. Run `npm run prisma:generate` - before `typecheck` or `build` so the client types exist. CI does this explicitly. -- **`db push` against a real database is manual.** `npm run prisma:push` is run by hand. - Do not add a push step to the workflow. -- Migration history begins at `prisma/migrations/0_init` (versioned baseline). +- Schema SSOT: `src/lib/db/schema.ts` (9 models). Types flow from it via `$inferSelect`; + enum vocabulary lives in `src/lib/db/enums.ts` (dependency-free, safe for client code). +- **There is no codegen.** Typecheck and build read the schema module directly. +- Migrations live in `drizzle/` (`npm run db:generate` after a schema change; + `npm run db:migrate` applies them). **Running migrations against a real + database is manual / deploy-time only** — do not add a push step to CI's verify job. +- Migration history begins at `drizzle/0000_init` (baseline matching the tables the + retired Prisma migrations created — byte-identical names, proven by pg_dump diff) + plus `drizzle/0001_seed_org1` (org #1 reference data). The production database + predates this history and is baselined in the deploy ledger + (`public._deploy_schema_history`), so these two files never run there. ## CI @@ -42,8 +47,8 @@ calls it verbatim. Green `verify` locally ⇒ green CI. | Job | What it does | |---|---| -| `verify` | `npm ci` → `prisma generate` → `npm run verify` → `npm run build` | -| `integration` | `prisma migrate deploy` on a **fresh** Postgres, then the vote-spine integration spec | +| `verify` | `npm ci` → `npm run verify` → `npm run build` | +| `integration` | `drizzle-kit migrate` on a **fresh** Postgres, then the vote-spine integration spec | The integration job is why migrations must replay cleanly from the baseline: it builds the database from scratch every run. diff --git a/README.md b/README.md index 48ce6c4..0be5728 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,8 @@ Proposal (DRAFT) ──open──> VotingSession (OPEN) ──signed votes── ## Data model -`prisma/schema.prisma` is the SSOT — **9 models**, with types, validation and API -contracts derived from it. +`src/lib/db/schema.ts` is the SSOT — **9 models** (Drizzle), with types, validation +and API contracts derived from it. ``` Organization ── has many ──> Member (HUMAN | AGENT, own Bitcoin key) @@ -129,7 +129,7 @@ Bitcoin message signing and verification is `src/lib/bitcoin/message.ts`. |---|---| | Framework | Next.js 16.3 (App Router, `output: 'standalone'`) | | Language | TypeScript 5.5 (strict) | -| Database | PostgreSQL + Prisma 5.17 | +| Database | PostgreSQL + Drizzle ORM | | Auth | NextAuth v5 (beta) | | Styling | Tailwind CSS 3.4 — tokens in `src/app/globals.css` | | Bitcoin | `@noble/*` + `bs58check` (signing / verification) | @@ -147,8 +147,7 @@ cd solon npm install cp .env.example .env # set DATABASE_URL -npm run prisma:generate # no postinstall hook — run this before typecheck/build -npm run prisma:push +npm run db:migrate # applies drizzle/ migrations (baseline + seed) npm run dev # http://localhost:3000 ``` diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..84d8c00 --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,25 @@ +/** + * Drizzle Kit config. Schema lives in src/lib/db/schema.ts; generated SQL in + * ./drizzle (the deploy pipeline's apply-schema.sh probes exactly that path). + * + * dbCredentials are only needed by db:migrate / db:push / studio — `db:generate` + * is offline. Env files are loaded with Node's own loader (no dotenv dep). + */ +import { defineConfig } from "drizzle-kit"; + +for (const f of [".env.local", ".env"]) { + try { + process.loadEnvFile(f); + } catch { + // file absent (CI) — DATABASE_URL comes from the environment + } +} + +export default defineConfig({ + schema: "./src/lib/db/schema.ts", + out: "./drizzle", + dialect: "postgresql", + dbCredentials: { + url: process.env.DATABASE_URL ?? "postgres://unset:unset@localhost:5432/unset", + }, +}); diff --git a/drizzle/0000_init.sql b/drizzle/0000_init.sql new file mode 100644 index 0000000..364b15f --- /dev/null +++ b/drizzle/0000_init.sql @@ -0,0 +1,142 @@ +CREATE TYPE "public"."AuditEventType" AS ENUM('ORG_CREATED', 'MEMBER_ADDED', 'MEMBER_STATUS_CHANGED', 'PROPOSAL_CREATED', 'SESSION_OPENED', 'VOTE_CAST', 'SESSION_CLOSED', 'POLICY_ACTIVATED');--> statement-breakpoint +CREATE TYPE "public"."DecisionCategory" AS ENUM('ALLOCATION_POLICY', 'TREASURY_SPEND', 'OPERATIONS', 'AID_DISBURSEMENT', 'MEMBERSHIP', 'SAFETY', 'GOVERNANCE_RULES');--> statement-breakpoint +CREATE TYPE "public"."Electorate" AS ENUM('ALL_MEMBERS', 'HUMANS_ONLY');--> statement-breakpoint +CREATE TYPE "public"."KeyCustody" AS ENUM('SELF', 'SERVICE');--> statement-breakpoint +CREATE TYPE "public"."MemberStatus" AS ENUM('ACTIVE', 'SUSPENDED', 'RETIRED');--> statement-breakpoint +CREATE TYPE "public"."MemberType" AS ENUM('HUMAN', 'AGENT');--> statement-breakpoint +CREATE TYPE "public"."PolicyStatus" AS ENUM('ACTIVE', 'SUPERSEDED');--> statement-breakpoint +CREATE TYPE "public"."ProposalStatus" AS ENUM('DRAFT', 'OPEN', 'CLOSED');--> statement-breakpoint +CREATE TYPE "public"."SessionOutcome" AS ENUM('APPROVED', 'REJECTED', 'EXPIRED');--> statement-breakpoint +CREATE TYPE "public"."SessionStatus" AS ENUM('ACTIVE', 'CLOSED');--> statement-breakpoint +CREATE TYPE "public"."VoteThreshold" AS ENUM('SIMPLE_MAJORITY', 'SUPERMAJORITY');--> statement-breakpoint +CREATE TYPE "public"."VotingMethod" AS ENUM('SINGLE_CHOICE', 'CONSENT', 'APPROVAL', 'DOT', 'SCORE', 'RANKED');--> statement-breakpoint +CREATE TABLE "agent_api_keys" ( + "id" text PRIMARY KEY NOT NULL, + "member_id" text NOT NULL, + "key_hash" text NOT NULL, + "created_at" timestamp (3) DEFAULT now() NOT NULL, + "revoked_at" timestamp (3) +); +--> statement-breakpoint +CREATE TABLE "audit_events" ( + "id" text PRIMARY KEY NOT NULL, + "organization_id" text NOT NULL, + "event_type" "AuditEventType" NOT NULL, + "actor_member_id" text, + "subject_type" text NOT NULL, + "subject_id" text NOT NULL, + "payload" jsonb NOT NULL, + "created_at" timestamp (3) DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "members" ( + "id" text PRIMARY KEY NOT NULL, + "organization_id" text NOT NULL, + "display_name" text NOT NULL, + "member_type" "MemberType" NOT NULL, + "key_custody" "KeyCustody" NOT NULL, + "bitcoin_address" varchar(90) NOT NULL, + "public_key_hex" text, + "voting_weight" numeric(10, 2) DEFAULT 1 NOT NULL, + "status" "MemberStatus" DEFAULT 'ACTIVE' NOT NULL, + "oc_actor_id" text, + "system" text, + "joined_at" timestamp (3) DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "organizations" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "name" text NOT NULL, + "description" text, + "governance_profile" text DEFAULT 'TOWN' NOT NULL, + "created_at" timestamp (3) DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "policies" ( + "id" text PRIMARY KEY NOT NULL, + "organization_id" text NOT NULL, + "key" text NOT NULL, + "version" integer NOT NULL, + "content" jsonb NOT NULL, + "status" "PolicyStatus" NOT NULL, + "approved_by_session_id" text, + "activated_at" timestamp (3) DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "proposals" ( + "id" text PRIMARY KEY NOT NULL, + "organization_id" text NOT NULL, + "category" "DecisionCategory" NOT NULL, + "title" text NOT NULL, + "body" text NOT NULL, + "policy_key" text, + "proposed_content" jsonb, + "target" text, + "content_hash" text, + "method" "VotingMethod", + "options" jsonb, + "proposer_member_id" text NOT NULL, + "proposer_signature" text NOT NULL, + "status" "ProposalStatus" DEFAULT 'DRAFT' NOT NULL, + "created_at" timestamp (3) DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "treasury_sources" ( + "id" text PRIMARY KEY NOT NULL, + "organization_id" text NOT NULL, + "label" text NOT NULL, + "address" varchar(90) NOT NULL, + "created_at" timestamp (3) DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "votes" ( + "id" text PRIMARY KEY NOT NULL, + "session_id" text NOT NULL, + "member_id" text NOT NULL, + "ballot" jsonb NOT NULL, + "weight" numeric(10, 2) NOT NULL, + "signed_message" text NOT NULL, + "signature" text NOT NULL, + "created_at" timestamp (3) DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "voting_sessions" ( + "id" text PRIMARY KEY NOT NULL, + "proposal_id" text NOT NULL, + "status" "SessionStatus" DEFAULT 'ACTIVE' NOT NULL, + "opens_at" timestamp (3) DEFAULT now() NOT NULL, + "closes_at" timestamp (3) NOT NULL, + "electorate" "Electorate" NOT NULL, + "method" "VotingMethod" DEFAULT 'SINGLE_CHOICE' NOT NULL, + "options" jsonb, + "dot_budget" integer, + "threshold" "VoteThreshold" NOT NULL, + "quorum_percent" integer NOT NULL, + "eligible_count" integer NOT NULL, + "eligible_weight" numeric(12, 2) NOT NULL, + "outcome" "SessionOutcome", + "winning_option_key" text, + "closed_at" timestamp (3) +); +--> statement-breakpoint +ALTER TABLE "agent_api_keys" ADD CONSTRAINT "agent_api_keys_member_id_fkey" FOREIGN KEY ("member_id") REFERENCES "public"."members"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "audit_events" ADD CONSTRAINT "audit_events_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "members" ADD CONSTRAINT "members_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "policies" ADD CONSTRAINT "policies_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "policies" ADD CONSTRAINT "policies_approved_by_session_id_fkey" FOREIGN KEY ("approved_by_session_id") REFERENCES "public"."voting_sessions"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "proposals" ADD CONSTRAINT "proposals_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "proposals" ADD CONSTRAINT "proposals_proposer_member_id_fkey" FOREIGN KEY ("proposer_member_id") REFERENCES "public"."members"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "treasury_sources" ADD CONSTRAINT "treasury_sources_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "votes" ADD CONSTRAINT "votes_session_id_fkey" FOREIGN KEY ("session_id") REFERENCES "public"."voting_sessions"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "votes" ADD CONSTRAINT "votes_member_id_fkey" FOREIGN KEY ("member_id") REFERENCES "public"."members"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "voting_sessions" ADD CONSTRAINT "voting_sessions_proposal_id_fkey" FOREIGN KEY ("proposal_id") REFERENCES "public"."proposals"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +CREATE UNIQUE INDEX "agent_api_keys_key_hash_key" ON "agent_api_keys" USING btree ("key_hash");--> statement-breakpoint +CREATE INDEX "audit_events_organization_id_created_at_idx" ON "audit_events" USING btree ("organization_id","created_at");--> statement-breakpoint +CREATE UNIQUE INDEX "members_oc_actor_id_key" ON "members" USING btree ("oc_actor_id");--> statement-breakpoint +CREATE UNIQUE INDEX "members_organization_id_bitcoin_address_key" ON "members" USING btree ("organization_id","bitcoin_address");--> statement-breakpoint +CREATE UNIQUE INDEX "organizations_slug_key" ON "organizations" USING btree ("slug");--> statement-breakpoint +CREATE UNIQUE INDEX "policies_organization_id_key_version_key" ON "policies" USING btree ("organization_id","key","version");--> statement-breakpoint +CREATE UNIQUE INDEX "treasury_sources_organization_id_address_key" ON "treasury_sources" USING btree ("organization_id","address");--> statement-breakpoint +CREATE UNIQUE INDEX "votes_session_id_member_id_key" ON "votes" USING btree ("session_id","member_id");--> statement-breakpoint +CREATE UNIQUE INDEX "voting_sessions_proposal_id_key" ON "voting_sessions" USING btree ("proposal_id"); \ No newline at end of file diff --git a/prisma/migrations/1_seed_org1/migration.sql b/drizzle/0001_seed_org1.sql similarity index 100% rename from prisma/migrations/1_seed_org1/migration.sql rename to drizzle/0001_seed_org1.sql diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..0fcc367 --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,1107 @@ +{ + "id": "3308c5ab-1d42-4d5b-8d27-255f3615eb6a", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "member_id": { + "name": "member_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "agent_api_keys_key_hash_key": { + "name": "agent_api_keys_key_hash_key", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_member_id_fkey": { + "name": "agent_api_keys_member_id_fkey", + "tableFrom": "agent_api_keys", + "tableTo": "members", + "columnsFrom": [ + "member_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_events": { + "name": "audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "AuditEventType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "actor_member_id": { + "name": "actor_member_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_events_organization_id_created_at_idx": { + "name": "audit_events_organization_id_created_at_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_events_organization_id_fkey": { + "name": "audit_events_organization_id_fkey", + "tableFrom": "audit_events", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.members": { + "name": "members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "member_type": { + "name": "member_type", + "type": "MemberType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "key_custody": { + "name": "key_custody", + "type": "KeyCustody", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "bitcoin_address": { + "name": "bitcoin_address", + "type": "varchar(90)", + "primaryKey": false, + "notNull": true + }, + "public_key_hex": { + "name": "public_key_hex", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "voting_weight": { + "name": "voting_weight", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "1" + }, + "status": { + "name": "status", + "type": "MemberStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "oc_actor_id": { + "name": "oc_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system": { + "name": "system", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "members_oc_actor_id_key": { + "name": "members_oc_actor_id_key", + "columns": [ + { + "expression": "oc_actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "members_organization_id_bitcoin_address_key": { + "name": "members_organization_id_bitcoin_address_key", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bitcoin_address", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "members_organization_id_fkey": { + "name": "members_organization_id_fkey", + "tableFrom": "members", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "governance_profile": { + "name": "governance_profile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'TOWN'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organizations_slug_key": { + "name": "organizations_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.policies": { + "name": "policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "PolicyStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "approved_by_session_id": { + "name": "approved_by_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activated_at": { + "name": "activated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "policies_organization_id_key_version_key": { + "name": "policies_organization_id_key_version_key", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "policies_organization_id_fkey": { + "name": "policies_organization_id_fkey", + "tableFrom": "policies", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "policies_approved_by_session_id_fkey": { + "name": "policies_approved_by_session_id_fkey", + "tableFrom": "policies", + "tableTo": "voting_sessions", + "columnsFrom": [ + "approved_by_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proposals": { + "name": "proposals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "DecisionCategory", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "policy_key": { + "name": "policy_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_content": { + "name": "proposed_content", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "method": { + "name": "method", + "type": "VotingMethod", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proposer_member_id": { + "name": "proposer_member_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "proposer_signature": { + "name": "proposer_signature", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "ProposalStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "proposals_organization_id_fkey": { + "name": "proposals_organization_id_fkey", + "tableFrom": "proposals", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "proposals_proposer_member_id_fkey": { + "name": "proposals_proposer_member_id_fkey", + "tableFrom": "proposals", + "tableTo": "members", + "columnsFrom": [ + "proposer_member_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.treasury_sources": { + "name": "treasury_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address": { + "name": "address", + "type": "varchar(90)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "treasury_sources_organization_id_address_key": { + "name": "treasury_sources_organization_id_address_key", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "address", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "treasury_sources_organization_id_fkey": { + "name": "treasury_sources_organization_id_fkey", + "tableFrom": "treasury_sources", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.votes": { + "name": "votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "member_id": { + "name": "member_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ballot": { + "name": "ballot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "weight": { + "name": "weight", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true + }, + "signed_message": { + "name": "signed_message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "votes_session_id_member_id_key": { + "name": "votes_session_id_member_id_key", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "member_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "votes_session_id_fkey": { + "name": "votes_session_id_fkey", + "tableFrom": "votes", + "tableTo": "voting_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "votes_member_id_fkey": { + "name": "votes_member_id_fkey", + "tableFrom": "votes", + "tableTo": "members", + "columnsFrom": [ + "member_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.voting_sessions": { + "name": "voting_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "proposal_id": { + "name": "proposal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "SessionStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "opens_at": { + "name": "opens_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closes_at": { + "name": "closes_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "electorate": { + "name": "electorate", + "type": "Electorate", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "VotingMethod", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'SINGLE_CHOICE'" + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "dot_budget": { + "name": "dot_budget", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "threshold": { + "name": "threshold", + "type": "VoteThreshold", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "quorum_percent": { + "name": "quorum_percent", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "eligible_count": { + "name": "eligible_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "eligible_weight": { + "name": "eligible_weight", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "SessionOutcome", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "winning_option_key": { + "name": "winning_option_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "voting_sessions_proposal_id_key": { + "name": "voting_sessions_proposal_id_key", + "columns": [ + { + "expression": "proposal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "voting_sessions_proposal_id_fkey": { + "name": "voting_sessions_proposal_id_fkey", + "tableFrom": "voting_sessions", + "tableTo": "proposals", + "columnsFrom": [ + "proposal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.AuditEventType": { + "name": "AuditEventType", + "schema": "public", + "values": [ + "ORG_CREATED", + "MEMBER_ADDED", + "MEMBER_STATUS_CHANGED", + "PROPOSAL_CREATED", + "SESSION_OPENED", + "VOTE_CAST", + "SESSION_CLOSED", + "POLICY_ACTIVATED" + ] + }, + "public.DecisionCategory": { + "name": "DecisionCategory", + "schema": "public", + "values": [ + "ALLOCATION_POLICY", + "TREASURY_SPEND", + "OPERATIONS", + "AID_DISBURSEMENT", + "MEMBERSHIP", + "SAFETY", + "GOVERNANCE_RULES" + ] + }, + "public.Electorate": { + "name": "Electorate", + "schema": "public", + "values": [ + "ALL_MEMBERS", + "HUMANS_ONLY" + ] + }, + "public.KeyCustody": { + "name": "KeyCustody", + "schema": "public", + "values": [ + "SELF", + "SERVICE" + ] + }, + "public.MemberStatus": { + "name": "MemberStatus", + "schema": "public", + "values": [ + "ACTIVE", + "SUSPENDED", + "RETIRED" + ] + }, + "public.MemberType": { + "name": "MemberType", + "schema": "public", + "values": [ + "HUMAN", + "AGENT" + ] + }, + "public.PolicyStatus": { + "name": "PolicyStatus", + "schema": "public", + "values": [ + "ACTIVE", + "SUPERSEDED" + ] + }, + "public.ProposalStatus": { + "name": "ProposalStatus", + "schema": "public", + "values": [ + "DRAFT", + "OPEN", + "CLOSED" + ] + }, + "public.SessionOutcome": { + "name": "SessionOutcome", + "schema": "public", + "values": [ + "APPROVED", + "REJECTED", + "EXPIRED" + ] + }, + "public.SessionStatus": { + "name": "SessionStatus", + "schema": "public", + "values": [ + "ACTIVE", + "CLOSED" + ] + }, + "public.VoteThreshold": { + "name": "VoteThreshold", + "schema": "public", + "values": [ + "SIMPLE_MAJORITY", + "SUPERMAJORITY" + ] + }, + "public.VotingMethod": { + "name": "VotingMethod", + "schema": "public", + "values": [ + "SINGLE_CHOICE", + "CONSENT", + "APPROVAL", + "DOT", + "SCORE", + "RANKED" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..4a1840a --- /dev/null +++ b/drizzle/meta/0001_snapshot.json @@ -0,0 +1,1107 @@ +{ + "id": "1a8e5d65-e57c-47fa-ad4c-82952b706b3f", + "prevId": "3308c5ab-1d42-4d5b-8d27-255f3615eb6a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "member_id": { + "name": "member_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "agent_api_keys_key_hash_key": { + "name": "agent_api_keys_key_hash_key", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "agent_api_keys_member_id_fkey": { + "name": "agent_api_keys_member_id_fkey", + "tableFrom": "agent_api_keys", + "columnsFrom": [ + "member_id" + ], + "tableTo": "members", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_events": { + "name": "audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "AuditEventType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "actor_member_id": { + "name": "actor_member_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_events_organization_id_created_at_idx": { + "name": "audit_events_organization_id_created_at_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "audit_events_organization_id_fkey": { + "name": "audit_events_organization_id_fkey", + "tableFrom": "audit_events", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.members": { + "name": "members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "member_type": { + "name": "member_type", + "type": "MemberType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "key_custody": { + "name": "key_custody", + "type": "KeyCustody", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "bitcoin_address": { + "name": "bitcoin_address", + "type": "varchar(90)", + "primaryKey": false, + "notNull": true + }, + "public_key_hex": { + "name": "public_key_hex", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "voting_weight": { + "name": "voting_weight", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "1" + }, + "status": { + "name": "status", + "type": "MemberStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "oc_actor_id": { + "name": "oc_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system": { + "name": "system", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "members_oc_actor_id_key": { + "name": "members_oc_actor_id_key", + "columns": [ + { + "expression": "oc_actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "members_organization_id_bitcoin_address_key": { + "name": "members_organization_id_bitcoin_address_key", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bitcoin_address", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "members_organization_id_fkey": { + "name": "members_organization_id_fkey", + "tableFrom": "members", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "governance_profile": { + "name": "governance_profile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'TOWN'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organizations_slug_key": { + "name": "organizations_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.policies": { + "name": "policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "PolicyStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "approved_by_session_id": { + "name": "approved_by_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activated_at": { + "name": "activated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "policies_organization_id_key_version_key": { + "name": "policies_organization_id_key_version_key", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "policies_organization_id_fkey": { + "name": "policies_organization_id_fkey", + "tableFrom": "policies", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "restrict" + }, + "policies_approved_by_session_id_fkey": { + "name": "policies_approved_by_session_id_fkey", + "tableFrom": "policies", + "columnsFrom": [ + "approved_by_session_id" + ], + "tableTo": "voting_sessions", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proposals": { + "name": "proposals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "DecisionCategory", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "policy_key": { + "name": "policy_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_content": { + "name": "proposed_content", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "method": { + "name": "method", + "type": "VotingMethod", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proposer_member_id": { + "name": "proposer_member_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "proposer_signature": { + "name": "proposer_signature", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "ProposalStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "proposals_organization_id_fkey": { + "name": "proposals_organization_id_fkey", + "tableFrom": "proposals", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "restrict" + }, + "proposals_proposer_member_id_fkey": { + "name": "proposals_proposer_member_id_fkey", + "tableFrom": "proposals", + "columnsFrom": [ + "proposer_member_id" + ], + "tableTo": "members", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.treasury_sources": { + "name": "treasury_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address": { + "name": "address", + "type": "varchar(90)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "treasury_sources_organization_id_address_key": { + "name": "treasury_sources_organization_id_address_key", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "address", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "treasury_sources_organization_id_fkey": { + "name": "treasury_sources_organization_id_fkey", + "tableFrom": "treasury_sources", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.votes": { + "name": "votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "member_id": { + "name": "member_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ballot": { + "name": "ballot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "weight": { + "name": "weight", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true + }, + "signed_message": { + "name": "signed_message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "votes_session_id_member_id_key": { + "name": "votes_session_id_member_id_key", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "member_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "votes_session_id_fkey": { + "name": "votes_session_id_fkey", + "tableFrom": "votes", + "columnsFrom": [ + "session_id" + ], + "tableTo": "voting_sessions", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "restrict" + }, + "votes_member_id_fkey": { + "name": "votes_member_id_fkey", + "tableFrom": "votes", + "columnsFrom": [ + "member_id" + ], + "tableTo": "members", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.voting_sessions": { + "name": "voting_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "proposal_id": { + "name": "proposal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "SessionStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "opens_at": { + "name": "opens_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closes_at": { + "name": "closes_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "electorate": { + "name": "electorate", + "type": "Electorate", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "VotingMethod", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'SINGLE_CHOICE'" + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "dot_budget": { + "name": "dot_budget", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "threshold": { + "name": "threshold", + "type": "VoteThreshold", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "quorum_percent": { + "name": "quorum_percent", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "eligible_count": { + "name": "eligible_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "eligible_weight": { + "name": "eligible_weight", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "SessionOutcome", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "winning_option_key": { + "name": "winning_option_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "voting_sessions_proposal_id_key": { + "name": "voting_sessions_proposal_id_key", + "columns": [ + { + "expression": "proposal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "voting_sessions_proposal_id_fkey": { + "name": "voting_sessions_proposal_id_fkey", + "tableFrom": "voting_sessions", + "columnsFrom": [ + "proposal_id" + ], + "tableTo": "proposals", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.AuditEventType": { + "name": "AuditEventType", + "schema": "public", + "values": [ + "ORG_CREATED", + "MEMBER_ADDED", + "MEMBER_STATUS_CHANGED", + "PROPOSAL_CREATED", + "SESSION_OPENED", + "VOTE_CAST", + "SESSION_CLOSED", + "POLICY_ACTIVATED" + ] + }, + "public.DecisionCategory": { + "name": "DecisionCategory", + "schema": "public", + "values": [ + "ALLOCATION_POLICY", + "TREASURY_SPEND", + "OPERATIONS", + "AID_DISBURSEMENT", + "MEMBERSHIP", + "SAFETY", + "GOVERNANCE_RULES" + ] + }, + "public.Electorate": { + "name": "Electorate", + "schema": "public", + "values": [ + "ALL_MEMBERS", + "HUMANS_ONLY" + ] + }, + "public.KeyCustody": { + "name": "KeyCustody", + "schema": "public", + "values": [ + "SELF", + "SERVICE" + ] + }, + "public.MemberStatus": { + "name": "MemberStatus", + "schema": "public", + "values": [ + "ACTIVE", + "SUSPENDED", + "RETIRED" + ] + }, + "public.MemberType": { + "name": "MemberType", + "schema": "public", + "values": [ + "HUMAN", + "AGENT" + ] + }, + "public.PolicyStatus": { + "name": "PolicyStatus", + "schema": "public", + "values": [ + "ACTIVE", + "SUPERSEDED" + ] + }, + "public.ProposalStatus": { + "name": "ProposalStatus", + "schema": "public", + "values": [ + "DRAFT", + "OPEN", + "CLOSED" + ] + }, + "public.SessionOutcome": { + "name": "SessionOutcome", + "schema": "public", + "values": [ + "APPROVED", + "REJECTED", + "EXPIRED" + ] + }, + "public.SessionStatus": { + "name": "SessionStatus", + "schema": "public", + "values": [ + "ACTIVE", + "CLOSED" + ] + }, + "public.VoteThreshold": { + "name": "VoteThreshold", + "schema": "public", + "values": [ + "SIMPLE_MAJORITY", + "SUPERMAJORITY" + ] + }, + "public.VotingMethod": { + "name": "VotingMethod", + "schema": "public", + "values": [ + "SINGLE_CHOICE", + "CONSENT", + "APPROVAL", + "DOT", + "SCORE", + "RANKED" + ] + } + }, + "schemas": {}, + "views": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..97d8f37 --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1788301110166, + "tag": "0000_init", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1788301114024, + "tag": "0001_seed_org1", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0b2061a..f516d3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,10 @@ "@fleet/design-tokens": "github:bitbaum/design-tokens#v1.1.0", "@noble/hashes": "2.4.0", "@noble/secp256k1": "3.1.0", - "@prisma/adapter-pg": "^7.10.0", - "@prisma/client": "^7.10.0", "@scure/base": "^2.3.0", "bs58check": "^4.0.0", "clsx": "2.1.1", + "drizzle-orm": "^0.45.1", "lucide-react": "1.35.0", "next": "16.3.3", "next-auth": "^5.0.0-beta.32", @@ -32,11 +31,11 @@ "@types/pg": "^8.23.1", "@types/react": "19.2.18", "@types/react-dom": "19.2.5", + "drizzle-kit": "^0.31.9", "eslint": "10.9.1", "eslint-config-next": "16.3.3", "postcss": "8.5.26", "prettier": "3.9.6", - "prisma": "7.10.0", "puppeteer": "25.9.0", "ts-node": "10.9.2", "tsx": "^4.23.12", @@ -386,36 +385,13 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@electric-sql/pglite": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.3.tgz", - "integrity": "sha512-ichuWTgtd4mOM1G4SpyGJa5trT03lWbMypDV0fUXUCXg5hiHqVAz/bZyV68NqmkLB7WcYmj1RMJVSp8HV/v/ZQ==", - "devOptional": true, + "node_modules/@drizzle-team/brocli": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", + "dev": true, "license": "Apache-2.0" }, - "node_modules/@electric-sql/pglite-socket": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.1.3.tgz", - "integrity": "sha512-LAciWM0M1dCL8hlsxu2venbVZcdxema0BtDfpWYVqr+Y468UADw0pFWidhKw1M8sfJ8rdLT71tjMmnirf/IZRQ==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "pglite-server": "dist/scripts/server.js" - }, - "peerDependencies": { - "@electric-sql/pglite": "0.4.3" - } - }, - "node_modules/@electric-sql/pglite-tools": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.3.3.tgz", - "integrity": "sha512-AlzLJTRJ8+UFgK8CmxIpyIpJ0+YaFw02IiOSdYrqxwPXdSyeIShz8aa9Tq+tYFXdPwcaMp/Fc80mQZ1dkOQ/wg==", - "devOptional": true, - "license": "Apache-2.0", - "peerDependencies": { - "@electric-sql/pglite": "0.4.3" - } - }, "node_modules/@emnapi/core": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", @@ -450,27 +426,22 @@ "tslib": "^2.4.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@esbuild-kit/core-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "deprecated": "Merged into tsx: https://tsx.hirok.io", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" + "dependencies": { + "esbuild": "~0.18.20", + "source-map-support": "^0.5.21" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", "cpu": [ "arm" ], @@ -481,13 +452,13 @@ "android" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", "cpu": [ "arm64" ], @@ -498,13 +469,13 @@ "android" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", "cpu": [ "x64" ], @@ -515,13 +486,13 @@ "android" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", "cpu": [ "arm64" ], @@ -532,13 +503,13 @@ "darwin" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", "cpu": [ "x64" ], @@ -549,13 +520,13 @@ "darwin" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", "cpu": [ "arm64" ], @@ -566,13 +537,13 @@ "freebsd" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", "cpu": [ "x64" ], @@ -583,13 +554,13 @@ "freebsd" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", "cpu": [ "arm" ], @@ -600,13 +571,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", "cpu": [ "arm64" ], @@ -617,13 +588,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", "cpu": [ "ia32" ], @@ -634,13 +605,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", "cpu": [ "loong64" ], @@ -651,13 +622,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", "cpu": [ "mips64el" ], @@ -668,13 +639,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", "cpu": [ "ppc64" ], @@ -685,13 +656,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", "cpu": [ "riscv64" ], @@ -702,13 +673,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", "cpu": [ "s390x" ], @@ -719,13 +690,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", "cpu": [ "x64" ], @@ -736,15 +707,15 @@ "linux" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", @@ -753,13 +724,13 @@ "netbsd" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", "cpu": [ "x64" ], @@ -767,16 +738,33 @@ "license": "MIT", "optional": true, "os": [ - "netbsd" + "openbsd" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", "cpu": [ "arm64" ], @@ -784,16 +772,33 @@ "license": "MIT", "optional": true, "os": [ - "openbsd" + "win32" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", "cpu": [ "x64" ], @@ -801,50 +806,100 @@ "license": "MIT", "optional": true, "os": [ - "openbsd" + "win32" ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/openharmony-arm64": { + "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/@esbuild-kit/esm-loader": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "deprecated": "Merged into tsx: https://tsx.hirok.io", + "dev": true, + "license": "MIT", + "dependencies": { + "@esbuild-kit/core-utils": "^3.3.2", + "get-tsconfig": "^4.7.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ - "arm64" + "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "openharmony" + "aix" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/sunos-x64": { + "node_modules/@esbuild/android-arm": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ - "x64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "sunos" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-arm64": { + "node_modules/@esbuild/android-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], @@ -852,53 +907,410 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-ia32": { + "node_modules/@esbuild/android-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ - "ia32" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-x64": { + "node_modules/@esbuild/darwin-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { "node": ">=18" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", - "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -1928,306 +2340,44 @@ "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.4.0" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.146.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.146.0.tgz", - "integrity": "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@panva/hkdf": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", - "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/@playwright/test": { - "version": "1.62.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", - "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.62.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@prisma/adapter-pg": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.10.0.tgz", - "integrity": "sha512-N7nwSor0HO1Kz6xBv0TPAjAPysKK0fac6p4fVN3ensLOuzc/83Fgmln5k92eK/cvzqdkSR/2kkAqlbcdwVrwpw==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/driver-adapter-utils": "7.10.0", - "@types/pg": "^8.16.0", - "pg": "^8.16.3", - "postgres-array": "3.0.4" - } - }, - "node_modules/@prisma/client": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.10.0.tgz", - "integrity": "sha512-Ubw/QS9JGIBSBUsyxAUQuK/Jcu0Tsva7le7QbLd91Kix9yJvYDdj5QkwgEbbZniH80dd+sziQcALPc+HnvQC8Q==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/client-runtime-utils": "7.10.0" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24.0" - }, - "peerDependencies": { - "prisma": "*", - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/@prisma/client-runtime-utils": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.10.0.tgz", - "integrity": "sha512-cnCy7lUV8/CctgKVEmqAbSLAmwqJdE/qAlqTBk/0NDk59zEb2cZ0M0M0E4vVPnqbSEYudRroQDvOWfUZH6RIfw==", - "license": "Apache-2.0" - }, - "node_modules/@prisma/config": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.10.0.tgz", - "integrity": "sha512-Rcg828gIRE3HOQ3pOATFjV5d/P0U9OIobxhd/IMxlfWjA4vru0eGwb0AIwFw0rmcLMVShohZYWPixVxkBHsxUA==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "c12": "3.3.4", - "deepmerge-ts": "7.1.5", - "effect": "3.20.0", - "empathic": "2.0.0" - } - }, - "node_modules/@prisma/dev": { - "version": "0.24.17", - "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.24.17.tgz", - "integrity": "sha512-UvdZzmpFwknnfreh6Jije84ekkYGPYEJhXG1tFzCsCfQyzJifrOo38eZc0qajzvaC6OLUOrN9ML5XfCnEZL9DA==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "@electric-sql/pglite": "0.4.3", - "@electric-sql/pglite-socket": "0.1.3", - "@electric-sql/pglite-tools": "0.3.3", - "@prisma/get-platform": "7.2.0", - "@prisma/query-plan-executor": "7.2.0", - "@prisma/streams-local": "0.1.11", - "find-my-way": "9.7.0", - "foreground-child": "3.3.1", - "get-port-please": "3.2.0", - "pathe": "2.0.3", - "proper-lockfile": "4.1.2", - "remeda": "2.33.4", - "std-env": "3.10.0", - "valibot": "1.4.2", - "zeptomatch": "2.1.0" - } - }, - "node_modules/@prisma/dev/node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@prisma/driver-adapter-utils": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.10.0.tgz", - "integrity": "sha512-u8zkcRLlaryO652T4qavBg0HmzNW5tSKdsCn6hc1PhWAp/J6k0vrxLuUs+b9o+HcjsK7Dfa01o4OFSn0frauJA==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.10.0" - } - }, - "node_modules/@prisma/driver-adapter-utils/node_modules/@prisma/debug": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.10.0.tgz", - "integrity": "sha512-caygJKtltmRIgdJ3jRpkOr7yM4DW6zxo5uOmojKWFb3asnxWoRkQOwZmXBgD8FZp4htrX+nMpcWqDwzlQ1+Y4g==", - "license": "Apache-2.0" - }, - "node_modules/@prisma/engines": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.10.0.tgz", - "integrity": "sha512-KNumN6NHFwybvfdYzTee9pqwx5PvknpWAaHn6L5NsbrKdl+SQrsVZs9opKs6U6SAsvB26HDt3WybRjOhgoWOYQ==", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.10.0", - "@prisma/engines-version": "7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3", - "@prisma/fetch-engine": "7.10.0", - "@prisma/get-platform": "7.10.0" - } - }, - "node_modules/@prisma/engines-version": { - "version": "7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3.tgz", - "integrity": "sha512-8OJ6RuZTZ06eFUOtBwxVmv8XMmOW6HWN5F+uxUbZkGxR0Bfab1dfAdXaHPmR5mb59E+fmUo8IOzXlbLY1SClbw==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/engines/node_modules/@prisma/debug": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.10.0.tgz", - "integrity": "sha512-caygJKtltmRIgdJ3jRpkOr7yM4DW6zxo5uOmojKWFb3asnxWoRkQOwZmXBgD8FZp4htrX+nMpcWqDwzlQ1+Y4g==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.10.0.tgz", - "integrity": "sha512-0bra1LFYi8xNw0yqV62bHJNQk4BKleOngZiqPWIQc7a3+9q6rqsnqJ15BuepP8943PFMvtmgnP52juZsyYkA6w==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.10.0" - } - }, - "node_modules/@prisma/fetch-engine": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.10.0.tgz", - "integrity": "sha512-Zqyu8DY14t6W/xwmAxUYWCXtHrvQnSvT644EAZSsdM8NSmCS74vJJbBKdVsK3ucFpnUWkEpbO1a0CxJXrg130g==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.10.0", - "@prisma/engines-version": "7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3", - "@prisma/get-platform": "7.10.0" - } - }, - "node_modules/@prisma/fetch-engine/node_modules/@prisma/debug": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.10.0.tgz", - "integrity": "sha512-caygJKtltmRIgdJ3jRpkOr7yM4DW6zxo5uOmojKWFb3asnxWoRkQOwZmXBgD8FZp4htrX+nMpcWqDwzlQ1+Y4g==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.10.0.tgz", - "integrity": "sha512-0bra1LFYi8xNw0yqV62bHJNQk4BKleOngZiqPWIQc7a3+9q6rqsnqJ15BuepP8943PFMvtmgnP52juZsyYkA6w==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.10.0" - } - }, - "node_modules/@prisma/get-platform": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", - "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.2.0" - } - }, - "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", - "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/query-plan-executor": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz", - "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/streams-local": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@prisma/streams-local/-/streams-local-0.1.11.tgz", - "integrity": "sha512-0TcebL559MByKqTJ+SsrFIEg228iw8UCVRFckzgfRSiJqczhs+MuAgWOF9lnOIV/IVqvu+KMnFTH0eDeTQMpUg==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "ajv": "^8.12.0", - "better-result": "^2.7.0", - "env-paths": "^3.0.0", - "proper-lockfile": "^4.1.2" - }, + "license": "MIT", "engines": { - "bun": ">=1.2.0", - "node": ">=22.0.0" + "node": ">=12.4.0" } }, - "node_modules/@prisma/streams-local/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "devOptional": true, + "node_modules/@oxc-project/types": { + "version": "0.146.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.146.0.tgz", + "integrity": "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==", + "dev": true, "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@prisma/streams-local/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "devOptional": true, - "license": "MIT" + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } }, - "node_modules/@prisma/studio-core": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.33.0.tgz", - "integrity": "sha512-V2fX/nKEymNTrHXwfP26PGjoLStO35Ogu+ex7CFJbLrMYEcZxxZpiSNOs7px23Hk5mzLWvM5RsqG6Ka+rha+wg==", + "node_modules/@playwright/test": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@radix-ui/react-toggle": "1.1.10", - "@visx/curve": "4.0.1-alpha.0", - "@visx/event": "4.0.1-alpha.0", - "@visx/grid": "4.0.1-alpha.0", - "@visx/group": "4.0.1-alpha.0", - "@visx/responsive": "4.0.1-alpha.0", - "@visx/scale": "4.0.1-alpha.0", - "@visx/shape": "4.0.1-alpha.0", - "d3-array": "3.2.4", - "d3-shape": "3.2.0", - "elkjs": "0.11.1" + "playwright": "1.62.1" }, - "engines": { - "node": "^20.19 || ^22.12 || >=24.0", - "pnpm": "8" + "bin": { + "playwright": "cli.js" }, - "peerDependencies": { - "@types/react": "^18.0.0 || ^19.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "engines": { + "node": ">=20" } }, "node_modules/@puppeteer/browsers": { @@ -2259,153 +2409,6 @@ } } }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", - "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", - "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", - "devOptional": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", - "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", - "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", - "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", - "devOptional": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@rolldown/binding-android-arm-eabi": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.5.tgz", @@ -3390,95 +3393,6 @@ "assertion-error": "^2.0.1" } }, - "node_modules/@types/d3-array": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz", - "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/d3-delaunay": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", - "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/d3-format": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", - "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/d3-color": "*" - } - }, - "node_modules/@types/d3-path": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/d3-time": "*" - } - }, - "node_modules/@types/d3-shape": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", - "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/d3-path": "*" - } - }, - "node_modules/@types/d3-time": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", - "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/d3-time-format": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.1.0.tgz", - "integrity": "sha512-/myT3I7EwlukNOX2xVdMzb8FRgNzRMpsZddwst9Ld/VFe6LyJyRp0s32l/V9XoUzk+Gqu56F/oGk6507+8BxrA==", - "devOptional": true, - "license": "MIT" - }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -3500,13 +3414,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "devOptional": true, - "license": "MIT" - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3521,17 +3428,11 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/lodash": { - "version": "4.17.25", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.25.tgz", - "integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==", - "devOptional": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "26.4.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.0.tgz", "integrity": "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==", + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~8.3.0" @@ -3541,6 +3442,7 @@ "version": "8.23.1", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.23.1.tgz", "integrity": "sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==", + "devOptional": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -4154,157 +4056,6 @@ "win32" ] }, - "node_modules/@visx/curve": { - "version": "4.0.1-alpha.0", - "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-4.0.1-alpha.0.tgz", - "integrity": "sha512-jRu61Uz274pV1zyioXmboyrLutYbnKsgjj4njSGCnhdXj5GkZvZbg+ThDb6oOzoAnJOBRLz4rzPlWvNJOzuVMg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@visx/vendor": "4.0.0-alpha.0" - } - }, - "node_modules/@visx/event": { - "version": "4.0.1-alpha.0", - "resolved": "https://registry.npmjs.org/@visx/event/-/event-4.0.1-alpha.0.tgz", - "integrity": "sha512-EQqCMSv/s8NbFjo+hz3FKsvvYfP+2QslsFJ/24/O5l/W+7UC6J6aAvO0ujVwrTwdYbuQ+vhxKi1xdPdKR/qj1g==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/react": "*", - "@visx/point": "4.0.1-alpha.0" - } - }, - "node_modules/@visx/grid": { - "version": "4.0.1-alpha.0", - "resolved": "https://registry.npmjs.org/@visx/grid/-/grid-4.0.1-alpha.0.tgz", - "integrity": "sha512-rycutGmTHO+znNdPumheWMglm7YfpffvRwUkVy5zy4WoORIuKTMkDxwnOzHG2xMxU3EE/YCd37xFV5AxA30yeg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/react": "*", - "@visx/curve": "4.0.1-alpha.0", - "@visx/group": "4.0.1-alpha.0", - "@visx/point": "4.0.1-alpha.0", - "@visx/scale": "4.0.1-alpha.0", - "@visx/shape": "4.0.1-alpha.0", - "classnames": "^2.3.1" - }, - "peerDependencies": { - "react": "^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" - } - }, - "node_modules/@visx/group": { - "version": "4.0.1-alpha.0", - "resolved": "https://registry.npmjs.org/@visx/group/-/group-4.0.1-alpha.0.tgz", - "integrity": "sha512-V19l7iQ7jccBv8kao/EByuI6o4xtxzzLV9nqVI1hRvmdzTVsuLpqlwzYCZUXJaTVvUWf8s4D2SQFjGkj/Nw+0w==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/react": "*", - "classnames": "^2.3.1" - }, - "peerDependencies": { - "react": "^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" - } - }, - "node_modules/@visx/point": { - "version": "4.0.1-alpha.0", - "resolved": "https://registry.npmjs.org/@visx/point/-/point-4.0.1-alpha.0.tgz", - "integrity": "sha512-ijTfr/Nx09f03vIj9nyTr3z4Xth4Y75427UaogJh6dnIRLMEFHQOwNu791sbfiNj0a+ZXuaE32h0vKrFe4/8Qg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@visx/responsive": { - "version": "4.0.1-alpha.0", - "resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-4.0.1-alpha.0.tgz", - "integrity": "sha512-o+1zGywQZY0+yOx3Iw87wc4bbPJRr/HnIukTwfOz4UVyj9pB1OQNVHB7OORO1+LBHJceWpB31co/ZV9KHncKrA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/lodash": "^4.17.13", - "@types/react": "*", - "lodash": "^4.17.21" - }, - "peerDependencies": { - "react": "^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" - } - }, - "node_modules/@visx/scale": { - "version": "4.0.1-alpha.0", - "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-4.0.1-alpha.0.tgz", - "integrity": "sha512-nzjeE87vFSAXGWFiiNfBpNLAf0Q8Qmf6syvKLjqNi4kGZkdhbUll3E/59YsgWXmjM8+llPLWzGsP+JPvo5eq1A==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@visx/vendor": "4.0.0-alpha.0" - } - }, - "node_modules/@visx/shape": { - "version": "4.0.1-alpha.0", - "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-4.0.1-alpha.0.tgz", - "integrity": "sha512-62QeiVNmPlterQGwhkEDcbq7M0MqY0lBsK5QKXtM9ZoPZWkuGV3aykA3+Xu20B2FAvyJq4LqJzBc7Sxr+EAdbA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/lodash": "^4.17.13", - "@types/react": "*", - "@visx/curve": "4.0.1-alpha.0", - "@visx/group": "4.0.1-alpha.0", - "@visx/scale": "4.0.1-alpha.0", - "@visx/vendor": "4.0.0-alpha.0", - "classnames": "^2.3.1", - "lodash": "^4.17.21" - }, - "peerDependencies": { - "react": "^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" - } - }, - "node_modules/@visx/vendor": { - "version": "4.0.0-alpha.0", - "resolved": "https://registry.npmjs.org/@visx/vendor/-/vendor-4.0.0-alpha.0.tgz", - "integrity": "sha512-6I+MuqXBcv9jnlcVowHoHKSdk9gXTWkHLKyqBwRWg7LY6A3Ei8SHfubpqGV5rBUSppxMq2RszPJUS6w+H0YgmQ==", - "devOptional": true, - "license": "MIT and ISC", - "dependencies": { - "@types/d3-array": "3.0.3", - "@types/d3-color": "3.1.0", - "@types/d3-delaunay": "6.0.1", - "@types/d3-format": "3.0.1", - "@types/d3-geo": "3.1.0", - "@types/d3-interpolate": "3.0.1", - "@types/d3-path": "3.1.1", - "@types/d3-scale": "4.0.2", - "@types/d3-shape": "3.1.7", - "@types/d3-time": "3.0.0", - "@types/d3-time-format": "2.1.0", - "d3-array": "3.2.1", - "d3-color": "3.1.0", - "d3-delaunay": "6.0.2", - "d3-format": "3.1.0", - "d3-geo": "3.1.0", - "d3-interpolate": "3.0.1", - "d3-path": "3.1.0", - "d3-scale": "4.0.2", - "d3-shape": "3.2.0", - "d3-time": "3.1.0", - "d3-time-format": "4.1.0", - "internmap": "2.0.3" - } - }, - "node_modules/@visx/vendor/node_modules/d3-array": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz", - "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@vitest/expect": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", @@ -4710,16 +4461,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/aws-ssl-profiles": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", - "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/axe-core": { "version": "4.13.0", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz", @@ -4765,13 +4506,6 @@ "node": ">=6.0.0" } }, - "node_modules/better-result": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.10.0.tgz", - "integrity": "sha512-oQhh0y1qo2/ZKdAAEvHZAqKKiHOFU5k/bW96fE2ScgQOVkJRiHwB+nOS1SgFsYqRlxMDWvefXi9Q3px7QvgNDw==", - "devOptional": true, - "license": "MIT" - }, "node_modules/brace-expansion": { "version": "5.0.9", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", @@ -4873,34 +4607,12 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/c12": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", - "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "chokidar": "^5.0.0", - "confbox": "^0.2.4", - "defu": "^6.1.6", - "dotenv": "^17.3.1", - "exsolve": "^1.0.8", - "giget": "^3.2.0", - "jiti": "^2.6.1", - "ohash": "^2.0.11", - "pathe": "^2.0.3", - "perfect-debounce": "^2.1.0", - "pkg-types": "^2.3.0", - "rc9": "^3.0.1" - }, - "peerDependencies": { - "magicast": "*" - }, - "peerDependenciesMeta": { - "magicast": { - "optional": true - } - } + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" }, "node_modules/call-bind": { "version": "1.0.9", @@ -4982,22 +4694,6 @@ "node": ">=18" } }, - "node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/chromium-bidi": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-17.0.2.tgz", @@ -5025,13 +4721,6 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", - "devOptional": true, - "license": "MIT" - }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -5094,13 +4783,6 @@ "dev": true, "license": "MIT" }, - "node_modules/confbox": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", - "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", - "devOptional": true, - "license": "MIT" - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -5137,361 +4819,791 @@ "devOptional": true, "license": "MIT" }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "devOptional": true, - "license": "ISC", + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", "dependencies": { - "internmap": "1 - 2" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" }, "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "devOptional": true, - "license": "ISC", + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" } }, - "node_modules/d3-delaunay": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", - "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", - "devOptional": true, - "license": "ISC", + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", "dependencies": { - "delaunator": "5" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "devOptional": true, - "license": "ISC", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">=12" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", "devOptional": true, - "license": "ISC", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1666840", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1666840.tgz", + "integrity": "sha512-gCcO42XCHKEs7Ag0S7aGYsnJ7hlgrO3qderYqeiY0Eqk+0GFfuvT13IA0hHreJTa2KCdDVyGMeOhdMNmrrTjVg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "d3-array": "2.5.0 - 3" + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/drizzle-kit": { + "version": "0.31.10", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.10.tgz", + "integrity": "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@drizzle-team/brocli": "^0.10.2", + "@esbuild-kit/esm-loader": "^2.5.5", + "esbuild": "^0.25.4", + "tsx": "^4.21.0" }, + "bin": { + "drizzle-kit": "bin.cjs" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3" - }, + "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "devOptional": true, - "license": "ISC", + "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "d3-path": "^3.1.0" - }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "d3-array": "2 - 3" - }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "d3-time": "1 - 3" - }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "node_modules/drizzle-kit/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], "dev": true, - "license": "BSD-2-Clause" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "node_modules/drizzle-kit/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "node_modules/drizzle-kit/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" + "node": ">=18" } }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "node_modules/drizzle-kit/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/drizzle-kit/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "node_modules/drizzle-kit/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge-ts": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", - "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", - "devOptional": true, - "license": "BSD-3-Clause", + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=16.0.0" + "node": ">=18" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "node_modules/drizzle-kit/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "node_modules/drizzle-kit/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/defu": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", - "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/delaunator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", - "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "robust-predicates": "^3.0.2" + "node_modules/drizzle-kit/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "devOptional": true, - "license": "Apache-2.0", + "node_modules/drizzle-kit/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=0.10" + "node": ">=18" } }, - "node_modules/destr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", - "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "devOptional": true, - "license": "MIT" - }, - "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==", - "devOptional": true, - "license": "Apache-2.0", + "node_modules/drizzle-kit/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/devtools-protocol": { - "version": "0.0.1666840", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1666840.tgz", - "integrity": "sha512-gCcO42XCHKEs7Ag0S7aGYsnJ7hlgrO3qderYqeiY0Eqk+0GFfuvT13IA0hHreJTa2KCdDVyGMeOhdMNmrrTjVg==", + "node_modules/drizzle-kit/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BSD-3-Clause" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/diff": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", - "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "node_modules/drizzle-kit/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=0.3.1" + "node": ">=18" } }, - "node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "node_modules/drizzle-kit/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/dotenv": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", - "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", - "devOptional": true, - "license": "BSD-2-Clause", + "node_modules/drizzle-kit/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { - "node": ">=12" + "node": ">=18" }, - "funding": { - "url": "https://dotenvx.com" + "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" + } + }, + "node_modules/drizzle-orm": { + "version": "0.45.2", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.2.tgz", + "integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==", + "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": { @@ -5509,17 +5621,6 @@ "node": ">= 0.4" } }, - "node_modules/effect": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz", - "integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "fast-check": "^3.23.1" - } - }, "node_modules/electron-to-chromium": { "version": "1.5.405", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.405.tgz", @@ -5527,13 +5628,6 @@ "dev": true, "license": "ISC" }, - "node_modules/elkjs": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.11.1.tgz", - "integrity": "sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==", - "devOptional": true, - "license": "EPL-2.0" - }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -5541,16 +5635,6 @@ "dev": true, "license": "MIT" }, - "node_modules/empathic": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", - "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/enhanced-resolve": { "version": "5.24.5", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", @@ -5565,19 +5649,6 @@ "node": ">=10.13.0" } }, - "node_modules/env-paths": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", - "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/es-abstract": { "version": "1.24.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", @@ -6384,43 +6455,6 @@ "node": ">=12.0.0" } }, - "node_modules/exsolve": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", - "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/fast-check": { - "version": "3.23.2", - "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", - "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", - "devOptional": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT", - "dependencies": { - "pure-rand": "^6.1.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/fast-decode-uri-component": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", - "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", - "devOptional": true, - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6472,33 +6506,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-querystring": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", - "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "fast-decode-uri-component": "^1.0.1" - } - }, - "node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/fastq": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.2.tgz", @@ -6535,21 +6542,6 @@ "node": ">=8" } }, - "node_modules/find-my-way": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.7.0.tgz", - "integrity": "sha512-f2JHn75x2JlwUwLenZypgczR7YWMb/uO9BvUXtus+JMgkbIkLADd38cI4EiV+OQqrGo1Zlq6V8wnqMJ8e62wUQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-querystring": "^1.0.0", - "safe-regex2": "^5.0.0" - }, - "engines": { - "node": ">=20" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -6604,23 +6596,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -6679,16 +6654,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "is-property": "^1.0.2" - } - }, "node_modules/generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", @@ -6757,13 +6722,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-port-please": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz", - "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==", - "devOptional": true, - "license": "MIT" - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -6809,16 +6767,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/giget": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/giget/-/giget-3.3.1.tgz", - "integrity": "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==", - "devOptional": true, - "license": "MIT", - "bin": { - "giget": "dist/cli.mjs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -6869,20 +6817,6 @@ "devOptional": true, "license": "ISC" }, - "node_modules/grammex": { - "version": "3.1.13", - "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.13.tgz", - "integrity": "sha512-LnPnhOBLEJEVKS8WFDVaA397L9Kq55Q9oSITJiVLHVdhAclfUkWzQv74KhvZHKL2Q09Pb1XdsrOsZ4LfTFFTEg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/graphmatch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz", - "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==", - "devOptional": true, - "license": "MIT" - }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -6984,23 +6918,6 @@ "hermes-estree": "0.25.1" } }, - "node_modules/iconv-lite": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", - "devOptional": true, - "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/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -7036,16 +6953,6 @@ "node": ">= 0.4" } }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "devOptional": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -7319,13 +7226,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", - "devOptional": true, - "license": "MIT" - }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -7925,20 +7825,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "devOptional": true, - "license": "Apache-2.0" - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -7962,22 +7848,6 @@ "yallist": "^3.0.2" } }, - "node_modules/lru.min": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", - "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", - "devOptional": true, - "license": "MIT", - "engines": { - "bun": ">=1.0.0", - "deno": ">=1.30.0", - "node": ">=8.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wellwelwel" - } - }, "node_modules/lucide-react": { "version": "1.35.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.35.0.tgz", @@ -8088,40 +7958,6 @@ "dev": true, "license": "MIT" }, - "node_modules/mysql2": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", - "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "aws-ssl-profiles": "^1.1.1", - "denque": "^2.1.0", - "generate-function": "^2.3.1", - "iconv-lite": "^0.7.0", - "long": "^5.2.1", - "lru.min": "^1.0.0", - "named-placeholders": "^1.1.3", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/named-placeholders": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", - "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "lru.min": "^1.1.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/nanoid": { "version": "3.3.18", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", @@ -8456,13 +8292,6 @@ "node": ">=12.20.0" } }, - "node_modules/ohash": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", - "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", - "devOptional": true, - "license": "MIT" - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -8566,13 +8395,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/perfect-debounce": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", - "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", - "devOptional": true, - "license": "MIT" - }, "node_modules/pg": { "version": "8.23.0", "resolved": "https://registry.npmjs.org/pg/-/pg-8.23.0.tgz", @@ -8690,18 +8512,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkg-types": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", - "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.2.4", - "exsolve": "^1.0.8", - "pathe": "^2.0.3" - } - }, "node_modules/playwright": { "version": "1.62.1", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", @@ -8773,29 +8583,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postgres": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", - "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", - "devOptional": true, - "license": "Unlicense", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/porsager" - } - }, - "node_modules/postgres-array": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", - "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/postgres-bytea": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", @@ -8871,40 +8658,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/prisma": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.10.0.tgz", - "integrity": "sha512-o0ornyJOWgygVAzGCpr8PdXV8EJLHyVGDDUr/voBQt8Azzw8cYTByzzPGcA/m4tCkPcnJA8raEOv2CslsKhPEw==", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/config": "7.10.0", - "@prisma/dev": "0.24.17", - "@prisma/engines": "7.10.0", - "@prisma/studio-core": "0.33.0", - "mysql2": "3.15.3", - "postgres": "3.4.7" - }, - "bin": { - "prisma": "build/index.js" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24.0" - }, - "peerDependencies": { - "better-sqlite3": ">=9.0.0", - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "better-sqlite3": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -8917,25 +8670,6 @@ "react-is": "^16.13.1" } }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proper-lockfile/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "devOptional": true, - "license": "ISC" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -8999,23 +8733,6 @@ "url": "https://github.com/sponsors/antonk52" } }, - "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "devOptional": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -9037,17 +8754,6 @@ ], "license": "MIT" }, - "node_modules/rc9": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", - "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "defu": "^6.1.6", - "destr": "^2.0.5" - } - }, "node_modules/react": { "version": "19.2.8", "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", @@ -9076,20 +8782,6 @@ "dev": true, "license": "MIT" }, - "node_modules/readdirp": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", - "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -9134,26 +8826,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/remeda": { - "version": "2.33.4", - "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz", - "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==", - "devOptional": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/remeda" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -9164,26 +8836,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/ret": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", - "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -9195,13 +8847,6 @@ "node": ">=0.10.0" } }, - "node_modules/robust-predicates": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", - "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", - "devOptional": true, - "license": "Unlicense" - }, "node_modules/rolldown": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.5.tgz", @@ -9315,36 +8960,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-regex2": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz", - "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "ret": "~0.5.0" - }, - "bin": { - "safe-regex2": "bin/safe-regex2.js" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true, - "license": "MIT" - }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -9364,12 +8979,6 @@ "node": ">=10" } }, - "node_modules/seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", - "devOptional": true - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -9575,17 +9184,14 @@ "dev": true, "license": "ISC" }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "devOptional": true, - "license": "ISC", + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=0.10.0" } }, "node_modules/source-map-js": { @@ -9597,6 +9203,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -9606,16 +9223,6 @@ "node": ">= 10.x" } }, - "node_modules/sqlstring": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -10228,6 +9835,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "devOptional": true, "license": "MIT" }, "node_modules/unrs-resolver": { @@ -10316,21 +9924,6 @@ "dev": true, "license": "MIT" }, - "node_modules/valibot": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.4.2.tgz", - "integrity": "sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==", - "devOptional": true, - "license": "MIT", - "peerDependencies": { - "typescript": ">=5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/vite": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", @@ -10821,17 +10414,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zeptomatch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz", - "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "grammex": "^3.1.11", - "graphmatch": "^1.1.0" - } - }, "node_modules/zod": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", diff --git a/package.json b/package.json index 1adc42d..8bd517a 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "private": true, "scripts": { "dev": "next dev", - "build": "prisma generate && next build", + "build": "next build", "start": "next start", "lint": "eslint .", "typecheck": "tsc --noEmit", @@ -17,20 +17,20 @@ "test:e2e": "playwright test", "test:puppeteer": "BASE_URL=${BASE_URL:-http://localhost:3000} node tests/puppeteer/smoke.mjs", "test:puppeteer:mega": "BASE_URL=${BASE_URL:-http://localhost:3000} node tests/puppeteer/mega-menu.mjs", - "prisma:generate": "prisma generate", - "prisma:push": "prisma db push", "format": "prettier --write .", - "format:check": "prettier --check ." + "format:check": "prettier --check .", + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", + "db:push": "drizzle-kit push" }, "dependencies": { "@fleet/design-tokens": "github:bitbaum/design-tokens#v1.1.0", "@noble/hashes": "2.4.0", "@noble/secp256k1": "3.1.0", - "@prisma/adapter-pg": "^7.10.0", - "@prisma/client": "^7.10.0", "@scure/base": "^2.3.0", "bs58check": "^4.0.0", "clsx": "2.1.1", + "drizzle-orm": "^0.45.1", "lucide-react": "1.35.0", "next": "16.3.3", "next-auth": "^5.0.0-beta.32", @@ -47,11 +47,11 @@ "@types/pg": "^8.23.1", "@types/react": "19.2.18", "@types/react-dom": "19.2.5", + "drizzle-kit": "^0.31.9", "eslint": "10.9.1", "eslint-config-next": "16.3.3", "postcss": "8.5.26", "prettier": "3.9.6", - "prisma": "7.10.0", "puppeteer": "25.9.0", "ts-node": "10.9.2", "tsx": "^4.23.12", diff --git a/prisma.config.ts b/prisma.config.ts deleted file mode 100644 index a5f030c..0000000 --- a/prisma.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import "dotenv/config"; -import { defineConfig, env } from "@prisma/config"; - -export default defineConfig({ - schema: "prisma/schema.prisma", - datasource: { - url: env("DATABASE_URL"), - }, -}); diff --git a/prisma/migrations/0_init/migration.sql b/prisma/migrations/0_init/migration.sql deleted file mode 100644 index e1511b2..0000000 --- a/prisma/migrations/0_init/migration.sql +++ /dev/null @@ -1,226 +0,0 @@ --- CreateEnum -CREATE TYPE "MemberType" AS ENUM ('HUMAN', 'AGENT'); - --- CreateEnum -CREATE TYPE "KeyCustody" AS ENUM ('SELF', 'SERVICE'); - --- CreateEnum -CREATE TYPE "MemberStatus" AS ENUM ('ACTIVE', 'SUSPENDED', 'RETIRED'); - --- CreateEnum -CREATE TYPE "DecisionCategory" AS ENUM ('ALLOCATION_POLICY', 'TREASURY_SPEND', 'OPERATIONS', 'AID_DISBURSEMENT', 'MEMBERSHIP', 'SAFETY', 'GOVERNANCE_RULES'); - --- CreateEnum -CREATE TYPE "Electorate" AS ENUM ('ALL_MEMBERS', 'HUMANS_ONLY'); - --- CreateEnum -CREATE TYPE "VoteThreshold" AS ENUM ('SIMPLE_MAJORITY', 'SUPERMAJORITY'); - --- CreateEnum -CREATE TYPE "ProposalStatus" AS ENUM ('DRAFT', 'OPEN', 'CLOSED'); - --- CreateEnum -CREATE TYPE "SessionStatus" AS ENUM ('ACTIVE', 'CLOSED'); - --- CreateEnum -CREATE TYPE "SessionOutcome" AS ENUM ('APPROVED', 'REJECTED', 'EXPIRED'); - --- CreateEnum -CREATE TYPE "VoteChoice" AS ENUM ('YES', 'NO', 'ABSTAIN'); - --- CreateEnum -CREATE TYPE "PolicyStatus" AS ENUM ('ACTIVE', 'SUPERSEDED'); - --- CreateEnum -CREATE TYPE "AuditEventType" AS ENUM ('ORG_CREATED', 'MEMBER_ADDED', 'MEMBER_STATUS_CHANGED', 'PROPOSAL_CREATED', 'SESSION_OPENED', 'VOTE_CAST', 'SESSION_CLOSED', 'POLICY_ACTIVATED'); - --- CreateTable -CREATE TABLE "organizations" ( - "id" TEXT NOT NULL, - "slug" TEXT NOT NULL, - "name" TEXT NOT NULL, - "description" TEXT, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "organizations_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "members" ( - "id" TEXT NOT NULL, - "organization_id" TEXT NOT NULL, - "display_name" TEXT NOT NULL, - "member_type" "MemberType" NOT NULL, - "key_custody" "KeyCustody" NOT NULL, - "bitcoin_address" VARCHAR(90) NOT NULL, - "public_key_hex" TEXT, - "voting_weight" DECIMAL(10,2) NOT NULL DEFAULT 1, - "status" "MemberStatus" NOT NULL DEFAULT 'ACTIVE', - "oc_actor_id" TEXT, - "system" TEXT, - "joined_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "members_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "proposals" ( - "id" TEXT NOT NULL, - "organization_id" TEXT NOT NULL, - "category" "DecisionCategory" NOT NULL, - "title" TEXT NOT NULL, - "body" TEXT NOT NULL, - "policy_key" TEXT, - "proposed_content" JSONB, - "target" TEXT, - "content_hash" TEXT, - "proposer_member_id" TEXT NOT NULL, - "proposer_signature" TEXT NOT NULL, - "status" "ProposalStatus" NOT NULL DEFAULT 'DRAFT', - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "proposals_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "voting_sessions" ( - "id" TEXT NOT NULL, - "proposal_id" TEXT NOT NULL, - "status" "SessionStatus" NOT NULL DEFAULT 'ACTIVE', - "opens_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "closes_at" TIMESTAMP(3) NOT NULL, - "electorate" "Electorate" NOT NULL, - "threshold" "VoteThreshold" NOT NULL, - "quorum_percent" INTEGER NOT NULL, - "eligible_count" INTEGER NOT NULL, - "eligible_weight" DECIMAL(12,2) NOT NULL, - "outcome" "SessionOutcome", - "closed_at" TIMESTAMP(3), - - CONSTRAINT "voting_sessions_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "votes" ( - "id" TEXT NOT NULL, - "session_id" TEXT NOT NULL, - "member_id" TEXT NOT NULL, - "choice" "VoteChoice" NOT NULL, - "weight" DECIMAL(10,2) NOT NULL, - "signed_message" TEXT NOT NULL, - "signature" TEXT NOT NULL, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "votes_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "policies" ( - "id" TEXT NOT NULL, - "organization_id" TEXT NOT NULL, - "key" TEXT NOT NULL, - "version" INTEGER NOT NULL, - "content" JSONB NOT NULL, - "status" "PolicyStatus" NOT NULL, - "approved_by_session_id" TEXT, - "activated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "policies_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "treasury_sources" ( - "id" TEXT NOT NULL, - "organization_id" TEXT NOT NULL, - "label" TEXT NOT NULL, - "address" VARCHAR(90) NOT NULL, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "treasury_sources_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "audit_events" ( - "id" TEXT NOT NULL, - "organization_id" TEXT NOT NULL, - "event_type" "AuditEventType" NOT NULL, - "actor_member_id" TEXT, - "subject_type" TEXT NOT NULL, - "subject_id" TEXT NOT NULL, - "payload" JSONB NOT NULL, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "audit_events_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "agent_api_keys" ( - "id" TEXT NOT NULL, - "member_id" TEXT NOT NULL, - "key_hash" TEXT NOT NULL, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "revoked_at" TIMESTAMP(3), - - CONSTRAINT "agent_api_keys_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "organizations_slug_key" ON "organizations"("slug"); - --- CreateIndex -CREATE UNIQUE INDEX "members_oc_actor_id_key" ON "members"("oc_actor_id"); - --- CreateIndex -CREATE UNIQUE INDEX "members_organization_id_bitcoin_address_key" ON "members"("organization_id", "bitcoin_address"); - --- CreateIndex -CREATE UNIQUE INDEX "voting_sessions_proposal_id_key" ON "voting_sessions"("proposal_id"); - --- CreateIndex -CREATE UNIQUE INDEX "votes_session_id_member_id_key" ON "votes"("session_id", "member_id"); - --- CreateIndex -CREATE UNIQUE INDEX "policies_organization_id_key_version_key" ON "policies"("organization_id", "key", "version"); - --- CreateIndex -CREATE UNIQUE INDEX "treasury_sources_organization_id_address_key" ON "treasury_sources"("organization_id", "address"); - --- CreateIndex -CREATE INDEX "audit_events_organization_id_created_at_idx" ON "audit_events"("organization_id", "created_at"); - --- CreateIndex -CREATE UNIQUE INDEX "agent_api_keys_key_hash_key" ON "agent_api_keys"("key_hash"); - --- AddForeignKey -ALTER TABLE "members" ADD CONSTRAINT "members_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "organizations"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "proposals" ADD CONSTRAINT "proposals_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "organizations"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "proposals" ADD CONSTRAINT "proposals_proposer_member_id_fkey" FOREIGN KEY ("proposer_member_id") REFERENCES "members"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "voting_sessions" ADD CONSTRAINT "voting_sessions_proposal_id_fkey" FOREIGN KEY ("proposal_id") REFERENCES "proposals"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "votes" ADD CONSTRAINT "votes_session_id_fkey" FOREIGN KEY ("session_id") REFERENCES "voting_sessions"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "votes" ADD CONSTRAINT "votes_member_id_fkey" FOREIGN KEY ("member_id") REFERENCES "members"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "policies" ADD CONSTRAINT "policies_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "organizations"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "policies" ADD CONSTRAINT "policies_approved_by_session_id_fkey" FOREIGN KEY ("approved_by_session_id") REFERENCES "voting_sessions"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "treasury_sources" ADD CONSTRAINT "treasury_sources_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "organizations"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "audit_events" ADD CONSTRAINT "audit_events_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "organizations"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "agent_api_keys" ADD CONSTRAINT "agent_api_keys_member_id_fkey" FOREIGN KEY ("member_id") REFERENCES "members"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - diff --git a/prisma/migrations/2_voting_methods/migration.sql b/prisma/migrations/2_voting_methods/migration.sql deleted file mode 100644 index 16591e3..0000000 --- a/prisma/migrations/2_voting_methods/migration.sql +++ /dev/null @@ -1,63 +0,0 @@ --- Voting methods: the ballot becomes a typed, signed payload. --- --- Before this, a vote could only be YES/NO/ABSTAIN and the column `choice` --- held it. Now the ballot is JSON whose shape its method defines, and the --- canonical encoding of that JSON is what the member's signature covers. --- --- The backfill is the careful part. Every historical vote's stored --- `signed_message` ends in `choice:yes|no|abstain`, and that text is what the --- stored signature actually signs. Rewriting `choice` into a --- `{"method":"single_choice","choice":"yes"}` ballot has to preserve that --- exactly, because `single_choice.canonical()` returns the bare word and so --- reproduces the identical message. No historical signature changes meaning, --- and every one of them still verifies. Dropping `choice` afterwards removes a --- second copy of a fact the ballot now owns. - --- 1. The method enum. -CREATE TYPE "VotingMethod" AS ENUM ('SINGLE_CHOICE', 'CONSENT', 'APPROVAL', 'DOT', 'SCORE', 'RANKED'); - --- 1b. Each organization decides by one structure. Existing organizations keep --- deciding exactly as they did: TOWN reproduces the pre-profile rules. -ALTER TABLE "organizations" - ADD COLUMN "governance_profile" TEXT NOT NULL DEFAULT 'TOWN'; - --- 2. Proposals may state the question's shape and its answer space. -ALTER TABLE "proposals" - ADD COLUMN "method" "VotingMethod", - ADD COLUMN "options" JSONB; - --- 3. Sessions snapshot method, options and dot budget alongside the existing --- rules, and record which option won for ranking methods. -ALTER TABLE "voting_sessions" - ADD COLUMN "method" "VotingMethod" NOT NULL DEFAULT 'SINGLE_CHOICE', - ADD COLUMN "options" JSONB, - ADD COLUMN "dot_budget" INTEGER, - ADD COLUMN "winning_option_key" TEXT; - --- 4. Votes carry a ballot. Added nullable so the backfill can populate it --- before the NOT NULL constraint is applied. -ALTER TABLE "votes" ADD COLUMN "ballot" JSONB; - --- 5. Backfill: every existing vote is a single-choice ballot. lower() because --- the ballot form is the lowercase word that appears in the signed message. -UPDATE "votes" -SET "ballot" = jsonb_build_object('method', 'single_choice', 'choice', lower("choice"::text)) -WHERE "ballot" IS NULL; - --- 6. Refuse to proceed if any vote failed to backfill — a vote without a --- ballot is a vote whose meaning we cannot state, and silently defaulting --- it would forge one. -DO $$ -DECLARE orphaned INTEGER; -BEGIN - SELECT count(*) INTO orphaned FROM "votes" WHERE "ballot" IS NULL; - IF orphaned > 0 THEN - RAISE EXCEPTION 'aborting: % vote(s) have no ballot after backfill', orphaned; - END IF; -END $$; - -ALTER TABLE "votes" ALTER COLUMN "ballot" SET NOT NULL; - --- 7. The old column and its type are now a second source of truth. Remove them. -ALTER TABLE "votes" DROP COLUMN "choice"; -DROP TYPE "VoteChoice"; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml deleted file mode 100644 index 99e4f20..0000000 --- a/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma deleted file mode 100644 index da6b642..0000000 --- a/prisma/schema.prisma +++ /dev/null @@ -1,305 +0,0 @@ -// Solon governance schema v2 — the SSOT for all types (client generated from here). -// -// Design invariants: -// - Solon never holds private keys: members (human OR agent) register a Bitcoin -// address; every vote and proposal carries a Bitcoin signed-message signature. -// - VotingSession snapshots its rules (electorate/threshold/quorum/eligibility) -// at open, so a past decision stays explainable after policy changes. -// - AuditEvent is append-only: no code path may update or delete rows. -// - Policy versions: v1 is the seeded bootstrap; every later version requires an -// APPROVED voting session (enforced in src/lib/domain/voting.ts). - -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "postgresql" -} - -enum MemberType { - HUMAN - AGENT -} - -/// Who holds the member's private key. Never Solon — SELF is a human's own -/// wallet, SERVICE is an agent system's own environment (OC box, FC box). -enum KeyCustody { - SELF - SERVICE -} - -enum MemberStatus { - ACTIVE - SUSPENDED - RETIRED -} - -enum DecisionCategory { - ALLOCATION_POLICY - TREASURY_SPEND - OPERATIONS - AID_DISBURSEMENT - MEMBERSHIP - SAFETY - GOVERNANCE_RULES -} - -enum Electorate { - ALL_MEMBERS - HUMANS_ONLY -} - -enum VoteThreshold { - SIMPLE_MAJORITY - SUPERMAJORITY -} - -enum ProposalStatus { - DRAFT - OPEN - CLOSED -} - -enum SessionStatus { - ACTIVE - CLOSED -} - -enum SessionOutcome { - APPROVED - REJECTED - EXPIRED -} - -/// The shape of the question. See src/lib/domain/methods — each value there -/// owns its ballot schema, its signed encoding, and how it is counted. -enum VotingMethod { - SINGLE_CHOICE - CONSENT - APPROVAL - DOT - SCORE - RANKED -} - -enum PolicyStatus { - ACTIVE - SUPERSEDED -} - -enum AuditEventType { - ORG_CREATED - MEMBER_ADDED - MEMBER_STATUS_CHANGED - PROPOSAL_CREATED - SESSION_OPENED - VOTE_CAST - SESSION_CLOSED - POLICY_ACTIVATED -} - -model Organization { - id String @id @default(uuid()) - slug String @unique - name String - description String? - /// Which structure this organization decides by — see - /// src/lib/config/governance-profiles.ts. Changing it is itself a - /// GOVERNANCE_RULES decision. - governanceProfile String @default("TOWN") @map("governance_profile") - createdAt DateTime @default(now()) @map("created_at") - - members Member[] - proposals Proposal[] - policies Policy[] - treasurySources TreasurySource[] - auditEvents AuditEvent[] - - @@map("organizations") -} - -model Member { - id String @id @default(uuid()) - organizationId String @map("organization_id") - displayName String @map("display_name") - memberType MemberType @map("member_type") - keyCustody KeyCustody @map("key_custody") - /// The address votes must recover to. The only voting credential there is. - bitcoinAddress String @map("bitcoin_address") @db.VarChar(90) - publicKeyHex String? @map("public_key_hex") - votingWeight Decimal @default(1) @map("voting_weight") @db.Decimal(10, 2) - status MemberStatus @default(ACTIVE) - /// OrangeCat actor id once the member linked via OIDC login (humans only). - ocActorId String? @unique @map("oc_actor_id") - /// For agents: which system runs them, e.g. "orangecat:cat", "fleetcrown:loki". - system String? - joinedAt DateTime @default(now()) @map("joined_at") - - organization Organization @relation(fields: [organizationId], references: [id]) - proposals Proposal[] - votes Vote[] - apiKeys AgentApiKey[] - - @@unique([organizationId, bitcoinAddress]) - @@map("members") -} - -model Proposal { - id String @id @default(uuid()) - organizationId String @map("organization_id") - category DecisionCategory - title String - /// Markdown rationale — what is proposed and why. - body String - /// For policy proposals: which policy key this would change… - policyKey String? @map("policy_key") - /// …the exact proposed content… - proposedContent Json? @map("proposed_content") - /// …an external target ("orangecat:allocation_policy:")… - target String? - /// …and sha256 of the canonical JSON of proposedContent — what voters sign over. - contentHash String? @map("content_hash") - /// How this proposal should be decided. Null falls back to the organization's - /// governance profile for this category — the profile is the default, this is - /// the deliberate override. - method VotingMethod? - /// The answer space for multi-option methods: [{key, label}, …]. Null for - /// yes/no questions, which carry their answers in the method itself. - options Json? - proposerMemberId String @map("proposer_member_id") - /// Bitcoin signed-message signature over proposalMessage() by the proposer. - proposerSignature String @map("proposer_signature") - status ProposalStatus @default(DRAFT) - createdAt DateTime @default(now()) @map("created_at") - - organization Organization @relation(fields: [organizationId], references: [id]) - proposer Member @relation(fields: [proposerMemberId], references: [id]) - session VotingSession? - - @@map("proposals") -} - -model VotingSession { - id String @id @default(uuid()) - proposalId String @unique @map("proposal_id") - status SessionStatus @default(ACTIVE) - opensAt DateTime @default(now()) @map("opens_at") - closesAt DateTime @map("closes_at") - - // Snapshot at open — a past decision must stay explainable after the - // governance config changes. - electorate Electorate - method VotingMethod @default(SINGLE_CHOICE) - /// The exact options put to the members, frozen at open. Editing the - /// proposal afterwards cannot change what was voted on. - options Json? - /// Dots each member was given, for DOT sessions. Snapshotted for the same - /// reason as everything else here: a count run next year must use the budget - /// these voters actually had. - dotBudget Int? @map("dot_budget") - threshold VoteThreshold - quorumPercent Int @map("quorum_percent") - eligibleCount Int @map("eligible_count") - eligibleWeight Decimal @map("eligible_weight") @db.Decimal(12, 2) - - outcome SessionOutcome? - /// For ranking methods: the option that won. Null for yes/no decisions. - winningOptionKey String? @map("winning_option_key") - closedAt DateTime? @map("closed_at") - - proposal Proposal @relation(fields: [proposalId], references: [id]) - votes Vote[] - policies Policy[] - - @@map("voting_sessions") -} - -model Vote { - id String @id @default(uuid()) - sessionId String @map("session_id") - memberId String @map("member_id") - /// The ballot as cast, in the shape its method admits. The single source of - /// truth for what this member voted: `signedMessage` carries the canonical - /// encoding of exactly this value, so the two can always be checked against - /// each other. - ballot Json - /// Member's weight snapshotted at cast time. - weight Decimal @db.Decimal(10, 2) - /// The exact canonical message that was signed — stored so anyone can re-verify. - signedMessage String @map("signed_message") - signature String - createdAt DateTime @default(now()) @map("created_at") - - session VotingSession @relation(fields: [sessionId], references: [id]) - member Member @relation(fields: [memberId], references: [id]) - - @@unique([sessionId, memberId]) - @@map("votes") -} - -model Policy { - id String @id @default(uuid()) - organizationId String @map("organization_id") - key String - version Int - content Json - status PolicyStatus - /// Null only for the seeded bootstrap version. Every later version must - /// reference the APPROVED session that legitimated it. - approvedBySessionId String? @map("approved_by_session_id") - activatedAt DateTime @default(now()) @map("activated_at") - - organization Organization @relation(fields: [organizationId], references: [id]) - approvedBySession VotingSession? @relation(fields: [approvedBySessionId], references: [id]) - - @@unique([organizationId, key, version]) - @@map("policies") -} - -/// Watch-only treasury source. Solon never holds funds — it points at -/// independently verifiable on-chain addresses. -model TreasurySource { - id String @id @default(uuid()) - organizationId String @map("organization_id") - label String - address String @db.VarChar(90) - createdAt DateTime @default(now()) @map("created_at") - - organization Organization @relation(fields: [organizationId], references: [id]) - - @@unique([organizationId, address]) - @@map("treasury_sources") -} - -/// Append-only public audit log. No update or delete path exists in code. -model AuditEvent { - id String @id @default(uuid()) - organizationId String @map("organization_id") - eventType AuditEventType @map("event_type") - actorMemberId String? @map("actor_member_id") - subjectType String @map("subject_type") - subjectId String @map("subject_id") - payload Json - createdAt DateTime @default(now()) @map("created_at") - - organization Organization @relation(fields: [organizationId], references: [id]) - - @@index([organizationId, createdAt]) - @@map("audit_events") -} - -/// Transport auth for agent members calling the write API. The API key gets a -/// request in the door; the Bitcoin signature is the authorization artifact. -model AgentApiKey { - id String @id @default(uuid()) - memberId String @map("member_id") - /// sha256 hex of the plaintext key (plaintext shown once at mint). - keyHash String @unique @map("key_hash") - createdAt DateTime @default(now()) @map("created_at") - revokedAt DateTime? @map("revoked_at") - - member Member @relation(fields: [memberId], references: [id]) - - @@map("agent_api_keys") -} diff --git a/scripts/add-member.ts b/scripts/add-member.ts index 290a466..485545c 100644 --- a/scripts/add-member.ts +++ b/scripts/add-member.ts @@ -22,7 +22,9 @@ */ import { parseArgs } from "node:util"; import { randomBytes } from "node:crypto"; -import { prisma } from "../src/lib/db"; +import { and, eq } from "drizzle-orm"; +import { db } from "../src/lib/db/client"; +import { agentApiKeys, auditEvents, members, organizations } from "../src/lib/db/schema"; import { sha256Hex } from "../src/lib/domain/canonical"; const { values } = parseArgs({ @@ -62,25 +64,26 @@ async function main() { process.exit(1); } - const organization = await prisma.organization.findUnique({ where: { slug: org } }); + const organization = await db.query.organizations.findFirst({ + where: eq(organizations.slug, org), + }); if (!organization) { console.error(`organization "${org}" not found`); process.exit(1); } - const existing = await prisma.member.findUnique({ - where: { - organizationId_bitcoinAddress: { organizationId: organization.id, bitcoinAddress: address }, - }, + const existing = await db.query.members.findFirst({ + where: and(eq(members.organizationId, organization.id), eq(members.bitcoinAddress, address)), }); if (existing) { console.log(`member already registered: ${existing.id} (${existing.displayName})`); process.exit(0); } - const member = await prisma.$transaction(async (tx) => { - const m = await tx.member.create({ - data: { + const member = await db.transaction(async (tx) => { + const [m] = await tx + .insert(members) + .values({ organizationId: organization.id, displayName: name, memberType: type, @@ -91,22 +94,20 @@ async function main() { votingWeight: values.weight, system: values.system ?? null, ocActorId: values["oc-actor"] ?? null, - }, - }); - await tx.auditEvent.create({ - data: { - organizationId: organization.id, - eventType: "MEMBER_ADDED", - subjectType: "member", - subjectId: m.id, - payload: { - displayName: name, - memberType: type, - bitcoinAddress: address, - ...(values.system ? { system: values.system } : {}), - ...(values["oc-actor"] ? { ocActorId: values["oc-actor"] } : {}), - note: "operator bootstrap — roster changes after genesis go through MEMBERSHIP votes", - }, + }) + .returning(); + await tx.insert(auditEvents).values({ + organizationId: organization.id, + eventType: "MEMBER_ADDED", + subjectType: "member", + subjectId: m.id, + payload: { + displayName: name, + memberType: type, + bitcoinAddress: address, + ...(values.system ? { system: values.system } : {}), + ...(values["oc-actor"] ? { ocActorId: values["oc-actor"] } : {}), + note: "operator bootstrap — roster changes after genesis go through MEMBERSHIP votes", }, }); return m; @@ -119,9 +120,7 @@ async function main() { process.exit(1); } const plaintext = `sk_solon_${randomBytes(24).toString("hex")}`; - await prisma.agentApiKey.create({ - data: { memberId: member.id, keyHash: sha256Hex(plaintext) }, - }); + await db.insert(agentApiKeys).values({ memberId: member.id, keyHash: sha256Hex(plaintext) }); console.log(`API key (shown once, store it in the agent's env now):\n${plaintext}`); } } @@ -131,4 +130,4 @@ main() console.error(e); process.exit(1); }) - .finally(() => prisma.$disconnect()); + .finally(() => db.$client.end()); diff --git a/scripts/add-treasury-source.ts b/scripts/add-treasury-source.ts index d9bf95c..d1b78ba 100644 --- a/scripts/add-treasury-source.ts +++ b/scripts/add-treasury-source.ts @@ -10,7 +10,9 @@ * --label "OrangeCat platform" --address */ import { parseArgs } from "node:util"; -import { prisma } from "../src/lib/db"; +import { and, eq } from "drizzle-orm"; +import { db } from "../src/lib/db/client"; +import { organizations, treasurySources } from "../src/lib/db/schema"; const { values } = parseArgs({ options: { @@ -27,21 +29,19 @@ async function main() { process.exit(1); } - const organization = await prisma.organization.findUnique({ - where: { slug: org }, + const organization = await db.query.organizations.findFirst({ + where: eq(organizations.slug, org), }); if (!organization) { console.error(`organization "${org}" not found`); process.exit(1); } - const existing = await prisma.treasurySource.findUnique({ - where: { - organizationId_address: { - organizationId: organization.id, - address, - }, - }, + const existing = await db.query.treasurySources.findFirst({ + where: and( + eq(treasurySources.organizationId, organization.id), + eq(treasurySources.address, address), + ), }); if (existing) { console.log( @@ -50,13 +50,10 @@ async function main() { process.exit(0); } - const source = await prisma.treasurySource.create({ - data: { - organizationId: organization.id, - label, - address, - }, - }); + const [source] = await db + .insert(treasurySources) + .values({ organizationId: organization.id, label, address }) + .returning(); console.log(`treasury source created: ${source.id} (${source.label} ${source.address})`); } @@ -65,4 +62,4 @@ main() console.error(e); process.exit(1); }) - .finally(() => prisma.$disconnect()); + .finally(() => db.$client.end()); diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx index dcfd4a4..daecaf8 100644 --- a/src/app/(dashboard)/dashboard/page.tsx +++ b/src/app/(dashboard)/dashboard/page.tsx @@ -1,6 +1,9 @@ import Link from "next/link"; import NextAction from "@/components/dashboard/next-action"; -import { prisma } from "@/lib/db"; +import { desc, eq, inArray } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { auditEvents, proposals, votingSessions } from "@/lib/db/schema"; +import { primaryOrg } from "@/lib/domain/org"; import { sessionAggregate } from "@/lib/domain/voting"; import { summarizeAggregate } from "@/lib/domain/methods/summary"; import { treasuryReport } from "@/lib/domain/treasury"; @@ -26,17 +29,21 @@ export default async function DashboardOverview() { let dbError = false; try { - org = await prisma.organization.findFirst({ - orderBy: { createdAt: "asc" }, - }); + org = (await primaryOrg()) ?? null; // Scoped to this organization on purpose: an unscoped findFirst returns // the newest session in the whole database, so a second organization would // silently surface its vote on this one's dashboard. const s = org - ? await prisma.votingSession.findFirst({ - where: { proposal: { organizationId: org.id } }, - orderBy: { opensAt: "desc" }, - include: { proposal: true }, + ? await db.query.votingSessions.findFirst({ + where: inArray( + votingSessions.proposalId, + db + .select({ id: proposals.id }) + .from(proposals) + .where(eq(proposals.organizationId, org.id)), + ), + orderBy: desc(votingSessions.opensAt), + with: { proposal: true }, }) : null; if (s) { @@ -56,11 +63,11 @@ export default async function DashboardOverview() { ? `${report.sources.length} source(s), ${report.totalSats.toString()} sats on-chain` : `${report.sources.length} source(s) registered; balance currently unresolved`; } - events = await prisma.auditEvent.findMany({ - where: { organizationId: org.id }, - orderBy: { createdAt: "desc" }, - take: 3, - select: { id: true, eventType: true, createdAt: true }, + events = await db.query.auditEvents.findMany({ + where: eq(auditEvents.organizationId, org.id), + orderBy: desc(auditEvents.createdAt), + limit: 3, + columns: { id: true, eventType: true, createdAt: true }, }); } } catch { diff --git a/src/app/(dashboard)/dashboard/treasury/page.tsx b/src/app/(dashboard)/dashboard/treasury/page.tsx index 0184583..ea381e2 100644 --- a/src/app/(dashboard)/dashboard/treasury/page.tsx +++ b/src/app/(dashboard)/dashboard/treasury/page.tsx @@ -1,6 +1,6 @@ import BitcoinTreasury from "@/components/dashboard/bitcoin-treasury"; import { treasuryReport } from "@/lib/domain/treasury"; -import { prisma } from "@/lib/db"; +import { primaryOrg } from "@/lib/domain/org"; export const dynamic = "force-dynamic"; @@ -8,9 +8,7 @@ export default async function TreasuryPage() { let org = null; let dbError = false; try { - org = await prisma.organization.findFirst({ - orderBy: { createdAt: "asc" }, - }); + org = (await primaryOrg()) ?? null; } catch { dbError = true; } diff --git a/src/app/(dashboard)/dashboard/voting/page.tsx b/src/app/(dashboard)/dashboard/voting/page.tsx index 755e378..0ae9628 100644 --- a/src/app/(dashboard)/dashboard/voting/page.tsx +++ b/src/app/(dashboard)/dashboard/voting/page.tsx @@ -1,9 +1,11 @@ import VotingInterface from "@/components/dashboard/voting-interface"; import { readOptions, sessionAggregate } from "@/lib/domain/voting"; -import { methodId } from "@/lib/domain/methods/prisma-enum"; +import { methodId } from "@/lib/domain/methods/db-enum"; import { DEFAULT_DOT_BUDGET } from "@/lib/domain/methods"; import { primaryOrg } from "@/lib/domain/org"; -import { prisma } from "@/lib/db"; +import { desc, eq, inArray } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { proposals, votingSessions } from "@/lib/db/schema"; export const dynamic = "force-dynamic"; @@ -15,11 +17,17 @@ export default async function VotingPage() { // up here as if it were this one's. const org = await primaryOrg(); session = org - ? await prisma.votingSession.findFirst({ - where: { proposal: { organizationId: org.id } }, - orderBy: { opensAt: "desc" }, - include: { proposal: true }, - }) + ? ((await db.query.votingSessions.findFirst({ + where: inArray( + votingSessions.proposalId, + db + .select({ id: proposals.id }) + .from(proposals) + .where(eq(proposals.organizationId, org.id)), + ), + orderBy: desc(votingSessions.opensAt), + with: { proposal: true }, + })) ?? null) : null; } catch { dbError = true; diff --git a/src/app/api/orgs/[slug]/audit/route.ts b/src/app/api/orgs/[slug]/audit/route.ts index 6222092..23fb02d 100644 --- a/src/app/api/orgs/[slug]/audit/route.ts +++ b/src/app/api/orgs/[slug]/audit/route.ts @@ -1,21 +1,24 @@ import { NextResponse } from "next/server"; -import { prisma } from "@/lib/db"; +import { desc, eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { auditEvents } from "@/lib/db/schema"; +import { orgBySlug } from "@/lib/domain/org"; export const dynamic = "force-dynamic"; /** Public read: the append-only audit stream, newest first. */ export async function GET(req: Request, ctx: { params: Promise<{ slug: string }> }) { const params = await ctx.params; - const org = await prisma.organization.findUnique({ where: { slug: params.slug } }); + const org = await orgBySlug(params.slug); if (!org) return NextResponse.json({ error: "Organization not found" }, { status: 404 }); const limitParam = Number(new URL(req.url).searchParams.get("limit")); const take = Number.isInteger(limitParam) && limitParam > 0 ? Math.min(limitParam, 500) : 200; - const events = await prisma.auditEvent.findMany({ - where: { organizationId: org.id }, - orderBy: { createdAt: "desc" }, - take, + const events = await db.query.auditEvents.findMany({ + where: eq(auditEvents.organizationId, org.id), + orderBy: desc(auditEvents.createdAt), + limit: take, }); return NextResponse.json({ diff --git a/src/app/api/orgs/[slug]/policies/[key]/route.ts b/src/app/api/orgs/[slug]/policies/[key]/route.ts index 298647c..5488ddc 100644 --- a/src/app/api/orgs/[slug]/policies/[key]/route.ts +++ b/src/app/api/orgs/[slug]/policies/[key]/route.ts @@ -1,5 +1,8 @@ import { NextResponse } from "next/server"; -import { prisma } from "@/lib/db"; +import { and, desc, eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { policies } from "@/lib/db/schema"; +import { orgBySlug } from "@/lib/domain/org"; export const dynamic = "force-dynamic"; @@ -10,12 +13,12 @@ export const dynamic = "force-dynamic"; */ export async function GET(_: Request, ctx: { params: Promise<{ slug: string; key: string }> }) { const params = await ctx.params; - const org = await prisma.organization.findUnique({ where: { slug: params.slug } }); + const org = await orgBySlug(params.slug); if (!org) return NextResponse.json({ error: "Organization not found" }, { status: 404 }); - const versions = await prisma.policy.findMany({ - where: { organizationId: org.id, key: params.key }, - orderBy: { version: "desc" }, + const versions = await db.query.policies.findMany({ + where: and(eq(policies.organizationId, org.id), eq(policies.key, params.key)), + orderBy: desc(policies.version), }); if (versions.length === 0) return NextResponse.json({ error: "Policy not found" }, { status: 404 }); diff --git a/src/app/api/orgs/[slug]/proposals/route.ts b/src/app/api/orgs/[slug]/proposals/route.ts index d7f29d9..effe973 100644 --- a/src/app/api/orgs/[slug]/proposals/route.ts +++ b/src/app/api/orgs/[slug]/proposals/route.ts @@ -1,21 +1,24 @@ import { NextResponse } from "next/server"; -import { prisma } from "@/lib/db"; +import { desc, eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { proposals as proposalsTable } from "@/lib/db/schema"; +import { orgBySlug } from "@/lib/domain/org"; export const dynamic = "force-dynamic"; /** Public read: all proposals of an organization, newest first. */ export async function GET(_: Request, ctx: { params: Promise<{ slug: string }> }) { const params = await ctx.params; - const org = await prisma.organization.findUnique({ where: { slug: params.slug } }); + const org = await orgBySlug(params.slug); if (!org) return NextResponse.json({ error: "Organization not found" }, { status: 404 }); - const proposals = await prisma.proposal.findMany({ - where: { organizationId: org.id }, - orderBy: { createdAt: "desc" }, - take: 200, - include: { - proposer: { select: { displayName: true, memberType: true, bitcoinAddress: true } }, - session: { select: { id: true, status: true, outcome: true, closesAt: true } }, + const proposals = await db.query.proposals.findMany({ + where: eq(proposalsTable.organizationId, org.id), + orderBy: desc(proposalsTable.createdAt), + limit: 200, + with: { + proposer: { columns: { displayName: true, memberType: true, bitcoinAddress: true } }, + session: { columns: { id: true, status: true, outcome: true, closesAt: true } }, }, }); diff --git a/src/app/api/orgs/[slug]/route.ts b/src/app/api/orgs/[slug]/route.ts index fb46595..01328f7 100644 --- a/src/app/api/orgs/[slug]/route.ts +++ b/src/app/api/orgs/[slug]/route.ts @@ -1,5 +1,7 @@ import { NextResponse } from "next/server"; -import { prisma } from "@/lib/db"; +import { asc, eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { members, organizations } from "@/lib/db/schema"; export const dynamic = "force-dynamic"; @@ -10,11 +12,11 @@ export const dynamic = "force-dynamic"; */ export async function GET(_: Request, ctx: { params: Promise<{ slug: string }> }) { const params = await ctx.params; - const org = await prisma.organization.findUnique({ - where: { slug: params.slug }, - include: { + const org = await db.query.organizations.findFirst({ + where: eq(organizations.slug, params.slug), + with: { members: { - select: { + columns: { id: true, displayName: true, memberType: true, @@ -26,7 +28,7 @@ export async function GET(_: Request, ctx: { params: Promise<{ slug: string }> } system: true, joinedAt: true, }, - orderBy: { joinedAt: "asc" }, + orderBy: asc(members.joinedAt), }, }, }); diff --git a/src/app/api/orgs/[slug]/treasury/route.ts b/src/app/api/orgs/[slug]/treasury/route.ts index bb5f5be..af23786 100644 --- a/src/app/api/orgs/[slug]/treasury/route.ts +++ b/src/app/api/orgs/[slug]/treasury/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { prisma } from "@/lib/db"; +import { orgBySlug } from "@/lib/domain/org"; import { treasuryReport } from "@/lib/domain/treasury"; export const dynamic = "force-dynamic"; @@ -12,7 +12,7 @@ export const dynamic = "force-dynamic"; */ export async function GET(_: Request, ctx: { params: Promise<{ slug: string }> }) { const params = await ctx.params; - const org = await prisma.organization.findUnique({ where: { slug: params.slug } }); + const org = await orgBySlug(params.slug); if (!org) return NextResponse.json({ error: "Organization not found" }, { status: 404 }); const report = await treasuryReport(org.id); diff --git a/src/app/api/proposals/route.ts b/src/app/api/proposals/route.ts index 9388d19..6870a83 100644 --- a/src/app/api/proposals/route.ts +++ b/src/app/api/proposals/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { z } from "zod"; -import { DecisionCategory, VotingMethod } from "@prisma/client"; +import { DecisionCategory, VotingMethod } from "@/lib/db/enums"; import { createProposal } from "@/lib/domain/proposals"; const BodySchema = z.object({ diff --git a/src/app/api/sessions/[sessionId]/close/route.ts b/src/app/api/sessions/[sessionId]/close/route.ts index c134534..4a4ad0f 100644 --- a/src/app/api/sessions/[sessionId]/close/route.ts +++ b/src/app/api/sessions/[sessionId]/close/route.ts @@ -1,5 +1,7 @@ import { NextResponse } from "next/server"; -import { prisma } from "@/lib/db"; +import { eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { votingSessions } from "@/lib/db/schema"; import { closeSession } from "@/lib/domain/voting"; import { emitDecisionFinalized } from "@/lib/webhooks"; @@ -17,10 +19,11 @@ export async function POST(_: Request, ctx: { params: Promise<{ sessionId: strin try { const result = await closeSession(params.sessionId); - const proposal = await prisma.proposal.findFirst({ - where: { session: { id: params.sessionId } }, - include: { organization: { select: { id: true, slug: true } } }, + const closedSession = await db.query.votingSessions.findFirst({ + where: eq(votingSessions.id, params.sessionId), + with: { proposal: { with: { organization: { columns: { id: true, slug: true } } } } }, }); + const proposal = closedSession?.proposal; if (proposal) { await emitDecisionFinalized({ decision_id: params.sessionId, diff --git a/src/app/api/sessions/[sessionId]/route.ts b/src/app/api/sessions/[sessionId]/route.ts index 80a97b5..dff48e4 100644 --- a/src/app/api/sessions/[sessionId]/route.ts +++ b/src/app/api/sessions/[sessionId]/route.ts @@ -1,15 +1,17 @@ import { NextResponse } from "next/server"; -import { prisma } from "@/lib/db"; +import { eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { votingSessions } from "@/lib/db/schema"; import { sessionTally } from "@/lib/domain/voting"; /** Public read: a voting session, its snapshotted rules, and the live tally. */ export async function GET(_: Request, ctx: { params: Promise<{ sessionId: string }> }) { const params = await ctx.params; - const session = await prisma.votingSession.findUnique({ - where: { id: params.sessionId }, - include: { + const session = await db.query.votingSessions.findFirst({ + where: eq(votingSessions.id, params.sessionId), + with: { proposal: { - select: { + columns: { id: true, title: true, category: true, diff --git a/src/app/ecosystem/page.tsx b/src/app/ecosystem/page.tsx index 460a2cf..3bff704 100644 --- a/src/app/ecosystem/page.tsx +++ b/src/app/ecosystem/page.tsx @@ -1,8 +1,15 @@ import PageLayout from "@/components/ui/page-layout"; -import { prisma } from "@/lib/db"; +import { and, asc, desc, eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { + members as membersTable, + policies as policiesTable, + proposals as proposalsTable, +} from "@/lib/db/schema"; +import { primaryOrg } from "@/lib/domain/org"; import { ECOSYSTEM_PILLARS } from "@/lib/config/ecosystem"; import { CATEGORY_ELECTORATE } from "@/lib/config/governance"; -import { Electorate, type DecisionCategory } from "@prisma/client"; +import { Electorate, type DecisionCategory } from "@/lib/db/enums"; export const dynamic = "force-dynamic"; @@ -43,15 +50,13 @@ export default async function EcosystemPage() { let dbError = false; try { - org = await prisma.organization.findFirst({ - orderBy: { createdAt: "asc" }, - }); + org = (await primaryOrg()) ?? null; if (org) { [members, policies, proposals] = await Promise.all([ - prisma.member.findMany({ - where: { organizationId: org.id }, - orderBy: { joinedAt: "asc" }, - select: { + db.query.members.findMany({ + where: eq(membersTable.organizationId, org.id), + orderBy: asc(membersTable.joinedAt), + columns: { id: true, displayName: true, memberType: true, @@ -60,22 +65,24 @@ export default async function EcosystemPage() { status: true, }, }), - prisma.policy.findMany({ - where: { organizationId: org.id, status: "ACTIVE" }, - orderBy: { key: "asc" }, - select: { key: true, version: true, content: true }, + db.query.policies.findMany({ + where: and(eq(policiesTable.organizationId, org.id), eq(policiesTable.status, "ACTIVE")), + orderBy: asc(policiesTable.key), + columns: { key: true, version: true, content: true }, }), - prisma.proposal.findMany({ - where: { organizationId: org.id }, - orderBy: { createdAt: "desc" }, - take: 10, - select: { + db.query.proposals.findMany({ + where: eq(proposalsTable.organizationId, org.id), + orderBy: desc(proposalsTable.createdAt), + limit: 10, + columns: { id: true, title: true, category: true, status: true, + }, + with: { session: { - select: { id: true, status: true, outcome: true }, + columns: { id: true, status: true, outcome: true }, }, }, }), diff --git a/src/app/governance/audit/page.tsx b/src/app/governance/audit/page.tsx index 0af30fb..7a29087 100644 --- a/src/app/governance/audit/page.tsx +++ b/src/app/governance/audit/page.tsx @@ -1,7 +1,10 @@ import Link from "next/link"; import PageLayout from "@/components/ui/page-layout"; -import { prisma } from "@/lib/db"; -import type { AuditEvent, AuditEventType } from "@prisma/client"; +import { desc, eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { auditEvents } from "@/lib/db/schema"; +import { primaryOrg } from "@/lib/domain/org"; +import type { AuditEvent, AuditEventType } from "@/lib/db/schema"; export const dynamic = "force-dynamic"; @@ -56,14 +59,12 @@ export default async function AuditPage() { let events: AuditEvent[] = []; let dbError = false; try { - org = await prisma.organization.findFirst({ - orderBy: { createdAt: "asc" }, - }); + org = (await primaryOrg()) ?? null; if (org) { - events = await prisma.auditEvent.findMany({ - where: { organizationId: org.id }, - orderBy: { createdAt: "desc" }, - take: 200, + events = await db.query.auditEvents.findMany({ + where: eq(auditEvents.organizationId, org.id), + orderBy: desc(auditEvents.createdAt), + limit: 200, }); } } catch { diff --git a/src/app/proposals/[proposalId]/page.tsx b/src/app/proposals/[proposalId]/page.tsx index d4260f3..a1bcbc8 100644 --- a/src/app/proposals/[proposalId]/page.tsx +++ b/src/app/proposals/[proposalId]/page.tsx @@ -1,8 +1,10 @@ import Link from "next/link"; import { notFound } from "next/navigation"; -import { prisma } from "@/lib/db"; +import { eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { proposals } from "@/lib/db/schema"; import { readOptions, sessionAggregate } from "@/lib/domain/voting"; -import { methodId } from "@/lib/domain/methods/prisma-enum"; +import { methodId } from "@/lib/domain/methods/db-enum"; import { DEFAULT_DOT_BUDGET } from "@/lib/domain/methods"; import VotingInterface from "@/components/dashboard/voting-interface"; import OpenSessionButton from "@/components/governance/open-session-button"; @@ -20,9 +22,9 @@ export default async function ProposalPage({ params: Promise<{ proposalId: string }>; }) { const { proposalId } = await params; - const proposal = await prisma.proposal.findUnique({ - where: { id: proposalId }, - include: { proposer: true, session: true, organization: true }, + const proposal = await db.query.proposals.findFirst({ + where: eq(proposals.id, proposalId), + with: { proposer: true, session: true, organization: true }, }); if (!proposal) notFound(); diff --git a/src/app/proposals/page.tsx b/src/app/proposals/page.tsx index 121e9fd..7bc7dee 100644 --- a/src/app/proposals/page.tsx +++ b/src/app/proposals/page.tsx @@ -1,5 +1,7 @@ import Link from "next/link"; -import { prisma } from "@/lib/db"; +import { desc, eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { proposals as proposalsTable } from "@/lib/db/schema"; import { primaryOrg } from "@/lib/domain/org"; export const metadata = { title: "Proposals — Solon" }; @@ -14,10 +16,10 @@ const STATUS_ACTION: Record = { export default async function ProposalsPage() { const org = await primaryOrg(); const proposals = org - ? await prisma.proposal.findMany({ - where: { organizationId: org.id }, - orderBy: { createdAt: "desc" }, - include: { proposer: true, session: true }, + ? await db.query.proposals.findMany({ + where: eq(proposalsTable.organizationId, org.id), + orderBy: desc(proposalsTable.createdAt), + with: { proposer: true, session: true }, }) : []; diff --git a/src/components/dashboard/next-action.tsx b/src/components/dashboard/next-action.tsx index a15c697..1b7dd67 100644 --- a/src/components/dashboard/next-action.tsx +++ b/src/components/dashboard/next-action.tsx @@ -1,8 +1,10 @@ import Link from "next/link"; -import { SessionStatus } from "@prisma/client"; +import { and, desc, eq, inArray } from "drizzle-orm"; +import { SessionStatus } from "@/lib/db/enums"; import { auth } from "@/lib/auth"; import { memberForActor } from "@/lib/auth/recognition"; -import { prisma } from "@/lib/db"; +import { db } from "@/lib/db/client"; +import { organizations, proposals, votes, votingSessions } from "@/lib/db/schema"; import { genesisOpen } from "@/lib/domain/membership"; interface NextStep { @@ -53,18 +55,25 @@ async function nextStep(orgSlug: string): Promise { }; } - const active = await prisma.votingSession.findFirst({ - where: { - status: SessionStatus.ACTIVE, - proposal: { organization: { slug: orgSlug } }, - }, - orderBy: { opensAt: "desc" }, - include: { proposal: true }, + const active = await db.query.votingSessions.findFirst({ + where: and( + eq(votingSessions.status, SessionStatus.ACTIVE), + inArray( + votingSessions.proposalId, + db + .select({ id: proposals.id }) + .from(proposals) + .innerJoin(organizations, eq(proposals.organizationId, organizations.id)) + .where(eq(organizations.slug, orgSlug)), + ), + ), + orderBy: desc(votingSessions.opensAt), + with: { proposal: true }, }); if (active) { - const alreadyVoted = await prisma.vote.findFirst({ - where: { sessionId: active.id, memberId: member.id }, + const alreadyVoted = await db.query.votes.findFirst({ + where: and(eq(votes.sessionId, active.id), eq(votes.memberId, member.id)), }); if (!alreadyVoted) { return { @@ -76,9 +85,18 @@ async function nextStep(orgSlug: string): Promise { } } - const draft = await prisma.proposal.findFirst({ - where: { status: "DRAFT", organization: { slug: orgSlug } }, - orderBy: { createdAt: "desc" }, + const draft = await db.query.proposals.findFirst({ + where: and( + eq(proposals.status, "DRAFT"), + inArray( + proposals.organizationId, + db + .select({ id: organizations.id }) + .from(organizations) + .where(eq(organizations.slug, orgSlug)), + ), + ), + orderBy: desc(proposals.createdAt), }); if (draft) { return { diff --git a/src/lib/auth/recognition.ts b/src/lib/auth/recognition.ts index af8cab7..c011bae 100644 --- a/src/lib/auth/recognition.ts +++ b/src/lib/auth/recognition.ts @@ -1,4 +1,6 @@ -import { prisma } from "@/lib/db"; +import { eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { members } from "@/lib/db/schema"; /** * Login on Solon is recognition, not authority. A session shows you your @@ -32,8 +34,8 @@ export function isRecognizableProfile(profile: OrangeCatProfile | undefined | nu * most one member. */ export function memberForActor(actorId: string) { - return prisma.member.findUnique({ - where: { ocActorId: actorId }, - include: { organization: { select: { slug: true, name: true } } }, + return db.query.members.findFirst({ + where: eq(members.ocActorId, actorId), + with: { organization: { columns: { slug: true, name: true } } }, }); } diff --git a/src/lib/config/__tests__/governance-profiles.test.ts b/src/lib/config/__tests__/governance-profiles.test.ts index 59f980a..2bf56bf 100644 --- a/src/lib/config/__tests__/governance-profiles.test.ts +++ b/src/lib/config/__tests__/governance-profiles.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { DecisionCategory, Electorate, VoteThreshold } from "@prisma/client"; +import { DecisionCategory, Electorate, VoteThreshold } from "@/lib/db/enums"; import { CATEGORY_ELECTORATE, CATEGORY_QUORUM_PERCENT, CATEGORY_THRESHOLD } from "../governance"; import { GOVERNANCE_PROFILES, electorateFor, profileFor, ruleFor } from "../governance-profiles"; import { ALL_METHODS } from "@/lib/domain/methods"; diff --git a/src/lib/config/__tests__/governance.test.ts b/src/lib/config/__tests__/governance.test.ts index a9a84f6..862e476 100644 --- a/src/lib/config/__tests__/governance.test.ts +++ b/src/lib/config/__tests__/governance.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { DecisionCategory, Electorate } from "@prisma/client"; +import { DecisionCategory, Electorate } from "@/lib/db/enums"; import { CATEGORY_ELECTORATE, CATEGORY_QUORUM_PERCENT, diff --git a/src/lib/config/governance-profiles.ts b/src/lib/config/governance-profiles.ts index 7ea779c..905bfa2 100644 --- a/src/lib/config/governance-profiles.ts +++ b/src/lib/config/governance-profiles.ts @@ -1,4 +1,4 @@ -import { DecisionCategory, Electorate, VoteThreshold } from "@prisma/client"; +import { DecisionCategory, Electorate, VoteThreshold } from "@/lib/db/enums"; import type { MethodId } from "@/lib/domain/methods/types"; import { CATEGORY_ELECTORATE } from "./governance"; diff --git a/src/lib/config/governance.ts b/src/lib/config/governance.ts index eac03d7..6c53a56 100644 --- a/src/lib/config/governance.ts +++ b/src/lib/config/governance.ts @@ -1,4 +1,4 @@ -import { DecisionCategory, Electorate, VoteThreshold } from "@prisma/client"; +import { DecisionCategory, Electorate, VoteThreshold } from "@/lib/db/enums"; /** * SSOT for who decides what, and how. diff --git a/src/lib/db.ts b/src/lib/db.ts deleted file mode 100644 index f6ce7a2..0000000 --- a/src/lib/db.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { PrismaClient } from "@prisma/client"; -import { PrismaPg } from "@prisma/adapter-pg"; - -const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined }; - -const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }); - -export const prisma = - globalForPrisma.prisma || - new PrismaClient({ - adapter, - log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], - }); - -if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; diff --git a/src/lib/db/client.ts b/src/lib/db/client.ts new file mode 100644 index 0000000..c1a0457 --- /dev/null +++ b/src/lib/db/client.ts @@ -0,0 +1,41 @@ +/** + * The single database door — Drizzle over a node-postgres pool (fleet house + * pattern). + * + * Lazy initialization: the pool is created on first use, not at import time, + * so `next build` and CI stay hermetic without a DATABASE_URL — the error only + * fires when a route actually touches the database (and the server components + * that do already catch it and render their fallback). + * + * Hot-reload-safe: in development the pool is stashed on globalThis so Next's + * module reloads do not leak connections. + */ +import { drizzle } from "drizzle-orm/node-postgres"; +import { Pool } from "pg"; +import * as schema from "./schema"; + +type Db = ReturnType>; + +const globalForDb = globalThis as unknown as { solonDb: Db | undefined }; + +function getDb(): Db { + if (globalForDb.solonDb) return globalForDb.solonDb; + const url = process.env.DATABASE_URL; + if (!url) throw new Error("DATABASE_URL is not set — required for database operations"); + const db = drizzle(new Pool({ connectionString: url }), { schema }); + if (process.env.NODE_ENV !== "production") globalForDb.solonDb = db; + return db; +} + +/** Proxy that initializes the real client on first property access. */ +export const db = new Proxy({} as Db, { + get(_target, prop, receiver) { + const real = getDb(); + const value = Reflect.get(real, prop, receiver); + return typeof value === "function" ? value.bind(real) : value; + }, +}); + +export type Database = Db; +/** The transaction handle domain code receives inside db.transaction(). */ +export type Tx = Parameters[0]>[0]; diff --git a/src/lib/db/enums.ts b/src/lib/db/enums.ts new file mode 100644 index 0000000..c46cdb0 --- /dev/null +++ b/src/lib/db/enums.ts @@ -0,0 +1,92 @@ +/** + * Governance enums — the domain vocabulary, dependency-free. + * + * These const objects mirror the Postgres enum types one-to-one (the pgEnum + * definitions in ./schema.ts are built from these tuples, so the two cannot + * drift). They live apart from the schema so that client components and the + * voter's browser can use the vocabulary without pulling a database layer + * into the bundle — the same reason the old Prisma-enum bridge existed. + * + * Value semantics are identical to the Prisma client's generated enums: + * `MemberType.HUMAN === "HUMAN"`, and the type is the union of the values. + */ + +function enumLike(values: T) { + return Object.fromEntries(values.map((v) => [v, v])) as { [K in T[number]]: K }; +} + +export const MEMBER_TYPES = ["HUMAN", "AGENT"] as const; +export const MemberType = enumLike(MEMBER_TYPES); +export type MemberType = (typeof MEMBER_TYPES)[number]; + +export const KEY_CUSTODIES = ["SELF", "SERVICE"] as const; +export const KeyCustody = enumLike(KEY_CUSTODIES); +export type KeyCustody = (typeof KEY_CUSTODIES)[number]; + +export const MEMBER_STATUSES = ["ACTIVE", "SUSPENDED", "RETIRED"] as const; +export const MemberStatus = enumLike(MEMBER_STATUSES); +export type MemberStatus = (typeof MEMBER_STATUSES)[number]; + +export const DECISION_CATEGORIES = [ + "ALLOCATION_POLICY", + "TREASURY_SPEND", + "OPERATIONS", + "AID_DISBURSEMENT", + "MEMBERSHIP", + "SAFETY", + "GOVERNANCE_RULES", +] as const; +export const DecisionCategory = enumLike(DECISION_CATEGORIES); +export type DecisionCategory = (typeof DECISION_CATEGORIES)[number]; + +export const ELECTORATES = ["ALL_MEMBERS", "HUMANS_ONLY"] as const; +export const Electorate = enumLike(ELECTORATES); +export type Electorate = (typeof ELECTORATES)[number]; + +export const VOTE_THRESHOLDS = ["SIMPLE_MAJORITY", "SUPERMAJORITY"] as const; +export const VoteThreshold = enumLike(VOTE_THRESHOLDS); +export type VoteThreshold = (typeof VOTE_THRESHOLDS)[number]; + +export const PROPOSAL_STATUSES = ["DRAFT", "OPEN", "CLOSED"] as const; +export const ProposalStatus = enumLike(PROPOSAL_STATUSES); +export type ProposalStatus = (typeof PROPOSAL_STATUSES)[number]; + +export const SESSION_STATUSES = ["ACTIVE", "CLOSED"] as const; +export const SessionStatus = enumLike(SESSION_STATUSES); +export type SessionStatus = (typeof SESSION_STATUSES)[number]; + +export const SESSION_OUTCOMES = ["APPROVED", "REJECTED", "EXPIRED"] as const; +export const SessionOutcome = enumLike(SESSION_OUTCOMES); +export type SessionOutcome = (typeof SESSION_OUTCOMES)[number]; + +/** + * The shape of the question. See src/lib/domain/methods — each value there + * owns its ballot schema, its signed encoding, and how it is counted. + */ +export const VOTING_METHODS = [ + "SINGLE_CHOICE", + "CONSENT", + "APPROVAL", + "DOT", + "SCORE", + "RANKED", +] as const; +export const VotingMethod = enumLike(VOTING_METHODS); +export type VotingMethod = (typeof VOTING_METHODS)[number]; + +export const POLICY_STATUSES = ["ACTIVE", "SUPERSEDED"] as const; +export const PolicyStatus = enumLike(POLICY_STATUSES); +export type PolicyStatus = (typeof POLICY_STATUSES)[number]; + +export const AUDIT_EVENT_TYPES = [ + "ORG_CREATED", + "MEMBER_ADDED", + "MEMBER_STATUS_CHANGED", + "PROPOSAL_CREATED", + "SESSION_OPENED", + "VOTE_CAST", + "SESSION_CLOSED", + "POLICY_ACTIVATED", +] as const; +export const AuditEventType = enumLike(AUDIT_EVENT_TYPES); +export type AuditEventType = (typeof AUDIT_EVENT_TYPES)[number]; diff --git a/src/lib/db/schema.ts b/src/lib/db/schema.ts new file mode 100644 index 0000000..2a0f00c --- /dev/null +++ b/src/lib/db/schema.ts @@ -0,0 +1,478 @@ +/** + * Solon governance schema v2 — the SSOT for all types (inferred from here). + * + * Ported 1:1 from prisma/schema.prisma when the fleet standardized on Drizzle; + * the live database was shaped by Prisma's migrations, so every table, column, + * enum, index and constraint name here matches that SQL byte-for-byte + * (`organizations_slug_key`, `members_organization_id_fkey`, …). Nothing in + * this file may rename or reshape an existing object — that would be a schema + * change wearing a refactor's clothes. + * + * Design invariants (unchanged from v2): + * - Solon never holds private keys: members (human OR agent) register a Bitcoin + * address; every vote and proposal carries a Bitcoin signed-message signature. + * - VotingSession snapshots its rules (electorate/threshold/quorum/eligibility) + * at open, so a past decision stays explainable after policy changes. + * - AuditEvent is append-only: no code path may update or delete rows. + * - Policy versions: v1 is the seeded bootstrap; every later version requires an + * APPROVED voting session (enforced in src/lib/domain/voting.ts). + * + * Ids are minted in the app (crypto.randomUUID), exactly as Prisma's + * `@default(uuid())` was — the columns carry no database default. + */ +import { relations, sql } from "drizzle-orm"; +import { + foreignKey, + index, + integer, + jsonb, + numeric, + pgEnum, + pgTable, + text, + timestamp, + uniqueIndex, + varchar, +} from "drizzle-orm/pg-core"; +import { randomUUID } from "node:crypto"; +import { + AUDIT_EVENT_TYPES, + DECISION_CATEGORIES, + ELECTORATES, + KEY_CUSTODIES, + MEMBER_STATUSES, + MEMBER_TYPES, + POLICY_STATUSES, + PROPOSAL_STATUSES, + SESSION_OUTCOMES, + SESSION_STATUSES, + VOTE_THRESHOLDS, + VOTING_METHODS, +} from "./enums"; + +export * from "./enums"; + +// Postgres enum type names are the Prisma-era PascalCase ones — they already +// exist in the live database under exactly these names. +export const memberTypeEnum = pgEnum("MemberType", MEMBER_TYPES); +export const keyCustodyEnum = pgEnum("KeyCustody", KEY_CUSTODIES); +export const memberStatusEnum = pgEnum("MemberStatus", MEMBER_STATUSES); +export const decisionCategoryEnum = pgEnum("DecisionCategory", DECISION_CATEGORIES); +export const electorateEnum = pgEnum("Electorate", ELECTORATES); +export const voteThresholdEnum = pgEnum("VoteThreshold", VOTE_THRESHOLDS); +export const proposalStatusEnum = pgEnum("ProposalStatus", PROPOSAL_STATUSES); +export const sessionStatusEnum = pgEnum("SessionStatus", SESSION_STATUSES); +export const sessionOutcomeEnum = pgEnum("SessionOutcome", SESSION_OUTCOMES); +export const votingMethodEnum = pgEnum("VotingMethod", VOTING_METHODS); +export const policyStatusEnum = pgEnum("PolicyStatus", POLICY_STATUSES); +export const auditEventTypeEnum = pgEnum("AuditEventType", AUDIT_EVENT_TYPES); + +const uuid = () => randomUUID(); + +export const organizations = pgTable( + "organizations", + { + id: text("id").primaryKey().$defaultFn(uuid), + slug: text("slug").notNull(), + name: text("name").notNull(), + description: text("description"), + /** + * Which structure this organization decides by — see + * src/lib/config/governance-profiles.ts. Changing it is itself a + * GOVERNANCE_RULES decision. + */ + governanceProfile: text("governance_profile").notNull().default("TOWN"), + createdAt: timestamp("created_at", { precision: 3, mode: "date" }).notNull().defaultNow(), + }, + (t) => [uniqueIndex("organizations_slug_key").on(t.slug)], +); + +export const members = pgTable( + "members", + { + id: text("id").primaryKey().$defaultFn(uuid), + organizationId: text("organization_id").notNull(), + displayName: text("display_name").notNull(), + memberType: memberTypeEnum("member_type").notNull(), + /** Who holds the member's private key. Never Solon — SELF is a human's own + * wallet, SERVICE is an agent system's own environment (OC box, FC box). */ + keyCustody: keyCustodyEnum("key_custody").notNull(), + /** The address votes must recover to. The only voting credential there is. */ + bitcoinAddress: varchar("bitcoin_address", { length: 90 }).notNull(), + publicKeyHex: text("public_key_hex"), + votingWeight: numeric("voting_weight", { precision: 10, scale: 2 }) + .notNull() + .default(sql`1`), + status: memberStatusEnum("status").notNull().default("ACTIVE"), + /** OrangeCat actor id once the member linked via OIDC login (humans only). */ + ocActorId: text("oc_actor_id"), + /** For agents: which system runs them, e.g. "orangecat:cat", "fleetcrown:loki". */ + system: text("system"), + joinedAt: timestamp("joined_at", { precision: 3, mode: "date" }).notNull().defaultNow(), + }, + (t) => [ + uniqueIndex("members_oc_actor_id_key").on(t.ocActorId), + uniqueIndex("members_organization_id_bitcoin_address_key").on( + t.organizationId, + t.bitcoinAddress, + ), + foreignKey({ + columns: [t.organizationId], + foreignColumns: [organizations.id], + name: "members_organization_id_fkey", + }) + .onDelete("restrict") + .onUpdate("cascade"), + ], +); + +export const proposals = pgTable( + "proposals", + { + id: text("id").primaryKey().$defaultFn(uuid), + organizationId: text("organization_id").notNull(), + category: decisionCategoryEnum("category").notNull(), + title: text("title").notNull(), + /** Markdown rationale — what is proposed and why. */ + body: text("body").notNull(), + /** For policy proposals: which policy key this would change… */ + policyKey: text("policy_key"), + /** …the exact proposed content… */ + proposedContent: jsonb("proposed_content"), + /** …an external target ("orangecat:allocation_policy:")… */ + target: text("target"), + /** …and sha256 of the canonical JSON of proposedContent — what voters sign over. */ + contentHash: text("content_hash"), + /** + * How this proposal should be decided. Null falls back to the organization's + * governance profile for this category — the profile is the default, this is + * the deliberate override. + */ + method: votingMethodEnum("method"), + /** + * The answer space for multi-option methods: [{key, label}, …]. Null for + * yes/no questions, which carry their answers in the method itself. + */ + options: jsonb("options"), + proposerMemberId: text("proposer_member_id").notNull(), + /** Bitcoin signed-message signature over proposalMessage() by the proposer. */ + proposerSignature: text("proposer_signature").notNull(), + status: proposalStatusEnum("status").notNull().default("DRAFT"), + createdAt: timestamp("created_at", { precision: 3, mode: "date" }).notNull().defaultNow(), + }, + (t) => [ + foreignKey({ + columns: [t.organizationId], + foreignColumns: [organizations.id], + name: "proposals_organization_id_fkey", + }) + .onDelete("restrict") + .onUpdate("cascade"), + foreignKey({ + columns: [t.proposerMemberId], + foreignColumns: [members.id], + name: "proposals_proposer_member_id_fkey", + }) + .onDelete("restrict") + .onUpdate("cascade"), + ], +); + +export const votingSessions = pgTable( + "voting_sessions", + { + id: text("id").primaryKey().$defaultFn(uuid), + proposalId: text("proposal_id").notNull(), + status: sessionStatusEnum("status").notNull().default("ACTIVE"), + opensAt: timestamp("opens_at", { precision: 3, mode: "date" }).notNull().defaultNow(), + closesAt: timestamp("closes_at", { precision: 3, mode: "date" }).notNull(), + + // Snapshot at open — a past decision must stay explainable after the + // governance config changes. + electorate: electorateEnum("electorate").notNull(), + method: votingMethodEnum("method").notNull().default("SINGLE_CHOICE"), + /** + * The exact options put to the members, frozen at open. Editing the + * proposal afterwards cannot change what was voted on. + */ + options: jsonb("options"), + /** + * Dots each member was given, for DOT sessions. Snapshotted for the same + * reason as everything else here: a count run next year must use the budget + * these voters actually had. + */ + dotBudget: integer("dot_budget"), + threshold: voteThresholdEnum("threshold").notNull(), + quorumPercent: integer("quorum_percent").notNull(), + eligibleCount: integer("eligible_count").notNull(), + eligibleWeight: numeric("eligible_weight", { precision: 12, scale: 2 }).notNull(), + + outcome: sessionOutcomeEnum("outcome"), + /** For ranking methods: the option that won. Null for yes/no decisions. */ + winningOptionKey: text("winning_option_key"), + closedAt: timestamp("closed_at", { precision: 3, mode: "date" }), + }, + (t) => [ + uniqueIndex("voting_sessions_proposal_id_key").on(t.proposalId), + foreignKey({ + columns: [t.proposalId], + foreignColumns: [proposals.id], + name: "voting_sessions_proposal_id_fkey", + }) + .onDelete("restrict") + .onUpdate("cascade"), + ], +); + +export const votes = pgTable( + "votes", + { + id: text("id").primaryKey().$defaultFn(uuid), + sessionId: text("session_id").notNull(), + memberId: text("member_id").notNull(), + /** + * The ballot as cast, in the shape its method admits. The single source of + * truth for what this member voted: `signedMessage` carries the canonical + * encoding of exactly this value, so the two can always be checked against + * each other. + */ + ballot: jsonb("ballot").notNull(), + /** Member's weight snapshotted at cast time. */ + weight: numeric("weight", { precision: 10, scale: 2 }).notNull(), + /** The exact canonical message that was signed — stored so anyone can re-verify. */ + signedMessage: text("signed_message").notNull(), + signature: text("signature").notNull(), + createdAt: timestamp("created_at", { precision: 3, mode: "date" }).notNull().defaultNow(), + }, + (t) => [ + uniqueIndex("votes_session_id_member_id_key").on(t.sessionId, t.memberId), + foreignKey({ + columns: [t.sessionId], + foreignColumns: [votingSessions.id], + name: "votes_session_id_fkey", + }) + .onDelete("restrict") + .onUpdate("cascade"), + foreignKey({ + columns: [t.memberId], + foreignColumns: [members.id], + name: "votes_member_id_fkey", + }) + .onDelete("restrict") + .onUpdate("cascade"), + ], +); + +export const policies = pgTable( + "policies", + { + id: text("id").primaryKey().$defaultFn(uuid), + organizationId: text("organization_id").notNull(), + key: text("key").notNull(), + version: integer("version").notNull(), + content: jsonb("content").notNull(), + status: policyStatusEnum("status").notNull(), + /** + * Null only for the seeded bootstrap version. Every later version must + * reference the APPROVED session that legitimated it. + */ + approvedBySessionId: text("approved_by_session_id"), + activatedAt: timestamp("activated_at", { precision: 3, mode: "date" }).notNull().defaultNow(), + }, + (t) => [ + uniqueIndex("policies_organization_id_key_version_key").on(t.organizationId, t.key, t.version), + foreignKey({ + columns: [t.organizationId], + foreignColumns: [organizations.id], + name: "policies_organization_id_fkey", + }) + .onDelete("restrict") + .onUpdate("cascade"), + foreignKey({ + columns: [t.approvedBySessionId], + foreignColumns: [votingSessions.id], + name: "policies_approved_by_session_id_fkey", + }) + .onDelete("set null") + .onUpdate("cascade"), + ], +); + +/** + * Watch-only treasury source. Solon never holds funds — it points at + * independently verifiable on-chain addresses. + */ +export const treasurySources = pgTable( + "treasury_sources", + { + id: text("id").primaryKey().$defaultFn(uuid), + organizationId: text("organization_id").notNull(), + label: text("label").notNull(), + address: varchar("address", { length: 90 }).notNull(), + createdAt: timestamp("created_at", { precision: 3, mode: "date" }).notNull().defaultNow(), + }, + (t) => [ + uniqueIndex("treasury_sources_organization_id_address_key").on(t.organizationId, t.address), + foreignKey({ + columns: [t.organizationId], + foreignColumns: [organizations.id], + name: "treasury_sources_organization_id_fkey", + }) + .onDelete("restrict") + .onUpdate("cascade"), + ], +); + +/** Append-only public audit log. No update or delete path exists in code. */ +export const auditEvents = pgTable( + "audit_events", + { + id: text("id").primaryKey().$defaultFn(uuid), + organizationId: text("organization_id").notNull(), + eventType: auditEventTypeEnum("event_type").notNull(), + actorMemberId: text("actor_member_id"), + subjectType: text("subject_type").notNull(), + subjectId: text("subject_id").notNull(), + payload: jsonb("payload").notNull(), + createdAt: timestamp("created_at", { precision: 3, mode: "date" }).notNull().defaultNow(), + }, + (t) => [ + index("audit_events_organization_id_created_at_idx").on(t.organizationId, t.createdAt), + foreignKey({ + columns: [t.organizationId], + foreignColumns: [organizations.id], + name: "audit_events_organization_id_fkey", + }) + .onDelete("restrict") + .onUpdate("cascade"), + ], +); + +/** + * Transport auth for agent members calling the write API. The API key gets a + * request in the door; the Bitcoin signature is the authorization artifact. + */ +export const agentApiKeys = pgTable( + "agent_api_keys", + { + id: text("id").primaryKey().$defaultFn(uuid), + memberId: text("member_id").notNull(), + /** sha256 hex of the plaintext key (plaintext shown once at mint). */ + keyHash: text("key_hash").notNull(), + createdAt: timestamp("created_at", { precision: 3, mode: "date" }).notNull().defaultNow(), + revokedAt: timestamp("revoked_at", { precision: 3, mode: "date" }), + }, + (t) => [ + uniqueIndex("agent_api_keys_key_hash_key").on(t.keyHash), + foreignKey({ + columns: [t.memberId], + foreignColumns: [members.id], + name: "agent_api_keys_member_id_fkey", + }) + .onDelete("restrict") + .onUpdate("cascade"), + ], +); + +// --------------------------------------------------------------------------- +// Relations (for db.query relational reads) +// --------------------------------------------------------------------------- + +export const organizationsRelations = relations(organizations, ({ many }) => ({ + members: many(members), + proposals: many(proposals), + policies: many(policies), + treasurySources: many(treasurySources), + auditEvents: many(auditEvents), +})); + +export const membersRelations = relations(members, ({ one, many }) => ({ + organization: one(organizations, { + fields: [members.organizationId], + references: [organizations.id], + }), + proposals: many(proposals), + votes: many(votes), + apiKeys: many(agentApiKeys), +})); + +export const proposalsRelations = relations(proposals, ({ one }) => ({ + organization: one(organizations, { + fields: [proposals.organizationId], + references: [organizations.id], + }), + proposer: one(members, { + fields: [proposals.proposerMemberId], + references: [members.id], + }), + session: one(votingSessions, { + fields: [proposals.id], + references: [votingSessions.proposalId], + }), +})); + +export const votingSessionsRelations = relations(votingSessions, ({ one, many }) => ({ + proposal: one(proposals, { + fields: [votingSessions.proposalId], + references: [proposals.id], + }), + votes: many(votes), + policies: many(policies), +})); + +export const votesRelations = relations(votes, ({ one }) => ({ + session: one(votingSessions, { + fields: [votes.sessionId], + references: [votingSessions.id], + }), + member: one(members, { + fields: [votes.memberId], + references: [members.id], + }), +})); + +export const policiesRelations = relations(policies, ({ one }) => ({ + organization: one(organizations, { + fields: [policies.organizationId], + references: [organizations.id], + }), + approvedBySession: one(votingSessions, { + fields: [policies.approvedBySessionId], + references: [votingSessions.id], + }), +})); + +export const treasurySourcesRelations = relations(treasurySources, ({ one }) => ({ + organization: one(organizations, { + fields: [treasurySources.organizationId], + references: [organizations.id], + }), +})); + +export const auditEventsRelations = relations(auditEvents, ({ one }) => ({ + organization: one(organizations, { + fields: [auditEvents.organizationId], + references: [organizations.id], + }), +})); + +export const agentApiKeysRelations = relations(agentApiKeys, ({ one }) => ({ + member: one(members, { + fields: [agentApiKeys.memberId], + references: [members.id], + }), +})); + +// --------------------------------------------------------------------------- +// Row types (what the ORM client used to generate as model types) +// --------------------------------------------------------------------------- + +export type Organization = typeof organizations.$inferSelect; +export type Member = typeof members.$inferSelect; +export type Proposal = typeof proposals.$inferSelect; +export type VotingSession = typeof votingSessions.$inferSelect; +export type Vote = typeof votes.$inferSelect; +export type Policy = typeof policies.$inferSelect; +export type TreasurySource = typeof treasurySources.$inferSelect; +export type AuditEvent = typeof auditEvents.$inferSelect; +export type AgentApiKey = typeof agentApiKeys.$inferSelect; diff --git a/src/lib/domain/__tests__/membership.integration.test.ts b/src/lib/domain/__tests__/membership.integration.test.ts index 55189f2..278afde 100644 --- a/src/lib/domain/__tests__/membership.integration.test.ts +++ b/src/lib/domain/__tests__/membership.integration.test.ts @@ -10,8 +10,10 @@ */ import { describe, expect, it } from "vitest"; import { randomUUID } from "node:crypto"; -import { MemberStatus, MemberType } from "@prisma/client"; -import { prisma } from "@/lib/db"; +import { and, eq } from "drizzle-orm"; +import { MemberStatus, MemberType } from "@/lib/db/enums"; +import { db } from "@/lib/db/client"; +import { auditEvents, members, organizations } from "@/lib/db/schema"; import { generateKeyPair, registrationMessage, signMessage } from "@/lib/bitcoin/message"; import { genesisOpen, registerMember } from "@/lib/domain/membership"; @@ -19,7 +21,7 @@ const RUN = process.env.INTEGRATION === "1"; async function freshOrg() { const slug = `mem-${randomUUID().slice(0, 8)}`; - await prisma.organization.create({ data: { slug, name: "Membership Test Org" } }); + await db.insert(organizations).values({ slug, name: "Membership Test Org" }); return slug; } @@ -51,15 +53,16 @@ describe.runIf(RUN)("founding seat", () => { const seated = await registerMember(first.input); expect(seated).toMatchObject({ registered: true, verified: true, genesis: true }); - const member = await prisma.member.findUniqueOrThrow({ - where: { id: seated.memberId! }, + const member = await db.query.members.findFirst({ + where: eq(members.id, seated.memberId!), }); + if (!member) throw new Error("seated member missing"); expect(member.memberType).toBe(MemberType.HUMAN); expect(member.status).toBe(MemberStatus.ACTIVE); // The grant is on the record, flagged as what it is. - const event = await prisma.auditEvent.findFirst({ - where: { subjectId: member.id, subjectType: "member" }, + const event = await db.query.auditEvents.findFirst({ + where: and(eq(auditEvents.subjectId, member.id), eq(auditEvents.subjectType, "member")), }); expect((event?.payload as { genesis?: boolean })?.genesis).toBe(true); @@ -109,9 +112,7 @@ describe.runIf(RUN)("founding seat", () => { expect(again.registered).toBe(false); expect(again.reason).toMatch(/already linked|already registered|founding seat is taken/); - const count = await prisma.member.count({ - where: { bitcoinAddress: first.pair.address }, - }); + const count = await db.$count(members, eq(members.bitcoinAddress, first.pair.address)); expect(count).toBe(1); }); }); diff --git a/src/lib/domain/__tests__/methods.integration.test.ts b/src/lib/domain/__tests__/methods.integration.test.ts index c833f8f..2eca461 100644 --- a/src/lib/domain/__tests__/methods.integration.test.ts +++ b/src/lib/domain/__tests__/methods.integration.test.ts @@ -8,8 +8,10 @@ */ import { describe, expect, it } from "vitest"; import { randomUUID } from "node:crypto"; -import { DecisionCategory, SessionOutcome, VotingMethod } from "@prisma/client"; -import { prisma } from "@/lib/db"; +import { eq } from "drizzle-orm"; +import { DecisionCategory, SessionOutcome, VotingMethod } from "@/lib/db/enums"; +import { db } from "@/lib/db/client"; +import { members, organizations, proposals, votes } from "@/lib/db/schema"; import { generateKeyPair, signMessage, voteMessage } from "@/lib/bitcoin/message"; import { canonicalBallot } from "@/lib/domain/methods"; import { closeSession, openSession, submitVote } from "@/lib/domain/voting"; @@ -24,42 +26,45 @@ const OPTIONS = [ async function fixture() { const slug = `dot-${randomUUID().slice(0, 8)}`; - const org = await prisma.organization.create({ - data: { slug, name: "Dot Vote Org", governanceProfile: "COOPERATIVE" }, - }); + const [org] = await db + .insert(organizations) + .values({ slug, name: "Dot Vote Org", governanceProfile: "COOPERATIVE" }) + .returning(); const alice = generateKeyPair(); const bob = generateKeyPair(); - const members: Record = {}; + const roster: Record = {}; for (const [name, pair, weight] of [ ["Alice", alice, 1], ["Bob", bob, 2], ] as const) { - const m = await prisma.member.create({ - data: { + const [m] = await db + .insert(members) + .values({ organizationId: org.id, displayName: name, memberType: "HUMAN", keyCustody: "SELF", bitcoinAddress: pair.address, - votingWeight: weight, + votingWeight: String(weight), status: "ACTIVE", - }, - }); - members[name] = m.id; + }) + .returning(); + roster[name] = m.id; } - const proposal = await prisma.proposal.create({ - data: { + const [proposal] = await db + .insert(proposals) + .values({ organizationId: org.id, category: DecisionCategory.ALLOCATION_POLICY, title: "How should we split the retrofit budget?", body: "Three candidate works.", method: VotingMethod.DOT, options: OPTIONS, - proposerMemberId: members.Alice, + proposerMemberId: roster.Alice, proposerSignature: "sig", status: "DRAFT", - }, - }); + }) + .returning(); return { org, alice, bob, proposal }; } @@ -127,7 +132,7 @@ describe.runIf(RUN)("dot allocation (database integration)", () => { const result = await submitVote(session.id, tampered); expect(result.stored).toBe(false); expect(result.verified).toBe(false); - expect(await prisma.vote.count({ where: { sessionId: session.id } })).toBe(0); + expect(await db.$count(votes, eq(votes.sessionId, session.id))).toBe(0); }); it("refuses a ballot that overspends the budget, before any signature check", async () => { @@ -145,7 +150,7 @@ describe.runIf(RUN)("dot allocation (database integration)", () => { it("will not open a multi-option session without options to choose between", async () => { const { proposal } = await fixture(); - await prisma.proposal.update({ where: { id: proposal.id }, data: { options: [] } }); + await db.update(proposals).set({ options: [] }).where(eq(proposals.id, proposal.id)); await expect(openSession(proposal.id)).rejects.toThrow(/at least two options/); }); }); diff --git a/src/lib/domain/__tests__/tally.test.ts b/src/lib/domain/__tests__/tally.test.ts index 888a9ca..c6d71a3 100644 --- a/src/lib/domain/__tests__/tally.test.ts +++ b/src/lib/domain/__tests__/tally.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { SessionOutcome, VoteThreshold } from "@prisma/client"; +import { SessionOutcome, VoteThreshold } from "@/lib/db/enums"; import { decideOutcome, tallyOf } from "../tally"; import { aggregateBallots } from "../methods"; import type { Aggregate } from "../methods/types"; diff --git a/src/lib/domain/__tests__/vote-spine.integration.test.ts b/src/lib/domain/__tests__/vote-spine.integration.test.ts index 57fa3fe..e9af423 100644 --- a/src/lib/domain/__tests__/vote-spine.integration.test.ts +++ b/src/lib/domain/__tests__/vote-spine.integration.test.ts @@ -4,13 +4,15 @@ * electorate rules, early-close refusal, quorum). * * Runs only with INTEGRATION=1 and a DATABASE_URL whose schema is migrated - * (CI: postgres service container + `prisma migrate deploy` — which also + * (CI: postgres service container + `drizzle-kit migrate` — which also * proves the migrations replay on a fresh database). Plain `npm test` skips it. */ import { describe, expect, it } from "vitest"; import { randomUUID } from "node:crypto"; -import { AuditEventType, DecisionCategory, PolicyStatus, SessionOutcome } from "@prisma/client"; -import { prisma } from "@/lib/db"; +import { and, eq } from "drizzle-orm"; +import { AuditEventType, DecisionCategory, PolicyStatus, SessionOutcome } from "@/lib/db/enums"; +import { db } from "@/lib/db/client"; +import { agentApiKeys, auditEvents, members, organizations, policies } from "@/lib/db/schema"; import { generateKeyPair, proposalMessage, @@ -28,18 +30,17 @@ const RUN = process.env.INTEGRATION === "1"; describe.runIf(RUN)("vote spine (database integration)", () => { it("carries a policy proposal from signature to activated version", async () => { const slug = `it-${randomUUID().slice(0, 8)}`; - const org = await prisma.organization.create({ - data: { slug, name: "Integration Test Org" }, - }); + const [org] = await db + .insert(organizations) + .values({ slug, name: "Integration Test Org" }) + .returning(); // Bootstrap policy v1 — the version the vote will supersede. - await prisma.policy.create({ - data: { - organizationId: org.id, - key: "allocation_policy", - version: 1, - content: { max_cat_daily_spend_btc: 0.001 }, - status: PolicyStatus.ACTIVE, - }, + await db.insert(policies).values({ + organizationId: org.id, + key: "allocation_policy", + version: 1, + content: { max_cat_daily_spend_btc: 0.001 }, + status: PolicyStatus.ACTIVE, }); const alice = generateKeyPair(); @@ -50,16 +51,14 @@ describe.runIf(RUN)("vote spine (database integration)", () => { ["Bob", bob, "HUMAN", null], ["Cat", agent, "AGENT", "orangecat:cat"], ] as const) { - await prisma.member.create({ - data: { - organizationId: org.id, - displayName: name, - memberType: type, - keyCustody: type === "HUMAN" ? "SELF" : "SERVICE", - bitcoinAddress: pair.address, - publicKeyHex: pair.publicKeyHex, - system, - }, + await db.insert(members).values({ + organizationId: org.id, + displayName: name, + memberType: type, + keyCustody: type === "HUMAN" ? "SELF" : "SERVICE", + bitcoinAddress: pair.address, + publicKeyHex: pair.publicKeyHex, + system, }); } @@ -91,13 +90,12 @@ describe.runIf(RUN)("vote spine (database integration)", () => { expect(noKey).toMatchObject({ created: false, verified: true }); expect(noKey.reason).toMatch(/API key/); - const agentMember = await prisma.member.findFirstOrThrow({ - where: { organizationId: org.id, bitcoinAddress: agent.address }, + const agentMember = await db.query.members.findFirst({ + where: and(eq(members.organizationId, org.id), eq(members.bitcoinAddress, agent.address)), }); + if (!agentMember) throw new Error("agent member missing"); const apiKey = `sk_solon_test_${randomUUID()}`; - await prisma.agentApiKey.create({ - data: { memberId: agentMember.id, keyHash: sha256Hex(apiKey) }, - }); + await db.insert(agentApiKeys).values({ memberId: agentMember.id, keyHash: sha256Hex(apiKey) }); const filed = await createProposal({ ...base, apiKey }); expect(filed.created).toBe(true); @@ -135,27 +133,25 @@ describe.runIf(RUN)("vote spine (database integration)", () => { expect(closed.outcome).toBe(SessionOutcome.APPROVED); // --- Policy v2 exists, references the session, v1 superseded --- - const v2 = await prisma.policy.findUniqueOrThrow({ - where: { - organizationId_key_version: { - organizationId: org.id, - key: "allocation_policy", - version: 2, - }, - }, + const v2 = await db.query.policies.findFirst({ + where: and( + eq(policies.organizationId, org.id), + eq(policies.key, "allocation_policy"), + eq(policies.version, 2), + ), }); + if (!v2) throw new Error("policy v2 missing"); expect(v2.status).toBe(PolicyStatus.ACTIVE); expect(v2.approvedBySessionId).toBe(session.id); expect(v2.content).toEqual(proposedContent); - const v1 = await prisma.policy.findUniqueOrThrow({ - where: { - organizationId_key_version: { - organizationId: org.id, - key: "allocation_policy", - version: 1, - }, - }, + const v1 = await db.query.policies.findFirst({ + where: and( + eq(policies.organizationId, org.id), + eq(policies.key, "allocation_policy"), + eq(policies.version, 1), + ), }); + if (!v1) throw new Error("policy v1 missing"); expect(v1.status).toBe(PolicyStatus.SUPERSEDED); // --- The decision document self-verifies, the way a consumer would --- @@ -190,9 +186,9 @@ describe.runIf(RUN)("vote spine (database integration)", () => { expect(recomputed).toEqual(d.tally); // --- The audit trail recorded every step --- - const events = await prisma.auditEvent.findMany({ - where: { organizationId: org.id }, - select: { eventType: true }, + const events = await db.query.auditEvents.findMany({ + where: eq(auditEvents.organizationId, org.id), + columns: { eventType: true }, }); const types = events.map((e) => e.eventType); for (const expected of [ @@ -208,23 +204,24 @@ describe.runIf(RUN)("vote spine (database integration)", () => { it("keeps agents out of HUMANS_ONLY sessions", async () => { const slug = `it-${randomUUID().slice(0, 8)}`; - const org = await prisma.organization.create({ data: { slug, name: "Humans Only Org" } }); + const [org] = await db + .insert(organizations) + .values({ slug, name: "Humans Only Org" }) + .returning(); const human = generateKeyPair(); const agent = generateKeyPair(); for (const [name, pair, type] of [ ["Human", human, "HUMAN"], ["Agent", agent, "AGENT"], ] as const) { - await prisma.member.create({ - data: { - organizationId: org.id, - displayName: name, - memberType: type, - keyCustody: type === "HUMAN" ? "SELF" : "SERVICE", - bitcoinAddress: pair.address, - publicKeyHex: pair.publicKeyHex, - system: type === "AGENT" ? "test:agent" : null, - }, + await db.insert(members).values({ + organizationId: org.id, + displayName: name, + memberType: type, + keyCustody: type === "HUMAN" ? "SELF" : "SERVICE", + bitcoinAddress: pair.address, + publicKeyHex: pair.publicKeyHex, + system: type === "AGENT" ? "test:agent" : null, }); } diff --git a/src/lib/domain/decision.ts b/src/lib/domain/decision.ts index 2a440a7..340f8eb 100644 --- a/src/lib/domain/decision.ts +++ b/src/lib/domain/decision.ts @@ -1,8 +1,9 @@ -import { SessionStatus } from "@prisma/client"; -import { prisma } from "@/lib/db"; +import { asc, eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { SessionStatus, votes, votingSessions } from "@/lib/db/schema"; import { proposalMessage } from "@/lib/bitcoin/message"; import { aggregateBallots } from "@/lib/domain/methods"; -import { methodId } from "@/lib/domain/methods/prisma-enum"; +import { methodId } from "@/lib/domain/methods/db-enum"; import { readOptions } from "@/lib/domain/voting"; import { tallyOf } from "@/lib/domain/tally"; @@ -15,14 +16,14 @@ import { tallyOf } from "@/lib/domain/tally"; * trust this server's arithmetic. */ export async function decisionDocument(sessionId: string) { - const session = await prisma.votingSession.findUnique({ - where: { id: sessionId }, - include: { + const session = await db.query.votingSessions.findFirst({ + where: eq(votingSessions.id, sessionId), + with: { proposal: { - include: { - organization: { select: { id: true, slug: true, name: true } }, + with: { + organization: { columns: { id: true, slug: true, name: true } }, proposer: { - select: { + columns: { id: true, displayName: true, memberType: true, @@ -33,9 +34,9 @@ export async function decisionDocument(sessionId: string) { }, }, votes: { - include: { + with: { member: { - select: { + columns: { id: true, displayName: true, memberType: true, @@ -44,7 +45,7 @@ export async function decisionDocument(sessionId: string) { }, }, }, - orderBy: { createdAt: "asc" }, + orderBy: asc(votes.createdAt), }, }, }); diff --git a/src/lib/domain/membership.ts b/src/lib/domain/membership.ts index c2c4bcc..6b5d60b 100644 --- a/src/lib/domain/membership.ts +++ b/src/lib/domain/membership.ts @@ -1,5 +1,14 @@ -import { AuditEventType, KeyCustody, MemberStatus, MemberType } from "@prisma/client"; -import { prisma } from "@/lib/db"; +import { and, count, eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { + AuditEventType, + KeyCustody, + MemberStatus, + MemberType, + auditEvents, + members, + organizations, +} from "@/lib/db/schema"; import { registrationMessage, verifyMessage } from "@/lib/bitcoin/message"; export interface RegisterMemberInput { @@ -20,6 +29,13 @@ export interface RegisterMemberResult { memberId?: string; } +const activeHumansOf = (organizationId: string) => + and( + eq(members.organizationId, organizationId), + eq(members.memberType, MemberType.HUMAN), + eq(members.status, MemberStatus.ACTIVE), + ); + /** * Why this exists at all: `MEMBERSHIP` is a HUMANS_ONLY category, and * `openSession` refuses an empty electorate. With zero human members, a @@ -37,7 +53,9 @@ export interface RegisterMemberResult { * is empty. */ export async function registerMember(input: RegisterMemberInput): Promise { - const org = await prisma.organization.findUnique({ where: { slug: input.orgSlug } }); + const org = await db.query.organizations.findFirst({ + where: eq(organizations.slug, input.orgSlug), + }); if (!org) return { registered: false, verified: false, reason: "organization not found" }; const message = registrationMessage({ @@ -57,8 +75,8 @@ export async function registerMember(input: RegisterMemberInput): Promise 0) { return { registered: false, @@ -101,18 +116,16 @@ export async function registerMember(input: RegisterMemberInput): Promise { - const stillEmpty = await tx.member.count({ - where: { - organizationId: org.id, - memberType: MemberType.HUMAN, - status: MemberStatus.ACTIVE, - }, - }); + const member = await db.transaction(async (tx) => { + const [{ stillEmpty }] = await tx + .select({ stillEmpty: count() }) + .from(members) + .where(activeHumansOf(org.id)); if (stillEmpty > 0) throw new Error("GENESIS_TAKEN"); - const created = await tx.member.create({ - data: { + const [created] = await tx + .insert(members) + .values({ organizationId: org.id, displayName: input.displayName, memberType: MemberType.HUMAN, @@ -120,22 +133,20 @@ export async function registerMember(input: RegisterMemberInput): Promise { - const org = await prisma.organization.findUnique({ where: { slug: orgSlug } }); - if (!org) return false; - const humanCount = await prisma.member.count({ - where: { - organizationId: org.id, - memberType: MemberType.HUMAN, - status: MemberStatus.ACTIVE, - }, + const org = await db.query.organizations.findFirst({ + where: eq(organizations.slug, orgSlug), }); + if (!org) return false; + const [{ humanCount }] = await db + .select({ humanCount: count() }) + .from(members) + .where(activeHumansOf(org.id)); return humanCount === 0; } diff --git a/src/lib/domain/methods/prisma-enum.ts b/src/lib/domain/methods/db-enum.ts similarity index 76% rename from src/lib/domain/methods/prisma-enum.ts rename to src/lib/domain/methods/db-enum.ts index db0cbdf..f7709e5 100644 --- a/src/lib/domain/methods/prisma-enum.ts +++ b/src/lib/domain/methods/db-enum.ts @@ -1,13 +1,13 @@ -import { VotingMethod } from "@prisma/client"; +import { VotingMethod } from "@/lib/db/enums"; import type { MethodId } from "./types"; /** * The storage form ⇄ the domain form. * - * Kept apart from the method registry so that registry stays free of Prisma: - * the voter's browser has to compute a ballot's canonical encoding to show - * them what they are signing, and it should not have to load a database client - * to do it. + * Kept apart from the method registry so that registry stays free of storage + * concerns: the voter's browser has to compute a ballot's canonical encoding + * to show them what they are signing, and it should not have to know how the + * database spells a method to do it. */ const TO_ID: Record = { [VotingMethod.SINGLE_CHOICE]: "single_choice", diff --git a/src/lib/domain/org.ts b/src/lib/domain/org.ts index 2503274..1676c14 100644 --- a/src/lib/domain/org.ts +++ b/src/lib/domain/org.ts @@ -1,4 +1,6 @@ -import { prisma } from "@/lib/db"; +import { asc, eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { organizations } from "@/lib/db/schema"; /** * Solon is built for many organizations but ships governing one. Until a @@ -7,5 +9,10 @@ import { prisma } from "@/lib/db"; * different definitions of which org the page is about. */ export function primaryOrg() { - return prisma.organization.findFirst({ orderBy: { createdAt: "asc" } }); + return db.query.organizations.findFirst({ orderBy: asc(organizations.createdAt) }); +} + +/** The one lookup every public org endpoint starts with. */ +export function orgBySlug(slug: string) { + return db.query.organizations.findFirst({ where: eq(organizations.slug, slug) }); } diff --git a/src/lib/domain/proposals.ts b/src/lib/domain/proposals.ts index 2f0ff06..c2cebc6 100644 --- a/src/lib/domain/proposals.ts +++ b/src/lib/domain/proposals.ts @@ -1,16 +1,21 @@ +import { and, eq, isNull } from "drizzle-orm"; +import { db } from "@/lib/db/client"; import { AuditEventType, - DecisionCategory, MemberStatus, MemberType, - Prisma, - VotingMethod, -} from "@prisma/client"; -import { prisma } from "@/lib/db"; + agentApiKeys, + auditEvents, + members, + organizations, + proposals, + type DecisionCategory, + type VotingMethod, +} from "@/lib/db/schema"; import { proposalMessage, verifyMessage } from "@/lib/bitcoin/message"; import { canonicalJson, contentHashOf, sha256Hex } from "@/lib/domain/canonical"; import { optionsSchema, type BallotOption } from "@/lib/domain/methods/types"; -import { methodId } from "@/lib/domain/methods/prisma-enum"; +import { methodId } from "@/lib/domain/methods/db-enum"; import { methodSpec } from "@/lib/domain/methods"; export interface CreateProposalInput { @@ -60,7 +65,9 @@ export async function createProposal(input: CreateProposalInput): Promise { - const p = await tx.proposal.create({ - data: { + const proposal = await db.transaction(async (tx) => { + const [p] = await tx + .insert(proposals) + .values({ organizationId: org.id, category: input.category, title: input.title, body: input.body, policyKey: hasPolicyKey ? input.policyKey : null, - proposedContent: hasContent - ? (input.proposedContent as Prisma.InputJsonValue) - : Prisma.DbNull, + proposedContent: hasContent ? input.proposedContent : null, target: input.target ?? null, contentHash, method: input.method ?? null, - options: options ? (options as unknown as Prisma.InputJsonValue) : Prisma.DbNull, + options: options ?? null, proposerMemberId: member.id, proposerSignature: input.signature, - }, - }); - await tx.auditEvent.create({ - data: { - organizationId: org.id, - eventType: AuditEventType.PROPOSAL_CREATED, - actorMemberId: member.id, - subjectType: "proposal", - subjectId: p.id, - payload: { - category: input.category, - title: input.title, - memberType: member.memberType, - ...(contentHash ? { policyKey: input.policyKey, contentHash } : {}), - }, + }) + .returning(); + await tx.insert(auditEvents).values({ + organizationId: org.id, + eventType: AuditEventType.PROPOSAL_CREATED, + actorMemberId: member.id, + subjectType: "proposal", + subjectId: p.id, + payload: { + category: input.category, + title: input.title, + memberType: member.memberType, + ...(contentHash ? { policyKey: input.policyKey, contentHash } : {}), }, }); return p; diff --git a/src/lib/domain/tally.ts b/src/lib/domain/tally.ts index a2630a0..88a1eb9 100644 --- a/src/lib/domain/tally.ts +++ b/src/lib/domain/tally.ts @@ -1,4 +1,4 @@ -import { VoteThreshold, SessionOutcome } from "@prisma/client"; +import { VoteThreshold, SessionOutcome } from "@/lib/db/enums"; import { SUPERMAJORITY_FRACTION } from "@/lib/config/governance"; import type { Aggregate } from "@/lib/domain/methods/types"; diff --git a/src/lib/domain/treasury.ts b/src/lib/domain/treasury.ts index e383c96..8cb1fec 100644 --- a/src/lib/domain/treasury.ts +++ b/src/lib/domain/treasury.ts @@ -1,4 +1,6 @@ -import { prisma } from "@/lib/db"; +import { asc, eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; +import { treasurySources } from "@/lib/db/schema"; import { getAddressBalance } from "@/lib/bitcoin/mempool"; export interface TreasurySourceBalance { @@ -41,9 +43,9 @@ export interface TreasuryReport { * as "unavailable". */ export async function treasuryReport(organizationId: string): Promise { - const rows = await prisma.treasurySource.findMany({ - where: { organizationId }, - orderBy: { createdAt: "asc" }, + const rows = await db.query.treasurySources.findMany({ + where: eq(treasurySources.organizationId, organizationId), + orderBy: asc(treasurySources.createdAt), }); const sources = await Promise.all( diff --git a/src/lib/domain/voting.ts b/src/lib/domain/voting.ts index 563276f..6008317 100644 --- a/src/lib/domain/voting.ts +++ b/src/lib/domain/voting.ts @@ -1,16 +1,22 @@ +import { and, count, desc, eq } from "drizzle-orm"; +import { db } from "@/lib/db/client"; import { AuditEventType, Electorate, MemberStatus, MemberType, PolicyStatus, - Prisma, ProposalStatus, SessionOutcome, SessionStatus, VotingMethod, -} from "@prisma/client"; -import { prisma } from "@/lib/db"; + auditEvents, + members, + policies, + proposals, + votes, + votingSessions, +} from "@/lib/db/schema"; import { verifyMessage, voteMessage } from "@/lib/bitcoin/message"; import { VOTING_WINDOW_DAYS } from "@/lib/config/governance"; import { electorateFor, ruleFor } from "@/lib/config/governance-profiles"; @@ -21,7 +27,7 @@ import { parseBallot, DEFAULT_DOT_BUDGET, } from "@/lib/domain/methods"; -import { methodEnum, methodId } from "@/lib/domain/methods/prisma-enum"; +import { methodEnum, methodId } from "@/lib/domain/methods/db-enum"; import { optionsSchema, type Aggregate, type BallotOption } from "@/lib/domain/methods/types"; import { closeRefusal, decideOutcome, tallyOf, type Tally } from "@/lib/domain/tally"; @@ -43,7 +49,7 @@ export interface SubmitVoteResult { } /** Options as stored on a proposal or session — validated on the way in and out. */ -export function readOptions(raw: Prisma.JsonValue | null | undefined): BallotOption[] { +export function readOptions(raw: unknown): BallotOption[] { if (raw == null) return []; const parsed = optionsSchema.safeParse(raw); return parsed.success ? parsed.data : []; @@ -58,9 +64,9 @@ export function readOptions(raw: Prisma.JsonValue | null | undefined): BallotOpt * taken under the old one. */ export async function openSession(proposalId: string) { - const proposal = await prisma.proposal.findUnique({ - where: { id: proposalId }, - include: { organization: true }, + const proposal = await db.query.proposals.findFirst({ + where: eq(proposals.id, proposalId), + with: { organization: true }, }); if (!proposal) throw new Error("proposal not found"); if (proposal.status !== ProposalStatus.DRAFT) { @@ -81,14 +87,18 @@ export async function openSession(proposalId: string) { } const electorate = electorateFor(proposal.category); - const eligible = await prisma.member.findMany({ - where: { - organizationId: proposal.organizationId, - status: MemberStatus.ACTIVE, - ...(electorate === Electorate.HUMANS_ONLY ? { memberType: MemberType.HUMAN } : {}), - }, - select: { votingWeight: true }, - }); + const eligible = await db + .select({ votingWeight: members.votingWeight }) + .from(members) + .where( + and( + eq(members.organizationId, proposal.organizationId), + eq(members.status, MemberStatus.ACTIVE), + ...(electorate === Electorate.HUMANS_ONLY + ? [eq(members.memberType, MemberType.HUMAN)] + : []), + ), + ); if (eligible.length === 0) { throw new Error( "no eligible members — a session with an empty electorate cannot decide anything", @@ -100,45 +110,44 @@ export async function openSession(proposalId: string) { closesAt.setDate(closesAt.getDate() + VOTING_WINDOW_DAYS); const dotBudget = method === VotingMethod.DOT ? DEFAULT_DOT_BUDGET : null; - const session = await prisma.$transaction(async (tx) => { - const s = await tx.votingSession.create({ - data: { + const session = await db.transaction(async (tx) => { + const [s] = await tx + .insert(votingSessions) + .values({ proposalId, status: SessionStatus.ACTIVE, closesAt, electorate, method, - options: spec.needsOptions ? (options as unknown as Prisma.InputJsonValue) : Prisma.DbNull, + options: spec.needsOptions ? options : null, dotBudget, threshold: rule.threshold, quorumPercent: rule.quorumPercent, eligibleCount: eligible.length, - eligibleWeight: new Prisma.Decimal(eligibleWeight.toFixed(2)), - }, - }); - await tx.proposal.update({ - where: { id: proposalId }, - data: { status: ProposalStatus.OPEN }, - }); - await tx.auditEvent.create({ - data: { - organizationId: proposal.organizationId, - eventType: AuditEventType.SESSION_OPENED, - subjectType: "voting_session", - subjectId: s.id, - payload: { - proposalId, - profile: proposal.organization.governanceProfile, - method, - options: options.map((o) => o.key), - dotBudget, - electorate, - threshold: rule.threshold, - quorumPercent: rule.quorumPercent, - eligibleCount: eligible.length, - eligibleWeight, - closesAt: closesAt.toISOString(), - }, + eligibleWeight: eligibleWeight.toFixed(2), + }) + .returning(); + await tx + .update(proposals) + .set({ status: ProposalStatus.OPEN }) + .where(eq(proposals.id, proposalId)); + await tx.insert(auditEvents).values({ + organizationId: proposal.organizationId, + eventType: AuditEventType.SESSION_OPENED, + subjectType: "voting_session", + subjectId: s.id, + payload: { + proposalId, + profile: proposal.organization.governanceProfile, + method, + options: options.map((o) => o.key), + dotBudget, + electorate, + threshold: rule.threshold, + quorumPercent: rule.quorumPercent, + eligibleCount: eligible.length, + eligibleWeight, + closesAt: closesAt.toISOString(), }, }); return s; @@ -160,9 +169,9 @@ export async function submitVote( sessionId: string, input: SubmitVoteInput, ): Promise { - const session = await prisma.votingSession.findUnique({ - where: { id: sessionId }, - include: { proposal: true }, + const session = await db.query.votingSessions.findFirst({ + where: eq(votingSessions.id, sessionId), + with: { proposal: true }, }); if (!session) return { stored: false, verified: false, reason: "voting session not found" }; if (session.status !== SessionStatus.ACTIVE) { @@ -191,12 +200,12 @@ export async function submitVote( }; } - const member = await prisma.member.findFirst({ - where: { - organizationId: session.proposal.organizationId, - bitcoinAddress: input.address, - status: MemberStatus.ACTIVE, - }, + const member = await db.query.members.findFirst({ + where: and( + eq(members.organizationId, session.proposal.organizationId), + eq(members.bitcoinAddress, input.address), + eq(members.status, MemberStatus.ACTIVE), + ), }); if (!member) { return { @@ -213,38 +222,39 @@ export async function submitVote( }; } - const ballotJson = parsed.ballot as Prisma.InputJsonValue; - const vote = await prisma.$transaction(async (tx) => { - const v = await tx.vote.upsert({ - where: { sessionId_memberId: { sessionId, memberId: member.id } }, - create: { + const ballotJson = parsed.ballot; + const vote = await db.transaction(async (tx) => { + const [v] = await tx + .insert(votes) + .values({ sessionId, memberId: member.id, ballot: ballotJson, weight: member.votingWeight, signedMessage: message, signature: input.signature, - }, - update: { - ballot: ballotJson, - signedMessage: message, - signature: input.signature, - createdAt: new Date(), - }, - }); - await tx.auditEvent.create({ - data: { - organizationId: session.proposal.organizationId, - eventType: AuditEventType.VOTE_CAST, - actorMemberId: member.id, - subjectType: "vote", - subjectId: v.id, - payload: { - sessionId, - method: session.method, - memberType: member.memberType, - weight: Number(member.votingWeight), + }) + .onConflictDoUpdate({ + target: [votes.sessionId, votes.memberId], + set: { + ballot: ballotJson, + signedMessage: message, + signature: input.signature, + createdAt: new Date(), }, + }) + .returning(); + await tx.insert(auditEvents).values({ + organizationId: session.proposal.organizationId, + eventType: AuditEventType.VOTE_CAST, + actorMemberId: member.id, + subjectType: "vote", + subjectId: v.id, + payload: { + sessionId, + method: session.method, + memberType: member.memberType, + weight: Number(member.votingWeight), }, }); return v; @@ -256,17 +266,18 @@ export async function submitVote( /** Weighted aggregate over stored (already-verified) ballots in a session. */ export async function sessionAggregate(sessionId: string): Promise { - const session = await prisma.votingSession.findUniqueOrThrow({ - where: { id: sessionId }, - select: { method: true, options: true, dotBudget: true }, - }); - const votes = await prisma.vote.findMany({ - where: { sessionId }, - select: { ballot: true, weight: true }, + const session = await db.query.votingSessions.findFirst({ + where: eq(votingSessions.id, sessionId), + columns: { method: true, options: true, dotBudget: true }, }); + if (!session) throw new Error("voting session not found"); + const stored = await db + .select({ ballot: votes.ballot, weight: votes.weight }) + .from(votes) + .where(eq(votes.sessionId, sessionId)); return aggregateBallots( methodId(session.method), - votes.map((v) => ({ ballot: v.ballot, weight: Number(v.weight) })), + stored.map((v) => ({ ballot: v.ballot, weight: Number(v.weight) })), readOptions(session.options), { dotBudget: session.dotBudget }, ); @@ -283,14 +294,17 @@ export async function sessionTally(sessionId: string): Promise { * create an active policy version with a session reference. */ export async function closeSession(sessionId: string) { - const session = await prisma.votingSession.findUnique({ - where: { id: sessionId }, - include: { proposal: true }, + const session = await db.query.votingSessions.findFirst({ + where: eq(votingSessions.id, sessionId), + with: { proposal: true }, }); if (!session) throw new Error("voting session not found"); if (session.status !== SessionStatus.ACTIVE) throw new Error(`session already ${session.status}`); - const votesCast = await prisma.vote.count({ where: { sessionId } }); + const [{ votesCast }] = await db + .select({ votesCast: count() }) + .from(votes) + .where(eq(votes.sessionId, sessionId)); const refusal = closeRefusal({ now: new Date(), closesAt: session.closesAt, @@ -307,32 +321,31 @@ export async function closeSession(sessionId: string) { eligibleWeight: Number(session.eligibleWeight), }); - return prisma.$transaction(async (tx) => { - const closed = await tx.votingSession.update({ - where: { id: sessionId }, - data: { + return db.transaction(async (tx) => { + const [closed] = await tx + .update(votingSessions) + .set({ status: SessionStatus.CLOSED, outcome: decision.outcome, winningOptionKey: decision.winningOptionKey, closedAt: new Date(), - }, - }); - await tx.proposal.update({ - where: { id: session.proposalId }, - data: { status: ProposalStatus.CLOSED }, - }); - await tx.auditEvent.create({ - data: { - organizationId: session.proposal.organizationId, - eventType: AuditEventType.SESSION_CLOSED, - subjectType: "voting_session", - subjectId: sessionId, - payload: { - outcome: decision.outcome, - winningOptionKey: decision.winningOptionKey, - method: session.method, - aggregate: aggregate as unknown as Prisma.InputJsonValue, - }, + }) + .where(eq(votingSessions.id, sessionId)) + .returning(); + await tx + .update(proposals) + .set({ status: ProposalStatus.CLOSED }) + .where(eq(proposals.id, session.proposalId)); + await tx.insert(auditEvents).values({ + organizationId: session.proposal.organizationId, + eventType: AuditEventType.SESSION_CLOSED, + subjectType: "voting_session", + subjectId: sessionId, + payload: { + outcome: decision.outcome, + winningOptionKey: decision.winningOptionKey, + method: session.method, + aggregate, }, }); @@ -343,35 +356,38 @@ export async function closeSession(sessionId: string) { session.proposal.proposedContent !== null ) { const organizationId = session.proposal.organizationId; - const current = await tx.policy.findFirst({ - where: { organizationId, key: policyKey, status: PolicyStatus.ACTIVE }, - orderBy: { version: "desc" }, + const current = await tx.query.policies.findFirst({ + where: and( + eq(policies.organizationId, organizationId), + eq(policies.key, policyKey), + eq(policies.status, PolicyStatus.ACTIVE), + ), + orderBy: desc(policies.version), }); const nextVersion = (current?.version ?? 0) + 1; if (current) { - await tx.policy.update({ - where: { id: current.id }, - data: { status: PolicyStatus.SUPERSEDED }, - }); + await tx + .update(policies) + .set({ status: PolicyStatus.SUPERSEDED }) + .where(eq(policies.id, current.id)); } - const activated = await tx.policy.create({ - data: { + const [activated] = await tx + .insert(policies) + .values({ organizationId, key: policyKey, version: nextVersion, - content: session.proposal.proposedContent as Prisma.InputJsonValue, + content: session.proposal.proposedContent, status: PolicyStatus.ACTIVE, approvedBySessionId: sessionId, - }, - }); - await tx.auditEvent.create({ - data: { - organizationId, - eventType: AuditEventType.POLICY_ACTIVATED, - subjectType: "policy", - subjectId: activated.id, - payload: { key: policyKey, version: nextVersion, approvedBySessionId: sessionId }, - }, + }) + .returning(); + await tx.insert(auditEvents).values({ + organizationId, + eventType: AuditEventType.POLICY_ACTIVATED, + subjectType: "policy", + subjectId: activated.id, + payload: { key: policyKey, version: nextVersion, approvedBySessionId: sessionId }, }); }