3.2 KiB
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<Caps> shape declared by
@suaveplan/api-types/plugin-context.
Features
addTypes/addResolversaccumulate — multiple calls compose rather than overwrite.addLoaderrejects duplicate names withGRAPHQL.LOADER_DUPLICATE_NAMEand wraps the suppliedBatchFnin aLoaderFactorywhosecreate()yields a DataLoader-like per-request handle.freeze()returns an immutable snapshot and blocks further writes.- Optional
pluginoption threads the plugin name through error details so collisions are diagnostic-rich.
Usage: Basic
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<string, Invoice>("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:
import { createGraphqlRegistry } from "@suaveplan/api-graphql/registry";
const registry = createGraphqlRegistry();
registry.addLoader<string, User>("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<Caps>(options?) → GraphqlRegistry<Caps> & { freeze }— build a fresh accumulator.GraphqlRegistry<Caps>—{ addTypes, addResolvers, addLoader }.FrozenRegistry<Caps>—{ 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.