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 9d4eb1f..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 @@ -36,16 +35,22 @@ 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, +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 -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. @@ -76,9 +81,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/bun.lock b/bun.lock index cfa6ef3..a50144f 100644 --- a/bun.lock +++ b/bun.lock @@ -13,6 +13,8 @@ "@intx/authz": "0.4.0", "@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/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/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 29956b2..e7eb16b 100644 --- a/package.json +++ b/package.json @@ -56,9 +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:acceptance": "bun run build && bun test --cwd examples/reference-host" + "test": "bun test src tests", + "test:coverage": "bun test --coverage src tests" }, "dependencies": { "@hono/standard-validator": "^0.2.3", @@ -91,7 +90,9 @@ "@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", + "@intx/hub-sessions": "0.4.0" }, "overrides": { "drizzle-orm": "0.45.2" 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); - }); -}); - 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/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/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"); - }); -}); diff --git a/tests/file-round-trip.test.ts b/tests/file-round-trip.test.ts new file mode 100644 index 0000000..6d22cd2 --- /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/db-harness.ts b/tests/lib/db-harness.ts new file mode 100644 index 0000000..a151b9d --- /dev/null +++ b/tests/lib/db-harness.ts @@ -0,0 +1,146 @@ +// 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; +}; + +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 { + 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/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) }; + }, + }; +} diff --git a/tests/migrations.test.ts b/tests/migrations.test.ts new file mode 100644 index 0000000..326155e --- /dev/null +++ b/tests/migrations.test.ts @@ -0,0 +1,92 @@ +import { afterAll, beforeAll, describe, expect, test } from "bun:test"; +import { sql } from "drizzle-orm"; +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"]; + +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", + ]); + }); + + 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(); + } + }); +}); 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..22cb85c 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; @@ -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 new file mode 100644 index 0000000..9f7c34c --- /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); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index ff999de..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", "scripts"] + "include": ["src", "tests"] }