Prisma ORM 8 is here.Read the docs

Building blocks

Compose the ready-made cron, storage, and streams Modules instead of building scheduled jobs, blob storage, or event streams yourself.

Building blocks are Modules you compose instead of implementing a capability yourself. Each owns its internals and hands you a typed port, so adding one is a couple of lines. The first-party set ships inside @prisma/composer-prisma-cloud. It is small, and growing:

ImportWhat you getExposes
cron from @prisma/composer-prisma-cloud/cronA scheduler that fires your jobs at your service on an intervalnothing
storage from @prisma/composer-prisma-cloud/storageAn S3-backed blob store, credentials includedstore
streams from @prisma/composer-prisma-cloud/streamsDurable append-only event streams, backed by a storestreams

Before implementing a capability by hand, check whether a block already owns it. Wiring one in takes a few lines, while an integration you write yourself is code you maintain.

Cron

Cron is the block most apps want first. You supply two things, a schedule and a runner service that exposes the trigger contract, and the Module does the rest.

The schedule is the single source of truth for job ids and intervals:

src/promotions/service.ts
import { defineSchedule, triggerContract } from '@prisma/composer-prisma-cloud/cron';

export const schedule = defineSchedule({ rotateSpecial: '30s' });

export default compute({
  name: 'promotions',
  deps: { catalog: rpc(catalogContract) },
  build: node({ module: import.meta.url, entry: '../dist/server.mjs' }),
  expose: { trigger: triggerContract },
});

In the server, map each job id to work. serveSchedule checks the map against the schedule, so an unhandled job does not compile:

src/promotions/server.ts
import { serveSchedule } from '@prisma/composer-prisma-cloud/cron';

const handler = serveSchedule(service, schedule, {
  rotateSpecial: (deps) => deps.catalog.rotateSpecial({}),
});

In the module, the cron Module's boundary deps mirror the runner's own, so the root wires it like any other edge:

module.ts
provision(cron({ schedule, runner: promotionsService }), {
  deps: { catalog: catalog.rpc },
});

A runner that declares an input schema (see Service input) takes its binding on cron() itself, with envSecret(...) where the schema expects a secret. It is required exactly when the runner declares a schema, the same rule provision() applies:

module.ts
provision(
  cron({
    schedule,
    runner: ingestService,
    input: { token: envSecret('INGEST_TOKEN') },
  }),
  { deps: { catalog: catalog.rpc } },
);

The scheduler is the one service in your app that never sleeps. Compute scales an idle service to zero, and the scheduler receives no requests of its own, so it holds the platform's keep-awake guard for its whole lifetime. One warm instance per app is the cost of the clock; the runner sleeps like any other service and wakes when the scheduler calls it.

Storage and streams

The storage Module provisions an S3-backed blob store, with its own Postgres and minted credentials, and exposes a store port. The streams Module builds durable append-only event streams on top of a store. For working versions, including the streams Module's secret binding, see examples/storage and examples/streams in the Composer repository.

If you want the raw S3 surface without the Module, provision a bucket resource instead; the two share a contract kind, so a service wired to s3() can be rewired to a bucket without changing its declaration.

Where new blocks come from

An extension is a package that brings its own Modules, resources, or deploy target, using the same mechanism @prisma/composer-prisma-cloud itself uses. The convention is an npm package named prisma-composer-*, which is how you and your agent find one. An extension is installed like any dependency and enters the deploy through the extensions array in prisma-composer.config.ts.

The ecosystem is new: today the three Modules above plus the ones you write are the whole set. Treat prisma-composer-* as the place to look, and check that a package actually exists on npm before depending on it.

Next steps

On this page