diff --git a/.agents/skills/effect-httpapi-workers-patterns/SKILL.md b/.agents/skills/effect-httpapi-workers-patterns/SKILL.md index eb77ca3b..5f424e48 100644 --- a/.agents/skills/effect-httpapi-workers-patterns/SKILL.md +++ b/.agents/skills/effect-httpapi-workers-patterns/SKILL.md @@ -1,6 +1,6 @@ --- name: effect-httpapi-workers-patterns -description: Build, refactor, or review Effect v4 HttpApi services on Cloudflare Workers. Use when working with effect/unstable/httpapi, HttpApiBuilder, generated Effect clients, typed API contracts, request context, tagged errors, Worker observability, Maple, OpenTelemetry, OTLP, or Cloudflare logs and traces. +description: Build, refactor, or review Effect v4 HttpApi services on Cloudflare Workers. Use when working with effect/http-api, HttpApiBuilder, generated Effect clients, typed API contracts, request context, tagged errors, Worker observability, OpenTelemetry, OTLP, or Cloudflare logs and traces. --- # Effect HttpApi Workers Patterns @@ -12,6 +12,7 @@ Use this skill when editing or reviewing an Effect HttpApi stack, generated Effe - Keep API contracts separate from server implementation. - Contract modules define `HttpApi`, `HttpApiGroup`, endpoints, schemas, tagged errors, and security declarations. - Server route modules only implement `HttpApiBuilder.group(...)` live layers. +- Import `HttpApi`, `HttpApiBuilder`, `HttpApiClient`, and `HttpApiMiddleware` from `effect/http-api`. This repository pins Effect 4.0.3, which does not export `effect/unstable/httpapi` or `effect/httpapi`. - Prefer named `Schema.Class` response/request types and `Schema.TaggedError` errors with `httpApiStatus`. - Avoid broad `JsonRecord`, `AnyJson`, and generic `{ error: string }` contracts for real endpoints. - If a generated client requires `unknown` or extra services, fix the schema contract instead of casting at the call site. @@ -44,16 +45,9 @@ Use this skill when editing or reviewing an Effect HttpApi stack, generated Effe - Production should use metadata/configurable rates. - Keep invocation logs enabled until application logs are proven complete. - Use Cloudflare Observability destinations for platform OTEL export when possible. -- If also exporting Effect app spans/logs via `effect/unstable/observability/Otlp`, pass a base OTLP URL. Do not pass a URL that already ends in `/v1/traces`, `/v1/logs`, or `/v1/metrics` because Effect appends those paths. -- Keep OTLP headers and tokens in secrets/env vars; never in source or non-secret metadata. - -## Maple With Effect - -- Use `@maple-dev/effect-sdk/cloudflare` for Worker-local Effect logs and traces. -- Provide the Maple telemetry layer to the same runtime that runs route handlers; do not run route telemetry in a separate runtime. -- Flush Maple telemetry at the Worker request boundary with `ctx.waitUntil(telemetry.flush(env))` after the response path has scheduled its Effect logs/spans. -- In local dev, use `OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318` and run `maple start` before the app dev command. -- Avoid duplicate exports: route code should use Effect logs/spans, the Worker should provide one Maple layer, and Cloudflare Worker Observability should remain the platform collection path for deployed stages. +- If a Worker also exports Effect app spans or logs, import `Otlp` from `effect/observability/Otlp`. Pass a base OTLP URL. Do not pass a URL that already ends in `/v1/traces`, `/v1/logs`, or `/v1/metrics`, because Effect appends those paths. +- Keep OTLP headers and tokens in secrets or env vars. Never put them in source or non-secret metadata. +- This repository does not install Maple (`@maple-dev/effect-sdk`). Deployed logs and traces go through Cloudflare Worker Observability. Do not add a Maple layer or a local `maple start` collector here. ## Cloudflare Workers General diff --git a/apps/web/README.md b/apps/web/README.md index 64ea4258..a5c1e02e 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -123,36 +123,39 @@ nub run typecheck # Type check ## Programmatic Client -For headless or script usage, the `BackgroundSessionsClient` is available: +Scripts call the typed `S0Api` client, `makeS0ApiClient` from `@solzero/api`. Pass a user API key +as `bearerToken`. User API keys start with `oiak_`, and the client sends `Authorization: Bearer`. ```ts -import { BackgroundSessionsClient } from "./src/session-client" - -const apiKey = process.env.S0_API_KEY -if (!apiKey) { - throw new Error("S0_API_KEY is required") +import { Effect } from "effect" +import { makeS0ApiClient } from "@solzero/api" + +function createSession(apiKey: string) { + return Effect.gen(function* () { + const client = yield* makeS0ApiClient({ + baseUrl: "http://localhost:3000/api", + bearerToken: apiKey, + }) + + return yield* client.sessions.create({ + payload: { + sessionKind: "isolate", + repoOwner: "cloudflare", + repoName: "sandbox-sdk", + title: "Investigate flaky e2e test", + }, + }) + }) } - -const client = new BackgroundSessionsClient({ - auth: { kind: "api-key", apiKey }, - baseUrl: "http://localhost:3000/api", -}) - -const session = await client.createSession({ - repoOwner: "cloudflare", - repoName: "sandbox-sdk", - title: "Investigate flaky e2e test", -}) ``` -Browser code should instead use `auth: { kind: "browser-session" }` with the same-origin -`/api` BFF. Do not embed a user API key in a browser bundle. +Browser code uses `runApi` from `src/lib/api-client.ts`. That client calls the same-origin `/api` +prefix and sends the session cookie. Do not embed a user API key in a browser bundle. -## API Routes +## API -- `/api/auth/api-keys` - create/list user API keys -- `/api/auth/[...all]` - Better Auth proxy routes -- `/api/sessions` - Session CRUD (proxies to control plane) -- `/api/sessions/[id]/*` - Session archive, prompt, ws-token (proxies) -- `/api/repos` - Repository list (proxies) -- `/api/secrets` - Global secrets (proxies) +`S0Api` in `@solzero/api` is the public HTTP contract. Callers use the `/api` prefix on the web +origin. A deployed Cloudflare stage sends `/api` to the API Worker on a path route. Local +development and `workers.dev` ingress use the TanStack `/api/$` route, which forwards the request +through the `S0_API` service binding. The binding uses the same Worker entrypoint and the same +endpoint authentication. diff --git a/packages/api/src/client.ts b/packages/api/src/client.ts index b4c52f0d..909688a9 100644 --- a/packages/api/src/client.ts +++ b/packages/api/src/client.ts @@ -27,6 +27,7 @@ export function controlPlaneAuthClient(options: { bearerToken?: string | Redacte ) } +/** Builds the typed `S0Api` client. `bearerToken` is sent as `Authorization: Bearer`. */ export function makeS0ApiClient(options: { baseUrl: string | URL bearerToken?: string | Redacted.Redacted