@bluelibs/runner 5.3.0 → 5.5.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 (189) hide show
  1. package/README.md +23 -22
  2. package/dist/browser/index.cjs +6159 -3049
  3. package/dist/browser/index.cjs.map +1 -1
  4. package/dist/browser/index.mjs +6157 -3046
  5. package/dist/browser/index.mjs.map +1 -1
  6. package/dist/edge/index.cjs +6159 -3049
  7. package/dist/edge/index.cjs.map +1 -1
  8. package/dist/edge/index.mjs +6157 -3046
  9. package/dist/edge/index.mjs.map +1 -1
  10. package/dist/node/node.cjs +10843 -10814
  11. package/dist/node/node.cjs.map +1 -1
  12. package/dist/node/node.mjs +10801 -10770
  13. package/dist/node/node.mjs.map +1 -1
  14. package/dist/types/definers/builders/asyncContext/fluent-builder.interface.d.ts +4 -0
  15. package/dist/types/definers/builders/error/fluent-builder.interface.d.ts +8 -1
  16. package/dist/types/definers/builders/error/index.d.ts +1 -0
  17. package/dist/types/definers/builders/error/types.d.ts +2 -1
  18. package/dist/types/definers/builders/error/utils.d.ts +1 -0
  19. package/dist/types/definers/builders/event/fluent-builder.interface.d.ts +10 -0
  20. package/dist/types/definers/builders/event/utils.d.ts +1 -4
  21. package/dist/types/definers/builders/hook/fluent-builder.interface.d.ts +3 -0
  22. package/dist/types/definers/builders/hook/types.d.ts +3 -0
  23. package/dist/types/definers/builders/hook/utils.d.ts +1 -4
  24. package/dist/types/definers/builders/middleware/index.d.ts +4 -2
  25. package/dist/types/definers/builders/middleware/resource.interface.d.ts +7 -0
  26. package/dist/types/definers/builders/middleware/task.interface.d.ts +7 -0
  27. package/dist/types/definers/builders/middleware/types.d.ts +22 -3
  28. package/dist/types/definers/builders/middleware/utils.d.ts +3 -4
  29. package/dist/types/definers/builders/override/hook.d.ts +3 -0
  30. package/dist/types/definers/builders/override/index.d.ts +5 -0
  31. package/dist/types/definers/builders/resource/fluent-builder.interface.d.ts +15 -0
  32. package/dist/types/definers/builders/resource/index.d.ts +0 -1
  33. package/dist/types/definers/builders/resource/types.d.ts +1 -0
  34. package/dist/types/definers/builders/resource/utils.d.ts +1 -4
  35. package/dist/types/definers/builders/shared/mergeUtils.d.ts +5 -0
  36. package/dist/types/definers/builders/tag/fluent-builder.interface.d.ts +4 -0
  37. package/dist/types/definers/builders/tag/utils.d.ts +1 -0
  38. package/dist/types/definers/builders/task/fluent-builder.interface.d.ts +4 -0
  39. package/dist/types/definers/builders/task/index.d.ts +2 -2
  40. package/dist/types/definers/builders/task/utils.d.ts +1 -4
  41. package/dist/types/definers/builders/utils.d.ts +7 -2
  42. package/dist/types/definers/defineError.d.ts +12 -2
  43. package/dist/types/definers/resourceFork.d.ts +3 -1
  44. package/dist/types/definers/tools.d.ts +12 -12
  45. package/dist/types/errors/domain-error-ids.d.ts +44 -0
  46. package/dist/types/errors/domain-runtime.errors.d.ts +136 -0
  47. package/dist/types/errors/foundation.errors.d.ts +125 -0
  48. package/dist/types/errors/generic.errors.d.ts +1 -0
  49. package/dist/types/errors/model-runtime.errors.d.ts +20 -0
  50. package/dist/types/errors.d.ts +5 -74
  51. package/dist/types/globals/cron/cron-parser.d.ts +4 -0
  52. package/dist/types/globals/cron/cron.errors.d.ts +8 -0
  53. package/dist/types/globals/cron/cron.resource.d.ts +12 -0
  54. package/dist/types/globals/cron/cron.tag.d.ts +2 -0
  55. package/dist/types/globals/cron/types.d.ts +25 -0
  56. package/dist/types/globals/globalMiddleware.d.ts +87 -13
  57. package/dist/types/globals/globalResources.d.ts +110 -24
  58. package/dist/types/globals/globalTags.d.ts +10 -2
  59. package/dist/types/globals/middleware/cache.middleware.d.ts +173 -13
  60. package/dist/types/globals/middleware/circuitBreaker.middleware.d.ts +5 -1
  61. package/dist/types/globals/middleware/concurrency.middleware.d.ts +3 -0
  62. package/dist/types/globals/middleware/fallback.middleware.d.ts +8 -1
  63. package/dist/types/globals/middleware/rateLimit.middleware.d.ts +4 -1
  64. package/dist/types/globals/middleware/retry.middleware.d.ts +5 -0
  65. package/dist/types/globals/middleware/temporal.middleware.d.ts +19 -19
  66. package/dist/types/globals/middleware/timeout.middleware.d.ts +4 -1
  67. package/dist/types/globals/resources/eventManager.resource.d.ts +2 -0
  68. package/dist/types/globals/resources/logger.resource.d.ts +2 -0
  69. package/dist/types/globals/resources/middlewareManager.resource.d.ts +2 -0
  70. package/dist/types/globals/resources/queue.resource.d.ts +2 -0
  71. package/dist/types/globals/resources/runtime.resource.d.ts +2 -0
  72. package/dist/types/globals/resources/serializer.resource.d.ts +2 -0
  73. package/dist/types/globals/resources/store.resource.d.ts +2 -0
  74. package/dist/types/globals/resources/taskRunner.resource.d.ts +2 -0
  75. package/dist/types/globals/resources/tunnel/plan.d.ts +6 -3
  76. package/dist/types/globals/resources/tunnel/tunnel.policy.tag.d.ts +2 -0
  77. package/dist/types/globals/resources/tunnel/types.d.ts +7 -2
  78. package/dist/types/globals/types.d.ts +6 -1
  79. package/dist/types/models/BuiltinsRegistry.d.ts +2 -0
  80. package/dist/types/models/DependencyProcessor.d.ts +27 -6
  81. package/dist/types/models/EventManager.d.ts +9 -9
  82. package/dist/types/models/LogPrinter.d.ts +5 -5
  83. package/dist/types/models/Logger.d.ts +16 -16
  84. package/dist/types/models/MiddlewareManager.d.ts +20 -2
  85. package/dist/types/models/OverrideManager.d.ts +4 -1
  86. package/dist/types/models/Queue.d.ts +2 -0
  87. package/dist/types/models/RunResult.d.ts +290 -26
  88. package/dist/types/models/Store.d.ts +29 -12
  89. package/dist/types/models/StoreRegistry.d.ts +16 -13
  90. package/dist/types/models/StoreValidator.d.ts +2 -0
  91. package/dist/types/models/TaskRunner.d.ts +3 -1
  92. package/dist/types/models/VisibilityTracker.d.ts +75 -0
  93. package/dist/types/models/dependency-processor/DependencyExtractor.d.ts +23 -0
  94. package/dist/types/models/dependency-processor/HookEventBuffer.d.ts +15 -0
  95. package/dist/types/models/dependency-processor/ResourceScheduler.d.ts +10 -0
  96. package/dist/types/models/event/EmissionExecutor.d.ts +5 -4
  97. package/dist/types/models/event/ListenerRegistry.d.ts +2 -0
  98. package/dist/types/models/index.d.ts +1 -3
  99. package/dist/types/models/middleware/InterceptorRegistry.d.ts +13 -4
  100. package/dist/types/models/middleware/ResourceMiddlewareComposer.d.ts +3 -2
  101. package/dist/types/models/middleware/ValidationHelper.d.ts +3 -6
  102. package/dist/types/models/utils/buildDependencyGraph.d.ts +12 -0
  103. package/dist/types/models/utils/dependencyStrategies.d.ts +15 -0
  104. package/dist/types/models/utils/disposeOrder.d.ts +11 -0
  105. package/dist/types/models/utils/resourceDependencyIds.d.ts +1 -0
  106. package/dist/types/node/durable/bus/MemoryEventBus.d.ts +10 -1
  107. package/dist/types/node/durable/bus/NoopEventBus.d.ts +1 -1
  108. package/dist/types/node/durable/bus/RedisEventBus.d.ts +8 -2
  109. package/dist/types/node/durable/core/CronParser.d.ts +2 -13
  110. package/dist/types/node/durable/core/DurableResource.d.ts +15 -32
  111. package/dist/types/node/durable/core/DurableService.d.ts +1 -0
  112. package/dist/types/node/durable/core/DurableWorker.d.ts +4 -2
  113. package/dist/types/node/durable/core/createRunnerDurableRuntime.d.ts +2 -0
  114. package/dist/types/node/durable/core/interfaces/bus.d.ts +1 -1
  115. package/dist/types/node/durable/core/interfaces/resource.d.ts +61 -0
  116. package/dist/types/node/durable/core/interfaces/service.d.ts +2 -0
  117. package/dist/types/node/durable/core/interfaces/store.d.ts +5 -0
  118. package/dist/types/node/durable/core/managers/ExecutionManager.d.ts +1 -0
  119. package/dist/types/node/durable/core/managers/PollingManager.d.ts +3 -1
  120. package/dist/types/node/durable/core/resource.d.ts +6 -5
  121. package/dist/types/node/durable/core/utils.d.ts +11 -1
  122. package/dist/types/node/durable/queue/RabbitMQQueue.d.ts +4 -0
  123. package/dist/types/node/durable/resources/memoryDurableResource.d.ts +7 -5
  124. package/dist/types/node/durable/resources/redisDurableResource.d.ts +7 -5
  125. package/dist/types/node/durable/store/MemoryStore.d.ts +2 -0
  126. package/dist/types/node/durable/store/RedisStore.d.ts +1 -0
  127. package/dist/types/node/durable/tags/durableWorkflow.tag.d.ts +6 -1
  128. package/dist/types/node/exposure/allowList.d.ts +2 -1
  129. package/dist/types/node/exposure/handlers/contextWrapper.d.ts +6 -2
  130. package/dist/types/node/exposure/handlers/errorHandlers.d.ts +1 -0
  131. package/dist/types/node/exposure/handlers/eventHandler.d.ts +1 -0
  132. package/dist/types/node/exposure/handlers/taskHandler.d.ts +3 -1
  133. package/dist/types/node/exposure/logging.d.ts +1 -0
  134. package/dist/types/node/exposure/requestContext.d.ts +1 -1
  135. package/dist/types/node/exposure/requestHandlers.d.ts +3 -1
  136. package/dist/types/node/exposure/requestIdentity.d.ts +3 -0
  137. package/dist/types/node/exposure/resource.d.ts +7 -7
  138. package/dist/types/node/exposure/resourceTypes.d.ts +6 -0
  139. package/dist/types/node/exposure/serverLifecycle.d.ts +2 -0
  140. package/dist/types/node/http/http-mixed-client.factory.resource.d.ts +1 -1
  141. package/dist/types/node/http/http-smart-client.factory.resource.d.ts +1 -1
  142. package/dist/types/node/node.d.ts +1 -184
  143. package/dist/types/node/tunnel/allowlist.d.ts +10 -1
  144. package/dist/types/platform/adapters/browser.d.ts +1 -1
  145. package/dist/types/platform/adapters/edge.d.ts +17 -0
  146. package/dist/types/platform/adapters/node-als.d.ts +1 -1
  147. package/dist/types/platform/adapters/node.d.ts +1 -1
  148. package/dist/types/platform/adapters/universal-generic.d.ts +5 -2
  149. package/dist/types/platform/adapters/universal.d.ts +1 -1
  150. package/dist/types/platform/index.d.ts +3 -2
  151. package/dist/types/platform/types.d.ts +8 -2
  152. package/dist/types/public.d.ts +376 -20
  153. package/dist/types/run.d.ts +17 -1
  154. package/dist/types/serializer/Serializer.d.ts +1 -41
  155. package/dist/types/serializer/errors.d.ts +8 -0
  156. package/dist/types/serializer/marker-key-escapes.d.ts +2 -0
  157. package/dist/types/serializer/serialize-utils.d.ts +1 -1
  158. package/dist/types/serializer/type-registry.d.ts +0 -1
  159. package/dist/types/serializer/types.d.ts +7 -6
  160. package/dist/types/testing.d.ts +1 -0
  161. package/dist/types/tools/LockableMap.d.ts +20 -0
  162. package/dist/types/tools/getAllThrows.d.ts +13 -0
  163. package/dist/types/tools/throws.d.ts +1 -1
  164. package/dist/types/types/error.d.ts +25 -2
  165. package/dist/types/types/event.d.ts +35 -0
  166. package/dist/types/types/hook.d.ts +8 -0
  167. package/dist/types/types/resource.d.ts +13 -1
  168. package/dist/types/types/resourceMiddleware.d.ts +8 -0
  169. package/dist/types/types/runner.d.ts +56 -0
  170. package/dist/types/types/storeTypes.d.ts +5 -1
  171. package/dist/types/types/symbols.d.ts +1 -1
  172. package/dist/types/types/tag.d.ts +1 -0
  173. package/dist/types/types/tagged.d.ts +2 -2
  174. package/dist/types/types/task.d.ts +3 -3
  175. package/dist/types/types/taskMiddleware.d.ts +8 -0
  176. package/dist/types/types/utilities.d.ts +25 -3
  177. package/dist/ui/assets/index-B4lZaXFJ.js +141 -0
  178. package/dist/ui/assets/index-Y_9aLumt.css +1 -0
  179. package/dist/ui/index.html +2 -3
  180. package/dist/universal/index.cjs +6159 -3049
  181. package/dist/universal/index.cjs.map +1 -1
  182. package/dist/universal/index.mjs +6157 -3046
  183. package/dist/universal/index.mjs.map +1 -1
  184. package/package.json +18 -15
  185. package/readmes/AI.md +181 -45
  186. package/dist/ui/assets/index-2cb8f39f.js +0 -141
  187. package/dist/ui/assets/index-b1f988bf.css +0 -1
  188. /package/dist/types/{tunnels → tools}/buildUniversalManifest.d.ts +0 -0
  189. /package/dist/types/{processHooks.d.ts → tools/processShutdownHooks.d.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bluelibs/runner",
3
- "version": "5.3.0",
3
+ "version": "5.5.0",
4
4
  "description": "BlueLibs Runner",
5
5
  "sideEffects": false,
6
6
  "main": "dist/universal/index.cjs",
@@ -96,6 +96,9 @@
96
96
  "typecheck": "tsc -p config/ts/tsconfig.json --noEmit",
97
97
  "lint": "eslint --config config/eslint/eslint.config.mjs ./src",
98
98
  "lint:fix": "eslint --config config/eslint/eslint.config.mjs ./src --fix",
99
+ "audit": "npm audit --audit-level=moderate",
100
+ "audit:prod": "npm audit --omit=dev --audit-level=high",
101
+ "audit:full": "npm audit --audit-level=moderate",
99
102
  "qa": "npm run coverage:ai && npm run lint:fix && npm run typecheck && npm run build",
100
103
  "prepublishOnly": "npm run clean && npm run build && npm run build:dashboard",
101
104
  "postbuild": "npm run guide:compose",
@@ -106,23 +109,23 @@
106
109
  "guide:compose": "node ./scripts/compose-guide.mjs"
107
110
  },
108
111
  "devDependencies": {
112
+ "@eslint/js": "^10.0.1",
109
113
  "@types/amqplib": "^0.10.7",
110
114
  "@types/benchmark": "^2.1.5",
111
115
  "@types/busboy": "^1.5.4",
112
116
  "@types/express": "^5.0.3",
113
- "@types/graphql": "^0.11.3",
114
117
  "@types/jest": "^27.0.0",
115
- "@types/node": "^20.0.0",
116
- "@typescript-eslint/eslint-plugin": "8.39.0",
117
- "@typescript-eslint/parser": "8.39.0",
118
+ "@types/node": "^25.2.3",
119
+ "@typescript-eslint/eslint-plugin": "8.56.0",
120
+ "@typescript-eslint/parser": "8.56.0",
118
121
  "benchmark": "^2.1.4",
119
122
  "busboy": "^1.6.0",
120
- "eslint": "^9.39.2",
123
+ "eslint": "^10.0.0",
121
124
  "eslint-config-prettier": "^10.1.8",
122
- "eslint-plugin-jest": "^29.11.2",
123
- "eslint-plugin-prettier": "^5.5.4",
124
- "eslint-plugin-unused-imports": "^4.3.0",
125
- "express": "^5.1.0",
125
+ "eslint-plugin-jest": "^29.15.0",
126
+ "eslint-plugin-prettier": "^5.5.5",
127
+ "eslint-plugin-unused-imports": "^4.4.1",
128
+ "express": "^5.2.1",
126
129
  "globals": "^16.5.0",
127
130
  "jest": "^29.0.0",
128
131
  "jest-environment-jsdom": "^30.1.2",
@@ -133,11 +136,11 @@
133
136
  "tailwindcss": "^4.1.12",
134
137
  "ts-jest": "^29.0.0",
135
138
  "tsup": "^8.0.0",
136
- "typedoc": "^0.26.7",
137
- "typedoc-material-theme": "^1.1.0",
139
+ "typedoc": "^0.28.17",
140
+ "typedoc-material-theme": "^1.4.1",
138
141
  "typedoc-plugin-pages": "^1.1.0",
139
- "typescript": "^5.6.2",
140
- "typescript-eslint": "^8.51.0",
142
+ "typescript": "^5.9.3",
143
+ "typescript-eslint": "^8.56.0",
141
144
  "zod": "^4.0.17"
142
145
  },
143
146
  "typings": "dist/types/index.d.ts",
@@ -156,12 +159,12 @@
156
159
  ],
157
160
  "license": "MIT",
158
161
  "dependencies": {
162
+ "cron-parser": "^5.4.0",
159
163
  "lru-cache": "^11.1.0"
160
164
  },
161
165
  "optionalDependencies": {
162
166
  "amqplib": "^0.10.9",
163
167
  "busboy": "^1.6.0",
164
- "cron-parser": "^5.4.0",
165
168
  "ioredis": "^5.7.0"
166
169
  },
167
170
  "prettier": {
package/readmes/AI.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  For the landing overview, see [README.md](../README.md). For the complete guide, see [FULL_GUIDE.md](./FULL_GUIDE.md).
6
6
 
7
- **Durable Workflows (Node-only):** For persistence and crash recovery, see `DURABLE_WORKFLOWS.md`. Includes `ctx.switch()` (replay-safe branching) and `durable.describe()` (DI-accurate flow shape export) — see `DURABLE_WORKFLOWS_AI.md` for quick reference.
7
+ **Durable Workflows (Node-only):** For persistence and crash recovery, see `DURABLE_WORKFLOWS.md`. Includes `ctx.switch()` (replay-safe branching), `durable.describe()` (DI-accurate flow shape export), and `durableWorkflowTag.defaults` (default input for `describe(task)` when omitted) — see `DURABLE_WORKFLOWS_AI.md` for quick reference.
8
8
 
9
9
  ## Serializer Safety
10
10
 
@@ -12,13 +12,21 @@ When deserializing untrusted payloads, configure the serializer to restrict
12
12
  symbol handling so payloads cannot grow the global Symbol registry.
13
13
 
14
14
  ```ts
15
- import { Serializer, SymbolPolicy } from "@bluelibs/runner";
15
+ import { Serializer } from "@bluelibs/runner";
16
16
 
17
17
  const serializer = new Serializer({
18
- symbolPolicy: SymbolPolicy.WellKnownOnly,
18
+ symbolPolicy: "well-known-only",
19
19
  });
20
20
  ```
21
21
 
22
+ `Serializer` also supports hardening knobs:
23
+
24
+ - `allowedTypes` to allow-list runtime type ids during deserialize
25
+ - `maxDepth` to cap recursion depth
26
+ - `maxRegExpPatternLength` and `allowUnsafeRegExp` to guard RegExp payloads
27
+
28
+ Default behavior note: `symbolPolicy` defaults to `"allow-all"`; use `"well-known-only"` (or stricter) for untrusted inputs.
29
+
22
30
  ## Resources
23
31
 
24
32
  ```ts
@@ -29,6 +37,8 @@ import { nodeExposure } from "@bluelibs/runner/node";
29
37
  const server = r
30
38
  .resource<{ port: number }>("app.server")
31
39
  .context(() => ({ app: express() }))
40
+ // .schema is an alias for .configSchema / .inputSchema / .payloadSchema etc.
41
+ .schema(z.object({ port: z.number().default(3000) }))
32
42
  .init(async ({ port }, _deps, ctx) => {
33
43
  ctx.app.use(express.json());
34
44
  const listener = ctx.app.listen(port);
@@ -40,8 +50,8 @@ const server = r
40
50
  const createUser = r
41
51
  .task("app.tasks.createUser")
42
52
  .dependencies({ logger: globals.resources.logger })
43
- .inputSchema<{ name: string }>({ parse: (value) => value })
44
- .resultSchema<{ id: string; name: string }>({ parse: (value) => value })
53
+ .schema<{ name: string }>({ parse: (value) => value }) // parses the input
54
+ .resultSchema<{ id: string; name: string }>({ parse: (value) => value }) // parses the response
45
55
  .run(async (input, { logger }) => {
46
56
  await logger.info(`Creating user ${input.name}`);
47
57
  return { id: "user-1", name: input.name };
@@ -75,9 +85,16 @@ await runtime.runTask(createUser, { name: "Ada" });
75
85
  // runtime.dispose() when you are done.
76
86
  ```
77
87
 
78
- - `r.*.with(config)` produces a configured copy of the definition.
88
+ - `.with(config)` exists on configurable built definitions (for example resources, task/resource middleware, and tags). Fluent builders use chained methods plus `.build()`.
89
+ - Entry generics are supported for convenience: `r.resource<Config>(id)` seeds config typing even before `.schema()`/`.configSchema()`/`.init(...)`; config-only resources can omit `.init()` when they only orchestrate `.register((config) => ...)`.
79
90
  - `r.*.fork(newId, { register: "keep" | "drop" | "deep", reId })` creates a new resource with a different id but the same definition. Use `register: "drop"` to avoid re-registering nested items, or `register: "deep"` to deep-fork **registered resources** with new ids via `reId` (other registerables are not kept; resource dependencies pointing to deep-forked resources are remapped to those forks). Export forked resources to use as dependencies.
80
- - `run(root)` wires dependencies, runs `init`, emits lifecycle events, and returns helpers such as `runTask`, `getResourceValue`, `getResourceConfig`, and `dispose`.
91
+ - Resource boundaries can be narrowed with `.exports([...])` (on object definitions or fluent builders) to enforce encapsulation:
92
+ - **Omit `.exports()`**: Everything remains public (backward compatible).
93
+ - **`.exports([])`**: Nothing is public. Everything else in that resource subtree is private across boundaries (tasks, hooks, middleware, etc.).
94
+ - **Scoping**: Provides safer refactors as internal items cannot be referenced from outside. It also scopes `.everywhere()` middleware: non-exported middleware only applies inside its own registration subtree.
95
+ - **Transitive Visibility**: If a resource exports a child resource, that child's own exported surface is visible transitively, but each intermediate boundary must allow the path (e.g., `A -> B -> C` is blocked if `B.exports([])`).
96
+ - **Validation**: Visibility is validated at `run(...)` init time. IDs remain globally unique even for private items.
97
+ - `run(root)` wires dependencies, runs `init`, emits lifecycle events, and returns a runtime object (`IRuntime`) with helpers such as `runTask`, `emitEvent`, `getResourceValue`, `getLazyResourceValue`, `getResourceConfig`, `getRootId`, `getRootConfig`, `getRootValue`, and `dispose`.
81
98
  - Enable verbose logging with `run(root, { debug: "verbose" })`.
82
99
 
83
100
  ### Resource Forking
@@ -97,7 +114,7 @@ import { r } from "@bluelibs/runner";
97
114
  // Assuming: userService, loggingMiddleware, and tracingMiddleware are defined elsewhere
98
115
  const sendEmail = r
99
116
  .task("app.tasks.sendEmail")
100
- .inputSchema<{ to: string; subject: string; body: string }>({
117
+ .schema<{ to: string; subject: string; body: string }>({
101
118
  parse: (value) => value,
102
119
  })
103
120
  .dependencies({ emailer: userService })
@@ -114,8 +131,12 @@ const sendEmail = r
114
131
  - `.dependencies()` accepts a literal map or function `(config) => deps`; appends (shallow-merge) by default
115
132
  - `.middleware()` appends by default
116
133
  - `.tags()` appends by default
134
+ - `.schema()` is a unified alias for `inputSchema`, `configSchema`, `payloadSchema`, and `dataSchema` (errors).
135
+ - For tasks, `.schema()` maps to `inputSchema` only; keep output validation explicit with `.resultSchema()`.
136
+ - Entry generic is supported for convenience: `r.task<Input>(id)` seeds input typing. Later explicit typing in `.schema()`/`.inputSchema()`/`.run((input: ...))` still has priority.
117
137
  - Pass `{ override: true }` to any of these methods to replace instead of append
118
138
  - Provide result validation with `.resultSchema()` when the function returns structured data
139
+ - All builders support `.meta({ ... })` for documentation and tooling metadata.
119
140
 
120
141
  ## Events and Hooks
121
142
 
@@ -157,7 +178,7 @@ const sendWelcomeEmail = r
157
178
  .build();
158
179
  ```
159
180
 
160
- - Use `.on(onAnyOf(...))` to listen to several events while keeping inference.
181
+ - Use `.on(onAnyOf(...))` to listen to several events while keeping inference. Import `onAnyOf` from `@bluelibs/runner/defs` (or `@bluelibs/runner` if you already re-export it in your local facade).
161
182
  - Hooks can set `.order(priority)`; lower numbers run first. Call `event.stopPropagation()` inside `run` to cancel downstream hooks.
162
183
  - Wildcard hooks use `.on("*")` and receive every emission except events tagged with `globals.tags.excludeFromGlobalHooks`.
163
184
  - Use `.parallel(true)` on event definitions to enable batched parallel execution:
@@ -166,6 +187,30 @@ const sendWelcomeEmail = r
166
187
  - All listeners in a failing batch run to completion; if multiple fail, an `AggregateError` with all errors is thrown
167
188
  - Propagation is checked between batches only (not mid-batch since parallel listeners can't be stopped mid-flight)
168
189
  - If any listener throws, subsequent batches will not run
190
+ - Event emitters (dependency-injected or `runtime.emitEvent`) support options:
191
+ - `failureMode: "fail-fast" | "aggregate"`
192
+ - `throwOnError` (default `true`)
193
+ - `report` (when `true`, returns `IEventEmitReport`)
194
+ - `report: true` is useful when you want to aggregate hook failures without throwing immediately:
195
+
196
+ ```ts
197
+ import { r } from "@bluelibs/runner";
198
+
199
+ const notify = r.event("app.events.notify").build();
200
+
201
+ const task = r
202
+ .task("app.tasks.notify")
203
+ .dependencies({ notify })
204
+ .run(async (_input, { notify }) => {
205
+ const report = await notify(undefined, {
206
+ report: true,
207
+ throwOnError: false,
208
+ failureMode: "aggregate",
209
+ });
210
+ return report.failedListeners;
211
+ })
212
+ .build();
213
+ ```
169
214
 
170
215
  ## Middleware
171
216
 
@@ -205,6 +250,7 @@ const cacheResources = r.middleware
205
250
  Attach middleware using `.middleware([auditTasks])` on the definition that owns it, and register the middleware alongside the target resource or task at the root.
206
251
 
207
252
  - Contract middleware: middleware can declare `Config`, `Input`, `Output` generics; tasks using it must conform (contracts intersect across `.middleware([...])` and `.tags([...])`). Collisions surface as `InputContractViolationError` / `OutputContractViolationError` in TypeScript; if you add `.inputSchema()`, ensure the schema's inferred type includes the contract shape.
253
+ - Entry generic convenience is available for middleware too: `r.middleware.task<Input>(id)` seeds task input contract typing and `r.middleware.resource<Config>(id)` seeds middleware config typing. The explicit multi-generic form (`<Config, Input, Output>`) remains available.
208
254
 
209
255
  ```ts
210
256
  type AuthConfig = { requiredRole: string };
@@ -279,7 +325,7 @@ const getHealth = r
279
325
 
280
326
  Retrieve tagged items by using `globals.resources.store` inside a hook or resource and calling `store.getTasksWithTag(tag)`.
281
327
 
282
- **Node durable workflows must be tagged** with `durableWorkflowTag` from `@bluelibs/runner/node` to be discoverable via `durable.getWorkflows()` at runtime. This tag is required, not optional. Workflow execution is explicit via the durable API (`durable.start(...)` / `durable.startAndWait(...)`). The tag is discovery metadata only; `startAndWait(...)` provides the unified result envelope `{ durable: { executionId }, data }`.
328
+ **Node durable workflows must be tagged** with `durableWorkflowTag` from `@bluelibs/runner/node` to be discoverable via `durable.getWorkflows()` at runtime. This tag is required, not optional. Workflow execution is explicit via the durable API (`durable.start(...)` / `durable.startAndWait(...)`) and these are the current, non-deprecated methods. The legacy aliases `durable.startExecution(...)`, `durable.execute(...)`, and `durable.executeStrict(...)` remain available as deprecated compatibility methods (`startExecution` -> `start`, `execute` -> `startAndWait(...).data`, `executeStrict` -> `startAndWait`). The tag is discovery metadata only; `startAndWait(...)` provides the unified result envelope `{ durable: { executionId }, data }`.
283
329
 
284
330
  - Contract tags (a "smart tag"): define type contracts for task input/output (or resource config/value) via `r.tag<TConfig, TInputContract, TOutputContract>(id)`. They don't change runtime behavior; they shape the inferred types and compose with contract middleware.
285
331
  - Smart tags: built-in tags like `globals.tags.system`, `globals.tags.debug`, and `globals.tags.excludeFromGlobalHooks` change framework behavior; use them for per-component debug or to opt out of global hooks.
@@ -296,11 +342,50 @@ const getUser = r
296
342
  .build();
297
343
  ```
298
344
 
345
+ ## Cron Scheduling
346
+
347
+ Use `globals.tags.cron` to schedule tasks with cron expressions. The scheduler lives in `globals.resources.cron` and is registered by default, so tagged tasks begin scheduling automatically at runtime startup.
348
+
349
+ ```ts
350
+ import { r, globals } from "@bluelibs/runner";
351
+
352
+ const cleanupTask = r
353
+ .task("app.tasks.cleanup")
354
+ .tags([
355
+ globals.tags.cron.with({
356
+ expression: "*/5 * * * *",
357
+ immediate: true,
358
+ onError: "continue",
359
+ }),
360
+ ])
361
+ .run(async () => {
362
+ // cleanup logic
363
+ })
364
+ .build();
365
+
366
+ const app = r.resource("app").register([cleanupTask]).build();
367
+ ```
368
+
369
+ `globals.tags.cron.with({...})` options:
370
+
371
+ - `expression` (required): 5-field cron expression
372
+ - `input`: static input passed to the task on each run
373
+ - `timezone`: timezone for scheduling
374
+ - `immediate`: run once immediately at startup, then continue schedule
375
+ - `enabled`: disable schedule when `false`
376
+ - `onError`: `"continue"` (default) or `"stop"`
377
+ - `silent`: suppress all cron log output for this task when `true` (default `false`)
378
+
379
+ Notes:
380
+
381
+ - One cron tag per task is supported. If you need multiple schedules, use task forking and tag each fork.
382
+ - Cron startup logs are emitted through `globals.resources.logger`.
383
+
299
384
  ## Async Context
300
385
 
301
- Async Context provides per-request/thread-local state via the platform's `AsyncLocalStorage` (Node). Use the fluent builder under `r.asyncContext` or the classic `asyncContext({ ... })` export.
386
+ Async Context provides per-request/thread-local state via the platform's `AsyncLocalStorage` (Node, and Deno when `AsyncLocalStorage` is available). Use the fluent builder under `r.asyncContext` or the classic `asyncContext({ ... })` export.
302
387
 
303
- > **Platform Note**: `AsyncLocalStorage` is Node.js-only. Async Context is unavailable in browsers/edge runtimes.
388
+ > **Platform Note**: Async Context requires `AsyncLocalStorage`. It is available in Node.js and Deno (when exposed), and unavailable in browser runtimes.
304
389
 
305
390
  ```ts
306
391
  import { r } from "@bluelibs/runner";
@@ -339,9 +424,40 @@ const whoAmI = r
339
424
  const app = r.resource("app").register([requestContext, whoAmI]).build();
340
425
  ```
341
426
 
427
+ ## Queue
428
+
429
+ `Queue` is a cooperative FIFO task queue. Tasks run one-after-another, with dead-lock detection and graceful disposal.
430
+
431
+ The global resource `globals.resources.queue` provides a named queue factory — each `id` gets its own isolated `Queue` instance.
432
+
433
+ **Key methods:**
434
+
435
+ - `queue.run(id, task)` — schedule `task` (receives an `AbortSignal`) on the queue identified by `id`; creates the queue lazily.
436
+
437
+ **Event lifecycle:** `enqueue` → `start` → `finish` | `error`. On disposal: `disposed`. On cancel: `cancel`.
438
+
439
+ ```ts
440
+ import { r, run, globals } from "@bluelibs/runner";
441
+
442
+ const processOrder = r
443
+ .task("app.tasks.processOrder")
444
+ .dependencies({ queue: globals.resources.queue })
445
+ .run(async (input: { orderId: string }, { queue }) => {
446
+ // Tasks with the same orderId run sequentially
447
+ return queue.run(input.orderId, async (signal) => {
448
+ if (signal.aborted) return;
449
+ // ... process order
450
+ return { processed: true };
451
+ });
452
+ })
453
+ .build();
454
+ ```
455
+
456
+ For advanced usage, import `Queue` directly and use `on(type, handler)` / `once(type, handler)` to observe lifecycle events. Call `dispose({ cancel: true })` to abort in-flight work via the `AbortSignal`.
457
+
342
458
  ## Errors
343
459
 
344
- Define typed, namespaced errors with a fluent builder. Built helpers expose `throw` and `is`:
460
+ Define typed, namespaced errors with a fluent builder. Built helpers expose `new`, `create` (alias), `throw`, and `is`:
345
461
 
346
462
  ```ts
347
463
  import { r } from "@bluelibs/runner";
@@ -350,21 +466,29 @@ import { r } from "@bluelibs/runner";
350
466
  const AppError = r
351
467
  .error<{ code: number; message: string }>("app.errors.AppError")
352
468
  .httpCode(400)
353
- .dataSchema({ parse: (value) => value })
469
+ // .schema is an alias for .dataSchema in errors
470
+ .schema(z.object({ code: z.number(), message: z.string() }))
354
471
  .format((d) => `[${d.code}] ${d.message}`)
355
472
  .remediation("Check the request payload and retry with valid data.")
473
+ .tags([criticalTag])
356
474
  .build();
357
475
 
358
476
  try {
359
477
  AppError.throw({ code: 400, message: "Oops" });
360
478
  } catch (err) {
361
- if (AppError.is(err)) {
479
+ if (AppError.is(err, { code: 400 })) {
480
+ // Narrowed to RunnerError<TData>
362
481
  // err.message -> "[400] Oops\n\nRemediation: Check the request payload and retry with valid data."
363
482
  // err.httpCode -> 400
364
483
  // err.remediation -> "Check the request payload and retry with valid data."
365
484
  // AppError.httpCode -> 400
366
485
  }
367
486
  }
487
+
488
+ const error = AppError.new({ code: 400, message: "Oops" });
489
+ throw error;
490
+
491
+ // .create() is deprecated; use .new() or .throw() for better DX.
368
492
  ```
369
493
 
370
494
  - Recommended ids: `{domain}.errors.{PascalCaseName}` (for example: `app.errors.InvalidCredentials`).
@@ -372,10 +496,17 @@ try {
372
496
  - `.httpCode(number)` sets an HTTP status for the error helper (must be an integer in `100..599`). The helper exposes `helper.httpCode`, and thrown typed errors expose `error.httpCode`.
373
497
  - `.remediation(stringOrFn)` attaches fix-it advice. Accepts a static string or `(data) => string`. When present, `error.message` and `error.toString()` include `\n\nRemediation: <advice>`. The raw advice is also available via `error.remediation`.
374
498
  - `message` is not required in the data unless your custom formatter expects it.
499
+ - `helper.new(data)` constructs and returns a typed `RunnerError` without throwing (useful for `throw helper.new(data)` semantics).
500
+ - `helper.is(err, partialData?)` accepts an optional partial data filter and performs shallow strict matching (`===`) on each provided key.
501
+ - `helper.tags` and `helper.meta` expose documentation metadata for introspection.
502
+ - For circular dependency detection, use `circularDependencyError`. Backward-compatible aliases `circularDependenciesError` and `dependencyCycleError` remain exported as deprecated names.
375
503
  - Declare a task/resource error contract with `.throws([AppError])` (or ids). This is declarative only and does not imply DI.
504
+ - `.throws()` is also available on hooks, task middleware, and resource middleware builders — same semantics.
505
+ - `.throws([...])` accepts error helpers or string ids, normalizes to ids, and deduplicates repeated declarations.
506
+ - `store.getAllThrows(task | resource)` aggregates all declared error ids from a task or resource and its full dependency chain: own throws, local + everywhere middleware throws, resource dependency throws (with their middleware), and — for tasks — hook throws for events the task can emit. Returns a deduplicated `readonly string[]`.
376
507
  - Use `r.error.is(err)` to check if an error is _any_ Runner error (not just a specific one). This type guard narrows to `RunnerError` with `id`, `data`, `httpCode`, and `remediation` properties. Useful in catch blocks or error filters:
377
508
  ```ts
378
- if (r.error.is(err)) {
509
+ if (r.error.is(err, { code: 400 })) {
379
510
  console.error(`Runner error: ${err.id} (${err.httpCode || "N/A"})`);
380
511
  }
381
512
  ```
@@ -401,27 +532,40 @@ try {
401
532
  Override a task/resource/hook/middleware while preserving `id`. Use the helper or the fluent override builder:
402
533
 
403
534
  ```ts
404
- const mockMailer = r
535
+ const mockMailer = r.override(realMailer, async () => new MockMailer());
536
+
537
+ const tracedMailer = r
405
538
  .override(realMailer)
406
- .init(async () => new MockMailer())
539
+ .init(async (config, deps) => {
540
+ const base = await realMailer.init(config, deps);
541
+ return { ...base, trace: true };
542
+ })
407
543
  .build();
408
544
 
409
545
  const app = r
410
546
  .resource("app")
411
547
  .register([realMailer])
412
- .overrides([mockMailer])
548
+ .overrides([mockMailer, tracedMailer])
413
549
  .build();
414
550
  ```
415
551
 
552
+ - `r.override(base, fn)` is a typed shorthand for common behavior swaps:
553
+ - task/hook/task-middleware/resource-middleware: replaces `run`
554
+ - resource: replaces `init`
416
555
  - `r.override(base)` starts from the base definition and applies fluent mutations using the same composition rules as the base builder.
556
+ - `r.override(...)` creates replacement definitions; `.overrides([...])` applies them in a specific container during bootstrap.
557
+ - Registering only the replacement definition is valid; registering both base and replacement in `.register([...])` causes duplicate-id errors.
558
+ - `.overrides([...])` requires the target id to already be present in the graph; if you wanted a second resource instance instead of replacement, use `.fork("new.id")`.
417
559
  - Hook overrides keep the same `.on` target; only behavior/metadata is overridable.
418
560
  - The `override(base, patch)` helper remains for direct, shallow patches.
419
561
 
420
562
  ## Runtime & Lifecycle
421
563
 
422
- - `run(root, options)` wires dependencies, initializes resources, and returns helpers: `runTask`, `emitEvent`, `getResourceValue`, `getResourceConfig`, `store`, `logger`, and `dispose`.
423
- - Run options highlights: `debug` (normal/verbose or custom config), `logs` (printThreshold/strategy/buffer), `errorBoundary` and `onUnhandledError`, `shutdownHooks`, `dryRun`.
424
- - Task interceptors: inside resource init, call `deps.someTask.intercept(async (next, input) => next(input))` to wrap a single task execution at runtime (runs inside middleware; won't run if middleware short-circuits).
564
+ - `run(root, options)` wires dependencies, initializes resources, and returns the runtime object: `runTask`, `emitEvent`, `getResourceValue`, `getLazyResourceValue`, `getResourceConfig`, `getRootId`, `getRootConfig`, `getRootValue`, `store`, `logger`, and `dispose`. `getLazyResourceValue` is available only when `run(..., { lazy: true })` is enabled.
565
+ - `emitEvent(event, payload, options?)` accepts the same emission options (`failureMode`, `throwOnError`, `report`) as dependency emitters.
566
+ - Run options highlights: `debug` (normal/verbose or custom config), `logs` (printThreshold/strategy/buffer), `errorBoundary` and `onUnhandledError`, `shutdownHooks`, `dryRun`, `lazy`, and `initMode` (`"sequential"` or `"parallel"`; string literal values work without importing enums).
567
+ - `lazy` + `initMode: "parallel"`: startup still parallelizes resources that are actually needed during bootstrap (respecting dependency readiness); only startup-unused resources stay deferred for `getLazyResourceValue(...)`.
568
+ - Task interceptors: inside resource init, call `deps.someTask.intercept(async (next, input) => next(input))` to wrap a single task execution at runtime (runs inside middleware; won't run if middleware short-circuits). Use `deps.someTask.getInterceptingResourceIds()` to inspect which resources registered local interceptors (unique ids, registration order).
425
569
  - Shutdown hooks: install signal listeners to call `dispose` (default in `run`).
426
570
  - Unhandled errors: `onUnhandledError` receives a structured context (kind and source) for telemetry or controlled shutdown.
427
571
 
@@ -473,10 +617,14 @@ const app = r
473
617
 
474
618
  Tunnels let you call Runner tasks/events across a process boundary over a small HTTP surface (Node-only exposure via `nodeExposure`), while preserving task ids, middleware, validation, typed errors, and async context.
475
619
 
620
+ Important boundary: tunnels are for inter-runner/service-to-service communication, not for exposing a public browser-facing API directly.
621
+
476
622
  For "no call-site changes", register a client-mode tunnel resource tagged with `globals.tags.tunnel` plus phantom tasks for the remote ids; the tunnel middleware auto-routes selected tasks/events to an HTTP client. For explicit boundaries, create a client once and call `client.task(id, input)` / `client.event(id, payload)` directly. Full guide: `readmes/TUNNELS.md`.
477
623
 
478
624
  Node client note: prefer `createHttpMixedClient` (it uses the serialized-JSON path via Runner `Serializer` + `fetch` when possible and switches to the streaming-capable Smart path when needed). If a task may return a stream even for plain JSON inputs (ex: downloads), set `forceSmart` on Mixed (or use `createHttpSmartClient` directly).
479
625
 
626
+ Node exposure hardening: use `x-runner-request-id` for request correlation, set `allowAsyncContext: false` on server tunnel resources unless context propagation is required, and enforce rate limiting at the edge/proxy layer.
627
+
480
628
  ## Serialization
481
629
 
482
630
  Runner ships with a serializer that round-trips Dates, RegExp, binary, and custom shapes across Node and web.
@@ -498,18 +646,15 @@ const serializerSetup = r
498
646
  public value: number,
499
647
  public unit: string,
500
648
  ) {}
501
- typeName() {
502
- return "Distance";
503
- }
504
- toJSONValue() {
505
- return { value: this.value, unit: this.unit };
506
- }
507
649
  }
508
650
 
509
- serializer.addType(
510
- "Distance",
511
- (json) => new Distance(json.value, json.unit),
512
- );
651
+ serializer.addType({
652
+ id: "Distance",
653
+ is: (obj): obj is Distance => obj instanceof Distance,
654
+ serialize: (d) => ({ value: d.value, unit: d.unit }),
655
+ deserialize: (json) => new Distance(json.value, json.unit),
656
+ strategy: "value",
657
+ });
513
658
  })
514
659
  .build();
515
660
  ```
@@ -544,13 +689,15 @@ test("sends welcome email", async () => {
544
689
  - `globals.resources.logger` exposes the framework logger; register your own logger resource and override it at the root to capture logs centrally.
545
690
  - Hooks and tasks emit metadata through `globals.resources.store`. Query it for dashboards or editor plugins.
546
691
  - Use middleware for tracing (`r.middleware.task("...").run(...)`) to wrap every task call.
692
+ - Global infra resources are split into dedicated modules under `src/globals/resources/*.resource.ts` (for example `store.resource.ts`, `logger.resource.ts`, `eventManager.resource.ts`), while public consumption remains `globals.resources.*`.
547
693
  - `Semaphore` and `Queue` publish local lifecycle events through isolated `EventManager` instances (`on/once`). These are separate from the global EventManager used for business-level application events. Event names: semaphore → `queued/acquired/released/timeout/aborted/disposed`; queue → `enqueue/start/finish/error/cancel/disposed`.
548
694
 
549
695
  ## Metadata & Namespacing
550
696
 
551
697
  - Meta: `.meta({ title, description })` on tasks/resources/events/middleware for human-friendly docs and tooling; extend meta types via module augmentation when needed.
552
698
  - Namespacing: keep ids consistent with `domain.resources.name`, `domain.tasks.name`, `domain.events.name`, `domain.hooks.on-name`, `domain.middleware.{task|resource}.name`, `domain.errors.ErrorName`, and `domain.ctx.name`.
553
- - Runtime validation: `inputSchema`, `resultSchema`, `payloadSchema`, `configSchema` share the same `parse(input)` contract; config validation happens on `.with()`, task/event validation happens on call/emit.
699
+ - File Structure: While not strictly enforced, prefer co-locating definitions by domain in a feature-driven folder structure (e.g., `src/domains/users/tasks/createUser.task.ts`) and naming files after the item type (`*.task.ts`, `*.resource.ts`, `*.event.ts`) for easier navigation and AI context ingestion.
700
+ - Runtime validation: `inputSchema`, `resultSchema`, `payloadSchema`, `configSchema` share the same `parse(input)` contract; config validation happens on `.with()`, task/event validation happens on call/emit. Use `.schema()` as a unified alias (input/payload/schema/data) for simplicity.
554
701
 
555
702
  ## Advanced Patterns
556
703
 
@@ -558,15 +705,4 @@ test("sends welcome email", async () => {
558
705
  - **Conditional registration:** `.register((config) => (config.enableFeature ? [featureResource] : []))`.
559
706
  - **Async coordination:** `Semaphore` (O(1) linked queue for heavy contention) and `Queue` live in the main package. Both use isolated EventManagers internally for their lifecycle events, separate from the global EventManager used for business-level application events.
560
707
  - **Event safety:** Runner detects event emission cycles and throws an `EventCycleError` with the offending chain.
561
- - **Internal services:** access `globals.resources.store`, `globals.resources.taskRunner`, and `globals.resources.eventManager` for advanced introspection or custom tooling.
562
-
563
- ## Interop With Classic APIs
564
-
565
- Existing code that uses `resource({ ... })`, `task({ ... })`, or `defineX` keeps working. You can gradually migrate:
566
-
567
- ```ts
568
- import { r, resource as classicResource } from "@bluelibs/runner";
569
-
570
- const classic = classicResource({ id: "legacy", init: async () => "ok" });
571
- const modern = r.resource("modern").register([classic]).build();
572
- ```
708
+ - **Internal services:** `globals.resources.runtime` resolves to the same runtime object returned by `run(...)`. It supports `runTask`, `emitEvent`, `getResourceValue`, `getLazyResourceValue`, `getResourceConfig`, `getRootId`, `getRootConfig`, `getRootValue`, and `dispose`. Bootstrap note: when injected inside a resource `init()`, only that resource's dependencies are guaranteed initialized; unrelated resources may still be pending.