Recipes
The Powerhouse Recipes repository is a collection of small, standalone, runnable projects. Each one isolates a single Reactor pattern — a processor, a read model, a sync channel, an auth scheme, a schema migration — so you can read it end to end, run it locally, and lift the parts you need into your own package.
This page is a map. For each recipe it gives the core idea, the Reactor primitives it exercises, and links straight to the source on GitHub. Click through to the repo for full setup instructions, demos, and expected output. For the same recipes rendered inline behind expandable toggles, see the Cookbook.
:::info Examples, not packages
Recipes are reference implementations, not published libraries. Clone the repo, read the recipe, and adapt the code — don't depend on the @powerhousedao/example-* packages in production.
:::
Running a recipe
Every recipe is a workspace package with a runnable demo. From the repo root:
pnpm install
pnpm build
# then run a single recipe's demo, e.g.
pnpm --filter @powerhousedao/saga start
Each recipe's README lists its exact pnpm --filter … start command. The in-memory recipes run against PGlite with no external services; the ones that need PostgreSQL document their connection setup in their own README.
Processors & read models
The most common Reactor extension point. A processor reacts to operations post-ready (after a chain is readable) to perform side effects; a read model indexes pre-ready to build a derived view that readers can trust the instant they see it. These recipes cover both ends.
Analytics Processor
An IProcessor that projects expense-report operations into the Powerhouse analytics-engine time-series store, then answers rollups by category, month, and currency.
- Maps each
OperationWithContexttoAnalyticsSeriesInputrows viaIAnalyticsStore.addSeriesValues, registered through aProcessorFactorywith a filter andstartFrom: "beginning". - Models corrections as append-only compensating deltas (
ADDwrites+amount;UPDATEwrites-oldthen+new;DELETEwrites-old), so the financial series stays auditable. - Idempotent replay: an
operation.index === 0for an already-seen document triggersclearSeriesBySourceand a rebuild, so re-delivery always converges to identical totals.
Source · src/processor.ts · src/query.ts
Concepts · IProcessor.onOperations · ProcessorFactory · IAnalyticsStore · AnalyticsPath · AnalyticsQueryEngine — see Processors
Audit Trail
A processor that reads ActionSigner context off every operation and writes an immutable audit log to PostgreSQL, fronted by a GraphQL subgraph.
- Mines signer identity from
operation.action.context.signer(user.address,networkId,chainId, app credentials) and batch-inserts one Kysely statement per operation batch. - Pairs with
ReactorClientBuilder.withSignerso client writes automatically populate the signer context the processor reads. - Exposes a graphql-yoga subgraph to query entries by user, document, or time range.
Source · src/processor.ts · src/subgraph.ts
Concepts · IProcessor.onOperations · ActionSigner · ProcessorFactory · ReactorClientBuilder.withSigner — see Processors, Reactor Client
Custom Read Model
Implements IReadModel directly and registers it with ReactorBuilder.withReadModel() to maintain a document-count-per-type materialized view.
- Implements the
IReadModelcontract directly (justindexOperationsplus query getters) instead of extendingBaseReadModel— the right choice when you don't need catch-up/rewind plumbing. - Registered read models index pre-ready: their work completes before
JOB_READ_READYfires, so the view is consistent the instant downstream subscribers see the event. This is the key contrast with post-ready processors.
Source · src/document-count-read-model.ts · src/index.ts
Concepts · IReadModel.indexOperations · ReactorBuilder.withReadModel · pre-ready vs post-ready · JOB_READ_READY — see Processors, Working with the Reactor
Full-Text Search Processor
A processor that flattens each document's resulting state into searchable text and maintains a PostgreSQL tsvector index for ranked keyword search.
- Indexes from
context.resultingStaterather than individual action inputs, so it stays agnostic to any document model's action set; dedupes to the last operation perdocumentIdwithin a batch. - Recursively flattens arbitrary JSON state into one string and maintains the row with an idempotent
INSERT … ON CONFLICTupsert; reacts to theDELETE_DOCUMENTaction by removing the row. startFrom: "beginning"back-indexes existing history; queries useplainto_tsqueryandts_rankover a GIN index.
Source · processor.ts · query.ts
Concepts · IProcessor.onOperations · context.resultingState · processorManager.registerFactory · tsvector — see Processors, Storage and Scaling
Relational DB Subgraph
A document-type-agnostic RelationalDbProcessor that projects every document into denormalized, Kysely-managed Postgres tables and serves them through a GraphQL subgraph.
- Subclass
RelationalDbProcessor<DB>and maintain the relational read model inonOperations— parsecontext.resultingState, handleDELETE_DOCUMENTby clearing rows. - Owns its schema through an
initAndUpgrade()override that runs an idempotent (ifNotExists) Kysely migration, so registration is safe to repeat; uses the typed query builder, dropping to rawsqlonly for anON CONFLICTupsert. - A separate query layer feeds a graphql-yoga subgraph that can compose into a supergraph.
Source · src/processor.ts · src/subgraph.ts
Concepts · RelationalDbProcessor · IRelationalDb · initAndUpgrade · GraphQL subgraph — see Processors, Storage and Scaling
Semantic Search Processor
A processor read model that embeds document state in-process with Transformers.js (MiniLM) into PGlite + pgvector and answers cosine-similarity queries — no external API.
- In-process embeddings (ONNX-quantized
all-MiniLM-L6-v2, 384-dim) stored and queried in PGlite's pgvector extension — no embedding service, no separate vector database. - Skips the expensive embed step when a SHA-256
content_hashmatches the stored row; bakes the dimension into thevector(384)column and asserts the length before insert so a mismatched model fails loudly. - Injects the embedder as a dependency, so tests run against a deterministic offline fake.
Source · processor.ts · embedder.ts · query.ts
Concepts · IProcessor.onOperations · context.resultingState · pgvector · Transformers.js — see Processors, Storage and Scaling
Cross-document automation & orchestration
Treating the Reactor as an automation engine: one operation triggers work on other documents, or a single call provisions many documents at once.
Batch Progress
Creates a dependency-ordered set of documents in a single IReactor.executeBatch call and tracks each job's lifecycle live via the EventBus.
- One
executeBatchsubmits a graph of jobs; each job'sdependsOn(referencing other jobs by key) lets the Reactor resolve ordering and parallelism across both document and drive scopes. - Subscribe to
JOB_PENDING/JOB_RUNNING/JOB_WRITE_READY/JOB_READ_READY/JOB_FAILEDon the EventBus, mappingresult.jobs[key].idback to each job for a live progress view. JobAwaiterreconciles events that arrived afterexecuteBatchreturned, so awaiting a terminal status is race-free.
Source · src/create-project.ts · src/index.ts
Concepts · IReactor.executeBatch · dependsOn · IEventBus.subscribe · JobAwaiter.waitForJob — see Advanced Reactor Usage, Error Handling
Cross-Document Reactor
Drives event-driven automation by subscribing to all document changes through an IReactorClient and dispatching actions on related documents from inside the handler.
client.subscribe({}, cb)with an empty filter delivers everyDocumentChangeEvent; the handler branches onDocumentChangeType.Updated.- Resolves relationships at runtime with
client.find({ parentId })plus a naming convention, then acts viaclient.rename. - A local re-entrancy guard (a
reactingflag) stops the handler's own write — which fires another change event — from re-triggering the rule.
Source · src/index.ts
Concepts · IReactorClient.subscribe · DocumentChangeEvent · client.find · re-entrancy guard — see Reactor Client
Saga Coordination Processor
A processor implements the saga pattern: operations on one document dispatch follow-up actions to others, all correlated by a shared saga_id.
- Declarative step definitions (trigger match, target resolver, action builder) drive which incoming operation fires which follow-up; the processor calls
IReactor.executeon a different document. - A re-entrancy flag prevents the processor from reacting to the operations it itself dispatched, so the saga can't loop forever.
- Correlation lives entirely in the processor's own
saga_logtable keyed bysaga_id— no changes to any document model or interface.
Source · src/processor.ts · src/demo.ts
Concepts · IProcessor.onOperations · IReactor.execute · re-entrancy guard · saga correlation — see Processors
External integration & ingestion
Bridging the Reactor to the outside world — pulling external events in, and pushing operations out — without losing idempotency or authenticity.
Inbound Webhook Bridge
A standalone node:http endpoint that HMAC-verifies signed external webhooks against the raw request bytes and dispatches each verified event as a typed document action.
- Verifies the HMAC over the exact raw bytes before
JSON.parse— a GraphQL/JSON resolver would parse too early, and aJSON.parse→JSON.stringifyround-trip changes the bytes and breaks the MAC. - Stripe-style scheme: HMAC over the
timestampjoined to therawBodywithcrypto.timingSafeEqualplus a replay window, so folding the timestamp into the MAC makes the freshness check tamper-proof. - Idempotency survives restarts by storing processed event ids in document state (
processedEventIds), not processor memory; the reducer independently throwsDuplicateEventas a second line of defense.
Source · src/webhook-bridge.ts · src/signature.ts
Concepts · IReactor.execute · JobAwaiter.waitForJob · action creators · reducer dedup guards — see Error Handling, Working with the Reactor
External Feed Ingest
A long-running polling worker that idempotently ingests an external feed into an event-sourced ledger document, seeding its dedup set and watermark from document state so a restart never double-ingests.
- The document is the checkpoint store:
seedFromState()rebuilds the in-memory dedup set and high-watermark fromreactor.get().state, so a crashed poller resumes with no side database. - Dedupes on the stable
externalIdchecked against state, not the delivery cursor — at-least-once feeds redeliver the same id past any watermark; the watermark is only a fetch optimization. - Corrections are explicit append-only operations: a restatement marks the old entry
SUPERSEDEDand appends a newRECORDEDentry — the original payload is never mutated.
Source · src/poller.ts · document-models/feed-ledger/v1/src/reducers/ingest.ts
Concepts · IReactor.get / execute · JobAwaiter · typed reducer errors · append-only corrections — see Error Handling, Reactor Client
Discord Webhook Processor
A processor that forwards filtered document operations to a Discord webhook as rich embeds, HMAC-signing every request.
- A
ProcessorFilteron the record (rather than branching insideonOperations) scopes which operations reach the processor. - Chunks the operations array to respect a downstream system's batch limit (Discord's 10-embed cap) across multiple
fetchPOSTs. - Signs each outbound request with an HMAC-SHA256
X-Reactor-Signatureover the exact JSON body.
Source · discord-webhook-processor.ts · demo.ts
Concepts · IProcessor.onOperations · ProcessorFilter · startFrom: "current" · HMAC signing — see Processors
Auth, signing & security
Where identity comes from (action.context.signer), how it's verified, and how access decisions are made — inside the reducer, at a gate, or after the fact.
Role-Based Auth
A custom document model that embeds RBAC in document state and enforces it inside reducer operations by reading the caller from action.context.signer.user.address.
- Authorization lives in the reducer, not a server gate — so access control replicates with the document; each operation throws if the caller is missing or lacks the required role.
- Roles are state, not config:
creator/admins/membersare fields, and the firstbootstrapcaller auto-promotes to permanent root admin, protected byCannotRevokeCreator/LastAdmininvariants. - Rejections surface as a typed error taxonomy (
NotAuthorized,NotAdmin, …) captured ontooperation.errorin the document's log.
Source · document-models/role-based-auth/v1/src/reducers/access.ts · src/demo.ts
Concepts · document-model reducer · action.context.signer · generated error taxonomy — see Working with the Reactor
Rate Limiter
A read-only processor that counts operations per signer address over a sliding window and trips a shared AuthService cooldown, which a request gate consults to throttle abusive users.
- Decouples observation from enforcement: the processor only signals (
authService.cooldown) and never blocks, while the request gate (resolver/middleware) readsauthService.isAllowedbefore forwarding mutations. - Derives the per-user key from
signer.user.address; sliding-window counting in a plainMap, reset inonDisconnect.
Source · src/rate-limiter-processor.ts · src/auth-service.ts
Concepts · IProcessor.onOperations · signer.user.address · AuthService gate · startFrom: "current" — see Processors
Signed Operations Verifier
A standalone script that builds cryptographically signed operations with an ISigner and verifies each one per-signature, detecting tampered and unsigned operations.
- Builds a self-contained ECDSA P-256
ISignerwithRenownCryptoBuilderplus in-memory key storage — no filesystem or network. buildSignedActionadvances the document through the reducer between actions, so each signature captures the correct previous-state hash;verifyOperationSignaturechecks each signature tuple.- Tamper and unsigned detection: corrupting a signature element or removing
action.contextflips verification to invalid / unsigned respectively.
Source · src/verify-operations.ts · src/verify-operations.test.ts
Concepts · ISigner · RenownCryptoSigner · buildSignedAction · verifyOperationSignature — see Advanced Reactor Usage
Document-model patterns
Patterns that live in the document model itself — how schemas evolve over time, and how to model a container without paying for it.
Document Versioning
Migrates a document model's schema from v1 to v2 with an UpgradeManifest and a pure upgradeReducer, so operation logs recorded under the old schema still replay into the new state shape.
- Declares every schema version in one codegen spec so
ph generateemits a versionedv1/v2/upgradestree plus a wiredUpgradeManifest. - The upgrade reducer must patch both
stateandinitialState: replay folds domain operations from the storedinitialStateand never re-runs theUPGRADE_DOCUMENTop, so a state-only migration passes live but fails replay withHashMismatchError. - The latest reducer owns the entire history — retired operations are kept and their reducer maps old fields onto new ones, so cross-version replay converges.
Source · document-models/todo/upgrades/v2.ts · src/upgrade.ts
Concepts · UpgradeManifest · upgradeReducer · computeUpgradePath · replayDocument · HashMismatchError — see Document Model Registry
Drive Override
A custom container document that tracks its children through the reactor's built-in ADD_RELATIONSHIP action instead of document-drive's ADD_FILE, keeping container state O(1).
- The container is an ordinary
DocumentModelModuleholding only metadata (name/description, oneSET_METADATAop) — no embeddednodes[], so its state and operation log stay flat as children scale past 10,000. - Children are linked with
addRelationshipAction(...)dispatched throughreactor.execute; the indexer writes one row to theDocumentRelationshiptable and the container's reducer never sees them. - Enumerate with
IDocumentIndexer.getOutgoing/getIncomingand cursor paging — DB-native indexed queries, also exposed over GraphQL.
Source · src/index.ts · document-models/custom-container/v1/src/reducers/metadata.ts
Concepts · addRelationshipAction · IDocumentIndexer · ConsistencyToken · DocumentModelModule — see Working with the Reactor
Tooling & operations
Operating a Reactor from the outside: exporting state, watching the live event stream, monitoring sync health, and moving the underlying database.
Document Snapshot Exporter
A CLI that exports document state and operation history to JSON using IReactor consistency tokens for read-after-write, contrasted side-by-side with the auto-managed IReactorClient.
- Threads the
ConsistencyTokenfrom a completed write intoreactor.get()/getOperations(), so a read reflects the just-finished write even while background indexing lags. - Side-by-side comparison via a
--modeflag: low-levelIReactor(returnsJobInfo, manual awaiting + tokens) versusIReactorClient(awaits and manages consistency internally, returns the document directly). - Handles the differing
getOperationsshapes — a per-scopeRecordforIReactorversus a flatPagedResultsfor the client.
Source · src/export-reactor.ts · src/export-client.ts
Concepts · ConsistencyToken · IReactor vs IReactorClient · JobAwaiter · getOperations — see Working with the Reactor, Reactor Client
Subscription CLI
A Node CLI that connects to a Reactor's GraphQL subscriptions endpoint over WebSocket and prints documentChanges (and optional jobChanges) events live.
- Consumes real-time subscriptions from outside the reactor process via the
graphql-wsprotocol, rather than from an in-process processor. - Server-side filters the change stream with a
SearchFilterInput(by document type and/or parent drive); passes bearer-token auth throughconnectionParams. - Optionally watches a single job's lifecycle via
jobChanges(jobId), and cleans up per-stream subscriptions onSIGINT/SIGTERM.
Source · src/index.ts
Concepts · documentChanges · jobChanges · SearchFilterInput · graphql-ws — see Synchronization, Reactor Client
Sync Health Monitor
Subscribes to the Reactor EventBus sync lifecycle events to maintain live replication-health counters and exposes them through a GraphQL subgraph.
- Folds all five
SyncEventTypes(pending / succeeded / failed / dead-letter / connection-state) into health counters and derives a healthy / degraded / unhealthy status. - Implements
IChannelFactoryandIChannelto bridge two reactors entirely in-process, calling the success/failure lifecycle hooks on eachSyncOperationitem (transported/executed/failed) and routing failed sends to a dead-letter mailbox. - Registers peers via
syncModule.syncManager.add(...)and serves asyncHealthquery over a graphql-yoga subgraph.
Source · src/health-monitor.ts · src/internal-channel.ts
Concepts · IEventBus.subscribe · SyncEventTypes · IChannel / IChannelFactory · syncManager.add — see Synchronization
DB Migrate
Three Bash scripts that export, import, and migrate a PostgreSQL database by running pg_dump / pg_restore / psql inside the postgres:17 Docker image — no local Postgres tools needed.
- Runs the Postgres client tools from the official Docker image, so Docker is the only prerequisite for backing up or moving a Reactor relational store.
migrate.shstreamspg_dumpstraight intopg_restore(orpsql) over a shell pipe, copying one database into another in a single pass with no intermediate dump file.- A code-free operations recipe — no document models, processors, or subgraphs.
Source · migrate.sh · export.sh
Concepts · pg_dump · pg_restore · Docker — see Storage and Scaling