/** * REST CRUD generated from resources (SPEC.md §43). * * This package must not depend on `@assemora/resources` (SPEC.md §8), and it does * not: a resource describes itself into the Schema Registry, and what is generated * here comes from that description. Writes go to the Command Bus and reads to the * Query Bus, so the generated endpoints take the same path as everything else. */ import { type CommandBus, type QueryBus, type SchemaRegistry } from '@assemora/core'; import { type Route } from './route.js'; /** The part of a resource description these endpoints need. */ export type CrudResource = { readonly name: string; readonly label: string; readonly api: { readonly create: boolean; readonly read: boolean; readonly update: boolean; readonly delete: boolean; }; }; /** * One generated endpoint, named the way a caller thinks of it (SPEC.md §43). * * Finer than the four `api` flags a resource declares: `read` publishes both the * listing and the single read, and a version that replaces one of them with a route * of its own has to be able to say which (SPEC.md §47). */ export type CrudOperation = 'list' | 'get' | 'create' | 'update' | 'delete'; export declare const CRUD_OPERATIONS: readonly CrudOperation[]; /** What a resource's own `api` flags publish, in the order `crudRoutes` generates them. */ export declare const publishedOperations: (resource: CrudResource) => readonly CrudOperation[]; /** * What a resource is *now*, asked by name. * * A generated endpoint is a consequence of a resource rather than a declaration of its * own, so it has to be able to outlive the snapshot it was generated from: a collection * is deleted, or narrows its `api` flags, in a process that is already serving * (SPEC.md §37, §43). The server keeps this map in step with the registry — see * `HttpServer.mountResources()` — and both ways in ask it the same question. */ export type CrudLookup = (name: string) => CrudResource | undefined; /** The path parameter the dispatching pair carries a resource's name in. */ export declare const RESOURCE_PARAM = "resource"; export type CrudBuses = { readonly commands: CommandBus; readonly queries: QueryBus; }; /** Reads the resource descriptions out of the registry, whoever put them there. */ export declare const crudResources: (registry: SchemaRegistry) => CrudResource[]; export type CrudRouteOptions = { /** * Which of the five to generate. Every one the resource's own `api` flags allow, * unless a caller narrows it — which is what lets a version publish four generated * endpoints and a fifth of its own (SPEC.md §47). */ readonly operations?: readonly CrudOperation[]; /** * Where the endpoints look their resource up when they are asked, rather than * trusting the description they were generated from. * * A resource declared in TypeScript never changes, and a version is a snapshot by * design — so this is left out for both. A collection is neither: it is deleted, and * it narrows its `api` flags, while this process serves (SPEC.md §37, §43). Its * endpoints were mounted at boot and Fastify unmounts nothing, so without this a * collection went on answering at an address it had just stopped publishing, and a * deleted one answered from deep inside `entries.list` instead of from here. */ readonly current?: CrudLookup; }; export declare const crudRoutes: (resources: readonly CrudResource[], buses: CrudBuses, options?: CrudRouteOptions) => Route[]; /** * The one address a resource that arrives later can be reached at (SPEC.md §37, §43). * * A collection is a row. It is made in Studio or by an agent while the process is * running, and Fastify will not take a new route once the instance is listening — so the * endpoint generated per resource above cannot exist for it, and `/api/testimonials` * answered 404 for as long as that process lived. That is the whole of what made a * collection a second-class resource: everything else about it — the commands, the * policies, the field permissions, the revisions, the audit log, Studio, MCP, the * component schemas — was already the same as a resource declared in TypeScript. * * So the five endpoints are generated once more, parameterised by the resource's name * instead of carrying it, and they dispatch through the same five functions the * per-resource endpoints call. `find-my-way` prefers a static segment to a parametric * one whatever the order of registration, so a resource that *does* have endpoints of * its own keeps them and nothing about it changes. * * A name the registry does not describe is refused here rather than handed to the buses: * this pair sits under every unclaimed one- and two-segment address below the API * prefix, and "there is nothing here" has to keep meaning that. A resource that switched * an operation off is refused for the same reason — `api: { create: false }` is not a * door this may quietly reopen. Both refusals are `publishing`'s, which is also what the * per-resource endpoints ask, so one condition has one answer wherever it is met. * * These routes describe *themselves* to nobody: `/api/{resource}` is not an address a * caller means, and putting it in OpenAPI, the API Explorer and the generated SDK would * replace five documented endpoints with one that documents nothing. What is described * is each resource's own paths, which `HttpServer.mountResources()` keeps in step with * the registry — which is also why this is not part of the package's public API. Half of * this mechanism is the description, and mounting the pair without it would serve * addresses nothing documents. */ export declare const crudDispatchRoutes: (current: CrudLookup, buses: CrudBuses) => Route[]; //# sourceMappingURL=crud.d.ts.map