@bluelibs/runner 3.0.0 → 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 (64) hide show
  1. package/README.md +103 -2
  2. package/dist/define.d.ts +3 -3
  3. package/dist/define.js +39 -14
  4. package/dist/define.js.map +1 -1
  5. package/dist/defs.d.ts +59 -44
  6. package/dist/defs.js +3 -1
  7. package/dist/defs.js.map +1 -1
  8. package/dist/errors.d.ts +5 -5
  9. package/dist/errors.js +6 -5
  10. package/dist/errors.js.map +1 -1
  11. package/dist/globals/globalEvents.d.ts +11 -11
  12. package/dist/globals/globalMiddleware.d.ts +1 -1
  13. package/dist/globals/globalResources.d.ts +3 -3
  14. package/dist/globals/middleware/cache.middleware.d.ts +2 -2
  15. package/dist/globals/middleware/cache.middleware.js.map +1 -1
  16. package/dist/globals/resources/queue.resource.d.ts +2 -2
  17. package/dist/globals/resources/queue.resource.js.map +1 -1
  18. package/dist/index.d.ts +13 -13
  19. package/dist/models/DependencyProcessor.d.ts +4 -4
  20. package/dist/models/DependencyProcessor.js +2 -2
  21. package/dist/models/DependencyProcessor.js.map +1 -1
  22. package/dist/models/EventManager.d.ts +5 -5
  23. package/dist/models/EventManager.js.map +1 -1
  24. package/dist/models/Logger.d.ts +1 -1
  25. package/dist/models/Logger.js +29 -27
  26. package/dist/models/Logger.js.map +1 -1
  27. package/dist/models/OverrideManager.d.ts +2 -2
  28. package/dist/models/OverrideManager.js.map +1 -1
  29. package/dist/models/ResourceInitializer.js +3 -1
  30. package/dist/models/ResourceInitializer.js.map +1 -1
  31. package/dist/models/Store.d.ts +8 -8
  32. package/dist/models/Store.js.map +1 -1
  33. package/dist/models/StoreConstants.d.ts +2 -2
  34. package/dist/models/StoreRegistry.d.ts +8 -8
  35. package/dist/models/StoreRegistry.js.map +1 -1
  36. package/dist/models/StoreValidator.d.ts +2 -2
  37. package/dist/models/StoreValidator.js +1 -1
  38. package/dist/models/StoreValidator.js.map +1 -1
  39. package/dist/models/TaskRunner.d.ts +1 -1
  40. package/dist/tools/getCallerFile.d.ts +9 -1
  41. package/dist/tools/getCallerFile.js +41 -0
  42. package/dist/tools/getCallerFile.js.map +1 -1
  43. package/package.json +1 -1
  44. package/src/__tests__/models/EventManager.test.ts +48 -46
  45. package/src/__tests__/run.anonymous.test.ts +679 -0
  46. package/src/__tests__/run.middleware.test.ts +146 -0
  47. package/src/__tests__/run.test.ts +1 -1
  48. package/src/__tests__/tools/getCallerFile.test.ts +117 -2
  49. package/src/__tests__/typesafety.test.ts +17 -2
  50. package/src/define.ts +47 -22
  51. package/src/defs.ts +76 -89
  52. package/src/errors.ts +13 -10
  53. package/src/globals/middleware/cache.middleware.ts +1 -1
  54. package/src/globals/resources/queue.resource.ts +1 -1
  55. package/src/models/DependencyProcessor.ts +7 -10
  56. package/src/models/EventManager.ts +25 -14
  57. package/src/models/Logger.ts +51 -39
  58. package/src/models/OverrideManager.ts +3 -3
  59. package/src/models/ResourceInitializer.ts +3 -1
  60. package/src/models/Store.ts +2 -2
  61. package/src/models/StoreRegistry.ts +27 -16
  62. package/src/models/StoreValidator.ts +13 -8
  63. package/src/models/TaskRunner.ts +1 -1
  64. package/src/tools/getCallerFile.ts +54 -2
@@ -401,3 +401,149 @@ describe("Configurable Middleware (.with)", () => {
401
401
  await run(app);
402
402
  });
403
403
  });
404
+
405
+ describe("Middleware.everywhere()", () => {
406
+ it("should work with { tasks: true, resources: true }", async () => {
407
+ const calls: string[] = [];
408
+ const everywhereMiddleware = defineMiddleware({
409
+ id: "everywhere.middleware",
410
+ run: async ({ next, task, resource }) => {
411
+ if (task) {
412
+ calls.push(`task:${String(task.definition.id)}`);
413
+ }
414
+ if (resource) {
415
+ calls.push(`resource:${String(resource.definition.id)}`);
416
+ }
417
+ return next();
418
+ },
419
+ });
420
+
421
+ const testTask = defineTask({
422
+ id: "test.task",
423
+ run: async () => "Task executed",
424
+ });
425
+
426
+ const testResource = defineResource({
427
+ id: "test.resource",
428
+ async init() {
429
+ return "Resource initialized";
430
+ },
431
+ });
432
+
433
+ const app = defineResource({
434
+ id: "app",
435
+ register: [
436
+ everywhereMiddleware.everywhere({ tasks: true, resources: true }),
437
+ testTask,
438
+ testResource,
439
+ ],
440
+ dependencies: { testTask, testResource },
441
+ async init(_, { testTask, testResource }) {
442
+ await testTask();
443
+ expect(testResource).toBe("Resource initialized");
444
+ },
445
+ });
446
+
447
+ await run(app);
448
+
449
+ expect(calls).toContain("resource:app");
450
+ expect(calls).toContain("resource:test.resource");
451
+ expect(calls).toContain("task:test.task");
452
+ });
453
+
454
+ it("should work with { tasks: true, resources: false }", async () => {
455
+ const calls: string[] = [];
456
+ const everywhereMiddleware = defineMiddleware({
457
+ id: "everywhere.middleware",
458
+ run: async ({ next, task, resource }) => {
459
+ if (task) {
460
+ calls.push(`task:${String(task.definition.id)}`);
461
+ }
462
+ if (resource) {
463
+ calls.push(`resource:${String(resource.definition.id)}`);
464
+ }
465
+ return next();
466
+ },
467
+ });
468
+
469
+ const testTask = defineTask({
470
+ id: "test.task",
471
+ run: async () => "Task executed",
472
+ });
473
+
474
+ const testResource = defineResource({
475
+ id: "test.resource",
476
+ async init() {
477
+ return "Resource initialized";
478
+ },
479
+ });
480
+
481
+ const app = defineResource({
482
+ id: "app",
483
+ register: [
484
+ everywhereMiddleware.everywhere({ tasks: true, resources: false }),
485
+ testTask,
486
+ testResource,
487
+ ],
488
+ dependencies: { testTask, testResource },
489
+ async init(_, { testTask, testResource }) {
490
+ await testTask();
491
+ expect(testResource).toBe("Resource initialized");
492
+ },
493
+ });
494
+
495
+ await run(app);
496
+
497
+ expect(calls).not.toContain("resource:app");
498
+ expect(calls).not.toContain("resource:test.resource");
499
+ expect(calls).toContain("task:test.task");
500
+ });
501
+
502
+ it("should work with { tasks: false, resources: true }", async () => {
503
+ const calls: string[] = [];
504
+ const everywhereMiddleware = defineMiddleware({
505
+ id: "everywhere.middleware",
506
+ run: async ({ next, task, resource }) => {
507
+ if (task) {
508
+ calls.push(`task:${String(task.definition.id)}`);
509
+ }
510
+ if (resource) {
511
+ calls.push(`resource:${String(resource.definition.id)}`);
512
+ }
513
+ return next();
514
+ },
515
+ });
516
+
517
+ const testTask = defineTask({
518
+ id: "test.task",
519
+ run: async () => "Task executed",
520
+ });
521
+
522
+ const testResource = defineResource({
523
+ id: "test.resource",
524
+ async init() {
525
+ return "Resource initialized";
526
+ },
527
+ });
528
+
529
+ const app = defineResource({
530
+ id: "app",
531
+ register: [
532
+ everywhereMiddleware.everywhere({ tasks: false, resources: true }),
533
+ testTask,
534
+ testResource,
535
+ ],
536
+ dependencies: { testTask, testResource },
537
+ async init(_, { testTask, testResource }) {
538
+ await testTask();
539
+ expect(testResource).toBe("Resource initialized");
540
+ },
541
+ });
542
+
543
+ await run(app);
544
+
545
+ expect(calls).toContain("resource:app");
546
+ expect(calls).toContain("resource:test.resource");
547
+ expect(calls).not.toContain("task:test.task");
548
+ });
549
+ });
@@ -93,7 +93,7 @@ describe("run", () => {
93
93
  });
94
94
 
95
95
  it("should be able to register an task that emits an event", async () => {
96
- const testEvent = defineEvent({ id: "test.event" });
96
+ const testEvent = defineEvent<{ message: string }>({ id: "test.event" });
97
97
  const eventHandler = jest.fn();
98
98
 
99
99
  const testTask = defineTask({
@@ -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", () => {
@@ -45,7 +48,119 @@ describe("getCallerFile", () => {
45
48
 
46
49
  expect((task as any)[symbols.filePath]).toContain("getCallerFile.test");
47
50
  expect((resource as any)[symbols.filePath]).toContain("getCallerFile.test");
48
- expect((middleware as any)[symbols.filePath]).toContain("getCallerFile.test");
51
+ expect((middleware as any)[symbols.filePath]).toContain(
52
+ "getCallerFile.test"
53
+ );
49
54
  expect((event as any)[symbols.filePath]).toContain("getCallerFile.test");
50
55
  });
51
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
+ });
157
+
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
+ );
165
+ });
166
+ });
@@ -4,7 +4,14 @@ import {
4
4
  defineResource,
5
5
  defineMiddleware,
6
6
  } from "../define";
7
- import { RegisterableItems } from "../defs";
7
+ import {
8
+ IEventDefinition,
9
+ IMiddlewareDefinition,
10
+ IResource,
11
+ IResourceWithConfig,
12
+ ITaskDefinition,
13
+ RegisterableItems,
14
+ } from "../defs";
8
15
 
9
16
  describe("typesafety", () => {
10
17
  it("tasks, resources: should have propper type safety for dependeices", async () => {
@@ -45,6 +52,10 @@ describe("typesafety", () => {
45
52
  id: "event",
46
53
  });
47
54
 
55
+ const eventWithoutArguments = defineEvent({
56
+ id: "event",
57
+ });
58
+
48
59
  const baseTask = defineTask({
49
60
  id: "task",
50
61
  run: async (input: InputTask) => "Task executed",
@@ -104,7 +115,7 @@ describe("typesafety", () => {
104
115
  // @ts-expect-error
105
116
  middlewareWithOptionalConfig.with({ message: 123 }),
106
117
  ],
107
- dependencies: { task, dummyResource, event },
118
+ dependencies: { task, dummyResource, event, eventWithoutArguments },
108
119
  init: async (_, deps) => {
109
120
  const result = await deps.task({
110
121
  message: "Hello, World!",
@@ -115,6 +126,10 @@ describe("typesafety", () => {
115
126
  deps.event();
116
127
  // @ts-expect-error
117
128
  deps.event({ messagex: "Hello, World!" });
129
+ deps.eventWithoutArguments();
130
+ deps.eventWithoutArguments({});
131
+ // @ts-expect-error
132
+ deps.eventWithoutArguments({ something: false });
118
133
 
119
134
  // @ts-expect-error
120
135
  deps.dummyResource as number;
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,15 +10,15 @@ import {
11
10
  DependencyMapType,
12
11
  DependencyValuesType,
13
12
  IMiddleware,
14
- IHookDefinition,
15
13
  IEvent,
16
- IEventDefinitionConfig,
17
14
  symbolEvent,
18
15
  RegisterableItems,
19
16
  symbolMiddlewareConfigured,
17
+ symbolFilePath,
18
+ symbolIndexResource,
20
19
  } from "./defs";
21
20
  import { Errors } from "./errors";
22
- import { getCallerFile } from "./tools/getCallerFile";
21
+ import { generateCallerIdFromFile, getCallerFile } from "./tools/getCallerFile";
23
22
 
24
23
  // Helper function to get the caller file
25
24
 
@@ -32,10 +31,12 @@ export function defineTask<
32
31
  taskConfig: ITaskDefinition<Input, Output, Deps, TOn>
33
32
  ): ITask<Input, Output, Deps, TOn> {
34
33
  const filePath = getCallerFile();
34
+ const isAnonymous = !Boolean(taskConfig.id);
35
+ const id = taskConfig.id || generateCallerIdFromFile(filePath, "task");
35
36
  return {
36
37
  [symbols.task]: true,
37
38
  [symbols.filePath]: filePath,
38
- id: taskConfig.id,
39
+ id,
39
40
  dependencies: taskConfig.dependencies || ({} as Deps),
40
41
  middleware: taskConfig.middleware || [],
41
42
  run: taskConfig.run,
@@ -44,19 +45,25 @@ export function defineTask<
44
45
  events: {
45
46
  beforeRun: {
46
47
  ...defineEvent({
47
- id: `${taskConfig.id}.events.beforeRun`,
48
+ id: isAnonymous
49
+ ? Symbol(`anonymous-task.events.beforeRun`)
50
+ : `${id as string}.events.beforeRun`,
48
51
  }),
49
52
  [symbols.filePath]: getCallerFile(),
50
53
  },
51
54
  afterRun: {
52
55
  ...defineEvent({
53
- id: `${taskConfig.id}.events.afterRun`,
56
+ id: isAnonymous
57
+ ? Symbol(`anonymous-task.events.afterRun`)
58
+ : `${id as string}.events.afterRun`,
54
59
  }),
55
60
  [symbols.filePath]: getCallerFile(),
56
61
  },
57
62
  onError: {
58
63
  ...defineEvent({
59
- id: `${taskConfig.id}.events.onError`,
64
+ id: isAnonymous
65
+ ? Symbol(`anonymous-task.events.onError`)
66
+ : `${id as string}.events.onError`,
60
67
  }),
61
68
  [symbols.filePath]: getCallerFile(),
62
69
  },
@@ -74,11 +81,17 @@ export function defineResource<
74
81
  >(
75
82
  constConfig: IResourceDefinition<TConfig, TValue, TDeps, TPrivate>
76
83
  ): IResource<TConfig, TValue, TDeps, TPrivate> {
77
- const filePath = getCallerFile();
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");
78
91
  return {
79
92
  [symbols.resource]: true,
80
93
  [symbols.filePath]: filePath,
81
- id: constConfig.id,
94
+ id,
82
95
  dependencies: constConfig.dependencies,
83
96
  dispose: constConfig.dispose,
84
97
  register: constConfig.register || [],
@@ -97,19 +110,25 @@ export function defineResource<
97
110
  events: {
98
111
  beforeInit: {
99
112
  ...defineEvent({
100
- id: `${constConfig.id}.events.beforeInit`,
113
+ id: isAnonymous
114
+ ? Symbol(`anonymous-resource.events.beforeInit`)
115
+ : `${id as string}.events.beforeInit`,
101
116
  }),
102
117
  [symbols.filePath]: filePath,
103
118
  },
104
119
  afterInit: {
105
120
  ...defineEvent({
106
- id: `${constConfig.id}.events.afterInit`,
121
+ id: isAnonymous
122
+ ? Symbol(`anonymous-resource.events.afterInit`)
123
+ : `${id as string}.events.afterInit`,
107
124
  }),
108
125
  [symbols.filePath]: filePath,
109
126
  },
110
127
  onError: {
111
128
  ...defineEvent({
112
- id: `${constConfig.id}.events.onError`,
129
+ id: isAnonymous
130
+ ? Symbol(`anonymous-resource.events.onError`)
131
+ : `${id as string}.events.onError`,
113
132
  }),
114
133
  [symbols.filePath]: filePath,
115
134
  },
@@ -146,24 +165,28 @@ export function defineIndex<
146
165
  (dependencies as any)[key] = item as any;
147
166
  }
148
167
  }
168
+ const callerFilePath = getCallerFile();
149
169
 
150
170
  return defineResource({
151
- id: `index.${Math.random().toString(36).slice(2)}`,
152
171
  register,
153
172
  dependencies,
154
173
  async init(_, deps) {
155
174
  return deps as any;
156
175
  },
176
+ [symbols.filePath]: callerFilePath,
177
+ [symbols.indexResource]: true,
157
178
  });
158
179
  }
159
180
 
160
- export function defineEvent<TPayload = any>(
161
- config: IEventDefinitionConfig<TPayload>
162
- ): IEventDefinition<TPayload> {
181
+ export function defineEvent<TPayload = void>(
182
+ config: IEventDefinition<TPayload>
183
+ ): IEvent<TPayload> {
184
+ const callerFilePath = getCallerFile();
163
185
  return {
164
- [symbols.filePath]: getCallerFile(),
165
- [symbolEvent]: true,
166
186
  ...config,
187
+ id: config.id || generateCallerIdFromFile(callerFilePath, "event"),
188
+ [symbols.filePath]: callerFilePath,
189
+ [symbolEvent]: true, // This is a workaround
167
190
  };
168
191
  }
169
192
 
@@ -184,10 +207,12 @@ export function defineMiddleware<
184
207
  >(
185
208
  middlewareDef: IMiddlewareDefinition<TConfig, TDependencies>
186
209
  ): IMiddleware<TConfig, TDependencies> {
210
+ const filePath = getCallerFile();
187
211
  const object = {
188
- [symbols.filePath]: getCallerFile(),
212
+ [symbols.filePath]: filePath,
189
213
  [symbols.middleware]: true,
190
214
  config: {} as TConfig,
215
+ id: middlewareDef.id || generateCallerIdFromFile(filePath, "middleware"),
191
216
  ...middlewareDef,
192
217
  dependencies: middlewareDef.dependencies || ({} as TDependencies),
193
218
  } as IMiddleware<TConfig, TDependencies>;
@@ -212,7 +237,7 @@ export function defineMiddleware<
212
237
  [symbols.middlewareEverywhereTasks]: tasks,
213
238
  [symbols.middlewareEverywhereResources]: resources,
214
239
  everywhere() {
215
- throw Errors.middlewareAlreadyGlobal(middlewareDef.id);
240
+ throw Errors.middlewareAlreadyGlobal(object.id);
216
241
  },
217
242
  };
218
243
  },
@@ -233,7 +258,7 @@ export function isResourceWithConfig(
233
258
  return definition && definition[symbols.resourceWithConfig];
234
259
  }
235
260
 
236
- export function isEvent(definition: any): definition is IEventDefinition {
261
+ export function isEvent(definition: any): definition is IEvent {
237
262
  return definition && definition[symbols.event];
238
263
  }
239
264