From dbcb49dd12dd9b084a48960ec7ed4c8cb5eab160 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 25 Sep 2026 00:01:33 -0700 Subject: [PATCH 1/9] test: add a real-Postgres harness for end-to-end suites in tests/ Each suite gets its own throwaway database with Interchange's control plane and this package's migrations applied, and a createArtifactRoutes app mounted for a seeded tenant principal behind the platform's real grant evaluator. Creating and dropping those databases goes through the same destructive-test gate as the src suite. bun run test now runs tests/ alongside src/, and tsconfig includes tests/ in place of the scripts/ directory that no longer exists. --- CONTRIBUTING.md | 7 ++ bun.lock | 1 + bunfig.toml | 2 +- package.json | 7 +- tests/lib/db-harness.ts | 140 ++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 2 +- 6 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 tests/lib/db-harness.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9d4eb1f..820be4a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,6 +39,13 @@ ephemeral database (`artifact_core`, or any name ending in `_test`). Without bot suite throws before mutating. The gate itself is pure URL/env parsing and is covered by unit tests that do not need Postgres. +End-to-end suites live in `tests/`. `tests/lib/db-harness.ts` creates a fresh +`artifact__test` database per suite on the `ARTIFACT_DATABASE_URL` server, +applies Interchange's `runMigrations` and `runArtifactMigrations`, and drops it +afterwards. `artifactApp` mounts `createArtifactRoutes` for a seeded tenant +principal, authorized by the platform's real `createRequireGrant` over the +database's `grant` table. `bun run test` runs `src/` and `tests/`. + ## The reference host is the acceptance suite, not a demo `examples/reference-host` mounts the package on a real `@intx/hub-api` app against a diff --git a/bun.lock b/bun.lock index cfa6ef3..dba182a 100644 --- a/bun.lock +++ b/bun.lock @@ -13,6 +13,7 @@ "@intx/authz": "0.4.0", "@intx/db": "0.4.0", "@intx/hub-api": "0.4.0", + "@intx/hub-common": "0.4.0", "@intx/types": "0.4.0", "@types/bun": "1.1.14", "@types/node": "22.10.5", diff --git a/bunfig.toml b/bunfig.toml index a1a95df..350b164 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -16,4 +16,4 @@ coverageThreshold = 0.8 # schema.ts is pure declarations — its "functions" are drizzle index/column # callbacks the coverage instrumentation cannot attribute, not logic. -coveragePathIgnorePatterns = ["src/schema.ts", "src/test-helpers.ts"] +coveragePathIgnorePatterns = ["src/schema.ts", "src/test-helpers.ts", "tests/lib"] diff --git a/package.json b/package.json index 29956b2..0349faa 100644 --- a/package.json +++ b/package.json @@ -56,8 +56,8 @@ "typecheck": "tsc --noEmit", "build": "rm -rf dist && tsc -p tsconfig.build.json", "prepack": "bun run build", - "test": "bun test src", - "test:coverage": "bun test --coverage src", + "test": "bun test src tests", + "test:coverage": "bun test --coverage src tests", "test:acceptance": "bun run build && bun test --cwd examples/reference-host" }, "dependencies": { @@ -91,7 +91,8 @@ "@intx/hub-api": "0.4.0", "@intx/authz": "0.4.0", "@intx/agent": "0.4.0", - "@intx/db": "0.4.0" + "@intx/db": "0.4.0", + "@intx/hub-common": "0.4.0" }, "overrides": { "drizzle-orm": "0.45.2" diff --git a/tests/lib/db-harness.ts b/tests/lib/db-harness.ts new file mode 100644 index 0000000..cf588de --- /dev/null +++ b/tests/lib/db-harness.ts @@ -0,0 +1,140 @@ +// Real-Postgres harness for the tests/ suites: a fresh database per suite with +// Interchange's control plane and this package's migrations applied, and a +// mounted artifact app for a seeded tenant principal. +import { randomUUID } from "node:crypto"; +import { + createDB, + createGrantStore, + runMigrations, + schema as intx, + type DBConfig, +} from "@intx/db"; +import { createRequireGrant, type TenantEnv } from "@intx/hub-api"; +import { generateId } from "@intx/hub-common"; +import { Hono } from "hono"; +import postgres from "postgres"; +import { + createArtifactRoutes, + InlineContentStore, + runArtifactMigrations, + type ContentStore, +} from "../../src/index.js"; +import { + assertDestructiveArtifactTestsAllowed, + databaseConfig, + DATABASE_URL, +} from "../../src/test-helpers.js"; + +type Tenant = typeof intx.tenant.$inferSelect; +type Principal = typeof intx.principal.$inferSelect; +type HostDb = ReturnType["db"]; + +export type Actor = { tenant: Tenant; principal: Principal }; + +export type TestDb = { + db: HostDb; + config: DBConfig; + close: () => Promise; +}; + +async function admin(run: (sql: postgres.Sql) => Promise): Promise { + const sql = postgres(DATABASE_URL, { max: 1, onnotice: () => undefined }); + try { + return await run(sql); + } finally { + await sql.end(); + } +} + +/** Creates `artifact__test`, migrates it, and drops it on `close`. */ +export async function createTestDb(): Promise { + assertDestructiveArtifactTestsAllowed(DATABASE_URL); + const name = `artifact_${randomUUID().replaceAll("-", "").slice(0, 12)}_test`; + await admin((sql) => sql.unsafe(`CREATE DATABASE "${name}"`)); + const server = databaseConfig(DATABASE_URL); + const config: DBConfig = { + host: server.host, + port: server.port, + user: server.user, + password: server.password, + database: name, + }; + await runMigrations(config, { schema: "public" }); + await runArtifactMigrations(config, { schema: "public" }); + const handle = createDB(config); + return { + db: handle.db, + config, + close: async () => { + await handle.close(); + await admin((sql) => sql.unsafe(`DROP DATABASE IF EXISTS "${name}" WITH (FORCE)`)); + }, + }; +} + +/** A tenant with one active user principal, allowed to create artifacts. */ +export async function seedActor(db: HostDb, slug: string): Promise { + const [tenant] = await db + .insert(intx.tenant) + .values({ id: generateId("tenant"), name: slug, slug, domain: `${slug}.example` }) + .returning(); + const [principal] = await db + .insert(intx.principal) + .values({ + id: generateId("principal"), + tenantId: tenant!.id, + kind: "user", + refId: `user-${slug}`, + status: "active", + }) + .returning(); + await grant(db, { tenant: tenant!, principal: principal! }, "artifact:*", "create"); + return { tenant: tenant!, principal: principal! }; +} + +export async function grant( + db: HostDb, + actor: Actor, + resource: string, + action: string, +): Promise { + await db.insert(intx.grant).values({ + id: generateId("grant"), + tenantId: actor.tenant.id, + principalId: actor.principal.id, + roleId: null, + resource, + action, + effect: "allow", + origin: "system", + conditions: null, + }); +} + +/** + * `createArtifactRoutes` mounted at `/api` for `actor`, authorized by the + * platform's real `createRequireGrant` over the database's `grant` table. + */ +export function artifactApp( + db: HostDb, + actor: Actor, + contentStore: ContentStore = InlineContentStore, +): Hono { + const app = new Hono(); + app.use("*", async (c, next) => { + c.set("tenant", actor.tenant); + c.set("principal", actor.principal); + await next(); + }); + return app.route( + "/api", + createArtifactRoutes({ + db, + contentStore, + requireGrant: createRequireGrant({ + grantStore: createGrantStore(db), + conditionRegistry: {}, + }), + }), + ); +} diff --git a/tsconfig.json b/tsconfig.json index ff999de..80acaa9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,5 +10,5 @@ "noEmit": true, "types": ["node", "@types/bun"] }, - "include": ["src", "scripts"] + "include": ["src", "tests"] } From 7d4e73a26bd18f35985ebd55b1e9102d0ea4c398 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 25 Sep 2026 00:01:33 -0700 Subject: [PATCH 2/9] test: add an upload, version, download round trip on a filesystem store A file uploaded through the mounted app into a disk-backed ContentStore downloads byte-for-byte, at version 1 and again after a metadata-only revision creates version 2. Downloading the older version of a stored file is refused, since stored bytes are not versioned. --- tests/file-round-trip.test.ts | 61 +++++++++++++++++++++++++++++++++++ tests/lib/fs-content-store.ts | 37 +++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 tests/file-round-trip.test.ts create mode 100644 tests/lib/fs-content-store.ts diff --git a/tests/file-round-trip.test.ts b/tests/file-round-trip.test.ts new file mode 100644 index 0000000..0f0a041 --- /dev/null +++ b/tests/file-round-trip.test.ts @@ -0,0 +1,61 @@ +import { afterAll, beforeAll, describe, expect, test } from "bun:test"; +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import type { Hono } from "hono"; +import type { TenantEnv } from "@intx/hub-api"; +import { artifactApp, createTestDb, grant, seedActor, type TestDb } from "./lib/db-harness.js"; +import { createFsContentStore } from "./lib/fs-content-store.js"; + +let testDb: TestDb; +let dir: string; +let app: Hono; + +beforeAll(async () => { + testDb = await createTestDb(); + dir = await mkdtemp(join(tmpdir(), "artifact-store-")); + const actor = await seedActor(testDb.db, "acme"); + await grant(testDb.db, actor, "artifact:*", "write"); + app = artifactApp(testDb.db, actor, createFsContentStore(dir)); +}); + +afterAll(async () => { + await testDb.close(); + await rm(dir, { recursive: true, force: true }); +}); + +async function download(id: string, version?: number): Promise { + const query = version === undefined ? "" : `?version=${version}`; + return await app.request(`/api/artifacts/${id}/download${query}`); +} + +describe("upload, version, download on a filesystem ContentStore", () => { + test("the uploaded bytes come back unchanged across a new version", async () => { + const bytes = new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x00, 0xff, 0x10, 0x80]); + const form = new FormData(); + form.append("files", new File([bytes], "report.pdf", { type: "application/pdf" })); + const uploaded = await app.request("/api/artifacts/upload", { method: "POST", body: form }); + expect(uploaded.status).toBe(201); + const { artifacts } = (await uploaded.json()) as { artifacts: { id: string }[] }; + const id = artifacts[0]!.id; + + const v1 = await download(id, 1); + expect(v1.status).toBe(200); + expect(new Uint8Array(await v1.arrayBuffer())).toEqual(bytes); + + const revised = await app.request(`/api/artifacts/${id}/versions`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ title: "report (final).pdf" }), + }); + expect(revised.status).toBe(200); + + const v2 = await download(id, 2); + expect(v2.status).toBe(200); + expect(new Uint8Array(await v2.arrayBuffer())).toEqual(bytes); + + // Uploaded bytes live in the store, not in version history, so an older + // version of a stored file is refused rather than served as current bytes. + expect((await download(id, 1)).status).toBe(400); + }); +}); diff --git a/tests/lib/fs-content-store.ts b/tests/lib/fs-content-store.ts new file mode 100644 index 0000000..9b077c8 --- /dev/null +++ b/tests/lib/fs-content-store.ts @@ -0,0 +1,37 @@ +// A ContentStore that keeps file bytes on disk, one file per upload, referenced +// from the artifact's `source.upload.id` the way InlineContentStore references +// its bytea row. +import { randomUUID } from "node:crypto"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { uploadRefFromSource } from "../../src/content-store.js"; +import type { ContentStore } from "../../src/index.js"; + +export function createFsContentStore(dir: string): ContentStore { + const pathFor = (tenantId: string, id: string) => join(dir, tenantId, id); + return { + async put(_tx, scope, blob) { + const id = randomUUID(); + await mkdir(join(dir, scope.tenantId), { recursive: true }); + await writeFile(pathFor(scope.tenantId, id), blob.bytes); + return { + content: "", + source: { + upload: { + id, + filename: blob.filename, + mimeType: blob.mimeType, + size: blob.bytes.byteLength, + }, + }, + }; + }, + async get(_db, artifact) { + const ref = uploadRefFromSource(artifact.source); + if (ref?.id === undefined || artifact.tenantId === null) return null; + const bytes = await readFile(pathFor(artifact.tenantId, ref.id)).catch(() => null); + if (bytes === null) return null; + return { filename: ref.filename, mimeType: ref.mimeType, bytes: new Uint8Array(bytes) }; + }, + }; +} From c06c7816928716ca884a2f020980ace955232213 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 25 Sep 2026 00:01:34 -0700 Subject: [PATCH 3/9] test: assert upload policy rejections through the mounted app An oversize file answers 413 and a disallowed MIME type answers 415, and neither leaves an artifact behind. --- tests/upload-policy.test.ts | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/upload-policy.test.ts diff --git a/tests/upload-policy.test.ts b/tests/upload-policy.test.ts new file mode 100644 index 0000000..bf5b731 --- /dev/null +++ b/tests/upload-policy.test.ts @@ -0,0 +1,44 @@ +import { afterAll, beforeAll, describe, expect, test } from "bun:test"; +import type { Hono } from "hono"; +import type { TenantEnv } from "@intx/hub-api"; +import { MAX_UPLOAD_BYTES } from "../src/index.js"; +import { artifactApp, createTestDb, seedActor, type TestDb } from "./lib/db-harness.js"; + +let testDb: TestDb; +let app: Hono; + +beforeAll(async () => { + testDb = await createTestDb(); + app = artifactApp(testDb.db, await seedActor(testDb.db, "acme")); +}); + +afterAll(async () => { + await testDb.close(); +}); + +async function upload(file: File): Promise { + const form = new FormData(); + form.append("files", file); + return await app.request("/api/artifacts/upload", { method: "POST", body: form }); +} + +async function artifactCount(): Promise { + const res = await app.request("/api/artifacts?limit=100"); + return ((await res.json()) as { artifacts: unknown[] }).artifacts.length; +} + +describe("upload policy through the mounted app", () => { + test("an oversize file is 413 and stores nothing", async () => { + const big = new File([new Uint8Array(MAX_UPLOAD_BYTES + 1)], "big.pdf", { + type: "application/pdf", + }); + expect((await upload(big)).status).toBe(413); + expect(await artifactCount()).toBe(0); + }); + + test("a disallowed MIME type is 415 and stores nothing", async () => { + const script = new File(["#!/bin/sh\n"], "run.sh", { type: "application/x-sh" }); + expect((await upload(script)).status).toBe(415); + expect(await artifactCount()).toBe(0); + }); +}); From 733bba009575bb2ab157059aa189cecc8a198a09 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 25 Sep 2026 00:03:14 -0700 Subject: [PATCH 4/9] test(migrations): cut the tests to the runMigrations contract The runner's contract is that applying it creates the artifacts schema and tables, and applying it again changes nothing. Those two checks move to tests/ on a fresh database. The rest restated DDL line by line (constraints, indexes, foreign-key targets) that the SQL file itself states. --- CONTRIBUTING.md | 5 +- src/migrations.test.ts | 190 --------------------------------------- tests/migrations.test.ts | 78 ++++++++++++++++ 3 files changed, 80 insertions(+), 193 deletions(-) delete mode 100644 src/migrations.test.ts create mode 100644 tests/migrations.test.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 820be4a..fd91acd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,9 +83,8 @@ is no ledger: a schema change is a new file whose statements are safe to re-run, never an edit that assumes it runs once. `schema.ts` and `migrations/` must agree — every query goes through the drizzle -table objects, and a test asserts the migrations create exactly the tables -`schema.ts` declares, no more and no less. Change one, change the other, in the -same commit. +table objects, and the route suites fail when a column they write is missing. +Change one, change the other, in the same commit. ## Pull requests diff --git a/src/migrations.test.ts b/src/migrations.test.ts deleted file mode 100644 index cee514c..0000000 --- a/src/migrations.test.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { afterAll, beforeAll, describe, expect, test } from "bun:test"; -import { getTableName, is, sql } from "drizzle-orm"; -import { PgTable } from "drizzle-orm/pg-core"; -import { createArtifactDb } from "./db.js"; -import { runArtifactMigrations } from "./migrations.js"; -import * as schema from "./schema.js"; -import { - assertDestructiveArtifactTestsAllowed, - databaseConfig, - DATABASE_URL, - ensureControlPlane, -} from "./test-helpers.js"; - -const SCHEMA = "artifacts"; -const config = databaseConfig(DATABASE_URL); - -/** - * DERIVED FROM `schema.ts`, never restated, so a table added to the schema - * without a migration (or the reverse) turns the equality check red. - */ -const DECLARED_TABLES = (Object.values(schema) as unknown[]) - .filter((value): value is PgTable => is(value, PgTable)) - .map(getTableName) - .sort(); - -const { db, close } = createArtifactDb(DATABASE_URL); - -async function packageTables(): Promise { - const rows = await db.execute<{ table_name: string }>(sql` - SELECT table_name FROM information_schema.tables - WHERE table_schema = ${SCHEMA} - `); - return rows.map((r) => r.table_name).sort(); -} - -async function rejection(query: ReturnType): Promise { - return await db.execute(query).then( - () => "", - (error: unknown) => String((error as { cause?: unknown }).cause), - ); -} - -beforeAll(async () => { - assertDestructiveArtifactTestsAllowed(DATABASE_URL); - await ensureControlPlane(db); -}); - -afterAll(close); - -describe("runArtifactMigrations", () => { - test("creates exactly the tables schema.ts declares, and re-running is a no-op", async () => { - await db.execute(sql`DROP SCHEMA IF EXISTS ${sql.identifier(SCHEMA)} CASCADE`); - await runArtifactMigrations(config, { schema: "public" }); - await runArtifactMigrations(config, { schema: "public" }); - - expect(DECLARED_TABLES.length).toBeGreaterThan(0); - expect(await packageTables()).toEqual(DECLARED_TABLES); - }); - - // It is the advisory lock, not IF NOT EXISTS, that makes concurrent cold - // starts safe: the catalogue check and insert are not atomic. - test("concurrent first boots against a fresh database all succeed", async () => { - await db.execute(sql`DROP SCHEMA IF EXISTS ${sql.identifier(SCHEMA)} CASCADE`); - await Promise.all( - [0, 1, 2, 3].map(() => runArtifactMigrations(config, { schema: "public" })), - ); - expect(await packageTables()).toEqual(DECLARED_TABLES); - }); - - test("drops the migration ledger earlier releases kept", async () => { - await db.execute(sql` - CREATE TABLE IF NOT EXISTS "artifacts"."migrations" ("id" text PRIMARY KEY) - `); - await runArtifactMigrations(config, { schema: "public" }); - expect(await packageTables()).toEqual(DECLARED_TABLES); - }); - - test("brings a database from before 0.1.0 up to 0.1.0's columns", async () => { - await db.execute(sql` - ALTER TABLE "artifacts"."artifact" - DROP COLUMN "metadata", DROP COLUMN "content_sha256", - ALTER COLUMN "created_at" TYPE timestamp - `); - await db.execute(sql` - ALTER TABLE "artifacts"."artifact_version" - DROP COLUMN "metadata", DROP COLUMN "parent_version_ids", DROP COLUMN "content_sha256" - `); - await runArtifactMigrations(config, { schema: "public" }); - const columns = await db.execute<{ column: string; type: string }>(sql` - SELECT table_name || '.' || column_name AS column, udt_name AS type - FROM information_schema.columns - WHERE table_schema = ${SCHEMA} - AND column_name IN ('metadata', 'parent_version_ids', 'content_sha256', 'created_at') - AND table_name IN ('artifact', 'artifact_version') - ORDER BY 1 - `); - expect(columns.map((c) => `${c.column} ${c.type}`)).toEqual([ - "artifact.content_sha256 text", - "artifact.created_at timestamptz", - "artifact.metadata jsonb", - "artifact_version.content_sha256 text", - "artifact_version.created_at timestamptz", - "artifact_version.metadata jsonb", - "artifact_version.parent_version_ids _text", - ]); - }); - - test("points the tenant and principal foreign keys at the host schema", async () => { - const host = "artifact_host_test"; - await db.execute(sql`DROP SCHEMA IF EXISTS ${sql.identifier(SCHEMA)} CASCADE`); - await db.execute(sql`DROP SCHEMA IF EXISTS ${sql.identifier(host)} CASCADE`); - await db.execute(sql`CREATE SCHEMA ${sql.identifier(host)}`); - await db.execute(sql`CREATE TABLE ${sql.identifier(host)}."tenant" ("id" text PRIMARY KEY)`); - await db.execute( - sql`CREATE TABLE ${sql.identifier(host)}."principal" ("id" text PRIMARY KEY)`, - ); - try { - await runArtifactMigrations(config, { schema: host }); - const targets = await db.execute<{ target: string }>(sql` - SELECT DISTINCT tn.nspname AS target - FROM pg_constraint con - JOIN pg_class c ON c.oid = con.conrelid - JOIN pg_namespace n ON n.oid = c.relnamespace - JOIN pg_class t ON t.oid = con.confrelid - JOIN pg_namespace tn ON tn.oid = t.relnamespace - WHERE con.contype = 'f' AND n.nspname = ${SCHEMA} AND t.relname IN ('tenant', 'principal') - `); - expect(targets.map((r) => r.target)).toEqual([host]); - } finally { - await db.execute(sql`DROP SCHEMA IF EXISTS ${sql.identifier(SCHEMA)} CASCADE`); - await db.execute(sql`DROP SCHEMA ${sql.identifier(host)} CASCADE`); - await runArtifactMigrations(config, { schema: "public" }); - } - }); - - test("refuses an empty schema name", async () => { - await expect(runArtifactMigrations(config, { schema: "" })).rejects.toThrow( - "schema name must not be empty", - ); - }); - - test("the keyset index carries the id tie-break", async () => { - const [keyset] = await db.execute<{ indexdef: string }>(sql` - SELECT indexdef FROM pg_indexes - WHERE schemaname = ${SCHEMA} AND indexname = 'artifact_tenant_updated_id_idx' - `); - expect(keyset?.indexdef).toContain("(tenant_id, updated_at, id)"); - }); - - test("tenant_id NOT NULL and the version and size CHECKs are enforced", async () => { - expect( - await rejection(sql` - INSERT INTO "artifacts"."artifact" ("tenant_id", "kind", "title", "content") - VALUES (NULL, 'document', 'orphan', 'body') - `), - ).toMatch(/null value in column "tenant_id"/); - expect( - await rejection(sql` - INSERT INTO "artifacts"."artifact" ("tenant_id", "kind", "title", "content", "version") - VALUES ('acme', 'document', 'bad', 'body', 0) - `), - ).toMatch(/artifact_version_gte_1/); - - const [row] = await db.execute<{ id: string }>(sql` - INSERT INTO "artifacts"."artifact" ("tenant_id", "kind", "title", "content") - VALUES ('acme', 'document', 'ok', 'body') - RETURNING "id" - `); - expect( - await rejection(sql` - INSERT INTO "artifacts"."artifact_version" - ("artifact_id", "version", "title", "content", "author_id") - VALUES (${row!.id}, 0, 'ok', 'body', 'user-1') - `), - ).toMatch(/artifact_version_version_gte_1/); - expect( - await rejection(sql` - INSERT INTO "artifacts"."upload" ("tenant_id", "filename", "mime_type", "content", "size") - VALUES ('acme', 'x.bin', 'application/octet-stream', decode('00', 'hex'), -1) - `), - ).toMatch(/upload_size_gte_0/); - expect( - await rejection(sql` - INSERT INTO "artifacts"."mail_attachment_ref" - ("tenant_id", "instance_id", "mail_id", "artifact_id", "name", "mime_type", "size") - VALUES ('acme', 'inst', 'mail', ${row!.id}, 'a.bin', 'application/octet-stream', -1) - `), - ).toMatch(/mail_attachment_ref_size_gte_0/); - }); -}); diff --git a/tests/migrations.test.ts b/tests/migrations.test.ts new file mode 100644 index 0000000..9591c47 --- /dev/null +++ b/tests/migrations.test.ts @@ -0,0 +1,78 @@ +import { afterAll, beforeAll, describe, expect, test } from "bun:test"; +import { sql } from "drizzle-orm"; +import { runArtifactMigrations } from "../src/index.js"; +import { createTestDb, seedActor, type TestDb } from "./lib/db-harness.js"; + +const TABLES = ["artifact", "artifact_version", "mail_attachment_ref", "upload"]; + +let testDb: TestDb; + +beforeAll(async () => { + testDb = await createTestDb(); +}); + +afterAll(async () => { + await testDb.close(); +}); + +async function packageTables(): Promise { + const rows = await testDb.db.execute<{ table_name: string }>(sql` + SELECT table_name FROM information_schema.tables + WHERE table_schema = 'artifacts' + ORDER BY table_name + `); + return rows.map((row) => row.table_name); +} + +describe("runArtifactMigrations", () => { + test("creates the artifacts schema and its tables", async () => { + expect(await packageTables()).toEqual(TABLES); + }); + + test("applying twice changes nothing and keeps existing rows", async () => { + const { tenant } = await seedActor(testDb.db, "acme"); + await testDb.db.execute(sql` + INSERT INTO "artifacts"."artifact" ("tenant_id", "kind", "title", "content") + VALUES (${tenant.id}, 'document', 'kept', 'body') + `); + + await runArtifactMigrations(testDb.config, { schema: "public" }); + await runArtifactMigrations(testDb.config, { schema: "public" }); + + expect(await packageTables()).toEqual(TABLES); + const rows = await testDb.db.execute<{ title: string }>( + sql`SELECT "title" FROM "artifacts"."artifact"`, + ); + expect(rows.map((row) => row.title)).toEqual(["kept"]); + }); + + test("brings a database from before 0.1.0 up to 0.1.0's columns", async () => { + await testDb.db.execute(sql` + ALTER TABLE "artifacts"."artifact" + DROP COLUMN "metadata", DROP COLUMN "content_sha256", + ALTER COLUMN "created_at" TYPE timestamp + `); + await testDb.db.execute(sql` + ALTER TABLE "artifacts"."artifact_version" + DROP COLUMN "metadata", DROP COLUMN "parent_version_ids", DROP COLUMN "content_sha256" + `); + await runArtifactMigrations(testDb.config, { schema: "public" }); + const columns = await testDb.db.execute<{ column: string; type: string }>(sql` + SELECT table_name || '.' || column_name AS column, udt_name AS type + FROM information_schema.columns + WHERE table_schema = 'artifacts' + AND column_name IN ('metadata', 'parent_version_ids', 'content_sha256', 'created_at') + AND table_name IN ('artifact', 'artifact_version') + ORDER BY 1 + `); + expect(columns.map((c) => `${c.column} ${c.type}`)).toEqual([ + "artifact.content_sha256 text", + "artifact.created_at timestamptz", + "artifact.metadata jsonb", + "artifact_version.content_sha256 text", + "artifact_version.created_at timestamptz", + "artifact_version.metadata jsonb", + "artifact_version.parent_version_ids _text", + ]); + }); +}); From 81e7b2779afc311edbedebcc42917736509dde2f Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 25 Sep 2026 00:05:22 -0700 Subject: [PATCH 5/9] test: delete the destructive-test gate, counts and preview unit tests The gate test exercised the test harness, not the package. Counts and preview are served by routes, and the route suite now covers the counts walk across more than one page, skipping other tenants and archived rows, so the direct unit tests were duplicates. --- CONTRIBUTING.md | 3 +- src/counts.test.ts | 95 ------------------------- src/destructive-tests-gate.test.ts | 109 ----------------------------- src/mount.test.ts | 15 ++++ src/preview.test.ts | 79 --------------------- 5 files changed, 16 insertions(+), 285 deletions(-) delete mode 100644 src/counts.test.ts delete mode 100644 src/destructive-tests-gate.test.ts delete mode 100644 src/preview.test.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fd91acd..6f1c5de 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,8 +36,7 @@ The harness truncates package tables between tests and drops the package schema couple of migration cases. Those paths are fail-closed: set `ALLOW_DESTRUCTIVE_ARTIFACT_TESTS=1` and point `ARTIFACT_DATABASE_URL` at an allowlisted ephemeral database (`artifact_core`, or any name ending in `_test`). Without both, the -suite throws before mutating. The gate itself is pure URL/env parsing and is covered by -unit tests that do not need Postgres. +suite throws before mutating. End-to-end suites live in `tests/`. `tests/lib/db-harness.ts` creates a fresh `artifact__test` database per suite on the `ARTIFACT_DATABASE_URL` server, diff --git a/src/counts.test.ts b/src/counts.test.ts deleted file mode 100644 index d720537..0000000 --- a/src/counts.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import { - ArtifactCountsIncompleteError, - countArtifactsBySegments, - MAX_COUNT_PAGES, -} from "./counts.js"; -import { setArtifactArchived } from "./artifacts.js"; -import { seedArtifact, testDb } from "./test-helpers.js"; -import type { ArtifactDb } from "./db.js"; -import { listArtifacts } from "./artifacts.js"; - -describe("countArtifactsBySegments", () => { - test("returns just `all` when no segments are supplied", async () => { - const db = await testDb(); - await seedArtifact(db, { kind: "document" }); - await seedArtifact(db, { kind: "document" }); - - const counts = await countArtifactsBySegments(db, "acme", {}); - expect(counts).toEqual({ all: 2 }); - }); - - test("tallies each row against every predicate", async () => { - const db = await testDb(); - await seedArtifact(db, { kind: "document" }); - await seedArtifact(db, { kind: "document" }); - await seedArtifact(db, { kind: "sheet" }); - - const counts = await countArtifactsBySegments(db, "acme", { - document: (row) => row.kind === "document", - sheet: (row) => row.kind === "sheet", - routine: (row) => row.kind === "routine", - }); - expect(counts).toEqual({ all: 3, document: 2, sheet: 1, routine: 0 }); - }); - - // The walk pages at 100 rows/page — seed past that boundary so a passing - // test proves the cursor loop, not just a single-page happy path. - test("walks past a single page", async () => { - const db = await testDb(); - await Promise.all( - Array.from({ length: 120 }, (_, i) => - seedArtifact(db, { kind: "document", title: `doc-${i}` }), - ), - ); - - const counts = await countArtifactsBySegments(db, "acme", { - document: (row) => row.kind === "document", - }); - expect(counts).toEqual({ all: 120, document: 120 }); - }); - - test("never counts another tenant's artifacts", async () => { - const db = await testDb(); - await seedArtifact(db, { tenantId: "acme" }); - await seedArtifact(db, { tenantId: "other" }); - - const counts = await countArtifactsBySegments(db, "acme", {}); - expect(counts).toEqual({ all: 1 }); - }); - - test("excludes archived artifacts, matching GET /artifacts' default view", async () => { - const db = await testDb(); - const row = await seedArtifact(db, { kind: "document" }); - await seedArtifact(db, { kind: "document" }); - await setArtifactArchived(db, row, true); - - const counts = await countArtifactsBySegments(db, "acme", {}); - expect(counts).toEqual({ all: 1 }); - }); - -}); - -// `ArtifactCountsIncompleteError` and the page cap it backstops are exercised -// end to end at the route level (mount.test.ts), where the honest-503 -// contract lives; the walk's cursor plumbing itself is real Postgres -// pagination, not something worth faking a stalled cursor for here. -test("MAX_COUNT_PAGES is the documented cap", () => { - expect(MAX_COUNT_PAGES).toBe(200); - expect(ArtifactCountsIncompleteError.prototype).toBeInstanceOf(Error); -}); - -// Sanity: countArtifactsBySegments' walk must agree with what GET /artifacts -// itself would page through — same underlying listArtifacts call, same -// exclusions. -test("agrees with listArtifacts' own row set", async () => { - const db = await testDb(); - await seedArtifact(db, { kind: "document" }); - await seedArtifact(db, { kind: "sheet" }); - - const [counts, page] = await Promise.all([ - countArtifactsBySegments(db, "acme", {}), - listArtifacts(db, "acme", {}), - ]); - expect(counts.all).toBe(page.rows.length); -}); diff --git a/src/destructive-tests-gate.test.ts b/src/destructive-tests-gate.test.ts deleted file mode 100644 index 5b20030..0000000 --- a/src/destructive-tests-gate.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import { - ALLOW_DESTRUCTIVE_ARTIFACT_TESTS, - assertDestructiveArtifactTestsAllowed, - databaseNameFromConnectionString, - isAllowlistedArtifactTestDatabase, -} from "./test-helpers.js"; - -describe("databaseNameFromConnectionString", () => { - test("reads the path segment of a postgres URL", () => { - expect( - databaseNameFromConnectionString( - "postgres://postgres:postgres@localhost:5457/artifact_core", - ), - ).toBe("artifact_core"); - }); - - test("strips query parameters", () => { - expect( - databaseNameFromConnectionString( - "postgresql://u:p@db.example:5432/artifacts_test?sslmode=require", - ), - ).toBe("artifacts_test"); - }); - - test("decodes percent-encoded names", () => { - expect( - databaseNameFromConnectionString("postgres://localhost/my%2Ddb_test"), - ).toBe("my-db_test"); - }); - - test("refuses a URL with no database name", () => { - expect(() => databaseNameFromConnectionString("postgres://localhost/")).toThrow( - /database name/i, - ); - }); -}); - -describe("isAllowlistedArtifactTestDatabase", () => { - test("allows the documented default name", () => { - expect(isAllowlistedArtifactTestDatabase("artifact_core")).toBe(true); - }); - - test("allows names ending in _test", () => { - expect(isAllowlistedArtifactTestDatabase("artifacts_test")).toBe(true); - expect(isAllowlistedArtifactTestDatabase("foo_test")).toBe(true); - }); - - test("refuses production-looking names", () => { - expect(isAllowlistedArtifactTestDatabase("postgres")).toBe(false); - expect(isAllowlistedArtifactTestDatabase("interchange")).toBe(false); - expect(isAllowlistedArtifactTestDatabase("artifact_prod")).toBe(false); - expect(isAllowlistedArtifactTestDatabase("test")).toBe(false); - }); -}); - -describe("assertDestructiveArtifactTestsAllowed", () => { - const ephemeral = - "postgres://postgres:postgres@localhost:5457/artifact_core"; - const production = - "postgres://postgres:postgres@localhost:5432/interchange"; - - test("refuses when the opt-in env is unset, even on an allowlisted name", () => { - expect(() => - assertDestructiveArtifactTestsAllowed(ephemeral, {}), - ).toThrow(ALLOW_DESTRUCTIVE_ARTIFACT_TESTS); - }); - - test("refuses when the opt-in env is not exactly 1", () => { - expect(() => - assertDestructiveArtifactTestsAllowed(ephemeral, { - [ALLOW_DESTRUCTIVE_ARTIFACT_TESTS]: "true", - }), - ).toThrow(ALLOW_DESTRUCTIVE_ARTIFACT_TESTS); - }); - - test("refuses a non-allowlisted database name even with opt-in", () => { - expect(() => - assertDestructiveArtifactTestsAllowed(production, { - [ALLOW_DESTRUCTIVE_ARTIFACT_TESTS]: "1", - }), - ).toThrow(/interchange/); - }); - - test("allows allowlisted name with explicit opt-in", () => { - expect(() => - assertDestructiveArtifactTestsAllowed(ephemeral, { - [ALLOW_DESTRUCTIVE_ARTIFACT_TESTS]: "1", - }), - ).not.toThrow(); - expect(() => - assertDestructiveArtifactTestsAllowed( - "postgres://localhost/artifacts_test", - { [ALLOW_DESTRUCTIVE_ARTIFACT_TESTS]: "1" }, - ), - ).not.toThrow(); - }); - - test("error message names both requirements", () => { - try { - assertDestructiveArtifactTestsAllowed(production, {}); - expect.unreachable("should have thrown"); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - expect(message).toContain(ALLOW_DESTRUCTIVE_ARTIFACT_TESTS); - expect(message).toMatch(/artifact_core|_test/); - } - }); -}); diff --git a/src/mount.test.ts b/src/mount.test.ts index 3e5e02c..6524fcd 100644 --- a/src/mount.test.ts +++ b/src/mount.test.ts @@ -1759,6 +1759,21 @@ describe("GET /artifacts/counts", () => { expect(await res.json()).toEqual({ all: 3, document: 1, sheet: 2 }); }); + test("walks past one page, skipping other tenants and archived rows", async () => { + const db = await testDb(); + await Promise.all( + Array.from({ length: 120 }, (_, i) => + seedArtifact(db, { kind: "document", title: `doc-${i}` }), + ), + ); + await seedArtifact(db, { tenantId: "other" }); + await setArtifactArchived(db, await seedArtifact(db), true); + const app = host(db, { countSegments: { document: (row) => row.kind === "document" } }); + + const res = await app.request("/artifacts/counts"); + expect(await res.json()).toEqual({ all: 120, document: 120 }); + }); + test("403s with no resolvable principal, matching every other collection-adjacent route's auth story here", async () => { const db = await testDb(); const app = host(db, { principal: null }); diff --git a/src/preview.test.ts b/src/preview.test.ts deleted file mode 100644 index 81b9711..0000000 --- a/src/preview.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import { artifactPreviewHeaders, resolveArtifactPreview } from "./preview.js"; -import { InlineContentStore } from "./content-store.js"; -import { ARTIFACT_UPLOAD_POLICY, createFileArtifact } from "./uploads.js"; -import { getArtifact } from "./artifacts.js"; -import { seedArtifact, SCOPE, testDb } from "./test-helpers.js"; -import type { ArtifactDb } from "./db.js"; - -async function uploadFile( - db: ArtifactDb, - filename: string, - mimeType: string, - body: string, -) { - const row = await db.transaction((tx) => - createFileArtifact(tx, InlineContentStore, { - scope: SCOPE, - ownerPrincipalId: SCOPE.principalId, - filename, - mimeType, - bytes: new TextEncoder().encode(body), - policy: ARTIFACT_UPLOAD_POLICY, - }), - ); - // Re-fetch: createFileArtifact's return may not carry the out-of-band blob. - return (await getArtifact(db, row.id))!; -} - -describe("resolveArtifactPreview", () => { - test("resolves an uploaded text/html file's body", async () => { - const db = await testDb(); - const row = await uploadFile(db, "page.html", "text/html", "

hi

"); - - const result = await resolveArtifactPreview(db, InlineContentStore, row); - expect(result).toEqual({ status: "ok", html: "

hi

" }); - }); - - test("rejects a non-HTML upload as unsupported", async () => { - const db = await testDb(); - const row = await uploadFile(db, "notes.txt", "text/plain", "just text"); - - const result = await resolveArtifactPreview(db, InlineContentStore, row); - expect(result).toEqual({ status: "unsupported" }); - }); - - test("rejects a plain text/document artifact (not downloadable) as unsupported, not a 500", async () => { - const db = await testDb(); - const row = await seedArtifact(db, { kind: "document", content: "

fake

" }); - - const result = await resolveArtifactPreview(db, InlineContentStore, row); - expect(result).toEqual({ status: "unsupported" }); - }); - - test("reports not_found when the referenced upload blob is gone", async () => { - const db = await testDb(); - const row = await seedArtifact(db, { - kind: "file", - source: { - origin: "manual", - upload: { id: "does-not-exist", filename: "gone.html", mimeType: "text/html" }, - }, - }); - - const result = await resolveArtifactPreview(db, InlineContentStore, row); - expect(result).toEqual({ status: "not_found" }); - }); -}); - -describe("artifactPreviewHeaders", () => { - test("locks the response down: sandboxed, no network reach, nosniff", () => { - const headers = artifactPreviewHeaders(); - expect(headers["Content-Type"]).toBe("text/html; charset=utf-8"); - expect(headers["X-Content-Type-Options"]).toBe("nosniff"); - const csp = headers["Content-Security-Policy"]!; - expect(csp).toContain("sandbox allow-scripts"); - expect(csp).toContain("default-src 'none'"); - expect(csp).not.toContain("X-Frame-Options"); - }); -}); From f4c3fbe5f0a04baa027905ad53ae09d4b3733366 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 25 Sep 2026 00:06:21 -0700 Subject: [PATCH 6/9] test: delete artifacts.test.ts cases the route suite already covers Version bump and history paging, the create size limits, the not-found answers for archived, skill-draft and cross-tenant rows, idempotent archive and the expectedVersion precondition are all asserted through the mounted routes in mount.test.ts. What stays covers logic no route reaches: find-by-title, find-or-version races, concurrent writers and archives, lineage and digest rules, and serialization. --- src/artifacts.test.ts | 140 ------------------------------------------ 1 file changed, 140 deletions(-) diff --git a/src/artifacts.test.ts b/src/artifacts.test.ts index 60f2c8b..623882b 100644 --- a/src/artifacts.test.ts +++ b/src/artifacts.test.ts @@ -1,7 +1,6 @@ import { describe, expect, test } from "bun:test"; import { and, eq, sql } from "drizzle-orm"; import { - ArtifactNotFoundError, ArtifactSizeError, ArtifactValidationError, createArtifact, @@ -16,7 +15,6 @@ import { serializeArtifact, setArtifactArchived, sha256Hex, - VersionConflictError, writeArtifactVersion, } from "./artifacts.js"; import { artifact, artifactVersion } from "./schema.js"; @@ -86,74 +84,9 @@ describe("create", () => { files: { "index.html": "

hi

" }, }); }); - test("rejects oversize title and content before insert", async () => { - const db = await testDb(); - await expect( - db.transaction((tx) => - createArtifact(tx, { - scope: SCOPE, - ownerPrincipalId: SCOPE.principalId, - kind: "document", - title: "x".repeat(MAX_ARTIFACT_TITLE_LENGTH + 1), - content: "ok", - source: { origin: "manual" }, - }), - ), - ).rejects.toBeInstanceOf(ArtifactSizeError); - - await expect( - db.transaction((tx) => - createArtifact(tx, { - scope: SCOPE, - ownerPrincipalId: SCOPE.principalId, - kind: "document", - title: "ok", - content: "x".repeat(MAX_ARTIFACT_CONTENT_BYTES + 1), - source: { origin: "manual" }, - }), - ), - ).rejects.toBeInstanceOf(ArtifactSizeError); - }); }); describe("versioning", () => { - test("bumps the version and appends history", async () => { - const db = await testDb(); - const row = await seedArtifact(db, { title: "Draft", content: "v1" }); - - const second = await writeArtifactVersion(db, { - scope: SCOPE, - artifactId: row.id, - content: "v2", - }); - expect(second.version).toBe(2); - expect(second.title).toBe("Draft"); - - const history = await listArtifactVersions(db, row.id); - expect(history.versions.map((h) => h.version)).toEqual([2, 1]); - expect(history.nextCursor).toBeNull(); - expect((await getArtifactVersion(db, row.id, 1))?.content).toBe("v1"); - expect((await getArtifactVersion(db, row.id, 2))?.content).toBe("v2"); - }); - - test("paginates version history newest-first without content", async () => { - const db = await testDb(); - const row = await seedArtifact(db, { title: "Draft", content: "v1" }); - await writeArtifactVersion(db, { scope: SCOPE, artifactId: row.id, content: "v2" }); - await writeArtifactVersion(db, { scope: SCOPE, artifactId: row.id, content: "v3" }); - - const page1 = await listArtifactVersions(db, row.id, { limit: 2 }); - expect(page1.versions.map((v) => v.version)).toEqual([3, 2]); - expect(page1.versions[0]).not.toHaveProperty("content"); - expect(page1.nextCursor).toBe("2"); - - const page2 = await listArtifactVersions(db, row.id, { - limit: 2, - cursor: Number(page1.nextCursor), - }); - expect(page2.versions.map((v) => v.version)).toEqual([1]); - expect(page2.nextCursor).toBeNull(); - }); test("rejects oversize revise fields", async () => { const db = await testDb(); @@ -192,32 +125,6 @@ describe("versioning", () => { expect(rows.length).toBe(4); }); - test("an archived artifact presents as not found, not forbidden", async () => { - const db = await testDb(); - const row = await seedArtifact(db); - await setArtifactArchived(db, row, true); - - await expect( - writeArtifactVersion(db, { scope: SCOPE, artifactId: row.id, content: "x" }), - ).rejects.toBeInstanceOf(ArtifactNotFoundError); - }); - - test("a skill-draft presents as not found", async () => { - const db = await testDb(); - const id = await seedSkillDraft(db, "scratch"); - await expect( - writeArtifactVersion(db, { scope: SCOPE, artifactId: id, content: "x" }), - ).rejects.toBeInstanceOf(ArtifactNotFoundError); - }); - - test("another tenant's artifact presents as not found", async () => { - const db = await testDb(); - const row = await seedArtifact(db, { tenantId: "other" }); - await expect( - writeArtifactVersion(db, { scope: SCOPE, artifactId: row.id, content: "x" }), - ).rejects.toBeInstanceOf(ArtifactNotFoundError); - }); - test("refuses a revision that changes nothing", async () => { const db = await testDb(); const row = await seedArtifact(db); @@ -228,19 +135,6 @@ describe("versioning", () => { }); describe("archive", () => { - test("is idempotent: re-archiving keeps the original timestamp", async () => { - const db = await testDb(); - const row = await seedArtifact(db); - - const archived = await setArtifactArchived(db, row, true); - expect(archived.archivedAt).not.toBeNull(); - const again = await setArtifactArchived(db, archived, true); - expect(again.archivedAt?.getTime()).toBe(archived.archivedAt!.getTime()); - - const restored = await setArtifactArchived(db, again, false); - expect(restored.archivedAt).toBeNull(); - expect((await setArtifactArchived(db, restored, false)).archivedAt).toBeNull(); - }); test("returned archivedAt matches durable DB state", async () => { const db = await testDb(); @@ -742,37 +636,3 @@ describe("content digest", () => { }); }); -describe("expectedVersion precondition", () => { - test("a matching expectedVersion succeeds", async () => { - const db = await testDb(); - const row = await seedArtifact(db, { content: "v1" }); - - const revised = await writeArtifactVersion(db, { - scope: SCOPE, - artifactId: row.id, - content: "v2", - expectedVersion: 1, - }); - expect(revised.version).toBe(2); - }); - - test("a mismatched expectedVersion 409s (VersionConflictError) and writes nothing", async () => { - const db = await testDb(); - const row = await seedArtifact(db, { content: "v1" }); - - await expect( - writeArtifactVersion(db, { - scope: SCOPE, - artifactId: row.id, - content: "v2", - expectedVersion: 5, - }), - ).rejects.toBeInstanceOf(VersionConflictError); - - const history = await listArtifactVersions(db, row.id); - expect(history.versions.length).toBe(1); - const current = await getArtifact(db, row.id); - expect(current?.version).toBe(1); - }); -}); - From e8ba45d1e410af331713d3b9c44d4734e8774699 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 25 Sep 2026 00:08:55 -0700 Subject: [PATCH 7/9] test: run the reference-host acceptance suite in bun run test The acceptance scenarios move to tests/ and run with the rest of the suite instead of behind a separate test:acceptance script and CI step. The reference host stops being its own package: the root tsconfig maps @corbits/artifacts to src/ so the example compiles and runs in place, and its stale Interchange wiring is updated to the 0.4.0 types. The built dist/ stays covered by CI's Node consumer smoke test. --- .github/workflows/test.yml | 13 +- CONTRIBUTING.md | 11 +- bun.lock | 1 + examples/reference-host/bun.lock | 451 ------------------ examples/reference-host/package.json | 25 - examples/reference-host/src/index.ts | 39 +- examples/reference-host/tsconfig.json | 13 - package.json | 6 +- .../reference-host.test.ts | 2 +- tsconfig.json | 3 +- 10 files changed, 24 insertions(+), 540 deletions(-) delete mode 100644 examples/reference-host/bun.lock delete mode 100644 examples/reference-host/package.json delete mode 100644 examples/reference-host/tsconfig.json rename examples/reference-host/test/acceptance.test.ts => tests/reference-host.test.ts (99%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1758669..81c3db5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,26 +39,15 @@ jobs: - run: bun install --frozen-lockfile - # typecheck builds first: examples import @corbits/artifacts through the - # published dist types, same path a consumer resolves. - name: typecheck run: bun run typecheck - - name: unit + integration tests + - name: unit, integration and reference-host acceptance tests run: bun run test:coverage - # dist/ is already emitted by typecheck; re-run for a clean package build - # so acceptance and the node consumer smoke test do not depend on typecheck - # having side-effected the tree. - name: build run: bun run build - - name: install reference-host dependencies - run: bun install --cwd examples/reference-host - - - name: reference-host acceptance - run: bun test --cwd examples/reference-host - # A consumer on plain Node must be able to install and import the # tarball; Node cannot strip types, so a src-pointing manifest would die # here rather than after publish. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f1c5de..81f9365 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,9 +14,8 @@ docker run -d --name corbits-artifact-pg -p 5457:5432 \ export ALLOW_DESTRUCTIVE_ARTIFACT_TESTS=1 bun run typecheck -bun run test # unit + integration +bun run test # unit, integration and reference-host acceptance bun run build # dist/ (JS + .d.ts) -bun run test:acceptance # builds, then examples/reference-host ``` Tests expect `postgres://postgres:postgres@localhost:5457/artifact_core` (override with @@ -48,10 +47,10 @@ database's `grant` table. `bun run test` runs `src/` and `tests/`. ## The reference host is the acceptance suite, not a demo `examples/reference-host` mounts the package on a real `@intx/hub-api` app against a -live Postgres and asserts the end-to-end scenarios, consuming the package through the -built `dist/` — the same artifact a consumer installs. That is why `test:acceptance` -builds first: running it against stale output is how a green acceptance run stops -meaning anything. +live Postgres. `tests/reference-host.test.ts` asserts the end-to-end scenarios against +it as part of `bun run test`; the example imports `@corbits/artifacts`, which the +root `tsconfig.json` maps to `src/`. CI's Node consumer smoke test covers the built +`dist/` a consumer installs. If you change the route factory, a port, or anything about how a host wires this up, the reference host is where that change has to be shown working. diff --git a/bun.lock b/bun.lock index dba182a..a50144f 100644 --- a/bun.lock +++ b/bun.lock @@ -14,6 +14,7 @@ "@intx/db": "0.4.0", "@intx/hub-api": "0.4.0", "@intx/hub-common": "0.4.0", + "@intx/hub-sessions": "0.4.0", "@intx/types": "0.4.0", "@types/bun": "1.1.14", "@types/node": "22.10.5", diff --git a/examples/reference-host/bun.lock b/examples/reference-host/bun.lock deleted file mode 100644 index 37eae64..0000000 --- a/examples/reference-host/bun.lock +++ /dev/null @@ -1,451 +0,0 @@ -{ - "lockfileVersion": 2, - "configVersion": 1, - "workspaces": { - "": { - "name": "reference-host", - "dependencies": { - "@corbits/artifacts": "file:../..", - "@intx/db": "0.4.0", - "@intx/hub-api": "0.4.0", - "@intx/hub-common": "0.4.0", - "@intx/hub-sessions": "0.4.0", - "@intx/types": "0.4.0", - "drizzle-orm": "0.45.2", - "hono": "4.12.32", - "hono-openapi": "1.2.0", - "postgres": "3.4.9", - }, - "devDependencies": { - "@types/bun": "1.1.14", - "@types/node": "22.10.5", - "typescript": "5.7.2", - }, - }, - }, - "packages": { - "@ark/schema": ["@ark/schema@0.56.0", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA=="], - - "@ark/util": ["@ark/util@0.56.0", "", {}, "sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA=="], - - "@better-auth/core": ["@better-auth/core@1.7.5", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.41.1", "@standard-schema/spec": "^1.1.0", "zod": "^4.5.4" }, "peerDependencies": { "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.2", "@opentelemetry/api": "^1.9.0", "better-call": "1.4.0", "jose": "^6.1.0", "kysely": "^0.28.5 || ^0.29.0", "nanostores": "^1.0.1" }, "optionalPeers": ["@opentelemetry/api"] }, "sha512-kVlSu4H8OKQfjg4b/Zj5MOaospt83N0JbX38wsDzE58Yw95jzovFkU3pxzB1eUFYc4mkuhUMZD8iT1gpUjNMcQ=="], - - "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.7.5", "", { "peerDependencies": { "@better-auth/core": "^1.7.5", "@better-auth/utils": "0.4.2", "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0" }, "optionalPeers": ["drizzle-orm"] }, "sha512-9SM7v1735SoaedRDcDbHc5ULgXEd2vUlEJkvRHpMF2Q9qf59TRh1b5A9hryyecyi56bm/0CNUDU3nY0uVWj5/Q=="], - - "@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.7.5", "", { "peerDependencies": { "@better-auth/core": "^1.7.5", "@better-auth/utils": "0.4.2", "kysely": "^0.28.17 || ^0.29.0" }, "optionalPeers": ["kysely"] }, "sha512-1wE5gvnjW+c1i4GrLtL9HLn3s0Xrq4YneCDan1NO1dpz0mEguLFNlzfnUGykTFrDwvFh2X5rkIbA8ALCj6WzXQ=="], - - "@better-auth/memory-adapter": ["@better-auth/memory-adapter@1.7.5", "", { "peerDependencies": { "@better-auth/core": "^1.7.5", "@better-auth/utils": "0.4.2" } }, "sha512-YDmnfR9zOXbn5SNYYwfHBPuc19hg1d1C1vUXb+Hm8Q91pTsstFNX5OTlZbo7f28q06FpogAEfGGCN/2QV39cig=="], - - "@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.7.5", "", { "peerDependencies": { "@better-auth/core": "^1.7.5", "@better-auth/utils": "0.4.2", "mongodb": "^6.0.0 || ^7.0.0" }, "optionalPeers": ["mongodb"] }, "sha512-Yq0LfF0VlA9Kfjcjp/v43MSsChv7vKd1ct0Mt15px4zlqaCPIGfPbjw9YNM6jTo0fGpqLR0n15PllSWmLLRp9g=="], - - "@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.7.5", "", { "peerDependencies": { "@better-auth/core": "^1.7.5", "@better-auth/utils": "0.4.2", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-QfW6HS9vK0FMcbI/GsQLdplICxOz0EPzYWGONZT4ovL3cSItd4YH/09USbPPVl+VUQCdznAQIk+n+NKvHdmSag=="], - - "@better-auth/telemetry": ["@better-auth/telemetry@1.7.5", "", { "peerDependencies": { "@better-auth/core": "^1.7.5", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.2" } }, "sha512-e/REPqMy9Em+gC6G0xWBikiMLRuy532Er7jqdoNkPBa65FbywgWcm1cZbgdW5CnHsrM3OLZsmKn14yeGoDISeg=="], - - "@better-auth/utils": ["@better-auth/utils@0.4.2", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A=="], - - "@better-fetch/fetch": ["@better-fetch/fetch@1.3.2", "", {}, "sha512-Gs7n99b5tqUC6cQAPbV0uED3IraHB6xQbHLQ/C3l7ZFafHScOx9pQ+DYmP5blbLFShVWLqxNUlI9wi4xU/X+ow=="], - - "@corbits/artifacts": ["@corbits/artifacts@file:../..", { "dependencies": { "@hono/standard-validator": "^0.2.3" }, "devDependencies": { "@intx/authz": "0.3.0", "@intx/hub-api": "0.3.0", "@intx/types": "0.3.0", "@types/bun": "1.1.14", "@types/node": "22.10.5", "arktype": "2.1.29", "drizzle-orm": "0.45.2", "hono": "4.12.32", "hono-openapi": "1.2.0", "postgres": "3.4.9", "typescript": "5.7.2" }, "peerDependencies": { "@intx/hub-api": "^0.3.0", "@intx/types": "^0.3.0", "arktype": "^2.1.29", "drizzle-orm": "^0.45.2", "hono": "^4.12.32", "hono-openapi": "^1.2.0", "postgres": "^3.4.9" } }], - - "@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="], - - "@hono/standard-validator": ["@hono/standard-validator@0.2.3", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "hono": ">=3.9.0" } }, "sha512-bp9vHu6Va6SfMHC3D4ZLBbT/woi+AZ9CRdTXQu3kLJuLh2W/Gb9UO4hijS+BQAGFXi4EGpXdetxpzwTAawSVeg=="], - - "@intx/agent": ["@intx/agent@0.4.0", "", { "dependencies": { "@intx/inference": "0.4.0", "@intx/log": "0.4.0", "@intx/mime": "0.4.0", "@intx/types": "0.4.0", "arktype": "^2.1.29" } }, "sha512-qBlqDYnsaRMsug/lEKwUvPgQfjiwppTpfw5JaUh6pwKi0dGvckv3ugjJvLdHM052nTFMgLgOwo1LvbRjPZ5Uwg=="], - - "@intx/authz": ["@intx/authz@0.3.0", "", { "dependencies": { "@intx/types": "0.3.0" } }, "sha512-kyIKQAVjfB8H84mES0QA4cNnYfpPoyLmQITVBxiuXv1VO/K74TQZnCDpoo0IL7oFk2q7bbDXSAEE4ubMBVTHzA=="], - - "@intx/crypto": ["@intx/crypto@0.4.0", "", { "dependencies": { "@intx/types": "0.4.0" } }, "sha512-4/qWypC90GevbV9u1i/93tn46jsXhj8wE1Pn+awg8/ECpwhHnhPLT5Uy423Arr9eGmRmF6HiMyXcEgH1Z9/Rkw=="], - - "@intx/db": ["@intx/db@0.4.0", "", { "dependencies": { "@intx/authz": "0.4.0", "@intx/crypto": "0.4.0", "@intx/hub-common": "0.4.0", "@intx/log": "0.4.0", "@intx/types": "0.4.0", "arktype": "^2.1.29", "drizzle-orm": "^0.45.1", "postgres": "^3.4.8" } }, "sha512-B26emrRrpIAlRygTWs6akVA6V/rN07v4YRxj3iTxzAOJVayIHSmPuPmKxqKN/AsBgLq6H1q94RP9sq05yb4twg=="], - - "@intx/hub-api": ["@intx/hub-api@0.4.0", "", { "dependencies": { "@hono/standard-validator": "^0.2.2", "@intx/agent": "0.4.0", "@intx/authz": "0.4.0", "@intx/crypto": "0.4.0", "@intx/db": "0.4.0", "@intx/hub-common": "0.4.0", "@intx/hub-sessions": "0.4.0", "@intx/log": "0.4.0", "@intx/mime": "0.4.0", "@intx/storage-isogit": "0.4.0", "@intx/types": "0.4.0", "@intx/workflow-deploy": "0.4.0", "arktype": "^2.1.29", "better-auth": "^1.4.18", "drizzle-orm": "^0.45.1", "hono": "^4.11.9", "hono-openapi": "^1.2.0", "isomorphic-git": "^1.27.2", "ssri": "^12.0.0" } }, "sha512-3Txw7Im38BdRujYeRlxgrzCdvOG1v92556AjNa/6WXSwgXt9Mn2fB4RoQrA6oUHcm10Y94WZh3IjaQsXFl7Qvg=="], - - "@intx/hub-common": ["@intx/hub-common@0.4.0", "", { "dependencies": { "@intx/types": "0.4.0" } }, "sha512-4dcRX3kamqduf1Yil85GVsjXKnAnu6uqSYrAsl4vGpuV6R2DCy7z/PHd+DtIqk6TJrRdW7DtfkNXwSEc3dn2VQ=="], - - "@intx/hub-sessions": ["@intx/hub-sessions@0.4.0", "", { "dependencies": { "@intx/agent": "0.4.0", "@intx/crypto": "0.4.0", "@intx/db": "0.4.0", "@intx/hub-common": "0.4.0", "@intx/log": "0.4.0", "@intx/mime": "0.4.0", "@intx/pack-transport": "0.4.0", "@intx/storage-isogit": "0.4.0", "@intx/tool-packaging": "0.4.0", "@intx/types": "0.4.0", "@intx/workflow": "0.4.0", "@intx/workflow-deploy": "0.4.0", "arktype": "^2.1.29", "drizzle-orm": "^0.45.1", "isomorphic-git": "^1.27.2", "tar": "^7.5.1" } }, "sha512-/3nd/B/gENy3lq+MA2AR95tBu3Rkkh176XM+A8/mNMGnolPBGw+1JBUGGJ8gvdvAfo64dwRcEs3BI/VD7zFlPQ=="], - - "@intx/inference": ["@intx/inference@0.4.0", "", { "dependencies": { "@intx/log": "0.4.0", "@intx/types": "0.4.0", "arktype": "^2.1.29" } }, "sha512-cuaqCqotmjg0OzGUm8QmFJr1xX5wPVS0LeuhrxHsj0X3ImoCzVB4e/ttTc+DaP3K1tTMQ/8Q8HHCAPX36mOecQ=="], - - "@intx/log": ["@intx/log@0.4.0", "", { "dependencies": { "@logtape/hono": "^2.0.2", "@logtape/logtape": "^2.0.2" }, "peerDependencies": { "hono": "^4.0.0" }, "optionalPeers": ["hono"] }, "sha512-f1IPZ/9YpEqlA0I2UZPOlRJOTDoOFEZRwZoUVuw8DpgR1WatOMm4hA0E8o6Mtz+pA0ux7gVdls4y/JZcgypsfQ=="], - - "@intx/mime": ["@intx/mime@0.4.0", "", { "dependencies": { "@intx/crypto": "0.4.0", "@intx/types": "0.4.0", "arktype": "^2.1.29" } }, "sha512-sWrohF2uwVL5AZHbxIEUNiVUBhULff4XB7g6keCmyQWr296GKSvG/HmbihSXxiIvmmDzh9cl+6ZAJz/taxFNog=="], - - "@intx/pack-transport": ["@intx/pack-transport@0.4.0", "", { "dependencies": { "@intx/types": "0.4.0" } }, "sha512-I+axd067ZFXaTRtr2zmBXl6/yU253wLc/YWEdXiPiqz06mIspR2BFJMbSXPzV4pM26n+1tv/9E5VNBQtdiYmSA=="], - - "@intx/storage-isogit": ["@intx/storage-isogit@0.4.0", "", { "dependencies": { "@intx/log": "0.4.0", "@intx/mime": "0.4.0", "@intx/types": "0.4.0", "@isomorphic-git/lightning-fs": "4.7.0", "arktype": "^2.1.29", "buffer": "^6.0.3", "isomorphic-git": "^1.27.2" } }, "sha512-3H4dhUsWmAixLEBp79vq9NVUf6Z4++pKzaODWFEgNfhLX2WeV5J/t6soUamL6T/8eok/SDgQ/sZiZMiJ5kDKOw=="], - - "@intx/tool-packaging": ["@intx/tool-packaging@0.4.0", "", { "dependencies": { "@intx/agent": "0.4.0", "@intx/log": "0.4.0", "@intx/storage-isogit": "0.4.0", "@intx/types": "0.4.0", "arktype": "^2.1.29", "isomorphic-git": "^1.27.2", "npm-package-arg": "^12.0.2", "npm-pick-manifest": "^10.0.0", "npm-registry-fetch": "^19.0.0", "semver": "^7.7.2", "ssri": "^12.0.0", "tar": "^7.5.1" } }, "sha512-eBZIbGUcEBp0ppMCceA1PPd75LvXpUyWitqTIu7AsLxRNlU9gbE1hymWPXnhUKnt3LgSlHUi959Ehd6jG6mR7g=="], - - "@intx/types": ["@intx/types@0.4.0", "", { "dependencies": { "arktype": "^2.1.29", "semver": "^7.7.2" } }, "sha512-nJ2fcmskMOZNMRfbHs5ueKpCANHNttoL+sZgIoFaVUF/QLpFWfVp0YV72CQAaa9bVv9j5kRSwtSOCCGSlwQ6Dw=="], - - "@intx/workflow": ["@intx/workflow@0.4.0", "", { "dependencies": { "@intx/agent": "0.4.0", "@intx/inference": "0.4.0", "@intx/types": "0.4.0", "arktype": "^2.1.29" } }, "sha512-F5Is/lyKb4/WguAVYMDXwtI4ddUvqXbg34rcM6hPUOqupJHpbbD9+ZK/6TDfvbCiEgginbTVwMmNj6Fyg8vU+Q=="], - - "@intx/workflow-deploy": ["@intx/workflow-deploy@0.4.0", "", { "dependencies": { "@intx/agent": "0.4.0", "@intx/tool-packaging": "0.4.0", "@intx/types": "0.4.0", "@intx/workflow": "0.4.0" } }, "sha512-TSLg9lPZvJ+leg2n0I/SWxZjcNQtzsH2oKLD04ptEsBZcbIuMINsQeZBmYV5Brdovy0J/SrWZyrOJ8lc+k/Fdw=="], - - "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], - - "@isomorphic-git/idb-keyval": ["@isomorphic-git/idb-keyval@3.3.2", "", {}, "sha512-r8/AdpiS0/WJCNR/t/gsgL+M8NMVj/ek7s60uz3LmpCaTF2mEVlZJlB01ZzalgYzRLXwSPC92o+pdzjM7PN/pA=="], - - "@isomorphic-git/lightning-fs": ["@isomorphic-git/lightning-fs@4.7.0", "", { "dependencies": { "@isomorphic-git/idb-keyval": "3.3.2", "isomorphic-textencoder": "1.0.1", "just-debounce-it": "1.1.0", "just-once": "1.1.0" }, "bin": { "superblocktxt": "src/superblocktxt.js" } }, "sha512-eJg541itXKCOyj3DBAnd0KNY1mNgi29GCpy7FgKWsgatu/ulEKv+dMxxjhUNL68Jh3uPTaGNfeAjdJiHUYEGnw=="], - - "@logtape/hono": ["@logtape/hono@2.2.8", "", { "peerDependencies": { "@logtape/logtape": "^2.2.8", "hono": "^4.0.0" } }, "sha512-pDpClmd8j1zIXe3xqKsCnPUJbXM7hb7uRUUvs3nln9twEWB52plaARC2tO8D0iX3GwbqVqMKVtWfhhqtRVNAuA=="], - - "@logtape/logtape": ["@logtape/logtape@2.3.5", "", {}, "sha512-s4LlalG0JO2Fg8ENy1aQVdOS47VOD2pip6mOW29TNuCxW5zOhrQk/ck/xs77M7O15b2pK2KnDSr7L0Tf6LIUXg=="], - - "@noble/ciphers": ["@noble/ciphers@2.4.0", "", {}, "sha512-AnjFn0Jv92laAkvMrghlFZq4qQCIN/4DxFV/eooqtC2YTjB7kBeLMS2T9KJX4Dn+ZVXLOwK0lSgqDtx9gvxtiw=="], - - "@noble/hashes": ["@noble/hashes@2.4.0", "", {}, "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA=="], - - "@npmcli/agent": ["@npmcli/agent@4.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg=="], - - "@npmcli/fs": ["@npmcli/fs@5.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og=="], - - "@npmcli/redact": ["@npmcli/redact@4.0.0", "", {}, "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q=="], - - "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="], - - "@standard-community/standard-json": ["@standard-community/standard-json@0.3.6", "", { "dependencies": { "quansync": "^0.2.11" }, "peerDependencies": { "@standard-schema/spec": "^1.0.0", "@types/json-schema": "^7.0.15", "@valibot/to-json-schema": "^1.3.0", "arktype": "^2.1.20", "effect": "^3.20.0", "sury": "^10.0.0", "typebox": "^1.0.17", "valibot": "^1.4.2", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-to-json-schema"] }, "sha512-tuXKz1Ps4al0W4xhfwii7v4lskKtOv3QE7mcouRVThrBdOR6JzY4Vea3nsxcEnte84bOJvMvXGmOMWE3IBbHyA=="], - - "@standard-community/standard-openapi": ["@standard-community/standard-openapi@0.2.10", "", { "peerDependencies": { "@standard-community/standard-json": "^0.3.5", "@standard-schema/spec": "^1.0.0", "arktype": "^2.1.20", "effect": "^3.20.0", "openapi-types": "^12.1.3", "sury": "^10.0.0", "typebox": "^1.0.0", "valibot": "^1.4.2", "zod": "^3.25.0 || ^4.0.0", "zod-openapi": "^4" }, "optionalPeers": ["arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-openapi"] }, "sha512-whaCN9eu5zv5gu6Kyeh5am/S3AyS1nqo57/wAxs/KPS7+94UhBUZmkIetmXzm22qIV0zMrHt3y+2VfjkbkYYTA=="], - - "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@types/bun": ["@types/bun@1.1.14", "", { "dependencies": { "bun-types": "1.1.37" } }, "sha512-opVYiFGtO2af0dnWBdZWlioLBoxSdDO5qokaazLhq8XQtGZbY4pY3/JxY8Zdf/hEwGubbp7ErZXoN1+h2yesxA=="], - - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - - "@types/node": ["@types/node@22.10.5", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ=="], - - "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], - - "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], - - "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], - - "arkregex": ["arkregex@0.0.5", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw=="], - - "arktype": ["arktype@2.1.29", "", { "dependencies": { "@ark/schema": "0.56.0", "@ark/util": "0.56.0", "arkregex": "0.0.5" } }, "sha512-jyfKk4xIOzvYNayqnD8ZJQqOwcrTOUbIU4293yrzAjA3O1dWh61j71ArMQ6tS/u4pD7vabSPe7nG3RCyoXW6RQ=="], - - "async-lock": ["async-lock@1.4.1", "", {}, "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ=="], - - "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], - - "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - - "better-auth": ["better-auth@1.7.5", "", { "dependencies": { "@better-auth/core": "1.7.5", "@better-auth/drizzle-adapter": "1.7.5", "@better-auth/kysely-adapter": "1.7.5", "@better-auth/memory-adapter": "1.7.5", "@better-auth/mongo-adapter": "1.7.5", "@better-auth/prisma-adapter": "1.7.5", "@better-auth/telemetry": "1.7.5", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.2", "@noble/ciphers": "^2.2.0", "@noble/hashes": "^2.2.0", "better-call": "1.4.0", "defu": "^6.1.4", "jose": "^6.2.3", "kysely": "^0.28.17 || ^0.29.0", "nanostores": "^1.3.0", "zod": "^4.5.4" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "drizzle-kit": ">=0.31.4 || >=1.0.0-beta.1", "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-aKE0Zt2EPTpFvmq4/oATNyG/mAfc6JUqWkW9pzGlrVnzUb0lso7GJ9BPxD6JPhLqYV1a9zOAb0uAz1Q5fm+eHA=="], - - "better-call": ["better-call@1.4.0", "", { "dependencies": { "@better-auth/utils": "^0.5.0", "@better-fetch/fetch": "^1.3.1", "rou3": "^0.9.1", "set-cookie-parser": "^3.1.2" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-bBKOT4vv1kZLDgxVePdilk/Jwkn+dtRRsmi3DzHcDP+WnswyVl6dR59l2HEeP/0cB+bDoopASAesWDPIdd/zZA=="], - - "brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], - - "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - - "bun-types": ["bun-types@1.1.37", "", { "dependencies": { "@types/node": "~20.12.8", "@types/ws": "~8.5.10" } }, "sha512-C65lv6eBr3LPJWFZ2gswyrGZ82ljnH8flVE03xeXxKhi2ZGtFiO4isRKTKnitbSqtRAcaqYSR6djt1whI66AbA=="], - - "cacache": ["cacache@20.0.4", "", { "dependencies": { "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", "glob": "^13.0.0", "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^13.0.0" } }, "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA=="], - - "call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="], - - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - - "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], - - "clean-git-ref": ["clean-git-ref@2.0.1", "", {}, "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw=="], - - "content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], - - "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], - - "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], - - "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], - - "diff3": ["diff3@0.0.3", "", {}, "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g=="], - - "drizzle-orm": ["drizzle-orm@0.45.2", "", { "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" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "prisma", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="], - - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - - "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], - - "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], - - "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], - - "fast-text-encoding": ["fast-text-encoding@1.0.6", "", {}, "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w=="], - - "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], - - "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], - - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - - "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], - - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - - "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], - - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - - "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], - - "hono": ["hono@4.12.32", "", {}, "sha512-XcuyW9qE2kJn07PkecMOBd5Vq/hMy7mmGw+idz1yblbg9N17ijJODrvPkn7/dwL3Kulj8LcRJ69DLOWf91dRUg=="], - - "hono-openapi": ["hono-openapi@1.2.0", "", { "peerDependencies": { "@hono/standard-validator": "^0.2.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-t3u4v8YCltExDl4d9cLqg/mcrYFSs9Gjb5puF1CePPrvv1JQOo1Kc50HAmGt47CWHIoc/W8Q9LY3t3yqU0dxFw=="], - - "hosted-git-info": ["hosted-git-info@8.1.0", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw=="], - - "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], - - "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], - - "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - - "iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], - - "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - - "ip-address": ["ip-address@10.7.2", "", {}, "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w=="], - - "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], - - "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], - - "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - - "isomorphic-git": ["isomorphic-git@1.42.2", "", { "dependencies": { "async-lock": "^1.4.1", "clean-git-ref": "^2.0.1", "crc-32": "^1.2.0", "diff3": "0.0.3", "ignore": "^5.1.4", "minimisted": "^2.0.0", "pako": "^1.0.10", "pify": "^4.0.1", "readable-stream": "^4.0.0", "sha.js": "^2.4.12", "simple-get": "^4.0.1" }, "bin": { "isogit": "cli.cjs" } }, "sha512-Rb7czWhLO4JYqmg4an01nNomuAgFbWWK2CVfkHGBOvK5t7hrGTKOXPx/csscJ1j9o3uXFzNcC6njgvCafSmIPw=="], - - "isomorphic-textencoder": ["isomorphic-textencoder@1.0.1", "", { "dependencies": { "fast-text-encoding": "^1.0.0" } }, "sha512-676hESgHullDdHDsj469hr+7t3i/neBKU9J7q1T4RHaWwLAsaQnywC0D1dIUId0YZ+JtVrShzuBk1soo0+GVcQ=="], - - "jose": ["jose@6.2.12", "", {}, "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw=="], - - "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], - - "just-debounce-it": ["just-debounce-it@1.1.0", "", {}, "sha512-87Nnc0qZKgBZuhFZjYVjSraic0x7zwjhaTMrCKlj0QYKH6lh0KbFzVnfu6LHan03NO7J8ygjeBeD0epejn5Zcg=="], - - "just-once": ["just-once@1.1.0", "", {}, "sha512-+rZVpl+6VyTilK7vB/svlMPil4pxqIJZkbnN7DKZTOzyXfun6ZiFeq2Pk4EtCEHZ0VU4EkdFzG8ZK5F3PErcDw=="], - - "kysely": ["kysely@0.29.6", "", {}, "sha512-hHaB8C/rfzDDtr/t8YZwxAuPJTT0zHyaPoVzcXwDYhYNAgH/4sIfVhi/XLLIY+bL/FqaIJnjATDbi8ObSELmxg=="], - - "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "make-fetch-happen": ["make-fetch-happen@15.0.6", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", "ssri": "^13.0.0" } }, "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw=="], - - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - - "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], - - "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], - - "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - - "minimisted": ["minimisted@2.0.1", "", { "dependencies": { "minimist": "^1.2.5" } }, "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA=="], - - "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], - - "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], - - "minipass-fetch": ["minipass-fetch@5.0.2", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^2.0.0", "minizlib": "^3.0.1" }, "optionalDependencies": { "iconv-lite": "^0.7.2" } }, "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ=="], - - "minipass-flush": ["minipass-flush@1.0.7", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA=="], - - "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], - - "minipass-sized": ["minipass-sized@2.0.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA=="], - - "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "nanostores": ["nanostores@1.5.3", "", {}, "sha512-rQLB6eV4f2AW/n3L0JmwCROpaisYy9EDEADvEFSd1C/qG8hB6O5TPlh9A791JRbJr4CnMQBzptDcvD9OR1+6WA=="], - - "negotiator": ["negotiator@1.1.0", "", { "dependencies": { "content-type": "^2.1.0" } }, "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg=="], - - "npm-install-checks": ["npm-install-checks@7.1.2", "", { "dependencies": { "semver": "^7.1.1" } }, "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ=="], - - "npm-normalize-package-bin": ["npm-normalize-package-bin@4.0.0", "", {}, "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w=="], - - "npm-package-arg": ["npm-package-arg@12.0.2", "", { "dependencies": { "hosted-git-info": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^6.0.0" } }, "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA=="], - - "npm-pick-manifest": ["npm-pick-manifest@10.0.0", "", { "dependencies": { "npm-install-checks": "^7.1.0", "npm-normalize-package-bin": "^4.0.0", "npm-package-arg": "^12.0.0", "semver": "^7.3.5" } }, "sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ=="], - - "npm-registry-fetch": ["npm-registry-fetch@19.1.1", "", { "dependencies": { "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minizlib": "^3.0.1", "npm-package-arg": "^13.0.0", "proc-log": "^6.0.0" } }, "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw=="], - - "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], - - "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], - - "p-map": ["p-map@7.0.8", "", {}, "sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw=="], - - "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], - - "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], - - "pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="], - - "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], - - "postgres": ["postgres@3.4.9", "", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="], - - "proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="], - - "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], - - "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], - - "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], - - "rou3": ["rou3@0.9.2", "", {}, "sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ=="], - - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - - "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - - "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], - - "set-cookie-parser": ["set-cookie-parser@3.1.2", "", {}, "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw=="], - - "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], - - "sha.js": ["sha.js@2.4.12", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.0" }, "bin": { "sha.js": "bin.js" } }, "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w=="], - - "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], - - "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], - - "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], - - "socks": ["socks@2.8.10", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ=="], - - "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], - - "ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], - - "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], - - "tar": ["tar@7.5.22", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA=="], - - "to-buffer": ["to-buffer@1.2.2", "", { "dependencies": { "isarray": "^2.0.5", "safe-buffer": "^5.2.1", "typed-array-buffer": "^1.0.3" } }, "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw=="], - - "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], - - "typescript": ["typescript@5.7.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg=="], - - "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "validate-npm-package-name": ["validate-npm-package-name@6.0.2", "", {}, "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ=="], - - "which-typed-array": ["which-typed-array@1.1.23", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-JMh8aK+1B/0bk/YNupICmH5MgCq6yNLKYQUfsZtSDLLCCmxUkHjUY+oOlIa90lO7lHN1woAfpenLdlKpXy9o+A=="], - - "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - - "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], - - "zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="], - - "@corbits/artifacts/@intx/hub-api": ["@intx/hub-api@0.3.0", "", { "dependencies": { "@hono/standard-validator": "^0.2.2", "@intx/agent": "0.3.0", "@intx/authz": "0.3.0", "@intx/crypto": "0.3.0", "@intx/db": "0.3.0", "@intx/hub-common": "0.3.0", "@intx/hub-sessions": "0.3.0", "@intx/log": "0.3.0", "@intx/mime": "0.3.0", "@intx/storage-isogit": "0.3.0", "@intx/types": "0.3.0", "@intx/workflow-deploy": "0.3.0", "arktype": "^2.1.29", "better-auth": "^1.4.18", "drizzle-orm": "^0.45.1", "hono": "^4.11.9", "hono-openapi": "^1.2.0", "isomorphic-git": "^1.27.2", "ssri": "^12.0.0" } }, "sha512-o3KuJilWywIjQ82q+t3kC5syHm/JCexnEYegngBZ7HD4lvbEoUkJnmhVUS5NxEFsr2Oqp0jEe4kpC96oJ2sKDA=="], - - "@corbits/artifacts/@intx/types": ["@intx/types@0.3.0", "", { "dependencies": { "arktype": "^2.1.29", "semver": "^7.7.2" } }, "sha512-PJ+v3IhtfZ4J7ZqJ8E39TteRMnC6Y503Q0JeCJdskkK+jhcjx01z7TJSozucagauAlpyh8Hn/9CyWFl53Ji0DQ=="], - - "@intx/authz/@intx/types": ["@intx/types@0.3.0", "", { "dependencies": { "arktype": "^2.1.29", "semver": "^7.7.2" } }, "sha512-PJ+v3IhtfZ4J7ZqJ8E39TteRMnC6Y503Q0JeCJdskkK+jhcjx01z7TJSozucagauAlpyh8Hn/9CyWFl53Ji0DQ=="], - - "@intx/db/@intx/authz": ["@intx/authz@0.4.0", "", { "dependencies": { "@intx/types": "0.4.0" } }, "sha512-A9x4EzcECnFjhDoqj0XjaOb1GaxRyJucXOuIbY+fcPluUuMCGFvExD6DJZwjJmIYdeCvL8i96gPkncu/fAR9qA=="], - - "@intx/hub-api/@intx/authz": ["@intx/authz@0.4.0", "", { "dependencies": { "@intx/types": "0.4.0" } }, "sha512-A9x4EzcECnFjhDoqj0XjaOb1GaxRyJucXOuIbY+fcPluUuMCGFvExD6DJZwjJmIYdeCvL8i96gPkncu/fAR9qA=="], - - "@npmcli/agent/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], - - "better-call/@better-auth/utils": ["@better-auth/utils@0.5.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA=="], - - "bun-types/@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="], - - "cacache/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], - - "cacache/ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="], - - "make-fetch-happen/proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], - - "make-fetch-happen/ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="], - - "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "npm-registry-fetch/npm-package-arg": ["npm-package-arg@13.0.2", "", { "dependencies": { "hosted-git-info": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^7.0.0" } }, "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA=="], - - "npm-registry-fetch/proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], - - "path-scurry/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], - - "@corbits/artifacts/@intx/hub-api/@intx/agent": ["@intx/agent@0.3.0", "", { "dependencies": { "@intx/inference": "0.3.0", "@intx/log": "0.3.0", "@intx/mime": "0.3.0", "@intx/types": "0.3.0", "arktype": "^2.1.29" } }, "sha512-a7cgmH8FSsGQ7BpLKQhEwZYRfofWh3lWEGgKFsk53zdgjndtDfqAf2I64QMPAqC2Y2Npag/z1NA5lEsIqAGvPg=="], - - "@corbits/artifacts/@intx/hub-api/@intx/crypto": ["@intx/crypto@0.3.0", "", { "dependencies": { "@intx/types": "0.3.0" } }, "sha512-NsRzvkFGb0Pcsm9uLFFSXNl6Cbu+ii6VkYAw81qs9IbZzbAFfsZSUVQaX6EZuPNyJ8KK4KmBtqmioq1mhXsJww=="], - - "@corbits/artifacts/@intx/hub-api/@intx/db": ["@intx/db@0.3.0", "", { "dependencies": { "@intx/authz": "0.3.0", "@intx/crypto": "0.3.0", "@intx/log": "0.3.0", "@intx/types": "0.3.0", "arktype": "^2.1.29", "drizzle-orm": "^0.45.1", "postgres": "^3.4.8" } }, "sha512-MXNeUVJTftQuSHj5zSUBQsUcFN3wUBs8t6yT6lqjbaNxpT1C7kGx8qXb95WuadZln+krlLhyoUwGMF7EmYvlnw=="], - - "@corbits/artifacts/@intx/hub-api/@intx/hub-common": ["@intx/hub-common@0.3.0", "", { "dependencies": { "@intx/types": "0.3.0" } }, "sha512-dA8pTmBTu1JHGd3rGJrMsRWrai4GBBNnPxpZFFtXwQPzgL290lx2R+9BpRo2apdKbmgd+KOVshLvOdcUhAqghw=="], - - "@corbits/artifacts/@intx/hub-api/@intx/hub-sessions": ["@intx/hub-sessions@0.3.0", "", { "dependencies": { "@intx/agent": "0.3.0", "@intx/crypto": "0.3.0", "@intx/db": "0.3.0", "@intx/hub-common": "0.3.0", "@intx/log": "0.3.0", "@intx/mime": "0.3.0", "@intx/pack-transport": "0.3.0", "@intx/storage-isogit": "0.3.0", "@intx/tool-packaging": "0.3.0", "@intx/types": "0.3.0", "@intx/workflow": "0.3.0", "@intx/workflow-deploy": "0.3.0", "arktype": "^2.1.29", "drizzle-orm": "^0.45.1", "isomorphic-git": "^1.27.2", "tar": "^7.5.1" } }, "sha512-L9B/vuQakvbj4CYrEhbNwu0z/S9/2jBXmaJdxaC1zfakbOiHya4HFW/YAZKAgOi7z0cpxFftsK/RN3Yyrd/+Wg=="], - - "@corbits/artifacts/@intx/hub-api/@intx/log": ["@intx/log@0.3.0", "", { "dependencies": { "@logtape/hono": "^2.0.2", "@logtape/logtape": "^2.0.2" }, "peerDependencies": { "hono": "^4.0.0" }, "optionalPeers": ["hono"] }, "sha512-iooPSZjiEUO1A91X5STRhOAk+2RprliB4nbriV/dOOz86kuxZ60y5T8ISQ5Jaj1US870gntN6QwnefY38aWi2Q=="], - - "@corbits/artifacts/@intx/hub-api/@intx/mime": ["@intx/mime@0.3.0", "", { "dependencies": { "@intx/crypto": "0.3.0", "@intx/types": "0.3.0", "arktype": "^2.1.29" } }, "sha512-jpKZZpfWRQJ6fI8BEpHnr83N+1ZHrnOMY978B/6bgcjFn3qtm1f3KiR0yaQ8eF3r1VF327r3H2qiCCx0OFN9Ow=="], - - "@corbits/artifacts/@intx/hub-api/@intx/storage-isogit": ["@intx/storage-isogit@0.3.0", "", { "dependencies": { "@intx/log": "0.3.0", "@intx/mime": "0.3.0", "@intx/types": "0.3.0", "@isomorphic-git/lightning-fs": "4.7.0", "arktype": "^2.1.29", "buffer": "^6.0.3", "isomorphic-git": "^1.27.2" } }, "sha512-nzhl9WvxV66J1jjBfuAOjvwgiuS1DScnYomCPQrBxZNG35M5CYR3xyqR0RN0GfTj/7JlKWk6rF6Lu44aZDfzuw=="], - - "@corbits/artifacts/@intx/hub-api/@intx/workflow-deploy": ["@intx/workflow-deploy@0.3.0", "", { "dependencies": { "@intx/agent": "0.3.0", "@intx/tool-packaging": "0.3.0", "@intx/types": "0.3.0", "@intx/workflow": "0.3.0" } }, "sha512-eiCFS+e6DV9UTWFX5eINQCbXHi2w5Wn9SXtAOHZa4PIXOzxXKVJL/uXE/c6TfCVqhrsDdzYwrtt9koi5qmYXCg=="], - - "bun-types/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], - - "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], - - "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], - - "npm-registry-fetch/npm-package-arg/hosted-git-info": ["hosted-git-info@9.0.3", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg=="], - - "npm-registry-fetch/npm-package-arg/validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], - - "@corbits/artifacts/@intx/hub-api/@intx/agent/@intx/inference": ["@intx/inference@0.3.0", "", { "dependencies": { "@intx/log": "0.3.0", "@intx/types": "0.3.0", "arktype": "^2.1.29" } }, "sha512-2DxqWRp5cSziXgDXPomSy0HTLVesBCtZErsKXYFnliTwYM6U+GRKk5rzwWMMgMWMFbtUaiogPExcNQYBYmfjQA=="], - - "@corbits/artifacts/@intx/hub-api/@intx/hub-sessions/@intx/pack-transport": ["@intx/pack-transport@0.3.0", "", { "dependencies": { "@intx/types": "0.3.0" } }, "sha512-EQ1wM16323aIFhw3Ak6HwB8YqzKSAfRmJUwjIfqyXk2t6TGv4grws6dIs7Yau1dc372s/SO+GhvFaKgvI6XTWA=="], - - "@corbits/artifacts/@intx/hub-api/@intx/hub-sessions/@intx/tool-packaging": ["@intx/tool-packaging@0.3.0", "", { "dependencies": { "@intx/agent": "0.3.0", "@intx/log": "0.3.0", "@intx/storage-isogit": "0.3.0", "@intx/types": "0.3.0", "arktype": "^2.1.29", "isomorphic-git": "^1.27.2", "npm-package-arg": "^12.0.2", "npm-pick-manifest": "^10.0.0", "npm-registry-fetch": "^19.0.0", "semver": "^7.7.2", "ssri": "^12.0.0", "tar": "^7.5.1" } }, "sha512-Lr/xdEjXTindJRm+eITvfeqpW/yl71thYjr3HXl41GJ9wpuOZPCjm9vBoRVKhUSwmIUKBYbm2VplXCCXaS7f4g=="], - - "@corbits/artifacts/@intx/hub-api/@intx/hub-sessions/@intx/workflow": ["@intx/workflow@0.3.0", "", { "dependencies": { "@intx/agent": "0.3.0", "@intx/inference": "0.3.0", "@intx/types": "0.3.0", "arktype": "^2.1.29" } }, "sha512-vaIqTohNNdCVhvZ4btWbIbpjp3140e5939tQP7t+rSkucTvnn6US+bXho1qUJyNQ577mf3YXHGgBCW8ppsnscA=="], - - "@corbits/artifacts/@intx/hub-api/@intx/workflow-deploy/@intx/tool-packaging": ["@intx/tool-packaging@0.3.0", "", { "dependencies": { "@intx/agent": "0.3.0", "@intx/log": "0.3.0", "@intx/storage-isogit": "0.3.0", "@intx/types": "0.3.0", "arktype": "^2.1.29", "isomorphic-git": "^1.27.2", "npm-package-arg": "^12.0.2", "npm-pick-manifest": "^10.0.0", "npm-registry-fetch": "^19.0.0", "semver": "^7.7.2", "ssri": "^12.0.0", "tar": "^7.5.1" } }, "sha512-Lr/xdEjXTindJRm+eITvfeqpW/yl71thYjr3HXl41GJ9wpuOZPCjm9vBoRVKhUSwmIUKBYbm2VplXCCXaS7f4g=="], - - "@corbits/artifacts/@intx/hub-api/@intx/workflow-deploy/@intx/workflow": ["@intx/workflow@0.3.0", "", { "dependencies": { "@intx/agent": "0.3.0", "@intx/inference": "0.3.0", "@intx/types": "0.3.0", "arktype": "^2.1.29" } }, "sha512-vaIqTohNNdCVhvZ4btWbIbpjp3140e5939tQP7t+rSkucTvnn6US+bXho1qUJyNQ577mf3YXHGgBCW8ppsnscA=="], - - "npm-registry-fetch/npm-package-arg/hosted-git-info/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], - - "@corbits/artifacts/@intx/hub-api/@intx/hub-sessions/@intx/workflow/@intx/inference": ["@intx/inference@0.3.0", "", { "dependencies": { "@intx/log": "0.3.0", "@intx/types": "0.3.0", "arktype": "^2.1.29" } }, "sha512-2DxqWRp5cSziXgDXPomSy0HTLVesBCtZErsKXYFnliTwYM6U+GRKk5rzwWMMgMWMFbtUaiogPExcNQYBYmfjQA=="], - - "@corbits/artifacts/@intx/hub-api/@intx/workflow-deploy/@intx/workflow/@intx/inference": ["@intx/inference@0.3.0", "", { "dependencies": { "@intx/log": "0.3.0", "@intx/types": "0.3.0", "arktype": "^2.1.29" } }, "sha512-2DxqWRp5cSziXgDXPomSy0HTLVesBCtZErsKXYFnliTwYM6U+GRKk5rzwWMMgMWMFbtUaiogPExcNQYBYmfjQA=="], - } -} diff --git a/examples/reference-host/package.json b/examples/reference-host/package.json deleted file mode 100644 index 0cdc9ac..0000000 --- a/examples/reference-host/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "reference-host", - "private": true, - "type": "module", - "scripts": { - "test": "bun test" - }, - "devDependencies": { - "@types/bun": "1.1.14", - "@types/node": "22.10.5", - "typescript": "5.7.2" - }, - "dependencies": { - "@corbits/artifacts": "file:../..", - "@intx/db": "0.4.0", - "@intx/hub-api": "0.4.0", - "@intx/hub-common": "0.4.0", - "@intx/hub-sessions": "0.4.0", - "@intx/types": "0.4.0", - "drizzle-orm": "0.45.2", - "hono": "4.12.32", - "hono-openapi": "1.2.0", - "postgres": "3.4.9" - } -} diff --git a/examples/reference-host/src/index.ts b/examples/reference-host/src/index.ts index cec62eb..e9cd724 100644 --- a/examples/reference-host/src/index.ts +++ b/examples/reference-host/src/index.ts @@ -11,7 +11,7 @@ // rows for it. // // This module only BUILDS the host. The acceptance scenarios live in -// `test/acceptance.test.ts` and run under `bun test`, so they are collected by +// `tests/reference-host.test.ts` and run under `bun run test`, so they are collected by // CI like any other test instead of being a hand-rolled assert script nothing // executes. import { sql } from "drizzle-orm"; @@ -49,24 +49,10 @@ import { type ContentStore, type SerializedArtifactBase, } from "@corbits/artifacts"; - -export const DATABASE_URL = - process.env.ARTIFACT_DATABASE_URL ?? - "postgres://postgres:postgres@localhost:5457/artifact_core"; +import { databaseConfig, DATABASE_URL } from "../../../src/test-helpers.js"; const EPOCH = new Date(0); -function parsePostgresUrl(raw: string): DBConfig { - const url = new URL(raw); - return { - host: url.hostname, - port: Number(url.port === "" ? "5432" : url.port), - user: decodeURIComponent(url.username), - password: decodeURIComponent(url.password), - database: url.pathname.replace(/^\//, ""), - }; -} - /** Display-only decorator. Adds a label, never changes what is returned. */ async function decorate(_tenantId: string, rows: readonly SerializedArtifactBase[]) { for (const row of rows) { @@ -138,7 +124,7 @@ export async function createReferenceHost(): Promise { // ONE pool. The artifact module mounts on the handle the host already has // from `createDB` — the seam takes any drizzle postgres-js instance, so there // is no second connection to the same database. - const config = parsePostgresUrl(DATABASE_URL); + const config = databaseConfig(DATABASE_URL); const hub = createDB(config); const db: ArtifactDb = hub.db; @@ -285,21 +271,15 @@ export async function createReferenceHost(): Promise { }; // A bare Interchange host: real sidecar router, real event-collector - // registry. It runs no agent sessions, so its SessionService refuses every - // launch verb rather than pretending to serve it. - const authenticateSidecar: SidecarAuthenticator = async ({ sidecarId }) => ({ - kind: "sidecar", - sidecarId, - }); + // registry. It runs no sidecars or agent sessions, so it rejects every + // sidecar handshake and its SessionService refuses every verb rather than + // pretending to serve it. + const authenticateSidecar: SidecarAuthenticator = async () => null; const refuse = (verb: string) => (): never => { throw new Error(`reference-host runs no agent sessions: ${verb}`); }; const sessionService: SessionService = { stageWorkflowStep: refuse("stageWorkflowStep"), - deployInstanceAtHead: refuse("deployInstanceAtHead"), - deploySingleStepAtHead: refuse("deploySingleStepAtHead"), - deployWorkflowDefinition: refuse("deployWorkflowDefinition"), - sendUserMessage: refuse("sendUserMessage"), endSession: refuse("endSession"), }; @@ -312,7 +292,10 @@ export async function createReferenceHost(): Promise { getSession, authHandler: () => new Response("", { status: 404 }), db: hub.db, - sidecarRouter: createSidecarRouter({ authenticateSidecar }), + sidecarRouter: createSidecarRouter({ + authenticateSidecar, + validateSidecarIdentity: async () => false, + }), sessionService, eventCollectors: createEventCollectorRegistry({ db: hub.db }), assetService: null, diff --git a/examples/reference-host/tsconfig.json b/examples/reference-host/tsconfig.json deleted file mode 100644 index d7ccb48..0000000 --- a/examples/reference-host/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "types": [ - "node", - "@types/bun" - ] - }, - "include": [ - "src", - "test" - ] -} diff --git a/package.json b/package.json index 0349faa..e7eb16b 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,7 @@ "build": "rm -rf dist && tsc -p tsconfig.build.json", "prepack": "bun run build", "test": "bun test src tests", - "test:coverage": "bun test --coverage src tests", - "test:acceptance": "bun run build && bun test --cwd examples/reference-host" + "test:coverage": "bun test --coverage src tests" }, "dependencies": { "@hono/standard-validator": "^0.2.3", @@ -92,7 +91,8 @@ "@intx/authz": "0.4.0", "@intx/agent": "0.4.0", "@intx/db": "0.4.0", - "@intx/hub-common": "0.4.0" + "@intx/hub-common": "0.4.0", + "@intx/hub-sessions": "0.4.0" }, "overrides": { "drizzle-orm": "0.45.2" diff --git a/examples/reference-host/test/acceptance.test.ts b/tests/reference-host.test.ts similarity index 99% rename from examples/reference-host/test/acceptance.test.ts rename to tests/reference-host.test.ts index 784e444..fb474bc 100644 --- a/examples/reference-host/test/acceptance.test.ts +++ b/tests/reference-host.test.ts @@ -17,7 +17,7 @@ import { UnsupportedUploadTypeError, type ContentStore, } from "@corbits/artifacts"; -import { createReferenceHost, type ReferenceHost } from "../src/index.js"; +import { createReferenceHost, type ReferenceHost } from "../examples/reference-host/src/index.js"; let host: ReferenceHost; const json = async (res: Response): Promise => (await res.json()) as T; diff --git a/tsconfig.json b/tsconfig.json index 80acaa9..c5d604d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,8 @@ "verbatimModuleSyntax": true, "skipLibCheck": true, "noEmit": true, - "types": ["node", "@types/bun"] + "types": ["node", "@types/bun"], + "paths": { "@corbits/artifacts": ["./src/index.ts"] } }, "include": ["src", "tests"] } From da20d57a7b0cb6866b7326a7c998c1a18ccdd520 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 25 Sep 2026 07:25:58 -0700 Subject: [PATCH 8/9] test(migrations): cover the documented createArtifactDb boot path --- tests/lib/db-harness.ts | 6 ++++++ tests/migrations.test.ts | 18 ++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/lib/db-harness.ts b/tests/lib/db-harness.ts index cf588de..a151b9d 100644 --- a/tests/lib/db-harness.ts +++ b/tests/lib/db-harness.ts @@ -37,6 +37,12 @@ export type TestDb = { close: () => Promise; }; +export function connectionString(config: DBConfig): string { + const user = encodeURIComponent(config.user); + const password = encodeURIComponent(config.password ?? ""); + return `postgres://${user}:${password}@${config.host}:${config.port}/${config.database}`; +} + async function admin(run: (sql: postgres.Sql) => Promise): Promise { const sql = postgres(DATABASE_URL, { max: 1, onnotice: () => undefined }); try { diff --git a/tests/migrations.test.ts b/tests/migrations.test.ts index 9591c47..7bbfef0 100644 --- a/tests/migrations.test.ts +++ b/tests/migrations.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { sql } from "drizzle-orm"; -import { runArtifactMigrations } from "../src/index.js"; -import { createTestDb, seedActor, type TestDb } from "./lib/db-harness.js"; +import { createArtifactDb, runArtifactMigrations } from "../src/index.js"; +import { connectionString, createTestDb, seedActor, type TestDb } from "./lib/db-harness.js"; const TABLES = ["artifact", "artifact_version", "mail_attachment_ref", "upload"]; @@ -75,4 +75,18 @@ describe("runArtifactMigrations", () => { "artifact_version.parent_version_ids _text", ]); }); + + test("a host boots with createArtifactDb after migrating, and close releases it", async () => { + const { db, close } = createArtifactDb(connectionString(testDb.config)); + try { + const rows = await db.execute<{ table_name: string }>(sql` + SELECT table_name FROM information_schema.tables + WHERE table_schema = 'artifacts' + ORDER BY table_name + `); + expect(rows.map((row) => row.table_name)).toEqual(TABLES); + } finally { + await close(); + } + }); }); From cca67385f243f6fa4c02b66d568f89ed2fdcfb8f Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 25 Sep 2026 11:07:53 -0700 Subject: [PATCH 9/9] test: skip teardown when suite setup failed A failed beforeAll left afterAll throwing on an undefined handle, which buried the real setup error under a second TypeError. --- tests/file-round-trip.test.ts | 2 +- tests/migrations.test.ts | 2 +- tests/reference-host.test.ts | 2 +- tests/upload-policy.test.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/file-round-trip.test.ts b/tests/file-round-trip.test.ts index 0f0a041..6d22cd2 100644 --- a/tests/file-round-trip.test.ts +++ b/tests/file-round-trip.test.ts @@ -20,7 +20,7 @@ beforeAll(async () => { }); afterAll(async () => { - await testDb.close(); + await testDb?.close(); await rm(dir, { recursive: true, force: true }); }); diff --git a/tests/migrations.test.ts b/tests/migrations.test.ts index 7bbfef0..326155e 100644 --- a/tests/migrations.test.ts +++ b/tests/migrations.test.ts @@ -12,7 +12,7 @@ beforeAll(async () => { }); afterAll(async () => { - await testDb.close(); + await testDb?.close(); }); async function packageTables(): Promise { diff --git a/tests/reference-host.test.ts b/tests/reference-host.test.ts index fb474bc..22cb85c 100644 --- a/tests/reference-host.test.ts +++ b/tests/reference-host.test.ts @@ -33,7 +33,7 @@ beforeAll(async () => { }); afterAll(async () => { - await host.close(); + await host?.close(); }); // The one artifact the later scenarios (archive, authz, deep link) reuse. diff --git a/tests/upload-policy.test.ts b/tests/upload-policy.test.ts index bf5b731..9f7c34c 100644 --- a/tests/upload-policy.test.ts +++ b/tests/upload-policy.test.ts @@ -13,7 +13,7 @@ beforeAll(async () => { }); afterAll(async () => { - await testDb.close(); + await testDb?.close(); }); async function upload(file: File): Promise {