@tailor-platform/sdk 2.32.0 → 2.33.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 (61) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/{application-D0xNJbag.mjs → application-B9_NHiUC.mjs} +40 -32
  3. package/dist/application-B9_NHiUC.mjs.map +1 -0
  4. package/dist/application-Bet1K5C4.mjs +1 -0
  5. package/dist/cli/commands/deploy/staticwebsite.d.mts +1 -0
  6. package/dist/cli/lib.mjs +1 -1
  7. package/dist/cli/main.mjs +44 -33
  8. package/dist/cli/main.mjs.map +1 -1
  9. package/dist/cli/services/resolver/service.d.mts +1 -0
  10. package/dist/cli/shared/client.d.mts +52 -1
  11. package/dist/cli/ts-hook.d.mts +0 -2
  12. package/dist/cli/ts-hook.mjs +0 -113
  13. package/dist/completion/zsh-worker.zsh +25 -5
  14. package/dist/configure/services/staticwebsite/index.d.mts +6 -1
  15. package/dist/{kysely-type-CBwsdx16.mjs → kysely-type-DxyIWXc6.mjs} +3 -3
  16. package/dist/kysely-type-DxyIWXc6.mjs.map +1 -0
  17. package/dist/manager-DTRhIupk.mjs +2 -0
  18. package/dist/manager-DTRhIupk.mjs.map +1 -0
  19. package/dist/plugin/builtin/enum-constants/index.mjs +2 -2
  20. package/dist/plugin/builtin/enum-constants/index.mjs.map +1 -1
  21. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  22. package/dist/plugin/index.mjs +1 -1
  23. package/dist/plugin/types.d.mts +3 -0
  24. package/dist/register-ts-hook-Dp9reSRG.mjs +1053 -0
  25. package/dist/register-ts-hook-Dp9reSRG.mjs.map +1 -0
  26. package/dist/repl-editor-DkrCZmmg.mjs +2 -0
  27. package/dist/repl-editor-DkrCZmmg.mjs.map +1 -0
  28. package/dist/service_pb-6EkMfdOb.mjs +2 -0
  29. package/dist/service_pb-6EkMfdOb.mjs.map +1 -0
  30. package/dist/service_pb-CsJKokDl.mjs +1 -0
  31. package/dist/tailor-proto/src/tailor/v1/metadata_pb.d.mts +5 -2
  32. package/dist/tailor-proto/src/tailor/v1/security_pb.d.mts +213 -0
  33. package/dist/tailor-proto/src/tailor/v1/security_resource_pb.d.mts +301 -0
  34. package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +200 -8
  35. package/dist/tailor-proto/src/tailor/v1/staticwebsite_pb.d.mts +201 -1
  36. package/dist/tailor-proto/src/tailor/v1/staticwebsite_resource_pb.d.mts +88 -1
  37. package/dist/types/staticwebsite.generated.d.mts +9 -1
  38. package/dist/vitest/index.d.mts +2 -2
  39. package/dist/vitest/index.mjs +1 -1
  40. package/dist/vitest/index.mjs.map +1 -1
  41. package/dist/vitest/pglite-kysely.d.mts +19 -0
  42. package/dist/{workspace_resource_pb-CaoM2npP.mjs → workspace_resource_pb-C4CkoYww.mjs} +2 -2
  43. package/dist/workspace_resource_pb-C4CkoYww.mjs.map +1 -0
  44. package/docs/plugin/custom.md +34 -0
  45. package/docs/plugin/index.md +1 -0
  46. package/docs/services/staticwebsite.md +31 -1
  47. package/docs/services/tailordb/migration.md +33 -10
  48. package/package.json +5 -5
  49. package/dist/application-C2bPmmbh.mjs +0 -1
  50. package/dist/application-D0xNJbag.mjs.map +0 -1
  51. package/dist/kysely-type-CBwsdx16.mjs.map +0 -1
  52. package/dist/manager-Bk-7vkwd.mjs +0 -2
  53. package/dist/manager-Bk-7vkwd.mjs.map +0 -1
  54. package/dist/register-ts-hook-_dHIREtU.mjs +0 -1050
  55. package/dist/register-ts-hook-_dHIREtU.mjs.map +0 -1
  56. package/dist/repl-editor-DGJBfIt1.mjs +0 -2
  57. package/dist/repl-editor-DGJBfIt1.mjs.map +0 -1
  58. package/dist/service_pb-Bbu2QSIr.mjs +0 -2
  59. package/dist/service_pb-Bbu2QSIr.mjs.map +0 -1
  60. package/dist/service_pb-urxAIYHT.mjs +0 -1
  61. package/dist/workspace_resource_pb-CaoM2npP.mjs.map +0 -1
@@ -90,6 +90,40 @@ interface Plugin<TableConfig = unknown, PluginConfig = unknown> {
90
90
  | `executors` | `PluginGeneratedExecutor[]` | Additional executors to generate |
91
91
  | `extends` | `{ fields?: Record<string, TailorAnyDBField> }` | Fields to add to the source table |
92
92
 
93
+ Resolvers returned by `onTableLoaded` or `onNamespaceLoaded` are loaded, bundled, and deployed to
94
+ the resolver namespace (a `resolver` key in your config) with the same name as the hook's TailorDB
95
+ namespace (a `db` key). These are separate namespaces that only share the name: with
96
+ `db: { main: ... }` and `resolver: { billing: ... }`, generated resolvers go to a `main` resolver
97
+ namespace, not `billing`. If you configure a resolver namespace with that name, generated resolvers
98
+ join its configured files and use its `defaultPermission`. Otherwise, a resolver namespace is created
99
+ automatically. Configure `defaultPermission` to restrict access; without it, generated resolvers are
100
+ accessible to anonymous callers, like regular resolvers with no permission declared. Generating into
101
+ an external resolver namespace or duplicating a resolver name within a namespace fails the build.
102
+
103
+ For example, a namespace hook can return a query using `t` field builders:
104
+
105
+ ```typescript
106
+ onNamespaceLoaded() {
107
+ return {
108
+ resolvers: [{
109
+ name: "greet",
110
+ operation: "query",
111
+ inputFields: { name: t.string() },
112
+ outputFields: { message: t.string() },
113
+ body: 'return { message: `Hello, ${context.input.name}!` };',
114
+ }],
115
+ };
116
+ }
117
+ ```
118
+
119
+ The `body` string contains JavaScript statements with access to `context.input`, `context.caller`,
120
+ `context.invoker`, and `context.env`. It can use `await` and platform globals, but cannot capture
121
+ plugin-local variables. Embed configuration values as literals with `JSON.stringify` when building
122
+ the string. Field validators must also be self-contained: they can use their arguments and standard
123
+ JavaScript globals, and references to other names are rejected during generation. Input validation and date conversion follow the same rules as regular
124
+ resolvers. Invalid field definitions or JavaScript syntax fail the build with the plugin and namespace
125
+ identified in the error.
126
+
93
127
  Tables in `tables` are validated as complete TailorDB table definitions before registration.
94
128
  Fields in `extends.fields` are validated as part of the resulting source table. You can return a
95
129
  table builder directly or a structural copy such as `{ ...db.table(...) }`; a copy is accepted as
@@ -102,6 +102,7 @@ export const plugins = definePlugins(
102
102
  Plugins can generate:
103
103
 
104
104
  - **Tables**: Additional TailorDB tables (e.g., `CustomerHistory`, `Deleted_Customer`)
105
+ - **Resolvers**: Queries and mutations
105
106
  - **Executors**: Event handlers triggered by record changes
106
107
  - **Field Extensions**: Additional fields added to the source table
107
108
  - **Output Files**: TypeScript code and other files via generation-time hooks
@@ -78,7 +78,37 @@ defineStaticWebSite("my-website", {
78
78
 
79
79
  After deploying, use `tailor staticwebsite domain get <domain>` to check domain status and retrieve the CNAME targets required for DNS configuration.
80
80
 
81
- A domain can be associated with only one workspace at a time. To set custom domains only in the workspace that owns the domain, see [Multi-Environment Configuration](../multi-environment.md#settings-that-belong-to-a-single-environment).
81
+ A domain can be associated with only one workspace at a time. To set custom domains only in the workspace that owns the domain, see [Multi-Environment Configuration](../multi-environment.md#settings-that-belong-to-a-single-environment). To hand a domain to another site or workspace without taking it offline, see [Moving a domain](#moving-a-domain).
82
+
83
+ ### Moving a domain
84
+
85
+ A domain already serving traffic can be moved to another static website — in this workspace or another one — without downtime, without reissuing its certificate and without changing DNS. The site that currently holds the domain offers it; the receiving site accepts by listing the domain normally.
86
+
87
+ On the side giving the domain up, replace the plain entry with the destination:
88
+
89
+ ```typescript
90
+ defineStaticWebSite("my-website", {
91
+ customDomains: [{ domain: "app.example.com", transferTo: { workspaceId: "..." } }],
92
+ });
93
+ ```
94
+
95
+ Deploying opens the offer. On the receiving side, list the domain as usual and deploy:
96
+
97
+ ```typescript
98
+ defineStaticWebSite("new-website", {
99
+ customDomains: ["app.example.com"],
100
+ });
101
+ ```
102
+
103
+ That deploy completes the move. The next plan on the giving side reports that the domain moved, and the entry should then be deleted — it describes a move that already happened, not ongoing state.
104
+
105
+ The two deploys must be separate runs, even when both sites live in the same workspace: the receiving side cannot accept an offer that the same run is still opening, and `tailor deploy` says so rather than failing partway.
106
+
107
+ These rules apply while an offer is open:
108
+
109
+ - An offer expires after 7 days. Deploying the entry again after that opens a fresh offer.
110
+ - Neither the domain nor the static website holding it can be removed. Change the entry back to a plain string and deploy to withdraw the offer first; `tailor deploy` and `tailor remove` both refuse before changing anything.
111
+ - An offer takes a moment to become ready. Until it is, the receiving side cannot accept it and its deploy is refused; the giving side's plan says so.
82
112
 
83
113
  ## Type-safe URL References
84
114
 
@@ -462,6 +462,25 @@ tailor deploy --migration-skip-steps main-db/backfillUser,main-db/backfillInvoic
462
462
 
463
463
  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.
464
464
 
465
+ #### Rolling back instead of resuming
466
+
467
+ By default a failure after another step has completed leaves the migration in progress and the namespace's tables in maintenance mode until you fix the step and deploy again. When you would rather restore the tables first and fix the script afterwards, export `onFailure` next to `steps`:
468
+
469
+ ```typescript
470
+ export const onFailure = "rollback";
471
+
472
+ export const steps = {
473
+ // ...
474
+ } satisfies MigrationSteps;
475
+ ```
476
+
477
+ - `onFailure` is `"resume"` (the default, described above) or `"rollback"`. Write it as `export const onFailure = "rollback"` with one of those two string literals; `tailor tailordb migration validate` and `tailor deploy` reject anything else. A script that runs `main` ignores it.
478
+ - With `"rollback"`, a failed step after another step has completed, and post-migration schema changes that fail after every step completed, roll back the pre-migration schema changes like a failing `main` script: tables that already existed are restored to their previous shape and GraphQL permissions, and tables the migration newly introduced are dropped. The in-progress record is cleared before the schema is rolled back, the tables leave maintenance mode, and the checkpoint is not advanced. `tailor deploy` then fails with the step's error under the code `MIGRATION_ROLLED_BACK`, and its suggestion says the next deploy runs every step again. A migration that an earlier deploy left in progress is rolled back the same way once its script exports `"rollback"`, including the tables and GraphQL permissions its earlier Pre-phase wrote.
479
+ - Steps that already completed stay committed. Their changes to existing fields remain in the database, and nothing is recorded as in progress, so the next deploy runs every step again from the beginning. Every step must be safe to run again, or a rolled-back run applies those changes twice. Do not rely on data a step wrote to a field or table that the rollback removed.
480
+ - If `tailor deploy` cannot tell whether a step ran, or the migration's workflow execution may still be running (for example the connection to it was lost), it does not roll anything back even with `"rollback"`: the migration stays in progress as described for a `main` script below. A migration an earlier deploy left in progress is rolled back only when its earlier run is confirmed to have ended, so a failure before that run is inspected, such as a schema error while applying the Pre-phase, keeps it in progress.
481
+ - If the in-progress record cannot be cleared, `tailor deploy` does not roll the schema back. It logs a warning and keeps the migration in progress with its tables in maintenance mode, because a record that outlives a rollback would make the next deploy resume against a schema its completed steps no longer match. Deploy again to resume it as described above, or roll it back without deploying with `tailor tailordb migration resolve --rollback`.
482
+ - If the schema or GraphQL permission of some tables cannot be rolled back, `tailor deploy` still fails with the step's original error. It also warns `Migration <namespace>/<N> was not rolled back completely` and lists the tables that need manual repair, and separately the tables whose GraphQL permission could not be set back. Check them in the workspace and repair them by hand; deploying again does not release them.
483
+
465
484
  ### Settling a migration that stays in progress
466
485
 
467
486
  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:
@@ -905,10 +924,10 @@ Use `tailor tailordb migration rebaseline` rather than combining `migration gene
905
924
 
906
925
  ## Failure Recovery
907
926
 
908
- A script that exports `steps` follows different rules once one of its steps has completed: the migration stays in progress and the next deploy resumes it. See [Splitting a migration into steps](#splitting-a-migration-into-steps). Otherwise, if the pre-migration phase or `migrate.ts` fails:
927
+ A script that exports `steps` follows different rules once one of its steps has completed: the migration stays in progress and the next deploy resumes it, unless the script exports `onFailure = "rollback"`. See [Splitting a migration into steps](#splitting-a-migration-into-steps) and [Rolling back instead of resuming](#rolling-back-instead-of-resuming). Otherwise, if the pre-migration phase or `migrate.ts` fails:
909
928
 
910
929
  - **The transaction rolls back** for that migration's script. Database changes the script made are undone.
911
- - **The pre-migration schema changes are rolled back** to the prior checkpoint: tables that already existed are restored to their previous shape, and tables the migration newly introduced are dropped. The workspace is left at its prior checkpoint and prior schema — not half-applied.
930
+ - **The pre-migration schema changes are rolled back** to the prior checkpoint: tables that already existed are restored to their previous shape and GraphQL permissions, and tables the migration newly introduced are dropped. The workspace is left at its prior checkpoint and prior schema — not half-applied.
912
931
  - The whole `apply` aborts and the checkpoint label is not bumped. Subsequent migrations in the same run do not execute.
913
932
 
914
933
  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.
@@ -1037,10 +1056,12 @@ npm install -D @electric-sql/pglite
1037
1056
 
1038
1057
  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>`.
1039
1058
 
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 retargets the relation 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
+
1040
1061
  ```typescript
1041
1062
  // migrations/0005/migrate.pglite.test.ts
1042
1063
  import { PGlite } from "@electric-sql/pglite";
1043
- import { createKyselyPGlite, type Unmigrated } from "@tailor-platform/sdk/vitest";
1064
+ import { createKyselyPGlite, prepareFixtures, type Unmigrated } from "@tailor-platform/sdk/vitest";
1044
1065
  import { afterAll, beforeAll, describe, expect, test } from "vitest";
1045
1066
  import { pgliteSchema } from "./db.pglite";
1046
1067
  import { main } from "./migrate";
@@ -1061,13 +1082,15 @@ afterAll(async () => {
1061
1082
  describe("0005 add required email", () => {
1062
1083
  test("backfills null emails and keeps existing ones", async () => {
1063
1084
  const now = new Date();
1064
- await db
1065
- .insertInto("User")
1066
- .values([
1067
- { name: "a", email: null, createdAt: now, updatedAt: now },
1068
- { name: "b", email: "b@example.com", createdAt: now, updatedAt: now },
1069
- ])
1070
- .execute();
1085
+ await prepareFixtures(pglite, async () => {
1086
+ await db
1087
+ .insertInto("User")
1088
+ .values([
1089
+ { name: "a", email: null, createdAt: now, updatedAt: now },
1090
+ { name: "b", email: "b@example.com", createdAt: now, updatedAt: now },
1091
+ ])
1092
+ .execute();
1093
+ });
1071
1094
 
1072
1095
  await db.transaction().execute((trx) => main(trx));
1073
1096
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailor-platform/sdk",
3
- "version": "2.32.0",
3
+ "version": "2.33.0",
4
4
  "description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -160,10 +160,10 @@
160
160
  "@bufbuild/protovalidate": "1.3.0",
161
161
  "@connectrpc/connect": "2.2.0",
162
162
  "@connectrpc/connect-node": "2.2.0",
163
- "@inquirer/core": "12.0.4",
164
- "@inquirer/prompts": "8.7.3",
163
+ "@inquirer/core": "12.0.5",
164
+ "@inquirer/prompts": "8.7.4",
165
165
  "@jridgewell/trace-mapping": "0.3.31",
166
- "@napi-rs/keyring": "2.1.1",
166
+ "@napi-rs/keyring": "2.1.2",
167
167
  "@opentelemetry/api": "1.9.1",
168
168
  "@opentelemetry/context-async-hooks": "2.12.0",
169
169
  "@opentelemetry/exporter-trace-otlp-proto": "0.223.0",
@@ -196,7 +196,7 @@
196
196
  "p-limit": "7.3.3",
197
197
  "pathe": "2.0.3",
198
198
  "pgsql-ast-parser": "12.0.2",
199
- "pkg-types": "2.3.3",
199
+ "pkg-types": "2.4.0",
200
200
  "rolldown": "1.2.14",
201
201
  "semver": "7.8.5",
202
202
  "sql-highlight": "6.1.0",
@@ -1 +0,0 @@
1
- export{t as defineApplication,n as generatePluginFilesIfNeeded}from"./application-D0xNJbag.mjs";