@tailor-platform/sdk 2.0.0-next.1 → 2.0.0-next.2
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.
- package/CHANGELOG.md +56 -0
- package/dist/application-Dtqap5jM.mjs +3 -0
- package/dist/{client-z_oHGVNy.mjs → application-XuMWK4eq.mjs} +5861 -20
- package/dist/application-XuMWK4eq.mjs.map +1 -0
- package/dist/cli/index.mjs +179 -122
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +75 -16
- package/dist/cli/lib.mjs +5 -6
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/completion/zsh-worker.zsh +105 -26
- package/dist/configure/index.d.mts +9 -8
- package/dist/configure/index.mjs +56 -19
- package/dist/configure/index.mjs.map +1 -1
- package/dist/context-Bd266-ru.mjs.map +1 -1
- package/dist/{context-BuuIb8CC.d.mts → context-C2lEi9uw.d.mts} +7 -28
- package/dist/{crashreport-pr6Rhvza.mjs → crashreport-BMWcxeSE.mjs} +1 -1
- package/dist/{crashreport-BsjAkFWw.mjs → crashreport-DFq-vsU0.mjs} +5 -7
- package/dist/{crashreport-BsjAkFWw.mjs.map → crashreport-DFq-vsU0.mjs.map} +1 -1
- package/dist/{file-_oUZo76X.mjs → file-BbdFGdMV.mjs} +2 -10
- package/dist/file-BbdFGdMV.mjs.map +1 -0
- package/dist/{file-BB8Vs9O_.d.mts → file-Dq3NIt_F.d.mts} +3 -42
- package/dist/{file-utils-DcyIPFQh.mjs → file-utils-CYZnO1pX.mjs} +5 -5
- package/dist/file-utils-CYZnO1pX.mjs.map +1 -0
- package/dist/{globals-Crz8o65k.mjs → globals-Cf0sxIt8.mjs} +2 -2
- package/dist/{globals-Crz8o65k.mjs.map → globals-Cf0sxIt8.mjs.map} +1 -1
- package/dist/{http-adapter.generated-WgMnb7Sb.d.mts → http-adapter.generated-DFsXDdm5.d.mts} +11 -10
- package/dist/{index-BlpzXncY.d.mts → index-BI-_j9Z3.d.mts} +29 -32
- package/dist/{index-DjUdWlzf.d.mts → index-C4JirJH8.d.mts} +2 -2
- package/dist/{index-5vPyRu1y.d.mts → index-CZfWhr0a.d.mts} +2 -2
- package/dist/{index-CK7u9isy.d.mts → index-Cg8VKAdN.d.mts} +4 -4
- package/dist/{index-B7AKc18V.d.mts → index-DYRjoLXD.d.mts} +2 -2
- package/dist/{index-ZePLwxw7.d.mts → index-lFpcjHPU.d.mts} +8 -15
- package/dist/{index-CNYe5lnW.d.mts → index-nW7hE6oE.d.mts} +2 -2
- package/dist/{mock-BjFj5o1I.mjs → mock-FPxmnt-y.mjs} +4 -49
- package/dist/{mock-BjFj5o1I.mjs.map → mock-FPxmnt-y.mjs.map} +1 -1
- package/dist/plugin/builtin/enum-constants/index.d.mts +1 -1
- package/dist/plugin/builtin/file-utils/index.d.mts +1 -1
- package/dist/plugin/builtin/file-utils/index.mjs +1 -1
- package/dist/plugin/builtin/kysely-type/index.d.mts +1 -1
- package/dist/plugin/builtin/seed/index.d.mts +1 -1
- package/dist/plugin/builtin/seed/index.mjs +1 -1
- package/dist/plugin/index.d.mts +4 -3
- package/dist/plugin/index.mjs.map +1 -1
- package/dist/{registry-DdsYlL_P.mjs → registry-DH4m7eYo.mjs} +4 -2
- package/dist/registry-DH4m7eYo.mjs.map +1 -0
- package/dist/runtime/context.d.mts +1 -1
- package/dist/runtime/file.d.mts +2 -2
- package/dist/runtime/file.mjs +2 -2
- package/dist/runtime/globals.d.mts +6 -6
- package/dist/runtime/index.d.mts +4 -4
- package/dist/runtime/index.mjs +2 -2
- package/dist/runtime/workflow.d.mts +2 -2
- package/dist/runtime/workflow.mjs +1 -1
- package/dist/{runtime-n9NCkjee.mjs → runtime-CY4JvrDj.mjs} +777 -234
- package/dist/runtime-CY4JvrDj.mjs.map +1 -0
- package/dist/{schema-BhkpP5Hw.mjs → schema-Dtw9Orye.mjs} +16 -13
- package/dist/schema-Dtw9Orye.mjs.map +1 -0
- package/dist/{secret-file-DBqZhjFQ.mjs → secret-file-VSVGy1V0.mjs} +27 -2
- package/dist/{secret-file-DBqZhjFQ.mjs.map → secret-file-VSVGy1V0.mjs.map} +1 -1
- package/dist/{seed-jf3008-h.mjs → seed-izIEyP3z.mjs} +3 -4
- package/dist/seed-izIEyP3z.mjs.map +1 -0
- package/dist/service-DCqIWibD.mjs +3 -0
- package/dist/{service-DU1mVzri.mjs → service-DU1mVzri2.mjs} +1 -1
- package/dist/service-DU1mVzri2.mjs.map +1 -0
- package/dist/{service-CCL8ruDf.mjs → service-DjyqbCaJ.mjs} +7 -7
- package/dist/{service-CCL8ruDf.mjs.map → service-DjyqbCaJ.mjs.map} +1 -1
- package/dist/test-env-key-D7UkZp99.mjs +75 -0
- package/dist/test-env-key-D7UkZp99.mjs.map +1 -0
- package/dist/{types-ClhIrW_C.mjs → types-74etvaxy.mjs} +1 -1
- package/dist/{types-DhO_VEZd.d.mts → types-BDRml5C3.d.mts} +12 -12
- package/dist/{types-B2RpYyA_.mjs → types-BQijbo4m.mjs} +9 -9
- package/dist/types-BQijbo4m.mjs.map +1 -0
- package/dist/{types-DwDgacni.d.mts → types-BX4q6Mo6.d.mts} +3 -2
- package/dist/types-BZ7QKVE8.d.mts +21 -0
- package/dist/{types-DCUhgpyI.d.mts → types-CdcQh4Z2.d.mts} +5 -76
- package/dist/utils/test/index.d.mts +6 -15
- package/dist/utils/test/index.mjs +4 -13
- package/dist/utils/test/index.mjs.map +1 -1
- package/dist/vitest/environment.mjs +1 -1
- package/dist/vitest/index.d.mts +2 -2
- package/dist/vitest/index.mjs +4 -4
- package/dist/vitest/setup.mjs +2 -2
- package/dist/{workflow-DgemCAz3.mjs → workflow-BOmaZwwG.mjs} +8 -3
- package/dist/workflow-BOmaZwwG.mjs.map +1 -0
- package/dist/{workflow-BbKvGLQg.d.mts → workflow-BVy4XWjS.d.mts} +15 -10
- package/dist/{workflow.generated-DtQwEo-x.d.mts → workflow.generated-ClEjBYhm.d.mts} +3 -3
- package/docs/cli/executor.md +53 -0
- package/docs/cli/setup.md +35 -33
- package/docs/cli/workflow.md +157 -20
- package/docs/cli-reference.md +26 -20
- package/docs/github-actions.md +29 -16
- package/docs/migration/v2.md +475 -0
- package/docs/runtime.md +1 -1
- package/docs/services/auth.md +12 -12
- package/docs/services/executor.md +3 -3
- package/docs/services/resolver.md +6 -6
- package/docs/services/tailordb.md +14 -12
- package/docs/services/workflow.md +4 -4
- package/docs/testing.md +59 -47
- package/package.json +7 -7
- package/dist/application-DB2r36Et.mjs +0 -3
- package/dist/application-DqS1yBg3.mjs +0 -5680
- package/dist/application-DqS1yBg3.mjs.map +0 -1
- package/dist/client-Dbohmtkv.mjs +0 -3
- package/dist/client-z_oHGVNy.mjs.map +0 -1
- package/dist/file-_oUZo76X.mjs.map +0 -1
- package/dist/file-utils-DcyIPFQh.mjs.map +0 -1
- package/dist/job-fuc3j1Ma.mjs +0 -53
- package/dist/job-fuc3j1Ma.mjs.map +0 -1
- package/dist/registry-DdsYlL_P.mjs.map +0 -1
- package/dist/runtime-n9NCkjee.mjs.map +0 -1
- package/dist/schema-BhkpP5Hw.mjs.map +0 -1
- package/dist/seed-jf3008-h.mjs.map +0 -1
- package/dist/service-D6yonf2I.mjs +0 -3
- package/dist/service-DU1mVzri.mjs.map +0 -1
- package/dist/test-env-key-D9kM6ETE.mjs +0 -49
- package/dist/test-env-key-D9kM6ETE.mjs.map +0 -1
- package/dist/types-B2RpYyA_.mjs.map +0 -1
- package/dist/workflow-DgemCAz3.mjs.map +0 -1
|
@@ -260,7 +260,7 @@ Add hooks to execute functions during data creation or update. Hooks receive thr
|
|
|
260
260
|
|
|
261
261
|
- `value`: User input if provided, otherwise existing value on update or null on create
|
|
262
262
|
- `data`: Entire record data (for accessing other field values)
|
|
263
|
-
- `
|
|
263
|
+
- `invoker`: Principal performing the operation
|
|
264
264
|
|
|
265
265
|
#### Field-level Hooks
|
|
266
266
|
|
|
@@ -268,7 +268,7 @@ Set hooks directly on individual fields:
|
|
|
268
268
|
|
|
269
269
|
```typescript
|
|
270
270
|
db.string().hooks({
|
|
271
|
-
create: ({
|
|
271
|
+
create: ({ invoker }) => invoker?.id ?? "",
|
|
272
272
|
update: ({ value }) => value,
|
|
273
273
|
});
|
|
274
274
|
```
|
|
@@ -323,7 +323,7 @@ Add validation rules to fields. Validators receive three arguments (executed aft
|
|
|
323
323
|
|
|
324
324
|
- `value`: Field value after hook transformation
|
|
325
325
|
- `data`: Entire record data after hook transformations (for accessing other field values)
|
|
326
|
-
- `
|
|
326
|
+
- `invoker`: Principal performing the operation
|
|
327
327
|
|
|
328
328
|
Validators return `true` for success, `false` for failure. Use array form `[validator, errorMessage]` for custom error messages.
|
|
329
329
|
|
|
@@ -409,6 +409,8 @@ export const user = db.type("User", {
|
|
|
409
409
|
});
|
|
410
410
|
```
|
|
411
411
|
|
|
412
|
+
`db.fields.timestamps()` adds non-null `createdAt` and `updatedAt` datetime fields. Both fields are populated when a record is created; provided values are preserved so seed data can use historical timestamps. `updatedAt` is also refreshed automatically when a record is updated.
|
|
413
|
+
|
|
412
414
|
## Type Modifiers
|
|
413
415
|
|
|
414
416
|
### Composite Indexes
|
|
@@ -516,8 +518,8 @@ const user = db.type("User", {
|
|
|
516
518
|
...db.fields.timestamps(),
|
|
517
519
|
});
|
|
518
520
|
|
|
519
|
-
// Pick id and
|
|
520
|
-
user.pickFields(["id", "createdAt"], { optional: true });
|
|
521
|
+
// Pick id, createdAt, and updatedAt, making them optional
|
|
522
|
+
user.pickFields(["id", "createdAt", "updatedAt"], { optional: true });
|
|
521
523
|
```
|
|
522
524
|
|
|
523
525
|
Available options:
|
|
@@ -532,8 +534,8 @@ Available options:
|
|
|
532
534
|
Return all fields except the specified ones:
|
|
533
535
|
|
|
534
536
|
```typescript
|
|
535
|
-
// All fields except id and
|
|
536
|
-
user.omitFields(["id", "createdAt"]);
|
|
537
|
+
// All fields except id, createdAt, and updatedAt
|
|
538
|
+
user.omitFields(["id", "createdAt", "updatedAt"]);
|
|
537
539
|
```
|
|
538
540
|
|
|
539
541
|
#### Common Pattern: Input Schema Composition
|
|
@@ -548,9 +550,9 @@ export default createResolver({
|
|
|
548
550
|
name: "createUser",
|
|
549
551
|
operation: "mutation",
|
|
550
552
|
input: {
|
|
551
|
-
// id/createdAt are optional (auto-generated), other fields are required
|
|
552
|
-
...user.pickFields(["id", "createdAt"], { optional: true }),
|
|
553
|
-
...user.omitFields(["id", "createdAt"]),
|
|
553
|
+
// id/createdAt/updatedAt are optional (auto-generated), other fields are required
|
|
554
|
+
...user.pickFields(["id", "createdAt", "updatedAt"], { optional: true }),
|
|
555
|
+
...user.omitFields(["id", "createdAt", "updatedAt"]),
|
|
554
556
|
},
|
|
555
557
|
output: t.object({ id: t.uuid() }),
|
|
556
558
|
body: async (context) => {
|
|
@@ -567,8 +569,8 @@ import { t } from "@tailor-platform/sdk";
|
|
|
567
569
|
import { invoice } from "../../tailordb/invoice";
|
|
568
570
|
|
|
569
571
|
const schemaType = t.object({
|
|
570
|
-
...invoice.pickFields(["id", "createdAt"], { optional: true }),
|
|
571
|
-
...invoice.omitFields(["id", "createdAt", "invoiceNumber", "sequentialId"]),
|
|
572
|
+
...invoice.pickFields(["id", "createdAt", "updatedAt"], { optional: true }),
|
|
573
|
+
...invoice.omitFields(["id", "createdAt", "updatedAt", "invoiceNumber", "sequentialId"]),
|
|
572
574
|
});
|
|
573
575
|
```
|
|
574
576
|
|
|
@@ -180,8 +180,8 @@ export const processOrder = createWorkflowJob({
|
|
|
180
180
|
name: "process-order",
|
|
181
181
|
body: (input: { customerId: string }, { env, invoker }) => {
|
|
182
182
|
// `env` contains values from `tailor.config.ts` -> `env`.
|
|
183
|
-
// `invoker` is the principal running this job,
|
|
184
|
-
//
|
|
183
|
+
// `invoker` is the principal running this job, or the machine user
|
|
184
|
+
// configured through the trigger `invoker` option; `null` for anonymous calls.
|
|
185
185
|
// Trigger other jobs by calling .trigger() on the job object.
|
|
186
186
|
const customer = fetchCustomer.trigger({
|
|
187
187
|
customerId: input.customerId,
|
|
@@ -356,7 +356,7 @@ export default createWorkflow({
|
|
|
356
356
|
You can start a workflow execution from a resolver using `workflow.trigger()`.
|
|
357
357
|
|
|
358
358
|
- `workflow.trigger(args, options?)` returns a workflow run ID (`Promise<string>`).
|
|
359
|
-
- To run with machine-user permissions, pass `{
|
|
359
|
+
- To run with machine-user permissions, pass `{ invoker: "<machine-user>" }`. The name is type-narrowed to the machine users defined in your auth config.
|
|
360
360
|
|
|
361
361
|
```typescript
|
|
362
362
|
import { createResolver, t } from "@tailor-platform/sdk";
|
|
@@ -372,7 +372,7 @@ export default createResolver({
|
|
|
372
372
|
body: async ({ input }) => {
|
|
373
373
|
const workflowRunId = await orderProcessingWorkflow.trigger(
|
|
374
374
|
{ orderId: input.orderId, customerId: input.customerId },
|
|
375
|
-
{
|
|
375
|
+
{ invoker: "manager-machine-user" },
|
|
376
376
|
);
|
|
377
377
|
|
|
378
378
|
return { workflowRunId };
|
package/docs/testing.md
CHANGED
|
@@ -11,15 +11,15 @@ Lean on unit tests for the day-to-day feedback loop — they run fast and exerci
|
|
|
11
11
|
|
|
12
12
|
Unit-test entrypoints exposed by the SDK:
|
|
13
13
|
|
|
14
|
-
- `resolver.body({ input,
|
|
15
|
-
- `workflowJob.body(input, { env })` — invoke a workflow job body directly
|
|
14
|
+
- `resolver.body({ input, caller, invoker, env })` — invoke a resolver
|
|
15
|
+
- `workflowJob.body(input, { env, invoker })` — invoke a workflow job body directly
|
|
16
16
|
- `workflowJob.trigger(input)` — chain a workflow job through the workflow runtime
|
|
17
17
|
- `runWorkflowLocally(workflow, args)` — run a workflow chain locally with real job bodies
|
|
18
|
-
- `executor.operation.body(args)` — invoke a function-kind executor
|
|
18
|
+
- `executor.operation.body({ ...args, invoker })` — invoke a function-kind executor
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
For anonymous direct calls:
|
|
21
21
|
|
|
22
|
-
- `
|
|
22
|
+
- Pass `null` for anonymous `caller` / `invoker` context in direct unit tests.
|
|
23
23
|
|
|
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
|
|
|
@@ -99,7 +99,12 @@ test("resolver queries the database", async () => {
|
|
|
99
99
|
[], // COMMIT
|
|
100
100
|
);
|
|
101
101
|
|
|
102
|
-
const result = await resolver.body({
|
|
102
|
+
const result = await resolver.body({
|
|
103
|
+
input: { email: "test@example.com" },
|
|
104
|
+
caller: null,
|
|
105
|
+
invoker: null,
|
|
106
|
+
env: {},
|
|
107
|
+
});
|
|
103
108
|
|
|
104
109
|
expect(result).toEqual({ oldAge: 30, newAge: 31 });
|
|
105
110
|
expect(db.executedQueries).toHaveLength(3);
|
|
@@ -121,7 +126,12 @@ test("content-based mock", async () => {
|
|
|
121
126
|
return [];
|
|
122
127
|
});
|
|
123
128
|
|
|
124
|
-
const result = await resolver.body({
|
|
129
|
+
const result = await resolver.body({
|
|
130
|
+
input: { userId: "1" },
|
|
131
|
+
caller: null,
|
|
132
|
+
invoker: null,
|
|
133
|
+
env: {},
|
|
134
|
+
});
|
|
125
135
|
|
|
126
136
|
expect(db.executedQueries[0].query).toContain("SELECT");
|
|
127
137
|
});
|
|
@@ -163,6 +173,8 @@ wf.enqueueResult({ valid: true });
|
|
|
163
173
|
wf.enqueueResults({ valid: true }, { txnId: "txn-1" });
|
|
164
174
|
```
|
|
165
175
|
|
|
176
|
+
Use `wf.setEnv(...)` when locally-run workflow job bodies need configuration values. Per-run `runWorkflowLocally(..., { env })` options take precedence over the mock's env.
|
|
177
|
+
|
|
166
178
|
### SecretManager Mock
|
|
167
179
|
|
|
168
180
|
```typescript
|
|
@@ -265,28 +277,6 @@ test("mock file download stream", async () => {
|
|
|
265
277
|
});
|
|
266
278
|
```
|
|
267
279
|
|
|
268
|
-
For the deprecated `openDownloadStream`, enqueue an iterable of `StreamValue` items — `metadata`, one or more `chunk` items, and a terminal `complete`. Raw `Uint8Array` / `ArrayBuffer` chunks are rejected so tests stay aligned with the platform's structured stream contract.
|
|
269
|
-
|
|
270
|
-
```typescript
|
|
271
|
-
test("mock file download stream (deprecated openDownloadStream)", async () => {
|
|
272
|
-
using file = mockFile();
|
|
273
|
-
file.enqueueResult([
|
|
274
|
-
{
|
|
275
|
-
type: "metadata",
|
|
276
|
-
metadata: { contentType: "image/png", fileSize: 3, sha256sum: "abc" },
|
|
277
|
-
},
|
|
278
|
-
{ type: "chunk", data: new Uint8Array([1, 2]), position: 0 },
|
|
279
|
-
{ type: "chunk", data: new Uint8Array([3]), position: 2 },
|
|
280
|
-
{ type: "complete" },
|
|
281
|
-
]);
|
|
282
|
-
|
|
283
|
-
const stream = await tailordb.file.openDownloadStream("ns", "Doc", "attachment", "r-1");
|
|
284
|
-
const items = [];
|
|
285
|
-
for await (const item of stream) items.push(item);
|
|
286
|
-
expect(items).toHaveLength(4);
|
|
287
|
-
});
|
|
288
|
-
```
|
|
289
|
-
|
|
290
280
|
### Iconv Mock
|
|
291
281
|
|
|
292
282
|
```typescript
|
|
@@ -367,7 +357,6 @@ Unit tests call `.body()` (or `.trigger()`) directly on a resolver, workflow job
|
|
|
367
357
|
For pure logic with no external dependencies, invoke `.body()` directly:
|
|
368
358
|
|
|
369
359
|
```typescript
|
|
370
|
-
import { unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
|
|
371
360
|
import { describe, expect, test } from "vitest";
|
|
372
361
|
import resolver from "../src/resolver/add";
|
|
373
362
|
|
|
@@ -375,7 +364,8 @@ describe("add resolver", () => {
|
|
|
375
364
|
test("adds two numbers", async () => {
|
|
376
365
|
const result = await resolver.body({
|
|
377
366
|
input: { left: 1, right: 2 },
|
|
378
|
-
|
|
367
|
+
caller: null,
|
|
368
|
+
invoker: null,
|
|
379
369
|
env: {},
|
|
380
370
|
});
|
|
381
371
|
expect(result).toBe(3);
|
|
@@ -392,7 +382,6 @@ Stub the global `tailordb.Client` and queue raw query results in order. Best for
|
|
|
392
382
|
> 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()`.
|
|
393
383
|
|
|
394
384
|
```typescript
|
|
395
|
-
import { unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
|
|
396
385
|
import { afterAll, afterEach, beforeAll, describe, expect, test, vi } from "vitest";
|
|
397
386
|
import resolver from "../src/resolver/incrementUserAge";
|
|
398
387
|
|
|
@@ -422,7 +411,8 @@ describe("incrementUserAge resolver", () => {
|
|
|
422
411
|
|
|
423
412
|
const result = await resolver.body({
|
|
424
413
|
input: { email: "test@example.com" },
|
|
425
|
-
|
|
414
|
+
caller: null,
|
|
415
|
+
invoker: null,
|
|
426
416
|
env: {},
|
|
427
417
|
});
|
|
428
418
|
|
|
@@ -493,7 +483,6 @@ describe("decrementUserAge", () => {
|
|
|
493
483
|
Pass `mock.db` to functions that take a Kysely instance. When a resolver or executor calls `getDB()` internally there is no such seam, so spy the generated `getDB` and point it at the mock:
|
|
494
484
|
|
|
495
485
|
```typescript
|
|
496
|
-
import { unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
|
|
497
486
|
import { createKyselyMock } from "@tailor-platform/sdk/vitest";
|
|
498
487
|
import { describe, expect, test, vi } from "vitest";
|
|
499
488
|
import { getDB, type Namespace } from "../generated/db";
|
|
@@ -525,7 +514,8 @@ describe("upsertUsers resolver", () => {
|
|
|
525
514
|
{ name: "Existing", email: "exists@example.com", age: 41 },
|
|
526
515
|
],
|
|
527
516
|
},
|
|
528
|
-
|
|
517
|
+
caller: null,
|
|
518
|
+
invoker: null,
|
|
529
519
|
env: { appName: "Resolver Template", version: 1 },
|
|
530
520
|
});
|
|
531
521
|
|
|
@@ -544,7 +534,6 @@ Reach for [`mockTailordb`](#mocking-the-tailordb-client) instead when you want t
|
|
|
544
534
|
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`:
|
|
545
535
|
|
|
546
536
|
```typescript
|
|
547
|
-
import { unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
|
|
548
537
|
import { mockWorkflow } from "@tailor-platform/sdk/vitest";
|
|
549
538
|
import { describe, expect, test } from "vitest";
|
|
550
539
|
import resolver from "./resolveApproval";
|
|
@@ -559,7 +548,8 @@ describe("resolveApproval resolver", () => {
|
|
|
559
548
|
|
|
560
549
|
const result = await resolver.body({
|
|
561
550
|
input: { executionId: "exec-1", approved: true },
|
|
562
|
-
|
|
551
|
+
caller: null,
|
|
552
|
+
invoker: null,
|
|
563
553
|
env: {},
|
|
564
554
|
});
|
|
565
555
|
|
|
@@ -573,7 +563,7 @@ describe("resolveApproval resolver", () => {
|
|
|
573
563
|
|
|
574
564
|
### Testing Executors
|
|
575
565
|
|
|
576
|
-
Function-kind executors expose their handler as `executor.operation.body(args)`. The shape of `args` is determined by the trigger — for example, `recordCreatedTrigger({ type: user })` produces `{ newRecord }` typed against the type's output
|
|
566
|
+
Function-kind executors expose their handler as `executor.operation.body(args)`. The shape of `args` is determined by the trigger — for example, `recordCreatedTrigger({ type: user })` produces `{ newRecord }` typed against the type's output, plus runtime fields such as `env`, `actor`, and `invoker`. GraphQL, webhook, and workflow operation kinds are declarative and don't expose a user-authored body to test.
|
|
577
567
|
|
|
578
568
|
The `executor` template extracts shared DB access into a helper (`shared.ts`) and tests the helper directly against a mocked `tailordb.Client` (same TailorDB-mocking pattern as the resolver section). Executor handlers themselves stay thin and can be tested by spying on the helper:
|
|
579
569
|
|
|
@@ -590,6 +580,14 @@ describe("onUserCreated executor", () => {
|
|
|
590
580
|
throw new Error("expected function operation");
|
|
591
581
|
}
|
|
592
582
|
await onUserCreated.operation.body({
|
|
583
|
+
workspaceId: "workspace-1",
|
|
584
|
+
appNamespace: "app",
|
|
585
|
+
env: {},
|
|
586
|
+
actor: null,
|
|
587
|
+
invoker: null,
|
|
588
|
+
event: "created",
|
|
589
|
+
rawEvent: "tailordb.type_record.created",
|
|
590
|
+
typeName: "User",
|
|
593
591
|
newRecord: {
|
|
594
592
|
id: "user-1",
|
|
595
593
|
name: "Alice",
|
|
@@ -618,7 +616,7 @@ Workflow jobs expose the same `.body()` entrypoint as resolvers, plus `.trigger(
|
|
|
618
616
|
|
|
619
617
|
#### Simple job
|
|
620
618
|
|
|
621
|
-
Call `.body()` with the input and a stub `{ env: {} }`:
|
|
619
|
+
Call `.body()` with the input and a stub `{ env: {}, invoker: null }`:
|
|
622
620
|
|
|
623
621
|
```typescript
|
|
624
622
|
import { describe, expect, test } from "vitest";
|
|
@@ -626,14 +624,17 @@ import { validateOrder } from "./order-fulfillment";
|
|
|
626
624
|
|
|
627
625
|
describe("validateOrder", () => {
|
|
628
626
|
test("accepts a valid order", () => {
|
|
629
|
-
const result = validateOrder.body(
|
|
627
|
+
const result = validateOrder.body(
|
|
628
|
+
{ orderId: "order-1", amount: 100 },
|
|
629
|
+
{ env: {}, invoker: null },
|
|
630
|
+
);
|
|
630
631
|
expect(result).toEqual({ valid: true, orderId: "order-1" });
|
|
631
632
|
});
|
|
632
633
|
|
|
633
634
|
test("rejects a non-positive amount", () => {
|
|
634
|
-
expect(() =>
|
|
635
|
-
"
|
|
636
|
-
);
|
|
635
|
+
expect(() =>
|
|
636
|
+
validateOrder.body({ orderId: "order-1", amount: 0 }, { env: {}, invoker: null }),
|
|
637
|
+
).toThrow("Order amount must be positive");
|
|
637
638
|
});
|
|
638
639
|
});
|
|
639
640
|
```
|
|
@@ -663,7 +664,10 @@ describe("fulfillOrder", () => {
|
|
|
663
664
|
confirmed: true,
|
|
664
665
|
});
|
|
665
666
|
|
|
666
|
-
const result = await fulfillOrder.body(
|
|
667
|
+
const result = await fulfillOrder.body(
|
|
668
|
+
{ orderId: "order-1", amount: 100 },
|
|
669
|
+
{ env: {}, invoker: null },
|
|
670
|
+
);
|
|
667
671
|
|
|
668
672
|
expect(validateOrder.trigger).toHaveBeenCalledWith({ orderId: "order-1", amount: 100 });
|
|
669
673
|
expect(result).toMatchObject({ confirmed: true, paymentStatus: "completed" });
|
|
@@ -687,7 +691,10 @@ describe("processWithApproval", () => {
|
|
|
687
691
|
using wf = mockWorkflow();
|
|
688
692
|
wf.setWaitHandler({ approved: true });
|
|
689
693
|
|
|
690
|
-
const result = await processWithApproval.body(
|
|
694
|
+
const result = await processWithApproval.body(
|
|
695
|
+
{ orderId: "order-1" },
|
|
696
|
+
{ env: {}, invoker: null },
|
|
697
|
+
);
|
|
691
698
|
|
|
692
699
|
expect(result).toEqual({ orderId: "order-1", status: "approved" });
|
|
693
700
|
expect(wf.waitCalls[0]).toEqual({
|
|
@@ -700,7 +707,10 @@ describe("processWithApproval", () => {
|
|
|
700
707
|
using wf = mockWorkflow();
|
|
701
708
|
wf.setWaitHandler({ approved: false });
|
|
702
709
|
|
|
703
|
-
const result = await processWithApproval.body(
|
|
710
|
+
const result = await processWithApproval.body(
|
|
711
|
+
{ orderId: "order-2" },
|
|
712
|
+
{ env: {}, invoker: null },
|
|
713
|
+
);
|
|
704
714
|
|
|
705
715
|
expect(result.status).toBe("rejected");
|
|
706
716
|
});
|
|
@@ -729,6 +739,8 @@ describe("order-fulfillment workflow", () => {
|
|
|
729
739
|
|
|
730
740
|
Pass `{ env }` as the third argument when job bodies need configuration values during the local run.
|
|
731
741
|
|
|
742
|
+
If you already acquired `mockWorkflow()`, you can also call `wf.setEnv(...)` to reuse the same env across local workflow runs.
|
|
743
|
+
|
|
732
744
|
Like the platform runtime, the local runner re-runs the orchestrator body once per `.trigger()` call (N triggers means N+1 passes), so any side effects outside the trigger results fire on every pass. Keep the body deterministic and move repeatable side effects into the triggered jobs.
|
|
733
745
|
|
|
734
746
|
This helper is still a local runner. Use E2E tests when you need to verify deployed workflow scheduling, suspension, or replay behavior.
|
|
@@ -837,7 +849,7 @@ describe("user-profile-sync workflow", () => {
|
|
|
837
849
|
test("executes end to end", { timeout: 180_000 }, async () => {
|
|
838
850
|
const { executionId, wait } = await startWorkflow({
|
|
839
851
|
workflow: userProfileSync,
|
|
840
|
-
|
|
852
|
+
invoker: "admin",
|
|
841
853
|
arg: {
|
|
842
854
|
name: "workflow-test",
|
|
843
855
|
email: `wf-${randomUUID()}@example.com`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tailor-platform/sdk",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.2",
|
|
4
4
|
"description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
}
|
|
135
135
|
},
|
|
136
136
|
"dependencies": {
|
|
137
|
-
"@0no-co/graphql.web": "1.2
|
|
137
|
+
"@0no-co/graphql.web": "1.3.2",
|
|
138
138
|
"@badgateway/oauth2-client": "3.3.1",
|
|
139
139
|
"@bufbuild/protobuf": "2.12.0",
|
|
140
140
|
"@bufbuild/protovalidate": "1.2.0",
|
|
@@ -175,14 +175,14 @@
|
|
|
175
175
|
"pkg-types": "2.3.1",
|
|
176
176
|
"politty": "0.6.0",
|
|
177
177
|
"rolldown": "1.1.1",
|
|
178
|
-
"semver": "7.8.
|
|
178
|
+
"semver": "7.8.5",
|
|
179
179
|
"sql-highlight": "6.1.0",
|
|
180
180
|
"std-env": "4.1.0",
|
|
181
181
|
"table": "6.9.0",
|
|
182
182
|
"ts-cron-validator": "1.1.5",
|
|
183
183
|
"tsx": "4.22.4",
|
|
184
184
|
"type-fest": "5.7.0",
|
|
185
|
-
"undici": "8.
|
|
185
|
+
"undici": "8.5.0",
|
|
186
186
|
"xdg-basedir": "5.1.0",
|
|
187
187
|
"zod": "4.4.3"
|
|
188
188
|
},
|
|
@@ -192,14 +192,14 @@
|
|
|
192
192
|
"@types/mime-types": "3.0.1",
|
|
193
193
|
"@types/node": "24.13.2",
|
|
194
194
|
"@types/semver": "7.7.1",
|
|
195
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
195
|
+
"@typescript/native-preview": "7.0.0-dev.20260614.1",
|
|
196
196
|
"@vitest/coverage-v8": "4.1.8",
|
|
197
197
|
"oxfmt": "0.54.0",
|
|
198
198
|
"oxlint": "1.69.0",
|
|
199
199
|
"oxlint-tsgolint": "0.23.0",
|
|
200
|
-
"sonda": "0.
|
|
200
|
+
"sonda": "0.13.1",
|
|
201
201
|
"tsdown": "0.22.2",
|
|
202
|
-
"typescript": "
|
|
202
|
+
"typescript": "6.0.3",
|
|
203
203
|
"vitest": "4.1.8",
|
|
204
204
|
"zinfer": "0.1.8"
|
|
205
205
|
},
|