@tailor-platform/sdk 2.28.0 → 2.29.1
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 +67 -0
- package/README.md +2 -9
- package/agent-skills/tailor/SKILL.md +2 -1
- package/dist/{application-CvmKh5WT.mjs → application-dalGHdrI.mjs} +1 -1
- package/dist/{application-CgJLyJaF.mjs → application-rWYDAfNU.mjs} +29 -29
- package/dist/application-rWYDAfNU.mjs.map +1 -0
- package/dist/cli/cache/bundle-cache.d.mts +1 -0
- package/dist/cli/lib.mjs +1 -1
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/main.d.mts +5 -0
- package/dist/cli/main.mjs +46 -46
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/ts-hook.mjs +15 -11
- package/dist/completion/zsh-worker.zsh +59 -2
- package/dist/configure/config/types.d.mts +57 -0
- package/dist/configure/index.d.mts +1 -1
- package/dist/{register-ts-hook-yxshv0LV.mjs → register-ts-hook-CkfKqhJT.mjs} +78 -77
- package/dist/register-ts-hook-CkfKqhJT.mjs.map +1 -0
- package/dist/seed/index.d.mts +1 -1
- package/dist/seed/index.mjs +1 -1
- package/dist/seed/index.mjs.map +1 -1
- package/dist/seed-C3P_T_Eh.mjs.map +1 -1
- package/dist/types/helpers.d.mts +2 -5
- package/docs/cli/application.md +10 -5
- package/docs/cli/data.md +2 -0
- package/docs/cli/tailordb.md +36 -2
- package/docs/cli-reference.md +11 -0
- package/docs/configuration.md +55 -4
- package/docs/data-clone.md +71 -0
- package/docs/github-actions.md +90 -7
- package/docs/migration/v2.md +1 -1
- package/docs/plugin/custom.md +1 -1
- package/docs/quickstart.md +1 -1
- package/docs/services/resolver.md +1 -58
- package/docs/services/{tailordb.md → tailordb/index.md} +4 -4
- package/docs/services/{tailordb-migration.md → tailordb/migration.md} +24 -13
- package/docs/services/workflow.md +2 -2
- package/docs/testing.md +5 -5
- package/package.json +16 -16
- package/dist/application-CgJLyJaF.mjs.map +0 -1
- package/dist/register-ts-hook-yxshv0LV.mjs.map +0 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Cloning Application Data
|
|
2
|
+
|
|
3
|
+
`tailor data clone` copies the data of an application from one workspace to another, for example to fill a staging workspace with production records before testing a release against them. It copies the records of every TailorDB namespace registered in the application, and optionally TailorDB file content and IdP users.
|
|
4
|
+
|
|
5
|
+
For the full option list, see [`data clone`](./cli/data.md#data-clone).
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
The platform rejects a clone unless all of the following hold:
|
|
10
|
+
|
|
11
|
+
- The source and target workspaces are in the same region.
|
|
12
|
+
- You have editor access to both workspaces.
|
|
13
|
+
- The application is deployed to the target under the same name, with the same namespaces and the same TailorDB schema as the source. Deploy the version that runs in the source to the target first.
|
|
14
|
+
- The target is empty for everything the clone copies: the TailorDB records of the application's namespaces, plus its files with `--copy-files` and its IdP users with `--copy-idp-users`.
|
|
15
|
+
|
|
16
|
+
## Cloning into another workspace
|
|
17
|
+
|
|
18
|
+
1. Deploy the source's version of the application to the target workspace:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
tailor deploy -w <target-workspace-id>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
2. If the target already has records, empty its TailorDB tables:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
tailor tailordb truncate --all -w <target-workspace-id>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`truncate` removes TailorDB records only. When you clone files or IdP users, remove those from the target separately.
|
|
31
|
+
|
|
32
|
+
3. Start the clone:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
tailor data clone --from <source-workspace-id> --to <target-workspace-id>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The application is the one named by `name` in `tailor.config.ts`, so run all three steps from that application's project. The command asks for confirmation unless `--yes` is given.
|
|
39
|
+
|
|
40
|
+
## What is copied
|
|
41
|
+
|
|
42
|
+
| Data | Copied |
|
|
43
|
+
| ------------------------- | ------------------------- |
|
|
44
|
+
| TailorDB records | Always |
|
|
45
|
+
| TailorDB file content | With `--copy-files` |
|
|
46
|
+
| IdP users, with passwords | With `--copy-idp-users` |
|
|
47
|
+
| Configuration and schema | No — deploy them yourself |
|
|
48
|
+
|
|
49
|
+
Without `--copy-files`, file fields in the target keep their references, but the content they point to is absent. Without `--copy-idp-users`, records that store IdP user IDs refer to users that do not exist in the target.
|
|
50
|
+
|
|
51
|
+
Cloning IdP users copies credentials that sign in to the source. Do not clone users from production into a workspace that people outside the production audience can reach.
|
|
52
|
+
|
|
53
|
+
## Waiting for a clone
|
|
54
|
+
|
|
55
|
+
A clone runs asynchronously on the platform. The command prints an operation ID when the clone starts, then waits until it completes or fails.
|
|
56
|
+
|
|
57
|
+
Pressing Ctrl+C, or passing `--no-wait`, stops only the waiting; the clone keeps running. Check on it later with the operation ID:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Print the current status once
|
|
61
|
+
tailor data operation get --from <source-workspace-id> --to <target-workspace-id> --operation-id <operation-id>
|
|
62
|
+
|
|
63
|
+
# Wait until it completes or fails
|
|
64
|
+
tailor data operation get --from <source-workspace-id> --to <target-workspace-id> --operation-id <operation-id> --wait
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
With `--json`, `data clone` reports `status: "completed"` once the clone has finished, or `status: "started"` with `--no-wait`.
|
|
68
|
+
|
|
69
|
+
## Rehearsing migrations with cloned data
|
|
70
|
+
|
|
71
|
+
To test pending TailorDB migrations against a copy of real records, use `tailor tailordb migration test --data clone` instead of cloning by hand. It creates a temporary workspace, deploys the pre-migration schema, clones the records, and runs the pending migrations. That command copies TailorDB records only; see [Clone mode](./services/tailordb/migration.md#clone-mode).
|
package/docs/github-actions.md
CHANGED
|
@@ -35,7 +35,8 @@ tailor setup ci tag --name my-app-prod \
|
|
|
35
35
|
|
|
36
36
|
After running the command, follow the **Next steps** printed to the terminal:
|
|
37
37
|
run `tailor setup ci env` to get the commands that set the secrets and
|
|
38
|
-
variables each GitHub Environment needs
|
|
38
|
+
variables each GitHub Environment needs, and `tailor setup ci env --check` to
|
|
39
|
+
confirm none is missing (see
|
|
39
40
|
[Setting secrets and variables](#setting-secrets-and-variables)), then commit
|
|
40
41
|
the generated files.
|
|
41
42
|
|
|
@@ -98,7 +99,8 @@ The generated workflow builds one self-contained ERD viewer HTML file for each
|
|
|
98
99
|
owned TailorDB namespace in `tailor.config.ts`. The viewer compares the pull
|
|
99
100
|
request merge result with the base branch, can switch between the current schema
|
|
100
101
|
and highlighted diff, uploads the HTML files as unarchived Actions artifacts,
|
|
101
|
-
and upserts a PR comment with artifact links.
|
|
102
|
+
and upserts a PR comment with artifact links. On GitHub Enterprise Server, also
|
|
103
|
+
pass `--ghes` (see [GitHub Enterprise Server](#github-enterprise-server)).
|
|
102
104
|
|
|
103
105
|
ERD preview does not use Tailor Platform credentials. Fork pull requests still
|
|
104
106
|
build artifacts, but the comment step is skipped because fork tokens cannot
|
|
@@ -264,8 +266,8 @@ Everything else is yours, and re-running `setup` keeps it:
|
|
|
264
266
|
- **These inputs of managed steps:** `ignore` on `tailor-generate-check` and
|
|
265
267
|
`tailor-drift-check`, `fail-on-drift` on `tailor-drift-check`,
|
|
266
268
|
`install-command` on `tailor-install`, `node-version-file` on
|
|
267
|
-
`tailor-setup`, `label` on `tailor-plan`,
|
|
268
|
-
`tailor-notify`.
|
|
269
|
+
`tailor-setup`, `label` on `tailor-plan`, `ttl` on `tailor-preview-deploy`,
|
|
270
|
+
and `user-mapping` on `tailor-notify`.
|
|
269
271
|
- **Your own jobs in the `needs` of `tailor-result` and
|
|
270
272
|
`tailor-preview-result`**, so they count toward the workflow's result (see
|
|
271
273
|
[Requiring the workflow in branch protection](#requiring-the-workflow-in-branch-protection)).
|
|
@@ -296,6 +298,17 @@ The job is skipped whenever `tailor-preview-deploy` is skipped: when a pull
|
|
|
296
298
|
request is closed, for draft and fork pull requests, and for unlabeled ones with
|
|
297
299
|
`--require-preview-label`.
|
|
298
300
|
|
|
301
|
+
A preview workspace expires 7 days (`ttl: 7d` on `tailor-preview-deploy`) after
|
|
302
|
+
the last push to its pull request. When a pull request is closed,
|
|
303
|
+
`tailor-preview-cleanup` deletes its workspace and also deletes the other
|
|
304
|
+
expired preview workspaces of the same workspace name in the folder
|
|
305
|
+
(`TAILOR_PLATFORM_FOLDER_ID`, or directly under the organization when unset), so
|
|
306
|
+
a workspace whose own cleanup never ran does not stay forever. A pull request
|
|
307
|
+
that stays idle past the `ttl` can have its workspace deleted while it is still
|
|
308
|
+
open; the next push creates a fresh one. Change `ttl` to keep previews longer or
|
|
309
|
+
shorter. Workflows generated before this was added get these settings the next
|
|
310
|
+
time you run `tailor setup update`.
|
|
311
|
+
|
|
299
312
|
Each new preview workflow run for a pull request, such as after a push or when
|
|
300
313
|
the pull request is closed, cancels the run still in progress for it, including
|
|
301
314
|
your jobs, so they do not keep running against a workspace that the new run
|
|
@@ -468,14 +481,18 @@ protection rules no longer guard your deploys.
|
|
|
468
481
|
Environment the generated workflows use, the commands that create the
|
|
469
482
|
environment (only when it does not exist yet) and set its secrets and
|
|
470
483
|
variables. It is read-only, so re-run it whenever you add a target. When the
|
|
471
|
-
`origin` remote points at github.com, the output names that repository
|
|
472
|
-
|
|
473
|
-
|
|
484
|
+
`origin` remote points at github.com, the output names that repository;
|
|
485
|
+
otherwise it names the repository `gh repo view` reports for the current
|
|
486
|
+
directory, with its host on GitHub Enterprise Server. Either way the commands
|
|
487
|
+
work from any directory. When neither names a repository, `gh` resolves it from
|
|
488
|
+
the current directory when you run the commands, and the Terraform output leaves
|
|
489
|
+
it as a placeholder.
|
|
474
490
|
|
|
475
491
|
```bash
|
|
476
492
|
tailor setup ci env # gh CLI commands (default)
|
|
477
493
|
tailor setup ci env --format terraform # Terraform for the integrations/github provider
|
|
478
494
|
tailor setup ci env --environment my-app-stg # only one environment (repeat for several)
|
|
495
|
+
tailor setup ci env --check # check what is registered in GitHub (see below)
|
|
479
496
|
```
|
|
480
497
|
|
|
481
498
|
The list follows what each generated workflow actually reads:
|
|
@@ -517,6 +534,30 @@ protection settings (reviewers, wait timer, branch policy), so importing an
|
|
|
517
534
|
environment keeps the approval gate you configured; remove the `lifecycle` block
|
|
518
535
|
to manage those settings in Terraform instead.
|
|
519
536
|
|
|
537
|
+
### Checking what is registered
|
|
538
|
+
|
|
539
|
+
`tailor setup ci env --check` reads GitHub with `gh` and reports, for each
|
|
540
|
+
environment, which of these entries are registered. It fails when a required
|
|
541
|
+
entry is missing, reports missing optional entries without failing, and prints
|
|
542
|
+
the `gh` commands that set only the missing entries, with where each value comes
|
|
543
|
+
from. It changes nothing, and it checks names only, since GitHub never returns
|
|
544
|
+
secret values.
|
|
545
|
+
|
|
546
|
+
- An entry set on the repository, or on the organization and shared with the
|
|
547
|
+
repository, counts as registered, because the workflow reads it from there when
|
|
548
|
+
the environment does not set it. The report says where each entry was found.
|
|
549
|
+
- An entry registered as the other kind, such as `TAILOR_PLATFORM_WORKSPACE_ID`
|
|
550
|
+
set as a secret, is reported as missing, because the workflow does not read it.
|
|
551
|
+
`TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID` set as a secret still counts, with a
|
|
552
|
+
reminder to move it to a variable.
|
|
553
|
+
- `gh` must be installed and logged in (`gh auth login`) as an account with write
|
|
554
|
+
access to the repository, which listing secrets and variables requires. GitHub
|
|
555
|
+
answers 404 for a private repository the account cannot see, so the check
|
|
556
|
+
reports that as an access problem, not as missing entries.
|
|
557
|
+
|
|
558
|
+
`--environment` narrows the check the same way. `--check` cannot be combined with
|
|
559
|
+
`--format terraform`.
|
|
560
|
+
|
|
520
561
|
## GitHub Environments (approval gate)
|
|
521
562
|
|
|
522
563
|
Both the `plan` and `deploy` jobs are associated with a GitHub Environment — the
|
|
@@ -784,6 +825,48 @@ tailor setup ci env
|
|
|
784
825
|
|
|
785
826
|
Commit both workflow files and `.github/tailor.lock`.
|
|
786
827
|
|
|
828
|
+
## GitHub Enterprise Server
|
|
829
|
+
|
|
830
|
+
The generated workflows also run on GitHub Enterprise Server. Prepare the
|
|
831
|
+
server and its runners first:
|
|
832
|
+
|
|
833
|
+
- **Runners:** GitHub Enterprise Server has no GitHub-hosted runners. Change
|
|
834
|
+
`runs-on` on the managed jobs to the labels of your self-hosted runners;
|
|
835
|
+
re-running `setup` keeps it (see
|
|
836
|
+
[Customizing the generated workflow](#customizing-the-generated-workflow)).
|
|
837
|
+
The runners need Actions runner 2.327.1 or later, which runs the Node 24
|
|
838
|
+
actions the workflows use, and `gh` and `jq` on `PATH` (plus `node` with
|
|
839
|
+
`--erd-preview`).
|
|
840
|
+
- **Actions from github.com:** the workflows use `tailor-platform/actions`,
|
|
841
|
+
`actions/checkout`, `actions/setup-node`, `pnpm/action-setup`, and
|
|
842
|
+
`oven-sh/setup-bun` (and `actions/upload-artifact` with `--erd-preview`).
|
|
843
|
+
Make them available on the server with GitHub Connect or `actions-sync`. The
|
|
844
|
+
server ships its own copies of the `actions/*` ones, which may not include
|
|
845
|
+
the pinned versions: update them with `actions-sync`, or, with GitHub
|
|
846
|
+
Connect, delete them from the server's `actions` organization (see GitHub's
|
|
847
|
+
[Using the latest version of the official bundled actions](https://docs.github.com/en/enterprise-server@latest/admin/managing-github-actions-for-your-enterprise/managing-access-to-actions-from-githubcom/using-the-latest-version-of-the-official-bundled-actions)).
|
|
848
|
+
- **Environment settings:** sign `gh` in to the server
|
|
849
|
+
(`gh auth login --hostname <host>`) before you run `tailor setup ci env` in a
|
|
850
|
+
clone of the repository, so that the `gh` commands it prints and its Terraform
|
|
851
|
+
output target the server. When `gh` cannot tell the repository, the output
|
|
852
|
+
says to export `GH_HOST=<host>` before running the `gh` commands, or
|
|
853
|
+
`GITHUB_BASE_URL=https://<host>/` before running Terraform, so that they
|
|
854
|
+
call the server instead of github.com.
|
|
855
|
+
|
|
856
|
+
With `--erd-preview`, also pass `--ghes`:
|
|
857
|
+
|
|
858
|
+
```bash
|
|
859
|
+
tailor setup ci branch --name my-app-stg --erd-preview --ghes
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
GitHub Enterprise Server does not support `actions/upload-artifact` v4 or
|
|
863
|
+
later, which the workflow uses to upload each ERD viewer unzipped. With
|
|
864
|
+
`--ghes`, the workflow uploads it with v3 instead, so each viewer is a zip
|
|
865
|
+
file, and the PR comment links to the workflow run, which lists the artifacts.
|
|
866
|
+
`setup update` keeps `--ghes`; pass it again whenever you re-run
|
|
867
|
+
`setup ci branch`, or the workflow switches back to the upload that GitHub
|
|
868
|
+
Enterprise Server does not support.
|
|
869
|
+
|
|
787
870
|
## Keeping dependencies and actions updated
|
|
788
871
|
|
|
789
872
|
Run this command once from the repository root to add Renovate configuration:
|
package/docs/migration/v2.md
CHANGED
|
@@ -1736,7 +1736,7 @@ The CLI stores human users by their stable subject ID instead of email (email is
|
|
|
1736
1736
|
|
|
1737
1737
|
### `db.fields.timestamps()`: `updatedAt` becomes required
|
|
1738
1738
|
|
|
1739
|
-
The `updatedAt` field from `db.fields.timestamps()` changes from optional to required (non-null): it defaults to the current time and keeps refreshing automatically on every update, though a value you provide explicitly is still respected. Applying this change to a table that already has rows with `updatedAt: null` makes `deploy` fail with `field "updatedAt" cannot be updated from non-required to required when records with null values exist`. Backfill those rows first, e.g. `UPDATE <table> SET "updatedAt" = "createdAt" WHERE "updatedAt" IS NULL` for each affected table — see [TailorDB migrations](../services/tailordb
|
|
1739
|
+
The `updatedAt` field from `db.fields.timestamps()` changes from optional to required (non-null): it defaults to the current time and keeps refreshing automatically on every update, though a value you provide explicitly is still respected. Applying this change to a table that already has rows with `updatedAt: null` makes `deploy` fail with `field "updatedAt" cannot be updated from non-required to required when records with null values exist`. Backfill those rows first, e.g. `UPDATE <table> SET "updatedAt" = "createdAt" WHERE "updatedAt" IS NULL` for each affected table — see [TailorDB migrations](../services/tailordb/migration.md#performance-and-large-tables) for splitting a large backfill across primary-key ranges if a single `UPDATE` times out.
|
|
1740
1740
|
|
|
1741
1741
|
### Node.js minimum version raised to 22.15.0
|
|
1742
1742
|
|
package/docs/plugin/custom.md
CHANGED
|
@@ -516,9 +516,9 @@ export default createSoftDeletePlugin();
|
|
|
516
516
|
```typescript
|
|
517
517
|
// plugins/soft-delete/executors/on-delete.ts
|
|
518
518
|
import { createExecutor, recordDeletedTrigger } from "@tailor-platform/sdk";
|
|
519
|
-
import type { TailorAnyDBType } from "@tailor-platform/sdk";
|
|
520
519
|
import { withPluginContext } from "@tailor-platform/sdk/plugin";
|
|
521
520
|
import { getDB } from "generated/tailordb";
|
|
521
|
+
import type { TailorAnyDBType } from "@tailor-platform/sdk";
|
|
522
522
|
|
|
523
523
|
interface SoftDeleteContext {
|
|
524
524
|
sourceTable: TailorAnyDBType;
|
package/docs/quickstart.md
CHANGED
|
@@ -108,7 +108,7 @@ Deploy again to see the response.
|
|
|
108
108
|
|
|
109
109
|
## Next Steps
|
|
110
110
|
|
|
111
|
-
- Learn about [TailorDB](./services/tailordb.md) for database schema definition
|
|
111
|
+
- Learn about [TailorDB](./services/tailordb/index.md) for database schema definition
|
|
112
112
|
- Create custom [Resolvers](./services/resolver.md) with business logic
|
|
113
113
|
- Set up [Executors](./services/executor.md) for event-driven automation
|
|
114
114
|
- Explore [Templates](https://github.com/tailor-platform/sdk/tree/main/packages/create-sdk#available-templates) for more examples
|
|
@@ -11,64 +11,7 @@ Resolvers provide:
|
|
|
11
11
|
- Access to TailorDB via Kysely query builder
|
|
12
12
|
- User context for authentication/authorization
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
The SDK's Resolver is a simplified version of Tailor Platform's [Pipeline Resolver](https://docs.tailor.tech/guides/resolver).
|
|
17
|
-
|
|
18
|
-
| Pipeline Resolver | SDK Resolver |
|
|
19
|
-
| ---------------------------------------- | --------------------------------- |
|
|
20
|
-
| Multiple steps with different operations | Single `body` function |
|
|
21
|
-
| Declarative step configuration | Imperative TypeScript code |
|
|
22
|
-
| Built-in TailorDB/GraphQL steps | Direct database access via Kysely |
|
|
23
|
-
| CEL expressions for data transformation | Native TypeScript transformations |
|
|
24
|
-
|
|
25
|
-
### Example Comparison
|
|
26
|
-
|
|
27
|
-
**Pipeline Resolver (Tailor Platform native):**
|
|
28
|
-
|
|
29
|
-
```yaml
|
|
30
|
-
steps:
|
|
31
|
-
- name: getUser
|
|
32
|
-
operation: tailordb.query
|
|
33
|
-
params:
|
|
34
|
-
type: User
|
|
35
|
-
filter:
|
|
36
|
-
email: { eq: "{{ input.email }}" }
|
|
37
|
-
- name: updateAge
|
|
38
|
-
operation: tailordb.mutation
|
|
39
|
-
params:
|
|
40
|
-
type: User
|
|
41
|
-
id: "{{ steps.getUser.id }}"
|
|
42
|
-
input:
|
|
43
|
-
age: "{{ steps.getUser.age + 1 }}"
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**Resolver (SDK):**
|
|
47
|
-
|
|
48
|
-
```typescript
|
|
49
|
-
createResolver({
|
|
50
|
-
name: "incrementUserAge",
|
|
51
|
-
operation: "mutation",
|
|
52
|
-
input: { email: t.string() },
|
|
53
|
-
body: async (context) => {
|
|
54
|
-
const db = getDB("tailordb");
|
|
55
|
-
const user = await db
|
|
56
|
-
.selectFrom("User")
|
|
57
|
-
.selectAll()
|
|
58
|
-
.where("email", "=", context.input.email)
|
|
59
|
-
.executeTakeFirstOrThrow();
|
|
60
|
-
|
|
61
|
-
await db
|
|
62
|
-
.updateTable("User")
|
|
63
|
-
.set({ age: user.age + 1 })
|
|
64
|
-
.where("id", "=", user.id)
|
|
65
|
-
.execute();
|
|
66
|
-
|
|
67
|
-
return { oldAge: user.age, newAge: user.age + 1 };
|
|
68
|
-
},
|
|
69
|
-
output: t.object({ oldAge: t.int(), newAge: t.int() }),
|
|
70
|
-
});
|
|
71
|
-
```
|
|
14
|
+
For the official Tailor Platform documentation, see [Resolver Guide](https://docs.tailor.tech/guides/resolver).
|
|
72
15
|
|
|
73
16
|
## Creating a Resolver
|
|
74
17
|
|
|
@@ -535,7 +535,7 @@ db.table("User", {
|
|
|
535
535
|
- When not specified, `deploy` sets it from the executors taking part in the same run: `true` while one of them uses this table with `recordCreatedTrigger`, `recordUpdatedTrigger`, or `recordDeletedTrigger`, and `false` once none does. Removing the last such trigger turns publishing back off on the next `deploy`
|
|
536
536
|
- When explicitly set to `false` while an executor taking part in the same run uses this table, `deploy` fails
|
|
537
537
|
- An executor declared with `disabled: true` never runs, so it does not count as using the table
|
|
538
|
-
- While a `deploy` applies pending migrations with [maintenance mode](./
|
|
538
|
+
- While a `deploy` applies pending migrations with [maintenance mode](./migration.md#maintenance-mode) on, every table in the migrating namespace is restricted: the GraphQL create, update, and delete operations and bulk upsert are disabled, and the table's `read` setting is not switched off for the migration (a table the migration creates uses its configured setting), so nothing writes an intermediate schema and readable tables are not shut down. Record event publishing is also switched off so a migration script's writes do not reach executors from the previous deploy. With `maintenanceMode: false`, these restrictions are not applied. After success, the configured settings take effect. If a later migration fails, settings from the last confirmed checkpoint are restored; an uncommitted migration restores existing tables' prior settings and leaves its newly created tables restricted until a successful retry. A table that already refuses create, update, delete, and bulk upsert and publishes no record events is left as it is. Restoration is skipped if the checkpoint number or migration history changed concurrently, or if checkpoint ownership cannot be verified; the deploy then reports the namespace whose tables stay restricted and points to `tailor tailordb migration status --namespace <namespace>`. When a failed migration or a failed restoration leaves tables restricted, the deploy lists their names, and deploying again releases them once the migration completes. A table that a failed rollback could not delete or revert is the exception: the deploy also lists it, but the next deploy stops with remote schema drift until you delete or repair that table by hand. A table left behind by a failed post-checkpoint deletion also remains read-only for manual recovery, and the deploy does not list it by name
|
|
539
539
|
|
|
540
540
|
**Use cases:**
|
|
541
541
|
|
|
@@ -580,7 +580,7 @@ db.table("User", {
|
|
|
580
580
|
|
|
581
581
|
Control which GraphQL operations (`create`, `update`, `delete`, `read`) are exposed for a table. All operations are enabled by default.
|
|
582
582
|
|
|
583
|
-
While a `deploy` applies pending migrations with [maintenance mode](./
|
|
583
|
+
While a `deploy` applies pending migrations with [maintenance mode](./migration.md#maintenance-mode) on, the GraphQL `create`, `update`, and `delete` operations and bulk upsert are switched off across the migrating namespace, so nothing writes an intermediate schema. The `read` operation is not switched off: it keeps the setting the table has when the restrictions begin. With or without maintenance mode, a table that a migration touches follows that migration's schema from its first phase on, so a migration that changes `read` changes it before its script runs; a table no migration touches keeps its setting. A table the migration creates uses its configured `read` setting from the start. After success, the configured operations take effect. If a later migration fails while maintenance mode is on, operations from the last confirmed checkpoint are restored; an uncommitted migration restores existing tables' prior operations and leaves its newly created tables restricted until a successful retry. A table that already refuses create, update, delete, and bulk upsert and publishes no record events is left as it is. Restoration is skipped if the checkpoint number or migration history changed concurrently; the deploy then reports the namespace whose tables stay restricted, and deploying again releases them once the migration completes.
|
|
584
584
|
|
|
585
585
|
```typescript
|
|
586
586
|
db.table("Order", {
|
|
@@ -744,6 +744,6 @@ db.table("User", {
|
|
|
744
744
|
|
|
745
745
|
## Migrations
|
|
746
746
|
|
|
747
|
-
When you change a TailorDB table definition, the SDK can generate a migration that captures the diff and, for breaking changes, runs a data transformation script during `tailor deploy`. See the [TailorDB Migrations guide](./
|
|
747
|
+
When you change a TailorDB table definition, the SDK can generate a migration that captures the diff and, for breaking changes, runs a data transformation script during `tailor deploy`. See the [TailorDB Migrations guide](./migration.md) for the full workflow, configuration, supported change types, team coordination, and troubleshooting.
|
|
748
748
|
|
|
749
|
-
For the CLI command reference, see [`tailordb migration`](
|
|
749
|
+
For the CLI command reference, see [`tailordb migration`](../../cli/tailordb.md#tailordb-migration).
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
The migration system tracks changes to your TailorDB table definitions over time and applies them to deployed workspaces with optional data transformation scripts.
|
|
6
6
|
|
|
7
|
-
For the CLI command reference, see [`tailordb migration`](
|
|
7
|
+
For the CLI command reference, see [`tailordb migration`](../../cli/tailordb.md#tailordb-migration). This document covers concepts, workflows, and operational guidance.
|
|
8
8
|
|
|
9
9
|
## Overview
|
|
10
10
|
|
|
@@ -244,7 +244,7 @@ This writes a numbered migration with an empty `diff.json`, a `migrate.ts` skele
|
|
|
244
244
|
|
|
245
245
|
The command requires a clean state: if the namespace has schema changes that are not yet in migration files, generate the schema migration first. With multiple namespaces, pass `--namespace` to name the target. `--data-only` cannot be combined with `--init`, `--rename`, `--drop`, or `--expand-contract`.
|
|
246
246
|
|
|
247
|
-
A data-only migration runs in **every** workspace the history is applied to, including freshly created ones. Write the script so it is safe against tables with no matching rows (a set-based `UPDATE` with a `WHERE` clause is naturally a no-op on an empty table). For a fix that should run in a single environment only, or that is too large for one transaction, run it outside the migration history instead, for example as a one-off script scaffolded with [`tailor function script`](
|
|
247
|
+
A data-only migration runs in **every** workspace the history is applied to, including freshly created ones. Write the script so it is safe against tables with no matching rows (a set-based `UPDATE` with a `WHERE` clause is naturally a no-op on an empty table). For a fix that should run in a single environment only, or that is too large for one transaction, run it outside the migration history instead, for example as a one-off script scaffolded with [`tailor function script`](../../cli/function.md#function-script) and executed against a single workspace with [`tailor function run`](../../cli/function.md#function-run).
|
|
248
248
|
|
|
249
249
|
## Configuration
|
|
250
250
|
|
|
@@ -425,7 +425,7 @@ Because steps commit separately, a failure does not undo the steps that already
|
|
|
425
425
|
- If the steps changed since the earlier run, or that run is no longer available, every step runs again from the beginning. See [Editing steps of a migration in progress](#editing-steps-of-a-migration-in-progress).
|
|
426
426
|
- If every step completed but the post-migration schema changes fail (for example, a field the migration makes required still has empty values), the migration also stays in progress with its pre-migration schema, and the next deploy retries those schema changes without running the steps again.
|
|
427
427
|
- To run a completed step again (because it had a bug, or to fix rows the post-migration schema changes reject), change the steps: rename that step and update the `dependsOn` lists that name it, or add a step that fixes the rows. Every step then runs again from the beginning, so this relies on every step being safe to run again; to keep the other completed steps from running again, skip them as described below.
|
|
428
|
-
- While a migration is in progress, `migration set`, `migration sync`, and `migration rebaseline` refuse to run, and `migration test` refuses to use that workspace as its source. Finish the migration with `tailor deploy` first. The one exception is `migration set <N>` with the in-progress migration's own number: it marks that migration as completed and clears its in-progress record. Use it only when the migration's schema changes are already applied, for example after the deploy reported that the checkpoint write could not be confirmed.
|
|
428
|
+
- While a migration is in progress, `migration set`, `migration sync`, and `migration rebaseline` refuse to run, and `migration test` refuses to use that workspace as its source. Finish the migration with `tailor deploy` first, or settle it with `migration resolve` (see [Settling a migration that stays in progress](#settling-a-migration-that-stays-in-progress)). The one exception is `migration set <N>` with the in-progress migration's own number: it marks that migration as completed and clears its in-progress record. Use it only when the migration's schema changes are already applied, for example after the deploy reported that the checkpoint write could not be confirmed.
|
|
429
429
|
|
|
430
430
|
#### Editing steps of a migration in progress
|
|
431
431
|
|
|
@@ -450,6 +450,15 @@ tailor deploy --migration-skip-steps main-db/backfillUser,main-db/backfillInvoic
|
|
|
450
450
|
|
|
451
451
|
Write every step so that running it again is safe: besides the cases above, a step can run a second time when a deploy is interrupted right after it commits. Use `where` clauses that skip rows a step already migrated, as in the example. Rows written while a migration is in progress are not seen by steps that already completed. [Maintenance mode](#maintenance-mode) blocks GraphQL writes but not the writes your resolvers, executors, or workflows make through Kysely, and without it GraphQL clients can write too, so stop such writers until the migration finishes or write steps that tolerate them.
|
|
452
452
|
|
|
453
|
+
### Settling a migration that stays in progress
|
|
454
|
+
|
|
455
|
+
When you do not want to resume the migration with another deploy — you abandoned it, or you repaired the data by hand — settle it with `tailor tailordb migration resolve`. Pass exactly one of two options:
|
|
456
|
+
|
|
457
|
+
- `--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.
|
|
458
|
+
- `--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.
|
|
459
|
+
|
|
460
|
+
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.
|
|
461
|
+
|
|
453
462
|
## Supported Schema Changes
|
|
454
463
|
|
|
455
464
|
| Change Type | Breaking? | Migration Script? | Notes |
|
|
@@ -629,7 +638,9 @@ Clone mode copies TailorDB records from the source workspace after the identical
|
|
|
629
638
|
tailor tailordb migration test --data clone
|
|
630
639
|
```
|
|
631
640
|
|
|
632
|
-
|
|
641
|
+
Cloning requires editor access to both workspaces, which must be in the same region. Clone mode copies TailorDB records only: IdP users, file blobs, and metadata labels are not copied, and unlike [`tailor data clone`](../../data-clone.md) it has no `--copy-files` or `--copy-idp-users` option. File fields therefore retain references whose blobs are absent. The command polls the asynchronous operation and reports platform failures.
|
|
642
|
+
|
|
643
|
+
With `--target-workspace-id`, the designated target must be in the same region as the source; the command checks this before deploying anything to it.
|
|
633
644
|
|
|
634
645
|
The source schema is re-verified immediately before cloning; if the source workspace was deployed or otherwise changed after the test started, the command aborts instead of cloning data that no longer matches the deployed baseline.
|
|
635
646
|
|
|
@@ -687,7 +698,7 @@ This split is what allows existing rows to be backfilled before the database sta
|
|
|
687
698
|
| `"migration"` (default) | From before the first pending migration runs until the last one completes. |
|
|
688
699
|
| `"deploy"` | From before the first pending migration runs until the deploy has applied every other change, including resolvers, executors, and workflows, and has removed the resources it deletes. |
|
|
689
700
|
|
|
690
|
-
A deploy without pending migrations never enters maintenance mode. When `maintenanceMode` is unset, it behaves as `"migration"`, preserving the existing deployment behavior. The value is read from the config like any other, so it can differ per environment (see [Multi-Environment Configuration](
|
|
701
|
+
A deploy without pending migrations never enters maintenance mode. When `maintenanceMode` is unset, it behaves as `"migration"`, preserving the existing deployment behavior. The value is read from the config like any other, so it can differ per environment (see [Multi-Environment Configuration](../../multi-environment.md#varying-config-values-per-environment)):
|
|
691
702
|
|
|
692
703
|
```typescript
|
|
693
704
|
export default defineConfig({
|
|
@@ -711,7 +722,7 @@ Maintenance mode restricts GraphQL writes and record events only. Resolvers, exe
|
|
|
711
722
|
|
|
712
723
|
#### Keeping maintenance mode short
|
|
713
724
|
|
|
714
|
-
Maintenance mode lasts for the whole migration job, including the time the platform takes to start it, which can far exceed the time `migrate.ts` itself needs. Keep in `migrate.ts` only the work that must be done before the post-migration schema is applied, and move the rest into a workflow that you start after the deploy (for example with [`tailor workflow start`](
|
|
725
|
+
Maintenance mode lasts for the whole migration job, including the time the platform takes to start it, which can far exceed the time `migrate.ts` itself needs. Keep in `migrate.ts` only the work that must be done before the post-migration schema is applied, and move the rest into a workflow that you start after the deploy (for example with [`tailor workflow start`](../../cli/workflow.md#workflow-start)).
|
|
715
726
|
|
|
716
727
|
- **Must stay in `migrate.ts`**: anything the post-migration schema enforces or removes — filling a field that becomes required, resolving duplicates before a unique index, converting values for a field type change, and copying data out of a field or table that the migration renames or removes.
|
|
717
728
|
- **Can move to a workflow**: work the post-migration schema does not depend on, such as filling an optional field, recomputing derived values, or cleaning up rows. Requests that run before the workflow finishes see the data in its unfixed state.
|
|
@@ -734,7 +745,7 @@ When [maintenance mode](#maintenance-mode) is on, `tailor deploy` reports how lo
|
|
|
734
745
|
|
|
735
746
|
Phases that repeat across several migrations are added up. The report is printed only after the migrations succeed and the tables leave maintenance mode; when a migration fails, deploy reports the error instead (see [Failure Recovery](#failure-recovery)). When deploy resumes a migration that an earlier deploy left in progress, the tables were already in maintenance mode before it started, and the report covers only this deploy. A job can be reported as started on the platform well before its code runs, so deploy tells waiting from running by a line every job of a migration logs before your script: `[tailor-sdk] migration script started`. You may see that line in function execution logs; deploy leaves it out of the logs it prints. A script that exports [`steps`](#splitting-a-migration-into-steps) waits for each step's job to start, and that time counts as `waiting to start` too. Time when deploy cannot tell whether the script is running — because the run or a running job's logs could not be read, or because an older SDK version started the run and it never logs the line — is reported as `waiting to start or running` instead.
|
|
736
747
|
|
|
737
|
-
Deploy checks the run every few seconds, so a boundary between `waiting to start` and `running` can be off by that much. With `--json`, the same breakdown is in `tailordbMaintenance` (see [Deploy JSON result](
|
|
748
|
+
Deploy checks the run every few seconds, so a boundary between `waiting to start` and `running` can be off by that much. With `--json`, the same breakdown is in `tailordbMaintenance` (see [Deploy JSON result](../../cli/application.md#deploy)). Add `--verbose` to also print how long each phase took and every change in the job's status.
|
|
738
749
|
|
|
739
750
|
### Schema verification
|
|
740
751
|
|
|
@@ -976,8 +987,8 @@ When `migrate.ts` already exists (the usual case for breaking changes, where `mi
|
|
|
976
987
|
// migrations/0005/migrate.test.ts
|
|
977
988
|
import { createKyselyMock } from "@tailor-platform/sdk/vitest";
|
|
978
989
|
import { describe, expect, test } from "vitest";
|
|
979
|
-
import type { Database } from "./db";
|
|
980
990
|
import { main } from "./migrate";
|
|
991
|
+
import type { Database } from "./db";
|
|
981
992
|
|
|
982
993
|
describe("0005 add required email", () => {
|
|
983
994
|
test("backfills null emails", async () => {
|
|
@@ -992,11 +1003,11 @@ describe("0005 add required email", () => {
|
|
|
992
1003
|
});
|
|
993
1004
|
```
|
|
994
1005
|
|
|
995
|
-
Stage the rows each query returns with `mock.enqueueResult(...)` or `mock.setQueryResolver(...)` when the script reads before writing; call `main(trx, { env: { ... } })` when the script takes a `MigrationContext`. See [Kysely-layer mock](
|
|
1006
|
+
Stage the rows each query returns with `mock.enqueueResult(...)` or `mock.setQueryResolver(...)` when the script reads before writing; call `main(trx, { env: { ... } })` when the script takes a `MigrationContext`. See [Kysely-layer mock](../../testing.md#kysely-layer-mock-createkyselymock) for the full mock API.
|
|
996
1007
|
|
|
997
1008
|
These tests need no platform connection and no `tailor-runtime` environment — they run in a plain Vitest setup. Vitest's default `include` pattern already picks up `migrations/**/migrate.test.ts`; if your config narrows `include`, add the migrations directory. The test file is ignored by `tailor deploy` and never ships to the platform.
|
|
998
1009
|
|
|
999
|
-
A migration generated under [`defaultDateRepresentation`](
|
|
1010
|
+
A migration generated under [`defaultDateRepresentation`](../../configuration.md#application-settings) `"temporal"` or `"date"` records that in its `diff.json`, and deploy runs the script with Temporal or `Date` values for `t.date()`, `t.datetime()`, and `t.time()` fields that omit `as`. The scaffold calls `applyDateRepresentation` from `@tailor-platform/sdk/vitest` with the recorded value, or with `"string"` for a migration that records none, and imports `migrate.ts` after that call, so the test runs the script the same way deploy does even after the setting changes in `tailor.config.ts`. Keep that call when editing the test. The record pins the values at run time only: the types of `t` fields declared inside `migrate.ts` follow the current `tailor.d.ts` like everywhere else, so give such fields an explicit `as` to keep their types and values aligned after the setting changes. The helper selects the representation but does not provide `Temporal` itself, so run such a test in the [`tailor-runtime` Vitest environment](../../testing.md#temporal), which installs it on Node.js versions without native Temporal; this is the one case where a migration unit test needs that environment (a migration recorded under `"date"`, or without a record, needs no Temporal and runs in a plain Vitest setup).
|
|
1000
1011
|
|
|
1001
1012
|
### Executing migrate.ts against a local Postgres (PGlite)
|
|
1002
1013
|
|
|
@@ -1013,9 +1024,9 @@ The generated `db.pglite.ts` exports the `CREATE TABLE` script for the same sche
|
|
|
1013
1024
|
import { PGlite } from "@electric-sql/pglite";
|
|
1014
1025
|
import { createKyselyPGlite, type Unmigrated } from "@tailor-platform/sdk/vitest";
|
|
1015
1026
|
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
1016
|
-
import type { Database } from "./db";
|
|
1017
1027
|
import { pgliteSchema } from "./db.pglite";
|
|
1018
1028
|
import { main } from "./migrate";
|
|
1029
|
+
import type { Database } from "./db";
|
|
1019
1030
|
|
|
1020
1031
|
const pglite = new PGlite();
|
|
1021
1032
|
const db = createKyselyPGlite<Unmigrated<Database>>(pglite);
|
|
@@ -1053,14 +1064,14 @@ describe("0005 add required email", () => {
|
|
|
1053
1064
|
|
|
1054
1065
|
Pass nested field values as JavaScript objects or arrays of objects, without `JSON.stringify`.
|
|
1055
1066
|
|
|
1056
|
-
When the migration was generated with `kyselyTypePlugin({ temporal: true })`, its `db.ts` types date and datetime fields as `Temporal.PlainDate` and `Temporal.Instant`, and top-level time fields as `Temporal.PlainTime`; nested time fields remain strings. Deploy runs the script with those values. Create the instance with `createKyselyPGlite<Unmigrated<Database>>(pglite, { temporal: true })` so PGlite reads top-level temporal columns back the same way; the scaffold adds the option for such migrations. Run the test in the [`tailor-runtime` Vitest environment](
|
|
1067
|
+
When the migration was generated with `kyselyTypePlugin({ temporal: true })`, its `db.ts` types date and datetime fields as `Temporal.PlainDate` and `Temporal.Instant`, and top-level time fields as `Temporal.PlainTime`; nested time fields remain strings. Deploy runs the script with those values. Create the instance with `createKyselyPGlite<Unmigrated<Database>>(pglite, { temporal: true })` so PGlite reads top-level temporal columns back the same way; the scaffold adds the option for such migrations. Run the test in the [`tailor-runtime` Vitest environment](../../testing.md#temporal), which provides `Temporal`. A migration generated before `temporal` was enabled still runs with `Date` values on deploy, so its test leaves the option out. As in the Kysely mock scaffold, the test calls `applyDateRepresentation` with the migration's recorded `defaultDateRepresentation` (or `"string"`) so `t` date fields parse the way they do on deploy; a migration recorded under `"temporal"` likewise needs the `tailor-runtime` environment for `Temporal`.
|
|
1057
1068
|
Generated migration types use `Record<string, unknown>` for each nested object so scripts can
|
|
1058
1069
|
work with both old and new members during a migration; narrow member values before using them.
|
|
1059
1070
|
|
|
1060
1071
|
Two caveats keep this from replacing a scratch workspace:
|
|
1061
1072
|
|
|
1062
1073
|
- PGlite runs full PostgreSQL, while TailorDB supports [a subset of it](https://docs.tailor.tech/guides/function/accessing-tailordb#supported-sql-queries) — a statement that passes here can still be rejected on deploy.
|
|
1063
|
-
- `db.pglite.ts` mirrors the column shape, not the platform: hooks, validations, and permissions do not run, and the limits listed under [Real SQL execution with PGlite](
|
|
1074
|
+
- `db.pglite.ts` mirrors the column shape, not the platform: hooks, validations, and permissions do not run, and the limits listed under [Real SQL execution with PGlite](../../testing.md#real-sql-execution-with-pglite-mocktailordbwithpglite) apply.
|
|
1064
1075
|
|
|
1065
1076
|
### Testing a multi-step script
|
|
1066
1077
|
|
|
@@ -488,9 +488,9 @@ Pass the runtime key through the `executionPolicyKey` option on `job.start()`. F
|
|
|
488
488
|
|
|
489
489
|
```typescript
|
|
490
490
|
import { createWorkflowJob } from "@tailor-platform/sdk";
|
|
491
|
-
import { executionPolicies } from "./policies";
|
|
492
|
-
import { sendNotification } from "./jobs/send-notification";
|
|
493
491
|
import { fetchTenant } from "./jobs/fetch-tenant";
|
|
492
|
+
import { sendNotification } from "./jobs/send-notification";
|
|
493
|
+
import { executionPolicies } from "./policies";
|
|
494
494
|
|
|
495
495
|
export const mainJob = createWorkflowJob({
|
|
496
496
|
name: "main-job",
|
package/docs/testing.md
CHANGED
|
@@ -44,9 +44,9 @@ The Tailor Platform function runtime only provides Web Standard APIs. Node.js bu
|
|
|
44
44
|
### Setup
|
|
45
45
|
|
|
46
46
|
```typescript
|
|
47
|
+
import { tailorRuntime } from "@tailor-platform/sdk/vitest";
|
|
47
48
|
// vitest.config.ts
|
|
48
49
|
import { defineConfig } from "vitest/config";
|
|
49
|
-
import { tailorRuntime } from "@tailor-platform/sdk/vitest";
|
|
50
50
|
|
|
51
51
|
export default defineConfig({
|
|
52
52
|
plugins: [tailorRuntime()],
|
|
@@ -219,7 +219,7 @@ To hand-write DDL instead — for a table not in the schema, or to add a constra
|
|
|
219
219
|
|
|
220
220
|
- The PGlite instance is yours: the mock never closes it, so close it in `afterAll`. Reuse one instance across a suite — creating one per test is slow.
|
|
221
221
|
- Pass the same instance under several namespaces to drive them against one shared database. Two namespaces with a same-named table cannot share one instance, because the second script leaves the first table as it is.
|
|
222
|
-
- Seed through `getDB` itself. When a column type rejects a value that only the test must stage, use `createKyselyPGlite<Unmigrated<...>>(pglite)` instead — see [Testing Migrations Locally](./services/tailordb
|
|
222
|
+
- Seed through `getDB` itself. When a column type rejects a value that only the test must stage, use `createKyselyPGlite<Unmigrated<...>>(pglite)` instead — see [Testing Migrations Locally](./services/tailordb/migration.md#testing-migrations-locally). This only affects test setup; it cannot supply a `.serial()` value for an insert issued by the code under test.
|
|
223
223
|
- With `kyselyTypePlugin({ temporal: true })`, the generated `getDB` reads top-level date, datetime, and time columns back from PGlite as `Temporal.PlainDate`, `Temporal.Instant`, and `Temporal.PlainTime`, as it does on the platform. Values inside nested objects stay strings in PGlite (see above); on the platform, nested date/datetime fields are Temporal values while nested time fields remain strings.
|
|
224
224
|
- Transactions on a shared instance are serialized: while one is open, queries from other `getDB` instances wait. Do not use `test.concurrent` with a shared instance, and do not query the same instance through a second `getDB` from inside a transaction — that waits on itself.
|
|
225
225
|
- PGlite runs full PostgreSQL while TailorDB supports a subset of it, and TailorDB hooks, validations, and permissions do not run here — a test passing on PGlite can still behave differently on the platform. Keep [`mockTailordb`](#tailordb-mock) or [`createKyselyMock`](#kysely-layer-mock-createkyselymock) tests for query shape and error paths, and E2E tests for platform behavior.
|
|
@@ -448,7 +448,7 @@ export default defineConfig({
|
|
|
448
448
|
|
|
449
449
|
This makes `tailor.secretmanager.getSecret("vault", "key")` return the values defined in your config. You can still override with `mockSecretmanager({ secrets: ... })` in individual tests: a per-test overlay applies only within that test, and the config-loaded secrets remain available to every other test.
|
|
450
450
|
|
|
451
|
-
The same `config` option applies the config's [`defaultDateRepresentation`](./configuration.md#application-settings) to tests: with `"temporal"`, `t.date()`, `t.datetime()`, and `t.time()` fields that omit `as` parse to Temporal values in test files and in the modules they import, as they do in deployed functions, and with `"date"` they parse to `Date` values. The config is read once when Vitest starts, in the Vitest process itself rather than under the `tailor-runtime` restrictions, so restart Vitest after changing the setting. A config file that is missing or does not parse fails the run, as does a value other than `"temporal"` or `"date"`. A config that throws while loading, for example on an environment variable only the deploy environment sets, prints a warning and keeps string values for that run, as the secrets loader already tolerates; the warning notes that tests then disagree with `tailor.d.ts` if the config sets `"temporal"` or `"date"`. Tests that call `resolver.body()` directly bypass the SDK's input conversion and must pass values in the representation the body expects. To apply a representation in a test file regardless of the config, for example in a plain Vitest setup or for a [migration test](./services/tailordb
|
|
451
|
+
The same `config` option applies the config's [`defaultDateRepresentation`](./configuration.md#application-settings) to tests: with `"temporal"`, `t.date()`, `t.datetime()`, and `t.time()` fields that omit `as` parse to Temporal values in test files and in the modules they import, as they do in deployed functions, and with `"date"` they parse to `Date` values. The config is read once when Vitest starts, in the Vitest process itself rather than under the `tailor-runtime` restrictions, so restart Vitest after changing the setting. A config file that is missing or does not parse fails the run, as does a value other than `"temporal"` or `"date"`. A config that throws while loading, for example on an environment variable only the deploy environment sets, prints a warning and keeps string values for that run, as the secrets loader already tolerates; the warning notes that tests then disagree with `tailor.d.ts` if the config sets `"temporal"` or `"date"`. Tests that call `resolver.body()` directly bypass the SDK's input conversion and must pass values in the representation the body expects. To apply a representation in a test file regardless of the config, for example in a plain Vitest setup or for a [migration test](./services/tailordb/migration.md#testing-migrations-locally) pinned to the value it was generated under, call `applyDateRepresentation` from `@tailor-platform/sdk/vitest` with `"temporal"`, `"date"`, or `"string"` (the representation of a project that leaves the setting unset); it returns a function that restores the previous representation. The helper only selects the representation: parsing such a field needs `Temporal`, which the `tailor-runtime` environment installs when Node.js lacks it, so outside that environment the test must provide a Temporal implementation itself.
|
|
452
452
|
|
|
453
453
|
### Per-Project Configuration
|
|
454
454
|
|
|
@@ -590,9 +590,9 @@ describe("incrementUserAge resolver", () => {
|
|
|
590
590
|
Once the logic gets more involved, mocking raw SQL calls becomes brittle. Push database access behind a `DbOperations` interface and test the pure function that uses it:
|
|
591
591
|
|
|
592
592
|
```typescript
|
|
593
|
+
import type { Namespace } from "../generated/db";
|
|
593
594
|
// src/resolver/decrementUserAge.ts
|
|
594
595
|
import type { Selectable } from "@tailor-platform/sdk/kysely";
|
|
595
|
-
import type { Namespace } from "../generated/db";
|
|
596
596
|
|
|
597
597
|
export interface DbOperations {
|
|
598
598
|
transaction: <T>(fn: (ops: DbOperations) => Promise<T>) => Promise<T>;
|
|
@@ -690,7 +690,7 @@ describe("upsertUsers resolver", () => {
|
|
|
690
690
|
|
|
691
691
|
Reach for [`mockTailordb`](#mocking-the-tailordb-client) instead when you want to drive the raw query sequence at the `tailordb.Client` level rather than at the Kysely layer, or [`mockTailordbWithPGlite`](#real-sql-execution-with-pglite-mocktailordbwithpglite) to execute the queries against a real in-memory Postgres.
|
|
692
692
|
|
|
693
|
-
TailorDB migration scripts (`migrate.ts`) are unit-tested the same way: the generated `db.ts` exports the `Database` interface to type the mock, and `tailor tailordb migration script <N> --with-test` scaffolds a ready-to-fill test. To execute a migration script against real rows in an in-memory Postgres, use `createKyselyPGlite` with `@electric-sql/pglite` and the generated `db.pglite.ts` schema. See [Testing Migrations Locally](./services/tailordb
|
|
693
|
+
TailorDB migration scripts (`migrate.ts`) are unit-tested the same way: the generated `db.ts` exports the `Database` interface to type the mock, and `tailor tailordb migration script <N> --with-test` scaffolds a ready-to-fill test. To execute a migration script against real rows in an in-memory Postgres, use `createKyselyPGlite` with `@electric-sql/pglite` and the generated `db.pglite.ts` schema. See [Testing Migrations Locally](./services/tailordb/migration.md#testing-migrations-locally).
|
|
694
694
|
|
|
695
695
|
#### Resolvers that resume a workflow
|
|
696
696
|
|