Skip to content

Commit 602cdec

Browse files
committed
Add package-owned workflow store schemas
1 parent 9a6f05d commit 602cdec

45 files changed

Lines changed: 4554 additions & 206 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/api/host-adapters.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ export const config = {
167167
}
168168
```
169169

170+
The Netlify adapter only wakes the runtime. Apply the store adapter's
171+
package-owned migration during deploy/setup; do not define Workflow's
172+
`workflow_*` tables in application schema files unless you are intentionally
173+
building typed admin access.
174+
170175
## Vercel
171176

172177
Install:
@@ -217,6 +222,10 @@ Configure Vercel Cron directly in `vercel.json`:
217222
}
218223
```
219224

225+
The Vercel adapter only wakes the runtime. Apply the store adapter's
226+
package-owned migration during deploy/setup; route code owns host config, while
227+
Workflow owns the runtime persistence schema.
228+
220229
## Response shape
221230

222231
Successful adapters return:

docs/api/store-adapters.md

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,12 @@ import { createDrizzlePostgresWorkflowStore } from '@tanstack/workflow-store-dri
4343

4444
const db = drizzle(new Pool({ connectionString: process.env.DATABASE_URL }))
4545
const store = createDrizzlePostgresWorkflowStore({ db })
46+
```
4647

47-
await store.ensureSchema()
48+
Apply the package-owned migration during setup/deploy:
49+
50+
```bash
51+
psql "$DATABASE_URL" -f node_modules/@tanstack/workflow-store-drizzle-postgres/migrations/0000_workflow_store.sql
4852
```
4953

5054
Use it in the runtime:
@@ -76,6 +80,74 @@ Options:
7680
| `schema` | Optional Postgres schema name. |
7781
| `tables` | Optional table name overrides. |
7882

83+
## Package-owned migrations
84+
85+
The Drizzle/Postgres package owns the durable Workflow schema. Apps should apply
86+
the SQL artifact from the installed package instead of mirroring `workflow_*`
87+
tables in application Drizzle schema files.
88+
89+
```bash
90+
psql "$DATABASE_URL" -f node_modules/@tanstack/workflow-store-drizzle-postgres/migrations/0000_workflow_store.sql
91+
```
92+
93+
Programmatic setup can use the exported migration helpers:
94+
95+
```ts
96+
import {
97+
getDrizzlePostgresWorkflowStoreMigrationSql,
98+
getDrizzlePostgresWorkflowStoreMigrations,
99+
} from '@tanstack/workflow-store-drizzle-postgres'
100+
```
101+
102+
Schema changes are versioned with `@tanstack/workflow-store-drizzle-postgres`.
103+
Apply new package migrations when upgrading the adapter. Runtime code assumes the
104+
database schema is compatible with the installed store adapter version.
105+
106+
The initial migration creates `workflow_schema_migrations` and records applied
107+
Workflow store migrations. Future migrations will be appended as new numbered SQL
108+
files in the package. Apply them in order during deploy/setup before running the
109+
new adapter version.
110+
111+
Maintainers changing the Drizzle/Postgres store schema should follow the
112+
package-local `SCHEMA_MIGRATIONS.md` checklist.
113+
114+
## Cloudflare D1
115+
116+
Install:
117+
118+
```bash
119+
pnpm add @tanstack/workflow-store-cloudflare-d1
120+
```
121+
122+
Create the store from a D1 binding:
123+
124+
```ts
125+
import { createCloudflareD1WorkflowStore } from '@tanstack/workflow-store-cloudflare-d1'
126+
127+
const store = createCloudflareD1WorkflowStore({
128+
db: env.WORKFLOW_DB,
129+
})
130+
```
131+
132+
Apply the package-owned D1 migration during setup/deploy:
133+
134+
```txt
135+
node_modules/@tanstack/workflow-store-cloudflare-d1/migrations/0000_workflow_store.sql
136+
```
137+
138+
Programmatic setup can use the exported migration helpers:
139+
140+
```ts
141+
import {
142+
getCloudflareD1WorkflowStoreMigrationSql,
143+
getCloudflareD1WorkflowStoreMigrations,
144+
} from '@tanstack/workflow-store-cloudflare-d1'
145+
```
146+
147+
D1 stores JSON payloads as text and uses SQLite integer timestamps. Its claim
148+
operations use atomic conditional updates and leases rather than Postgres
149+
`FOR UPDATE SKIP LOCKED`.
150+
79151
## `store.ensureSchema`
80152

81153
Creates the required tables and indexes if they do not exist.
@@ -84,17 +156,17 @@ Creates the required tables and indexes if they do not exist.
84156
await store.ensureSchema()
85157
```
86158

87-
Use this from an explicit app-owned bootstrap/admin script, not from every
88-
request or sweep. Runtime and host adapters assume the schema exists. In
89-
production, you may want to run equivalent SQL through your migration system
90-
instead of calling this at application startup.
159+
Use this from tests, local demos, or an explicit admin bootstrap script, not
160+
from every request or sweep. Runtime and host adapters assume the schema exists.
161+
Production deploys should prefer the package-owned SQL migration artifact.
91162

92163
## Default tables
93164

94165
`defaultDrizzlePostgresWorkflowStoreTables` contains:
95166

96167
| Table key | Default table |
97168
| --- | --- |
169+
| `schemaMigrations` | `workflow_schema_migrations` |
98170
| `runs` | `workflow_runs` |
99171
| `runStates` | `workflow_run_states` |
100172
| `eventLocks` | `workflow_event_locks` |
@@ -104,6 +176,23 @@ instead of calling this at application startup.
104176
| `schedules` | `workflow_schedules` |
105177
| `scheduleBuckets` | `workflow_schedule_buckets` |
106178

179+
## Optional Drizzle table definitions
180+
181+
The package exports Drizzle table definitions for apps that explicitly want
182+
typed read access for dashboards, diagnostics, or admin tooling:
183+
184+
```ts
185+
import {
186+
workflowEvents,
187+
workflowRuns,
188+
workflowSchedules,
189+
workflowSchemaMigrations,
190+
} from '@tanstack/workflow-store-drizzle-postgres'
191+
```
192+
193+
These exports are optional. Normal runtime use does not require adding Workflow
194+
tables to your app schema.
195+
107196
## In-memory store
108197

109198
For tests and demos:

docs/cookbook/index.md

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,27 +95,33 @@ export const workflowRuntime = defineWorkflowRuntime({
9595
})
9696
```
9797

98-
## Create workflow tables
98+
## Apply workflow store migrations
9999

100-
Use an app-owned bootstrap script for schema setup. Do not create tables from a
101-
host sweep handler.
100+
Workflow owns its durable store schema. Apply the package-owned SQL migration
101+
during setup/deploy instead of copying `workflow_*` tables into your app's
102+
Drizzle schema.
102103

103-
```ts
104-
// scripts/workflow-ensure-schema.ts
105-
import { workflowStore } from '../src/workflows/store.server'
106-
107-
await workflowStore.ensureSchema()
104+
```bash
105+
psql "$DATABASE_URL" -f node_modules/@tanstack/workflow-store-drizzle-postgres/migrations/0000_workflow_store.sql
108106
```
109107

108+
If your deploy system wants a package script:
109+
110110
```json
111111
{
112112
"scripts": {
113-
"workflow:ensure-schema": "tsx scripts/workflow-ensure-schema.ts"
113+
"workflow:migrate": "psql \"$DATABASE_URL\" -f node_modules/@tanstack/workflow-store-drizzle-postgres/migrations/0000_workflow_store.sql"
114114
}
115115
}
116116
```
117117

118-
Run this against the same `DATABASE_URL` your deployed functions use.
118+
Run this against the same `DATABASE_URL` your deployed functions use. Keep
119+
`store.ensureSchema()` for tests, local demos, and explicit admin bootstrap
120+
scripts.
121+
122+
The migration records itself in `workflow_schema_migrations`. Future Workflow
123+
store schema changes will ship as additional numbered SQL files in the adapter
124+
package.
119125

120126
## Start a run from HTTP
121127

docs/guide/deployment.md

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,29 +62,39 @@ not create tables during a cron tick, scheduled function, or worker alarm. That
6262
keeps production behavior explicit: migrations and bootstrap steps belong to the
6363
app deploy/admin process, not the background sweep path.
6464

65-
For the Drizzle/Postgres adapter, create an app-owned script:
65+
For the Drizzle/Postgres adapter, Workflow owns the `workflow_*` schema. Apply
66+
the package-owned migration against the same database your deployed functions
67+
use:
6668

67-
```ts
68-
// scripts/workflow-ensure-schema.ts
69-
import { workflowStore } from '../src/workflows/store.server'
69+
```bash
70+
psql "$DATABASE_URL" -f node_modules/@tanstack/workflow-store-drizzle-postgres/migrations/0000_workflow_store.sql
71+
```
7072

71-
await workflowStore.ensureSchema()
73+
For Cloudflare D1, use the D1-compatible package-owned migration artifact:
74+
75+
```txt
76+
node_modules/@tanstack/workflow-store-cloudflare-d1/migrations/0000_workflow_store.sql
7277
```
7378

74-
Wire it into your app scripts:
79+
Wire it into your app scripts if your deploy provider runs package scripts:
7580

7681
```json
7782
{
7883
"scripts": {
79-
"workflow:ensure-schema": "tsx scripts/workflow-ensure-schema.ts",
84+
"workflow:migrate": "psql \"$DATABASE_URL\" -f node_modules/@tanstack/workflow-store-drizzle-postgres/migrations/0000_workflow_store.sql",
8085
"workflow:sweep": "tsx scripts/workflow-sweep.ts"
8186
}
8287
}
8388
```
8489

85-
Run `pnpm workflow:ensure-schema` against the same `DATABASE_URL` your deployed
86-
functions use. In production, you can also apply equivalent SQL through your
87-
normal migration system.
90+
Do not copy Workflow's internal table declarations into your app Drizzle schema.
91+
Keep app code focused on workflow definitions and host entrypoints. Keep
92+
`store.ensureSchema()` for tests, local demos, and explicit admin bootstrap
93+
scripts.
94+
95+
The migration creates `workflow_schema_migrations` so future store schema changes
96+
can be tracked as package-owned numbered migrations. Apply new store migrations
97+
before rolling out a store adapter version that expects them.
8898

8999
## Cloudflare
90100

@@ -229,6 +239,8 @@ event arrays.
229239
"Run now" action to test the path before publishing.
230240
- Use a durable external store such as Postgres, Netlify Database, Neon, or
231241
another store adapter.
242+
- Apply the store adapter's package-owned migration during deploy/setup. The
243+
scheduled function should only own host config and the sweep handler.
232244
- Keep `maxDurationMs` below the function timeout.
233245

234246
## Vercel
@@ -288,6 +300,8 @@ The adapter validates that header when you pass `cronSecret`.
288300
sweeps, use a plan that supports the cadence or call the sweep through another
289301
scheduler.
290302
- Use a durable external store such as Postgres. Function memory is not a store.
303+
- Apply the store adapter's package-owned migration during deploy/setup. The
304+
route should only own host config and the sweep handler.
291305

292306
## Choosing a sweep cadence
293307

docs/guide/index.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ This guide walks through the production shape:
3131
| `@tanstack/workflow-core` | The replay engine, workflow builder, primitives, middleware, version routing, and low-level `RunStore`. |
3232
| `@tanstack/workflow-runtime` | The deployment-independent runtime, execution store contract, schedules, timers, leases, and sweep driver. |
3333
| `@tanstack/workflow-store-drizzle-postgres` | A Postgres implementation of the runtime execution store contract using Drizzle as the SQL surface. |
34-
| `@tanstack/workflow-cloudflare` | A Cloudflare Worker `scheduled()` handler and Wrangler cron config helper that call `runtime.sweep()`. |
34+
| `@tanstack/workflow-store-cloudflare-d1` | A Cloudflare D1 implementation of the runtime execution store contract. |
35+
| `@tanstack/workflow-cloudflare` | A Cloudflare Worker `scheduled()` handler that calls `runtime.sweep()`. |
3536
| `@tanstack/workflow-railway` | A Railway Cron Job command helper that calls `runtime.sweep()` and exits. |
36-
| `@tanstack/workflow-netlify` | A Netlify Scheduled Function handler and config helper that call `runtime.sweep()`. |
37-
| `@tanstack/workflow-vercel` | A Vercel route handler and cron config helper that call `runtime.sweep()`. |
37+
| `@tanstack/workflow-netlify` | A Netlify Scheduled Function handler that calls `runtime.sweep()`. |
38+
| `@tanstack/workflow-vercel` | A Vercel route handler that calls `runtime.sweep()`. |
3839

3940
The important boundary is this:
4041

@@ -172,8 +173,13 @@ import { createDrizzlePostgresWorkflowStore } from '@tanstack/workflow-store-dri
172173

173174
const db = drizzle(new Pool({ connectionString: process.env.DATABASE_URL }))
174175
const store = createDrizzlePostgresWorkflowStore({ db })
176+
```
177+
178+
Apply the package-owned Workflow store migration before runtime sweeps or
179+
requests hit that database:
175180

176-
await store.ensureSchema()
181+
```bash
182+
psql "$DATABASE_URL" -f node_modules/@tanstack/workflow-store-drizzle-postgres/migrations/0000_workflow_store.sql
177183
```
178184

179185
Then pass `store` to `defineWorkflowRuntime`. The runtime will use it for:
@@ -340,6 +346,8 @@ export const config = {
340346
```
341347

342348
The Scheduled Function only wakes the runtime. It does not store workflow state.
349+
Apply the package-owned store migration during deploy/setup; do not mirror
350+
Workflow's `workflow_*` tables in app schema files.
343351

344352
## Deploy on Vercel
345353

@@ -375,6 +383,9 @@ Configure Vercel Cron:
375383
```
376384

377385
The Vercel Cron Job wakes the route. The database decides what is actually due.
386+
Apply the package-owned store migration during deploy/setup; app code owns the
387+
workflow definitions and route config, while Workflow owns its persistence
388+
schema.
378389

379390
## Why this works on serverless hosts
380391

docs/guide/persistence.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,22 @@ const store = createDrizzlePostgresWorkflowStore({
165165
db,
166166
schema: 'public',
167167
})
168+
```
169+
170+
Apply the package-owned migration during setup/deploy:
168171

169-
await store.ensureSchema()
172+
```bash
173+
psql "$DATABASE_URL" -f node_modules/@tanstack/workflow-store-drizzle-postgres/migrations/0000_workflow_store.sql
170174
```
171175

172-
`ensureSchema()` is useful for local demos, tests, and explicit bootstrap
173-
scripts. Runtime sweeps and host adapters do not call it for you. A missing table
174-
during `runtime.sweep()` means the deployed database has not been migrated yet,
175-
not that the host adapter failed.
176+
Workflow owns the `workflow_*` table definitions, indexes, leases, timers,
177+
schedules, and compatibility expectations. Apps should not copy those tables
178+
into their own Drizzle schema for normal runtime use.
179+
180+
`ensureSchema()` is still useful for local demos, tests, and explicit admin
181+
bootstrap scripts. Runtime sweeps and host adapters do not call it for you. A
182+
missing table during `runtime.sweep()` means the deployed database has not been
183+
migrated yet, not that the host adapter failed.
176184

177185
You can override table names:
178186

@@ -189,6 +197,16 @@ const store = createDrizzlePostgresWorkflowStore({
189197
The default tables include runs, run states, event locks, events, timers, signal
190198
deliveries, schedules, and schedule buckets.
191199

200+
Future schema changes are versioned with
201+
`@tanstack/workflow-store-drizzle-postgres`. Apply new package migrations as part
202+
of upgrading the store package. The runtime assumes the database schema is
203+
compatible with the installed store adapter version.
204+
205+
The first migration also creates `workflow_schema_migrations` and records the
206+
applied migration ID. Future store migrations will be published as additional
207+
numbered SQL files and should be applied in order before the new adapter version
208+
handles production traffic.
209+
192210
## Retention
193211

194212
Terminal runs usually remain for some period so:

docs/installation.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,30 @@ pnpm add @tanstack/workflow-store-drizzle-postgres drizzle-orm pg
4444
import { createDrizzlePostgresWorkflowStore } from '@tanstack/workflow-store-drizzle-postgres'
4545

4646
const store = createDrizzlePostgresWorkflowStore({ db })
47-
await store.ensureSchema()
4847
```
4948

49+
Apply the package-owned store migration during setup/deploy:
50+
51+
```bash
52+
psql "$DATABASE_URL" -f node_modules/@tanstack/workflow-store-drizzle-postgres/migrations/0000_workflow_store.sql
53+
```
54+
55+
For Cloudflare D1:
56+
57+
```bash
58+
pnpm add @tanstack/workflow-store-cloudflare-d1
59+
```
60+
61+
```ts
62+
import { createCloudflareD1WorkflowStore } from '@tanstack/workflow-store-cloudflare-d1'
63+
64+
const store = createCloudflareD1WorkflowStore({ db: env.WORKFLOW_DB })
65+
```
66+
67+
Copy or reference the package-owned SQL artifact from
68+
`node_modules/@tanstack/workflow-store-cloudflare-d1/migrations/0000_workflow_store.sql`
69+
in your D1 migration flow.
70+
5071
## Server framework
5172

5273
Engine is framework-agnostic. Two entry points:

0 commit comments

Comments
 (0)