ADR 0006 — Route groups and GET/DELETE query params
- Status: Accepted
- Date: 2026-05-13
Context
Two gaps surfaced when preparing real projects to run on stitchkit:
buildContext()ignoredinputSchemafor GET and DELETE — everylistendpoint with filters (?status=active&limit=20) was broken.- Some projects mount services under a path prefix
(
/bots/:botId/...,/api/{prefix}/...). stitchkit only built flat paths.
Decision
Query params on GET and DELETE. GET and DELETE parse inputSchema from the
URL query. searchParams.getAll() supports repeated keys as arrays
(?tag=a&tag=b). DELETE sniffs the content-type: a JSON body when
application/json, the query otherwise. Coercion is the schema author’s
responsibility — query params are always strings; a schema that wants a number
uses z.coerce.number(). The framework adds no automatic coercion (rejected:
auto-coerce primitives — magic; a withQueryCoercion helper — redundant with
z.coerce.*).
Route groups. ServerConfig accepts groups: RouteGroup[] alongside flat
services. A RouteGroup has a pathPrefix, its services, and optional
per-group hooks. :param segments from the prefix (:botId) are matched and
placed into the context. (Rejected: a scopePaths map keyed by the scope string
— ties URL structure to the scope vocabulary; a mutable ServiceDef.pathPrefix
— mutating the service after implement() is dirty.) Flat services remain
valid, so the simple case stays simple.
Consequences
- List endpoints work with query filters; arrays are supported.
- Grouping is explicit and decoupled from the scope vocabulary; per-group hooks
enrich the context (e.g. resolve
:botId) before the handler runs. - Coercion stays in the contract — no framework magic to reason around.