Queries
A query is a named, typed read over a projection: "give me this student's submissions, newest first." It takes validated input, reads the projection's state, and returns a result. It never touches the write side — reads come from the read model, which is what keeps them fast and free of surprises.
Shape
ts
import { defineQuery } from "@nwire/forge";
export const submissionsByStudent = defineQuery(SubmissionsByStudent, {
name: "submissions.by-student",
description: "Avi's submission history, newest first.",
input: z.object({
studentId: z.string(),
status: z.enum(["submitted", "under-review", "graded"]).optional(),
}),
execute: (state, { studentId, status }) => {
const all = state.byStudent[studentId] ?? [];
return status ? all.filter((s) => s.status === status) : all;
},
slo: { p95LatencyMs: 50 },
cacheable: true,
});Usage
ts
// Register the query as a handler when building the App:
createApp({ appName: "submissions", registry: defineRegistry({ handlers: [submissionsByStudent] }) });
// HTTP wire — mount the query under a route binding. The query IS the
// handler, so the wire references it directly.
app.wire(
get("/submissions", { query: submissionsByStudentInput }),
submissionsByStudent,
);
// From an action handler — projection state read via ctx.query
const reviewSubmission = defineAction({
name: "submissions.review",
input: ReviewSubmissionInput,
handler: async (ctx) => {
const { input } = ctx;
const history = await ctx.query(submissionsByStudent, { studentId: input.studentId });
// …
},
});Why queries instead of "just call the projection"
Three reasons:
- Validation — query inputs go through Zod, same as any handler input.
- Wire ergonomics — wire a query under a route binding and the runtime parses the query-string input through its schema.
- Observability — queries show up in the trace view, and observed latency is scored against the
slo.p95LatencyMsyou declare.
Multi-tenancy
The runtime passes envelope.tenant to the projection store before invoking execute. Tenant data is scoped automatically — you never thread it manually.
See also
defineQueryreference — full API.- Projection — the read model a query serves.
- Wires — mounting a query under a route.