@bluelibs/runner 2.2.3 → 3.0.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 (204) hide show
  1. package/README.md +1315 -942
  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 +22 -3
  9. package/dist/define.js +52 -8
  10. package/dist/define.js.map +1 -1
  11. package/dist/defs.d.ts +52 -31
  12. package/dist/defs.js +10 -2
  13. package/dist/defs.js.map +1 -1
  14. package/dist/errors.js +1 -1
  15. package/dist/errors.js.map +1 -1
  16. package/dist/event.types.d.ts +18 -0
  17. package/dist/event.types.js +4 -0
  18. package/dist/event.types.js.map +1 -0
  19. package/dist/examples/registrator-example.d.ts +122 -0
  20. package/dist/examples/registrator-example.js +147 -0
  21. package/dist/examples/registrator-example.js.map +1 -0
  22. package/dist/globals/globalEvents.d.ts +41 -0
  23. package/dist/globals/globalEvents.js +94 -0
  24. package/dist/globals/globalEvents.js.map +1 -0
  25. package/dist/globals/globalMiddleware.d.ts +23 -0
  26. package/dist/globals/globalMiddleware.js +15 -0
  27. package/dist/globals/globalMiddleware.js.map +1 -0
  28. package/dist/globals/globalResources.d.ts +27 -0
  29. package/dist/globals/globalResources.js +47 -0
  30. package/dist/globals/globalResources.js.map +1 -0
  31. package/dist/globals/middleware/cache.middleware.d.ts +34 -0
  32. package/dist/globals/middleware/cache.middleware.js +85 -0
  33. package/dist/globals/middleware/cache.middleware.js.map +1 -0
  34. package/dist/globals/middleware/requireContext.middleware.d.ts +6 -0
  35. package/dist/globals/middleware/requireContext.middleware.js +25 -0
  36. package/dist/globals/middleware/requireContext.middleware.js.map +1 -0
  37. package/dist/globals/middleware/retry.middleware.d.ts +20 -0
  38. package/dist/globals/middleware/retry.middleware.js +34 -0
  39. package/dist/globals/middleware/retry.middleware.js.map +1 -0
  40. package/dist/globals/resources/queue.resource.d.ts +7 -0
  41. package/dist/globals/resources/queue.resource.js +31 -0
  42. package/dist/globals/resources/queue.resource.js.map +1 -0
  43. package/dist/index.d.ts +45 -9
  44. package/dist/index.js +14 -9
  45. package/dist/index.js.map +1 -1
  46. package/dist/middleware.types.d.ts +40 -0
  47. package/dist/middleware.types.js +4 -0
  48. package/dist/middleware.types.js.map +1 -0
  49. package/dist/models/DependencyProcessor.d.ts +2 -1
  50. package/dist/models/DependencyProcessor.js +11 -13
  51. package/dist/models/DependencyProcessor.js.map +1 -1
  52. package/dist/models/EventManager.d.ts +5 -0
  53. package/dist/models/EventManager.js +44 -2
  54. package/dist/models/EventManager.js.map +1 -1
  55. package/dist/models/Logger.d.ts +30 -12
  56. package/dist/models/Logger.js +130 -42
  57. package/dist/models/Logger.js.map +1 -1
  58. package/dist/models/OverrideManager.d.ts +13 -0
  59. package/dist/models/OverrideManager.js +70 -0
  60. package/dist/models/OverrideManager.js.map +1 -0
  61. package/dist/models/Queue.d.ts +25 -0
  62. package/dist/models/Queue.js +54 -0
  63. package/dist/models/Queue.js.map +1 -0
  64. package/dist/models/ResourceInitializer.d.ts +5 -2
  65. package/dist/models/ResourceInitializer.js +20 -14
  66. package/dist/models/ResourceInitializer.js.map +1 -1
  67. package/dist/models/Semaphore.d.ts +61 -0
  68. package/dist/models/Semaphore.js +166 -0
  69. package/dist/models/Semaphore.js.map +1 -0
  70. package/dist/models/Store.d.ts +17 -72
  71. package/dist/models/Store.js +71 -269
  72. package/dist/models/Store.js.map +1 -1
  73. package/dist/models/StoreConstants.d.ts +11 -0
  74. package/dist/models/StoreConstants.js +18 -0
  75. package/dist/models/StoreConstants.js.map +1 -0
  76. package/dist/models/StoreRegistry.d.ts +25 -0
  77. package/dist/models/StoreRegistry.js +171 -0
  78. package/dist/models/StoreRegistry.js.map +1 -0
  79. package/dist/models/StoreTypes.d.ts +21 -0
  80. package/dist/models/StoreTypes.js +3 -0
  81. package/dist/models/StoreTypes.js.map +1 -0
  82. package/dist/models/StoreValidator.d.ts +10 -0
  83. package/dist/models/StoreValidator.js +41 -0
  84. package/dist/models/StoreValidator.js.map +1 -0
  85. package/dist/models/TaskRunner.js +39 -24
  86. package/dist/models/TaskRunner.js.map +1 -1
  87. package/dist/models/VarStore.d.ts +17 -0
  88. package/dist/models/VarStore.js +60 -0
  89. package/dist/models/VarStore.js.map +1 -0
  90. package/dist/models/index.d.ts +3 -0
  91. package/dist/models/index.js +3 -0
  92. package/dist/models/index.js.map +1 -1
  93. package/dist/resource.types.d.ts +31 -0
  94. package/dist/resource.types.js +3 -0
  95. package/dist/resource.types.js.map +1 -0
  96. package/dist/run.d.ts +4 -1
  97. package/dist/run.js +6 -3
  98. package/dist/run.js.map +1 -1
  99. package/dist/symbols.d.ts +24 -0
  100. package/dist/symbols.js +29 -0
  101. package/dist/symbols.js.map +1 -0
  102. package/dist/task.types.d.ts +55 -0
  103. package/dist/task.types.js +23 -0
  104. package/dist/task.types.js.map +1 -0
  105. package/dist/tools/registratorId.d.ts +4 -0
  106. package/dist/tools/registratorId.js +40 -0
  107. package/dist/tools/registratorId.js.map +1 -0
  108. package/dist/tools/simpleHash.d.ts +9 -0
  109. package/dist/tools/simpleHash.js +34 -0
  110. package/dist/tools/simpleHash.js.map +1 -0
  111. package/dist/types/base-interfaces.d.ts +18 -0
  112. package/dist/types/base-interfaces.js +6 -0
  113. package/dist/types/base-interfaces.js.map +1 -0
  114. package/dist/types/base.d.ts +13 -0
  115. package/dist/types/base.js +3 -0
  116. package/dist/types/base.js.map +1 -0
  117. package/dist/types/dependencies.d.ts +22 -0
  118. package/dist/types/dependencies.js +3 -0
  119. package/dist/types/dependencies.js.map +1 -0
  120. package/dist/types/dependency-core.d.ts +14 -0
  121. package/dist/types/dependency-core.js +5 -0
  122. package/dist/types/dependency-core.js.map +1 -0
  123. package/dist/types/events.d.ts +52 -0
  124. package/dist/types/events.js +6 -0
  125. package/dist/types/events.js.map +1 -0
  126. package/dist/types/hooks.d.ts +16 -0
  127. package/dist/types/hooks.js +5 -0
  128. package/dist/types/hooks.js.map +1 -0
  129. package/dist/types/index.d.ts +14 -0
  130. package/dist/types/index.js +27 -0
  131. package/dist/types/index.js.map +1 -0
  132. package/dist/types/meta.d.ts +13 -0
  133. package/dist/types/meta.js +5 -0
  134. package/dist/types/meta.js.map +1 -0
  135. package/dist/types/middleware.d.ts +38 -0
  136. package/dist/types/middleware.js +6 -0
  137. package/dist/types/middleware.js.map +1 -0
  138. package/dist/types/registerable.d.ts +10 -0
  139. package/dist/types/registerable.js +5 -0
  140. package/dist/types/registerable.js.map +1 -0
  141. package/dist/types/resources.d.ts +44 -0
  142. package/dist/types/resources.js +5 -0
  143. package/dist/types/resources.js.map +1 -0
  144. package/dist/types/symbols.d.ts +24 -0
  145. package/dist/types/symbols.js +30 -0
  146. package/dist/types/symbols.js.map +1 -0
  147. package/dist/types/tasks.d.ts +41 -0
  148. package/dist/types/tasks.js +5 -0
  149. package/dist/types/tasks.js.map +1 -0
  150. package/dist/types/utilities.d.ts +7 -0
  151. package/dist/types/utilities.js +5 -0
  152. package/dist/types/utilities.js.map +1 -0
  153. package/package.json +10 -6
  154. package/src/__tests__/benchmark/benchmark.test.ts +1 -1
  155. package/src/__tests__/context.test.ts +91 -0
  156. package/src/__tests__/errors.test.ts +8 -5
  157. package/src/__tests__/globalEvents.test.ts +1 -1
  158. package/src/__tests__/globals/cache.middleware.test.ts +772 -0
  159. package/src/__tests__/globals/queue.resource.test.ts +141 -0
  160. package/src/__tests__/globals/requireContext.middleware.test.ts +98 -0
  161. package/src/__tests__/globals/retry.middleware.test.ts +157 -0
  162. package/src/__tests__/index.helper.test.ts +55 -0
  163. package/src/__tests__/models/EventManager.test.ts +144 -0
  164. package/src/__tests__/models/Logger.test.ts +291 -34
  165. package/src/__tests__/models/Queue.test.ts +189 -0
  166. package/src/__tests__/models/ResourceInitializer.test.ts +8 -6
  167. package/src/__tests__/models/Semaphore.test.ts +713 -0
  168. package/src/__tests__/models/Store.test.ts +40 -0
  169. package/src/__tests__/models/TaskRunner.test.ts +86 -5
  170. package/src/__tests__/run.middleware.test.ts +166 -12
  171. package/src/__tests__/run.overrides.test.ts +13 -10
  172. package/src/__tests__/run.test.ts +363 -12
  173. package/src/__tests__/setOutput.test.ts +244 -0
  174. package/src/__tests__/tools/getCallerFile.test.ts +9 -9
  175. package/src/__tests__/typesafety.test.ts +54 -39
  176. package/src/context.ts +86 -0
  177. package/src/define.ts +84 -14
  178. package/src/defs.ts +91 -41
  179. package/src/errors.ts +3 -1
  180. package/src/{globalEvents.ts → globals/globalEvents.ts} +13 -12
  181. package/src/globals/globalMiddleware.ts +14 -0
  182. package/src/{globalResources.ts → globals/globalResources.ts} +14 -10
  183. package/src/globals/middleware/cache.middleware.ts +115 -0
  184. package/src/globals/middleware/requireContext.middleware.ts +36 -0
  185. package/src/globals/middleware/retry.middleware.ts +56 -0
  186. package/src/globals/resources/queue.resource.ts +34 -0
  187. package/src/index.ts +9 -5
  188. package/src/models/DependencyProcessor.ts +36 -40
  189. package/src/models/EventManager.ts +45 -5
  190. package/src/models/Logger.ts +170 -48
  191. package/src/models/OverrideManager.ts +84 -0
  192. package/src/models/Queue.ts +66 -0
  193. package/src/models/ResourceInitializer.ts +38 -20
  194. package/src/models/Semaphore.ts +208 -0
  195. package/src/models/Store.ts +94 -342
  196. package/src/models/StoreConstants.ts +17 -0
  197. package/src/models/StoreRegistry.ts +217 -0
  198. package/src/models/StoreTypes.ts +46 -0
  199. package/src/models/StoreValidator.ts +38 -0
  200. package/src/models/TaskRunner.ts +53 -40
  201. package/src/models/index.ts +3 -0
  202. package/src/run.ts +7 -4
  203. package/src/__tests__/index.ts +0 -15
  204. 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
@@ -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
+ });
659
707
 
660
- return {
661
- dispose: () => store.dispose(),
662
- };
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
663
722
  },
664
723
  });
665
724
 
666
725
  const result = await run(app);
726
+ expect(result.value + 1).toBe(43); // should work as number
727
+ expect(result.value).toBe(42);
667
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
+ });
832
+
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
839
+ },
840
+ });
841
+
842
+ const result = await run(app);
843
+ expect(result.value).toBe("hello world test");
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
+ });
@@ -38,14 +38,14 @@ describe("getCallerFile", () => {
38
38
  id: "event",
39
39
  });
40
40
 
41
- expect(task[symbols.filePath]).toBeDefined();
42
- expect(resource[symbols.filePath]).toBeDefined();
43
- expect(middleware[symbols.filePath]).toBeDefined();
44
- expect(event[symbols.filePath]).toBeDefined();
45
-
46
- expect(task[symbols.filePath]).toContain("getCallerFile.test");
47
- expect(resource[symbols.filePath]).toContain("getCallerFile.test");
48
- expect(middleware[symbols.filePath]).toContain("getCallerFile.test");
49
- expect(event[symbols.filePath]).toContain("getCallerFile.test");
41
+ expect((task as any)[symbols.filePath]).toBeDefined();
42
+ expect((resource as any)[symbols.filePath]).toBeDefined();
43
+ expect((middleware as any)[symbols.filePath]).toBeDefined();
44
+ expect((event as any)[symbols.filePath]).toBeDefined();
45
+
46
+ expect((task as any)[symbols.filePath]).toContain("getCallerFile.test");
47
+ expect((resource as any)[symbols.filePath]).toContain("getCallerFile.test");
48
+ expect((middleware as any)[symbols.filePath]).toContain("getCallerFile.test");
49
+ expect((event as any)[symbols.filePath]).toContain("getCallerFile.test");
50
50
  });
51
51
  });