@tailor-platform/sdk 2.15.0 → 2.17.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.
Files changed (102) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/dist/{application-BpoMu4Af.mjs → application-ChuNPwnl.mjs} +28 -28
  3. package/dist/application-ChuNPwnl.mjs.map +1 -0
  4. package/dist/application-Oh5dMmb5.mjs +1 -0
  5. package/dist/cli/commands/executor/jobs.d.mts +1 -0
  6. package/dist/cli/commands/generate/seed/bundler.d.mts +18 -0
  7. package/dist/cli/commands/tailordb/migrate/config.d.mts +1 -0
  8. package/dist/cli/commands/tailordb/migrate/snapshot-files.d.mts +5 -1
  9. package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +2 -2
  10. package/dist/cli/commands/workflow/waiter.d.mts +1 -0
  11. package/dist/cli/commands/workspace/create.d.mts +16 -1
  12. package/dist/cli/commands/workspace/expiry.d.mts +5 -0
  13. package/dist/cli/commands/workspace/get.d.mts +6 -1
  14. package/dist/cli/commands/workspace/list.d.mts +1 -0
  15. package/dist/cli/lib.d.mts +4 -3
  16. package/dist/cli/lib.mjs +1 -1
  17. package/dist/cli/lib.mjs.map +1 -1
  18. package/dist/cli/main.mjs +57 -59
  19. package/dist/cli/main.mjs.map +1 -1
  20. package/dist/cli/shared/command.d.mts +1 -1
  21. package/dist/cli/shared/error-json.d.mts +21 -1
  22. package/dist/cli/shared/errors.d.mts +1 -0
  23. package/dist/cli/shared/github-actions.d.mts +13 -0
  24. package/dist/cli/shared/logger.d.mts +18 -0
  25. package/dist/completion/zsh-worker.zsh +92 -4
  26. package/dist/configure/index.mjs +1 -1
  27. package/dist/configure/index.mjs.map +1 -1
  28. package/dist/crashreport-CgymxQDu.mjs +1 -0
  29. package/dist/crashreport-Cyuz1qiu.mjs +42 -0
  30. package/dist/crashreport-Cyuz1qiu.mjs.map +1 -0
  31. package/dist/errors-BjJnpXkK.mjs +7 -0
  32. package/dist/errors-BjJnpXkK.mjs.map +1 -0
  33. package/dist/field-parse-CzlKC4b7.mjs +2 -0
  34. package/dist/field-parse-CzlKC4b7.mjs.map +1 -0
  35. package/dist/guards-ForsrxnH.mjs +2 -0
  36. package/dist/guards-ForsrxnH.mjs.map +1 -0
  37. package/dist/kysely-type-B_oA8D1k.mjs +43 -0
  38. package/dist/kysely-type-B_oA8D1k.mjs.map +1 -0
  39. package/dist/{logger-CCjs1DuH.mjs → logger-72hM4JWZ.mjs} +4 -4
  40. package/dist/logger-72hM4JWZ.mjs.map +1 -0
  41. package/dist/manager-C26Gi1bX.mjs +2 -0
  42. package/dist/manager-C26Gi1bX.mjs.map +1 -0
  43. package/dist/node-builtins-DYfhPBnz.mjs +2 -0
  44. package/dist/node-builtins-DYfhPBnz.mjs.map +1 -0
  45. package/dist/plugin/builtin/kysely-type/index.d.mts +2 -0
  46. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  47. package/dist/plugin/builtin/seed/index.mjs +1 -1
  48. package/dist/plugin/builtin/seed/seed-type-processor.d.mts +17 -0
  49. package/dist/plugin/get-generated-table.d.mts +13 -0
  50. package/dist/plugin/index.d.mts +2 -2
  51. package/dist/plugin/index.mjs +1 -1
  52. package/dist/plugin/index.mjs.map +1 -1
  53. package/dist/register-ts-hook-Dn-XlVSD.mjs +917 -0
  54. package/dist/register-ts-hook-Dn-XlVSD.mjs.map +1 -0
  55. package/dist/schema-DRyQEabV.mjs +2 -0
  56. package/dist/schema-DRyQEabV.mjs.map +1 -0
  57. package/dist/{seed-CCc9Xk66.mjs → seed-C3P_T_Eh.mjs} +28 -9
  58. package/dist/seed-C3P_T_Eh.mjs.map +1 -0
  59. package/dist/service-B3OiWCYg.mjs +1 -0
  60. package/dist/service-B4Gh_bbL.mjs +2 -0
  61. package/dist/service-B4Gh_bbL.mjs.map +1 -0
  62. package/dist/service-eM7Fd8zS.mjs +7 -0
  63. package/dist/service-eM7Fd8zS.mjs.map +1 -0
  64. package/dist/tailordb-ddl-Fgm2cNvT.mjs +7 -0
  65. package/dist/tailordb-ddl-Fgm2cNvT.mjs.map +1 -0
  66. package/dist/utils/test/index.d.mts +7 -5
  67. package/dist/utils/test/index.mjs +1 -1
  68. package/dist/utils/test/index.mjs.map +1 -1
  69. package/dist/vitest/index.mjs +1 -1
  70. package/dist/vitest/index.mjs.map +1 -1
  71. package/dist/vitest/mocks/tailordb-pglite.d.mts +5 -4
  72. package/docs/cli/tailordb.md +9 -9
  73. package/docs/cli/workspace.md +133 -20
  74. package/docs/cli-reference.md +101 -28
  75. package/docs/plugin/custom.md +36 -4
  76. package/docs/services/tailordb-migration.md +31 -28
  77. package/docs/testing.md +23 -21
  78. package/package.json +11 -10
  79. package/dist/application-BpoMu4Af.mjs.map +0 -1
  80. package/dist/application-fIhVKX78.mjs +0 -1
  81. package/dist/crashreport-BN28xp5B.mjs +0 -42
  82. package/dist/crashreport-BN28xp5B.mjs.map +0 -1
  83. package/dist/crashreport-By23O2k-.mjs +0 -1
  84. package/dist/errors-BlX4gUw5.mjs +0 -4
  85. package/dist/errors-BlX4gUw5.mjs.map +0 -1
  86. package/dist/field-column-type-QMtF6lUp.mjs +0 -2
  87. package/dist/field-column-type-QMtF6lUp.mjs.map +0 -1
  88. package/dist/kysely-type-B-BOlXH7.mjs +0 -43
  89. package/dist/kysely-type-B-BOlXH7.mjs.map +0 -1
  90. package/dist/logger-CCjs1DuH.mjs.map +0 -1
  91. package/dist/node-builtins-TQHhwNzG.mjs +0 -2
  92. package/dist/node-builtins-TQHhwNzG.mjs.map +0 -1
  93. package/dist/register-ts-hook-CAUcxCt4.mjs +0 -711
  94. package/dist/register-ts-hook-CAUcxCt4.mjs.map +0 -1
  95. package/dist/schema-BTioi2dP.mjs +0 -2
  96. package/dist/schema-BTioi2dP.mjs.map +0 -1
  97. package/dist/seed-CCc9Xk66.mjs.map +0 -1
  98. package/dist/service-7SCy2bhm.mjs +0 -7
  99. package/dist/service-7SCy2bhm.mjs.map +0 -1
  100. package/dist/service-CWFQ8EVo.mjs +0 -1
  101. package/dist/service-CqbQXFDp.mjs +0 -2
  102. package/dist/service-CqbQXFDp.mjs.map +0 -1
@@ -24,7 +24,8 @@ migrations/
24
24
  ├── 0001/ # First change
25
25
  │ ├── diff.json # Field-level diff from 0000
26
26
  │ ├── migrate.ts # Data migration script (auto-generated for breaking changes; can be added manually via `migration script`)
27
- │ └── db.ts # Kysely types for the script (pre-migration shape)
27
+ │ ├── db.ts # Kysely types for the script (pre-migration shape)
28
+ │ └── db.pglite.ts # CREATE TABLE script of that shape, for PGlite tests
28
29
  ├── 0002/
29
30
  │ └── diff.json # No script — non-breaking changes only
30
31
  └── ...
@@ -129,7 +130,7 @@ No `migrate.ts` is generated automatically because the schema change itself is n
129
130
  tailor tailordb migration script 0002
130
131
  ```
131
132
 
132
- This writes `migrations/0002/migrate.ts` and `migrations/0002/db.ts` next to the existing `diff.json` (add `--with-test` to also scaffold a `migrate.test.ts` — see [Testing Migrations Locally](#testing-migrations-locally)). The removed field stays readable inside `migrate.ts` because the pre-migration phase keeps it on the table until the script finishes (see [Per-migration phases](#per-migration-phases)). The next `tailor deploy` runs the script automatically — `migrate.ts` is executed whenever the file exists on disk, regardless of whether the diff itself required it.
133
+ This writes `migrations/0002/migrate.ts`, `migrations/0002/db.ts`, and `migrations/0002/db.pglite.ts` next to the existing `diff.json` (add `--with-test` to also scaffold the tests — see [Testing Migrations Locally](#testing-migrations-locally)). The removed field stays readable inside `migrate.ts` because the pre-migration phase keeps it on the table until the script finishes (see [Per-migration phases](#per-migration-phases)). The next `tailor deploy` runs the script automatically — `migrate.ts` is executed whenever the file exists on disk, regardless of whether the diff itself required it.
133
134
 
134
135
  If the data loss is intentional and no script is needed, record that decision the same way as for breaking changes (see [Breaking changes without a script](#breaking-changes-without-a-script)):
135
136
 
@@ -267,13 +268,15 @@ export default defineConfig({
267
268
 
268
269
  ## Generated Files
269
270
 
270
- | File | When generated | Description |
271
- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
272
- | `0000/schema.json` | First `migration generate` | Full snapshot of all tables in the namespace. |
273
- | `XXXX/diff.json` | Every subsequent migration | Field-level diff against the previous snapshot. |
274
- | `XXXX/migrate.ts` | Auto-generated for breaking changes and `--data-only` migrations; added manually via `tailordb migration script` for warning-tier changes | Data transformation script. The `main` export receives a Kysely `Transaction`. |
275
- | `XXXX/db.ts` | Generated once when `migrate.ts` is created | Kysely types reflecting the schema **before** this migration. Exports `Database`, `Transaction`, and `MigrationContext`. |
276
- | `XXXX/migrate.test.ts` | Added via `tailordb migration script --with-test` | Unit-test scaffold for `migrate.ts` (see [Testing Migrations Locally](#testing-migrations-locally)). Never deployed. |
271
+ | File | When generated | Description |
272
+ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
273
+ | `0000/schema.json` | First `migration generate` | Full snapshot of all tables in the namespace. |
274
+ | `XXXX/diff.json` | Every subsequent migration | Field-level diff against the previous snapshot. |
275
+ | `XXXX/migrate.ts` | Auto-generated for breaking changes and `--data-only` migrations; added manually via `tailordb migration script` for warning-tier changes | Data transformation script. The `main` export receives a Kysely `Transaction`. |
276
+ | `XXXX/db.ts` | Generated once when `migrate.ts` is created | Kysely types reflecting the schema **before** this migration. Exports `Database`, `Transaction`, and `MigrationContext`. |
277
+ | `XXXX/db.pglite.ts` | Generated with `db.ts` | `CREATE TABLE` script of the same schema, for running `migrate.ts` on PGlite. Never deployed. |
278
+ | `XXXX/migrate.test.ts` | Added via `tailordb migration script --with-test` | Unit-test scaffold for `migrate.ts` (see [Testing Migrations Locally](#testing-migrations-locally)). Never deployed. |
279
+ | `XXXX/migrate.pglite.test.ts` | Added via `tailordb migration script --with-test` when `@electric-sql/pglite` is installed | PGlite test scaffold for `migrate.ts`. Never deployed. |
277
280
 
278
281
  `db.ts` reflects the pre-migration schema because the script runs after the pre-migration phase has temporarily relaxed breaking constraints (e.g., a new `required` field is added as `optional` first), so the data being read still matches the previous shape.
279
282
 
@@ -386,7 +389,7 @@ The `env` values are injected at bundle time (the same mechanism as resolvers/ex
386
389
  | 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 |
387
390
  | 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. |
388
391
  | Change array → single value | - | - | **Not supported** — see [Converting a field type](#converting-a-field-type) |
389
- | Change single value → array | - | - | **Not supported** — see [Converting a field type](#converting-a-field-type) |
392
+ | Change single value → array | Yes | Yes | Two migrations, generated together after you confirm — see [Converting a field type](#converting-a-field-type). Each stored value becomes a one-element array, so the conversion needs no edits. |
390
393
 
391
394
  ### Field type changes
392
395
 
@@ -460,7 +463,7 @@ The generated `never` annotation intentionally causes a TypeScript error until y
460
463
 
461
464
  ### Converting a field type
462
465
 
463
- A field type change outside the verified in-place pairs — `string` → `integer`, for example — cannot be applied in one step, because the field would have to hold both shapes at once. `migration generate` offers to carry the values through a temporary field instead:
466
+ A field type change outside the verified in-place pairs — `string` → `integer`, for example — cannot be applied in one step, because the field would have to hold both shapes at once. The same holds when a single value becomes an array (`string` → `string[]`): the stored values must be rewritten as arrays before the field can take the new shape. `migration generate` offers to carry the values through a temporary field instead:
464
467
 
465
468
  ```
466
469
  User.price changes from string to integer, which cannot be applied in one step.
@@ -469,9 +472,11 @@ User.price changes from string to integer, which cannot be applied in one step.
469
472
 
470
473
  Confirming writes two migrations:
471
474
 
472
- 1. **The conversion.** Adds a temporary field (`priceMigrate`), converts each stored value into it, and clears and removes the original field. Edit the conversion expression before deploying: the generated `never` annotation fails your typecheck, and `tailordb migration validate` rejects the migration while the review marker is still there.
475
+ 1. **The conversion.** Adds a temporary field (`priceMigrate`), converts each stored value into it, and clears and removes the original field. Edit the conversion expression before deploying: the generated `never` annotation fails your typecheck, and `tailordb migration validate` rejects the migration while the review marker is still there. When only the array-ness changes (`string` → `string[]`), the conversion stores each value as a one-element array and carries no review marker; when the element type changes as well (`integer` → `string[]`), you convert the element and the script wraps it.
473
476
  2. **The rename.** Renames the temporary field back to `price`. Its copy script is complete, but this migration also carries every other schema change the same run picked up, so review it as you would any generated migration.
474
477
 
478
+ If converting to an array also reduces a decimal field's `scale` or removes enum values, the conversion keeps the review marker. Edit the element conversion to satisfy the target field before deploying.
479
+
475
480
  `tailor deploy` applies both. Because the conversion only touches rows whose original value is still set, a re-run resumes where it stopped rather than converting a row twice.
476
481
 
477
482
  The original field is removed in the first migration rather than the second, because the rename needs its name free. Your script can still read it while the conversion runs.
@@ -490,7 +495,7 @@ Without the flag the command fails rather than converting anything, so a scripte
490
495
 
491
496
  Some changes are still rejected and need a temporary field you add yourself — add the new field, write a script that fills it and clears the old one, then remove the old field and rename the temporary one in a later migration:
492
497
 
493
- - Array-to-scalar and scalar-to-array, since collapsing an array has no answer the generated script could choose for you.
498
+ - A field that is already an array: collapsing it into a single value has no answer the generated script could choose for you, and changing its element type (`string[]` → `integer[]`) is not generated either.
494
499
  - A field that is unique, or that an index, relationship, permission, or table-level script names. Those keep pointing at the original name, which the conversion removes.
495
500
 
496
501
  ## Testing Pending Migrations
@@ -809,7 +814,7 @@ Scaffold a ready-to-fill test next to the script with:
809
814
  tailor tailordb migration script 0005 --with-test
810
815
  ```
811
816
 
812
- When `migrate.ts` already exists (the usual case for breaking changes, where `migration generate` creates it), the command adds only `migrate.test.ts`. Or write the test by hand:
817
+ When `migrate.ts` already exists (the usual case for breaking changes, where `migration generate` creates it), the command adds only the tests that do not exist yet, plus a missing `db.pglite.ts`. Or write the test by hand:
813
818
 
814
819
  ```typescript
815
820
  // migrations/0005/migrate.test.ts
@@ -843,30 +848,24 @@ A statement-level test verifies what the script issues, not what it does to data
843
848
  npm install -D @electric-sql/pglite
844
849
  ```
845
850
 
846
- Create the tables the script touches (matching the shape in the generated `db.ts`), stage rows, then run the script in a transaction. 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>`.
851
+ 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>`.
847
852
 
848
853
  ```typescript
849
854
  // migrations/0005/migrate.pglite.test.ts
850
855
  import { PGlite } from "@electric-sql/pglite";
851
- import { sql } from "@tailor-platform/sdk/kysely";
852
856
  import { createKyselyPGlite, type Unmigrated } from "@tailor-platform/sdk/vitest";
853
857
  import { afterAll, beforeAll, describe, expect, test } from "vitest";
854
858
  import type { Database } from "./db";
859
+ import { pgliteSchema } from "./db.pglite";
855
860
  import { main } from "./migrate";
856
861
 
857
- const db = createKyselyPGlite<Unmigrated<Database>>(new PGlite());
862
+ const pglite = new PGlite();
863
+ const db = createKyselyPGlite<Unmigrated<Database>>(pglite);
858
864
 
865
+ // PGlite loads Postgres on first use, which can take longer than the default hook timeout.
859
866
  beforeAll(async () => {
860
- await sql`
861
- CREATE TABLE "User" (
862
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
863
- "name" text NOT NULL,
864
- "email" text,
865
- "createdAt" timestamptz NOT NULL,
866
- "updatedAt" timestamptz NOT NULL
867
- )
868
- `.execute(db);
869
- });
867
+ await pglite.exec(pgliteSchema.tailordb);
868
+ }, 60_000);
870
869
 
871
870
  afterAll(async () => {
872
871
  await db.destroy();
@@ -894,10 +893,14 @@ describe("0005 add required email", () => {
894
893
  });
895
894
  ```
896
895
 
896
+ Pass nested field values as JavaScript objects or arrays of objects, without `JSON.stringify`.
897
+ Generated migration types use `Record<string, unknown>` for each nested object so scripts can
898
+ work with both old and new members during a migration; narrow member values before using them.
899
+
897
900
  Two caveats keep this from replacing a scratch workspace:
898
901
 
899
902
  - 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.
900
- - The `CREATE TABLE` statements are yours, so they can drift from the schema the platform actually has.
903
+ - `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.
901
904
 
902
905
  ### Beyond unit tests
903
906
 
package/docs/testing.md CHANGED
@@ -149,26 +149,30 @@ Pass `{ onUnhandled: "error" }` to make an unmatched query fail instead of retur
149
149
 
150
150
  Instead of staging responses, back TailorDB with [`@electric-sql/pglite`](https://pglite.dev/) — an in-memory PostgreSQL (install it as a devDependency) — so the queries a resolver, executor, or workflow job issues through `getDB()` execute against real data. `getDB(namespace)` needs no test-side swap: acquire the mock, and each namespace you list resolves to its PGlite instance.
151
151
 
152
- Create the tables the test touches with `CREATE TABLE` statements matching the generated Kysely types — `text` for string and enum fields, `timestamptz` for date/datetime, `jsonb` for nested objects. The schema only has to match what your code reads and writes, not TailorDB's storage; relations are not enforced.
152
+ Let `kyselyTypePlugin` generate the `CREATE TABLE` script for you: set `pgliteSchemaPath` next to `distPath`, and `tailor generate` writes a module exporting one script per namespace, derived from the same table definitions as the Kysely types.
153
+
154
+ ```typescript
155
+ // tailor.config.ts
156
+ kyselyTypePlugin({
157
+ distPath: "./generated/db.ts",
158
+ pgliteSchemaPath: "./generated/db.pglite.ts",
159
+ });
160
+ ```
161
+
162
+ Run the namespace's script once per PGlite instance. Every statement is `IF NOT EXISTS`, so applying it again to an instance that already has the tables is harmless.
153
163
 
154
164
  ```typescript
155
165
  import { PGlite } from "@electric-sql/pglite";
156
166
  import { mockTailordbWithPGlite } from "@tailor-platform/sdk/vitest";
157
167
  import { afterAll, beforeAll, expect, test } from "vitest";
158
168
  import { getDB } from "../generated/db";
169
+ import { pgliteSchema } from "../generated/db.pglite";
159
170
  import resolver from "./upsertUsers";
160
171
 
161
172
  const pglite = new PGlite();
162
173
 
163
174
  beforeAll(async () => {
164
- await pglite.exec(`
165
- CREATE TABLE "User" (
166
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
167
- "name" text NOT NULL,
168
- "email" text NOT NULL,
169
- "age" integer NOT NULL
170
- );
171
- `);
175
+ await pglite.exec(pgliteSchema["main-db"]);
172
176
  });
173
177
 
174
178
  afterAll(async () => {
@@ -201,21 +205,19 @@ test("upserts against real rows", async () => {
201
205
  });
202
206
  ```
203
207
 
204
- A `.serial()` field is omitted from generated `getDB()` inserts, so its PGlite column must generate a value. Use an identity for an integer serial. For a formatted string serial, create a sequence and reproduce the format in its `DEFAULT` expression:
208
+ 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 not enforced.
205
209
 
206
- ```sql
207
- CREATE SEQUENCE "invoiceNumberSequence" START WITH 1000;
208
- CREATE TABLE "Invoice" (
209
- "sequentialId" integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
210
- "invoiceNumber" text NOT NULL
211
- DEFAULT ('INV-' || lpad(nextval('"invoiceNumberSequence"')::text, 5, '0'))
212
- );
213
- ```
210
+ What the script cannot reproduce:
211
+
212
+ - 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.
213
+ - 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.
214
+ - A datetime inside a nested object reads back as a string from `jsonb`, not a `Date`.
215
+ - 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.
214
216
 
215
- PGlite does not apply the TailorDB `.serial()` configuration itself. Match the `start`, `format`, and any limit that the behavior under test relies on.
217
+ 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.
216
218
 
217
219
  - 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.
218
- - Pass the same instance under several namespaces to drive them against one shared database.
220
+ - 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.
219
221
  - 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.
220
222
  - 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.
221
223
  - 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.
@@ -666,7 +668,7 @@ describe("upsertUsers resolver", () => {
666
668
 
667
669
  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.
668
670
 
669
- 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`. See [Testing Migrations Locally](./services/tailordb-migration.md#testing-migrations-locally).
671
+ 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).
670
672
 
671
673
  #### Resolvers that resume a workflow
672
674
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailor-platform/sdk",
3
- "version": "2.15.0",
3
+ "version": "2.17.0",
4
4
  "description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -153,10 +153,10 @@
153
153
  "@badgateway/oauth2-client": "3.3.1",
154
154
  "@bufbuild/protobuf": "2.14.1",
155
155
  "@bufbuild/protovalidate": "1.2.0",
156
- "@connectrpc/connect": "2.1.2",
157
- "@connectrpc/connect-node": "2.1.2",
158
- "@inquirer/core": "12.0.2",
159
- "@inquirer/prompts": "8.7.1",
156
+ "@connectrpc/connect": "2.2.0",
157
+ "@connectrpc/connect-node": "2.2.0",
158
+ "@inquirer/core": "12.0.3",
159
+ "@inquirer/prompts": "8.7.2",
160
160
  "@jridgewell/trace-mapping": "0.3.31",
161
161
  "@napi-rs/keyring": "2.0.0",
162
162
  "@opentelemetry/api": "1.9.1",
@@ -170,7 +170,7 @@
170
170
  "@secretlint/secretlint-rule-preset-recommend": "13.0.5",
171
171
  "@standard-schema/spec": "1.1.0",
172
172
  "@tailor-platform/function-kysely-tailordb": "0.1.3",
173
- "@toiroakr/lines-db": "0.12.6",
173
+ "@toiroakr/lines-db": "0.12.7",
174
174
  "@toiroakr/read-multiline": "0.4.1",
175
175
  "@urql/core": "6.0.3",
176
176
  "amaro": "1.1.11",
@@ -190,7 +190,7 @@
190
190
  "p-limit": "7.3.2",
191
191
  "pathe": "2.0.3",
192
192
  "pgsql-ast-parser": "12.0.2",
193
- "pkg-types": "2.3.2",
193
+ "pkg-types": "2.3.3",
194
194
  "rolldown": "1.2.7",
195
195
  "semver": "7.8.5",
196
196
  "sql-highlight": "6.1.0",
@@ -201,15 +201,16 @@
201
201
  "zod": "4.5.4"
202
202
  },
203
203
  "devDependencies": {
204
+ "@electric-sql/pglite": "0.5.8",
204
205
  "@opentelemetry/sdk-trace-base": "2.11.0",
205
206
  "@tailor-platform/shared": "^0.0.0",
206
207
  "@tailor-platform/tailor-proto": "^0.0.1",
207
208
  "@types/mime-types": "3.0.1",
208
- "@types/node": "24.13.3",
209
+ "@types/node": "24.13.4",
209
210
  "@types/semver": "7.8.0",
210
211
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
211
212
  "@vitest/coverage-v8": "5.0.0",
212
- "eslint-plugin-zod": "4.12.0",
213
+ "eslint-plugin-zod": "4.12.1",
213
214
  "oxfmt": "0.66.0",
214
215
  "oxlint": "1.81.0",
215
216
  "oxlint-tsgolint": "7.0.2001",
@@ -217,7 +218,7 @@
217
218
  "tsdown": "0.23.0",
218
219
  "typescript": "6.0.3",
219
220
  "vitest": "5.0.0",
220
- "zinfer": "0.4.5"
221
+ "zinfer": "0.4.6"
221
222
  },
222
223
  "peerDependencies": {
223
224
  "@electric-sql/pglite": ">=0.2.0",