@bluelibs/runner 3.4.3 → 4.1.1
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/AI.md +638 -0
- package/README.md +1071 -586
- package/dist/context.d.ts +4 -8
- package/dist/context.js +5 -12
- package/dist/context.js.map +1 -1
- package/dist/define.d.ts +9 -113
- package/dist/define.js +29 -364
- package/dist/define.js.map +1 -1
- package/dist/definers/defineEvent.d.ts +2 -0
- package/dist/definers/defineEvent.js +23 -0
- package/dist/definers/defineEvent.js.map +1 -0
- package/dist/definers/defineHook.d.ts +6 -0
- package/dist/definers/defineHook.js +24 -0
- package/dist/definers/defineHook.js.map +1 -0
- package/dist/definers/defineOverride.d.ts +14 -0
- package/dist/definers/defineOverride.js +13 -0
- package/dist/definers/defineOverride.js.map +1 -0
- package/dist/definers/defineResource.d.ts +2 -0
- package/dist/definers/defineResource.js +69 -0
- package/dist/definers/defineResource.js.map +1 -0
- package/dist/definers/defineResourceMiddleware.d.ts +2 -0
- package/dist/definers/defineResourceMiddleware.js +42 -0
- package/dist/definers/defineResourceMiddleware.js.map +1 -0
- package/dist/definers/defineTag.d.ts +12 -0
- package/dist/definers/defineTag.js +106 -0
- package/dist/definers/defineTag.js.map +1 -0
- package/dist/definers/defineTask.d.ts +15 -0
- package/dist/definers/defineTask.js +42 -0
- package/dist/definers/defineTask.js.map +1 -0
- package/dist/definers/defineTaskMiddleware.d.ts +2 -0
- package/dist/definers/defineTaskMiddleware.js +42 -0
- package/dist/definers/defineTaskMiddleware.js.map +1 -0
- package/dist/definers/tools.d.ts +45 -0
- package/dist/definers/tools.js +75 -0
- package/dist/definers/tools.js.map +1 -0
- package/dist/defs.d.ts +16 -424
- package/dist/defs.js +26 -38
- package/dist/defs.js.map +1 -1
- package/dist/errors.d.ts +23 -8
- package/dist/errors.js +50 -10
- package/dist/errors.js.map +1 -1
- package/dist/globals/globalEvents.d.ts +15 -39
- package/dist/globals/globalEvents.js +20 -81
- package/dist/globals/globalEvents.js.map +1 -1
- package/dist/globals/globalMiddleware.d.ts +24 -17
- package/dist/globals/globalMiddleware.js +12 -4
- package/dist/globals/globalMiddleware.js.map +1 -1
- package/dist/globals/globalResources.d.ts +13 -28
- package/dist/globals/globalResources.js +15 -7
- package/dist/globals/globalResources.js.map +1 -1
- package/dist/globals/globalTags.d.ts +9 -0
- package/dist/globals/globalTags.js +23 -0
- package/dist/globals/globalTags.js.map +1 -0
- package/dist/globals/middleware/cache.middleware.d.ts +10 -17
- package/dist/globals/middleware/cache.middleware.js +4 -16
- package/dist/globals/middleware/cache.middleware.js.map +1 -1
- package/dist/globals/middleware/requireContext.middleware.d.ts +1 -1
- package/dist/globals/middleware/requireContext.middleware.js +5 -14
- package/dist/globals/middleware/requireContext.middleware.js.map +1 -1
- package/dist/globals/middleware/retry.middleware.d.ts +2 -1
- package/dist/globals/middleware/retry.middleware.js +32 -5
- package/dist/globals/middleware/retry.middleware.js.map +1 -1
- package/dist/globals/middleware/timeout.middleware.d.ts +2 -1
- package/dist/globals/middleware/timeout.middleware.js +31 -5
- package/dist/globals/middleware/timeout.middleware.js.map +1 -1
- package/dist/globals/resources/debug/debug.resource.d.ts +7 -0
- package/dist/globals/resources/debug/debug.resource.js +29 -0
- package/dist/globals/resources/debug/debug.resource.js.map +1 -0
- package/dist/globals/resources/debug/debug.tag.d.ts +2 -0
- package/dist/globals/resources/debug/debug.tag.js +12 -0
- package/dist/globals/resources/debug/debug.tag.js.map +1 -0
- package/dist/globals/resources/debug/debugConfig.resource.d.ts +22 -0
- package/dist/globals/resources/debug/debugConfig.resource.js +20 -0
- package/dist/globals/resources/debug/debugConfig.resource.js.map +1 -0
- package/dist/globals/resources/debug/executionTracker.middleware.d.ts +50 -0
- package/dist/globals/resources/debug/executionTracker.middleware.js +87 -0
- package/dist/globals/resources/debug/executionTracker.middleware.js.map +1 -0
- package/dist/globals/resources/debug/globalEvent.hook.d.ts +27 -0
- package/dist/globals/resources/debug/globalEvent.hook.js +38 -0
- package/dist/globals/resources/debug/globalEvent.hook.js.map +1 -0
- package/dist/globals/resources/debug/hook.hook.d.ts +25 -0
- package/dist/globals/resources/debug/hook.hook.js +42 -0
- package/dist/globals/resources/debug/hook.hook.js.map +1 -0
- package/dist/globals/resources/debug/index.d.ts +6 -0
- package/dist/{types → globals/resources/debug}/index.js +6 -11
- package/dist/globals/resources/debug/index.js.map +1 -0
- package/dist/globals/resources/debug/middleware.hook.d.ts +25 -0
- package/dist/globals/resources/debug/middleware.hook.js +71 -0
- package/dist/globals/resources/debug/middleware.hook.js.map +1 -0
- package/dist/globals/resources/debug/types.d.ts +25 -0
- package/dist/globals/resources/debug/types.js +65 -0
- package/dist/globals/resources/debug/types.js.map +1 -0
- package/dist/globals/resources/debug/utils.d.ts +2 -0
- package/dist/globals/resources/debug/utils.js +9 -0
- package/dist/globals/resources/debug/utils.js.map +1 -0
- package/dist/globals/resources/queue.resource.d.ts +3 -3
- package/dist/globals/resources/queue.resource.js.map +1 -1
- package/dist/globals/types.d.ts +1 -0
- package/dist/{task.types.js → globals/types.js} +2 -7
- package/dist/globals/types.js.map +1 -0
- package/dist/index.d.ts +58 -85
- package/dist/index.js +23 -10
- package/dist/index.js.map +1 -1
- package/dist/models/DependencyProcessor.d.ts +8 -6
- package/dist/models/DependencyProcessor.js +129 -30
- package/dist/models/DependencyProcessor.js.map +1 -1
- package/dist/models/EventManager.d.ts +127 -7
- package/dist/models/EventManager.js +251 -78
- package/dist/models/EventManager.js.map +1 -1
- package/dist/models/LogPrinter.d.ts +55 -0
- package/dist/models/LogPrinter.js +196 -0
- package/dist/models/LogPrinter.js.map +1 -0
- package/dist/models/Logger.d.ts +47 -27
- package/dist/models/Logger.js +133 -155
- package/dist/models/Logger.js.map +1 -1
- package/dist/models/MiddlewareManager.d.ts +86 -0
- package/dist/models/MiddlewareManager.js +409 -0
- package/dist/models/MiddlewareManager.js.map +1 -0
- package/dist/models/OverrideManager.d.ts +3 -3
- package/dist/models/OverrideManager.js +22 -7
- package/dist/models/OverrideManager.js.map +1 -1
- package/dist/models/ResourceInitializer.d.ts +4 -3
- package/dist/models/ResourceInitializer.js +12 -68
- package/dist/models/ResourceInitializer.js.map +1 -1
- package/dist/models/RunResult.d.ts +35 -0
- package/dist/models/RunResult.js +68 -0
- package/dist/models/RunResult.js.map +1 -0
- package/dist/models/Store.d.ts +30 -17
- package/dist/models/Store.js +87 -25
- package/dist/models/Store.js.map +1 -1
- package/dist/models/StoreRegistry.d.ts +34 -19
- package/dist/models/StoreRegistry.js +248 -100
- package/dist/models/StoreRegistry.js.map +1 -1
- package/dist/models/StoreValidator.d.ts +5 -7
- package/dist/models/StoreValidator.js +50 -17
- package/dist/models/StoreValidator.js.map +1 -1
- package/dist/models/TaskRunner.d.ts +3 -2
- package/dist/models/TaskRunner.js +6 -103
- package/dist/models/TaskRunner.js.map +1 -1
- package/dist/models/UnhandledError.d.ts +11 -0
- package/dist/models/UnhandledError.js +30 -0
- package/dist/models/UnhandledError.js.map +1 -0
- package/dist/models/index.d.ts +3 -0
- package/dist/models/index.js +3 -0
- package/dist/models/index.js.map +1 -1
- package/dist/{tools → models/utils}/findCircularDependencies.js +8 -16
- package/dist/models/utils/findCircularDependencies.js.map +1 -0
- package/dist/models/utils/safeStringify.d.ts +3 -0
- package/dist/models/utils/safeStringify.js +45 -0
- package/dist/models/utils/safeStringify.js.map +1 -0
- package/dist/processHooks.d.ts +2 -0
- package/dist/processHooks.js +70 -0
- package/dist/processHooks.js.map +1 -0
- package/dist/run.d.ts +14 -27
- package/dist/run.js +100 -36
- package/dist/run.js.map +1 -1
- package/dist/testing.d.ts +5 -4
- package/dist/testing.js +3 -2
- package/dist/testing.js.map +1 -1
- package/dist/tools/getCallerFile.d.ts +0 -8
- package/dist/tools/getCallerFile.js +0 -51
- package/dist/tools/getCallerFile.js.map +1 -1
- package/dist/types/contracts.d.ts +55 -0
- package/dist/types/contracts.js +4 -0
- package/dist/types/contracts.js.map +1 -0
- package/dist/types/event.d.ts +32 -7
- package/dist/types/event.js +14 -1
- package/dist/types/event.js.map +1 -1
- package/dist/types/hook.d.ts +23 -0
- package/dist/{models/StoreTypes.js → types/hook.js} +2 -1
- package/dist/types/hook.js.map +1 -0
- package/dist/types/meta.d.ts +6 -1
- package/dist/types/meta.js +4 -2
- package/dist/types/meta.js.map +1 -1
- package/dist/types/resource.d.ts +40 -52
- package/dist/types/resource.js +1 -0
- package/dist/types/resource.js.map +1 -1
- package/dist/types/resourceMiddleware.d.ts +47 -0
- package/dist/{middleware.types.js → types/resourceMiddleware.js} +1 -1
- package/dist/types/resourceMiddleware.js.map +1 -0
- package/dist/types/runner.d.ts +37 -0
- package/dist/types/{base.js → runner.js} +1 -1
- package/dist/types/runner.js.map +1 -0
- package/dist/types/storeTypes.d.ts +40 -0
- package/dist/types/{metadata.js → storeTypes.js} +1 -1
- package/dist/types/storeTypes.js.map +1 -0
- package/dist/types/symbols.d.ts +10 -21
- package/dist/types/symbols.js +17 -22
- package/dist/types/symbols.js.map +1 -1
- package/dist/types/tag.d.ts +46 -0
- package/dist/{resource.types.js → types/tag.js} +2 -1
- package/dist/types/tag.js.map +1 -0
- package/dist/types/task.d.ts +28 -52
- package/dist/types/task.js +1 -0
- package/dist/types/task.js.map +1 -1
- package/dist/types/taskMiddleware.d.ts +48 -0
- package/dist/{event.types.js → types/taskMiddleware.js} +1 -1
- package/dist/types/taskMiddleware.js.map +1 -0
- package/dist/types/utilities.d.ts +109 -6
- package/dist/types/utilities.js +16 -2
- package/dist/types/utilities.js.map +1 -1
- package/package.json +14 -5
- package/dist/cli/extract-docs.d.ts +0 -2
- package/dist/cli/extract-docs.js +0 -88
- package/dist/cli/extract-docs.js.map +0 -1
- package/dist/common.types.d.ts +0 -20
- package/dist/common.types.js +0 -4
- package/dist/common.types.js.map +0 -1
- package/dist/defs/core.d.ts +0 -144
- package/dist/defs/core.js +0 -6
- package/dist/defs/core.js.map +0 -1
- package/dist/defs/symbols.d.ts +0 -42
- package/dist/defs/symbols.js +0 -45
- package/dist/defs/symbols.js.map +0 -1
- package/dist/defs/tags.d.ts +0 -70
- package/dist/defs/tags.js +0 -6
- package/dist/defs/tags.js.map +0 -1
- package/dist/defs.returnTag.d.ts +0 -36
- package/dist/defs.returnTag.js +0 -4
- package/dist/defs.returnTag.js.map +0 -1
- package/dist/docs/introspect.d.ts +0 -7
- package/dist/docs/introspect.js +0 -199
- package/dist/docs/introspect.js.map +0 -1
- package/dist/docs/markdown.d.ts +0 -2
- package/dist/docs/markdown.js +0 -148
- package/dist/docs/markdown.js.map +0 -1
- package/dist/docs/model.d.ts +0 -62
- package/dist/docs/model.js +0 -33
- package/dist/docs/model.js.map +0 -1
- package/dist/event.types.d.ts +0 -18
- package/dist/event.types.js.map +0 -1
- package/dist/examples/express-mongo/index.d.ts +0 -0
- package/dist/examples/express-mongo/index.js +0 -3
- package/dist/examples/express-mongo/index.js.map +0 -1
- package/dist/examples/registrator-example.d.ts +0 -122
- package/dist/examples/registrator-example.js +0 -147
- package/dist/examples/registrator-example.js.map +0 -1
- package/dist/express/docsRouter.d.ts +0 -12
- package/dist/express/docsRouter.js +0 -54
- package/dist/express/docsRouter.js.map +0 -1
- package/dist/globalEvents.d.ts +0 -40
- package/dist/globalEvents.js +0 -94
- package/dist/globalEvents.js.map +0 -1
- package/dist/globalResources.d.ts +0 -10
- package/dist/globalResources.js +0 -43
- package/dist/globalResources.js.map +0 -1
- package/dist/middleware.types.d.ts +0 -40
- package/dist/middleware.types.js.map +0 -1
- package/dist/models/StoreConstants.d.ts +0 -14
- package/dist/models/StoreConstants.js +0 -19
- package/dist/models/StoreConstants.js.map +0 -1
- package/dist/models/StoreTypes.d.ts +0 -21
- package/dist/models/StoreTypes.js.map +0 -1
- package/dist/models/VarStore.d.ts +0 -17
- package/dist/models/VarStore.js +0 -60
- package/dist/models/VarStore.js.map +0 -1
- package/dist/resource.types.d.ts +0 -31
- package/dist/resource.types.js.map +0 -1
- package/dist/symbols.d.ts +0 -24
- package/dist/symbols.js +0 -29
- package/dist/symbols.js.map +0 -1
- package/dist/t1.d.ts +0 -1
- package/dist/t1.js +0 -13
- package/dist/t1.js.map +0 -1
- package/dist/task.types.d.ts +0 -55
- package/dist/task.types.js.map +0 -1
- package/dist/tools/findCircularDependencies.js.map +0 -1
- package/dist/tools/registratorId.d.ts +0 -4
- package/dist/tools/registratorId.js +0 -40
- package/dist/tools/registratorId.js.map +0 -1
- package/dist/tools/simpleHash.d.ts +0 -9
- package/dist/tools/simpleHash.js +0 -34
- package/dist/tools/simpleHash.js.map +0 -1
- package/dist/types/base-interfaces.d.ts +0 -18
- package/dist/types/base-interfaces.js +0 -6
- package/dist/types/base-interfaces.js.map +0 -1
- package/dist/types/base.d.ts +0 -13
- package/dist/types/base.js.map +0 -1
- package/dist/types/dependencies.d.ts +0 -51
- package/dist/types/dependencies.js +0 -3
- package/dist/types/dependencies.js.map +0 -1
- package/dist/types/dependency-core.d.ts +0 -14
- package/dist/types/dependency-core.js +0 -5
- package/dist/types/dependency-core.js.map +0 -1
- package/dist/types/events.d.ts +0 -52
- package/dist/types/events.js +0 -6
- package/dist/types/events.js.map +0 -1
- package/dist/types/hooks.d.ts +0 -16
- package/dist/types/hooks.js +0 -5
- package/dist/types/hooks.js.map +0 -1
- package/dist/types/index.d.ts +0 -8
- package/dist/types/index.js.map +0 -1
- package/dist/types/metadata.d.ts +0 -75
- package/dist/types/metadata.js.map +0 -1
- package/dist/types/middleware.d.ts +0 -63
- package/dist/types/middleware.js +0 -3
- package/dist/types/middleware.js.map +0 -1
- package/dist/types/registerable.d.ts +0 -10
- package/dist/types/registerable.js +0 -5
- package/dist/types/registerable.js.map +0 -1
- package/dist/types/resources.d.ts +0 -44
- package/dist/types/resources.js +0 -5
- package/dist/types/resources.js.map +0 -1
- package/dist/types/tasks.d.ts +0 -41
- package/dist/types/tasks.js +0 -5
- package/dist/types/tasks.js.map +0 -1
- package/src/__tests__/benchmark/benchmark.test.ts +0 -148
- package/src/__tests__/benchmark/task-benchmark.test.ts +0 -132
- package/src/__tests__/context.test.ts +0 -91
- package/src/__tests__/createTestResource.test.ts +0 -139
- package/src/__tests__/errors.test.ts +0 -341
- package/src/__tests__/globalEvents.test.ts +0 -542
- package/src/__tests__/globals/cache.middleware.test.ts +0 -772
- package/src/__tests__/globals/queue.resource.test.ts +0 -141
- package/src/__tests__/globals/requireContext.middleware.test.ts +0 -98
- package/src/__tests__/globals/retry.middleware.test.ts +0 -157
- package/src/__tests__/globals/timeout.middleware.test.ts +0 -88
- package/src/__tests__/index.helper.test.ts +0 -55
- package/src/__tests__/models/EventManager.test.ts +0 -585
- package/src/__tests__/models/Logger.test.ts +0 -519
- package/src/__tests__/models/Queue.test.ts +0 -189
- package/src/__tests__/models/ResourceInitializer.test.ts +0 -148
- package/src/__tests__/models/Semaphore.test.ts +0 -713
- package/src/__tests__/models/Store.test.ts +0 -227
- package/src/__tests__/models/TaskRunner.test.ts +0 -221
- package/src/__tests__/override.test.ts +0 -104
- package/src/__tests__/recursion/README.md +0 -3
- package/src/__tests__/recursion/a.resource.ts +0 -25
- package/src/__tests__/recursion/b.resource.ts +0 -33
- package/src/__tests__/recursion/c.resource.ts +0 -18
- package/src/__tests__/run.anonymous.test.ts +0 -706
- package/src/__tests__/run.dynamic-register-and-dependencies.test.ts +0 -1185
- package/src/__tests__/run.middleware.test.ts +0 -620
- package/src/__tests__/run.overrides.test.ts +0 -424
- package/src/__tests__/run.test.ts +0 -1040
- package/src/__tests__/setOutput.test.ts +0 -244
- package/src/__tests__/tags.test.ts +0 -396
- package/src/__tests__/tools/findCircularDependencies.test.ts +0 -217
- package/src/__tests__/tools/getCallerFile.test.ts +0 -179
- package/src/__tests__/typesafety.test.ts +0 -423
- package/src/__tests__/validation-edge-cases.test.ts +0 -111
- package/src/__tests__/validation-interface.test.ts +0 -428
- package/src/context.ts +0 -86
- package/src/define.ts +0 -492
- package/src/defs.returnTag.ts +0 -91
- package/src/defs.ts +0 -596
- package/src/errors.ts +0 -105
- package/src/globals/globalEvents.ts +0 -125
- package/src/globals/globalMiddleware.ts +0 -16
- package/src/globals/globalResources.ts +0 -53
- package/src/globals/middleware/cache.middleware.ts +0 -115
- package/src/globals/middleware/requireContext.middleware.ts +0 -36
- package/src/globals/middleware/retry.middleware.ts +0 -56
- package/src/globals/middleware/timeout.middleware.ts +0 -46
- package/src/globals/resources/queue.resource.ts +0 -34
- package/src/index.ts +0 -39
- package/src/models/DependencyProcessor.ts +0 -257
- package/src/models/EventManager.ts +0 -210
- package/src/models/Logger.ts +0 -282
- package/src/models/OverrideManager.ts +0 -79
- package/src/models/Queue.ts +0 -66
- package/src/models/ResourceInitializer.ts +0 -165
- package/src/models/Semaphore.ts +0 -208
- package/src/models/Store.ts +0 -193
- package/src/models/StoreConstants.ts +0 -18
- package/src/models/StoreRegistry.ts +0 -253
- package/src/models/StoreTypes.ts +0 -47
- package/src/models/StoreValidator.ts +0 -43
- package/src/models/TaskRunner.ts +0 -203
- package/src/models/index.ts +0 -8
- package/src/run.ts +0 -116
- package/src/testing.ts +0 -66
- package/src/tools/findCircularDependencies.ts +0 -69
- package/src/tools/getCallerFile.ts +0 -96
- /package/dist/{tools → models/utils}/findCircularDependencies.d.ts +0 -0
package/AI.md
ADDED
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
# BlueLibs Runner: Minimal Guide
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @bluelibs/runner
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Core Philosophy
|
|
10
|
+
|
|
11
|
+
BlueLibs Runner is a **powerful and integrated** framework. It provides a comprehensive set of tools for building robust, testable, and maintainable applications by combining a predictable Dependency Injection (DI) container with a dynamic metadata and eventing system.
|
|
12
|
+
|
|
13
|
+
## DI Container Guarantees
|
|
14
|
+
|
|
15
|
+
This is the foundation of trust for any DI framework.
|
|
16
|
+
|
|
17
|
+
- **Circular Dependencies**: A runtime circular dependency (e.g., `A → B → A`) is a fatal error. The runner **will fail to start** and will throw a descriptive error showing the full dependency chain, forcing you to fix the architecture.
|
|
18
|
+
- **Override Precedence**: Overrides are applied top-down. In case of conflicting overrides for the same `id`, the one defined closest to the root `run()` call wins. The "root is the boss."
|
|
19
|
+
|
|
20
|
+
## TL;DR
|
|
21
|
+
|
|
22
|
+
- **Lifecycle**: `run() → init resources (deps first) → 'ready' event → dispose() (reverse order)`
|
|
23
|
+
- **Tasks**: Functions with DI and middleware. Flow: `call → middleware → input validation → run() → result validation → return`
|
|
24
|
+
- **Resources**: Managed singletons (init/dispose).
|
|
25
|
+
- **Events**: Decoupled communication. Flow: `emit → validation → find & order hooks → run hooks (stoppable)`
|
|
26
|
+
- **Hooks**: Lightweight event listeners. Async and awaited by default.
|
|
27
|
+
- **Middleware**: Cross-cutting concerns. Async and awaited by default.
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import express from "express";
|
|
33
|
+
import { resource, task, run } from "@bluelibs/runner";
|
|
34
|
+
|
|
35
|
+
const server = resource({
|
|
36
|
+
id: "app.server",
|
|
37
|
+
// "context" is for private state between init() and dispose()
|
|
38
|
+
context: () => ({ value: null }),
|
|
39
|
+
init: async (config: { port: number }, dependencies, ctx) => {
|
|
40
|
+
ctx.value = "some-value"; // Store private state for dispose()
|
|
41
|
+
|
|
42
|
+
const app = express();
|
|
43
|
+
const server = app.listen(config.port);
|
|
44
|
+
return { app, server };
|
|
45
|
+
},
|
|
46
|
+
dispose: async ({ server }, config, deps, ctx) => server.close(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const createUser = task({
|
|
50
|
+
id: "app.tasks.createUser",
|
|
51
|
+
dependencies: { server },
|
|
52
|
+
run: async (user: { name: string }, deps) => ({ id: "u1", ...user }),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const app = resource({
|
|
56
|
+
id: "app",
|
|
57
|
+
// Resources with configurations must be registered with with() unless the configuration allows all optional
|
|
58
|
+
// All elements must be registered for them to be used in the system
|
|
59
|
+
register: [server.with({ port: 3000 }), createUser],
|
|
60
|
+
dependencies: { server, createUser },
|
|
61
|
+
init: async (_, { server, createUser }) => {
|
|
62
|
+
server.app.post("/users", async (req, res) =>
|
|
63
|
+
res.json(await createUser(req.body)),
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Run with optional debug/logs
|
|
69
|
+
// If app had a config app.with(config) for 1st arg
|
|
70
|
+
await run(app, {
|
|
71
|
+
debug: "normal", // "normal" | "verbose" | DebugConfig
|
|
72
|
+
logs: { printThreshold: "info", printStrategy: "pretty" },
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Events & Hooks
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { event, hook, globals } from "@bluelibs/runner";
|
|
80
|
+
|
|
81
|
+
const userRegistered = event<{ userId: string; email: string }>({
|
|
82
|
+
id: "app.events.userRegistered",
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const sendWelcome = hook({
|
|
86
|
+
id: "app.hooks.sendWelcome",
|
|
87
|
+
on: userRegistered,
|
|
88
|
+
run: async (e) => console.log(`Welcome ${e.data.email}`),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Wildcard listener
|
|
92
|
+
const audit = hook({
|
|
93
|
+
id: "app.hooks.audit",
|
|
94
|
+
on: "*",
|
|
95
|
+
run: (e) => console.log(e.id),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Exclude internal events from "*"
|
|
99
|
+
const internal = event({
|
|
100
|
+
id: "app.events.internal",
|
|
101
|
+
tags: [globals.tags.excludeFromGlobalHooks],
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Multiple Events per Hook
|
|
106
|
+
|
|
107
|
+
Listen to multiple events with type-safe common fields:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const h = hook({
|
|
111
|
+
id: "app.hooks.multi",
|
|
112
|
+
on: [event1, event2, event3],
|
|
113
|
+
run: async (ev) => {
|
|
114
|
+
// helper utility
|
|
115
|
+
if (isOneOf(ev, [event1, event2])) {
|
|
116
|
+
// all common fields from event1 and event2, if just event1, it will be just event1
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Interception APIs
|
|
123
|
+
|
|
124
|
+
Low-level interception is available for advanced observability and control:
|
|
125
|
+
|
|
126
|
+
- `eventManager.intercept((next, event) => Promise<void>)` — wraps event emission
|
|
127
|
+
- `eventManager.interceptHook((next, hook, event) => Promise<any>)` — wraps hook execution
|
|
128
|
+
- `middlewareManager.intercept("task" | "resource", (next, input) => Promise<any>)` — wraps middleware execution
|
|
129
|
+
- `middlewareManager.interceptMiddleware(middleware, interceptor)` — per-middleware interception
|
|
130
|
+
|
|
131
|
+
Prefer task-level `task.intercept()` for application logic; use the above for cross-cutting concerns.
|
|
132
|
+
|
|
133
|
+
## Unhandled Errors
|
|
134
|
+
|
|
135
|
+
By default, unhandled errors are just logged. You can customize this via `run(app, { onUnhandledError })`:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
await run(app, {
|
|
139
|
+
errorBoundary: true, // Catch process-level errors (default: true)
|
|
140
|
+
onUnhandledError: async ({ error, kind, source }) => {
|
|
141
|
+
// kind: "task" | "middleware" | "resourceInit" | "hook" | "process" | "run"
|
|
142
|
+
// source: optional origin hint (ex: "uncaughtException")
|
|
143
|
+
await telemetry.capture(error as Error, { kind, source });
|
|
144
|
+
|
|
145
|
+
// Optionally decide on remediation strategy
|
|
146
|
+
if (kind === "process") {
|
|
147
|
+
// For hard process faults, prefer fast, clean exit after flushing logs
|
|
148
|
+
await flushAll();
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
If you prefer event-driven handling, you can still emit your own custom events from this callback.
|
|
156
|
+
|
|
157
|
+
## Debug (zero‑overhead when disabled)
|
|
158
|
+
|
|
159
|
+
Enable globally at run time:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
await run(app, { debug: "verbose" }); // "normal" or DebugConfig
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
or per‑component via tag:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { globals, task } from "@bluelibs/runner";
|
|
169
|
+
|
|
170
|
+
const critical = task({
|
|
171
|
+
id: "app.tasks.critical",
|
|
172
|
+
meta: {
|
|
173
|
+
tags: [
|
|
174
|
+
globals.tags.debug.with({ logTaskInput: true, logTaskResult: true }),
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
run: async () => "ok",
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Logger (direct API)
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { resource, globals } from "@bluelibs/runner";
|
|
185
|
+
|
|
186
|
+
const logsExtension = resource({
|
|
187
|
+
id: "app.logs",
|
|
188
|
+
dependencies: { logger: globals.resources.logger },
|
|
189
|
+
init: async (_, { logger }) => {
|
|
190
|
+
logger.info("test", { data }); // "trace", "debug", "info", "warn", "error", "critical"
|
|
191
|
+
const sublogger = logger.with({
|
|
192
|
+
source: "app.logs",
|
|
193
|
+
context: {},
|
|
194
|
+
});
|
|
195
|
+
logger.onLog((log) => {
|
|
196
|
+
// ship or transform
|
|
197
|
+
});
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Middleware (global or local)
|
|
203
|
+
|
|
204
|
+
Middleware now supports type contracts with `<Config, Input, Output>` signature:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import {
|
|
208
|
+
taskMiddleware,
|
|
209
|
+
resourceMiddleware,
|
|
210
|
+
resource,
|
|
211
|
+
task,
|
|
212
|
+
globals,
|
|
213
|
+
} from "@bluelibs/runner";
|
|
214
|
+
|
|
215
|
+
// Custom task middleware with type contracts
|
|
216
|
+
const auth = taskMiddleware<
|
|
217
|
+
{ role: string },
|
|
218
|
+
{ user: { role: string } },
|
|
219
|
+
{ user: { role: string; verified: boolean } }
|
|
220
|
+
>({
|
|
221
|
+
id: "app.middleware.auth",
|
|
222
|
+
run: async ({ task, next }, _, cfg) => {
|
|
223
|
+
if (task.input?.user?.role !== cfg.role) throw new Error("Unauthorized");
|
|
224
|
+
const result = await next(task.input);
|
|
225
|
+
return { user: { ...task.input.user, verified: true } };
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Resource middleware can augment a resource's behavior after it's initialized.
|
|
230
|
+
// For example, this `softDelete` middleware intercepts the `delete` method
|
|
231
|
+
// of a resource and replaces it with a non-destructive update.
|
|
232
|
+
const softDelete = resourceMiddleware({
|
|
233
|
+
id: "app.middleware.softDelete",
|
|
234
|
+
run: async ({ next }) => {
|
|
235
|
+
const resourceInstance = await next(); // The original resource instance
|
|
236
|
+
|
|
237
|
+
// This example assumes the resource has `update` and `delete` methods.
|
|
238
|
+
// A more robust implementation would check for their existence.
|
|
239
|
+
|
|
240
|
+
// Monkey-patch the 'delete' method
|
|
241
|
+
const originalDelete = resourceInstance.delete;
|
|
242
|
+
resourceInstance.delete = async (id: string, ...args) => {
|
|
243
|
+
// Instead of deleting, call 'update' to mark as deleted
|
|
244
|
+
return resourceInstance.update(id, { deletedAt: new Date() }, ...args);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
return resourceInstance;
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const adminOnly = task({
|
|
252
|
+
id: "app.tasks.adminOnly",
|
|
253
|
+
middleware: [auth.with({ role: "admin" })],
|
|
254
|
+
run: async () => "secret",
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Built-in middleware patterns
|
|
258
|
+
const {
|
|
259
|
+
task: { retry, timeout, cache },
|
|
260
|
+
// available: resource: { retry, timeout, cache } as well, same configs.
|
|
261
|
+
} = globals.middleware;
|
|
262
|
+
|
|
263
|
+
// Example of custom middleware with full type contracts
|
|
264
|
+
const validationMiddleware = taskMiddleware<
|
|
265
|
+
{ strict: boolean },
|
|
266
|
+
{ data: unknown },
|
|
267
|
+
{ data: any; validated: boolean }
|
|
268
|
+
>({
|
|
269
|
+
id: "app.middleware.validation",
|
|
270
|
+
run: async ({ task, next }, _, config) => {
|
|
271
|
+
// Validation logic here
|
|
272
|
+
const result = await next(task.input);
|
|
273
|
+
return { ...result, validated: true };
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const resilientTask = task({
|
|
278
|
+
id: "app.tasks.resilient",
|
|
279
|
+
middleware: [
|
|
280
|
+
// Retry with exponential backoff, allow each with timeout
|
|
281
|
+
retry.with({
|
|
282
|
+
retries: 3,
|
|
283
|
+
delayStrategy: (attempt) => 1000 * attempt,
|
|
284
|
+
stopRetryIf: (error) => error.message === "Invalid credentials",
|
|
285
|
+
}),
|
|
286
|
+
// Timeout protection (propose-timeout)
|
|
287
|
+
timeout.with({ ttl: 10000 }),
|
|
288
|
+
// Caching first (onion-level)
|
|
289
|
+
cache.with({
|
|
290
|
+
ttl: 60000,
|
|
291
|
+
keyBuilder: (taskId, input) => `${taskId}-${JSON.stringify(input)}`,
|
|
292
|
+
}),
|
|
293
|
+
],
|
|
294
|
+
run: async () => expensiveApiCall(),
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Global middleware
|
|
298
|
+
const globalTaskMiddleware = taskMiddleware({
|
|
299
|
+
id: "...",
|
|
300
|
+
everywhere: true, // Use everywhere: (task) => boolean, where true means it gets applied
|
|
301
|
+
// ... rest as usual ...
|
|
302
|
+
// if you have dependencies as task, exclude them via everywhere filter.
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Similar behavior for resourceMiddleware({ ... });
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Context (request-scoped values)
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
import { createContext } from "@bluelibs/runner";
|
|
312
|
+
|
|
313
|
+
const UserCtx = createContext<{ userId: string }>("app.userContext");
|
|
314
|
+
|
|
315
|
+
// In middleware or entry-point
|
|
316
|
+
UserCtx.provide({ userId: "u1" }, async () => {
|
|
317
|
+
await someTask(); // has access to the context
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// In a task or hook
|
|
321
|
+
const user = UserCtx.use(); // -> { userId: "u1" }
|
|
322
|
+
|
|
323
|
+
// In a task definition
|
|
324
|
+
const task = {
|
|
325
|
+
middleware: [UserCtx.require()], // This middleware works only in tasks.
|
|
326
|
+
};
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## System Shutdown & Error Boundary
|
|
330
|
+
|
|
331
|
+
The framework includes built-in support for graceful shutdowns:
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
const { dispose } = await run(app, {
|
|
335
|
+
shutdownHooks: true, // Automatically handle SIGTERM/SIGINT (default: true)
|
|
336
|
+
errorBoundary: true, // Catch unhandled errors and rejections (default: true)
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Run Options (high‑level)
|
|
341
|
+
|
|
342
|
+
- debug: "normal" | "verbose" | DebugConfig
|
|
343
|
+
- logs: {
|
|
344
|
+
- printThreshold?: LogLevel | null;
|
|
345
|
+
- printStrategy?: "pretty" | "json" | "json-pretty" | "plain";
|
|
346
|
+
- bufferLogs?: boolean
|
|
347
|
+
- }
|
|
348
|
+
- errorBoundary: boolean (default true)
|
|
349
|
+
- shutdownHooks: boolean (default true)
|
|
350
|
+
- onUnhandledError(error) {}
|
|
351
|
+
|
|
352
|
+
Note: `globals` is a convenience object exposing framework internals:
|
|
353
|
+
|
|
354
|
+
- `globals.events` (ready)
|
|
355
|
+
- `globals.resources` (store, taskRunner, eventManager, logger, cache, queue)
|
|
356
|
+
- `globals.middleware` (retry, cache, timeout, requireContext)
|
|
357
|
+
- `globals.tags` (system, debug, excludeFromGlobalHooks)
|
|
358
|
+
|
|
359
|
+
## Overrides
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
import { override, resource } from "@bluelibs/runner";
|
|
363
|
+
|
|
364
|
+
const emailer = resource({ id: "app.emailer", init: async () => new SMTP() });
|
|
365
|
+
const testEmailer = override(emailer, { init: async () => new MockSMTP() });
|
|
366
|
+
|
|
367
|
+
const app = resource({
|
|
368
|
+
id: "app",
|
|
369
|
+
register: [emailer],
|
|
370
|
+
overrides: [testEmailer],
|
|
371
|
+
});
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Namespacing
|
|
375
|
+
|
|
376
|
+
As your app grows, use a consistent naming convention. This is the recommended format:
|
|
377
|
+
|
|
378
|
+
| Type | Format |
|
|
379
|
+
| ---------- | -------------------------------------- |
|
|
380
|
+
| Tasks | `{domain}.tasks.{taskName}` |
|
|
381
|
+
| Hooks | `{domain}.hooks.on{EventName}` |
|
|
382
|
+
| Resources | `{domain}.resources.{resourceName}` |
|
|
383
|
+
| Events | `{domain}.events.{eventName}` |
|
|
384
|
+
| Middleware | `{domain}.middleware.{middlewareName}` |
|
|
385
|
+
|
|
386
|
+
## Factory Pattern
|
|
387
|
+
|
|
388
|
+
Use a resource to act as a factory for creating class instances. The resource is configured once, and the resulting function can be used throughout the app.
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
const myFactory = resource({
|
|
392
|
+
id: "app.factories.myFactory",
|
|
393
|
+
init: async (config: SomeConfigType) => {
|
|
394
|
+
// The resource's value is a factory function
|
|
395
|
+
return (input: any) => {
|
|
396
|
+
return new MyClass(input, config.someOption);
|
|
397
|
+
};
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const app = resource({
|
|
402
|
+
id: "app",
|
|
403
|
+
register: [myFactory.with({ someOption: "configured" })],
|
|
404
|
+
dependencies: { myFactory },
|
|
405
|
+
init: async (_, { myFactory }) => {
|
|
406
|
+
const instance = myFactory({ someInput: "hello" });
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Testing
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
import { resource, run, override } from "@bluelibs/runner";
|
|
415
|
+
|
|
416
|
+
const app = resource({
|
|
417
|
+
id: "app",
|
|
418
|
+
register: [
|
|
419
|
+
/* tasks/resources */
|
|
420
|
+
],
|
|
421
|
+
});
|
|
422
|
+
const harness = resource({
|
|
423
|
+
id: "test",
|
|
424
|
+
register: [app],
|
|
425
|
+
overrides: [
|
|
426
|
+
/* test overrides */
|
|
427
|
+
],
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const rr = await run(harness);
|
|
431
|
+
await rr.runTask(id | task, { input: 1 });
|
|
432
|
+
// rr.getResourceValue(id | resource)
|
|
433
|
+
// await rr.emitEvent(event, payload)
|
|
434
|
+
// rr.logger.info("xxx")
|
|
435
|
+
await rr.dispose();
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Metadata & Tags
|
|
439
|
+
|
|
440
|
+
```ts
|
|
441
|
+
import { tag, globals, task, resource } from "@bluelibs/runner";
|
|
442
|
+
|
|
443
|
+
// Simple tags and debug/system globals
|
|
444
|
+
const perf = tag<{ warnAboveMs: number }>({ id: "perf" });
|
|
445
|
+
const contractTag = tag<void, void, { result: string }>({ id: "contract" });
|
|
446
|
+
|
|
447
|
+
const processPayment = task({
|
|
448
|
+
id: "app.tasks.pay",
|
|
449
|
+
tags: [perf.with({ warnAboveMs: 1000 })],
|
|
450
|
+
meta: {
|
|
451
|
+
title: "Process Payment",
|
|
452
|
+
description: "Detailed",
|
|
453
|
+
},
|
|
454
|
+
run: async () => {
|
|
455
|
+
/* ... */
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
const internalSvc = resource({
|
|
460
|
+
id: "app.resources.internal",
|
|
461
|
+
register: [perf],
|
|
462
|
+
tags: [globals.tags.system],
|
|
463
|
+
init: async () => ({}),
|
|
464
|
+
});
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Tag Contracts (type‑enforced returns)
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
// Contract enforces the awaited return type (Config, Input, Output)
|
|
471
|
+
const userContract = tag<void, void, { name: string }>({ id: "contract.user" });
|
|
472
|
+
|
|
473
|
+
const getProfile = task({
|
|
474
|
+
id: "app.tasks.getProfile",
|
|
475
|
+
tags: [userContract],
|
|
476
|
+
run: async () => ({ name: "Ada" }), // must contain { name: string }
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const profileService = resource({
|
|
480
|
+
id: "app.resources.profile",
|
|
481
|
+
tags: [userContract],
|
|
482
|
+
init: async () => ({ name: "Ada" }),
|
|
483
|
+
});
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### Tag Extraction (behavior flags)
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
const perf = tag<{ warnAboveMs: number }>({ id: "perf" });
|
|
490
|
+
|
|
491
|
+
const perfMiddleware = taskMiddleware({
|
|
492
|
+
id: "app.middleware.perf",
|
|
493
|
+
run: async ({ task, next }) => {
|
|
494
|
+
const cfg = perf.extract(task.definition);
|
|
495
|
+
// use perf.exists(task.definition) to check for existence
|
|
496
|
+
if (!cfg) return next(task.input);
|
|
497
|
+
// performance hooks here ...
|
|
498
|
+
},
|
|
499
|
+
});
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Intercept Tasks via Tags (programmatic wiring)
|
|
503
|
+
|
|
504
|
+
Use a hook on `globals.events.ready` to discover and intercept tasks by tag:
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
import { hook, globals, tag } from "@bluelibs/runner";
|
|
508
|
+
|
|
509
|
+
const apiTag = tag<void>({ id: "api" });
|
|
510
|
+
|
|
511
|
+
const addTracingToApiTasks = hook({
|
|
512
|
+
id: "app.hooks.traceApis",
|
|
513
|
+
on: globals.events.ready,
|
|
514
|
+
dependencies: { store: globals.resources.store },
|
|
515
|
+
run: async (_, { store }) => {
|
|
516
|
+
const apiTasks = store.getTasksWithTag(apiTag); // tag object or string id
|
|
517
|
+
apiTasks.forEach((taskDef) => {
|
|
518
|
+
taskDef.intercept(async (next, input) => {
|
|
519
|
+
// ...
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
// Apply same concept to routing like fastify, express routes based on tag config to your fastify instance.
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Route registration via tags (ready hook)
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
import { hook, globals } from "@bluelibs/runner";
|
|
531
|
+
import { httpTag } from "./http.tag"; // your structured tag (method, path, schemas, etc.)
|
|
532
|
+
import { expressServer } from "./expressServer"; // resource that returns { app, port }
|
|
533
|
+
|
|
534
|
+
const registerRoutes = hook({
|
|
535
|
+
id: "app.hooks.registerRoutes",
|
|
536
|
+
on: globals.events.ready,
|
|
537
|
+
dependencies: { store: globals.resources.store, server: expressServer },
|
|
538
|
+
run: async (_, { store, server }) => {
|
|
539
|
+
const tasks = store.getTasksWithTag(httpTag);
|
|
540
|
+
tasks.forEach((t) => {
|
|
541
|
+
const cfg = httpTag.extract(t.meta?.tags || []);
|
|
542
|
+
if (!cfg?.config) return;
|
|
543
|
+
const { method, path } = cfg.config;
|
|
544
|
+
if (!method || !path) return;
|
|
545
|
+
(server.app as any)[method.toLowerCase()](path, async (req, res) => {
|
|
546
|
+
const result = await t({ ...req.body, ...req.query, ...req.params });
|
|
547
|
+
res.json(result);
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
},
|
|
551
|
+
});
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
## Key Patterns & Features
|
|
555
|
+
|
|
556
|
+
- **Optional Dependencies**: Gracefully handle missing services by defining dependencies as optional. The dependency will be `null` if not registered.
|
|
557
|
+
`dependencies: { analytics: analyticsService.optional() }`
|
|
558
|
+
|
|
559
|
+
- **Stop Propagation**: Prevent other hooks from running for a specific event.
|
|
560
|
+
`// inside a hook`
|
|
561
|
+
`event.stopPropagation()`
|
|
562
|
+
|
|
563
|
+
That’s it. Small surface area, strong primitives, great DX.
|
|
564
|
+
|
|
565
|
+
## Concurrency: Semaphore & Queue
|
|
566
|
+
|
|
567
|
+
```ts
|
|
568
|
+
import { Semaphore, Queue } from "@bluelibs/runner";
|
|
569
|
+
|
|
570
|
+
// Semaphore: limit parallelism
|
|
571
|
+
const dbSem = new Semaphore(5);
|
|
572
|
+
const users = await dbSem.withPermit(async () =>
|
|
573
|
+
db.query("SELECT * FROM users"),
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
// Queue: FIFO with cooperative cancellation
|
|
577
|
+
const queue = new Queue();
|
|
578
|
+
const result = await queue.run(async (signal) => {
|
|
579
|
+
signal.throwIfAborted();
|
|
580
|
+
return await doWork();
|
|
581
|
+
});
|
|
582
|
+
await queue.dispose({ cancel: true });
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
## Handling Circular Types (even if runtime is fine)
|
|
586
|
+
|
|
587
|
+
Rarely, when TypeScript struggles with circular type inference, break the chain with an explicit interface:
|
|
588
|
+
|
|
589
|
+
```ts
|
|
590
|
+
import type { IResource } from "@bluelibs/runner";
|
|
591
|
+
|
|
592
|
+
export const cResource = resource({
|
|
593
|
+
id: "c.resource",
|
|
594
|
+
dependencies: { a: aResource },
|
|
595
|
+
init: async (_, { a }) => `C depends on ${a}`,
|
|
596
|
+
}) as IResource<void, string>; // void config, returns string
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
## Validation (optional and library‑agnostic)
|
|
600
|
+
|
|
601
|
+
Interface any library can implement:
|
|
602
|
+
|
|
603
|
+
```ts
|
|
604
|
+
interface IValidationSchema<T> {
|
|
605
|
+
parse(input: unknown): T;
|
|
606
|
+
}
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
Works out of the box with Zod (`z.object(...).parse`), and can be adapted for Yup/Joi with small wrappers.
|
|
610
|
+
|
|
611
|
+
```ts
|
|
612
|
+
import { z } from "zod";
|
|
613
|
+
|
|
614
|
+
// Task input/result validation
|
|
615
|
+
const inputSchema = z.object({ email: z.string().email() });
|
|
616
|
+
const resultSchema = z.object({ id: z.string(), email: z.string().email() });
|
|
617
|
+
|
|
618
|
+
task({
|
|
619
|
+
// ...
|
|
620
|
+
inputSchema, // validates before run
|
|
621
|
+
resultSchema, // validates awaited return
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
resource({
|
|
625
|
+
configSchema, // Resource config validation (runs on .with())
|
|
626
|
+
resultSchema, // Runs after initialization
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
event({
|
|
630
|
+
payloadSchema, // Runs on event emission
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// Middleware config validation (runs on .with())
|
|
634
|
+
middleware({
|
|
635
|
+
// ...
|
|
636
|
+
configSchema, // runs on .with()
|
|
637
|
+
});
|
|
638
|
+
```
|