@bluelibs/runner 1.1.0 → 1.3.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 (72) hide show
  1. package/README.md +309 -114
  2. package/dist/DependencyProcessor.d.ts +2 -2
  3. package/dist/DependencyProcessor.js +3 -3
  4. package/dist/DependencyProcessor.js.map +1 -1
  5. package/dist/Store.d.ts +24 -1
  6. package/dist/Store.js +108 -34
  7. package/dist/Store.js.map +1 -1
  8. package/dist/TaskRunner.d.ts +3 -0
  9. package/dist/TaskRunner.js +3 -0
  10. package/dist/TaskRunner.js.map +1 -1
  11. package/dist/define.d.ts +2 -2
  12. package/dist/define.js +13 -9
  13. package/dist/define.js.map +1 -1
  14. package/dist/defs.d.ts +21 -10
  15. package/dist/globalEvents.d.ts +5 -1
  16. package/dist/globalEvents.js +3 -0
  17. package/dist/globalEvents.js.map +1 -1
  18. package/dist/globalResources.d.ts +5 -3
  19. package/dist/globalResources.js +4 -0
  20. package/dist/globalResources.js.map +1 -1
  21. package/dist/index.d.ts +11 -7
  22. package/dist/index.js +3 -3
  23. package/dist/index.js.map +1 -1
  24. package/dist/models/DependencyProcessor.d.ts +49 -0
  25. package/dist/models/DependencyProcessor.js +185 -0
  26. package/dist/models/DependencyProcessor.js.map +1 -0
  27. package/dist/models/EventManager.d.ts +18 -0
  28. package/dist/models/EventManager.js +99 -0
  29. package/dist/models/EventManager.js.map +1 -0
  30. package/dist/models/Logger.d.ts +33 -0
  31. package/dist/models/Logger.js +76 -0
  32. package/dist/models/Logger.js.map +1 -0
  33. package/dist/models/ResourceInitializer.d.ts +14 -0
  34. package/dist/models/ResourceInitializer.js +86 -0
  35. package/dist/models/ResourceInitializer.js.map +1 -0
  36. package/dist/models/Store.d.ts +91 -0
  37. package/dist/models/Store.js +312 -0
  38. package/dist/models/Store.js.map +1 -0
  39. package/dist/models/TaskRunner.d.ts +25 -0
  40. package/dist/models/TaskRunner.js +100 -0
  41. package/dist/models/TaskRunner.js.map +1 -0
  42. package/dist/models/index.d.ts +5 -0
  43. package/dist/models/index.js +22 -0
  44. package/dist/models/index.js.map +1 -0
  45. package/dist/run.d.ts +3 -3
  46. package/dist/run.js +12 -6
  47. package/dist/run.js.map +1 -1
  48. package/package.json +1 -1
  49. package/src/__tests__/index.ts +9 -4
  50. package/src/__tests__/models/EventManager.test.ts +385 -0
  51. package/src/__tests__/models/Logger.test.ts +140 -0
  52. package/src/__tests__/{ResourceInitializer.test.ts → models/ResourceInitializer.test.ts} +5 -4
  53. package/src/__tests__/{Store.test.ts → models/Store.test.ts} +4 -4
  54. package/src/__tests__/{TaskRunner.test.ts → models/TaskRunner.test.ts} +9 -7
  55. package/src/__tests__/run.hooks.test.ts +31 -0
  56. package/src/__tests__/run.middleware.test.ts +223 -0
  57. package/src/__tests__/run.overrides.test.ts +392 -0
  58. package/src/__tests__/run.test.ts +112 -131
  59. package/src/define.ts +18 -11
  60. package/src/defs.ts +23 -10
  61. package/src/globalEvents.ts +8 -2
  62. package/src/globalResources.ts +8 -3
  63. package/src/index.ts +3 -3
  64. package/src/{DependencyProcessor.ts → models/DependencyProcessor.ts} +23 -14
  65. package/src/{EventManager.ts → models/EventManager.ts} +56 -27
  66. package/src/models/Logger.ts +100 -0
  67. package/src/{ResourceInitializer.ts → models/ResourceInitializer.ts} +51 -6
  68. package/src/{Store.ts → models/Store.ts} +159 -47
  69. package/src/{TaskRunner.ts → models/TaskRunner.ts} +11 -5
  70. package/src/models/index.ts +5 -0
  71. package/src/run.ts +13 -7
  72. package/src/__tests__/EventManager.test.ts +0 -114
package/README.md CHANGED
@@ -6,9 +6,9 @@
6
6
  <a href="https://bluelibs.github.io/runner/" target="_blank"><img src="https://img.shields.io/badge/read-typedocs-blue" alt="Docs" /></a>
7
7
  </p>
8
8
 
9
- These are the building blocks to create amazing applications. It's a more functional approach to building small and large-scale applications.
9
+ BlueLibs Runner is a framework that provides a functional approach to building applications, whether small or large-scale. Its core concepts include Tasks, Resources, Events, and Middleware. Tasks represent the units of logic, while resources are singletons that provide shared services across the application. Events facilitate communication between different parts of the system, and middleware allows interception and modification of task execution. The framework emphasizes an async-first philosophy, ensuring that all operations are executed asynchronously for smoother application flow.
10
10
 
11
- These are the building blocks:
11
+ ## Building Blocks
12
12
 
13
13
  - **Tasks**: Core units of logic that encapsulate specific tasks. They can depend on resources, other tasks, and event emitters.
14
14
  - **Resources**: Singleton objects providing shared functionality. They can be constants, services, functions. They can depend on other resources, tasks, and event emitters.
@@ -89,7 +89,7 @@ Resources are more like services, they are singletons, they are meant to be used
89
89
 
90
90
  ### Resource dispose()
91
91
 
92
- Resources can have a `dispose()` method that can be used to clean up resources. This is useful for cleaning up resources like closing database connections, etc. You typically want to use this when you have opened pending connections or you need to do some cleanup or a graceful shutdown.
92
+ Resources can have a `dispose()` method that can be used for cleanups. This is useful for cleaning up resources like closing database connections, etc. You typically want to use this when you have opened pending connections or you need to do some cleanup or a graceful shutdown.
93
93
 
94
94
  ```ts
95
95
  import { task, run, resource } from "@bluelibs/runner";
@@ -99,24 +99,26 @@ const dbResource = resource({
99
99
  const db = await connectToDatabase();
100
100
  return db;
101
101
  },
102
+ // the value returned from init() will be passed to dispose()
102
103
  async dispose(db, config, deps) {
103
104
  return db.close();
104
105
  },
105
106
  });
106
107
  ```
107
108
 
108
- If you want to call dispose, you have to do it through the global store.
109
+ If you want to call dispose, you have to do it through the global resource called `store`, as everything is encapsulated.
109
110
 
110
111
  ```ts
111
- import { task, run, resource, global } from "@bluelibs/runner";
112
+ import { task, run, resource, globals } from "@bluelibs/runner";
112
113
 
113
114
  const app = resource({
114
115
  id: "app",
115
116
  register: [dbResource],
116
117
  dependencies: {
117
- store: global.resources.store,
118
+ store: globals.resources.store,
118
119
  },
119
120
  async init(_, deps) {
121
+ // We use the fact that we can reuse the value we got from here
120
122
  return {
121
123
  dispose: async () => deps.store.dispose(),
122
124
  };
@@ -128,15 +130,43 @@ const value = await run(app);
128
130
  await value.dispose();
129
131
  ```
130
132
 
131
- ## Encapsulation
133
+ ### Resource with()
134
+
135
+ Resources can be configured with a configuration object. This is useful when you want to pass in configuration to them. For example, you're building a library and you're initialising a mailer service, you can pass in the SMTP credentials as a configuration.
136
+
137
+ ```ts
138
+ import { task, run, resource } from "@bluelibs/runner";
139
+
140
+ type Config = { smtpUrl: string; defaultFrom: string };
141
+ const emailerResource = resource({
142
+ async init(config: Config) {
143
+ // run config checks
144
+ return {
145
+ sendEmail: async (to: string, subject: string, body: string) => {
146
+ // send *email*
147
+ },
148
+ };
149
+ },
150
+ });
151
+
152
+ const app = resource({
153
+ id: "app",
154
+ register: [
155
+ // proper autocompletion is present
156
+ emailerResource.with({ smtpUrl: "smtp://localhost", defaultFrom: "" }),
157
+ ],
158
+ });
159
+ ```
132
160
 
133
- We want to make sure that our tasks are not dependent on the outside world. This is why we have the `dependencies` object.
161
+ If by any chance your main `app` has configs then they will be passed via the second argument of `run`, like this:
134
162
 
135
- You cannot call on an task outside from dependencies. And not only that, it has to be explicitly registered to the container.
163
+ ```ts
164
+ run(app, config);
165
+ ```
136
166
 
137
167
  ## Dependencies
138
168
 
139
- You can depend on `tasks`, `resources`, `events` and `middleware`.
169
+ You can depend on `tasks`, `resources`, `events` and (indirectly) on `middleware`.
140
170
 
141
171
  ```ts
142
172
  import { task, resource, run, event } from "@bluelibs/runner";
@@ -150,7 +180,7 @@ const helloWorld = task({
150
180
 
151
181
  const app = resource({
152
182
  id: "app",
153
- register: [helloWorld],
183
+ register: [helloWorld, logMiddleware],
154
184
  dependencies: {
155
185
  helloWorld,
156
186
  },
@@ -194,7 +224,7 @@ const root = resource({
194
224
 
195
225
  There are only 2 ways to listen to events:
196
226
 
197
- #### `on` property
227
+ ### `on` property
198
228
 
199
229
  ```ts
200
230
  import { task, run, event } from "@bluelibs/runner";
@@ -211,24 +241,24 @@ const helloTask = task({
211
241
  },
212
242
  });
213
243
 
214
- const root = resource({
244
+ const app = resource({
215
245
  id: "app",
216
246
  register: [afterRegisterEvent, helloTask],
217
247
  dependencies: {
218
248
  afterRegisterEvent,
219
249
  },
220
250
  async init(_, deps) {
221
- deps.afterRegisterEvent({ userId: "XXX" });
251
+ await deps.afterRegisterEvent({ userId: "XXX" });
222
252
  },
223
253
  });
224
254
  ```
225
255
 
226
256
  ### `hooks` property
227
257
 
228
- This is only for resources
258
+ This can only be applied to a `resource()`.
229
259
 
230
260
  ```ts
231
- import { task, resource, run, event } from "@bluelibs/runner";
261
+ import { task, resource, run, event, global } from "@bluelibs/runner";
232
262
 
233
263
  const afterRegisterEvent = event<{ userId: string }>({
234
264
  id: "app.user.registered",
@@ -237,14 +267,12 @@ const afterRegisterEvent = event<{ userId: string }>({
237
267
  const root = resource({
238
268
  id: "app",
239
269
  register: [afterRegisterEvent],
240
- dependencies: {
241
- // ...
242
- }
270
+ dependencies: {},
243
271
  hooks: [
244
272
  {
245
- event: afterRegisterEvent,
273
+ event: global.events.afterInit,
246
274
  async run(event, deps) {
247
- // the dependencies from init() also arrive inside the hooks()
275
+ // both dependencies and event are properly infered through typescript
248
276
  console.log("User has been registered!");
249
277
  },
250
278
  },
@@ -255,12 +283,55 @@ const root = resource({
255
283
  });
256
284
  ```
257
285
 
286
+ ### hooks wildcard
287
+
288
+ You can listen to all events by using the wildcard `*`.
289
+
290
+ ```ts
291
+ import { task, resource, run, event, global } from "@bluelibs/runner";
292
+
293
+ const afterRegisterEvent = event<{ userId: string }>({
294
+ id: "app.user.registered",
295
+ });
296
+
297
+ const root = resource({
298
+ id: "app",
299
+ register: [afterRegisterEvent],
300
+ dependencies: {},
301
+ hooks: [
302
+ {
303
+ event: "*",
304
+ async run(event, deps) {
305
+ console.log(
306
+ "Generic event detected",
307
+ event.id,
308
+ event.data,
309
+ event.timestamp
310
+ );
311
+ },
312
+ },
313
+ ],
314
+ async init(_, deps) {
315
+ deps.afterRegisterEvent({ userId: "XXX" });
316
+ },
317
+ });
318
+ ```
319
+
320
+ When using hooks, inside resource() you benefit of autocompletion, in order to keep things clean, if your hooks become large and long consider switching to tasks and `on`. This is a more explicit way to listen to events, and your resource registers them.
321
+
322
+ The hooks from a `resource` are mostly used for configuration, and blending in the system.
323
+
324
+ ### When to use either?
325
+
326
+ - `hooks` are for resources to extend each other, compose functionalities, they are mostly used for configuration and blending in the system.
327
+ - `on` is for when you want to perform a task when something happens.
328
+
258
329
  ## Middleware
259
330
 
260
- Middleware is a way to intercept the execution of tasks. It's a powerful way to add additional functionality to your tasks. First middleware that gets registered is the first that runs, the last middleware that runs is 'closest' to the task, most likely the last element inside `middleware` array at task level.
331
+ Middleware is a way to intercept the execution of tasks or initialization of resources. It's a powerful way to add additional functionality. First middleware that gets registered is the first that runs, giving it a form of priority, the last middleware that runs is 'closest' to the task, most likely the last element inside `middleware` array at task level.
261
332
 
262
333
  ```ts
263
- import { task, run, event } from "@bluelibs/runner";
334
+ import { task, resource, run, event } from "@bluelibs/runner";
264
335
 
265
336
  const logMiddleware = middleware({
266
337
  id: "app.middleware.log",
@@ -268,11 +339,18 @@ const logMiddleware = middleware({
268
339
  // inject tasks, resources, eventCallers here.
269
340
  },
270
341
  async run(data, deps) {
271
- const { taskDefinition, next, input } = data;
272
-
273
- console.log("Before task", taskDefinition.id);
274
- const result = await next(input); // pass the input to the next middleware or task
275
- console.log("After task", taskDefinition.id);
342
+ const { taskDefinition, resourceDefinition, config, next, input } = data;
343
+
344
+ // The middleware can be for a task or a resource, depending on which you get the right elements.
345
+ if (taskDefinition) {
346
+ console.log("Before task", taskDefinition.id);
347
+ const result = await next(input); // pass the input to the next middleware or task
348
+ console.log("After task", taskDefinition.id);
349
+ } else {
350
+ console.log("Before resource", resourceDefinition.id);
351
+ const result = await next(config); // pass the input to the next middleware or task
352
+ console.log("After resource", resourceDefinition.id);
353
+ }
276
354
 
277
355
  return result;
278
356
  },
@@ -287,34 +365,14 @@ const helloTask = task({
287
365
  });
288
366
  ```
289
367
 
290
- You can use middleware creators (function that returns) for configurable middlewares such as:
291
-
292
- ```ts
293
- import { middleware } from "@bluelibs/runner";
294
-
295
- function createLogMiddleware(config) {
296
- return middleware({
297
- // your config-based middleware here.
298
- });
299
- }
300
- ```
301
-
302
- However, if you want to register a middleware for all tasks, here's how you can do it:
368
+ However, if you want to register a middleware for all tasks and resources, here's how you can do it:
303
369
 
304
370
  ```ts
305
371
  import { run, resource } from "@bluelibs/runner";
306
372
 
307
373
  const logMiddleware = middleware({
308
374
  id: "app.middleware.log",
309
- async run(data, deps) {
310
- const { taskDefinition, next, input } = data;
311
-
312
- console.log("Before task", task.id);
313
- const result = await next(input);
314
- console.log("After task", task.id);
315
-
316
- return result;
317
- },
375
+ // ... rest
318
376
  });
319
377
 
320
378
  const root = resource({
@@ -325,20 +383,9 @@ const root = resource({
325
383
 
326
384
  The middleware can only be registered once. This means that if you register a middleware as global, you cannot specify it as a task middleware.
327
385
 
328
- ### Middleware for resources
329
-
330
- Unfortunately, middleware for resources is not supported at the moment. The main reason for this is simplicity and the fact that resources are not meant to be executed, but rather to be initialized.
331
-
332
- You have access to the global events if you want to hook into the initialisation system.
333
-
334
- ### When to use either?
335
-
336
- - `hooks` are for resources to extend each other, compose functionalities, they are mostly used for configuration and blending in the system.
337
- - `on` is for when you want to perform a task when something happens.
338
-
339
386
  ## Errors
340
387
 
341
- If an error is thrown in a task, the error will be propagated up.
388
+ If an error is thrown in a task, the error will be propagated up to the top runner.
342
389
 
343
390
  ```ts
344
391
  import { task, run, event } from "@bluelibs/runner";
@@ -361,7 +408,9 @@ const app = resource({
361
408
  },
362
409
  });
363
410
 
364
- run(app);
411
+ run(app).catch((err) => {
412
+ console.error(err);
413
+ });
365
414
  ```
366
415
 
367
416
  You can listen to errors via events:
@@ -370,8 +419,11 @@ You can listen to errors via events:
370
419
  const helloWorld = task({
371
420
  id: "app.onError",
372
421
  on: helloWorld.events.onError,
373
- run({ error, input }, deps) {
422
+ run({ error, input, suppress }, deps) {
374
423
  // this will be called when an error happens
424
+
425
+ // if you handled the error, and you don't want it propagated to the top, supress the propagation.
426
+ suppress();
375
427
  },
376
428
  });
377
429
  ```
@@ -396,7 +448,7 @@ const helloWorld = task({
396
448
  });
397
449
  ```
398
450
 
399
- This is particularly helpful to use in conjunction with global middlewares, or global events, etc.
451
+ This is particularly helpful to use in conjunction with global middlewares, or global events, they can read some meta tag definition and act accordingly.
400
452
 
401
453
  The interfaces look like this:
402
454
 
@@ -494,7 +546,7 @@ Now you can freely use any of the tasks, resources, events, and middlewares from
494
546
 
495
547
  This approach is very powerful when you have multiple packages and you want to compose them together.
496
548
 
497
- ## Real world usage.
549
+ ## Real world examples
498
550
 
499
551
  Typically you have an express server (to handle HTTP requests), a database, and a bunch of services. You can define all of these in a single file and run them.
500
552
 
@@ -569,47 +621,7 @@ const app = resource({
569
621
  run();
570
622
  ```
571
623
 
572
- ### Resources can receive configs
573
-
574
- Resources are super configurable.
575
-
576
- ```ts
577
- import { resource, run } from "@bluelibs/runner";
578
-
579
- type EmailerOptions = {
580
- smtpUrl: string;
581
- defaultFrom: string;
582
- };
583
-
584
- const emailerResource = resource({
585
- id: "app.config",
586
- async init(config: EmailerOptions) {
587
- return {
588
- sendEmail: async (to: string, subject: string, body: string) => {
589
- // send *email*
590
- },
591
- };
592
- // or return some service that sends email
593
- },
594
- });
595
-
596
- const app = resource({
597
- id: "app",
598
- register: [
599
- // You can pass the config here
600
- emailerResource.with({
601
- smtpUrl: "smtp://localhost",
602
- defaultFrom: "",
603
- }),
604
- // Leaving it simply emailerResource is similar to passing an empty object.
605
- // We leave this for simplicity in some cases but we recommend using .with() for clarity.
606
- ],
607
- });
608
-
609
- run(app);
610
- ```
611
-
612
- ## Useful events
624
+ ## Global Events
613
625
 
614
626
  ### Task level
615
627
 
@@ -649,15 +661,19 @@ const app = resource({
649
661
  run(app);
650
662
  ```
651
663
 
652
- ## Resource level
664
+ ### Business Config
653
665
 
654
666
  ```ts
655
667
  import { task, run, event } from "@bluelibs/runner";
656
668
 
669
+ const businessData = {
670
+ pricePerSubscription: 9.99,
671
+ };
672
+
657
673
  const businessConfig = resource({
658
674
  id: "app.config",
659
675
  async init() {
660
- return businessData;
676
+ return businessData; // if you use it as a const you will have full typesafety
661
677
  },
662
678
  });
663
679
 
@@ -689,10 +705,12 @@ const app = resource({
689
705
  run(app);
690
706
  ```
691
707
 
692
- ## Moving further
708
+ ### Moving further
693
709
 
694
710
  This is just a "language" of developing applications. It simplifies dependency injection to the barebones, it forces you to think more functional and use classes less.
695
711
 
712
+ This doesn't mean you shouldn't use classes, just not for hooking things up together.
713
+
696
714
  You can add many services or external things into the runner ecosystem with things like:
697
715
 
698
716
  ```ts
@@ -742,7 +760,7 @@ const app = resource({
742
760
  run(app);
743
761
  ```
744
762
 
745
- ## Inter-communication between resources
763
+ ### Inter-communication between resources
746
764
 
747
765
  By stating dependencies you often don't care about the initialisation order, but sometimes you really do, for example, let's imagine a security service that allows you to inject a custom hashing function let's say to shift from md5 to sha256.
748
766
 
@@ -804,6 +822,7 @@ const securityResource = resource({
804
822
  securityConfigurationPhaseEvent,
805
823
  },
806
824
  async init(config: SecurityOptions) {
825
+ // Give the ability to other listeners to modify the configuration
807
826
  securityConfigurationPhaseEvent(config);
808
827
 
809
828
  return {
@@ -827,11 +846,187 @@ const app = resource({
827
846
  });
828
847
  ```
829
848
 
830
- ## Support
849
+ ## Overrides
850
+
851
+ Previously, we explored how we can extend functionality through events. However, sometimes you want to override a resource with a new one or simply swap out a task or a middleware that you import from another package and they don't offer the ability.
852
+
853
+ ```ts
854
+ import { resource, run, event } from "@bluelibs/runner";
855
+
856
+ // This example is for resources but override works for tasks, events, and middleware as well.
857
+ const securityResource = resource({
858
+ id: "app.security",
859
+ async init() {
860
+ // returns a security service
861
+ },
862
+ });
863
+
864
+ const override = resource({
865
+ ...securityResource,
866
+ init: async () => {
867
+ // a new and custom service
868
+ },
869
+ });
870
+
871
+ const app = resource({
872
+ id: "app",
873
+ register: [securityResource], // this resource might be registered by any element in the dependency tree.
874
+ overrides: [override],
875
+ });
876
+ ```
877
+
878
+ Now the `securityResource` will be overriden by the new one and whenever it's used it will use the new one.
879
+
880
+ Overrides can only happen once and only if the overriden resource is registered. If two resources try to override the same resource, an error will be thrown.
881
+
882
+ ## Logging
883
+
884
+ We expose through globals a logger that you can use to log things.
885
+
886
+ By default logs are not printed unless a resource listens to the log event. This is by design, when something is logged an event is emitted. You can listen to this event and print the logs.
887
+
888
+ ```ts
889
+ import { task, run, event, globals } from "@bluelibs/runner";
890
+
891
+ const helloWorld = task({
892
+ id: "app.helloWorld",
893
+ dependencies: {
894
+ logger: globals.resources.logger,
895
+ },
896
+ run: async (_, { logger }) => {
897
+ await logger.info("Hello World!");
898
+ // or logger.log(level, data);
899
+ },
900
+ });
901
+ ```
902
+
903
+ ### Logs Summary Table
904
+
905
+ | Log Level | Description | Usage Example |
906
+ | ------------ | ----------------------------------------- | -------------------------------------- |
907
+ | **trace** | Very detailed logs, usually for debugging | "Entering function X with params Y." |
908
+ | **debug** | Detailed debug information | "Fetching user data: userId=123." |
909
+ | **info** | General application information | "Service started on port 8080." |
910
+ | **warn** | Indicates a potential issue | "Disk space running low." |
911
+ | **error** | Indicates a significant problem | "Unable to connect to database." |
912
+ | **critical** | Serious problem causing a crash | "System out of memory, shutting down." |
913
+
914
+ ### Print logs
915
+
916
+ Logs don't get printed by default in this system.
917
+
918
+ ```ts
919
+ import { task, run, event, globals, resource } from "@bluelibs/runner";
920
+
921
+ const printLog = task({
922
+ id: "app.task.printLog",
923
+ on: globals.events.log,
924
+ dependencies: {
925
+ logger: globals.resources.logger,
926
+ },
927
+ run: async (event, { logger }) => {
928
+ logger.print(event);
929
+ },
930
+ });
831
931
 
832
- This package is part of the [BlueLibs](https://www.bluelibs.com) family. If you enjoy this work, please show your support by starring [the main repository](https://github.com/bluelibs/bluelibs).
932
+ const app = resource({
933
+ id: "root",
934
+ register: [printLog],
935
+ });
936
+
937
+ // Now your app will print all logs
938
+ ```
939
+
940
+ You can in theory do it in `hooks` as well, but as specified `hooks` are mostly used for configuration and blending in the system.
941
+
942
+ The logger's `log()` function is async as it works with events. If you don't want your system hanging on logs, simply omit the `await`
943
+
944
+ ## Testing
945
+
946
+ ### Unit Testing
947
+
948
+ You can easily test your resources and tasks by running them in a test environment.
949
+
950
+ The only bits that you need to test are the `run` function and the `init` functions with the propper dependencies.
951
+
952
+ ```ts
953
+ import { task, resource } from "@bluelibs/runner";
954
+
955
+ const helloWorld = task({
956
+ id: "app.helloWorld",
957
+ run: async () => {
958
+ return "Hello World!";
959
+ },
960
+ });
961
+
962
+ const helloWorldResource = resource({
963
+ id: "app.helloWorldResource",
964
+ init: async () => {
965
+ return "Hello World!";
966
+ },
967
+ });
968
+
969
+ // sample tests for the task
970
+ describe("app.helloWorld", () => {
971
+ it("should return Hello World!", async () => {
972
+ const result = await helloWorld.run(input, dependencies); // pass in the arguments and the mocked dependencies.
973
+ expect(result).toBe("Hello World!");
974
+ });
975
+ });
976
+
977
+ // sample tests for the resource
978
+ describe("app.helloWorldResource", () => {
979
+ it("should return Hello World!", async () => {
980
+ const result = await helloWorldResource.init(config, dependencies); // pass in the arguments and the mocked dependencies.
981
+ expect(result).toBe("Hello World!");
982
+ });
983
+ });
984
+ ```
985
+
986
+ ### Integration
987
+
988
+ Unit testing can be very simply with mocks, since all dependencies are explicit. However, if you would like to run an integration test, and have a task be tested and within the full container.
989
+
990
+ ```ts
991
+ import { task, resource, run, global } from "@bluelibs/runner";
992
+
993
+ const task = task({
994
+ id: "app.myTask",
995
+ run: async () => {
996
+ return "Hello World!";
997
+ },
998
+ });
999
+
1000
+ const app = resource({
1001
+ id: "app",
1002
+ register: [myTask],
1003
+ });
1004
+ ```
1005
+
1006
+ Then your tests can now be cleaner:
1007
+
1008
+ ```ts
1009
+ describe("app", () => {
1010
+ it("an example to override a task or resource", async () => {
1011
+ const testApp = resource({
1012
+ id: "app.test",
1013
+ register: [myApp], // wrap your existing app
1014
+ overrides: [override], // apply the overrides
1015
+ init: async (_, deps) => {
1016
+ // you can now test a task simply by depending on it, and running it, then asserting the response of run()
1017
+ },
1018
+ });
1019
+
1020
+ // Same concept applies for resources as well.
1021
+
1022
+ await run(testApp);
1023
+ });
1024
+ });
1025
+ ```
1026
+
1027
+ ## Support
833
1028
 
834
- For feedback or suggestions, please use our [feedback form](https://forms.gle/DTMg5Urgqey9QqLFA).
1029
+ This package is part of the [BlueLibs](https://www.bluelibs.com) family. If you enjoy this work, please show your support by starring [the main repository](https://github.com/bluelibs/runner).
835
1030
 
836
1031
  ## License
837
1032
 
@@ -29,13 +29,13 @@ export declare class DependencyProcessor {
29
29
  * Processes all hooks, should run before emission of any event.
30
30
  * @returns
31
31
  */
32
- processHooks(): void;
32
+ attachHooks(): void;
33
33
  /**
34
34
  * Processes the hooks for resources
35
35
  * @param hooks
36
36
  * @param deps
37
37
  */
38
- processHooksForResource(resourceStoreElement: ResourceStoreElementType<any, any, {}>): void;
38
+ attachHooksToResource(resourceStoreElement: ResourceStoreElementType<any, any, {}>): void;
39
39
  extractDependencies<T extends DependencyMapType>(map: T): Promise<DependencyValuesType<T>>;
40
40
  extractDependency(object: any): Promise<any>;
41
41
  /**
@@ -76,11 +76,11 @@ class DependencyProcessor {
76
76
  * Processes all hooks, should run before emission of any event.
77
77
  * @returns
78
78
  */
79
- processHooks() {
79
+ attachHooks() {
80
80
  // iterate through resources and send them to processHooks
81
81
  for (const resource of this.store.resources.values()) {
82
82
  if (resource.resource.hooks) {
83
- this.processHooksForResource(resource);
83
+ this.attachHooksToResource(resource);
84
84
  }
85
85
  }
86
86
  }
@@ -89,7 +89,7 @@ class DependencyProcessor {
89
89
  * @param hooks
90
90
  * @param deps
91
91
  */
92
- processHooksForResource(resourceStoreElement) {
92
+ attachHooksToResource(resourceStoreElement) {
93
93
  let hooks = resourceStoreElement.resource.hooks;
94
94
  if (typeof hooks === "function") {
95
95
  hooks = hooks(resourceStoreElement.config);