@bluelibs/runner 2.2.4 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/README.md +1409 -935
  2. package/dist/common.types.d.ts +20 -0
  3. package/dist/common.types.js +4 -0
  4. package/dist/common.types.js.map +1 -0
  5. package/dist/context.d.ts +34 -0
  6. package/dist/context.js +58 -0
  7. package/dist/context.js.map +1 -0
  8. package/dist/define.d.ts +24 -5
  9. package/dist/define.js +89 -20
  10. package/dist/define.js.map +1 -1
  11. package/dist/defs.d.ts +109 -73
  12. package/dist/defs.js +12 -2
  13. package/dist/defs.js.map +1 -1
  14. package/dist/errors.d.ts +5 -5
  15. package/dist/errors.js +6 -5
  16. package/dist/errors.js.map +1 -1
  17. package/dist/event.types.d.ts +18 -0
  18. package/dist/event.types.js +4 -0
  19. package/dist/event.types.js.map +1 -0
  20. package/dist/examples/registrator-example.d.ts +122 -0
  21. package/dist/examples/registrator-example.js +147 -0
  22. package/dist/examples/registrator-example.js.map +1 -0
  23. package/dist/globals/globalEvents.d.ts +41 -0
  24. package/dist/globals/globalEvents.js +94 -0
  25. package/dist/globals/globalEvents.js.map +1 -0
  26. package/dist/globals/globalMiddleware.d.ts +23 -0
  27. package/dist/globals/globalMiddleware.js +15 -0
  28. package/dist/globals/globalMiddleware.js.map +1 -0
  29. package/dist/globals/globalResources.d.ts +27 -0
  30. package/dist/globals/globalResources.js +47 -0
  31. package/dist/globals/globalResources.js.map +1 -0
  32. package/dist/globals/middleware/cache.middleware.d.ts +34 -0
  33. package/dist/globals/middleware/cache.middleware.js +85 -0
  34. package/dist/globals/middleware/cache.middleware.js.map +1 -0
  35. package/dist/globals/middleware/requireContext.middleware.d.ts +6 -0
  36. package/dist/globals/middleware/requireContext.middleware.js +25 -0
  37. package/dist/globals/middleware/requireContext.middleware.js.map +1 -0
  38. package/dist/globals/middleware/retry.middleware.d.ts +20 -0
  39. package/dist/globals/middleware/retry.middleware.js +34 -0
  40. package/dist/globals/middleware/retry.middleware.js.map +1 -0
  41. package/dist/globals/resources/queue.resource.d.ts +7 -0
  42. package/dist/globals/resources/queue.resource.js +31 -0
  43. package/dist/globals/resources/queue.resource.js.map +1 -0
  44. package/dist/index.d.ts +54 -18
  45. package/dist/index.js +14 -9
  46. package/dist/index.js.map +1 -1
  47. package/dist/middleware.types.d.ts +40 -0
  48. package/dist/middleware.types.js +4 -0
  49. package/dist/middleware.types.js.map +1 -0
  50. package/dist/models/DependencyProcessor.d.ts +6 -5
  51. package/dist/models/DependencyProcessor.js +13 -15
  52. package/dist/models/DependencyProcessor.js.map +1 -1
  53. package/dist/models/EventManager.d.ts +9 -4
  54. package/dist/models/EventManager.js +44 -2
  55. package/dist/models/EventManager.js.map +1 -1
  56. package/dist/models/Logger.d.ts +30 -13
  57. package/dist/models/Logger.js +132 -54
  58. package/dist/models/Logger.js.map +1 -1
  59. package/dist/models/OverrideManager.d.ts +13 -0
  60. package/dist/models/OverrideManager.js +70 -0
  61. package/dist/models/OverrideManager.js.map +1 -0
  62. package/dist/models/Queue.d.ts +25 -0
  63. package/dist/models/Queue.js +54 -0
  64. package/dist/models/Queue.js.map +1 -0
  65. package/dist/models/ResourceInitializer.d.ts +5 -2
  66. package/dist/models/ResourceInitializer.js +22 -14
  67. package/dist/models/ResourceInitializer.js.map +1 -1
  68. package/dist/models/Semaphore.d.ts +61 -0
  69. package/dist/models/Semaphore.js +166 -0
  70. package/dist/models/Semaphore.js.map +1 -0
  71. package/dist/models/Store.d.ts +18 -73
  72. package/dist/models/Store.js +71 -269
  73. package/dist/models/Store.js.map +1 -1
  74. package/dist/models/StoreConstants.d.ts +11 -0
  75. package/dist/models/StoreConstants.js +18 -0
  76. package/dist/models/StoreConstants.js.map +1 -0
  77. package/dist/models/StoreRegistry.d.ts +25 -0
  78. package/dist/models/StoreRegistry.js +171 -0
  79. package/dist/models/StoreRegistry.js.map +1 -0
  80. package/dist/models/StoreTypes.d.ts +21 -0
  81. package/dist/models/StoreTypes.js +3 -0
  82. package/dist/models/StoreTypes.js.map +1 -0
  83. package/dist/models/StoreValidator.d.ts +10 -0
  84. package/dist/models/StoreValidator.js +41 -0
  85. package/dist/models/StoreValidator.js.map +1 -0
  86. package/dist/models/TaskRunner.d.ts +1 -1
  87. package/dist/models/TaskRunner.js +39 -24
  88. package/dist/models/TaskRunner.js.map +1 -1
  89. package/dist/models/VarStore.d.ts +17 -0
  90. package/dist/models/VarStore.js +60 -0
  91. package/dist/models/VarStore.js.map +1 -0
  92. package/dist/models/index.d.ts +3 -0
  93. package/dist/models/index.js +3 -0
  94. package/dist/models/index.js.map +1 -1
  95. package/dist/resource.types.d.ts +31 -0
  96. package/dist/resource.types.js +3 -0
  97. package/dist/resource.types.js.map +1 -0
  98. package/dist/run.d.ts +4 -1
  99. package/dist/run.js +6 -3
  100. package/dist/run.js.map +1 -1
  101. package/dist/symbols.d.ts +24 -0
  102. package/dist/symbols.js +29 -0
  103. package/dist/symbols.js.map +1 -0
  104. package/dist/task.types.d.ts +55 -0
  105. package/dist/task.types.js +23 -0
  106. package/dist/task.types.js.map +1 -0
  107. package/dist/tools/getCallerFile.d.ts +9 -1
  108. package/dist/tools/getCallerFile.js +41 -0
  109. package/dist/tools/getCallerFile.js.map +1 -1
  110. package/dist/tools/registratorId.d.ts +4 -0
  111. package/dist/tools/registratorId.js +40 -0
  112. package/dist/tools/registratorId.js.map +1 -0
  113. package/dist/tools/simpleHash.d.ts +9 -0
  114. package/dist/tools/simpleHash.js +34 -0
  115. package/dist/tools/simpleHash.js.map +1 -0
  116. package/dist/types/base-interfaces.d.ts +18 -0
  117. package/dist/types/base-interfaces.js +6 -0
  118. package/dist/types/base-interfaces.js.map +1 -0
  119. package/dist/types/base.d.ts +13 -0
  120. package/dist/types/base.js +3 -0
  121. package/dist/types/base.js.map +1 -0
  122. package/dist/types/dependencies.d.ts +22 -0
  123. package/dist/types/dependencies.js +3 -0
  124. package/dist/types/dependencies.js.map +1 -0
  125. package/dist/types/dependency-core.d.ts +14 -0
  126. package/dist/types/dependency-core.js +5 -0
  127. package/dist/types/dependency-core.js.map +1 -0
  128. package/dist/types/events.d.ts +52 -0
  129. package/dist/types/events.js +6 -0
  130. package/dist/types/events.js.map +1 -0
  131. package/dist/types/hooks.d.ts +16 -0
  132. package/dist/types/hooks.js +5 -0
  133. package/dist/types/hooks.js.map +1 -0
  134. package/dist/types/index.d.ts +14 -0
  135. package/dist/types/index.js +27 -0
  136. package/dist/types/index.js.map +1 -0
  137. package/dist/types/meta.d.ts +13 -0
  138. package/dist/types/meta.js +5 -0
  139. package/dist/types/meta.js.map +1 -0
  140. package/dist/types/middleware.d.ts +38 -0
  141. package/dist/types/middleware.js +6 -0
  142. package/dist/types/middleware.js.map +1 -0
  143. package/dist/types/registerable.d.ts +10 -0
  144. package/dist/types/registerable.js +5 -0
  145. package/dist/types/registerable.js.map +1 -0
  146. package/dist/types/resources.d.ts +44 -0
  147. package/dist/types/resources.js +5 -0
  148. package/dist/types/resources.js.map +1 -0
  149. package/dist/types/symbols.d.ts +24 -0
  150. package/dist/types/symbols.js +30 -0
  151. package/dist/types/symbols.js.map +1 -0
  152. package/dist/types/tasks.d.ts +41 -0
  153. package/dist/types/tasks.js +5 -0
  154. package/dist/types/tasks.js.map +1 -0
  155. package/dist/types/utilities.d.ts +7 -0
  156. package/dist/types/utilities.js +5 -0
  157. package/dist/types/utilities.js.map +1 -0
  158. package/package.json +10 -6
  159. package/src/__tests__/benchmark/benchmark.test.ts +1 -1
  160. package/src/__tests__/context.test.ts +91 -0
  161. package/src/__tests__/errors.test.ts +8 -5
  162. package/src/__tests__/globalEvents.test.ts +1 -1
  163. package/src/__tests__/globals/cache.middleware.test.ts +772 -0
  164. package/src/__tests__/globals/queue.resource.test.ts +141 -0
  165. package/src/__tests__/globals/requireContext.middleware.test.ts +98 -0
  166. package/src/__tests__/globals/retry.middleware.test.ts +157 -0
  167. package/src/__tests__/index.helper.test.ts +55 -0
  168. package/src/__tests__/models/EventManager.test.ts +157 -11
  169. package/src/__tests__/models/Logger.test.ts +291 -34
  170. package/src/__tests__/models/Queue.test.ts +189 -0
  171. package/src/__tests__/models/ResourceInitializer.test.ts +8 -6
  172. package/src/__tests__/models/Semaphore.test.ts +713 -0
  173. package/src/__tests__/models/Store.test.ts +40 -0
  174. package/src/__tests__/models/TaskRunner.test.ts +86 -5
  175. package/src/__tests__/run.anonymous.test.ts +679 -0
  176. package/src/__tests__/run.middleware.test.ts +312 -12
  177. package/src/__tests__/run.overrides.test.ts +13 -10
  178. package/src/__tests__/run.test.ts +364 -13
  179. package/src/__tests__/setOutput.test.ts +244 -0
  180. package/src/__tests__/tools/getCallerFile.test.ts +124 -9
  181. package/src/__tests__/typesafety.test.ts +71 -41
  182. package/src/context.ts +86 -0
  183. package/src/define.ts +129 -34
  184. package/src/defs.ts +156 -119
  185. package/src/errors.ts +15 -10
  186. package/src/{globalEvents.ts → globals/globalEvents.ts} +13 -12
  187. package/src/globals/globalMiddleware.ts +14 -0
  188. package/src/{globalResources.ts → globals/globalResources.ts} +14 -10
  189. package/src/globals/middleware/cache.middleware.ts +115 -0
  190. package/src/globals/middleware/requireContext.middleware.ts +36 -0
  191. package/src/globals/middleware/retry.middleware.ts +56 -0
  192. package/src/globals/resources/queue.resource.ts +34 -0
  193. package/src/index.ts +9 -5
  194. package/src/models/DependencyProcessor.ts +42 -49
  195. package/src/models/EventManager.ts +64 -13
  196. package/src/models/Logger.ts +181 -64
  197. package/src/models/OverrideManager.ts +84 -0
  198. package/src/models/Queue.ts +66 -0
  199. package/src/models/ResourceInitializer.ts +40 -20
  200. package/src/models/Semaphore.ts +208 -0
  201. package/src/models/Store.ts +94 -342
  202. package/src/models/StoreConstants.ts +17 -0
  203. package/src/models/StoreRegistry.ts +228 -0
  204. package/src/models/StoreTypes.ts +46 -0
  205. package/src/models/StoreValidator.ts +43 -0
  206. package/src/models/TaskRunner.ts +54 -41
  207. package/src/models/index.ts +3 -0
  208. package/src/run.ts +7 -4
  209. package/src/tools/getCallerFile.ts +54 -2
  210. package/src/__tests__/index.ts +0 -15
  211. package/src/examples/express-mongo/index.ts +0 -1
@@ -5,7 +5,40 @@ import {
5
5
  defineMiddleware,
6
6
  } from "../define";
7
7
  import { run } from "../run";
8
- import { globalResources } from "../globalResources";
8
+ import { globalResources } from "../globals/globalResources";
9
+
10
+ describe("main exports", () => {
11
+ it("should export all public APIs correctly", async () => {
12
+ // Test main index exports for 100% coverage
13
+ const mainExports = await import("../index");
14
+
15
+ expect(typeof mainExports.task).toBe("function");
16
+ expect(typeof mainExports.resource).toBe("function");
17
+ expect(typeof mainExports.event).toBe("function");
18
+ expect(typeof mainExports.middleware).toBe("function");
19
+ expect(typeof mainExports.run).toBe("function");
20
+ expect(typeof mainExports.globals).toBe("object");
21
+ expect(typeof mainExports.definitions).toBe("object");
22
+ expect(typeof mainExports.Store).toBe("function");
23
+ expect(typeof mainExports.EventManager).toBe("function");
24
+ expect(typeof mainExports.TaskRunner).toBe("function");
25
+
26
+ // Test that aliases work the same as direct imports
27
+ const directTask = defineTask({ id: "test", run: async () => "direct" });
28
+ const aliasTask = mainExports.task({
29
+ id: "test2",
30
+ run: async () => "alias",
31
+ });
32
+
33
+ expect(directTask.id).toBe("test");
34
+ expect(aliasTask.id).toBe("test2");
35
+
36
+ // Test globals sub-properties for complete coverage
37
+ expect(typeof mainExports.globals.events).toBe("object");
38
+ expect(typeof mainExports.globals.resources).toBe("object");
39
+ expect(typeof mainExports.globals.middlewares).toBe("object");
40
+ });
41
+ });
9
42
 
10
43
  describe("run", () => {
11
44
  // Tasks
@@ -60,7 +93,7 @@ describe("run", () => {
60
93
  });
61
94
 
62
95
  it("should be able to register an task that emits an event", async () => {
63
- const testEvent = defineEvent({ id: "test.event" });
96
+ const testEvent = defineEvent<{ message: string }>({ id: "test.event" });
64
97
  const eventHandler = jest.fn();
65
98
 
66
99
  const testTask = defineTask({
@@ -251,22 +284,22 @@ describe("run", () => {
251
284
  });
252
285
 
253
286
  it("should throw an error if there's an infinite dependency", async () => {
254
- const task1 = defineTask({
287
+ const task1: any = defineTask({
255
288
  id: "task1",
256
- dependencies: () => ({ task2 }), // Corrected line
289
+ dependencies: (): any => ({ task2 }), // Corrected line
257
290
  run: async () => "Task 1",
258
291
  });
259
292
 
260
- const task2 = defineTask({
293
+ const task2: any = defineTask({
261
294
  id: "task2",
262
295
  dependencies: { task1 },
263
296
  run: async () => "Task 2",
264
297
  });
265
298
 
266
299
  // define circular dependency resources
267
- const resource1 = defineResource({
300
+ const resource1: any = defineResource({
268
301
  id: "resource1",
269
- dependencies: () => ({
302
+ dependencies: (): any => ({
270
303
  resource2,
271
304
  }),
272
305
  init: async () => "Resource 1",
@@ -532,7 +565,8 @@ describe("run", () => {
532
565
  },
533
566
  });
534
567
 
535
- await expect(run(app)).resolves.toBe("ok");
568
+ const result = await run(app);
569
+ expect(result.value).toBe("ok");
536
570
  expect(supressMock).toHaveBeenCalledTimes(2);
537
571
  });
538
572
  });
@@ -653,18 +687,335 @@ describe("run", () => {
653
687
  const app = defineResource({
654
688
  id: "app",
655
689
  register: [testResource],
656
- dependencies: { testResource, store: globalResources.store },
657
- async init(_, { testResource, store }) {
690
+ dependencies: { testResource },
691
+ async init(_, { testResource }) {
658
692
  expect(testResource).toBe("Resource Value");
693
+ return testResource;
694
+ },
695
+ });
696
+
697
+ const result = await run(app);
698
+ expect(result.value).toBe("Resource Value");
699
+ await result.dispose();
700
+ expect(disposeFn).toHaveBeenCalledWith(
701
+ "Resource Value",
702
+ {},
703
+ {},
704
+ undefined
705
+ );
706
+ });
707
+
708
+ it("should work with primitive return values", async () => {
709
+ const disposeFn = jest.fn();
710
+ const testResource = defineResource({
711
+ id: "test.resource",
712
+ dispose: disposeFn,
713
+ init: async () => "Resource Value",
714
+ });
715
+
716
+ const app = defineResource({
717
+ id: "app",
718
+ register: [testResource],
719
+ dependencies: { testResource },
720
+ async init(_, { testResource }) {
721
+ return 42; // primitive number
722
+ },
723
+ });
724
+
725
+ const result = await run(app);
726
+ expect(result.value + 1).toBe(43); // should work as number
727
+ expect(result.value).toBe(42);
728
+ await result.dispose();
729
+ expect(disposeFn).toHaveBeenCalled();
730
+ });
731
+
732
+ it("should work with object return values", async () => {
733
+ const disposeFn = jest.fn();
734
+ const testResource = defineResource({
735
+ id: "test.resource",
736
+ dispose: disposeFn,
737
+ init: async () => "Resource Value",
738
+ });
739
+
740
+ const app = defineResource({
741
+ id: "app",
742
+ register: [testResource],
743
+ dependencies: { testResource },
744
+ async init(_, { testResource }) {
745
+ return { api: "server", value: 42 };
746
+ },
747
+ });
748
+
749
+ const result = await run(app);
750
+ expect(result.value.api).toBe("server");
751
+ expect(result.value.value).toBe(42);
752
+ await result.dispose();
753
+ expect(disposeFn).toHaveBeenCalled();
754
+ });
755
+
756
+ it("should work with null return values", async () => {
757
+ const disposeFn = jest.fn();
758
+ const testResource = defineResource({
759
+ id: "test.resource",
760
+ dispose: disposeFn,
761
+ init: async () => "Resource Value",
762
+ });
763
+
764
+ const app = defineResource({
765
+ id: "app",
766
+ register: [testResource],
767
+ dependencies: { testResource },
768
+ async init(_, { testResource }) {
769
+ return null; // null return value
770
+ },
771
+ });
772
+
773
+ const result = await run(app);
774
+ expect(result.value).toBe(null);
775
+ await result.dispose();
776
+ expect(disposeFn).toHaveBeenCalled();
777
+ });
778
+
779
+ it("should work with undefined return values", async () => {
780
+ const disposeFn = jest.fn();
781
+ const testResource = defineResource({
782
+ id: "test.resource",
783
+ dispose: disposeFn,
784
+ init: async () => "Resource Value",
785
+ });
786
+
787
+ const app = defineResource({
788
+ id: "app",
789
+ register: [testResource],
790
+ dependencies: { testResource },
791
+ async init(_, { testResource }) {
792
+ return undefined; // undefined return value
793
+ },
794
+ });
795
+
796
+ const result = await run(app);
797
+ expect(result.value).toBe(undefined);
798
+ await result.dispose();
799
+ expect(disposeFn).toHaveBeenCalled();
800
+ });
801
+
802
+ it("should work with boolean return values", async () => {
803
+ const disposeFn = jest.fn();
804
+ const testResource = defineResource({
805
+ id: "test.resource",
806
+ dispose: disposeFn,
807
+ init: async () => "Resource Value",
808
+ });
809
+
810
+ const app = defineResource({
811
+ id: "app",
812
+ register: [testResource],
813
+ dependencies: { testResource },
814
+ async init(_, { testResource }) {
815
+ return true; // boolean return value
816
+ },
817
+ });
818
+
819
+ const result = await run(app);
820
+ expect(result.value).toBe(true);
821
+ await result.dispose();
822
+ expect(disposeFn).toHaveBeenCalled();
823
+ });
824
+
825
+ it("should forward string methods correctly", async () => {
826
+ const disposeFn = jest.fn();
827
+ const testResource = defineResource({
828
+ id: "test.resource",
829
+ dispose: disposeFn,
830
+ init: async () => "Resource Value",
831
+ });
659
832
 
660
- return {
661
- dispose: () => store.dispose(),
662
- };
833
+ const app = defineResource({
834
+ id: "app",
835
+ register: [testResource],
836
+ dependencies: { testResource },
837
+ async init(_, { testResource }) {
838
+ return "hello world test"; // string return value
663
839
  },
664
840
  });
665
841
 
666
842
  const result = await run(app);
843
+ expect(result.value).toBe("hello world test");
667
844
  await result.dispose();
845
+ expect(disposeFn).toHaveBeenCalled();
846
+ });
847
+
848
+ it("should work with symbol return values", async () => {
849
+ const disposeFn = jest.fn();
850
+ const testResource = defineResource({
851
+ id: "test.resource",
852
+ dispose: disposeFn,
853
+ init: async () => "Resource Value",
854
+ });
855
+
856
+ const app = defineResource({
857
+ id: "app",
858
+ register: [testResource],
859
+ dependencies: { testResource },
860
+ async init(_, { testResource }) {
861
+ return Symbol("test"); // symbol return value
862
+ },
863
+ });
864
+
865
+ const result = await run(app);
866
+ expect(typeof result.value).toBe("symbol");
867
+ expect(result.value.toString()).toBe("Symbol(test)");
868
+ await result.dispose();
869
+ expect(disposeFn).toHaveBeenCalled();
870
+ });
871
+
872
+ it("should work with bigint return values", async () => {
873
+ const disposeFn = jest.fn();
874
+ const testResource = defineResource({
875
+ id: "test.resource",
876
+ dispose: disposeFn,
877
+ init: async () => "Resource Value",
878
+ });
879
+
880
+ const app = defineResource({
881
+ id: "app",
882
+ register: [testResource],
883
+ dependencies: { testResource },
884
+ async init(_, { testResource }) {
885
+ return BigInt(123); // bigint return value
886
+ },
887
+ });
888
+
889
+ const result = await run(app);
890
+ expect(typeof result.value).toBe("bigint");
891
+ expect(result.value).toBe(BigInt(123));
892
+ expect(result.value.toString()).toBe("123");
893
+ await result.dispose();
894
+ expect(disposeFn).toHaveBeenCalled();
895
+ });
896
+ });
897
+
898
+ describe("private context resources", () => {
899
+ it("should share private context between init and dispose", async () => {
900
+ const disposeFn = jest.fn();
901
+ const dbResource = defineResource({
902
+ id: "db.resource",
903
+ context: () => ({ connections: [] as string[] }),
904
+ async init(config, deps, context) {
905
+ context.connections.push("main-db");
906
+ // @ts-expect-error - should not allow access to non-existent properties
907
+ context.nonExistentProperty;
908
+ // @ts-expect-error - should not allow writing to non-existent properties
909
+ context.anotherProperty = "test";
910
+ return "connected";
911
+ },
912
+ async dispose(value, config, deps, context) {
913
+ expect(context.connections).toEqual(["main-db"]);
914
+ disposeFn();
915
+
916
+ context.connections.length = 0; // cleanup
917
+ // @ts-expect-error - should not allow access to non-existent properties in dispose
918
+ context.undefinedProperty;
919
+ },
920
+ });
921
+
922
+ const app = defineResource({
923
+ id: "app",
924
+ register: [dbResource],
925
+ dependencies: { dbResource },
926
+ async init(_, { dbResource }) {
927
+ return dbResource;
928
+ },
929
+ });
930
+
931
+ const result = await run(app);
932
+ await result.dispose();
933
+
934
+ expect(disposeFn).toHaveBeenCalled();
935
+ });
936
+
937
+ it("should work without context", async () => {
938
+ const simpleResource = defineResource({
939
+ id: "simple.resource",
940
+ async init(config, deps) {
941
+ return "simple value";
942
+ },
943
+ });
944
+
945
+ const app = defineResource({
946
+ id: "app",
947
+ register: [simpleResource],
948
+ dependencies: { simpleResource },
949
+ async init(_, { simpleResource }) {
950
+ return simpleResource;
951
+ },
952
+ });
953
+
954
+ const result = await run(app);
955
+ expect(result.value).toBe("simple value");
956
+ await result.dispose();
957
+ });
958
+
959
+ it("should work with private context and dispose only", async () => {
960
+ const disposeFn = jest.fn();
961
+
962
+ // Test dispose function with private context but no init
963
+ const contextOnlyResource = defineResource({
964
+ id: "context.only",
965
+ context: () => ({ cleanupTasks: ["task1", "task2"] }),
966
+ // This resource only has dispose, testing the private context in dispose scenario
967
+ dispose: async function (value, config, deps, context) {
968
+ // When there's no init, dispose still gets called but private context should be available
969
+ // Note: This won't have private context since init wasn't called
970
+ disposeFn();
971
+ },
972
+ });
973
+
974
+ const app = defineResource({
975
+ id: "app",
976
+ register: [contextOnlyResource],
977
+ dependencies: { contextOnlyResource },
978
+ async init(_, { contextOnlyResource }) {
979
+ // Resource without init should be undefined
980
+ expect(contextOnlyResource).toBeUndefined();
981
+ return "app started";
982
+ },
983
+ });
984
+
985
+ const result = await run(app);
986
+ expect(result.value).toBe("app started");
987
+ await result.dispose();
988
+ expect(disposeFn).toHaveBeenCalled();
989
+ });
990
+
991
+ it("should handle resources without init method and proper disposal types", async () => {
992
+ const disposeFn = jest.fn();
993
+
994
+ // Resource without init - just registers other resources
995
+ const registrationOnlyResource = defineResource({
996
+ id: "registration.only",
997
+ // No init method
998
+ dispose: disposeFn,
999
+ register: [],
1000
+ });
1001
+
1002
+ const app = defineResource({
1003
+ id: "app",
1004
+ register: [registrationOnlyResource],
1005
+ dependencies: { registrationOnlyResource },
1006
+ async init(_, { registrationOnlyResource }) {
1007
+ // registrationOnlyResource should be undefined since no init
1008
+ expect(registrationOnlyResource).toBeUndefined();
1009
+ return 42;
1010
+ },
1011
+ });
1012
+
1013
+ const result = await run(app);
1014
+ expect(result.value).toBe(42);
1015
+ await result.dispose();
1016
+
1017
+ // dispose should be called with undefined value since no init
1018
+ expect(disposeFn).toHaveBeenCalledWith(undefined, {}, {}, {});
668
1019
  });
669
1020
  });
670
1021
  });
@@ -0,0 +1,244 @@
1
+ import { defineTask, defineResource } from "../define";
2
+ import { run } from "../run";
3
+
4
+ describe("setOutput functionality", () => {
5
+ it("should allow afterRun event listeners to modify task output", async () => {
6
+ const originalTask = defineTask({
7
+ id: "original.task",
8
+ run: async (input: string) => `Hello, ${input}!`,
9
+ });
10
+
11
+ const transformListener = defineTask({
12
+ id: "transform.listener",
13
+ on: originalTask.events.afterRun,
14
+ run: async (event) => {
15
+ const transformedOutput = event.data.output.toUpperCase();
16
+ event.data.setOutput(transformedOutput);
17
+ },
18
+ });
19
+
20
+ const app = defineResource({
21
+ id: "app",
22
+ register: [originalTask, transformListener],
23
+ dependencies: { originalTask },
24
+ async init(_, { originalTask }) {
25
+ const result = await originalTask("World");
26
+ expect(result).toBe("HELLO, WORLD!");
27
+ return result;
28
+ },
29
+ });
30
+
31
+ await run(app);
32
+ });
33
+
34
+ it("should preserve original output if setOutput is not called", async () => {
35
+ const originalTask = defineTask({
36
+ id: "original.task",
37
+ run: async (input: string) => `Hello, ${input}!`,
38
+ });
39
+
40
+ const observerListener = defineTask({
41
+ id: "observer.listener",
42
+ on: originalTask.events.afterRun,
43
+ run: async (event) => {
44
+ // Just observe, don't modify
45
+ expect(event.data.output).toBe("Hello, World!");
46
+ },
47
+ });
48
+
49
+ const app = defineResource({
50
+ id: "app",
51
+ register: [originalTask, observerListener],
52
+ dependencies: { originalTask },
53
+ async init(_, { originalTask }) {
54
+ const result = await originalTask("World");
55
+ expect(result).toBe("Hello, World!");
56
+ return result;
57
+ },
58
+ });
59
+
60
+ await run(app);
61
+ });
62
+
63
+ it("should handle multiple afterRun listeners with setOutput (last one wins)", async () => {
64
+ const originalTask = defineTask({
65
+ id: "original.task",
66
+ run: async (input: number) => input * 2,
67
+ });
68
+
69
+ const firstTransform = defineTask({
70
+ id: "first.transform",
71
+ on: originalTask.events.afterRun,
72
+ listenerOrder: 1,
73
+ run: async (event) => {
74
+ event.data.setOutput(event.data.output + 10);
75
+ },
76
+ });
77
+
78
+ const secondTransform = defineTask({
79
+ id: "second.transform",
80
+ on: originalTask.events.afterRun,
81
+ listenerOrder: 2,
82
+ run: async (event) => {
83
+ event.data.setOutput(event.data.output * 3);
84
+ },
85
+ });
86
+
87
+ const app = defineResource({
88
+ id: "app",
89
+ register: [originalTask, firstTransform, secondTransform],
90
+ dependencies: { originalTask },
91
+ async init(_, { originalTask }) {
92
+ const result = await originalTask(5);
93
+ // Original: 5 * 2 = 10
94
+ // First transform: 10 + 10 = 20
95
+ // Second transform: 20 * 3 = 60
96
+ expect(result).toBe(60);
97
+ return result;
98
+ },
99
+ });
100
+
101
+ await run(app);
102
+ });
103
+
104
+ it("should work with external library scenario", async () => {
105
+ // Simulate an external library task
106
+ const externalLibraryTask = defineTask({
107
+ id: "external.library.task",
108
+ run: async (data: { name: string; age: number }) => {
109
+ return {
110
+ id: Math.random(),
111
+ name: data.name,
112
+ age: data.age,
113
+ timestamp: Date.now(),
114
+ };
115
+ },
116
+ });
117
+
118
+ // Create a transformer for the external task
119
+ const resultTransformer = defineTask({
120
+ id: "result.transformer",
121
+ on: externalLibraryTask.events.afterRun,
122
+ run: async (event) => {
123
+ const result = event.data.output;
124
+ // Add some computed fields
125
+ const enrichedResult = {
126
+ ...result,
127
+ displayName: `${result.name} (${result.age} years old)`,
128
+ isAdult: result.age >= 18,
129
+ };
130
+ event.data.setOutput(enrichedResult);
131
+ },
132
+ });
133
+
134
+ const app = defineResource({
135
+ id: "app",
136
+ register: [externalLibraryTask, resultTransformer],
137
+ dependencies: { externalLibraryTask },
138
+ async init(_, { externalLibraryTask }) {
139
+ const result = await externalLibraryTask({ name: "Alice", age: 25 });
140
+
141
+ expect(result).toMatchObject({
142
+ name: "Alice",
143
+ age: 25,
144
+ displayName: "Alice (25 years old)",
145
+ isAdult: true,
146
+ });
147
+ expect(result.id).toBeDefined();
148
+ expect(result.timestamp).toBeDefined();
149
+
150
+ return result;
151
+ },
152
+ });
153
+
154
+ await run(app);
155
+ });
156
+
157
+ it("should handle type safety with setOutput", async () => {
158
+ interface TaskOutput {
159
+ message: string;
160
+ count: number;
161
+ }
162
+
163
+ const typedTask = defineTask({
164
+ id: "typed.task",
165
+ run: async (input: string): Promise<TaskOutput> => ({
166
+ message: `Hello, ${input}!`,
167
+ count: input.length,
168
+ }),
169
+ });
170
+
171
+ const typedTransformer = defineTask({
172
+ id: "typed.transformer",
173
+ on: typedTask.events.afterRun,
174
+ run: async (event) => {
175
+ const result = event.data.output;
176
+ const newResult: TaskOutput = {
177
+ message: result.message.toUpperCase(),
178
+ count: result.count * 2,
179
+ };
180
+ event.data.setOutput(newResult);
181
+ },
182
+ });
183
+
184
+ const app = defineResource({
185
+ id: "app",
186
+ register: [typedTask, typedTransformer],
187
+ dependencies: { typedTask },
188
+ async init(_, { typedTask }) {
189
+ const result = await typedTask("World");
190
+ expect(result).toEqual({
191
+ message: "HELLO, WORLD!",
192
+ count: 10, // "World".length * 2
193
+ });
194
+ return result;
195
+ },
196
+ });
197
+
198
+ await run(app);
199
+ });
200
+
201
+ it("should work with middleware and setOutput together", async () => {
202
+ const { defineMiddleware } = await import("../define");
203
+
204
+ const testMiddleware = defineMiddleware({
205
+ id: "test.middleware",
206
+ run: async ({ next, task }) => {
207
+ const result = await next(task?.input);
208
+ return `[middleware: ${result}]`;
209
+ },
210
+ });
211
+
212
+ const originalTask = defineTask({
213
+ id: "original.task",
214
+ middleware: [testMiddleware],
215
+ run: async (input: string) => `Hello, ${input}!`,
216
+ });
217
+
218
+ const outputTransformer = defineTask({
219
+ id: "output.transformer",
220
+ on: originalTask.events.afterRun,
221
+ run: async (event) => {
222
+ // The output here already includes middleware processing
223
+ const transformed = event.data.output.replace(
224
+ "middleware:",
225
+ "processed:"
226
+ );
227
+ event.data.setOutput(transformed);
228
+ },
229
+ });
230
+
231
+ const app = defineResource({
232
+ id: "app",
233
+ register: [testMiddleware, originalTask, outputTransformer],
234
+ dependencies: { originalTask },
235
+ async init(_, { originalTask }) {
236
+ const result = await originalTask("World");
237
+ expect(result).toBe("[processed: Hello, World!]");
238
+ return result;
239
+ },
240
+ });
241
+
242
+ await run(app);
243
+ });
244
+ });