@tailor-platform/sdk 2.30.0 → 2.33.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 (120) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/dist/ai/index.d.mts +112 -0
  3. package/dist/ai/index.mjs +2 -0
  4. package/dist/ai/index.mjs.map +1 -0
  5. package/dist/aigateway-DsWDjzk4.mjs.map +1 -1
  6. package/dist/{application-D0xNJbag.mjs → application-B9_NHiUC.mjs} +40 -32
  7. package/dist/application-B9_NHiUC.mjs.map +1 -0
  8. package/dist/application-Bet1K5C4.mjs +1 -0
  9. package/dist/assert-WeXvmG4j.mjs.map +1 -1
  10. package/dist/authconnection-CynFBIv8.mjs.map +1 -1
  11. package/dist/brand-C8nMKhJC.mjs.map +1 -1
  12. package/dist/cli/commands/deploy/staticwebsite.d.mts +1 -0
  13. package/dist/cli/lib.mjs +1 -1
  14. package/dist/cli/lib.mjs.map +1 -1
  15. package/dist/cli/main.mjs +44 -33
  16. package/dist/cli/main.mjs.map +1 -1
  17. package/dist/cli/services/resolver/service.d.mts +1 -0
  18. package/dist/cli/shared/client.d.mts +52 -1
  19. package/dist/cli/ts-hook.d.mts +0 -2
  20. package/dist/cli/ts-hook.mjs +0 -113
  21. package/dist/completion/zsh-worker.zsh +30 -6
  22. package/dist/configure/index.mjs.map +1 -1
  23. package/dist/configure/services/staticwebsite/index.d.mts +6 -1
  24. package/dist/context-D0QjfxzD.mjs.map +1 -1
  25. package/dist/crashreport-BCIy33Cz.mjs.map +1 -1
  26. package/dist/date-CgP7gMG_.mjs.map +1 -1
  27. package/dist/date-representation-Cr7W7Pn2.mjs.map +1 -1
  28. package/dist/errors-BOF68LJ5.mjs.map +1 -1
  29. package/dist/es-builtins-SfvXRVoG.mjs.map +1 -1
  30. package/dist/field-parse-C41e3UAF.mjs.map +1 -1
  31. package/dist/file-DKBOj5q4.mjs.map +1 -1
  32. package/dist/globals-BCmGX05J.mjs.map +1 -1
  33. package/dist/guards-yDXyKC9F.mjs.map +1 -1
  34. package/dist/iconv-DlFMt2gW.mjs.map +1 -1
  35. package/dist/idp-G_ojPBB5.mjs.map +1 -1
  36. package/dist/interceptor-DQg3cR_9.mjs.map +1 -1
  37. package/dist/kysely/index.mjs.map +1 -1
  38. package/dist/kysely-type-DxyIWXc6.mjs +51 -0
  39. package/dist/kysely-type-DxyIWXc6.mjs.map +1 -0
  40. package/dist/logger-DP2BjQ93.mjs.map +1 -1
  41. package/dist/logger-RgtbhJ83.mjs.map +1 -1
  42. package/dist/manager-DTRhIupk.mjs +2 -0
  43. package/dist/manager-DTRhIupk.mjs.map +1 -0
  44. package/dist/migration-steps-C6F3zNdQ.mjs.map +1 -1
  45. package/dist/multiline-EyzjEwn9.mjs.map +1 -1
  46. package/dist/node-builtins-DYfhPBnz.mjs.map +1 -1
  47. package/dist/package-json-C690ceex.mjs.map +1 -1
  48. package/dist/platform-serialize-DkiTdHOt.mjs.map +1 -1
  49. package/dist/plugin/builtin/enum-constants/index.mjs +2 -2
  50. package/dist/plugin/builtin/enum-constants/index.mjs.map +1 -1
  51. package/dist/plugin/builtin/file-utils/index.mjs.map +1 -1
  52. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  53. package/dist/plugin/index.mjs +1 -1
  54. package/dist/plugin/index.mjs.map +1 -1
  55. package/dist/plugin/types.d.mts +3 -0
  56. package/dist/register-ts-hook-Dp9reSRG.mjs +1053 -0
  57. package/dist/register-ts-hook-Dp9reSRG.mjs.map +1 -0
  58. package/dist/registry-BijIDRVA.mjs.map +1 -1
  59. package/dist/repl-editor-DkrCZmmg.mjs +2 -0
  60. package/dist/repl-editor-DkrCZmmg.mjs.map +1 -0
  61. package/dist/schema-B6hJQWp6.mjs.map +1 -1
  62. package/dist/secret-file-C9wp_FCX.mjs.map +1 -1
  63. package/dist/secretmanager-vHQoXdQz.mjs.map +1 -1
  64. package/dist/seed/index.mjs.map +1 -1
  65. package/dist/seed-Cn-3JApy.mjs.map +1 -1
  66. package/dist/service-9SBPNpK8.mjs.map +1 -1
  67. package/dist/service-9rwi_I4d.mjs.map +1 -1
  68. package/dist/service_pb-6EkMfdOb.mjs +2 -0
  69. package/dist/service_pb-6EkMfdOb.mjs.map +1 -0
  70. package/dist/service_pb-CsJKokDl.mjs +1 -0
  71. package/dist/tailor-proto/src/tailor/v1/metadata_pb.d.mts +5 -2
  72. package/dist/tailor-proto/src/tailor/v1/security_pb.d.mts +213 -0
  73. package/dist/tailor-proto/src/tailor/v1/security_resource_pb.d.mts +301 -0
  74. package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +200 -8
  75. package/dist/tailor-proto/src/tailor/v1/staticwebsite_pb.d.mts +201 -1
  76. package/dist/tailor-proto/src/tailor/v1/staticwebsite_resource_pb.d.mts +88 -1
  77. package/dist/telemetry-DfT2UfI4.mjs +2 -0
  78. package/dist/telemetry-DfT2UfI4.mjs.map +1 -0
  79. package/dist/{telemetry-DpkFGBmu.mjs → telemetry-nqOtDWNO.mjs} +1 -1
  80. package/dist/type-source--ZNcV8RJ.mjs.map +1 -1
  81. package/dist/types/staticwebsite.generated.d.mts +9 -1
  82. package/dist/user-agent-vdHYF3QL.mjs.map +1 -1
  83. package/dist/utils/test/index.mjs.map +1 -1
  84. package/dist/vitest/environment.mjs.map +1 -1
  85. package/dist/vitest/index.d.mts +2 -2
  86. package/dist/vitest/index.mjs +1 -1
  87. package/dist/vitest/index.mjs.map +1 -1
  88. package/dist/vitest/pglite-kysely.d.mts +19 -0
  89. package/dist/vitest/setup.mjs.map +1 -1
  90. package/dist/wait-point-invoker-eiP-IIux.mjs.map +1 -1
  91. package/dist/wait-point-registry-B-ESkTZX.mjs.map +1 -1
  92. package/dist/workflow-Cs9ISw6j.mjs.map +1 -1
  93. package/dist/{workspace_resource_pb-CaoM2npP.mjs → workspace_resource_pb-C4CkoYww.mjs} +2 -2
  94. package/dist/workspace_resource_pb-C4CkoYww.mjs.map +1 -0
  95. package/docs/ai.md +92 -0
  96. package/docs/cli/tailordb.md +17 -15
  97. package/docs/cli/workspace.md +1 -1
  98. package/docs/github-actions.md +98 -11
  99. package/docs/plugin/custom.md +34 -0
  100. package/docs/plugin/index.md +1 -0
  101. package/docs/services/staticwebsite.md +31 -1
  102. package/docs/services/tailordb/migration.md +78 -41
  103. package/docs/testing.md +2 -1
  104. package/package.json +20 -13
  105. package/dist/application-C2bPmmbh.mjs +0 -1
  106. package/dist/application-D0xNJbag.mjs.map +0 -1
  107. package/dist/kysely-type-BWMfV301.mjs +0 -48
  108. package/dist/kysely-type-BWMfV301.mjs.map +0 -1
  109. package/dist/manager-Bk-7vkwd.mjs +0 -2
  110. package/dist/manager-Bk-7vkwd.mjs.map +0 -1
  111. package/dist/register-ts-hook-CDnN_Jx2.mjs +0 -1012
  112. package/dist/register-ts-hook-CDnN_Jx2.mjs.map +0 -1
  113. package/dist/repl-editor-DGJBfIt1.mjs +0 -2
  114. package/dist/repl-editor-DGJBfIt1.mjs.map +0 -1
  115. package/dist/service_pb-Bbu2QSIr.mjs +0 -2
  116. package/dist/service_pb-Bbu2QSIr.mjs.map +0 -1
  117. package/dist/service_pb-urxAIYHT.mjs +0 -1
  118. package/dist/telemetry-Xk7J829P.mjs +0 -2
  119. package/dist/telemetry-Xk7J829P.mjs.map +0 -1
  120. package/dist/workspace_resource_pb-CaoM2npP.mjs.map +0 -1
@@ -173,9 +173,17 @@ During deploy, the pre-migration phase keeps the old field and adds the new fiel
173
173
 
174
174
  If you decline the prompt (or confirm the removal with `--drop`), the change stays a plain removal + addition with the usual data-loss warning.
175
175
 
176
+ #### Changing nested member constraints
177
+
178
+ Adding a required member, making an existing member required, or removing enum values inside a nested field requires a migration script, including inside arrays of objects. During Pre-migration, added or newly required members are optional and removed enum values remain allowed. Post-migration enforces the target constraints after your script fills missing values or replaces removed enum values. A newly added optional object may keep required children: those children are required only when the object is present.
179
+
180
+ The generated script includes a `TODO` for each nested constraint change. Replace these with updates to the containing nested field, preserving its other members and handling objects inside arrays. A confirmed member rename generates its copy step alongside these TODOs.
181
+
182
+ 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
+
176
184
  #### Renaming a member inside a nested field
177
185
 
178
- 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 nested member constraints other than the new member's requiredness and unique constraint are not relaxed: 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.
186
+ 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.
179
187
 
180
188
  ```
181
189
  ? User.address.zip was removed and zipCode was added with a compatible type. Was it renamed to zipCode? (Y/n)
@@ -199,7 +207,7 @@ Warning: data loss possible:
199
207
  - User.address.zip: Nested member removed (existing values will no longer be accessible through the schema). Possibly renamed to zipCode: confirm it with --rename "User.address.zip:<newName>" to scaffold a copy script, or keep the removal and copy the values yourself
200
208
  ```
201
209
 
202
- The pre-migration phase keeps a removed member on the nested field until the script finishes, exactly like a removed top-level field, so a custom script can still read it. Nested fields reach the script as objects. Renaming a nested member cannot be combined in one migration with renaming its table or the nested field itself, and an object-typed member cannot be renamed in the same migration as one of its own members; split such changes into separate migrations (rename the object first, then its member).
210
+ The pre-migration phase keeps a removed member on the nested field until the script finishes, exactly like a removed top-level field, so a custom script can still read it. Nested fields reach the script as objects. Renaming a nested member requires every ancestor, including the top-level nested field, to keep its nested type and array-ness. It cannot be combined in one migration with renaming its table or the nested field itself, and an object-typed member cannot be renamed in the same migration as one of its own members; split such changes into separate migrations (rename the object first, then its member).
203
211
 
204
212
  ### Renaming a table
205
213
 
@@ -454,6 +462,25 @@ tailor deploy --migration-skip-steps main-db/backfillUser,main-db/backfillInvoic
454
462
 
455
463
  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.
456
464
 
465
+ #### Rolling back instead of resuming
466
+
467
+ By default a failure after another step has completed leaves the migration in progress and the namespace's tables in maintenance mode until you fix the step and deploy again. When you would rather restore the tables first and fix the script afterwards, export `onFailure` next to `steps`:
468
+
469
+ ```typescript
470
+ export const onFailure = "rollback";
471
+
472
+ export const steps = {
473
+ // ...
474
+ } satisfies MigrationSteps;
475
+ ```
476
+
477
+ - `onFailure` is `"resume"` (the default, described above) or `"rollback"`. Write it as `export const onFailure = "rollback"` with one of those two string literals; `tailor tailordb migration validate` and `tailor deploy` reject anything else. A script that runs `main` ignores it.
478
+ - With `"rollback"`, a failed step after another step has completed, and post-migration schema changes that fail after every step completed, roll back the pre-migration schema changes like a failing `main` script: tables that already existed are restored to their previous shape and GraphQL permissions, and tables the migration newly introduced are dropped. The in-progress record is cleared before the schema is rolled back, the tables leave maintenance mode, and the checkpoint is not advanced. `tailor deploy` then fails with the step's error under the code `MIGRATION_ROLLED_BACK`, and its suggestion says the next deploy runs every step again. A migration that an earlier deploy left in progress is rolled back the same way once its script exports `"rollback"`, including the tables and GraphQL permissions its earlier Pre-phase wrote.
479
+ - Steps that already completed stay committed. Their changes to existing fields remain in the database, and nothing is recorded as in progress, so the next deploy runs every step again from the beginning. Every step must be safe to run again, or a rolled-back run applies those changes twice. Do not rely on data a step wrote to a field or table that the rollback removed.
480
+ - If `tailor deploy` cannot tell whether a step ran, or the migration's workflow execution may still be running (for example the connection to it was lost), it does not roll anything back even with `"rollback"`: the migration stays in progress as described for a `main` script below. A migration an earlier deploy left in progress is rolled back only when its earlier run is confirmed to have ended, so a failure before that run is inspected, such as a schema error while applying the Pre-phase, keeps it in progress.
481
+ - If the in-progress record cannot be cleared, `tailor deploy` does not roll the schema back. It logs a warning and keeps the migration in progress with its tables in maintenance mode, because a record that outlives a rollback would make the next deploy resume against a schema its completed steps no longer match. Deploy again to resume it as described above, or roll it back without deploying with `tailor tailordb migration resolve --rollback`.
482
+ - If the schema or GraphQL permission of some tables cannot be rolled back, `tailor deploy` still fails with the step's original error. It also warns `Migration <namespace>/<N> was not rolled back completely` and lists the tables that need manual repair, and separately the tables whose GraphQL permission could not be set back. Check them in the workspace and repair them by hand; deploying again does not release them.
483
+
457
484
  ### Settling a migration that stays in progress
458
485
 
459
486
  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:
@@ -465,33 +492,35 @@ Tables that the failed migration removes are deleted with their data under `--co
465
492
 
466
493
  ## Supported Schema Changes
467
494
 
468
- | Change Type | Breaking? | Migration Script? | Notes |
469
- | --------------------------------- | --------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
470
- | Add optional field | No | No | Schema change only |
471
- | Add required field | Yes | Yes | Script populates default values |
472
- | Remove field | No | Optional | Warning tier — no script is auto-generated, but you can add one with `tailordb migration script` to preserve or clear data before the field leaves the active schema. The field stays readable from `migrate.ts` during Pre-migration. |
473
- | Rename field | Yes | Yes | Confirmed interactively at generate time or via `--rename "Table.oldField:newField"` — see [Renaming a field](#renaming-a-field). Auto-generated script copies values from the old field to the new one; both fields coexist during Pre-migration. |
474
- | Remove nested member | No | Optional | Warning tier — see [Renaming a member inside a nested field](#renaming-a-member-inside-a-nested-field). The member stays readable from `migrate.ts` during Pre-migration. |
475
- | Rename nested member | Yes | Yes | Confirmed interactively at generate time or via `--rename "Table.field.oldMember:newMember"`. Auto-generated script rewrites each row's nested value; the old member stays readable and the new member is optional during Pre-migration. |
476
- | Change optional → required | Yes | Yes | Script sets defaults for null values |
477
- | Change required → optional | No | No | Schema change only |
478
- | Add index (non-unique) | No | No | Schema change only |
479
- | Add unique index | Yes | Yes | Script must resolve duplicate value combinations across the index fields |
480
- | Change unique index fields | Yes | Yes | Treated like adding a new unique constraint over the new field set |
481
- | Remove index | No | No | Schema change only (removing the unique constraint from an index is also non-breaking) |
482
- | Add unique constraint | Yes | Yes | Script must resolve duplicate values |
483
- | Remove unique constraint | No | No | Schema change only |
484
- | Change decimal scale | Yes | Yes | Auto-generated script re-saves existing rows under the new scale. Decreasing scale rounds values half-up and can lose precision. If the same change adds a unique constraint, duplicate handling runs after re-saving. |
485
- | Add enum value | No | No | Schema change only |
486
- | Remove enum value | Yes | Yes | Script migrates records with removed values |
487
- | Add table | No | No | Schema change only |
488
- | 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. |
489
- | 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. |
490
- | Change foreign key target table | Yes | Yes | Script updates references to the new target |
491
- | 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 |
492
- | 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. |
493
- | Change array → single value | - | - | **Not supported** — see [Converting a field type](#converting-a-field-type) |
494
- | Change single value → array | Yes | Yes | Two migrations, generated together after you confirm — see [Converting a field type](#converting-a-field-type). Each stored value becomes a one-element array, so the conversion needs no edits. |
495
+ | Change Type | Breaking? | Migration Script? | Notes |
496
+ | ----------------------------------------------------------------------------- | --------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
497
+ | Add optional field | No | No | Schema change only |
498
+ | Add required field | Yes | Yes | Script populates default values |
499
+ | Remove field | No | Optional | Warning tier — no script is auto-generated, but you can add one with `tailordb migration script` to preserve or clear data before the field leaves the active schema. The field stays readable from `migrate.ts` during Pre-migration. |
500
+ | Rename field | Yes | Yes | Confirmed interactively at generate time or via `--rename "Table.oldField:newField"` — see [Renaming a field](#renaming-a-field). Auto-generated script copies values from the old field to the new one; both fields coexist during Pre-migration. |
501
+ | Remove nested member | No | Optional | Warning tier — see [Renaming a member inside a nested field](#renaming-a-member-inside-a-nested-field). The member stays readable from `migrate.ts` during Pre-migration. |
502
+ | Add required nested member / make member required / remove nested enum values | Yes | Yes | Pre-migration relaxes the member constraints. Complete the generated TODOs to transform the nested values before Post-migration. |
503
+ | Change nested member type / array-ness / decimal scale / foreign key target | Rejected | — | Add a new member, transform and copy values, then remove the old member in separate migrations. |
504
+ | Rename nested member | Yes | Yes | Confirmed interactively at generate time or via `--rename "Table.field.oldMember:newMember"`. Auto-generated script rewrites each row's nested value; the old member stays readable and the new member is optional during Pre-migration. |
505
+ | Change optional → required | Yes | Yes | Script sets defaults for null values |
506
+ | Change required → optional | No | No | Schema change only |
507
+ | Add index (non-unique) | No | No | Schema change only |
508
+ | Add unique index | Yes | Yes | Script must resolve duplicate value combinations across the index fields |
509
+ | Change unique index fields | Yes | Yes | Treated like adding a new unique constraint over the new field set |
510
+ | Remove index | No | No | Schema change only (removing the unique constraint from an index is also non-breaking) |
511
+ | Add unique constraint | Yes | Yes | Script must resolve duplicate values |
512
+ | Remove unique constraint | No | No | Schema change only |
513
+ | Change decimal scale | Yes | Yes | Auto-generated script re-saves existing rows under the new scale. Decreasing scale rounds values half-up and can lose precision. If the same change adds a unique constraint, duplicate handling runs after re-saving. |
514
+ | Add enum value | No | No | Schema change only |
515
+ | Remove enum value | Yes | Yes | Script migrates records with removed values |
516
+ | Add table | No | No | Schema change only |
517
+ | 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. |
518
+ | 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. |
519
+ | Change foreign key target table | Yes | Yes | Script updates references to the new target |
520
+ | 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 |
521
+ | 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. |
522
+ | Change array → single value | - | - | **Not supported** — see [Converting a field type](#converting-a-field-type) |
523
+ | Change single value → array | Yes | Yes | Two migrations, generated together after you confirm — see [Converting a field type](#converting-a-field-type). Each stored value becomes a one-element array, so the conversion needs no edits. |
495
524
 
496
525
  ### Field type changes
497
526
 
@@ -611,13 +640,15 @@ Some changes are still rejected and need a temporary field you add yourself —
611
640
  The command performs the following sequence:
612
641
 
613
642
  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, as `tailor deploy` does, but only when its schema matches the `0000` snapshot; otherwise the command fails and lists the differences. A namespace that was never deployed also fails.
614
- 2. Creates a temporary workspace in the same region, organization, and folder as the source, unless `--target-workspace-id` names an existing throwaway workspace.
643
+ 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.
615
644
  3. Deploys the checkpoint snapshots and writes their checkpoint labels.
616
645
  4. Loads fixture data or clones source records.
617
646
  5. Runs the normal deployment pipeline, including every pending pre-migration, `migrate.ts`, and post-migration phase.
618
647
  6. Optionally runs an assertion script against the migrated data.
619
648
  7. Deletes an automatically-created workspace after success or failure.
620
649
 
650
+ Right after creating an automatically-created workspace (step 2), the command records a 24-hour expiry on it. If the run is interrupted before step 7 — for example, a CI job is cancelled or reaches its time limit — the command cannot delete the workspace, so it stays. Once the 24 hours have passed, `tailor workspace prune --expired` (with a location option such as `--organization-root`, `--folder-id`, or `--personal`) deletes it. A workspace given with `--target-workspace-id` is not given an expiry. If recording the expiry fails, the command warns and continues the test.
651
+
621
652
  Both the pre-migration and final TailorDB schemas come from committed migration snapshots. Ungenerated changes in the current table definitions are not included in the rehearsal.
622
653
 
623
654
  Executors are omitted from the baseline deployment so loading fixture or cloned records cannot trigger current event handlers against the older schema. Auth user profiles are also deferred until the pending migrations finish, while configured machine users remain available to run seed and migration scripts. The final deployment restores the configured executors and user profiles. Static websites are deployed so configuration references to their URLs resolve, but their workspace-bound custom domains are omitted from migration-test deployments.
@@ -667,6 +698,8 @@ To inspect the result after a run, pass `--keep` so the automatically created wo
667
698
  tailor tailordb migration test --keep
668
699
  ```
669
700
 
701
+ A kept workspace records no expiry unless you also pass `--keep-ttl <age>` (for example `--keep-ttl 7d`), so `tailor workspace prune --expired` does not delete it.
702
+
670
703
  Alternatively, provide an empty designated throwaway workspace. This mode never deletes the target and requires explicit acknowledgment:
671
704
 
672
705
  ```bash
@@ -685,7 +718,7 @@ When you run `tailor deploy`, the SDK detects pending migrations (anything past
685
718
 
686
719
  For each pending migration:
687
720
 
688
- 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. 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.
721
+ 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.
689
722
  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).
690
723
  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.
691
724
  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.
@@ -891,10 +924,10 @@ Use `tailor tailordb migration rebaseline` rather than combining `migration gene
891
924
 
892
925
  ## Failure Recovery
893
926
 
894
- A script that exports `steps` follows different rules once one of its steps has completed: the migration stays in progress and the next deploy resumes it. See [Splitting a migration into steps](#splitting-a-migration-into-steps). Otherwise, if the pre-migration phase or `migrate.ts` fails:
927
+ A script that exports `steps` follows different rules once one of its steps has completed: the migration stays in progress and the next deploy resumes it, unless the script exports `onFailure = "rollback"`. See [Splitting a migration into steps](#splitting-a-migration-into-steps) and [Rolling back instead of resuming](#rolling-back-instead-of-resuming). Otherwise, if the pre-migration phase or `migrate.ts` fails:
895
928
 
896
929
  - **The transaction rolls back** for that migration's script. Database changes the script made are undone.
897
- - **The pre-migration schema changes are rolled back** to the prior checkpoint: tables that already existed are restored to their previous shape, and tables the migration newly introduced are dropped. The workspace is left at its prior checkpoint and prior schema — not half-applied.
930
+ - **The pre-migration schema changes are rolled back** to the prior checkpoint: tables that already existed are restored to their previous shape and GraphQL permissions, and tables the migration newly introduced are dropped. The workspace is left at its prior checkpoint and prior schema — not half-applied.
898
931
  - The whole `apply` aborts and the checkpoint label is not bumped. Subsequent migrations in the same run do not execute.
899
932
 
900
933
  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.
@@ -1023,10 +1056,12 @@ npm install -D @electric-sql/pglite
1023
1056
 
1024
1057
  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>`.
1025
1058
 
1059
+ 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. 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 retargets the relation 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).
1060
+
1026
1061
  ```typescript
1027
1062
  // migrations/0005/migrate.pglite.test.ts
1028
1063
  import { PGlite } from "@electric-sql/pglite";
1029
- import { createKyselyPGlite, type Unmigrated } from "@tailor-platform/sdk/vitest";
1064
+ import { createKyselyPGlite, prepareFixtures, type Unmigrated } from "@tailor-platform/sdk/vitest";
1030
1065
  import { afterAll, beforeAll, describe, expect, test } from "vitest";
1031
1066
  import { pgliteSchema } from "./db.pglite";
1032
1067
  import { main } from "./migrate";
@@ -1047,13 +1082,15 @@ afterAll(async () => {
1047
1082
  describe("0005 add required email", () => {
1048
1083
  test("backfills null emails and keeps existing ones", async () => {
1049
1084
  const now = new Date();
1050
- await db
1051
- .insertInto("User")
1052
- .values([
1053
- { name: "a", email: null, createdAt: now, updatedAt: now },
1054
- { name: "b", email: "b@example.com", createdAt: now, updatedAt: now },
1055
- ])
1056
- .execute();
1085
+ await prepareFixtures(pglite, async () => {
1086
+ await db
1087
+ .insertInto("User")
1088
+ .values([
1089
+ { name: "a", email: null, createdAt: now, updatedAt: now },
1090
+ { name: "b", email: "b@example.com", createdAt: now, updatedAt: now },
1091
+ ])
1092
+ .execute();
1093
+ });
1057
1094
 
1058
1095
  await db.transaction().execute((trx) => main(trx));
1059
1096
 
package/docs/testing.md CHANGED
@@ -209,11 +209,12 @@ test("upserts against real rows", async () => {
209
209
  });
210
210
  ```
211
211
 
212
- The generated columns follow the Kysely types, not TailorDB's storage: `text` for string and enum fields, `timestamptz` for datetime, `date` and `time` for date and time, `numeric` for decimal, rounded to the configured scale and read back with exactly that many fractional digits, `jsonb` for nested objects (and arrays of them), Postgres arrays for other array fields. `id` is a generated `uuid` primary key, `.unique()` fields and unique `.indexes()` are enforced, so `ON CONFLICT` upserts behave, and `.default()` values become column defaults (`"now"` becomes the current time). `.serial()` fields are assigned by the database from the configured `start`, `maxValue`, and format. Relations are not enforced.
212
+ The generated columns follow the Kysely types, not TailorDB's storage: `text` for string and enum fields, `timestamptz` for datetime, `date` and `time` for date and time, `numeric` for decimal, rounded to the configured scale and read back with exactly that many fractional digits, `jsonb` for nested objects (and arrays of them), Postgres arrays for other array fields. `id` is a generated `uuid` primary key, `.unique()` fields and unique `.indexes()` are enforced, so `ON CONFLICT` upserts behave, and `.default()` values become column defaults (`"now"` becomes the current time). `.serial()` fields are assigned by the database from the configured `start`, `maxValue`, and format. Relations are enforced as the platform does: a row that points at a parent that does not exist is rejected with an error containing `violates foreign key constraint`, so create parent rows before their children. This includes relations on array fields. A relation declared on a field inside a nested object is not enforced, as on the platform.
213
213
 
214
214
  What the script cannot reproduce:
215
215
 
216
216
  - Hooks, validations, and permissions do not run. A required field whose value only its own field-level create hook supplies is created nullable, so inserts that omit it succeed; give it a `.default()` if the test reads it back. A field filled by a table-level hook stays `NOT NULL`, as its Kysely type still requires it on insert.
217
+ - Relations on array fields, and relations from a field whose type differs from the field it points at (such as a `string` field related to `id`), are checked only when a row is written. Deleting a parent that such a row still points at is rejected by the platform but not by PGlite, and changing the parent's `id` is not checked either, so a test must not expect PGlite to reject them.
217
218
  - Serial formats are reproduced for a single `%d`, `%x`, or `%X` specifier with an optional zero-padded width; an octal `%o` format fails generation with an error naming the field.
218
219
  - `gapless` is not reproduced: the numbers come from a sequence, so an insert that fails still uses up its number.
219
220
  - A date or datetime inside a nested object reads back as a string from `jsonb`, not a `Date`, or a `Temporal.PlainDate` or `Temporal.Instant` under `kyselyTypePlugin({ temporal: true })`, although the platform returns those types.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailor-platform/sdk",
3
- "version": "2.30.0",
3
+ "version": "2.33.0",
4
4
  "description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -82,6 +82,11 @@
82
82
  "import": "./dist/seed/index.mjs",
83
83
  "default": "./dist/seed/index.mjs"
84
84
  },
85
+ "./ai": {
86
+ "types": "./dist/ai/index.d.mts",
87
+ "import": "./dist/ai/index.mjs",
88
+ "default": "./dist/ai/index.mjs"
89
+ },
85
90
  "./vitest": {
86
91
  "types": "./dist/vitest/index.d.mts",
87
92
  "import": "./dist/vitest/index.mjs",
@@ -155,14 +160,15 @@
155
160
  "@bufbuild/protovalidate": "1.3.0",
156
161
  "@connectrpc/connect": "2.2.0",
157
162
  "@connectrpc/connect-node": "2.2.0",
158
- "@inquirer/core": "12.0.4",
159
- "@inquirer/prompts": "8.7.3",
163
+ "@inquirer/core": "12.0.5",
164
+ "@inquirer/prompts": "8.7.4",
160
165
  "@jridgewell/trace-mapping": "0.3.31",
161
- "@napi-rs/keyring": "2.1.1",
166
+ "@napi-rs/keyring": "2.1.2",
162
167
  "@opentelemetry/api": "1.9.1",
168
+ "@opentelemetry/context-async-hooks": "2.12.0",
163
169
  "@opentelemetry/exporter-trace-otlp-proto": "0.223.0",
164
170
  "@opentelemetry/resources": "2.12.0",
165
- "@opentelemetry/sdk-trace-node": "2.12.0",
171
+ "@opentelemetry/sdk-trace": "2.12.0",
166
172
  "@opentelemetry/semantic-conventions": "1.43.0",
167
173
  "@oxc-project/types": "0.154.0",
168
174
  "@politty/zod": "0.5.2",
@@ -182,7 +188,7 @@
182
188
  "globals": "17.13.0",
183
189
  "graphql": "17.0.2",
184
190
  "inflection": "3.0.2",
185
- "kysely": "0.29.6",
191
+ "kysely": "0.29.7",
186
192
  "lines-db": "0.18.0",
187
193
  "mime-types": "3.0.2",
188
194
  "open": "11.0.4",
@@ -190,8 +196,8 @@
190
196
  "p-limit": "7.3.3",
191
197
  "pathe": "2.0.3",
192
198
  "pgsql-ast-parser": "12.0.2",
193
- "pkg-types": "2.3.3",
194
- "rolldown": "1.2.13",
199
+ "pkg-types": "2.4.0",
200
+ "rolldown": "1.2.14",
195
201
  "semver": "7.8.5",
196
202
  "sql-highlight": "6.1.0",
197
203
  "std-env": "4.3.0",
@@ -203,21 +209,22 @@
203
209
  "zod": "4.6.7"
204
210
  },
205
211
  "devDependencies": {
212
+ "@ark/attest": "0.57.0",
206
213
  "@electric-sql/pglite": "0.5.10",
207
- "@opentelemetry/sdk-trace-base": "2.12.0",
208
214
  "@tailor-platform/shared": "^0.0.0",
209
215
  "@tailor-platform/tailor-proto": "^0.0.1",
210
216
  "@types/mime-types": "3.0.1",
211
217
  "@types/node": "24.19.2",
212
218
  "@types/semver": "7.8.0",
213
- "@typescript/native-preview": "7.0.0-dev.20260707.2",
214
219
  "@vitest/coverage-v8": "5.0.5",
215
- "eslint-plugin-zod": "5.0.0",
220
+ "eslint-plugin-zod": "5.2.0",
216
221
  "oxfmt": "0.73.0",
217
222
  "oxlint": "1.88.0",
218
223
  "oxlint-tsgolint": "7.0.2004",
219
224
  "sonda": "0.14.0",
220
225
  "tsdown": "0.23.1",
226
+ "tsrs": "0.16.0-ts7.1.0-dev.20260929",
227
+ "tsx": "4.23.15",
221
228
  "typescript": "6.0.3",
222
229
  "vitest": "5.0.5",
223
230
  "zinfer": "0.4.7"
@@ -256,8 +263,8 @@
256
263
  "check:import-cycles": "node --experimental-strip-types scripts/check-import-cycles.ts",
257
264
  "lint:fix": "oxlint --type-aware . --fix",
258
265
  "typecheck": "tsc --noEmit",
259
- "typecheck:go": "tsgo --tsBuildInfoFile ./.tsgo.tsbuildinfo",
260
- "perf:typecheck": "bash scripts/perf/exec.sh",
266
+ "typecheck:rs": "node scripts/typecheck-native.ts",
267
+ "perf:attest": "tsx scripts/perf/attest/run.ts",
261
268
  "perf:runtime": "bash scripts/perf/runtime-perf.sh",
262
269
  "postinstall": "node postinstall.mjs",
263
270
  "measure:bundle": "node ../../scripts/measure-bundle-size-sonda.js",
@@ -1 +0,0 @@
1
- export{t as defineApplication,n as generatePluginFilesIfNeeded}from"./application-D0xNJbag.mjs";