@tailor-platform/sdk 2.27.0 → 2.29.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.
Files changed (184) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/README.md +0 -8
  3. package/agent-skills/tailor/SKILL.md +4 -2
  4. package/dist/application-dalGHdrI.mjs +1 -0
  5. package/dist/application-rWYDAfNU.mjs +200 -0
  6. package/dist/application-rWYDAfNU.mjs.map +1 -0
  7. package/dist/cli/cache/bundle-cache.d.mts +1 -0
  8. package/dist/cli/commands/executor/get.d.mts +2 -1
  9. package/dist/cli/commands/executor/jobs.d.mts +2 -1
  10. package/dist/cli/commands/executor/trigger.d.mts +2 -1
  11. package/dist/cli/commands/function/get.d.mts +2 -1
  12. package/dist/cli/commands/function/list.d.mts +2 -1
  13. package/dist/cli/commands/organization/folder/create.d.mts +2 -1
  14. package/dist/cli/commands/organization/folder/delete.d.mts +2 -1
  15. package/dist/cli/commands/organization/folder/get.d.mts +2 -1
  16. package/dist/cli/commands/organization/folder/list.d.mts +2 -1
  17. package/dist/cli/commands/organization/folder/update.d.mts +2 -1
  18. package/dist/cli/commands/organization/get.d.mts +2 -1
  19. package/dist/cli/commands/organization/tree.d.mts +2 -1
  20. package/dist/cli/commands/organization/update.d.mts +2 -1
  21. package/dist/cli/commands/show.d.mts +2 -1
  22. package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +2 -1
  23. package/dist/cli/commands/tailordb/migrate/snapshot-types.d.mts +3 -1
  24. package/dist/cli/commands/workflow/executions.d.mts +2 -1
  25. package/dist/cli/commands/workflow/get.d.mts +2 -1
  26. package/dist/cli/commands/workflow/start.d.mts +2 -1
  27. package/dist/cli/commands/workspace/app/health.d.mts +2 -1
  28. package/dist/cli/commands/workspace/app/list.d.mts +2 -1
  29. package/dist/cli/commands/workspace/create.d.mts +5 -2
  30. package/dist/cli/commands/workspace/delete.d.mts +2 -1
  31. package/dist/cli/commands/workspace/get.d.mts +2 -1
  32. package/dist/cli/commands/workspace/restore.d.mts +2 -1
  33. package/dist/cli/commands/workspace/user/invite.d.mts +2 -1
  34. package/dist/cli/commands/workspace/user/list.d.mts +2 -1
  35. package/dist/cli/commands/workspace/user/remove.d.mts +2 -1
  36. package/dist/cli/commands/workspace/user/update.d.mts +2 -1
  37. package/dist/cli/lib.d.mts +1 -1
  38. package/dist/cli/lib.mjs +1 -1
  39. package/dist/cli/lib.mjs.map +1 -1
  40. package/dist/cli/main.d.mts +7 -0
  41. package/dist/cli/main.mjs +55 -42
  42. package/dist/cli/main.mjs.map +1 -1
  43. package/dist/cli/query/index.d.mts +2 -1
  44. package/dist/cli/shared/args.d.mts +4 -2
  45. package/dist/cli/shared/client.d.mts +6 -1
  46. package/dist/cli/shared/context.d.mts +2 -1
  47. package/dist/cli/shared/logger.d.mts +5 -2
  48. package/dist/cli/shared/readonly-guard.d.mts +2 -1
  49. package/dist/cli/shared/seed-context.d.mts +5 -0
  50. package/dist/cli/shared/type-generator.d.mts +2 -1
  51. package/dist/cli/ts-hook.mjs +15 -11
  52. package/dist/completion/zsh-worker.zsh +275 -19
  53. package/dist/configure/config/types.d.mts +80 -1
  54. package/dist/configure/services/aigateway/types.d.mts +2 -1
  55. package/dist/configure/services/auth/types.d.mts +2 -1
  56. package/dist/configure/services/executor/executor.d.mts +2 -1
  57. package/dist/configure/services/executor/operation.d.mts +2 -1
  58. package/dist/configure/services/executor/trigger/event.d.mts +2 -1
  59. package/dist/configure/services/executor/trigger/schedule.d.mts +2 -1
  60. package/dist/configure/services/http-adapter/http-adapter.d.mts +2 -1
  61. package/dist/configure/services/idp/permission.d.mts +2 -1
  62. package/dist/configure/services/idp/types.d.mts +2 -1
  63. package/dist/configure/services/resolver/permission.d.mts +2 -1
  64. package/dist/configure/services/resolver/resolver.d.mts +2 -1
  65. package/dist/configure/services/secrets/types.d.mts +2 -1
  66. package/dist/configure/services/staticwebsite/types.d.mts +2 -1
  67. package/dist/configure/services/tailordb/permission.d.mts +2 -1
  68. package/dist/configure/services/tailordb/schema.d.mts +4 -3
  69. package/dist/configure/services/tailordb/types.d.mts +12 -1
  70. package/dist/configure/services/workflow/execution-policy.types.d.mts +2 -1
  71. package/dist/configure/services/workflow/job.d.mts +2 -1
  72. package/dist/configure/services/workflow/wait-point.d.mts +2 -1
  73. package/dist/configure/types/field.types.d.mts +2 -1
  74. package/dist/configure/types/permission-operand.types.d.mts +2 -1
  75. package/dist/configure/types/type.d.mts +2 -1
  76. package/dist/{crashreport-DFpRn-LS.mjs → crashreport-BAG8c7hB.mjs} +4 -4
  77. package/dist/{crashreport-DFpRn-LS.mjs.map → crashreport-BAG8c7hB.mjs.map} +1 -1
  78. package/dist/crashreport-BcXuocwI.mjs +1 -0
  79. package/dist/{errors-CpvSChyL.mjs → errors-D3qpNHR6.mjs} +2 -2
  80. package/dist/{errors-CpvSChyL.mjs.map → errors-D3qpNHR6.mjs.map} +1 -1
  81. package/dist/kysely-type-BWMfV301.mjs +48 -0
  82. package/dist/kysely-type-BWMfV301.mjs.map +1 -0
  83. package/dist/logger-DuaT2zY3.mjs +9 -0
  84. package/dist/logger-DuaT2zY3.mjs.map +1 -0
  85. package/dist/package-json-D1BRkjtr.mjs +1 -0
  86. package/dist/parser/service/tailordb/types.d.mts +3 -1
  87. package/dist/plugin/builtin/enum-constants/index.d.mts +2 -1
  88. package/dist/plugin/builtin/file-utils/index.d.mts +2 -1
  89. package/dist/plugin/builtin/kysely-type/index.d.mts +2 -1
  90. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  91. package/dist/plugin/builtin/seed/index.d.mts +11 -1
  92. package/dist/plugin/builtin/seed/index.mjs +1 -1
  93. package/dist/register-ts-hook-CkfKqhJT.mjs +1014 -0
  94. package/dist/register-ts-hook-CkfKqhJT.mjs.map +1 -0
  95. package/dist/runtime/aigateway.mjs +1 -1
  96. package/dist/runtime/authconnection.mjs +1 -1
  97. package/dist/runtime/context.d.mts +2 -1
  98. package/dist/runtime/context.mjs +1 -1
  99. package/dist/runtime/date.d.mts +2 -1
  100. package/dist/runtime/field-parse.d.mts +2 -1
  101. package/dist/runtime/file.d.mts +2 -1
  102. package/dist/runtime/file.mjs +1 -1
  103. package/dist/runtime/globals.d.mts +2 -1
  104. package/dist/runtime/iconv.mjs +1 -1
  105. package/dist/runtime/idp.mjs +1 -1
  106. package/dist/runtime/logger.mjs +1 -1
  107. package/dist/runtime/secretmanager.mjs +1 -1
  108. package/dist/runtime/types.d.mts +2 -1
  109. package/dist/runtime/workflow.d.mts +2 -1
  110. package/dist/runtime/workflow.mjs +1 -1
  111. package/dist/schema-B6hJQWp6.mjs.map +1 -1
  112. package/dist/seed/index.d.mts +17 -4
  113. package/dist/seed/index.mjs +6 -6
  114. package/dist/seed/index.mjs.map +1 -1
  115. package/dist/seed-C3P_T_Eh.mjs.map +1 -1
  116. package/dist/{service-Cc7OY0Oh.mjs → service-CETr0AKY.mjs} +3 -3
  117. package/dist/service-CETr0AKY.mjs.map +1 -0
  118. package/dist/service-DREWLYxD.mjs +1 -0
  119. package/dist/{service-DFmt9UJP.mjs → service-b1g22NN9.mjs} +2 -2
  120. package/dist/{service-DFmt9UJP.mjs.map → service-b1g22NN9.mjs.map} +1 -1
  121. package/dist/service_pb-Bbu2QSIr.mjs +2 -0
  122. package/dist/service_pb-Bbu2QSIr.mjs.map +1 -0
  123. package/dist/service_pb-urxAIYHT.mjs +1 -0
  124. package/dist/tailor-proto/src/tailor/v1/events_pb.d.mts +53 -1
  125. package/dist/tailor-proto/src/tailor/v1/events_resource_pb.d.mts +33 -0
  126. package/dist/tailor-proto/src/tailor/v1/function_resource_pb.d.mts +0 -8
  127. package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +18 -1
  128. package/dist/tailor-proto/src/tailor/v1/tailordb_resource_pb.d.mts +4 -0
  129. package/dist/telemetry-DpkFGBmu.mjs +1 -0
  130. package/dist/{telemetry-Bklv9kQY.mjs → telemetry-Xk7J829P.mjs} +2 -2
  131. package/dist/{telemetry-Bklv9kQY.mjs.map → telemetry-Xk7J829P.mjs.map} +1 -1
  132. package/dist/types/tailordb.generated.d.mts +2 -0
  133. package/dist/vitest/index.mjs +1 -1
  134. package/dist/vitest/index.mjs.map +1 -1
  135. package/dist/vitest/mock-kysely.d.mts +2 -1
  136. package/dist/vitest/mocks/aigateway.d.mts +2 -1
  137. package/dist/vitest/mocks/authconnection.d.mts +2 -1
  138. package/dist/vitest/mocks/file.d.mts +2 -1
  139. package/dist/vitest/mocks/iconv.d.mts +2 -1
  140. package/dist/vitest/mocks/idp.d.mts +2 -1
  141. package/dist/vitest/mocks/logger.d.mts +2 -1
  142. package/dist/vitest/mocks/secretmanager.d.mts +2 -1
  143. package/dist/vitest/mocks/tailordb-pglite.d.mts +2 -1
  144. package/dist/vitest/mocks/tailordb.d.mts +2 -1
  145. package/dist/vitest/mocks/workflow.d.mts +3 -1
  146. package/dist/vitest/pglite-kysely.d.mts +2 -1
  147. package/dist/vitest/workflow-local.d.mts +2 -1
  148. package/dist/{workspace_resource_pb-ChWejcj0.mjs → workspace_resource_pb-CaoM2npP.mjs} +2 -2
  149. package/dist/{workspace_resource_pb-ChWejcj0.mjs.map → workspace_resource_pb-CaoM2npP.mjs.map} +1 -1
  150. package/docs/cli/application.md +21 -3
  151. package/docs/cli/data.md +105 -0
  152. package/docs/cli/secret.md +6 -6
  153. package/docs/cli/tailordb.md +52 -16
  154. package/docs/cli/user.md +37 -7
  155. package/docs/cli/workspace.md +9 -1
  156. package/docs/cli-reference.md +36 -3
  157. package/docs/configuration.md +62 -2
  158. package/docs/github-actions.md +179 -13
  159. package/docs/multi-environment.md +24 -0
  160. package/docs/plugin/custom.md +1 -1
  161. package/docs/services/idp.md +1 -1
  162. package/docs/services/resolver.md +1 -58
  163. package/docs/services/tailordb-migration.md +80 -12
  164. package/docs/services/tailordb.md +17 -4
  165. package/docs/services/workflow.md +2 -2
  166. package/docs/testing.md +3 -2
  167. package/package.json +22 -22
  168. package/dist/application-DFeuELRo.mjs +0 -200
  169. package/dist/application-DFeuELRo.mjs.map +0 -1
  170. package/dist/application-nT_4a5HO.mjs +0 -1
  171. package/dist/crashreport-CBaRJl5h.mjs +0 -1
  172. package/dist/kysely-type-RDX59E1j.mjs +0 -48
  173. package/dist/kysely-type-RDX59E1j.mjs.map +0 -1
  174. package/dist/logger-Db-YRTwK.mjs +0 -9
  175. package/dist/logger-Db-YRTwK.mjs.map +0 -1
  176. package/dist/package-json-CmqhDJ__.mjs +0 -1
  177. package/dist/register-ts-hook-D6KXrGrB.mjs +0 -1009
  178. package/dist/register-ts-hook-D6KXrGrB.mjs.map +0 -1
  179. package/dist/service-Cc7OY0Oh.mjs.map +0 -1
  180. package/dist/service-sH2MZ4_W.mjs +0 -1
  181. package/dist/service_pb-Dw2ilc1O.mjs +0 -2
  182. package/dist/service_pb-Dw2ilc1O.mjs.map +0 -1
  183. package/dist/service_pb-GDOqfwb6.mjs +0 -1
  184. package/dist/telemetry-DXHP3VVi.mjs +0 -1
@@ -35,7 +35,8 @@ tailor setup ci tag --name my-app-prod \
35
35
 
36
36
  After running the command, follow the **Next steps** printed to the terminal:
37
37
  run `tailor setup ci env` to get the commands that set the secrets and
38
- variables each GitHub Environment needs (see
38
+ variables each GitHub Environment needs, and `tailor setup ci env --check` to
39
+ confirm none is missing (see
39
40
  [Setting secrets and variables](#setting-secrets-and-variables)), then commit
40
41
  the generated files.
41
42
 
@@ -98,7 +99,8 @@ The generated workflow builds one self-contained ERD viewer HTML file for each
98
99
  owned TailorDB namespace in `tailor.config.ts`. The viewer compares the pull
99
100
  request merge result with the base branch, can switch between the current schema
100
101
  and highlighted diff, uploads the HTML files as unarchived Actions artifacts,
101
- and upserts a PR comment with artifact links.
102
+ and upserts a PR comment with artifact links. On GitHub Enterprise Server, also
103
+ pass `--ghes` (see [GitHub Enterprise Server](#github-enterprise-server)).
102
104
 
103
105
  ERD preview does not use Tailor Platform credentials. Fork pull requests still
104
106
  build artifacts, but the comment step is skipped because fork tokens cannot
@@ -194,6 +196,26 @@ the `plan` job as well as `deploy`, because `plan` must enter the environment to
194
196
  read the variable. (A token-based read that lets `plan` run without entering the
195
197
  environment is planned.)
196
198
 
199
+ ## Package manager
200
+
201
+ The generated workflow sets up your package manager, installs dependencies with
202
+ it, and runs `tailor` through it. `setup ci` detects the package manager from the
203
+ lockfile at the repository root (`pnpm-lock.yaml`, `yarn.lock`, `bun.lock`,
204
+ `bun.lockb`, `package-lock.json`, or `npm-shrinkwrap.json`), or, when there is
205
+ none, from the `packageManager` or `devEngines.packageManager` field of the root
206
+ `package.json`. When neither names one, `setup ci` stops instead of guessing.
207
+
208
+ Pass `--package-manager` (`pnpm`, `npm`, `yarn`, or `bun`) to choose it
209
+ yourself, for example to generate the workflow before the lockfile exists:
210
+
211
+ ```bash
212
+ tailor setup ci branch --name my-app-stg --package-manager pnpm
213
+ ```
214
+
215
+ The workflow installs dependencies at the repository root, so commit the
216
+ lockfile there before it runs. `tailor setup update` keeps a package manager
217
+ chosen with `--package-manager` and detects it again otherwise.
218
+
197
219
  ## Generated files
198
220
 
199
221
  Running a workflow setup subcommand creates or updates:
@@ -244,8 +266,8 @@ Everything else is yours, and re-running `setup` keeps it:
244
266
  - **These inputs of managed steps:** `ignore` on `tailor-generate-check` and
245
267
  `tailor-drift-check`, `fail-on-drift` on `tailor-drift-check`,
246
268
  `install-command` on `tailor-install`, `node-version-file` on
247
- `tailor-setup`, `label` on `tailor-plan`, and `user-mapping` on
248
- `tailor-notify`.
269
+ `tailor-setup`, `label` on `tailor-plan`, `ttl` on `tailor-preview-deploy`,
270
+ and `user-mapping` on `tailor-notify`.
249
271
  - **Your own jobs in the `needs` of `tailor-result` and
250
272
  `tailor-preview-result`**, so they count toward the workflow's result (see
251
273
  [Requiring the workflow in branch protection](#requiring-the-workflow-in-branch-protection)).
@@ -276,6 +298,17 @@ The job is skipped whenever `tailor-preview-deploy` is skipped: when a pull
276
298
  request is closed, for draft and fork pull requests, and for unlabeled ones with
277
299
  `--require-preview-label`.
278
300
 
301
+ A preview workspace expires 7 days (`ttl: 7d` on `tailor-preview-deploy`) after
302
+ the last push to its pull request. When a pull request is closed,
303
+ `tailor-preview-cleanup` deletes its workspace and also deletes the other
304
+ expired preview workspaces of the same workspace name in the folder
305
+ (`TAILOR_PLATFORM_FOLDER_ID`, or directly under the organization when unset), so
306
+ a workspace whose own cleanup never ran does not stay forever. A pull request
307
+ that stays idle past the `ttl` can have its workspace deleted while it is still
308
+ open; the next push creates a fresh one. Change `ttl` to keep previews longer or
309
+ shorter. Workflows generated before this was added get these settings the next
310
+ time you run `tailor setup update`.
311
+
279
312
  Each new preview workflow run for a pull request, such as after a push or when
280
313
  the pull request is closed, cancels the run still in progress for it, including
281
314
  your jobs, so they do not keep running against a workspace that the new run
@@ -448,14 +481,18 @@ protection rules no longer guard your deploys.
448
481
  Environment the generated workflows use, the commands that create the
449
482
  environment (only when it does not exist yet) and set its secrets and
450
483
  variables. It is read-only, so re-run it whenever you add a target. When the
451
- `origin` remote points at github.com, the output names that repository, so the
452
- commands work from any directory; otherwise `gh` resolves the repository from
453
- the current directory and the Terraform output leaves it as a placeholder.
484
+ `origin` remote points at github.com, the output names that repository;
485
+ otherwise it names the repository `gh repo view` reports for the current
486
+ directory, with its host on GitHub Enterprise Server. Either way the commands
487
+ work from any directory. When neither names a repository, `gh` resolves it from
488
+ the current directory when you run the commands, and the Terraform output leaves
489
+ it as a placeholder.
454
490
 
455
491
  ```bash
456
492
  tailor setup ci env # gh CLI commands (default)
457
493
  tailor setup ci env --format terraform # Terraform for the integrations/github provider
458
494
  tailor setup ci env --environment my-app-stg # only one environment (repeat for several)
495
+ tailor setup ci env --check # check what is registered in GitHub (see below)
459
496
  ```
460
497
 
461
498
  The list follows what each generated workflow actually reads:
@@ -497,6 +534,30 @@ protection settings (reviewers, wait timer, branch policy), so importing an
497
534
  environment keeps the approval gate you configured; remove the `lifecycle` block
498
535
  to manage those settings in Terraform instead.
499
536
 
537
+ ### Checking what is registered
538
+
539
+ `tailor setup ci env --check` reads GitHub with `gh` and reports, for each
540
+ environment, which of these entries are registered. It fails when a required
541
+ entry is missing, reports missing optional entries without failing, and prints
542
+ the `gh` commands that set only the missing entries, with where each value comes
543
+ from. It changes nothing, and it checks names only, since GitHub never returns
544
+ secret values.
545
+
546
+ - An entry set on the repository, or on the organization and shared with the
547
+ repository, counts as registered, because the workflow reads it from there when
548
+ the environment does not set it. The report says where each entry was found.
549
+ - An entry registered as the other kind, such as `TAILOR_PLATFORM_WORKSPACE_ID`
550
+ set as a secret, is reported as missing, because the workflow does not read it.
551
+ `TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID` set as a secret still counts, with a
552
+ reminder to move it to a variable.
553
+ - `gh` must be installed and logged in (`gh auth login`) as an account with write
554
+ access to the repository, which listing secrets and variables requires. GitHub
555
+ answers 404 for a private repository the account cannot see, so the check
556
+ reports that as an access problem, not as missing entries.
557
+
558
+ `--environment` narrows the check the same way. `--check` cannot be combined with
559
+ `--format terraform`.
560
+
500
561
  ## GitHub Environments (approval gate)
501
562
 
502
563
  Both the `plan` and `deploy` jobs are associated with a GitHub Environment — the
@@ -553,6 +614,67 @@ plan job also enters the environment, allow `refs/pull/*/merge` as well so pull
553
614
  request plans keep running, and note that dry runs from other branches are then
554
615
  rejected too.
555
616
 
617
+ ## Seeding the workspace after deploy
618
+
619
+ Pass `--seed` to `setup ci branch`, `setup ci tag`, or `setup ci preview` to seed
620
+ the workspace with `tailor seed apply` right after each deploy. Add
621
+ `--seed-dataset <name>` (repeatable, implies `--seed`) to apply
622
+ [datasets](https://github.com/tailor-platform/sdk/tree/main/packages/sdk-plugin-seed#datasets)
623
+ on top of the base seed data:
624
+
625
+ ```bash
626
+ tailor setup ci branch --seed-dataset stg
627
+ tailor setup ci preview --region us-west --seed-dataset preview
628
+ ```
629
+
630
+ The deploy job gains a `tailor-seed` step after the deploy step. Branch targets
631
+ validate seed data before deploy and also in the PR or dry-run plan job; tag
632
+ targets validate it in the plan job before deploy. Both use
633
+ `tailor seed validate --upsert` with the selected datasets to check the `id`
634
+ and required fields that `seed apply --upsert` needs. Preview targets validate
635
+ and apply the data after deploy.
636
+
637
+ | Target | Seeds with | Effect |
638
+ | ----------- | ------------------------------------ | ----------------------------------------------------------------------------- |
639
+ | branch, tag | `tailor seed apply --upsert` | The fields of each committed seed row are written; other rows are kept |
640
+ | preview | `tailor seed apply --truncate --yes` | The preview workspace is emptied, IdP users included, and seeded from scratch |
641
+
642
+ Setup stops unless the config uses `seedPlugin` with `machineUserName`, which
643
+ names the machine user that seeds, and an existing `<distPath>/data/` directory.
644
+ Every `--seed-dataset` must name an entry of
645
+ the `seedPlugin` `datasets` option or a directory under `<distPath>/data/`.
646
+ Commit the dataset directories the workflow seeds: a gitignored dataset such as
647
+ a per-developer `local/` is not in the CI checkout.
648
+ Install `@tailor-platform/sdk-plugin-seed` so the `tailor seed` commands resolve
649
+ in CI. For branch and tag targets, use a version that supports
650
+ `tailor seed validate --upsert`; update the setup and seed plugins together.
651
+ Give every TailorDB seed row an `id` and
652
+ every field its table requires: `--upsert` matches rows by id, so run
653
+ `tailor seed fill` and commit the result.
654
+ Seeding is available for a target that deploys one app.
655
+
656
+ Before enabling it, note that:
657
+
658
+ - Every branch or tag deploy writes the fields of the committed seed rows over
659
+ the workspace, `_User` rows and their passwords included, so an edit to one of
660
+ those fields in the workspace is undone. Each row is updated like any other update: update hooks
661
+ and validation run, `updatedAt` changes, and executors with
662
+ `recordUpdatedTrigger` fire once per seeded row on every deploy.
663
+ - Every preview deploy deletes the data reviewers added to the preview
664
+ workspace. That is every push, and with `--require-preview-label` also every
665
+ label added to the pull request, since each one redeploys the preview.
666
+ - A seed failure fails the deploy job and its notification, although the deploy
667
+ itself went through.
668
+ - The step shares the deploy job's timeout. IdP users take about a second each.
669
+ - Steps you added after the deploy step stay right after it, so they run before
670
+ `tailor-seed`. Move a step below `tailor-seed` when it needs the seeded data,
671
+ and move it again if you regenerate without `--seed` and then with it.
672
+ - `tailor-seed` is an SDK-managed step. To seed another way (a different mode,
673
+ `--skip-idp`, a condition), leave `--seed` off and add your own step.
674
+
675
+ Re-run `setup ci` with the flags each time you regenerate the workflow; `setup
676
+ update` keeps them.
677
+
556
678
  ## Monorepo setup
557
679
 
558
680
  For a monorepo where your SDK app lives in a subdirectory, pass `--dir`:
@@ -673,8 +795,9 @@ a branch target deploys only its target branch, so use Option 1 there.
673
795
  - **Schema and data are not rolled back.** If the older config expects a schema
674
796
  state that no longer exists, the plan may show errors. In that case, review
675
797
  the diff carefully before proceeding.
676
- - **Seed data** is not part of the deployment pipeline and is unaffected by
677
- rollbacks.
798
+ - **Seed data** is not rolled back. Without `--seed`, the pipeline does not
799
+ touch it; with `--seed`, deploying an older commit seeds the rows committed
800
+ there on top of the current data.
678
801
  - **Static websites** are not yet integrated into the generated pipeline.
679
802
  Static asset rollbacks must be performed manually.
680
803
 
@@ -702,6 +825,48 @@ tailor setup ci env
702
825
 
703
826
  Commit both workflow files and `.github/tailor.lock`.
704
827
 
828
+ ## GitHub Enterprise Server
829
+
830
+ The generated workflows also run on GitHub Enterprise Server. Prepare the
831
+ server and its runners first:
832
+
833
+ - **Runners:** GitHub Enterprise Server has no GitHub-hosted runners. Change
834
+ `runs-on` on the managed jobs to the labels of your self-hosted runners;
835
+ re-running `setup` keeps it (see
836
+ [Customizing the generated workflow](#customizing-the-generated-workflow)).
837
+ The runners need Actions runner 2.327.1 or later, which runs the Node 24
838
+ actions the workflows use, and `gh` and `jq` on `PATH` (plus `node` with
839
+ `--erd-preview`).
840
+ - **Actions from github.com:** the workflows use `tailor-platform/actions`,
841
+ `actions/checkout`, `actions/setup-node`, `pnpm/action-setup`, and
842
+ `oven-sh/setup-bun` (and `actions/upload-artifact` with `--erd-preview`).
843
+ Make them available on the server with GitHub Connect or `actions-sync`. The
844
+ server ships its own copies of the `actions/*` ones, which may not include
845
+ the pinned versions: update them with `actions-sync`, or, with GitHub
846
+ Connect, delete them from the server's `actions` organization (see GitHub's
847
+ [Using the latest version of the official bundled actions](https://docs.github.com/en/enterprise-server@latest/admin/managing-github-actions-for-your-enterprise/managing-access-to-actions-from-githubcom/using-the-latest-version-of-the-official-bundled-actions)).
848
+ - **Environment settings:** sign `gh` in to the server
849
+ (`gh auth login --hostname <host>`) before you run `tailor setup ci env` in a
850
+ clone of the repository, so that the `gh` commands it prints and its Terraform
851
+ output target the server. When `gh` cannot tell the repository, the output
852
+ says to export `GH_HOST=<host>` before running the `gh` commands, or
853
+ `GITHUB_BASE_URL=https://<host>/` before running Terraform, so that they
854
+ call the server instead of github.com.
855
+
856
+ With `--erd-preview`, also pass `--ghes`:
857
+
858
+ ```bash
859
+ tailor setup ci branch --name my-app-stg --erd-preview --ghes
860
+ ```
861
+
862
+ GitHub Enterprise Server does not support `actions/upload-artifact` v4 or
863
+ later, which the workflow uses to upload each ERD viewer unzipped. With
864
+ `--ghes`, the workflow uploads it with v3 instead, so each viewer is a zip
865
+ file, and the PR comment links to the workflow run, which lists the artifacts.
866
+ `setup update` keeps `--ghes`; pass it again whenever you re-run
867
+ `setup ci branch`, or the workflow switches back to the upload that GitHub
868
+ Enterprise Server does not support.
869
+
705
870
  ## Keeping dependencies and actions updated
706
871
 
707
872
  Run this command once from the repository root to add Renovate configuration:
@@ -781,10 +946,11 @@ It regenerates every workflow recorded in
781
946
  have to re-type `setup ci branch`, `setup ci tag`, and the rest one by one.
782
947
  Your own jobs, steps, and settings are kept (see
783
948
  [Customizing the generated workflow](#customizing-the-generated-workflow)). A
784
- branch that was detected from the repository default branch is detected again,
785
- and the parts that follow your config — the migration drift check, seed
786
- validation, and ERD preview namespaces — are derived
787
- from the current `tailor.config.ts`.
949
+ branch that was detected from the repository default branch and a
950
+ [package manager](#package-manager) that was detected from the repository root
951
+ are detected again, and the parts that follow your config — the migration drift
952
+ check, seed validation, and ERD preview namespaces — are derived from the
953
+ current `tailor.config.ts`.
788
954
 
789
955
  A target that cannot be regenerated, for example because you edited a managed
790
956
  part, does not stop the others: `update` regenerates the rest, then lists the
@@ -96,3 +96,27 @@ defineStaticWebSite("my-website", {
96
96
  customDomains: process.env.APP_ENV === "production" ? ["app.example.com"] : undefined,
97
97
  });
98
98
  ```
99
+
100
+ ## Seed data per environment and per scenario
101
+
102
+ `seedPlugin` seeds every environment from the JSONL files in `<distPath>/data/`. Rows that only some environments or scenarios need go into **datasets** — directories under `<distPath>/data/` that hold JSONL files for some of the tables — and are applied on top of the base seed data with `tailor seed apply --dataset <name>`:
103
+
104
+ ```
105
+ seed/data/
106
+ *.schema.ts, *.jsonl # base seed data, applied everywhere
107
+ cogs/Item.jsonl # scenario: rows only a costing demo needs
108
+ dev/Account.jsonl # environment: the dev variant of a table
109
+ stg/Account.jsonl # environment: the staging variant of the same table
110
+ local/_User.jsonl # per developer, gitignored
111
+ ```
112
+
113
+ A table whose rows differ per environment lives only in the per-environment datasets, since a row may be defined in only one of the selected files. Name the combinations each environment uses in the `datasets` option of `seedPlugin`, so a deploy pipeline runs `tailor seed apply --dataset preview` instead of repeating the list:
114
+
115
+ ```typescript
116
+ seedPlugin({
117
+ distPath: "./seed",
118
+ datasets: { preview: ["cogs", "dev"] },
119
+ });
120
+ ```
121
+
122
+ The `tailor seed` commands come from the `@tailor-platform/sdk-plugin-seed` CLI plugin; see its [Datasets](https://github.com/tailor-platform/sdk/tree/main/packages/sdk-plugin-seed#datasets) guide for validating, filling, and dumping datasets.
@@ -516,9 +516,9 @@ export default createSoftDeletePlugin();
516
516
  ```typescript
517
517
  // plugins/soft-delete/executors/on-delete.ts
518
518
  import { createExecutor, recordDeletedTrigger } from "@tailor-platform/sdk";
519
- import type { TailorAnyDBType } from "@tailor-platform/sdk";
520
519
  import { withPluginContext } from "@tailor-platform/sdk/plugin";
521
520
  import { getDB } from "generated/tailordb";
521
+ import type { TailorAnyDBType } from "@tailor-platform/sdk";
522
522
 
523
523
  interface SoftDeleteContext {
524
524
  sourceTable: TailorAnyDBType;
@@ -106,7 +106,7 @@ defineIdp("my-idp", {
106
106
  **Operators:** `"="`, `"!="`, `"in"`, `"not in"`
107
107
 
108
108
  > [!WARNING]
109
- > A policy of `[{ user: "_loggedIn" }, "=", true]` permits every authenticated user to run the operation against any IdP user. For `read` and `update` this exposes and lets them change other users' login information (such as email addresses), and for `sendPasswordResetEmail` it lets them send reset emails to anyone. Use it only when every user who can sign in to the project is trusted. For an IdP used by external users (such as business partners), restrict each operation to an administrator role as shown above.
109
+ > A policy of `[{ user: "_loggedIn" }, "=", true]` permits every authenticated user to run the operation against any IdP user. For `read` and `update` this exposes and lets them change other users' login information (such as email addresses), and for `sendPasswordResetEmail` it lets them send reset emails to anyone. Use it only when every user who can sign in to the project is trusted. For an IdP used by external users (such as business partners), restrict each operation to an administrator role as shown above. `tailor deploy` warns about each rule whose only condition is `_loggedIn`.
110
110
 
111
111
  **Helper:** `unsafeAllowAllIdPPermission` grants full access without conditions. Intended only for development and testing.
112
112
 
@@ -11,64 +11,7 @@ Resolvers provide:
11
11
  - Access to TailorDB via Kysely query builder
12
12
  - User context for authentication/authorization
13
13
 
14
- ## Comparison with Tailor Platform Pipeline Resolver
15
-
16
- The SDK's Resolver is a simplified version of Tailor Platform's [Pipeline Resolver](https://docs.tailor.tech/guides/resolver).
17
-
18
- | Pipeline Resolver | SDK Resolver |
19
- | ---------------------------------------- | --------------------------------- |
20
- | Multiple steps with different operations | Single `body` function |
21
- | Declarative step configuration | Imperative TypeScript code |
22
- | Built-in TailorDB/GraphQL steps | Direct database access via Kysely |
23
- | CEL expressions for data transformation | Native TypeScript transformations |
24
-
25
- ### Example Comparison
26
-
27
- **Pipeline Resolver (Tailor Platform native):**
28
-
29
- ```yaml
30
- steps:
31
- - name: getUser
32
- operation: tailordb.query
33
- params:
34
- type: User
35
- filter:
36
- email: { eq: "{{ input.email }}" }
37
- - name: updateAge
38
- operation: tailordb.mutation
39
- params:
40
- type: User
41
- id: "{{ steps.getUser.id }}"
42
- input:
43
- age: "{{ steps.getUser.age + 1 }}"
44
- ```
45
-
46
- **Resolver (SDK):**
47
-
48
- ```typescript
49
- createResolver({
50
- name: "incrementUserAge",
51
- operation: "mutation",
52
- input: { email: t.string() },
53
- body: async (context) => {
54
- const db = getDB("tailordb");
55
- const user = await db
56
- .selectFrom("User")
57
- .selectAll()
58
- .where("email", "=", context.input.email)
59
- .executeTakeFirstOrThrow();
60
-
61
- await db
62
- .updateTable("User")
63
- .set({ age: user.age + 1 })
64
- .where("id", "=", user.id)
65
- .execute();
66
-
67
- return { oldAge: user.age, newAge: user.age + 1 };
68
- },
69
- output: t.object({ oldAge: t.int(), newAge: t.int() }),
70
- });
71
- ```
14
+ For the official Tailor Platform documentation, see [Resolver Guide](https://docs.tailor.tech/guides/resolver).
72
15
 
73
16
  ## Creating a Resolver
74
17
 
@@ -419,13 +419,13 @@ A script with a single step runs like a `main` script: one job and one transacti
419
419
  Because steps commit separately, a failure does not undo the steps that already completed:
420
420
 
421
421
  - If the first step fails before any other step has completed, the migration fails like a `main` script: the pre-migration schema changes are rolled back.
422
- - If a step fails after another step has completed, `tailor deploy` stops and leaves the migration **in progress**. The pre-migration schema stays in place, the checkpoint is not advanced, and the namespace's tables stay in maintenance mode, as while the migration runs: GraphQL create, update, and delete operations, bulk upsert, and record events remain disabled, while `read` keeps its setting. `tailor tailordb migration status` reports the migration as in progress.
422
+ - If a step fails after another step has completed, `tailor deploy` stops and leaves the migration **in progress**. The pre-migration schema stays in place and the checkpoint is not advanced. With [maintenance mode](#maintenance-mode) on, the namespace's tables stay in it, as while the migration runs: GraphQL create, update, and delete operations, bulk upsert, and record events remain disabled, while `read` keeps its setting. `tailor tailordb migration status` reports the migration as in progress.
423
423
  - If `tailor deploy` cannot confirm whether the steps started, for example because the connection dropped while starting them, the migration also stays in progress. Run `tailor deploy` again; it checks whether the steps ran before continuing.
424
424
  - Fix the failing step and run `tailor deploy` again. The deploy resumes the migration: completed steps do not run again, and the failed step and the steps after it run with the updated script. A completed step is treated like an applied migration — editing it afterwards does not run it again.
425
425
  - 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).
426
426
  - 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.
427
427
  - 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.
428
- - 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. Finish the migration with `tailor deploy` first. The one exception is `migration set <N>` with the in-progress migration's own number: it marks that migration as completed and clears its in-progress record. Use it only when the migration's schema changes are already applied, for example after the deploy reported that the checkpoint write could not be confirmed.
428
+ - 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. Finish the migration with `tailor deploy` first, or settle it with `migration resolve` (see [Settling a migration that stays in progress](#settling-a-migration-that-stays-in-progress)). The one exception is `migration set <N>` with the in-progress migration's own number: it marks that migration as completed and clears its in-progress record. Use it only when the migration's schema changes are already applied, for example after the deploy reported that the checkpoint write could not be confirmed.
429
429
 
430
430
  #### Editing steps of a migration in progress
431
431
 
@@ -448,7 +448,16 @@ tailor deploy --migration-skip-steps main-db/backfillUser,main-db/backfillInvoic
448
448
  - The option applies to the migration an earlier deploy left in progress. Deploy fails before changing anything when an entry names a step that did not succeed, a step the migration does not define, a namespace without a migration in progress, or a namespace that no deployed config defines. It also fails when the earlier run's step results cannot be read, because then it cannot confirm which steps succeeded; deploy again to retry.
449
449
  - It takes effect for the deploy you pass it to. If that run fails, the next `tailor deploy` resumes it as usual, and the skipped steps stay completed without passing the option again.
450
450
 
451
- Write every step so that running it again is safe: besides the cases above, a step can run a second time when a deploy is interrupted right after it commits. Use `where` clauses that skip rows a step already migrated, as in the example. Maintenance mode blocks GraphQL writes, not the writes your resolvers, executors, or workflows make through Kysely; rows they write while a migration is in progress are not seen by steps that already completed, so stop such writers until the migration finishes or write steps that tolerate them.
451
+ Write every step so that running it again is safe: besides the cases above, a step can run a second time when a deploy is interrupted right after it commits. Use `where` clauses that skip rows a step already migrated, as in the example. Rows written while a migration is in progress are not seen by steps that already completed. [Maintenance mode](#maintenance-mode) blocks GraphQL writes but not the writes your resolvers, executors, or workflows make through Kysely, and without it GraphQL clients can write too, so stop such writers until the migration finishes or write steps that tolerate them.
452
+
453
+ ### Settling a migration that stays in progress
454
+
455
+ When you do not want to resume the migration with another deploy — you abandoned it, or you repaired the data by hand — settle it with `tailor tailordb migration resolve`. Pass exactly one of two options:
456
+
457
+ - `--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.
458
+ - `--complete` sends the schema of the failed migration itself, clears the in-progress record, and moves the checkpoint to that migration, without running its `migrate.ts` again. Repair the data first, for example with `tailor query`, and make sure no record lacks a value for a field the migration makes required: the update is rejected while such a record exists, and the migration stays in progress.
459
+
460
+ Tables that the failed migration removes are deleted with their data under `--complete`. The command refuses to run while an execution of the failed migration is still running; wait for it to finish first. Both options take the namespace's tables out of [maintenance mode](#maintenance-mode) and remove the temporary resources of the failed run. The command shows what it will change and asks for confirmation (`--yes` skips it). As with `migration sync`, the local migration history must reproduce the current table definitions, so settle the migration before you change the tables. If sending the schema stops partway, run the same command again.
452
461
 
453
462
  ## Supported Schema Changes
454
463
 
@@ -677,6 +686,65 @@ For each pending migration:
677
686
 
678
687
  This split is what allows existing rows to be backfilled before the database starts rejecting nulls, and what lets `migrate.ts` traverse foreign-key fields that the same migration removes.
679
688
 
689
+ ### Maintenance mode
690
+
691
+ `maintenanceMode` in `defineConfig` decides whether `tailor deploy` puts each namespace with pending migrations into maintenance mode while it applies them. A table in maintenance mode accepts no GraphQL `create`, `update`, `delete`, or bulk upsert operation and publishes no record events; its `read` setting is left as it is.
692
+
693
+ | Value | Maintenance mode |
694
+ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
695
+ | `false` | None. Nothing is restricted while migrations run. |
696
+ | `"migration"` (default) | From before the first pending migration runs until the last one completes. |
697
+ | `"deploy"` | From before the first pending migration runs until the deploy has applied every other change, including resolvers, executors, and workflows, and has removed the resources it deletes. |
698
+
699
+ A deploy without pending migrations never enters maintenance mode. When `maintenanceMode` is unset, it behaves as `"migration"`, preserving the existing deployment behavior. The value is read from the config like any other, so it can differ per environment (see [Multi-Environment Configuration](../multi-environment.md#varying-config-values-per-environment)):
700
+
701
+ ```typescript
702
+ export default defineConfig({
703
+ name: "my-app",
704
+ maintenanceMode: process.env.APP_ENV === "production" ? "deploy" : false,
705
+ });
706
+ ```
707
+
708
+ **Without maintenance mode**, the SDK does not guarantee what requests that run during the migration observe. A table can still have its [pre-migration shape](#per-migration-phases), with new required fields optional and removed fields present; a client can write rows after `migrate.ts` has already read the table; and the records `migrate.ts` writes publish events to the executors of the previous deploy, which were written for the old shape. Use it only where clients and executors tolerate that.
709
+
710
+ **`"migration"` vs `"deploy"`**: the new resolvers and executors are deployed after the migrations. With `"migration"`, the migrated tables accept requests and publish events again while the resolvers and executors of the previous deploy are still in place. `"deploy"` keeps the tables in maintenance mode until they are replaced.
711
+
712
+ Maintenance mode restricts GraphQL writes and record events only. Resolvers, executors (including scheduled and webhook triggers), and workflows keep running, and the writes they make through Kysely are not blocked.
713
+
714
+ **When a deploy stops early:**
715
+
716
+ - 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 keeps the tables it created in maintenance mode until a later deploy applies it. 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.
717
+ - A [multi-step migration](#splitting-a-migration-into-steps) left in progress keeps its tables in maintenance mode until a deploy completes it.
718
+ - 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.
719
+ - 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, and otherwise reports remote schema drift (see [Failure Recovery](#failure-recovery)).
720
+
721
+ #### Keeping maintenance mode short
722
+
723
+ Maintenance mode lasts for the whole migration job, including the time the platform takes to start it, which can far exceed the time `migrate.ts` itself needs. Keep in `migrate.ts` only the work that must be done before the post-migration schema is applied, and move the rest into a workflow that you start after the deploy (for example with [`tailor workflow start`](../cli/workflow.md#workflow-start)).
724
+
725
+ - **Must stay in `migrate.ts`**: anything the post-migration schema enforces or removes — filling a field that becomes required, resolving duplicates before a unique index, converting values for a field type change, and copying data out of a field or table that the migration renames or removes.
726
+ - **Can move to a workflow**: work the post-migration schema does not depend on, such as filling an optional field, recomputing derived values, or cleaning up rows. Requests that run before the workflow finishes see the data in its unfixed state.
727
+
728
+ ### Time spent in maintenance mode
729
+
730
+ When [maintenance mode](#maintenance-mode) is on, `tailor deploy` reports how long the migrating namespaces' tables were restricted, split into phases. With `"deploy"`, the window also includes the remaining deployment changes and ends when their restrictions are lifted. No maintenance report is emitted with `maintenanceMode: false`.
731
+
732
+ | Phase | Covers |
733
+ | ------------------ | ---------------------------------------------------------------------------------------- |
734
+ | `restrict` | Putting the tables into maintenance mode |
735
+ | `pre-migration` | Pre-migration schema changes |
736
+ | `job setup` | Bundling `migrate.ts` and registering it as a job |
737
+ | `waiting to start` | Time when the platform has accepted the job but none of its code runs yet |
738
+ | `running` | Time when the script runs |
739
+ | `job cleanup` | Collecting the script's logs and removing its job, unless its steps run as separate jobs |
740
+ | `post-migration` | Post-migration schema changes, the checkpoint update, and cleanup |
741
+ | `remaining deploy` | With `"deploy"`, applying the remaining deployment changes while tables stay restricted |
742
+ | `restore` | Taking the tables out of maintenance mode |
743
+
744
+ Phases that repeat across several migrations are added up. The report is printed only after the migrations succeed and the tables leave maintenance mode; when a migration fails, deploy reports the error instead (see [Failure Recovery](#failure-recovery)). When deploy resumes a migration that an earlier deploy left in progress, the tables were already in maintenance mode before it started, and the report covers only this deploy. A job can be reported as started on the platform well before its code runs, so deploy tells waiting from running by a line every job of a migration logs before your script: `[tailor-sdk] migration script started`. You may see that line in function execution logs; deploy leaves it out of the logs it prints. A script that exports [`steps`](#splitting-a-migration-into-steps) waits for each step's job to start, and that time counts as `waiting to start` too. Time when deploy cannot tell whether the script is running — because the run or a running job's logs could not be read, or because an older SDK version started the run and it never logs the line — is reported as `waiting to start or running` instead.
745
+
746
+ Deploy checks the run every few seconds, so a boundary between `waiting to start` and `running` can be off by that much. With `--json`, the same breakdown is in `tailordbMaintenance` (see [Deploy JSON result](../cli/application.md#deploy)). Add `--verbose` to also print how long each phase took and every change in the job's status.
747
+
680
748
  ### Schema verification
681
749
 
682
750
  Before running migrations, `apply` performs two checks:
@@ -716,14 +784,14 @@ tailor deploy --no-schema-check
716
784
  ### Example output
717
785
 
718
786
  ```
719
- ℹ Found 2 pending migration(s) to execute.
720
- ℹ Executing 2 pending migration(s)...
721
- ℹ Using machine user: admin-machine-user for namespace 'tailordb'
787
+ ℹ Executing 1 data migration(s)...
722
788
 
723
- ✔ Migration tailordb/0002 completed successfully
724
- ✔ Migration tailordb/0003 completed successfully
789
+ ℹ Using machine user: admin-machine-user for namespace 'tailordb'
790
+ 10:42:31 AM ℹ Migration tailordb/0002 started running after waiting 15m02s for its job to start.
791
+ ✓ Migration tailordb/0002 completed successfully (waiting to start 15m02s, running 1m41s)
725
792
 
726
- ✔ All migrations completed successfully.
793
+ ✔ All data migrations completed successfully.
794
+ ℹ Tables of namespace tailordb were in maintenance mode for 16m56s (restrict 1.2s, pre-migration 3.4s, job setup 2.1s, waiting to start 15m02s, running 1m41s, job cleanup 0.8s, post-migration 4.0s, restore 1.1s).
727
795
  ✔ Successfully applied changes.
728
796
  ```
729
797
 
@@ -825,7 +893,7 @@ A script that exports `steps` follows different rules once one of its steps has
825
893
 
826
894
  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.
827
895
 
828
- 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, and the deploy reports that it could not confirm the outcome. A later deploy refuses to run the migration while that run is still executing. Run `tailor workflow executions --workflow-name <name>` with the workflow name the error reports until the execution has finished or none is listed. If it succeeded, run `tailor tailordb migration sync <N>` with the migration's own number; otherwise run `tailor tailordb migration sync <N-1>`. Then deploy again. Do not deploy before running sync, even with `--no-schema-check` to skip the drift check: the deploy may run the `main` script again even if the execution succeeded.
896
+ 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, and the deploy reports that it could not confirm the outcome. A later deploy refuses to run the migration while that run is still executing. Run `tailor workflow executions --workflow-name <name>` with the workflow name the error reports until the execution has finished or none is listed. If it succeeded, run `tailor tailordb migration sync <N>` with the migration's own number; otherwise run `tailor tailordb migration sync <N-1>`. Then deploy again. Do not deploy before running sync, even with `--no-schema-check` to skip the drift check: the deploy may run the `main` script again even if the execution succeeded.
829
897
 
830
898
  `migration sync` refuses to run while a migration is in progress. When a steps migration's post-migration schema changes fail and the deploy cannot return its tables to the pre-migration schema, it logs a warning, and the next deploy stops with remote schema drift on those tables. Run `tailor tailordb migration validate` from a checkout of the deployed migration history. If the only issue it reports is that drift, run `tailor deploy --no-schema-check`: the deploy applies the pre-migration schema again, skips the completed steps unless the steps changed, and retries the post-migration schema changes. `--no-schema-check` skips the schema checks of every namespace, so do not use it while `validate` reports any other issue.
831
899
 
@@ -917,8 +985,8 @@ When `migrate.ts` already exists (the usual case for breaking changes, where `mi
917
985
  // migrations/0005/migrate.test.ts
918
986
  import { createKyselyMock } from "@tailor-platform/sdk/vitest";
919
987
  import { describe, expect, test } from "vitest";
920
- import type { Database } from "./db";
921
988
  import { main } from "./migrate";
989
+ import type { Database } from "./db";
922
990
 
923
991
  describe("0005 add required email", () => {
924
992
  test("backfills null emails", async () => {
@@ -954,9 +1022,9 @@ The generated `db.pglite.ts` exports the `CREATE TABLE` script for the same sche
954
1022
  import { PGlite } from "@electric-sql/pglite";
955
1023
  import { createKyselyPGlite, type Unmigrated } from "@tailor-platform/sdk/vitest";
956
1024
  import { afterAll, beforeAll, describe, expect, test } from "vitest";
957
- import type { Database } from "./db";
958
1025
  import { pgliteSchema } from "./db.pglite";
959
1026
  import { main } from "./migrate";
1027
+ import type { Database } from "./db";
960
1028
 
961
1029
  const pglite = new PGlite();
962
1030
  const db = createKyselyPGlite<Unmigrated<Database>>(pglite);
@@ -431,16 +431,29 @@ db.string().vector();
431
431
 
432
432
  ```typescript
433
433
  db.int().serial({
434
- start: 0,
434
+ start: 1,
435
435
  maxValue: 100,
436
436
  });
437
437
 
438
438
  db.string().serial({
439
- start: 0,
439
+ start: 1,
440
440
  format: "CUST_%d",
441
441
  });
442
442
  ```
443
443
 
444
+ A serial field cannot be used inside a `db.object()` field.
445
+
446
+ > **Preview:** The `gapless` option is a preview feature. Its behavior and configuration may change before general availability.
447
+
448
+ Set `gapless: true` to number records without gaps. A number is used only by a create that commits, so a create that fails leaves no hole. Numbered creates of the table run one at a time, so use it only when contiguous numbering is required. See the [TailorDB Serial documentation](https://docs.tailor.tech/guides/tailordb/fields#serial) for what the gapless guarantee covers.
449
+
450
+ ```typescript
451
+ db.int().serial({
452
+ start: 1,
453
+ gapless: true,
454
+ });
455
+ ```
456
+
444
457
  ### Common Fields
445
458
 
446
459
  ```typescript
@@ -522,7 +535,7 @@ db.table("User", {
522
535
  - When not specified, `deploy` sets it from the executors taking part in the same run: `true` while one of them uses this table with `recordCreatedTrigger`, `recordUpdatedTrigger`, or `recordDeletedTrigger`, and `false` once none does. Removing the last such trigger turns publishing back off on the next `deploy`
523
536
  - When explicitly set to `false` while an executor taking part in the same run uses this table, `deploy` fails
524
537
  - An executor declared with `disabled: true` never runs, so it does not count as using the table
525
- - While a `deploy` applies pending migrations, every table in the migrating namespace is restricted: the GraphQL create, update, and delete operations and bulk upsert are disabled, and the table's `read` setting is not switched off for the migration (a table the migration creates uses its configured setting), so nothing writes an intermediate schema and readable tables are not shut down. Record event publishing is also switched off so a migration script's writes do not reach executors from the previous deploy. After success, the configured settings take effect. If a later migration fails, settings from the last confirmed checkpoint are restored; an uncommitted migration restores existing tables' prior settings and leaves its newly created tables restricted until a successful retry. A table that already refuses create, update, delete, and bulk upsert and publishes no record events is left as it is. Restoration is skipped if the checkpoint number or migration history changed concurrently, or if checkpoint ownership cannot be verified; the deploy then reports the namespace whose tables stay restricted and points to `tailor tailordb migration status --namespace <namespace>`. When a failed migration or a failed restoration leaves tables restricted, the deploy lists their names, and deploying again releases them once the migration completes. A table that a failed rollback could not delete or revert is the exception: the deploy also lists it, but the next deploy stops with remote schema drift until you delete or repair that table by hand. A table left behind by a failed post-checkpoint deletion also remains read-only for manual recovery, and the deploy does not list it by name
538
+ - While a `deploy` applies pending migrations with [maintenance mode](./tailordb-migration.md#maintenance-mode) on, every table in the migrating namespace is restricted: the GraphQL create, update, and delete operations and bulk upsert are disabled, and the table's `read` setting is not switched off for the migration (a table the migration creates uses its configured setting), so nothing writes an intermediate schema and readable tables are not shut down. Record event publishing is also switched off so a migration script's writes do not reach executors from the previous deploy. With `maintenanceMode: false`, these restrictions are not applied. After success, the configured settings take effect. If a later migration fails, settings from the last confirmed checkpoint are restored; an uncommitted migration restores existing tables' prior settings and leaves its newly created tables restricted until a successful retry. A table that already refuses create, update, delete, and bulk upsert and publishes no record events is left as it is. Restoration is skipped if the checkpoint number or migration history changed concurrently, or if checkpoint ownership cannot be verified; the deploy then reports the namespace whose tables stay restricted and points to `tailor tailordb migration status --namespace <namespace>`. When a failed migration or a failed restoration leaves tables restricted, the deploy lists their names, and deploying again releases them once the migration completes. A table that a failed rollback could not delete or revert is the exception: the deploy also lists it, but the next deploy stops with remote schema drift until you delete or repair that table by hand. A table left behind by a failed post-checkpoint deletion also remains read-only for manual recovery, and the deploy does not list it by name
526
539
 
527
540
  **Use cases:**
528
541
 
@@ -567,7 +580,7 @@ db.table("User", {
567
580
 
568
581
  Control which GraphQL operations (`create`, `update`, `delete`, `read`) are exposed for a table. All operations are enabled by default.
569
582
 
570
- While a `deploy` applies pending migrations, the GraphQL `create`, `update`, and `delete` operations and bulk upsert are switched off across the migrating namespace, so nothing writes an intermediate schema. The `read` operation is not switched off: it keeps the setting the table has when the restrictions begin. A table that a migration touches then follows that migration's schema from its first phase on, so a migration that changes `read` changes it before its script runs; a table no migration touches keeps its setting. A table the migration creates uses its configured `read` setting from the start. After success, the configured operations take effect. If a later migration fails, operations from the last confirmed checkpoint are restored; an uncommitted migration restores existing tables' prior operations and leaves its newly created tables restricted until a successful retry. A table that already refuses create, update, delete, and bulk upsert and publishes no record events is left as it is. Restoration is skipped if the checkpoint number or migration history changed concurrently; the deploy then reports the namespace whose tables stay restricted, and deploying again releases them once the migration completes.
583
+ While a `deploy` applies pending migrations with [maintenance mode](./tailordb-migration.md#maintenance-mode) on, the GraphQL `create`, `update`, and `delete` operations and bulk upsert are switched off across the migrating namespace, so nothing writes an intermediate schema. The `read` operation is not switched off: it keeps the setting the table has when the restrictions begin. With or without maintenance mode, a table that a migration touches follows that migration's schema from its first phase on, so a migration that changes `read` changes it before its script runs; a table no migration touches keeps its setting. A table the migration creates uses its configured `read` setting from the start. After success, the configured operations take effect. If a later migration fails while maintenance mode is on, operations from the last confirmed checkpoint are restored; an uncommitted migration restores existing tables' prior operations and leaves its newly created tables restricted until a successful retry. A table that already refuses create, update, delete, and bulk upsert and publishes no record events is left as it is. Restoration is skipped if the checkpoint number or migration history changed concurrently; the deploy then reports the namespace whose tables stay restricted, and deploying again releases them once the migration completes.
571
584
 
572
585
  ```typescript
573
586
  db.table("Order", {
@@ -488,9 +488,9 @@ Pass the runtime key through the `executionPolicyKey` option on `job.start()`. F
488
488
 
489
489
  ```typescript
490
490
  import { createWorkflowJob } from "@tailor-platform/sdk";
491
- import { executionPolicies } from "./policies";
492
- import { sendNotification } from "./jobs/send-notification";
493
491
  import { fetchTenant } from "./jobs/fetch-tenant";
492
+ import { sendNotification } from "./jobs/send-notification";
493
+ import { executionPolicies } from "./policies";
494
494
 
495
495
  export const mainJob = createWorkflowJob({
496
496
  name: "main-job",