@tailor-platform/sdk 1.57.0 → 1.59.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.
- package/CHANGELOG.md +75 -0
- package/dist/{application-CdkoGX27.mjs → application-FnWOxBk7.mjs} +48 -28
- package/dist/application-FnWOxBk7.mjs.map +1 -0
- package/dist/application-VOdgMtOD.mjs +4 -0
- package/dist/{authconnection-TsdLYaLs.d.mts → authconnection-BIYzEh2p.d.mts} +2 -2
- package/dist/authconnection-D8SJGMpj.mjs.map +1 -1
- package/dist/cli/erd-viewer-assets/app.js +4 -4
- package/dist/cli/index.mjs +172 -20
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +581 -2
- package/dist/cli/lib.mjs +4 -3
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/client-B-jRdlC_.mjs +4 -0
- package/dist/{client-DLPEPJ_s.mjs → client-W5P4NYYX.mjs} +154 -12
- package/dist/client-W5P4NYYX.mjs.map +1 -0
- package/dist/configure/index.d.mts +2 -2
- package/dist/configure/index.mjs +8 -8
- package/dist/configure/index.mjs.map +1 -1
- package/dist/{crashreport-Bm2mN5tg.mjs → crashreport-D3DvAzdg.mjs} +3 -3
- package/dist/crashreport-D3DvAzdg.mjs.map +1 -0
- package/dist/{crashreport-C5oHvHUC.mjs → crashreport-lnVTnbB5.mjs} +1 -1
- package/dist/file-B58Dm-2P.mjs.map +1 -1
- package/dist/{file-VTJbbOL3.d.mts → file-BzK8z3X-.d.mts} +2 -2
- package/dist/globals-ByrCoDip.mjs +109 -0
- package/dist/globals-ByrCoDip.mjs.map +1 -0
- package/dist/iconv-DreIffeM.mjs.map +1 -1
- package/dist/{iconv-Chu6Hit2.d.mts → iconv-kwrmd1U_.d.mts} +2 -2
- package/dist/{idp-Di9N4FSJ.d.mts → idp-BlBPtXJ-.d.mts} +2 -2
- package/dist/idp-Ch95ag8h.mjs.map +1 -1
- package/dist/{index-B61gFI9a.d.mts → index-Cr6ufjZ5.d.mts} +12 -9
- package/dist/{index-DTSQthwF.d.mts → index-DRhMpdnA.d.mts} +7 -7
- package/dist/{job-CEAJLiGp.mjs → job-BpsFXPbi.mjs} +8 -17
- package/dist/job-BpsFXPbi.mjs.map +1 -0
- package/dist/mock-Dpu__UeJ.mjs +805 -0
- package/dist/mock-Dpu__UeJ.mjs.map +1 -0
- package/dist/registry-D0uB0OrK.mjs +178 -0
- package/dist/registry-D0uB0OrK.mjs.map +1 -0
- package/dist/{repl-editor-ihh8koiR.mjs → repl-editor-Y9QJDL0K.mjs} +3 -9
- package/dist/{repl-editor-ihh8koiR.mjs.map → repl-editor-Y9QJDL0K.mjs.map} +1 -1
- package/dist/runtime/authconnection.d.mts +1 -1
- package/dist/runtime/file.d.mts +1 -1
- package/dist/runtime/globals.d.mts +5 -5
- package/dist/runtime/iconv.d.mts +1 -1
- package/dist/runtime/idp.d.mts +1 -1
- package/dist/runtime/index.d.mts +7 -7
- package/dist/runtime/secretmanager.d.mts +1 -1
- package/dist/runtime/workflow.d.mts +1 -1
- package/dist/{runtime-1YuaoNr8.mjs → runtime-CrUa8Z2k.mjs} +383 -402
- package/dist/runtime-CrUa8Z2k.mjs.map +1 -0
- package/dist/secretmanager-B9h-U_8U.mjs.map +1 -1
- package/dist/{secretmanager-BhpDmxwT.d.mts → secretmanager-CKLB3wAQ.d.mts} +2 -2
- package/dist/utils/test/index.d.mts +6 -6
- package/dist/utils/test/index.mjs +7 -7
- package/dist/utils/test/index.mjs.map +1 -1
- package/dist/vitest/environment.mjs +3 -4
- package/dist/vitest/environment.mjs.map +1 -1
- package/dist/vitest/index.d.mts +167 -120
- package/dist/vitest/index.mjs +6 -6
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/setup.d.mts +1 -1
- package/dist/vitest/setup.mjs +4 -3
- package/dist/vitest/setup.mjs.map +1 -1
- package/dist/workflow--aPbA8Uq.mjs.map +1 -1
- package/dist/{workflow-dYYH7QFa.d.mts → workflow-CMamswkK.d.mts} +2 -2
- package/dist/{workflow.generated-Kz-nQrTf.d.mts → workflow.generated-CV77NlFp.d.mts} +3 -2
- package/docs/cli/application.md +5 -5
- package/docs/cli/auth.md +55 -6
- package/docs/cli/function.md +2 -2
- package/docs/cli/staticwebsite.md +137 -0
- package/docs/cli-reference.md +17 -14
- package/docs/configuration.md +1 -1
- package/docs/generator/builtin.md +1 -1
- package/docs/runtime.md +9 -12
- package/docs/services/auth.md +0 -11
- package/docs/services/staticwebsite.md +13 -0
- package/docs/testing.md +92 -85
- package/package.json +8 -8
- package/dist/application-CdkoGX27.mjs.map +0 -1
- package/dist/application-x_mURdR0.mjs +0 -4
- package/dist/client-DLPEPJ_s.mjs.map +0 -1
- package/dist/client-DrzwCD1W.mjs +0 -4
- package/dist/crashreport-Bm2mN5tg.mjs.map +0 -1
- package/dist/job-CEAJLiGp.mjs.map +0 -1
- package/dist/mock-B6PI49C_.mjs +0 -844
- package/dist/mock-B6PI49C_.mjs.map +0 -1
- package/dist/runtime-1YuaoNr8.mjs.map +0 -1
- package/dist/test-env-key-CSnK4W1Y.mjs +0 -30
- package/dist/test-env-key-CSnK4W1Y.mjs.map +0 -1
package/docs/cli-reference.md
CHANGED
|
@@ -200,15 +200,16 @@ Commands for managing workspaces and profiles.
|
|
|
200
200
|
|
|
201
201
|
Commands for managing Auth service resources.
|
|
202
202
|
|
|
203
|
-
| Command | Description
|
|
204
|
-
| ------------------------------------------------------------------ |
|
|
205
|
-
| [authconnection authorize](./cli/auth.md#authconnection-authorize) | Authorize an auth connection via OAuth2 flow.
|
|
206
|
-
| [authconnection list](./cli/auth.md#authconnection-list) | List all auth connections.
|
|
207
|
-
| [authconnection revoke](./cli/auth.md#authconnection-revoke) | Revoke an auth connection.
|
|
208
|
-
| [
|
|
209
|
-
| [machineuser
|
|
210
|
-
| [
|
|
211
|
-
| [oauth2client
|
|
203
|
+
| Command | Description |
|
|
204
|
+
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
|
|
205
|
+
| [authconnection authorize](./cli/auth.md#authconnection-authorize) | Authorize an auth connection via OAuth2 flow. |
|
|
206
|
+
| [authconnection list](./cli/auth.md#authconnection-list) | List all auth connections. |
|
|
207
|
+
| [authconnection revoke](./cli/auth.md#authconnection-revoke) | Revoke an auth connection's tokens (keeps the connection; use 'delete' to remove it). |
|
|
208
|
+
| [authconnection delete](./cli/auth.md#authconnection-delete) | Delete an auth connection entirely. |
|
|
209
|
+
| [machineuser list](./cli/auth.md#machineuser-list) | List all machine users in the application. |
|
|
210
|
+
| [machineuser token](./cli/auth.md#machineuser-token) | Get an access token for a machine user. |
|
|
211
|
+
| [oauth2client list](./cli/auth.md#oauth2client-list) | List all OAuth2 clients in the application. |
|
|
212
|
+
| [oauth2client get](./cli/auth.md#oauth2client-get) | Get OAuth2 client credentials (including client secret). |
|
|
212
213
|
|
|
213
214
|
### [Workflow Commands](./cli/workflow.md)
|
|
214
215
|
|
|
@@ -263,11 +264,13 @@ Commands for managing secrets and vaults.
|
|
|
263
264
|
|
|
264
265
|
Commands for managing and deploying static websites.
|
|
265
266
|
|
|
266
|
-
| Command
|
|
267
|
-
|
|
|
268
|
-
| [staticwebsite deploy](./cli/staticwebsite.md#staticwebsite-deploy)
|
|
269
|
-
| [staticwebsite list](./cli/staticwebsite.md#staticwebsite-list)
|
|
270
|
-
| [staticwebsite get](./cli/staticwebsite.md#staticwebsite-get)
|
|
267
|
+
| Command | Description |
|
|
268
|
+
| ----------------------------------------------------------------------------- | ----------------------------------------------------- |
|
|
269
|
+
| [staticwebsite deploy](./cli/staticwebsite.md#staticwebsite-deploy) | Deploy a static website from a local build directory. |
|
|
270
|
+
| [staticwebsite domain list](./cli/staticwebsite.md#staticwebsite-domain-list) | List custom domains for a static website. |
|
|
271
|
+
| [staticwebsite domain get](./cli/staticwebsite.md#staticwebsite-domain-get) | Get details of a custom domain. |
|
|
272
|
+
| [staticwebsite list](./cli/staticwebsite.md#staticwebsite-list) | List all static websites in a workspace. |
|
|
273
|
+
| [staticwebsite get](./cli/staticwebsite.md#staticwebsite-get) | Get details of a specific static website. |
|
|
271
274
|
|
|
272
275
|
### [Crash Report Commands](./cli/crashreport.md)
|
|
273
276
|
|
package/docs/configuration.md
CHANGED
|
@@ -39,7 +39,7 @@ export default defineConfig({
|
|
|
39
39
|
|
|
40
40
|
**Disable Introspection**: Disable GraphQL introspection. Default is `false`.
|
|
41
41
|
|
|
42
|
-
**Log Level**: Controls which `console.*` calls are kept when deployment functions are bundled. Supported values are `"DEBUG"`, `"INFO"`, `"WARN"`, `"ERROR"`, and `"SILENT"`. The default is `"DEBUG"` and keeps all console calls. For production deployments, use `"WARN"` to keep `console.warn` and `console.error` while dropping debug, log, and info calls:
|
|
42
|
+
**Log Level**: Controls which `console.*` calls are kept when deployment functions are bundled. Supported values are `"DEBUG"`, `"INFO"`, `"WARN"`, `"ERROR"`, and `"SILENT"`. The default is `"DEBUG"` and keeps all console calls. `console.log` is treated as a DEBUG-level call (matching the platform's OpenTelemetry severity mapping), so it is dropped at `"INFO"` and above. For production deployments, use `"WARN"` to keep `console.warn` and `console.error` while dropping debug, log, and info calls:
|
|
43
43
|
|
|
44
44
|
```typescript
|
|
45
45
|
export default defineConfig({
|
|
@@ -22,7 +22,7 @@ Generates a TypeScript file containing:
|
|
|
22
22
|
|
|
23
23
|
- Type definitions for all TailorDB types
|
|
24
24
|
- `getDB(namespace)` function to create Kysely instances
|
|
25
|
-
- Utility types for `Timestamp`, `Serial`, and `ObjectColumnType` (
|
|
25
|
+
- Utility types for `Timestamp`, `Serial`, and `ObjectColumnType` (used for nested object fields so insert and select types stay correct)
|
|
26
26
|
|
|
27
27
|
### Usage
|
|
28
28
|
|
package/docs/runtime.md
CHANGED
|
@@ -82,29 +82,26 @@ The runtime entry re-exports the following namespaces. Detailed signatures, para
|
|
|
82
82
|
|
|
83
83
|
## Testing
|
|
84
84
|
|
|
85
|
-
`@tailor-platform/sdk/vitest` ships mock controllers for every runtime namespace. Pair them with the `tailor-runtime` Vitest environment so your unit tests run against the same wrappers your production code does.
|
|
85
|
+
`@tailor-platform/sdk/vitest` ships mock controllers for every runtime namespace. Pair them with the `tailor-runtime` Vitest environment so your unit tests run against the same wrappers your production code does. Each controller is a factory — acquire it with a `using` declaration and its state is reset automatically when the test scope exits (no `beforeEach(() => mock.reset())` needed). Requires TypeScript ≥ 5.2 and a runtime with `Symbol.dispose` (Node ≥ 20.4; the SDK targets Node ≥ 22).
|
|
86
86
|
|
|
87
87
|
```ts
|
|
88
88
|
import { iconv, secretmanager } from "@tailor-platform/sdk/runtime";
|
|
89
|
-
import {
|
|
90
|
-
import {
|
|
91
|
-
|
|
92
|
-
beforeEach(() => {
|
|
93
|
-
iconvMock.reset();
|
|
94
|
-
secretmanagerMock.reset();
|
|
95
|
-
});
|
|
89
|
+
import { mockIconv, mockSecretmanager } from "@tailor-platform/sdk/vitest";
|
|
90
|
+
import { expect, test } from "vitest";
|
|
96
91
|
|
|
97
92
|
test("encodes via iconv", () => {
|
|
98
|
-
|
|
93
|
+
using iconvM = mockIconv();
|
|
94
|
+
iconvM.setResolver(() => new Uint8Array([0x82, 0xa0]));
|
|
99
95
|
|
|
100
96
|
const out = iconv.convert("あ", "UTF-8", "Shift_JIS");
|
|
101
97
|
|
|
102
98
|
expect(out).toEqual(new Uint8Array([0x82, 0xa0]));
|
|
103
|
-
expect(
|
|
104
|
-
});
|
|
99
|
+
expect(iconvM.calls[0]?.method).toBe("convert");
|
|
100
|
+
}); // iconvM disposed here — the iconv mock is removed (previous state restored)
|
|
105
101
|
|
|
106
102
|
test("reads from a vault", async () => {
|
|
107
|
-
|
|
103
|
+
using sm = mockSecretmanager();
|
|
104
|
+
sm.setSecrets({ "my-vault": { API_KEY: "sk-123" } });
|
|
108
105
|
|
|
109
106
|
await expect(secretmanager.getSecret("my-vault", "API_KEY")).resolves.toBe("sk-123");
|
|
110
107
|
});
|
package/docs/services/auth.md
CHANGED
|
@@ -521,14 +521,3 @@ tailor-sdk oauth2client get <name>
|
|
|
521
521
|
```
|
|
522
522
|
|
|
523
523
|
See [Auth Resource Commands](../cli/auth.md) for full documentation.
|
|
524
|
-
|
|
525
|
-
## SDK vs Platform Naming
|
|
526
|
-
|
|
527
|
-
> **Note for Platform developers**: The SDK uses different names than the underlying Platform API for user attributes:
|
|
528
|
-
>
|
|
529
|
-
> | SDK | Platform API | Description |
|
|
530
|
-
> | --------------- | --------------- | -------------------------------- |
|
|
531
|
-
> | `attributes` | `attribute_map` | Key-value map of user attributes |
|
|
532
|
-
> | `attributeList` | `attributes` | Ordered list of UUID values |
|
|
533
|
-
>
|
|
534
|
-
> This mapping is handled automatically by the SDK. If you're reading Platform documentation or API responses, be aware of this naming difference.
|
|
@@ -9,6 +9,7 @@ Static Website provides:
|
|
|
9
9
|
- Static file hosting
|
|
10
10
|
- Type-safe URL references for configuration
|
|
11
11
|
- IP address restrictions
|
|
12
|
+
- Custom domain support
|
|
12
13
|
|
|
13
14
|
For the official Tailor Platform documentation, see [Static Website Guide](https://docs.tailor.tech/guides/static-website-hosting).
|
|
14
15
|
|
|
@@ -61,6 +62,18 @@ defineStaticWebSite("my-website", {
|
|
|
61
62
|
});
|
|
62
63
|
```
|
|
63
64
|
|
|
65
|
+
### customDomains
|
|
66
|
+
|
|
67
|
+
Associate custom domains with the static website:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
defineStaticWebSite("my-website", {
|
|
71
|
+
customDomains: ["app.example.com"],
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
After deploying, use `tailor-sdk staticwebsite domain get <domain>` to check domain status and retrieve the CNAME targets required for DNS configuration.
|
|
76
|
+
|
|
64
77
|
## Type-safe URL References
|
|
65
78
|
|
|
66
79
|
The returned website object provides a `url` property that resolves to the actual URL at deployment time. Use this for type-safe configuration:
|
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` (
|
|
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
|
-
- `
|
|
25
|
-
- `
|
|
26
|
-
- `
|
|
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** —
|
|
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
|
-
###
|
|
61
|
+
### Acquiring mocks with `using`
|
|
62
62
|
|
|
63
|
-
|
|
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 {
|
|
66
|
+
import { mockTailordb } from "@tailor-platform/sdk/vitest";
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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(
|
|
84
|
-
expect(
|
|
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
|
-
|
|
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(
|
|
123
|
+
expect(db.executedQueries[0].query).toContain("SELECT");
|
|
104
124
|
});
|
|
105
125
|
```
|
|
106
126
|
|
|
107
127
|
### Workflow Mock
|
|
108
128
|
|
|
109
|
-
|
|
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 {
|
|
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
|
-
|
|
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(
|
|
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
|
-
`
|
|
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
|
-
|
|
157
|
+
wf.enqueueResult({ valid: true });
|
|
139
158
|
|
|
140
159
|
// Multiple responses for subsequent calls (FIFO)
|
|
141
|
-
|
|
160
|
+
wf.enqueueResults({ valid: true }, { txnId: "txn-1" });
|
|
142
161
|
```
|
|
143
162
|
|
|
144
163
|
### SecretManager Mock
|
|
145
164
|
|
|
146
165
|
```typescript
|
|
147
|
-
import {
|
|
148
|
-
|
|
149
|
-
beforeEach(() => secretmanagerMock.reset());
|
|
166
|
+
import { mockSecretmanager } from "@tailor-platform/sdk/vitest";
|
|
150
167
|
|
|
151
168
|
test("reads secrets from vault", async () => {
|
|
152
|
-
|
|
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(
|
|
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 {
|
|
168
|
-
|
|
169
|
-
beforeEach(() => authconnectionMock.reset());
|
|
183
|
+
import { mockAuthconnection } from "@tailor-platform/sdk/vitest";
|
|
170
184
|
|
|
171
185
|
test("returns configured token", async () => {
|
|
172
|
-
|
|
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 {
|
|
187
|
-
|
|
188
|
-
beforeEach(() => idpMock.reset());
|
|
201
|
+
import { mockIdp } from "@tailor-platform/sdk/vitest";
|
|
189
202
|
|
|
190
203
|
test("resolver-based", async () => {
|
|
191
|
-
|
|
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
|
-
|
|
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(
|
|
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 {
|
|
215
|
-
|
|
216
|
-
beforeEach(() => fileMock.reset());
|
|
229
|
+
import { mockFile } from "@tailor-platform/sdk/vitest";
|
|
217
230
|
|
|
218
231
|
test("mock file download", async () => {
|
|
219
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
275
|
-
|
|
276
|
-
beforeEach(() => iconvMock.reset());
|
|
290
|
+
import { mockIconv } from "@tailor-platform/sdk/vitest";
|
|
277
291
|
|
|
278
292
|
test("mock encoding conversion", () => {
|
|
279
|
-
|
|
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(
|
|
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 `
|
|
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), `
|
|
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 [`
|
|
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 `
|
|
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 {
|
|
531
|
-
import {
|
|
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
|
-
|
|
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(
|
|
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 `
|
|
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 {
|
|
667
|
-
import {
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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()
|
|
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 {
|
|
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.
|
|
3
|
+
"version": "1.59.0",
|
|
4
4
|
"description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -175,14 +175,14 @@
|
|
|
175
175
|
"pkg-types": "2.3.1",
|
|
176
176
|
"politty": "0.5.1",
|
|
177
177
|
"rolldown": "1.0.3",
|
|
178
|
-
"semver": "7.8.
|
|
178
|
+
"semver": "7.8.2",
|
|
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.4.0",
|
|
186
186
|
"xdg-basedir": "5.1.0",
|
|
187
187
|
"zod": "4.4.3"
|
|
188
188
|
},
|
|
@@ -190,9 +190,9 @@
|
|
|
190
190
|
"@opentelemetry/sdk-trace-base": "2.7.1",
|
|
191
191
|
"@types/madge": "5.0.3",
|
|
192
192
|
"@types/mime-types": "3.0.1",
|
|
193
|
-
"@types/node": "24.
|
|
193
|
+
"@types/node": "24.13.1",
|
|
194
194
|
"@types/semver": "7.7.1",
|
|
195
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
195
|
+
"@typescript/native-preview": "7.0.0-dev.20260605.1",
|
|
196
196
|
"@vitest/coverage-v8": "4.1.8",
|
|
197
197
|
"oxfmt": "0.53.0",
|
|
198
198
|
"oxlint": "1.68.0",
|
|
@@ -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",
|