Skip to content

feat(examples): store demo app (Compose Coffee) + the deploy bugs it flushed out#71

Merged
wmadden-electric merged 9 commits into
mainfrom
claude/prisma-compose-demo-app-07178e
Jul 13, 2026
Merged

feat(examples): store demo app (Compose Coffee) + the deploy bugs it flushed out#71
wmadden-electric merged 9 commits into
mainfrom
claude/prisma-compose-demo-app-07178e

Conversation

@wmadden-electric

Copy link
Copy Markdown
Contributor

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.ts composes four components with four typed edges; orders consumes catalog through a module boundary input — an edge storefront-auth doesn't show
  • data layer is Prisma Next (ADR-0022): PSL contracts, emitted artifacts, deploy-applied migrations, typed clients from load() — no SQL strings, no row mappers
  • cron({ schedule, runner }) demonstrates composing a framework-shipped shared module
  • unit test via mockService fakes; pnpm dev runs the storefront locally against in-memory fakes on loopback (no cloud)
  • DEMO.md is a 5-minute standup talk track

Deployed 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:

  1. node/control assemble broke on any service file not named service.ts — tsdown names output after the entry basename, and the post-build check looked for service.mjs; the shared cron scheduler ships scheduler-service.mjs, so any app composing cron() failed to deploy. Fixed with a named entry.
  2. Every pnPostgres() service crash-looped at boot since the package consolidation: pg was a phantom dep (imported by @internal/prisma-cloud, declared by nobody), so with skipNodeModulesBundle: false it got baked into the public dist while @prisma-next/postgres resolved a second copy at app-bundle time — and the PN runtime's instanceof Pool binding sniff rejected the pool from the other copy. Fixed by passing the explicit binding: { kind: "pgPool", pool } form and declaring pg where it's imported (deduplicating the bundle).
  3. First query after an idle spell failed with "Connection terminated unexpectedly": resilientPool kept idle clients for 30s but Prisma Postgres closes idle direct connections much sooner (FT-5219), and the failure surfaces at query() 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 / test green across the example tree and touched framework packages; biome + dependency-cruiser clean
  • live deploy verified: seeded menu served over RPC, placeOrder round trip renders on the page, ★ special rotates on the 30s schedule, first-hit-after-idle renders clean post-fix

🤖 Generated with Claude Code

wmadden-electric and others added 7 commits July 13, 2026 13:49
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>
@pkg-pr-new

pkg-pr-new Bot commented Jul 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@prisma/compose@71
npm i https://pkg.pr.new/@prisma/compose-prisma-cloud@71

commit: 496f60f

…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>
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-electric wmadden-electric merged commit 3c2100a into main Jul 13, 2026
14 of 15 checks passed
@wmadden-electric wmadden-electric deleted the claude/prisma-compose-demo-app-07178e branch July 13, 2026 16:46
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