@tailor-platform/sdk 2.10.0 → 2.12.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 (52) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/application-BFTkaW6X.mjs +1 -0
  3. package/dist/{application-D2E3FDfF.mjs → application-D3HlhR3i.mjs} +32 -31
  4. package/dist/application-D3HlhR3i.mjs.map +1 -0
  5. package/dist/cli/commands/generate/seed/bundler.d.mts +2 -2
  6. package/dist/cli/commands/tailordb/migrate/bundler.d.mts +1 -1
  7. package/dist/cli/commands/workspace/user/remove.d.mts +1 -1
  8. package/dist/cli/commands/workspace/user/update.d.mts +1 -1
  9. package/dist/cli/lib.mjs +1 -1
  10. package/dist/cli/main.mjs +41 -41
  11. package/dist/cli/main.mjs.map +1 -1
  12. package/dist/cli/shared/script-executor.d.mts +2 -2
  13. package/dist/completion/zsh-worker.zsh +18 -2
  14. package/dist/idp-G_ojPBB5.mjs.map +1 -1
  15. package/dist/kysely-type-B-BOlXH7.mjs +43 -0
  16. package/dist/kysely-type-B-BOlXH7.mjs.map +1 -0
  17. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  18. package/dist/{register-ts-hook-D6aNriu3.mjs → register-ts-hook-rBH346V_.mjs} +63 -63
  19. package/dist/register-ts-hook-rBH346V_.mjs.map +1 -0
  20. package/dist/seed-CMkupmX8.mjs.map +1 -1
  21. package/dist/service-BzsSm2f6.mjs +7 -0
  22. package/dist/service-BzsSm2f6.mjs.map +1 -0
  23. package/dist/service-CmwD9bwv.mjs +1 -0
  24. package/dist/service_pb-CFVlGYdr.mjs +1 -0
  25. package/dist/{service_pb-BNxUPfP3.mjs → service_pb-CxHztLk1.mjs} +2 -2
  26. package/dist/{service_pb-BNxUPfP3.mjs.map → service_pb-CxHztLk1.mjs.map} +1 -1
  27. package/dist/tailor-proto/src/tailor/v1/auth_resource_pb.d.mts +10 -0
  28. package/dist/vitest/index.d.mts +3 -2
  29. package/dist/vitest/index.mjs +1 -1
  30. package/dist/vitest/index.mjs.map +1 -1
  31. package/dist/vitest/mocks/tailordb-pglite.d.mts +88 -0
  32. package/dist/vitest/pglite-kysely.d.mts +13 -5
  33. package/dist/workflow-Cs9ISw6j.mjs.map +1 -1
  34. package/dist/workspace_resource_pb-u9bj6p3i.mjs +2 -0
  35. package/dist/workspace_resource_pb-u9bj6p3i.mjs.map +1 -0
  36. package/docs/cli/user.md +43 -7
  37. package/docs/cli-reference.md +1 -0
  38. package/docs/plugin/custom.md +11 -0
  39. package/docs/plugin/index.md +5 -0
  40. package/docs/testing.md +78 -2
  41. package/package.json +7 -7
  42. package/dist/application-D2E3FDfF.mjs.map +0 -1
  43. package/dist/application-rySTKrFa.mjs +0 -1
  44. package/dist/kysely-type-CmvAz1Xj.mjs +0 -43
  45. package/dist/kysely-type-CmvAz1Xj.mjs.map +0 -1
  46. package/dist/register-ts-hook-D6aNriu3.mjs.map +0 -1
  47. package/dist/service-COC9whur.mjs +0 -1
  48. package/dist/service-DDon86tM.mjs +0 -7
  49. package/dist/service-DDon86tM.mjs.map +0 -1
  50. package/dist/service_pb-D-xFu9Ub.mjs +0 -1
  51. package/dist/workspace_resource_pb-B7S-zx4Z.mjs +0 -2
  52. package/dist/workspace_resource_pb-B7S-zx4Z.mjs.map +0 -1
package/docs/cli/user.md CHANGED
@@ -66,9 +66,28 @@ See [Global Options](../cli-reference.md#global-options) for options available t
66
66
 
67
67
  **Commands**
68
68
 
69
- | Command | Description |
70
- | --------------------------- | ------------------------------------------------------------------------------------- |
71
- | [`auth token`](#auth-token) | Print a valid Tailor Platform access token to stdout, refreshing it first if expired. |
69
+ | Command | Description |
70
+ | ----------------------------- | ------------------------------------------------------------------------------------- |
71
+ | [`auth status`](#auth-status) | Show the active Tailor Platform authentication status without printing tokens. |
72
+ | [`auth token`](#auth-token) | Print a valid Tailor Platform access token to stdout, refreshing it first if expired. |
73
+
74
+ ### auth status
75
+
76
+ Show the active Tailor Platform authentication status without printing tokens.
77
+
78
+ **Usage**
79
+
80
+ ```
81
+ tailor auth status [options]
82
+ ```
83
+
84
+ **Options**
85
+
86
+ | Option | Alias | Description | Required | Default | Env |
87
+ | --------------------- | ----- | ----------------- | -------- | ------- | ------------------------- |
88
+ | `--profile <PROFILE>` | `-p` | Workspace profile | No | - | `TAILOR_PLATFORM_PROFILE` |
89
+
90
+ See [Global Options](../cli-reference.md#global-options) for options available to all commands.
72
91
 
73
92
  ### auth token
74
93
 
@@ -261,16 +280,33 @@ When no subcommand is provided, defaults to `list`.
261
280
  **Output (default):**
262
281
 
263
282
  ```
264
- token-name-1: read/write
265
- token-name-2: read
283
+ ┌──────────────┬────────────┬──────────────┬──────────────┐
284
+ name scopes │ createdAt │ lastUsedAt │
285
+ ├──────────────┼────────────┼──────────────┼──────────────┤
286
+ │ token-name-1 │ read/write │ 8 months ago │ 6 months ago │
287
+ │ token-name-2 │ read │ 8 months ago │ never │
288
+ └──────────────┴────────────┴──────────────┴──────────────┘
266
289
  ```
267
290
 
291
+ `lastUsedAt` reads `never` until the token has been used to authenticate, and is
292
+ updated at most once per hour.
293
+
268
294
  **Output (`-j, --json`):**
269
295
 
270
296
  ```json
271
297
  [
272
- { "name": "token-name-1", "scopes": ["read", "write"] },
273
- { "name": "token-name-2", "scopes": ["read"] }
298
+ {
299
+ "name": "token-name-1",
300
+ "scopes": ["read", "write"],
301
+ "createdAt": "2026-01-02T03:04:05.000Z",
302
+ "lastUsedAt": "2026-03-04T05:06:07.000Z"
303
+ },
304
+ {
305
+ "name": "token-name-2",
306
+ "scopes": ["read"],
307
+ "createdAt": "2026-01-02T03:04:05.000Z",
308
+ "lastUsedAt": null
309
+ }
274
310
  ]
275
311
  ```
276
312
 
@@ -231,6 +231,7 @@ Commands for authentication and user management.
231
231
  | [login](./cli/user.md#login) | Login to Tailor Platform. |
232
232
  | [logout](./cli/user.md#logout) | Logout from Tailor Platform. |
233
233
  | [auth](./cli/user.md#auth) | Authentication helpers for scripts and plugins. |
234
+ | [auth status](./cli/user.md#auth-status) | Show the active Tailor Platform authentication status without printing tokens. |
234
235
  | [auth token](./cli/user.md#auth-token) | Print a valid Tailor Platform access token to stdout, refreshing it first if expired. |
235
236
  | [user](./cli/user.md#user) | Manage Tailor Platform users. |
236
237
  | [user current](./cli/user.md#user-current) | Show current user. |
@@ -83,6 +83,13 @@ interface Plugin<TableConfig = unknown, PluginConfig = unknown> {
83
83
  | `executors` | `PluginGeneratedExecutor[]` | Additional executors to generate |
84
84
  | `extends` | `{ fields?: Record<string, TailorAnyDBField> }` | Fields to add to the source table |
85
85
 
86
+ Tables in `tables` are validated as complete TailorDB table definitions before registration.
87
+ Fields in `extends.fields` are validated as part of the resulting source table. You can return a
88
+ table builder directly or a structural copy such as `{ ...db.table(...) }`; a copy is accepted as
89
+ long as it retains valid table schema properties. Malformed output stops the build with an error
90
+ that identifies the plugin and relevant table output, without partially registering tables from
91
+ that source table's plugin processing.
92
+
86
93
  **Use cases**:
87
94
 
88
95
  - Generate derived tables (e.g., archive tables, history tables) from user-defined tables
@@ -115,6 +122,10 @@ onTableLoaded(context) {
115
122
 
116
123
  Same as `TablePluginOutput` but without `extends` (namespace plugins cannot extend a source table).
117
124
 
125
+ Tables in `tables` undergo the same validation and support structural copies. If any returned
126
+ table is malformed, the build stops before namespace-generated tables are registered and the
127
+ error identifies the plugin and relevant table output.
128
+
118
129
  **Use cases**:
119
130
 
120
131
  - Generate tables that don't derive from a specific user table (e.g., audit log, settings table)
@@ -102,6 +102,11 @@ Plugins can generate:
102
102
  - **Field Extensions**: Additional fields added to the source table
103
103
  - **Output Files**: TypeScript code and other files via generation-time hooks
104
104
 
105
+ Tables produced by definition-time hooks are validated before registration. This includes
106
+ generated tables and source tables after field extensions are applied. Malformed output stops
107
+ the build with an error that identifies the plugin and relevant table output, without partially
108
+ registering tables from that processing step.
109
+
105
110
  Generated files are placed under `.tailor/<plugin-id>/` (the plugin ID is sanitized,
106
111
  e.g. `@example/soft-delete` → `example-soft-delete`), such as:
107
112
 
package/docs/testing.md CHANGED
@@ -24,6 +24,7 @@ For anonymous direct calls:
24
24
  Platform API mocks under `@tailor-platform/sdk/vitest` (for use with the [`tailor-runtime` Vitest environment](#runtime-environment-emulation-beta) below):
25
25
 
26
26
  - `mockTailordb` — TailorDB query stubs and call recording
27
+ - `mockTailordbWithPGlite` — TailorDB backed by a real in-memory Postgres (PGlite)
27
28
  - `mockWorkflow` — `tailor.workflow` job / wait / resolve mocks
28
29
  - `runWorkflowLocally` — local full-chain workflow runner
29
30
  - `mockSecretmanager`, `mockAuthconnection`, `mockIdp`, `mockFile`, `mockIconv`, `mockAigateway`, `mockLogger` — corresponding platform API mocks
@@ -63,7 +64,7 @@ export default defineConfig({
63
64
 
64
65
  ### Acquiring mocks with `using`
65
66
 
66
- Each mock controller (`mockTailordb`, `mockWorkflow`, `mockSecretmanager`, `mockAuthconnection`, `mockIdp`, `mockFile`, `mockIconv`, `mockAigateway`, `mockLogger`) is a **factory function**. Acquire it inside a test with a [`using` declaration](https://github.com/tc39/proposal-explicit-resource-management) — its state is reset automatically when the test scope exits, so you no longer need `beforeEach(() => mock.reset())`:
67
+ Each mock controller (`mockTailordb`, `mockTailordbWithPGlite`, `mockWorkflow`, `mockSecretmanager`, `mockAuthconnection`, `mockIdp`, `mockFile`, `mockIconv`, `mockAigateway`, `mockLogger`) is a **factory function**. Acquire it inside a test with a [`using` declaration](https://github.com/tc39/proposal-explicit-resource-management) — its state is reset automatically when the test scope exits, so you no longer need `beforeEach(() => mock.reset())`:
67
68
 
68
69
  ```typescript
69
70
  import { mockTailordb } from "@tailor-platform/sdk/vitest";
@@ -144,6 +145,81 @@ Within one `mockTailordb()` instance, use either `onQuery()` matchers or a direc
144
145
 
145
146
  Pass `{ onUnhandled: "error" }` to make an unmatched query fail instead of returning an empty result.
146
147
 
148
+ #### Real SQL execution with PGlite (`mockTailordbWithPGlite`)
149
+
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
+
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.
153
+
154
+ ```typescript
155
+ import { PGlite } from "@electric-sql/pglite";
156
+ import { mockTailordbWithPGlite } from "@tailor-platform/sdk/vitest";
157
+ import { afterAll, beforeAll, expect, test } from "vitest";
158
+ import { getDB } from "../generated/db";
159
+ import resolver from "./upsertUsers";
160
+
161
+ const pglite = new PGlite();
162
+
163
+ 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
+ `);
172
+ });
173
+
174
+ afterAll(async () => {
175
+ await pglite.close();
176
+ });
177
+
178
+ test("upserts against real rows", async () => {
179
+ using _db = mockTailordbWithPGlite({ namespaces: { "main-db": pglite } });
180
+ const db = getDB("main-db");
181
+ await db
182
+ .insertInto("User")
183
+ .values({ name: "Existing", email: "exists@example.com", age: 40 })
184
+ .execute();
185
+
186
+ const result = await resolver.body({
187
+ input: {
188
+ users: [
189
+ { name: "Newcomer", email: "new@example.com", age: 22 },
190
+ { name: "Existing", email: "exists@example.com", age: 41 },
191
+ ],
192
+ },
193
+ caller: null,
194
+ invoker: null,
195
+ env: { appName: "Resolver Template", version: 1 },
196
+ });
197
+
198
+ expect(result).toEqual({ created: 1, updated: 1 });
199
+ const rows = await db.selectFrom("User").selectAll().orderBy("email", "asc").execute();
200
+ expect(rows.map((row) => row.age)).toEqual([41, 22]);
201
+ });
202
+ ```
203
+
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:
205
+
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
+ ```
214
+
215
+ PGlite does not apply the TailorDB `.serial()` configuration itself. Match the `start`, `format`, and any limit that the behavior under test relies on.
216
+
217
+ - 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.
219
+ - 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
+ - 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
+ - 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.
222
+
147
223
  ### Workflow Mock
148
224
 
149
225
  Workflow job `.start()` calls use the platform workflow runtime. Acquire `mockWorkflow()` when you want to provide start responses with `setJobHandler` / `enqueueResult` or assert on `startedJobs`. If no response is configured, the mock throws so missing job mocks fail loudly. Use `job(definition)` or `workflow(definition)` to get a stable, fully typed Vitest mock for one definition:
@@ -592,7 +668,7 @@ describe("upsertUsers resolver", () => {
592
668
  });
593
669
  ```
594
670
 
595
- 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.
671
+ 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.
596
672
 
597
673
  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).
598
674
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailor-platform/sdk",
3
- "version": "2.10.0",
3
+ "version": "2.12.0",
4
4
  "description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -169,13 +169,13 @@
169
169
  "@secretlint/secretlint-rule-preset-recommend": "13.0.5",
170
170
  "@standard-schema/spec": "1.1.0",
171
171
  "@tailor-platform/function-kysely-tailordb": "0.1.3",
172
- "@toiroakr/lines-db": "0.12.2",
172
+ "@toiroakr/lines-db": "0.12.5",
173
173
  "@toiroakr/read-multiline": "0.4.1",
174
174
  "@urql/core": "6.0.3",
175
175
  "amaro": "1.1.11",
176
176
  "confbox": "0.2.4",
177
177
  "date-fns": "4.4.0",
178
- "es-toolkit": "1.51.0",
178
+ "es-toolkit": "1.52.0",
179
179
  "find-up-simple": "1.0.1",
180
180
  "get-east-asian-width": "1.6.0",
181
181
  "get-tsconfig": "4.14.3",
@@ -184,21 +184,21 @@
184
184
  "inflection": "3.0.2",
185
185
  "kysely": "0.29.5",
186
186
  "mime-types": "3.0.2",
187
- "open": "11.0.1",
187
+ "open": "11.0.2",
188
188
  "oxc-parser": "0.147.0",
189
189
  "p-limit": "7.3.1",
190
190
  "pathe": "2.0.3",
191
191
  "pgsql-ast-parser": "12.0.2",
192
192
  "pkg-types": "2.3.1",
193
193
  "politty": "0.11.9",
194
- "rolldown": "1.2.5",
194
+ "rolldown": "1.2.6",
195
195
  "semver": "7.8.5",
196
196
  "sql-highlight": "6.1.0",
197
197
  "std-env": "4.2.0",
198
198
  "ts-cron-validator": "1.1.5",
199
199
  "type-fest": "5.8.0",
200
200
  "xdg-basedir": "5.1.0",
201
- "zod": "4.4.3"
201
+ "zod": "4.5.4"
202
202
  },
203
203
  "devDependencies": {
204
204
  "@opentelemetry/sdk-trace-base": "2.10.0",
@@ -242,7 +242,7 @@
242
242
  "scripts": {
243
243
  "test": "vitest",
244
244
  "test:unit": "vitest --project=unit*",
245
- "test:e2e": "vitest --project=e2e",
245
+ "test:e2e": "vitest --project=e2e*",
246
246
  "test:coverage": "vitest --coverage",
247
247
  "docs:check": "vitest run --project=unit* src/cli/docs.test.ts",
248
248
  "docs:update": "POLITTY_DOCS_UPDATE=true vitest run --project=unit* src/cli/docs.test.ts",