--- status: accepted date: 2026-06-01 supersedes: ADR-033 (partially — OQ-17, OQ-18, OQ-19 updated) --- # ADR-048: OperationSpecs as Repository Surface ## Context ADR-033 established JSON path queries with hand-written CRUD for v1. The question was whether the repository layer would be hand-written query functions, auto-generated from table definitions, or something else. The `@alkdev/operations` package provides `OperationSpec` — a serializable descriptor for a query, mutation, or subscription operation. Hubs and spokes register specs in an `OperationRegistry` along with handlers. The operations runtime handles execution, call protocol routing, subscriptions, access control, and validation. This presents a natural alternative to hand-written CRUD: storage outputs `OperationSpec[]` describing CRUD operations per table, and the consumer wires them into the operations registry. ## Decision Storage does not ship a "repository layer" of hand-written query functions. Instead, it outputs `OperationSpec[]` per table — flat arrays describing CRUD operations. The consumer (hub/spoke) imports these specs, registers handlers, and the operations runtime handles execution. ```ts // Storage defines table + operation contracts export const graphTypes = sqliteTable("graph_types", { ... }); export const graphTypeSpecs: OperationSpec[] = [ { name: "create", namespace: "graph_types", type: "mutation", ... }, { name: "find", namespace: "graph_types", type: "query", ... }, { name: "list", namespace: "graph_types", type: "query", ... }, { name: "update", namespace: "graph_types", type: "mutation", ... }, { name: "delete", namespace: "graph_types", type: "mutation", ... }, ]; // Hub registers specs + handlers for (const spec of graphTypeSpecs) { registry.registerSpec(spec); registry.registerHandler(`${spec.namespace}.${spec.name}`, handler); } ``` The handler is consumer-provided. Storage defines the contract (input/output schemas, operation type); the hub provides the execution (Drizzle queries, Honker transactions, notifications). `@alkdev/operations` is a type-only peer dependency of storage. No circular dependency. ## Consequences - **No hand-written repository functions** — storage avoids the maintenance burden of typed CRUD code. The contract is the OperationSpec. - **Consumer owns execution** — the hub decides how to handle each operation (raw Drizzle query, with Honker notification, with access control, etc.). Storage doesn't execute queries. - **Subscriptions for free** — OperationSpec supports `type: "subscription"`. Table-level change streams become operation subscriptions rather than custom event handling. - **Clean dependency graph** — storage depends on operations only for the `OperationSpec` type (peer dep). No runtime dependency. - **Attribute queries remain JSON path** — for metagraph tables where attributes are dynamic JSON, `json_extract()` is still the query mechanism. Domain-specific tables with native columns produce simpler specs. - **Supersedes ADR-033 partially** — the "hand-written CRUD" part is replaced. The "JSON path queries for attributes" part still applies to metagraph tables.