feat(examples): add the worker deployment and the examples index - #6
Merged
Conversation
The same ApplicationModule + PersistenceModule composition, booted under an in-memory queue worker instead of the oRPC server — with no change to order-application, order-infrastructure or the kernel. The same DuplicateOrder that is a typed CONFLICT over HTTP is a dead-letter here, and a Defect that is an INTERNAL_SERVER_ERROR over HTTP is worth another delivery on a queue. queueWorkerRuntime declares a non-empty needs of its own ([PlaceOrder, Logger], two of the three the module exports), pinned in both directions by needs-gate.test-d.ts. A delivery is the unit and the message id is the traceId, since a retried message is two units; the disposition is applied inside the unit, because acking a message and flushing a response are the same obligation. Adds examples/README.md as the index — the layering, the dependency direction, and the two runtimes being the only ones in the repo with a non-empty needs — plus links from the root README, and CLAUDE.md records that examples/ is part of the gate, that the Prisma client is generated at test time, and why oRPC is pinned to an exact beta.
There was a problem hiding this comment.
Pull request overview
Adds a second example deployment (order-worker) that boots the existing clean-architecture order application under an in-memory queue worker runtime, plus an examples index and cross-links so the repo documentation points at the “one application, two runtimes” proof.
Changes:
- Introduce
examples/order-workeras a new workspace: queue broker abstraction, queue runtime, env parsing, composition root, and test suite (including needs-gate type tests). - Add
examples/README.mdas an index explaining the five example packages, layering, and the “same Result, two transports” mapping. - Update root docs/spec and repo tooling to reference and include the new workspace (README, CLAUDE, knip, lockfile).
Reviewed changes
Copilot reviewed 20 out of 22 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| README.md | Links to examples/ and updates documentation section to reference the examples set. |
| pnpm-lock.yaml | Adds lock entries for the new examples/order-worker workspace dependencies. |
| knip.json | Registers examples/order-worker entrypoint for Knip workspace analysis. |
| examples/README.md | New examples index explaining package roles, layering, and the two deployments. |
| examples/order-worker/vitest.config.ts | New Vitest config for the worker example package. |
| examples/order-worker/tsconfig.test-d.json | New TS config for type-level tests (*.test-d.ts) in worker package. |
| examples/order-worker/tsconfig.json | New TS config for the worker package (incl. allowImportingTsExtensions). |
| examples/order-worker/src/vitest.d.ts | Adds @unthrown/vitest type registration for tests. |
| examples/order-worker/src/test-fixtures.ts | Provides Vitest fixtures for starting apps, queue access, and specialized test graphs. |
| examples/order-worker/src/queue.ts | Implements an in-memory queue/broker abstraction with publish/claim/requeue/settle. |
| examples/order-worker/src/queue-runtime.ts | Implements the queue worker Runtime and maps Result → ack/retry/dead-letter. |
| examples/order-worker/src/queue-runtime.spec.ts | End-to-end runtime behavior specs (ack, dead-letter, retry, info publishing, draining). |
| examples/order-worker/src/needs-gate.test-d.ts | Type-level tests pinning the runtime needs gate behavior at start(...). |
| examples/order-worker/src/module.ts | Defines OrderWorkerModule composition root for the worker deployment. |
| examples/order-worker/src/main.ts | Worker process entry: validate env, start, and runMain. |
| examples/order-worker/src/index.ts | Package public exports barrel for worker example. |
| examples/order-worker/src/env.ts | Env schema and parsing as a Result value (no throwing parse). |
| examples/order-worker/src/env.spec.ts | Env parsing tests (defaults, parsing, malformed values, range validation). |
| examples/order-worker/README.md | Package README documenting purpose, mapping, unit/trace semantics, and running commands. |
| examples/order-worker/package.json | New workspace package definition and scripts (test/typecheck/test:types). |
| examples/order-api/README.md | Cross-links to order-worker and updates test count line. |
| CLAUDE.md | Updates authoritative spec to include examples/ as part of the gate and documents example-related conventions. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
`publish`'s docstring claimed a settlement always arrives "because the worker's attempt budget is finite". The budget bounds the redeliveries of a job a worker has *claimed*; nothing bounds the wait for a claim. With no worker running — or one that stopped or drained with jobs still queued — the promise never resolves and an awaited `publish` hangs. Say so, rather than inventing broker behaviour to satisfy the old sentence: settling still-pending messages on shutdown is the opposite of what a durable queue does, it cannot be honoured by the AMQP channel this example promises swaps in unchanged, and it would still not cover the no-worker case. `Serving.drain` gets the same treatment: it stops *claiming*, so a message not yet claimed stays in the queue unsettled and the drain does not wait for it. Two specs pin both halves, racing a publish against one macrotask turn via the new `withinATurn` fixture — a bound, since the delivery path holds no timer — so the legitimate never-settles state fails in a millisecond instead of timing out. 150 tests -> 152.
btravers
added a commit
that referenced
this pull request
Aug 12, 2026
…to 0
The previous commit's bare `z.coerce.number()` dropped a guard that was earning
its keep. `Number("")` is `0`, and a port's `min` is `0` so that an ephemeral
bind stays expressible — so `PORT=` silently bound an ephemeral port instead of
being reported. That is a regression, not a documented quirk, and an example
that ships a footgun teaches the footgun.
A non-empty trimmed string in front of the coercion closes it while keeping the
simplification the reviewer asked for:
z.string().trim().min(1)
.pipe(z.coerce.number<string>().int().min(min).max(max))
.default(fallback)
Still no regex and no hand-rolled `.transform(Number)`. The `<string>` type
argument is required because `z.coerce.number()`'s input is `unknown`, which
`.pipe` will not accept from a `string`. `.default(...)` still applies only when
the variable is genuinely absent, so reject-to-default is unchanged too.
All seven cases are now pinned by a test in each of the three env specs: absent,
"", whitespace, "abc", "3.5", a valid value, out of range. `order-api`'s original
`{ PORT: "abc", PROBE_PORT: "" }` fixture is restored and passes unchanged, and
the notes describing the hole are gone from the three env.ts files, the README
and CLAUDE.md #6 — which states the rule plainly again.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Task 4 — the last of the clean-architecture example plan. A second runtime (an in-memory queue worker) over the same application, plus the examples index and docs.
What this proves
The kernel's headline claim is one process, one runtime: several runtime kinds exist, a process boots exactly one, and the same application module boots under any of them.
order-apiproved a runtime could be built. This proves the claim.packages/start,order-domain,order-applicationandorder-infrastructureare byte-unchanged — verified by diff stat, not asserted. The worker composes the sameApplicationModule+PersistenceModuleand boots it under a different transport, and nothing in the layers below moved to accommodate it.RunUnit,UnitMetaandServing.infofit a queue unmodified.The same
Result, two transports, the kernel involved in neither. This is the sharpest demonstration in the example set:OkDuplicateOrderORPCErrorcodeCONFLICTInvalidQuantityORPCErrorcodeINVALID_QUANTITYDefectINTERNAL_SERVER_ERRORThat last row is the interesting inversion: a 500 over HTTP is precisely the outcome worth redelivering on a queue. It gives the defect channel a runtime meaning rather than leaving it as "the thing that isn't an error".
Every arm is named — no
P._, per this repo'sno-catch-all-patternrule.The needs gate holds here too, verified in both directions by deleting the
@ts-expect-errorand confirming theUNSATISFIED RUNTIME NEEDSarity error, then restoring.Also: each job is its own unit with a distinct trace id, and draining waits for the in-flight job (
{ inFlightAtStart: 1, completed: 1, abandoned: 0 }).Docs
examples/README.md(followingbtravstack/di's shape — the layering, the dependency direction, and that these are the only runtimes in the repo with non-emptyneeds), a link from the root README, andCLAUDE.mdrecording thatexamples/is part of the gate, that the Prisma client is generated at test time, and that oRPC is pinned to a beta becauselatestis the 1.x line.Verification
150 tests (93 kernel + 57 examples), up from 139. Full six-command gate green, no Docker.
One process note, disclosed rather than buried: spec and implementation were written together, so the suite was never seen red. In place of that, the two assertions this task turns on were mutation-tested — routing the
DuplicateOrderarm toack, and collapsing the per-job id to a constant — and both fail loudly. Outputs are in the task report.