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:
+27
-4
@@ -6,7 +6,8 @@ stream start/stop, pairing, displays, library) — built on [Effect](https://eff
|
||||
|
||||
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.
|
||||
- **`@punktfunk/host/effect`** — the Effect-native surface for plugins and composed programs:
|
||||
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
|
||||
|
||||
// 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) => {
|
||||
console.log(`${e.stream.client} started ${e.stream.mode}${e.stream.hdr ? " HDR" : ""}`);
|
||||
});
|
||||
pf.events.on("pairing.pending", async (e) => {
|
||||
// notify your phone, then decide through the API:
|
||||
// await pf.request("POST", `/native/pending/${id}/approve`);
|
||||
// notify your phone, then decide through the typed API:
|
||||
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:
|
||||
|
||||
```ts
|
||||
@@ -43,6 +54,18 @@ const program = events().pipe(
|
||||
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 1–3 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
|
||||
|
||||
`connect()` / `PunktfunkHostLive()` resolve, in order:
|
||||
@@ -166,7 +189,7 @@ Windows Task Scheduler: a task triggered *At log on* running
|
||||
|
||||
```sh
|
||||
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 test
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user