@bluelibs/runner 5.4.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 (170) hide show
  1. package/README.md +23 -22
  2. package/dist/browser/index.cjs +5426 -2403
  3. package/dist/browser/index.cjs.map +1 -1
  4. package/dist/browser/index.mjs +5426 -2400
  5. package/dist/browser/index.mjs.map +1 -1
  6. package/dist/edge/index.cjs +5426 -2403
  7. package/dist/edge/index.cjs.map +1 -1
  8. package/dist/edge/index.mjs +5426 -2400
  9. package/dist/edge/index.mjs.map +1 -1
  10. package/dist/node/node.cjs +7208 -6935
  11. package/dist/node/node.cjs.map +1 -1
  12. package/dist/node/node.mjs +7167 -6936
  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/hook/fluent-builder.interface.d.ts +3 -0
  21. package/dist/types/definers/builders/hook/types.d.ts +3 -0
  22. package/dist/types/definers/builders/middleware/index.d.ts +4 -2
  23. package/dist/types/definers/builders/middleware/resource.interface.d.ts +7 -0
  24. package/dist/types/definers/builders/middleware/task.interface.d.ts +7 -0
  25. package/dist/types/definers/builders/middleware/types.d.ts +22 -3
  26. package/dist/types/definers/builders/middleware/utils.d.ts +2 -0
  27. package/dist/types/definers/builders/override/hook.d.ts +3 -0
  28. package/dist/types/definers/builders/override/index.d.ts +5 -0
  29. package/dist/types/definers/builders/resource/fluent-builder.interface.d.ts +15 -0
  30. package/dist/types/definers/builders/resource/index.d.ts +0 -1
  31. package/dist/types/definers/builders/resource/types.d.ts +1 -0
  32. package/dist/types/definers/builders/tag/fluent-builder.interface.d.ts +4 -0
  33. package/dist/types/definers/builders/tag/utils.d.ts +1 -0
  34. package/dist/types/definers/builders/task/fluent-builder.interface.d.ts +4 -0
  35. package/dist/types/definers/builders/task/index.d.ts +2 -2
  36. package/dist/types/definers/builders/utils.d.ts +6 -1
  37. package/dist/types/definers/defineError.d.ts +12 -2
  38. package/dist/types/definers/resourceFork.d.ts +3 -1
  39. package/dist/types/definers/tools.d.ts +12 -12
  40. package/dist/types/errors/domain-error-ids.d.ts +44 -0
  41. package/dist/types/errors/domain-runtime.errors.d.ts +136 -0
  42. package/dist/types/errors/foundation.errors.d.ts +125 -0
  43. package/dist/types/errors/generic.errors.d.ts +1 -0
  44. package/dist/types/errors/model-runtime.errors.d.ts +20 -0
  45. package/dist/types/errors.d.ts +5 -73
  46. package/dist/types/globals/cron/cron-parser.d.ts +4 -0
  47. package/dist/types/globals/cron/cron.errors.d.ts +8 -0
  48. package/dist/types/globals/cron/cron.resource.d.ts +12 -0
  49. package/dist/types/globals/cron/cron.tag.d.ts +2 -0
  50. package/dist/types/globals/cron/types.d.ts +25 -0
  51. package/dist/types/globals/globalMiddleware.d.ts +87 -13
  52. package/dist/types/globals/globalResources.d.ts +110 -24
  53. package/dist/types/globals/globalTags.d.ts +10 -2
  54. package/dist/types/globals/middleware/cache.middleware.d.ts +173 -13
  55. package/dist/types/globals/middleware/circuitBreaker.middleware.d.ts +5 -1
  56. package/dist/types/globals/middleware/concurrency.middleware.d.ts +3 -0
  57. package/dist/types/globals/middleware/fallback.middleware.d.ts +8 -1
  58. package/dist/types/globals/middleware/rateLimit.middleware.d.ts +4 -1
  59. package/dist/types/globals/middleware/retry.middleware.d.ts +5 -0
  60. package/dist/types/globals/middleware/temporal.middleware.d.ts +19 -19
  61. package/dist/types/globals/middleware/timeout.middleware.d.ts +4 -1
  62. package/dist/types/globals/resources/eventManager.resource.d.ts +2 -0
  63. package/dist/types/globals/resources/logger.resource.d.ts +2 -0
  64. package/dist/types/globals/resources/middlewareManager.resource.d.ts +2 -0
  65. package/dist/types/globals/resources/queue.resource.d.ts +2 -0
  66. package/dist/types/globals/resources/runtime.resource.d.ts +2 -0
  67. package/dist/types/globals/resources/serializer.resource.d.ts +2 -0
  68. package/dist/types/globals/resources/store.resource.d.ts +2 -0
  69. package/dist/types/globals/resources/taskRunner.resource.d.ts +2 -0
  70. package/dist/types/globals/resources/tunnel/plan.d.ts +6 -3
  71. package/dist/types/globals/resources/tunnel/tunnel.policy.tag.d.ts +2 -0
  72. package/dist/types/globals/resources/tunnel/types.d.ts +7 -2
  73. package/dist/types/globals/types.d.ts +6 -1
  74. package/dist/types/models/BuiltinsRegistry.d.ts +2 -0
  75. package/dist/types/models/DependencyProcessor.d.ts +26 -5
  76. package/dist/types/models/EventManager.d.ts +9 -9
  77. package/dist/types/models/LogPrinter.d.ts +5 -5
  78. package/dist/types/models/Logger.d.ts +16 -16
  79. package/dist/types/models/MiddlewareManager.d.ts +18 -0
  80. package/dist/types/models/OverrideManager.d.ts +4 -1
  81. package/dist/types/models/Queue.d.ts +2 -0
  82. package/dist/types/models/RunResult.d.ts +290 -26
  83. package/dist/types/models/Store.d.ts +20 -3
  84. package/dist/types/models/StoreRegistry.d.ts +2 -0
  85. package/dist/types/models/StoreValidator.d.ts +2 -0
  86. package/dist/types/models/TaskRunner.d.ts +3 -1
  87. package/dist/types/models/VisibilityTracker.d.ts +75 -0
  88. package/dist/types/models/dependency-processor/DependencyExtractor.d.ts +23 -0
  89. package/dist/types/models/dependency-processor/HookEventBuffer.d.ts +15 -0
  90. package/dist/types/models/dependency-processor/ResourceScheduler.d.ts +10 -0
  91. package/dist/types/models/event/EmissionExecutor.d.ts +5 -4
  92. package/dist/types/models/event/ListenerRegistry.d.ts +2 -0
  93. package/dist/types/models/index.d.ts +1 -3
  94. package/dist/types/models/middleware/InterceptorRegistry.d.ts +13 -4
  95. package/dist/types/models/middleware/ResourceMiddlewareComposer.d.ts +1 -0
  96. package/dist/types/models/middleware/ValidationHelper.d.ts +3 -6
  97. package/dist/types/models/utils/disposeOrder.d.ts +5 -1
  98. package/dist/types/models/utils/resourceDependencyIds.d.ts +1 -0
  99. package/dist/types/node/durable/bus/MemoryEventBus.d.ts +10 -1
  100. package/dist/types/node/durable/bus/NoopEventBus.d.ts +1 -1
  101. package/dist/types/node/durable/bus/RedisEventBus.d.ts +8 -2
  102. package/dist/types/node/durable/core/CronParser.d.ts +2 -13
  103. package/dist/types/node/durable/core/DurableResource.d.ts +14 -32
  104. package/dist/types/node/durable/core/DurableService.d.ts +1 -0
  105. package/dist/types/node/durable/core/DurableWorker.d.ts +4 -2
  106. package/dist/types/node/durable/core/createRunnerDurableRuntime.d.ts +2 -0
  107. package/dist/types/node/durable/core/interfaces/bus.d.ts +1 -1
  108. package/dist/types/node/durable/core/interfaces/resource.d.ts +61 -0
  109. package/dist/types/node/durable/core/interfaces/service.d.ts +2 -0
  110. package/dist/types/node/durable/core/interfaces/store.d.ts +5 -0
  111. package/dist/types/node/durable/core/managers/ExecutionManager.d.ts +1 -0
  112. package/dist/types/node/durable/core/managers/PollingManager.d.ts +3 -1
  113. package/dist/types/node/durable/core/resource.d.ts +4 -3
  114. package/dist/types/node/durable/core/utils.d.ts +11 -1
  115. package/dist/types/node/durable/queue/RabbitMQQueue.d.ts +4 -0
  116. package/dist/types/node/durable/resources/memoryDurableResource.d.ts +5 -3
  117. package/dist/types/node/durable/resources/redisDurableResource.d.ts +5 -3
  118. package/dist/types/node/durable/store/MemoryStore.d.ts +2 -0
  119. package/dist/types/node/durable/store/RedisStore.d.ts +1 -0
  120. package/dist/types/node/exposure/allowList.d.ts +2 -1
  121. package/dist/types/node/exposure/handlers/contextWrapper.d.ts +6 -2
  122. package/dist/types/node/exposure/handlers/errorHandlers.d.ts +1 -0
  123. package/dist/types/node/exposure/handlers/eventHandler.d.ts +1 -0
  124. package/dist/types/node/exposure/handlers/taskHandler.d.ts +3 -1
  125. package/dist/types/node/exposure/logging.d.ts +1 -0
  126. package/dist/types/node/exposure/requestHandlers.d.ts +3 -1
  127. package/dist/types/node/exposure/requestIdentity.d.ts +3 -0
  128. package/dist/types/node/exposure/resource.d.ts +4 -4
  129. package/dist/types/node/exposure/resourceTypes.d.ts +6 -0
  130. package/dist/types/node/exposure/serverLifecycle.d.ts +2 -0
  131. package/dist/types/node/node.d.ts +1 -1
  132. package/dist/types/node/tunnel/allowlist.d.ts +10 -1
  133. package/dist/types/platform/adapters/browser.d.ts +1 -1
  134. package/dist/types/platform/adapters/node-als.d.ts +1 -1
  135. package/dist/types/platform/adapters/node.d.ts +1 -1
  136. package/dist/types/platform/adapters/universal-generic.d.ts +2 -2
  137. package/dist/types/platform/adapters/universal.d.ts +1 -1
  138. package/dist/types/platform/index.d.ts +2 -2
  139. package/dist/types/platform/types.d.ts +1 -1
  140. package/dist/types/public.d.ts +374 -20
  141. package/dist/types/run.d.ts +17 -1
  142. package/dist/types/serializer/Serializer.d.ts +1 -41
  143. package/dist/types/serializer/errors.d.ts +8 -0
  144. package/dist/types/serializer/marker-key-escapes.d.ts +2 -0
  145. package/dist/types/serializer/serialize-utils.d.ts +1 -1
  146. package/dist/types/serializer/type-registry.d.ts +0 -1
  147. package/dist/types/serializer/types.d.ts +7 -6
  148. package/dist/types/testing.d.ts +1 -0
  149. package/dist/types/tools/getAllThrows.d.ts +13 -0
  150. package/dist/types/tools/throws.d.ts +1 -1
  151. package/dist/types/types/error.d.ts +25 -2
  152. package/dist/types/types/event.d.ts +35 -0
  153. package/dist/types/types/hook.d.ts +8 -0
  154. package/dist/types/types/resource.d.ts +13 -1
  155. package/dist/types/types/resourceMiddleware.d.ts +8 -0
  156. package/dist/types/types/runner.d.ts +56 -0
  157. package/dist/types/types/storeTypes.d.ts +5 -1
  158. package/dist/types/types/tag.d.ts +1 -0
  159. package/dist/types/types/tagged.d.ts +2 -2
  160. package/dist/types/types/task.d.ts +2 -2
  161. package/dist/types/types/taskMiddleware.d.ts +8 -0
  162. package/dist/types/types/utilities.d.ts +25 -3
  163. package/dist/ui/assets/{index-Bo7Gi6Vq.js → index-B4lZaXFJ.js} +48 -48
  164. package/dist/ui/index.html +1 -1
  165. package/dist/universal/index.cjs +5426 -2403
  166. package/dist/universal/index.cjs.map +1 -1
  167. package/dist/universal/index.mjs +5426 -2400
  168. package/dist/universal/index.mjs.map +1 -1
  169. package/package.json +18 -14
  170. package/readmes/AI.md +147 -42
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bluelibs/runner",
3
- "version": "5.4.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,22 +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
117
  "@types/jest": "^27.0.0",
114
- "@types/node": "^20.0.0",
115
- "@typescript-eslint/eslint-plugin": "8.39.0",
116
- "@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",
117
121
  "benchmark": "^2.1.4",
118
122
  "busboy": "^1.6.0",
119
- "eslint": "^9.39.2",
123
+ "eslint": "^10.0.0",
120
124
  "eslint-config-prettier": "^10.1.8",
121
- "eslint-plugin-jest": "^29.11.2",
122
- "eslint-plugin-prettier": "^5.5.4",
123
- "eslint-plugin-unused-imports": "^4.3.0",
124
- "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",
125
129
  "globals": "^16.5.0",
126
130
  "jest": "^29.0.0",
127
131
  "jest-environment-jsdom": "^30.1.2",
@@ -132,11 +136,11 @@
132
136
  "tailwindcss": "^4.1.12",
133
137
  "ts-jest": "^29.0.0",
134
138
  "tsup": "^8.0.0",
135
- "typedoc": "^0.26.7",
136
- "typedoc-material-theme": "^1.1.0",
139
+ "typedoc": "^0.28.17",
140
+ "typedoc-material-theme": "^1.4.1",
137
141
  "typedoc-plugin-pages": "^1.1.0",
138
- "typescript": "^5.6.2",
139
- "typescript-eslint": "^8.51.0",
142
+ "typescript": "^5.9.3",
143
+ "typescript-eslint": "^8.56.0",
140
144
  "zod": "^4.0.17"
141
145
  },
142
146
  "typings": "dist/types/index.d.ts",
@@ -155,12 +159,12 @@
155
159
  ],
156
160
  "license": "MIT",
157
161
  "dependencies": {
162
+ "cron-parser": "^5.4.0",
158
163
  "lru-cache": "^11.1.0"
159
164
  },
160
165
  "optionalDependencies": {
161
166
  "amqplib": "^0.10.9",
162
167
  "busboy": "^1.6.0",
163
- "cron-parser": "^5.4.0",
164
168
  "ioredis": "^5.7.0"
165
169
  },
166
170
  "prettier": {
package/readmes/AI.md CHANGED
@@ -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,6 +342,45 @@ 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
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.
@@ -372,7 +457,7 @@ For advanced usage, import `Queue` directly and use `on(type, handler)` / `once(
372
457
 
373
458
  ## Errors
374
459
 
375
- 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`:
376
461
 
377
462
  ```ts
378
463
  import { r } from "@bluelibs/runner";
@@ -381,21 +466,29 @@ import { r } from "@bluelibs/runner";
381
466
  const AppError = r
382
467
  .error<{ code: number; message: string }>("app.errors.AppError")
383
468
  .httpCode(400)
384
- .dataSchema({ parse: (value) => value })
469
+ // .schema is an alias for .dataSchema in errors
470
+ .schema(z.object({ code: z.number(), message: z.string() }))
385
471
  .format((d) => `[${d.code}] ${d.message}`)
386
472
  .remediation("Check the request payload and retry with valid data.")
473
+ .tags([criticalTag])
387
474
  .build();
388
475
 
389
476
  try {
390
477
  AppError.throw({ code: 400, message: "Oops" });
391
478
  } catch (err) {
392
- if (AppError.is(err)) {
479
+ if (AppError.is(err, { code: 400 })) {
480
+ // Narrowed to RunnerError<TData>
393
481
  // err.message -> "[400] Oops\n\nRemediation: Check the request payload and retry with valid data."
394
482
  // err.httpCode -> 400
395
483
  // err.remediation -> "Check the request payload and retry with valid data."
396
484
  // AppError.httpCode -> 400
397
485
  }
398
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.
399
492
  ```
400
493
 
401
494
  - Recommended ids: `{domain}.errors.{PascalCaseName}` (for example: `app.errors.InvalidCredentials`).
@@ -403,10 +496,17 @@ try {
403
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`.
404
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`.
405
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.
406
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[]`.
407
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:
408
508
  ```ts
409
- if (r.error.is(err)) {
509
+ if (r.error.is(err, { code: 400 })) {
410
510
  console.error(`Runner error: ${err.id} (${err.httpCode || "N/A"})`);
411
511
  }
412
512
  ```
@@ -432,27 +532,40 @@ try {
432
532
  Override a task/resource/hook/middleware while preserving `id`. Use the helper or the fluent override builder:
433
533
 
434
534
  ```ts
435
- const mockMailer = r
535
+ const mockMailer = r.override(realMailer, async () => new MockMailer());
536
+
537
+ const tracedMailer = r
436
538
  .override(realMailer)
437
- .init(async () => new MockMailer())
539
+ .init(async (config, deps) => {
540
+ const base = await realMailer.init(config, deps);
541
+ return { ...base, trace: true };
542
+ })
438
543
  .build();
439
544
 
440
545
  const app = r
441
546
  .resource("app")
442
547
  .register([realMailer])
443
- .overrides([mockMailer])
548
+ .overrides([mockMailer, tracedMailer])
444
549
  .build();
445
550
  ```
446
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`
447
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")`.
448
559
  - Hook overrides keep the same `.on` target; only behavior/metadata is overridable.
449
560
  - The `override(base, patch)` helper remains for direct, shallow patches.
450
561
 
451
562
  ## Runtime & Lifecycle
452
563
 
453
- - `run(root, options)` wires dependencies, initializes resources, and returns helpers: `runTask`, `emitEvent`, `getResourceValue`, `getResourceConfig`, `store`, `logger`, and `dispose`.
454
- - Run options highlights: `debug` (normal/verbose or custom config), `logs` (printThreshold/strategy/buffer), `errorBoundary` and `onUnhandledError`, `shutdownHooks`, `dryRun`.
455
- - 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).
456
569
  - Shutdown hooks: install signal listeners to call `dispose` (default in `run`).
457
570
  - Unhandled errors: `onUnhandledError` receives a structured context (kind and source) for telemetry or controlled shutdown.
458
571
 
@@ -504,10 +617,14 @@ const app = r
504
617
 
505
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.
506
619
 
620
+ Important boundary: tunnels are for inter-runner/service-to-service communication, not for exposing a public browser-facing API directly.
621
+
507
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`.
508
623
 
509
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).
510
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
+
511
628
  ## Serialization
512
629
 
513
630
  Runner ships with a serializer that round-trips Dates, RegExp, binary, and custom shapes across Node and web.
@@ -529,18 +646,15 @@ const serializerSetup = r
529
646
  public value: number,
530
647
  public unit: string,
531
648
  ) {}
532
- typeName() {
533
- return "Distance";
534
- }
535
- toJSONValue() {
536
- return { value: this.value, unit: this.unit };
537
- }
538
649
  }
539
650
 
540
- serializer.addType(
541
- "Distance",
542
- (json) => new Distance(json.value, json.unit),
543
- );
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
+ });
544
658
  })
545
659
  .build();
546
660
  ```
@@ -575,13 +689,15 @@ test("sends welcome email", async () => {
575
689
  - `globals.resources.logger` exposes the framework logger; register your own logger resource and override it at the root to capture logs centrally.
576
690
  - Hooks and tasks emit metadata through `globals.resources.store`. Query it for dashboards or editor plugins.
577
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.*`.
578
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`.
579
694
 
580
695
  ## Metadata & Namespacing
581
696
 
582
697
  - Meta: `.meta({ title, description })` on tasks/resources/events/middleware for human-friendly docs and tooling; extend meta types via module augmentation when needed.
583
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`.
584
- - 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.
585
701
 
586
702
  ## Advanced Patterns
587
703
 
@@ -589,15 +705,4 @@ test("sends welcome email", async () => {
589
705
  - **Conditional registration:** `.register((config) => (config.enableFeature ? [featureResource] : []))`.
590
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.
591
707
  - **Event safety:** Runner detects event emission cycles and throws an `EventCycleError` with the offending chain.
592
- - **Internal services:** access `globals.resources.store`, `globals.resources.taskRunner`, and `globals.resources.eventManager` for advanced introspection or custom tooling.
593
-
594
- ## Interop With Classic APIs
595
-
596
- Existing code that uses `resource({ ... })`, `task({ ... })`, or `defineX` keeps working. You can gradually migrate:
597
-
598
- ```ts
599
- import { r, resource as classicResource } from "@bluelibs/runner";
600
-
601
- const classic = classicResource({ id: "legacy", init: async () => "ok" });
602
- const modern = r.resource("modern").register([classic]).build();
603
- ```
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.