@tailor-platform/sdk 2.29.0 → 2.29.2
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 +28 -0
- package/README.md +2 -1
- package/agent-skills/tailor/SKILL.md +2 -1
- package/dist/aigateway-DsWDjzk4.mjs.map +1 -1
- package/dist/application-D8jruzwo.mjs +199 -0
- package/dist/application-D8jruzwo.mjs.map +1 -0
- package/dist/{application-dalGHdrI.mjs → application-Dm_21z-i.mjs} +1 -1
- package/dist/assert-WeXvmG4j.mjs.map +1 -1
- package/dist/authconnection-CynFBIv8.mjs.map +1 -1
- package/dist/brand-C8nMKhJC.mjs.map +1 -1
- package/dist/cli/lib.mjs +1 -1
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/main.mjs +50 -50
- package/dist/cli/main.mjs.map +1 -1
- package/dist/completion/zsh-worker.zsh +1 -1
- package/dist/configure/index.d.mts +1 -1
- package/dist/configure/index.mjs.map +1 -1
- package/dist/context-D0QjfxzD.mjs.map +1 -1
- package/dist/crashreport-BAG8c7hB.mjs.map +1 -1
- package/dist/date-CgP7gMG_.mjs.map +1 -1
- package/dist/date-representation-Cr7W7Pn2.mjs.map +1 -1
- package/dist/errors-D3qpNHR6.mjs.map +1 -1
- package/dist/es-builtins-SfvXRVoG.mjs.map +1 -1
- package/dist/field-parse-C41e3UAF.mjs.map +1 -1
- package/dist/file-DKBOj5q4.mjs.map +1 -1
- package/dist/globals-BCmGX05J.mjs.map +1 -1
- package/dist/guards-yDXyKC9F.mjs.map +1 -1
- package/dist/iconv-DlFMt2gW.mjs.map +1 -1
- package/dist/idp-G_ojPBB5.mjs.map +1 -1
- package/dist/interceptor-DQg3cR_9.mjs.map +1 -1
- package/dist/kysely/index.mjs.map +1 -1
- package/dist/kysely-type-BWMfV301.mjs.map +1 -1
- package/dist/logger-DP2BjQ93.mjs.map +1 -1
- package/dist/logger-DuaT2zY3.mjs.map +1 -1
- package/dist/manager-Bk-7vkwd.mjs.map +1 -1
- package/dist/migration-steps-C6F3zNdQ.mjs.map +1 -1
- package/dist/multiline-EyzjEwn9.mjs.map +1 -1
- package/dist/node-builtins-DYfhPBnz.mjs.map +1 -1
- package/dist/package-json-C690ceex.mjs.map +1 -1
- package/dist/platform-serialize-DkiTdHOt.mjs.map +1 -1
- package/dist/plugin/builtin/enum-constants/index.mjs.map +1 -1
- package/dist/plugin/builtin/file-utils/index.mjs.map +1 -1
- package/dist/plugin/index.mjs.map +1 -1
- package/dist/{register-ts-hook-CkfKqhJT.mjs → register-ts-hook-B5ZbbJwS.mjs} +75 -77
- package/dist/register-ts-hook-B5ZbbJwS.mjs.map +1 -0
- package/dist/registry-BijIDRVA.mjs.map +1 -1
- package/dist/repl-editor-DGJBfIt1.mjs.map +1 -1
- package/dist/schema-B6hJQWp6.mjs.map +1 -1
- package/dist/secret-file-C9wp_FCX.mjs.map +1 -1
- package/dist/secretmanager-vHQoXdQz.mjs.map +1 -1
- package/dist/seed/index.mjs.map +1 -1
- package/dist/seed-C3P_T_Eh.mjs.map +1 -1
- package/dist/service-C_CogLQr.mjs +1 -0
- package/dist/service-b1g22NN9.mjs.map +1 -1
- package/dist/service-l_tEI8sS.mjs +8 -0
- package/dist/service-l_tEI8sS.mjs.map +1 -0
- package/dist/service_pb-Bbu2QSIr.mjs.map +1 -1
- package/dist/telemetry-Xk7J829P.mjs.map +1 -1
- package/dist/type-source--ZNcV8RJ.mjs.map +1 -1
- package/dist/types/helpers.d.mts +2 -5
- package/dist/user-agent-vdHYF3QL.mjs.map +1 -1
- package/dist/utils/test/index.mjs.map +1 -1
- package/dist/vitest/environment.mjs.map +1 -1
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/mocks/file.d.mts +1 -1
- package/dist/vitest/setup.mjs.map +1 -1
- package/dist/wait-point-invoker-eiP-IIux.mjs.map +1 -1
- package/dist/wait-point-registry-B-ESkTZX.mjs.map +1 -1
- package/dist/workflow-Cs9ISw6j.mjs.map +1 -1
- package/dist/workspace_resource_pb-CaoM2npP.mjs.map +1 -1
- package/docs/cli/application.md +2 -2
- package/docs/cli/data.md +2 -0
- package/docs/cli/tailordb.md +2 -2
- package/docs/configuration.md +4 -2
- package/docs/data-clone.md +71 -0
- package/docs/migration/v2.md +1 -1
- package/docs/quickstart.md +1 -1
- package/docs/services/executor.md +1 -1
- package/docs/services/{tailordb.md → tailordb/index.md} +4 -4
- package/docs/services/{tailordb-migration.md → tailordb/migration.md} +12 -10
- package/docs/testing.md +3 -3
- package/package.json +8 -8
- package/dist/application-rWYDAfNU.mjs +0 -200
- package/dist/application-rWYDAfNU.mjs.map +0 -1
- package/dist/register-ts-hook-CkfKqhJT.mjs.map +0 -1
- package/dist/service-CETr0AKY.mjs +0 -7
- package/dist/service-CETr0AKY.mjs.map +0 -1
- package/dist/service-DREWLYxD.mjs +0 -1
package/docs/cli/application.md
CHANGED
|
@@ -100,7 +100,7 @@ long their tables were in maintenance mode, equal to the sum of `phases`), `phas
|
|
|
100
100
|
(`restrict`, `preMigration`, `jobSetup`, `waitingToStart`, `running`, `waitingOrRunning`,
|
|
101
101
|
`jobCleanup`, `postMigration`, `remainingDeploy`, `restore`), and `migrations` with each script's `namespace`,
|
|
102
102
|
`migrationNumber`, `startObserved`, `waitingToStartMs`, `runningMs`, and `waitingOrRunningMs`. See
|
|
103
|
-
[Time spent in maintenance mode](../services/tailordb
|
|
103
|
+
[Time spent in maintenance mode](../services/tailordb/migration.md#time-spent-in-maintenance-mode).
|
|
104
104
|
|
|
105
105
|
```sh
|
|
106
106
|
tailor deploy --json > deploy-result.json
|
|
@@ -187,7 +187,7 @@ When migrations are configured (`db.tailordb.migration` in config), the `deploy`
|
|
|
187
187
|
3. Runs the pending migration scripts
|
|
188
188
|
4. Updates the migration checkpoint so the same migrations are not re-run
|
|
189
189
|
|
|
190
|
-
See [Automatic Migration Execution](../services/tailordb
|
|
190
|
+
See [Automatic Migration Execution](../services/tailordb/migration.md#automatic-migration-execution) for details on automatic migration execution.
|
|
191
191
|
|
|
192
192
|
**Concurrent Deploys:**
|
|
193
193
|
|
package/docs/cli/data.md
CHANGED
package/docs/cli/tailordb.md
CHANGED
|
@@ -84,7 +84,7 @@ tailor tailordb truncate User Post --yes
|
|
|
84
84
|
|
|
85
85
|
Manage TailorDB schema migrations.
|
|
86
86
|
|
|
87
|
-
Note: Migration scripts are automatically executed during `tailor deploy`. See [Automatic Migration Execution](../services/tailordb
|
|
87
|
+
Note: Migration scripts are automatically executed during `tailor deploy`. See [Automatic Migration Execution](../services/tailordb/migration.md#automatic-migration-execution) for details.
|
|
88
88
|
|
|
89
89
|
**Usage**
|
|
90
90
|
|
|
@@ -367,7 +367,7 @@ tailor tailordb migration validate [options]
|
|
|
367
367
|
|
|
368
368
|
See [Global Options](../cli-reference.md#global-options) for options available to all commands.
|
|
369
369
|
|
|
370
|
-
**See also:** For migration concepts, configuration, workflow, and troubleshooting, see the [TailorDB Migrations guide](../services/tailordb
|
|
370
|
+
**See also:** For migration concepts, configuration, workflow, and troubleshooting, see the [TailorDB Migrations guide](../services/tailordb/migration.md).
|
|
371
371
|
|
|
372
372
|
### tailordb erd
|
|
373
373
|
|
package/docs/configuration.md
CHANGED
|
@@ -4,7 +4,7 @@ The SDK uses TypeScript for configuration files. By default, it uses `tailor.con
|
|
|
4
4
|
|
|
5
5
|
For service-specific documentation, see:
|
|
6
6
|
|
|
7
|
-
- [TailorDB](./services/tailordb.md) - Database schema definition
|
|
7
|
+
- [TailorDB](./services/tailordb/index.md) - Database schema definition
|
|
8
8
|
- [Resolver](./services/resolver.md) - Custom GraphQL resolvers
|
|
9
9
|
- [Executor](./services/executor.md) - Event-driven handlers
|
|
10
10
|
- [Workflow](./services/workflow.md) - Job orchestration
|
|
@@ -103,7 +103,7 @@ The default applies wherever the SDK parses or serializes `t` fields on your beh
|
|
|
103
103
|
|
|
104
104
|
The setting is per `tailor.config.ts`, but the field types are declared through `tailor.d.ts`, which TypeScript merges across every file in a program. If one `tsconfig.json` includes the `tailor.d.ts` of several applications, they must agree on `defaultDateRepresentation`. When they differ, `t.date()`, `t.datetime()`, and `t.time()` called without `as` fail to type-check in that program until the configs are aligned or each application gets its own `tsconfig.json`.
|
|
105
105
|
|
|
106
|
-
**Maintenance Mode** (`maintenanceMode`): Whether `deploy` restricts the TailorDB namespaces it migrates while it applies pending migrations: `"migration"` (the default) restricts them until the migrations complete, `"deploy"` until the whole deploy completes, and `false` does not restrict them. See [Maintenance mode](./services/tailordb
|
|
106
|
+
**Maintenance Mode** (`maintenanceMode`): Whether `deploy` restricts the TailorDB namespaces it migrates while it applies pending migrations: `"migration"` (the default) restricts them until the migrations complete, `"deploy"` until the whole deploy completes, and `false` does not restrict them. See [Maintenance mode](./services/tailordb/migration.md#maintenance-mode) for what is restricted and what concurrent requests can observe without it.
|
|
107
107
|
|
|
108
108
|
```typescript
|
|
109
109
|
export default defineConfig({
|
|
@@ -503,6 +503,8 @@ export default defineConfig({
|
|
|
503
503
|
|
|
504
504
|
Configure plugins using `definePlugins()`, exported as `export const plugins`.
|
|
505
505
|
|
|
506
|
+
`kyselyTypePlugin` is required to use `getDB()` in resolvers, executors, and workflows.
|
|
507
|
+
|
|
506
508
|
```typescript
|
|
507
509
|
import { definePlugins } from "@tailor-platform/sdk";
|
|
508
510
|
import { enumConstantsPlugin } from "@tailor-platform/sdk/plugin/enum-constants";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Cloning Application Data
|
|
2
|
+
|
|
3
|
+
`tailor data clone` copies the data of an application from one workspace to another, for example to fill a staging workspace with production records before testing a release against them. It copies the records of every TailorDB namespace registered in the application, and optionally TailorDB file content and IdP users.
|
|
4
|
+
|
|
5
|
+
For the full option list, see [`data clone`](./cli/data.md#data-clone).
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
The platform rejects a clone unless all of the following hold:
|
|
10
|
+
|
|
11
|
+
- The source and target workspaces are in the same region.
|
|
12
|
+
- You have editor access to both workspaces.
|
|
13
|
+
- The application is deployed to the target under the same name, with the same namespaces and the same TailorDB schema as the source. Deploy the version that runs in the source to the target first.
|
|
14
|
+
- The target is empty for everything the clone copies: the TailorDB records of the application's namespaces, plus its files with `--copy-files` and its IdP users with `--copy-idp-users`.
|
|
15
|
+
|
|
16
|
+
## Cloning into another workspace
|
|
17
|
+
|
|
18
|
+
1. Deploy the source's version of the application to the target workspace:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
tailor deploy -w <target-workspace-id>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
2. If the target already has records, empty its TailorDB tables:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
tailor tailordb truncate --all -w <target-workspace-id>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`truncate` removes TailorDB records only. When you clone files or IdP users, remove those from the target separately.
|
|
31
|
+
|
|
32
|
+
3. Start the clone:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
tailor data clone --from <source-workspace-id> --to <target-workspace-id>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The application is the one named by `name` in `tailor.config.ts`, so run all three steps from that application's project. The command asks for confirmation unless `--yes` is given.
|
|
39
|
+
|
|
40
|
+
## What is copied
|
|
41
|
+
|
|
42
|
+
| Data | Copied |
|
|
43
|
+
| ------------------------- | ------------------------- |
|
|
44
|
+
| TailorDB records | Always |
|
|
45
|
+
| TailorDB file content | With `--copy-files` |
|
|
46
|
+
| IdP users, with passwords | With `--copy-idp-users` |
|
|
47
|
+
| Configuration and schema | No — deploy them yourself |
|
|
48
|
+
|
|
49
|
+
Without `--copy-files`, file fields in the target keep their references, but the content they point to is absent. Without `--copy-idp-users`, records that store IdP user IDs refer to users that do not exist in the target.
|
|
50
|
+
|
|
51
|
+
Cloning IdP users copies credentials that sign in to the source. Do not clone users from production into a workspace that people outside the production audience can reach.
|
|
52
|
+
|
|
53
|
+
## Waiting for a clone
|
|
54
|
+
|
|
55
|
+
A clone runs asynchronously on the platform. The command prints an operation ID when the clone starts, then waits until it completes or fails.
|
|
56
|
+
|
|
57
|
+
Pressing Ctrl+C, or passing `--no-wait`, stops only the waiting; the clone keeps running. Check on it later with the operation ID:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Print the current status once
|
|
61
|
+
tailor data operation get --from <source-workspace-id> --to <target-workspace-id> --operation-id <operation-id>
|
|
62
|
+
|
|
63
|
+
# Wait until it completes or fails
|
|
64
|
+
tailor data operation get --from <source-workspace-id> --to <target-workspace-id> --operation-id <operation-id> --wait
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
With `--json`, `data clone` reports `status: "completed"` once the clone has finished, or `status: "started"` with `--no-wait`.
|
|
68
|
+
|
|
69
|
+
## Rehearsing migrations with cloned data
|
|
70
|
+
|
|
71
|
+
To test pending TailorDB migrations against a copy of real records, use `tailor tailordb migration test --data clone` instead of cloning by hand. It creates a temporary workspace, deploys the pre-migration schema, clones the records, and runs the pending migrations. That command copies TailorDB records only; see [Clone mode](./services/tailordb/migration.md#clone-mode).
|
package/docs/migration/v2.md
CHANGED
|
@@ -1736,7 +1736,7 @@ The CLI stores human users by their stable subject ID instead of email (email is
|
|
|
1736
1736
|
|
|
1737
1737
|
### `db.fields.timestamps()`: `updatedAt` becomes required
|
|
1738
1738
|
|
|
1739
|
-
The `updatedAt` field from `db.fields.timestamps()` changes from optional to required (non-null): it defaults to the current time and keeps refreshing automatically on every update, though a value you provide explicitly is still respected. Applying this change to a table that already has rows with `updatedAt: null` makes `deploy` fail with `field "updatedAt" cannot be updated from non-required to required when records with null values exist`. Backfill those rows first, e.g. `UPDATE <table> SET "updatedAt" = "createdAt" WHERE "updatedAt" IS NULL` for each affected table — see [TailorDB migrations](../services/tailordb
|
|
1739
|
+
The `updatedAt` field from `db.fields.timestamps()` changes from optional to required (non-null): it defaults to the current time and keeps refreshing automatically on every update, though a value you provide explicitly is still respected. Applying this change to a table that already has rows with `updatedAt: null` makes `deploy` fail with `field "updatedAt" cannot be updated from non-required to required when records with null values exist`. Backfill those rows first, e.g. `UPDATE <table> SET "updatedAt" = "createdAt" WHERE "updatedAt" IS NULL` for each affected table — see [TailorDB migrations](../services/tailordb/migration.md#performance-and-large-tables) for splitting a large backfill across primary-key ranges if a single `UPDATE` times out.
|
|
1740
1740
|
|
|
1741
1741
|
### Node.js minimum version raised to 22.15.0
|
|
1742
1742
|
|
package/docs/quickstart.md
CHANGED
|
@@ -108,7 +108,7 @@ Deploy again to see the response.
|
|
|
108
108
|
|
|
109
109
|
## Next Steps
|
|
110
110
|
|
|
111
|
-
- Learn about [TailorDB](./services/tailordb.md) for database schema definition
|
|
111
|
+
- Learn about [TailorDB](./services/tailordb/index.md) for database schema definition
|
|
112
112
|
- Create custom [Resolvers](./services/resolver.md) with business logic
|
|
113
113
|
- Set up [Executors](./services/executor.md) for event-driven automation
|
|
114
114
|
- Explore [Templates](https://github.com/tailor-platform/sdk/tree/main/packages/create-sdk#available-templates) for more examples
|
|
@@ -194,7 +194,7 @@ export default createExecutor({
|
|
|
194
194
|
});
|
|
195
195
|
```
|
|
196
196
|
|
|
197
|
-
The available workflow events are `started`, `completed`, `retried`, `resumed`, `wait_started`, and `wait_resolved`. To observe job-level events, use `workflowJobExecutionStartedTrigger()`, `workflowJobExecutionCompletedTrigger()`, `workflowJobExecutionWaitStartedTrigger()`, `workflowJobExecutionWaitResolvedTrigger()`, or `workflowJobExecutionTrigger()`.
|
|
197
|
+
The available workflow events are `started`, `completed`, `retried`, `resumed`, `wait_started`, and `wait_resolved`; their single-event helpers are `workflowExecutionStartedTrigger()`, `workflowExecutionCompletedTrigger()`, `workflowExecutionRetriedTrigger()`, `workflowExecutionResumedTrigger()`, `workflowExecutionWaitStartedTrigger()`, and `workflowExecutionWaitResolvedTrigger()`. To observe job-level events, use `workflowJobExecutionStartedTrigger()`, `workflowJobExecutionCompletedTrigger()`, `workflowJobExecutionWaitStartedTrigger()`, `workflowJobExecutionWaitResolvedTrigger()`, or `workflowJobExecutionTrigger()`.
|
|
198
198
|
|
|
199
199
|
`completed` events include `success`; when it is `false`, `error` contains the failure message. A job released from a wait point emits `wait_resolved` instead of `completed`.
|
|
200
200
|
|
|
@@ -535,7 +535,7 @@ db.table("User", {
|
|
|
535
535
|
- When not specified, `deploy` sets it from the executors taking part in the same run: `true` while one of them uses this table with `recordCreatedTrigger`, `recordUpdatedTrigger`, or `recordDeletedTrigger`, and `false` once none does. Removing the last such trigger turns publishing back off on the next `deploy`
|
|
536
536
|
- When explicitly set to `false` while an executor taking part in the same run uses this table, `deploy` fails
|
|
537
537
|
- An executor declared with `disabled: true` never runs, so it does not count as using the table
|
|
538
|
-
- While a `deploy` applies pending migrations with [maintenance mode](./
|
|
538
|
+
- While a `deploy` applies pending migrations with [maintenance mode](./migration.md#maintenance-mode) on, every table in the migrating namespace is restricted: the GraphQL create, update, and delete operations and bulk upsert are disabled, and the table's `read` setting is not switched off for the migration (a table the migration creates uses its configured setting), so nothing writes an intermediate schema and readable tables are not shut down. Record event publishing is also switched off so a migration script's writes do not reach executors from the previous deploy. With `maintenanceMode: false`, these restrictions are not applied. After success, the configured settings take effect. If a later migration fails, settings from the last confirmed checkpoint are restored; an uncommitted migration restores existing tables' prior settings and leaves its newly created tables restricted until a successful retry. A table that already refuses create, update, delete, and bulk upsert and publishes no record events is left as it is. Restoration is skipped if the checkpoint number or migration history changed concurrently, or if checkpoint ownership cannot be verified; the deploy then reports the namespace whose tables stay restricted and points to `tailor tailordb migration status --namespace <namespace>`. When a failed migration or a failed restoration leaves tables restricted, the deploy lists their names, and deploying again releases them once the migration completes. A table that a failed rollback could not delete or revert is the exception: the deploy also lists it, but the next deploy stops with remote schema drift until you delete or repair that table by hand. A table left behind by a failed post-checkpoint deletion also remains read-only for manual recovery, and the deploy does not list it by name
|
|
539
539
|
|
|
540
540
|
**Use cases:**
|
|
541
541
|
|
|
@@ -580,7 +580,7 @@ db.table("User", {
|
|
|
580
580
|
|
|
581
581
|
Control which GraphQL operations (`create`, `update`, `delete`, `read`) are exposed for a table. All operations are enabled by default.
|
|
582
582
|
|
|
583
|
-
While a `deploy` applies pending migrations with [maintenance mode](./
|
|
583
|
+
While a `deploy` applies pending migrations with [maintenance mode](./migration.md#maintenance-mode) on, the GraphQL `create`, `update`, and `delete` operations and bulk upsert are switched off across the migrating namespace, so nothing writes an intermediate schema. The `read` operation is not switched off: it keeps the setting the table has when the restrictions begin. With or without maintenance mode, a table that a migration touches follows that migration's schema from its first phase on, so a migration that changes `read` changes it before its script runs; a table no migration touches keeps its setting. A table the migration creates uses its configured `read` setting from the start. After success, the configured operations take effect. If a later migration fails while maintenance mode is on, operations from the last confirmed checkpoint are restored; an uncommitted migration restores existing tables' prior operations and leaves its newly created tables restricted until a successful retry. A table that already refuses create, update, delete, and bulk upsert and publishes no record events is left as it is. Restoration is skipped if the checkpoint number or migration history changed concurrently; the deploy then reports the namespace whose tables stay restricted, and deploying again releases them once the migration completes.
|
|
584
584
|
|
|
585
585
|
```typescript
|
|
586
586
|
db.table("Order", {
|
|
@@ -744,6 +744,6 @@ db.table("User", {
|
|
|
744
744
|
|
|
745
745
|
## Migrations
|
|
746
746
|
|
|
747
|
-
When you change a TailorDB table definition, the SDK can generate a migration that captures the diff and, for breaking changes, runs a data transformation script during `tailor deploy`. See the [TailorDB Migrations guide](./
|
|
747
|
+
When you change a TailorDB table definition, the SDK can generate a migration that captures the diff and, for breaking changes, runs a data transformation script during `tailor deploy`. See the [TailorDB Migrations guide](./migration.md) for the full workflow, configuration, supported change types, team coordination, and troubleshooting.
|
|
748
748
|
|
|
749
|
-
For the CLI command reference, see [`tailordb migration`](
|
|
749
|
+
For the CLI command reference, see [`tailordb migration`](../../cli/tailordb.md#tailordb-migration).
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
The migration system tracks changes to your TailorDB table definitions over time and applies them to deployed workspaces with optional data transformation scripts.
|
|
6
6
|
|
|
7
|
-
For the CLI command reference, see [`tailordb migration`](
|
|
7
|
+
For the CLI command reference, see [`tailordb migration`](../../cli/tailordb.md#tailordb-migration). This document covers concepts, workflows, and operational guidance.
|
|
8
8
|
|
|
9
9
|
## Overview
|
|
10
10
|
|
|
@@ -244,7 +244,7 @@ This writes a numbered migration with an empty `diff.json`, a `migrate.ts` skele
|
|
|
244
244
|
|
|
245
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`.
|
|
246
246
|
|
|
247
|
-
A data-only migration runs in **every** workspace the history is applied to, including freshly created ones. Write the script so it is safe against tables with no matching rows (a set-based `UPDATE` with a `WHERE` clause is naturally a no-op on an empty table). For a fix that should run in a single environment only, or that is too large for one transaction, run it outside the migration history instead, for example as a one-off script scaffolded with [`tailor function script`](
|
|
247
|
+
A data-only migration runs in **every** workspace the history is applied to, including freshly created ones. Write the script so it is safe against tables with no matching rows (a set-based `UPDATE` with a `WHERE` clause is naturally a no-op on an empty table). For a fix that should run in a single environment only, or that is too large for one transaction, run it outside the migration history instead, for example as a one-off script scaffolded with [`tailor function script`](../../cli/function.md#function-script) and executed against a single workspace with [`tailor function run`](../../cli/function.md#function-run).
|
|
248
248
|
|
|
249
249
|
## Configuration
|
|
250
250
|
|
|
@@ -638,7 +638,9 @@ Clone mode copies TailorDB records from the source workspace after the identical
|
|
|
638
638
|
tailor tailordb migration test --data clone
|
|
639
639
|
```
|
|
640
640
|
|
|
641
|
-
|
|
641
|
+
Cloning requires editor access to both workspaces, which must be in the same region. Clone mode copies TailorDB records only: IdP users, file blobs, and metadata labels are not copied, and unlike [`tailor data clone`](../../data-clone.md) it has no `--copy-files` or `--copy-idp-users` option. File fields therefore retain references whose blobs are absent. The command polls the asynchronous operation and reports platform failures.
|
|
642
|
+
|
|
643
|
+
With `--target-workspace-id`, the designated target must be in the same region as the source; the command checks this before deploying anything to it.
|
|
642
644
|
|
|
643
645
|
The source schema is re-verified immediately before cloning; if the source workspace was deployed or otherwise changed after the test started, the command aborts instead of cloning data that no longer matches the deployed baseline.
|
|
644
646
|
|
|
@@ -696,7 +698,7 @@ This split is what allows existing rows to be backfilled before the database sta
|
|
|
696
698
|
| `"migration"` (default) | From before the first pending migration runs until the last one completes. |
|
|
697
699
|
| `"deploy"` | From before the first pending migration runs until the deploy has applied every other change, including resolvers, executors, and workflows, and has removed the resources it deletes. |
|
|
698
700
|
|
|
699
|
-
A deploy without pending migrations never enters maintenance mode. When `maintenanceMode` is unset, it behaves as `"migration"`, preserving the existing deployment behavior. The value is read from the config like any other, so it can differ per environment (see [Multi-Environment Configuration](
|
|
701
|
+
A deploy without pending migrations never enters maintenance mode. When `maintenanceMode` is unset, it behaves as `"migration"`, preserving the existing deployment behavior. The value is read from the config like any other, so it can differ per environment (see [Multi-Environment Configuration](../../multi-environment.md#varying-config-values-per-environment)):
|
|
700
702
|
|
|
701
703
|
```typescript
|
|
702
704
|
export default defineConfig({
|
|
@@ -720,7 +722,7 @@ Maintenance mode restricts GraphQL writes and record events only. Resolvers, exe
|
|
|
720
722
|
|
|
721
723
|
#### Keeping maintenance mode short
|
|
722
724
|
|
|
723
|
-
Maintenance mode lasts for the whole migration job, including the time the platform takes to start it, which can far exceed the time `migrate.ts` itself needs. Keep in `migrate.ts` only the work that must be done before the post-migration schema is applied, and move the rest into a workflow that you start after the deploy (for example with [`tailor workflow start`](
|
|
725
|
+
Maintenance mode lasts for the whole migration job, including the time the platform takes to start it, which can far exceed the time `migrate.ts` itself needs. Keep in `migrate.ts` only the work that must be done before the post-migration schema is applied, and move the rest into a workflow that you start after the deploy (for example with [`tailor workflow start`](../../cli/workflow.md#workflow-start)).
|
|
724
726
|
|
|
725
727
|
- **Must stay in `migrate.ts`**: anything the post-migration schema enforces or removes — filling a field that becomes required, resolving duplicates before a unique index, converting values for a field type change, and copying data out of a field or table that the migration renames or removes.
|
|
726
728
|
- **Can move to a workflow**: work the post-migration schema does not depend on, such as filling an optional field, recomputing derived values, or cleaning up rows. Requests that run before the workflow finishes see the data in its unfixed state.
|
|
@@ -743,7 +745,7 @@ When [maintenance mode](#maintenance-mode) is on, `tailor deploy` reports how lo
|
|
|
743
745
|
|
|
744
746
|
Phases that repeat across several migrations are added up. The report is printed only after the migrations succeed and the tables leave maintenance mode; when a migration fails, deploy reports the error instead (see [Failure Recovery](#failure-recovery)). When deploy resumes a migration that an earlier deploy left in progress, the tables were already in maintenance mode before it started, and the report covers only this deploy. A job can be reported as started on the platform well before its code runs, so deploy tells waiting from running by a line every job of a migration logs before your script: `[tailor-sdk] migration script started`. You may see that line in function execution logs; deploy leaves it out of the logs it prints. A script that exports [`steps`](#splitting-a-migration-into-steps) waits for each step's job to start, and that time counts as `waiting to start` too. Time when deploy cannot tell whether the script is running — because the run or a running job's logs could not be read, or because an older SDK version started the run and it never logs the line — is reported as `waiting to start or running` instead.
|
|
745
747
|
|
|
746
|
-
Deploy checks the run every few seconds, so a boundary between `waiting to start` and `running` can be off by that much. With `--json`, the same breakdown is in `tailordbMaintenance` (see [Deploy JSON result](
|
|
748
|
+
Deploy checks the run every few seconds, so a boundary between `waiting to start` and `running` can be off by that much. With `--json`, the same breakdown is in `tailordbMaintenance` (see [Deploy JSON result](../../cli/application.md#deploy)). Add `--verbose` to also print how long each phase took and every change in the job's status.
|
|
747
749
|
|
|
748
750
|
### Schema verification
|
|
749
751
|
|
|
@@ -1001,11 +1003,11 @@ describe("0005 add required email", () => {
|
|
|
1001
1003
|
});
|
|
1002
1004
|
```
|
|
1003
1005
|
|
|
1004
|
-
Stage the rows each query returns with `mock.enqueueResult(...)` or `mock.setQueryResolver(...)` when the script reads before writing; call `main(trx, { env: { ... } })` when the script takes a `MigrationContext`. See [Kysely-layer mock](
|
|
1006
|
+
Stage the rows each query returns with `mock.enqueueResult(...)` or `mock.setQueryResolver(...)` when the script reads before writing; call `main(trx, { env: { ... } })` when the script takes a `MigrationContext`. See [Kysely-layer mock](../../testing.md#kysely-layer-mock-createkyselymock) for the full mock API.
|
|
1005
1007
|
|
|
1006
1008
|
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.
|
|
1007
1009
|
|
|
1008
|
-
A migration generated under [`defaultDateRepresentation`](
|
|
1010
|
+
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).
|
|
1009
1011
|
|
|
1010
1012
|
### Executing migrate.ts against a local Postgres (PGlite)
|
|
1011
1013
|
|
|
@@ -1062,14 +1064,14 @@ describe("0005 add required email", () => {
|
|
|
1062
1064
|
|
|
1063
1065
|
Pass nested field values as JavaScript objects or arrays of objects, without `JSON.stringify`.
|
|
1064
1066
|
|
|
1065
|
-
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](
|
|
1067
|
+
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`.
|
|
1066
1068
|
Generated migration types use `Record<string, unknown>` for each nested object so scripts can
|
|
1067
1069
|
work with both old and new members during a migration; narrow member values before using them.
|
|
1068
1070
|
|
|
1069
1071
|
Two caveats keep this from replacing a scratch workspace:
|
|
1070
1072
|
|
|
1071
1073
|
- 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.
|
|
1072
|
-
- `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](
|
|
1074
|
+
- `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.
|
|
1073
1075
|
|
|
1074
1076
|
### Testing a multi-step script
|
|
1075
1077
|
|
package/docs/testing.md
CHANGED
|
@@ -219,7 +219,7 @@ To hand-write DDL instead — for a table not in the schema, or to add a constra
|
|
|
219
219
|
|
|
220
220
|
- The PGlite instance is yours: the mock never closes it, so close it in `afterAll`. Reuse one instance across a suite — creating one per test is slow.
|
|
221
221
|
- Pass the same instance under several namespaces to drive them against one shared database. Two namespaces with a same-named table cannot share one instance, because the second script leaves the first table as it is.
|
|
222
|
-
- Seed through `getDB` itself. When a column type rejects a value that only the test must stage, use `createKyselyPGlite<Unmigrated<...>>(pglite)` instead — see [Testing Migrations Locally](./services/tailordb
|
|
222
|
+
- Seed through `getDB` itself. When a column type rejects a value that only the test must stage, use `createKyselyPGlite<Unmigrated<...>>(pglite)` instead — see [Testing Migrations Locally](./services/tailordb/migration.md#testing-migrations-locally). This only affects test setup; it cannot supply a `.serial()` value for an insert issued by the code under test.
|
|
223
223
|
- With `kyselyTypePlugin({ temporal: true })`, the generated `getDB` reads top-level date, datetime, and time columns back from PGlite as `Temporal.PlainDate`, `Temporal.Instant`, and `Temporal.PlainTime`, as it does on the platform. Values inside nested objects stay strings in PGlite (see above); on the platform, nested date/datetime fields are Temporal values while nested time fields remain strings.
|
|
224
224
|
- Transactions on a shared instance are serialized: while one is open, queries from other `getDB` instances wait. Do not use `test.concurrent` with a shared instance, and do not query the same instance through a second `getDB` from inside a transaction — that waits on itself.
|
|
225
225
|
- PGlite runs full PostgreSQL while TailorDB supports a subset of it, and TailorDB hooks, validations, and permissions do not run here — a test passing on PGlite can still behave differently on the platform. Keep [`mockTailordb`](#tailordb-mock) or [`createKyselyMock`](#kysely-layer-mock-createkyselymock) tests for query shape and error paths, and E2E tests for platform behavior.
|
|
@@ -448,7 +448,7 @@ export default defineConfig({
|
|
|
448
448
|
|
|
449
449
|
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.
|
|
450
450
|
|
|
451
|
-
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
|
|
451
|
+
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.
|
|
452
452
|
|
|
453
453
|
### Per-Project Configuration
|
|
454
454
|
|
|
@@ -690,7 +690,7 @@ describe("upsertUsers resolver", () => {
|
|
|
690
690
|
|
|
691
691
|
Reach for [`mockTailordb`](#mocking-the-tailordb-client) instead when you want to drive the raw query sequence at the `tailordb.Client` level rather than at the Kysely layer, or [`mockTailordbWithPGlite`](#real-sql-execution-with-pglite-mocktailordbwithpglite) to execute the queries against a real in-memory Postgres.
|
|
692
692
|
|
|
693
|
-
TailorDB migration scripts (`migrate.ts`) are unit-tested the same way: the generated `db.ts` exports the `Database` interface to type the mock, and `tailor tailordb migration script <N> --with-test` scaffolds a ready-to-fill test. To execute a migration script against real rows in an in-memory Postgres, use `createKyselyPGlite` with `@electric-sql/pglite` and the generated `db.pglite.ts` schema. See [Testing Migrations Locally](./services/tailordb
|
|
693
|
+
TailorDB migration scripts (`migrate.ts`) are unit-tested the same way: the generated `db.ts` exports the `Database` interface to type the mock, and `tailor tailordb migration script <N> --with-test` scaffolds a ready-to-fill test. To execute a migration script against real rows in an in-memory Postgres, use `createKyselyPGlite` with `@electric-sql/pglite` and the generated `db.pglite.ts` schema. See [Testing Migrations Locally](./services/tailordb/migration.md#testing-migrations-locally).
|
|
694
694
|
|
|
695
695
|
#### Resolvers that resume a workflow
|
|
696
696
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tailor-platform/sdk",
|
|
3
|
-
"version": "2.29.
|
|
3
|
+
"version": "2.29.2",
|
|
4
4
|
"description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -165,12 +165,12 @@
|
|
|
165
165
|
"@opentelemetry/sdk-trace-node": "2.12.0",
|
|
166
166
|
"@opentelemetry/semantic-conventions": "1.43.0",
|
|
167
167
|
"@oxc-project/types": "0.154.0",
|
|
168
|
-
"@politty/zod": "0.5.
|
|
168
|
+
"@politty/zod": "0.5.2",
|
|
169
169
|
"@secretlint/core": "13.0.7",
|
|
170
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/read-multiline": "0.4.
|
|
173
|
+
"@toiroakr/read-multiline": "0.4.3",
|
|
174
174
|
"@urql/core": "6.0.3",
|
|
175
175
|
"amaro": "1.2.1",
|
|
176
176
|
"confbox": "0.3.1",
|
|
@@ -191,11 +191,11 @@
|
|
|
191
191
|
"pathe": "2.0.3",
|
|
192
192
|
"pgsql-ast-parser": "12.0.2",
|
|
193
193
|
"pkg-types": "2.3.3",
|
|
194
|
-
"rolldown": "1.2.
|
|
194
|
+
"rolldown": "1.2.14",
|
|
195
195
|
"semver": "7.8.5",
|
|
196
196
|
"sql-highlight": "6.1.0",
|
|
197
197
|
"std-env": "4.3.0",
|
|
198
|
-
"temporal-polyfill": "1.0.
|
|
198
|
+
"temporal-polyfill": "1.0.6",
|
|
199
199
|
"temporal-spec": "1.0.1",
|
|
200
200
|
"ts-cron-validator": "1.1.5",
|
|
201
201
|
"type-fest": "5.10.0",
|
|
@@ -203,7 +203,7 @@
|
|
|
203
203
|
"zod": "4.6.7"
|
|
204
204
|
},
|
|
205
205
|
"devDependencies": {
|
|
206
|
-
"@electric-sql/pglite": "0.5.
|
|
206
|
+
"@electric-sql/pglite": "0.5.10",
|
|
207
207
|
"@opentelemetry/sdk-trace-base": "2.12.0",
|
|
208
208
|
"@tailor-platform/shared": "^0.0.0",
|
|
209
209
|
"@tailor-platform/tailor-proto": "^0.0.1",
|
|
@@ -211,7 +211,7 @@
|
|
|
211
211
|
"@types/node": "24.19.2",
|
|
212
212
|
"@types/semver": "7.8.0",
|
|
213
213
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
214
|
-
"@vitest/coverage-v8": "5.0.
|
|
214
|
+
"@vitest/coverage-v8": "5.0.5",
|
|
215
215
|
"eslint-plugin-zod": "5.0.0",
|
|
216
216
|
"oxfmt": "0.73.0",
|
|
217
217
|
"oxlint": "1.88.0",
|
|
@@ -219,7 +219,7 @@
|
|
|
219
219
|
"sonda": "0.14.0",
|
|
220
220
|
"tsdown": "0.23.1",
|
|
221
221
|
"typescript": "6.0.3",
|
|
222
|
-
"vitest": "5.0.
|
|
222
|
+
"vitest": "5.0.5",
|
|
223
223
|
"zinfer": "0.4.7"
|
|
224
224
|
},
|
|
225
225
|
"peerDependencies": {
|