@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
@@ -1,4 +1,4 @@
1
- import { globalEvents } from "../globalEvents";
1
+ import { globalEvents } from "../globals/globalEvents";
2
2
  import { EventManager } from "./EventManager";
3
3
 
4
4
  export type LogLevels =
@@ -9,17 +9,32 @@ export type LogLevels =
9
9
  | "error"
10
10
  | "critical";
11
11
 
12
+ export interface LogInfo {
13
+ source?: string | symbol;
14
+ error?: Error;
15
+ data?: Record<string, any>;
16
+ [key: string]: any;
17
+ }
18
+
12
19
  export interface ILog {
13
20
  level: string;
14
21
  source?: string;
15
- data: any;
22
+ message: any;
16
23
  timestamp: Date;
24
+ error?: {
25
+ name: string;
26
+ message: string;
27
+ stack?: string;
28
+ };
29
+ data?: Record<string, any>;
30
+ context?: Record<string, any>;
17
31
  }
18
32
 
19
33
  export class Logger {
20
34
  printThreshold: LogLevels | null = null;
35
+ private boundContext: Record<string, any> = {};
21
36
 
22
- public severity = {
37
+ public static Severity = {
23
38
  trace: 0,
24
39
  debug: 1,
25
40
  info: 2,
@@ -28,32 +43,71 @@ export class Logger {
28
43
  critical: 5,
29
44
  };
30
45
 
31
- constructor(protected eventManager: EventManager) {}
46
+ constructor(
47
+ protected eventManager: EventManager,
48
+ boundContext: Record<string, any> = {}
49
+ ) {
50
+ this.boundContext = { ...boundContext };
51
+ }
32
52
 
33
53
  /**
34
- * @param level
35
- * @param message
54
+ * Creates a new logger instance with additional bound context
55
+ */
56
+ public with(context: Record<string, any>): Logger {
57
+ return new Logger(this.eventManager, {
58
+ ...this.boundContext,
59
+ ...context,
60
+ });
61
+ }
62
+
63
+ private extractErrorInfo(error: Error): {
64
+ name: string;
65
+ message: string;
66
+ stack?: string;
67
+ } {
68
+ return {
69
+ name: error.name,
70
+ message: error.message,
71
+ stack: error.stack,
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Core logging method with structured LogInfo
36
77
  */
37
- public async log(
38
- level: LogLevels,
39
- data: any,
40
- source?: string
41
- ): Promise<void> {
78
+ public log(level: LogLevels, message: any, logInfo: LogInfo = {}): void {
79
+ const { source, error, data, ...context } = logInfo;
80
+
42
81
  const log: ILog = {
43
82
  level,
44
- data,
45
- source: source,
83
+ message,
84
+ source: source || this.boundContext.source,
46
85
  timestamp: new Date(),
86
+ error: error ? this.extractErrorInfo(error) : undefined,
87
+ data: data || undefined,
88
+ context: { ...this.boundContext, ...context },
47
89
  };
48
90
 
49
91
  if (
50
92
  this.printThreshold &&
51
- this.severity[level] >= this.severity[this.printThreshold]
93
+ Logger.Severity[level] >= Logger.Severity[this.printThreshold]
52
94
  ) {
53
95
  this.print(log);
54
96
  }
55
97
 
56
- await this.eventManager.emit(globalEvents.log, log, source || "unknown");
98
+ if (this.eventManager.hasListeners(globalEvents.log)) {
99
+ setImmediate(() => {
100
+ this.eventManager
101
+ .emit(
102
+ globalEvents.log,
103
+ log,
104
+ source || this.boundContext.source || "unknown"
105
+ )
106
+ .catch((err) => {
107
+ console.error("Logger event emission failed:", err);
108
+ });
109
+ });
110
+ }
57
111
  }
58
112
 
59
113
  /**
@@ -65,73 +119,136 @@ export class Logger {
65
119
  }
66
120
 
67
121
  public print(log: ILog) {
68
- // Extract the relevant information from the log
69
- const { level, source: context, data, timestamp } = log;
70
-
71
- // Format the timestamp to a more readable format
72
- const formattedTimestamp = timestamp.toISOString();
122
+ const { level, source, message, timestamp, error, data, context } = log;
123
+
124
+ // Color codes for different log levels
125
+ const colors = {
126
+ trace: "\x1b[90m", // bright black/gray
127
+ debug: "\x1b[36m", // cyan
128
+ info: "\x1b[32m", // green
129
+ warn: "\x1b[33m", // yellow
130
+ error: "\x1b[31m", // red
131
+ critical: "\x1b[35m", // magenta
132
+ reset: "\x1b[0m", // reset
133
+ bold: "\x1b[1m", // bold
134
+ dim: "\x1b[2m", // dim
135
+ blue: "\x1b[34m", // blue
136
+ red: "\x1b[31m", // red
137
+ cyan: "\x1b[36m", // cyan
138
+ };
73
139
 
74
- // Format the log level for better visibility
75
- const levelStr = `[${level.toUpperCase()}]`;
140
+ const levelColor = colors[level as keyof typeof colors] || colors.info;
141
+
142
+ // Format timestamp
143
+ const time = timestamp.toLocaleTimeString("en-US", {
144
+ hour12: false,
145
+ hour: "2-digit",
146
+ minute: "2-digit",
147
+ second: "2-digit",
148
+ });
149
+ const ms = timestamp.getMilliseconds().toString().padStart(3, "0");
150
+ const formattedTime = `${colors.dim}${time}.${ms}${colors.reset}`;
151
+
152
+ // Format level with color and padding
153
+ const levelStr = `${levelColor}${colors.bold}${level
154
+ .toUpperCase()
155
+ .padEnd(8)}${colors.reset}`;
156
+
157
+ // Format source
158
+ const sourceStr = source ? `${colors.blue}[${source}]${colors.reset} ` : "";
159
+
160
+ // Format the main message
161
+ let messageStr: string;
162
+ if (typeof message === "object") {
163
+ messageStr = JSON.stringify(message, null, 2);
164
+ } else {
165
+ messageStr = String(message);
166
+ }
76
167
 
77
- // Format the context, if provided
78
- const contextStr = context ? `(${context})` : "";
168
+ // Build the main log line
169
+ const mainLine = `${formattedTime} ${levelStr} ${sourceStr}${messageStr}`;
170
+
171
+ // Start building output lines
172
+ const lines = [mainLine];
173
+
174
+ // Add error information if present
175
+ if (error) {
176
+ lines.push(
177
+ `${colors.dim}├─ ${colors.red}Error: ${error.name}${colors.reset}`
178
+ );
179
+ lines.push(
180
+ `${colors.dim}├─ ${colors.red}${error.message}${colors.reset}`
181
+ );
182
+ if (error.stack) {
183
+ const stackLines = error.stack.split("\n").slice(1, 4); // Show first 3 stack frames
184
+ stackLines.forEach((line, index) => {
185
+ const prefix = index === stackLines.length - 1 ? "└─" : "├─";
186
+ lines.push(
187
+ `${colors.dim}${prefix} ${colors.red}${line.trim()}${colors.reset}`
188
+ );
189
+ });
190
+ }
191
+ }
79
192
 
80
- // Handle different data types, especially if it's an error
81
- let dataStr: string;
82
- if (data instanceof Error) {
83
- dataStr = `Error: ${data.name} - ${data.message}\nStack Trace:\n${data.stack}`;
84
- } else if (typeof data === "object") {
85
- dataStr = this.safeStringify(data, 2); // Pretty-print JSON objects
86
- } else {
87
- dataStr = String(data); // Convert any other type to string
193
+ // Add structured data if present
194
+ if (data && Object.keys(data).length > 0) {
195
+ lines.push(`${colors.dim}├─ ${colors.cyan}Data:${colors.reset}`);
196
+ const dataStr = JSON.stringify(data, null, 2);
197
+ const dataLines = dataStr.split("\n");
198
+ dataLines.forEach((line, index) => {
199
+ const prefix = index === dataLines.length - 1 ? "└─" : "├─";
200
+ lines.push(
201
+ `${colors.dim}${prefix} ${colors.cyan}${line}${colors.reset}`
202
+ );
203
+ });
88
204
  }
89
205
 
90
- // Construct the final log message
91
- const logMessage = `${formattedTimestamp} ${levelStr} ${contextStr} - ${dataStr}`;
206
+ // Add context if present (excluding common context we already show)
207
+ const filteredContext = context ? { ...context } : {};
208
+ delete filteredContext.source; // Already shown in source
209
+
210
+ if (filteredContext && Object.keys(filteredContext).length > 0) {
211
+ lines.push(`${colors.dim}└─ ${colors.blue}Context:${colors.reset}`);
212
+ const contextStr = JSON.stringify(filteredContext, null, 2);
213
+ const contextLines = contextStr.split("\n");
214
+ contextLines.forEach((line, index) => {
215
+ const prefix = index === contextLines.length - 1 ? " " : " ";
216
+ lines.push(
217
+ `${colors.dim}${prefix} ${colors.blue}${line}${colors.reset}`
218
+ );
219
+ });
220
+ }
92
221
 
93
- // Print the log message
94
- console.log(logMessage);
95
- }
222
+ // Output all lines
223
+ lines.forEach((line) => console.log(line));
96
224
 
97
- public async info(data: any, source?: string) {
98
- await this.log("info", data, source);
225
+ // Add a subtle separator for multi-line logs
226
+ if (lines.length > 1) {
227
+ console.log(`${colors.dim}${colors.reset}`);
228
+ }
99
229
  }
100
230
 
101
- public async error(data: any, source?: string) {
102
- await this.log("error", data, source);
231
+ public info(message: any, logInfo: LogInfo = {}) {
232
+ this.log("info", message, logInfo);
103
233
  }
104
234
 
105
- public async warn(data: any, source?: string) {
106
- await this.log("warn", data, source);
235
+ public error(message: any, logInfo: LogInfo = {}) {
236
+ this.log("error", message, logInfo);
107
237
  }
108
238
 
109
- public async debug(data: any, source?: string) {
110
- await this.log("debug", data, source);
239
+ public warn(message: any, logInfo: LogInfo = {}) {
240
+ this.log("warn", message, logInfo);
111
241
  }
112
242
 
113
- public async trace(data: any, source?: string) {
114
- await this.log("trace", data, source);
243
+ public debug(message: any, logInfo: LogInfo = {}) {
244
+ this.log("debug", message, logInfo);
115
245
  }
116
246
 
117
- public async critical(data: any, source?: string) {
118
- await this.log("critical", data, source);
247
+ public trace(message: any, logInfo: LogInfo = {}) {
248
+ this.log("trace", message, logInfo);
119
249
  }
120
250
 
121
- private safeStringify(obj, indent = 2) {
122
- const seen = new WeakSet();
123
- return JSON.stringify(
124
- obj,
125
- (key, value) => {
126
- if (typeof value === "object" && value !== null) {
127
- if (seen.has(value)) {
128
- return "[Circular]";
129
- }
130
- seen.add(value);
131
- }
132
- return value;
133
- },
134
- indent
135
- );
251
+ public critical(message: any, logInfo: LogInfo = {}) {
252
+ this.log("critical", message, logInfo);
136
253
  }
137
254
  }
@@ -0,0 +1,84 @@
1
+ import {
2
+ IResource,
3
+ IMiddleware,
4
+ ITask,
5
+ IResourceWithConfig,
6
+ RegisterableItems,
7
+ } from "../defs";
8
+ import * as utils from "../define";
9
+ import { Errors } from "../errors";
10
+ import {
11
+ TaskStoreElementType,
12
+ MiddlewareStoreElementType,
13
+ ResourceStoreElementType,
14
+ } from "./StoreTypes";
15
+ import { StoreRegistry } from "./StoreRegistry";
16
+
17
+ export class OverrideManager {
18
+ public overrides: Map<
19
+ string | symbol,
20
+ IResource | IMiddleware | ITask | IResourceWithConfig
21
+ > = new Map();
22
+
23
+ public overrideRequests: Set<{
24
+ source: string | symbol;
25
+ override: RegisterableItems;
26
+ }> = new Set();
27
+
28
+ constructor(private readonly registry: StoreRegistry) {}
29
+
30
+ storeOverridesDeeply<C>(element: IResource<C, any, any>) {
31
+ element.overrides.forEach((override) => {
32
+ if (utils.isResource(override)) {
33
+ this.storeOverridesDeeply(override);
34
+ }
35
+
36
+ let id: string | symbol;
37
+ if (utils.isResourceWithConfig(override)) {
38
+ this.storeOverridesDeeply(override.resource);
39
+ id = override.resource.id;
40
+ } else {
41
+ id = override.id;
42
+ }
43
+
44
+ this.overrideRequests.add({ source: element.id, override });
45
+ this.overrides.set(id, override);
46
+ });
47
+ }
48
+
49
+ processOverrides() {
50
+ // If we are trying to use override on something that wasn't previously registered, we throw an error.
51
+ for (const override of this.overrides.values()) {
52
+ let hasAnyItem = false;
53
+ if (utils.isTask(override)) {
54
+ hasAnyItem = this.registry.tasks.has(override.id);
55
+ } else if (utils.isResource(override)) {
56
+ hasAnyItem = this.registry.resources.has(override.id);
57
+ } else if (utils.isMiddleware(override)) {
58
+ hasAnyItem = this.registry.middlewares.has(override.id);
59
+ } else if (utils.isResourceWithConfig(override)) {
60
+ hasAnyItem = this.registry.resources.has(override.resource.id);
61
+ }
62
+
63
+ if (!hasAnyItem) {
64
+ const id = utils.isResourceWithConfig(override)
65
+ ? override.resource.id
66
+ : override.id;
67
+
68
+ throw Errors.dependencyNotFound(id);
69
+ }
70
+ }
71
+
72
+ for (const override of this.overrides.values()) {
73
+ if (utils.isTask(override)) {
74
+ this.registry.storeTask(override, false);
75
+ } else if (utils.isResource(override)) {
76
+ this.registry.storeResource(override, false);
77
+ } else if (utils.isMiddleware(override)) {
78
+ this.registry.storeMiddleware(override, false);
79
+ } else if (utils.isResourceWithConfig(override)) {
80
+ this.registry.storeResourceWithConfig(override, false);
81
+ }
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,66 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+
3
+ /**
4
+ * Cooperative task queue.
5
+ * • Tasks run one‑after‑another (FIFO ordering).
6
+ * • Dead‑lock detection prevents nesting.
7
+ * • dispose() drains or cancels outstanding tasks, then rejects new ones.
8
+ */
9
+ export class Queue {
10
+ private tail: Promise<unknown> = Promise.resolve();
11
+ private disposed = false;
12
+ private abortController = new AbortController();
13
+
14
+ // true while inside a queued task → helps detect "queue in queue"
15
+ private readonly executionContext = new AsyncLocalStorage<boolean>();
16
+
17
+ /**
18
+ * Schedule an asynchronous task.
19
+ * @param task – receives an AbortSignal so it can cancel early if desired.
20
+ */
21
+ public run<T>(task: (signal: AbortSignal) => Promise<T>): Promise<T> {
22
+ // 1. refuse new work if we've disposed
23
+ if (this.disposed) {
24
+ return Promise.reject(new Error("Queue has been disposed"));
25
+ }
26
+
27
+ // 2. detect dead‑locks (a queued task adding another queued task)
28
+ if (this.executionContext.getStore()) {
29
+ return Promise.reject(
30
+ new Error(
31
+ "Dead‑lock detected: a queued task attempted to queue another task"
32
+ )
33
+ );
34
+ }
35
+
36
+ const { signal } = this.abortController;
37
+
38
+ // 3. chain task after the current tail
39
+ const result = this.tail.then(() =>
40
+ this.executionContext.run(true, () => task(signal))
41
+ );
42
+
43
+ // 4. preserve the chain even if the task rejects (swallow internally)
44
+ this.tail = result.catch(() => {});
45
+
46
+ return result;
47
+ }
48
+
49
+ /**
50
+ * Disposes the queue.
51
+ * @param options.cancel – if true, broadcasts AbortSignal to running task.
52
+ * default: false (waits for tasks to finish).
53
+ */
54
+ public async dispose(options: { cancel?: boolean } = {}): Promise<void> {
55
+ if (this.disposed) return;
56
+
57
+ this.disposed = true;
58
+
59
+ if (options.cancel) {
60
+ this.abortController.abort(); // notify cooperative tasks
61
+ }
62
+
63
+ // wait for everything already chained to settle
64
+ await this.tail.catch(() => {});
65
+ }
66
+ }
@@ -5,8 +5,9 @@ import {
5
5
  IResource,
6
6
  } from "../defs";
7
7
  import { EventManager } from "./EventManager";
8
- import { globalEvents } from "../globalEvents";
9
- import { MiddlewareStoreElementType, Store } from "./Store";
8
+ import { globalEvents } from "../globals/globalEvents";
9
+ import { Store } from "./Store";
10
+ import { MiddlewareStoreElementType } from "./StoreTypes";
10
11
  import { Logger } from "./Logger";
11
12
 
12
13
  export class ResourceInitializer {
@@ -23,12 +24,14 @@ export class ResourceInitializer {
23
24
  public async initializeResource<
24
25
  TConfig = null,
25
26
  TValue = any,
26
- TDeps extends DependencyMapType = {}
27
+ TDeps extends DependencyMapType = {},
28
+ TContext = any
27
29
  >(
28
30
  resource: IResource<TConfig, TValue, TDeps>,
29
31
  config: TConfig,
30
32
  dependencies: DependencyValuesType<TDeps>
31
- ): Promise<TValue | undefined> {
33
+ ): Promise<{ value: TValue; context: TContext }> {
34
+ const context = resource.context?.();
32
35
  await this.eventManager.emit(
33
36
  globalEvents.resources.beforeInit,
34
37
  {
@@ -44,15 +47,22 @@ export class ResourceInitializer {
44
47
  resource.id
45
48
  );
46
49
 
47
- let error, value;
50
+ let error: any, value: TValue | undefined;
48
51
  try {
49
- value = await this.initWithMiddleware(resource, config, dependencies);
52
+ if (resource.init) {
53
+ value = await this.initWithMiddleware(
54
+ resource,
55
+ config,
56
+ dependencies,
57
+ context
58
+ );
59
+ }
50
60
 
51
61
  await this.eventManager.emit(
52
62
  resource.events.afterInit,
53
63
  {
54
64
  config,
55
- value,
65
+ value: value as TValue,
56
66
  },
57
67
  resource.id
58
68
  );
@@ -61,14 +71,16 @@ export class ResourceInitializer {
61
71
  {
62
72
  config,
63
73
  resource,
64
- value,
74
+ value: value as TValue,
65
75
  },
66
76
  resource.id
67
77
  );
68
78
 
69
- this.logger.debug(`Resource ${resource.id} initialized`, resource.id);
79
+ this.logger.debug(`Resource ${resource.id.toString()} initialized`, {
80
+ source: resource.id,
81
+ });
70
82
 
71
- return value;
83
+ return { value: value as TValue, context };
72
84
  } catch (e) {
73
85
  error = e;
74
86
  let isSuppressed = false;
@@ -80,7 +92,7 @@ export class ResourceInitializer {
80
92
  await this.eventManager.emit(
81
93
  resource.events.onError,
82
94
  {
83
- error,
95
+ error: error as Error,
84
96
  suppress,
85
97
  },
86
98
  resource.id
@@ -88,7 +100,7 @@ export class ResourceInitializer {
88
100
  await this.eventManager.emit(
89
101
  globalEvents.resources.onError,
90
102
  {
91
- error,
103
+ error: error as Error,
92
104
  resource,
93
105
  suppress,
94
106
  },
@@ -96,23 +108,28 @@ export class ResourceInitializer {
96
108
  );
97
109
 
98
110
  if (!isSuppressed) throw e;
111
+
112
+ return { value: undefined as TValue, context: {} as TContext };
99
113
  }
100
114
  }
101
115
 
102
- public async initWithMiddleware<C, V, D extends DependencyMapType>(
103
- resource: IResource<C, V>,
116
+ public async initWithMiddleware<C, V, D extends DependencyMapType, TContext>(
117
+ resource: IResource<C, V, D, TContext>,
104
118
  config: C,
105
- dependencies: D
119
+ dependencies: DependencyValuesType<D>,
120
+ context: TContext
106
121
  ) {
107
122
  let next = async (config: C): Promise<V | undefined> => {
108
123
  if (resource.init) {
109
- return resource.init.call(null, config, dependencies);
124
+ return resource.init.call(null, config, dependencies, context);
110
125
  }
111
126
  };
112
127
 
113
128
  const existingMiddlewares = resource.middleware;
114
129
  const createdMiddlewares = [
115
- ...this.store.getGlobalMiddlewares(existingMiddlewares.map((x) => x.id)),
130
+ ...this.store.getEverywhereMiddlewareForResources(
131
+ existingMiddlewares.map((x) => x.id)
132
+ ),
116
133
  ...existingMiddlewares,
117
134
  ];
118
135
 
@@ -126,11 +143,14 @@ export class ResourceInitializer {
126
143
  next = async (config: C) => {
127
144
  return storeMiddleware.middleware.run(
128
145
  {
129
- resourceDefinition: resource as any,
130
- config: config,
146
+ resource: {
147
+ definition: resource,
148
+ config,
149
+ },
131
150
  next: nextFunction,
132
151
  },
133
- storeMiddleware.computedDependencies
152
+ storeMiddleware.computedDependencies,
153
+ middleware.config
134
154
  );
135
155
  };
136
156
  }