Skip to content

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:

  1. Validation — query inputs go through zod, same as action inputs
  2. HTTP wire ergonomicsdefineRoute({ "GET /x": myQuery }) mounts it automatically with the right from-query-params parsing
  3. 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

MIT licensed.