feat(examples): add the Prisma persistence layer - #4
Merged
Conversation
The infrastructure layer of the clean-architecture example: a Prisma-backed OrderRepository over in-memory SQLite, and the PersistenceModule that closes ApplicationModule's one unmet need. The load-bearing piece is the adapter's error translation. @unthrown/prisma's tryCreate reports a P2002 as UniqueConstraintViolation; the adapter turns it into the application's own DuplicateOrder with an exhaustive mapErrCases that names every case — there is no P._ and no wildcard to hide behind. The other two P-codes are unreachable against a relation-free single-model schema and go to the defect channel, which is also the only place they can go: the port's E is the single type DuplicateOrder. The specs run against a real in-memory SQLite database (@prisma/adapter-better-sqlite3), so the duplicate is a genuine constraint violation rather than a canned error. The generated client is gitignored and minted by the test/typecheck scripts. The database provider uses di's acquire/release arm, so scope teardown disconnects a real client.
There was a problem hiding this comment.
Pull request overview
Adds the infrastructure layer for the clean-architecture order example by introducing a Prisma-backed OrderRepository over in-memory SQLite, intended to satisfy ApplicationModule’s unmet OrderRepository port while keeping Prisma/database vocabulary behind the adapter boundary.
Changes:
- Introduces a new
examples/order-infrastructureworkspace with Prisma schema/config, a DI module (PersistenceModule), and a Prisma-based repository adapter. - Adds tests proving unique-constraint translation (
P2002→DuplicateOrder) and that corrupt persisted rows surface as defects rather than widening domain error channels. - Updates workspace configuration and lockfile to include Prisma + better-sqlite3 dependencies and ignores generated Prisma client output.
Reviewed changes
Copilot reviewed 13 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-workspace.yaml | Adds Prisma-related catalog deps and workspace pnpm rules for builds/peers. |
| pnpm-lock.yaml | Locks new Prisma/better-sqlite3 dependency graph and adds the new example importer. |
| examples/order-infrastructure/vitest.config.ts | Vitest config for the new example workspace. |
| examples/order-infrastructure/tsconfig.json | TS config tuned for generated Prisma client and node test environment. |
| examples/order-infrastructure/src/vitest.d.ts | Registers @unthrown/vitest matchers for the example tests. |
| examples/order-infrastructure/src/database.ts | In-memory SQLite Prisma client setup + DI port/provider. |
| examples/order-infrastructure/src/prisma-order-repository.ts | Prisma adapter translating Prisma errors to domain errors/defects. |
| examples/order-infrastructure/src/prisma-order-repository.spec.ts | Integration tests for repository behavior + module scoping/teardown. |
| examples/order-infrastructure/src/module.ts | Defines PersistenceModule exporting only OrderRepository. |
| examples/order-infrastructure/src/index.ts | Public exports for the example package. |
| examples/order-infrastructure/README.md | Documents the layering boundary and error translation behavior. |
| examples/order-infrastructure/prisma/schema.prisma | Defines the Order model and unique constraint used by the tests. |
| examples/order-infrastructure/prisma.config.ts | Prisma config pointing to the example schema. |
| examples/order-infrastructure/package.json | New workspace package definition and scripts (generate/test/typecheck). |
| docs/superpowers/plans/2026-08-11-examples-clean-architecture.md | Updates plan doc to reflect Serving.info / runtimeInfo() API. |
| .gitignore | Ignores generated Prisma client output (**/src/generated/prisma/). |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (1)
examples/order-infrastructure/src/prisma-order-repository.spec.ts:82
- The optional chaining on
escapedcan lead to asserting againstundefinedrather than the repository instance. After makingescapednon-optional (e.g., via definite assignment), callescaped.find(...)directly so the test fails in a clear way if the assignment ever stops happening.
await expect(escaped?.find("o-1")).toBeDefect();
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
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 2 of the clean-architecture example slice: the infrastructure layer. A Prisma-backed
OrderRepositoryover in-memory SQLite, satisfying the port thatApplicationModuleleaves as an unmet need.What this proves
Infrastructure vocabulary does not reach the application layer.
@unthrown/prisma'stryCreatereturnsErr(UniqueConstraintViolation)for P2002; the adapter translates it into the domain's ownDuplicateOrderthrough an exhaustivemapErrCaseswith every case named — noP._, per this repo's ownno-catch-all-patternrule.That claim is proven against a real database — real in-memory SQLite via
@prisma/adapter-better-sqlite3, a real@uniqueindex — and by four mutations, all killed:Ok(...)— the constraint is real, not simulateddefect(...)Defect([UniqueConstraintViolation])ForeignKeyViolationarmThe last two are the interesting ones: the layering boundary is enforced by the type checker, not by a test somebody could delete.
ForeignKeyViolationandRecordNotFoundroute todefect(...)becauseOrderRepository.save's error channel is the single typeDuplicateOrder— widening it to carry infrastructure failures is exactly what the port exists to prevent. Both are also unreachable against a relation-free single-model schema.Mechanics
@unthrown/drizzlewas rejected for these examples precisely because it needs a Docker daemon; an example whose job is to be cloned and run must not.prisma generate && vitest run), the pattern@unthrown/prisma's own package uses.acquire/releasearm, so the client disconnects on scope close — which also exercises the kernel's teardown reaching a real resource.Verification
113 tests, up from 107; the 107 existing ones unchanged. Full six-command gate green with no Docker running.
packages/start/has an empty diff.Note for the next task
Each
Module.scopedopens its own in-memory database, so an HTTP example wanting data to survive across requests must hold one outer scope and useModule.forkScopeper request — which is di's documented request-scope pattern, and worth demonstrating rather than working around.