@tailor-platform/sdk 1.74.0 → 1.74.1

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 (55) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/{application-DwmoI6ZQ.mjs → application-BsH6tkZC.mjs} +43 -5
  3. package/dist/application-BsH6tkZC.mjs.map +1 -0
  4. package/dist/application-CSUhZMb5.mjs +3 -0
  5. package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +1 -16
  6. package/dist/cli/index.mjs +22 -33
  7. package/dist/cli/index.mjs.map +1 -1
  8. package/dist/cli/lib.d.mts +2 -2
  9. package/dist/cli/lib.mjs +3 -3
  10. package/dist/cli/lib.mjs.map +1 -1
  11. package/dist/completion/zsh-worker.zsh +1 -1
  12. package/dist/configure/index.mjs +2 -2
  13. package/dist/configure/services/tailordb/schema.d.mts +5 -19
  14. package/dist/configure/types/field-runtime.d.mts +17 -0
  15. package/dist/configure/types/type.d.mts +1 -18
  16. package/dist/field-runtime-9bfXBAOK.mjs +183 -0
  17. package/dist/field-runtime-9bfXBAOK.mjs.map +1 -0
  18. package/dist/plugin/builtin/seed/index.mjs +1 -1
  19. package/dist/{runtime-Zkg3SUwi.mjs → runtime-DV1EnfMs.mjs} +229 -394
  20. package/dist/runtime-DV1EnfMs.mjs.map +1 -0
  21. package/dist/{schema-DAdZSFOd.mjs → schema-Dl_y0m9e.mjs} +8 -167
  22. package/dist/schema-Dl_y0m9e.mjs.map +1 -0
  23. package/dist/secretmanager-Bd45j7an.mjs +98 -0
  24. package/dist/secretmanager-Bd45j7an.mjs.map +1 -0
  25. package/dist/{seed-DoMTMLcm.mjs → seed-fm0LeYP2.mjs} +14 -3
  26. package/dist/seed-fm0LeYP2.mjs.map +1 -0
  27. package/dist/types-BwzB2okW.mjs +199 -0
  28. package/dist/types-BwzB2okW.mjs.map +1 -0
  29. package/dist/types-KlawuVKa.mjs +4 -0
  30. package/dist/vitest/index.d.mts +8 -1
  31. package/dist/vitest/index.mjs +776 -1
  32. package/dist/vitest/index.mjs.map +1 -1
  33. package/dist/vitest/mock.d.mts +8 -363
  34. package/dist/vitest/mocks/aigateway.d.mts +30 -0
  35. package/dist/vitest/mocks/authconnection.d.mts +28 -0
  36. package/dist/vitest/mocks/file.d.mts +41 -0
  37. package/dist/vitest/mocks/iconv.d.mts +27 -0
  38. package/dist/vitest/mocks/idp.d.mts +44 -0
  39. package/dist/vitest/mocks/secretmanager.d.mts +33 -0
  40. package/dist/vitest/mocks/tailordb.d.mts +71 -0
  41. package/dist/vitest/mocks/workflow.d.mts +115 -0
  42. package/dist/vitest/setup.mjs +1 -1
  43. package/package.json +8 -8
  44. package/dist/application-BndtMTFY.mjs +0 -3
  45. package/dist/application-DwmoI6ZQ.mjs.map +0 -1
  46. package/dist/field-DOsJCPFa.mjs +0 -22
  47. package/dist/field-DOsJCPFa.mjs.map +0 -1
  48. package/dist/mock-Ce1GCi4-.mjs +0 -871
  49. package/dist/mock-Ce1GCi4-.mjs.map +0 -1
  50. package/dist/runtime-Zkg3SUwi.mjs.map +0 -1
  51. package/dist/schema-DAdZSFOd.mjs.map +0 -1
  52. package/dist/seed-DoMTMLcm.mjs.map +0 -1
  53. package/dist/types-qhFZ8y9B.mjs +0 -4
  54. package/dist/types-ywCGpzDZ.mjs +0 -360
  55. package/dist/types-ywCGpzDZ.mjs.map +0 -1
@@ -1,9 +1,11 @@
1
+ import { a as getRegisteredWorkflow, d as buildJobContext, f as clearWorkflowTestEnv, i as getRegisteredJob, m as platformSerialize, p as writeWorkflowTestEnv, t as TRIGGER_DEFAULT } from "../registry-BozuxbPp.mjs";
1
2
  import { t as assertDefined } from "../assert-DBxo8jPo.mjs";
2
- import { a as mockIdp, c as mockWorkflow, i as mockIconv, n as mockAuthconnection, o as mockSecretmanager, r as mockFile, s as mockTailordb, t as mockAigateway } from "../mock-Ce1GCi4-.mjs";
3
+ import { i as withDispose, n as tailorRoot, r as tailordbRoot, t as mockSecretmanager } from "../secretmanager-Bd45j7an.mjs";
3
4
  import { builtinModules } from "node:module";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import { dirname, isAbsolute, matchesGlob, relative, resolve } from "node:path";
6
7
  import { ColumnNode, InsertQueryNode, Kysely, PostgresAdapter, PostgresIntrospector, PostgresQueryCompiler, PrimitiveValueListNode, ReferenceNode, UpdateQueryNode, ValueListNode, ValueNode, ValuesNode } from "kysely";
8
+ import { vi } from "vitest";
7
9
 
8
10
  //#region src/vitest/blocked-modules.ts
9
11
  /**
@@ -289,6 +291,779 @@ function createEnvironmentPlugin(options) {
289
291
  };
290
292
  }
291
293
 
294
+ //#endregion
295
+ //#region src/vitest/mocks/aigateway.ts
296
+ /**
297
+ * Acquire a disposable mock for `tailor.aigateway`. Restored on dispose.
298
+ * @returns Disposable AI Gateway mock control object
299
+ * @example
300
+ * ```typescript
301
+ * import { mockAigateway } from "@tailor-platform/sdk/vitest";
302
+ *
303
+ * test("resolves an AI Gateway URL", async () => {
304
+ * using aigateway = mockAigateway();
305
+ * aigateway.setUrls({ "my-aigateway": "https://my-aigateway.example.com" });
306
+ * // …
307
+ * });
308
+ * ```
309
+ */
310
+ function mockAigateway() {
311
+ const root = tailorRoot();
312
+ const prev = root.aigateway;
313
+ let urls = {};
314
+ const get = vi.fn(async (name) => {
315
+ const url = urls[name];
316
+ if (url === void 0) throw new Error(`No AI Gateway registered for "${name}". Acquire mockAigateway() and call setUrls(...).`);
317
+ return { url };
318
+ });
319
+ root.aigateway = { get };
320
+ return withDispose({
321
+ /** The `get` `vi.fn`. */
322
+ get,
323
+ setUrls(value) {
324
+ urls = value;
325
+ },
326
+ get calls() {
327
+ return get.mock.calls.map(([name]) => ({ name }));
328
+ },
329
+ reset() {
330
+ urls = {};
331
+ get.mockClear();
332
+ }
333
+ }, () => {
334
+ root.aigateway = prev;
335
+ });
336
+ }
337
+
338
+ //#endregion
339
+ //#region src/vitest/mocks/authconnection.ts
340
+ /**
341
+ * Acquire a disposable mock for `tailor.authconnection`. Restored on dispose.
342
+ * @returns Disposable AuthConnection mock control object
343
+ * @example
344
+ * ```typescript
345
+ * import { mockAuthconnection } from "@tailor-platform/sdk/vitest";
346
+ *
347
+ * test("returns configured token", async () => {
348
+ * using ac = mockAuthconnection();
349
+ * ac.setTokens({ google: { access_token: "ya29.xxx" } });
350
+ * // …
351
+ * });
352
+ * ```
353
+ */
354
+ function mockAuthconnection() {
355
+ const root = tailorRoot();
356
+ const prev = root.authconnection;
357
+ let tokens = {};
358
+ const getConnectionToken = vi.fn(async (connectionName) => tokens[connectionName] ?? { access_token: "mock-token" });
359
+ root.authconnection = { getConnectionToken };
360
+ return withDispose({
361
+ /** The `getConnectionToken` `vi.fn`. */
362
+ getConnectionToken,
363
+ setTokens(value) {
364
+ tokens = value;
365
+ },
366
+ get calls() {
367
+ return getConnectionToken.mock.calls.map(([connectionName]) => ({ connectionName }));
368
+ },
369
+ reset() {
370
+ tokens = {};
371
+ getConnectionToken.mockClear();
372
+ }
373
+ }, () => {
374
+ root.authconnection = prev;
375
+ });
376
+ }
377
+
378
+ //#endregion
379
+ //#region src/vitest/mocks/file.ts
380
+ const FILE_DEFAULTS = {
381
+ upload: { metadata: {
382
+ fileSize: 0,
383
+ sha256sum: ""
384
+ } },
385
+ download: {
386
+ data: /* @__PURE__ */ new Uint8Array(),
387
+ metadata: {
388
+ contentType: "",
389
+ fileSize: 0,
390
+ sha256sum: "",
391
+ lastUploadedAt: ""
392
+ }
393
+ },
394
+ downloadAsBase64: {
395
+ data: "",
396
+ metadata: {
397
+ contentType: "",
398
+ fileSize: 0,
399
+ sha256sum: "",
400
+ lastUploadedAt: ""
401
+ }
402
+ },
403
+ delete: void 0,
404
+ getMetadata: {
405
+ contentType: "",
406
+ fileSize: 0,
407
+ sha256sum: "",
408
+ urlPath: ""
409
+ },
410
+ downloadStream: null,
411
+ uploadStream: { metadata: {
412
+ fileSize: 0,
413
+ sha256sum: ""
414
+ } }
415
+ };
416
+ function toFileStream(value) {
417
+ if (value !== null && typeof value === "object" && Symbol.asyncIterator in value && typeof value.close === "function") return value;
418
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) throw new TypeError("openDownloadStream expects an iterable of StreamValue items (e.g. [{ type: \"chunk\", data, position }, { type: \"complete\" }]); got raw bytes. Wrap the bytes in a structured chunk first.");
419
+ if (value !== null && typeof value === "object" && (Symbol.iterator in value || Symbol.asyncIterator in value)) {
420
+ const source = value;
421
+ const inner = Symbol.asyncIterator in source ? source[Symbol.asyncIterator]() : source[Symbol.iterator]();
422
+ const stream = {
423
+ async next() {
424
+ const r = await inner.next();
425
+ if (!r.done) assertStreamValue(r.value);
426
+ return r.done ? {
427
+ done: true,
428
+ value: void 0
429
+ } : r;
430
+ },
431
+ async close() {},
432
+ [Symbol.asyncIterator]() {
433
+ return stream;
434
+ }
435
+ };
436
+ return stream;
437
+ }
438
+ const empty = {
439
+ async next() {
440
+ return {
441
+ done: true,
442
+ value: void 0
443
+ };
444
+ },
445
+ async close() {},
446
+ [Symbol.asyncIterator]() {
447
+ return empty;
448
+ }
449
+ };
450
+ return empty;
451
+ }
452
+ function assertStreamValue(v) {
453
+ if (v === null || typeof v !== "object") throw new TypeError(`openDownloadStream expected a StreamValue item ({ type: "metadata" | "chunk" | "complete", ... }); got ${typeof v === "object" ? "null" : typeof v}.`);
454
+ if (v instanceof ArrayBuffer || ArrayBuffer.isView(v)) throw new TypeError("openDownloadStream expected a StreamValue item, got raw bytes. Wrap the bytes in a structured chunk first (e.g. { type: \"chunk\", data, position }).");
455
+ const type = v.type;
456
+ if (type !== "metadata" && type !== "chunk" && type !== "complete") throw new TypeError(`openDownloadStream expected a StreamValue item with type "metadata" | "chunk" | "complete"; got ${JSON.stringify(type)}.`);
457
+ }
458
+ /**
459
+ * Acquire a disposable mock for `tailordb.file`. Restored on dispose.
460
+ * @returns Disposable File mock control object
461
+ * @example
462
+ * ```typescript
463
+ * import { mockFile } from "@tailor-platform/sdk/vitest";
464
+ *
465
+ * test("mock file download", async () => {
466
+ * using file = mockFile();
467
+ * file.enqueueResult({ data: new Uint8Array([1, 2, 3]), metadata: { ... } });
468
+ * // …
469
+ * });
470
+ * ```
471
+ */
472
+ function mockFile() {
473
+ const root = tailordbRoot();
474
+ const prev = root.file;
475
+ const queue = [];
476
+ let resolver = () => null;
477
+ const calls = [];
478
+ function handle(method, namespace, typeName, fieldName, recordId) {
479
+ const call = {
480
+ method,
481
+ namespace,
482
+ typeName,
483
+ fieldName,
484
+ recordId
485
+ };
486
+ calls.push(call);
487
+ if (queue.length > 0) return queue.shift();
488
+ const resolved = resolver(method, call);
489
+ if (resolved != null) return resolved;
490
+ const fallback = FILE_DEFAULTS[method];
491
+ return fallback === void 0 ? void 0 : structuredClone(fallback);
492
+ }
493
+ root.file = {
494
+ async upload(namespace, typeName, fieldName, recordId) {
495
+ return handle("upload", namespace, typeName, fieldName, recordId);
496
+ },
497
+ async download(namespace, typeName, fieldName, recordId) {
498
+ return handle("download", namespace, typeName, fieldName, recordId);
499
+ },
500
+ async downloadAsBase64(namespace, typeName, fieldName, recordId) {
501
+ return handle("downloadAsBase64", namespace, typeName, fieldName, recordId);
502
+ },
503
+ async delete(namespace, typeName, fieldName, recordId) {
504
+ handle("delete", namespace, typeName, fieldName, recordId);
505
+ },
506
+ async getMetadata(namespace, typeName, fieldName, recordId) {
507
+ return handle("getMetadata", namespace, typeName, fieldName, recordId);
508
+ },
509
+ async openDownloadStream(namespace, typeName, fieldName, recordId) {
510
+ return toFileStream(handle("openDownloadStream", namespace, typeName, fieldName, recordId));
511
+ },
512
+ async downloadStream(namespace, typeName, fieldName, recordId) {
513
+ const resolved = handle("downloadStream", namespace, typeName, fieldName, recordId);
514
+ if (resolved != null) return resolved;
515
+ return {
516
+ body: new ReadableStream({ start(c) {
517
+ c.close();
518
+ } }),
519
+ metadata: {
520
+ contentType: "",
521
+ fileSize: 0,
522
+ sha256sum: "",
523
+ lastUploadedAt: ""
524
+ }
525
+ };
526
+ },
527
+ async uploadStream(namespace, typeName, fieldName, recordId) {
528
+ return handle("uploadStream", namespace, typeName, fieldName, recordId);
529
+ }
530
+ };
531
+ return withDispose({
532
+ setResolver(value) {
533
+ resolver = value;
534
+ },
535
+ /**
536
+ * Enqueue a single result for the next `tailordb.file` call (FIFO; falls
537
+ * back to `setResolver` when exhausted).
538
+ * @param result - Result to return from the next file call
539
+ */
540
+ enqueueResult(result) {
541
+ queue.push(result);
542
+ },
543
+ /**
544
+ * Enqueue results for multiple subsequent `tailordb.file` calls.
545
+ * @param results - Results to enqueue, one per upcoming call
546
+ */
547
+ enqueueResults(...results) {
548
+ queue.push(...results);
549
+ },
550
+ get calls() {
551
+ return calls;
552
+ },
553
+ reset() {
554
+ queue.length = 0;
555
+ resolver = () => null;
556
+ calls.length = 0;
557
+ }
558
+ }, () => {
559
+ root.file = prev;
560
+ });
561
+ }
562
+
563
+ //#endregion
564
+ //#region src/vitest/mocks/iconv.ts
565
+ function isUtf8(encoding) {
566
+ return encoding === "UTF8" || encoding === "UTF-8";
567
+ }
568
+ function defaultIconvResult(method, args) {
569
+ switch (method) {
570
+ case "convert":
571
+ case "convertBuffer": return isUtf8(args[2]) ? "" : /* @__PURE__ */ new Uint8Array();
572
+ case "decode": return "";
573
+ case "encode": return isUtf8(args[1]) ? "" : /* @__PURE__ */ new Uint8Array();
574
+ case "encodings": return [];
575
+ default: return;
576
+ }
577
+ }
578
+ /**
579
+ * Acquire a disposable mock for `tailor.iconv`. Restored on dispose.
580
+ * @returns Disposable Iconv mock control object
581
+ * @example
582
+ * ```typescript
583
+ * import { mockIconv } from "@tailor-platform/sdk/vitest";
584
+ *
585
+ * test("mock encoding conversion", () => {
586
+ * using iconv = mockIconv();
587
+ * iconv.setResolver((method) => (method === "decode" ? "decoded-text" : null));
588
+ * // …
589
+ * });
590
+ * ```
591
+ */
592
+ function mockIconv() {
593
+ const root = tailorRoot();
594
+ const prev = root.iconv;
595
+ let resolver = null;
596
+ const calls = [];
597
+ function handle(method, args) {
598
+ calls.push({
599
+ method,
600
+ args: [...args]
601
+ });
602
+ if (resolver) {
603
+ const result = resolver(method, args);
604
+ if (result != null) return result;
605
+ }
606
+ return defaultIconvResult(method, args);
607
+ }
608
+ class MockIconv {
609
+ #fromEncoding;
610
+ #toEncoding;
611
+ constructor(fromEncoding, toEncoding) {
612
+ this.#fromEncoding = fromEncoding;
613
+ this.#toEncoding = toEncoding;
614
+ }
615
+ convert(input) {
616
+ return handle("convert", [
617
+ input,
618
+ this.#fromEncoding,
619
+ this.#toEncoding
620
+ ]);
621
+ }
622
+ }
623
+ root.iconv = {
624
+ convert: (str, from, to) => handle("convert", [
625
+ str,
626
+ from,
627
+ to
628
+ ]),
629
+ convertBuffer: (buf, from, to) => handle("convertBuffer", [
630
+ buf,
631
+ from,
632
+ to
633
+ ]),
634
+ decode: (buf, encoding) => handle("decode", [buf, encoding]),
635
+ encode: (str, encoding) => handle("encode", [str, encoding]),
636
+ encodings: () => handle("encodings", []),
637
+ Iconv: MockIconv
638
+ };
639
+ return withDispose({
640
+ setResolver(value) {
641
+ resolver = value;
642
+ },
643
+ get calls() {
644
+ return calls;
645
+ },
646
+ reset() {
647
+ resolver = null;
648
+ calls.length = 0;
649
+ }
650
+ }, () => {
651
+ root.iconv = prev;
652
+ });
653
+ }
654
+
655
+ //#endregion
656
+ //#region src/vitest/mocks/idp.ts
657
+ const IDP_DEFAULTS = {
658
+ users: {
659
+ users: [],
660
+ nextPageToken: null,
661
+ totalCount: 0
662
+ },
663
+ user: {
664
+ id: "mock-id",
665
+ name: "mock-user",
666
+ disabled: false,
667
+ mfaEnrolled: false,
668
+ mfaFactorIds: []
669
+ },
670
+ userByName: {
671
+ id: "mock-id",
672
+ name: "mock-user",
673
+ disabled: false,
674
+ mfaEnrolled: false,
675
+ mfaFactorIds: []
676
+ },
677
+ createUser: {
678
+ id: "mock-id",
679
+ name: "mock-user",
680
+ disabled: false,
681
+ mfaEnrolled: false,
682
+ mfaFactorIds: []
683
+ },
684
+ updateUser: {
685
+ id: "mock-id",
686
+ name: "mock-user",
687
+ disabled: false,
688
+ mfaEnrolled: false,
689
+ mfaFactorIds: []
690
+ },
691
+ deleteUser: true,
692
+ sendPasswordResetEmail: true,
693
+ unenrollMfa: true
694
+ };
695
+ /**
696
+ * Acquire a disposable mock for `tailor.idp`. Restored on dispose.
697
+ * @returns Disposable IDP mock control object
698
+ * @example
699
+ * ```typescript
700
+ * import { mockIdp } from "@tailor-platform/sdk/vitest";
701
+ *
702
+ * test("resolver-based", async () => {
703
+ * using idp = mockIdp();
704
+ * idp.setResolver((method) =>
705
+ * method === "user" ? { id: "u-1", name: "alice", disabled: false } : null,
706
+ * );
707
+ * // …
708
+ * });
709
+ * ```
710
+ */
711
+ function mockIdp() {
712
+ const root = tailorRoot();
713
+ const prev = root.idp;
714
+ const queue = [];
715
+ let resolver = () => null;
716
+ const calls = [];
717
+ function handle(method, args, namespace) {
718
+ calls.push({
719
+ method,
720
+ args,
721
+ namespace
722
+ });
723
+ if (queue.length > 0) return queue.shift();
724
+ const resolved = resolver(method, args, namespace);
725
+ if (resolved != null) return resolved;
726
+ const fallback = IDP_DEFAULTS[method];
727
+ return fallback === void 0 ? void 0 : structuredClone(fallback);
728
+ }
729
+ const Client = vi.fn(function(config) {
730
+ const namespace = config.namespace;
731
+ this.users = async (options) => handle("users", [options], namespace);
732
+ this.user = async (userId) => handle("user", [userId], namespace);
733
+ this.userByName = async (name) => handle("userByName", [name], namespace);
734
+ this.createUser = async (input) => handle("createUser", [input], namespace);
735
+ this.updateUser = async (input) => handle("updateUser", [input], namespace);
736
+ this.deleteUser = async (userId) => handle("deleteUser", [userId], namespace);
737
+ this.sendPasswordResetEmail = async (input) => handle("sendPasswordResetEmail", [input], namespace);
738
+ this.unenrollMfa = async (input) => handle("unenrollMfa", [input], namespace);
739
+ });
740
+ root.idp = { Client };
741
+ return withDispose({
742
+ /** The mock IDP `Client` constructor (`vi.fn`). */
743
+ Client,
744
+ setResolver(value) {
745
+ resolver = value;
746
+ },
747
+ /**
748
+ * Enqueue a single result for the next IDP call (FIFO; falls back to
749
+ * `setResolver` when exhausted).
750
+ * @param result - Result to return from the next IDP call
751
+ */
752
+ enqueueResult(result) {
753
+ queue.push(result);
754
+ },
755
+ /**
756
+ * Enqueue results for multiple subsequent IDP calls.
757
+ * @param results - Results to enqueue, one per upcoming call
758
+ */
759
+ enqueueResults(...results) {
760
+ queue.push(...results);
761
+ },
762
+ get calls() {
763
+ return calls;
764
+ },
765
+ reset() {
766
+ queue.length = 0;
767
+ resolver = () => null;
768
+ calls.length = 0;
769
+ }
770
+ }, () => {
771
+ root.idp = prev;
772
+ });
773
+ }
774
+
775
+ //#endregion
776
+ //#region src/vitest/mocks/tailordb.ts
777
+ var MockQueryResult = class {
778
+ command;
779
+ rowCount;
780
+ rows;
781
+ constructor(rows) {
782
+ this.command = "";
783
+ this.rowCount = rows.length;
784
+ this.rows = rows;
785
+ }
786
+ };
787
+ /**
788
+ * Acquire a disposable mock for TailorDB operations. Installs a mock
789
+ * `tailordb.Client` whose `queryObject` is a shared `vi.fn()` (so query
790
+ * responses can be staged before the client is constructed). Restored on
791
+ * dispose.
792
+ * @returns Disposable TailorDB mock control object
793
+ * @example
794
+ * ```typescript
795
+ * import { mockTailordb } from "@tailor-platform/sdk/vitest";
796
+ *
797
+ * test("order-based", async () => {
798
+ * using db = mockTailordb();
799
+ * db.enqueueResults([], [{ age: 30 }], []); // BEGIN / SELECT / COMMIT
800
+ * // …
801
+ * expect(db.queryObject).toHaveBeenCalledTimes(3);
802
+ * expect(db.Client).toHaveBeenCalledWith({ namespace: "tailordb" });
803
+ * });
804
+ * ```
805
+ */
806
+ function mockTailordb() {
807
+ const root = tailordbRoot();
808
+ const prevClient = root.Client;
809
+ const queryObject = vi.fn(async (_query, _params = []) => new MockQueryResult([]));
810
+ const connect = vi.fn(async () => {});
811
+ const createdClients = [];
812
+ const Client = vi.fn(function(config) {
813
+ const record = {
814
+ namespace: config?.namespace,
815
+ ended: false
816
+ };
817
+ createdClients.push(record);
818
+ this.connect = connect;
819
+ this.end = vi.fn(async () => {
820
+ record.ended = true;
821
+ });
822
+ this.queryObject = queryObject;
823
+ this.createTransaction = (name) => {
824
+ if (!name) throw new Error("Transaction name must be a non-empty string");
825
+ return {
826
+ begin: async () => {},
827
+ commit: async () => {},
828
+ rollback: async () => {},
829
+ queryObject
830
+ };
831
+ };
832
+ });
833
+ root.Client = Client;
834
+ return withDispose({
835
+ /** The mock `tailordb.Client` constructor (`vi.fn`). */
836
+ Client,
837
+ /** The shared `queryObject` `vi.fn` used by every client and transaction. */
838
+ queryObject,
839
+ /**
840
+ * Set a fallback query resolver. Called when the enqueue queue is empty.
841
+ * @param resolver - Function that returns rows for a given query and params
842
+ */
843
+ setQueryResolver(resolver) {
844
+ queryObject.mockImplementation(async (query, params = []) => new MockQueryResult(resolver(query, params) ?? []));
845
+ },
846
+ /**
847
+ * Enqueue rows for the next `queryObject` call (FIFO; takes priority over
848
+ * `setQueryResolver`). Call with no arguments for an empty result.
849
+ * @param rows - Row objects to return from the next `queryObject` call
850
+ */
851
+ enqueueResult(...rows) {
852
+ queryObject.mockImplementationOnce(async () => new MockQueryResult(rows));
853
+ },
854
+ /**
855
+ * Enqueue rows for multiple subsequent `queryObject` calls (FIFO).
856
+ * @param rowsList - Rows arrays, one per upcoming query
857
+ */
858
+ enqueueResults(...rowsList) {
859
+ for (const rows of rowsList) queryObject.mockImplementationOnce(async () => new MockQueryResult(rows));
860
+ },
861
+ /**
862
+ * All queries executed via `queryObject`, in order, derived from the vi.fn
863
+ * call records.
864
+ * @returns Executed queries array
865
+ */
866
+ get executedQueries() {
867
+ return queryObject.mock.calls.map(([query, params]) => ({
868
+ query,
869
+ params: params ?? []
870
+ }));
871
+ },
872
+ /**
873
+ * All TailorDB clients created, with their namespace and end state.
874
+ * @returns Created clients array
875
+ */
876
+ get createdClients() {
877
+ return createdClients;
878
+ },
879
+ /** Reset query responses and recorded calls (keeps the mock installed). */
880
+ reset() {
881
+ queryObject.mockReset();
882
+ queryObject.mockImplementation(async () => new MockQueryResult([]));
883
+ connect.mockClear();
884
+ Client.mockClear();
885
+ createdClients.length = 0;
886
+ }
887
+ }, () => {
888
+ root.Client = prevClient;
889
+ });
890
+ }
891
+
892
+ //#endregion
893
+ //#region src/vitest/mocks/workflow.ts
894
+ /**
895
+ * Acquire a disposable mock for workflow operations (`tailor.workflow`).
896
+ * Restored on dispose.
897
+ * @returns Disposable workflow mock control object
898
+ * @example
899
+ * ```typescript
900
+ * import { mockWorkflow } from "@tailor-platform/sdk/vitest";
901
+ *
902
+ * test("job handler", async () => {
903
+ * using wf = mockWorkflow();
904
+ * wf.setJobHandler((name) => (name === "validate" ? { valid: true } : null));
905
+ * await runWorkflowUnderTest(); // calls tailor.workflow.triggerJobFunction("validate", {})
906
+ * expect(wf.triggerJobFunction).toHaveBeenCalledWith("validate", {});
907
+ * });
908
+ * ```
909
+ */
910
+ function mockWorkflow() {
911
+ const root = tailorRoot();
912
+ const prev = root.workflow;
913
+ const defaultTriggerJob = (jobName, args) => {
914
+ const body = getRegisteredJob(jobName);
915
+ return body ? body(args, buildJobContext()) : null;
916
+ };
917
+ const defaultTriggerWorkflow = async (workflowName, args, _options) => {
918
+ const wf = getRegisteredWorkflow(workflowName);
919
+ if (wf) await installedTriggerJobFunction(wf.mainJobName, args);
920
+ return TRIGGER_DEFAULT;
921
+ };
922
+ const defaultResumeWorkflow = async (executionId) => executionId;
923
+ const triggerJobFunction = vi.fn(defaultTriggerJob);
924
+ const triggerWorkflow = vi.fn(defaultTriggerWorkflow);
925
+ const resumeWorkflow = vi.fn(defaultResumeWorkflow);
926
+ const wait = vi.fn((_key, _payload) => null);
927
+ const resolve = vi.fn(async (_executionId, _key, _callback) => {});
928
+ const installedTriggerJobFunction = (jobName, args) => {
929
+ const out = triggerJobFunction(jobName, platformSerialize(args));
930
+ return out instanceof Promise ? out.then((v) => platformSerialize(v)) : platformSerialize(out);
931
+ };
932
+ root.workflow = {
933
+ triggerJobFunction: installedTriggerJobFunction,
934
+ triggerWorkflow: (...call) => call.length >= 3 ? triggerWorkflow(call[0], platformSerialize(call[1]), call[2]) : triggerWorkflow(call[0], platformSerialize(call[1])),
935
+ resumeWorkflow: (executionId) => resumeWorkflow(executionId),
936
+ wait: (key, payload) => wait(key, platformSerialize(payload)),
937
+ resolve: (executionId, key, callback) => resolve(executionId, key, (payload) => {
938
+ const out = callback(payload);
939
+ return out instanceof Promise ? out.then((v) => platformSerialize(v)) : platformSerialize(out);
940
+ })
941
+ };
942
+ return withDispose({
943
+ /** The `triggerJobFunction` `vi.fn`. */
944
+ triggerJobFunction,
945
+ /** The `triggerWorkflow` `vi.fn`. */
946
+ triggerWorkflow,
947
+ /** The `resumeWorkflow` `vi.fn`. */
948
+ resumeWorkflow,
949
+ /** The `wait` `vi.fn`. */
950
+ wait,
951
+ /** The `resolve` `vi.fn`. */
952
+ resolve,
953
+ /**
954
+ * Set a fallback job handler. Called when the enqueue queue is empty.
955
+ * @param handler - Function returning a result for a job name and args
956
+ */
957
+ setJobHandler(handler) {
958
+ triggerJobFunction.mockImplementation((name, args) => handler(name, args));
959
+ },
960
+ /**
961
+ * Enqueue a single result for the next `triggerJobFunction` call (FIFO;
962
+ * takes priority over `setJobHandler`).
963
+ * @param result - Result to return from the next call
964
+ */
965
+ enqueueResult(result) {
966
+ triggerJobFunction.mockImplementationOnce(() => result);
967
+ },
968
+ /**
969
+ * Enqueue results for multiple subsequent `triggerJobFunction` calls (FIFO).
970
+ * @param results - Results to enqueue, one per upcoming call
971
+ */
972
+ enqueueResults(...results) {
973
+ for (const result of results) triggerJobFunction.mockImplementationOnce(() => result);
974
+ },
975
+ /**
976
+ * All jobs triggered via `triggerJobFunction`, in order.
977
+ * @returns Triggered jobs array
978
+ */
979
+ get triggeredJobs() {
980
+ return triggerJobFunction.mock.calls.map(([jobName, args]) => ({
981
+ jobName,
982
+ args
983
+ }));
984
+ },
985
+ /**
986
+ * Configure what `triggerWorkflow` returns. Pass a string (same id every
987
+ * call) or `(name, args, options) => string`. Default: a placeholder UUID.
988
+ * @param handler - Static execution ID or a function returning one
989
+ */
990
+ setTriggerHandler(handler) {
991
+ triggerWorkflow.mockImplementation(typeof handler === "function" ? async (name, args, options) => handler(name, args, options) : async () => handler);
992
+ },
993
+ /**
994
+ * Configure what `resumeWorkflow` returns. Pass a string (same id every
995
+ * call) or `(executionId) => string`. Default: echoes the input executionId.
996
+ * @param handler - Static execution ID or a function returning one
997
+ */
998
+ setResumeHandler(handler) {
999
+ resumeWorkflow.mockImplementation(typeof handler === "function" ? async (executionId) => handler(executionId) : async () => handler);
1000
+ },
1001
+ /**
1002
+ * Configure what `wait` returns. Pass `(key, payload) => unknown` or any
1003
+ * other value to return it for every call. Default: `null`.
1004
+ * @param handler - Static value or a function returning one
1005
+ */
1006
+ setWaitHandler: ((handler) => {
1007
+ wait.mockImplementation(typeof handler === "function" ? (key, payload) => handler(key, payload) : () => handler);
1008
+ }),
1009
+ /**
1010
+ * Set the `env` passed to job bodies invoked via `createWorkflowJob().trigger()`.
1011
+ * Cleared on dispose / reset.
1012
+ * @param env - Env passed to job bodies.
1013
+ */
1014
+ setEnv(env) {
1015
+ writeWorkflowTestEnv({ ...env });
1016
+ },
1017
+ /**
1018
+ * Configure how `resolve` runs the user-supplied callback. Default: callback
1019
+ * is not invoked (records the call only).
1020
+ * @param handler - Function invoked per `resolve` call
1021
+ */
1022
+ setResolveHandler(handler) {
1023
+ resolve.mockImplementation(async (executionId, key, callback) => {
1024
+ await handler(executionId, key, callback);
1025
+ });
1026
+ },
1027
+ /**
1028
+ * `wait` calls reshaped as `{ key, payload }` for assertions.
1029
+ * @returns Wait call records
1030
+ */
1031
+ get waitCalls() {
1032
+ return wait.mock.calls.map(([key, payload]) => ({
1033
+ key,
1034
+ payload
1035
+ }));
1036
+ },
1037
+ /**
1038
+ * `resolve` calls reshaped as `{ executionId, key }` for assertions.
1039
+ * @returns Resolve call records
1040
+ */
1041
+ get resolveCalls() {
1042
+ return resolve.mock.calls.map(([executionId, key]) => ({
1043
+ executionId,
1044
+ key
1045
+ }));
1046
+ },
1047
+ /** Reset all workflow responses and recorded calls (keeps the mock installed). */
1048
+ reset() {
1049
+ triggerJobFunction.mockReset();
1050
+ triggerJobFunction.mockImplementation(defaultTriggerJob);
1051
+ triggerWorkflow.mockReset();
1052
+ triggerWorkflow.mockImplementation(defaultTriggerWorkflow);
1053
+ resumeWorkflow.mockReset();
1054
+ resumeWorkflow.mockImplementation(defaultResumeWorkflow);
1055
+ wait.mockReset();
1056
+ wait.mockImplementation(() => null);
1057
+ resolve.mockReset();
1058
+ resolve.mockImplementation(async () => {});
1059
+ clearWorkflowTestEnv();
1060
+ }
1061
+ }, () => {
1062
+ root.workflow = prev;
1063
+ clearWorkflowTestEnv();
1064
+ });
1065
+ }
1066
+
292
1067
  //#endregion
293
1068
  //#region src/vitest/mock-kysely.ts
294
1069
  /**