Skip to content

feat(examples): add the Prisma persistence layer - #4

Merged
btravers merged 3 commits into
mainfrom
feat/examples-persistence
Aug 11, 2026
Merged

feat(examples): add the Prisma persistence layer#4
btravers merged 3 commits into
mainfrom
feat/examples-persistence

Conversation

@btravers

Copy link
Copy Markdown
Contributor

Task 2 of the clean-architecture example slice: the infrastructure layer. A Prisma-backed OrderRepository over in-memory SQLite, satisfying the port that ApplicationModule leaves as an unmet need.

What this proves

Infrastructure vocabulary does not reach the application layer. @unthrown/prisma's tryCreate returns Err(UniqueConstraintViolation) for P2002; the adapter translates it into the domain's own DuplicateOrder through an exhaustive mapErrCases with every case named — no P._, per this repo's own no-catch-all-pattern rule.

That claim is proven against a real database — real in-memory SQLite via @prisma/adapter-better-sqlite3, a real @unique index — and by four mutations, all killed:

Mutation Result
Drop the UNIQUE index test fails with Ok(...) — the constraint is real, not simulated
Route P2002 to defect(...) test fails with Defect([UniqueConstraintViolation])
Return the Prisma error untranslated does not compile
Delete the ForeignKeyViolation arm does not compile, naming the unhandled case

The last two are the interesting ones: the layering boundary is enforced by the type checker, not by a test somebody could delete.

ForeignKeyViolation and RecordNotFound route to defect(...) because OrderRepository.save's error channel is the single type DuplicateOrder — 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

  • No Docker, no network at test time. @unthrown/drizzle was rejected for these examples precisely because it needs a Docker daemon; an example whose job is to be cloned and run must not.
  • The generated Prisma client is gitignored and generated at test time (prisma generate && vitest run), the pattern @unthrown/prisma's own package uses.
  • The provider uses di's acquire/release arm, 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.scoped opens its own in-memory database, so an HTTP example wanting data to survive across requests must hold one outer scope and use Module.forkScope per request — which is di's documented request-scope pattern, and worth demonstrating rather than working around.

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.
Copilot AI lite review requested due to automatic review settings August 11, 2026 20:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-infrastructure workspace with Prisma schema/config, a DI module (PersistenceModule), and a Prisma-based repository adapter.
  • Adds tests proving unique-constraint translation (P2002DuplicateOrder) 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 escaped can lead to asserting against undefined rather than the repository instance. After making escaped non-optional (e.g., via definite assignment), call escaped.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.

Comment thread examples/order-infrastructure/src/database.ts Outdated
Comment thread examples/order-infrastructure/src/prisma-order-repository.spec.ts Outdated
@btravers
btravers merged commit 466b845 into main Aug 11, 2026
13 checks passed
@btravers
btravers deleted the feat/examples-persistence branch August 11, 2026 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants