From 3520718f65e5c85799bcc14442d797ee88972f2c Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Thu, 4 Jun 2026 19:45:33 +0300 Subject: [PATCH 1/4] =?UTF-8?q?feat(db):=20scaffold=20@relavium/db=20?= =?UTF-8?q?=E2=80=94=20Drizzle=20schema,=20migrations,=20SQLite=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stand up @relavium/db (Phase 0 · 0.I): the Drizzle SQLite schema for the nine Phase-1 local tables (llm_providers, model_catalog, agents, workflows, runs, step_executions, messages, run_events, run_costs), faithful to the canonical DDL — TEXT UUIDs, JSON-as-TEXT, epoch-ms INTEGER timestamps, integer micro-cents, CHECK-string enums, integer-bool (0/1) defaults, soft-delete partial indexes, and ON DELETE CASCADE on the run children. The runs.status / runs.execution_mode CHECK value sets are imported from @relavium/shared so the persisted enums cannot drift from the contract. Adds the drizzle-kit migration set, a better-sqlite3 client factory (WAL + foreign_keys = ON) and migration runner, and a Vitest smoke test (6 tests) that applies every migration to a fresh DB, round-trips a runs + run_events row, proves the run -> step_executions -> messages and run -> run_events cascades, and asserts the status/execution_mode CHECKs fire by constraint name (FK satisfied first, so only the CHECK can reject). The Node-side SQLite driver (ADR-0005 left it open) is pinned to better-sqlite3 in ADR-0021. @types/node is pinned to the engines floor (20.11) so a Node-22-only API is a type error, not a runtime break. Also broadens @relavium/shared's exports with a `default` condition so CJS-condition tooling (drizzle-kit) resolves it. Refs: ADR-0005, ADR-0021 Co-Authored-By: Claude Opus 4.8 (1M context) --- .prettierignore | 4 + ...005-sqlite-drizzle-local-postgres-cloud.md | 8 +- .../0021-node-sqlite-driver-better-sqlite3.md | 79 + docs/decisions/README.md | 1 + docs/tech-stack.md | 2 +- packages/db/README.md | 51 +- packages/db/drizzle.config.ts | 18 + .../db/drizzle/0000_cuddly_war_machine.sql | 190 +++ packages/db/drizzle/meta/0000_snapshot.json | 1432 ++++++++++++++++ packages/db/drizzle/meta/_journal.json | 13 + packages/db/package.json | 40 + packages/db/src/client.test.ts | 224 +++ packages/db/src/client.ts | 56 + packages/db/src/index.ts | 44 + packages/db/src/schema.ts | 373 +++++ packages/db/tsconfig.build.json | 9 + packages/db/tsconfig.json | 13 + packages/shared/package.json | 3 +- pnpm-lock.yaml | 1452 ++++++++++++++++- pnpm-workspace.yaml | 11 + 20 files changed, 3966 insertions(+), 57 deletions(-) create mode 100644 docs/decisions/0021-node-sqlite-driver-better-sqlite3.md create mode 100644 packages/db/drizzle.config.ts create mode 100644 packages/db/drizzle/0000_cuddly_war_machine.sql create mode 100644 packages/db/drizzle/meta/0000_snapshot.json create mode 100644 packages/db/drizzle/meta/_journal.json create mode 100644 packages/db/package.json create mode 100644 packages/db/src/client.test.ts create mode 100644 packages/db/src/client.ts create mode 100644 packages/db/src/index.ts create mode 100644 packages/db/src/schema.ts create mode 100644 packages/db/tsconfig.build.json create mode 100644 packages/db/tsconfig.json diff --git a/.prettierignore b/.prettierignore index 25758522..17f062f7 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,10 @@ **/coverage/** **/node_modules/** +# Generated drizzle-kit migration artifacts (SQL + snapshot/journal JSON) — owned by +# drizzle-kit's serializer, regenerated by `pnpm --filter @relavium/db db:generate`. +**/drizzle/** + # Lockfile pnpm-lock.yaml diff --git a/docs/decisions/0005-sqlite-drizzle-local-postgres-cloud.md b/docs/decisions/0005-sqlite-drizzle-local-postgres-cloud.md index 3055abc1..d7b28a3b 100644 --- a/docs/decisions/0005-sqlite-drizzle-local-postgres-cloud.md +++ b/docs/decisions/0005-sqlite-drizzle-local-postgres-cloud.md @@ -2,7 +2,13 @@ - **Status**: Accepted - **Date**: 2026-06-03 -- **Related**: [0001-tauri-v2-over-electron.md](0001-tauri-v2-over-electron.md), [0006-os-keychain-for-api-keys.md](0006-os-keychain-for-api-keys.md), [0008-local-first-phase-1-cloud-phase-2.md](0008-local-first-phase-1-cloud-phase-2.md), [tech-stack.md](../tech-stack.md) +- **Related**: [0001-tauri-v2-over-electron.md](0001-tauri-v2-over-electron.md), [0006-os-keychain-for-api-keys.md](0006-os-keychain-for-api-keys.md), [0008-local-first-phase-1-cloud-phase-2.md](0008-local-first-phase-1-cloud-phase-2.md), [0021-node-sqlite-driver-better-sqlite3.md](0021-node-sqlite-driver-better-sqlite3.md), [tech-stack.md](../tech-stack.md) + +> Amended 2026-06-04: this ADR fixed "SQLite + Drizzle" and the desktop's encrypted +> `tauri-plugin-sql` path but left the **Node-side** SQLite driver open. That gap is now +> closed by [ADR-0021](0021-node-sqlite-driver-better-sqlite3.md): `@relavium/db` uses +> `better-sqlite3` for its Node consumers (the migration runner, tests, and the Phase-2 +> CLI). The decision here is unchanged. ## Context diff --git a/docs/decisions/0021-node-sqlite-driver-better-sqlite3.md b/docs/decisions/0021-node-sqlite-driver-better-sqlite3.md new file mode 100644 index 00000000..39bef283 --- /dev/null +++ b/docs/decisions/0021-node-sqlite-driver-better-sqlite3.md @@ -0,0 +1,79 @@ +# ADR-0021: better-sqlite3 as the Node-side SQLite driver for `@relavium/db` + +- **Status**: Accepted +- **Date**: 2026-06-04 +- **Related**: [ADR-0005](0005-sqlite-drizzle-local-postgres-cloud.md), [ADR-0001](0001-tauri-v2-over-electron.md), [ADR-0018](0018-desktop-execution-and-rust-egress.md), [tech-stack.md](../tech-stack.md) + +## Context + +[ADR-0005](0005-sqlite-drizzle-local-postgres-cloud.md) settled the store — +**SQLite + Drizzle locally, PostgreSQL cloud, one Drizzle schema in `packages/db`** — +but it named only the *desktop's* SQLite access path (`tauri-plugin-sql`, Rust-side, +SQLCipher-encrypted; see [ADR-0001](0001-tauri-v2-over-electron.md) and +[ADR-0018](0018-desktop-execution-and-rust-egress.md)). It left **open** which driver +the *Node-side* consumers use. That gap has to be closed in Phase 0 (workstream 0.I), +because the `@relavium/db` scaffold needs a concrete driver for two things that run in +Node, not in the Tauri WebView: + +- the **migration runner / client factory** the package exports, and +- the **Vitest smoke test** that opens a fresh database, applies every `drizzle-kit` + migration, and round-trips a row. + +Phase-2's CLI run-history reader is also a Node consumer. The desktop reaches SQLite +through the Rust plugin and does **not** load this driver, so the choice is scoped to +Node contexts (tests now, CLI later), not the desktop binary. + +Constraints that frame the choice: the repo's runtime floor is `engines.node >= 20.11.0` +(`.nvmrc` pins the 22 line); CI must build the driver without a bespoke native toolchain; +and CLAUDE.md non-negotiable #2 forbids a new runtime dependency without an ADR — hence +this record. Drizzle is fixed by ADR-0005; only the underlying driver is being decided. + +## Decision + +**We use `better-sqlite3` as the Node-side SQLite driver for `@relavium/db`, wired +through Drizzle's `drizzle-orm/better-sqlite3` adapter and its migrator.** Versions are +pinned in [tech-stack.md](../tech-stack.md) via the pnpm catalog. + +Considered options: + +1. **`better-sqlite3` + `drizzle-orm/better-sqlite3`** — Drizzle's reference SQLite + driver. Synchronous, which keeps the client factory and the migration runner trivial + (no async ceremony around schema setup); runs on Node ≥ 20.11 (matches `engines`); and + ships prebuilt binaries for common platforms so CI needs no native compiler. *Chosen.* +2. **Node's built-in `node:sqlite`** — zero added dependency, but it requires Node ≥ 22.5 + (above our 20.11 floor), is still flagged experimental, and Drizzle's adapter for it is + immature. Adopting it would force raising the Node floor for a Phase-0 scaffold and bet + on an unstable API. *Rejected.* +3. **`@libsql/client` + `drizzle-orm/libsql`** — cross-platform and async, but oriented at + remote / embedded-replica libSQL use we do not need for a single local file in Phase 1, + and heavier than `better-sqlite3` for no Phase-1 benefit. *Rejected.* + +This decision concerns only the Node-side **driver**. It does not touch the desktop's +encrypted `tauri-plugin-sql` path, the dialect-identical schema, or the Phase-2 Postgres +port — those remain as set by ADR-0005. + +## Consequences + +### Positive + +- The `@relavium/db` client factory and migration runner are synchronous and small; the + 0.I smoke test can open a fresh DB, run every migration, and assert a round-trip with no + async plumbing. +- Node ≥ 20.11 compatibility holds, so the documented `engines` floor stays honest and a + fresh checkout builds on the pinned toolchain without extra setup. +- Prebuilt binaries mean CI installs the driver without a native build step in the common + case, keeping the 0.G pipeline fast and deterministic. +- It is the most documented Drizzle SQLite path, so Phase-1/2 Node consumers (CLI run + history) inherit a well-trodden integration. + +### Negative + +- `better-sqlite3` is a native module: platforms without a prebuilt binary fall back to a + source build (needs a C++ toolchain), and the binary is ABI-bound to the Node version — + mitigated by pinning Node via `.nvmrc` and installing with a frozen lockfile in CI. +- It is a Node-only driver and is **not** the desktop's runtime path (the desktop uses the + Rust `tauri-plugin-sql`), so the persistence layer is exercised through two different + SQLite bindings; the single Drizzle schema is the shared contract that keeps them honest, + and migrations must be validated against the desktop path when that surface lands. +- Synchronous I/O blocks the calling thread; acceptable for the CLI and tests, and a + non-issue for the desktop, which never loads this driver. diff --git a/docs/decisions/README.md b/docs/decisions/README.md index e733bdd5..9751c16b 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -64,6 +64,7 @@ flowchart TD | 0018 | [Desktop execution model — engine in WebView, Rust-delegated LLM egress](0018-desktop-execution-and-rust-egress.md) | Accepted | 2026-06-04 | | 0019 | [Node-side OS-keychain access for the CLI — a maintained library, not the archived keytar](0019-cli-node-keychain-library.md) | Accepted | 2026-06-04 | | 0020 | [Zod as the runtime schema and validation library](0020-zod-runtime-schema-library.md) | Accepted | 2026-06-04 | +| 0021 | [better-sqlite3 as the Node-side SQLite driver for `@relavium/db`](0021-node-sqlite-driver-better-sqlite3.md) | Accepted | 2026-06-04 | ## Creating a new ADR diff --git a/docs/tech-stack.md b/docs/tech-stack.md index 3ddd9c5e..33323854 100644 --- a/docs/tech-stack.md +++ b/docs/tech-stack.md @@ -17,7 +17,7 @@ locked. | Frontend (all surfaces) | **Vite + React 19 + TanStack Router** | Not Next.js — SSE streaming + ReactFlow canvas are incompatible with RSC / edge runtime. See [ADR-0002](decisions/0002-vite-react-tanstack-not-nextjs.md). | | Multi-LLM | **Internal `@relavium/llm` abstraction** over official provider SDKs (`@anthropic-ai/sdk`, `openai`, `@google/genai`; DeepSeek via the OpenAI-compatible adapter) | No 3rd-party framework, no Vercel, no Python sidecar; an owned provider-agnostic seam gives unified streaming + tool calling + cost across Anthropic / OpenAI / Gemini / DeepSeek with no vendor lock-in. See [ADR-0011](decisions/0011-internal-llm-abstraction.md). | | Orchestration engine | **Pure TypeScript** (`packages/core`) | No LangGraph-Python; the same concepts are implementable in TS. Engine is framework-agnostic and runs identically on every surface. | -| Database (local, Phase 1) | **SQLite + Drizzle ORM** (SQLCipher) | Tauri plugin available; encrypted at rest with SQLCipher. | +| Database (local, Phase 1) | **SQLite + Drizzle ORM** (SQLCipher) | Tauri plugin available; encrypted at rest with SQLCipher. Node-side consumers (CLI, `@relavium/db` tests) use the **`better-sqlite3`** driver — see [ADR-0021](decisions/0021-node-sqlite-driver-better-sqlite3.md). | | Database (cloud, Phase 2) | **PostgreSQL 16 + Redis 7 + BullMQ** | *Phase 2 only.* Same Drizzle schema, different driver. | | API key storage | **OS keychain**, one `KeychainStore` interface with a per-surface accessor — desktop `tauri-plugin-keychain` (Rust), **CLI `@napi-rs/keyring`** (Node; *not* the archived `keytar`, see [ADR-0019](decisions/0019-cli-node-keychain-library.md)), VS Code `vscode.SecretStorage` | macOS Keychain / Windows Credential Manager / libsecret. Never plaintext, never sent to the frontend. See [ADR-0006](decisions/0006-os-keychain-for-api-keys.md). | | CLI | **TypeScript + commander.js + ink** (`@clack/prompts` setup wizards; bundled to a single ESM bundle with `tsup`) | Same language as the engine; React for the TUI. | diff --git a/packages/db/README.md b/packages/db/README.md index fd73390f..d7d75651 100644 --- a/packages/db/README.md +++ b/packages/db/README.md @@ -1,11 +1,46 @@ -# `@relavium/db` — placeholder +# `@relavium/db` -Drizzle schema + migrations — one schema, two dialects (SQLite local, Postgres -cloud) ([ADR-0005](../../docs/decisions/0005-sqlite-drizzle-local-postgres-cloud.md)). +Drizzle schema + migrations + the local SQLite client — **one schema, two dialects** +(SQLite local, Postgres cloud) ([ADR-0005](../../docs/decisions/0005-sqlite-drizzle-local-postgres-cloud.md)). +Canonical DDL: [database-schema.md](../../docs/reference/desktop/database-schema.md). -> **Not built yet.** This package is a directory placeholder. It is scaffolded in -> [Phase 0 workstream 0.I](../../docs/roadmap/phases/phase-0-foundations.md) (schema + -> migrations + SQLite client), ahead of its Phase-1/2 consumers. It has no `package.json` -> yet, so pnpm/Turborepo do not treat it as a workspace. +## Status -Canonical DDL: [database-schema.md](../../docs/reference/desktop/database-schema.md). +**Phase 0 scaffold (workstream 0.I).** Built but **not wired into a running engine** — +its Phase-1 (engine checkpoint/resume) and Phase-2 (CLI run history) consumers come +later. The package ships: + +- The **Drizzle SQLite schema** ([src/schema.ts](src/schema.ts)) for the nine Phase-1 + tables — `llm_providers`, `model_catalog`, `agents`, `workflows`, `runs`, + `step_executions`, `messages`, `run_events`, `run_costs` — mirroring the canonical DDL. +- A **`drizzle-kit` migration set** ([drizzle/](drizzle/)). +- The **SQLite client factory + migration runner** ([src/client.ts](src/client.ts)) over + `better-sqlite3` ([ADR-0021](../../docs/decisions/0021-node-sqlite-driver-better-sqlite3.md)). +- A **smoke test** that applies every migration to a fresh DB and round-trips a row. + +It depends only on `@relavium/shared` (the contract enums its CHECKs reuse) and Drizzle. + +## Conventions (from the canonical DDL) + +- **UUID** primary keys are `TEXT`, generated in app code — never a DB default. +- **JSON** is `TEXT` (a JSON string); **tags** are a JSON array as `TEXT`. +- **Timestamps** are `INTEGER` epoch-milliseconds; **money** is `INTEGER` micro-cents. +- **Enums** are `TEXT` + a `CHECK (... IN (...))`. The `runs.status` / `runs.execution_mode` + value sets are imported from `@relavium/shared` so the persisted CHECK can never drift + from the logical contract. +- **Soft delete** is a nullable `deleted_at` with partial indexes (`WHERE deleted_at IS NULL`). +- Table/column names are kept **dialect-identical** for the Phase-2 Postgres port. +- The client applies `PRAGMA journal_mode = WAL` and `PRAGMA foreign_keys = ON`. + +The desktop reaches SQLite through the Rust `tauri-plugin-sql` (SQLCipher-encrypted at +rest); this `better-sqlite3` client is the **Node-side** path (tests now, CLI later). + +## Scripts + +| Script | What it does | +|--------|--------------| +| `build` | `tsc -p tsconfig.build.json` → `dist/` | +| `typecheck` | `tsc -p tsconfig.json --noEmit` (includes tests) | +| `lint` | `eslint src` | +| `test` | `vitest run` (the migration + round-trip smoke test) | +| `db:generate` | `drizzle-kit generate` — diff `src/schema.ts` → a new migration in `drizzle/` | diff --git a/packages/db/drizzle.config.ts b/packages/db/drizzle.config.ts new file mode 100644 index 00000000..67d2a3bb --- /dev/null +++ b/packages/db/drizzle.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'drizzle-kit'; + +/** + * drizzle-kit configuration for the local SQLite dialect. `pnpm db:generate` diffs + * `src/schema.ts` against the migration history in `./drizzle` and emits the next + * migration. The schema/column names are kept dialect-identical so the Phase-2 + * Postgres port (ADR-0005) is a `dialect` change, not a rewrite. + * + * This file is loaded by drizzle-kit's own bundler (not `tsc`); it is excluded from + * the package build and ESLint-ignored as a `*.config.*` tooling file. + */ +export default defineConfig({ + dialect: 'sqlite', + schema: './src/schema.ts', + out: './drizzle', + strict: true, + verbose: true, +}); diff --git a/packages/db/drizzle/0000_cuddly_war_machine.sql b/packages/db/drizzle/0000_cuddly_war_machine.sql new file mode 100644 index 00000000..aee71840 --- /dev/null +++ b/packages/db/drizzle/0000_cuddly_war_machine.sql @@ -0,0 +1,190 @@ +CREATE TABLE `agents` ( + `id` text PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `slug` text NOT NULL, + `description` text, + `model_id` text NOT NULL, + `system_prompt` text DEFAULT '' NOT NULL, + `tools` text DEFAULT '[]' NOT NULL, + `config` text DEFAULT '{}' NOT NULL, + `input_schema` text, + `output_schema` text, + `tags` text DEFAULT '[]' NOT NULL, + `source_path` text, + `is_active` integer DEFAULT 1 NOT NULL, + `deleted_at` integer, + `created_at` integer NOT NULL, + `updated_at` integer NOT NULL, + FOREIGN KEY (`model_id`) REFERENCES `model_catalog`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE UNIQUE INDEX `idx_agents_slug` ON `agents` (`slug`) WHERE "agents"."deleted_at" is null;--> statement-breakpoint +CREATE INDEX `idx_agents_model` ON `agents` (`model_id`);--> statement-breakpoint +CREATE INDEX `idx_agents_active` ON `agents` (`is_active`,"created_at" desc) WHERE "agents"."deleted_at" is null;--> statement-breakpoint +CREATE TABLE `llm_providers` ( + `id` text PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `display_name` text NOT NULL, + `base_url` text NOT NULL, + `api_key_keychain_ref` text, + `default_headers` text DEFAULT '{}' NOT NULL, + `is_active` integer DEFAULT 1 NOT NULL, + `deleted_at` integer, + `created_at` integer NOT NULL, + `updated_at` integer NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX `idx_llm_providers_name` ON `llm_providers` (`name`) WHERE "llm_providers"."deleted_at" is null;--> statement-breakpoint +CREATE TABLE `messages` ( + `id` text PRIMARY KEY NOT NULL, + `step_execution_id` text NOT NULL, + `run_id` text NOT NULL, + `sequence_number` integer NOT NULL, + `role` text NOT NULL, + `content` text, + `content_parts` text, + `tool_calls` text, + `tool_call_id` text, + `name` text, + `finish_reason` text, + `created_at` integer NOT NULL, + FOREIGN KEY (`step_execution_id`) REFERENCES `step_executions`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE INDEX `idx_messages_step` ON `messages` (`step_execution_id`,`sequence_number`);--> statement-breakpoint +CREATE INDEX `idx_messages_run` ON `messages` (`run_id`,`created_at`);--> statement-breakpoint +CREATE TABLE `model_catalog` ( + `id` text PRIMARY KEY NOT NULL, + `provider_id` text NOT NULL, + `model_id` text NOT NULL, + `display_name` text NOT NULL, + `context_window_tokens` integer NOT NULL, + `max_output_tokens` integer NOT NULL, + `input_cost_per_mtok_microcents` integer DEFAULT 0 NOT NULL, + `output_cost_per_mtok_microcents` integer DEFAULT 0 NOT NULL, + `cached_input_cost_per_mtok_microcents` integer DEFAULT 0 NOT NULL, + `supports_tool_calling` integer DEFAULT 0 NOT NULL, + `supports_vision` integer DEFAULT 0 NOT NULL, + `supports_streaming` integer DEFAULT 1 NOT NULL, + `supports_json_mode` integer DEFAULT 0 NOT NULL, + `capabilities` text DEFAULT '{}' NOT NULL, + `deprecation_date` integer, + `is_active` integer DEFAULT 1 NOT NULL, + `deleted_at` integer, + `created_at` integer NOT NULL, + `updated_at` integer NOT NULL, + FOREIGN KEY (`provider_id`) REFERENCES `llm_providers`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE UNIQUE INDEX `idx_model_catalog_provider_model` ON `model_catalog` (`provider_id`,`model_id`) WHERE "model_catalog"."deleted_at" is null;--> statement-breakpoint +CREATE INDEX `idx_model_catalog_provider` ON `model_catalog` (`provider_id`);--> statement-breakpoint +CREATE INDEX `idx_model_catalog_active` ON `model_catalog` (`is_active`) WHERE "model_catalog"."deleted_at" is null;--> statement-breakpoint +CREATE TABLE `run_costs` ( + `id` text PRIMARY KEY NOT NULL, + `run_id` text NOT NULL, + `node_id` text NOT NULL, + `model_id` text, + `input_tokens` integer DEFAULT 0 NOT NULL, + `output_tokens` integer DEFAULT 0 NOT NULL, + `cost_microcents` integer DEFAULT 0 NOT NULL, + `created_at` integer NOT NULL, + FOREIGN KEY (`run_id`) REFERENCES `runs`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`model_id`) REFERENCES `model_catalog`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE INDEX `idx_run_costs_run` ON `run_costs` (`run_id`);--> statement-breakpoint +CREATE TABLE `run_events` ( + `id` text PRIMARY KEY NOT NULL, + `run_id` text NOT NULL, + `step_execution_id` text, + `seq` integer NOT NULL, + `event_type` text NOT NULL, + `level` text DEFAULT 'info' NOT NULL, + `node_id` text, + `payload_json` text DEFAULT '{}' NOT NULL, + `ts` integer NOT NULL, + FOREIGN KEY (`run_id`) REFERENCES `runs`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE INDEX `idx_run_events_run_seq` ON `run_events` (`run_id`,`seq`);--> statement-breakpoint +CREATE INDEX `idx_run_events_step` ON `run_events` (`step_execution_id`,`ts`) WHERE "run_events"."step_execution_id" is not null;--> statement-breakpoint +CREATE INDEX `idx_run_events_run_type` ON `run_events` (`run_id`,`event_type`,`ts`);--> statement-breakpoint +CREATE TABLE `runs` ( + `id` text PRIMARY KEY NOT NULL, + `workflow_id` text NOT NULL, + `workflow_path` text, + `project_root` text, + `workflow_definition_snapshot` text NOT NULL, + `status` text DEFAULT 'pending' NOT NULL, + `execution_mode` text DEFAULT 'local' NOT NULL, + `trigger_type` text DEFAULT 'manual' NOT NULL, + `trigger_metadata` text DEFAULT '{}' NOT NULL, + `input_json` text DEFAULT '{}' NOT NULL, + `output_json` text, + `error_json` text, + `started_at` integer, + `completed_at` integer, + `total_input_tokens` integer DEFAULT 0 NOT NULL, + `total_output_tokens` integer DEFAULT 0 NOT NULL, + `total_cost_microcents` integer DEFAULT 0 NOT NULL, + `deleted_at` integer, + `created_at` integer NOT NULL, + `updated_at` integer NOT NULL, + FOREIGN KEY (`workflow_id`) REFERENCES `workflows`(`id`) ON UPDATE no action ON DELETE no action, + CONSTRAINT "runs_status_check" CHECK("runs"."status" in ('pending', 'running', 'paused', 'completed', 'failed', 'cancelled')), + CONSTRAINT "runs_execution_mode_check" CHECK("runs"."execution_mode" in ('local', 'cloud', 'managed')) +); +--> statement-breakpoint +CREATE INDEX `idx_runs_workflow` ON `runs` (`workflow_id`,"created_at" desc);--> statement-breakpoint +CREATE INDEX `idx_runs_status` ON `runs` (`status`,"created_at" desc) WHERE "runs"."deleted_at" is null;--> statement-breakpoint +CREATE INDEX `idx_runs_cost` ON `runs` (`workflow_id`,`created_at`,`total_cost_microcents`) WHERE "runs"."deleted_at" is null;--> statement-breakpoint +CREATE TABLE `step_executions` ( + `id` text PRIMARY KEY NOT NULL, + `run_id` text NOT NULL, + `node_id` text NOT NULL, + `node_type` text NOT NULL, + `agent_id` text, + `agent_snapshot` text, + `model_id` text, + `attempt_number` integer DEFAULT 1 NOT NULL, + `status` text DEFAULT 'pending' NOT NULL, + `input_json` text DEFAULT '{}' NOT NULL, + `output_json` text, + `error_json` text, + `started_at` integer, + `completed_at` integer, + `duration_ms` integer, + `input_tokens` integer DEFAULT 0 NOT NULL, + `output_tokens` integer DEFAULT 0 NOT NULL, + `cached_tokens` integer DEFAULT 0 NOT NULL, + `cost_microcents` integer DEFAULT 0 NOT NULL, + `created_at` integer NOT NULL, + `updated_at` integer NOT NULL, + FOREIGN KEY (`run_id`) REFERENCES `runs`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`agent_id`) REFERENCES `agents`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`model_id`) REFERENCES `model_catalog`(`id`) ON UPDATE no action ON DELETE no action, + CONSTRAINT "step_executions_status_check" CHECK("step_executions"."status" in ('pending', 'running', 'completed', 'failed', 'skipped')) +); +--> statement-breakpoint +CREATE INDEX `idx_step_exec_run` ON `step_executions` (`run_id`,`created_at`);--> statement-breakpoint +CREATE INDEX `idx_step_exec_run_node` ON `step_executions` (`run_id`,`node_id`,`attempt_number`);--> statement-breakpoint +CREATE INDEX `idx_step_exec_agent` ON `step_executions` (`agent_id`,"created_at" desc) WHERE "step_executions"."agent_id" is not null;--> statement-breakpoint +CREATE INDEX `idx_step_exec_model` ON `step_executions` (`model_id`,"created_at" desc) WHERE "step_executions"."model_id" is not null;--> statement-breakpoint +CREATE INDEX `idx_step_exec_cost` ON `step_executions` (`model_id`,`created_at`,`cost_microcents`) WHERE "step_executions"."model_id" is not null;--> statement-breakpoint +CREATE TABLE `workflows` ( + `id` text PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `slug` text NOT NULL, + `description` text, + `definition` text NOT NULL, + `input_schema` text, + `tags` text DEFAULT '[]' NOT NULL, + `source_path` text, + `is_active` integer DEFAULT 1 NOT NULL, + `deleted_at` integer, + `created_at` integer NOT NULL, + `updated_at` integer NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX `idx_workflows_slug` ON `workflows` (`slug`) WHERE "workflows"."deleted_at" is null;--> statement-breakpoint +CREATE INDEX `idx_workflows_active` ON `workflows` (`is_active`,"updated_at" desc) WHERE "workflows"."deleted_at" is null; \ No newline at end of file diff --git a/packages/db/drizzle/meta/0000_snapshot.json b/packages/db/drizzle/meta/0000_snapshot.json new file mode 100644 index 00000000..81c018ed --- /dev/null +++ b/packages/db/drizzle/meta/0000_snapshot.json @@ -0,0 +1,1432 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "a6902f81-890b-4dcc-a83c-7f496ee1fb5d", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "agents": { + "name": "agents", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "system_prompt": { + "name": "system_prompt", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "tools": { + "name": "tools", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "input_schema": { + "name": "input_schema", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output_schema": { + "name": "output_schema", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "source_path": { + "name": "source_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "1" + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_agents_slug": { + "name": "idx_agents_slug", + "columns": [ + "slug" + ], + "isUnique": true, + "where": "\"agents\".\"deleted_at\" is null" + }, + "idx_agents_model": { + "name": "idx_agents_model", + "columns": [ + "model_id" + ], + "isUnique": false + }, + "idx_agents_active": { + "name": "idx_agents_active", + "columns": [ + "is_active", + "\"created_at\" desc" + ], + "isUnique": false, + "where": "\"agents\".\"deleted_at\" is null" + } + }, + "foreignKeys": { + "agents_model_id_model_catalog_id_fk": { + "name": "agents_model_id_model_catalog_id_fk", + "tableFrom": "agents", + "tableTo": "model_catalog", + "columnsFrom": [ + "model_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "llm_providers": { + "name": "llm_providers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "api_key_keychain_ref": { + "name": "api_key_keychain_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_headers": { + "name": "default_headers", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "1" + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_llm_providers_name": { + "name": "idx_llm_providers_name", + "columns": [ + "name" + ], + "isUnique": true, + "where": "\"llm_providers\".\"deleted_at\" is null" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messages": { + "name": "messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "step_execution_id": { + "name": "step_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sequence_number": { + "name": "sequence_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_parts": { + "name": "content_parts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_calls": { + "name": "tool_calls", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "finish_reason": { + "name": "finish_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_messages_step": { + "name": "idx_messages_step", + "columns": [ + "step_execution_id", + "sequence_number" + ], + "isUnique": false + }, + "idx_messages_run": { + "name": "idx_messages_run", + "columns": [ + "run_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "messages_step_execution_id_step_executions_id_fk": { + "name": "messages_step_execution_id_step_executions_id_fk", + "tableFrom": "messages", + "tableTo": "step_executions", + "columnsFrom": [ + "step_execution_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "model_catalog": { + "name": "model_catalog", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_window_tokens": { + "name": "context_window_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "max_output_tokens": { + "name": "max_output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "input_cost_per_mtok_microcents": { + "name": "input_cost_per_mtok_microcents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "output_cost_per_mtok_microcents": { + "name": "output_cost_per_mtok_microcents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cached_input_cost_per_mtok_microcents": { + "name": "cached_input_cost_per_mtok_microcents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "supports_tool_calling": { + "name": "supports_tool_calling", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "0" + }, + "supports_vision": { + "name": "supports_vision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "0" + }, + "supports_streaming": { + "name": "supports_streaming", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "1" + }, + "supports_json_mode": { + "name": "supports_json_mode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "0" + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "deprecation_date": { + "name": "deprecation_date", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "1" + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_model_catalog_provider_model": { + "name": "idx_model_catalog_provider_model", + "columns": [ + "provider_id", + "model_id" + ], + "isUnique": true, + "where": "\"model_catalog\".\"deleted_at\" is null" + }, + "idx_model_catalog_provider": { + "name": "idx_model_catalog_provider", + "columns": [ + "provider_id" + ], + "isUnique": false + }, + "idx_model_catalog_active": { + "name": "idx_model_catalog_active", + "columns": [ + "is_active" + ], + "isUnique": false, + "where": "\"model_catalog\".\"deleted_at\" is null" + } + }, + "foreignKeys": { + "model_catalog_provider_id_llm_providers_id_fk": { + "name": "model_catalog_provider_id_llm_providers_id_fk", + "tableFrom": "model_catalog", + "tableTo": "llm_providers", + "columnsFrom": [ + "provider_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "run_costs": { + "name": "run_costs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cost_microcents": { + "name": "cost_microcents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_run_costs_run": { + "name": "idx_run_costs_run", + "columns": [ + "run_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "run_costs_run_id_runs_id_fk": { + "name": "run_costs_run_id_runs_id_fk", + "tableFrom": "run_costs", + "tableTo": "runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "run_costs_model_id_model_catalog_id_fk": { + "name": "run_costs_model_id_model_catalog_id_fk", + "tableFrom": "run_costs", + "tableTo": "model_catalog", + "columnsFrom": [ + "model_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "run_events": { + "name": "run_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "step_execution_id": { + "name": "step_execution_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'info'" + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload_json": { + "name": "payload_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "ts": { + "name": "ts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_run_events_run_seq": { + "name": "idx_run_events_run_seq", + "columns": [ + "run_id", + "seq" + ], + "isUnique": false + }, + "idx_run_events_step": { + "name": "idx_run_events_step", + "columns": [ + "step_execution_id", + "ts" + ], + "isUnique": false, + "where": "\"run_events\".\"step_execution_id\" is not null" + }, + "idx_run_events_run_type": { + "name": "idx_run_events_run_type", + "columns": [ + "run_id", + "event_type", + "ts" + ], + "isUnique": false + } + }, + "foreignKeys": { + "run_events_run_id_runs_id_fk": { + "name": "run_events_run_id_runs_id_fk", + "tableFrom": "run_events", + "tableTo": "runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "runs": { + "name": "runs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workflow_path": { + "name": "workflow_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_root": { + "name": "project_root", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workflow_definition_snapshot": { + "name": "workflow_definition_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "execution_mode": { + "name": "execution_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'local'" + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'manual'" + }, + "trigger_metadata": { + "name": "trigger_metadata", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "input_json": { + "name": "input_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "output_json": { + "name": "output_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_json": { + "name": "error_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_cost_microcents": { + "name": "total_cost_microcents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_runs_workflow": { + "name": "idx_runs_workflow", + "columns": [ + "workflow_id", + "\"created_at\" desc" + ], + "isUnique": false + }, + "idx_runs_status": { + "name": "idx_runs_status", + "columns": [ + "status", + "\"created_at\" desc" + ], + "isUnique": false, + "where": "\"runs\".\"deleted_at\" is null" + }, + "idx_runs_cost": { + "name": "idx_runs_cost", + "columns": [ + "workflow_id", + "created_at", + "total_cost_microcents" + ], + "isUnique": false, + "where": "\"runs\".\"deleted_at\" is null" + } + }, + "foreignKeys": { + "runs_workflow_id_workflows_id_fk": { + "name": "runs_workflow_id_workflows_id_fk", + "tableFrom": "runs", + "tableTo": "workflows", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "runs_status_check": { + "name": "runs_status_check", + "value": "\"runs\".\"status\" in ('pending', 'running', 'paused', 'completed', 'failed', 'cancelled')" + }, + "runs_execution_mode_check": { + "name": "runs_execution_mode_check", + "value": "\"runs\".\"execution_mode\" in ('local', 'cloud', 'managed')" + } + } + }, + "step_executions": { + "name": "step_executions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "node_type": { + "name": "node_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_snapshot": { + "name": "agent_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attempt_number": { + "name": "attempt_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "input_json": { + "name": "input_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "output_json": { + "name": "output_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_json": { + "name": "error_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cached_tokens": { + "name": "cached_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cost_microcents": { + "name": "cost_microcents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_step_exec_run": { + "name": "idx_step_exec_run", + "columns": [ + "run_id", + "created_at" + ], + "isUnique": false + }, + "idx_step_exec_run_node": { + "name": "idx_step_exec_run_node", + "columns": [ + "run_id", + "node_id", + "attempt_number" + ], + "isUnique": false + }, + "idx_step_exec_agent": { + "name": "idx_step_exec_agent", + "columns": [ + "agent_id", + "\"created_at\" desc" + ], + "isUnique": false, + "where": "\"step_executions\".\"agent_id\" is not null" + }, + "idx_step_exec_model": { + "name": "idx_step_exec_model", + "columns": [ + "model_id", + "\"created_at\" desc" + ], + "isUnique": false, + "where": "\"step_executions\".\"model_id\" is not null" + }, + "idx_step_exec_cost": { + "name": "idx_step_exec_cost", + "columns": [ + "model_id", + "created_at", + "cost_microcents" + ], + "isUnique": false, + "where": "\"step_executions\".\"model_id\" is not null" + } + }, + "foreignKeys": { + "step_executions_run_id_runs_id_fk": { + "name": "step_executions_run_id_runs_id_fk", + "tableFrom": "step_executions", + "tableTo": "runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "step_executions_agent_id_agents_id_fk": { + "name": "step_executions_agent_id_agents_id_fk", + "tableFrom": "step_executions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "step_executions_model_id_model_catalog_id_fk": { + "name": "step_executions_model_id_model_catalog_id_fk", + "tableFrom": "step_executions", + "tableTo": "model_catalog", + "columnsFrom": [ + "model_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "step_executions_status_check": { + "name": "step_executions_status_check", + "value": "\"step_executions\".\"status\" in ('pending', 'running', 'completed', 'failed', 'skipped')" + } + } + }, + "workflows": { + "name": "workflows", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "definition": { + "name": "definition", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "input_schema": { + "name": "input_schema", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "source_path": { + "name": "source_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "1" + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_workflows_slug": { + "name": "idx_workflows_slug", + "columns": [ + "slug" + ], + "isUnique": true, + "where": "\"workflows\".\"deleted_at\" is null" + }, + "idx_workflows_active": { + "name": "idx_workflows_active", + "columns": [ + "is_active", + "\"updated_at\" desc" + ], + "isUnique": false, + "where": "\"workflows\".\"deleted_at\" is null" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": { + "idx_agents_active": { + "columns": { + "\"created_at\" desc": { + "isExpression": true + } + } + }, + "idx_runs_workflow": { + "columns": { + "\"created_at\" desc": { + "isExpression": true + } + } + }, + "idx_runs_status": { + "columns": { + "\"created_at\" desc": { + "isExpression": true + } + } + }, + "idx_step_exec_agent": { + "columns": { + "\"created_at\" desc": { + "isExpression": true + } + } + }, + "idx_step_exec_model": { + "columns": { + "\"created_at\" desc": { + "isExpression": true + } + } + }, + "idx_workflows_active": { + "columns": { + "\"updated_at\" desc": { + "isExpression": true + } + } + } + } + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json new file mode 100644 index 00000000..45f52c31 --- /dev/null +++ b/packages/db/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1780591282277, + "tag": "0000_cuddly_war_machine", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/packages/db/package.json b/packages/db/package.json new file mode 100644 index 00000000..a485a38e --- /dev/null +++ b/packages/db/package.json @@ -0,0 +1,40 @@ +{ + "name": "@relavium/db", + "version": "0.0.0", + "private": true, + "type": "module", + "description": "Drizzle schema + migrations + the local SQLite client — one schema, two dialects (ADR-0005).", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist", + "drizzle" + ], + "scripts": { + "build": "tsc -p tsconfig.build.json", + "typecheck": "tsc -p tsconfig.json --noEmit", + "lint": "eslint src", + "test": "vitest run", + "db:generate": "drizzle-kit generate" + }, + "dependencies": { + "@relavium/shared": "workspace:*", + "better-sqlite3": "catalog:", + "drizzle-orm": "catalog:" + }, + "devDependencies": { + "@types/better-sqlite3": "catalog:", + "@types/node": "catalog:", + "drizzle-kit": "catalog:", + "eslint": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + } +} diff --git a/packages/db/src/client.test.ts b/packages/db/src/client.test.ts new file mode 100644 index 00000000..1d9576ce --- /dev/null +++ b/packages/db/src/client.test.ts @@ -0,0 +1,224 @@ +import { randomUUID } from 'node:crypto'; +import { mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { eq, sql } from 'drizzle-orm'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +import { createClient, runMigrations, type DbClient } from './client.js'; +import { messages, runEvents, runs, stepExecutions, workflows } from './schema.js'; + +/** + * 0.I smoke test: apply every migration to a fresh on-disk SQLite database via the + * client factory, assert the Phase-1 schema materializes, and round-trip a row through + * `runs` + `run_events` — schema correctness only, no engine. + */ + +const TS = 1_700_000_000_000; // fixed epoch-ms so assertions are deterministic + +/** The nine Phase-1 local tables (database-schema.md). */ +const EXPECTED_TABLES = [ + 'llm_providers', + 'model_catalog', + 'agents', + 'workflows', + 'runs', + 'step_executions', + 'messages', + 'run_events', + 'run_costs', +] as const; + +let tmpDir: string; +let dbFile: string; +let client: DbClient; + +beforeAll(() => { + tmpDir = mkdtempSync(join(tmpdir(), 'relavium-db-')); + dbFile = join(tmpDir, 'test.db'); + client = createClient(dbFile); + runMigrations(client.db); +}); + +afterAll(() => { + client.sqlite.close(); + rmSync(tmpDir, { recursive: true, force: true }); +}); + +describe('@relavium/db migrations + client', () => { + it('opens a real file and applies WAL + foreign_keys pragmas', () => { + expect(client.sqlite.pragma('journal_mode', { simple: true })).toBe('wal'); + expect(client.sqlite.pragma('foreign_keys', { simple: true })).toBe(1); + }); + + it('creates every Phase-1 table', () => { + // drizzle's `db.all()` runs a raw query and returns the rows typed as T[]; the + // generic is the caller's row contract for the SELECT (safe — we own the query). + const tables = client.db + .all<{ name: string }>(sql`select name from sqlite_master where type = 'table'`) + .map((r) => r.name); + for (const t of EXPECTED_TABLES) { + expect(tables).toContain(t); + } + }); + + it('creates the partial-unique and lookup indexes', () => { + const indexes = client.db + .all<{ name: string }>(sql`select name from sqlite_master where type = 'index'`) + .map((r) => r.name); + // A representative subset across unique, partial, and composite indexes. + for (const idx of [ + 'idx_workflows_slug', + 'idx_runs_status', + 'idx_run_events_run_seq', + 'idx_step_exec_model', + ]) { + expect(indexes).toContain(idx); + } + }); + + it('round-trips a runs + run_events row', () => { + const workflowId = randomUUID(); + const runId = randomUUID(); + const eventId = randomUUID(); + + client.db + .insert(workflows) + .values({ + id: workflowId, + name: 'Smoke WF', + slug: 'smoke-wf', + definition: '{"nodes":[],"edges":[]}', + createdAt: TS, + updatedAt: TS, + }) + .run(); + + client.db + .insert(runs) + .values({ + id: runId, + workflowId, + workflowDefinitionSnapshot: '{"nodes":[],"edges":[]}', + status: 'running', + executionMode: 'local', + createdAt: TS, + updatedAt: TS, + }) + .run(); + + client.db + .insert(runEvents) + .values({ id: eventId, runId, seq: 0, eventType: 'run:started', ts: TS }) + .run(); + + const run = client.db.select().from(runs).where(eq(runs.id, runId)).get(); + expect(run).toMatchObject({ + id: runId, + workflowId, + status: 'running', + executionMode: 'local', + triggerType: 'manual', // applied DEFAULT + totalCostMicrocents: 0, // applied DEFAULT + }); + + const events = client.db.select().from(runEvents).where(eq(runEvents.runId, runId)).all(); + expect(events).toHaveLength(1); + expect(events[0]).toMatchObject({ eventType: 'run:started', level: 'info', seq: 0 }); + }); + + it('cascades run_events, step_executions, and messages when the parent run is deleted', () => { + const workflowId = randomUUID(); + const runId = randomUUID(); + const stepId = randomUUID(); + + client.db + .insert(workflows) + .values({ + id: workflowId, + name: 'Cascade WF', + slug: 'cascade-wf', + definition: '{}', + createdAt: TS, + updatedAt: TS, + }) + .run(); + client.db + .insert(runs) + .values({ + id: runId, + workflowId, + workflowDefinitionSnapshot: '{}', + createdAt: TS, + updatedAt: TS, + }) + .run(); + client.db + .insert(runEvents) + .values({ id: randomUUID(), runId, seq: 0, eventType: 'run:completed', ts: TS }) + .run(); + // A step_execution and its message — to exercise the chained cascade + // run → step_executions → messages (the hot runtime path). + client.db + .insert(stepExecutions) + .values({ id: stepId, runId, nodeId: 'n1', nodeType: 'agent', createdAt: TS, updatedAt: TS }) + .run(); + client.db + .insert(messages) + .values({ + id: randomUUID(), + stepExecutionId: stepId, + runId, + sequenceNumber: 0, + role: 'assistant', + createdAt: TS, + }) + .run(); + + client.db.delete(runs).where(eq(runs.id, runId)).run(); + + expect(client.db.select().from(runEvents).where(eq(runEvents.runId, runId)).all()).toHaveLength( + 0, + ); + expect( + client.db.select().from(stepExecutions).where(eq(stepExecutions.runId, runId)).all(), + ).toHaveLength(0); + expect(client.db.select().from(messages).where(eq(messages.runId, runId)).all()).toHaveLength( + 0, + ); + }); + + it('rejects a status / execution_mode outside the CHECK value set (not just the FK)', () => { + // Insert a real workflow so the FK is satisfied — then ONLY the CHECK can reject these + // rows. Without it, a non-existent workflow_id would throw an FK violation and the test + // would pass even if the CHECK were removed. Assert the specific constraint name fires. + const workflowId = randomUUID(); + client.db + .insert(workflows) + .values({ + id: workflowId, + name: 'Check WF', + slug: 'check-wf', + definition: '{}', + createdAt: TS, + updatedAt: TS, + }) + .run(); + const base = { workflowId, workflowDefinitionSnapshot: '{}', createdAt: TS, updatedAt: TS }; + expect(() => + client.db + .insert(runs) + // @ts-expect-error — 'bogus' is not a valid status. + .values({ ...base, id: randomUUID(), status: 'bogus' }) + .run(), + ).toThrow(/CHECK constraint failed: runs_status_check/i); + expect(() => + client.db + .insert(runs) + // @ts-expect-error — 'turbo' is not a valid execution_mode. + .values({ ...base, id: randomUUID(), executionMode: 'turbo' }) + .run(), + ).toThrow(/CHECK constraint failed: runs_execution_mode_check/i); + }); +}); diff --git a/packages/db/src/client.ts b/packages/db/src/client.ts new file mode 100644 index 00000000..2f3e4a05 --- /dev/null +++ b/packages/db/src/client.ts @@ -0,0 +1,56 @@ +import { fileURLToPath } from 'node:url'; + +import Database from 'better-sqlite3'; +import { drizzle, type BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import { migrate } from 'drizzle-orm/better-sqlite3/migrator'; + +import * as schema from './schema.js'; + +/** + * The local SQLite client for `@relavium/db`, wired over `better-sqlite3` + * ([ADR-0021](../../../docs/decisions/0021-node-sqlite-driver-better-sqlite3.md)) and + * Drizzle. This is the **Node-side** path (CLI, tests); the desktop reaches SQLite + * through the Rust `tauri-plugin-sql` and does not load this driver. + * + * Scope is schema/migrations only (Phase 0 workstream 0.I) — no engine wiring. SQLCipher + * encryption-at-rest (ADR-0005) is applied by the desktop's Rust setup hook, not here. + */ + +/** A Drizzle handle bound to the full Relavium schema. */ +export type Db = BetterSQLite3Database; + +/** A connected client: the Drizzle handle plus the raw driver for lifecycle control. */ +export interface DbClient { + readonly db: Db; + /** The underlying better-sqlite3 connection (call `.close()` when done). */ + readonly sqlite: Database.Database; +} + +/** + * Open a SQLite database and return a schema-bound Drizzle client. `path` defaults to a + * private in-memory database; pass a filesystem path for a persistent local store. + * + * Applies the project PRAGMAs: `journal_mode = WAL` (concurrent reads while a run writes; + * a no-op for in-memory) and `foreign_keys = ON` (SQLite does not enforce FKs per + * connection by default — the CASCADE rules in the schema depend on it). + */ +export function createClient(path = ':memory:'): DbClient { + const sqlite = new Database(path); + sqlite.pragma('journal_mode = WAL'); + sqlite.pragma('foreign_keys = ON'); + const db = drizzle(sqlite, { schema }); + return { db, sqlite }; +} + +/** The packaged migration set (`./drizzle`), resolved relative to this module so it works + * from both `src/` (tests) and the built `dist/` (consumers) — both sit one level under + * the package root. */ +const MIGRATIONS_DIR = fileURLToPath(new URL('../drizzle', import.meta.url)); + +/** + * Apply every pending `drizzle-kit` migration to the given client. Idempotent: Drizzle + * tracks applied migrations, so re-running is a no-op. Surfaces call this on first use. + */ +export function runMigrations(db: Db): void { + migrate(db, { migrationsFolder: MIGRATIONS_DIR }); +} diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts new file mode 100644 index 00000000..e66b2b1e --- /dev/null +++ b/packages/db/src/index.ts @@ -0,0 +1,44 @@ +/** + * `@relavium/db` — the Drizzle schema, the local SQLite client, and the migration + * runner for Relavium's run history, event log, and cost data. **One schema, two + * dialects** ([ADR-0005](../../docs/decisions/0005-sqlite-drizzle-local-postgres-cloud.md)); + * the canonical DDL is [database-schema.md](../../docs/reference/desktop/database-schema.md). + * + * Curated public surface: the schema tables + their inferred row types, and the client + * factory / migration runner. Internal column helpers in `schema.ts` are not exported. + */ + +export { + llmProviders, + modelCatalog, + agents, + workflows, + runs, + stepExecutions, + messages, + runEvents, + runCosts, +} from './schema.js'; + +export type { + LlmProviderRow, + NewLlmProviderRow, + ModelCatalogRow, + NewModelCatalogRow, + AgentRow, + NewAgentRow, + WorkflowRow, + NewWorkflowRow, + RunRow, + NewRunRow, + StepExecutionRow, + NewStepExecutionRow, + MessageRow, + NewMessageRow, + RunEventRow, + NewRunEventRow, + RunCostRow, + NewRunCostRow, +} from './schema.js'; + +export { createClient, runMigrations, type Db, type DbClient } from './client.js'; diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts new file mode 100644 index 00000000..feceabff --- /dev/null +++ b/packages/db/src/schema.ts @@ -0,0 +1,373 @@ +import { + EXECUTION_MODES, + RunStatusSchema, + type ExecutionMode, + type RunStatus, +} from '@relavium/shared'; +import { desc, sql } from 'drizzle-orm'; +import { check, index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'; + +/** + * The Phase-1 local table set, modeling the canonical DDL in + * [database-schema.md](../../../docs/reference/desktop/database-schema.md) as a Drizzle + * SQLite schema. **One schema, two dialects** ([ADR-0005](../../../docs/decisions/0005-sqlite-drizzle-local-postgres-cloud.md)): + * table and column names are kept dialect-identical so the Phase-2 Postgres port is a + * driver/dialect change, not a rewrite. + * + * Conventions enforced here (from database-schema.md §conventions): + * - **UUID** primary keys are `TEXT`, generated in application code — never a DB default. + * - **JSON** documents are stored as `TEXT` (a JSON string; queried with `json_extract`). + * Kept as `string` at this layer; typed accessors are a consumer concern (Phase 1). + * - **Timestamps** are `INTEGER` Unix **epoch-milliseconds** (timezone handled in app code). + * - **Money** is `INTEGER` **micro-cents** (1 micro-cent = 1e-8 USD). + * - **Enums** are `TEXT` + a `CHECK (... IN (...))` constraint — never a native enum type. + * The `runs.status` and `runs.execution_mode` value sets are imported from + * `@relavium/shared` so the persisted CHECK can never drift from the logical contract. + * - **Booleans** are `INTEGER` 0/1 (Drizzle `mode: 'boolean'`). + * - **Soft delete** is a nullable `deleted_at` epoch-ms cursor with partial unique/active + * indexes (`WHERE deleted_at IS NULL`). + * + * No engine wiring lives here — this is schema/migrations only (Phase 0 workstream 0.I); + * the FK-cascade and index choices come straight from the reference DDL. + */ + +// --- Column-convention helpers (each returns a fresh builder per call) --- + +/** UUID stored as TEXT, generated in application code (no DB default). */ +const uuidPk = () => text('id').primaryKey(); +/** Epoch-millisecond timestamp stored as INTEGER. */ +const epochMs = (name: string) => integer(name); +/** Integer micro-cents (money), defaulting to 0. */ +const microcents = (name: string) => integer(name).notNull().default(0); +/** A non-negative token counter, defaulting to 0. */ +const tokenCount = (name: string) => integer(name).notNull().default(0); +/** A JSON document stored as a TEXT string. */ +const jsonText = (name: string) => text(name); + +/** An INTEGER bool (0/1) flag with a 0/1 default, matching the canonical DDL exactly + * (Drizzle `mode: 'boolean'` reads/writes JS booleans over the 0/1 storage). */ +const boolFlag = (name: string, def: boolean) => + integer(name, { mode: 'boolean' }) + .notNull() + .default(def ? sql`1` : sql`0`); + +/** A `CHECK (col IN ('a','b',...))` from a closed value list. Values come from our own + * `as const` constants (never user input); the single-quote escape is belt-and-suspenders + * so the helper is safe even if a value with a quote is ever passed. */ +const inList = (values: readonly string[]) => + sql.raw(values.map((v) => `'${v.replace(/'/g, "''")}'`).join(', ')); + +// --- 1. llm_providers (no FKs) --- + +export const llmProviders = sqliteTable( + 'llm_providers', + { + id: uuidPk(), + name: text('name').notNull(), + displayName: text('display_name').notNull(), + baseUrl: text('base_url').notNull(), + // keychain `account` identifier — NEVER the key itself (ADR-0006). + apiKeyKeychainRef: text('api_key_keychain_ref'), + defaultHeaders: jsonText('default_headers').notNull().default('{}'), + isActive: boolFlag('is_active', true), + deletedAt: epochMs('deleted_at'), + createdAt: epochMs('created_at').notNull(), + updatedAt: epochMs('updated_at').notNull(), + }, + (t) => [ + uniqueIndex('idx_llm_providers_name') + .on(t.name) + .where(sql`${t.deletedAt} is null`), + ], +); + +// --- 2. model_catalog (-> llm_providers) --- + +export const modelCatalog = sqliteTable( + 'model_catalog', + { + id: uuidPk(), + providerId: text('provider_id') + .notNull() + .references(() => llmProviders.id), + modelId: text('model_id').notNull(), + displayName: text('display_name').notNull(), + contextWindowTokens: integer('context_window_tokens').notNull(), + maxOutputTokens: integer('max_output_tokens').notNull(), + inputCostPerMtokMicrocents: microcents('input_cost_per_mtok_microcents'), + outputCostPerMtokMicrocents: microcents('output_cost_per_mtok_microcents'), + cachedInputCostPerMtokMicrocents: microcents('cached_input_cost_per_mtok_microcents'), + supportsToolCalling: boolFlag('supports_tool_calling', false), + supportsVision: boolFlag('supports_vision', false), + supportsStreaming: boolFlag('supports_streaming', true), + supportsJsonMode: boolFlag('supports_json_mode', false), + capabilities: jsonText('capabilities').notNull().default('{}'), + deprecationDate: epochMs('deprecation_date'), + isActive: boolFlag('is_active', true), + deletedAt: epochMs('deleted_at'), + createdAt: epochMs('created_at').notNull(), + updatedAt: epochMs('updated_at').notNull(), + }, + (t) => [ + uniqueIndex('idx_model_catalog_provider_model') + .on(t.providerId, t.modelId) + .where(sql`${t.deletedAt} is null`), + index('idx_model_catalog_provider').on(t.providerId), + index('idx_model_catalog_active') + .on(t.isActive) + .where(sql`${t.deletedAt} is null`), + ], +); + +// --- 3. agents (-> model_catalog) --- + +export const agents = sqliteTable( + 'agents', + { + id: uuidPk(), + name: text('name').notNull(), + slug: text('slug').notNull(), + description: text('description'), + modelId: text('model_id') + .notNull() + .references(() => modelCatalog.id), + systemPrompt: text('system_prompt').notNull().default(''), + tools: jsonText('tools').notNull().default('[]'), + config: jsonText('config').notNull().default('{}'), + inputSchema: jsonText('input_schema'), + outputSchema: jsonText('output_schema'), + tags: jsonText('tags').notNull().default('[]'), + sourcePath: text('source_path'), + isActive: boolFlag('is_active', true), + deletedAt: epochMs('deleted_at'), + createdAt: epochMs('created_at').notNull(), + updatedAt: epochMs('updated_at').notNull(), + }, + (t) => [ + uniqueIndex('idx_agents_slug') + .on(t.slug) + .where(sql`${t.deletedAt} is null`), + index('idx_agents_model').on(t.modelId), + index('idx_agents_active') + .on(t.isActive, desc(t.createdAt)) + .where(sql`${t.deletedAt} is null`), + ], +); + +// --- 4. workflows (no FKs) --- + +export const workflows = sqliteTable( + 'workflows', + { + id: uuidPk(), + name: text('name').notNull(), + slug: text('slug').notNull(), + description: text('description'), + definition: jsonText('definition').notNull(), + inputSchema: jsonText('input_schema'), + tags: jsonText('tags').notNull().default('[]'), + sourcePath: text('source_path'), + isActive: boolFlag('is_active', true), + deletedAt: epochMs('deleted_at'), + createdAt: epochMs('created_at').notNull(), + updatedAt: epochMs('updated_at').notNull(), + }, + (t) => [ + uniqueIndex('idx_workflows_slug') + .on(t.slug) + .where(sql`${t.deletedAt} is null`), + index('idx_workflows_active') + .on(t.isActive, desc(t.updatedAt)) + .where(sql`${t.deletedAt} is null`), + ], +); + +// --- 5. runs (-> workflows) --- + +export const runs = sqliteTable( + 'runs', + { + id: uuidPk(), + workflowId: text('workflow_id') + .notNull() + .references(() => workflows.id), + workflowPath: text('workflow_path'), + projectRoot: text('project_root'), + // The frozen graph that actually ran — for replay/resume after the YAML changes. + workflowDefinitionSnapshot: jsonText('workflow_definition_snapshot').notNull(), + status: text('status').$type().notNull().default('pending'), + executionMode: text('execution_mode').$type().notNull().default('local'), + // trigger_type carries NO strict CHECK (database-schema.md) — webhook/schedule are + // Phase-2 values that may legitimately appear; only the default is pinned here. + triggerType: text('trigger_type').notNull().default('manual'), + triggerMetadata: jsonText('trigger_metadata').notNull().default('{}'), + inputJson: jsonText('input_json').notNull().default('{}'), + outputJson: jsonText('output_json'), + errorJson: jsonText('error_json'), + startedAt: epochMs('started_at'), + completedAt: epochMs('completed_at'), + totalInputTokens: tokenCount('total_input_tokens'), + totalOutputTokens: tokenCount('total_output_tokens'), + totalCostMicrocents: microcents('total_cost_microcents'), + deletedAt: epochMs('deleted_at'), + createdAt: epochMs('created_at').notNull(), + updatedAt: epochMs('updated_at').notNull(), + }, + (t) => [ + // CHECK value sets imported from @relavium/shared so the persisted enum cannot + // drift from the logical RunSchema contract. + check('runs_status_check', sql`${t.status} in (${inList(RunStatusSchema.options)})`), + check('runs_execution_mode_check', sql`${t.executionMode} in (${inList(EXECUTION_MODES)})`), + index('idx_runs_workflow').on(t.workflowId, desc(t.createdAt)), + index('idx_runs_status') + .on(t.status, desc(t.createdAt)) + .where(sql`${t.deletedAt} is null`), + index('idx_runs_cost') + .on(t.workflowId, t.createdAt, t.totalCostMicrocents) + .where(sql`${t.deletedAt} is null`), + ], +); + +// --- 6. step_executions (-> runs CASCADE, agents, model_catalog) --- + +const STEP_STATUSES = ['pending', 'running', 'completed', 'failed', 'skipped'] as const; +type StepStatus = (typeof STEP_STATUSES)[number]; + +export const stepExecutions = sqliteTable( + 'step_executions', + { + id: uuidPk(), + runId: text('run_id') + .notNull() + .references(() => runs.id, { onDelete: 'cascade' }), + nodeId: text('node_id').notNull(), + nodeType: text('node_type').notNull(), + agentId: text('agent_id').references(() => agents.id), + agentSnapshot: jsonText('agent_snapshot'), + modelId: text('model_id').references(() => modelCatalog.id), + attemptNumber: integer('attempt_number').notNull().default(1), + status: text('status').$type().notNull().default('pending'), + inputJson: jsonText('input_json').notNull().default('{}'), + outputJson: jsonText('output_json'), + errorJson: jsonText('error_json'), + startedAt: epochMs('started_at'), + completedAt: epochMs('completed_at'), + durationMs: integer('duration_ms'), + inputTokens: tokenCount('input_tokens'), + outputTokens: tokenCount('output_tokens'), + cachedTokens: tokenCount('cached_tokens'), + costMicrocents: microcents('cost_microcents'), + createdAt: epochMs('created_at').notNull(), + updatedAt: epochMs('updated_at').notNull(), + }, + (t) => [ + check('step_executions_status_check', sql`${t.status} in (${inList(STEP_STATUSES)})`), + index('idx_step_exec_run').on(t.runId, t.createdAt), + index('idx_step_exec_run_node').on(t.runId, t.nodeId, t.attemptNumber), + index('idx_step_exec_agent') + .on(t.agentId, desc(t.createdAt)) + .where(sql`${t.agentId} is not null`), + index('idx_step_exec_model') + .on(t.modelId, desc(t.createdAt)) + .where(sql`${t.modelId} is not null`), + index('idx_step_exec_cost') + .on(t.modelId, t.createdAt, t.costMicrocents) + .where(sql`${t.modelId} is not null`), + ], +); + +// --- 7. messages (-> step_executions CASCADE; run_id denormalized, no FK) --- + +export const messages = sqliteTable( + 'messages', + { + id: uuidPk(), + stepExecutionId: text('step_execution_id') + .notNull() + .references(() => stepExecutions.id, { onDelete: 'cascade' }), + // Denormalized for per-run query efficiency; the reference DDL declares no FK here. + runId: text('run_id').notNull(), + sequenceNumber: integer('sequence_number').notNull(), + // role values (system|user|assistant|tool) are documented literals; the reference + // DDL declares no CHECK on role, so none is added here. + role: text('role').notNull(), + content: text('content'), + contentParts: jsonText('content_parts'), + toolCalls: jsonText('tool_calls'), + toolCallId: text('tool_call_id'), + name: text('name'), + finishReason: text('finish_reason'), + createdAt: epochMs('created_at').notNull(), + }, + (t) => [ + index('idx_messages_step').on(t.stepExecutionId, t.sequenceNumber), + index('idx_messages_run').on(t.runId, t.createdAt), + ], +); + +// --- 8. run_events (-> runs CASCADE; step_execution_id denormalized, no FK) --- + +export const runEvents = sqliteTable( + 'run_events', + { + id: uuidPk(), + runId: text('run_id') + .notNull() + .references(() => runs.id, { onDelete: 'cascade' }), + stepExecutionId: text('step_execution_id'), + // monotonic per run — used for gap detection on reconnect/resync. + seq: integer('seq').notNull(), + eventType: text('event_type').notNull(), + level: text('level').notNull().default('info'), + nodeId: text('node_id'), + payloadJson: jsonText('payload_json').notNull().default('{}'), + ts: epochMs('ts').notNull(), + }, + (t) => [ + index('idx_run_events_run_seq').on(t.runId, t.seq), + index('idx_run_events_step') + .on(t.stepExecutionId, t.ts) + .where(sql`${t.stepExecutionId} is not null`), + index('idx_run_events_run_type').on(t.runId, t.eventType, t.ts), + ], +); + +// --- 9. run_costs (-> runs CASCADE, model_catalog) --- + +export const runCosts = sqliteTable( + 'run_costs', + { + id: uuidPk(), + runId: text('run_id') + .notNull() + .references(() => runs.id, { onDelete: 'cascade' }), + nodeId: text('node_id').notNull(), + modelId: text('model_id').references(() => modelCatalog.id), + inputTokens: tokenCount('input_tokens'), + outputTokens: tokenCount('output_tokens'), + costMicrocents: microcents('cost_microcents'), + createdAt: epochMs('created_at').notNull(), + }, + (t) => [index('idx_run_costs_run').on(t.runId)], +); + +// --- Inferred row types (select + insert) for each table --- + +export type LlmProviderRow = typeof llmProviders.$inferSelect; +export type NewLlmProviderRow = typeof llmProviders.$inferInsert; +export type ModelCatalogRow = typeof modelCatalog.$inferSelect; +export type NewModelCatalogRow = typeof modelCatalog.$inferInsert; +export type AgentRow = typeof agents.$inferSelect; +export type NewAgentRow = typeof agents.$inferInsert; +export type WorkflowRow = typeof workflows.$inferSelect; +export type NewWorkflowRow = typeof workflows.$inferInsert; +export type RunRow = typeof runs.$inferSelect; +export type NewRunRow = typeof runs.$inferInsert; +export type StepExecutionRow = typeof stepExecutions.$inferSelect; +export type NewStepExecutionRow = typeof stepExecutions.$inferInsert; +export type MessageRow = typeof messages.$inferSelect; +export type NewMessageRow = typeof messages.$inferInsert; +export type RunEventRow = typeof runEvents.$inferSelect; +export type NewRunEventRow = typeof runEvents.$inferInsert; +export type RunCostRow = typeof runCosts.$inferSelect; +export type NewRunCostRow = typeof runCosts.$inferInsert; diff --git a/packages/db/tsconfig.build.json b/packages/db/tsconfig.build.json new file mode 100644 index 00000000..d5c084ce --- /dev/null +++ b/packages/db/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "rootDir": "src", + "outDir": "dist" + }, + "exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"] +} diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json new file mode 100644 index 00000000..a885ce9c --- /dev/null +++ b/packages/db/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + // Typecheck-only config: no emit, so rootDir/outDir live in tsconfig.build.json. + // Unlike `@relavium/shared` (`types: []`), this package is Node-side persistence: + // it imports `node:*` builtins and the `better-sqlite3` driver, so `@types/node` + // is the platform boundary here. The driver's own types resolve via + // `@types/better-sqlite3` through normal module resolution (explicit import). + "noEmit": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/shared/package.json b/packages/shared/package.json index 5f05268d..d4549290 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -7,7 +7,8 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js" + "import": "./dist/index.js", + "default": "./dist/index.js" } }, "main": "./dist/index.js", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7186c917..f2887947 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,9 +9,24 @@ catalogs: '@eslint/js': specifier: ^9.17.0 version: 9.39.4 + '@types/better-sqlite3': + specifier: ^7.6.13 + version: 7.6.13 + '@types/node': + specifier: ^20.11.0 + version: 20.19.41 '@vitest/coverage-v8': specifier: ^3.0.0 version: 3.2.6 + better-sqlite3: + specifier: ^12.10.0 + version: 12.10.0 + drizzle-kit: + specifier: ^0.31.10 + version: 0.31.10 + drizzle-orm: + specifier: ^0.45.2 + version: 0.45.2 eslint: specifier: ^9.17.0 version: 9.39.4 @@ -46,7 +61,7 @@ importers: version: 9.39.4 '@vitest/coverage-v8': specifier: 'catalog:' - version: 3.2.6(vitest@3.2.6) + version: 3.2.6(vitest@3.2.6(@types/node@22.19.19)(tsx@4.22.4)) eslint: specifier: 'catalog:' version: 9.39.4 @@ -67,7 +82,38 @@ importers: version: 8.60.1(eslint@9.39.4)(typescript@5.9.3) vitest: specifier: 'catalog:' - version: 3.2.6 + version: 3.2.6(@types/node@22.19.19)(tsx@4.22.4) + + packages/db: + dependencies: + '@relavium/shared': + specifier: workspace:* + version: link:../shared + better-sqlite3: + specifier: 'catalog:' + version: 12.10.0 + drizzle-orm: + specifier: 'catalog:' + version: 0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0) + devDependencies: + '@types/better-sqlite3': + specifier: 'catalog:' + version: 7.6.13 + '@types/node': + specifier: 'catalog:' + version: 20.19.41 + drizzle-kit: + specifier: 'catalog:' + version: 0.31.10 + eslint: + specifier: 'catalog:' + version: 9.39.4 + typescript: + specifier: 'catalog:' + version: 5.9.3 + vitest: + specifier: 'catalog:' + version: 3.2.6(@types/node@20.19.41)(tsx@4.22.4) packages/shared: dependencies: @@ -83,7 +129,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 3.2.6 + version: 3.2.6(@types/node@22.19.19)(tsx@4.22.4) packages: @@ -112,162 +158,617 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@drizzle-team/brocli@0.10.2': + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + + '@esbuild-kit/core-utils@3.3.2': + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild-kit/esm-loader@2.6.5': + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.27.7': resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.28.0': + resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.18.20': + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.27.7': resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.28.0': + resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.18.20': + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.27.7': resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.28.0': + resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.18.20': + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.27.7': resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.28.0': + resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.18.20': + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.27.7': resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.28.0': + resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.18.20': + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.27.7': resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.28.0': + resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.18.20': + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.27.7': resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.28.0': + resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.18.20': + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.27.7': resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.28.0': + resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.18.20': + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.27.7': resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.28.0': + resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.18.20': + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.27.7': resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.28.0': + resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.18.20': + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.27.7': resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.28.0': + resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.18.20': + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.27.7': resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.28.0': + resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.18.20': + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.27.7': resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.28.0': + resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.18.20': + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.27.7': resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.28.0': + resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.18.20': + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.27.7': resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.28.0': + resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.18.20': + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.27.7': resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.28.0': + resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.18.20': + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.27.7': resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.28.0': + resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.27.7': resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.28.0': + resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.18.20': + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.27.7': resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.28.0': + resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.27.7': resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.28.0': + resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.18.20': + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.27.7': resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.28.0': + resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.27.7': resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.7': - resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + '@esbuild/openharmony-arm64@0.28.0': + resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.18.20': + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.28.0': + resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.18.20': + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.27.7': resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.28.0': + resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.18.20': + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.27.7': resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.28.0': + resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.18.20': + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.27.7': resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.0': + resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -506,6 +1007,9 @@ packages: cpu: [arm64] os: [win32] + '@types/better-sqlite3@7.6.13': + resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -518,6 +1022,12 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@20.19.41': + resolution: {integrity: sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==} + + '@types/node@22.19.19': + resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} + '@typescript-eslint/eslint-plugin@8.60.1': resolution: {integrity: sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -661,6 +1171,19 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + better-sqlite3@12.10.0: + resolution: {integrity: sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==} + engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + brace-expansion@1.1.15: resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} @@ -671,6 +1194,12 @@ packages: resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -691,6 +1220,9 @@ packages: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -714,13 +1246,121 @@ packages: supports-color: optional: true + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + drizzle-kit@0.31.10: + resolution: {integrity: sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==} + hasBin: true + + drizzle-orm@0.45.2: + resolution: {integrity: sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1.13' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=14.0.0' + gel: '>=2' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + prisma: '*' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@upstash/redis': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + gel: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -730,14 +1370,32 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.27.7: resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} engines: {node: '>=18'} hasBin: true + esbuild@0.28.0: + resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} + engines: {node: '>=18'} + hasBin: true + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -797,6 +1455,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} @@ -823,6 +1485,9 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -838,11 +1503,20 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -863,6 +1537,9 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -879,6 +1556,12 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -962,6 +1645,10 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} @@ -973,10 +1660,16 @@ packages: resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -985,9 +1678,19 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-abi@3.92.0: + resolution: {integrity: sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==} + engines: {node: '>=10'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1037,6 +1740,12 @@ packages: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. + hasBin: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1046,19 +1755,36 @@ packages: engines: {node: '>=14'} hasBin: true + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + rollup@4.61.1: resolution: {integrity: sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + semver@7.8.1: resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} engines: {node: '>=10'} @@ -1079,10 +1805,23 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -1097,6 +1836,9 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1105,6 +1847,10 @@ packages: resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1116,6 +1862,13 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + test-exclude@7.0.2: resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==} engines: {node: '>=18'} @@ -1148,6 +1901,14 @@ packages: peerDependencies: typescript: '>=4.8.4' + tsx@4.22.4: + resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} + engines: {node: '>=18.0.0'} + hasBin: true + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + turbo@2.9.16: resolution: {integrity: sha512-NqgRQy6j6dPYcdSdv0q1g9QsZg7SWg87RERM8otw/1AtKU2yTFVClOM7cbwKzOonZr/Ek1blTBucw64L9H0Bwg==} hasBin: true @@ -1168,9 +1929,15 @@ packages: engines: {node: '>=14.17'} hasBin: true + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -1266,6 +2033,9 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -1273,106 +2043,340 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} -snapshots: +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@1.0.2': {} + + '@drizzle-team/brocli@0.10.2': {} + + '@esbuild-kit/core-utils@3.3.2': + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + + '@esbuild-kit/esm-loader@2.6.5': + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.14.0 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/aix-ppc64@0.28.0': + optional: true + + '@esbuild/android-arm64@0.18.20': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.28.0': + optional: true + + '@esbuild/android-arm@0.18.20': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-arm@0.28.0': + optional: true + + '@esbuild/android-x64@0.18.20': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/android-x64@0.28.0': + optional: true + + '@esbuild/darwin-arm64@0.18.20': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.28.0': + optional: true + + '@esbuild/darwin-x64@0.18.20': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.28.0': + optional: true + + '@esbuild/freebsd-arm64@0.18.20': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.28.0': + optional: true + + '@esbuild/freebsd-x64@0.18.20': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.28.0': + optional: true + + '@esbuild/linux-arm64@0.18.20': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.28.0': + optional: true + + '@esbuild/linux-arm@0.18.20': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-arm@0.28.0': + optional: true + + '@esbuild/linux-ia32@0.18.20': + optional: true - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + '@esbuild/linux-ia32@0.25.12': + optional: true - '@babel/helper-string-parser@7.29.7': {} + '@esbuild/linux-ia32@0.27.7': + optional: true - '@babel/helper-validator-identifier@7.29.7': {} + '@esbuild/linux-ia32@0.28.0': + optional: true - '@babel/parser@7.29.7': - dependencies: - '@babel/types': 7.29.7 + '@esbuild/linux-loong64@0.18.20': + optional: true - '@babel/types@7.29.7': - dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 + '@esbuild/linux-loong64@0.25.12': + optional: true - '@bcoe/v8-coverage@1.0.2': {} + '@esbuild/linux-loong64@0.27.7': + optional: true - '@esbuild/aix-ppc64@0.27.7': + '@esbuild/linux-loong64@0.28.0': optional: true - '@esbuild/android-arm64@0.27.7': + '@esbuild/linux-mips64el@0.18.20': optional: true - '@esbuild/android-arm@0.27.7': + '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/android-x64@0.27.7': + '@esbuild/linux-mips64el@0.27.7': optional: true - '@esbuild/darwin-arm64@0.27.7': + '@esbuild/linux-mips64el@0.28.0': optional: true - '@esbuild/darwin-x64@0.27.7': + '@esbuild/linux-ppc64@0.18.20': optional: true - '@esbuild/freebsd-arm64@0.27.7': + '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/freebsd-x64@0.27.7': + '@esbuild/linux-ppc64@0.27.7': optional: true - '@esbuild/linux-arm64@0.27.7': + '@esbuild/linux-ppc64@0.28.0': optional: true - '@esbuild/linux-arm@0.27.7': + '@esbuild/linux-riscv64@0.18.20': optional: true - '@esbuild/linux-ia32@0.27.7': + '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/linux-loong64@0.27.7': + '@esbuild/linux-riscv64@0.27.7': optional: true - '@esbuild/linux-mips64el@0.27.7': + '@esbuild/linux-riscv64@0.28.0': optional: true - '@esbuild/linux-ppc64@0.27.7': + '@esbuild/linux-s390x@0.18.20': optional: true - '@esbuild/linux-riscv64@0.27.7': + '@esbuild/linux-s390x@0.25.12': optional: true '@esbuild/linux-s390x@0.27.7': optional: true + '@esbuild/linux-s390x@0.28.0': + optional: true + + '@esbuild/linux-x64@0.18.20': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + '@esbuild/linux-x64@0.27.7': optional: true + '@esbuild/linux-x64@0.28.0': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + '@esbuild/netbsd-arm64@0.27.7': optional: true + '@esbuild/netbsd-arm64@0.28.0': + optional: true + + '@esbuild/netbsd-x64@0.18.20': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + '@esbuild/netbsd-x64@0.27.7': optional: true + '@esbuild/netbsd-x64@0.28.0': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + '@esbuild/openbsd-arm64@0.27.7': optional: true + '@esbuild/openbsd-arm64@0.28.0': + optional: true + + '@esbuild/openbsd-x64@0.18.20': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + '@esbuild/openbsd-x64@0.27.7': optional: true + '@esbuild/openbsd-x64@0.28.0': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + '@esbuild/openharmony-arm64@0.27.7': optional: true + '@esbuild/openharmony-arm64@0.28.0': + optional: true + + '@esbuild/sunos-x64@0.18.20': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + '@esbuild/sunos-x64@0.27.7': optional: true + '@esbuild/sunos-x64@0.28.0': + optional: true + + '@esbuild/win32-arm64@0.18.20': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + '@esbuild/win32-arm64@0.27.7': optional: true + '@esbuild/win32-arm64@0.28.0': + optional: true + + '@esbuild/win32-ia32@0.18.20': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + '@esbuild/win32-ia32@0.27.7': optional: true + '@esbuild/win32-ia32@0.28.0': + optional: true + + '@esbuild/win32-x64@0.18.20': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + '@esbuild/win32-x64@0.27.7': optional: true + '@esbuild/win32-x64@0.28.0': + optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': dependencies: eslint: 9.39.4 @@ -1556,6 +2560,10 @@ snapshots: '@turbo/windows-arm64@2.9.16': optional: true + '@types/better-sqlite3@7.6.13': + dependencies: + '@types/node': 20.19.41 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -1567,6 +2575,15 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/node@20.19.41': + dependencies: + undici-types: 6.21.0 + + '@types/node@22.19.19': + dependencies: + undici-types: 6.21.0 + optional: true + '@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -1658,7 +2675,7 @@ snapshots: '@typescript-eslint/types': 8.60.1 eslint-visitor-keys: 5.0.1 - '@vitest/coverage-v8@3.2.6(vitest@3.2.6)': + '@vitest/coverage-v8@3.2.6(vitest@3.2.6(@types/node@22.19.19)(tsx@4.22.4))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -1673,7 +2690,7 @@ snapshots: std-env: 3.10.0 test-exclude: 7.0.2 tinyrainbow: 2.0.0 - vitest: 3.2.6 + vitest: 3.2.6(@types/node@22.19.19)(tsx@4.22.4) transitivePeerDependencies: - supports-color @@ -1685,13 +2702,21 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.6(vite@7.3.5)': + '@vitest/mocker@3.2.6(vite@7.3.5(@types/node@20.19.41)(tsx@4.22.4))': + dependencies: + '@vitest/spy': 3.2.6 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.5(@types/node@20.19.41)(tsx@4.22.4) + + '@vitest/mocker@3.2.6(vite@7.3.5(@types/node@22.19.19)(tsx@4.22.4))': dependencies: '@vitest/spy': 3.2.6 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.5 + vite: 7.3.5(@types/node@22.19.19)(tsx@4.22.4) '@vitest/pretty-format@3.2.6': dependencies: @@ -1756,6 +2781,23 @@ snapshots: balanced-match@4.0.4: {} + base64-js@1.5.1: {} + + better-sqlite3@12.10.0: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + brace-expansion@1.1.15: dependencies: balanced-match: 1.0.2 @@ -1769,6 +2811,13 @@ snapshots: dependencies: balanced-match: 4.0.4 + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + cac@6.7.14: {} callsites@3.1.0: {} @@ -1788,6 +2837,8 @@ snapshots: check-error@2.1.3: {} + chownr@1.1.4: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -1806,18 +2857,96 @@ snapshots: dependencies: ms: 2.1.3 + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + deep-eql@5.0.2: {} + deep-extend@0.6.0: {} + deep-is@0.1.4: {} + detect-libc@2.1.2: {} + + drizzle-kit@0.31.10: + dependencies: + '@drizzle-team/brocli': 0.10.2 + '@esbuild-kit/esm-loader': 2.6.5 + esbuild: 0.25.12 + tsx: 4.22.4 + + drizzle-orm@0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0): + optionalDependencies: + '@types/better-sqlite3': 7.6.13 + better-sqlite3: 12.10.0 + eastasianwidth@0.2.0: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + es-module-lexer@1.7.0: {} + esbuild@0.18.20: + 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 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + esbuild@0.27.7: optionalDependencies: '@esbuild/aix-ppc64': 0.27.7 @@ -1847,6 +2976,35 @@ snapshots: '@esbuild/win32-ia32': 0.27.7 '@esbuild/win32-x64': 0.27.7 + esbuild@0.28.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.0 + '@esbuild/android-arm': 0.28.0 + '@esbuild/android-arm64': 0.28.0 + '@esbuild/android-x64': 0.28.0 + '@esbuild/darwin-arm64': 0.28.0 + '@esbuild/darwin-x64': 0.28.0 + '@esbuild/freebsd-arm64': 0.28.0 + '@esbuild/freebsd-x64': 0.28.0 + '@esbuild/linux-arm': 0.28.0 + '@esbuild/linux-arm64': 0.28.0 + '@esbuild/linux-ia32': 0.28.0 + '@esbuild/linux-loong64': 0.28.0 + '@esbuild/linux-mips64el': 0.28.0 + '@esbuild/linux-ppc64': 0.28.0 + '@esbuild/linux-riscv64': 0.28.0 + '@esbuild/linux-s390x': 0.28.0 + '@esbuild/linux-x64': 0.28.0 + '@esbuild/netbsd-arm64': 0.28.0 + '@esbuild/netbsd-x64': 0.28.0 + '@esbuild/openbsd-arm64': 0.28.0 + '@esbuild/openbsd-x64': 0.28.0 + '@esbuild/openharmony-arm64': 0.28.0 + '@esbuild/sunos-x64': 0.28.0 + '@esbuild/win32-arm64': 0.28.0 + '@esbuild/win32-ia32': 0.28.0 + '@esbuild/win32-x64': 0.28.0 + escape-string-regexp@4.0.0: {} eslint-config-prettier@9.1.2(eslint@9.39.4): @@ -1925,6 +3083,8 @@ snapshots: esutils@2.0.3: {} + expand-template@2.0.3: {} + expect-type@1.3.0: {} fast-deep-equal@3.1.3: {} @@ -1941,6 +3101,8 @@ snapshots: dependencies: flat-cache: 4.0.1 + file-uri-to-path@1.0.0: {} + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -1958,9 +3120,17 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + fs-constants@1.0.0: {} + fsevents@2.3.3: optional: true + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + github-from-package@0.0.0: {} + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 @@ -1980,6 +3150,8 @@ snapshots: html-escaper@2.0.2: {} + ieee754@1.2.1: {} + ignore@5.3.2: {} ignore@7.0.5: {} @@ -1991,6 +3163,10 @@ snapshots: imurmurhash@0.1.4: {} + inherits@2.0.4: {} + + ini@1.3.8: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -2075,6 +3251,8 @@ snapshots: dependencies: semver: 7.8.1 + mimic-response@3.1.0: {} + minimatch@10.2.5: dependencies: brace-expansion: 5.0.6 @@ -2087,14 +3265,28 @@ snapshots: dependencies: brace-expansion: 2.1.1 + minimist@1.2.8: {} + minipass@7.1.3: {} + mkdirp-classic@0.5.3: {} + ms@2.1.3: {} nanoid@3.3.12: {} + napi-build-utils@2.0.0: {} + natural-compare@1.4.0: {} + node-abi@3.92.0: + dependencies: + semver: 7.8.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2141,14 +3333,49 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.92.0 + pump: 3.0.4 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} prettier@3.8.3: {} + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode@2.3.1: {} + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + rollup@4.61.1: dependencies: '@types/estree': 1.0.9 @@ -2180,6 +3407,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.61.1 fsevents: 2.3.3 + safe-buffer@5.2.1: {} + semver@7.8.1: {} shebang-command@2.0.0: @@ -2192,8 +3421,23 @@ snapshots: signal-exit@4.1.0: {} + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + source-map-js@1.2.1: {} + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + stackback@0.0.2: {} std-env@3.10.0: {} @@ -2210,6 +3454,10 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.2.0 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -2218,6 +3466,8 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} strip-literal@3.1.0: @@ -2228,6 +3478,21 @@ snapshots: dependencies: has-flag: 4.0.0 + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.4 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + test-exclude@7.0.2: dependencies: '@istanbuljs/schema': 0.1.6 @@ -2253,6 +3518,16 @@ snapshots: dependencies: typescript: 5.9.3 + tsx@4.22.4: + dependencies: + esbuild: 0.28.0 + optionalDependencies: + fsevents: 2.3.3 + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + turbo@2.9.16: optionalDependencies: '@turbo/darwin-64': 2.9.16 @@ -2279,17 +3554,21 @@ snapshots: typescript@5.9.3: {} + undici-types@6.21.0: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 - vite-node@3.2.4: + util-deprecate@1.0.2: {} + + vite-node@3.2.4(@types/node@20.19.41)(tsx@4.22.4): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.5 + vite: 7.3.5(@types/node@20.19.41)(tsx@4.22.4) transitivePeerDependencies: - '@types/node' - jiti @@ -2304,7 +3583,28 @@ snapshots: - tsx - yaml - vite@7.3.5: + vite-node@3.2.4(@types/node@22.19.19)(tsx@4.22.4): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.3.5(@types/node@22.19.19)(tsx@4.22.4) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@7.3.5(@types/node@20.19.41)(tsx@4.22.4): dependencies: esbuild: 0.27.7 fdir: 6.5.0(picomatch@4.0.4) @@ -2313,13 +3613,69 @@ snapshots: rollup: 4.61.1 tinyglobby: 0.2.17 optionalDependencies: + '@types/node': 20.19.41 fsevents: 2.3.3 + tsx: 4.22.4 - vitest@3.2.6: + vite@7.3.5(@types/node@22.19.19)(tsx@4.22.4): + dependencies: + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.15 + rollup: 4.61.1 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 22.19.19 + fsevents: 2.3.3 + tsx: 4.22.4 + + vitest@3.2.6(@types/node@20.19.41)(tsx@4.22.4): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.6 + '@vitest/mocker': 3.2.6(vite@7.3.5(@types/node@20.19.41)(tsx@4.22.4)) + '@vitest/pretty-format': 3.2.6 + '@vitest/runner': 3.2.6 + '@vitest/snapshot': 3.2.6 + '@vitest/spy': 3.2.6 + '@vitest/utils': 3.2.6 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.17 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.3.5(@types/node@20.19.41)(tsx@4.22.4) + vite-node: 3.2.4(@types/node@20.19.41)(tsx@4.22.4) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.19.41 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vitest@3.2.6(@types/node@22.19.19)(tsx@4.22.4): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.6 - '@vitest/mocker': 3.2.6(vite@7.3.5) + '@vitest/mocker': 3.2.6(vite@7.3.5(@types/node@22.19.19)(tsx@4.22.4)) '@vitest/pretty-format': 3.2.6 '@vitest/runner': 3.2.6 '@vitest/snapshot': 3.2.6 @@ -2337,9 +3693,11 @@ snapshots: tinyglobby: 0.2.17 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.3.5 - vite-node: 3.2.4 + vite: 7.3.5(@types/node@22.19.19)(tsx@4.22.4) + vite-node: 3.2.4(@types/node@22.19.19)(tsx@4.22.4) why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.19.19 transitivePeerDependencies: - jiti - less @@ -2377,6 +3735,8 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.2.0 + wrappy@1.0.2: {} + yocto-queue@0.1.0: {} zod@3.25.76: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 48f25fe5..748b177f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -15,5 +15,16 @@ catalog: typescript: ^5.7.2 typescript-eslint: ^8.18.1 vitest: ^3.0.0 + # Type packages (dev): @types/node is pinned to the `engines` runtime FLOOR + # (node >=20.11.0), NOT .nvmrc's dev/CI version (22), so a Node-22-only API (e.g. + # `node:sqlite`, which ADR-0021 rejects for needing >=22.5) is a type error rather than + # a runtime break on the supported floor. + '@types/better-sqlite3': ^7.6.13 + '@types/node': ^20.11.0 + drizzle-kit: ^0.31.10 # Runtime zod: ^3.23.0 + # @relavium/db persistence stack — Drizzle ORM (ADR-0005) over better-sqlite3, + # the pinned Node-side SQLite driver (ADR-0021). + drizzle-orm: ^0.45.2 + better-sqlite3: ^12.10.0 From 7a855dc61691f03429bb0a9e56777b45ea8b7927 Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Thu, 4 Jun 2026 19:45:35 +0300 Subject: [PATCH 2/4] build(repo): scaffold the no-vendor-type-across-the-seam lint fence (0.F) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire the @relavium/llm seam fence before Phase 1's first adapter exists, applied to BOTH TS and JS source: two complementary ESLint rules forbid every provider-SDK (@anthropic-ai/sdk, openai, @google/genai) import outside packages/llm/src/adapters/*. no-restricted-imports covers the static forms (bare, subpath, `import type`, and `export … from` re-exports); no-restricted-syntax closes the rest — dynamic `import()`, ANY non-literal computed `import(expr)`, the `import('…').T` type query, and `require()`. The config-file ignore is scoped to repo/package/app-root tooling configs so a source file named `*.config.ts` cannot escape the fence. The fence is lifted only inside the adapter zone. A quarantined fixture exercises all nine forms and assert-fence.mjs asserts each rule family fires its EXACT count (plus a config-named source file stays fenced) — so a partial regression fails the check rather than passing on the remaining errors. Wired as `pnpm lint:fence-check`. Refs: ADR-0011 Co-Authored-By: Claude Opus 4.8 (1M context) --- eslint.config.mjs | 106 ++++++++++++++++-- package.json | 1 + tools/lint-fixtures/assert-fence.mjs | 66 +++++++++++ .../lint-fixtures/forbidden-in-name.config.ts | 12 ++ .../lint-fixtures/forbidden-vendor-import.ts | 37 ++++++ 5 files changed, 214 insertions(+), 8 deletions(-) create mode 100644 tools/lint-fixtures/assert-fence.mjs create mode 100644 tools/lint-fixtures/forbidden-in-name.config.ts create mode 100644 tools/lint-fixtures/forbidden-vendor-import.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index 8afec1fc..098f542a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,6 +3,62 @@ import js from '@eslint/js'; import tseslint from 'typescript-eslint'; import prettier from 'eslint-config-prettier'; +/** + * The no-vendor-type-across-the-`@relavium/llm`-seam fence (0.F). Provider SDKs may be + * imported ONLY inside `packages/llm/src/adapters/*`; everywhere else — the engine + * (`@relavium/core`) and every surface — depends on Relavium/Zod seam types only + * (ADR-0011, code-style-typescript.md §module-boundaries). + * + * The fence is TWO complementary rules applied to BOTH TS and JS source (TypeScript-first, + * but a stray `.js`/`.mjs` must not be an escape hatch), because a vendor SDK — or a + * vendor *type*, the core failure mode — can cross the seam through several syntaxes: + * - `no-restricted-imports` covers the STATIC forms: `import …`, `export … from …`, + * `export * from …`, the subpath `openai/resources` (via `patterns`), and + * `import type { … } from …` (the @typescript-eslint variant, used for TS, polices + * type-only imports too). + * - `no-restricted-syntax` covers the forms `no-restricted-imports` cannot see: dynamic + * `import('openai')`, ANY non-literal dynamic `import(expr)` (a static check cannot see + * through a computed specifier, so it is banned outright outside adapters), the + * import-type query `import('openai').X` (a pure type leak), and `require('openai')`. + * All are lifted together only inside the adapter zone, and every form is exercised by the + * fixture under `tools/lint-fixtures/` (asserted live by `assert-fence.mjs`). + */ +const SEAM_VENDOR_SDKS = ['@anthropic-ai/sdk', 'openai', '@google/genai']; +const SEAM_MESSAGE = + 'Provider SDKs must not cross the @relavium/llm seam — import them only inside ' + + 'packages/llm/src/adapters/* (ADR-0011). The engine and surfaces use Relavium/Zod ' + + 'seam types only, never a vendor SDK type.'; +const SEAM_DYNAMIC_MESSAGE = + 'Dynamic import() with a non-literal specifier is not allowed outside ' + + 'packages/llm/src/adapters/* — a computed specifier can smuggle a provider SDK past the ' + + '@relavium/llm seam (ADR-0011). Use a static import of a Relavium/seam module; if a ' + + 'computed import is genuinely needed, justify it with an explicit eslint-disable.'; + +// Shared options for both the @typescript-eslint (TS) and core (JS) no-restricted-imports. +const seamImportOptions = { + paths: SEAM_VENDOR_SDKS.map((name) => ({ name, message: SEAM_MESSAGE })), + patterns: [{ group: SEAM_VENDOR_SDKS.map((name) => `${name}/*`), message: SEAM_MESSAGE }], +}; +const seamImportEntry = /** @type {const} */ (['error', seamImportOptions]); + +// A regex matching each vendor specifier and any subpath (slashes escaped for the esquery +// attribute literal). Drives the AST selectors that catch the non-static import forms. +const SEAM_SPECIFIER_RE = '^(@anthropic-ai\\/sdk|openai|@google\\/genai)(\\/.*)?$'; +const seamSyntaxRules = /** @type {const} */ ([ + 'error', + // dynamic import of a vendor specifier: `import('openai')` + { selector: `ImportExpression[source.value=/${SEAM_SPECIFIER_RE}/]`, message: SEAM_MESSAGE }, + // ANY non-literal dynamic import — a computed specifier evades the static check above. + { selector: "ImportExpression:not([source.type='Literal'])", message: SEAM_DYNAMIC_MESSAGE }, + // import-type query (a pure type leak): `import('openai').OpenAI` + { selector: `TSImportType Literal[value=/${SEAM_SPECIFIER_RE}/]`, message: SEAM_MESSAGE }, + // CommonJS interop: `require('openai')` + { + selector: `CallExpression[callee.name='require'] > Literal[value=/${SEAM_SPECIFIER_RE}/]`, + message: SEAM_MESSAGE, + }, +]); + /** * Single root ESLint flat config shared by every package (no per-package config * without a justified override). ESLint owns correctness; Prettier owns formatting @@ -10,22 +66,26 @@ import prettier from 'eslint-config-prettier'; * * Type-aware rules (`recommendedTypeChecked`) are scoped to the **TS family** * (`.ts`/`.tsx`/`.mts`/`.cts`) and wired to `projectService`, so they have a program - * for every TS variant — including the `.tsx` that arrives with `packages/ui` (0.H). - * Attaching type-aware rules to a file with no project crashes ESLint, so JS files get - * `disableTypeChecked`. The no-vendor-type-across-the-`@relavium/llm`-seam import fence - * (built on the built-in `no-restricted-imports`, per the standard) is wired in 0.F. + * for every TS variant — including the `.tsx` that arrives with `packages/ui`. Attaching + * type-aware rules to a file with no project crashes ESLint, so JS files get + * `disableTypeChecked`. The seam fence above is applied to both families. */ export default tseslint.config( { - // Not source: build output, caches, deps, and tooling/config files (at any depth — - // a bare `*.config.*` would only match the repo root, leaving nested per-package - // config files to be type-aware-linted with no project, which crashes ESLint). + // Not source: build output, caches, and deps. ignores: [ '**/dist/**', '**/.turbo/**', '**/coverage/**', '**/node_modules/**', - '**/*.config.*', + // Tooling/config files live at the repo, package, or app ROOT (never under a package + // `src/`) and are not in any tsconfig, so type-aware linting would crash on them. + // Ignore them by their ROOT locations only — a broad `**/*.config.*` would also + // swallow a source file named `*.config.ts` and silently exempt it from the seam + // fence. New nested config locations are added here explicitly, by design. + '*.config.*', + 'packages/*/*.config.*', + 'apps/*/*.config.*', ], }, js.configs.recommended, @@ -49,6 +109,10 @@ export default tseslint.config( // CLAUDE.md non-negotiables, pinned as errors across the whole TS family. '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-floating-promises': 'error', + // The seam fence (0.F): static specifiers (incl. `import type`) + the + // dynamic/import-type/require forms that evade the first. + '@typescript-eslint/no-restricted-imports': seamImportEntry, + 'no-restricted-syntax': seamSyntaxRules, }, }, { @@ -56,6 +120,32 @@ export default tseslint.config( // require a TS program and would otherwise crash ESLint on a file with no project. files: ['**/*.{js,jsx,mjs,cjs}'], extends: [tseslint.configs.disableTypeChecked], + rules: { + // The seam fence on JS source too. JS has no `import type`, so the core + // `no-restricted-imports` is the right variant here. + 'no-restricted-imports': seamImportEntry, + 'no-restricted-syntax': seamSyntaxRules, + }, + }, + { + // The seam's ONE legal zone: provider SDKs are imported only inside the adapters, + // which translate vendor shapes to Relavium/Zod seam types (ADR-0011). Every seam + // rule (both variants + the syntax rule) is lifted here and nowhere else. + files: ['packages/llm/src/adapters/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'], + rules: { + '@typescript-eslint/no-restricted-imports': 'off', + 'no-restricted-imports': 'off', + 'no-restricted-syntax': 'off', + }, + }, + { + // Quarantined lint fixtures (0.F): files that pull a forbidden vendor SDK across the + // seam through every syntax on purpose, so `tools/lint-fixtures/assert-fence.mjs` can + // prove the fence is airtight. They belong to no tsconfig, so type-aware linting and + // the project service are off here — the seam rules (not type-aware) still fire. + files: ['tools/lint-fixtures/**/*.{ts,mts,cts}'], + extends: [tseslint.configs.disableTypeChecked], + languageOptions: { parserOptions: { projectService: false } }, }, // Prettier compatibility must come last — it turns off all formatting rules. prettier, diff --git a/package.json b/package.json index 3728fb4d..5283d616 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "test": "turbo run test", "coverage": "vitest run --coverage", "ci": "turbo run lint typecheck test", + "lint:fence-check": "node tools/lint-fixtures/assert-fence.mjs", "format": "prettier --write .", "format:check": "prettier --check ." }, diff --git a/tools/lint-fixtures/assert-fence.mjs b/tools/lint-fixtures/assert-fence.mjs new file mode 100644 index 00000000..5c5aa680 --- /dev/null +++ b/tools/lint-fixtures/assert-fence.mjs @@ -0,0 +1,66 @@ +/** + * Assert the no-vendor-type-across-the-`@relavium/llm`-seam fence (0.F) is AIRTIGHT. + * + * Lints the quarantined fixtures with the repo ESLint config and asserts: + * 1. `forbidden-vendor-import.ts` fires EXACTLY the expected count per seam rule — the + * fixture is the precise spec, so a partial regression (a broken `patterns` group, a + * dropped syntax selector) changes a count and fails here rather than passing on the + * remaining errors. + * 2. `forbidden-in-name.config.ts` (a `*.config.ts` SOURCE file) is NOT ignored and still + * trips the seam rule — proving the config-file ignore can't be used as an escape hatch. + * + * Exits non-zero so CI fails loudly the moment any seam path stops being policed. Run from + * the repo root: node tools/lint-fixtures/assert-fence.mjs + */ +import { ESLint } from 'eslint'; + +const MAIN = 'tools/lint-fixtures/forbidden-vendor-import.ts'; +const CONFIG_NAMED = 'tools/lint-fixtures/forbidden-in-name.config.ts'; + +const STATIC_RULE = '@typescript-eslint/no-restricted-imports'; // bare, subpath, type-only, 2× re-export +const SYNTAX_RULE = 'no-restricted-syntax'; // dynamic, non-literal dynamic, import-type query, require +const EXPECT_STATIC = 5; +const EXPECT_SYNTAX = 4; + +const eslint = new ESLint(); +const results = await eslint.lintFiles([MAIN, CONFIG_NAMED]); +const resultFor = (suffix) => results.find((r) => r.filePath.endsWith(suffix)); +const seamErrors = (res, ruleId) => + (res?.messages ?? []).filter((m) => m.ruleId === ruleId && m.severity === 2).length; + +const fail = (msg, res) => { + console.error(`✗ ${msg}`); + for (const m of res?.messages ?? []) { + console.error(` L${m.line} [${m.ruleId ?? 'fatal'}] ${m.message}`); + } + process.exit(1); +}; + +// 1. The main fixture must fire EXACTLY the expected counts. +const main = resultFor('/forbidden-vendor-import.ts'); +const staticHits = seamErrors(main, STATIC_RULE); +const syntaxHits = seamErrors(main, SYNTAX_RULE); +if (staticHits !== EXPECT_STATIC || syntaxHits !== EXPECT_SYNTAX) { + fail( + `Seam fence count drift on ${MAIN}: ${STATIC_RULE} ${staticHits}/${EXPECT_STATIC}, ` + + `${SYNTAX_RULE} ${syntaxHits}/${EXPECT_SYNTAX} — a vendor-import syntax stopped (or ` + + 'started) being policed (ADR-0011 seam regression).', + main, + ); +} + +// 2. The config-named SOURCE file must NOT be ignored — the seam rule must still fire. +const cfg = resultFor('/forbidden-in-name.config.ts'); +if (!cfg || seamErrors(cfg, STATIC_RULE) < 1) { + fail( + `Config-named source file ${CONFIG_NAMED} escaped the seam fence — the config-file ` + + 'ignore was re-broadened and now swallows source files (ADR-0011 seam regression).', + cfg, + ); +} + +console.log( + `✓ Seam fence airtight: ${STATIC_RULE} ${staticHits}/${EXPECT_STATIC}, ` + + `${SYNTAX_RULE} ${syntaxHits}/${EXPECT_SYNTAX} on the fixture; ` + + 'config-named source file still fenced.', +); diff --git a/tools/lint-fixtures/forbidden-in-name.config.ts b/tools/lint-fixtures/forbidden-in-name.config.ts new file mode 100644 index 00000000..197bf87a --- /dev/null +++ b/tools/lint-fixtures/forbidden-in-name.config.ts @@ -0,0 +1,12 @@ +/** + * QUARANTINED LINT FIXTURE — regression guard for the config-file ignore. + * + * A SOURCE file named `*.config.ts` must NOT escape the seam fence: the config-file ignore + * is scoped to repo/package/app-root tooling configs only, never a `src/`-style source + * file. `assert-fence.mjs` lints this file and asserts the seam rule still fires on it — if + * the ignore is ever re-broadened to a global config glob, this file goes silent and the + * check fails. The specifier intentionally does not resolve. + */ +import '@anthropic-ai/sdk'; + +export const x = 0; diff --git a/tools/lint-fixtures/forbidden-vendor-import.ts b/tools/lint-fixtures/forbidden-vendor-import.ts new file mode 100644 index 00000000..783ac56f --- /dev/null +++ b/tools/lint-fixtures/forbidden-vendor-import.ts @@ -0,0 +1,37 @@ +/** + * QUARANTINED LINT FIXTURE — not real source, not part of any build or tsconfig. + * + * It pulls provider SDKs across the @relavium/llm seam from a NON-adapter zone through + * EVERY syntax a real bypass could use. `tools/lint-fixtures/assert-fence.mjs` lints this + * file and asserts BOTH seam rules fire EXACTLY their expected counts — so this file is + * the precise spec of what the fence guarantees. If any line below stops failing lint, the + * seam guard has regressed (ADR-0011). + * + * The specifiers intentionally do not resolve (the SDKs are not installed); both seam + * rules are syntactic, so resolution is irrelevant. + */ + +// --- Static forms — caught by @typescript-eslint/no-restricted-imports (5) --- +// Bare specifier (`paths`). +import '@anthropic-ai/sdk'; +// Subpath (`patterns` group). +import 'openai/resources'; +// Type-only import — a vendor TYPE leak via the `import type … from` form. +import type { GenerateContentResponse } from '@google/genai'; +// Re-export, star form. +export * from '@anthropic-ai/sdk'; +// Re-export, named form. +export { OpenAI } from 'openai'; + +// --- Non-static forms — caught by no-restricted-syntax (4) --- +// Dynamic import of a literal vendor specifier. +export const lazy = () => import('openai'); +// Non-literal dynamic import — a computed specifier that evades the literal check. +const computed = '@anthropic-ai/sdk'; +export const sneaky2 = () => import(computed); +// Import-type query — a pure type leak via the `import('…').T` operator. +export type Leaked2 = import('@anthropic-ai/sdk').Anthropic; +// CommonJS interop. +export const sneaky = require('@google/genai'); + +export type Leaked = GenerateContentResponse; From 671beb659290f1c14e423ef3874c44110d3e3bfe Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Thu, 4 Jun 2026 19:45:35 +0300 Subject: [PATCH 3/4] ci(repo): add the GitHub Actions lint/typecheck/test pipeline (0.G) Add ci.yml running `pnpm turbo run lint typecheck test` (+ build, format:check, and the seam fence-check) on every push and PR with a frozen lockfile and the Turborepo cache, plus a CI-only strict-peer-dependency gate. The GitHub Actions .turbo cache is the always-on layer (no-change re-runs hit); the Vercel-style remote cache is opt-in via secrets. Reserves the Phase-1 conformance and nightly live-API lanes as commented anchors. The `ci` job is the required check; branch protection is set in repo settings. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 130 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..165eefb3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,130 @@ +# Relavium CI — the green gate every push and PR must pass (Phase 0 · 0.G). +# +# Runs lint + typecheck + test across every workspace in dependency order with the +# Turborepo cache, plus the formatting check and the no-vendor-type-across-the-seam fence. +# +# Branch protection (set in the GitHub repo settings, not here): the `ci` job below is the +# REQUIRED status check to merge into `main`. The `peer-dep-gate` job is advisory until the +# surface packages land their peers in Phase 1. +# +# Caching: the always-on layer is the GitHub Actions `.turbo` cache (restored/saved below), +# which makes a no-change re-run a Turborepo cache hit — the M0 "demonstrably hitting" +# criterion. The Vercel-style Turborepo REMOTE cache (cross-runner sharing) is opt-in: add +# repo secrets `TURBO_TOKEN` and `TURBO_TEAM` and Turbo picks them up automatically below. +name: CI + +on: + push: + branches: [main, development] + pull_request: + +# Cancel superseded runs on the same ref so CI tracks the latest push. +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + # Picked up by Turbo for remote caching when the secrets are configured; harmless if empty. + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + +jobs: + ci: + name: lint · typecheck · test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up pnpm + uses: pnpm/action-setup@v4 # version comes from the root package.json `packageManager` + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: pnpm + + - name: Install (frozen lockfile) + run: pnpm install --frozen-lockfile + + - name: Restore Turborepo cache + uses: actions/cache@v4 + with: + path: .turbo + key: turbo-${{ runner.os }}-${{ github.sha }} + restore-keys: | + turbo-${{ runner.os }}- + + # The required gate: lint, typecheck, and test every workspace, then build. + - name: Lint, typecheck, test + run: pnpm turbo run lint typecheck test + # Not redundant with typecheck: `build` runs each package's emit (tsconfig.build.json + # → dist), which `typecheck` (tsconfig.json --noEmit) does not — it catches + # emit-only failures and proves the published artifact compiles. + - name: Build + run: pnpm turbo run build + + # Formatting is run, not argued (code-style-typescript.md). + - name: Format check + run: pnpm format:check + + # Prove the no-vendor-type-across-the-@relavium/llm-seam fence is live (0.F). + - name: Seam fence is enforced + run: pnpm lint:fence-check + + # CI-only strict peer-dependency gate (.npmrc keeps this OFF for local installs so fresh + # checkouts resolve while the surface packages' peers are not all in the tree yet). Catch + # peer drift here without breaking local dev. + peer-dep-gate: + name: strict peer-dependency check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: pnpm + - name: Install with strict peers + run: pnpm install --frozen-lockfile --config.strict-peer-dependencies=true + +# --- Reserved Phase-1 lanes (TODO: enable with the first provider adapter) ------------ +# The per-provider conformance suite and the nightly live-API lane land WITH the adapters +# in Phase 1 (testing.md); only their CI slots are reserved here so the testing standard +# maps cleanly onto lanes from day one. Do not enable until `packages/llm` exists. +# +# conformance: +# name: provider conformance (fixtures) +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - uses: pnpm/action-setup@v4 +# - uses: actions/setup-node@v4 +# with: { node-version-file: .nvmrc, cache: pnpm } +# - run: pnpm install --frozen-lockfile +# - run: pnpm turbo run test:conformance # fixture mode — no network, no keys +# +# Nightly live-API lane — runs the conformance suite against real providers using keys +# from CI secrets. Separate workflow trigger so it never gates a PR: +# # on: +# # schedule: +# # - cron: '0 7 * * *' # 07:00 UTC nightly +# live-api: +# name: provider conformance (live, nightly) +# if: github.event_name == 'schedule' +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - uses: pnpm/action-setup@v4 +# - uses: actions/setup-node@v4 +# with: { node-version-file: .nvmrc, cache: pnpm } +# - run: pnpm install --frozen-lockfile +# - run: pnpm turbo run test:conformance:live +# env: +# ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} +# OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} +# GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} +# -------------------------------------------------------------------------------------- From 6492ad407f950d91acd741f90045d56abfbc694f Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Thu, 4 Jun 2026 20:41:23 +0300 Subject: [PATCH 4/4] =?UTF-8?q?fix(repo):=20address=20PR=20#3=20review=20?= =?UTF-8?q?=E2=80=94=20pin=20CI=20actions=20to=20SHAs,=20Windows-safe=20fe?= =?UTF-8?q?nce-check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ci: pin every third-party action to a full commit SHA (with a `# vX.Y.Z` tracking comment) and set `persist-credentials: false` on checkout — closes the SonarCloud security hotspot, so a moved tag can no longer inject unreviewed code. - tools/lint-fixtures/assert-fence.mjs: match ESLint results by `path.basename` instead of an `endsWith('/…')` suffix, so the fence-check works on Windows (`\`). - packages/db: `inList` uses `String.prototype.replaceAll` for clarity (output is identical — verified no migration drift); fix a README grammar nit. Skipped, with reason: - boolean defaults keep `sql`1`/`sql`0``: drizzle's `.default(true)` emits `DEFAULT true` in the DDL, which diverges from the canonical `DEFAULT 1/0` (database-schema.md). The explicit literal is intentional for byte-fidelity, and the `boolFlag(name, def: boolean)` wrapper keeps the call site type-safe. - no CHECK added on `messages.role`: the canonical DDL declares none; adding one here would drift from the single-canonical-home reference (it belongs in database-schema.md first, as a deliberate contract change). Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 37 +++++++++++++++++----------- packages/db/README.md | 2 +- packages/db/src/schema.ts | 2 +- tools/lint-fixtures/assert-fence.mjs | 10 +++++--- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 165eefb3..8901849b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,9 @@ # which makes a no-change re-run a Turborepo cache hit — the M0 "demonstrably hitting" # criterion. The Vercel-style Turborepo REMOTE cache (cross-runner sharing) is opt-in: add # repo secrets `TURBO_TOKEN` and `TURBO_TEAM` and Turbo picks them up automatically below. +# +# Third-party actions are pinned to a full commit SHA (the `# vX.Y.Z` comment tracks the +# human-readable release) so a moved tag can't inject unreviewed code; bumps are deliberate. name: CI on: @@ -36,13 +39,16 @@ jobs: name: lint · typecheck · test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false - name: Set up pnpm - uses: pnpm/action-setup@v4 # version comes from the root package.json `packageManager` + # pnpm version comes from the root package.json `packageManager`. + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.4.0 - name: Set up Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version-file: .nvmrc cache: pnpm @@ -51,7 +57,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Restore Turborepo cache - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: .turbo key: turbo-${{ runner.os }}-${{ github.sha }} @@ -82,9 +88,11 @@ jobs: name: strict peer-dependency check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false + - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.4.0 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version-file: .nvmrc cache: pnpm @@ -94,15 +102,16 @@ jobs: # --- Reserved Phase-1 lanes (TODO: enable with the first provider adapter) ------------ # The per-provider conformance suite and the nightly live-API lane land WITH the adapters # in Phase 1 (testing.md); only their CI slots are reserved here so the testing standard -# maps cleanly onto lanes from day one. Do not enable until `packages/llm` exists. +# maps cleanly onto lanes from day one. Do not enable until `packages/llm` exists — and +# pin each action to a commit SHA (as above) when uncommenting. # # conformance: # name: provider conformance (fixtures) # runs-on: ubuntu-latest # steps: -# - uses: actions/checkout@v4 -# - uses: pnpm/action-setup@v4 -# - uses: actions/setup-node@v4 +# - uses: actions/checkout@ # pin on enable +# - uses: pnpm/action-setup@ +# - uses: actions/setup-node@ # with: { node-version-file: .nvmrc, cache: pnpm } # - run: pnpm install --frozen-lockfile # - run: pnpm turbo run test:conformance # fixture mode — no network, no keys @@ -117,9 +126,9 @@ jobs: # if: github.event_name == 'schedule' # runs-on: ubuntu-latest # steps: -# - uses: actions/checkout@v4 -# - uses: pnpm/action-setup@v4 -# - uses: actions/setup-node@v4 +# - uses: actions/checkout@ # pin on enable +# - uses: pnpm/action-setup@ +# - uses: actions/setup-node@ # with: { node-version-file: .nvmrc, cache: pnpm } # - run: pnpm install --frozen-lockfile # - run: pnpm turbo run test:conformance:live diff --git a/packages/db/README.md b/packages/db/README.md index d7d75651..1b2d893f 100644 --- a/packages/db/README.md +++ b/packages/db/README.md @@ -18,7 +18,7 @@ later. The package ships: `better-sqlite3` ([ADR-0021](../../docs/decisions/0021-node-sqlite-driver-better-sqlite3.md)). - A **smoke test** that applies every migration to a fresh DB and round-trips a row. -It depends only on `@relavium/shared` (the contract enums its CHECKs reuse) and Drizzle. +It depends only on `@relavium/shared` (the contract enums that its CHECKs reuse) and Drizzle. ## Conventions (from the canonical DDL) diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index feceabff..df3d4ddb 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -55,7 +55,7 @@ const boolFlag = (name: string, def: boolean) => * `as const` constants (never user input); the single-quote escape is belt-and-suspenders * so the helper is safe even if a value with a quote is ever passed. */ const inList = (values: readonly string[]) => - sql.raw(values.map((v) => `'${v.replace(/'/g, "''")}'`).join(', ')); + sql.raw(values.map((v) => `'${v.replaceAll("'", "''")}'`).join(', ')); // --- 1. llm_providers (no FKs) --- diff --git a/tools/lint-fixtures/assert-fence.mjs b/tools/lint-fixtures/assert-fence.mjs index 5c5aa680..f494d657 100644 --- a/tools/lint-fixtures/assert-fence.mjs +++ b/tools/lint-fixtures/assert-fence.mjs @@ -12,6 +12,8 @@ * Exits non-zero so CI fails loudly the moment any seam path stops being policed. Run from * the repo root: node tools/lint-fixtures/assert-fence.mjs */ +import { basename } from 'node:path'; + import { ESLint } from 'eslint'; const MAIN = 'tools/lint-fixtures/forbidden-vendor-import.ts'; @@ -24,7 +26,9 @@ const EXPECT_SYNTAX = 4; const eslint = new ESLint(); const results = await eslint.lintFiles([MAIN, CONFIG_NAMED]); -const resultFor = (suffix) => results.find((r) => r.filePath.endsWith(suffix)); +// Match by basename (not an `endsWith('/…')` suffix) so it works on Windows, where +// `filePath` uses `\` separators. +const resultFor = (name) => results.find((r) => basename(r.filePath) === name); const seamErrors = (res, ruleId) => (res?.messages ?? []).filter((m) => m.ruleId === ruleId && m.severity === 2).length; @@ -37,7 +41,7 @@ const fail = (msg, res) => { }; // 1. The main fixture must fire EXACTLY the expected counts. -const main = resultFor('/forbidden-vendor-import.ts'); +const main = resultFor('forbidden-vendor-import.ts'); const staticHits = seamErrors(main, STATIC_RULE); const syntaxHits = seamErrors(main, SYNTAX_RULE); if (staticHits !== EXPECT_STATIC || syntaxHits !== EXPECT_SYNTAX) { @@ -50,7 +54,7 @@ if (staticHits !== EXPECT_STATIC || syntaxHits !== EXPECT_SYNTAX) { } // 2. The config-named SOURCE file must NOT be ignored — the seam rule must still fire. -const cfg = resultFor('/forbidden-in-name.config.ts'); +const cfg = resultFor('forbidden-in-name.config.ts'); if (!cfg || seamErrors(cfg, STATIC_RULE) < 1) { fail( `Config-named source file ${CONFIG_NAMED} escaped the seam fence — the config-file ` +