@tailor-platform/sdk 1.40.1 → 1.43.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 (46) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +23 -0
  3. package/dist/{actor-B2oEmlTc.d.mts → actor-DzCuoMlP.d.mts} +2 -2
  4. package/dist/{application-EvhIIVg0.mjs → application-DQpD_kHR.mjs} +87 -8
  5. package/dist/application-DQpD_kHR.mjs.map +1 -0
  6. package/dist/application-DUcmoFdc.mjs +4 -0
  7. package/dist/brand-Ll48SMXe.mjs.map +1 -1
  8. package/dist/cli/index.mjs +31 -26
  9. package/dist/cli/index.mjs.map +1 -1
  10. package/dist/cli/lib.d.mts +49 -7
  11. package/dist/cli/lib.mjs +3 -3
  12. package/dist/cli/lib.mjs.map +1 -1
  13. package/dist/configure/index.d.mts +5 -5
  14. package/dist/configure/index.mjs +83 -3
  15. package/dist/configure/index.mjs.map +1 -1
  16. package/dist/{index-Chvw1Eod.d.mts → index-0Dk-fDWi.d.mts} +2 -2
  17. package/dist/{index-CiNNNpuH.d.mts → index-BEEL1-6Z.d.mts} +2 -2
  18. package/dist/{index-D_ezppY7.d.mts → index-Br4XCvX1.d.mts} +103 -86
  19. package/dist/{index-BtXZdz-F.d.mts → index-DdsUV-aA.d.mts} +2 -2
  20. package/dist/{index-reFAYSX7.d.mts → index-ZZYEd_0R.d.mts} +2 -2
  21. package/dist/{job-p6zf8Qpg.mjs → job-BOvKyNdT.mjs} +15 -9
  22. package/dist/job-BOvKyNdT.mjs.map +1 -0
  23. package/dist/plugin/builtin/enum-constants/index.d.mts +1 -1
  24. package/dist/plugin/builtin/file-utils/index.d.mts +1 -1
  25. package/dist/plugin/builtin/kysely-type/index.d.mts +1 -1
  26. package/dist/plugin/builtin/seed/index.d.mts +1 -1
  27. package/dist/plugin/index.d.mts +2 -2
  28. package/dist/{runtime-B9R1TzLD.mjs → runtime-Bn68JXnL.mjs} +195 -77
  29. package/dist/runtime-Bn68JXnL.mjs.map +1 -0
  30. package/dist/{tailor-db-field-CoFKRCYW.d.mts → tailor-db-field-D_z185oq.d.mts} +36 -6
  31. package/dist/utils/test/index.d.mts +36 -3
  32. package/dist/utils/test/index.mjs +78 -9
  33. package/dist/utils/test/index.mjs.map +1 -1
  34. package/dist/{workflow.generated-Btz6srLR.d.mts → workflow.generated-CDCnZNkH.d.mts} +2 -2
  35. package/docs/cli/function.md +83 -1
  36. package/docs/cli-reference.md +3 -1
  37. package/docs/services/executor.md +4 -0
  38. package/docs/services/idp.md +16 -0
  39. package/docs/services/resolver.md +4 -2
  40. package/docs/services/workflow.md +117 -3
  41. package/docs/testing.md +95 -7
  42. package/package.json +17 -17
  43. package/dist/application-CE2s_a6w.mjs +0 -4
  44. package/dist/application-EvhIIVg0.mjs.map +0 -1
  45. package/dist/job-p6zf8Qpg.mjs.map +0 -1
  46. package/dist/runtime-B9R1TzLD.mjs.map +0 -1
@@ -60,7 +60,7 @@ export const fetchCustomer = createWorkflowJob({
60
60
 
61
61
  Workflow job inputs and outputs are serialized as JSON when passed between jobs. This imposes type constraints:
62
62
 
63
- **Input types** must be JSON-compatible — only primitives (`string`, `number`, `boolean`, `null`), arrays, and plain objects are allowed. `Date`, `Map`, `Set`, functions, and other non-serializable types cannot be used.
63
+ **Input types** must be JSON-compatible — primitives (`string`, `number`, `boolean`), arrays, and plain objects are allowed. `Date`, `Map`, `Set`, functions, and other non-serializable types cannot be used. Top-level `null` is also rejected because the platform normalizes top-level `null`/`undefined` args to `{}` (nested `null` inside objects or arrays is preserved).
64
64
 
65
65
  ```typescript
66
66
  // OK
@@ -78,9 +78,17 @@ export const badJob = createWorkflowJob({
78
78
  // ...
79
79
  },
80
80
  });
81
+
82
+ // Compile error — top-level null would be normalized to {} by the platform
83
+ export const nullJob = createWorkflowJob({
84
+ name: "null-job",
85
+ body: async (input: { id: string } | null) => {
86
+ // ...
87
+ },
88
+ });
81
89
  ```
82
90
 
83
- **Output types** are more permissive `Date` and objects with `toJSON()` are allowed because they are serialized via `JSON.stringify` at runtime (e.g., `Date` becomes a string).
91
+ **Output types** have the same restriction as inputs: must be JsonValue-compatible (plain objects/arrays; no class instances or functions). Values with methods (function-typed properties) are rejected at compile time this covers class instances like `Date` or `RegExp` as well as any plain object that exposes a method such as `toJSON()`.
84
92
 
85
93
  These constraints are enforced at compile time — you will get a type error if you use an unsupported type.
86
94
 
@@ -175,8 +183,10 @@ import { sendNotification } from "./jobs/send-notification";
175
183
  // Jobs must be named exports
176
184
  export const processOrder = createWorkflowJob({
177
185
  name: "process-order",
178
- body: async (input: { customerId: string }, { env }) => {
186
+ body: async (input: { customerId: string }, { env, invoker }) => {
179
187
  // `env` contains values from `tailor.config.ts` -> `env`.
188
+ // `invoker` is the principal running this job, overridden by `authInvoker`
189
+ // when set; `null` for anonymous calls.
180
190
  // Trigger other jobs by calling .trigger() on the job object.
181
191
  const customer = await fetchCustomer.trigger({
182
192
  customerId: input.customerId,
@@ -196,6 +206,110 @@ export default createWorkflow({
196
206
  });
197
207
  ```
198
208
 
209
+ ## Wait Points
210
+
211
+ Wait points allow a workflow job to suspend execution and wait for an external signal before resuming. This enables human-in-the-loop patterns such as approvals, reviews, and manual confirmations.
212
+
213
+ ### Defining Wait Points
214
+
215
+ Use `defineWaitPoint` to declare a single typed wait point:
216
+
217
+ ```typescript
218
+ import { defineWaitPoint } from "@tailor-platform/sdk";
219
+
220
+ export const approval = defineWaitPoint<
221
+ { message: string; requestId: string },
222
+ { approved: boolean }
223
+ >("approval");
224
+ ```
225
+
226
+ For multiple wait points, use `defineWaitPoints` with a builder callback. Property names become wait point keys, and JSDoc on each property is preserved in IDE autocompletion:
227
+
228
+ ```typescript
229
+ import { defineWaitPoints } from "@tailor-platform/sdk";
230
+
231
+ export const waitPoints = defineWaitPoints((define) => ({
232
+ /** Manager approval step */
233
+ managerApproval: define<{ amount: number }, { approved: boolean }>(),
234
+ /** Finance review step */
235
+ financeReview: define<{ invoiceId: string }, { validated: boolean }>(),
236
+ }));
237
+
238
+ await waitPoints.managerApproval.wait({ amount: 50000 });
239
+ ```
240
+
241
+ Both accept two type parameters:
242
+
243
+ - **`Payload`** — Data sent when the job suspends (passed to `.wait()`). Must be a pure JSON value (`string`, `number`, `boolean`, `null`, arrays, plain objects). Use `undefined` if no payload is needed.
244
+ - **`Result`** — Data returned when the wait point is resolved (returned from `.wait()`, produced by the `.resolve()` callback). Must be a pure JSON value.
245
+
246
+ Both must be JsonValue-compatible (plain objects/arrays; no class instances or functions). Values with methods (function-typed properties) are rejected at compile time — this covers class instances like `Date` or `RegExp` as well as any plain object that exposes a method such as `toJSON()`. Convert such values to `string` (e.g. ISO strings) or `number` (epoch millis) before passing them through a wait point.
247
+
248
+ ### Waiting in a Job
249
+
250
+ Call `.wait()` inside a workflow job body to suspend execution:
251
+
252
+ ```typescript
253
+ import { createWorkflow, createWorkflowJob, defineWaitPoint } from "@tailor-platform/sdk";
254
+
255
+ export const approval = defineWaitPoint<
256
+ { message: string; requestId: string },
257
+ { approved: boolean }
258
+ >("approval");
259
+
260
+ export const processWithApproval = createWorkflowJob({
261
+ name: "process-with-approval",
262
+ body: async (input: { orderId: string }) => {
263
+ // Suspends here until resolved externally
264
+ const result = await approval.wait({
265
+ message: `Please approve order ${input.orderId}`,
266
+ requestId: input.orderId,
267
+ });
268
+
269
+ if (!result.approved) {
270
+ return { orderId: input.orderId, status: "rejected" as const };
271
+ }
272
+ return { orderId: input.orderId, status: "approved" as const };
273
+ },
274
+ });
275
+
276
+ export default createWorkflow({
277
+ name: "approval-workflow",
278
+ mainJob: processWithApproval,
279
+ });
280
+ ```
281
+
282
+ ### Resolving from a Resolver
283
+
284
+ Call `.resolve()` from a resolver (or executor) to resume a suspended job. The callback receives the payload that was passed to `.wait()` and returns the result:
285
+
286
+ ```typescript
287
+ import { createResolver, t } from "@tailor-platform/sdk";
288
+ import { approval } from "../workflows/approval";
289
+
290
+ export default createResolver({
291
+ name: "resolveApproval",
292
+ description: "Resolve a waiting approval",
293
+ operation: "mutation",
294
+ input: {
295
+ executionId: t.string(),
296
+ approved: t.bool(),
297
+ },
298
+ body: async ({ input }) => {
299
+ await approval.resolve(input.executionId, (payload) => {
300
+ console.log("Resolving:", payload.message);
301
+ return { approved: input.approved };
302
+ });
303
+ return { resolved: true };
304
+ },
305
+ output: t.object({
306
+ resolved: t.bool(),
307
+ }),
308
+ });
309
+ ```
310
+
311
+ Wait points can be imported and used in any file (workflow jobs, resolvers, executors). For local testing, see [Testing Wait Points](../testing.md#testing-wait-points).
312
+
199
313
  ## Retry Policy
200
314
 
201
315
  You can configure automatic retry behavior with exponential backoff by setting `retryPolicy` on a workflow. All fields are required when `retryPolicy` is set:
package/docs/testing.md CHANGED
@@ -10,7 +10,7 @@ npm create @tailor-platform/sdk -- --template testing <your-project-name>
10
10
 
11
11
  ## Unit Tests
12
12
 
13
- Unit tests verify resolver logic without requiring deployment.
13
+ Unit tests verify resolver and workflow logic locally without requiring deployment.
14
14
 
15
15
  ### Simple Resolver Testing
16
16
 
@@ -171,13 +171,52 @@ describe("decrementUserAge resolver", () => {
171
171
  - Mock high-level operations instead of low-level SQL queries
172
172
  - **Best for:** Complex business logic with multiple database operations
173
173
 
174
- ## Workflow Tests
174
+ ### Testing Resolvers that Call `.resolve()`
175
175
 
176
- Test workflows locally without deploying to Tailor Platform.
176
+ Use `setupWaitPointMock` to mock `tailor.workflow.resolve` when testing resolvers that resume a suspended workflow execution.
177
177
 
178
- ### Job Unit Tests
178
+ ```typescript
179
+ import { afterEach } from "vitest";
180
+ import { setupWaitPointMock, unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
181
+ import resolver from "./resolvers/resolveApproval";
182
+
183
+ const TailorGlobal = globalThis as { tailor?: { workflow?: Record<string, unknown> } };
184
+
185
+ describe("resolveApproval resolver", () => {
186
+ afterEach(() => {
187
+ delete TailorGlobal.tailor;
188
+ });
189
+
190
+ test("resolves approval", async () => {
191
+ const { resolveCalls } = setupWaitPointMock({
192
+ onResolve: (_execId, _key, callback) => {
193
+ const result = callback({ message: "Please approve", orderId: "order-1" });
194
+ expect(result).toEqual({ approved: true });
195
+ },
196
+ });
197
+
198
+ const result = await resolver.body({
199
+ input: { executionId: "exec-1", approved: true },
200
+ user: unauthenticatedTailorUser,
201
+ env: {},
202
+ });
179
203
 
180
- Test individual job logic by calling `.body()` directly:
204
+ expect(result).toEqual({ resolved: true });
205
+ expect(resolveCalls).toHaveLength(1);
206
+ expect(resolveCalls[0]).toEqual({ executionId: "exec-1", key: "approval" });
207
+ });
208
+ });
209
+ ```
210
+
211
+ **Key points:**
212
+
213
+ - `onResolve` lets you verify the callback behavior in resolvers that call `.resolve()`
214
+ - Clean up mocks in `afterEach` by deleting `TailorGlobal.tailor`
215
+ - **Best for:** Resolvers that resume suspended workflow executions
216
+
217
+ ### Workflow Job Unit Tests
218
+
219
+ Test individual workflow job logic locally without deploying. Call `.body()` directly:
181
220
 
182
221
  ```typescript
183
222
  import workflow, { addNumbers, calculate } from "./workflows/calculation";
@@ -218,9 +257,9 @@ describe("workflow with dependencies", () => {
218
257
 
219
258
  **Note:** To execute dependent jobs without mocking, and they require `env`, use `vi.stubEnv(WORKFLOW_TEST_ENV_KEY, ...)` and call `.trigger()` directly as shown in the integration test section below.
220
259
 
221
- ### Integration Tests with `.trigger()`
260
+ ### Workflow Integration Tests with `.trigger()`
222
261
 
223
- Test the full workflow execution using `workflow.mainJob.trigger()`:
262
+ Test the full workflow execution locally using `workflow.mainJob.trigger()`:
224
263
 
225
264
  ```typescript
226
265
  import { WORKFLOW_TEST_ENV_KEY } from "@tailor-platform/sdk/test";
@@ -252,6 +291,55 @@ describe("workflow integration", () => {
252
291
  - Use `workflow.mainJob.trigger()` to execute the full workflow chain and get the result
253
292
  - **Best for:** Testing workflow orchestration and job dependencies
254
293
 
294
+ ### Testing Jobs with Wait Points
295
+
296
+ Use `setupWaitPointMock` to mock `tailor.workflow.wait` when testing jobs that suspend on wait points:
297
+
298
+ ```typescript
299
+ import { afterEach, vi } from "vitest";
300
+ import { setupWaitPointMock } from "@tailor-platform/sdk/test";
301
+ import { processWithApproval } from "./workflows/approval";
302
+
303
+ const TailorGlobal = globalThis as { tailor?: { workflow?: Record<string, unknown> } };
304
+
305
+ describe("approval workflow", () => {
306
+ afterEach(() => {
307
+ delete TailorGlobal.tailor;
308
+ });
309
+
310
+ test("approved flow returns approved status", async () => {
311
+ const { waitCalls } = setupWaitPointMock({
312
+ onWait: (_key, _payload) => ({ approved: true }),
313
+ });
314
+
315
+ const result = await processWithApproval.body({ orderId: "order-1" }, { env: {} });
316
+
317
+ expect(result).toEqual({ orderId: "order-1", status: "approved" });
318
+ expect(waitCalls).toHaveLength(1);
319
+ expect(waitCalls[0]).toEqual({
320
+ key: "approval",
321
+ payload: { message: "Please approve order order-1", orderId: "order-1" },
322
+ });
323
+ });
324
+
325
+ test("rejected flow returns rejected status", async () => {
326
+ setupWaitPointMock({
327
+ onWait: () => ({ approved: false }),
328
+ });
329
+
330
+ const result = await processWithApproval.body({ orderId: "order-2" }, { env: {} });
331
+
332
+ expect(result).toEqual({ orderId: "order-2", status: "rejected" });
333
+ });
334
+ });
335
+ ```
336
+
337
+ **Key points:**
338
+
339
+ - `onWait` controls what `.wait()` returns — use it to test different branches (approved/rejected)
340
+ - Clean up mocks in `afterEach` by deleting `TailorGlobal.tailor`
341
+ - **Best for:** Jobs that suspend on wait points for human-in-the-loop approval
342
+
255
343
  ## End-to-End (E2E) Tests
256
344
 
257
345
  E2E tests verify your application works correctly when deployed to Tailor Platform. They test the full stack including GraphQL API, database operations, and authentication.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailor-platform/sdk",
3
- "version": "1.40.1",
3
+ "version": "1.43.0",
4
4
  "description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -81,8 +81,8 @@
81
81
  "@bufbuild/protobuf": "2.11.0",
82
82
  "@connectrpc/connect": "2.1.1",
83
83
  "@connectrpc/connect-node": "2.1.1",
84
- "@inquirer/core": "11.1.8",
85
- "@inquirer/prompts": "8.4.1",
84
+ "@inquirer/core": "11.1.9",
85
+ "@inquirer/prompts": "8.4.2",
86
86
  "@jridgewell/trace-mapping": "0.3.31",
87
87
  "@liam-hq/cli": "0.7.24",
88
88
  "@napi-rs/keyring": "1.2.0",
@@ -91,12 +91,12 @@
91
91
  "@opentelemetry/resources": "2.7.0",
92
92
  "@opentelemetry/sdk-trace-node": "2.7.0",
93
93
  "@opentelemetry/semantic-conventions": "1.40.0",
94
- "@oxc-project/types": "0.126.0",
94
+ "@oxc-project/types": "0.127.0",
95
95
  "@standard-schema/spec": "1.1.0",
96
96
  "@tailor-platform/function-kysely-tailordb": "0.1.3",
97
- "@tailor-platform/function-types": "0.8.4",
98
- "@toiroakr/lines-db": "0.9.1",
99
- "@toiroakr/read-multiline": "0.3.1",
97
+ "@tailor-platform/function-types": "0.8.5",
98
+ "@toiroakr/lines-db": "0.9.2",
99
+ "@toiroakr/read-multiline": "0.3.2",
100
100
  "@urql/core": "6.0.1",
101
101
  "chalk": "5.6.2",
102
102
  "chokidar": "5.0.0",
@@ -113,7 +113,7 @@
113
113
  "multiline-ts": "4.0.1",
114
114
  "open": "11.0.0",
115
115
  "ora": "9.3.0",
116
- "oxc-parser": "0.126.0",
116
+ "oxc-parser": "0.127.0",
117
117
  "p-limit": "7.3.0",
118
118
  "pathe": "2.0.3",
119
119
  "pgsql-ast-parser": "12.0.2",
@@ -123,11 +123,11 @@
123
123
  "semver": "7.7.4",
124
124
  "serve": "14.2.6",
125
125
  "sql-highlight": "6.1.0",
126
- "std-env": "4.0.0",
126
+ "std-env": "4.1.0",
127
127
  "table": "6.9.0",
128
128
  "ts-cron-validator": "1.1.5",
129
129
  "tsx": "4.21.0",
130
- "type-fest": "5.5.0",
130
+ "type-fest": "5.6.0",
131
131
  "xdg-basedir": "5.1.0",
132
132
  "zod": "4.3.6"
133
133
  },
@@ -138,18 +138,18 @@
138
138
  "@types/mime-types": "3.0.1",
139
139
  "@types/node": "24.12.2",
140
140
  "@types/semver": "7.7.1",
141
- "@typescript/native-preview": "7.0.0-dev.20260417.1",
141
+ "@typescript/native-preview": "7.0.0-dev.20260423.1",
142
142
  "@vitest/coverage-v8": "4.1.4",
143
- "eslint": "10.2.0",
143
+ "eslint": "10.2.1",
144
144
  "eslint-plugin-jsdoc": "62.9.0",
145
- "eslint-plugin-oxlint": "1.60.0",
146
- "oxfmt": "0.45.0",
147
- "oxlint": "1.60.0",
148
- "oxlint-tsgolint": "0.20.0",
145
+ "eslint-plugin-oxlint": "1.61.0",
146
+ "oxfmt": "0.46.0",
147
+ "oxlint": "1.61.0",
148
+ "oxlint-tsgolint": "0.21.1",
149
149
  "sonda": "0.11.1",
150
150
  "tsdown": "0.21.9",
151
151
  "typescript": "5.9.3",
152
- "typescript-eslint": "8.58.2",
152
+ "typescript-eslint": "8.59.0",
153
153
  "vitest": "4.1.4",
154
154
  "zinfer": "0.1.8"
155
155
  },
@@ -1,4 +0,0 @@
1
-
2
- import { n as generatePluginFilesIfNeeded, r as loadApplication, t as defineApplication } from "./application-EvhIIVg0.mjs";
3
-
4
- export { defineApplication };