@tailor-platform/sdk 1.44.1 → 1.45.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 +18 -0
- package/dist/{actor-DzCuoMlP.d.mts → actor-CzX_hVOP.d.mts} +2 -2
- package/dist/{application-vJxYH5LE.mjs → application-BsSJZRCq.mjs} +17 -3
- package/dist/application-BsSJZRCq.mjs.map +1 -0
- package/dist/application-C0O80PUX.mjs +4 -0
- package/dist/cli/index.mjs +2 -2
- package/dist/cli/lib.d.mts +6 -6
- package/dist/cli/lib.mjs +2 -2
- package/dist/configure/index.d.mts +5 -5
- package/dist/configure/index.mjs +16 -4
- package/dist/configure/index.mjs.map +1 -1
- package/dist/{index-DdsUV-aA.d.mts → index-Bd-0JRjy.d.mts} +2 -2
- package/dist/{index-ZZYEd_0R.d.mts → index-C_f357K2.d.mts} +2 -2
- package/dist/{index-BOfTiouP.d.mts → index-CemBzf4z.d.mts} +41 -8
- package/dist/{index-BEEL1-6Z.d.mts → index-DIz-15Cx.d.mts} +2 -2
- package/dist/{index-0Dk-fDWi.d.mts → index-SnYVi8ww.d.mts} +2 -2
- package/dist/plugin/builtin/enum-constants/index.d.mts +1 -1
- package/dist/plugin/builtin/file-utils/index.d.mts +1 -1
- package/dist/plugin/builtin/kysely-type/index.d.mts +1 -1
- package/dist/plugin/builtin/seed/index.d.mts +1 -1
- package/dist/plugin/index.d.mts +2 -2
- package/dist/{runtime-DQqulWPm.mjs → runtime-Bmq4L9pj.mjs} +71 -25
- package/dist/runtime-Bmq4L9pj.mjs.map +1 -0
- package/dist/{tailor-db-field-D_z185oq.d.mts → tailor-db-field-CY1D1PtT.d.mts} +5 -2
- package/dist/utils/test/index.d.mts +3 -3
- package/dist/{workflow.generated-B7Mupf5V.d.mts → workflow.generated--YammZcl.d.mts} +2 -2
- package/docs/cli/tailordb.md +35 -415
- package/docs/services/executor.md +15 -1
- package/docs/services/idp.md +2 -2
- package/docs/services/tailordb-migration.md +418 -0
- package/docs/services/tailordb.md +6 -0
- package/package.json +3 -3
- package/dist/application-Cot5-3g3.mjs +0 -4
- package/dist/application-vJxYH5LE.mjs.map +0 -1
- package/dist/runtime-DQqulWPm.mjs.map +0 -1
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
# TailorDB Migrations
|
|
2
|
+
|
|
3
|
+
> **Beta:** The `tailordb migration` command and the migration runtime are beta features. They may introduce breaking changes in future releases. The CLI emits a beta warning on every invocation.
|
|
4
|
+
|
|
5
|
+
The migration system tracks changes to your TailorDB type definitions over time and applies them to deployed workspaces with optional data transformation scripts.
|
|
6
|
+
|
|
7
|
+
For the CLI command reference, see [`tailordb migration`](../cli/tailordb.md#tailordb-migration). This document covers concepts, workflows, and operational guidance.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
**Key Properties**
|
|
12
|
+
|
|
13
|
+
- **Local snapshot–based diff detection** — each migration is generated by diffing your current type definitions against the previous snapshot stored in `migrations/<NNNN>/`.
|
|
14
|
+
- **Transaction-wrapped data migrations** — each `migrate.ts` script runs inside a database transaction on the platform; if the script throws, all changes in that migration roll back.
|
|
15
|
+
- **Automatic execution during `apply`** — `tailor-sdk apply` detects pending migrations, runs the two-stage type update (pre-migration → script → post-migration), and updates the migration checkpoint label.
|
|
16
|
+
- **Type-safe scripts** — the generated `db.ts` provides Kysely types that reflect the schema state **before** the migration runs, so transformations are written against the actual data shape.
|
|
17
|
+
|
|
18
|
+
**Files in `migrations/`**
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
migrations/
|
|
22
|
+
├── 0000/ # Initial schema snapshot
|
|
23
|
+
│ └── schema.json
|
|
24
|
+
├── 0001/ # First change
|
|
25
|
+
│ ├── diff.json # Field-level diff from 0000
|
|
26
|
+
│ ├── migrate.ts # Data migration script (only if breaking)
|
|
27
|
+
│ └── db.ts # Kysely types for the script (pre-migration shape)
|
|
28
|
+
├── 0002/
|
|
29
|
+
│ └── diff.json # No script — non-breaking changes only
|
|
30
|
+
└── ...
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`0000` always contains a full snapshot. `0001` and onward contain a diff plus, for breaking changes, a script and its types. **Commit the entire `migrations/` directory to version control.**
|
|
34
|
+
|
|
35
|
+
## Initial Setup
|
|
36
|
+
|
|
37
|
+
### New project
|
|
38
|
+
|
|
39
|
+
When you start with no `migrations/` directory:
|
|
40
|
+
|
|
41
|
+
1. Add the `migration` block to `tailor.config.ts` (see [Configuration](#configuration)).
|
|
42
|
+
2. Define your initial types in `tailordb/`.
|
|
43
|
+
3. Generate the initial migration:
|
|
44
|
+
```bash
|
|
45
|
+
tailor-sdk tailordb migration generate
|
|
46
|
+
```
|
|
47
|
+
This creates `migrations/0000/schema.json` from your current types.
|
|
48
|
+
4. Run `tailor-sdk apply`. The migration label is set to `0000` on the deployed namespace.
|
|
49
|
+
|
|
50
|
+
### Adding migrations to an existing project
|
|
51
|
+
|
|
52
|
+
If you already have a deployed workspace whose schema matches your local type definitions:
|
|
53
|
+
|
|
54
|
+
1. Add the `migration` block to `tailor.config.ts`.
|
|
55
|
+
2. Run `tailor-sdk tailordb migration generate` to create `0000/schema.json` from current local types.
|
|
56
|
+
3. Run `tailor-sdk apply`. Because remote schema already matches, no script runs; only the migration label is set.
|
|
57
|
+
|
|
58
|
+
If your local types and remote schema have **diverged**, reconcile them before introducing migrations — either update local types to match remote, or accept that the first non-`0000` migration will reflect that gap.
|
|
59
|
+
|
|
60
|
+
### Resetting
|
|
61
|
+
|
|
62
|
+
`tailor-sdk tailordb migration generate --init` deletes the existing `migrations/` directory and starts over from `0000`. Use this only on projects that are not yet deployed, or when you have decided to re-baseline (the next `apply` will see all migrations as new and require coordination — see [Resetting a deployed project](#resetting-a-deployed-project)).
|
|
63
|
+
|
|
64
|
+
## Migration Workflow
|
|
65
|
+
|
|
66
|
+
A typical change cycle:
|
|
67
|
+
|
|
68
|
+
1. **Modify a type definition.**
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// tailordb/user.ts
|
|
72
|
+
export const user = db.type("User", {
|
|
73
|
+
name: db.string(),
|
|
74
|
+
email: db.string(), // ← new required field
|
|
75
|
+
...db.fields.timestamps(),
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
2. **Generate the migration.**
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
tailor-sdk tailordb migration generate --name "add email to user"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Output:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
Generated migration 0001
|
|
89
|
+
Diff file: ./migrations/0001/diff.json
|
|
90
|
+
Migration script: ./migrations/0001/migrate.ts
|
|
91
|
+
DB types: ./migrations/0001/db.ts
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If `EDITOR` or `VISUAL` is set, `migrate.ts` opens automatically.
|
|
95
|
+
|
|
96
|
+
3. **Edit `migrate.ts`** to populate data for the new required field:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import type { Transaction } from "./db";
|
|
100
|
+
|
|
101
|
+
export async function main(trx: Transaction): Promise<void> {
|
|
102
|
+
await trx
|
|
103
|
+
.updateTable("User")
|
|
104
|
+
.set({ email: "default@example.com" })
|
|
105
|
+
.where("email", "is", null)
|
|
106
|
+
.execute();
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
4. **Apply.**
|
|
111
|
+
```bash
|
|
112
|
+
tailor-sdk apply
|
|
113
|
+
```
|
|
114
|
+
The pre-migration phase relaxes the new field to optional, the script runs and populates values, then the post-migration phase enforces `required: true`.
|
|
115
|
+
|
|
116
|
+
## Configuration
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// tailor.config.ts
|
|
120
|
+
export default defineConfig({
|
|
121
|
+
name: "my-app",
|
|
122
|
+
db: {
|
|
123
|
+
tailordb: {
|
|
124
|
+
files: ["./tailordb/*.ts"],
|
|
125
|
+
migration: {
|
|
126
|
+
directory: "./migrations",
|
|
127
|
+
// Optional. Defaults to the first machine user in auth.machineUsers.
|
|
128
|
+
machineUser: "admin-machine-user",
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
| Option | Type | Description |
|
|
136
|
+
| ----------------------- | ------ | --------------------------------------------------------------------------------------------------------- |
|
|
137
|
+
| `migration.directory` | string | Directory path for migration files. Required when migrations are enabled for the namespace. |
|
|
138
|
+
| `migration.machineUser` | string | Machine user used to run migration scripts. Optional; defaults to the first entry in `auth.machineUsers`. |
|
|
139
|
+
|
|
140
|
+
## Generated Files
|
|
141
|
+
|
|
142
|
+
| File | When generated | Description |
|
|
143
|
+
| ------------------ | -------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
144
|
+
| `0000/schema.json` | First `migration generate` | Full snapshot of all types in the namespace. |
|
|
145
|
+
| `XXXX/diff.json` | Every subsequent migration | Field-level diff against the previous snapshot. |
|
|
146
|
+
| `XXXX/migrate.ts` | Only for breaking changes | Data transformation script. The `main` export receives a Kysely `Transaction`. |
|
|
147
|
+
| `XXXX/db.ts` | Generated alongside `migrate.ts` | Kysely types reflecting the schema **before** this migration. Re-generated on every `migration generate`. |
|
|
148
|
+
|
|
149
|
+
`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.
|
|
150
|
+
|
|
151
|
+
## Migration Script Anatomy
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import type { Transaction } from "./db";
|
|
155
|
+
|
|
156
|
+
export async function main(trx: Transaction): Promise<void> {
|
|
157
|
+
// SELECT is supported.
|
|
158
|
+
const users = await trx.selectFrom("User").select(["id", "name"]).execute();
|
|
159
|
+
|
|
160
|
+
// Loop and transform.
|
|
161
|
+
for (const u of users) {
|
|
162
|
+
await trx
|
|
163
|
+
.updateTable("User")
|
|
164
|
+
.set({ displayName: u.name.toUpperCase() })
|
|
165
|
+
.where("id", "=", u.id)
|
|
166
|
+
.execute();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Rules**
|
|
172
|
+
|
|
173
|
+
- Always use the `trx` argument for database access. Anything that bypasses `trx` is not part of the transaction and will not roll back on failure.
|
|
174
|
+
- Do not import resolvers, executors, or other SDK runtime services. The migration script runs as a standalone bundle on the platform with only Kysely access; SDK service helpers are not available.
|
|
175
|
+
- Standard Node-compatible packages are bundled. Keep dependencies minimal — every import is shipped to the platform.
|
|
176
|
+
- `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.
|
|
177
|
+
- 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).
|
|
178
|
+
|
|
179
|
+
## Supported Schema Changes
|
|
180
|
+
|
|
181
|
+
| Change Type | Breaking? | Migration Script? | Notes |
|
|
182
|
+
| ------------------------------ | --------- | ----------------- | ------------------------------------------------------------------------------------- |
|
|
183
|
+
| Add optional field | No | No | Schema change only |
|
|
184
|
+
| Add required field | Yes | Yes | Script populates default values |
|
|
185
|
+
| Remove field | No | No | Schema change only — data is preserved server-side |
|
|
186
|
+
| Change optional → required | Yes | Yes | Script sets defaults for null values |
|
|
187
|
+
| Change required → optional | No | No | Schema change only |
|
|
188
|
+
| Add index | No | No | Schema change only |
|
|
189
|
+
| Remove index | No | No | Schema change only |
|
|
190
|
+
| Add unique constraint | Yes | Yes | Script must resolve duplicate values |
|
|
191
|
+
| Remove unique constraint | No | No | Schema change only |
|
|
192
|
+
| Add enum value | No | No | Schema change only |
|
|
193
|
+
| Remove enum value | Yes | Yes | Script migrates records with removed values |
|
|
194
|
+
| Add type | No | No | Schema change only |
|
|
195
|
+
| Remove type | No | No | Schema change only — data is preserved server-side |
|
|
196
|
+
| Change foreign key target type | Yes | Yes | Script updates references to the new target |
|
|
197
|
+
| Change field type | - | - | **Not supported** — see [3-step migration](#3-step-migration-for-unsupported-changes) |
|
|
198
|
+
| Change array → single value | - | - | **Not supported** — see [3-step migration](#3-step-migration-for-unsupported-changes) |
|
|
199
|
+
| Change single value → array | - | - | **Not supported** — see [3-step migration](#3-step-migration-for-unsupported-changes) |
|
|
200
|
+
|
|
201
|
+
### 3-step migration for unsupported changes
|
|
202
|
+
|
|
203
|
+
Field type changes (e.g., `string` → `integer`) and array-cardinality changes are not detected by the diff engine. Use a 3-step strategy:
|
|
204
|
+
|
|
205
|
+
1. **Migration N**: Add a new field with the desired type (e.g., `fieldName_new`). Write a script that populates it from the old field.
|
|
206
|
+
2. **Migration N+1**: Remove the old field.
|
|
207
|
+
3. **Migration N+2**: Add the field back with the original name and the new type. Script copies from the temporary field, then remove the temporary field in migration N+3 (or in the same step if you can express it).
|
|
208
|
+
|
|
209
|
+
The same pattern works for switching between scalar and array.
|
|
210
|
+
|
|
211
|
+
## Automatic Migration Execution
|
|
212
|
+
|
|
213
|
+
When you run `tailor-sdk apply`, the SDK detects pending migrations (anything past the current `sdk-migration` label on the deployed namespace) and runs them in order before continuing with the rest of the apply.
|
|
214
|
+
|
|
215
|
+
### Per-migration phases
|
|
216
|
+
|
|
217
|
+
For each pending migration:
|
|
218
|
+
|
|
219
|
+
1. **Pre-migration**: Type changes that would be breaking are applied in a relaxed form first. Newly-required fields are added as optional; fields whose `optional → required` transition is breaking are temporarily kept optional. Non-breaking changes that are part of the same migration are also applied here.
|
|
220
|
+
2. **Script execution**: If `diff.requiresMigrationScript` is true, `migrate.ts` is bundled and sent to the platform via the script execution API. It runs as the configured machine user inside a transaction.
|
|
221
|
+
3. **Post-migration**: Required constraints are enforced; field/type deletions are applied; the `sdk-migration` label is bumped to this migration's number.
|
|
222
|
+
|
|
223
|
+
This split is what allows existing rows to be backfilled before the database starts rejecting nulls.
|
|
224
|
+
|
|
225
|
+
### Schema verification
|
|
226
|
+
|
|
227
|
+
Before running migrations, `apply` performs two checks:
|
|
228
|
+
|
|
229
|
+
1. **Local schema check** — your current type definitions must match the latest snapshot in `migrations/`. If they don't, you forgot to run `migration generate`.
|
|
230
|
+
2. **Remote schema check** — the deployed schema is reconstructed from migration history; the actual remote schema must match. Drift here means someone applied a different set of migrations or edited the schema out-of-band.
|
|
231
|
+
|
|
232
|
+
On drift you'll see something like:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
✖ Remote schema drift detected:
|
|
236
|
+
Namespace: tailordb
|
|
237
|
+
Remote migration: 0007
|
|
238
|
+
Differences:
|
|
239
|
+
Type 'User':
|
|
240
|
+
- Field 'email': required: remote=false, expected=true
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
To bypass both checks (not recommended outside of recovery scenarios):
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
tailor-sdk apply --no-schema-check
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Example output
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
ℹ Found 2 pending migration(s) to execute.
|
|
253
|
+
ℹ Executing 2 pending migration(s)...
|
|
254
|
+
ℹ Using machine user: admin-machine-user for namespace 'tailordb'
|
|
255
|
+
|
|
256
|
+
✔ Migration tailordb/0002 completed successfully
|
|
257
|
+
✔ Migration tailordb/0003 completed successfully
|
|
258
|
+
|
|
259
|
+
✔ All migrations completed successfully.
|
|
260
|
+
✔ Successfully applied changes.
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## `migration set` Semantics
|
|
264
|
+
|
|
265
|
+
`tailor-sdk tailordb migration set <N>` updates the `sdk-migration` label on the deployed namespace's metadata. **It does not modify any data or schema.** It only changes which migrations the next `apply` will consider pending.
|
|
266
|
+
|
|
267
|
+
| Movement | Effect on next `apply` | Effect on data |
|
|
268
|
+
| -------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------- |
|
|
269
|
+
| Forward (e.g., `0001` → `0003`) | Migrations `0002` and `0003` are skipped — they will not run. | None. |
|
|
270
|
+
| Backward (e.g., `0003` → `0001`) | Migrations `0002` and `0003` become pending and will re-execute on apply. | None directly — but the re-executed scripts may rewrite data. |
|
|
271
|
+
|
|
272
|
+
Use cases:
|
|
273
|
+
|
|
274
|
+
- **Recovery from drift** — you investigated, manually fixed the remote, and want the SDK's bookkeeping to reflect reality.
|
|
275
|
+
- **Re-running a faulty migration** in a development workspace — set backward, fix `migrate.ts`, apply.
|
|
276
|
+
- **Skipping a migration** that you know was already applied out-of-band.
|
|
277
|
+
|
|
278
|
+
`migration set` does not perform a true rollback. To undo a schema/data change in production, write a new forward migration that reverses it (see [Rollback Strategy](#rollback-strategy)).
|
|
279
|
+
|
|
280
|
+
## Team Workflow and CI/CD
|
|
281
|
+
|
|
282
|
+
### Branch coordination
|
|
283
|
+
|
|
284
|
+
Migration numbers are assigned sequentially, so two developers branching off the same point and each generating `0005` will collide. Conventions that work:
|
|
285
|
+
|
|
286
|
+
- **Don't generate migrations on long-lived feature branches.** Generate them just before merge, after rebasing onto main.
|
|
287
|
+
- **Resolve collisions by re-generating.** If your branch has `0005` but main now has `0005` from another PR, delete your `0005/` directory, rebase, and run `migration generate` again. Re-edit the resulting `migrate.ts`.
|
|
288
|
+
- **Treat migration files as merge-conflict-prone.** They are committed JSON and TypeScript, so review them in PRs. The `diff.json` is the source of truth — if review focuses there, regenerating after rebase is straightforward.
|
|
289
|
+
|
|
290
|
+
### CI / CD
|
|
291
|
+
|
|
292
|
+
- For non-interactive environments, pass `--yes` to `migration generate` and `--yes` to `apply`. `apply` runs migrations automatically when the `migrations/` directory is configured.
|
|
293
|
+
- Run `tailor-sdk tailordb migration status` in CI to detect "developer forgot to commit a migration" situations early. The exit code is non-zero only on errors, so check the output.
|
|
294
|
+
- Avoid running migrations in parallel against the same workspace — there is no locking. Serialize deploys per environment.
|
|
295
|
+
|
|
296
|
+
### Resetting a deployed project
|
|
297
|
+
|
|
298
|
+
`migration generate --init` is destructive locally but does not touch the deployed workspace. Re-baselining a deployed project requires:
|
|
299
|
+
|
|
300
|
+
1. Run `migration generate --init` to start over from `0000`.
|
|
301
|
+
2. Run `tailor-sdk tailordb migration set 0` against the deployed namespace.
|
|
302
|
+
3. Run `tailor-sdk apply` — the new `0000` becomes the baseline.
|
|
303
|
+
|
|
304
|
+
Coordinate this with your team because everyone else's local migrations will be invalidated.
|
|
305
|
+
|
|
306
|
+
## Failure Recovery
|
|
307
|
+
|
|
308
|
+
If a `migrate.ts` throws:
|
|
309
|
+
|
|
310
|
+
- **The transaction rolls back** for that migration's script. Database changes the script made are undone.
|
|
311
|
+
- **The pre-migration phase already ran** before the script. Type-level relaxations (e.g., a field changed to optional) **are not undone**. The post-migration phase, including the label bump, does not run.
|
|
312
|
+
- The whole `apply` aborts. Subsequent migrations in the same run do not execute.
|
|
313
|
+
|
|
314
|
+
After a failure:
|
|
315
|
+
|
|
316
|
+
1. Read the `Logs:` block in the apply output to find the cause.
|
|
317
|
+
2. Fix `migrate.ts` (or the data it depends on).
|
|
318
|
+
3. Re-run `tailor-sdk apply`. The same migration runs again because its label was never bumped.
|
|
319
|
+
4. If the pre-migration relaxation is causing problems for application code in the meantime, accept the temporary optionality or roll forward with a fix; do not try to manually re-tighten the schema, or you'll create remote drift.
|
|
320
|
+
|
|
321
|
+
If a migration **succeeds in script** but the post-migration phase fails (rare; usually due to constraint violation that the script should have prevented), the situation is the same as above plus the data changes from the script are persisted. Investigate, fix, and re-run.
|
|
322
|
+
|
|
323
|
+
## Rollback Strategy
|
|
324
|
+
|
|
325
|
+
There is no automatic down-migration. To roll back a schema/data change in production, write a new forward migration that reverses the previous one. For example, to undo a `0005` that added a required `email` field:
|
|
326
|
+
|
|
327
|
+
1. Edit your type definitions to remove the field.
|
|
328
|
+
2. `migration generate --name "rollback 0005 email"` produces `0006` with a removal diff.
|
|
329
|
+
3. Apply.
|
|
330
|
+
|
|
331
|
+
In **development workspaces**, a quicker option is to fix `0005/migrate.ts` in place, run `migration set <previous>` to re-mark it pending, and apply. Do not do this on production — it confuses migration history across environments.
|
|
332
|
+
|
|
333
|
+
## Machine User and Permissions
|
|
334
|
+
|
|
335
|
+
Migration scripts execute server-side under a machine user identity. The CLI selects the user in this priority order:
|
|
336
|
+
|
|
337
|
+
1. `db.<namespace>.migration.machineUser` if set in `tailor.config.ts`.
|
|
338
|
+
2. The first entry in `auth.machineUsers` otherwise.
|
|
339
|
+
|
|
340
|
+
The CLI logs the selected user before running scripts (`Using machine user: ...`).
|
|
341
|
+
|
|
342
|
+
**Permissions required**
|
|
343
|
+
|
|
344
|
+
The machine user needs read/write access to every type the migration script touches. If your migrations alter data across multiple types, the simplest path is to give the migration user broad access (e.g., an `ADMIN` role) and restrict day-to-day machine users separately. If the user lacks permission, the script fails with a permission error in `Logs:`.
|
|
345
|
+
|
|
346
|
+
If you see `No machine user available for migration execution`, either:
|
|
347
|
+
|
|
348
|
+
- Add `machineUsers: { ... }` to your auth config and `tailor-sdk apply` it, or
|
|
349
|
+
- Set `migration.machineUser` to an existing machine user name in the db config.
|
|
350
|
+
|
|
351
|
+
## Multi-Namespace Coordination
|
|
352
|
+
|
|
353
|
+
If your project defines multiple TailorDB namespaces (`db: { ns1: { ... }, ns2: { ... } }`), each has its own `migrations/` directory and its own migration label. During `apply`:
|
|
354
|
+
|
|
355
|
+
- Migrations are grouped by namespace and executed namespace by namespace.
|
|
356
|
+
- Within a namespace, migrations run sequentially in number order.
|
|
357
|
+
- There is no cross-namespace ordering guarantee. Do not write a migration in `ns1` that depends on data produced by a migration in `ns2` running first.
|
|
358
|
+
- Each namespace can specify its own `migration.machineUser`.
|
|
359
|
+
|
|
360
|
+
## Performance and Large Tables
|
|
361
|
+
|
|
362
|
+
The migration script runs in a single transaction. For tables with many rows:
|
|
363
|
+
|
|
364
|
+
- Prefer set-based SQL (`updateTable(...).set(...).where(...)`) over per-row loops.
|
|
365
|
+
- If a per-row loop is unavoidable, batch by primary key range. Avoid `OFFSET`-based pagination — it scans previously-seen rows on every page.
|
|
366
|
+
- 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.
|
|
367
|
+
- Add `LIMIT` and resumability (idempotent `where` clauses) so a re-run after a transient failure converges.
|
|
368
|
+
|
|
369
|
+
## Testing Migrations Locally
|
|
370
|
+
|
|
371
|
+
The platform-side execution path is hard to fully replicate locally, but you can sanity-check `migrate.ts` logic:
|
|
372
|
+
|
|
373
|
+
- The bundle that ships to the platform is plain JavaScript exporting `main(trx)`. You can import it in a Vitest test, pass a Kysely-compatible mock or a local SQLite database with the same schema as `db.ts`, and assert the resulting state.
|
|
374
|
+
- Run `migration generate` on a clean working copy first, review `diff.json`, then run again after editing types to ensure the diff matches what you intended.
|
|
375
|
+
- For non-trivial migrations, apply against a scratch workspace before promoting to staging or production.
|
|
376
|
+
|
|
377
|
+
## Environment-Specific Strategies
|
|
378
|
+
|
|
379
|
+
A migration script is a function — branching on environment (e.g., to skip a backfill in dev) is just normal TypeScript. The migration runtime injects nothing environment-specific into the script itself; if you need environment awareness, query a sentinel record or rely on data shape. Avoid env-vars inside `migrate.ts` because the script is bundled and shipped to the platform; the platform-side environment is what counts.
|
|
380
|
+
|
|
381
|
+
For genuinely different schemas across environments, prefer separate workspaces with the same migration history rather than divergent `migrations/` directories.
|
|
382
|
+
|
|
383
|
+
## Troubleshooting
|
|
384
|
+
|
|
385
|
+
### Remote schema drift detected
|
|
386
|
+
|
|
387
|
+
**Cause:** Remote schema doesn't match what the migration history says it should be.
|
|
388
|
+
|
|
389
|
+
**Resolution:**
|
|
390
|
+
|
|
391
|
+
1. `tailor-sdk tailordb migration status` to see local vs remote.
|
|
392
|
+
2. Compare with teammates — has someone applied different migrations?
|
|
393
|
+
3. If remote was changed manually, decide whether to update local migrations to match or to use `migration set <N>` to align bookkeeping.
|
|
394
|
+
4. As a last resort in non-production environments, `--no-schema-check` skips both checks. Do not use this as a routine workaround.
|
|
395
|
+
|
|
396
|
+
### "No machine user available for migration execution"
|
|
397
|
+
|
|
398
|
+
**Cause:** Neither `migration.machineUser` is set nor are there any machine users in `auth.machineUsers`.
|
|
399
|
+
|
|
400
|
+
**Resolution:** Add a machine user to auth, apply auth changes, then re-run.
|
|
401
|
+
|
|
402
|
+
### "Machine user not found"
|
|
403
|
+
|
|
404
|
+
**Cause:** `migration.machineUser` references a name that doesn't exist in the deployed auth config.
|
|
405
|
+
|
|
406
|
+
**Resolution:** Either add the machine user to `auth.machineUsers` and apply, or change `migration.machineUser` to a valid name.
|
|
407
|
+
|
|
408
|
+
### Migration script execution fails
|
|
409
|
+
|
|
410
|
+
**Cause:** Runtime error in your `migrate.ts`, a permission error from the machine user, or a constraint violation when post-migration tightens types.
|
|
411
|
+
|
|
412
|
+
**Resolution:** Read the `Logs:` block. Fix the script or the data assumption it relies on, and re-run `tailor-sdk apply`. The label is not bumped on failure, so the same migration retries.
|
|
413
|
+
|
|
414
|
+
### `migrate.ts` not found for a migration that needs one
|
|
415
|
+
|
|
416
|
+
**Cause:** `diff.requiresMigrationScript` is true but `migrate.ts` is missing from the migration directory.
|
|
417
|
+
|
|
418
|
+
**Resolution:** Either re-run `migration generate` (it skips already-generated diffs but will fill in a missing script), or restore the file from version control.
|
|
@@ -586,3 +586,9 @@ db.type("User", {
|
|
|
586
586
|
```
|
|
587
587
|
|
|
588
588
|
**Warning**: Do not use `unsafeAllowAllTypePermission` or `unsafeAllowAllGqlPermission` in production environments as they effectively disable authorization checks.
|
|
589
|
+
|
|
590
|
+
## Migrations
|
|
591
|
+
|
|
592
|
+
When you change a TailorDB type definition, the SDK can generate a migration that captures the diff and, for breaking changes, runs a data transformation script during `tailor-sdk apply`. See the [TailorDB Migrations guide](./tailordb-migration.md) for the full workflow, configuration, supported change types, team coordination, and troubleshooting.
|
|
593
|
+
|
|
594
|
+
For the CLI command reference, see [`tailordb migration`](../cli/tailordb.md#tailordb-migration).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tailor-platform/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.45.0",
|
|
4
4
|
"description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"mime-types": "3.0.2",
|
|
113
113
|
"multiline-ts": "4.0.1",
|
|
114
114
|
"open": "11.0.0",
|
|
115
|
-
"ora": "9.
|
|
115
|
+
"ora": "9.4.0",
|
|
116
116
|
"oxc-parser": "0.127.0",
|
|
117
117
|
"p-limit": "7.3.0",
|
|
118
118
|
"pathe": "2.0.3",
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
"@types/mime-types": "3.0.1",
|
|
139
139
|
"@types/node": "24.12.2",
|
|
140
140
|
"@types/semver": "7.7.1",
|
|
141
|
-
"@typescript/native-preview": "7.0.0-dev.20260424.
|
|
141
|
+
"@typescript/native-preview": "7.0.0-dev.20260424.2",
|
|
142
142
|
"@vitest/coverage-v8": "4.1.5",
|
|
143
143
|
"eslint": "10.2.1",
|
|
144
144
|
"eslint-plugin-jsdoc": "62.9.0",
|