@tailor-platform/sdk 1.58.0 → 1.60.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 (80) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/dist/{application-B59TaTk_.mjs → application-FnWOxBk7.mjs} +46 -27
  3. package/dist/application-FnWOxBk7.mjs.map +1 -0
  4. package/dist/application-VOdgMtOD.mjs +4 -0
  5. package/dist/{authconnection-TsdLYaLs.d.mts → authconnection-BIYzEh2p.d.mts} +2 -2
  6. package/dist/authconnection-D8SJGMpj.mjs.map +1 -1
  7. package/dist/cli/erd-viewer-assets/app.js +4 -4
  8. package/dist/cli/index.mjs +57 -13
  9. package/dist/cli/index.mjs.map +1 -1
  10. package/dist/cli/lib.mjs +4 -3
  11. package/dist/cli/lib.mjs.map +1 -1
  12. package/dist/client-B-jRdlC_.mjs +4 -0
  13. package/dist/{client-62B-r3MN.mjs → client-W5P4NYYX.mjs} +117 -4
  14. package/dist/{client-62B-r3MN.mjs.map → client-W5P4NYYX.mjs.map} +1 -1
  15. package/dist/configure/index.d.mts +1 -1
  16. package/dist/configure/index.mjs +8 -8
  17. package/dist/configure/index.mjs.map +1 -1
  18. package/dist/{crashreport-CCGpLUlP.mjs → crashreport-D3DvAzdg.mjs} +3 -3
  19. package/dist/crashreport-D3DvAzdg.mjs.map +1 -0
  20. package/dist/{crashreport-CXD_Kjk-.mjs → crashreport-lnVTnbB5.mjs} +1 -1
  21. package/dist/file-B58Dm-2P.mjs.map +1 -1
  22. package/dist/{file-VTJbbOL3.d.mts → file-BzK8z3X-.d.mts} +2 -2
  23. package/dist/globals-ByrCoDip.mjs +109 -0
  24. package/dist/globals-ByrCoDip.mjs.map +1 -0
  25. package/dist/iconv-DreIffeM.mjs.map +1 -1
  26. package/dist/{iconv-Chu6Hit2.d.mts → iconv-kwrmd1U_.d.mts} +2 -2
  27. package/dist/{idp-Di9N4FSJ.d.mts → idp-BlBPtXJ-.d.mts} +2 -2
  28. package/dist/idp-Ch95ag8h.mjs.map +1 -1
  29. package/dist/{index-BWoHfE-i.d.mts → index-Cr6ufjZ5.d.mts} +10 -8
  30. package/dist/{index-DTSQthwF.d.mts → index-DRhMpdnA.d.mts} +7 -7
  31. package/dist/{job-CEAJLiGp.mjs → job-BpsFXPbi.mjs} +8 -17
  32. package/dist/job-BpsFXPbi.mjs.map +1 -0
  33. package/dist/mock-Dpu__UeJ.mjs +805 -0
  34. package/dist/mock-Dpu__UeJ.mjs.map +1 -0
  35. package/dist/registry-D0uB0OrK.mjs +178 -0
  36. package/dist/registry-D0uB0OrK.mjs.map +1 -0
  37. package/dist/{repl-editor-ihh8koiR.mjs → repl-editor-Y9QJDL0K.mjs} +3 -9
  38. package/dist/{repl-editor-ihh8koiR.mjs.map → repl-editor-Y9QJDL0K.mjs.map} +1 -1
  39. package/dist/runtime/authconnection.d.mts +1 -1
  40. package/dist/runtime/file.d.mts +1 -1
  41. package/dist/runtime/globals.d.mts +5 -5
  42. package/dist/runtime/iconv.d.mts +1 -1
  43. package/dist/runtime/idp.d.mts +1 -1
  44. package/dist/runtime/index.d.mts +7 -7
  45. package/dist/runtime/secretmanager.d.mts +1 -1
  46. package/dist/runtime/workflow.d.mts +1 -1
  47. package/dist/{runtime-BC-FbQkg.mjs → runtime-CrUa8Z2k.mjs} +238 -273
  48. package/dist/runtime-CrUa8Z2k.mjs.map +1 -0
  49. package/dist/secretmanager-B9h-U_8U.mjs.map +1 -1
  50. package/dist/{secretmanager-BhpDmxwT.d.mts → secretmanager-CKLB3wAQ.d.mts} +2 -2
  51. package/dist/utils/test/index.d.mts +5 -5
  52. package/dist/utils/test/index.mjs +7 -7
  53. package/dist/utils/test/index.mjs.map +1 -1
  54. package/dist/vitest/environment.mjs +3 -4
  55. package/dist/vitest/environment.mjs.map +1 -1
  56. package/dist/vitest/index.d.mts +167 -120
  57. package/dist/vitest/index.mjs +6 -6
  58. package/dist/vitest/index.mjs.map +1 -1
  59. package/dist/vitest/setup.d.mts +1 -1
  60. package/dist/vitest/setup.mjs +4 -3
  61. package/dist/vitest/setup.mjs.map +1 -1
  62. package/dist/workflow--aPbA8Uq.mjs.map +1 -1
  63. package/dist/{workflow-dYYH7QFa.d.mts → workflow-CMamswkK.d.mts} +2 -2
  64. package/docs/cli/auth.md +40 -0
  65. package/docs/cli-reference.md +1 -0
  66. package/docs/configuration.md +1 -1
  67. package/docs/runtime.md +9 -12
  68. package/docs/services/auth.md +19 -1
  69. package/docs/testing.md +92 -85
  70. package/package.json +5 -5
  71. package/dist/application-B59TaTk_.mjs.map +0 -1
  72. package/dist/application-gO_pa5BO.mjs +0 -4
  73. package/dist/client-BWl3f1XS.mjs +0 -4
  74. package/dist/crashreport-CCGpLUlP.mjs.map +0 -1
  75. package/dist/job-CEAJLiGp.mjs.map +0 -1
  76. package/dist/mock-B6PI49C_.mjs +0 -844
  77. package/dist/mock-B6PI49C_.mjs.map +0 -1
  78. package/dist/runtime-BC-FbQkg.mjs.map +0 -1
  79. package/dist/test-env-key-CSnK4W1Y.mjs +0 -30
  80. package/dist/test-env-key-CSnK4W1Y.mjs.map +0 -1
package/docs/testing.md CHANGED
@@ -19,11 +19,11 @@ Helpers under `@tailor-platform/sdk/test`:
19
19
 
20
20
  - `unauthenticatedTailorUser` — default `user` value for resolver contexts
21
21
 
22
- Platform API mocks under `@tailor-platform/sdk/vitest` (auto-injected by the [`tailor-runtime` Vitest environment](#runtime-environment-emulation-beta) below):
22
+ Platform API mocks under `@tailor-platform/sdk/vitest` (for use with the [`tailor-runtime` Vitest environment](#runtime-environment-emulation-beta) below):
23
23
 
24
- - `tailordbMock` — TailorDB query stubs and call recording
25
- - `workflowMock` — `tailor.workflow` job / wait / resolve mocks
26
- - `secretmanagerMock`, `authconnectionMock`, `idpMock`, `fileMock`, `iconvMock` — corresponding platform API mocks
24
+ - `mockTailordb` — TailorDB query stubs and call recording
25
+ - `mockWorkflow` — `tailor.workflow` job / wait / resolve mocks
26
+ - `mockSecretmanager`, `mockAuthconnection`, `mockIdp`, `mockFile`, `mockIconv` — corresponding platform API mocks
27
27
 
28
28
  For tighter alignment with the production runtime — Node.js module blocking, Web-only globals, and platform API mocks — pair the resolver helpers with the [`tailor-runtime` Vitest environment](#runtime-environment-emulation-beta) below.
29
29
 
@@ -56,22 +56,41 @@ export default defineConfig({
56
56
 
57
57
  1. **Node.js module blocking** — `import { randomBytes } from "node:crypto"` in production code throws an error with a suggestion for the Web Standard API alternative (`globalThis.crypto`). Test files (`*.test.ts`, `*.spec.ts`) are exempt.
58
58
  2. **Node.js globals removal** — Only globals available in the platform runtime are kept (whitelist). `Buffer`, `global`, `setImmediate`, `__dirname`, `__filename`, `performance`, and others are removed.
59
- 3. **Platform API mocks** — `globalThis.tailordb`, `globalThis.tailor`, `TailorErrors`, `TailorErrorMessage`, `TailorDBFileError` are auto-injected with mock control objects for response configuration and call recording.
59
+ 3. **Platform API mocks** — the platform error classes (`TailorErrors`, `TailorErrorMessage`, `TailorDBFileError`) and `tailor.context` are always available. The other namespaces (`tailordb.Client`, `tailor.workflow`, `tailor.secretmanager`, …) are mocked when you acquire the corresponding `mockX()` — see below.
60
60
 
61
- ### TailorDB Mock
61
+ ### Acquiring mocks with `using`
62
62
 
63
- The environment auto-injects a mock `tailordb.Client`. Use `tailordbMock` to configure responses and assert on executed queries:
63
+ Each mock controller (`mockTailordb`, `mockWorkflow`, `mockSecretmanager`, `mockAuthconnection`, `mockIdp`, `mockFile`, `mockIconv`) 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())`:
64
64
 
65
65
  ```typescript
66
- import { tailordbMock } from "@tailor-platform/sdk/vitest";
66
+ import { mockTailordb } from "@tailor-platform/sdk/vitest";
67
67
 
68
- beforeEach(() => {
69
- tailordbMock.reset();
70
- });
68
+ test("...", () => {
69
+ using db = mockTailordb();
70
+ db.enqueueResult({ age: 30 });
71
+ // …
72
+ }); // reset automatically here
73
+ ```
74
+
75
+ The mock functions are also exposed directly (e.g. `db.queryObject`, `wf.triggerJobFunction`) so you can assert on them with `expect(...).toHaveBeenCalledWith(...)`.
76
+
77
+ > **Requirements:** `using` requires TypeScript ≥ 5.2 and a runtime that provides `Symbol.dispose` (Node ≥ 20.4 — the SDK already targets Node ≥ 22, and Vitest's transformer downlevels the syntax for you).
78
+ >
79
+ > **Acquire what you use:** a namespace is only mocked while you hold it with `using`, so code under test that calls a platform API (e.g. `tailor.workflow`, `tailordb.Client`) must run inside a test that has acquired the matching `mockX()`. The error classes and `tailor.context` are always present.
80
+ >
81
+ > **Seeded secrets survive:** secrets seeded from `tailor.config.ts` stay available across tests; a per-test `mockSecretmanager().setSecrets(...)` override applies only within that test.
82
+
83
+ ### TailorDB Mock
84
+
85
+ Acquire `mockTailordb()` to install the mock `tailordb.Client`, configure responses, and assert on executed queries:
86
+
87
+ ```typescript
88
+ import { mockTailordb } from "@tailor-platform/sdk/vitest";
71
89
 
72
90
  test("resolver queries the database", async () => {
91
+ using db = mockTailordb();
73
92
  // Order-based: stage rows for each upcoming query in one call
74
- tailordbMock.enqueueResults(
93
+ db.enqueueResults(
75
94
  [], // BEGIN (empty result)
76
95
  [{ age: 30 }], // SELECT (one row)
77
96
  [], // COMMIT
@@ -80,8 +99,8 @@ test("resolver queries the database", async () => {
80
99
  const result = await resolver.body({ input: { email: "test@example.com" } });
81
100
 
82
101
  expect(result).toEqual({ oldAge: 30, newAge: 31 });
83
- expect(tailordbMock.executedQueries).toHaveLength(3);
84
- expect(tailordbMock.createdClients).toMatchObject([{ namespace: "tailordb" }]);
102
+ expect(db.executedQueries).toHaveLength(3);
103
+ expect(db.createdClients).toMatchObject([{ namespace: "tailordb" }]);
85
104
  });
86
105
  ```
87
106
 
@@ -93,30 +112,28 @@ Three response modes:
93
112
 
94
113
  ```typescript
95
114
  test("content-based mock", async () => {
96
- tailordbMock.setQueryResolver((query) => {
115
+ using db = mockTailordb();
116
+ db.setQueryResolver((query) => {
97
117
  if (query.includes("SELECT")) return [{ id: "1", name: "test" }];
98
118
  return [];
99
119
  });
100
120
 
101
121
  const result = await resolver.body({ input: { userId: "1" } });
102
122
 
103
- expect(tailordbMock.executedQueries[0].query).toContain("SELECT");
123
+ expect(db.executedQueries[0].query).toContain("SELECT");
104
124
  });
105
125
  ```
106
126
 
107
127
  ### Workflow Mock
108
128
 
109
- The environment auto-injects `tailor.workflow.triggerJobFunction`. Use `workflowMock` to configure job responses:
129
+ `.trigger()` runs the real job bodies locally out of the box (see [Running a full workflow locally](#running-a-full-workflow-locally)). Acquire `mockWorkflow()` when you want to override responses with `setJobHandler` / `enqueueResult` or assert on `triggeredJobs`:
110
130
 
111
131
  ```typescript
112
- import { workflowMock } from "@tailor-platform/sdk/vitest";
113
-
114
- beforeEach(() => {
115
- workflowMock.reset();
116
- });
132
+ import { mockWorkflow } from "@tailor-platform/sdk/vitest";
117
133
 
118
134
  test("workflow triggers jobs", async () => {
119
- workflowMock.setJobHandler((jobName, args) => {
135
+ using wf = mockWorkflow();
136
+ wf.setJobHandler((jobName, args) => {
120
137
  if (jobName === "validate-order") return { valid: true };
121
138
  if (jobName === "process-payment") return { txnId: "txn-1" };
122
139
  return null;
@@ -124,52 +141,50 @@ test("workflow triggers jobs", async () => {
124
141
 
125
142
  const result = await main({ input: { orderId: "o-1" } });
126
143
 
127
- expect(workflowMock.triggeredJobs).toEqual([
144
+ expect(wf.triggeredJobs).toEqual([
128
145
  { jobName: "validate-order", args: { orderId: "o-1" } },
129
146
  { jobName: "process-payment", args: { orderId: "o-1" } },
130
147
  ]);
131
148
  });
132
149
  ```
133
150
 
134
- `workflowMock` also supports order-based responses:
151
+ `mockWorkflow()` also supports order-based responses:
135
152
 
136
153
  ```typescript
154
+ using wf = mockWorkflow();
155
+
137
156
  // Single response for the next triggerJobFunction call
138
- workflowMock.enqueueResult({ valid: true });
157
+ wf.enqueueResult({ valid: true });
139
158
 
140
159
  // Multiple responses for subsequent calls (FIFO)
141
- workflowMock.enqueueResults({ valid: true }, { txnId: "txn-1" });
160
+ wf.enqueueResults({ valid: true }, { txnId: "txn-1" });
142
161
  ```
143
162
 
144
163
  ### SecretManager Mock
145
164
 
146
165
  ```typescript
147
- import { secretmanagerMock } from "@tailor-platform/sdk/vitest";
148
-
149
- beforeEach(() => secretmanagerMock.reset());
166
+ import { mockSecretmanager } from "@tailor-platform/sdk/vitest";
150
167
 
151
168
  test("reads secrets from vault", async () => {
152
- secretmanagerMock.setSecrets({
169
+ using sm = mockSecretmanager();
170
+ sm.setSecrets({
153
171
  "my-vault": { API_KEY: "sk-123", DB_PASS: "secret" },
154
172
  });
155
173
 
156
174
  const key = await tailor.secretmanager.getSecret("my-vault", "API_KEY");
157
175
  expect(key).toBe("sk-123");
158
- expect(secretmanagerMock.calls).toEqual([
159
- { method: "getSecret", vault: "my-vault", name: "API_KEY" },
160
- ]);
176
+ expect(sm.calls).toEqual([{ method: "getSecret", vault: "my-vault", name: "API_KEY" }]);
161
177
  });
162
178
  ```
163
179
 
164
180
  ### AuthConnection Mock
165
181
 
166
182
  ```typescript
167
- import { authconnectionMock } from "@tailor-platform/sdk/vitest";
168
-
169
- beforeEach(() => authconnectionMock.reset());
183
+ import { mockAuthconnection } from "@tailor-platform/sdk/vitest";
170
184
 
171
185
  test("returns configured token", async () => {
172
- authconnectionMock.setTokens({
186
+ using ac = mockAuthconnection();
187
+ ac.setTokens({
173
188
  google: { access_token: "ya29.xxx", expires_in: 3600 },
174
189
  });
175
190
 
@@ -183,12 +198,11 @@ When no token is configured for a connection, it returns `{ access_token: "mock-
183
198
  ### IDP Mock
184
199
 
185
200
  ```typescript
186
- import { idpMock } from "@tailor-platform/sdk/vitest";
187
-
188
- beforeEach(() => idpMock.reset());
201
+ import { mockIdp } from "@tailor-platform/sdk/vitest";
189
202
 
190
203
  test("resolver-based", async () => {
191
- idpMock.setResolver((method, args) => {
204
+ using idp = mockIdp();
205
+ idp.setResolver((method, args) => {
192
206
  if (method === "user") return { id: "u-1", name: "alice", disabled: false };
193
207
  return null; // falls back to defaults
194
208
  });
@@ -199,31 +213,31 @@ test("resolver-based", async () => {
199
213
  });
200
214
 
201
215
  test("queue-based", async () => {
202
- idpMock.enqueueResult({ id: "u-1", name: "alice", disabled: false });
216
+ using idp = mockIdp();
217
+ idp.enqueueResult({ id: "u-1", name: "alice", disabled: false });
203
218
 
204
219
  const client = new tailor.idp.Client({ namespace: "my-ns" });
205
220
  const user = await client.user("u-1");
206
221
  expect(user.name).toBe("alice");
207
- expect(idpMock.calls).toMatchObject([{ method: "user", namespace: "my-ns" }]);
222
+ expect(idp.calls).toMatchObject([{ method: "user", namespace: "my-ns" }]);
208
223
  });
209
224
  ```
210
225
 
211
226
  ### File Mock
212
227
 
213
228
  ```typescript
214
- import { fileMock } from "@tailor-platform/sdk/vitest";
215
-
216
- beforeEach(() => fileMock.reset());
229
+ import { mockFile } from "@tailor-platform/sdk/vitest";
217
230
 
218
231
  test("mock file download", async () => {
219
- fileMock.enqueueResult({
232
+ using file = mockFile();
233
+ file.enqueueResult({
220
234
  data: new Uint8Array([1, 2, 3]),
221
235
  metadata: { contentType: "image/png", fileSize: 3, sha256sum: "abc", lastUploadedAt: "" },
222
236
  });
223
237
 
224
238
  const result = await tailordb.file.download("ns", "Doc", "attachment", "r-1");
225
239
  expect(result.data).toEqual(new Uint8Array([1, 2, 3]));
226
- expect(fileMock.calls).toMatchObject([{ method: "download", recordId: "r-1" }]);
240
+ expect(file.calls).toMatchObject([{ method: "download", recordId: "r-1" }]);
227
241
  });
228
242
  ```
229
243
 
@@ -231,13 +245,14 @@ For `downloadStream`, enqueue a `FileDownloadStreamResponse` object with a `Read
231
245
 
232
246
  ```typescript
233
247
  test("mock file download stream", async () => {
248
+ using file = mockFile();
234
249
  const body = new ReadableStream({
235
250
  start(controller) {
236
251
  controller.enqueue(new Uint8Array([1, 2, 3]));
237
252
  controller.close();
238
253
  },
239
254
  });
240
- fileMock.enqueueResult({
255
+ file.enqueueResult({
241
256
  body,
242
257
  metadata: { contentType: "image/png", fileSize: 3, sha256sum: "abc", lastUploadedAt: "" },
243
258
  });
@@ -251,7 +266,8 @@ For the deprecated `openDownloadStream`, enqueue an iterable of `StreamValue` it
251
266
 
252
267
  ```typescript
253
268
  test("mock file download stream (deprecated openDownloadStream)", async () => {
254
- fileMock.enqueueResult([
269
+ using file = mockFile();
270
+ file.enqueueResult([
255
271
  {
256
272
  type: "metadata",
257
273
  metadata: { contentType: "image/png", fileSize: 3, sha256sum: "abc" },
@@ -271,19 +287,18 @@ test("mock file download stream (deprecated openDownloadStream)", async () => {
271
287
  ### Iconv Mock
272
288
 
273
289
  ```typescript
274
- import { iconvMock } from "@tailor-platform/sdk/vitest";
275
-
276
- beforeEach(() => iconvMock.reset());
290
+ import { mockIconv } from "@tailor-platform/sdk/vitest";
277
291
 
278
292
  test("mock encoding conversion", () => {
279
- iconvMock.setResolver((method, args) => {
293
+ using iconv = mockIconv();
294
+ iconv.setResolver((method, args) => {
280
295
  if (method === "decode") return "decoded-text";
281
296
  return null; // falls back to default empty string
282
297
  });
283
298
 
284
299
  const result = tailor.iconv.decode(new Uint8Array([0x48, 0x69]), "UTF-8");
285
300
  expect(result).toBe("decoded-text");
286
- expect(iconvMock.calls).toMatchObject([{ method: "decode" }]);
301
+ expect(iconv.calls).toMatchObject([{ method: "decode" }]);
287
302
  });
288
303
  ```
289
304
 
@@ -298,7 +313,7 @@ export default defineConfig({
298
313
  });
299
314
  ```
300
315
 
301
- This makes `tailor.secretmanager.getSecret("vault", "key")` return the values defined in your config. You can still override with `secretmanagerMock.setSecrets()` in individual tests.
316
+ This makes `tailor.secretmanager.getSecret("vault", "key")` return the values defined in your config. You can still override with `using sm = mockSecretmanager(); sm.setSecrets(...)` in individual tests: a per-test override applies only within that test, and the config-loaded secrets remain available to every other test.
302
317
 
303
318
  ### Per-Project Configuration
304
319
 
@@ -371,7 +386,7 @@ describe("add resolver", () => {
371
386
 
372
387
  Stub the global `tailordb.Client` and queue raw query results in order. Best for resolvers that issue a short, predictable query sequence:
373
388
 
374
- > If you are running with the [`tailor-runtime` Vitest environment](#runtime-environment-emulation-beta), `tailordb.Client` is auto-injected drive it with `tailordbMock` instead of `vi.stubGlobal()`.
389
+ > If you are running with the [`tailor-runtime` Vitest environment](#runtime-environment-emulation-beta), acquire `using db = mockTailordb()` to install and drive the mock `tailordb.Client` instead of `vi.stubGlobal()`.
375
390
 
376
391
  ```typescript
377
392
  import { unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
@@ -519,25 +534,22 @@ describe("upsertUsers resolver", () => {
519
534
  });
520
535
  ```
521
536
 
522
- Reach for [`tailordbMock`](#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.
537
+ 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.
523
538
 
524
539
  #### Resolvers that resume a workflow
525
540
 
526
- Resolvers that call `waitPoint.resolve(...)` delegate to `tailor.workflow.resolve` at runtime. With the `tailor-runtime` environment active, use `workflowMock.setResolveHandler` to drive the user-supplied callback and inspect `workflowMock.resolveCalls`:
541
+ Resolvers that call `waitPoint.resolve(...)` delegate to `tailor.workflow.resolve` at runtime. With the `tailor-runtime` environment active, use `mockWorkflow().setResolveHandler` to drive the user-supplied callback and inspect `resolveCalls`:
527
542
 
528
543
  ```typescript
529
544
  import { unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
530
- import { workflowMock } from "@tailor-platform/sdk/vitest";
531
- import { beforeEach, describe, expect, test } from "vitest";
545
+ import { mockWorkflow } from "@tailor-platform/sdk/vitest";
546
+ import { describe, expect, test } from "vitest";
532
547
  import resolver from "./resolveApproval";
533
548
 
534
549
  describe("resolveApproval resolver", () => {
535
- beforeEach(() => {
536
- workflowMock.reset();
537
- });
538
-
539
550
  test("resolves approval with approved=true", async () => {
540
- workflowMock.setResolveHandler((_executionId, _key, callback) => {
551
+ using wf = mockWorkflow();
552
+ wf.setResolveHandler((_executionId, _key, callback) => {
541
553
  const result = callback({ message: "Please approve order order-1", orderId: "order-1" });
542
554
  expect(result).toEqual({ approved: true });
543
555
  });
@@ -549,7 +561,7 @@ describe("resolveApproval resolver", () => {
549
561
  });
550
562
 
551
563
  expect(result).toEqual({ resolved: true });
552
- expect(workflowMock.resolveCalls).toEqual([{ executionId: "exec-1", key: "approval" }]);
564
+ expect(wf.resolveCalls).toEqual([{ executionId: "exec-1", key: "approval" }]);
553
565
  });
554
566
  });
555
567
  ```
@@ -660,32 +672,30 @@ describe("fulfillOrder", () => {
660
672
 
661
673
  #### Jobs that wait on approval
662
674
 
663
- `.wait()` calls delegate to `tailor.workflow.wait`. With the `tailor-runtime` environment active, use `workflowMock.setWaitHandler` to drive each branch and inspect `workflowMock.waitCalls`:
675
+ `.wait()` calls delegate to `tailor.workflow.wait`. With the `tailor-runtime` environment active, use `mockWorkflow().setWaitHandler` to drive each branch and inspect `waitCalls`:
664
676
 
665
677
  ```typescript
666
- import { workflowMock } from "@tailor-platform/sdk/vitest";
667
- import { beforeEach, describe, expect, test } from "vitest";
678
+ import { mockWorkflow } from "@tailor-platform/sdk/vitest";
679
+ import { describe, expect, test } from "vitest";
668
680
  import { processWithApproval } from "./approval";
669
681
 
670
682
  describe("processWithApproval", () => {
671
- beforeEach(() => {
672
- workflowMock.reset();
673
- });
674
-
675
683
  test("returns approved status when .wait() resolves positively", async () => {
676
- workflowMock.setWaitHandler({ approved: true });
684
+ using wf = mockWorkflow();
685
+ wf.setWaitHandler({ approved: true });
677
686
 
678
687
  const result = await processWithApproval.body({ orderId: "order-1" }, { env: {} });
679
688
 
680
689
  expect(result).toEqual({ orderId: "order-1", status: "approved" });
681
- expect(workflowMock.waitCalls[0]).toEqual({
690
+ expect(wf.waitCalls[0]).toEqual({
682
691
  key: "approval",
683
692
  payload: { message: "Please approve order order-1", orderId: "order-1" },
684
693
  });
685
694
  });
686
695
 
687
696
  test("returns rejected status when .wait() resolves negatively", async () => {
688
- workflowMock.setWaitHandler({ approved: false });
697
+ using wf = mockWorkflow();
698
+ wf.setWaitHandler({ approved: false });
689
699
 
690
700
  const result = await processWithApproval.body({ orderId: "order-2" }, { env: {} });
691
701
 
@@ -698,19 +708,14 @@ describe("processWithApproval", () => {
698
708
 
699
709
  #### Running a full workflow locally
700
710
 
701
- To exercise the full chain with real job bodies, call `workflow.mainJob.trigger()`. Dependent jobs run their real `.body()` functions. Use `workflowMock.setEnv()` to control the env value that triggered jobs receive in their context (defaults to `{}`):
711
+ To exercise the full chain with real job bodies, just call `workflow.mainJob.trigger()` — no `mockWorkflow()` needed. Dependent jobs run their real `.body()` functions, and trigger args/results cross the same JSON boundary as the platform, so a non-serializable payload fails the test exactly as it would in production:
702
712
 
703
713
  ```typescript
704
- import { workflowMock } from "@tailor-platform/sdk/vitest";
705
- import { afterEach, describe, expect, test } from "vitest";
714
+ import { describe, expect, test } from "vitest";
706
715
  import workflow from "./order-fulfillment";
707
716
 
708
717
  describe("order-fulfillment workflow", () => {
709
- afterEach(() => workflowMock.reset());
710
-
711
718
  test("mainJob.trigger() executes all jobs", async () => {
712
- workflowMock.setEnv({ PAYMENT_GATEWAY: "stripe" });
713
-
714
719
  const result = await workflow.mainJob.trigger({ orderId: "order-3", amount: 300 });
715
720
 
716
721
  expect(result).toMatchObject({ confirmed: true, paymentStatus: "completed" });
@@ -718,6 +723,8 @@ describe("order-fulfillment workflow", () => {
718
723
  });
719
724
  ```
720
725
 
726
+ Acquire `mockWorkflow()` only when you need to override a dependent job with `wf.setJobHandler(...)` / `wf.enqueueResult(...)` (the rest still run their real bodies), control the env via `wf.setEnv(...)`, or assert on `wf.triggeredJobs`.
727
+
721
728
  **Use when:** you want to verify orchestration end to end without the cost of a real deployment.
722
729
 
723
730
  ## End-to-End (E2E) Tests
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailor-platform/sdk",
3
- "version": "1.58.0",
3
+ "version": "1.60.0",
4
4
  "description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -182,7 +182,7 @@
182
182
  "ts-cron-validator": "1.1.5",
183
183
  "tsx": "4.22.4",
184
184
  "type-fest": "5.7.0",
185
- "undici": "8.3.0",
185
+ "undici": "8.4.0",
186
186
  "xdg-basedir": "5.1.0",
187
187
  "zod": "4.4.3"
188
188
  },
@@ -221,11 +221,11 @@
221
221
  },
222
222
  "scripts": {
223
223
  "test": "vitest",
224
- "test:unit": "vitest --project=unit",
224
+ "test:unit": "vitest --project=unit*",
225
225
  "test:e2e": "vitest --project=e2e",
226
226
  "test:coverage": "vitest --coverage",
227
- "docs:check": "vitest run --project=unit src/cli/docs.test.ts",
228
- "docs:update": "POLITTY_DOCS_UPDATE=true vitest run --project=unit src/cli/docs.test.ts",
227
+ "docs:check": "vitest run --project=unit* src/cli/docs.test.ts",
228
+ "docs:update": "POLITTY_DOCS_UPDATE=true vitest run --project=unit* src/cli/docs.test.ts",
229
229
  "build": "tsdown",
230
230
  "lint": "oxlint --type-aware .",
231
231
  "check:public-api-jsdoc": "tsx scripts/check-public-api-jsdoc.ts",