@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.
- package/README.md +309 -114
- package/dist/DependencyProcessor.d.ts +2 -2
- package/dist/DependencyProcessor.js +3 -3
- package/dist/DependencyProcessor.js.map +1 -1
- package/dist/Store.d.ts +24 -1
- package/dist/Store.js +108 -34
- package/dist/Store.js.map +1 -1
- package/dist/TaskRunner.d.ts +3 -0
- package/dist/TaskRunner.js +3 -0
- package/dist/TaskRunner.js.map +1 -1
- package/dist/define.d.ts +2 -2
- package/dist/define.js +13 -9
- package/dist/define.js.map +1 -1
- package/dist/defs.d.ts +21 -10
- package/dist/globalEvents.d.ts +5 -1
- package/dist/globalEvents.js +3 -0
- package/dist/globalEvents.js.map +1 -1
- package/dist/globalResources.d.ts +5 -3
- package/dist/globalResources.js +4 -0
- package/dist/globalResources.js.map +1 -1
- package/dist/index.d.ts +11 -7
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/models/DependencyProcessor.d.ts +49 -0
- package/dist/models/DependencyProcessor.js +185 -0
- package/dist/models/DependencyProcessor.js.map +1 -0
- package/dist/models/EventManager.d.ts +18 -0
- package/dist/models/EventManager.js +99 -0
- package/dist/models/EventManager.js.map +1 -0
- package/dist/models/Logger.d.ts +33 -0
- package/dist/models/Logger.js +76 -0
- package/dist/models/Logger.js.map +1 -0
- package/dist/models/ResourceInitializer.d.ts +14 -0
- package/dist/models/ResourceInitializer.js +86 -0
- package/dist/models/ResourceInitializer.js.map +1 -0
- package/dist/models/Store.d.ts +91 -0
- package/dist/models/Store.js +312 -0
- package/dist/models/Store.js.map +1 -0
- package/dist/models/TaskRunner.d.ts +25 -0
- package/dist/models/TaskRunner.js +100 -0
- package/dist/models/TaskRunner.js.map +1 -0
- package/dist/models/index.d.ts +5 -0
- package/dist/models/index.js +22 -0
- package/dist/models/index.js.map +1 -0
- package/dist/run.d.ts +3 -3
- package/dist/run.js +12 -6
- package/dist/run.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/index.ts +9 -4
- package/src/__tests__/models/EventManager.test.ts +385 -0
- package/src/__tests__/models/Logger.test.ts +140 -0
- package/src/__tests__/{ResourceInitializer.test.ts → models/ResourceInitializer.test.ts} +5 -4
- package/src/__tests__/{Store.test.ts → models/Store.test.ts} +4 -4
- package/src/__tests__/{TaskRunner.test.ts → models/TaskRunner.test.ts} +9 -7
- package/src/__tests__/run.hooks.test.ts +31 -0
- package/src/__tests__/run.middleware.test.ts +223 -0
- package/src/__tests__/run.overrides.test.ts +392 -0
- package/src/__tests__/run.test.ts +112 -131
- package/src/define.ts +18 -11
- package/src/defs.ts +23 -10
- package/src/globalEvents.ts +8 -2
- package/src/globalResources.ts +8 -3
- package/src/index.ts +3 -3
- package/src/{DependencyProcessor.ts → models/DependencyProcessor.ts} +23 -14
- package/src/{EventManager.ts → models/EventManager.ts} +56 -27
- package/src/models/Logger.ts +100 -0
- package/src/{ResourceInitializer.ts → models/ResourceInitializer.ts} +51 -6
- package/src/{Store.ts → models/Store.ts} +159 -47
- package/src/{TaskRunner.ts → models/TaskRunner.ts} +11 -5
- package/src/models/index.ts +5 -0
- package/src/run.ts +13 -7
- 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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
273
|
+
event: global.events.afterInit,
|
|
246
274
|
async run(event, deps) {
|
|
247
|
-
//
|
|
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
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
+
attachHooks(): void;
|
|
33
33
|
/**
|
|
34
34
|
* Processes the hooks for resources
|
|
35
35
|
* @param hooks
|
|
36
36
|
* @param deps
|
|
37
37
|
*/
|
|
38
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
92
|
+
attachHooksToResource(resourceStoreElement) {
|
|
93
93
|
let hooks = resourceStoreElement.resource.hooks;
|
|
94
94
|
if (typeof hooks === "function") {
|
|
95
95
|
hooks = hooks(resourceStoreElement.config);
|