@tailor-platform/sdk 2.26.0 → 2.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/dist/application-DFeuELRo.mjs +200 -0
- package/dist/application-DFeuELRo.mjs.map +1 -0
- package/dist/application-nT_4a5HO.mjs +1 -0
- package/dist/cli/commands/deploy/deploy.d.mts +2 -0
- package/dist/cli/commands/deploy/tailordb/migration-validation.d.mts +2 -1
- package/dist/cli/commands/tailordb/migrate/bundler.d.mts +4 -6
- package/dist/cli/commands/tailordb/migrate/diff-calculator.d.mts +6 -0
- package/dist/cli/commands/tailordb/migrate/remote-state.d.mts +1 -0
- package/dist/cli/lib.d.mts +2 -2
- package/dist/cli/lib.mjs +1 -1
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/main.mjs +54 -54
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/shared/workspace-name.d.mts +2 -0
- package/dist/completion/zsh-worker.zsh +4 -2
- package/dist/configure/config/types.d.mts +11 -0
- package/dist/configure/index.d.mts +6 -6
- package/dist/configure/index.mjs +1 -1
- package/dist/configure/index.mjs.map +1 -1
- package/dist/configure/services/auth/types.d.mts +6 -1
- package/dist/configure/services/executor/trigger/event.d.mts +1 -1
- package/dist/configure/services/executor/trigger/webhook.d.mts +1 -1
- package/dist/configure/services/idp/permission.d.mts +2 -2
- package/dist/configure/services/resolver/resolver.d.mts +1 -1
- package/dist/configure/services/tailordb/schema.d.mts +1 -1
- package/dist/configure/services/tailordb/types.d.mts +1 -1
- package/dist/configure/services/workflow/job.d.mts +1 -1
- package/dist/configure/types/field.types.d.mts +48 -11
- package/dist/configure/types/type.d.mts +1 -1
- package/dist/date-CgP7gMG_.mjs +2 -0
- package/dist/date-CgP7gMG_.mjs.map +1 -0
- package/dist/{secretmanager-5olfnI1b.mjs → date-representation-Cr7W7Pn2.mjs} +2 -2
- package/dist/date-representation-Cr7W7Pn2.mjs.map +1 -0
- package/dist/{field-parse-DhnFs9WG.mjs → field-parse-C41e3UAF.mjs} +2 -2
- package/dist/{field-parse-DhnFs9WG.mjs.map → field-parse-C41e3UAF.mjs.map} +1 -1
- package/dist/idp-G_ojPBB5.mjs.map +1 -1
- package/dist/{manager-8-JOvuLl.mjs → manager-Bk-7vkwd.mjs} +2 -2
- package/dist/{manager-8-JOvuLl.mjs.map → manager-Bk-7vkwd.mjs.map} +1 -1
- package/dist/migration-steps-C6F3zNdQ.mjs +3 -0
- package/dist/migration-steps-C6F3zNdQ.mjs.map +1 -0
- package/dist/plugin/index.mjs +1 -1
- package/dist/register-ts-hook-D6KXrGrB.mjs +1009 -0
- package/dist/register-ts-hook-D6KXrGrB.mjs.map +1 -0
- package/dist/runtime/idp.d.mts +6 -0
- package/dist/runtime/index.mjs +1 -1
- package/dist/runtime/types.d.mts +5 -0
- package/dist/{schema-CRtAmDcl.mjs → schema-B6hJQWp6.mjs} +2 -2
- package/dist/{schema-CRtAmDcl.mjs.map → schema-B6hJQWp6.mjs.map} +1 -1
- package/dist/seed/index.d.mts +3 -1
- package/dist/seed/index.mjs +7 -9
- package/dist/seed/index.mjs.map +1 -1
- package/dist/service-Cc7OY0Oh.mjs +7 -0
- package/dist/service-Cc7OY0Oh.mjs.map +1 -0
- package/dist/service-sH2MZ4_W.mjs +1 -0
- package/dist/service_pb-Dw2ilc1O.mjs +2 -0
- package/dist/{service_pb-ZClWtj1X.mjs.map → service_pb-Dw2ilc1O.mjs.map} +1 -1
- package/dist/service_pb-GDOqfwb6.mjs +1 -0
- package/dist/types/field.generated.d.mts +2 -2
- package/dist/utils/test/index.mjs +1 -1
- package/dist/vitest/date-representation.d.mts +23 -0
- package/dist/vitest/index.d.mts +3 -1
- package/dist/vitest/index.mjs +1 -1
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/migration-steps.d.mts +45 -0
- package/dist/vitest/mocks/file.d.mts +1 -1
- package/dist/vitest/mocks/idp.d.mts +1 -0
- package/dist/vitest/setup.d.mts +3 -1
- package/dist/vitest/setup.mjs +1 -1
- package/dist/vitest/setup.mjs.map +1 -1
- package/dist/{workspace_resource_pb-CxR_6fyN.mjs → workspace_resource_pb-ChWejcj0.mjs} +2 -2
- package/dist/workspace_resource_pb-ChWejcj0.mjs.map +1 -0
- package/docs/cli/application.md +24 -16
- package/docs/cli/function.md +2 -0
- package/docs/cli/workspace.md +1 -1
- package/docs/configuration.md +19 -6
- package/docs/github-actions.md +124 -58
- package/docs/plugin/custom.md +2 -1
- package/docs/services/auth.md +20 -0
- package/docs/services/executor.md +2 -2
- package/docs/services/idp.md +6 -1
- package/docs/services/resolver.md +3 -1
- package/docs/services/staticwebsite.md +5 -5
- package/docs/services/tailordb-migration.md +157 -35
- package/docs/services/tailordb.md +2 -2
- package/docs/testing.md +3 -0
- package/package.json +12 -12
- package/dist/application-ChqHuhZW.mjs +0 -1
- package/dist/application-DS0XKBtK.mjs +0 -200
- package/dist/application-DS0XKBtK.mjs.map +0 -1
- package/dist/date-Dri6Yas-.mjs +0 -2
- package/dist/date-Dri6Yas-.mjs.map +0 -1
- package/dist/register-ts-hook-BuwkLuxC.mjs +0 -925
- package/dist/register-ts-hook-BuwkLuxC.mjs.map +0 -1
- package/dist/secretmanager-5olfnI1b.mjs.map +0 -1
- package/dist/service-Bx-7Lz06.mjs +0 -1
- package/dist/service-CL1Bp4It.mjs +0 -7
- package/dist/service-CL1Bp4It.mjs.map +0 -1
- package/dist/service_pb-ZClWtj1X.mjs +0 -2
- package/dist/service_pb-dB3RrGeh.mjs +0 -1
- package/dist/workspace_resource_pb-CxR_6fyN.mjs.map +0 -1
|
@@ -97,15 +97,19 @@ A typical change cycle:
|
|
|
97
97
|
3. **Edit `migrate.ts`** to populate data for the new required field:
|
|
98
98
|
|
|
99
99
|
```typescript
|
|
100
|
-
import type { Transaction } from "./db";
|
|
100
|
+
import type { MigrationSteps, Transaction } from "./db";
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
async function populateUserEmail(trx: Transaction): Promise<void> {
|
|
103
103
|
await trx
|
|
104
104
|
.updateTable("User")
|
|
105
105
|
.set({ email: "default@example.com" })
|
|
106
106
|
.where("email", "is", null)
|
|
107
107
|
.execute();
|
|
108
108
|
}
|
|
109
|
+
|
|
110
|
+
export const steps = {
|
|
111
|
+
populateUserEmail: { run: populateUserEmail },
|
|
112
|
+
} satisfies MigrationSteps;
|
|
109
113
|
```
|
|
110
114
|
|
|
111
115
|
4. **Apply.**
|
|
@@ -236,7 +240,7 @@ Sometimes existing data must be transformed without any schema change — fixing
|
|
|
236
240
|
tailor tailordb migration generate --data-only --name "normalize legacy phone numbers"
|
|
237
241
|
```
|
|
238
242
|
|
|
239
|
-
This writes a numbered migration with an empty `diff.json`, a `migrate.ts` skeleton, and `db.ts` typed against the current schema. Edit `migrate.ts` to implement the transformation; the next `tailor deploy` runs it like any other migration script — in a single transaction, advancing the migration checkpoint (see [Performance and Large Tables](#performance-and-large-tables) for batching patterns). Because the entry is part of the migration history, the fix is versioned, ordered relative to schema changes, and applied once per workspace.
|
|
243
|
+
This writes a numbered migration with an empty `diff.json`, a `migrate.ts` skeleton (a single `migrate` step), and `db.ts` typed against the current schema. Edit `migrate.ts` to implement the transformation; the next `tailor deploy` runs it like any other migration script — in a single transaction, advancing the migration checkpoint (see [Performance and Large Tables](#performance-and-large-tables) for batching patterns). Because the entry is part of the migration history, the fix is versioned, ordered relative to schema changes, and applied once per workspace.
|
|
240
244
|
|
|
241
245
|
The command requires a clean state: if the namespace has schema changes that are not yet in migration files, generate the schema migration first. With multiple namespaces, pass `--namespace` to name the target. `--data-only` cannot be combined with `--init`, `--rename`, `--drop`, or `--expand-contract`.
|
|
242
246
|
|
|
@@ -268,21 +272,21 @@ export default defineConfig({
|
|
|
268
272
|
|
|
269
273
|
## Generated Files
|
|
270
274
|
|
|
271
|
-
| File | When generated | Description
|
|
272
|
-
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
273
|
-
| `0000/schema.json` | First `migration generate` | Full snapshot of all tables in the namespace.
|
|
274
|
-
| `XXXX/diff.json` | Every subsequent migration | Field-level diff against the previous snapshot.
|
|
275
|
-
| `XXXX/migrate.ts` | Auto-generated for breaking changes and `--data-only` migrations; added manually via `tailordb migration script` for warning-tier changes | Data transformation script. The `main` export receives a Kysely `Transaction
|
|
276
|
-
| `XXXX/db.ts` | Generated once when `migrate.ts` is created | Kysely types reflecting the schema **before** this migration. Exports `Database`, `Transaction`, and `MigrationContext`.
|
|
277
|
-
| `XXXX/db.pglite.ts` | Generated with `db.ts` | `CREATE TABLE` script of the same schema, for running `migrate.ts` on PGlite. Never deployed.
|
|
278
|
-
| `XXXX/migrate.test.ts` | Added via `tailordb migration script --with-test` | Unit-test scaffold for `migrate.ts` (see [Testing Migrations Locally](#testing-migrations-locally)). Never deployed.
|
|
279
|
-
| `XXXX/migrate.pglite.test.ts` | Added via `tailordb migration script --with-test` when `@electric-sql/pglite` is installed | PGlite test scaffold for `migrate.ts`. Never deployed.
|
|
275
|
+
| File | When generated | Description |
|
|
276
|
+
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
277
|
+
| `0000/schema.json` | First `migration generate` | Full snapshot of all tables in the namespace. |
|
|
278
|
+
| `XXXX/diff.json` | Every subsequent migration | Field-level diff against the previous snapshot. |
|
|
279
|
+
| `XXXX/migrate.ts` | Auto-generated for breaking changes and `--data-only` migrations; added manually via `tailordb migration script` for warning-tier changes | Data transformation script. The `main` export receives a Kysely `Transaction`; a `steps` export runs each step in its own transaction. |
|
|
280
|
+
| `XXXX/db.ts` | Generated once when `migrate.ts` is created | Kysely types reflecting the schema **before** this migration. Exports `Database`, `Transaction`, and `MigrationContext`. |
|
|
281
|
+
| `XXXX/db.pglite.ts` | Generated with `db.ts` | `CREATE TABLE` script of the same schema, for running `migrate.ts` on PGlite. Never deployed. |
|
|
282
|
+
| `XXXX/migrate.test.ts` | Added via `tailordb migration script --with-test` | Unit-test scaffold for `migrate.ts` (see [Testing Migrations Locally](#testing-migrations-locally)). Never deployed. |
|
|
283
|
+
| `XXXX/migrate.pglite.test.ts` | Added via `tailordb migration script --with-test` when `@electric-sql/pglite` is installed | PGlite test scaffold for `migrate.ts`. Never deployed. |
|
|
280
284
|
|
|
281
285
|
`db.ts` reflects the pre-migration schema because the script runs after the pre-migration phase has temporarily relaxed breaking constraints (e.g., a new `required` field is added as `optional` first), so the data being read still matches the previous shape.
|
|
282
286
|
|
|
283
287
|
### Migration file format compatibility
|
|
284
288
|
|
|
285
|
-
Migration files are versioned independently of the SDK package. This SDK writes format version `
|
|
289
|
+
Migration files are versioned independently of the SDK package. This SDK writes format version `8` and reads versions `1` through `8`. It normalizes supported older formats in memory; it never rewrites applied migration files on disk. Format version `6` records renames of members inside nested fields (`memberRenames`); older SDK versions refuse to read it rather than deploying such a migration without the copy step. Format version `7` records whether a migration's `db.ts` uses Temporal column types (`temporal`); older SDK versions refuse to read it rather than running the script with `Date` values. Format version `8` records the `defaultDateRepresentation` a migration script was generated under (`dateRepresentation`); older SDK versions refuse to read it rather than running the script with string values.
|
|
286
290
|
|
|
287
291
|
Supported histories also preserve the behavior of field hooks and validators saved by older SDKs, including access to the record and boolean validators with a separate error message. Legacy update hooks retain existing values for omitted fields; explicitly supplied values, including `null`, take precedence. This applies to both snapshots and diffs, including nested fields. Your existing migration files can remain as generated.
|
|
288
292
|
|
|
@@ -294,6 +298,8 @@ There is no migration-file conversion command. Keeping applied files unchanged p
|
|
|
294
298
|
|
|
295
299
|
## Migration Script Anatomy
|
|
296
300
|
|
|
301
|
+
`migration generate` and `migration script` scaffold `migrate.ts` as `steps`, with one step for each schema change that needs a data migration (see [Splitting a migration into steps](#splitting-a-migration-into-steps)). The examples in this section use `main`, the single-transaction form, which still works. A `steps` script receives the same transaction and `MigrationContext` in each step's `run`.
|
|
302
|
+
|
|
297
303
|
```typescript
|
|
298
304
|
import type { Transaction } from "./db";
|
|
299
305
|
|
|
@@ -365,6 +371,85 @@ The `env` values are injected at bundle time (the same mechanism as resolvers/ex
|
|
|
365
371
|
- `console.log` / `console.error` output is captured and surfaced under `Logs:` in the apply output. Use it sparingly for progress markers on long-running migrations.
|
|
366
372
|
- The script is idempotency-friendly by default because it runs inside a transaction, but **plan for re-execution**: if a later migration in the same `apply` fails, the platform may retry the apply, and you may want your script to tolerate already-migrated rows (e.g., `where("email", "is", null)` instead of unconditional updates).
|
|
367
373
|
|
|
374
|
+
### Splitting a migration into steps
|
|
375
|
+
|
|
376
|
+
A script that exports `main` runs as one job in one transaction. When the work is too large for one job, or splits naturally into independent parts, export `steps` instead. Each step runs as its own job in its own transaction, so each step gets the job's execution-time limit to itself and commits on its own.
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
import type { MigrationSteps } from "./db";
|
|
380
|
+
|
|
381
|
+
export const steps = {
|
|
382
|
+
backfillCustomer: {
|
|
383
|
+
run: async (trx) => {
|
|
384
|
+
await trx
|
|
385
|
+
.updateTable("Customer")
|
|
386
|
+
.set((eb) => ({ updatedAt: eb.ref("createdAt") }))
|
|
387
|
+
.where("updatedAt", "is", null)
|
|
388
|
+
.execute();
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
backfillInvoice: {
|
|
392
|
+
run: async (trx) => {
|
|
393
|
+
await trx
|
|
394
|
+
.updateTable("Invoice")
|
|
395
|
+
.set((eb) => ({ updatedAt: eb.ref("createdAt") }))
|
|
396
|
+
.where("updatedAt", "is", null)
|
|
397
|
+
.execute();
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
recomputeTotals: {
|
|
401
|
+
dependsOn: ["backfillInvoice"],
|
|
402
|
+
run: async (trx, { env }) => {
|
|
403
|
+
// Runs only after backfillInvoice has completed.
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
} satisfies MigrationSteps;
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
- A script exports either `main` or `steps`. If it exports both, `main` runs as before and `steps` is ignored with a warning from `tailor deploy` and `tailor tailordb migration validate`.
|
|
410
|
+
- Generated scripts export `steps`. Each schema change that needs a data migration gets its own step, named for what it does: `populate<Table><Field>` for an added required field (for example `populateUserEmail`), `rename<Table><Field>` for a renamed field, `copy<Old>To<New>` for a renamed table, `update<Table><Field>` for a changed field, `resolve<Table><Index>` for a unique index, and `convert<Table><Field>` for a field type conversion. When that name is not a valid step name (for example because it is longer than 64 characters), or when another step or a helper in the script already uses it, the step is named `change<N>` instead (`expand<N>` for a conversion through a temporary field), where N is the change's position in the migration; the fallback does not mean the schema-derived name was wrong. A step lists in `dependsOn` every earlier step that touches the same field (a foreign key that changes its target also touches `id` of the table it now points to), so steps that touch the same field run in the order the changes are listed and changes to different fields do not wait for each other. The copy of a renamed table's rows runs after the copies of the renamed tables its foreign keys point to, so the rows they reference already exist. It skips the rows whose ids the new table already has, so running it again does not insert them twice. A step that rewrites values that already exist (the duplicate resolution of a unique index or field, a field change, a conversion through a temporary field) has a comment above its function saying so: once it commits it cannot be undone, so check the values it writes before you deploy. Where the scaffold cannot pick a value for you (what an added required field holds in existing records, the new value of duplicates, the value that replaces a removed enum value, the new reference of a retargeted foreign key, the copy of renamed tables that reference each other), it calls `TODO(message)`, which `./db` exports. The migration fails at that call, and `tailordb migration validate` and `tailor deploy` reject a `migrate.ts` that still calls it before anything is changed. Replace each call with the value, then remove the `TODO` import. A `--data-only` migration, and a migration with no data statements to scaffold, get a single `migrate` step; split it into several steps where the work divides.
|
|
411
|
+
- Each step is an object with a `run` function — it receives its own transaction and the same `MigrationContext` as `main` — and an optional `dependsOn` list naming the steps that must complete before it starts.
|
|
412
|
+
- Steps with no dependency between them have no guaranteed order. Declaration order is not execution order, and a future SDK version may run independent steps concurrently, so declare `dependsOn` for every ordering your steps rely on.
|
|
413
|
+
- Declare `steps` directly as `export const steps = { ... }`, with each step written inside it as an object literal with a literal name and `dependsOn` list. Step names start with a letter and contain only letters, digits, and underscores, up to 64 characters. `tailor tailordb migration validate` and `tailor deploy` reject unknown dependencies and cycles before anything is changed.
|
|
414
|
+
- Each step runs as a separate job, so steps cannot hand data to each other through module-level variables; read what a step needs from the database.
|
|
415
|
+
- `MigrationSteps` is declared by `db.ts` files generated by this SDK version. With an older `db.ts`, annotate each `run` parameter with `Transaction` instead.
|
|
416
|
+
|
|
417
|
+
A script with a single step runs like a `main` script: one job and one transaction, and a failure inside the transaction rolls its data changes back. Nothing is recorded as in progress. If the post-migration schema changes fail after the transaction committed, the schema is restored to its state before the migration but the data changes stay, so the next deploy runs the step again and the step must tolerate rows it already migrated. The rules below apply once the script has two or more steps. A migration that an earlier deploy already left in progress keeps running step by step until it completes, even if the script has since been reduced to one step.
|
|
418
|
+
|
|
419
|
+
Because steps commit separately, a failure does not undo the steps that already completed:
|
|
420
|
+
|
|
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.
|
|
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
|
+
- 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
|
+
- 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
|
+
- 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
|
+
- 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.
|
|
429
|
+
|
|
430
|
+
#### Editing steps of a migration in progress
|
|
431
|
+
|
|
432
|
+
When `tailor deploy` finds a migration in progress, what you changed in `migrate.ts` decides how it continues:
|
|
433
|
+
|
|
434
|
+
| What you changed since the earlier deploy | What the next `tailor deploy` does |
|
|
435
|
+
| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
436
|
+
| The code of a step that has not completed (the failed step or a later one) | Resumes: completed steps do not run again, and the rest run with the updated code. |
|
|
437
|
+
| The code of a step that already completed | Resumes, but the change takes no effect: that step does not run again. |
|
|
438
|
+
| A step added, removed, or renamed, or the order the steps run in | Runs every step again from the beginning, and warns about it first. The warning lists the steps that had already succeeded. |
|
|
439
|
+
| Nothing, but the earlier run was canceled or is no longer available | Runs every step again from the beginning, and warns about it first. |
|
|
440
|
+
|
|
441
|
+
Running every step again runs completed steps a second time, which breaks a step that is not safe to run twice. To keep specific steps from running again, pass them to `--migration-skip-steps` as comma-separated `<namespace>/<step>` entries:
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
tailor deploy --migration-skip-steps main-db/backfillUser,main-db/backfillInvoice
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
- A skipped step is reported as completed and is not run. Only steps that succeeded in the migration's earlier run can be skipped, so a step you renamed cannot be.
|
|
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
|
+
- 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
|
+
|
|
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.
|
|
452
|
+
|
|
368
453
|
## Supported Schema Changes
|
|
369
454
|
|
|
370
455
|
| Change Type | Breaking? | Migration Script? | Notes |
|
|
@@ -425,9 +510,9 @@ A `float` field that is already unique cannot convert to `decimal` in place, bec
|
|
|
425
510
|
For example, changing `User.age` from `integer` to `float` generates a `migrate.ts` that scans non-null values in batches of 100:
|
|
426
511
|
|
|
427
512
|
```typescript
|
|
428
|
-
import type
|
|
513
|
+
import { TODO, type MigrationSteps, type Transaction } from "./db";
|
|
429
514
|
|
|
430
|
-
|
|
515
|
+
async function updateUserAge(trx: Transaction): Promise<void> {
|
|
431
516
|
// Normalize User.age from integer to float while the previous type is still active
|
|
432
517
|
{
|
|
433
518
|
let lastId: string | undefined;
|
|
@@ -445,11 +530,11 @@ export async function main(trx: Transaction): Promise<void> {
|
|
|
445
530
|
if (rows.length === 0) break;
|
|
446
531
|
|
|
447
532
|
for (const row of rows) {
|
|
448
|
-
// TODO(tailor-migration-review): Remove this marker and the `never` annotation after reviewing the normalization.
|
|
449
|
-
// Keep the value accepted by the active integer type and castable to float.
|
|
450
533
|
const sourceValue = row.age;
|
|
451
534
|
if (sourceValue === null) continue;
|
|
452
|
-
const normalizedValue
|
|
535
|
+
const normalizedValue = TODO(
|
|
536
|
+
"normalize User.age to a value the active integer type accepts and the float type can cast",
|
|
537
|
+
);
|
|
453
538
|
if (Object.is(normalizedValue, sourceValue)) continue;
|
|
454
539
|
await trx
|
|
455
540
|
.updateTable("User")
|
|
@@ -461,9 +546,13 @@ export async function main(trx: Transaction): Promise<void> {
|
|
|
461
546
|
}
|
|
462
547
|
}
|
|
463
548
|
}
|
|
549
|
+
|
|
550
|
+
export const steps = {
|
|
551
|
+
updateUserAge: { run: updateUserAge },
|
|
552
|
+
} satisfies MigrationSteps;
|
|
464
553
|
```
|
|
465
554
|
|
|
466
|
-
The generated `
|
|
555
|
+
The generated `TODO()` call stops the migration until you replace it, and `tailordb migration validate` and `tailor deploy` reject the migration while it is still there. If the existing values are already suitable for the target type, replace the call with `sourceValue` to accept the identity transformation; it does not write any rows. If values need application-specific normalization, replace it with your expression, keeping the result valid for both the active source type and the target type. The source field contract remains active until the script finishes; for example, an `integer` → `float` script cannot write fractional values during this phase.
|
|
467
556
|
|
|
468
557
|
### Converting a field type
|
|
469
558
|
|
|
@@ -476,10 +565,10 @@ User.price changes from string to integer, which cannot be applied in one step.
|
|
|
476
565
|
|
|
477
566
|
Confirming writes two migrations:
|
|
478
567
|
|
|
479
|
-
1. **The conversion.** Adds a temporary field (`priceMigrate`), converts each stored value into it, and clears and removes the original field. Edit the conversion expression before deploying: the generated `
|
|
568
|
+
1. **The conversion.** Adds a temporary field (`priceMigrate`), converts each stored value into it, and clears and removes the original field. Edit the conversion expression before deploying: the generated `TODO()` call fails the migration, and `tailordb migration validate` and `tailor deploy` reject it while the call is still there. When only the array-ness changes (`string` → `string[]`), the conversion stores each value as a one-element array and has no `TODO()` call; when the element type changes as well (`integer` → `string[]`), you convert the element and the script wraps it.
|
|
480
569
|
2. **The rename.** Renames the temporary field back to `price`. Its copy script is complete, but this migration also carries every other schema change the same run picked up, so review it as you would any generated migration.
|
|
481
570
|
|
|
482
|
-
If converting to an array also reduces a decimal field's `scale` or removes enum values, the conversion keeps the
|
|
571
|
+
If converting to an array also reduces a decimal field's `scale` or removes enum values, the conversion keeps the `TODO()` call. Edit the element conversion to satisfy the target field before deploying.
|
|
483
572
|
|
|
484
573
|
`tailor deploy` applies both. Because the conversion only touches rows whose original value is still set, a re-run resumes where it stopped rather than converting a row twice.
|
|
485
574
|
|
|
@@ -508,7 +597,7 @@ Some changes are still rejected and need a temporary field you add yourself —
|
|
|
508
597
|
|
|
509
598
|
The command performs the following sequence:
|
|
510
599
|
|
|
511
|
-
1. Reads each migration-enabled namespace's `sdk-migration` checkpoint from the source workspace and reconstructs that exact snapshot from local migration history.
|
|
600
|
+
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.
|
|
512
601
|
2. Creates a temporary workspace in the same region, organization, and folder as the source, unless `--target-workspace-id` names an existing throwaway workspace.
|
|
513
602
|
3. Deploys the checkpoint snapshots and writes their checkpoint labels.
|
|
514
603
|
4. Loads fixture data or clones source records.
|
|
@@ -582,7 +671,7 @@ When you run `tailor deploy`, the SDK detects pending migrations (anything past
|
|
|
582
671
|
For each pending migration:
|
|
583
672
|
|
|
584
673
|
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.
|
|
585
|
-
2. **Script execution**: If `migrate.ts` exists on disk for this migration, it is bundled and
|
|
674
|
+
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).
|
|
586
675
|
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.
|
|
587
676
|
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.
|
|
588
677
|
|
|
@@ -608,7 +697,7 @@ Namespace: tailordb
|
|
|
608
697
|
|
|
609
698
|
The error also points you at `migration status`, `migration generate`, `migration sync`, and `migration set` — see [Remote schema drift detected](#remote-schema-drift-detected) for which one applies.
|
|
610
699
|
|
|
611
|
-
To run the same checks without deploying — plus migration file integrity (numbering, parseable contents, a `migrate.ts` or a recorded `--no-script` acknowledgment for every migration that requires a script, and no
|
|
700
|
+
To run the same checks without deploying — plus migration file integrity (numbering, parseable contents, a `migrate.ts` or a recorded `--no-script` acknowledgment for every migration that requires a script, and no `TODO()` call left in a `migrate.ts`):
|
|
612
701
|
|
|
613
702
|
```bash
|
|
614
703
|
tailor tailordb migration validate
|
|
@@ -728,13 +817,17 @@ Use `tailor tailordb migration rebaseline` rather than combining `migration gene
|
|
|
728
817
|
|
|
729
818
|
## Failure Recovery
|
|
730
819
|
|
|
731
|
-
|
|
820
|
+
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:
|
|
732
821
|
|
|
733
822
|
- **The transaction rolls back** for that migration's script. Database changes the script made are undone.
|
|
734
823
|
- **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.
|
|
735
824
|
- The whole `apply` aborts and the checkpoint label is not bumped. Subsequent migrations in the same run do not execute.
|
|
736
825
|
|
|
737
|
-
The rollback is best-effort per table; if reverting a table fails, a warning is logged and the original migration error is still reported.
|
|
826
|
+
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
|
+
|
|
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.
|
|
829
|
+
|
|
830
|
+
`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.
|
|
738
831
|
|
|
739
832
|
After a failure:
|
|
740
833
|
|
|
@@ -791,7 +884,7 @@ The migration script runs in a single transaction. For tables with many rows:
|
|
|
791
884
|
|
|
792
885
|
- Prefer set-based SQL (`updateTable(...).set(...).where(...)`) over per-row loops.
|
|
793
886
|
- If a per-row loop is unavoidable, batch by primary key range. Avoid `OFFSET`-based pagination — it scans previously-seen rows on every page.
|
|
794
|
-
- Long-running transactions can hit platform timeouts and hold locks. For very large backfills, consider splitting the work across multiple migrations, each operating on a subset.
|
|
887
|
+
- Long-running transactions can hit platform timeouts and hold locks. For very large backfills, consider splitting the work into [steps](#splitting-a-migration-into-steps) or across multiple migrations, each operating on a subset.
|
|
795
888
|
- Add `LIMIT` and resumability (idempotent `where` clauses) so a re-run after a transient failure converges.
|
|
796
889
|
|
|
797
890
|
### Migrations that take longer than a minute
|
|
@@ -799,12 +892,12 @@ The migration script runs in a single transaction. For tables with many rows:
|
|
|
799
892
|
Migration scripts run as workflow jobs, so they are not bound by the 60-second
|
|
800
893
|
limit that applies to synchronous script execution. `tailor deploy` waits for
|
|
801
894
|
the migration and reports its logs as usual. Execution time is still bounded —
|
|
802
|
-
a workflow job has its own
|
|
895
|
+
a workflow job has its own execution-time limit.
|
|
803
896
|
|
|
804
|
-
|
|
805
|
-
locks and transaction size continues to apply
|
|
806
|
-
|
|
807
|
-
|
|
897
|
+
A `main` script still runs in a single transaction, so the guidance above about
|
|
898
|
+
locks and transaction size continues to apply. When a migration's work does not
|
|
899
|
+
fit one job, split it into [steps](#splitting-a-migration-into-steps): each step
|
|
900
|
+
runs as its own job and transaction.
|
|
808
901
|
|
|
809
902
|
## Testing Migrations Locally
|
|
810
903
|
|
|
@@ -844,6 +937,8 @@ Stage the rows each query returns with `mock.enqueueResult(...)` or `mock.setQue
|
|
|
844
937
|
|
|
845
938
|
These tests need no platform connection and no `tailor-runtime` environment — they run in a plain Vitest setup. Vitest's default `include` pattern already picks up `migrations/**/migrate.test.ts`; if your config narrows `include`, add the migrations directory. The test file is ignored by `tailor deploy` and never ships to the platform.
|
|
846
939
|
|
|
940
|
+
A migration generated under [`defaultDateRepresentation`](../configuration.md#application-settings) `"temporal"` or `"date"` records that in its `diff.json`, and deploy runs the script with Temporal or `Date` values for `t.date()`, `t.datetime()`, and `t.time()` fields that omit `as`. The scaffold calls `applyDateRepresentation` from `@tailor-platform/sdk/vitest` with the recorded value, or with `"string"` for a migration that records none, and imports `migrate.ts` after that call, so the test runs the script the same way deploy does even after the setting changes in `tailor.config.ts`. Keep that call when editing the test. The record pins the values at run time only: the types of `t` fields declared inside `migrate.ts` follow the current `tailor.d.ts` like everywhere else, so give such fields an explicit `as` to keep their types and values aligned after the setting changes. The helper selects the representation but does not provide `Temporal` itself, so run such a test in the [`tailor-runtime` Vitest environment](../testing.md#temporal), which installs it on Node.js versions without native Temporal; this is the one case where a migration unit test needs that environment (a migration recorded under `"date"`, or without a record, needs no Temporal and runs in a plain Vitest setup).
|
|
941
|
+
|
|
847
942
|
### Executing migrate.ts against a local Postgres (PGlite)
|
|
848
943
|
|
|
849
944
|
A statement-level test verifies what the script issues, not what it does to data (e.g., whether a `where` clause matches the rows you intended). To run `main` against real rows locally, back Kysely with [`@electric-sql/pglite`](https://pglite.dev/) — an in-memory PostgreSQL — via `createKyselyPGlite` from `@tailor-platform/sdk/vitest`:
|
|
@@ -899,7 +994,7 @@ describe("0005 add required email", () => {
|
|
|
899
994
|
|
|
900
995
|
Pass nested field values as JavaScript objects or arrays of objects, without `JSON.stringify`.
|
|
901
996
|
|
|
902
|
-
When the migration was generated with `kyselyTypePlugin({ temporal: true })`, its `db.ts` types date and datetime fields as `Temporal.PlainDate` and `Temporal.Instant`, and top-level time fields as `Temporal.PlainTime`; nested time fields remain strings. Deploy runs the script with those values. Create the instance with `createKyselyPGlite<Unmigrated<Database>>(pglite, { temporal: true })` so PGlite reads top-level temporal columns back the same way; the scaffold adds the option for such migrations. Run the test in the [`tailor-runtime` Vitest environment](../testing.md#temporal), which provides `Temporal`. A migration generated before `temporal` was enabled still runs with `Date` values on deploy, so its test leaves the option out.
|
|
997
|
+
When the migration was generated with `kyselyTypePlugin({ temporal: true })`, its `db.ts` types date and datetime fields as `Temporal.PlainDate` and `Temporal.Instant`, and top-level time fields as `Temporal.PlainTime`; nested time fields remain strings. Deploy runs the script with those values. Create the instance with `createKyselyPGlite<Unmigrated<Database>>(pglite, { temporal: true })` so PGlite reads top-level temporal columns back the same way; the scaffold adds the option for such migrations. Run the test in the [`tailor-runtime` Vitest environment](../testing.md#temporal), which provides `Temporal`. A migration generated before `temporal` was enabled still runs with `Date` values on deploy, so its test leaves the option out. As in the Kysely mock scaffold, the test calls `applyDateRepresentation` with the migration's recorded `defaultDateRepresentation` (or `"string"`) so `t` date fields parse the way they do on deploy; a migration recorded under `"temporal"` likewise needs the `tailor-runtime` environment for `Temporal`.
|
|
903
998
|
Generated migration types use `Record<string, unknown>` for each nested object so scripts can
|
|
904
999
|
work with both old and new members during a migration; narrow member values before using them.
|
|
905
1000
|
|
|
@@ -908,6 +1003,33 @@ Two caveats keep this from replacing a scratch workspace:
|
|
|
908
1003
|
- PGlite runs full PostgreSQL, while TailorDB supports [a subset of it](https://docs.tailor.tech/guides/function/accessing-tailordb#supported-sql-queries) — a statement that passes here can still be rejected on deploy.
|
|
909
1004
|
- `db.pglite.ts` mirrors the column shape, not the platform: hooks, validations, and permissions do not run, and the limits listed under [Real SQL execution with PGlite](../testing.md#real-sql-execution-with-pglite-mocktailordbwithpglite) apply.
|
|
910
1005
|
|
|
1006
|
+
### Testing a multi-step script
|
|
1007
|
+
|
|
1008
|
+
`runMigrationSteps` from `@tailor-platform/sdk/vitest` runs a script's `steps` in the order `tailor deploy` runs them, each through the transaction callback you pass, so a test can check what a failing step leaves behind. When a step throws, it rejects with a `MigrationStepError` naming the failed step and the steps that completed before it. With `pglite` and `Database` set up as in the PGlite test above:
|
|
1009
|
+
|
|
1010
|
+
```typescript
|
|
1011
|
+
import {
|
|
1012
|
+
createKyselyPGlite,
|
|
1013
|
+
runMigrationSteps,
|
|
1014
|
+
type Unmigrated,
|
|
1015
|
+
} from "@tailor-platform/sdk/vitest";
|
|
1016
|
+
import { steps } from "./migrate";
|
|
1017
|
+
|
|
1018
|
+
const db = createKyselyPGlite<Unmigrated<Database>>(pglite);
|
|
1019
|
+
|
|
1020
|
+
test("backfills every table", async () => {
|
|
1021
|
+
await runMigrationSteps(steps, {
|
|
1022
|
+
transaction: (run) => db.transaction().execute(run),
|
|
1023
|
+
env: { STAGE: "test" },
|
|
1024
|
+
});
|
|
1025
|
+
// assert the rows
|
|
1026
|
+
});
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
`tailor tailordb migration script <N> --with-test` writes both test scaffolds against `steps` when `migrate.ts` exports `steps`, calling `runMigrationSteps` instead of `main`.
|
|
1030
|
+
|
|
1031
|
+
To test one step on its own, call its `run` with a transaction — and a `{ env }` context when the step takes one: `await db.transaction().execute((trx) => steps.backfillInvoice.run(trx))`. With `createKyselyMock`, pass `transaction: (run) => mock.withTx(run)`.
|
|
1032
|
+
|
|
911
1033
|
### Beyond unit tests
|
|
912
1034
|
|
|
913
1035
|
A unit test verifies which statements the script issues; a PGlite test verifies what they do to the rows you staged. Neither runs against your actual data. To cover that:
|
|
@@ -932,7 +1054,7 @@ For genuinely different schemas across environments, prefer separate workspaces
|
|
|
932
1054
|
1. `tailor tailordb migration status` to see local vs remote.
|
|
933
1055
|
2. Compare with teammates — has someone applied different migrations?
|
|
934
1056
|
3. If remote was changed manually, decide whether to update local migrations to match or to use `migration set <N>` to align bookkeeping.
|
|
935
|
-
4. To force the remote schema back to a known snapshot, use `migration sync <N>` (see [`migration sync` Semantics](#migration-sync-semantics)).
|
|
1057
|
+
4. To force the remote schema back to a known snapshot, use `migration sync <N>` (see [`migration sync` Semantics](#migration-sync-semantics)). If the drift follows a failed deploy that warned it could not restore a table's schema, see [Failure Recovery](#failure-recovery) instead.
|
|
936
1058
|
5. As a last resort in non-production environments, `--no-schema-check` skips both checks. Do not use this as a routine workaround.
|
|
937
1059
|
|
|
938
1060
|
### "Remote migration checkpoint is not in the local migration history" error
|
|
@@ -981,7 +1103,7 @@ One specific cause is a `migration rebaseline` run on a different environment wh
|
|
|
981
1103
|
|
|
982
1104
|
**Cause:** Runtime error in your `migrate.ts`, a permission error from the machine user, or a constraint violation when post-migration tightens tables.
|
|
983
1105
|
|
|
984
|
-
**Resolution:** Read the `Logs:` block. Fix the script or the data assumption it relies on, and re-run `tailor deploy`. The label is not bumped on failure, so the same migration retries.
|
|
1106
|
+
**Resolution:** Read the `Logs:` block. Fix the script or the data assumption it relies on, and re-run `tailor deploy`. The label is not bumped on failure, so the same migration retries. For a script that exports `steps`, the logs of each step are prefixed with its name, and the error names the failed step; the retry resumes from that step (see [Splitting a migration into steps](#splitting-a-migration-into-steps)).
|
|
985
1107
|
|
|
986
1108
|
### `migrate.ts` not found for a migration that needs one
|
|
987
1109
|
|
|
@@ -522,7 +522,7 @@ db.table("User", {
|
|
|
522
522
|
- 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
523
|
- When explicitly set to `false` while an executor taking part in the same run uses this table, `deploy` fails
|
|
524
524
|
- 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
|
|
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
|
|
526
526
|
|
|
527
527
|
**Use cases:**
|
|
528
528
|
|
|
@@ -567,7 +567,7 @@ db.table("User", {
|
|
|
567
567
|
|
|
568
568
|
Control which GraphQL operations (`create`, `update`, `delete`, `read`) are exposed for a table. All operations are enabled by default.
|
|
569
569
|
|
|
570
|
-
While a `deploy` applies pending migrations,
|
|
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.
|
|
571
571
|
|
|
572
572
|
```typescript
|
|
573
573
|
db.table("Order", {
|
package/docs/testing.md
CHANGED
|
@@ -324,6 +324,7 @@ test("namespace-specific mock", async () => {
|
|
|
324
324
|
disabled: false,
|
|
325
325
|
mfaEnrolled: false,
|
|
326
326
|
mfaFactorIds: [],
|
|
327
|
+
passwordUpdatedAt: null,
|
|
327
328
|
});
|
|
328
329
|
|
|
329
330
|
const client = new tailor.idp.Client({ namespace: "my-ns" });
|
|
@@ -446,6 +447,8 @@ export default defineConfig({
|
|
|
446
447
|
|
|
447
448
|
This makes `tailor.secretmanager.getSecret("vault", "key")` return the values defined in your config. You can still override with `mockSecretmanager({ secrets: ... })` in individual tests: a per-test overlay applies only within that test, and the config-loaded secrets remain available to every other test.
|
|
448
449
|
|
|
450
|
+
The same `config` option applies the config's [`defaultDateRepresentation`](./configuration.md#application-settings) to tests: with `"temporal"`, `t.date()`, `t.datetime()`, and `t.time()` fields that omit `as` parse to Temporal values in test files and in the modules they import, as they do in deployed functions, and with `"date"` they parse to `Date` values. The config is read once when Vitest starts, in the Vitest process itself rather than under the `tailor-runtime` restrictions, so restart Vitest after changing the setting. A config file that is missing or does not parse fails the run, as does a value other than `"temporal"` or `"date"`. A config that throws while loading, for example on an environment variable only the deploy environment sets, prints a warning and keeps string values for that run, as the secrets loader already tolerates; the warning notes that tests then disagree with `tailor.d.ts` if the config sets `"temporal"` or `"date"`. Tests that call `resolver.body()` directly bypass the SDK's input conversion and must pass values in the representation the body expects. To apply a representation in a test file regardless of the config, for example in a plain Vitest setup or for a [migration test](./services/tailordb-migration.md#testing-migrations-locally) pinned to the value it was generated under, call `applyDateRepresentation` from `@tailor-platform/sdk/vitest` with `"temporal"`, `"date"`, or `"string"` (the representation of a project that leaves the setting unset); it returns a function that restores the previous representation. The helper only selects the representation: parsing such a field needs `Temporal`, which the `tailor-runtime` environment installs when Node.js lacks it, so outside that environment the test must provide a Temporal implementation itself.
|
|
451
|
+
|
|
449
452
|
### Per-Project Configuration
|
|
450
453
|
|
|
451
454
|
Apply the runtime environment only to unit tests while keeping other test projects (e.g. e2e) in the default Node.js environment:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tailor-platform/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.27.0",
|
|
4
4
|
"description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
"dependencies": {
|
|
152
152
|
"@0no-co/graphql.web": "1.3.4",
|
|
153
153
|
"@badgateway/oauth2-client": "3.3.1",
|
|
154
|
-
"@bufbuild/protobuf": "2.
|
|
154
|
+
"@bufbuild/protobuf": "2.16.0",
|
|
155
155
|
"@bufbuild/protovalidate": "1.3.0",
|
|
156
156
|
"@connectrpc/connect": "2.2.0",
|
|
157
157
|
"@connectrpc/connect-node": "2.2.0",
|
|
@@ -166,11 +166,11 @@
|
|
|
166
166
|
"@opentelemetry/semantic-conventions": "1.43.0",
|
|
167
167
|
"@oxc-project/types": "0.152.0",
|
|
168
168
|
"@politty/zod": "0.3.0",
|
|
169
|
-
"@secretlint/core": "13.0.
|
|
170
|
-
"@secretlint/secretlint-rule-preset-recommend": "13.0.
|
|
169
|
+
"@secretlint/core": "13.0.7",
|
|
170
|
+
"@secretlint/secretlint-rule-preset-recommend": "13.0.7",
|
|
171
171
|
"@standard-schema/spec": "1.1.0",
|
|
172
172
|
"@tailor-platform/function-kysely-tailordb": "0.1.3",
|
|
173
|
-
"@toiroakr/lines-db": "0.
|
|
173
|
+
"@toiroakr/lines-db": "0.15.0",
|
|
174
174
|
"@toiroakr/read-multiline": "0.4.1",
|
|
175
175
|
"@urql/core": "6.0.3",
|
|
176
176
|
"amaro": "1.2.1",
|
|
@@ -180,13 +180,13 @@
|
|
|
180
180
|
"find-up-simple": "1.0.1",
|
|
181
181
|
"get-east-asian-width": "1.7.0",
|
|
182
182
|
"get-tsconfig": "4.14.3",
|
|
183
|
-
"globals": "17.
|
|
183
|
+
"globals": "17.13.0",
|
|
184
184
|
"graphql": "17.0.2",
|
|
185
185
|
"inflection": "3.0.2",
|
|
186
186
|
"kysely": "0.29.6",
|
|
187
187
|
"mime-types": "3.0.2",
|
|
188
188
|
"open": "11.0.4",
|
|
189
|
-
"oxc-parser": "0.
|
|
189
|
+
"oxc-parser": "0.152.0",
|
|
190
190
|
"p-limit": "7.3.3",
|
|
191
191
|
"pathe": "2.0.3",
|
|
192
192
|
"pgsql-ast-parser": "12.0.2",
|
|
@@ -194,7 +194,7 @@
|
|
|
194
194
|
"rolldown": "1.2.12",
|
|
195
195
|
"semver": "7.8.5",
|
|
196
196
|
"sql-highlight": "6.1.0",
|
|
197
|
-
"std-env": "4.
|
|
197
|
+
"std-env": "4.3.0",
|
|
198
198
|
"temporal-polyfill": "1.0.5",
|
|
199
199
|
"temporal-spec": "1.0.1",
|
|
200
200
|
"ts-cron-validator": "1.1.5",
|
|
@@ -208,18 +208,18 @@
|
|
|
208
208
|
"@tailor-platform/shared": "^0.0.0",
|
|
209
209
|
"@tailor-platform/tailor-proto": "^0.0.1",
|
|
210
210
|
"@types/mime-types": "3.0.1",
|
|
211
|
-
"@types/node": "24.19.
|
|
211
|
+
"@types/node": "24.19.1",
|
|
212
212
|
"@types/semver": "7.8.0",
|
|
213
213
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
214
|
-
"@vitest/coverage-v8": "5.0.
|
|
215
|
-
"eslint-plugin-zod": "
|
|
214
|
+
"@vitest/coverage-v8": "5.0.3",
|
|
215
|
+
"eslint-plugin-zod": "5.0.0",
|
|
216
216
|
"oxfmt": "0.71.0",
|
|
217
217
|
"oxlint": "1.86.0",
|
|
218
218
|
"oxlint-tsgolint": "7.0.2003",
|
|
219
219
|
"sonda": "0.14.0",
|
|
220
220
|
"tsdown": "0.23.0",
|
|
221
221
|
"typescript": "6.0.3",
|
|
222
|
-
"vitest": "5.0.
|
|
222
|
+
"vitest": "5.0.3",
|
|
223
223
|
"zinfer": "0.4.7"
|
|
224
224
|
},
|
|
225
225
|
"peerDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{n as e,t}from"./application-DS0XKBtK.mjs";export{t as defineApplication,e as generatePluginFilesIfNeeded};
|