# `registry` _(no submodules)_ ## Purpose Concrete accumulator-style `GraphqlRegistry` implementation. The host hands each plugin one of these during the GraphQL registration phase (design §10.1); plugins call `addTypes` / `addResolvers` / `addLoader` to contribute their schema slice. At schema-build time the engine calls `freeze()` to obtain an immutable snapshot. Generic over the capability slice (`Caps`) so resolvers and loader factories see the typed `RequestContext` shape declared by `@suaveplan/api-types/plugin-context`. ## Features - `addTypes` / `addResolvers` accumulate — multiple calls compose rather than overwrite. - `addLoader` rejects duplicate names with `GRAPHQL.LOADER_DUPLICATE_NAME` and wraps the supplied `BatchFn` in a `LoaderFactory` whose `create()` yields a DataLoader-like per-request handle. - `freeze()` returns an immutable snapshot and blocks further writes. - Optional `plugin` option threads the plugin name through error details so collisions are diagnostic-rich. ## Usage: Basic ```ts import { createGraphqlRegistry } from "@suaveplan/api-graphql/registry"; const registry = createGraphqlRegistry({ plugin: "Billing" }); registry.addTypes("extend type Query { invoices: [Invoice!]! }"); registry.addResolvers({ Query: { invoices: async () => [] } }); registry.addLoader("invoiceById", async (ids) => ids.map((id) => invoicesById.get(id) ?? null) ); const snapshot = registry.freeze(); ``` ## Usage: Advanced The `LoaderFactory.create()` returns a handle that batches `load(key)` calls within a single microtask tick. Within one request the host calls `create()` once and stores the handle on `RequestContext.capabilities`: ```ts import { createGraphqlRegistry } from "@suaveplan/api-graphql/registry"; const registry = createGraphqlRegistry(); registry.addLoader("userById", async (ids) => { const rows = await db.from("users").whereIn("id", ids); return ids.map((id) => rows.find((r) => r.id === id) ?? null); }); const factory = registry.freeze().loaders.get("userById")!; const loader = factory.create(); const [a, b, c] = await loader.loadMany(["u1", "u2", "u3"]); ``` ## API Reference - `createGraphqlRegistry(options?) → GraphqlRegistry & { freeze }` — build a fresh accumulator. - `GraphqlRegistry` — `{ addTypes, addResolvers, addLoader }`. - `FrozenRegistry` — `{ typeDefs, resolvers, loaders }`. - `CreateRegistryOptions` — `{ plugin? }`. ## Implementation Notes The freeze pattern matches what the host needs: plugins write during their `onRegister` phase; engines read during `buildSchema`. Once frozen, any mutation raises `GRAPHQL.LOADER_DUPLICATE_NAME` (the message indicates the registry is frozen — the code is shared because a frozen mutation is the same family of error as a duplicate). The bundled `LoaderFactory` uses microtask batching: `load(key)` enqueues and the queue flushes on `Promise.resolve().then(...)`. This matches the DataLoader semantics most engines expect. Engines that prefer their own loader runtime can ignore the bundled handle and call `factory.create()` once per request, then forward to their own DataLoader at the boundary.