feat(examples): store demo app (Compose Coffee) + the deploy bugs it flushed out#71
Merged
Merged
Conversation
Compose Coffee: a Next.js storefront, a catalog module and an orders module, each module owning its own Postgres. Unlike storefront-auth (the minimal PoC), this example reads as a real app: - catalog exposes listProducts/getProduct and seeds a menu - orders takes catalog as a boundary input and prices each order via catalog.getProduct at placement time — a module→module edge - the storefront page renders the menu and places orders through a server action, via the two injected typed clients - scripts/dev.ts runs the storefront locally against in-memory fakes of both contracts, no cloud needed - DEMO.md is a 5-minute walkthrough script Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Demonstrates composing a framework-shipped module: promotions defines the
schedule (rotateSpecial: 30s) and the runner mapping that job id to
catalog.rotateSpecial(); the root provisions cron({ schedule, runner })
and wires catalog.rpc into its boundary like any other edge.
catalog grows getSpecial/rotateSpecial and a singleton special table;
the storefront marks the special with a ★ that moves every 30s.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ame assembles
assemble() passed entry: [serviceModule] to tsdown, which names the output
after the entry file basename; the subsequent /^service\.m?js$/ lookup then
only matched modules literally named service.*. App services are, but the
shared cron scheduler ships scheduler-service.mjs, so deploying any app
composing cron() failed with "tsdown produced no service.js". A named entry
({ service: serviceModule }) pins the output filename.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
catalog and orders each swap postgres() for pnPostgres(contract) (ADR-0022): the data schema is authored in contract.prisma, emitted to a typed contract, and applied by the deploy from migrations/ — the raw create-table-at-boot SQL is gone. load() now hands each server the typed Prisma Next client, replacing Bun.SQL strings and hand-written row mappers with contract-typed queries (db.orm.public.Product.where(...)). RPC contracts, storefront, cron and fakes are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…g dep
buildClient handed the runtime `pg: resilientPool(url)`, whose binding
sniff is `instanceof Pool` — broken whenever a bundle carries two copies
of pg. It did, since the package consolidation: the public dist builds
with skipNodeModulesBundle: false, and pg was a phantom dep (imported by
@internal/prisma-cloud, declared by nobody), so it was baked into the
public dist chunk while @prisma-next/postgres resolved a second copy at
app-bundle time. Every pnPostgres service then crash-looped at boot with
"Unable to determine pg binding type".
Two fixes: the explicit `binding: { kind: "pgPool", pool }` form skips
class-identity sniffing entirely, and declaring pg where it is imported
externalizes it from the public dist, deduplicating the bundle.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Prisma Postgres closes idle direct connections well under 30s (FT-5219), so resilientPool with idleTimeoutMillis: 30_000 kept dead sockets: the first query after an idle spell failed with "Connection terminated unexpectedly" — surfacing at query() time, past retryTransientConnect, as an RPC 500. Match the proven 5s idle window and log (rather than die on) the pool's async idle-client errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
lint-staged auto-staged examples/store/.prisma-compose/alchemy.run.ts — a deploy-generated file full of machine-local absolute paths, regenerated on every run (same class as the already-ignored .makerkit/). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
commit: |
…d starts Two entries from deploying examples/store: Prisma Postgres closing idle direct connections under a pool's idle timeout (the FT-5219 family, pooled-client variant — one 500 after every idle spell), and intermittent ECONNRESET on service-to-service HTTP while the target cold-starts from scale-to-zero. Linear tickets still to be filed (marked in the entries). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
wmadden
approved these changes
Jul 13, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
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.
What
A readable example Prisma App for demoing Compose —
examples/store, "Compose Coffee": a real Next.js storefront, a catalog module and an orders module (each owning a Prisma Next-typed Postgres), and the shared cron module rotating a ★ special of the day. Replaces storefront-auth as the walkthrough example (that one stays as the minimal PoC).module.tscomposes four components with four typed edges; orders consumes catalog through a module boundary input — an edge storefront-auth doesn't showload()— no SQL strings, no row mapperscron({ schedule, runner })demonstrates composing a framework-shipped shared modulemockServicefakes;pnpm devruns the storefront locally against in-memory fakes on loopback (no cloud)DEMO.mdis a 5-minute standup talk trackDeployed and verified end to end on Prisma Cloud (menu from PN-seeded Postgres, buy flow through both RPC hops, cron rotation observed live).
Framework fixes the example flushed out
Building and deploying it hit three real bugs, fixed here:
node/controlassemble broke on any service file not namedservice.ts— tsdown names output after the entry basename, and the post-build check looked forservice.mjs; the shared cron scheduler shipsscheduler-service.mjs, so any app composingcron()failed to deploy. Fixed with a named entry.pnPostgres()service crash-looped at boot since the package consolidation:pgwas a phantom dep (imported by@internal/prisma-cloud, declared by nobody), so withskipNodeModulesBundle: falseit got baked into the public dist while@prisma-next/postgresresolved a second copy at app-bundle time — and the PN runtime'sinstanceof Poolbinding sniff rejected the pool from the other copy. Fixed by passing the explicitbinding: { kind: "pgPool", pool }form and declaringpgwhere it's imported (deduplicating the bundle).resilientPoolkept idle clients for 30s but Prisma Postgres closes idle direct connections much sooner (FT-5219), and the failure surfaces atquery()time where the connect-retry can't help. Idle timeout dropped to 5s; the pool's async idle-client errors are logged instead of becoming unhandled'error'events.Also gitignores the deploy-generated
.prisma-compose/stack files (machine-local absolute paths, regenerated every run — same class as.makerkit/).Verification
turbo build / typecheck / testgreen across the example tree and touched framework packages; biome + dependency-cruiser cleanplaceOrderround trip renders on the page, ★ special rotates on the 30s schedule, first-hit-after-idle renders clean post-fix🤖 Generated with Claude Code