@tailor-platform/sdk 2.0.0-next.5 → 2.0.0-next.7

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 (129) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/dist/{application-Bf8wxOi6.mjs → application-GzW98_Xr.mjs} +377 -1418
  3. package/dist/application-GzW98_Xr.mjs.map +1 -0
  4. package/dist/application-OM0taSPn.mjs +3 -0
  5. package/dist/cli/commands/deploy/deploy.d.mts +8 -3
  6. package/dist/cli/commands/generate/options.d.mts +0 -1
  7. package/dist/cli/commands/generate/service.d.mts +1 -1
  8. package/dist/cli/commands/staticwebsite/deploy.d.mts +19 -0
  9. package/dist/cli/commands/workspace/create.d.mts +3 -0
  10. package/dist/cli/commands/workspace/list.d.mts +1 -0
  11. package/dist/cli/commands/workspace/transform.d.mts +2 -0
  12. package/dist/cli/index.mjs +781 -1553
  13. package/dist/cli/index.mjs.map +1 -1
  14. package/dist/cli/lib.d.mts +7 -3
  15. package/dist/cli/lib.mjs +49 -3
  16. package/dist/cli/lib.mjs.map +1 -1
  17. package/dist/cli/shared/config-loader.d.mts +1 -1
  18. package/dist/cli/shared/readonly-guard.d.mts +25 -0
  19. package/dist/cli/shared/tailordb-namespaces.d.mts +37 -0
  20. package/dist/cli/shared/workspace-name.d.mts +1 -0
  21. package/dist/completion/zsh-worker.zsh +15 -303
  22. package/dist/configure/config/types.d.mts +5 -1
  23. package/dist/configure/index.mjs +20 -19
  24. package/dist/configure/index.mjs.map +1 -1
  25. package/dist/configure/services/auth/types.d.mts +18 -23
  26. package/dist/configure/services/executor/executor.d.mts +2 -13
  27. package/dist/configure/services/executor/operation.d.mts +8 -3
  28. package/dist/configure/services/tailordb/schema.d.mts +1 -7
  29. package/dist/configure/services/tailordb/types.d.mts +1 -1
  30. package/dist/configure/services/workflow/execution-policy.d.mts +2 -2
  31. package/dist/configure/services/workflow/job.d.mts +10 -10
  32. package/dist/configure/services/workflow/workflow.d.mts +6 -4
  33. package/dist/configure/types/type.d.mts +1 -9
  34. package/dist/{crashreport-D3RjuSYb.mjs → crashreport-0ioNRZ4K.mjs} +1 -1
  35. package/dist/{crashreport-Cg-pqRJV.mjs → crashreport-CxHne11E.mjs} +4 -3
  36. package/dist/{crashreport-Cg-pqRJV.mjs.map → crashreport-CxHne11E.mjs.map} +1 -1
  37. package/dist/errors-DfnF4ibq.mjs +100 -0
  38. package/dist/errors-DfnF4ibq.mjs.map +1 -0
  39. package/dist/{globals-BjhJJ2dN.mjs → globals-CBZ0egXT.mjs} +13 -10
  40. package/dist/globals-CBZ0egXT.mjs.map +1 -0
  41. package/dist/{logger-BEiZZ3qT.mjs → logger-BwS4ppwO.mjs} +3 -3
  42. package/dist/logger-BwS4ppwO.mjs.map +1 -0
  43. package/dist/parser/service/tailordb/type-source.d.mts +10 -0
  44. package/dist/{register-ts-hook-BhzVkej0.mjs → register-ts-hook-DvEs6YsL.mjs} +2571 -1636
  45. package/dist/register-ts-hook-DvEs6YsL.mjs.map +1 -0
  46. package/dist/{registry-BSbq6SPo.mjs → registry-i7EdJ-D5.mjs} +10 -10
  47. package/dist/registry-i7EdJ-D5.mjs.map +1 -0
  48. package/dist/runtime/field-parse.d.mts +10 -0
  49. package/dist/runtime/globals.d.mts +3 -3
  50. package/dist/runtime/index.mjs +1 -1
  51. package/dist/runtime/workflow.d.mts +18 -33
  52. package/dist/runtime/workflow.mjs +1 -1
  53. package/dist/{schema-iCd1ugGp.mjs → schema--xYWRGfe.mjs} +42 -24
  54. package/dist/schema--xYWRGfe.mjs.map +1 -0
  55. package/dist/{secret-file-aoNXJmKa.mjs → secret-file-C3J4Gy8r.mjs} +2 -27
  56. package/dist/secret-file-C3J4Gy8r.mjs.map +1 -0
  57. package/dist/{secretmanager-Bd45j7an.mjs → secretmanager-IY4UvinW.mjs} +42 -9
  58. package/dist/secretmanager-IY4UvinW.mjs.map +1 -0
  59. package/dist/service-BJAQ70e5.mjs +3 -0
  60. package/dist/{service-DeZeYa06.mjs → service-DaeJlBoH.mjs} +3 -3
  61. package/dist/{service-DeZeYa06.mjs.map → service-DaeJlBoH.mjs.map} +1 -1
  62. package/dist/{service-CzZEVrp9.mjs → service-Dmxa2I4i.mjs} +64 -28
  63. package/dist/service-Dmxa2I4i.mjs.map +1 -0
  64. package/dist/service_pb-BoLM0X4K.mjs +167 -0
  65. package/dist/service_pb-BoLM0X4K.mjs.map +1 -0
  66. package/dist/{service_pb-DZkfjB_6.mjs → service_pb-r1T7kpJc.mjs} +1 -1
  67. package/dist/types/executor.generated.d.mts +60 -42
  68. package/dist/user-agent-Bgsszb5I.mjs +28 -0
  69. package/dist/user-agent-Bgsszb5I.mjs.map +1 -0
  70. package/dist/utils/test/index.mjs +11 -11
  71. package/dist/utils/test/index.mjs.map +1 -1
  72. package/dist/utils/test/mock.d.mts +6 -6
  73. package/dist/vitest/environment.mjs +1 -1
  74. package/dist/vitest/index.d.mts +8 -8
  75. package/dist/vitest/index.mjs +613 -227
  76. package/dist/vitest/index.mjs.map +1 -1
  77. package/dist/vitest/mock.d.mts +8 -8
  78. package/dist/vitest/mocks/aigateway.d.mts +14 -4
  79. package/dist/vitest/mocks/authconnection.d.mts +14 -4
  80. package/dist/vitest/mocks/file.d.mts +23 -6
  81. package/dist/vitest/mocks/iconv.d.mts +28 -4
  82. package/dist/vitest/mocks/idp.d.mts +25 -9
  83. package/dist/vitest/mocks/secretmanager.d.mts +13 -4
  84. package/dist/vitest/mocks/tailordb.d.mts +48 -7
  85. package/dist/vitest/mocks/workflow.d.mts +59 -33
  86. package/dist/vitest/setup.mjs +2 -2
  87. package/dist/vitest/workflow-local.d.mts +2 -2
  88. package/dist/workflow-CsBvRE3g.mjs +34 -0
  89. package/dist/workflow-CsBvRE3g.mjs.map +1 -0
  90. package/dist/{service_pb-Bk7dL-yT.mjs → workspace_resource_pb-Db3fv68L.mjs} +47 -165
  91. package/dist/workspace_resource_pb-Db3fv68L.mjs.map +1 -0
  92. package/docs/cli/application.md +63 -15
  93. package/docs/cli/function.md +2 -2
  94. package/docs/cli/tailordb.md +8 -140
  95. package/docs/cli/workspace.md +6 -4
  96. package/docs/cli-reference.md +11 -8
  97. package/docs/configuration.md +2 -0
  98. package/docs/github-actions.md +8 -0
  99. package/docs/migration/v2.md +169 -11
  100. package/docs/runtime.md +5 -6
  101. package/docs/services/auth.md +7 -7
  102. package/docs/services/executor.md +5 -0
  103. package/docs/services/tailordb.md +44 -0
  104. package/docs/services/workflow.md +43 -43
  105. package/docs/testing.md +112 -96
  106. package/package.json +2 -5
  107. package/dist/application-Bf8wxOi6.mjs.map +0 -1
  108. package/dist/application-CUkjzfij.mjs +0 -3
  109. package/dist/cli/erd-viewer-assets/app.js +0 -1424
  110. package/dist/cli/erd-viewer-assets/index.html +0 -77
  111. package/dist/cli/erd-viewer-assets/serve.json +0 -13
  112. package/dist/cli/erd-viewer-assets/styles.css +0 -1036
  113. package/dist/configure/types/field-runtime.d.mts +0 -16
  114. package/dist/errors-118wUDRr.mjs +0 -42
  115. package/dist/errors-118wUDRr.mjs.map +0 -1
  116. package/dist/globals-BjhJJ2dN.mjs.map +0 -1
  117. package/dist/logger-BEiZZ3qT.mjs.map +0 -1
  118. package/dist/register-ts-hook-BhzVkej0.mjs.map +0 -1
  119. package/dist/registry-BSbq6SPo.mjs.map +0 -1
  120. package/dist/resource_pb-BRv7AMXK.mjs +0 -49
  121. package/dist/resource_pb-BRv7AMXK.mjs.map +0 -1
  122. package/dist/schema-iCd1ugGp.mjs.map +0 -1
  123. package/dist/secret-file-aoNXJmKa.mjs.map +0 -1
  124. package/dist/secretmanager-Bd45j7an.mjs.map +0 -1
  125. package/dist/service-BT0GPd01.mjs +0 -3
  126. package/dist/service-CzZEVrp9.mjs.map +0 -1
  127. package/dist/service_pb-Bk7dL-yT.mjs.map +0 -1
  128. package/dist/workflow-GNuSnsTx.mjs +0 -22
  129. package/dist/workflow-GNuSnsTx.mjs.map +0 -1
@@ -1,9 +1,10 @@
1
- import { i as getRegisteredJob, t as TRIGGER_DEFAULT } from "../registry-BSbq6SPo.mjs";
1
+ import { i as getRegisteredJob, t as START_DEFAULT } from "../registry-i7EdJ-D5.mjs";
2
2
  import { a as writeWorkflowTestEnv, n as clearWorkflowTestEnv, r as readWorkflowTestEnv, t as buildJobContext } from "../test-env-key-D7UkZp99.mjs";
3
3
  import { t as assertDefined } from "../assert-DBxo8jPo.mjs";
4
4
  import { t as platformSerialize } from "../platform-serialize-RoRtBS0v.mjs";
5
- import { i as withDispose, n as tailorRoot, r as tailordbRoot, t as mockSecretmanager } from "../secretmanager-Bd45j7an.mjs";
5
+ import { i as withDispose, n as tailorRoot, r as tailordbRoot, t as mockSecretmanager } from "../secretmanager-IY4UvinW.mjs";
6
6
  import { builtinModules } from "node:module";
7
+ import { isEqual } from "es-toolkit";
7
8
  import { fileURLToPath } from "node:url";
8
9
  import { dirname, isAbsolute, matchesGlob, relative, resolve } from "node:path";
9
10
  import { ColumnNode, InsertQueryNode, Kysely, PostgresAdapter, PostgresIntrospector, PostgresQueryCompiler, PrimitiveValueListNode, ReferenceNode, UpdateQueryNode, ValueListNode, ValueNode, ValuesNode } from "kysely";
@@ -297,27 +298,31 @@ function createEnvironmentPlugin(options) {
297
298
  //#region src/vitest/mocks/aigateway.ts
298
299
  /**
299
300
  * Acquire a disposable mock for `tailor.aigateway`. Restored on dispose.
301
+ * @param options - Initial AI Gateway URL fixtures
300
302
  * @returns Disposable AI Gateway mock control object
301
303
  * @example
302
304
  * ```typescript
303
305
  * import { mockAigateway } from "@tailor-platform/sdk/vitest";
304
306
  *
305
307
  * test("resolves an AI Gateway URL", async () => {
306
- * using aigateway = mockAigateway();
307
- * aigateway.setUrls({ "my-aigateway": "https://my-aigateway.example.com" });
308
+ * using aigateway = mockAigateway({
309
+ * urls: { "my-aigateway": "https://my-aigateway.example.com" },
310
+ * });
311
+ * aigateway.setUrl("my-aigateway", "https://replacement.example.com");
308
312
  * // …
309
313
  * });
310
314
  * ```
311
315
  */
312
- function mockAigateway() {
316
+ function mockAigateway(options = {}) {
313
317
  const root = tailorRoot();
314
318
  const prev = root.aigateway;
315
- let urls = {};
316
- const get = vi.fn(async (name) => {
319
+ let urls = { ...options.urls };
320
+ async function defaultGet(name) {
317
321
  const url = urls[name];
318
322
  if (url === void 0) throw new Error(`No AI Gateway registered for "${name}". Acquire mockAigateway() and call setUrls(...).`);
319
323
  return { url };
320
- });
324
+ }
325
+ const get = vi.fn(defaultGet);
321
326
  root.aigateway = { get };
322
327
  return withDispose({
323
328
  /** The `get` `vi.fn`. */
@@ -325,12 +330,22 @@ function mockAigateway() {
325
330
  setUrls(value) {
326
331
  urls = value;
327
332
  },
333
+ setUrl(name, url) {
334
+ urls = {
335
+ ...urls,
336
+ [name]: url
337
+ };
338
+ },
328
339
  get calls() {
329
340
  return get.mock.calls.map(([name]) => ({ name }));
330
341
  },
342
+ clear() {
343
+ get.mockClear();
344
+ },
331
345
  reset() {
332
346
  urls = {};
333
- get.mockClear();
347
+ get.mockReset();
348
+ get.mockImplementation(defaultGet);
334
349
  }
335
350
  }, () => {
336
351
  root.aigateway = prev;
@@ -341,23 +356,30 @@ function mockAigateway() {
341
356
  //#region src/vitest/mocks/authconnection.ts
342
357
  /**
343
358
  * Acquire a disposable mock for `tailor.authconnection`. Restored on dispose.
359
+ * @param options - Initial token fixtures and fallback behavior
344
360
  * @returns Disposable AuthConnection mock control object
345
361
  * @example
346
362
  * ```typescript
347
363
  * import { mockAuthconnection } from "@tailor-platform/sdk/vitest";
348
364
  *
349
365
  * test("returns configured token", async () => {
350
- * using ac = mockAuthconnection();
351
- * ac.setTokens({ google: { access_token: "ya29.xxx" } });
366
+ * using ac = mockAuthconnection({ tokens: { google: { access_token: "ya29.xxx" } } });
367
+ * ac.setToken("google", { access_token: "replacement" });
352
368
  * // …
353
369
  * });
354
370
  * ```
355
371
  */
356
- function mockAuthconnection() {
372
+ function mockAuthconnection(options = {}) {
357
373
  const root = tailorRoot();
358
374
  const prev = root.authconnection;
359
- let tokens = {};
360
- const getConnectionToken = vi.fn(async (connectionName) => tokens[connectionName] ?? { access_token: "mock-token" });
375
+ let tokens = { ...options.tokens };
376
+ async function defaultGetConnectionToken(connectionName) {
377
+ const token = tokens[connectionName];
378
+ if (token) return token;
379
+ if (options.onUnhandled === "error") throw new Error(`No AuthConnection token configured for "${connectionName}"`);
380
+ return { access_token: "mock-token" };
381
+ }
382
+ const getConnectionToken = vi.fn(defaultGetConnectionToken);
361
383
  root.authconnection = { getConnectionToken };
362
384
  return withDispose({
363
385
  /** The `getConnectionToken` `vi.fn`. */
@@ -365,12 +387,22 @@ function mockAuthconnection() {
365
387
  setTokens(value) {
366
388
  tokens = value;
367
389
  },
390
+ setToken(connectionName, token) {
391
+ tokens = {
392
+ ...tokens,
393
+ [connectionName]: token
394
+ };
395
+ },
368
396
  get calls() {
369
397
  return getConnectionToken.mock.calls.map(([connectionName]) => ({ connectionName }));
370
398
  },
399
+ clear() {
400
+ getConnectionToken.mockClear();
401
+ },
371
402
  reset() {
372
403
  tokens = {};
373
- getConnectionToken.mockClear();
404
+ getConnectionToken.mockReset();
405
+ getConnectionToken.mockImplementation(defaultGetConnectionToken);
374
406
  }
375
407
  }, () => {
376
408
  root.authconnection = prev;
@@ -379,6 +411,15 @@ function mockAuthconnection() {
379
411
 
380
412
  //#endregion
381
413
  //#region src/vitest/mocks/file.ts
414
+ const FILE_METHODS = [
415
+ "upload",
416
+ "download",
417
+ "downloadAsBase64",
418
+ "delete",
419
+ "getMetadata",
420
+ "downloadStream",
421
+ "uploadStream"
422
+ ];
382
423
  const FILE_DEFAULTS = {
383
424
  upload: { metadata: {
384
425
  fileSize: 0,
@@ -402,7 +443,6 @@ const FILE_DEFAULTS = {
402
443
  lastUploadedAt: ""
403
444
  }
404
445
  },
405
- delete: void 0,
406
446
  getMetadata: {
407
447
  contentType: "",
408
448
  fileSize: 0,
@@ -417,6 +457,7 @@ const FILE_DEFAULTS = {
417
457
  };
418
458
  /**
419
459
  * Acquire a disposable mock for `tailordb.file`. Restored on dispose.
460
+ * @param options - Controls behavior for calls without a configured result
420
461
  * @returns Disposable File mock control object
421
462
  * @example
422
463
  * ```typescript
@@ -424,74 +465,102 @@ const FILE_DEFAULTS = {
424
465
  *
425
466
  * test("mock file download", async () => {
426
467
  * using file = mockFile();
427
- * file.enqueueResult({ data: new Uint8Array([1, 2, 3]), metadata: { ... } });
468
+ * file.download.mockResolvedValue({ data: new Uint8Array(), metadata: { ... } });
428
469
  * // …
429
470
  * });
430
471
  * ```
431
472
  */
432
- function mockFile() {
473
+ function mockFile(options = {}) {
433
474
  const root = tailordbRoot();
434
475
  const prev = root.file;
476
+ const { onUnhandled = "fallback" } = options;
435
477
  const queue = [];
436
- let resolver = () => null;
437
478
  const calls = [];
479
+ let resolver = () => null;
438
480
  function handle(method, namespace, typeName, fieldName, recordId) {
439
- const call = {
481
+ if (queue.length > 0) return queue.shift();
482
+ const resolved = resolver(method, {
440
483
  method,
441
484
  namespace,
442
485
  typeName,
443
486
  fieldName,
444
487
  recordId
445
- };
446
- calls.push(call);
447
- if (queue.length > 0) return queue.shift();
448
- const resolved = resolver(method, call);
488
+ });
449
489
  if (resolved != null) return resolved;
490
+ if (onUnhandled === "error") throw new Error(`No File mock configured for "${method}"`);
450
491
  const fallback = FILE_DEFAULTS[method];
451
492
  return fallback === void 0 ? void 0 : structuredClone(fallback);
452
493
  }
494
+ const upload = vi.fn(async (...args) => {
495
+ const [namespace, typeName, fieldName, recordId] = args;
496
+ return handle("upload", namespace, typeName, fieldName, recordId);
497
+ });
498
+ const download = vi.fn(async (...args) => handle("download", ...args));
499
+ const downloadAsBase64 = vi.fn(async (...args) => handle("downloadAsBase64", ...args));
500
+ const deleteFile = vi.fn(async (...args) => {
501
+ handle("delete", ...args);
502
+ });
503
+ const getMetadata = vi.fn(async (...args) => handle("getMetadata", ...args));
504
+ const downloadStream = vi.fn(async (...args) => {
505
+ const resolved = handle("downloadStream", ...args);
506
+ if (resolved != null) return resolved;
507
+ return {
508
+ body: new ReadableStream({ start(controller) {
509
+ controller.close();
510
+ } }),
511
+ metadata: {
512
+ contentType: "",
513
+ fileSize: 0,
514
+ sha256sum: "",
515
+ lastUploadedAt: ""
516
+ }
517
+ };
518
+ });
519
+ const uploadStream = vi.fn(async (...args) => {
520
+ const [namespace, typeName, fieldName, recordId] = args;
521
+ return handle("uploadStream", namespace, typeName, fieldName, recordId);
522
+ });
523
+ const mocks = {
524
+ upload,
525
+ download,
526
+ downloadAsBase64,
527
+ delete: deleteFile,
528
+ getMetadata,
529
+ downloadStream,
530
+ uploadStream
531
+ };
532
+ function track(method, operation) {
533
+ return function(...args) {
534
+ calls.push({
535
+ method,
536
+ namespace: args[0],
537
+ typeName: args[1],
538
+ fieldName: args[2],
539
+ recordId: args[3]
540
+ });
541
+ return operation.apply(this, args);
542
+ };
543
+ }
453
544
  root.file = {
454
- async upload(namespace, typeName, fieldName, recordId) {
455
- return handle("upload", namespace, typeName, fieldName, recordId);
456
- },
457
- async download(namespace, typeName, fieldName, recordId) {
458
- return handle("download", namespace, typeName, fieldName, recordId);
459
- },
460
- async downloadAsBase64(namespace, typeName, fieldName, recordId) {
461
- return handle("downloadAsBase64", namespace, typeName, fieldName, recordId);
462
- },
463
- async delete(namespace, typeName, fieldName, recordId) {
464
- handle("delete", namespace, typeName, fieldName, recordId);
465
- },
466
- async getMetadata(namespace, typeName, fieldName, recordId) {
467
- return handle("getMetadata", namespace, typeName, fieldName, recordId);
468
- },
469
- async downloadStream(namespace, typeName, fieldName, recordId) {
470
- const resolved = handle("downloadStream", namespace, typeName, fieldName, recordId);
471
- if (resolved != null) return resolved;
472
- return {
473
- body: new ReadableStream({ start(c) {
474
- c.close();
475
- } }),
476
- metadata: {
477
- contentType: "",
478
- fileSize: 0,
479
- sha256sum: "",
480
- lastUploadedAt: ""
481
- }
482
- };
483
- },
484
- async uploadStream(namespace, typeName, fieldName, recordId) {
485
- return handle("uploadStream", namespace, typeName, fieldName, recordId);
486
- }
545
+ upload: track("upload", upload),
546
+ download: track("download", download),
547
+ downloadAsBase64: track("downloadAsBase64", downloadAsBase64),
548
+ delete: track("delete", deleteFile),
549
+ getMetadata: track("getMetadata", getMetadata),
550
+ downloadStream: track("downloadStream", downloadStream),
551
+ uploadStream: track("uploadStream", uploadStream)
487
552
  };
553
+ function allMocks() {
554
+ return FILE_METHODS.map((method) => mocks[method]);
555
+ }
488
556
  return withDispose({
557
+ ...mocks,
489
558
  setResolver(value) {
490
559
  resolver = value;
491
560
  },
492
561
  /**
493
- * Enqueue a single result for the next `tailordb.file` call (FIFO; falls
494
- * back to `setResolver` when exhausted).
562
+ * Enqueue a single result for the next `tailordb.file` call.
563
+ * The queue is shared across all methods and namespaces.
495
564
  * @param result - Result to return from the next file call
496
565
  */
497
566
  enqueueResult(result) {
@@ -499,6 +568,7 @@ function mockFile() {
499
568
  },
500
569
  /**
501
570
  * Enqueue results for multiple subsequent `tailordb.file` calls.
571
+ * The queue is shared across all methods and namespaces.
502
572
  * @param results - Results to enqueue, one per upcoming call
503
573
  */
504
574
  enqueueResults(...results) {
@@ -507,10 +577,15 @@ function mockFile() {
507
577
  get calls() {
508
578
  return calls;
509
579
  },
580
+ clear() {
581
+ calls.length = 0;
582
+ for (const mock of allMocks()) mock.mockClear();
583
+ },
510
584
  reset() {
511
585
  queue.length = 0;
512
- resolver = () => null;
513
586
  calls.length = 0;
587
+ resolver = () => null;
588
+ for (const mock of allMocks()) mock.mockReset();
514
589
  }
515
590
  }, () => {
516
591
  root.file = prev;
@@ -529,11 +604,11 @@ function defaultIconvResult(method, args) {
529
604
  case "decode": return "";
530
605
  case "encode": return isUtf8(args[1]) ? "" : /* @__PURE__ */ new Uint8Array();
531
606
  case "encodings": return [];
532
- default: return;
533
607
  }
534
608
  }
535
609
  /**
536
610
  * Acquire a disposable mock for `tailor.iconv`. Restored on dispose.
611
+ * @param options - Fallback behavior for unconfigured operations
537
612
  * @returns Disposable Iconv mock control object
538
613
  * @example
539
614
  * ```typescript
@@ -541,27 +616,66 @@ function defaultIconvResult(method, args) {
541
616
  *
542
617
  * test("mock encoding conversion", () => {
543
618
  * using iconv = mockIconv();
544
- * iconv.setResolver((method) => (method === "decode" ? "decoded-text" : null));
619
+ * iconv.decode.mockReturnValue("decoded-text");
545
620
  * // …
546
621
  * });
547
622
  * ```
548
623
  */
549
- function mockIconv() {
624
+ function mockIconv(options = {}) {
550
625
  const root = tailorRoot();
551
626
  const prev = root.iconv;
552
627
  let resolver = null;
553
628
  const calls = [];
554
- function handle(method, args) {
555
- calls.push({
556
- method,
557
- args: [...args]
558
- });
629
+ function resolve(method, args) {
559
630
  if (resolver) {
560
631
  const result = resolver(method, args);
561
632
  if (result != null) return result;
562
633
  }
634
+ if (options.onUnhandled === "error") throw new Error(`No Iconv mock behavior configured for "${method}"`);
563
635
  return defaultIconvResult(method, args);
564
636
  }
637
+ function defaultConvert(input, fromEncoding, toEncoding) {
638
+ return resolve("convert", [
639
+ input,
640
+ fromEncoding,
641
+ toEncoding
642
+ ]);
643
+ }
644
+ function defaultConvertBuffer(input, fromEncoding, toEncoding) {
645
+ return resolve("convertBuffer", [
646
+ input,
647
+ fromEncoding,
648
+ toEncoding
649
+ ]);
650
+ }
651
+ function defaultDecode(input, encoding) {
652
+ return resolve("decode", [input, encoding]);
653
+ }
654
+ function defaultEncode(input, encoding) {
655
+ return resolve("encode", [input, encoding]);
656
+ }
657
+ function defaultEncodings() {
658
+ return resolve("encodings", []);
659
+ }
660
+ const convert = vi.fn(defaultConvert);
661
+ const convertBuffer = vi.fn(defaultConvertBuffer);
662
+ const decode = vi.fn(defaultDecode);
663
+ const encode = vi.fn(defaultEncode);
664
+ const encodings = vi.fn(defaultEncodings);
665
+ function track(method, operation) {
666
+ return function(...args) {
667
+ calls.push({
668
+ method,
669
+ args: [...args]
670
+ });
671
+ return operation.apply(this, args);
672
+ };
673
+ }
674
+ const trackedConvert = track("convert", convert);
675
+ const trackedConvertBuffer = track("convertBuffer", convertBuffer);
676
+ const trackedDecode = track("decode", decode);
677
+ const trackedEncode = track("encode", encode);
678
+ const trackedEncodings = track("encodings", encodings);
565
679
  class MockIconv {
566
680
  #fromEncoding;
567
681
  #toEncoding;
@@ -570,39 +684,56 @@ function mockIconv() {
570
684
  this.#toEncoding = toEncoding;
571
685
  }
572
686
  convert(input) {
573
- return handle("convert", [
574
- input,
575
- this.#fromEncoding,
576
- this.#toEncoding
577
- ]);
687
+ return trackedConvert.call(this, input, this.#fromEncoding, this.#toEncoding);
578
688
  }
579
689
  }
580
690
  root.iconv = {
581
- convert: (str, from, to) => handle("convert", [
582
- str,
583
- from,
584
- to
585
- ]),
586
- convertBuffer: (buf, from, to) => handle("convertBuffer", [
587
- buf,
588
- from,
589
- to
590
- ]),
591
- decode: (buf, encoding) => handle("decode", [buf, encoding]),
592
- encode: (str, encoding) => handle("encode", [str, encoding]),
593
- encodings: () => handle("encodings", []),
691
+ convert: trackedConvert,
692
+ convertBuffer: trackedConvertBuffer,
693
+ decode: trackedDecode,
694
+ encode: trackedEncode,
695
+ encodings: trackedEncodings,
594
696
  Iconv: MockIconv
595
697
  };
698
+ function clear() {
699
+ calls.length = 0;
700
+ convert.mockClear();
701
+ convertBuffer.mockClear();
702
+ decode.mockClear();
703
+ encode.mockClear();
704
+ encodings.mockClear();
705
+ }
596
706
  return withDispose({
707
+ /** The `convert` `vi.fn`. */
708
+ convert,
709
+ /** The `convertBuffer` `vi.fn`. */
710
+ convertBuffer,
711
+ /** The `decode` `vi.fn`. */
712
+ decode,
713
+ /** The `encode` `vi.fn`. */
714
+ encode,
715
+ /** The `encodings` `vi.fn`. */
716
+ encodings,
597
717
  setResolver(value) {
598
718
  resolver = value;
599
719
  },
600
720
  get calls() {
601
721
  return calls;
602
722
  },
723
+ clear,
603
724
  reset() {
604
725
  resolver = null;
605
726
  calls.length = 0;
727
+ convert.mockReset();
728
+ convert.mockImplementation(defaultConvert);
729
+ convertBuffer.mockReset();
730
+ convertBuffer.mockImplementation(defaultConvertBuffer);
731
+ decode.mockReset();
732
+ decode.mockImplementation(defaultDecode);
733
+ encode.mockReset();
734
+ encode.mockImplementation(defaultEncode);
735
+ encodings.mockReset();
736
+ encodings.mockImplementation(defaultEncodings);
606
737
  }
607
738
  }, () => {
608
739
  root.iconv = prev;
@@ -611,99 +742,131 @@ function mockIconv() {
611
742
 
612
743
  //#endregion
613
744
  //#region src/vitest/mocks/idp.ts
745
+ const IDP_METHODS = [
746
+ "users",
747
+ "user",
748
+ "userByName",
749
+ "createUser",
750
+ "updateUser",
751
+ "deleteUser",
752
+ "sendPasswordResetEmail",
753
+ "unenrollMfa"
754
+ ];
755
+ const IDP_USER_DEFAULT = {
756
+ id: "mock-id",
757
+ name: "mock-user",
758
+ disabled: false,
759
+ mfaEnrolled: false,
760
+ mfaFactorIds: []
761
+ };
614
762
  const IDP_DEFAULTS = {
615
763
  users: {
616
764
  users: [],
617
765
  nextPageToken: null,
618
766
  totalCount: 0
619
767
  },
620
- user: {
621
- id: "mock-id",
622
- name: "mock-user",
623
- disabled: false,
624
- mfaEnrolled: false,
625
- mfaFactorIds: []
626
- },
627
- userByName: {
628
- id: "mock-id",
629
- name: "mock-user",
630
- disabled: false,
631
- mfaEnrolled: false,
632
- mfaFactorIds: []
633
- },
634
- createUser: {
635
- id: "mock-id",
636
- name: "mock-user",
637
- disabled: false,
638
- mfaEnrolled: false,
639
- mfaFactorIds: []
640
- },
641
- updateUser: {
642
- id: "mock-id",
643
- name: "mock-user",
644
- disabled: false,
645
- mfaEnrolled: false,
646
- mfaFactorIds: []
647
- },
768
+ user: IDP_USER_DEFAULT,
769
+ userByName: IDP_USER_DEFAULT,
770
+ createUser: IDP_USER_DEFAULT,
771
+ updateUser: IDP_USER_DEFAULT,
648
772
  deleteUser: true,
649
773
  sendPasswordResetEmail: true,
650
774
  unenrollMfa: true
651
775
  };
652
776
  /**
653
777
  * Acquire a disposable mock for `tailor.idp`. Restored on dispose.
778
+ * @param options - Controls behavior for calls without a configured result
654
779
  * @returns Disposable IDP mock control object
655
780
  * @example
656
781
  * ```typescript
657
782
  * import { mockIdp } from "@tailor-platform/sdk/vitest";
658
783
  *
659
- * test("resolver-based", async () => {
784
+ * test("returns a user", async () => {
660
785
  * using idp = mockIdp();
661
- * idp.setResolver((method) =>
662
- * method === "user" ? { id: "u-1", name: "alice", disabled: false } : null,
663
- * );
786
+ * idp.namespace("my-idp").user.mockResolvedValue({
787
+ * id: "u-1",
788
+ * name: "alice",
789
+ * disabled: false,
790
+ * mfaEnrolled: false,
791
+ * mfaFactorIds: [],
792
+ * });
664
793
  * // …
665
794
  * });
666
795
  * ```
667
796
  */
668
- function mockIdp() {
797
+ function mockIdp(options = {}) {
669
798
  const root = tailorRoot();
670
799
  const prev = root.idp;
800
+ const { onUnhandled = "fallback" } = options;
671
801
  const queue = [];
672
- let resolver = () => null;
673
802
  const calls = [];
803
+ let resolver = () => null;
804
+ const namespaces = /* @__PURE__ */ new Map();
805
+ function compatibilityArgs(method, args) {
806
+ return method === "users" && args.length === 0 ? [void 0] : args;
807
+ }
674
808
  function handle(method, args, namespace) {
675
- calls.push({
676
- method,
677
- args,
678
- namespace
679
- });
680
809
  if (queue.length > 0) return queue.shift();
681
- const resolved = resolver(method, args, namespace);
810
+ const resolved = resolver(method, compatibilityArgs(method, args), namespace);
682
811
  if (resolved != null) return resolved;
683
- const fallback = IDP_DEFAULTS[method];
684
- return fallback === void 0 ? void 0 : structuredClone(fallback);
812
+ if (onUnhandled === "error") throw new Error(`No IdP mock configured for "${namespace}.${method}"`);
813
+ return structuredClone(IDP_DEFAULTS[method]);
685
814
  }
686
- const Client = vi.fn(function(config) {
687
- const namespace = config.namespace;
688
- this.users = async (options) => handle("users", [options], namespace);
689
- this.user = async (userId) => handle("user", [userId], namespace);
690
- this.userByName = async (name) => handle("userByName", [name], namespace);
691
- this.createUser = async (input) => handle("createUser", [input], namespace);
692
- this.updateUser = async (input) => handle("updateUser", [input], namespace);
693
- this.deleteUser = async (userId) => handle("deleteUser", [userId], namespace);
694
- this.sendPasswordResetEmail = async (input) => handle("sendPasswordResetEmail", [input], namespace);
695
- this.unenrollMfa = async (input) => handle("unenrollMfa", [input], namespace);
696
- });
815
+ function createNamespaceMocks(namespace) {
816
+ return {
817
+ users: vi.fn(async (...args) => handle("users", args, namespace)),
818
+ user: vi.fn(async (...args) => handle("user", args, namespace)),
819
+ userByName: vi.fn(async (...args) => handle("userByName", args, namespace)),
820
+ createUser: vi.fn(async (...args) => handle("createUser", args, namespace)),
821
+ updateUser: vi.fn(async (...args) => handle("updateUser", args, namespace)),
822
+ deleteUser: vi.fn(async (...args) => handle("deleteUser", args, namespace)),
823
+ sendPasswordResetEmail: vi.fn(async (...args) => handle("sendPasswordResetEmail", args, namespace)),
824
+ unenrollMfa: vi.fn(async (...args) => handle("unenrollMfa", args, namespace))
825
+ };
826
+ }
827
+ function namespace(name) {
828
+ const existing = namespaces.get(name);
829
+ if (existing) return existing;
830
+ const mocks = createNamespaceMocks(name);
831
+ namespaces.set(name, mocks);
832
+ return mocks;
833
+ }
834
+ function track(method, mock, namespaceName) {
835
+ return function(...args) {
836
+ calls.push({
837
+ method,
838
+ args: [...compatibilityArgs(method, args)],
839
+ namespace: namespaceName
840
+ });
841
+ return mock.apply(this, args);
842
+ };
843
+ }
844
+ const defaultClient = function(config) {
845
+ const mocks = namespace(config.namespace);
846
+ this.users = track("users", mocks.users, config.namespace);
847
+ this.user = track("user", mocks.user, config.namespace);
848
+ this.userByName = track("userByName", mocks.userByName, config.namespace);
849
+ this.createUser = track("createUser", mocks.createUser, config.namespace);
850
+ this.updateUser = track("updateUser", mocks.updateUser, config.namespace);
851
+ this.deleteUser = track("deleteUser", mocks.deleteUser, config.namespace);
852
+ this.sendPasswordResetEmail = track("sendPasswordResetEmail", mocks.sendPasswordResetEmail, config.namespace);
853
+ this.unenrollMfa = track("unenrollMfa", mocks.unenrollMfa, config.namespace);
854
+ };
855
+ const Client = vi.fn(defaultClient);
697
856
  root.idp = { Client };
857
+ function allMocks() {
858
+ return [...namespaces.values()].flatMap((mocks) => IDP_METHODS.map((method) => mocks[method]));
859
+ }
698
860
  return withDispose({
699
861
  /** The mock IDP `Client` constructor (`vi.fn`). */
700
862
  Client,
863
+ namespace,
701
864
  setResolver(value) {
702
865
  resolver = value;
703
866
  },
704
867
  /**
705
- * Enqueue a single result for the next IDP call (FIFO; falls back to
706
- * `setResolver` when exhausted).
868
+ * Enqueue a single result for the next IDP call.
869
+ * The queue is shared across all methods and namespaces.
707
870
  * @param result - Result to return from the next IDP call
708
871
  */
709
872
  enqueueResult(result) {
@@ -711,6 +874,7 @@ function mockIdp() {
711
874
  },
712
875
  /**
713
876
  * Enqueue results for multiple subsequent IDP calls.
877
+ * The queue is shared across all methods and namespaces.
714
878
  * @param results - Results to enqueue, one per upcoming call
715
879
  */
716
880
  enqueueResults(...results) {
@@ -719,10 +883,18 @@ function mockIdp() {
719
883
  get calls() {
720
884
  return calls;
721
885
  },
886
+ clear() {
887
+ calls.length = 0;
888
+ Client.mockClear();
889
+ for (const mock of allMocks()) mock.mockClear();
890
+ },
722
891
  reset() {
723
892
  queue.length = 0;
724
- resolver = () => null;
725
893
  calls.length = 0;
894
+ resolver = () => null;
895
+ Client.mockReset();
896
+ Client.mockImplementation(defaultClient);
897
+ for (const mock of allMocks()) mock.mockReset();
726
898
  }
727
899
  }, () => {
728
900
  root.idp = prev;
@@ -731,6 +903,15 @@ function mockIdp() {
731
903
 
732
904
  //#endregion
733
905
  //#region src/vitest/mocks/tailordb.ts
906
+ function testRegex(regex, value) {
907
+ const lastIndex = regex.lastIndex;
908
+ regex.lastIndex = 0;
909
+ try {
910
+ return regex.test(value);
911
+ } finally {
912
+ regex.lastIndex = lastIndex;
913
+ }
914
+ }
734
915
  var MockQueryResult = class {
735
916
  command;
736
917
  rowCount;
@@ -746,27 +927,62 @@ var MockQueryResult = class {
746
927
  * `tailordb.Client` whose `queryObject` is a shared `vi.fn()` (so query
747
928
  * responses can be staged before the client is constructed). Restored on
748
929
  * dispose.
930
+ * @param options - Query fallback behavior
749
931
  * @returns Disposable TailorDB mock control object
750
932
  * @example
751
933
  * ```typescript
752
934
  * import { mockTailordb } from "@tailor-platform/sdk/vitest";
753
935
  *
754
- * test("order-based", async () => {
936
+ * test("query-based", async () => {
755
937
  * using db = mockTailordb();
756
- * db.enqueueResults([], [{ age: 30 }], []); // BEGIN / SELECT / COMMIT
938
+ * db.onQuery({ sql: /FROM users/, params: ["u-1"] }).returnsRows([{ age: 30 }]);
757
939
  * // …
758
- * expect(db.queryObject).toHaveBeenCalledTimes(3);
940
+ * expect(db.queryObject).toHaveBeenCalled();
759
941
  * expect(db.Client).toHaveBeenCalledWith({ namespace: "tailordb" });
760
942
  * });
761
943
  * ```
762
944
  */
763
- function mockTailordb() {
945
+ function mockTailordb(options = {}) {
764
946
  const root = tailordbRoot();
765
947
  const prevClient = root.Client;
766
- const queryObject = vi.fn(async (_query, _params = []) => new MockQueryResult([]));
767
- const connect = vi.fn(async () => {});
948
+ const rules = [];
949
+ let queryResolver;
950
+ function matchesQuery(matcher, query, params) {
951
+ if (typeof matcher === "function") return matcher(query, params);
952
+ if (typeof matcher === "string") return query === matcher;
953
+ if (matcher instanceof RegExp) return testRegex(matcher, query);
954
+ let sqlMatches;
955
+ if (typeof matcher.sql === "string") sqlMatches = query === matcher.sql;
956
+ else sqlMatches = testRegex(matcher.sql, query);
957
+ if (!sqlMatches || matcher.params === void 0) return sqlMatches;
958
+ if (typeof matcher.params === "function") return matcher.params(params);
959
+ const expectedParams = matcher.params;
960
+ return params.length === expectedParams.length && params.every((value, index) => isEqual(value, expectedParams[index]));
961
+ }
962
+ function queryResponse(response) {
963
+ if (response.type === "error") throw response.error;
964
+ return new MockQueryResult(response.rows);
965
+ }
966
+ async function defaultQuery(query, params = []) {
967
+ for (let i = rules.length - 1; i >= 0; i -= 1) {
968
+ const rule = rules[i];
969
+ if (!rule || !matchesQuery(rule.matcher, query, params)) continue;
970
+ const once = rule.once.shift();
971
+ if (once) return queryResponse(once);
972
+ if (rule.fallback) return queryResponse(rule.fallback);
973
+ }
974
+ if (queryResolver) return new MockQueryResult(queryResolver(query, params) ?? []);
975
+ if (options.onUnhandled === "error") throw new Error(`No TailorDB query behavior matched: ${query}`);
976
+ return new MockQueryResult([]);
977
+ }
978
+ const queryObject = vi.fn(defaultQuery);
979
+ const defaultConnect = async () => {};
980
+ const connect = vi.fn(defaultConnect);
768
981
  const createdClients = [];
769
- const Client = vi.fn(function(config) {
982
+ function enqueueRowsList(rowsList) {
983
+ for (const rows of rowsList) queryObject.mockImplementationOnce(async () => new MockQueryResult(rows));
984
+ }
985
+ const defaultClient = function(config) {
770
986
  const record = {
771
987
  namespace: config?.namespace,
772
988
  ended: false
@@ -786,7 +1002,8 @@ function mockTailordb() {
786
1002
  queryObject
787
1003
  };
788
1004
  };
789
- });
1005
+ };
1006
+ const Client = vi.fn(defaultClient);
790
1007
  root.Client = Client;
791
1008
  return withDispose({
792
1009
  /** The mock `tailordb.Client` constructor (`vi.fn`). */
@@ -798,7 +1015,53 @@ function mockTailordb() {
798
1015
  * @param resolver - Function that returns rows for a given query and params
799
1016
  */
800
1017
  setQueryResolver(resolver) {
801
- queryObject.mockImplementation(async (query, params = []) => new MockQueryResult(resolver(query, params) ?? []));
1018
+ queryResolver = resolver;
1019
+ queryObject.mockImplementation(defaultQuery);
1020
+ },
1021
+ /**
1022
+ * Configure responses for queries matching SQL text, parameters, or a predicate.
1023
+ * More recently registered matchers take precedence.
1024
+ * Do not combine matchers with a direct `queryObject.mockImplementation()` override.
1025
+ * @param matcher - Query matcher
1026
+ * @returns Chainable query behavior
1027
+ */
1028
+ onQuery(matcher) {
1029
+ const rule = {
1030
+ matcher,
1031
+ once: []
1032
+ };
1033
+ rules.push(rule);
1034
+ const behavior = {
1035
+ returnsRows(rows) {
1036
+ rule.fallback = {
1037
+ type: "rows",
1038
+ rows
1039
+ };
1040
+ return behavior;
1041
+ },
1042
+ returnsRowsOnce(rows) {
1043
+ rule.once.push({
1044
+ type: "rows",
1045
+ rows
1046
+ });
1047
+ return behavior;
1048
+ },
1049
+ rejects(error) {
1050
+ rule.fallback = {
1051
+ type: "error",
1052
+ error
1053
+ };
1054
+ return behavior;
1055
+ },
1056
+ rejectsOnce(error) {
1057
+ rule.once.push({
1058
+ type: "error",
1059
+ error
1060
+ });
1061
+ return behavior;
1062
+ }
1063
+ };
1064
+ return behavior;
802
1065
  },
803
1066
  /**
804
1067
  * Enqueue rows for the next `queryObject` call (FIFO; takes priority over
@@ -806,14 +1069,21 @@ function mockTailordb() {
806
1069
  * @param rows - Row objects to return from the next `queryObject` call
807
1070
  */
808
1071
  enqueueResult(...rows) {
809
- queryObject.mockImplementationOnce(async () => new MockQueryResult(rows));
1072
+ enqueueRowsList([rows]);
810
1073
  },
811
1074
  /**
812
1075
  * Enqueue rows for multiple subsequent `queryObject` calls (FIFO).
813
1076
  * @param rowsList - Rows arrays, one per upcoming query
814
1077
  */
815
1078
  enqueueResults(...rowsList) {
816
- for (const rows of rowsList) queryObject.mockImplementationOnce(async () => new MockQueryResult(rows));
1079
+ enqueueRowsList(rowsList);
1080
+ },
1081
+ /**
1082
+ * Enqueue row arrays for subsequent queries whose exact order is under test.
1083
+ * @param rowsList - Rows arrays, one per upcoming query
1084
+ */
1085
+ enqueueRows(...rowsList) {
1086
+ enqueueRowsList(rowsList);
817
1087
  },
818
1088
  /**
819
1089
  * All queries executed via `queryObject`, in order, derived from the vi.fn
@@ -833,13 +1103,24 @@ function mockTailordb() {
833
1103
  get createdClients() {
834
1104
  return createdClients;
835
1105
  },
1106
+ /** Clear recorded calls while preserving configured query behavior. */
1107
+ clear() {
1108
+ queryObject.mockClear();
1109
+ connect.mockClear();
1110
+ Client.mockClear();
1111
+ createdClients.length = 0;
1112
+ },
836
1113
  /** Reset query responses and recorded calls (keeps the mock installed). */
837
1114
  reset() {
838
1115
  queryObject.mockReset();
839
- queryObject.mockImplementation(async () => new MockQueryResult([]));
840
- connect.mockClear();
841
- Client.mockClear();
1116
+ queryObject.mockImplementation(defaultQuery);
1117
+ connect.mockReset();
1118
+ connect.mockImplementation(defaultConnect);
1119
+ Client.mockReset();
1120
+ Client.mockImplementation(defaultClient);
842
1121
  createdClients.length = 0;
1122
+ rules.length = 0;
1123
+ queryResolver = void 0;
843
1124
  }
844
1125
  }, () => {
845
1126
  root.Client = prevClient;
@@ -848,6 +1129,30 @@ function mockTailordb() {
848
1129
 
849
1130
  //#endregion
850
1131
  //#region src/vitest/mocks/workflow.ts
1132
+ const originalProcedures = /* @__PURE__ */ new WeakMap();
1133
+ function replaceProcedure(target, key, current) {
1134
+ const original = originalProcedures.get(current) ?? current;
1135
+ const defaultImplementation = function(...args) {
1136
+ return Reflect.apply(original, this, args);
1137
+ };
1138
+ const mock = vi.fn(defaultImplementation);
1139
+ originalProcedures.set(mock, original);
1140
+ const record = target;
1141
+ record[key] = mock;
1142
+ return {
1143
+ mock,
1144
+ scoped: {
1145
+ mockClear: () => mock.mockClear(),
1146
+ mockReset: () => mock.mockReset(),
1147
+ restore: () => {
1148
+ if (record[key] === mock) record[key] = current;
1149
+ }
1150
+ }
1151
+ };
1152
+ }
1153
+ function replaceStart(definition) {
1154
+ return replaceProcedure(definition, "start", definition.start);
1155
+ }
851
1156
  /**
852
1157
  * Acquire a disposable mock for workflow operations (`tailor.workflow`).
853
1158
  * Restored on dispose.
@@ -856,36 +1161,45 @@ function mockTailordb() {
856
1161
  * ```typescript
857
1162
  * import { mockWorkflow } from "@tailor-platform/sdk/vitest";
858
1163
  *
859
- * test("job handler", async () => {
1164
+ * test("job start", async () => {
860
1165
  * using wf = mockWorkflow();
861
- * wf.setJobHandler((name) => (name === "validate" ? { valid: true } : null));
862
- * await runWorkflowUnderTest(); // calls tailor.workflow.triggerJobFunction("validate", {})
863
- * expect(wf.triggerJobFunction).toHaveBeenCalledWith("validate", {});
1166
+ * const job = wf.job(validateOrder);
1167
+ * job.mockResolvedValue({ valid: true });
1168
+ * await runWorkflowUnderTest();
1169
+ * expect(job).toHaveBeenCalled();
864
1170
  * });
865
1171
  * ```
866
1172
  */
867
1173
  function mockWorkflow() {
868
1174
  const root = tailorRoot();
869
1175
  const prev = root.workflow;
870
- const defaultTriggerJob = (jobName, _args, _options) => {
1176
+ const prevEnv = readWorkflowTestEnv();
1177
+ const jobSpies = /* @__PURE__ */ new Map();
1178
+ const workflowSpies = /* @__PURE__ */ new Map();
1179
+ const waitPointMocks = /* @__PURE__ */ new Map();
1180
+ const scopedMocks = /* @__PURE__ */ new Set();
1181
+ const defaultStartJob = (jobName, _args, _options) => {
871
1182
  throw new Error(`No workflow job mock for "${jobName}". Call mockWorkflow().setJobHandler(...) or enqueueResult(...), or use runWorkflowLocally() for local workflow execution.`);
872
1183
  };
873
- const defaultTriggerWorkflow = async (_workflowName, _args, _options) => {
874
- return TRIGGER_DEFAULT;
1184
+ const defaultStartWorkflow = async (_workflowName, _args, _options) => {
1185
+ return START_DEFAULT;
875
1186
  };
876
- const defaultResumeWorkflow = async (executionId) => executionId;
877
- const triggerJobFunction = vi.fn(defaultTriggerJob);
878
- const triggerWorkflow = vi.fn(defaultTriggerWorkflow);
879
- const resumeWorkflow = vi.fn(defaultResumeWorkflow);
1187
+ const defaultResumeWorkflowExecution = async (executionId) => executionId;
1188
+ const startJobFunction = vi.fn(defaultStartJob);
1189
+ const startWorkflow = vi.fn(defaultStartWorkflow);
1190
+ const resumeWorkflowExecution = vi.fn(defaultResumeWorkflowExecution);
880
1191
  const wait = vi.fn((_key, _payload) => null);
881
1192
  const resolve = vi.fn(async (_executionId, _key, _callback) => {});
1193
+ const jobFunctionShim = (...call) => {
1194
+ const out = call.length >= 3 ? startJobFunction(call[0], platformSerialize(call[1]), call[2]) : startJobFunction(call[0], platformSerialize(call[1]));
1195
+ return out instanceof Promise ? out.then((v) => platformSerialize(v)) : platformSerialize(out);
1196
+ };
1197
+ const workflowShim = (...call) => call.length >= 3 ? startWorkflow(call[0], platformSerialize(call[1]), call[2]) : startWorkflow(call[0], platformSerialize(call[1]));
1198
+ const resumeShim = (executionId) => resumeWorkflowExecution(executionId);
882
1199
  root.workflow = {
883
- triggerJobFunction: (...call) => {
884
- const out = call.length >= 3 ? triggerJobFunction(call[0], platformSerialize(call[1]), call[2]) : triggerJobFunction(call[0], platformSerialize(call[1]));
885
- return out instanceof Promise ? out.then((v) => platformSerialize(v)) : platformSerialize(out);
886
- },
887
- triggerWorkflow: (...call) => call.length >= 3 ? triggerWorkflow(call[0], platformSerialize(call[1]), call[2]) : triggerWorkflow(call[0], platformSerialize(call[1])),
888
- resumeWorkflow: (executionId) => resumeWorkflow(executionId),
1200
+ startJobFunction: jobFunctionShim,
1201
+ startWorkflow: workflowShim,
1202
+ resumeWorkflowExecution: resumeShim,
889
1203
  wait: (key, payload) => wait(key, platformSerialize(payload)),
890
1204
  resolve: (executionId, key, callback) => resolve(executionId, key, (payload) => {
891
1205
  const out = callback(payload);
@@ -893,64 +1207,122 @@ function mockWorkflow() {
893
1207
  })
894
1208
  };
895
1209
  return withDispose({
896
- /** The `triggerJobFunction` `vi.fn`. */
897
- triggerJobFunction,
898
- /** The `triggerWorkflow` `vi.fn`. */
899
- triggerWorkflow,
900
- /** The `resumeWorkflow` `vi.fn`. */
901
- resumeWorkflow,
1210
+ /** The `startJobFunction` `vi.fn`. */
1211
+ startJobFunction,
1212
+ /** The `startWorkflow` `vi.fn`. */
1213
+ startWorkflow,
1214
+ /** The `resumeWorkflowExecution` `vi.fn`. */
1215
+ resumeWorkflowExecution,
902
1216
  /** The `wait` `vi.fn`. */
903
1217
  wait,
904
1218
  /** The `resolve` `vi.fn`. */
905
1219
  resolve,
906
1220
  /**
1221
+ * Get a stable, typed mock for a workflow job's `start` method.
1222
+ * The real start behavior is used until an implementation or result is configured.
1223
+ * @param definition - Workflow job definition to mock
1224
+ * @returns Typed `start` mock for the definition
1225
+ */
1226
+ job(definition) {
1227
+ const existing = jobSpies.get(definition);
1228
+ if (existing) return existing;
1229
+ const { mock, scoped } = replaceStart(definition);
1230
+ scopedMocks.add(scoped);
1231
+ jobSpies.set(definition, mock);
1232
+ return mock;
1233
+ },
1234
+ /**
1235
+ * Get a stable, typed mock for a workflow definition's `start` method.
1236
+ * The real start behavior is used until an implementation or result is configured.
1237
+ * @param definition - Workflow definition to mock
1238
+ * @returns Typed `start` mock for the definition
1239
+ */
1240
+ workflow(definition) {
1241
+ const existing = workflowSpies.get(definition);
1242
+ if (existing) return existing;
1243
+ const { mock, scoped } = replaceStart(definition);
1244
+ workflowSpies.set(definition, mock);
1245
+ scopedMocks.add(scoped);
1246
+ return mock;
1247
+ },
1248
+ /**
1249
+ * Get stable, typed mocks for a wait point's `wait` and `resolve` methods.
1250
+ * @param definition - Wait point definition to mock
1251
+ * @returns Typed wait point mock control object
1252
+ */
1253
+ waitPoint(definition) {
1254
+ const existing = waitPointMocks.get(definition);
1255
+ if (existing) return existing;
1256
+ const waitReplacement = replaceProcedure(definition, "wait", definition.wait);
1257
+ const resolveReplacement = replaceProcedure(definition, "resolve", definition.resolve);
1258
+ const waitSpy = waitReplacement.mock;
1259
+ const resolveSpy = resolveReplacement.mock;
1260
+ scopedMocks.add(waitReplacement.scoped);
1261
+ scopedMocks.add(resolveReplacement.scoped);
1262
+ const waitPointMock = {
1263
+ wait: waitSpy,
1264
+ resolve: resolveSpy,
1265
+ /**
1266
+ * Invoke the next and subsequent resolve callbacks with a wait payload.
1267
+ * @param payload - Payload originally supplied to the wait point
1268
+ */
1269
+ setResolvePayload(payload) {
1270
+ resolveSpy.mockImplementation(async (_executionId, callback) => {
1271
+ platformSerialize(await callback(platformSerialize(payload)));
1272
+ });
1273
+ }
1274
+ };
1275
+ waitPointMocks.set(definition, waitPointMock);
1276
+ return waitPointMock;
1277
+ },
1278
+ /**
907
1279
  * Set a fallback job handler. Called when the enqueue queue is empty.
908
1280
  * @param handler - Function returning a result for a job name, args, and options
909
1281
  */
910
1282
  setJobHandler(handler) {
911
- triggerJobFunction.mockImplementation((name, args, options) => handler(name, args, options));
1283
+ startJobFunction.mockImplementation((name, args, options) => handler(name, args, options));
912
1284
  },
913
1285
  /**
914
- * Enqueue a single result for the next `triggerJobFunction` call (FIFO;
1286
+ * Enqueue a single result for the next `startJobFunction` call (FIFO;
915
1287
  * takes priority over `setJobHandler`).
916
1288
  * @param result - Result to return from the next call
917
1289
  */
918
1290
  enqueueResult(result) {
919
- triggerJobFunction.mockImplementationOnce(() => result);
1291
+ startJobFunction.mockImplementationOnce(() => result);
920
1292
  },
921
1293
  /**
922
- * Enqueue results for multiple subsequent `triggerJobFunction` calls (FIFO).
1294
+ * Enqueue results for multiple subsequent `startJobFunction` calls (FIFO).
923
1295
  * @param results - Results to enqueue, one per upcoming call
924
1296
  */
925
1297
  enqueueResults(...results) {
926
- for (const result of results) triggerJobFunction.mockImplementationOnce(() => result);
1298
+ for (const result of results) startJobFunction.mockImplementationOnce(() => result);
927
1299
  },
928
1300
  /**
929
- * All jobs triggered via `triggerJobFunction`, in order.
930
- * @returns Triggered jobs array
1301
+ * All jobs started via `startJobFunction`, in order.
1302
+ * @returns Started jobs array
931
1303
  */
932
- get triggeredJobs() {
933
- return triggerJobFunction.mock.calls.map(([jobName, args, options]) => ({
1304
+ get startedJobs() {
1305
+ return startJobFunction.mock.calls.map(([jobName, args, options]) => ({
934
1306
  jobName,
935
1307
  args,
936
1308
  ...options !== void 0 && { options }
937
1309
  }));
938
1310
  },
939
1311
  /**
940
- * Configure what `triggerWorkflow` returns. Pass a string (same id every
1312
+ * Configure what `startWorkflow` returns. Pass a string (same id every
941
1313
  * call) or `(name, args, options) => string`. Default: a placeholder UUID.
942
1314
  * @param handler - Static execution ID or a function returning one
943
1315
  */
944
- setTriggerHandler(handler) {
945
- triggerWorkflow.mockImplementation(typeof handler === "function" ? async (name, args, options) => handler(name, args, options) : async () => handler);
1316
+ setStartHandler(handler) {
1317
+ startWorkflow.mockImplementation(typeof handler === "function" ? async (name, args, options) => handler(name, args, options) : async () => handler);
946
1318
  },
947
1319
  /**
948
- * Configure what `resumeWorkflow` returns. Pass a string (same id every
949
- * call) or `(executionId) => string`. Default: echoes the input executionId.
1320
+ * Configure what `resumeWorkflowExecution` returns. Pass a string (same id
1321
+ * every call) or `(executionId) => string`. Default: echoes the input executionId.
950
1322
  * @param handler - Static execution ID or a function returning one
951
1323
  */
952
1324
  setResumeHandler(handler) {
953
- resumeWorkflow.mockImplementation(typeof handler === "function" ? async (executionId) => handler(executionId) : async () => handler);
1325
+ resumeWorkflowExecution.mockImplementation(typeof handler === "function" ? async (executionId) => handler(executionId) : async () => handler);
954
1326
  },
955
1327
  /**
956
1328
  * Configure what `wait` returns. Pass `(key, payload) => unknown` or any
@@ -961,7 +1333,7 @@ function mockWorkflow() {
961
1333
  wait.mockImplementation(typeof handler === "function" ? (key, payload) => handler(key, payload) : () => handler);
962
1334
  }),
963
1335
  /**
964
- * Set the `env` passed to job bodies invoked via `createWorkflowJob().trigger()`.
1336
+ * Set the `env` passed to job bodies invoked via `createWorkflowJob().start()`.
965
1337
  * Cleared on dispose / reset.
966
1338
  * @param env - Env passed to job bodies.
967
1339
  */
@@ -998,29 +1370,41 @@ function mockWorkflow() {
998
1370
  key
999
1371
  }));
1000
1372
  },
1373
+ /** Clear recorded calls while preserving configured responses. */
1374
+ clear() {
1375
+ startJobFunction.mockClear();
1376
+ startWorkflow.mockClear();
1377
+ resumeWorkflowExecution.mockClear();
1378
+ wait.mockClear();
1379
+ resolve.mockClear();
1380
+ for (const mock of scopedMocks) mock.mockClear();
1381
+ },
1001
1382
  /** Reset all workflow responses and recorded calls (keeps the mock installed). */
1002
1383
  reset() {
1003
- triggerJobFunction.mockReset();
1004
- triggerJobFunction.mockImplementation(defaultTriggerJob);
1005
- triggerWorkflow.mockReset();
1006
- triggerWorkflow.mockImplementation(defaultTriggerWorkflow);
1007
- resumeWorkflow.mockReset();
1008
- resumeWorkflow.mockImplementation(defaultResumeWorkflow);
1384
+ startJobFunction.mockReset();
1385
+ startJobFunction.mockImplementation(defaultStartJob);
1386
+ startWorkflow.mockReset();
1387
+ startWorkflow.mockImplementation(defaultStartWorkflow);
1388
+ resumeWorkflowExecution.mockReset();
1389
+ resumeWorkflowExecution.mockImplementation(defaultResumeWorkflowExecution);
1009
1390
  wait.mockReset();
1010
1391
  wait.mockImplementation(() => null);
1011
1392
  resolve.mockReset();
1012
1393
  resolve.mockImplementation(async () => {});
1394
+ for (const mock of scopedMocks) mock.mockReset();
1013
1395
  clearWorkflowTestEnv();
1014
1396
  }
1015
1397
  }, () => {
1398
+ for (const mock of scopedMocks) mock.restore();
1016
1399
  root.workflow = prev;
1017
- clearWorkflowTestEnv();
1400
+ if (prevEnv !== void 0) writeWorkflowTestEnv(prevEnv);
1401
+ else clearWorkflowTestEnv();
1018
1402
  });
1019
1403
  }
1020
1404
 
1021
1405
  //#endregion
1022
1406
  //#region src/vitest/workflow-local.ts
1023
- var PendingTrigger = class {
1407
+ var PendingStart = class {
1024
1408
  jobName;
1025
1409
  args;
1026
1410
  constructor(jobName, args) {
@@ -1037,7 +1421,7 @@ async function runWorkflowLocally(workflow, args, options) {
1037
1421
  if (options?.env !== void 0) writeWorkflowTestEnv({ ...options.env });
1038
1422
  root.tailor = {
1039
1423
  ...previousTailor,
1040
- workflow: createLocalWorkflowRuntime(previousTailor?.workflow, runner.triggerJobFunction)
1424
+ workflow: createLocalWorkflowRuntime(previousTailor?.workflow, runner.startJobFunction)
1041
1425
  };
1042
1426
  try {
1043
1427
  return await runner.runJob(workflow.mainJob.name, args);
@@ -1050,19 +1434,19 @@ async function runWorkflowLocally(workflow, args, options) {
1050
1434
  }
1051
1435
  function createLocalJobRunner() {
1052
1436
  let activeExecution;
1053
- const triggerJobFunction = (jobName, args) => {
1054
- if (!activeExecution) throw new Error(`Cannot trigger workflow job "${jobName}" outside runWorkflowLocally() job execution.`);
1437
+ const startJobFunction = (jobName, args) => {
1438
+ if (!activeExecution) throw new Error(`Cannot start workflow job "${jobName}" outside runWorkflowLocally() job execution.`);
1055
1439
  if (activeExecution.pending) throw activeExecution.pending;
1056
1440
  const serializedArgs = platformSerialize(args);
1057
1441
  const index = activeExecution.cursor;
1058
1442
  activeExecution.cursor += 1;
1059
1443
  const cached = activeExecution.records[index];
1060
1444
  if (cached) {
1061
- assertSameTrigger(cached, jobName, serializedArgs);
1445
+ assertSameStart(cached, jobName, serializedArgs);
1062
1446
  if (cached.status === "rejected") throw cached.error;
1063
1447
  return platformSerialize(cached.result);
1064
1448
  }
1065
- const pending = new PendingTrigger(jobName, serializedArgs);
1449
+ const pending = new PendingStart(jobName, serializedArgs);
1066
1450
  activeExecution.pending = pending;
1067
1451
  throw pending;
1068
1452
  };
@@ -1080,15 +1464,15 @@ function createLocalJobRunner() {
1080
1464
  try {
1081
1465
  const out = await body(platformSerialize(args), buildJobContext());
1082
1466
  if (execution.pending) {
1083
- await settlePendingTrigger(records, execution.pending, runJob);
1467
+ await settlePendingStart(records, execution.pending, runJob);
1084
1468
  continue;
1085
1469
  }
1086
- if (execution.cursor !== records.length) throw new Error(`Workflow job trigger sequence changed while replaying "${name}". Expected ${records.length} trigger(s), but replay reached ${execution.cursor}.`);
1470
+ if (execution.cursor !== records.length) throw new Error(`Workflow job start sequence changed while replaying "${name}". Expected ${records.length} start(s), but replay reached ${execution.cursor}.`);
1087
1471
  return platformSerialize(out);
1088
1472
  } catch (cause) {
1089
- const pending = cause instanceof PendingTrigger ? cause : execution.pending;
1473
+ const pending = cause instanceof PendingStart ? cause : execution.pending;
1090
1474
  if (pending) {
1091
- await settlePendingTrigger(records, pending, runJob);
1475
+ await settlePendingStart(records, pending, runJob);
1092
1476
  continue;
1093
1477
  }
1094
1478
  throw cause;
@@ -1099,10 +1483,10 @@ function createLocalJobRunner() {
1099
1483
  };
1100
1484
  return {
1101
1485
  runJob,
1102
- triggerJobFunction
1486
+ startJobFunction
1103
1487
  };
1104
1488
  }
1105
- async function settlePendingTrigger(records, pending, runJob) {
1489
+ async function settlePendingStart(records, pending, runJob) {
1106
1490
  try {
1107
1491
  records.push({
1108
1492
  jobName: pending.jobName,
@@ -1119,22 +1503,24 @@ async function settlePendingTrigger(records, pending, runJob) {
1119
1503
  });
1120
1504
  }
1121
1505
  }
1122
- function assertSameTrigger(record, jobName, args) {
1506
+ function assertSameStart(record, jobName, args) {
1123
1507
  if (record.jobName === jobName && JSON.stringify(record.args) === JSON.stringify(args)) return;
1124
- throw new Error(`Workflow job trigger sequence changed while replaying. Expected ${record.jobName}(${JSON.stringify(record.args)}), but got ${jobName}(${JSON.stringify(args)}).`);
1508
+ throw new Error(`Workflow job start sequence changed while replaying. Expected ${record.jobName}(${JSON.stringify(record.args)}), but got ${jobName}(${JSON.stringify(args)}).`);
1125
1509
  }
1126
- function createLocalWorkflowRuntime(previous, triggerJobFunction) {
1510
+ function createLocalWorkflowRuntime(previous, startJobFunction) {
1511
+ const startWorkflow = async (name, args, options) => {
1512
+ if (previous) return await previous.startWorkflow(name, args, options);
1513
+ platformSerialize(args);
1514
+ return START_DEFAULT;
1515
+ };
1516
+ const resumeWorkflowExecution = async (executionId) => {
1517
+ if (previous) return await previous.resumeWorkflowExecution(executionId);
1518
+ return executionId;
1519
+ };
1127
1520
  return {
1128
- triggerJobFunction,
1129
- triggerWorkflow: async (name, args, options) => {
1130
- if (previous) return await previous.triggerWorkflow(name, args, options);
1131
- platformSerialize(args);
1132
- return TRIGGER_DEFAULT;
1133
- },
1134
- resumeWorkflow: async (executionId) => {
1135
- if (previous) return await previous.resumeWorkflow(executionId);
1136
- return executionId;
1137
- },
1521
+ startJobFunction,
1522
+ startWorkflow,
1523
+ resumeWorkflowExecution,
1138
1524
  wait: (key, payload) => {
1139
1525
  if (previous) return previous.wait(key, payload);
1140
1526
  throw new Error(`No wait handler for "${key}". Acquire mockWorkflow() and call setWaitHandler(...).`);