@bluelibs/runner 3.1.0 → 3.2.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.
@@ -1,5 +1,10 @@
1
1
  import { globalEvents } from "../globals/globalEvents";
2
- import { defineTask, defineResource } from "../define";
2
+ import {
3
+ defineTask,
4
+ defineResource,
5
+ defineMiddleware,
6
+ defineEvent,
7
+ } from "../define";
3
8
  import { run } from "../run";
4
9
 
5
10
  describe("Global Events", () => {
@@ -121,4 +126,417 @@ describe("Global Events", () => {
121
126
 
122
127
  expect(globalTaskOnErrorHandler).toHaveBeenCalled();
123
128
  });
129
+
130
+ it("should ensure global event listeners get their middleware called", async () => {
131
+ const middlewareExecutions: string[] = [];
132
+ const eventHandlerExecutions: string[] = [];
133
+
134
+ // Custom event to emit
135
+ const customEvent = defineEvent<{ message: string }>({
136
+ id: "test.customEvent",
137
+ meta: {
138
+ title: "Test Event",
139
+ description: "Test event for middleware verification",
140
+ tags: ["test"],
141
+ },
142
+ });
143
+
144
+ // Middleware that logs execution
145
+ const testMiddleware = defineMiddleware({
146
+ id: "test.middleware",
147
+ run: async ({ next, task }) => {
148
+ const taskId = task?.definition?.id || "unknown";
149
+ middlewareExecutions.push(`middleware-before:${String(taskId)}`);
150
+ const result = await next(task?.input);
151
+ middlewareExecutions.push(`middleware-after:${String(taskId)}`);
152
+ return result;
153
+ },
154
+ });
155
+
156
+ // Global event listener task with middleware
157
+ const globalEventHandler = defineTask({
158
+ id: "global.event.handler",
159
+ on: "*", // Global listener
160
+ middleware: [testMiddleware],
161
+ run: async (event) => {
162
+ if (event && event.id) {
163
+ eventHandlerExecutions.push(`global-handler:${event.id.toString()}`);
164
+ // Verify the event has meta included
165
+ expect(event.meta).toBeDefined();
166
+ if (event.id === customEvent.id) {
167
+ expect(event.meta.title).toBe("Test Event");
168
+ expect(event.meta.tags).toContain("test");
169
+ expect(event.data.message).toBe("Hello from custom event");
170
+ }
171
+ }
172
+ },
173
+ });
174
+
175
+ // Specific event listener task with middleware
176
+ const specificEventHandler = defineTask({
177
+ id: "specific.event.handler",
178
+ on: customEvent,
179
+ middleware: [testMiddleware],
180
+ run: async (event) => {
181
+ if (event && event.id) {
182
+ eventHandlerExecutions.push(
183
+ `specific-handler:${event.id.toString()}`
184
+ );
185
+ expect(event.meta.title).toBe("Test Event");
186
+ expect(event.data.message).toBe("Hello from custom event");
187
+ }
188
+ },
189
+ });
190
+
191
+ // Task that emits the custom event
192
+ const eventEmitter = defineTask({
193
+ id: "event.emitter",
194
+ dependencies: { customEvent },
195
+ run: async (_, { customEvent }) => {
196
+ await customEvent({ message: "Hello from custom event" });
197
+ return "Event emitted";
198
+ },
199
+ });
200
+
201
+ const app = defineResource({
202
+ id: "app",
203
+ register: [
204
+ customEvent,
205
+ testMiddleware,
206
+ globalEventHandler,
207
+ specificEventHandler,
208
+ eventEmitter,
209
+ ],
210
+ dependencies: { eventEmitter },
211
+ async init(_, { eventEmitter }) {
212
+ await eventEmitter();
213
+ },
214
+ });
215
+
216
+ await run(app);
217
+
218
+ // Verify middleware was called for global event listener
219
+ expect(middlewareExecutions).toContain(
220
+ "middleware-before:global.event.handler"
221
+ );
222
+ expect(middlewareExecutions).toContain(
223
+ "middleware-after:global.event.handler"
224
+ );
225
+
226
+ // Verify middleware was called for specific event listener
227
+ expect(middlewareExecutions).toContain(
228
+ "middleware-before:specific.event.handler"
229
+ );
230
+ expect(middlewareExecutions).toContain(
231
+ "middleware-after:specific.event.handler"
232
+ );
233
+
234
+ // Verify event handlers were executed
235
+ expect(eventHandlerExecutions).toContain("global-handler:test.customEvent");
236
+ expect(eventHandlerExecutions).toContain(
237
+ "specific-handler:test.customEvent"
238
+ );
239
+
240
+ // Verify global listeners also handle other events (like global events themselves)
241
+ expect(
242
+ eventHandlerExecutions.some(
243
+ (exec) =>
244
+ exec.includes("global-handler:") && exec.includes("beforeInit")
245
+ )
246
+ ).toBe(true);
247
+ });
248
+
249
+ it("should support stopPropagation in event listeners", async () => {
250
+ const eventHandlerExecutions: string[] = [];
251
+
252
+ const testEvent = defineEvent<{ value: number }>({
253
+ id: "test.propagationEvent",
254
+ meta: {
255
+ title: "Propagation Test Event",
256
+ tags: ["propagation", "test"],
257
+ },
258
+ });
259
+
260
+ // High priority listener that stops propagation
261
+ const highPriorityHandler = defineTask({
262
+ id: "high.priority.handler",
263
+ on: testEvent,
264
+ listenerOrder: -100, // Higher priority (runs first)
265
+ run: async (event) => {
266
+ eventHandlerExecutions.push("high-priority-executed");
267
+
268
+ if (event && event.data && event.data.value > 10) {
269
+ expect(event.isPropagationStopped()).toBe(false);
270
+ event.stopPropagation();
271
+ expect(event.isPropagationStopped()).toBe(true);
272
+ eventHandlerExecutions.push("propagation-stopped");
273
+ }
274
+ },
275
+ });
276
+
277
+ // Low priority listener that should be skipped when propagation is stopped
278
+ const lowPriorityHandler = defineTask({
279
+ id: "low.priority.handler",
280
+ on: testEvent,
281
+ listenerOrder: 100, // Lower priority (runs later)
282
+ run: async () => {
283
+ // This handler will only execute when propagation is NOT stopped
284
+ eventHandlerExecutions.push("low-priority-executed");
285
+ },
286
+ });
287
+
288
+ const eventEmitter = defineTask({
289
+ id: "propagation.emitter",
290
+ dependencies: { testEvent },
291
+ run: async (value: number, { testEvent }) => {
292
+ await testEvent({ value });
293
+ },
294
+ });
295
+
296
+ const app = defineResource({
297
+ id: "app",
298
+ register: [
299
+ testEvent,
300
+ highPriorityHandler,
301
+ lowPriorityHandler,
302
+ eventEmitter,
303
+ ],
304
+ dependencies: { eventEmitter },
305
+ async init(_, { eventEmitter }) {
306
+ // Test with value <= 10 (no propagation stop)
307
+ await eventEmitter(5);
308
+
309
+ // Test with value > 10 (propagation stop)
310
+ await eventEmitter(15);
311
+ },
312
+ });
313
+
314
+ await run(app);
315
+
316
+ // Verify both handlers executed for first event (value=5)
317
+ expect(
318
+ eventHandlerExecutions.filter((e) => e === "high-priority-executed")
319
+ ).toHaveLength(2);
320
+ expect(
321
+ eventHandlerExecutions.filter((e) => e === "low-priority-executed")
322
+ ).toHaveLength(1);
323
+
324
+ // Verify propagation was stopped for second event (value=15)
325
+ expect(
326
+ eventHandlerExecutions.filter((e) => e === "propagation-stopped")
327
+ ).toHaveLength(1);
328
+
329
+ // The low priority handler should NOT have run for the second event due to stopped propagation
330
+ // So it should only appear once (from the first event where propagation was not stopped)
331
+ });
332
+
333
+ it("should support global event listeners with both global and local middleware", async () => {
334
+ const middlewareExecutions: string[] = [];
335
+ const eventHandlerExecutions: string[] = [];
336
+
337
+ // Custom event to emit
338
+ const testEvent = defineEvent<{ message: string }>({
339
+ id: "test.globalMiddlewareEvent",
340
+ meta: {
341
+ title: "Global Middleware Test Event",
342
+ description: "Test event for global middleware verification",
343
+ tags: ["test", "global"],
344
+ },
345
+ });
346
+
347
+ // Global middleware that should be applied everywhere
348
+ const globalMiddleware = defineMiddleware({
349
+ id: "global.middleware",
350
+ run: async ({ next, task }) => {
351
+ const taskId = task?.definition?.id || "unknown";
352
+ middlewareExecutions.push(`global-middleware-before:${String(taskId)}`);
353
+ const result = await next(task?.input);
354
+ middlewareExecutions.push(`global-middleware-after:${String(taskId)}`);
355
+ return result;
356
+ },
357
+ }).everywhere(); // Make it global
358
+
359
+ // Local middleware specific to certain tasks
360
+ const localMiddleware = defineMiddleware({
361
+ id: "local.middleware",
362
+ run: async ({ next, task }) => {
363
+ const taskId = task?.definition?.id || "unknown";
364
+ middlewareExecutions.push(`local-middleware-before:${String(taskId)}`);
365
+ const result = await next(task?.input);
366
+ middlewareExecutions.push(`local-middleware-after:${String(taskId)}`);
367
+ return result;
368
+ },
369
+ });
370
+
371
+ // Global event listener task with local middleware
372
+ const globalEventHandler = defineTask({
373
+ id: "global.middleware.event.handler",
374
+ on: "*", // Global listener
375
+ middleware: [localMiddleware], // Local middleware
376
+ run: async (event) => {
377
+ if (event && event.id) {
378
+ eventHandlerExecutions.push(`global-handler:${event.id.toString()}`);
379
+ if (event.id === testEvent.id) {
380
+ expect(event.meta.title).toBe("Global Middleware Test Event");
381
+ expect(event.data.message).toBe("Hello from middleware test");
382
+ }
383
+ }
384
+ },
385
+ });
386
+
387
+ // Specific event listener task with local middleware
388
+ const specificEventHandler = defineTask({
389
+ id: "specific.middleware.event.handler",
390
+ on: testEvent,
391
+ middleware: [localMiddleware], // Local middleware
392
+ run: async (event) => {
393
+ if (event && event.id) {
394
+ eventHandlerExecutions.push(
395
+ `specific-handler:${event.id.toString()}`
396
+ );
397
+ expect(event.meta.title).toBe("Global Middleware Test Event");
398
+ expect(event.data.message).toBe("Hello from middleware test");
399
+ }
400
+ },
401
+ });
402
+
403
+ // Task that emits the test event
404
+ const eventEmitter = defineTask({
405
+ id: "middleware.event.emitter",
406
+ dependencies: { testEvent },
407
+ middleware: [localMiddleware], // This task also has local middleware
408
+ run: async (_, { testEvent }) => {
409
+ await testEvent({ message: "Hello from middleware test" });
410
+ return "Event emitted";
411
+ },
412
+ });
413
+
414
+ const app = defineResource({
415
+ id: "app",
416
+ register: [
417
+ globalMiddleware, // Register global middleware
418
+ localMiddleware,
419
+ testEvent,
420
+ globalEventHandler,
421
+ specificEventHandler,
422
+ eventEmitter,
423
+ ],
424
+ dependencies: { eventEmitter },
425
+ async init(_, { eventEmitter }) {
426
+ await eventEmitter();
427
+ },
428
+ });
429
+
430
+ await run(app);
431
+
432
+ // Verify global middleware was called for global event listener
433
+ expect(middlewareExecutions).toContain(
434
+ "global-middleware-before:global.middleware.event.handler"
435
+ );
436
+ expect(middlewareExecutions).toContain(
437
+ "global-middleware-after:global.middleware.event.handler"
438
+ );
439
+
440
+ // Verify local middleware was called for global event listener
441
+ expect(middlewareExecutions).toContain(
442
+ "local-middleware-before:global.middleware.event.handler"
443
+ );
444
+ expect(middlewareExecutions).toContain(
445
+ "local-middleware-after:global.middleware.event.handler"
446
+ );
447
+
448
+ // Verify global middleware was called for specific event listener
449
+ expect(middlewareExecutions).toContain(
450
+ "global-middleware-before:specific.middleware.event.handler"
451
+ );
452
+ expect(middlewareExecutions).toContain(
453
+ "global-middleware-after:specific.middleware.event.handler"
454
+ );
455
+
456
+ // Verify local middleware was called for specific event listener
457
+ expect(middlewareExecutions).toContain(
458
+ "local-middleware-before:specific.middleware.event.handler"
459
+ );
460
+ expect(middlewareExecutions).toContain(
461
+ "local-middleware-after:specific.middleware.event.handler"
462
+ );
463
+
464
+ // Verify global middleware was called for event emitter task
465
+ expect(middlewareExecutions).toContain(
466
+ "global-middleware-before:middleware.event.emitter"
467
+ );
468
+ expect(middlewareExecutions).toContain(
469
+ "global-middleware-after:middleware.event.emitter"
470
+ );
471
+
472
+ // Verify local middleware was called for event emitter task
473
+ expect(middlewareExecutions).toContain(
474
+ "local-middleware-before:middleware.event.emitter"
475
+ );
476
+ expect(middlewareExecutions).toContain(
477
+ "local-middleware-after:middleware.event.emitter"
478
+ );
479
+
480
+ // Verify event handlers were executed
481
+ expect(eventHandlerExecutions).toContain(
482
+ "global-handler:test.globalMiddlewareEvent"
483
+ );
484
+ expect(eventHandlerExecutions).toContain(
485
+ "specific-handler:test.globalMiddlewareEvent"
486
+ );
487
+
488
+ // Verify global listeners also handle other events (like global events themselves)
489
+ expect(
490
+ eventHandlerExecutions.some(
491
+ (exec) =>
492
+ exec.includes("global-handler:") && exec.includes("beforeInit")
493
+ )
494
+ ).toBe(true);
495
+ });
496
+
497
+ it("should support defineEvent without any config", async () => {
498
+ const eventHandlerExecutions: string[] = [];
499
+
500
+ // Define event with minimal config (just id)
501
+ const simpleEvent = defineEvent();
502
+
503
+ // Event listener for the simple event
504
+ const simpleEventHandler = defineTask({
505
+ id: "simple.event.handler",
506
+ on: simpleEvent,
507
+ run: async (event) => {
508
+ if (event && event.id) {
509
+ eventHandlerExecutions.push(`simple-handler:${event.id.toString()}`);
510
+ // Event should exist but meta might be minimal/undefined
511
+ expect(event.id).toBe(simpleEvent.id);
512
+ }
513
+ },
514
+ });
515
+
516
+ // Task that emits the simple event
517
+ const eventEmitter = defineTask({
518
+ id: "simple.event.emitter",
519
+ dependencies: { simpleEvent },
520
+ run: async (_, { simpleEvent }) => {
521
+ await simpleEvent();
522
+ return "Simple event emitted";
523
+ },
524
+ });
525
+
526
+ const app = defineResource({
527
+ id: "app",
528
+ register: [simpleEvent, simpleEventHandler, eventEmitter],
529
+ dependencies: { eventEmitter },
530
+ async init(_, { eventEmitter }) {
531
+ await eventEmitter();
532
+ },
533
+ });
534
+
535
+ await run(app);
536
+
537
+ // Verify the simple event handler was executed
538
+ expect(eventHandlerExecutions).toContain(
539
+ "simple-handler:Symbol(__tests__.globalEvents.test.event)"
540
+ );
541
+ });
124
542
  });
@@ -0,0 +1,3 @@
1
+ This is a test, tsc build will fail if the code is not correct.
2
+
3
+ These files are not ran they are just used to test the tsc build.
@@ -0,0 +1,25 @@
1
+ import { defineResource, defineTask } from "../../define";
2
+ import { bResource } from "./b.resource";
3
+
4
+ export const aResource = defineResource({
5
+ id: "a.resource",
6
+ dependencies: {
7
+ b: bResource,
8
+ },
9
+ async init(_, { b }) {
10
+ const result: string = b;
11
+ // @ts-expect-error
12
+ const result2: number = b;
13
+ return `A depends on ${b}`;
14
+ },
15
+ });
16
+
17
+ export const aTask = defineTask({
18
+ id: "a.task",
19
+ dependencies: {
20
+ a: aResource,
21
+ },
22
+ async run(_, { a }) {
23
+ return `Task A executed with dependency: ${a}`;
24
+ },
25
+ });
@@ -0,0 +1,33 @@
1
+ import { defineResource } from "../../define";
2
+ import { cResource } from "./c.resource";
3
+
4
+ export const bResource = defineResource({
5
+ id: "b.resource",
6
+ dependencies: {
7
+ c: cResource,
8
+ },
9
+ async init(_, { c }) {
10
+ const result: string = c;
11
+ // @ts-expect-error
12
+ const result2: number = c;
13
+ return `B depends on ${c}`;
14
+ },
15
+ });
16
+ export const b1Resource = defineResource({
17
+ id: "b.resource",
18
+ dependencies: {
19
+ c: cResource,
20
+ },
21
+ async init(_, { c }) {
22
+ return 123;
23
+ },
24
+ });
25
+ export const b2Resource = defineResource({
26
+ id: "b.resource",
27
+ dependencies: {
28
+ c: cResource,
29
+ },
30
+ async init(_, { c }) {
31
+ return true;
32
+ },
33
+ });
@@ -0,0 +1,18 @@
1
+ import { defineResource } from "../../define";
2
+ import { IResource } from "../../defs";
3
+ import { aResource, aTask } from "./a.resource";
4
+ import { b1Resource, b2Resource } from "./b.resource";
5
+
6
+ const value = Math.random() > 0.5 ? b1Resource : b2Resource;
7
+
8
+ export const cResource = defineResource({
9
+ id: "c.resource",
10
+ dependencies: {
11
+ aTask,
12
+ customResource: value,
13
+ },
14
+ async init(_, { aTask, customResource }) {
15
+ const result: string = await aTask(); // Still benefits of autocompletion
16
+ return `C depends on ${result}`;
17
+ },
18
+ }) as IResource<void, string>; // This is the key change.