Skip to content

Projection

A read-side view folded from events. CQRS: writes go through actors, reads through projections.

Projections are pure folds: (state, event) => state. No side effects, no reads from external systems. The runtime replays event history into them.

Shape

ts
import { defineProjection } from "@nwire/forge";

export const SubmissionsByStudent = defineProjection<{
  byStudent: Record<string, SubmissionSummary[]>;
}>("submissions-by-student", {
  description: "Index of submissions keyed by studentId — feeds Avi's history view.",
  freshness: { p95MsBehindStream: 50 },   // Studio scores observed lag against this

  listens: [
    AnswerSubmittedEvent,
    SubmissionAutoGradedEvent,
    SubmissionFlaggedForReviewEvent,
    SubmissionManuallyGradedEvent,
  ],

  initial: () => ({ byStudent: {} }),

  on: {
    [AnswerSubmittedEvent.name]: (state, event) => ({
      byStudent: {
        ...state.byStudent,
        [event.studentId]: [
          ...(state.byStudent[event.studentId] ?? []),
          { id: event.submissionId, status: "submitted", submittedAt: event.submittedAt },
        ],
      },
    }),
    [SubmissionAutoGradedEvent.name]: (state, event) => updateOne(state, event.studentId, event.submissionId, (s) => ({
      ...s,
      status: "graded",
      verdict: event.verdict,
    })),
    // …
  },
});

Where do they live?

In memory by default (InMemoryProjectionStore). For persistence:

ts
import { MongoProjectionStore } from "@nwire/store-mongo";
import { FileProjectionStore } from "@nwire/store-file";

createApp({ modules, projectionStore: new MongoProjectionStore(client) });

Multi-tenancy

Projection state is partitioned by envelope.tenant. Each tenant gets its own folded state. No code change required — the framework partitions automatically.

Backfill / replay

Projections are pure functions over the event log. Replay a tenant's state by re-feeding events into runtime.publish after clearing the store. (Studio's "Replay" feature, coming.)

Studio-aware metadata

FieldWhat Studio does
descriptionTooltip / detail panel
freshness: { p95MsBehindStream }Compares declared lag target to observed lag from telemetry

See also

MIT licensed.