/** * `http.resource` mount — kept off {@link ./triggers.ts} so edge `http.get` * apps do not pay for the five-verb CRUD helper. */ import { GATE_PUBLIC_NAME } from "../elements/gate/flatten.ts"; import { createGateAttach, createHttpTrigger, resourceMountBrand, type GateRef, type HttpMethod, type HttpTrigger, type ResourceFlowBag, type ResourceMount, type SignalSource, } from "./triggers.ts"; import { registerPendingResourceLiveMount } from "./resource-live.ts"; /** * Mount a CRUD resource at `path`: `list`/`create` on the base, `get` / * `update` / `remove` on `/:id`. Bind via `on(http.resource(…))`. * * @param path - Base path (`/notes`) * @param ops - The five FlowDefs (usually `resource.all()`) * @param gates - Shared gate chain */ export function httpResource

( path: P, ops: ResourceFlowBag, gates: readonly GateRef[] = [], ): ResourceMount { const id = `${path}/:id` as `${P}/:id`; const verb = (method: M, p: P | `${P}/:id`): HttpTrigger => createHttpTrigger(method, p, gates); // Live exposure — synthesized from the resource's live surface so SSE // rides the same gates as the CRUD verbs. const liveFlow = (ops as { readonly live?: { readonly signal: string; readonly flow: unknown } }) .live; // `store.resource(…, { live: omitted })` puts a pending surface on the ops // bag. `http.resource` records the mount slot; `oke()` drains it only when // the project-wide `store.live` flag is on. const pendingSignal = (ops as { readonly pendingLive?: { readonly signalName: string } }) .pendingLive?.signalName; if (pendingSignal !== undefined && liveFlow === undefined) { registerPendingResourceLiveMount({ path, gates, signalName: pendingSignal }); } const mounts = [ { trigger: verb("GET", path), flow: ops.list }, { trigger: verb("POST", path), flow: ops.create }, { trigger: verb("GET", id), flow: ops.get }, { trigger: verb("PATCH", id), flow: ops.update }, { trigger: verb("DELETE", id), flow: ops.remove }, ...(liveFlow !== undefined ? [ { trigger: createHttpTrigger("GET" as const, `${path}/live`, gates, { name: liveFlow.signal, } as SignalSource), flow: liveFlow.flow, }, ] : []), ]; const mount: ResourceMount = { [resourceMountBrand]: true, gates, ...(liveFlow !== undefined ? { live: { signal: liveFlow.signal, flow: liveFlow.flow } } : {}), mounts, gate: createGateAttach((next) => httpResource(path, ops, next), gates), public() { return httpResource(path, ops, [...gates, GATE_PUBLIC_NAME]); }, }; return mount; }