@tailor-platform/sdk 2.34.0 → 2.36.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 +62 -0
- package/dist/{application-Nce5z46R.mjs → application-BbORWCj-.mjs} +27 -27
- package/dist/application-BbORWCj-.mjs.map +1 -0
- package/dist/{application-CwpOIOTt.mjs → application-DaJbleMi.mjs} +1 -1
- package/dist/cli/commands/deploy/tailordb/migration-validation.d.mts +1 -2
- package/dist/cli/commands/tailordb/migrate/generate.d.mts +1 -1
- package/dist/cli/commands/workspace/create.d.mts +4 -2
- package/dist/cli/lib.mjs +1 -1
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/main.mjs +49 -49
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/services/application.d.mts +1 -0
- package/dist/cli/services/function-bundle.d.mts +1 -0
- package/dist/cli/shared/sdk-builder-pure-plugin.d.mts +1 -0
- package/dist/completion/zsh-worker.zsh +4 -4
- package/dist/configure/index.mjs.map +1 -1
- package/dist/configure/services/executor/trigger/webhook.d.mts +1 -0
- package/dist/configure/services/tailordb/schema.d.mts +6 -2
- package/dist/{kysely-type-DxyIWXc6.mjs → kysely-type-B0ri1-Xs.mjs} +9 -7
- package/dist/kysely-type-B0ri1-Xs.mjs.map +1 -0
- package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
- package/dist/register-ts-hook-iAcG1dfz.mjs +1057 -0
- package/dist/register-ts-hook-iAcG1dfz.mjs.map +1 -0
- package/dist/schema-BU1aIQ63.mjs.map +1 -1
- package/dist/service-DTrVVaDJ.mjs +1 -0
- package/dist/{service-eeXdVrDK.mjs → service-hIarp2Gl.mjs} +3 -3
- package/dist/service-hIarp2Gl.mjs.map +1 -0
- package/dist/vitest/index.mjs +1 -1
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/mocks/file.d.mts +1 -1
- package/dist/vitest/pglite-kysely.d.mts +5 -0
- package/docs/cli/application.md +1 -1
- package/docs/cli/tailordb.md +9 -7
- package/docs/cli/user.md +1 -1
- package/docs/cli/workspace.md +1 -1
- package/docs/cli-reference.md +6 -4
- package/docs/configuration.md +2 -0
- package/docs/services/executor.md +12 -5
- package/docs/services/tailordb/index.md +8 -0
- package/docs/services/tailordb/migration.md +30 -14
- package/docs/testing.md +2 -2
- package/package.json +10 -10
- package/dist/application-Nce5z46R.mjs.map +0 -1
- package/dist/cli/commands/tailordb/migrate/remote-state.d.mts +0 -1
- package/dist/kysely-type-DxyIWXc6.mjs.map +0 -1
- package/dist/register-ts-hook-CtWJL3EO.mjs +0 -1054
- package/dist/register-ts-hook-CtWJL3EO.mjs.map +0 -1
- package/dist/service-DEq_hdht.mjs +0 -1
- package/dist/service-eeXdVrDK.mjs.map +0 -1
|
@@ -45,9 +45,9 @@ export declare function mockFile(options?: MockFileOptions): {
|
|
|
45
45
|
calls: FileCall[];
|
|
46
46
|
clear(): void;
|
|
47
47
|
reset(): void;
|
|
48
|
-
delete: Mock<(namespace: string, tableName: string, fieldName: string, recordId: string) => Promise<void>>;
|
|
49
48
|
download: Mock<(namespace: string, tableName: string, fieldName: string, recordId: string) => Promise<FileDownloadResponse>>;
|
|
50
49
|
downloadAsBase64: Mock<(namespace: string, tableName: string, fieldName: string, recordId: string) => Promise<FileDownloadAsBase64Response>>;
|
|
50
|
+
delete: Mock<(namespace: string, tableName: string, fieldName: string, recordId: string) => Promise<void>>;
|
|
51
51
|
getMetadata: Mock<(namespace: string, tableName: string, fieldName: string, recordId: string) => Promise<FileMetadata>>;
|
|
52
52
|
downloadStream: Mock<(namespace: string, tableName: string, fieldName: string, recordId: string) => Promise<FileDownloadStreamResponse>>;
|
|
53
53
|
uploadStream: Mock<(namespace: string, tableName: string, fieldName: string, recordId: string, readableStream: ReadableStream<Uint8Array | ArrayBuffer>, options?: FileUploadStreamOptions) => Promise<FileUploadResponse>>;
|
|
@@ -64,6 +64,11 @@ export interface CreateKyselyPGliteOptions {
|
|
|
64
64
|
* PGlite runs full PostgreSQL while TailorDB supports a subset of it, so a
|
|
65
65
|
* statement passing here can still be rejected by the platform; keep a
|
|
66
66
|
* statement-level test (see `createKyselyMock`) alongside.
|
|
67
|
+
*
|
|
68
|
+
* While a transaction is open, other queries on the returned Kysely instance
|
|
69
|
+
* wait for it to finish. Inside the transaction, run every query on the
|
|
70
|
+
* transaction (`trx`): a query on the returned instance there waits on itself
|
|
71
|
+
* and never finishes.
|
|
67
72
|
* @param client - A `PGlite` instance from `@electric-sql/pglite`
|
|
68
73
|
* @param options - Adapter options
|
|
69
74
|
* @returns A Kysely instance that executes queries on the client and closes it on `destroy()`
|
package/docs/cli/application.md
CHANGED
|
@@ -82,7 +82,7 @@ See [Global Options](../cli-reference.md#global-options) for options available t
|
|
|
82
82
|
|
|
83
83
|
**Notes**
|
|
84
84
|
|
|
85
|
-
Workspace creation uses the selected user's default organization and folder set by `user update` when neither location is given and TAILOR_PLATFORM_TOKEN is not set. Giving --organization-id or --folder-id replaces both defaults. Organization and folder IDs loaded by --env-file also replace the defaults; command-line options take precedence, and empty env-file values are ignored. With --create-workspace, an existing workspace must match the resolved name, region, organization, and folder to be reused.
|
|
85
|
+
Every workspace belongs to an organization; when none is given or set as a default, interactive runs ask for one and non-interactive runs fail. Workspace creation uses the selected user's default organization and folder set by `user update` when neither location is given and TAILOR_PLATFORM_TOKEN is not set. Giving --organization-id or --folder-id replaces both defaults. Organization and folder IDs loaded by --env-file also replace the defaults; command-line options take precedence, and empty env-file values are ignored. With --create-workspace, an existing workspace must match the resolved name, region, organization, and folder to be reused.
|
|
86
86
|
|
|
87
87
|
**JSON result:**
|
|
88
88
|
|
package/docs/cli/tailordb.md
CHANGED
|
@@ -102,7 +102,7 @@ tailor tailordb migration <command>
|
|
|
102
102
|
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
103
103
|
| [`tailordb migration generate`](#tailordb-migration-generate) | Generate migration files by detecting schema differences between current local tables and the previous migration snapshot. |
|
|
104
104
|
| [`tailordb migration rebaseline`](#tailordb-migration-rebaseline) | Collapse the full migration history into a new 0000 baseline. |
|
|
105
|
-
| [`tailordb migration resolve`](#tailordb-migration-resolve) | Settle a migration that a failed deploy left partially applied.
|
|
105
|
+
| [`tailordb migration resolve`](#tailordb-migration-resolve) | Settle a migration that a failed deploy left partially applied, or whose run a deploy could not confirm. |
|
|
106
106
|
| [`tailordb migration script`](#tailordb-migration-script) | Add a migration script (migrate.ts) template to an existing migration directory, or record with --no-script that a migration intentionally has none. |
|
|
107
107
|
| [`tailordb migration set`](#tailordb-migration-set) | Set migration checkpoint to a specific number. |
|
|
108
108
|
| [`tailordb migration status`](#tailordb-migration-status) | Show the current migration status for TailorDB namespaces, including applied and pending migrations. |
|
|
@@ -129,9 +129,9 @@ tailor tailordb migration generate [options]
|
|
|
129
129
|
| `--yes` | `-y` | Skip confirmation prompts | No | `false` | - |
|
|
130
130
|
| `--config <CONFIG>` | `-c` | Path to Tailor config file | No | `"tailor.config.ts"` | `TAILOR_CONFIG_PATH` |
|
|
131
131
|
| `--name <NAME>` | `-n` | Optional description for the migration | No | - | - |
|
|
132
|
-
| `--init` | - | Delete existing migrations and start fresh
|
|
132
|
+
| `--init` | - | Delete existing migrations and start fresh (all namespaces with migrations configured, or only the one given by --namespace) | No | `false` | - |
|
|
133
133
|
| `--data-only` | - | Create a migration with no schema changes whose migration script runs a standalone data transformation | No | `false` | - |
|
|
134
|
-
| `--namespace <NAMESPACE>` | - | Target TailorDB namespace for --data-only (required if multiple namespaces exist)
|
|
134
|
+
| `--namespace <NAMESPACE>` | - | Target TailorDB namespace for --data-only (required if multiple namespaces exist), or the only namespace --init resets | No | - | - |
|
|
135
135
|
| `--rename <RENAME>` | - | Record a field, table, or nested member rename instead of remove + add (format: "Table.oldField:newField", "OldTable:NewTable", or "Table.field.oldMember:newMember"; repeatable). Renames require a migration script that copies the data. | No | - | - |
|
|
136
136
|
| `--drop <DROP>` | - | Confirm that a removed field, table, or nested member is a genuine removal, not a rename (format: "Table.field", "Table", or "Table.field.member"; repeatable). Required in non-interactive runs for a removal with rename candidates. | No | - | - |
|
|
137
137
|
| `--expand-contract <EXPAND_CONTRACT>` | - | Convert a field type, or a single value into an array, through a temporary field (format: "Table.field"; repeatable). Generates two migrations. | No | - | - |
|
|
@@ -166,7 +166,7 @@ Re-baselining removes migrations after 0000 from the working tree, records a new
|
|
|
166
166
|
|
|
167
167
|
#### tailordb migration resolve
|
|
168
168
|
|
|
169
|
-
Settle a migration that a failed deploy left partially applied.
|
|
169
|
+
Settle a migration that a failed deploy left partially applied, or whose run a deploy could not confirm.
|
|
170
170
|
|
|
171
171
|
**Usage**
|
|
172
172
|
|
|
@@ -190,11 +190,13 @@ See [Global Options](../cli-reference.md#global-options) for options available t
|
|
|
190
190
|
|
|
191
191
|
**Notes**
|
|
192
192
|
|
|
193
|
-
Use this after a deploy failed partway through a migration whose `migrate.ts` exports `steps
|
|
193
|
+
Use this after a deploy failed partway through a migration whose `migrate.ts` exports `steps`, or after a deploy could not confirm whether the script of a migration ran (`MIGRATION_OUTCOME_UNKNOWN`). The migration stays in progress: when `maintenanceMode` is on in your config, the tables of its namespace stay in maintenance mode, and `sync` and `rebaseline` are refused, while `set` accepts only the number of the in-progress migration. A deploy stops before changing anything while a run it could not confirm is not settled.
|
|
194
194
|
|
|
195
|
-
- `--rollback` sends the schema of the migration before the failed one. The next deploy runs the failed migration again from its first step
|
|
195
|
+
- `--rollback` sends the schema of the migration before the failed one. The next deploy runs the failed migration again, from its first step for a script that exports `steps`. Data changes the script already made are not undone.
|
|
196
196
|
- `--complete` sends the schema of the failed migration itself and records it as completed, without running its `migrate.ts` again. Fix the data first (for example with `tailor query`), and make sure no record lacks a value for a field that becomes required.
|
|
197
197
|
|
|
198
|
+
For a run that a deploy could not confirm, the command reads how the run ended and shows it before asking for confirmation: use `--complete` when it succeeded, and `--rollback` when it failed, was canceled, or did not start. It refuses while the run is still running, and warns when the option you chose does not fit how the run ended.
|
|
199
|
+
|
|
198
200
|
Both options remove the in-progress record, take the tables out of maintenance mode, and remove the temporary resources of the failed run, so `deploy` and the other migration commands work again. As with `tailordb migration sync`, the local migration history must reproduce the current table definitions. If sending the schema stops partway, run the command again.
|
|
199
201
|
|
|
200
202
|
#### tailordb migration script
|
|
@@ -286,7 +288,7 @@ See [Global Options](../cli-reference.md#global-options) for options available t
|
|
|
286
288
|
|
|
287
289
|
**Notes**
|
|
288
290
|
|
|
289
|
-
Local migration files are checked for a valid layout (numbering and required files in each migration directory) and a compatible format version, and deployed migration history IDs must match the local baseline. Invalid or incompatible files, history mismatches, and metadata lookup failures are reported per namespace and make the command exit non-zero; only a not-yet-deployed namespace is treated as having no applied migrations.
|
|
291
|
+
Local migration files are checked for a valid layout (numbering and required files in each migration directory) and a compatible format version, and deployed migration history IDs must match the local baseline. Invalid or incompatible files, history mismatches, a remote checkpoint ahead of the latest local migration, and metadata lookup failures are reported per namespace and make the command exit non-zero; only a not-yet-deployed namespace is treated as having no applied migrations.
|
|
290
292
|
|
|
291
293
|
#### tailordb migration sync
|
|
292
294
|
|
package/docs/cli/user.md
CHANGED
|
@@ -330,7 +330,7 @@ See [Global Options](../cli-reference.md#global-options) for options available t
|
|
|
330
330
|
|
|
331
331
|
**Notes**
|
|
332
332
|
|
|
333
|
-
`workspace create`, workspace creation during `deploy`, and the programmatic `createWorkspace()` use the default folder, or directly use the default organization when no folder is set. Giving either an organization or a folder replaces both defaults for that run. For the CLI, use --organization-id or --folder-id, or TAILOR_PLATFORM_ORGANIZATION_ID / TAILOR_PLATFORM_FOLDER_ID. The defaults are not used while TAILOR_PLATFORM_TOKEN is set, because that token does not belong to a user logged in here.
|
|
333
|
+
`workspace create`, workspace creation during `deploy`, and the programmatic `createWorkspace()` use the default folder, or directly use the default organization when no folder is set. Every workspace belongs to an organization, so creation fails when neither the command nor these defaults give one. Giving either an organization or a folder replaces both defaults for that run. For the CLI, use --organization-id or --folder-id, or TAILOR_PLATFORM_ORGANIZATION_ID / TAILOR_PLATFORM_FOLDER_ID. The defaults are not used while TAILOR_PLATFORM_TOKEN is set, because that token does not belong to a user logged in here.
|
|
334
334
|
|
|
335
335
|
Each run replaces both defaults: --default-organization-id alone clears the default folder, --default-folder-id is accepted only together with --default-organization-id, and --default-organization-id "" clears both. The IDs are not checked against the Platform here; `workspace create` and `deploy` point back to them when the Platform cannot find them or denies access to them.
|
|
336
336
|
|
package/docs/cli/workspace.md
CHANGED
|
@@ -121,7 +121,7 @@ See [Global Options](../cli-reference.md#global-options) for options available t
|
|
|
121
121
|
|
|
122
122
|
**Notes**
|
|
123
123
|
|
|
124
|
-
Without --organization-id and --folder-id, on the command line or through TAILOR_PLATFORM_ORGANIZATION_ID / TAILOR_PLATFORM_FOLDER_ID, the workspace is created in the default organization and folder that the logged-in user set with `user update`, unless TAILOR_PLATFORM_TOKEN is set. Giving either option replaces both defaults, and --folder-id requires --organization-id.
|
|
124
|
+
Every workspace belongs to an organization. Without --organization-id and --folder-id, on the command line or through TAILOR_PLATFORM_ORGANIZATION_ID / TAILOR_PLATFORM_FOLDER_ID, the workspace is created in the default organization and folder that the logged-in user set with `user update`, unless TAILOR_PLATFORM_TOKEN is set. When none of these gives an organization, the command fails. Giving either option replaces both defaults, and --folder-id requires --organization-id.
|
|
125
125
|
|
|
126
126
|
TAILOR_PLATFORM_ORGANIZATION_ID and TAILOR_PLATFORM_FOLDER_ID are also read from --env-file. Command-line options take precedence over the corresponding environment variables. Empty values in the env file are treated as not given.
|
|
127
127
|
|
package/docs/cli-reference.md
CHANGED
|
@@ -142,9 +142,11 @@ those messages describe the workflow's own checks, which can fail even when the
|
|
|
142
142
|
|
|
143
143
|
When the failure has a known source, the annotation carries it: `seed validate` reports the
|
|
144
144
|
offending JSONL file and line, including a line that is not valid JSON, and a rejected config
|
|
145
|
-
reports its file
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
reports its file. A `.ts` or `.mts` file that cannot be parsed — the config, a TailorDB type,
|
|
146
|
+
resolver, executor, workflow, or HTTP adapter file, the file passed to `function run`, or a file one
|
|
147
|
+
of them imports — reports that file and the line it failed on. Locations are written relative to
|
|
148
|
+
`GITHUB_WORKSPACE`; a file outside it is annotated without a location rather than with a path the
|
|
149
|
+
runner cannot resolve.
|
|
148
150
|
|
|
149
151
|
For a JSONL file containing a blank line, the annotation's line and the line printed in the report
|
|
150
152
|
text differ: the annotation counts every line in the file, while the printed line counts only the
|
|
@@ -332,7 +334,7 @@ Commands for managing TailorDB tables, data, and schema migrations.
|
|
|
332
334
|
| [tailordb migration](./cli/tailordb.md#tailordb-migration) | Manage TailorDB schema migrations. |
|
|
333
335
|
| [tailordb migration generate](./cli/tailordb.md#tailordb-migration-generate) | Generate migration files by detecting schema differences between current local tables and the previous migration snapshot. |
|
|
334
336
|
| [tailordb migration rebaseline](./cli/tailordb.md#tailordb-migration-rebaseline) | Collapse the full migration history into a new 0000 baseline. |
|
|
335
|
-
| [tailordb migration resolve](./cli/tailordb.md#tailordb-migration-resolve) | Settle a migration that a failed deploy left partially applied.
|
|
337
|
+
| [tailordb migration resolve](./cli/tailordb.md#tailordb-migration-resolve) | Settle a migration that a failed deploy left partially applied, or whose run a deploy could not confirm. |
|
|
336
338
|
| [tailordb migration script](./cli/tailordb.md#tailordb-migration-script) | Add a migration script (migrate.ts) template to an existing migration directory, or record with --no-script that a migration intentionally has none. |
|
|
337
339
|
| [tailordb migration set](./cli/tailordb.md#tailordb-migration-set) | Set migration checkpoint to a specific number. |
|
|
338
340
|
| [tailordb migration status](./cli/tailordb.md#tailordb-migration-status) | Show the current migration status for TailorDB namespaces, including applied and pending migrations. |
|
package/docs/configuration.md
CHANGED
|
@@ -211,6 +211,8 @@ export default defineConfig({
|
|
|
211
211
|
|
|
212
212
|
With `false`, a property read whose result is unused is removed, and so is a call that only exists to receive such a read. This also removes a read that exists only for its effect: a getter that initializes something when it is read, or a statement such as `settings.db.url;` that throws when `settings.db` is missing. Keep the default if your code or an installed package relies on a read like that.
|
|
213
213
|
|
|
214
|
+
Field and table definitions written with `db` and `t` count as having no effect, so they do not keep such a call in the bundle: with `propertyReadSideEffects: false`, `createService({ db: { company: shared.db.company }, fields: { note: db.string().index() } })` is removed when nothing uses its result. This applies to `db` and `t` that your own code imports by name from `@tailor-platform/sdk`, not to definitions in installed packages, and not when `treeshake.annotations` is `false`. A function called in a definition's arguments still runs, and keeps the whole definition in the bundle unless it is passed to the last method, as `label()` is in `db.string().description(label())` but not in `db.string().description(label()).index()`. A property read in a definition's arguments can be removed even with the default `propertyReadSideEffects`.
|
|
215
|
+
|
|
214
216
|
`treeshake` has four switches that control how unused code is removed from bundles. The values below are the SDK's defaults, which are tuned for the Tailor Platform runtime and rarely need changing. Most switches ask whether something has side effects, so `true` keeps it, while `annotations` asks whether to honor annotations, so `true` removes more:
|
|
215
217
|
|
|
216
218
|
```typescript
|
|
@@ -505,7 +505,7 @@ interface WebhookContext<T = unknown> {
|
|
|
505
505
|
body: T; // Parsed request body
|
|
506
506
|
headers: Record<string, string>; // Request headers
|
|
507
507
|
method: "POST" | "GET" | "PUT" | "DELETE"; // HTTP method
|
|
508
|
-
rawBody: string; // Raw request body
|
|
508
|
+
rawBody: string; // Raw request body, base64-encoded
|
|
509
509
|
}
|
|
510
510
|
```
|
|
511
511
|
|
|
@@ -513,11 +513,12 @@ interface WebhookContext<T = unknown> {
|
|
|
513
513
|
|
|
514
514
|
```typescript
|
|
515
515
|
import { createExecutor, incomingWebhookTrigger } from "@tailor-platform/sdk";
|
|
516
|
+
import { secretmanager } from "@tailor-platform/sdk/runtime";
|
|
516
517
|
|
|
517
|
-
|
|
518
|
+
type StripeWebhook = {
|
|
518
519
|
type: string;
|
|
519
520
|
data: { object: { id: string; amount: number } };
|
|
520
|
-
}
|
|
521
|
+
};
|
|
521
522
|
|
|
522
523
|
export default createExecutor({
|
|
523
524
|
name: "stripe-webhook",
|
|
@@ -527,8 +528,12 @@ export default createExecutor({
|
|
|
527
528
|
}>(),
|
|
528
529
|
operation: {
|
|
529
530
|
kind: "function",
|
|
530
|
-
body: async ({ body, headers }) => {
|
|
531
|
-
const
|
|
531
|
+
body: async ({ body, headers, rawBody }) => {
|
|
532
|
+
const secret = await secretmanager.getSecret("api-keys", "stripe-webhook-secret");
|
|
533
|
+
// Implement verifyStripeSignature following https://docs.stripe.com/webhooks?verify=verify-manually#verify-manually
|
|
534
|
+
if (!(await verifyStripeSignature(headers["stripe-signature"], rawBody, secret))) {
|
|
535
|
+
throw new Error("Invalid Stripe signature");
|
|
536
|
+
}
|
|
532
537
|
console.log(`Received ${body.type} event`);
|
|
533
538
|
// Process webhook...
|
|
534
539
|
},
|
|
@@ -536,6 +541,8 @@ export default createExecutor({
|
|
|
536
541
|
});
|
|
537
542
|
```
|
|
538
543
|
|
|
544
|
+
The webhook URL is public, so verify the signature over the unparsed request body before trusting `body`. `rawBody` is delivered base64-encoded; decode it to the original bytes before computing the signature.
|
|
545
|
+
|
|
539
546
|
**With custom response:**
|
|
540
547
|
|
|
541
548
|
```typescript
|
|
@@ -175,6 +175,14 @@ db.string().index();
|
|
|
175
175
|
db.string().unique();
|
|
176
176
|
```
|
|
177
177
|
|
|
178
|
+
`.unique()` and `.index()` also work on a field inside a `db.object()` that is not an array: with `.unique()` the value must be unique across rows, while `.index()` only indexes it and still allows duplicates. Inside an array of objects (`db.object(..., { array: true })`) the platform does not reject duplicate values, so both are rejected there with an error, as on an array field.
|
|
179
|
+
|
|
180
|
+
`.unique()` and `.index()` cannot be set on a `db.object()` field itself, whether it is a table field or a member of another `db.object()`, because the platform cannot index an object value.
|
|
181
|
+
|
|
182
|
+
A project that already uses `.unique()` or `.index()` on a field inside a `db.object()` was deployed with the setting stored as off, so its next `tailor deploy` stops with `MIGRATION_REMOTE_DRIFT`, and `tailor tailordb migration validate` fails the same way. `tailor tailordb migration generate` reports no differences. Run `tailor deploy --no-schema-check` once to apply the setting. If stored rows already hold duplicate values for a field that gains `.unique()`, that deploy fails and the schema stays unchanged until the duplicates are resolved.
|
|
183
|
+
|
|
184
|
+
A project that sets `.unique()` or `.index()` on a member that is itself a `db.object()` now fails with an error naming the field. Remove the modifier. Then, in a namespace with `migration.directory`, run `tailor tailordb migration generate` and `tailor deploy --no-schema-check` once; later deploys need no flag.
|
|
185
|
+
|
|
178
186
|
### Relations
|
|
179
187
|
|
|
180
188
|
Add a relation to field with automatic index and foreign key constraint:
|
|
@@ -64,7 +64,7 @@ If your local tables and remote schema have **diverged**, reconcile them before
|
|
|
64
64
|
|
|
65
65
|
### Resetting
|
|
66
66
|
|
|
67
|
-
`tailor tailordb migration generate --init` deletes the existing `migrations/` directory and creates `0000` from the current local tables. Use it only before the project is deployed. For a deployed migration history, use [`migration rebaseline`](#re-baselining-a-deployed-migration-history), which verifies the history and connected workspace before replacing any files.
|
|
67
|
+
`tailor tailordb migration generate --init` deletes the existing `migrations/` directory of every namespace with migrations configured and creates `0000` from the current local tables. Add `--namespace <name>` to reset only that namespace and leave the others untouched. Use it only before the project is deployed. For a deployed migration history, use [`migration rebaseline`](#re-baselining-a-deployed-migration-history), which verifies the history and connected workspace before replacing any files.
|
|
68
68
|
|
|
69
69
|
## Migration Workflow
|
|
70
70
|
|
|
@@ -181,6 +181,8 @@ The generated script includes a `TODO` for each nested constraint change. Replac
|
|
|
181
181
|
|
|
182
182
|
Changing a nested member's type, array-ness, decimal scale, or foreign key target is unsupported in a single migration. Use separate migrations to add a member with the target shape, copy or transform values in a custom script, and then remove the old member.
|
|
183
183
|
|
|
184
|
+
Adding a relation to an existing nested member is non-breaking, and its existing values are not checked. Fix values that point at no record before you deploy the migration that adds it: afterwards, updating a row that still holds one fails, even when the update changes only other values.
|
|
185
|
+
|
|
184
186
|
#### Renaming a member inside a nested field
|
|
185
187
|
|
|
186
188
|
Members inside a **nested field** (`db.object(...)`) are detected the same way: when `migration generate` finds a member removed from a nested field and a compatible member added under the same parent, it asks whether the member was renamed. Two members qualify only when copying the value preserves it exactly, because a rename copies existing values without transforming them: the type, array-ness, requiredness, foreign key target, decimal scale, hooks, and validations must match (index, unique, and vector may differ, as for a top-level rename), enum values may be added but not removed, an object-typed member must keep the same members recursively, and serial members never qualify.
|
|
@@ -277,10 +279,10 @@ export default defineConfig({
|
|
|
277
279
|
});
|
|
278
280
|
```
|
|
279
281
|
|
|
280
|
-
| Option | Type | Description
|
|
281
|
-
| ----------------------- | ------ |
|
|
282
|
-
| `migration.directory` | string | Directory path for migration files. Required when migrations are enabled for the namespace.
|
|
283
|
-
| `migration.machineUser` | string | Machine user used to run migration scripts. Optional; defaults to the first entry in `auth.machineUsers`.
|
|
282
|
+
| Option | Type | Description |
|
|
283
|
+
| ----------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
284
|
+
| `migration.directory` | string | Directory path for migration files. Required when migrations are enabled for the namespace. Each namespace needs its own directory. |
|
|
285
|
+
| `migration.machineUser` | string | Machine user used to run migration scripts. Optional; defaults to the first entry in `auth.machineUsers`. |
|
|
284
286
|
|
|
285
287
|
## Generated Files
|
|
286
288
|
|
|
@@ -419,14 +421,14 @@ export const steps = {
|
|
|
419
421
|
```
|
|
420
422
|
|
|
421
423
|
- 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`.
|
|
422
|
-
- 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`
|
|
424
|
+
- 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 is added or changes its target also touches the key it points to, `id` unless the relation sets another one), 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 or of a value that points at no record when a field gains a relation, 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.
|
|
423
425
|
- 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.
|
|
424
426
|
- 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.
|
|
425
427
|
- 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.
|
|
426
428
|
- 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.
|
|
427
429
|
- `MigrationSteps` is declared by `db.ts` files generated by this SDK version. With an older `db.ts`, annotate each `run` parameter with `Transaction` instead.
|
|
428
430
|
|
|
429
|
-
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.
|
|
431
|
+
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. The migration is recorded as in progress from the start of the script until its checkpoint is committed or its failure is rolled back, so that a deploy that loses track of the run leaves it for you to settle rather than running it again (see [Failure Recovery](#failure-recovery)). 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.
|
|
430
432
|
|
|
431
433
|
Because steps commit separately, a failure does not undo the steps that already completed:
|
|
432
434
|
|
|
@@ -437,7 +439,7 @@ Because steps commit separately, a failure does not undo the steps that already
|
|
|
437
439
|
- 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).
|
|
438
440
|
- 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.
|
|
439
441
|
- 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.
|
|
440
|
-
- 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
|
|
442
|
+
- 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 a multi-step migration left in progress with `tailor deploy` first, or settle it with `migration resolve`. A migration whose script run a deploy could not confirm is not finished by a later deploy, so settle that one with `migration resolve` (see [Settling a migration that stays in progress](#settling-a-migration-that-stays-in-progress)). The one exception to these refusals 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.
|
|
441
443
|
|
|
442
444
|
#### Editing steps of a migration in progress
|
|
443
445
|
|
|
@@ -483,13 +485,15 @@ export const steps = {
|
|
|
483
485
|
|
|
484
486
|
### Settling a migration that stays in progress
|
|
485
487
|
|
|
486
|
-
When you do not want to
|
|
488
|
+
A deploy resumes a multi-step migration left in progress. When you do not want it to — you abandoned the migration, or you repaired the data by hand — settle the migration with `tailor tailordb migration resolve`. Pass exactly one of two options:
|
|
487
489
|
|
|
488
490
|
- `--rollback` sends the schema of the migration before the failed one, clears the in-progress record, and leaves the checkpoint where it was. The next `tailor deploy` runs the failed migration again from its first step, so every step must be safe to run again. Data changes made by steps that already completed are not undone, and tables that exist only in the failed migration's schema are deleted with their data.
|
|
489
491
|
- `--complete` sends the schema of the failed migration itself, clears the in-progress record, and moves the checkpoint to that migration, without running its `migrate.ts` again. Repair the data first, for example with `tailor query`, and make sure no record lacks a value for a field the migration makes required: the update is rejected while such a record exists, and the migration stays in progress.
|
|
490
492
|
|
|
491
493
|
Tables that the failed migration removes are deleted with their data under `--complete`. The command refuses to run while an execution of the failed migration is still running; wait for it to finish first. Both options take the namespace's tables out of [maintenance mode](#maintenance-mode) and remove the temporary resources of the failed run. The command shows what it will change and asks for confirmation (`--yes` skips it). As with `migration sync`, the local migration history must reproduce the current table definitions, so settle the migration before you change the tables. If sending the schema stops partway, run the same command again.
|
|
492
494
|
|
|
495
|
+
The same command settles a migration whose script run a deploy could not confirm (see [Failure Recovery](#failure-recovery)). A deploy does not resume such a migration; it stops until this command settles it. The command reads how that run ended and shows it before asking for confirmation. Pass `--complete` when the run succeeded, and `--rollback` when it failed, was canceled, or did not start; `--rollback` deletes the tables and fields the migration introduced together with the data the script wrote to them, and the next deploy runs the script again. The command warns when the option you passed does not fit how the run ended, or when it cannot read the run.
|
|
496
|
+
|
|
493
497
|
## Supported Schema Changes
|
|
494
498
|
|
|
495
499
|
| Change Type | Breaking? | Migration Script? | Notes |
|
|
@@ -517,6 +521,7 @@ Tables that the failed migration removes are deleted with their data under `--co
|
|
|
517
521
|
| Remove table | No | Optional | Warning tier — no script is auto-generated, but you can add one with `tailordb migration script` to preserve data before the table leaves the active schema. The table stays readable from `migrate.ts` during Pre-migration. |
|
|
518
522
|
| Rename table | Yes | Yes | Confirmed interactively at generate time or via `--rename "OldTable:NewTable"` — see [Renaming a table](#renaming-a-table). Auto-generated script copies all rows preserving ids; both tables coexist until post-migration cleanup. |
|
|
519
523
|
| Change foreign key target table | Yes | Yes | Script updates references to the new target |
|
|
524
|
+
| Add relation to existing field | Yes | Yes | Script must resolve values that point at no record. The foreign key is withheld until Post-migration. A relation added to a nested member is non-breaking — see [Changing nested member constraints](#changing-nested-member-constraints). |
|
|
520
525
|
| Change field type (verified pair) | Yes | Yes | In-place for the pairs listed under [Field type changes](#field-type-changes); review the generated normalization scaffold and customize it only when existing values need transformation |
|
|
521
526
|
| Change field type (other pair) | Yes | Yes | Two migrations, generated together after you confirm — see [Converting a field type](#converting-a-field-type). Edit the conversion in the first; the second needs no changes. |
|
|
522
527
|
| Change array → single value | - | - | **Not supported** — see [Converting a field type](#converting-a-field-type) |
|
|
@@ -718,7 +723,7 @@ When you run `tailor deploy`, the SDK detects pending migrations (anything past
|
|
|
718
723
|
|
|
719
724
|
For each pending migration:
|
|
720
725
|
|
|
721
|
-
1. **Pre-migration**: Schema changes that would be breaking are applied in a relaxed form first. A verified in-place field type change keeps its complete previous field contract until Post-migration, including field and table-level hooks or validators changed by the same migration. Newly-required fields are added as optional; fields whose `optional → required` transition is breaking are temporarily kept optional. Fields that are being removed in this migration are temporarily kept on the table so that `migrate.ts` can still read them (for example, to `innerJoin` through a foreign key that is about to be dropped); members removed from a nested field are kept the same way, and the new member of a confirmed nested rename is added as optional and non-unique. Added or newly required nested members are optional and removed nested enum values remain allowed until Post-migration. For a renamed field, the old field is kept and the new field is added with its constraints relaxed, so the script can read the old field and write the new one. For a renamed table, the new table is created with its full constraints while the old table stays on the namespace until post-migration cleanup, so the script can copy rows between them. Breaking table-level index changes are relaxed the same way: a newly-added unique index is withheld, and an index gaining a unique constraint (or a unique index changing its field set) keeps its previous definition, so `migrate.ts` can resolve duplicates first. Non-breaking changes that are part of the same migration are also applied here.
|
|
726
|
+
1. **Pre-migration**: Schema changes that would be breaking are applied in a relaxed form first. A verified in-place field type change keeps its complete previous field contract until Post-migration, including field and table-level hooks or validators changed by the same migration. Newly-required fields are added as optional; fields whose `optional → required` transition is breaking are temporarily kept optional. Fields that are being removed in this migration are temporarily kept on the table so that `migrate.ts` can still read them (for example, to `innerJoin` through a foreign key that is about to be dropped); members removed from a nested field are kept the same way, and the new member of a confirmed nested rename is added as optional and non-unique. Added or newly required nested members are optional and removed nested enum values remain allowed until Post-migration. For a renamed field, the old field is kept and the new field is added with its constraints relaxed, so the script can read the old field and write the new one. For a renamed table, the new table is created with its full constraints while the old table stays on the namespace until post-migration cleanup, so the script can copy rows between them. A foreign key added to an existing top-level field is withheld until Post-migration, so `migrate.ts` can update rows whose values point at no record; Post-migration adds it without checking the stored values. 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.
|
|
722
727
|
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).
|
|
723
728
|
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.
|
|
724
729
|
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.
|
|
@@ -754,8 +759,9 @@ Maintenance mode restricts GraphQL writes and record events only. Resolvers, exe
|
|
|
754
759
|
|
|
755
760
|
- If a migration fails, the tables leave maintenance mode before the error is reported: they return to the settings of the last checkpoint that completed. A migration that did not reach its checkpoint returns existing tables to their previous settings and keeps the tables it created in maintenance mode until a later deploy applies it. The settings are not restored if the checkpoint number or migration history changed concurrently, or if checkpoint ownership cannot be verified. A table left behind by a failed post-checkpoint deletion also stays in maintenance mode for manual recovery.
|
|
756
761
|
- A [multi-step migration](#splitting-a-migration-into-steps) left in progress keeps its tables in maintenance mode until a deploy completes it.
|
|
762
|
+
- A migration whose script run a deploy could not confirm keeps its tables in maintenance mode until you settle it with `tailor tailordb migration resolve` (see [Failure Recovery](#failure-recovery)).
|
|
757
763
|
- With `"deploy"`, if a later part of the deploy fails, the tables leave maintenance mode before the error is reported. When the deploy ends maintenance mode, it leaves the tables of a namespace unchanged if that namespace's checkpoint number or migration history changed concurrently, or if checkpoint ownership cannot be verified, and reports it.
|
|
758
|
-
- If the deploy process is interrupted after a namespace's migrations completed but before maintenance mode ended, that namespace's tables stay in maintenance mode until a later `tailor deploy` releases them when it applies its TailorDB changes, before it replaces resolvers and executors. A namespace whose migrations had not all completed is not released this way: the next deploy resumes a multi-step migration left in progress, and otherwise reports remote schema drift (see [Failure Recovery](#failure-recovery)).
|
|
764
|
+
- If the deploy process is interrupted after a namespace's migrations completed but before maintenance mode ended, that namespace's tables stay in maintenance mode until a later `tailor deploy` releases them when it applies its TailorDB changes, before it replaces resolvers and executors. A namespace whose migrations had not all completed is not released this way: the next deploy resumes a multi-step migration left in progress, stops at a migration whose script was running until you settle it, and otherwise reports remote schema drift (see [Failure Recovery](#failure-recovery)), except when an earlier checkpoint of the interrupted deploy recorded the maintenance mode. Every migration checkpoint of a namespace records it, so with pending migrations `0001` and `0002`, a deploy interrupted right after the checkpoint of `0001` does not report drift: the next deploy keeps the tables in maintenance mode while it applies `0002` and releases them afterwards (with `"deploy"`, after the whole deploy).
|
|
759
765
|
|
|
760
766
|
#### Keeping maintenance mode short
|
|
761
767
|
|
|
@@ -915,7 +921,7 @@ When your branch and main each generated the same number, merging or rebasing st
|
|
|
915
921
|
|
|
916
922
|
- For non-interactive environments, pass `--yes` to `migration generate` and `--yes` to `apply`. `apply` runs migrations automatically when the `migrations/` directory is configured.
|
|
917
923
|
- Run `tailor tailordb migration validate` in CI to catch uncommitted migrations, broken migration files, unreviewed generated normalization logic, and remote schema drift before deploying. It exits with a non-zero code when validation fails and supports `--json`. Add `--strict` to also require an explicit acknowledgment (a `migrate.ts` or a recorded `--no-script` reason) for every pending migration that can drop data, so destructive changes cannot merge unnoticed.
|
|
918
|
-
- `tailor tailordb migration status` validates the layout and file-format compatibility of the full local history, compares its history ID with the deployed namespace, and shows applied and pending migrations for a human-readable comparison. Its exit code is non-zero on invalid or incompatible files, migration history mismatches, and remote read errors, so check the output.
|
|
924
|
+
- `tailor tailordb migration status` validates the layout and file-format compatibility of the full local history, compares its history ID with the deployed namespace, and shows applied and pending migrations for a human-readable comparison. Its exit code is non-zero on invalid or incompatible files, migration history mismatches, a remote checkpoint ahead of the latest local migration, and remote read errors, so check the output.
|
|
919
925
|
- Avoid running migrations in parallel against the same workspace — there is no locking. Serialize deploys per environment.
|
|
920
926
|
|
|
921
927
|
### Resetting a deployed project
|
|
@@ -932,7 +938,9 @@ A script that exports `steps` follows different rules once one of its steps has
|
|
|
932
938
|
|
|
933
939
|
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.
|
|
934
940
|
|
|
935
|
-
If `tailor deploy` cannot tell whether a `main` script ran, for example because the connection dropped while it started or waited for the script, it does not roll anything back: the pre-migration schema changes stay, the namespace's tables stay in [maintenance mode](#maintenance-mode) if it is on, and the deploy reports that it could not confirm the outcome.
|
|
941
|
+
If `tailor deploy` cannot tell whether a `main` script ran, for example because the connection dropped while it started or waited for the script, it does not roll anything back: the pre-migration schema changes stay, the namespace's tables stay in [maintenance mode](#maintenance-mode) if it is on, the migration stays recorded as in progress, and the deploy reports that it could not confirm the outcome. The same holds when the deploy process stops while the script runs. Until you settle the migration, `tailor deploy` stops before changing anything, including the other namespaces and resources of the application, and reports how the run stands, even with `--no-schema-check`; `migration sync` and `migration rebaseline` refuse to run, and `tailor tailordb migration status` reports the migration as in progress. Run `tailor workflow executions --workflow-name <name>` with the workflow name the error reports until the execution has finished. If it succeeded, run `tailor tailordb migration resolve --complete`, which applies the migration's schema without running the script again; otherwise run `tailor tailordb migration resolve --rollback`, and the next deploy runs the script again. See [Settling a migration that stays in progress](#settling-a-migration-that-stays-in-progress). `migration set <N>` with the migration's own number also marks it as completed, but it does not check the run or apply the schema, so use it only when the run succeeded and the post-migration schema is already in place.
|
|
942
|
+
|
|
943
|
+
If the post-migration schema changes fail after a `main` script succeeded and the deploy cannot clear the in-progress record, it keeps the pre-migration schema instead of rolling it back, so the data the script wrote stays. Settle the migration as above: `--complete` once the data meets the migration's schema, or `--rollback`.
|
|
936
944
|
|
|
937
945
|
`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.
|
|
938
946
|
|
|
@@ -1056,7 +1064,7 @@ npm install -D @electric-sql/pglite
|
|
|
1056
1064
|
|
|
1057
1065
|
The generated `db.pglite.ts` exports the `CREATE TABLE` script for the same schema `db.ts` types — the tables as the pre-migration phase leaves them while `migrate.ts` runs, including relaxed constraints, renamed fields under both names, and retained removed fields. Run it once on the PGlite instance, stage rows, then run the script in a transaction. `tailor tailordb migration script <N> --with-test` scaffolds this test too when `@electric-sql/pglite` is installed. Type the instance with `Unmigrated<Database>` rather than `Database`: `db.ts` types a column the migration makes required as `T | null` on read but `T` on write (and an enum it narrows as the old values on read but the new ones on write), so that `migrate.ts` cannot write what the migration is removing — which would also stop the test from staging the rows the script has to convert. `Unmigrated` lets every column be written with whatever it can still be read as; `main` still receives a `Transaction<Database>`.
|
|
1058
1066
|
|
|
1059
|
-
Relations are enforced as on the platform: a row that points at a parent that does not exist is rejected. Write the records the test starts from inside `prepareFixtures`, which leaves relations unchecked while it runs, so you can stage rows that predate a relation, such as rows whose parent does not exist that the script repairs. Relations are checked again when it returns, so the script itself still cannot write a missing parent. NOT NULL, UNIQUE, and CHECK constraints stay enforced. A relation is left unenforced while the pre-migration schema cannot satisfy it: when a pending type change leaves the field a different type from its target, when the migration
|
|
1067
|
+
Relations are enforced as on the platform: a row that points at a parent that does not exist is rejected. Write the records the test starts from inside `prepareFixtures`, which leaves relations unchecked while it runs, so you can stage rows that predate a relation, such as rows whose parent does not exist that the script repairs. Relations are checked again when it returns, so the script itself still cannot write a missing parent. The same holds in reverse: the script cannot delete a parent that a row still points at, or change the key a row points at, so delete rows in the opposite order of staging them. NOT NULL, UNIQUE, and CHECK constraints stay enforced. A relation is left unenforced while the pre-migration schema cannot satisfy it: when a pending type change leaves the field a different type from its target, when the migration adds the relation to an existing top-level field or retargets it to another table, or when the target field's unique constraint is still withheld. How relations on array fields and inside nested objects behave is described under [Real SQL execution with PGlite](../../testing.md#real-sql-execution-with-pglite-mocktailordbwithpglite).
|
|
1060
1068
|
|
|
1061
1069
|
```typescript
|
|
1062
1070
|
// migrations/0005/migrate.pglite.test.ts
|
|
@@ -1103,6 +1111,8 @@ describe("0005 add required email", () => {
|
|
|
1103
1111
|
});
|
|
1104
1112
|
```
|
|
1105
1113
|
|
|
1114
|
+
Queries on `db` wait while the transaction is open, so check the results after `transaction().execute()` returns, or query through `trx` inside it — a query on `db` from inside the transaction waits on itself and never finishes.
|
|
1115
|
+
|
|
1106
1116
|
Pass nested field values as JavaScript objects or arrays of objects, without `JSON.stringify`.
|
|
1107
1117
|
|
|
1108
1118
|
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`.
|
|
@@ -1156,6 +1166,12 @@ For genuinely different schemas across environments, prefer separate workspaces
|
|
|
1156
1166
|
|
|
1157
1167
|
## Troubleshooting
|
|
1158
1168
|
|
|
1169
|
+
### `MIGRATION_DIRECTORY_CONFLICT` error
|
|
1170
|
+
|
|
1171
|
+
**Cause:** Two or more TailorDB namespaces set `migration.directory` to the same directory (after resolving it against the config file's directory). For example, `tailordb` and `analyticsdb` both set `"./migrations"`. Both namespaces would read and write the same `0000/schema.json`, so `migration generate` could not tell them apart and `migration generate --init` would rebuild the history of both. Every `tailor tailordb migration` command and `tailor deploy` fail with this error before touching any migration files. The error lists every shared directory with the namespaces that use it.
|
|
1172
|
+
|
|
1173
|
+
**Resolution:** Give each namespace its own directory in `tailor.config.ts`, for example `"./migrations/tailordb"` and `"./migrations/analyticsdb"`.
|
|
1174
|
+
|
|
1159
1175
|
### Remote schema drift detected
|
|
1160
1176
|
|
|
1161
1177
|
**Cause:** Remote schema doesn't match what the migration history says it should be.
|
package/docs/testing.md
CHANGED
|
@@ -209,15 +209,15 @@ test("upserts against real rows", async () => {
|
|
|
209
209
|
});
|
|
210
210
|
```
|
|
211
211
|
|
|
212
|
-
The generated columns follow the Kysely types, not TailorDB's storage: `text` for string and enum fields, `timestamptz` for datetime, `date` and `time` for date and time, `numeric` for decimal, rounded to the configured scale and read back with exactly that many fractional digits, `jsonb` for nested objects (and arrays of them), Postgres arrays for other array fields. `id` is a generated `uuid` primary key, `.unique()` fields and unique `.indexes()` are enforced, so `ON CONFLICT` upserts behave, and `.default()` values become column defaults (`"now"` becomes the current time). `.serial()` fields are assigned by the database from the configured `start`, `maxValue`, and format. Relations are enforced as the platform does: a row that points at a parent that does not exist is rejected with an error containing `violates foreign key constraint`, so create parent rows before their children. This includes relations on array fields and on fields inside nested objects.
|
|
212
|
+
The generated columns follow the Kysely types, not TailorDB's storage: `text` for string and enum fields, `timestamptz` for datetime, `date` and `time` for date and time, `numeric` for decimal, rounded to the configured scale and read back with exactly that many fractional digits, `jsonb` for nested objects (and arrays of them), Postgres arrays for other array fields. `id` is a generated `uuid` primary key, `.unique()` fields, `.unique()` fields inside `db.object()`, and unique `.indexes()` are enforced, so `ON CONFLICT` upserts behave, and `.default()` values become column defaults (`"now"` becomes the current time). `.serial()` fields are assigned by the database from the configured `start`, `maxValue`, and format. Relations are enforced as the platform does: a row that points at a parent that does not exist is rejected with an error containing `violates foreign key constraint`, so create parent rows before their children. This includes relations on array fields and on fields inside nested objects. The reverse holds too: deleting a parent that a row still points at, or changing the key a row points at, is rejected with an error containing `violates foreign key constraint`, so delete child rows before their parents.
|
|
213
213
|
|
|
214
214
|
What the script cannot reproduce:
|
|
215
215
|
|
|
216
216
|
- Hooks, validations, and permissions do not run. A required field whose value only its own field-level create hook supplies is created nullable, so inserts that omit it succeed; give it a `.default()` if the test reads it back. A field filled by a table-level hook stays `NOT NULL`, as its Kysely type still requires it on insert.
|
|
217
|
-
- Relations on array fields and on fields inside nested objects, and relations from a field whose type differs from the field it points at (such as a `string` field related to `id`), are checked only when a row is written. Deleting a parent that such a row still points at is rejected by the platform but not by PGlite, and changing the parent's `id` is not checked either, so a test must not expect PGlite to reject them.
|
|
218
217
|
- Serial formats are reproduced for a single `%d`, `%x`, or `%X` specifier with an optional zero-padded width; an octal `%o` format fails generation with an error naming the field.
|
|
219
218
|
- `gapless` is not reproduced: the numbers come from a sequence, so an insert that fails still uses up its number.
|
|
220
219
|
- A date or datetime inside a nested object reads back as a string from `jsonb`, not a `Date`, or a `Temporal.PlainDate` or `Temporal.Instant` under `kyselyTypePlugin({ temporal: true })`, although the platform returns those types.
|
|
220
|
+
- A `.unique()` date, datetime, or time inside a nested object compares the stored text, so two spellings of the same instant (`2026-01-01T00:00:00Z` and `2026-01-01T01:00:00+01:00`) are both accepted, although the platform rejects the second. Use one spelling in tests.
|
|
221
221
|
- On a persistent PGlite (`dataDir`), tables created by an earlier run are kept as they were; drop them or start from an empty directory after changing a table definition.
|
|
222
222
|
|
|
223
223
|
To hand-write DDL instead — for a table not in the schema, or to add a constraint — run your own statements after the script, or without it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tailor-platform/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.36.0",
|
|
4
4
|
"description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -164,14 +164,14 @@
|
|
|
164
164
|
"@inquirer/prompts": "8.7.4",
|
|
165
165
|
"@jridgewell/trace-mapping": "0.3.31",
|
|
166
166
|
"@napi-rs/keyring": "2.1.2",
|
|
167
|
-
"@opentelemetry/api": "1.
|
|
168
|
-
"@opentelemetry/context-async-hooks": "
|
|
169
|
-
"@opentelemetry/exporter-trace-otlp-proto": "0.
|
|
170
|
-
"@opentelemetry/resources": "
|
|
171
|
-
"@opentelemetry/sdk-trace": "
|
|
167
|
+
"@opentelemetry/api": "1.10.0",
|
|
168
|
+
"@opentelemetry/context-async-hooks": "3.0.0",
|
|
169
|
+
"@opentelemetry/exporter-trace-otlp-proto": "0.300.0",
|
|
170
|
+
"@opentelemetry/resources": "3.0.0",
|
|
171
|
+
"@opentelemetry/sdk-trace": "3.0.0",
|
|
172
172
|
"@opentelemetry/semantic-conventions": "1.43.0",
|
|
173
173
|
"@oxc-project/types": "0.155.0",
|
|
174
|
-
"@politty/zod": "0.6.
|
|
174
|
+
"@politty/zod": "0.6.3",
|
|
175
175
|
"@secretlint/core": "13.0.7",
|
|
176
176
|
"@secretlint/secretlint-rule-preset-recommend": "13.0.7",
|
|
177
177
|
"@standard-schema/spec": "1.1.0",
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
"amaro": "1.2.2",
|
|
182
182
|
"confbox": "0.3.1",
|
|
183
183
|
"date-fns": "4.4.0",
|
|
184
|
-
"es-toolkit": "1.
|
|
184
|
+
"es-toolkit": "1.53.0",
|
|
185
185
|
"find-up-simple": "1.0.1",
|
|
186
186
|
"get-east-asian-width": "1.7.0",
|
|
187
187
|
"get-tsconfig": "4.14.3",
|
|
@@ -217,12 +217,12 @@
|
|
|
217
217
|
"@types/node": "24.19.2",
|
|
218
218
|
"@types/semver": "7.8.0",
|
|
219
219
|
"@vitest/coverage-v8": "5.0.5",
|
|
220
|
-
"eslint-plugin-zod": "5.2.
|
|
220
|
+
"eslint-plugin-zod": "5.2.2",
|
|
221
221
|
"oxfmt": "0.74.0",
|
|
222
222
|
"oxlint": "1.89.0",
|
|
223
223
|
"oxlint-tsgolint": "7.0.2005",
|
|
224
224
|
"sonda": "0.14.0",
|
|
225
|
-
"tsdown": "0.23.
|
|
225
|
+
"tsdown": "0.23.2",
|
|
226
226
|
"tsrs": "0.16.0-ts7.1.0-dev.20260929",
|
|
227
227
|
"tsx": "4.23.15",
|
|
228
228
|
"typescript": "6.0.3",
|