feat(sdk): Effect v4 + @effect/openapi-generator; typed pf.api & example ladder

Drop Orval for the first-party @effect/openapi-generator (OpenAPI 3.1 ->
Effect Schema + a typed HttpClient client) and bump effect 3.19 ->
4.0.0-beta.98. Port the hand-written surfaces to the v4 API (Result over
Either, Context.Service, Codec, Literals/Union arrays, Stream/Schedule/
Effect renames). Transport (CA-pinning fetch) and the reconnecting SSE
source are kept intact.

Make the SDK approachable for non-Effect users:
- Add pf.api.* on the Promise facade: the generated client surfaced as
  typed, Promise-native methods (await pf.api.listPairedClients()), so REST
  calls are autocompleted and checked instead of stringly-typed
  pf.request(method, path, body) + `as` casts. Zero-drift veneer over
  make(httpClient), backed by the same pinning fetch. pf.request stays as
  the untyped escape hatch.
- Re-tier examples into a 1-4 complexity ladder, rewritten onto pf.api.*
  (the typed payloads caught a wrong `launch` shape in provider-sync);
  the Effect example is labelled advanced. Add examples/ to tsconfig so
  they are typechecked (stops rot).

typecheck + 19 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-17 12:40:04 +02:00
parent 27a5d8daac
commit f012ebbcba
19 changed files with 1645 additions and 1974 deletions
+27 -4
View File
@@ -6,7 +6,8 @@ stream start/stop, pairing, displays, library) — built on [Effect](https://eff
Two surfaces, one core: Two surfaces, one core:
- **`@punktfunk/host`** — the Promise facade, the front door. `connect()`, `await`, `.on()`. - **`@punktfunk/host`** — the Promise facade, the front door. `connect()`, then `pf.api.*` (the
typed management API — every endpoint autocompletes, every response is typed) and `pf.events.on()`.
You never need to know Effect exists. You never need to know Effect exists.
- **`@punktfunk/host/effect`** — the Effect-native surface for plugins and composed programs: - **`@punktfunk/host/effect`** — the Effect-native surface for plugins and composed programs:
the `PunktfunkHost` service + layer, `Stream`-based events, typed errors the `PunktfunkHost` service + layer, `Stream`-based events, typed errors
@@ -21,15 +22,25 @@ import { connect } from "@punktfunk/host";
const pf = await connect(); // zero config on the host box const pf = await connect(); // zero config on the host box
// Typed API — autocomplete every endpoint, typed responses, no hand-written paths or casts.
const clients = await pf.api.listPairedClients();
console.log(`${clients.length} paired clients`);
// Live events:
pf.events.on("stream.started", (e) => { pf.events.on("stream.started", (e) => {
console.log(`${e.stream.client} started ${e.stream.mode}${e.stream.hdr ? " HDR" : ""}`); console.log(`${e.stream.client} started ${e.stream.mode}${e.stream.hdr ? " HDR" : ""}`);
}); });
pf.events.on("pairing.pending", async (e) => { pf.events.on("pairing.pending", async (e) => {
// notify your phone, then decide through the API: // notify your phone, then decide through the typed API:
// await pf.request("POST", `/native/pending/${id}/approve`); const pending = await pf.api.listPendingDevices();
const match = pending.find((d) => d.fingerprint === e.device.fingerprint);
if (match) await pf.api.approvePendingDevice(String(match.id), { payload: {} });
}); });
``` ```
Need something the generated client doesn't cover? `pf.request(method, path, body)` is the untyped
escape hatch (returns `unknown`).
The same, Effect-native: The same, Effect-native:
```ts ```ts
@@ -43,6 +54,18 @@ const program = events().pipe(
Effect.runPromise(program.pipe(Effect.provide(PunktfunkHostLive()))); Effect.runPromise(program.pipe(Effect.provide(PunktfunkHostLive())));
``` ```
## Examples
A complexity ladder in [`examples/`](./examples) — start at the top:
1. [`tail-events.ts`](./examples/tail-events.ts) — **hello world**: connect, one typed call, tail events.
2. [`notify-pairing.ts`](./examples/notify-pairing.ts) — **event → decision**: approve/deny pairing through the typed API.
3. [`provider-sync.ts`](./examples/provider-sync.ts) — **typed bulk REST**: declaratively reconcile a game-library provider.
4. [`couch-preset.effect.ts`](./examples/couch-preset.effect.ts) — **advanced, Effect-native**: only if you're composing Effect programs.
Examples 13 are the plain Promise facade and cover most automation; you only need example 4's
Effect surface for composed, interruptible programs. Run any with `bun examples/<file>.ts`.
## Connection resolution ## Connection resolution
`connect()` / `PunktfunkHostLive()` resolve, in order: `connect()` / `PunktfunkHostLive()` resolve, in order:
@@ -166,7 +189,7 @@ Windows Task Scheduler: a task triggered *At log on* running
```sh ```sh
bun install bun install
bun run gen # regenerate src/gen/schemas.ts from ../api/openapi.json bun run gen # regenerate src/gen/punktfunk.ts from ../api/openapi.json (@effect/openapi-generator)
bun run typecheck bun run typecheck
bun test bun test
``` ```
+91 -198
View File
@@ -5,11 +5,12 @@
"": { "": {
"name": "@punktfunk/host", "name": "@punktfunk/host",
"dependencies": { "dependencies": {
"effect": "^3.19.0", "effect": "^4.0.0-beta.98",
}, },
"devDependencies": { "devDependencies": {
"@effect/openapi-generator": "4.0.0-beta.98",
"@effect/platform-node": "4.0.0-beta.98",
"@types/bun": "^1.3.0", "@types/bun": "^1.3.0",
"orval": "^8.20.0",
"typescript": "^5.9.3", "typescript": "^5.9.3",
}, },
"optionalDependencies": { "optionalDependencies": {
@@ -18,286 +19,178 @@
}, },
}, },
"packages": { "packages": {
"@commander-js/extra-typings": ["@commander-js/extra-typings@15.0.0", "", { "peerDependencies": { "commander": "~15.0.0" } }, "sha512-yeJlba62xqmkgELUsn7356MEnzLLu/fw2x4lofFqGnXh6YysRdEs2BaLeLtg1+KU0AXvMeqQvTTp+3hBEBK+EA=="], "@effect/openapi-generator": ["@effect/openapi-generator@4.0.0-beta.98", "", { "dependencies": { "swagger2openapi": "^7.0.8" }, "peerDependencies": { "@effect/platform-node": "^4.0.0-beta.98", "effect": "^4.0.0-beta.98" }, "bin": { "openapigen": "dist/bin.js" } }, "sha512-7bqawr/HqJWqQ8H/bHyzBlLPA3LIIm3Y+cGYlIxnC/QVK795QpiEXb7uxTnP7V7w49V0sBtTerv4/9ZjsMffLQ=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.98", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.98", "mime": "^4.1.0", "undici": "^8.7.0" }, "peerDependencies": { "effect": "^4.0.0-beta.98", "ioredis": "^5.7.0" } }, "sha512-IQu1TiLXQEDSGkDBllyYjVadf+UqdjptryqX4mmktVTTbGDq7X4uVxe7cSgXuqZvyfG6kagTzwj2lfynxOaKQg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.98", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.21.0" }, "peerDependencies": { "effect": "^4.0.0-beta.98" } }, "sha512-iySXaffnCJX1sNAIp79ghhIeui9E5qwUQyqd1VLPkB9UNO4vdpd9B5fTEXwe7S/GusL4jsk9vSvX38XJgRFG1w=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], "@exodus/schemasafe": ["@exodus/schemasafe@1.3.0", "", {}, "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], "@ioredis/commands": ["@ioredis/commands@1.10.0", "", {}, "sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4", "", { "os": "linux", "cpu": "arm" }, "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
"@gerrit0/mini-shiki": ["@gerrit0/mini-shiki@3.23.0", "", { "dependencies": { "@shikijs/engine-oniguruma": "^3.23.0", "@shikijs/langs": "^3.23.0", "@shikijs/themes": "^3.23.0", "@shikijs/types": "^3.23.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg=="],
"@orval/angular": ["@orval/angular@8.22.0", "", { "dependencies": { "@orval/core": "8.22.0" } }, "sha512-lSvaNj+VHGIWBQOG104HfPNrk2xGjpArwy67u6ZBPzHo/OtDE5Tm1t+ARYseCOElFr4z3i3A62qjFLFy6zj00Q=="],
"@orval/axios": ["@orval/axios@8.22.0", "", { "dependencies": { "@orval/core": "8.22.0" } }, "sha512-fFu0UgTbpI9a1ayM7qCs55MMy6MlVgOpE3BBXGukCTBSwNwFLN47WdzQev/x0mAcs58pNKYT0KL4R9MXGnmgfg=="],
"@orval/core": ["@orval/core@8.22.0", "", { "dependencies": { "@scalar/openapi-types": "0.8.0", "acorn": "^8.15.0", "compare-versions": "^6.1.1", "debug": "^4.4.3", "esbuild": "^0.28.0", "esutils": "2.0.3", "fs-extra": "^11.3.2", "jiti": "^2.6.1", "jsesc": "^3.0.0", "remeda": "^2.33.6", "tinyglobby": "^0.2.16", "typedoc": "^0.28.19" }, "peerDependencies": { "@faker-js/faker": ">=10" }, "optionalPeers": ["@faker-js/faker"] }, "sha512-uKYi7+Smg6oQ2MxE0AS2FNI7bwssoxGLh391Uk0FU+DcTcSv9q2GmvoM8uwd827Hok29kD7AegHE8Mhmsa5uWg=="],
"@orval/effect": ["@orval/effect@8.22.0", "", { "dependencies": { "@orval/core": "8.22.0", "remeda": "^2.33.6" } }, "sha512-DACiw2+0ZsPJPKQbYlb9qFFFppaCBaT/HgIq2S1asH9ZCOOTZKm9VrRXpeCWINHC2w1BpdSATpytv+61UT5Y/A=="],
"@orval/fetch": ["@orval/fetch@8.22.0", "", { "dependencies": { "@orval/core": "8.22.0" } }, "sha512-G0r6hOdZG963H/8S4Vk1kWfU/hkQC/cwpDZL+mzcXgzrdG9NDloshTqmge/jP5lPsFAwfcRUmmts10OmuIE4BQ=="],
"@orval/hono": ["@orval/hono@8.22.0", "", { "dependencies": { "@orval/core": "8.22.0", "@orval/zod": "8.22.0", "fs-extra": "^11.3.2" }, "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-GMCpGZqCuGYSu10KTt2q3WYzCqEcTGxr18z3HgHv9dX22sv/V76dlLBh3SE72xp8Z7/jmoq3GKBdgU9k98ju0w=="],
"@orval/mcp": ["@orval/mcp@8.22.0", "", { "dependencies": { "@orval/core": "8.22.0", "@orval/fetch": "8.22.0", "@orval/zod": "8.22.0" } }, "sha512-ySa4EenAF8YMCpbmddJGO3lItTPC8Uf/iT+P2ezaowVgxOCPX/fjG7dd0fnVmrr0vaphi53yWx5o5DlT17FzLg=="],
"@orval/mock": ["@orval/mock@8.22.0", "", { "dependencies": { "@orval/core": "8.22.0", "remeda": "^2.33.6" } }, "sha512-ZCEFpdi4z+YOCg0Tsgz6DO/M1TSfYxE5urDWJgYNUKeD8XDEHFb2IgTiiaAwJunqWnRQuiMfGp9G81+u10Kx6w=="],
"@orval/query": ["@orval/query@8.22.0", "", { "dependencies": { "@orval/core": "8.22.0", "@orval/fetch": "8.22.0", "remeda": "^2.33.6" } }, "sha512-IH9049z860CLUeGEezGv+yOvUvcAyDnd9doDe1Ryi0WxsDul2Vh3LjCRUEQf4JZiyZezadWYYGzs0lwnCn/afA=="],
"@orval/solid-start": ["@orval/solid-start@8.22.0", "", { "dependencies": { "@orval/core": "8.22.0" } }, "sha512-Q1ZCWOrA6/VJnyj62XpPxXti9NpAA4lDZVqPL2uQ5gbxv+T+azEaazKqYIpSBJXbl1aEezujdcyg/DVUakWKEw=="],
"@orval/swr": ["@orval/swr@8.22.0", "", { "dependencies": { "@orval/core": "8.22.0", "@orval/fetch": "8.22.0" } }, "sha512-OSeWX3Af8ESapKIo3XpbOaTMaG8oeEtuucFyOUauqg6NyC6/9Ikcf2csBdF0AuIKA0QTcjcXxra/ccj1f7KnRg=="],
"@orval/zod": ["@orval/zod@8.22.0", "", { "dependencies": { "@orval/core": "8.22.0", "jsesc": "^3.0.0", "remeda": "^2.33.6" } }, "sha512-briHtUTz79fvCeIFfGW3IbmUIF9DOotUoWFa/sKUjRUG8PMGDWSjQzO8QdLzLoETVGo9g4TfGwp5Xjcs81GZTA=="],
"@scalar/helpers": ["@scalar/helpers@0.9.1", "", {}, "sha512-UKSLIPfN++f+zzbsZ+F6I0lNrR4yX4PtokjHgGwGiHapxT5VJqAF4zFTIP2KCd27XG7KnAIA7qq62O4xRBxc6w=="],
"@scalar/json-magic": ["@scalar/json-magic@0.12.18", "", { "dependencies": { "@scalar/helpers": "0.9.1", "pathe": "^2.0.3", "yaml": "^2.8.3" } }, "sha512-3oZr+jUUiwD3C+x2CROBtlyIUnwt9ScRhFfczuOTPZlD7Pnb5cyTTfkiHbxxU4qd66ij0Z3JA4X/Nj0OADyEiA=="],
"@scalar/openapi-parser": ["@scalar/openapi-parser@0.28.9", "", { "dependencies": { "@scalar/helpers": "0.9.1", "@scalar/json-magic": "0.12.18", "@scalar/openapi-types": "0.9.2", "@scalar/openapi-upgrader": "0.2.10", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.8.3" } }, "sha512-AYmqO7dR6zc1TSVhW5BpwY0nUMfG5JuF85/8YXn5ZXcCvjgoJu8dbxX52vIGK9rRwSn07HXCdKk7KPIF4xfOTA=="],
"@scalar/openapi-types": ["@scalar/openapi-types@0.8.0", "", {}, "sha512-WmaxVSfvY5K/TwcG2B2TU1WOe1As1uc2s7myswtP6dBlcjU3hM08SApxv/jmyGaCE8t4gO5BBhmHY4pDUfmr2g=="],
"@scalar/openapi-upgrader": ["@scalar/openapi-upgrader@0.2.10", "", { "dependencies": { "@scalar/openapi-types": "0.9.2" } }, "sha512-FRYwCl4IXRi7yAF5/3Jho0jc2akTZfr/vRTYMjhm+96Fq0LaEuHJAvPcSkkU+j3IWGie6LaCQMZTngcSjoIzgA=="],
"@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g=="],
"@shikijs/langs": ["@shikijs/langs@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg=="],
"@shikijs/themes": ["@shikijs/themes@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA=="],
"@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="],
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
"@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="],
"@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="], "@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
"acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="], "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="],
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="],
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
"chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], "call-me-maybe": ["call-me-maybe@1.0.2", "", {}, "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="],
"commander": ["commander@15.0.0", "", {}, "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg=="], "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
"compare-versions": ["compare-versions@6.1.1", "", {}, "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg=="], "cluster-key-slot": ["cluster-key-slot@1.1.1", "", {}, "sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"effect": ["effect@3.22.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-jhYFe0zTlIRqYFrKTS+6luhmS/Tm0f+JLo0K9KUxvtFab1SUGEszQi2ehOP6QzAZvy831lDmTwwzvVDZSPNz3g=="], "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], "effect": ["effect@4.0.0-beta.98", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.9.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.4", "multipasta": "^0.2.8", "toml": "^4.1.2", "uuid": "^14.0.1", "yaml": "^2.9.0" } }, "sha512-oz+bsG5h+6RNrw4t5GMfQrk/xBS8ROoqkYsuvRhBr5O7mCOrpvH/hbw+QrDzvKIpX4HJClwm86F94c87W0sJxg=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="], "es6-promise": ["es6-promise@3.3.1", "", {}, "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg=="],
"fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-check": ["fast-check@4.9.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg=="],
"fast-uri": ["fast-uri@3.1.3", "", {}, "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg=="], "fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="],
"figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
"find-up": ["find-up@8.0.0", "", { "dependencies": { "locate-path": "^8.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww=="], "http2-client": ["http2-client@1.3.5", "", {}, "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA=="],
"fs-extra": ["fs-extra@11.3.6", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA=="], "ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="],
"get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], "ioredis": ["ioredis@5.11.1", "", { "dependencies": { "@ioredis/commands": "1.10.0", "cluster-key-slot": "1.1.1", "debug": "4.4.3", "denque": "2.1.0", "redis-errors": "1.2.0", "redis-parser": "3.0.0", "standard-as-callback": "2.1.0" } }, "sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A=="],
"get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="],
"human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], "mime": ["mime@4.1.0", "", { "bin": { "mime": "bin/cli.js" } }, "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw=="],
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
"is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
"is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
"js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="],
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
"jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="],
"leven": ["leven@4.1.0", "", {}, "sha512-KZ9W9nWDT7rF7Dazg8xyLHGLrmpgq2nVNFUckhqdW3szVP6YhCpp/RAnpmVExA9JvrMynjwSLVrEj3AepHR6ew=="],
"linkify-it": ["linkify-it@5.0.2", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q=="],
"locate-path": ["locate-path@8.0.0", "", { "dependencies": { "p-locate": "^6.0.0" } }, "sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg=="],
"lunr": ["lunr@2.3.9", "", {}, "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="],
"markdown-it": ["markdown-it@14.3.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.5.0", "linkify-it": "^5.0.2", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw=="],
"mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="],
"minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], "msgpackr": ["msgpackr@2.0.4", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-o1C5KRmuRt+apqMr1HuGSqWStZoRBUpEsCsl15uM9VdAF1qHLtvMOU2En747EnTyEl6c4pzPewRMFF31s1CNbA=="],
"orval": ["orval@8.22.0", "", { "dependencies": { "@commander-js/extra-typings": "^15.0.0", "@orval/angular": "8.22.0", "@orval/axios": "8.22.0", "@orval/core": "8.22.0", "@orval/effect": "8.22.0", "@orval/fetch": "8.22.0", "@orval/hono": "8.22.0", "@orval/mcp": "8.22.0", "@orval/mock": "8.22.0", "@orval/query": "8.22.0", "@orval/solid-start": "8.22.0", "@orval/swr": "8.22.0", "@orval/zod": "8.22.0", "@scalar/json-magic": "^0.12.16", "@scalar/openapi-parser": "^0.28.7", "@scalar/openapi-types": "0.8.0", "chokidar": "^5.0.0", "commander": "^15.0.0", "execa": "^9.6.1", "find-up": "8.0.0", "fs-extra": "^11.3.2", "get-tsconfig": "^4.14.0", "jiti": "^2.6.1", "js-yaml": "4.2.0", "remeda": "^2.33.6", "string-argv": "^0.3.2", "typedoc": "^0.28.19", "typedoc-plugin-coverage": "^4.0.2", "typedoc-plugin-markdown": "^4.10.0" }, "peerDependencies": { "prettier": ">=3.0.0" }, "optionalPeers": ["prettier"], "bin": { "orval": "dist/bin/orval.mjs" } }, "sha512-N8UmB4DOhW+Z2SzVq4oS/pN3DsRPq+msrRU8NyRldP1i0yiqT6qo8mUxHPk2umqYjyY0FlR2mvPbi/Jf5CiP7A=="], "msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="],
"p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], "multipasta": ["multipasta@0.2.8", "", {}, "sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q=="],
"p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
"parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], "node-fetch-h2": ["node-fetch-h2@2.3.0", "", { "dependencies": { "http2-client": "^1.2.5" } }, "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "node-readfiles": ["node-readfiles@0.2.0", "", { "dependencies": { "es6-promise": "^3.2.1" } }, "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA=="],
"picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], "oas-kit-common": ["oas-kit-common@1.0.8", "", { "dependencies": { "fast-safe-stringify": "^2.0.7" } }, "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ=="],
"pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], "oas-linter": ["oas-linter@3.2.2", "", { "dependencies": { "@exodus/schemasafe": "^1.0.0-rc.2", "should": "^13.2.1", "yaml": "^1.10.0" } }, "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ=="],
"punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], "oas-resolver": ["oas-resolver@2.5.6", "", { "dependencies": { "node-fetch-h2": "^2.3.0", "oas-kit-common": "^1.0.8", "reftools": "^1.1.9", "yaml": "^1.10.0", "yargs": "^17.0.1" }, "bin": { "resolve": "resolve.js" } }, "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ=="],
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], "oas-schema-walker": ["oas-schema-walker@1.1.5", "", {}, "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ=="],
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], "oas-validator": ["oas-validator@5.0.8", "", { "dependencies": { "call-me-maybe": "^1.0.1", "oas-kit-common": "^1.0.8", "oas-linter": "^3.2.2", "oas-resolver": "^2.5.6", "oas-schema-walker": "^1.1.5", "reftools": "^1.1.9", "should": "^13.2.1", "yaml": "^1.10.0" } }, "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw=="],
"remeda": ["remeda@2.39.0", "", {}, "sha512-3Ki8dU1o3OVu4dwIQ2Pj+yiuP7OnEbmWAGmJ3yDRqopily5jsj8NWzPvbS89H85d6UdONKEcUnrfuHY6jN9vyw=="], "pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="],
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], "redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="],
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], "redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "reftools": ["reftools@1.1.9", "", {}, "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "should": ["should@13.2.3", "", { "dependencies": { "should-equal": "^2.0.0", "should-format": "^3.0.3", "should-type": "^1.4.0", "should-type-adaptors": "^1.0.1", "should-util": "^1.0.0" } }, "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ=="],
"string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], "should-equal": ["should-equal@2.0.0", "", { "dependencies": { "should-type": "^1.4.0" } }, "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA=="],
"strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], "should-format": ["should-format@3.0.3", "", { "dependencies": { "should-type": "^1.3.0", "should-type-adaptors": "^1.0.1" } }, "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q=="],
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], "should-type": ["should-type@1.4.0", "", {}, "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ=="],
"typedoc": ["typedoc@0.28.20", "", { "dependencies": { "@gerrit0/mini-shiki": "^3.23.0", "lunr": "^2.3.9", "markdown-it": "^14.3.0", "minimatch": "^10.2.5", "yaml": "^2.9.0" }, "peerDependencies": { "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x" }, "bin": { "typedoc": "bin/typedoc" } }, "sha512-uSKqkh8Cr48vllnEy+jdaAgOeR6Y+QCBW7usgUsKj7gJEfR7stw9U/fE49LBnj2tPRKPY0c0EBJSWe9Appmplg=="], "should-type-adaptors": ["should-type-adaptors@1.1.0", "", { "dependencies": { "should-type": "^1.3.0", "should-util": "^1.0.0" } }, "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA=="],
"typedoc-plugin-coverage": ["typedoc-plugin-coverage@4.0.3", "", { "peerDependencies": { "typedoc": "0.28.x" } }, "sha512-baim3wyMkqpX7rBzL/6iZ7wzKJuSr9ffP16RHOsdTUNoHUZeXLIZHSUBtUhXmNHaUNRgfqdmKLBwyggbJjGdeQ=="], "should-util": ["should-util@1.0.1", "", {}, "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g=="],
"typedoc-plugin-markdown": ["typedoc-plugin-markdown@4.12.0", "", { "peerDependencies": { "typedoc": "0.28.x" } }, "sha512-eJDEMAfxCmede22c/Jw7d0FA13ggAQv+KkwQYKYCdqI02cin6Rc9QRwbG/7XvvHWinuFejySnZVUWDtvGk3Vbg=="], "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"swagger2openapi": ["swagger2openapi@7.0.8", "", { "dependencies": { "call-me-maybe": "^1.0.1", "node-fetch": "^2.6.1", "node-fetch-h2": "^2.3.0", "node-readfiles": "^0.2.0", "oas-kit-common": "^1.0.8", "oas-resolver": "^2.5.6", "oas-schema-walker": "^1.1.5", "oas-validator": "^5.0.8", "reftools": "^1.1.9", "yaml": "^1.10.0", "yargs": "^17.0.1" }, "bin": { "swagger2openapi": "swagger2openapi.js", "oas-validate": "oas-validate.js", "boast": "boast.js" } }, "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g=="],
"toml": ["toml@4.3.0", "", {}, "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A=="],
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
"undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="], "undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="],
"undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
"unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], "uuid": ["uuid@14.0.1", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="],
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="],
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
"yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
"yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], "yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="],
"yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
"@scalar/openapi-parser/@scalar/openapi-types": ["@scalar/openapi-types@0.9.2", "", {}, "sha512-J0SZkNPgCrsFN0Rv8sRqZpDOQcRV44TCT82LsQcyWmcglTr7qtXukDmMlIyXs7JDd+DoPLbT2ea65bEHRs2W2g=="], "@effect/platform-node/undici": ["undici@8.7.0", "", {}, "sha512-N7iQtfyLhIMOFgQubvmLV26svHpO0bqKnAiWotTQCVKCmWrcGbBotPuW1x+xwYZ2VHdSTVUfPQQnlEt1/LouTQ=="],
"@scalar/openapi-upgrader/@scalar/openapi-types": ["@scalar/openapi-types@0.9.2", "", {}, "sha512-J0SZkNPgCrsFN0Rv8sRqZpDOQcRV44TCT82LsQcyWmcglTr7qtXukDmMlIyXs7JDd+DoPLbT2ea65bEHRs2W2g=="], "oas-linter/yaml": ["yaml@1.10.3", "", {}, "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA=="],
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], "oas-resolver/yaml": ["yaml@1.10.3", "", {}, "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA=="],
"oas-validator/yaml": ["yaml@1.10.3", "", {}, "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA=="],
"swagger2openapi/yaml": ["yaml@1.10.3", "", {}, "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA=="],
} }
} }
+13 -4
View File
@@ -1,6 +1,11 @@
// The RFC §7 quickstart, Effect-native: when the living-room TV starts a stream, apply a // ── Example 4/4 · ADVANCED, Effect-native ───────────────────────────────────────────────────
// display preset — a composed, typed, interruptible program. // You do NOT need this to use the SDK — examples 13 (the plain Promise `connect()` facade) cover
import { Effect, Stream } from "effect"; // most automation. Reach for `@punktfunk/host/effect` only when you're composing Effect programs
// and want the event stream, typed errors, and structured interruption as first-class values.
//
// Here: when the living-room TV starts a stream, apply a display preset — as a single composed,
// typed, interruptible program (a `Stream` of events piped into a request, provided a live layer).
import { Cause, Effect, Stream } from "effect";
import { events, PunktfunkHost, PunktfunkHostLive } from "../src/effect.js"; import { events, PunktfunkHost, PunktfunkHostLive } from "../src/effect.js";
const program = Effect.gen(function* () { const program = Effect.gen(function* () {
@@ -12,7 +17,11 @@ const program = Effect.gen(function* () {
Stream.runForEach(() => Stream.runForEach(() =>
pf pf
.request("PUT", "/display/settings", { mode: "preset", preset: "couch" }) .request("PUT", "/display/settings", { mode: "preset", preset: "couch" })
.pipe(Effect.catchAll((e) => Effect.logWarning(`preset failed: ${e}`))), .pipe(
Effect.catchCause((cause) =>
Effect.logWarning(`preset failed: ${Cause.pretty(cause)}`),
),
),
), ),
); );
}); });
+11 -5
View File
@@ -1,5 +1,7 @@
// The flagship hook-with-a-decision pattern (Promise facade): watch pairing requests, notify, // ── Example 2/4 · event → decision ──────────────────────────────────────────────────────────
// and approve/deny THROUGH the API — asynchronously, the punktfunk way (hooks never veto). // The flagship pattern: watch pairing requests, notify yourself, and approve/deny THROUGH the
// typed API — asynchronously, the punktfunk way (hooks never veto). Every call is checked and
// autocompleted; no hand-written paths, no casts.
import { connect } from "../src/index.js"; import { connect } from "../src/index.js";
const pf = await connect(); const pf = await connect();
@@ -7,9 +9,13 @@ console.log("watching for pairing requests…");
pf.events.on("pairing.pending", async (e) => { pf.events.on("pairing.pending", async (e) => {
console.log(`pairing request: ${e.device.name} (${e.device.fingerprint})`); console.log(`pairing request: ${e.device.name} (${e.device.fingerprint})`);
// Wire your real notifier here (ntfy, Pushover, Home Assistant, …), then decide:
// const pending = await pf.request("GET", "/native/pending") as { id: number }[]; // Wire your real notifier here (ntfy, Pushover, Home Assistant, …). Then decide by calling
// await pf.request("POST", `/native/pending/${pending[0].id}/approve`); // the API — find the pending device by fingerprint and approve it (send `{}` to keep the
// name it knocked with):
const pending = await pf.api.listPendingDevices();
const match = pending.find((d) => d.fingerprint === e.device.fingerprint);
if (match) await pf.api.approvePendingDevice(String(match.id), { payload: {} });
}); });
pf.events.on("pairing.completed", (e) => console.log(`paired: ${e.device.name}`)); pf.events.on("pairing.completed", (e) => console.log(`paired: ${e.device.name}`));
pf.events.on("pairing.denied", (e) => console.log(`denied: ${e.device.name}`)); pf.events.on("pairing.denied", (e) => console.log(`denied: ${e.device.name}`));
+22 -10
View File
@@ -1,6 +1,10 @@
// ── Example 3/4 · typed bulk REST ───────────────────────────────────────────────────────────
// The external game-library provider pattern (RFC §8): compute your desired title list and // The external game-library provider pattern (RFC §8): compute your desired title list and
// declaratively PUT it — the host diffs by your `external_id`, keeps host ids stable across // declaratively reconcile it — the host diffs by your `external_id`, keeps host ids stable across
// syncs, drops orphans, and never touches manual entries. Uninstall = one DELETE. // syncs, drops orphans, and never touches manual entries. Uninstall = one call.
//
// Note how the typed `payload` catches shape mistakes at compile time: `launch` is a
// `LaunchSpec` — `{ kind, value }`, not a bare command string.
import { connect } from "../src/index.js"; import { connect } from "../src/index.js";
const PROVIDER = "romm"; // your punktfunk-plugin-* name const PROVIDER = "romm"; // your punktfunk-plugin-* name
@@ -12,15 +16,23 @@ pf.events.on("library.changed", (e) => {
// Fetch your source of truth (a ROM manager, itch.io, a curated list…), then reconcile: // Fetch your source of truth (a ROM manager, itch.io, a curated list…), then reconcile:
const desired = [ const desired = [
{ external_id: "rom-1", title: "Chrono Trigger", launch: { command: "retroarch ..." } }, {
{ external_id: "rom-2", title: "Super Metroid", launch: { command: "retroarch ..." } }, external_id: "rom-1",
title: "Chrono Trigger",
launch: { kind: "command", value: "retroarch -L snes9x chrono-trigger.sfc" },
},
{
external_id: "rom-2",
title: "Super Metroid",
launch: { kind: "command", value: "retroarch -L snes9x super-metroid.sfc" },
},
]; ];
const entries = (await pf.request("PUT", `/library/provider/${PROVIDER}`, desired)) as { const entries = await pf.api.reconcileProviderEntries(PROVIDER, { payload: desired });
id: string; console.log(
title: string; `synced ${entries.length} titles:`,
}[]; entries.map((e) => `${e.title} (custom:${e.id})`),
console.log(`synced ${entries.length} titles:`, entries.map((e) => `${e.title} (custom:${e.id})`)); );
// …run on a schedule, or keep watching your source. Clean uninstall: // …run on a schedule, or keep watching your source. Clean uninstall:
// await pf.request("DELETE", `/library/provider/${PROVIDER}`); // await pf.api.deleteProviderEntries(PROVIDER);
pf.close(); pf.close();
+8 -3
View File
@@ -1,12 +1,17 @@
// Tail the host's lifecycle events — the SDK "hello world" (Promise facade). // ── Example 1/4 · the "hello world" ─────────────────────────────────────────────────────────
// Connect, make one typed API call, and tail the host's lifecycle events. No Effect knowledge
// needed — `connect()` returns a plain Promise client.
// //
// bun examples/tail-events.ts (on the host box: zero config) // bun examples/tail-events.ts (on the host box: zero config)
// PUNKTFUNK_MGMT_URL=… PUNKTFUNK_MGMT_TOKEN=… bun examples/tail-events.ts // PUNKTFUNK_MGMT_URL=… PUNKTFUNK_MGMT_TOKEN=… bun examples/tail-events.ts
import { connect } from "../src/index.js"; import { connect } from "../src/index.js";
const pf = await connect(); const pf = await connect();
const host = (await pf.request("GET", "/host")) as { hostname?: string };
console.log(`connected to ${host.hostname ?? "host"} — tailing events (^C to stop)`); // `pf.api.*` is the typed front door: `host` is a fully-typed HostInfo — no cast, autocomplete
// for every field.
const host = await pf.api.getHostInfo();
console.log(`connected to ${host.hostname} — tailing events (^C to stop)`);
pf.events.on("*", (e) => console.log(`[${e.seq}] ${e.kind}`, JSON.stringify(e))); pf.events.on("*", (e) => console.log(`[${e.seq}] ${e.kind}`, JSON.stringify(e)));
pf.events.on("unknown", (e) => console.log("[unknown kind]", JSON.stringify(e))); pf.events.on("unknown", (e) => console.log("[unknown kind]", JSON.stringify(e)));
-19
View File
@@ -1,19 +0,0 @@
import { defineConfig } from "orval";
// Generates the SDK's Effect Schemas from the host's checked-in OpenAPI document — the same
// single source of truth the web console's react-query client is generated from (RFC §7: the
// chain is Rust structs (utoipa) → OpenAPI → Effect Schemas → inferred TS types). Regenerate
// after any management-API change: `bun run gen` (CI drift-tests the output).
export default defineConfig({
punktfunk: {
input: {
target: "../api/openapi.json",
},
output: {
mode: "single",
target: "./src/gen/schemas.ts",
client: "effect",
clean: true,
},
},
});
+6 -5
View File
@@ -9,18 +9,19 @@
"./effect": "./src/effect.ts" "./effect": "./src/effect.ts"
}, },
"scripts": { "scripts": {
"gen": "orval --config ./orval.config.ts", "gen": "openapigen --spec ../api/openapi.json --name Punktfunk --format httpclient > src/gen/punktfunk.ts",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test": "bun test", "test": "bun test",
"runner": "bun src/runner-cli.ts" "runner": "bun src/runner-cli.ts"
}, },
"dependencies": { "dependencies": {
"effect": "^3.19.0" "effect": "^4.0.0-beta.98"
}, },
"devDependencies": { "devDependencies": {
"orval": "^8.20.0", "@effect/openapi-generator": "4.0.0-beta.98",
"typescript": "^5.9.3", "@effect/platform-node": "4.0.0-beta.98",
"@types/bun": "^1.3.0" "@types/bun": "^1.3.0",
"typescript": "^5.9.3"
}, },
"optionalDependencies": { "optionalDependencies": {
"undici": "^7.0.0" "undici": "^7.0.0"
+98
View File
@@ -0,0 +1,98 @@
// The typed management API for the Promise facade (RFC §7): every REST endpoint of the host as
// an autocompletable method with checked request/response types — named exactly as in the
// OpenAPI document (`listPairedClients`, `stopSession`, `reconcileProviderEntries`, …). This is
// the typed front door; `pf.request(method, path, body)` stays as the untyped escape hatch.
//
// It's a thin, zero-drift veneer over the generated client (`./gen/punktfunk.ts`, from
// `@effect/openapi-generator`): the generated methods return Effects, so each is run to a Promise
// here. The transport is the SAME CA-pinning fetch the rest of the SDK uses (config.ts), fed to
// Effect's `HttpClient` via the overridable `FetchHttpClient.Fetch` reference — so the loopback
// pin is preserved and there is still exactly one place that knows how to reach the host.
import { Effect } from "effect";
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
import * as HttpClient from "effect/unstable/http/HttpClient";
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
import type { ResolvedConfig } from "./config.js";
import * as gen from "./gen/punktfunk.js";
/**
* Make a trailing argument that merely *accepts* `undefined` genuinely optional. The generated
* methods type their options bag as `{…} | undefined` (a required param that tolerates `undefined`),
* so without this you'd have to write `listPairedClients(undefined)`. Endpoints with a required
* body (`{ payload }`, not `undefined`-able) are left untouched.
*/
type OptionalizeTail<A extends readonly unknown[]> = A extends readonly [
...infer Init,
infer Last,
]
? undefined extends Last
? [...Init, Last?]
: A
: A;
/** Each generated method returns an Effect; expose it as a Promise of the decoded success value. */
type Promiseify<T> = T extends (
...args: infer A
) => Effect.Effect<infer Success, infer _E, infer _R>
? (...args: OptionalizeTail<A>) => Promise<Success>
: never;
/** The event stream (an Effect `Stream`) and the void catch-all — served by `pf.events` instead. */
type NonApiMethods = "httpClient" | "streamEvents" | "streamEventsSse";
/**
* The host's management API, fully typed: `await pf.api.listPairedClients()` gives a typed array,
* `await pf.api.reconcileProviderEntries("romm", { payload })` checks the body at compile time —
* no hand-written paths, no `unknown` casts. A failing call rejects with the endpoint's typed
* error. The live-event SSE endpoint is intentionally absent — subscribe via `pf.events`.
*/
export type HostApi = {
readonly [K in Exclude<keyof gen.Punktfunk, NonApiMethods>]: Promiseify<
gen.Punktfunk[K]
>;
};
/** Build the pinning `HttpClient`: base URL + bearer + the config's CA-pinning fetch. */
const httpClientFor = (cfg: ResolvedConfig): Promise<HttpClient.HttpClient> =>
Effect.runPromise(
HttpClient.HttpClient.pipe(
Effect.map((client) =>
HttpClient.mapRequest((request: HttpClientRequest.HttpClientRequest) =>
request.pipe(
HttpClientRequest.prependUrl(cfg.url),
HttpClientRequest.bearerToken(cfg.token),
HttpClientRequest.acceptJson,
),
)(client),
),
Effect.provide(FetchHttpClient.layer),
// Override the default `globalThis.fetch` with the CA-pinning one (config.ts).
Effect.provideService(FetchHttpClient.Fetch, cfg.fetch),
),
);
/** Skipped at runtime too (not just in the type): these aren't Promise-shaped API calls. */
const NON_API: ReadonlySet<string> = new Set<NonApiMethods>([
"httpClient",
"streamEvents",
"streamEventsSse",
]);
/** The typed API surface over a resolved connection — each method runs its Effect to a Promise. */
export const makeHostApi = async (cfg: ResolvedConfig): Promise<HostApi> => {
const client = gen.make(await httpClientFor(cfg)) as unknown as Record<
string,
unknown
>;
const api: Record<string, unknown> = {};
for (const key of Object.keys(client)) {
if (NON_API.has(key)) continue;
const method = client[key];
if (typeof method !== "function") continue;
api[key] = (...args: unknown[]) =>
Effect.runPromise(
(method as (...a: unknown[]) => Effect.Effect<unknown>)(...args),
);
}
return api as HostApi;
};
+15 -12
View File
@@ -8,7 +8,7 @@ import {
Data, Data,
Effect, Effect,
Layer, Layer,
Option, Result,
Schema as S, Schema as S,
Stream, Stream,
} from "effect"; } from "effect";
@@ -62,7 +62,7 @@ export interface PunktfunkHostService {
/** GET + schema-validate (the generated schemas from `@punktfunk/host/effect`'s `api`). */ /** GET + schema-validate (the generated schemas from `@punktfunk/host/effect`'s `api`). */
readonly get: <A, I>( readonly get: <A, I>(
path: string, path: string,
schema: S.Schema<A, I>, schema: S.Codec<A, I>,
) => Effect.Effect<A, RequestError | VersionSkew>; ) => Effect.Effect<A, RequestError | VersionSkew>;
/** /**
* The lifecycle-event stream: decoded [`HostEvent`]s with automatic reconnect + * The lifecycle-event stream: decoded [`HostEvent`]s with automatic reconnect +
@@ -78,10 +78,10 @@ export interface PunktfunkHostService {
) => Stream.Stream<SseFrame, EventStreamError>; ) => Stream.Stream<SseFrame, EventStreamError>;
} }
export class PunktfunkHost extends Context.Tag("@punktfunk/host/PunktfunkHost")< export class PunktfunkHost extends Context.Service<
PunktfunkHost, PunktfunkHost,
PunktfunkHostService PunktfunkHostService
>() {} >()("@punktfunk/host/PunktfunkHost") {}
const toRequestError = (path: string, cause: unknown): RequestError => { const toRequestError = (path: string, cause: unknown): RequestError => {
if (cause instanceof HttpStatusError) { if (cause instanceof HttpStatusError) {
@@ -98,10 +98,10 @@ export const makeService = (cfg: ResolvedConfig): PunktfunkHostService => {
try: () => httpRequest(cfg, method, path, body), try: () => httpRequest(cfg, method, path, body),
catch: (cause) => toRequestError(path, cause), catch: (cause) => toRequestError(path, cause),
}); });
const get = <A, I>(path: string, schema: S.Schema<A, I>) => const get = <A, I>(path: string, schema: S.Codec<A, I>) =>
request("GET", path).pipe( request("GET", path).pipe(
Effect.flatMap((body) => Effect.flatMap((body) =>
S.decodeUnknown(schema)(body).pipe( S.decodeUnknownEffect(schema)(body).pipe(
Effect.mapError( Effect.mapError(
(e) => new VersionSkew({ path, issue: String(e) }), (e) => new VersionSkew({ path, issue: String(e) }),
), ),
@@ -119,29 +119,32 @@ export const makeService = (cfg: ResolvedConfig): PunktfunkHostService => {
const events = (opts?: EventStreamOptions) => { const events = (opts?: EventStreamOptions) => {
const warn = const warn =
opts?.onWarning ?? ((m: string) => console.warn(`[punktfunk] ${m}`)); opts?.onWarning ?? ((m: string) => console.warn(`[punktfunk] ${m}`));
// Drop-or-emit per frame. v4's `Stream.filterMap` wants a `Filter`; flat-mapping to
// `Stream.empty` (drop) / `Stream.succeed` (emit) expresses the same in stable
// primitives and keeps the `warn` side effects exactly where they were.
return eventsRaw(opts).pipe( return eventsRaw(opts).pipe(
Stream.filterMap((frame) => { Stream.flatMap((frame) => {
if (frame.event === "dropped") { if (frame.event === "dropped") {
warn( warn(
"event cursor fell off the host's ring — resync via the REST snapshots", "event cursor fell off the host's ring — resync via the REST snapshots",
); );
return Option.none(); return Stream.empty;
} }
let json: unknown; let json: unknown;
try { try {
json = JSON.parse(frame.data); json = JSON.parse(frame.data);
} catch { } catch {
warn(`unparseable event frame (${frame.event})`); warn(`unparseable event frame (${frame.event})`);
return Option.none(); return Stream.empty;
} }
const decoded = decodeHostEvent(json); const decoded = decodeHostEvent(json);
if (decoded._tag === "Left") { if (Result.isFailure(decoded)) {
// An unknown kind from a NEWER host is expected (additive-only wire) — // An unknown kind from a NEWER host is expected (additive-only wire) —
// it rides the raw channel; a consumer that wants it uses eventsRaw. // it rides the raw channel; a consumer that wants it uses eventsRaw.
warn(`unknown/undecodable event kind "${frame.event}"`); warn(`unknown/undecodable event kind "${frame.event}"`);
return Option.none(); return Stream.empty;
} }
return Option.some(decoded.right); return Stream.succeed(decoded.success);
}), }),
); );
}; };
+6 -3
View File
@@ -39,8 +39,11 @@ export {
export { type ConnectOptions, configDir, resolveConfig } from "./config.js"; export { type ConnectOptions, configDir, resolveConfig } from "./config.js";
export type { EventStreamOptions, SseFrame } from "./sse.js"; export type { EventStreamOptions, SseFrame } from "./sse.js";
export * from "./wire.js"; export * from "./wire.js";
/** The generated REST wire schemas (orval `client: 'effect'` over `api/openapi.json`). */ /**
export * as api from "./gen/schemas.js"; * The generated REST surface: wire Schemas plus a typed `HttpClient`-based client (`make`),
* from `@effect/openapi-generator` over `api/openapi.json`. Regenerate with `bun run gen`.
*/
export * as api from "./gen/punktfunk.js";
/** The decoded lifecycle-event stream of the ambient [`PunktfunkHost`]. */ /** The decoded lifecycle-event stream of the ambient [`PunktfunkHost`]. */
export const events = ( export const events = (
@@ -65,6 +68,6 @@ export const request = (
/** GET + schema-validate on the ambient service (schemas from [`api`]). */ /** GET + schema-validate on the ambient service (schemas from [`api`]). */
export const get = <A, I>( export const get = <A, I>(
path: string, path: string,
schema: S.Schema<A, I>, schema: S.Codec<A, I>,
): Effect.Effect<A, RequestError | VersionSkew, PunktfunkHost> => ): Effect.Effect<A, RequestError | VersionSkew, PunktfunkHost> =>
Effect.flatMap(PunktfunkHost, (s) => s.get(path, schema)); Effect.flatMap(PunktfunkHost, (s) => s.get(path, schema));
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+14 -4
View File
@@ -9,7 +9,8 @@
// pf.events.on("pairing.pending", async (e) => { // pf.events.on("pairing.pending", async (e) => {
// await notifyPhone(`Pairing request from ${e.device.name}`); // await notifyPhone(`Pairing request from ${e.device.name}`);
// }); // });
import type { Effect } from "effect"; import { type Effect, Result } from "effect";
import { type HostApi, makeHostApi } from "./api.js";
import type { PunktfunkHost } from "./client.js"; import type { PunktfunkHost } from "./client.js";
import { import {
type ConnectOptions, type ConnectOptions,
@@ -26,6 +27,7 @@ import {
kindMatches, kindMatches,
} from "./wire.js"; } from "./wire.js";
export type { HostApi } from "./api.js";
export { HttpStatusError } from "./core.js"; export { HttpStatusError } from "./core.js";
export type { ConnectOptions } from "./config.js"; export type { ConnectOptions } from "./config.js";
export type { export type {
@@ -66,7 +68,13 @@ export interface PunktfunkEvents {
export interface Punktfunk { export interface Punktfunk {
/** Resolved connection (URL/token/CA). */ /** Resolved connection (URL/token/CA). */
readonly config: ResolvedConfig; readonly config: ResolvedConfig;
/** One management-API request under `/api/v1` (`request("GET", "/status")`). */ /**
* The **typed** management API every REST endpoint as an autocompletable method with
* checked request/response types (`await pf.api.listPairedClients()`). This is the front
* door; reach for [`request`] only for something the generated client doesn't cover.
*/
readonly api: HostApi;
/** Untyped escape hatch: one raw `/api/v1` request (`request("GET", "/status")`). */
request(method: string, path: string, body?: unknown): Promise<unknown>; request(method: string, path: string, body?: unknown): Promise<unknown>;
/** The lifecycle-event subscription surface. */ /** The lifecycle-event subscription surface. */
readonly events: PunktfunkEvents; readonly events: PunktfunkEvents;
@@ -83,6 +91,7 @@ export const connect = async (options?: ConnectOptions): Promise<Punktfunk> => {
const cfg = await resolveConfig(options); const cfg = await resolveConfig(options);
// Fail fast on bad credentials/URL: one cheap authenticated probe. // Fail fast on bad credentials/URL: one cheap authenticated probe.
await httpRequest(cfg, "GET", "/host"); await httpRequest(cfg, "GET", "/host");
const api = await makeHostApi(cfg);
const listeners = new Set<Listener>(); const listeners = new Set<Listener>();
let pump: AsyncGenerator<SseFrame> | undefined; let pump: AsyncGenerator<SseFrame> | undefined;
@@ -118,11 +127,11 @@ export const connect = async (options?: ConnectOptions): Promise<Punktfunk> => {
continue; continue;
} }
const decoded = decodeHostEvent(json); const decoded = decodeHostEvent(json);
if (decoded._tag === "Left") { if (Result.isFailure(decoded)) {
dispatch("unknown", json); dispatch("unknown", json);
continue; continue;
} }
dispatch(decoded.right.kind, decoded.right); dispatch(decoded.success.kind, decoded.success);
} }
} catch (e) { } catch (e) {
if (!closed) warn(`event stream stopped: ${e}`); if (!closed) warn(`event stream stopped: ${e}`);
@@ -132,6 +141,7 @@ export const connect = async (options?: ConnectOptions): Promise<Punktfunk> => {
return { return {
config: cfg, config: cfg,
api,
request: (method, path, body) => httpRequest(cfg, method, path, body), request: (method, path, body) => httpRequest(cfg, method, path, body),
events: { events: {
on(pattern: string, cb: (ev: never) => unknown) { on(pattern: string, cb: (ev: never) => unknown) {
+9 -7
View File
@@ -38,7 +38,7 @@ export interface RunnerOptions {
/** Connection overrides handed to every unit's client/layer. */ /** Connection overrides handed to every unit's client/layer. */
connect?: ConnectOptions; connect?: ConnectOptions;
/** Restart backoff base (test seam). Default 1 s, capped at 60 s, jittered. */ /** Restart backoff base (test seam). Default 1 s, capped at 60 s, jittered. */
restartBase?: Duration.DurationInput; restartBase?: Duration.Input;
/** Line sink. Default: stamped stdout. */ /** Line sink. Default: stamped stdout. */
log?: (line: string) => void; log?: (line: string) => void;
} }
@@ -167,10 +167,12 @@ export const superviseUnit = (
options: RunnerOptions = {}, options: RunnerOptions = {},
): Effect.Effect<void> => { ): Effect.Effect<void> => {
const log = options.log ?? defaultLog; const log = options.log ?? defaultLog;
const restart = Schedule.exponential(options.restartBase ?? "1 second").pipe( // Exponential backoff, capped at 60 s (min-delay of the two schedules), then jittered.
Schedule.union(Schedule.spaced("60 seconds")), // cap // (v4 replaced `Schedule.union` with the array-form `Schedule.min`.)
Schedule.jittered, const restart = Schedule.min([
); Schedule.exponential(options.restartBase ?? "1 second"),
Schedule.spaced("60 seconds"),
]).pipe(Schedule.jittered);
let attempt = 0; let attempt = 0;
const once = Effect.suspend(() => { const once = Effect.suspend(() => {
attempt += 1; attempt += 1;
@@ -187,13 +189,13 @@ export const superviseUnit = (
), ),
), ),
), ),
Effect.tapErrorCause((cause) => Effect.tapCause((cause) =>
Effect.sync(() => Effect.sync(() =>
log(`[${unit.name}] failed: ${Cause.pretty(cause).split("\n")[0]}`), log(`[${unit.name}] failed: ${Cause.pretty(cause).split("\n")[0]}`),
), ),
), ),
Effect.retry(restart), Effect.retry(restart),
Effect.catchAllCause((cause) => Effect.catchCause((cause) =>
// A retry schedule that gives up (it doesn't, but stay total) — log and end. // A retry schedule that gives up (it doesn't, but stay total) — log and end.
Effect.sync(() => log(`[${unit.name}] gave up: ${Cause.pretty(cause)}`)), Effect.sync(() => log(`[${unit.name}] gave up: ${Cause.pretty(cause)}`)),
), ),
+10 -5
View File
@@ -6,10 +6,10 @@
// keys (Effect's default), and an unknown `kind` surfaces on the raw channel, never a throw. // keys (Effect's default), and an unknown `kind` surfaces on the raw channel, never a throw.
import { Schema as S } from "effect"; import { Schema as S } from "effect";
export const Plane = S.Literal("native", "gamestream"); export const Plane = S.Literals(["native", "gamestream"]);
export type Plane = S.Schema.Type<typeof Plane>; export type Plane = S.Schema.Type<typeof Plane>;
export const DisconnectReason = S.Literal("quit", "timeout", "error"); export const DisconnectReason = S.Literals(["quit", "timeout", "error"]);
export type DisconnectReason = S.Schema.Type<typeof DisconnectReason>; export type DisconnectReason = S.Schema.Type<typeof DisconnectReason>;
export const ClientRef = S.Struct({ export const ClientRef = S.Struct({
@@ -124,7 +124,7 @@ export const HostStopping = S.Struct({
}); });
/** Every known lifecycle event — discriminated on `kind`. */ /** Every known lifecycle event — discriminated on `kind`. */
export const HostEvent = S.Union( export const HostEvent = S.Union([
ClientConnected, ClientConnected,
ClientDisconnected, ClientDisconnected,
SessionStarted, SessionStarted,
@@ -139,7 +139,7 @@ export const HostEvent = S.Union(
LibraryChanged, LibraryChanged,
HostStarted, HostStarted,
HostStopping, HostStopping,
); ]);
export type HostEvent = S.Schema.Type<typeof HostEvent>; export type HostEvent = S.Schema.Type<typeof HostEvent>;
/** The known event kinds (for filters and the facade's `on()`). */ /** The known event kinds (for filters and the facade's `on()`). */
@@ -148,7 +148,12 @@ export type HostEventKind = HostEvent["kind"];
/** Narrow a HostEvent by kind: `EventOf<"stream.started">`. */ /** Narrow a HostEvent by kind: `EventOf<"stream.started">`. */
export type EventOf<K extends HostEventKind> = Extract<HostEvent, { kind: K }>; export type EventOf<K extends HostEventKind> = Extract<HostEvent, { kind: K }>;
export const decodeHostEvent = S.decodeUnknownEither(HostEvent); /**
* Decode one event JSON into a [`HostEvent`], as a [`Result`]: `Success` for a known kind,
* `Failure` for an unknown/undecodable one (a newer host rides the raw channel, never throws).
* (v4 replaced `Either` with `Result`; the callers branch on `Result.isFailure`.)
*/
export const decodeHostEvent = S.decodeUnknownResult(HostEvent);
/** /**
* Does `pattern` select `kind`? Exact kinds (`stream.started`) or `domain.*` prefixes on the * Does `pattern` select `kind`? Exact kinds (`stream.started`) or `domain.*` prefixes on the
+71
View File
@@ -0,0 +1,71 @@
// The typed `pf.api.*` surface end to end against a mock host: proves the generated client is
// reachable through the facade — base URL, bearer auth, the CA-pinning fetch injection, and
// schema decode all wired. (The event stream is covered by surfaces.test.ts.)
import { describe, expect, test } from "bun:test";
import { connect } from "../src/index.js";
const TOKEN = "test-token";
const mockHost = () =>
Bun.serve({
port: 0,
fetch(req) {
const url = new URL(req.url);
if (req.headers.get("authorization") !== `Bearer ${TOKEN}`) {
return Response.json({ error: "invalid credentials" }, { status: 401 });
}
switch (url.pathname) {
case "/api/v1/host":
return Response.json({ hostname: "mock" }); // connect() probe (undecoded)
case "/api/v1/clients":
return Response.json([
{ fingerprint: "abc123", subject: "CN=Living Room TV" },
]);
case "/api/v1/session":
// stopSession → 204 No Content (idempotent).
return new Response(null, { status: 204 });
default:
return Response.json({ error: "not found" }, { status: 404 });
}
},
});
describe("typed api surface", () => {
test("pf.api.* returns decoded, typed results", async () => {
const server = mockHost();
try {
const pf = await connect({
url: `http://127.0.0.1:${server.port}`,
token: TOKEN,
});
const clients = await pf.api.listPairedClients();
expect(clients).toHaveLength(1);
// Typed field access — no cast.
expect(clients[0]?.fingerprint).toBe("abc123");
expect(clients[0]?.subject).toBe("CN=Living Room TV");
// A 204 endpoint resolves to void, not a thrown "empty body".
await expect(pf.api.stopSession()).resolves.toBeUndefined();
pf.close();
} finally {
server.stop(true);
}
});
test("a failing call rejects", async () => {
const server = mockHost();
try {
const pf = await connect({
url: `http://127.0.0.1:${server.port}`,
token: TOKEN,
});
// /api/v1/gpus isn't served → 404 → the endpoint's typed error rejects.
await expect(pf.api.listGpus()).rejects.toThrow();
pf.close();
} finally {
server.stop(true);
}
});
});
+10 -10
View File
@@ -10,10 +10,10 @@ describe("wire", () => {
'{"seq":4182,"ts_ms":1700000000000,"schema":1,"kind":"stream.started","stream":{"mode":"3840x2160@120","hdr":true,"client":"Living Room TV","app":"steam:570","plane":"native"}}', '{"seq":4182,"ts_ms":1700000000000,"schema":1,"kind":"stream.started","stream":{"mode":"3840x2160@120","hdr":true,"client":"Living Room TV","app":"steam:570","plane":"native"}}',
), ),
); );
expect(stream._tag).toBe("Right"); expect(stream._tag).toBe("Success");
if (stream._tag === "Right" && stream.right.kind === "stream.started") { if (stream._tag === "Success" && stream.success.kind === "stream.started") {
expect(stream.right.stream.mode).toBe("3840x2160@120"); expect(stream.success.stream.mode).toBe("3840x2160@120");
expect(stream.right.stream.app).toBe("steam:570"); expect(stream.success.stream.app).toBe("steam:570");
} }
const disc = decodeHostEvent( const disc = decodeHostEvent(
@@ -21,15 +21,15 @@ describe("wire", () => {
'{"seq":1,"ts_ms":1700000000000,"schema":1,"kind":"client.disconnected","client":{"name":"Deck","fingerprint":"b1c2","plane":"gamestream"},"reason":"timeout"}', '{"seq":1,"ts_ms":1700000000000,"schema":1,"kind":"client.disconnected","client":{"name":"Deck","fingerprint":"b1c2","plane":"gamestream"},"reason":"timeout"}',
), ),
); );
expect(disc._tag).toBe("Right"); expect(disc._tag).toBe("Success");
if (disc._tag === "Right" && disc.right.kind === "client.disconnected") { if (disc._tag === "Success" && disc.success.kind === "client.disconnected") {
expect(disc.right.reason).toBe("timeout"); expect(disc.success.reason).toBe("timeout");
} }
const stopping = decodeHostEvent( const stopping = decodeHostEvent(
JSON.parse('{"seq":2,"ts_ms":1700000000000,"schema":1,"kind":"host.stopping"}'), JSON.parse('{"seq":2,"ts_ms":1700000000000,"schema":1,"kind":"host.stopping"}'),
); );
expect(stopping._tag).toBe("Right"); expect(stopping._tag).toBe("Success");
}); });
test("tolerates unknown keys (additive-only wire)", () => { test("tolerates unknown keys (additive-only wire)", () => {
@@ -41,7 +41,7 @@ describe("wire", () => {
source: "manual", source: "manual",
future_field: { anything: true }, future_field: { anything: true },
}); });
expect(r._tag).toBe("Right"); expect(r._tag).toBe("Success");
}); });
test("unknown kinds fail decode (they ride the raw channel)", () => { test("unknown kinds fail decode (they ride the raw channel)", () => {
@@ -51,7 +51,7 @@ describe("wire", () => {
schema: 1, schema: 1,
kind: "totally.new", kind: "totally.new",
}); });
expect(r._tag).toBe("Left"); expect(r._tag).toBe("Failure");
}); });
test("kindMatches mirrors the host filter semantics", () => { test("kindMatches mirrors the host filter semantics", () => {
+1 -1
View File
@@ -9,5 +9,5 @@
"noEmit": true, "noEmit": true,
"types": ["bun"] "types": ["bun"]
}, },
"include": ["src", "test"] "include": ["src", "test", "examples"]
} }