Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 125 additions & 139 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,156 +1,142 @@
# Prisma Composer

**Prisma Composer** is a TypeScript framework for building and deploying
applications composed of components. You build a **Prisma App** by composing
**Modules** with Prisma Composer. A component — a Module — owns its Services and
Resources and exposes typed **Inputs** and **Outputs**; you connect one Module's
Outputs to another's Inputs, and at runtime Prisma Composer injects the dependencies
that satisfy them. The whole app is itself a Module — the outermost one — and that
is what you deploy.

From that structure the framework derives your application's dependency graph and
uses Alchemy to provision and deploy it. Deployment targets like Prisma Cloud plug
in as extension packs, so you can deploy the same application to any of them.
**Prisma Composer** deploys multi-service TypeScript apps to Prisma Cloud
from a description you write in TypeScript. You declare each service and what
it depends on — other services, databases, schedules, secrets — compose them
into a **Prisma App**, and `prisma-composer deploy` provisions all of it:
Compute services, Prisma Postgres databases, the wiring between them. There
is no infrastructure configuration to write or maintain.

It earns its keep when an app is more than one piece. A lone server is easy
to deploy anywhere; the work starts when the storefront needs the catalog's
URL, the catalog needs its database's connection string, the cron job needs
credentials, and every one of those has to be repeated per environment.
Composer makes each of those edges a typed declaration and derives the rest.

Two rules shape everything:

- **Your code never reaches for its environment.** No `process.env`, no URLs.
Every dependency arrives typed, through one call — which is also why any
environment (production, a staging copy, a test) is just different injected
values.
- **The framework never bundles or transforms your code.** You build; the
deploy assembles what you built.

## What it looks like

A service declares its dependencies; app code receives them from
`service.load()`:

```ts
// service.ts — the declaration: pure data
export default compute({
name: 'storefront',
deps: { catalog: rpc(catalogContract) }, // "I call catalog's API"
build: nextjs({ module: import.meta.url, appDir: '..' }),
});

// app code — load() returns a client typed by that contract
const { catalog } = service.load();
const { products } = await catalog.listProducts({});
```

There's no infrastructure configuration to write or maintain.
The root module is the app — provision the pieces, wire the typed ports:

## Example App
```ts
export default module('store', ({ provision }) => {
const catalog = provision(catalogModule); // owns its own Postgres
const orders = provision(ordersModule, { deps: { catalog: catalog.rpc } });
provision(storefrontService, { deps: { catalog: catalog.rpc, orders: orders.rpc } });
});
```

Take an online store: a **storefront** the public browses, a **sales** service
that records orders, a **delivery** service that reacts when an order is placed,
**auth**, and a **Postgres** database the services share. In a Prisma App each of
those is a component, and the edges between them are connections you declare in
code.
That's [`examples/store`](examples/store/) — four components and their edges,
deployed with one command:

```mermaid
flowchart TB
User((User))

subgraph Storefront["Module: Storefront"]
SF["Next.js · Compute Service"]
subgraph Storefront["storefront"]
SF["Next.js"]
end
subgraph Sales["Module: Sales & Billing"]
SA["Sales API · Compute Service"]
INV[("Invoices · object storage")]
SA --- INV
subgraph Catalog["Module: catalog"]
CA["catalog API"]
CPG[("Postgres · typed by contract.prisma")]
CA --- CPG
end
subgraph Delivery["Module: Delivery"]
DE["Delivery · Compute Service"]
RD[("Updates · Redis")]
DE --- RD
subgraph Orders["Module: orders"]
OR["orders API"]
OPG[("Postgres · typed by contract.prisma")]
OR --- OPG
end
subgraph Auth["Module: Auth"]
AU["Auth API · Compute Service"]
subgraph Cron["cron (shared module)"]
CR["scheduler + promotions runner"]
end
PG[("Postgres — shared Resource")]

User -->|request/response · ingress| SF
SF -->|request/response| SA
SF -->|request/response| AU
SA -->|stream · order-placed| DE
SA -->|data · sales contract| PG
DE -->|data · delivery contract| PG
AU -->|data · auth contract| PG

User -->|HTTP| SF
SF -->|rpc · catalogContract| CA
SF -->|rpc · ordersContract| OR
OR -->|rpc · catalogContract| CA
CR -->|rpc · catalogContract| CA
```

Each box is a **Module**: a boundary that owns some code and data and is
reachable only through typed ports. catalog and orders each own their own
Postgres — a [Prisma Next](https://github.com/prisma/prisma-next)-typed one,
with migrations applied at deploy — and the root never sees them; the only
edges are the exposed, contract-typed RPC ports. Because nothing reaches
inside a boundary, every dependency in the app is an explicit,
compiler-checked edge.

## Getting started

```sh
pnpm add @prisma/composer @prisma/composer-prisma-cloud arktype
```

Start with **[Getting started](docs/guides/getting-started.md)** — an empty
directory to a deployed two-service app, plus how to port an app you already
have.

| Guide | Covers |
| --- | --- |
| [Getting started](docs/guides/getting-started.md) | Your first app end to end; porting an existing Node or Next.js app |
| [Building an app](docs/guides/building-an-app.md) | Contracts, databases (plain + Prisma Next-typed with migrations), reusable Modules, cron/storage/streams, config, secrets |
| [Testing](docs/guides/testing.md) | Unit tests with `mockService`, integration tests with `bootstrapService` |
| [Deploying and operating](docs/guides/deploying.md) | Stages, destroy, CI, how apps behave in production |

## Examples

Complete, deployable apps under [`examples/`](examples/):

| Example | Demonstrates |
| --- | --- |
| [pn-widgets](examples/pn-widgets/) | The minimal app: one service + one Prisma Next-typed Postgres |
| [storefront-auth](examples/storefront-auth/) | Next.js frontend + API service, a reusable Module owning its database, secrets |
| [store](examples/store/) | Four modules, typed databases with migrations, the shared cron module |
| [cron](examples/cron/) | Scheduled jobs: `defineSchedule` + `serveSchedule` + the cron module |
| [storage](examples/storage/) | The S3-backed blob store module |
| [streams](examples/streams/) | Durable append-only event streams over storage |

## For agents

A condensed version of the guides ships as an installable agent skill:

```sh
npx skills add prisma/composer --skill prisma-composer
```

## Modules

Each box above is a **Module**: a bounded context that owns some code and data and
is reachable only through a typed boundary. Because nothing reaches inside except
through that boundary, every dependency between Modules is an explicit edge in the
graph. A Module is scale-invariant — it composes recursively, and the whole
application is just the outermost Module — so the root needs no special construct.

A Module wraps two kinds of thing. **Services** are the units that run code — an
HTTP API, the Next.js storefront, a background worker. **Resources** are the
stateful things it depends on: Postgres is first-class, and anything else (object
storage, a cache, a queue) comes in as an Alchemy resource surfaced as a typed
capability.

You can see this in the diagram: every Module has a Compute Service, Sales also
owns a private invoices store, and Delivery a Redis instance for its live updates.
Those private Resources sit inside the boundary, so they never become edges between
Modules — only the shared Postgres, used by several Modules at once, does.

> A Module is a bounded context in the ports-and-adapters (hexagonal) sense — it
> reaches the world only through typed ports, which is exactly what its Inputs and
> Outputs are. The name is the one the neighbouring ecosystems already use for a
> composable, boundary-owning unit: a Nest or Angular module, a Terraform module.

## Connections

A Module declares **Inputs** for what it needs and **Outputs** for what it offers;
a connection joins one Module's Output to another's Input. There are two kinds.

**Communication** is one Module calling another — either **request/response** (the
storefront calls the sales API) or a **stream** (sales emits *order-placed*, and
delivery consumes it).

**Data** is a Module reading and writing a Postgres Resource. The data Input carries
a **contract** — a Prisma Next description of exactly the tables and columns the
Module may touch — and a Postgres satisfies the connection only if it meets that
contract. In the store, one Postgres is shared by sales, delivery, and auth, but
each owns a separate slice named by its own contract. The instance is shared; the
data boundaries are not, and each one stays a visible edge rather than a hidden
reach into another Module's tables.

## Topology

The wired-up graph of Modules, Resources, and connections is the **topology**.
The framework infers it from your TypeScript and emits it as a standalone artifact —
one you or an agent can query from the CLI without deploying anything.

## From code to a running app

Prisma Composer spans three planes and **lowers** your model down through them:

- **Authoring** — what you write: Modules, Services, Resources, connections.
- **Provisioning** — the framework turns the topology into an Alchemy resource
graph, and Alchemy's engine provisions it, run from your machine or your CD
pipeline.
- **Hosting** — what actually runs. On Prisma Cloud, each Service becomes Prisma
Compute, the shared Postgres becomes one managed database, and a stream becomes
a durable stream.

Prisma Cloud is one hosting target, shipped as an extension pack — the core knows
nothing about it, and a different target plugs in the same way. Because the target
supplies each Resource's implementation, that implementation is substitutable: the
same Module runs against managed Postgres in the cloud and a local one in
development, with no change to your code.

## Decisions

- **Prisma Composer owns composition and borrows everything beneath it.** The Module
model and the topology are its own; provisioning is Alchemy, data contracts are
Prisma Next, hosting is Prisma Cloud. It rebuilds none of them.
- **Thin core, fat targets.** The core is the Module / Input / Output model and the
lowering machinery, nothing more. Everything specific to a deployment target
lives in a swappable pack, and the core never branches on where you deploy.
- **Your code is the source of truth.** The topology is read out of your
TypeScript and type-checked, so it can't drift from a config you maintain on the
side.
- **No globals.** Application code never reads `process.env` or looks a service up
by name. Every resource a Module uses arrives as an injected, typed dependency.
- **Agent-first and realtime-first.** The topology is explicit, machine-readable,
and queryable, so agents and people reason about it directly. Streaming and
async are first-class from the start, not bolted onto request/response later.

## Status

The design lives in `docs/design/`; this page is the short version. Core,
extension packs, and the `prisma-composer` deploy CLI are implemented under
`packages/`, and the example apps under `examples/` deploy with
`prisma-composer deploy` / `prisma-composer destroy` (see
[`docs/design/10-domains/deploy-cli.md`](docs/design/10-domains/deploy-cli.md)).

- **Purpose and goals** — [`docs/design/00-purpose/`](docs/design/00-purpose/)
- **Principles** — [guiding](docs/design/01-principles/guiding-principles.md),
[architectural](docs/design/01-principles/architectural-principles.md)
- **Domain model** — [glossary](docs/design/03-domain-model/glossary.md),
[domain map](docs/design/03-domain-model/domain-map.md),
[layering](docs/design/03-domain-model/layering.md)
- **Worked example** — [`docs/design/02-example-app/`](docs/design/02-example-app/)
- **Inspirations** —
[`docs/design/04-inspirations/`](docs/design/04-inspirations/) (Alchemy, Convex,
and others)
See [`skills/`](skills/).

## Design & internals

For contributors — the design record, not required reading for building apps:

- **Purpose and principles** — [`docs/design/00-purpose/`](docs/design/00-purpose/),
[`docs/design/01-principles/`](docs/design/01-principles/)
- **Domain deep dives** — [`docs/design/10-domains/`](docs/design/10-domains/)
(core model, contracts, module composition, config, deploy CLI, testing)
- **Decisions** — [`docs/design/90-decisions/`](docs/design/90-decisions/) (the ADR index)
- **Reading order** — [`docs/design/README.md`](docs/design/README.md)
- **Platform footguns** — [`gotchas.md`](gotchas.md)
Loading
Loading