@tailor-platform/sdk 2.35.0 → 2.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +108 -0
- package/dist/{application-Bj2MtL-d.mjs → application-83WVdH85.mjs} +1 -1
- package/dist/{application-DIsQSnj3.mjs → application-Cida-j1g.mjs} +36 -36
- package/dist/application-Cida-j1g.mjs.map +1 -0
- package/dist/cli/commands/deploy/deploy.d.mts +6 -1
- package/dist/cli/commands/deploy/tailordb/migration-validation.d.mts +1 -0
- package/dist/cli/commands/executor/jobs.d.mts +3 -0
- package/dist/cli/commands/tailordb/migrate/bundler.d.mts +3 -0
- package/dist/cli/commands/tailordb/migrate/config.d.mts +4 -0
- package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +10 -0
- package/dist/cli/commands/tailordb/migrate/generate.d.mts +28 -2
- package/dist/cli/commands/tailordb/migrate/hints.d.mts +1 -0
- package/dist/cli/commands/tailordb/migrate/remote-state.d.mts +3 -0
- package/dist/cli/commands/tailordb/migrate/schema-checks.d.mts +3 -1
- package/dist/cli/commands/tailordb/migrate/snapshot-comparison.d.mts +4 -0
- package/dist/cli/commands/tailordb/migrate/snapshot-files.d.mts +44 -6
- package/dist/cli/commands/tailordb/migrate/snapshot-local.d.mts +2 -0
- package/dist/cli/commands/tailordb/migrate/snapshot-migrations.d.mts +2 -0
- package/dist/cli/commands/tailordb/migrate/snapshot-types.d.mts +8 -0
- package/dist/cli/commands/tailordb/migrate/types.d.mts +2 -0
- package/dist/cli/commands/workspace/create.d.mts +5 -3
- package/dist/cli/lib.d.mts +2 -2
- package/dist/cli/lib.mjs +1 -1
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/main.mjs +52 -51
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/shared/client.d.mts +26 -0
- package/dist/cli/shared/function-execution.d.mts +2 -0
- package/dist/cli/shared/prompt.d.mts +9 -4
- package/dist/completion/zsh-worker.zsh +21 -13
- package/dist/configure/config/index.d.mts +2 -0
- package/dist/configure/config/types.d.mts +2 -0
- package/dist/configure/index.mjs.map +1 -1
- package/dist/configure/services/executor/trigger/webhook.d.mts +1 -0
- package/dist/configure/services/tailordb/schema.d.mts +7 -2
- package/dist/configure/services/tailordb/types.d.mts +3 -0
- package/dist/crashreport-C2AcQNTe.mjs +1 -0
- package/dist/{crashreport-DGU6lwiX.mjs → crashreport-D_wf4GZh.mjs} +4 -4
- package/dist/{crashreport-DGU6lwiX.mjs.map → crashreport-D_wf4GZh.mjs.map} +1 -1
- package/dist/date-representation-Cr7W7Pn2.mjs.map +1 -1
- package/dist/{errors-BXmGBX-7.mjs → errors-Dmdnq0zi.mjs} +2 -2
- package/dist/errors-Dmdnq0zi.mjs.map +1 -0
- package/dist/{kysely-type-B0ri1-Xs.mjs → kysely-type-DVL90_oX.mjs} +6 -6
- package/dist/kysely-type-DVL90_oX.mjs.map +1 -0
- package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
- package/dist/plugin/builtin/seed/seed-type-processor.d.mts +4 -3
- package/dist/plugin/get-generated-table.d.mts +4 -0
- package/dist/plugin/index.mjs.map +1 -1
- package/dist/plugin/types.d.mts +55 -3
- package/dist/plugin/with-context.d.mts +16 -0
- package/dist/register-ts-hook-Ht4zCseV.mjs +1115 -0
- package/dist/register-ts-hook-Ht4zCseV.mjs.map +1 -0
- package/dist/schema-BU1aIQ63.mjs.map +1 -1
- package/dist/{secret-file-BxZ-J_Nz.mjs → secret-file-DiuEcLFP.mjs} +2 -2
- package/dist/{secret-file-BxZ-J_Nz.mjs.map → secret-file-DiuEcLFP.mjs.map} +1 -1
- package/dist/service-CZ0EdD2-.mjs +10 -0
- package/dist/service-CZ0EdD2-.mjs.map +1 -0
- package/dist/service-DDrbdyd-.mjs +2 -0
- package/dist/{service-CaGmqg0r.mjs.map → service-DDrbdyd-.mjs.map} +1 -1
- package/dist/service-WhSyhaho.mjs +1 -0
- package/dist/service_pb-BMHhVot2.mjs +1 -0
- package/dist/service_pb-BxMSK0gg.mjs +2 -0
- package/dist/service_pb-BxMSK0gg.mjs.map +1 -0
- package/dist/tailor-proto/src/tailor/v1/function_pb.d.mts +3 -3
- package/dist/tailor-proto/src/tailor/v1/function_resource_pb.d.mts +92 -0
- package/dist/tailor-proto/src/tailor/v1/notification_pb.d.mts +176 -0
- package/dist/tailor-proto/src/tailor/v1/notification_resource_pb.d.mts +172 -0
- package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +78 -0
- package/dist/types/app-config.generated.d.mts +1 -1
- package/dist/types/auth-connection.generated.d.mts +2 -2
- package/dist/types/auth.generated.d.mts +13 -13
- package/dist/types/executor.generated.d.mts +25 -25
- package/dist/types/field.generated.d.mts +2 -2
- package/dist/types/http-adapter.generated.d.mts +4 -4
- package/dist/types/idp.generated.d.mts +272 -272
- package/dist/types/resolver.generated.d.mts +10 -10
- package/dist/types/tailordb.generated.d.mts +84 -80
- package/dist/types/workflow.generated.d.mts +2 -2
- package/dist/vitest/date-representation.d.mts +2 -0
- package/dist/vitest/environment.d.mts +1 -0
- package/dist/vitest/environment.mjs.map +1 -1
- package/dist/vitest/index.mjs +1 -1
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/migration-steps.d.mts +13 -2
- package/dist/vitest/mock-kysely.d.mts +12 -2
- package/dist/vitest/mocks/aigateway.d.mts +7 -1
- package/dist/vitest/mocks/authconnection.d.mts +7 -1
- package/dist/vitest/mocks/file.d.mts +7 -1
- package/dist/vitest/mocks/iconv.d.mts +7 -1
- package/dist/vitest/mocks/idp.d.mts +7 -1
- package/dist/vitest/mocks/logger.d.mts +7 -1
- package/dist/vitest/mocks/secretmanager.d.mts +7 -1
- package/dist/vitest/mocks/tailordb-pglite.d.mts +12 -2
- package/dist/vitest/mocks/tailordb.d.mts +22 -4
- package/dist/vitest/mocks/workflow.d.mts +2 -1
- package/dist/vitest/pglite-kysely.d.mts +23 -2
- package/dist/vitest/pglite-temporal.d.mts +5 -1
- package/dist/vitest/workflow-local.d.mts +7 -0
- package/dist/{workspace_resource_pb-C4CkoYww.mjs → workspace_resource_pb-7smKERdZ.mjs} +2 -2
- package/dist/workspace_resource_pb-7smKERdZ.mjs.map +1 -0
- package/docs/cli/application.md +52 -22
- package/docs/cli/executor.md +2 -0
- package/docs/cli/function.md +3 -1
- package/docs/cli/tailordb.md +31 -23
- package/docs/cli/user.md +1 -1
- package/docs/cli/workflow.md +2 -0
- package/docs/cli/workspace.md +20 -18
- package/docs/cli-reference.md +10 -7
- package/docs/configuration.md +2 -2
- package/docs/data-clone.md +1 -1
- package/docs/github-actions.md +104 -15
- package/docs/plugin/custom.md +4 -4
- package/docs/runtime.md +1 -1
- package/docs/services/auth.md +2 -0
- package/docs/services/executor.md +12 -5
- package/docs/services/idp.md +2 -0
- package/docs/services/tailordb/index.md +12 -3
- package/docs/services/tailordb/migration.md +90 -38
- package/docs/services/workflow.md +1 -1
- package/docs/testing.md +9 -9
- package/package.json +22 -22
- package/dist/application-DIsQSnj3.mjs.map +0 -1
- package/dist/crashreport-WsYAWARW.mjs +0 -1
- package/dist/errors-BXmGBX-7.mjs.map +0 -1
- package/dist/kysely-type-B0ri1-Xs.mjs.map +0 -1
- package/dist/register-ts-hook-CQ7shKO5.mjs +0 -1055
- package/dist/register-ts-hook-CQ7shKO5.mjs.map +0 -1
- package/dist/service-CaGmqg0r.mjs +0 -2
- package/dist/service-DEq_hdht.mjs +0 -1
- package/dist/service-eeXdVrDK.mjs +0 -8
- package/dist/service-eeXdVrDK.mjs.map +0 -1
- package/dist/service_pb-6EkMfdOb.mjs +0 -2
- package/dist/service_pb-6EkMfdOb.mjs.map +0 -1
- package/dist/service_pb-CsJKokDl.mjs +0 -1
- package/dist/workspace_resource_pb-C4CkoYww.mjs.map +0 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
description: "Generate, test, and apply TailorDB schema migrations with optional data migration scripts, and recover from failed migrations."
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
# TailorDB Migrations
|
|
5
|
+
# TailorDB Migrations (Beta)
|
|
6
6
|
|
|
7
7
|
> **Beta:** The `tailordb migration` command and the migration runtime are beta features. They may introduce breaking changes in future releases. The CLI emits a beta warning on every invocation.
|
|
8
8
|
|
|
@@ -58,14 +58,21 @@ If you already have a deployed workspace whose schema matches your local table d
|
|
|
58
58
|
|
|
59
59
|
1. Add the `migration` block to `tailor.config.ts`.
|
|
60
60
|
2. Run `tailor tailordb migration generate` to create `0000/schema.json` from current local tables.
|
|
61
|
-
3. Run `tailor deploy`.
|
|
61
|
+
3. Run `tailor deploy`. It first checks that the deployed tables match `0000`. Because they do, no script runs; only the migration label is set.
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
Until a deploy sets the migration label, `tailor deploy` and `tailor tailordb migration validate` compare the deployed tables with `0000`. When they differ, `tailor deploy` stops with [remote schema drift](#remote-schema-drift-detected) and `migration validate` fails, for example when a table was edited in the console, or when a local change had not been deployed before you generated `0000`. Run `tailor tailordb migration validate` before the first deploy to check, and reconcile the difference before deploying:
|
|
64
|
+
|
|
65
|
+
- To keep the deployed tables, update your local tables to match them, then recreate `0000` with `tailor tailordb migration generate --init`. `--init` deletes every migration of the namespace, so do this only while no workspace has been deployed with these migrations.
|
|
66
|
+
- To replace the deployed tables with `0000`, run `tailor tailordb migration sync 0000`. Tables and fields that `0000` does not have are deleted together with their data.
|
|
67
|
+
|
|
68
|
+
If an earlier deploy stopped after creating only some of the tables, the next deploy reports the missing tables as drift; `tailor tailordb migration sync 0000` creates them.
|
|
64
69
|
|
|
65
70
|
### Resetting
|
|
66
71
|
|
|
67
72
|
`tailor tailordb migration generate --init` deletes the existing `migrations/` directory of every namespace with migrations configured and creates `0000` from the current local tables. Add `--namespace <name>` to reset only that namespace and leave the others untouched. Use it only before the project is deployed. For a deployed migration history, use [`migration rebaseline`](#re-baselining-a-deployed-migration-history), which verifies the history and connected workspace before replacing any files.
|
|
68
73
|
|
|
74
|
+
Declining the `--init` confirmation leaves existing migrations in place and fails with `MIGRATION_GENERATE_CANCELLED`.
|
|
75
|
+
|
|
69
76
|
## Migration Workflow
|
|
70
77
|
|
|
71
78
|
A typical change cycle:
|
|
@@ -122,6 +129,10 @@ A typical change cycle:
|
|
|
122
129
|
```
|
|
123
130
|
The pre-migration phase relaxes the new field to optional, the script runs and populates values, then the post-migration phase enforces `required: true`.
|
|
124
131
|
|
|
132
|
+
When you decline a namespace's breaking-change confirmation, `migration generate` skips that namespace, continues generating migrations for the others, and exits successfully with a `Skipped: <namespaces>` summary. Declining every namespace also succeeds without writing migrations. Check the summary before deploying if you intended to generate every namespace.
|
|
133
|
+
|
|
134
|
+
JSON output includes `skippedNamespaces` alongside `migrations`. It is always an empty array in actual `--json` runs because JSON mode does not prompt. Programmatic callers of `migrateGenerate()` receive a `MigrateGenerateResult` and can inspect `skippedNamespaces` before continuing.
|
|
135
|
+
|
|
125
136
|
### Warnings and optional migration scripts
|
|
126
137
|
|
|
127
138
|
Some non-breaking changes can still cause data loss — most notably removing a field (`field_removed`), removing a table (`table_removed`), or removing a member inside a nested field (reported on the nested field's `field_modified` change). `migration generate` reports these as **warnings**:
|
|
@@ -181,6 +192,8 @@ The generated script includes a `TODO` for each nested constraint change. Replac
|
|
|
181
192
|
|
|
182
193
|
Changing a nested member's type, array-ness, decimal scale, or foreign key target is unsupported in a single migration. Use separate migrations to add a member with the target shape, copy or transform values in a custom script, and then remove the old member.
|
|
183
194
|
|
|
195
|
+
Adding a relation to an existing nested member is non-breaking, and its existing values are not checked. Fix values that point at no record before you deploy the migration that adds it: afterwards, updating a row that still holds one fails, even when the update changes only other values.
|
|
196
|
+
|
|
184
197
|
#### Renaming a member inside a nested field
|
|
185
198
|
|
|
186
199
|
Members inside a **nested field** (`db.object(...)`) are detected the same way: when `migration generate` finds a member removed from a nested field and a compatible member added under the same parent, it asks whether the member was renamed. Two members qualify only when copying the value preserves it exactly, because a rename copies existing values without transforming them: the type, array-ness, requiredness, foreign key target, decimal scale, hooks, and validations must match (index, unique, and vector may differ, as for a top-level rename), enum values may be added but not removed, an object-typed member must keep the same members recursively, and serial members never qualify.
|
|
@@ -224,9 +237,9 @@ tailor tailordb migration generate --rename "User:Person"
|
|
|
224
237
|
tailor tailordb migration generate --drop "User"
|
|
225
238
|
```
|
|
226
239
|
|
|
227
|
-
Two tables qualify as a rename pair only when copying every row preserves the data: every field must keep its name, type, array-ness, required/unique constraints, foreign key target, and decimal scale; enum fields may gain values but not lose them; indexes must match. A self-referential foreign key is compared against the new table name and must be optional. Tables with serial fields (their values cannot be written by a script) or file fields (file contents are not copied) are never candidates. Name-derived and data-independent settings — `pluralForm`, description, table settings, permissions, hooks, and validations — may differ.
|
|
240
|
+
Two tables qualify as a rename pair only when copying every row preserves the data: every field must keep its name, type, array-ness, required/unique constraints, foreign key target, and decimal scale; enum fields may gain values but not lose them; indexes must match. A self-referential foreign key, including one on a field inside a nested field, is compared against the new table name and must be optional. A table is also not a candidate when a self-referential foreign key points at another column of the table that is itself a self-referential foreign key, or when a nested field holding a self-referential foreign key is covered by a unique constraint. Tables with serial fields (their values cannot be written by a script) or file fields (file contents are not copied) are never candidates. Name-derived and data-independent settings — `pluralForm`, description, table settings, permissions, hooks, and validations — may differ.
|
|
228
241
|
|
|
229
|
-
A confirmed rename is recorded as a single `table_renamed` change and treated as **breaking** for two reasons: existing records must be copied by the migration script, and the table's GraphQL API names (derived from the table name and `pluralForm`) change, which breaks API clients. The generated `migrate.ts` copies every row from the old table into the new one in id-ordered batches, preserving ids so stored foreign key references stay valid, and the generated `db.ts` exposes both the old table (readable) and the new table (writable). Self-referential foreign keys are inserted as null and backfilled after every row exists, so a reference to a row in a later batch cannot fail the copy.
|
|
242
|
+
A confirmed rename is recorded as a single `table_renamed` change and treated as **breaking** for two reasons: existing records must be copied by the migration script, and the table's GraphQL API names (derived from the table name and `pluralForm`) change, which breaks API clients. The generated `migrate.ts` copies every row from the old table into the new one in id-ordered batches, preserving ids so stored foreign key references stay valid, and the generated `db.ts` exposes both the old table (readable) and the new table (writable). Self-referential foreign keys, including those on fields inside nested fields, are inserted as null and backfilled after every row exists, so a reference to a row in a later batch cannot fail the copy.
|
|
230
243
|
|
|
231
244
|
Two caveats apply to the copy. The old table is not write-protected: rows written to it after the script's transaction commits — and before post-migration cleanup drops it — are not carried over, so pause writers to the renamed table for the duration of the deploy. And platform-managed record metadata (creation/update timestamps and actors) cannot be written by the script, so the new table's records carry the migration run's metadata instead of the original values.
|
|
232
245
|
|
|
@@ -269,7 +282,8 @@ export default defineConfig({
|
|
|
269
282
|
files: ["./tailordb/*.ts"],
|
|
270
283
|
migration: {
|
|
271
284
|
directory: "./migrations",
|
|
272
|
-
// Optional. Defaults to the first machine user in auth.machineUsers
|
|
285
|
+
// Optional. Defaults to the first machine user in auth.machineUsers,
|
|
286
|
+
// and must name one of them.
|
|
273
287
|
machineUser: "admin-machine-user",
|
|
274
288
|
},
|
|
275
289
|
},
|
|
@@ -277,10 +291,10 @@ export default defineConfig({
|
|
|
277
291
|
});
|
|
278
292
|
```
|
|
279
293
|
|
|
280
|
-
| Option | Type | Description
|
|
281
|
-
| ----------------------- | ------ |
|
|
282
|
-
| `migration.directory` | string | Directory path for migration files. Required when migrations are enabled for the namespace.
|
|
283
|
-
| `migration.machineUser` | string | Machine user used to run migration scripts. Optional; defaults to the first entry
|
|
294
|
+
| Option | Type | Description |
|
|
295
|
+
| ----------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
296
|
+
| `migration.directory` | string | Directory path for migration files. Required when migrations are enabled for the namespace. Each namespace needs its own directory. |
|
|
297
|
+
| `migration.machineUser` | string | Machine user used to run migration scripts. Must be defined in `auth.machineUsers`. Optional; defaults to the first entry there. |
|
|
284
298
|
|
|
285
299
|
## Generated Files
|
|
286
300
|
|
|
@@ -419,7 +433,7 @@ export const steps = {
|
|
|
419
433
|
```
|
|
420
434
|
|
|
421
435
|
- A script exports either `main` or `steps`. If it exports both, `main` runs as before and `steps` is ignored with a warning from `tailor deploy` and `tailor tailordb migration validate`.
|
|
422
|
-
- Generated scripts export `steps`. Each schema change that needs a data migration gets its own step, named for what it does: `populate<Table><Field>` for an added required field (for example `populateUserEmail`), `rename<Table><Field>` for a renamed field, `copy<Old>To<New>` for a renamed table, `update<Table><Field>` for a changed field, `resolve<Table><Index>` for a unique index, and `convert<Table><Field>` for a field type conversion. When that name is not a valid step name (for example because it is longer than 64 characters), or when another step or a helper in the script already uses it, the step is named `change<N>` instead (`expand<N>` for a conversion through a temporary field), where N is the change's position in the migration; the fallback does not mean the schema-derived name was wrong. A step lists in `dependsOn` every earlier step that touches the same field (a foreign key that changes its target also touches `id`
|
|
436
|
+
- Generated scripts export `steps`. Each schema change that needs a data migration gets its own step, named for what it does: `populate<Table><Field>` for an added required field (for example `populateUserEmail`), `rename<Table><Field>` for a renamed field, `copy<Old>To<New>` for a renamed table, `update<Table><Field>` for a changed field, `resolve<Table><Index>` for a unique index, and `convert<Table><Field>` for a field type conversion. When that name is not a valid step name (for example because it is longer than 64 characters), or when another step or a helper in the script already uses it, the step is named `change<N>` instead (`expand<N>` for a conversion through a temporary field), where N is the change's position in the migration; the fallback does not mean the schema-derived name was wrong. A step lists in `dependsOn` every earlier step that touches the same field (a foreign key that is added or changes its target also touches the key it points to, `id` unless the relation sets another one), so steps that touch the same field run in the order the changes are listed and changes to different fields do not wait for each other. The copy of a renamed table's rows runs after the copies of the renamed tables its foreign keys point to, so the rows they reference already exist. It skips the rows whose ids the new table already has, so running it again does not insert them twice. A step that rewrites values that already exist (the duplicate resolution of a unique index or field, a field change, a conversion through a temporary field) has a comment above its function saying so: once it commits it cannot be undone, so check the values it writes before you deploy. Where the scaffold cannot pick a value for you (what an added required field holds in existing records, the new value of duplicates, the value that replaces a removed enum value, the new reference of a retargeted foreign key or of a value that points at no record when a field gains a relation, the copy of renamed tables that reference each other), it calls `TODO(message)`, which `./db` exports. The migration fails at that call, and `tailordb migration validate` and `tailor deploy` reject a `migrate.ts` that still calls it before anything is changed. Replace each call with the value, then remove the `TODO` import. A `--data-only` migration, and a migration with no data statements to scaffold, get a single `migrate` step; split it into several steps where the work divides.
|
|
423
437
|
- Each step is an object with a `run` function — it receives its own transaction and the same `MigrationContext` as `main` — and an optional `dependsOn` list naming the steps that must complete before it starts.
|
|
424
438
|
- Steps with no dependency between them have no guaranteed order. Declaration order is not execution order, and a future SDK version may run independent steps concurrently, so declare `dependsOn` for every ordering your steps rely on.
|
|
425
439
|
- Declare `steps` directly as `export const steps = { ... }`, with each step written inside it as an object literal with a literal name and `dependsOn` list. Step names start with a letter and contain only letters, digits, and underscores, up to 64 characters. `tailor tailordb migration validate` and `tailor deploy` reject unknown dependencies and cycles before anything is changed.
|
|
@@ -437,7 +451,7 @@ Because steps commit separately, a failure does not undo the steps that already
|
|
|
437
451
|
- If the steps changed since the earlier run, or that run is no longer available, every step runs again from the beginning. See [Editing steps of a migration in progress](#editing-steps-of-a-migration-in-progress).
|
|
438
452
|
- If every step completed but the post-migration schema changes fail (for example, a field the migration makes required still has empty values), the migration also stays in progress with its pre-migration schema, and the next deploy retries those schema changes without running the steps again.
|
|
439
453
|
- To run a completed step again (because it had a bug, or to fix rows the post-migration schema changes reject), change the steps: rename that step and update the `dependsOn` lists that name it, or add a step that fixes the rows. Every step then runs again from the beginning, so this relies on every step being safe to run again; to keep the other completed steps from running again, skip them as described below.
|
|
440
|
-
-
|
|
454
|
+
- Finish a multi-step migration left in progress with `tailor deploy`, or settle it with `migration resolve`. Until then, the commands listed in [Settling a migration that stays in progress](#settling-a-migration-that-stays-in-progress) refuse to run.
|
|
441
455
|
|
|
442
456
|
#### Editing steps of a migration in progress
|
|
443
457
|
|
|
@@ -483,6 +497,8 @@ export const steps = {
|
|
|
483
497
|
|
|
484
498
|
### Settling a migration that stays in progress
|
|
485
499
|
|
|
500
|
+
While a migration is in progress, `migration set`, `migration sync`, and `migration rebaseline` refuse to run, and `migration test` refuses to use that workspace as its source. `tailor tailordb migration status` reports the migration as in progress and lists the commands that get it out of that state: `tailor deploy` to resume a multi-step migration, and `tailor tailordb migration resolve` to roll it back or complete it. For a migration whose script run a deploy could not confirm, it lists only `resolve`, because a deploy does not resume that migration.
|
|
501
|
+
|
|
486
502
|
A deploy resumes a multi-step migration left in progress. When you do not want it to — you abandoned the migration, or you repaired the data by hand — settle the migration with `tailor tailordb migration resolve`. Pass exactly one of two options:
|
|
487
503
|
|
|
488
504
|
- `--rollback` sends the schema of the migration before the failed one, clears the in-progress record, and leaves the checkpoint where it was. The next `tailor deploy` runs the failed migration again from its first step, so every step must be safe to run again. Data changes made by steps that already completed are not undone, and tables that exist only in the failed migration's schema are deleted with their data.
|
|
@@ -519,6 +535,7 @@ The same command settles a migration whose script run a deploy could not confirm
|
|
|
519
535
|
| Remove table | No | Optional | Warning tier — no script is auto-generated, but you can add one with `tailordb migration script` to preserve data before the table leaves the active schema. The table stays readable from `migrate.ts` during Pre-migration. |
|
|
520
536
|
| Rename table | Yes | Yes | Confirmed interactively at generate time or via `--rename "OldTable:NewTable"` — see [Renaming a table](#renaming-a-table). Auto-generated script copies all rows preserving ids; both tables coexist until post-migration cleanup. |
|
|
521
537
|
| Change foreign key target table | Yes | Yes | Script updates references to the new target |
|
|
538
|
+
| Add relation to existing field | Yes | Yes | Script must resolve values that point at no record. The foreign key is withheld until Post-migration. A relation added to a nested member is non-breaking — see [Changing nested member constraints](#changing-nested-member-constraints). |
|
|
522
539
|
| Change field type (verified pair) | Yes | Yes | In-place for the pairs listed under [Field type changes](#field-type-changes); review the generated normalization scaffold and customize it only when existing values need transformation |
|
|
523
540
|
| Change field type (other pair) | Yes | Yes | Two migrations, generated together after you confirm — see [Converting a field type](#converting-a-field-type). Edit the conversion in the first; the second needs no changes. |
|
|
524
541
|
| Change array → single value | - | - | **Not supported** — see [Converting a field type](#converting-a-field-type) |
|
|
@@ -641,7 +658,7 @@ Some changes are still rejected and need a temporary field you add yourself —
|
|
|
641
658
|
|
|
642
659
|
The command performs the following sequence:
|
|
643
660
|
|
|
644
|
-
1. Reads each migration-enabled namespace's `sdk-migration` checkpoint from the source workspace and reconstructs that exact snapshot from local migration history. A deployed namespace that has no checkpoint is treated as migration 0
|
|
661
|
+
1. Reads each migration-enabled namespace's `sdk-migration` checkpoint from the source workspace and reconstructs that exact snapshot from local migration history. A deployed namespace that has no checkpoint is treated as migration 0 when its schema matches the `0000` snapshot, as `tailor deploy` does; otherwise the command fails and lists the differences. A namespace that was never deployed, or has no tables yet, also fails.
|
|
645
662
|
2. Creates a temporary workspace in the same region as the source, unless `--target-workspace-id` names an existing throwaway workspace. By default it is created in the source's organization and folder. `--organization-id` places it at the root of that organization, and `--folder-id` places it in that folder (together with `--organization-id` when the folder is in another organization or the source is not in an organization). With `--target-workspace-id`, the given values must match where that workspace already is.
|
|
646
663
|
3. Deploys the checkpoint snapshots and writes their checkpoint labels.
|
|
647
664
|
4. Loads fixture data or clones source records.
|
|
@@ -665,7 +682,7 @@ tailor tailordb migration test --data seed
|
|
|
665
682
|
|
|
666
683
|
Rows are loaded only for tables present in the deployed pre-migration snapshots (and current schemas without migrations), in foreign-key dependency order. Fields introduced by pending migrations, including timestamp and nested fields, are removed before insertion so current fixtures can be loaded into the baseline schema. Missing table files are treated as empty. IdP `_User` fixtures are not loaded by this command.
|
|
667
684
|
|
|
668
|
-
Use `--machine-user` to
|
|
685
|
+
Use `--machine-user` to run the seed, the pending migration scripts, and the assertion as that machine user, in place of `db.<namespace>.migration.machineUser` and the first entry in `auth.machineUsers` (and, for the seed, the seed plugin's `machineUserName`). For the pending migration scripts, it must be defined in `auth.machineUsers`.
|
|
669
686
|
|
|
670
687
|
### Clone mode
|
|
671
688
|
|
|
@@ -720,8 +737,8 @@ When you run `tailor deploy`, the SDK detects pending migrations (anything past
|
|
|
720
737
|
|
|
721
738
|
For each pending migration:
|
|
722
739
|
|
|
723
|
-
1. **Pre-migration**: Schema changes that would be breaking are applied in a relaxed form first. A verified in-place field type change keeps its complete previous field contract until Post-migration, including field and table-level hooks or validators changed by the same migration. Newly-required fields are added as optional; fields whose `optional → required` transition is breaking are temporarily kept optional. Fields that are being removed in this migration are temporarily kept on the table so that `migrate.ts` can still read them (for example, to `innerJoin` through a foreign key that is about to be dropped); members removed from a nested field are kept the same way, and the new member of a confirmed nested rename is added as optional and non-unique. Added or newly required nested members are optional and removed nested enum values remain allowed until Post-migration. For a renamed field, the old field is kept and the new field is added with its constraints relaxed, so the script can read the old field and write the new one. For a renamed table, the new table is created with its full constraints while the old table stays on the namespace until post-migration cleanup, so the script can copy rows between them. Breaking table-level index changes are relaxed the same way: a newly-added unique index is withheld, and an index gaining a unique constraint (or a unique index changing its field set) keeps its previous definition, so `migrate.ts` can resolve duplicates first. Non-breaking changes that are part of the same migration are also applied here.
|
|
724
|
-
2. **Script execution**: If `migrate.ts` exists on disk for this migration, it is bundled and runs on the platform as the configured machine user inside a transaction — or, for a script that exports [`steps`](#splitting-a-migration-into-steps), one transaction per step. The script is hard-required for breaking changes (`diff.requiresMigrationScript`) — deploy fails if the file is missing, unless a `--no-script` acknowledgment was recorded (see [Breaking changes without a script](#breaking-changes-without-a-script)). It is also executed when present for warning-tier diffs — see [Warnings and optional migration scripts](#warnings-and-optional-migration-scripts).
|
|
740
|
+
1. **Pre-migration**: Schema changes that would be breaking are applied in a relaxed form first. A verified in-place field type change keeps its complete previous field contract until Post-migration, including field and table-level hooks or validators changed by the same migration. Newly-required fields are added as optional; fields whose `optional → required` transition is breaking are temporarily kept optional. Fields that are being removed in this migration are temporarily kept on the table so that `migrate.ts` can still read them (for example, to `innerJoin` through a foreign key that is about to be dropped); members removed from a nested field are kept the same way, and the new member of a confirmed nested rename is added as optional and non-unique. Added or newly required nested members are optional and removed nested enum values remain allowed until Post-migration. For a renamed field, the old field is kept and the new field is added with its constraints relaxed, so the script can read the old field and write the new one. For a renamed table, the new table is created with its full constraints while the old table stays on the namespace until post-migration cleanup, so the script can copy rows between them. A foreign key added to an existing top-level field is withheld until Post-migration, so `migrate.ts` can update rows whose values point at no record; Post-migration adds it without checking the stored values. Breaking table-level index changes are relaxed the same way: a newly-added unique index is withheld, and an index gaining a unique constraint (or a unique index changing its field set) keeps its previous definition, so `migrate.ts` can resolve duplicates first. Non-breaking changes that are part of the same migration are also applied here.
|
|
741
|
+
2. **Script execution**: If `migrate.ts` exists on disk for this migration, it is bundled and runs on the platform as the [configured machine user](#machine-user-and-permissions) inside a transaction — or, for a script that exports [`steps`](#splitting-a-migration-into-steps), one transaction per step. The script is hard-required for breaking changes (`diff.requiresMigrationScript`) — deploy fails if the file is missing, unless a `--no-script` acknowledgment was recorded (see [Breaking changes without a script](#breaking-changes-without-a-script)). It is also executed when present for warning-tier diffs — see [Warnings and optional migration scripts](#warnings-and-optional-migration-scripts).
|
|
725
742
|
3. **Post-migration schema**: Required constraints and the target field definitions are applied. Do not assume that removing a field clears its underlying stored JSON value.
|
|
726
743
|
4. **Checkpoint and cleanup**: The `sdk-migration` label is bumped to this migration's number, then removed GQL permissions and tables — including the old table left behind by a rename — are deleted. Advancing the checkpoint first prevents a failed checkpoint write from requiring the SDK to recreate irreversibly deleted records.
|
|
727
744
|
|
|
@@ -754,11 +771,11 @@ Maintenance mode restricts GraphQL writes and record events only. Resolvers, exe
|
|
|
754
771
|
|
|
755
772
|
**When a deploy stops early:**
|
|
756
773
|
|
|
757
|
-
- If a migration fails, the tables leave maintenance mode before the error is reported: they return to the settings of the last checkpoint that completed. A migration that did not reach its checkpoint returns existing tables to their previous settings and
|
|
774
|
+
- If a migration fails, the tables leave maintenance mode before the error is reported: they return to the settings of the last checkpoint that completed. A migration that did not reach its checkpoint returns existing tables to their previous settings and drops the tables it created (see [Failure Recovery](#failure-recovery)). A created table that the rollback cannot drop stays in maintenance mode, and the deploy names it for manual repair. The settings are not restored if the checkpoint number or migration history changed concurrently, or if checkpoint ownership cannot be verified. A table left behind by a failed post-checkpoint deletion also stays in maintenance mode for manual recovery.
|
|
758
775
|
- A [multi-step migration](#splitting-a-migration-into-steps) left in progress keeps its tables in maintenance mode until a deploy completes it.
|
|
759
776
|
- A migration whose script run a deploy could not confirm keeps its tables in maintenance mode until you settle it with `tailor tailordb migration resolve` (see [Failure Recovery](#failure-recovery)).
|
|
760
777
|
- With `"deploy"`, if a later part of the deploy fails, the tables leave maintenance mode before the error is reported. When the deploy ends maintenance mode, it leaves the tables of a namespace unchanged if that namespace's checkpoint number or migration history changed concurrently, or if checkpoint ownership cannot be verified, and reports it.
|
|
761
|
-
- If the deploy process is interrupted after a namespace's migrations completed but before maintenance mode ended, that namespace's tables stay in maintenance mode until a later `tailor deploy` releases them when it applies its TailorDB changes, before it replaces resolvers and executors. A namespace whose migrations had not all completed is not released this way: the next deploy resumes a multi-step migration left in progress, stops at a migration whose script was running until you settle it, and otherwise reports remote schema drift (see [Failure Recovery](#failure-recovery)).
|
|
778
|
+
- If the deploy process is interrupted after a namespace's migrations completed but before maintenance mode ended, that namespace's tables stay in maintenance mode until a later `tailor deploy` releases them when it applies its TailorDB changes, before it replaces resolvers and executors. A namespace whose migrations had not all completed is not released this way: the next deploy resumes a multi-step migration left in progress, stops at a migration whose script was running until you settle it, and otherwise reports remote schema drift (see [Failure Recovery](#failure-recovery)), except when an earlier checkpoint of the interrupted deploy recorded the maintenance mode. Every migration checkpoint of a namespace records it, so with pending migrations `0001` and `0002`, a deploy interrupted right after the checkpoint of `0001` does not report drift: the next deploy keeps the tables in maintenance mode while it applies `0002` and releases them afterwards (with `"deploy"`, after the whole deploy).
|
|
762
779
|
|
|
763
780
|
#### Keeping maintenance mode short
|
|
764
781
|
|
|
@@ -792,7 +809,7 @@ Deploy checks the run every few seconds, so a boundary between `waiting to start
|
|
|
792
809
|
Before running migrations, `apply` performs two checks:
|
|
793
810
|
|
|
794
811
|
1. **Local schema check** — your current table definitions must match the latest snapshot in `migrations/`. If they don't, you forgot to run `migration generate`.
|
|
795
|
-
2. **Remote schema check** — the deployed schema is reconstructed from migration history; the actual remote schema must match. Drift here means someone applied a different set of migrations or edited the schema out-of-band.
|
|
812
|
+
2. **Remote schema check** — the deployed schema is reconstructed from migration history; the actual remote schema must match. Drift here means someone applied a different set of migrations or edited the schema out-of-band. A namespace that has tables but no migration label yet must match `0000` (see [Adding migrations to an existing project](#adding-migrations-to-an-existing-project)).
|
|
796
813
|
|
|
797
814
|
On drift you'll see something like:
|
|
798
815
|
|
|
@@ -823,6 +840,8 @@ To bypass both checks during deploy (not recommended outside of recovery scenari
|
|
|
823
840
|
tailor deploy --no-schema-check
|
|
824
841
|
```
|
|
825
842
|
|
|
843
|
+
`--no-schema-check` does not bypass the check that the remote migration checkpoint is in the local migration history. See ["Remote migration checkpoint is not in the local migration history" error](#remote-migration-checkpoint-is-not-in-the-local-migration-history-error).
|
|
844
|
+
|
|
826
845
|
### Example output
|
|
827
846
|
|
|
828
847
|
```
|
|
@@ -855,13 +874,17 @@ tailor tailordb migration rebaseline --namespace tailordb
|
|
|
855
874
|
|
|
856
875
|
The command validates the migration files, verifies that replaying the latest migration exactly reproduces the local table definitions, and checks that the connected workspace is at that latest migration with no schema drift. After confirmation, it replaces the local history with the reconstructed baseline, records a new migration history ID in both `0000/schema.json` and remote metadata, and resets the connected workspace's `sdk-migration` label to `0000`. Use `--yes` only after arranging the same operational preconditions in non-interactive automation.
|
|
857
876
|
|
|
877
|
+
When the connected workspace records another migration history ID, or one that cannot be read, the command refuses to run. Run it from a checkout of the migration history deployed to that workspace. To replace the workspace's history with the local one on purpose, first run `tailor tailordb migration set <N> --force` with the latest local migration as `<N>` (see [`migration set` Semantics](#migration-set-semantics)). When the workspace's tables differ from the latest migration, the command also refuses to run; see [Remote schema drift detected](#remote-schema-drift-detected), where `tailor tailordb migration sync <N>` rewrites the tables to the latest migration.
|
|
878
|
+
|
|
858
879
|
Commit the resulting `migrations/` change before generating any new migrations. For another environment still carrying the exact checkpoint and history ID that the new baseline replaced, the next `tailor deploy` checks whether its remote schema exactly matches the new `0000`. If it does, deploy offers to reset the checkpoint to `0000` and move the environment to the new history ID before applying any later local migrations. A markerless history is eligible only for the first rebaseline, at the exact migration recorded as replaced. Any other checkpoint or history ID is rejected without changing remote metadata, even if its schema happens to match the baseline.
|
|
859
880
|
|
|
860
881
|
Partial squashing is not supported: re-baselining always replaces the full history for one namespace.
|
|
861
882
|
|
|
862
883
|
## `migration set` Semantics
|
|
863
884
|
|
|
864
|
-
`tailor tailordb migration set <N>` updates the `sdk-migration` label on the deployed namespace's metadata. **It does not modify any data or schema.** It only changes which migrations the next `apply` will consider pending. The command also
|
|
885
|
+
`tailor tailordb migration set <N>` updates the `sdk-migration` label on the deployed namespace's metadata. **It does not modify any data or schema.** It only changes which migrations the next `apply` will consider pending. The command also records the migration history ID of the local baseline on the namespace.
|
|
886
|
+
|
|
887
|
+
The command refuses to run when the namespace records a migration history ID other than the local one, or one that cannot be read, unless the namespace has neither a checkpoint nor a history ID yet. Run it from a checkout of the migration history deployed to the workspace. To write the local history over another one on purpose, for example after `migration rebaseline` re-baselined the local history but could not update the workspace, pass `--force`: the command shows the history ID it replaces, records the local history ID or, when the local history has none, removes the namespace's one, and still asks for confirmation unless you pass `--yes`.
|
|
865
888
|
|
|
866
889
|
The migration number is validated before anything is sent to the remote: it must be a 4-digit value (e.g. `0001`) or a bare integer (e.g. `1`) within 0–9999, and must exist in the working tree's migration history, which is itself validated (a gapped history is rejected). `0` is always accepted as the baseline (even when no migrations directory exists yet), provided the history passes validation.
|
|
867
890
|
|
|
@@ -880,7 +903,7 @@ Use cases:
|
|
|
880
903
|
|
|
881
904
|
## `migration sync` Semantics
|
|
882
905
|
|
|
883
|
-
`tailor tailordb migration sync <N>` reconstructs the schema snapshot at migration `N` from the working tree's migration history and **overwrites the remote schema to match it**, then sets the `sdk-migration` label to `N` and
|
|
906
|
+
`tailor tailordb migration sync <N>` reconstructs the schema snapshot at migration `N` from the working tree's migration history and **overwrites the remote schema to match it**, then sets the `sdk-migration` label to `N` and records the migration history ID of the local baseline. Unlike `migration set`, it changes the remote schema as well as the bookkeeping. Like `set`, it never runs `migrate.ts` scripts itself — it only changes what the next `apply` considers pending:
|
|
884
907
|
|
|
885
908
|
| Movement | Effect on next `apply` | Effect on data |
|
|
886
909
|
| -------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
@@ -889,9 +912,14 @@ Use cases:
|
|
|
889
912
|
|
|
890
913
|
Before anything is sent to the remote, `sync` verifies that replaying the full migration history reproduces the current local table definitions. If it does not — because migration files were edited and no longer match, or because a schema change has not been recorded with `migration generate` yet — the command fails without touching the remote. This means a rewritten migration history is validated before it can overwrite the deployed schema.
|
|
891
914
|
|
|
915
|
+
Like `set`, `sync` refuses to run when the namespace records another migration history ID, or one that cannot be read, before it reads or changes any table. With `--force`, it overwrites both the tables and the history ID with those of the local history, and still asks for confirmation unless you pass `--yes`.
|
|
916
|
+
|
|
892
917
|
Because syncing backward causes already-applied scripts to re-execute on the next deploy, **write `migrate.ts` scripts to be idempotent** (see [Performance and Large Tables](#performance-and-large-tables) for resumable `where` clauses).
|
|
893
918
|
|
|
894
|
-
The main use
|
|
919
|
+
The main use cases are:
|
|
920
|
+
|
|
921
|
+
- Recovering from drift after a `deploy --no-schema-check`, which deploys the local table definitions without checking them against the migration snapshots: run `migration sync <N>` to restore the remote to a known snapshot, then `tailor deploy` to apply the remaining migrations from the working tree.
|
|
922
|
+
- Moving a checkpoint that `tailor deploy` reports as not in the local migration history back to that history on purpose (see ["Remote migration checkpoint is not in the local migration history" error](#remote-migration-checkpoint-is-not-in-the-local-migration-history-error)).
|
|
895
923
|
|
|
896
924
|
## Team Workflow and CI/CD
|
|
897
925
|
|
|
@@ -918,7 +946,7 @@ When your branch and main each generated the same number, merging or rebasing st
|
|
|
918
946
|
|
|
919
947
|
- For non-interactive environments, pass `--yes` to `migration generate` and `--yes` to `apply`. `apply` runs migrations automatically when the `migrations/` directory is configured.
|
|
920
948
|
- Run `tailor tailordb migration validate` in CI to catch uncommitted migrations, broken migration files, unreviewed generated normalization logic, and remote schema drift before deploying. It exits with a non-zero code when validation fails and supports `--json`. Add `--strict` to also require an explicit acknowledgment (a `migrate.ts` or a recorded `--no-script` reason) for every pending migration that can drop data, so destructive changes cannot merge unnoticed.
|
|
921
|
-
- `tailor tailordb migration status` validates the layout and file-format compatibility of the full local history, compares its history ID with the deployed namespace, and shows applied and pending migrations for a human-readable comparison. Its exit code is non-zero on invalid or incompatible files, migration history mismatches, and remote read errors, so check the output.
|
|
949
|
+
- `tailor tailordb migration status` validates the layout and file-format compatibility of the full local history, compares its history ID with the deployed namespace, and shows applied and pending migrations for a human-readable comparison. Its exit code is non-zero on invalid or incompatible files, migration history mismatches, a remote checkpoint ahead of the latest local migration, and remote read errors, so check the output.
|
|
922
950
|
- Avoid running migrations in parallel against the same workspace — there is no locking. Serialize deploys per environment.
|
|
923
951
|
|
|
924
952
|
### Resetting a deployed project
|
|
@@ -935,7 +963,7 @@ A script that exports `steps` follows different rules once one of its steps has
|
|
|
935
963
|
|
|
936
964
|
The rollback is best-effort per table; if reverting a table fails, a warning is logged and the original migration error is still reported. The next deploy then stops with remote schema drift on that table. Run `tailor tailordb migration validate` to see which tables differ, return the namespace to its checkpoint with `tailor tailordb migration sync <N>`, where `<N>` is the checkpoint `tailor tailordb migration status` reports, and deploy again. `sync` deletes the tables and fields that are not in that migration's snapshot, together with any rows the failed migration wrote to them; the next deploy runs the failed migration again.
|
|
937
965
|
|
|
938
|
-
If `tailor deploy` cannot tell whether a `main` script ran, for example because the connection dropped while it started or waited for the script, it does not roll anything back: the pre-migration schema changes stay, the namespace's tables stay in [maintenance mode](#maintenance-mode) if it is on, the migration stays recorded as in progress, and the deploy reports that it could not confirm the outcome. The same holds when the deploy process stops while the script runs. Until you settle the migration, `tailor deploy` stops before changing anything, including the other namespaces and resources of the application, and reports how the run stands, even with `--no-schema-check
|
|
966
|
+
If `tailor deploy` cannot tell whether a `main` script ran, for example because the connection dropped while it started or waited for the script, it does not roll anything back: the pre-migration schema changes stay, the namespace's tables stay in [maintenance mode](#maintenance-mode) if it is on, the migration stays recorded as in progress, and the deploy reports that it could not confirm the outcome. The same holds when the deploy process stops while the script runs. Until you settle the migration, `tailor deploy` stops before changing anything, including the other namespaces and resources of the application, and reports how the run stands, even with `--no-schema-check`. Run `tailor workflow executions --workflow-name <name>` with the workflow name the error reports until the execution has finished. If it succeeded, run `tailor tailordb migration resolve --complete`, which applies the migration's schema without running the script again; otherwise run `tailor tailordb migration resolve --rollback`, and the next deploy runs the script again. See [Settling a migration that stays in progress](#settling-a-migration-that-stays-in-progress), which also lists the commands that refuse to run until then.
|
|
939
967
|
|
|
940
968
|
If the post-migration schema changes fail after a `main` script succeeded and the deploy cannot clear the in-progress record, it keeps the pre-migration schema instead of rolling it back, so the data the script wrote stays. Settle the migration as above: `--complete` once the data meets the migration's schema, or `--rollback`.
|
|
941
969
|
|
|
@@ -949,9 +977,11 @@ After a failure:
|
|
|
949
977
|
|
|
950
978
|
If a migration **succeeds in script** but its reversible **post-migration schema update** fails (rare; usually a constraint violation the script should have prevented), the SDK makes the same best-effort restoration to the prior-checkpoint schema. The script's committed data changes remain, so write migration scripts to tolerate re-execution.
|
|
951
979
|
|
|
980
|
+
Before a migration's script starts, `tailor deploy` checks that the namespace's checkpoint and migration history are still the ones it read before applying, or the checkpoint it committed itself. If another deploy or `tailor tailordb migration set` changed them in the meantime, or the deploy cannot read them, the script does not start and the migration is not recorded as in progress: the deploy handles the pre-migration schema as for a script that failed before it ran, and fails with `MIGRATION_CHECKPOINT_CONFLICT` (`MIGRATION_CHECKPOINT_UNVERIFIED` when it cannot read them). Run `tailor tailordb migration status` to see the checkpoint. Wait for the other deploy to finish, or, when no other deploy is running, return the namespace to the checkpoint the error names with `tailor tailordb migration sync <N>`, which also lifts the maintenance mode the deploy left in place, and deploy again. If the error says the checkpoint changed in a different migration history, or with a migration history label that cannot be read, `sync` also needs `--force`, which replaces the workspace's migration history ID with the local one. Pass it only when the local migration history should replace the deployed one; otherwise deploy from a checkout of the migration history deployed to the workspace.
|
|
981
|
+
|
|
952
982
|
The checkpoint is advanced only after the reversible post-migration schema updates succeed. If the checkpoint write reports an error, the SDK reads it back: a matching value is treated as committed. Any other observed value leaves the post-migration schema unchanged rather than risk rolling back a concurrent deployment; a value beyond the current migration confirms a concurrent deploy, while an older or missing value means the checkpoint must be repaired before retrying. If read-back also fails, the SDK likewise leaves the post-migration schema unchanged; verify the remote checkpoint before retrying.
|
|
953
983
|
|
|
954
|
-
Removed tables are deleted only after the checkpoint is committed. If that cleanup fails, the checkpoint remains at the new migration and the SDK fails closed: the leftover table is reported as remote schema drift on the next deploy.
|
|
984
|
+
Removed tables are deleted only after the checkpoint is committed. If that cleanup fails, the checkpoint remains at the new migration and the SDK fails closed: the leftover table is reported as remote schema drift on the next deploy. Run `tailor tailordb migration sync <N>` with the committed migration as `<N>` (the warning names the command) to delete the leftover table and its GQL permission, or remove them manually, and then deploy again. The SDK does not automatically ignore or delete a same-named remote table because it cannot distinguish failed cleanup from a table recreated after cleanup completed.
|
|
955
985
|
|
|
956
986
|
## Rollback Strategy
|
|
957
987
|
|
|
@@ -970,7 +1000,9 @@ Migration scripts execute server-side under a machine user identity. The CLI sel
|
|
|
970
1000
|
1. `db.<namespace>.migration.machineUser` if set in `tailor.config.ts`.
|
|
971
1001
|
2. The first entry in `auth.machineUsers` otherwise.
|
|
972
1002
|
|
|
973
|
-
The CLI logs the selected user before running scripts (`Using machine user: ...`).
|
|
1003
|
+
The selected user must be defined in `auth.machineUsers`. The CLI logs the selected user before running scripts (`Using machine user: ...`).
|
|
1004
|
+
|
|
1005
|
+
When a pending migration has a `migrate.ts`, `tailor deploy` checks while planning that auth is configured, that a machine user is selected and defined in `auth.machineUsers`, and that each script can be bundled. A failure stops the deploy before anything is applied, and `tailor deploy --dry-run` reports it too.
|
|
974
1006
|
|
|
975
1007
|
**Permissions required**
|
|
976
1008
|
|
|
@@ -979,7 +1011,7 @@ The machine user needs read/write access to every table the migration script tou
|
|
|
979
1011
|
If you see `No machine user available for migration execution`, either:
|
|
980
1012
|
|
|
981
1013
|
- Add `machineUsers: { ... }` to your auth config and `tailor deploy` it, or
|
|
982
|
-
- Set `migration.machineUser` to
|
|
1014
|
+
- Set `migration.machineUser` to a machine user defined in `auth.machineUsers`.
|
|
983
1015
|
|
|
984
1016
|
## Multi-Namespace Coordination
|
|
985
1017
|
|
|
@@ -1045,7 +1077,7 @@ describe("0005 add required email", () => {
|
|
|
1045
1077
|
});
|
|
1046
1078
|
```
|
|
1047
1079
|
|
|
1048
|
-
Stage the rows each query returns with `mock.enqueueResult(...)` or `mock.setQueryResolver(...)` when the script reads before writing; call `main(trx, { env: { ... } })` when the script takes a `MigrationContext`. See [Kysely-layer mock](../../testing.md#kysely-layer-mock-createkyselymock) for the full mock API.
|
|
1080
|
+
Stage the rows each query returns with `mock.enqueueResult(...)` or `mock.setQueryResolver(...)` when the script reads before writing; call `main(trx, { env: { ... } })` when the script takes a `MigrationContext`. See [Kysely-layer mock](../../testing.md#kysely-layer-mock-createkyselymock-beta) for the full mock API.
|
|
1049
1081
|
|
|
1050
1082
|
These tests need no platform connection and no `tailor-runtime` environment — they run in a plain Vitest setup. Vitest's default `include` pattern already picks up `migrations/**/migrate.test.ts`; if your config narrows `include`, add the migrations directory. The test file is ignored by `tailor deploy` and never ships to the platform.
|
|
1051
1083
|
|
|
@@ -1061,7 +1093,7 @@ npm install -D @electric-sql/pglite
|
|
|
1061
1093
|
|
|
1062
1094
|
The generated `db.pglite.ts` exports the `CREATE TABLE` script for the same schema `db.ts` types — the tables as the pre-migration phase leaves them while `migrate.ts` runs, including relaxed constraints, renamed fields under both names, and retained removed fields. Run it once on the PGlite instance, stage rows, then run the script in a transaction. `tailor tailordb migration script <N> --with-test` scaffolds this test too when `@electric-sql/pglite` is installed. Type the instance with `Unmigrated<Database>` rather than `Database`: `db.ts` types a column the migration makes required as `T | null` on read but `T` on write (and an enum it narrows as the old values on read but the new ones on write), so that `migrate.ts` cannot write what the migration is removing — which would also stop the test from staging the rows the script has to convert. `Unmigrated` lets every column be written with whatever it can still be read as; `main` still receives a `Transaction<Database>`.
|
|
1063
1095
|
|
|
1064
|
-
Relations are enforced as on the platform: a row that points at a parent that does not exist is rejected. Write the records the test starts from inside `prepareFixtures`, which leaves relations unchecked while it runs, so you can stage rows that predate a relation, such as rows whose parent does not exist that the script repairs. Relations are checked again when it returns, so the script itself still cannot write a missing parent. The same holds in reverse: the script cannot delete a parent that a row still points at, or change the key a row points at, so delete rows in the opposite order of staging them. NOT NULL, UNIQUE, and CHECK constraints stay enforced. A relation is left unenforced while the pre-migration schema cannot satisfy it: when a pending type change leaves the field a different type from its target, when the migration
|
|
1096
|
+
Relations are enforced as on the platform: a row that points at a parent that does not exist is rejected. Write the records the test starts from inside `prepareFixtures`, which leaves relations unchecked while it runs, so you can stage rows that predate a relation, such as rows whose parent does not exist that the script repairs. Relations are checked again when it returns, so the script itself still cannot write a missing parent. The same holds in reverse: the script cannot delete a parent that a row still points at, or change the key a row points at, so delete rows in the opposite order of staging them. NOT NULL, UNIQUE, and CHECK constraints stay enforced. A relation is left unenforced while the pre-migration schema cannot satisfy it: when a pending type change leaves the field a different type from its target, when the migration adds the relation to an existing top-level field or retargets it to another table, or when the target field's unique constraint is still withheld. How relations on array fields and inside nested objects behave is described under [Real SQL execution with PGlite](../../testing.md#real-sql-execution-with-pglite-mocktailordbwithpglite).
|
|
1065
1097
|
|
|
1066
1098
|
```typescript
|
|
1067
1099
|
// migrations/0005/migrate.pglite.test.ts
|
|
@@ -1108,6 +1140,8 @@ describe("0005 add required email", () => {
|
|
|
1108
1140
|
});
|
|
1109
1141
|
```
|
|
1110
1142
|
|
|
1143
|
+
Queries on `db` wait while the transaction is open, so check the results after `transaction().execute()` returns, or query through `trx` inside it — a query on `db` from inside the transaction waits on itself and never finishes.
|
|
1144
|
+
|
|
1111
1145
|
Pass nested field values as JavaScript objects or arrays of objects, without `JSON.stringify`.
|
|
1112
1146
|
|
|
1113
1147
|
When the migration was generated with `kyselyTypePlugin({ temporal: true })`, its `db.ts` types date and datetime fields as `Temporal.PlainDate` and `Temporal.Instant`, and top-level time fields as `Temporal.PlainTime`; nested time fields remain strings. Deploy runs the script with those values. Create the instance with `createKyselyPGlite<Unmigrated<Database>>(pglite, { temporal: true })` so PGlite reads top-level temporal columns back the same way; the scaffold adds the option for such migrations. Run the test in the [`tailor-runtime` Vitest environment](../../testing.md#temporal), which provides `Temporal`. A migration generated before `temporal` was enabled still runs with `Date` values on deploy, so its test leaves the option out. As in the Kysely mock scaffold, the test calls `applyDateRepresentation` with the migration's recorded `defaultDateRepresentation` (or `"string"`) so `t` date fields parse the way they do on deploy; a migration recorded under `"temporal"` likewise needs the `tailor-runtime` environment for `Temporal`.
|
|
@@ -1161,9 +1195,15 @@ For genuinely different schemas across environments, prefer separate workspaces
|
|
|
1161
1195
|
|
|
1162
1196
|
## Troubleshooting
|
|
1163
1197
|
|
|
1198
|
+
### `MIGRATION_DIRECTORY_CONFLICT` error
|
|
1199
|
+
|
|
1200
|
+
**Cause:** Two or more TailorDB namespaces set `migration.directory` to the same directory (after resolving it against the config file's directory). For example, `tailordb` and `analyticsdb` both set `"./migrations"`. Both namespaces would read and write the same `0000/schema.json`, so `migration generate` could not tell them apart and `migration generate --init` would rebuild the history of both. Every `tailor tailordb migration` command and `tailor deploy` fail with this error before touching any migration files. The error lists every shared directory with the namespaces that use it.
|
|
1201
|
+
|
|
1202
|
+
**Resolution:** Give each namespace its own directory in `tailor.config.ts`, for example `"./migrations/tailordb"` and `"./migrations/analyticsdb"`.
|
|
1203
|
+
|
|
1164
1204
|
### Remote schema drift detected
|
|
1165
1205
|
|
|
1166
|
-
**Cause:** Remote schema doesn't match what the migration history says it should be.
|
|
1206
|
+
**Cause:** Remote schema doesn't match what the migration history says it should be. When `tailor deploy` shows `Remote migration: <unset>`, or `migration validate` shows `no checkpoint yet`, the namespace has no migration label yet and its tables differ from `0000`; see [Adding migrations to an existing project](#adding-migrations-to-an-existing-project) instead of the steps below.
|
|
1167
1207
|
|
|
1168
1208
|
**Resolution:**
|
|
1169
1209
|
|
|
@@ -1171,11 +1211,11 @@ For genuinely different schemas across environments, prefer separate workspaces
|
|
|
1171
1211
|
2. Compare with teammates — has someone applied different migrations?
|
|
1172
1212
|
3. If remote was changed manually, decide whether to update local migrations to match or to use `migration set <N>` to align bookkeeping.
|
|
1173
1213
|
4. To force the remote schema back to a known snapshot, use `migration sync <N>` (see [`migration sync` Semantics](#migration-sync-semantics)). If the drift follows a failed deploy that warned it could not restore a table's schema, see [Failure Recovery](#failure-recovery) instead.
|
|
1174
|
-
5. As a last resort in non-production environments, `--no-schema-check` skips both checks. Do not use this as a routine workaround.
|
|
1214
|
+
5. As a last resort in non-production environments, `--no-schema-check` skips both checks. Do not use this as a routine workaround. It does not skip the checkpoint check described next.
|
|
1175
1215
|
|
|
1176
1216
|
### "Remote migration checkpoint is not in the local migration history" error
|
|
1177
1217
|
|
|
1178
|
-
**Cause:** The deployed namespace's checkpoint refers to a migration number, or a migration history ID, that the local `migrations/` directory no longer has a record of. This is a different code path from schema drift above: it fires before any schema comparison, because the CLI cannot even locate the remote's recorded position in the local history.
|
|
1218
|
+
**Cause:** The deployed namespace's checkpoint refers to a migration number, or a migration history ID, that the local `migrations/` directory no longer has a record of. This is a different code path from schema drift above: it fires before any schema comparison, because the CLI cannot even locate the remote's recorded position in the local history. `tailor deploy` stops with this error even with `--no-schema-check`, so a deploy from an outdated checkout never moves the checkpoint back or overwrites the tables with an older schema.
|
|
1179
1219
|
|
|
1180
1220
|
One specific cause is a `migration rebaseline` run on a different environment while this one had not yet caught up to the latest pre-rebaseline migration. `migration rebaseline` requires every other environment to already be at the latest migration (see [Re-baselining a deployed migration history](#re-baselining-a-deployed-migration-history)), but the CLI only verifies the one workspace it runs against. If this environment was behind, the error message names the migration this environment must reach (the migration every environment was required to be at before the rebaseline) alongside the migration it is actually at.
|
|
1181
1221
|
|
|
@@ -1185,7 +1225,13 @@ One specific cause is a `migration rebaseline` run on a different environment wh
|
|
|
1185
1225
|
2. Deploy this environment against that restored history until its checkpoint reaches the migration named in the error, running any `migrate.ts` scripts it still needs.
|
|
1186
1226
|
3. Switch back to the current (rebaselined) migration files and deploy again. The remote schema now matches the new baseline, so this deploy offers the automatic checkpoint reset to `0000` under the new history ID.
|
|
1187
1227
|
|
|
1188
|
-
**Resolution (any other cause):** Run `tailor tailordb migration status` to compare local and remote,
|
|
1228
|
+
**Resolution (any other cause):** Run `tailor tailordb migration status` to compare local and remote, and pull the latest migration files if your checkout is stale. To move the checkpoint back to the local migration history on purpose, run `tailor tailordb migration sync <N>`, which also rewrites the tables to snapshot `<N>`, or `tailor tailordb migration set <N>`, which moves only the checkpoint, with the latest local migration as `<N>` (the error suggests both commands). When the namespace records another migration history ID, or one that cannot be read, both commands also need `--force`, which replaces that history ID with the local one, or removes it when the local history has none (see [`migration set` Semantics](#migration-set-semantics)). Pass `--force` only when the local migration history should replace the deployed one; otherwise work from a checkout of the migration history deployed to the workspace. Then deploy again.
|
|
1229
|
+
|
|
1230
|
+
### `MIGRATION_FILES_INVALID` error
|
|
1231
|
+
|
|
1232
|
+
**Cause:** The `migrations/` directory of a namespace does not form a valid history: a migration number is missing, `0000` has no `schema.json` or has a `diff.json`, or a later migration has a `schema.json` or no `diff.json`. A deleted directory or a merge that kept only part of a migration is a common cause. `tailor deploy` and the `tailor tailordb migration` commands that read the history report this error, listing every problem.
|
|
1233
|
+
|
|
1234
|
+
**Resolution:** Follow the suggestion for each problem in the error. Otherwise restore the migration files from version control. If no workspace has been deployed with these migrations, you can instead recreate `0000` from the local tables with `tailor tailordb migration generate --init --namespace <name>`, which deletes every migration of the namespace.
|
|
1189
1235
|
|
|
1190
1236
|
### "Invalid schema snapshot" or "Invalid migration diff" error
|
|
1191
1237
|
|
|
@@ -1194,7 +1240,7 @@ One specific cause is a `migration rebaseline` run on a different environment wh
|
|
|
1194
1240
|
**Resolution:**
|
|
1195
1241
|
|
|
1196
1242
|
1. Read the error message — it includes the file path and the offending field.
|
|
1197
|
-
2. Restore the file from version control (`git checkout -- <path>`),
|
|
1243
|
+
2. Restore the file from version control (`git checkout -- <path>`); `migration generate` reads every migration file and stops with the same error until then. If the broken file belongs to the latest migration and no environment has deployed it, you can instead recreate that migration: copy out the files you edited in its directory (such as `migrate.ts` and its tests), delete the directory, run `migration generate` with the options that created it (such as `--data-only`, `--rename`, or `--drop`), copy your edits back, and record any `--no-script` reason again with `migration script`. A migration pair created with `--expand-contract` cannot be recreated this way, so restore its files from version control.
|
|
1198
1244
|
3. Do not hand-edit `schema.json` or `diff.json`; they are managed by the CLI.
|
|
1199
1245
|
|
|
1200
1246
|
### "Unsupported migration file format version" error
|
|
@@ -1205,15 +1251,21 @@ One specific cause is a `migration rebaseline` run on a different environment wh
|
|
|
1205
1251
|
|
|
1206
1252
|
### "No machine user available for migration execution"
|
|
1207
1253
|
|
|
1208
|
-
**Cause:** Neither `migration.machineUser` is set nor are there any machine users in `auth.machineUsers`.
|
|
1254
|
+
**Cause:** Neither `migration.machineUser` is set nor are there any machine users in `auth.machineUsers`. `tailor deploy` reports this while planning, including with `--dry-run`, before anything is applied.
|
|
1255
|
+
|
|
1256
|
+
**Resolution:** Add a machine user to `auth.machineUsers`, then run `tailor deploy` again. The deploy creates the machine user before it runs the migration scripts.
|
|
1257
|
+
|
|
1258
|
+
### "Machine user for migration execution is not defined in auth"
|
|
1259
|
+
|
|
1260
|
+
**Cause:** `migration.machineUser`, or `--machine-user` of `tailor tailordb migration test`, names a machine user that is not in `auth.machineUsers`. `tailor deploy` reports this while planning, including with `--dry-run`, before anything is applied.
|
|
1209
1261
|
|
|
1210
|
-
**Resolution:**
|
|
1262
|
+
**Resolution:** Either add the machine user to `auth.machineUsers`, or name one that is defined there.
|
|
1211
1263
|
|
|
1212
1264
|
### "Machine user not found"
|
|
1213
1265
|
|
|
1214
|
-
**Cause:** `migration
|
|
1266
|
+
**Cause:** In `tailor tailordb migration test`, the machine user that runs the seed or the assertion (from `--machine-user`, the seed plugin's `machineUserName`, or `migration.machineUser`) is not in `auth.machineUsers`, so the temporary workspace does not have it.
|
|
1215
1267
|
|
|
1216
|
-
**Resolution:** Either add the machine user to `auth.machineUsers
|
|
1268
|
+
**Resolution:** Either add the machine user to `auth.machineUsers`, or name one that is defined there.
|
|
1217
1269
|
|
|
1218
1270
|
### Migration script execution fails
|
|
1219
1271
|
|
|
@@ -352,7 +352,7 @@ export default createResolver({
|
|
|
352
352
|
});
|
|
353
353
|
```
|
|
354
354
|
|
|
355
|
-
Wait points can be imported and used in any file (workflow jobs, resolvers, executors). For local testing, see [Jobs that wait on approval](../testing.md#jobs-that-wait-on-approval) in the testing guide.
|
|
355
|
+
Wait points can be imported and used in any file (workflow jobs, resolvers, executors). For local testing, see [Jobs that wait on approval](../testing.md#jobs-that-wait-on-approval-beta) in the testing guide.
|
|
356
356
|
|
|
357
357
|
### Keys With Runtime Values
|
|
358
358
|
|
package/docs/testing.md
CHANGED
|
@@ -18,14 +18,14 @@ Unit-test entrypoints exposed by the SDK:
|
|
|
18
18
|
- `resolver.body({ input, caller, invoker, env })` — invoke a resolver
|
|
19
19
|
- `workflowJob.body(input, { env, invoker })` — invoke a workflow job body directly
|
|
20
20
|
- `workflowJob.start(input)` — chain a workflow job through the workflow runtime
|
|
21
|
-
- `runWorkflowLocally(workflow, args)` — run a workflow chain locally with real job bodies
|
|
21
|
+
- `runWorkflowLocally(workflow, args)` (Beta) — run a workflow chain locally with real job bodies
|
|
22
22
|
- `executor.operation.body({ ...args, invoker })` — invoke a function-kind executor
|
|
23
23
|
|
|
24
24
|
For anonymous direct calls:
|
|
25
25
|
|
|
26
26
|
- Pass `null` for anonymous `caller` / `invoker` context in direct unit tests.
|
|
27
27
|
|
|
28
|
-
Platform API mocks under `@tailor-platform/sdk/vitest` (for use with the [`tailor-runtime` Vitest environment](#runtime-environment-emulation-beta) below
|
|
28
|
+
Platform API mocks under `@tailor-platform/sdk/vitest` (Beta), for use with the [`tailor-runtime` Vitest environment](#runtime-environment-emulation-beta) below:
|
|
29
29
|
|
|
30
30
|
- `mockTailordb` — TailorDB query stubs and call recording
|
|
31
31
|
- `mockTailordbWithPGlite` — TailorDB backed by a real in-memory Postgres (PGlite)
|
|
@@ -227,7 +227,7 @@ To hand-write DDL instead — for a table not in the schema, or to add a constra
|
|
|
227
227
|
- Seed through `getDB` itself. When a column type rejects a value that only the test must stage, use `createKyselyPGlite<Unmigrated<...>>(pglite)` instead — see [Testing Migrations Locally](./services/tailordb/migration.md#testing-migrations-locally). This only affects test setup; it cannot supply a `.serial()` value for an insert issued by the code under test.
|
|
228
228
|
- With `kyselyTypePlugin({ temporal: true })`, the generated `getDB` reads top-level date, datetime, and time columns back from PGlite as `Temporal.PlainDate`, `Temporal.Instant`, and `Temporal.PlainTime`, as it does on the platform. Values inside nested objects stay strings in PGlite (see above); on the platform, nested date/datetime fields are Temporal values while nested time fields remain strings.
|
|
229
229
|
- Transactions on a shared instance are serialized: while one is open, queries from other `getDB` instances wait. Do not use `test.concurrent` with a shared instance, and do not query the same instance through a second `getDB` from inside a transaction — that waits on itself.
|
|
230
|
-
- PGlite runs full PostgreSQL while TailorDB supports a subset of it, and TailorDB hooks, validations, and permissions do not run here — a test passing on PGlite can still behave differently on the platform. Keep [`mockTailordb`](#tailordb-mock) or [`createKyselyMock`](#kysely-layer-mock-createkyselymock) tests for query shape and error paths, and E2E tests for platform behavior.
|
|
230
|
+
- PGlite runs full PostgreSQL while TailorDB supports a subset of it, and TailorDB hooks, validations, and permissions do not run here — a test passing on PGlite can still behave differently on the platform. Keep [`mockTailordb`](#tailordb-mock) or [`createKyselyMock`](#kysely-layer-mock-createkyselymock-beta) tests for query shape and error paths, and E2E tests for platform behavior.
|
|
231
231
|
|
|
232
232
|
### Workflow Mock
|
|
233
233
|
|
|
@@ -642,7 +642,7 @@ describe("decrementUserAge", () => {
|
|
|
642
642
|
|
|
643
643
|
**Use when:** multi-step business logic. The tests survive query rewrites because they assert high-level intent, not SQL shape.
|
|
644
644
|
|
|
645
|
-
#### Kysely-layer mock (`createKyselyMock`)
|
|
645
|
+
#### Kysely-layer mock (`createKyselyMock`) (Beta)
|
|
646
646
|
|
|
647
647
|
`createKyselyMock` returns a real Kysely instance whose execution is mocked. Stage the rows each query returns, run your code, then assert what it did — each query's SQL and parameters, how many `selects`/`inserts`/`updates`/`deletes` ran, and the value your code returned. Queries stay fully typed and compile to the same SQL as production.
|
|
648
648
|
|
|
@@ -697,7 +697,7 @@ Reach for [`mockTailordb`](#mocking-the-tailordb-client) instead when you want t
|
|
|
697
697
|
|
|
698
698
|
TailorDB migration scripts (`migrate.ts`) are unit-tested the same way: the generated `db.ts` exports the `Database` interface to type the mock, and `tailor tailordb migration script <N> --with-test` scaffolds a ready-to-fill test. To execute a migration script against real rows in an in-memory Postgres, use `createKyselyPGlite` with `@electric-sql/pglite` and the generated `db.pglite.ts` schema. See [Testing Migrations Locally](./services/tailordb/migration.md#testing-migrations-locally).
|
|
699
699
|
|
|
700
|
-
#### Resolvers that resume a workflow
|
|
700
|
+
#### Resolvers that resume a workflow (Beta)
|
|
701
701
|
|
|
702
702
|
Resolvers that call `waitPoint.resolve(...)` delegate to `tailor.workflow.resolve` at runtime. With the `tailor-runtime` environment active, use `mockWorkflow().waitPoint(definition)` to invoke the callback with the payload that originally suspended the job:
|
|
703
703
|
|
|
@@ -731,7 +731,7 @@ describe("resolveApproval resolver", () => {
|
|
|
731
731
|
|
|
732
732
|
`setResolvePayload` is typed from the wait-point definition. The lower-level `setResolveHandler` and `resolveCalls` APIs remain available when a test must control several wait points together.
|
|
733
733
|
|
|
734
|
-
#### Resolvers that resume failed workflows
|
|
734
|
+
#### Resolvers that resume failed workflows (Beta)
|
|
735
735
|
|
|
736
736
|
Resolvers or executors that call `workflow.resumeWorkflowExecution(executionId)` delegate to `tailor.workflow.resumeWorkflowExecution` at runtime. With the `tailor-runtime` environment active, use `mockWorkflow().setResumeHandler` to control the returned execution ID and inspect `resumeWorkflowExecution.mock.calls`:
|
|
737
737
|
|
|
@@ -838,7 +838,7 @@ describe("validateOrder", () => {
|
|
|
838
838
|
});
|
|
839
839
|
```
|
|
840
840
|
|
|
841
|
-
#### Jobs that start other jobs
|
|
841
|
+
#### Jobs that start other jobs (Beta)
|
|
842
842
|
|
|
843
843
|
Use `mockWorkflow().job(definition)` to replace dependent jobs with deterministic results:
|
|
844
844
|
|
|
@@ -878,7 +878,7 @@ describe("fulfillOrder", () => {
|
|
|
878
878
|
|
|
879
879
|
**Use when:** you want to isolate the orchestrating job from its dependencies.
|
|
880
880
|
|
|
881
|
-
#### Jobs that wait on approval
|
|
881
|
+
#### Jobs that wait on approval (Beta)
|
|
882
882
|
|
|
883
883
|
`.wait()` calls delegate to `tailor.workflow.wait`. With the `tailor-runtime` environment active, use `mockWorkflow().waitPoint(definition)` to drive each branch with a typed mock:
|
|
884
884
|
|
|
@@ -921,7 +921,7 @@ describe("processWithApproval", () => {
|
|
|
921
921
|
|
|
922
922
|
The lower-level `setWaitHandler` and `waitCalls` APIs remain available when one handler must cover several wait points.
|
|
923
923
|
|
|
924
|
-
#### Running a full workflow locally
|
|
924
|
+
#### Running a full workflow locally (Beta)
|
|
925
925
|
|
|
926
926
|
To exercise the full chain with real job bodies, call `runWorkflowLocally(workflow, args)`. Dependent jobs run their real `.body()` functions, and start args/results cross the same JSON boundary as the platform, so a non-serializable payload fails the test exactly as it would in production:
|
|
927
927
|
|