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
+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);
}
});
});