Query
A read function over a projection. CQRS: queries never read actors.
Shape
ts
import { defineQuery } from "@nwire/forge";
export const submissionsByStudent = defineQuery(SubmissionsByStudent, {
name: "submissions.by-student",
description: "Avi's submission history, newest first.",
schema: 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
// From the HTTP wire — mount as GET /submissions
defineModule("submissions", {
queries: [submissionsByStudent],
routes: defineRoute({
"GET /submissions": submissionsByStudent,
}),
});
// From a handler — projection state passed in by runtime
defineHandler(reviewSubmission, async (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 action inputs
- HTTP wire ergonomics —
defineRoute({ "GET /x": myQuery })mounts it automatically with the rightfrom-query-paramsparsing - Studio — queries appear in the EventStorm canvas as teal stickies; observed latency from the telemetry stream scores against
slo.p95LatencyMs
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
- defineQuery
- Projection — what queries read
- HTTP routes