# Registry The `OperationRegistry` is the central store for operation specs and handlers. It handles registration, validation, access control enforcement, and execution. ## Operation Types ```ts enum OperationType { QUERY = "query", MUTATION = "mutation", SUBSCRIPTION = "subscription", } ``` - **Query** — read-only, no side effects - **Mutation** — writes state, side effects - **Subscription** — streams results over time (async generator handler) ## Defining an Operation Every operation has a **spec** (serializable metadata) and optionally a **handler** (the function that runs). ```ts import { Type } from "@alkdev/typebox"; import { OperationType } from "@alkdev/operations"; const createTask = { name: "create", namespace: "task", version: "1.0.0", type: OperationType.MUTATION, description: "Create a new task", inputSchema: Type.Object({ title: Type.String(), priority: Type.Optional(Type.Union([Type.Literal("low"), Type.Literal("high")])), }), outputSchema: Type.Object({ id: Type.String(), title: Type.String(), }), accessControl: { requiredScopes: ["task:write"], }, handler: async (input: { title: string; priority?: string }) => { return { id: crypto.randomUUID(), title: input.title }; }, }; ``` ### Spec Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | `string` | yes | Operation name within its namespace | | `namespace` | `string` | yes | Grouping (e.g. `"task"`, `"user"`) | | `version` | `string` | yes | Semantic version | | `type` | `OperationType` | yes | `query`, `mutation`, or `subscription` | | `description` | `string` | yes | Human-readable description | | `inputSchema` | `TSchema` | yes | TypeBox schema for input validation | | `outputSchema` | `TSchema` | yes | TypeBox schema for output; use `Type.Unknown()` if untyped | | `accessControl` | `AccessControl` | yes | Scopes and resource requirements (see [Access Control](access-control.md)) | | `title` | `string` | no | Human-readable title | | `tags` | `string[]` | no | Tags for filtering/grouping | | `errorSchemas` | `ErrorDefinition[]` | no | Declared error codes and their schemas | | `_meta` | `Record` | no | Arbitrary metadata | ### Handler Signature **Query/Mutation** handlers return a value (or `ResponseEnvelope`): ```ts type OperationHandler = ( input: TInput, context: OperationContext, ) => Promise | TOutput; ``` **Subscription** handlers must be async generators: ```ts type SubscriptionHandler = ( input: TInput, context: OperationContext, ) => AsyncGenerator; ``` ## Registering Operations ### Combined registration ```ts const registry = new OperationRegistry(); registry.register(createTask); ``` `register()` stores both the spec and the handler. The `handler` field is optional — you can register the spec first and add the handler later. ### Batch registration ```ts registry.registerAll([createTask, listTasks, deleteTask]); ``` ### Separate spec and handler ```ts registry.registerSpec(mySpec); registry.registerHandler("task.create", myHandler); ``` This is useful when specs come from one source (e.g., OpenAPI import) and handlers from another. > `registerHandler` throws if no spec exists for the operation ID. ## Executing Operations ```ts const envelope = await registry.execute( "task.create", { title: "Ship it" }, { identity: { id: "user-1", scopes: ["task:write"] } }, ); ``` What `execute()` does: 1. Looks up the spec by `namespace.name` 2. Looks up the handler 3. **Enforces access control** (unless `context.trusted` is `true`) 4. **Validates input** against the spec's `inputSchema` 5. Runs the handler 6. **Wraps the result** in a `ResponseEnvelope` if not already one 7. **Casts the output** through `outputSchema` and warns on validation errors Returns `ResponseEnvelope`. See [Response Envelopes](response-envelopes.md). ### Execution context ```ts interface OperationContext { requestId?: string; parentRequestId?: string; identity?: Identity; trusted?: boolean; // set by buildEnv, not by callers metadata?: Record; env?: OperationEnv; // injected by buildEnv for inter-op calls } ``` ## Querying the Registry ```ts registry.get("task.create"); // spec + handler, or undefined registry.getSpec("task.create"); // spec only registry.getHandler("task.create"); // handler only registry.getByName("task", "create"); // same as get("task.create") registry.list(); // all specs + handlers registry.getAllSpecs(); // all specs only ``` ## Schema Adapters By default, the registry expects TypeBox schemas. If you use Zod or Valibot, pass a schema adapter: ```ts import { zodAdapter } from "@alkdev/operations/from-typemap"; const adapter = zodAdapter(); await adapter.init(); const registry = new OperationRegistry({ schemaAdapter: adapter }); ``` See [Adapters](adapters.md) for details.