@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,10 @@ import {
5
5
  defineEvent,
6
6
  } from "../../define";
7
7
  import { symbols } from "../../defs";
8
- import { getCallerFile } from "../../tools/getCallerFile";
8
+ import {
9
+ getCallerFile,
10
+ generateCallerIdFromFile,
11
+ } from "../../tools/getCallerFile";
9
12
 
10
13
  describe("getCallerFile", () => {
11
14
  it("should return the file name of the caller", () => {
@@ -38,14 +41,126 @@ describe("getCallerFile", () => {
38
41
  id: "event",
39
42
  });
40
43
 
41
- expect(task[symbols.filePath]).toBeDefined();
42
- expect(resource[symbols.filePath]).toBeDefined();
43
- expect(middleware[symbols.filePath]).toBeDefined();
44
- expect(event[symbols.filePath]).toBeDefined();
44
+ expect((task as any)[symbols.filePath]).toBeDefined();
45
+ expect((resource as any)[symbols.filePath]).toBeDefined();
46
+ expect((middleware as any)[symbols.filePath]).toBeDefined();
47
+ expect((event as any)[symbols.filePath]).toBeDefined();
48
+
49
+ expect((task as any)[symbols.filePath]).toContain("getCallerFile.test");
50
+ expect((resource as any)[symbols.filePath]).toContain("getCallerFile.test");
51
+ expect((middleware as any)[symbols.filePath]).toContain(
52
+ "getCallerFile.test"
53
+ );
54
+ expect((event as any)[symbols.filePath]).toContain("getCallerFile.test");
55
+ });
56
+ });
57
+
58
+ describe("generateCallerIdFromFile", () => {
59
+ it("should generate a symbol from a path containing src", () => {
60
+ const filePath =
61
+ "/Users/theodordiaconu/Projects/runner/src/globals/resources/queue.resource.ts";
62
+ const expectedDescription = "globals.resources.queue.resource";
63
+ expect(generateCallerIdFromFile(filePath).description).toEqual(
64
+ expectedDescription
65
+ );
66
+ });
67
+
68
+ it("should generate a symbol from a path not containing src", () => {
69
+ const filePath =
70
+ "/Users/theodordiaconu/Projects/runner/dist/globals/resources/queue.resource.ts";
71
+ const expectedDescription = "dist.globals.resources.queue.resource";
72
+ expect(generateCallerIdFromFile(filePath).description).toEqual(
73
+ expectedDescription
74
+ );
75
+ });
76
+
77
+ it("should handle paths with few parts when src is not present", () => {
78
+ const filePath = "a/b/c.ts";
79
+ const expectedDescription = "a.b.c";
80
+ expect(generateCallerIdFromFile(filePath).description).toEqual(
81
+ expectedDescription
82
+ );
83
+ });
84
+
85
+ it("should handle paths with backslashes", () => {
86
+ const filePath =
87
+ "C:\\Users\\theodordiaconu\\Projects\\runner\\src\\globals\\resources\\queue.resource.ts";
88
+ const expectedDescription = "globals.resources.queue.resource";
89
+ expect(generateCallerIdFromFile(filePath).description).toEqual(
90
+ expectedDescription
91
+ );
92
+ });
93
+
94
+ it("should handle file names without extensions", () => {
95
+ const filePath =
96
+ "/Users/theodordiaconu/Projects/runner/src/globals/resources/queue.resource";
97
+ const expectedDescription = "globals.resources.queue.resource";
98
+ expect(generateCallerIdFromFile(filePath).description).toEqual(
99
+ expectedDescription
100
+ );
101
+ });
102
+
103
+ it("should handle file names with multiple dots", () => {
104
+ const filePath =
105
+ "/Users/theodordiaconu/Projects/runner/src/globals/resources/queue.resource.test.ts";
106
+ const expectedDescription = "globals.resources.queue.resource.test";
107
+ expect(generateCallerIdFromFile(filePath).description).toEqual(
108
+ expectedDescription
109
+ );
110
+ });
111
+
112
+ it("should generate a symbol from a path containing node_modules", () => {
113
+ const filePath =
114
+ "/Users/theodordiaconu/Projects/runner/node_modules/some-package/dist/index.js";
115
+ const expectedDescription = "some-package.dist.index";
116
+ expect(generateCallerIdFromFile(filePath).description).toEqual(
117
+ expectedDescription
118
+ );
119
+ });
120
+
121
+ it("should respect the fallbackParts argument", () => {
122
+ const filePath = "a/b/c/d/e.ts";
123
+ const expectedDescription = "d.e";
124
+ expect(generateCallerIdFromFile(filePath, "", 2).description).toEqual(
125
+ expectedDescription
126
+ );
127
+ });
128
+
129
+ it("should append the suffix to the symbol description", () => {
130
+ const filePath = "a/b/c.ts";
131
+ const expectedDescription = "a.b.c.my-suffix";
132
+ expect(generateCallerIdFromFile(filePath, "my-suffix").description).toEqual(
133
+ expectedDescription
134
+ );
135
+ });
136
+
137
+ it("should not append the suffix if it is already in the file name", () => {
138
+ const filePath = "a/b/c.resource.ts";
139
+ const expectedDescription = "a.b.c.resource";
140
+ expect(generateCallerIdFromFile(filePath, "resource").description).toEqual(
141
+ expectedDescription
142
+ );
143
+ });
144
+
145
+ it("should handle empty path or path with no relevant parts", () => {
146
+ // Test with empty string - this creates relevantParts = [""] which is not empty
147
+ const filePath = "";
148
+ const expectedDescription = ".suffix";
149
+ expect(generateCallerIdFromFile(filePath, "suffix").description).toEqual(
150
+ expectedDescription
151
+ );
152
+
153
+ // Test case where 'src' is at the end, making relevantParts empty (triggers line 77 else branch)
154
+ const result = generateCallerIdFromFile("/some/path/src", "suffix");
155
+ expect(result.description).toEqual(".suffix");
156
+ });
45
157
 
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");
158
+ it("should use fallback parts when no src or node_modules found", () => {
159
+ // Test path without src or node_modules to trigger the else branch (line 59)
160
+ const filePath = "/some/other/deep/path/file.js";
161
+ const expectedDescription = "other.deep.path.file";
162
+ expect(generateCallerIdFromFile(filePath, "", 4).description).toEqual(
163
+ expectedDescription
164
+ );
50
165
  });
51
166
  });
@@ -1,5 +1,17 @@
1
- import { defineEvent, defineTask, defineResource } from "../define";
2
- import { RegisterableItems } from "../defs";
1
+ import {
2
+ defineEvent,
3
+ defineTask,
4
+ defineResource,
5
+ defineMiddleware,
6
+ } from "../define";
7
+ import {
8
+ IEventDefinition,
9
+ IMiddlewareDefinition,
10
+ IResource,
11
+ IResourceWithConfig,
12
+ ITaskDefinition,
13
+ RegisterableItems,
14
+ } from "../defs";
3
15
 
4
16
  describe("typesafety", () => {
5
17
  it("tasks, resources: should have propper type safety for dependeices", async () => {
@@ -7,10 +19,43 @@ describe("typesafety", () => {
7
19
  message: string;
8
20
  };
9
21
 
22
+ const middleware = defineMiddleware({
23
+ id: "middleware",
24
+ run: async (input, deps) => {
25
+ return input;
26
+ },
27
+ });
28
+
29
+ type MiddlewareConfig = {
30
+ message: string;
31
+ };
32
+
33
+ type MiddlewareOptionalConfig = {
34
+ message?: string;
35
+ };
36
+
37
+ const middlewareWithConfig = defineMiddleware({
38
+ id: "middleware.config",
39
+ run: async (input, deps, config: MiddlewareConfig) => {
40
+ return input;
41
+ },
42
+ });
43
+
44
+ const middlewareWithOptionalConfig = defineMiddleware({
45
+ id: "middleware.optional.config",
46
+ run: async (input, deps, config: MiddlewareOptionalConfig) => {
47
+ return input;
48
+ },
49
+ });
50
+
10
51
  const event = defineEvent<{ message: string }>({
11
52
  id: "event",
12
53
  });
13
54
 
55
+ const eventWithoutArguments = defineEvent({
56
+ id: "event",
57
+ });
58
+
14
59
  const baseTask = defineTask({
15
60
  id: "task",
16
61
  run: async (input: InputTask) => "Task executed",
@@ -58,7 +103,19 @@ describe("typesafety", () => {
58
103
 
59
104
  const testResource = defineResource({
60
105
  id: "test.resource",
61
- dependencies: { task, dummyResource, event },
106
+ middleware: [
107
+ middleware,
108
+ // @ts-expect-error
109
+ middlewareWithConfig,
110
+ middlewareWithConfig.with({ message: "Hello, World!" }),
111
+ // @ts-expect-error
112
+ middlewareWithConfig.with({ message: 123 }),
113
+ middlewareWithOptionalConfig,
114
+ middlewareWithOptionalConfig.with({ message: "Hello, World!" }),
115
+ // @ts-expect-error
116
+ middlewareWithOptionalConfig.with({ message: 123 }),
117
+ ],
118
+ dependencies: { task, dummyResource, event, eventWithoutArguments },
62
119
  init: async (_, deps) => {
63
120
  const result = await deps.task({
64
121
  message: "Hello, World!",
@@ -69,6 +126,10 @@ describe("typesafety", () => {
69
126
  deps.event();
70
127
  // @ts-expect-error
71
128
  deps.event({ messagex: "Hello, World!" });
129
+ deps.eventWithoutArguments();
130
+ deps.eventWithoutArguments({});
131
+ // @ts-expect-error
132
+ deps.eventWithoutArguments({ something: false });
72
133
 
73
134
  // @ts-expect-error
74
135
  deps.dummyResource as number;
@@ -82,6 +143,13 @@ describe("typesafety", () => {
82
143
  deps.task2;
83
144
  },
84
145
  register: [
146
+ middleware,
147
+ middlewareWithConfig,
148
+ middlewareWithOptionalConfig,
149
+ middlewareWithOptionalConfig.with({ message: "Hello, World!" }),
150
+ middlewareWithConfig.with({ message: "Hello, World!" }),
151
+ // @ts-expect-error
152
+ middlewareWithConfig.with({ message: 123 }),
85
153
  dummyResourceNoConfig,
86
154
  // @ts-expect-error
87
155
  dummyResourceNoConfig.with("hello"),
@@ -141,42 +209,4 @@ describe("typesafety", () => {
141
209
 
142
210
  expect(true).toBe(true);
143
211
  });
144
-
145
- it("should work with resources that register other resources and don't infer an any to them", async () => {
146
- const resourceBase = defineResource({
147
- id: "task",
148
- init: async (_, deps) => {
149
- deps.resource;
150
- return "";
151
- },
152
- dependencies: () => ({
153
- resource,
154
- }),
155
- });
156
-
157
- const resource = defineResource({
158
- id: "resource",
159
- register: [],
160
- });
161
-
162
- resource.register = [resourceBase];
163
-
164
- // @ts-expect-error
165
- resource.DOES_NOT_EXIST;
166
-
167
- expect(true).toBe(true);
168
- });
169
-
170
- it("should work to unit test with void arguments?", async () => {
171
- const resourceBase = defineResource({
172
- id: "task",
173
- init: async (_, deps) => {
174
- return "";
175
- },
176
- });
177
-
178
- if (resourceBase.init) {
179
- resourceBase.init(undefined, {});
180
- }
181
- });
182
212
  });
package/src/context.ts ADDED
@@ -0,0 +1,86 @@
1
+ import { AsyncLocalStorage } from "async_hooks";
2
+ import { defineMiddleware } from "./define";
3
+ import { IMiddleware, IMiddlewareConfigured } from "./defs";
4
+ import { requireContextMiddleware } from "./globals/middleware/requireContext.middleware";
5
+
6
+ /**
7
+ * Error thrown whenever a requested context is not available.
8
+ */
9
+ export class ContextError extends Error {
10
+ constructor(message: string) {
11
+ super(message);
12
+ this.name = "ContextError";
13
+ }
14
+ }
15
+
16
+ /**
17
+ * The generic Context object returned by `createContext`.
18
+ */
19
+ export interface Context<T> {
20
+ /** unique symbol used as key in the AsyncLocalStorage map */
21
+ readonly id: symbol;
22
+ /** Retrieve the current context value or throw */
23
+ use(): T;
24
+ /**
25
+ * Provide a value for this context during the lifetime of `fn()`
26
+ */
27
+ provide<R>(value: T, fn: () => Promise<R> | R): Promise<R> | R;
28
+ /**
29
+ * Generates a middleware that guarantees the context exists (and optionally
30
+ * enforces that certain keys are present on the context object).
31
+ */
32
+ require<K extends keyof T = never>(
33
+ keys?: K[]
34
+ ): IMiddlewareConfigured<{ context: Context<T> }>;
35
+ }
36
+
37
+ // The internal storage maps Context identifiers (symbols) to their values
38
+ export const storage = new AsyncLocalStorage<Map<symbol, unknown>>();
39
+
40
+ /** Returns the currently active store or undefined. */
41
+ function getCurrentStore(): Map<symbol, unknown> | undefined {
42
+ return storage.getStore();
43
+ }
44
+
45
+ /**
46
+ * Create a new typed Context. The result contains helpers similar to React’s
47
+ * Context API but adapted for async usage in Runner.
48
+ */
49
+ export function createContext<T>(name: string = "runner.context"): Context<T> {
50
+ const ctxId = Symbol(name);
51
+
52
+ function use(): T {
53
+ const store = getCurrentStore();
54
+ if (!store || !store.has(ctxId)) {
55
+ throw new ContextError(
56
+ `Context not available for symbol ${ctxId.toString()}`
57
+ );
58
+ }
59
+ return store.get(ctxId) as T;
60
+ }
61
+
62
+ function provide<R>(value: T, fn: () => Promise<R> | R): Promise<R> | R {
63
+ const currentStore = getCurrentStore();
64
+ const map = currentStore
65
+ ? new Map(currentStore)
66
+ : new Map<symbol, unknown>();
67
+ map.set(ctxId, value);
68
+
69
+ return storage.run(map, fn as any);
70
+ }
71
+
72
+ /**
73
+ * Generates a middleware that guarantees the context exists (and optionally
74
+ * enforces that certain keys are present on the context object).
75
+ */
76
+ function require(): IMiddlewareConfigured {
77
+ return requireContextMiddleware.with({ context: this as Context<T> });
78
+ }
79
+
80
+ return {
81
+ id: ctxId,
82
+ use,
83
+ provide,
84
+ require,
85
+ };
86
+ }
package/src/define.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { get } from "node:http";
2
1
  import {
3
2
  ITask,
4
3
  ITaskDefinition,
@@ -11,13 +10,15 @@ import {
11
10
  DependencyMapType,
12
11
  DependencyValuesType,
13
12
  IMiddleware,
14
- IHookDefinition,
15
13
  IEvent,
16
- IEventDefinitionConfig,
17
14
  symbolEvent,
15
+ RegisterableItems,
16
+ symbolMiddlewareConfigured,
17
+ symbolFilePath,
18
+ symbolIndexResource,
18
19
  } from "./defs";
19
20
  import { Errors } from "./errors";
20
- import { getCallerFile } from "./tools/getCallerFile";
21
+ import { generateCallerIdFromFile, getCallerFile } from "./tools/getCallerFile";
21
22
 
22
23
  // Helper function to get the caller file
23
24
 
@@ -30,30 +31,39 @@ export function defineTask<
30
31
  taskConfig: ITaskDefinition<Input, Output, Deps, TOn>
31
32
  ): ITask<Input, Output, Deps, TOn> {
32
33
  const filePath = getCallerFile();
34
+ const isAnonymous = !Boolean(taskConfig.id);
35
+ const id = taskConfig.id || generateCallerIdFromFile(filePath, "task");
33
36
  return {
34
37
  [symbols.task]: true,
35
38
  [symbols.filePath]: filePath,
36
- id: taskConfig.id,
39
+ id,
37
40
  dependencies: taskConfig.dependencies || ({} as Deps),
38
41
  middleware: taskConfig.middleware || [],
39
42
  run: taskConfig.run,
40
43
  on: taskConfig.on,
44
+ listenerOrder: taskConfig.listenerOrder,
41
45
  events: {
42
46
  beforeRun: {
43
47
  ...defineEvent({
44
- id: `${taskConfig.id}.events.beforeRun`,
48
+ id: isAnonymous
49
+ ? Symbol(`anonymous-task.events.beforeRun`)
50
+ : `${id as string}.events.beforeRun`,
45
51
  }),
46
52
  [symbols.filePath]: getCallerFile(),
47
53
  },
48
54
  afterRun: {
49
55
  ...defineEvent({
50
- id: `${taskConfig.id}.events.afterRun`,
56
+ id: isAnonymous
57
+ ? Symbol(`anonymous-task.events.afterRun`)
58
+ : `${id as string}.events.afterRun`,
51
59
  }),
52
60
  [symbols.filePath]: getCallerFile(),
53
61
  },
54
62
  onError: {
55
63
  ...defineEvent({
56
- id: `${taskConfig.id}.events.onError`,
64
+ id: isAnonymous
65
+ ? Symbol(`anonymous-task.events.onError`)
66
+ : `${id as string}.events.onError`,
57
67
  }),
58
68
  [symbols.filePath]: getCallerFile(),
59
69
  },
@@ -67,20 +77,27 @@ export function defineResource<
67
77
  TConfig = void,
68
78
  TValue = any,
69
79
  TDeps extends DependencyMapType = {},
70
- THooks = any
80
+ TPrivate = any
71
81
  >(
72
- constConfig: IResourceDefinition<TConfig, TValue, TDeps, THooks>
73
- ): IResource<TConfig, TValue, TDeps> {
74
- const filePath = getCallerFile();
82
+ constConfig: IResourceDefinition<TConfig, TValue, TDeps, TPrivate>
83
+ ): IResource<TConfig, TValue, TDeps, TPrivate> {
84
+ // The symbolFilePath might already come from defineIndex() for example
85
+ const filePath: string = constConfig[symbolFilePath] || getCallerFile();
86
+ const isIndexResource = constConfig[symbolIndexResource] || false;
87
+ const isAnonymous = !Boolean(constConfig.id);
88
+ const id =
89
+ constConfig.id ||
90
+ generateCallerIdFromFile(filePath, isIndexResource ? "index" : "resource");
75
91
  return {
76
92
  [symbols.resource]: true,
77
93
  [symbols.filePath]: filePath,
78
- id: constConfig.id,
94
+ id,
79
95
  dependencies: constConfig.dependencies,
80
96
  dispose: constConfig.dispose,
81
97
  register: constConfig.register || [],
82
98
  overrides: constConfig.overrides || [],
83
- init: constConfig.init || (async (...args: any[]) => undefined as TValue),
99
+ init: constConfig.init,
100
+ context: constConfig.context,
84
101
  with: function (config: TConfig) {
85
102
  return {
86
103
  [symbols.resourceWithConfig]: true,
@@ -93,19 +110,25 @@ export function defineResource<
93
110
  events: {
94
111
  beforeInit: {
95
112
  ...defineEvent({
96
- id: `${constConfig.id}.events.beforeInit`,
113
+ id: isAnonymous
114
+ ? Symbol(`anonymous-resource.events.beforeInit`)
115
+ : `${id as string}.events.beforeInit`,
97
116
  }),
98
117
  [symbols.filePath]: filePath,
99
118
  },
100
119
  afterInit: {
101
120
  ...defineEvent({
102
- id: `${constConfig.id}.events.afterInit`,
121
+ id: isAnonymous
122
+ ? Symbol(`anonymous-resource.events.afterInit`)
123
+ : `${id as string}.events.afterInit`,
103
124
  }),
104
125
  [symbols.filePath]: filePath,
105
126
  },
106
127
  onError: {
107
128
  ...defineEvent({
108
- id: `${constConfig.id}.events.onError`,
129
+ id: isAnonymous
130
+ ? Symbol(`anonymous-resource.events.onError`)
131
+ : `${id as string}.events.onError`,
109
132
  }),
110
133
  [symbols.filePath]: filePath,
111
134
  },
@@ -115,34 +138,106 @@ export function defineResource<
115
138
  };
116
139
  }
117
140
 
118
- export function defineEvent<TPayload = any>(
119
- config: IEventDefinitionConfig<TPayload>
120
- ): IEventDefinition<TPayload> {
141
+ /**
142
+ * Creates an "index" resource which groups multiple registerable items under
143
+ * a single dependency. The resulting resource registers every item, depends
144
+ * on the same items, and returns the resolved dependency map so users can
145
+ * access them naturally: `deps.services.myTask()` or `deps.services.myResource`.
146
+ */
147
+ export function defineIndex<
148
+ T extends Record<string, RegisterableItems>,
149
+ D extends {
150
+ [K in keyof T]: T[K] extends IResourceWithConfig<any, any, any>
151
+ ? T[K]["resource"]
152
+ : T[K];
153
+ } & DependencyMapType
154
+ >(items: T): IResource<void, DependencyValuesType<D>, D> {
155
+ const dependencies = {} as D;
156
+ const register: RegisterableItems[] = [];
157
+
158
+ for (const key of Object.keys(items) as (keyof T)[]) {
159
+ const item = items[key];
160
+ register.push(item);
161
+
162
+ if (isResourceWithConfig(item)) {
163
+ (dependencies as any)[key] = item.resource;
164
+ } else {
165
+ (dependencies as any)[key] = item as any;
166
+ }
167
+ }
168
+ const callerFilePath = getCallerFile();
169
+
170
+ return defineResource({
171
+ register,
172
+ dependencies,
173
+ async init(_, deps) {
174
+ return deps as any;
175
+ },
176
+ [symbols.filePath]: callerFilePath,
177
+ [symbols.indexResource]: true,
178
+ });
179
+ }
180
+
181
+ export function defineEvent<TPayload = void>(
182
+ config: IEventDefinition<TPayload>
183
+ ): IEvent<TPayload> {
184
+ const callerFilePath = getCallerFile();
121
185
  return {
122
- [symbols.filePath]: getCallerFile(),
123
- [symbolEvent]: true,
124
186
  ...config,
187
+ id: config.id || generateCallerIdFromFile(callerFilePath, "event"),
188
+ [symbols.filePath]: callerFilePath,
189
+ [symbolEvent]: true, // This is a workaround
125
190
  };
126
191
  }
127
192
 
128
- export function defineMiddleware<TDeps extends DependencyMapType = {}>(
129
- config: IMiddlewareDefinition<TDeps>
130
- ): IMiddleware<TDeps> {
193
+ export type MiddlewareEverywhereOptions = {
194
+ /**
195
+ * Enable this for tasks. Default is true.
196
+ */
197
+ tasks?: boolean;
198
+ /**
199
+ * Enable this for resources. Default is true.
200
+ */
201
+ resources?: boolean;
202
+ };
203
+
204
+ export function defineMiddleware<
205
+ TConfig extends Record<string, any>,
206
+ TDependencies extends DependencyMapType
207
+ >(
208
+ middlewareDef: IMiddlewareDefinition<TConfig, TDependencies>
209
+ ): IMiddleware<TConfig, TDependencies> {
210
+ const filePath = getCallerFile();
131
211
  const object = {
132
- [symbols.filePath]: getCallerFile(),
212
+ [symbols.filePath]: filePath,
133
213
  [symbols.middleware]: true,
134
- ...config,
135
- dependencies: config.dependencies || ({} as TDeps),
136
- };
214
+ config: {} as TConfig,
215
+ id: middlewareDef.id || generateCallerIdFromFile(filePath, "middleware"),
216
+ ...middlewareDef,
217
+ dependencies: middlewareDef.dependencies || ({} as TDependencies),
218
+ } as IMiddleware<TConfig, TDependencies>;
137
219
 
138
220
  return {
139
221
  ...object,
140
- global() {
222
+ with: (config: TConfig) => {
223
+ return {
224
+ ...object,
225
+ [symbolMiddlewareConfigured]: true,
226
+ config: {
227
+ ...object.config,
228
+ ...config,
229
+ },
230
+ };
231
+ },
232
+ everywhere(options: MiddlewareEverywhereOptions = {}) {
233
+ const { tasks = true, resources = true } = options;
234
+
141
235
  return {
142
236
  ...object,
143
- [symbols.middlewareGlobal]: true,
144
- global() {
145
- throw Errors.middlewareAlreadyGlobal(config.id);
237
+ [symbols.middlewareEverywhereTasks]: tasks,
238
+ [symbols.middlewareEverywhereResources]: resources,
239
+ everywhere() {
240
+ throw Errors.middlewareAlreadyGlobal(object.id);
146
241
  },
147
242
  };
148
243
  },
@@ -163,7 +258,7 @@ export function isResourceWithConfig(
163
258
  return definition && definition[symbols.resourceWithConfig];
164
259
  }
165
260
 
166
- export function isEvent(definition: any): definition is IEventDefinition {
261
+ export function isEvent(definition: any): definition is IEvent {
167
262
  return definition && definition[symbols.event];
168
263
  }
169
264