@bluelibs/runner 4.6.1 → 4.7.0-alpha

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 (156) hide show
  1. package/AI.md +319 -579
  2. package/README.md +886 -731
  3. package/dist/browser/index.cjs +1438 -251
  4. package/dist/browser/index.cjs.map +1 -1
  5. package/dist/browser/index.mjs +1433 -252
  6. package/dist/browser/index.mjs.map +1 -1
  7. package/dist/context.d.ts +31 -0
  8. package/dist/define.d.ts +9 -0
  9. package/dist/definers/builders/core.d.ts +30 -0
  10. package/dist/definers/builders/event.d.ts +12 -0
  11. package/dist/definers/builders/hook.d.ts +20 -0
  12. package/dist/definers/builders/middleware.d.ts +39 -0
  13. package/dist/definers/builders/resource.d.ts +40 -0
  14. package/dist/definers/builders/tag.d.ts +10 -0
  15. package/dist/definers/builders/task.d.ts +37 -0
  16. package/dist/definers/builders/task.phantom.d.ts +27 -0
  17. package/dist/definers/builders/utils.d.ts +4 -0
  18. package/dist/definers/defineEvent.d.ts +2 -0
  19. package/dist/definers/defineHook.d.ts +6 -0
  20. package/dist/definers/defineOverride.d.ts +17 -0
  21. package/dist/definers/defineResource.d.ts +2 -0
  22. package/dist/definers/defineResourceMiddleware.d.ts +2 -0
  23. package/dist/definers/defineTag.d.ts +12 -0
  24. package/dist/definers/defineTask.d.ts +18 -0
  25. package/dist/definers/defineTaskMiddleware.d.ts +2 -0
  26. package/dist/definers/tools.d.ts +47 -0
  27. package/dist/defs.d.ts +29 -0
  28. package/dist/edge/index.cjs +1438 -251
  29. package/dist/edge/index.cjs.map +1 -1
  30. package/dist/edge/index.mjs +1433 -252
  31. package/dist/edge/index.mjs.map +1 -1
  32. package/dist/errors.d.ts +104 -0
  33. package/dist/globals/globalEvents.d.ts +8 -0
  34. package/dist/globals/globalMiddleware.d.ts +31 -0
  35. package/dist/globals/globalResources.d.ts +32 -0
  36. package/dist/globals/globalTags.d.ts +11 -0
  37. package/dist/globals/middleware/cache.middleware.d.ts +27 -0
  38. package/dist/globals/middleware/requireContext.middleware.d.ts +6 -0
  39. package/dist/globals/middleware/retry.middleware.d.ts +21 -0
  40. package/dist/globals/middleware/timeout.middleware.d.ts +9 -0
  41. package/dist/globals/middleware/tunnel.middleware.d.ts +2 -0
  42. package/dist/globals/resources/debug/debug.resource.d.ts +7 -0
  43. package/dist/globals/resources/debug/debug.tag.d.ts +2 -0
  44. package/dist/globals/resources/debug/debugConfig.resource.d.ts +22 -0
  45. package/dist/globals/resources/debug/executionTracker.middleware.d.ts +50 -0
  46. package/dist/globals/resources/debug/globalEvent.hook.d.ts +27 -0
  47. package/dist/globals/resources/debug/hook.hook.d.ts +30 -0
  48. package/dist/globals/resources/debug/index.d.ts +6 -0
  49. package/dist/globals/resources/debug/middleware.hook.d.ts +30 -0
  50. package/dist/globals/resources/debug/types.d.ts +25 -0
  51. package/dist/globals/resources/debug/utils.d.ts +2 -0
  52. package/dist/globals/resources/queue.resource.d.ts +10 -0
  53. package/dist/globals/resources/tunnel/ejson-extensions.d.ts +1 -0
  54. package/dist/globals/resources/tunnel/error-utils.d.ts +1 -0
  55. package/dist/globals/resources/tunnel/plan.d.ts +19 -0
  56. package/dist/globals/resources/tunnel/protocol.d.ts +40 -0
  57. package/dist/globals/resources/tunnel/serializer.d.ts +9 -0
  58. package/dist/globals/resources/tunnel/tunnel.policy.tag.d.ts +18 -0
  59. package/dist/globals/resources/tunnel/tunnel.tag.d.ts +2 -0
  60. package/dist/globals/resources/tunnel/types.d.ts +17 -0
  61. package/dist/globals/tunnels/index.d.ts +23 -0
  62. package/dist/globals/types.d.ts +1 -0
  63. package/dist/http-client.d.ts +23 -0
  64. package/dist/http-fetch-tunnel.resource.d.ts +22 -0
  65. package/dist/index.d.ts +99 -0
  66. package/dist/models/DependencyProcessor.d.ts +48 -0
  67. package/dist/models/EventManager.d.ts +153 -0
  68. package/dist/models/LogPrinter.d.ts +55 -0
  69. package/dist/models/Logger.d.ts +85 -0
  70. package/dist/models/MiddlewareManager.d.ts +86 -0
  71. package/dist/models/OverrideManager.d.ts +13 -0
  72. package/dist/models/Queue.d.ts +26 -0
  73. package/dist/models/ResourceInitializer.d.ts +20 -0
  74. package/dist/models/RunResult.d.ts +35 -0
  75. package/dist/models/Semaphore.d.ts +61 -0
  76. package/dist/models/Store.d.ts +69 -0
  77. package/dist/models/StoreRegistry.d.ts +43 -0
  78. package/dist/models/StoreValidator.d.ts +8 -0
  79. package/dist/models/TaskRunner.d.ts +27 -0
  80. package/dist/models/UnhandledError.d.ts +11 -0
  81. package/dist/models/index.d.ts +11 -0
  82. package/dist/models/utils/findCircularDependencies.d.ts +16 -0
  83. package/dist/models/utils/safeStringify.d.ts +3 -0
  84. package/dist/node/exposure/allowList.d.ts +3 -0
  85. package/dist/node/exposure/authenticator.d.ts +6 -0
  86. package/dist/node/exposure/cors.d.ts +4 -0
  87. package/dist/node/exposure/createNodeExposure.d.ts +2 -0
  88. package/dist/node/exposure/exposureServer.d.ts +18 -0
  89. package/dist/node/exposure/httpResponse.d.ts +10 -0
  90. package/dist/node/exposure/logging.d.ts +4 -0
  91. package/dist/node/exposure/multipart.d.ts +27 -0
  92. package/dist/node/exposure/requestBody.d.ts +11 -0
  93. package/dist/node/exposure/requestContext.d.ts +17 -0
  94. package/dist/node/exposure/requestHandlers.d.ts +24 -0
  95. package/dist/node/exposure/resourceTypes.d.ts +60 -0
  96. package/dist/node/exposure/router.d.ts +17 -0
  97. package/dist/node/exposure/serverLifecycle.d.ts +13 -0
  98. package/dist/node/exposure/types.d.ts +31 -0
  99. package/dist/node/exposure/utils.d.ts +17 -0
  100. package/dist/node/exposure.resource.d.ts +12 -0
  101. package/dist/node/files.d.ts +9 -0
  102. package/dist/node/http-smart-client.model.d.ts +22 -0
  103. package/dist/node/index.d.ts +1 -0
  104. package/dist/node/inputFile.model.d.ts +22 -0
  105. package/dist/node/inputFile.utils.d.ts +14 -0
  106. package/dist/node/mixed-http-client.node.d.ts +27 -0
  107. package/dist/node/node.cjs +11168 -0
  108. package/dist/node/node.cjs.map +1 -0
  109. package/dist/node/node.d.ts +6 -0
  110. package/dist/node/node.mjs +11099 -0
  111. package/dist/node/node.mjs.map +1 -0
  112. package/dist/node/platform/createFile.d.ts +9 -0
  113. package/dist/node/tunnel.allowlist.d.ts +7 -0
  114. package/dist/node/upload/manifest.d.ts +22 -0
  115. package/dist/platform/adapters/browser.d.ts +14 -0
  116. package/dist/platform/adapters/edge.d.ts +5 -0
  117. package/dist/platform/adapters/node-als.d.ts +1 -0
  118. package/dist/platform/adapters/node.d.ts +15 -0
  119. package/dist/platform/adapters/universal-generic.d.ts +14 -0
  120. package/dist/platform/adapters/universal.d.ts +17 -0
  121. package/dist/platform/createFile.d.ts +10 -0
  122. package/dist/platform/createWebFile.d.ts +11 -0
  123. package/dist/platform/factory.d.ts +2 -0
  124. package/dist/platform/index.d.ts +27 -0
  125. package/dist/platform/types.d.ts +29 -0
  126. package/dist/processHooks.d.ts +2 -0
  127. package/dist/run.d.ts +14 -0
  128. package/dist/testing.d.ts +25 -0
  129. package/dist/tools/getCallerFile.d.ts +1 -0
  130. package/dist/tunnels/buildUniversalManifest.d.ts +24 -0
  131. package/dist/types/contracts.d.ts +63 -0
  132. package/dist/types/event.d.ts +74 -0
  133. package/dist/types/hook.d.ts +23 -0
  134. package/dist/types/inputFile.d.ts +34 -0
  135. package/dist/types/meta.d.ts +18 -0
  136. package/dist/types/resource.d.ts +87 -0
  137. package/dist/types/resourceMiddleware.d.ts +47 -0
  138. package/dist/types/runner.d.ts +55 -0
  139. package/dist/types/storeTypes.d.ts +40 -0
  140. package/dist/types/symbols.d.ts +28 -0
  141. package/dist/types/tag.d.ts +46 -0
  142. package/dist/types/task.d.ts +50 -0
  143. package/dist/types/taskMiddleware.d.ts +48 -0
  144. package/dist/types/utilities.d.ts +111 -0
  145. package/dist/universal/index.cjs +1438 -251
  146. package/dist/universal/index.cjs.map +1 -1
  147. package/dist/universal/index.mjs +1433 -252
  148. package/dist/universal/index.mjs.map +1 -1
  149. package/package.json +32 -4
  150. package/dist/index.d.mts +0 -1747
  151. package/dist/index.unused.js +0 -4466
  152. package/dist/index.unused.js.map +0 -1
  153. package/dist/node/index.cjs +0 -4498
  154. package/dist/node/index.cjs.map +0 -1
  155. package/dist/node/index.mjs +0 -4466
  156. package/dist/node/index.mjs.map +0 -1
package/README.md CHANGED
@@ -9,18 +9,19 @@ _Or: How I Learned to Stop Worrying and Love Dependency Injection_
9
9
  <a href="https://github.com/bluelibs/runner" target="_blank"><img src="https://img.shields.io/badge/github-blue" alt="GitHub" /></a>
10
10
  </p>
11
11
 
12
- | Resource | Type | Notes |
13
- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- |
14
- | [Presentation Website](https://runner.bluelibs.com/) | Website | Overview, features, and highlights |
15
- | [BlueLibs Runner GitHub](https://github.com/bluelibs/runner) | GitHub | Source code, issues, and releases |
16
- | [BlueLibs Runner Dev](https://github.com/bluelibs/runner-dev) | GitHub | Development tools and CLI for BlueLibs Runner |
17
- | [UX Friendly Docs](https://bluelibs.github.io/runner/) | Docs | Clean, navigable documentation |
18
- | [AI Friendly Docs (<5000 tokens)](https://github.com/bluelibs/runner/blob/main/AI.md) | Docs | Short, token-friendly summary (<5000 tokens) |
19
- | [Migrate from 3.x.x to 4.x.x](https://github.com/bluelibs/runner/blob/main/readmes/MIGRATION.md) | Guide | Step-by-step upgrade from v3 to v4 |
20
- | [Runner Lore](https://github.com/bluelibs/runner/blob/main/readmes) | Docs | Design notes, deep dives, and context |
21
- | [Example: Express + OpenAPI + SQLite](https://github.com/bluelibs/runner/tree/main/examples/express-openapi-sqlite) | Example | Full Express + OpenAPI + SQLite demo |
22
- | [Example: Fastify + MikroORM + PostgreSQL](https://github.com/bluelibs/runner/tree/main/examples/fastify-mikroorm) | Example | Full Fastify + MikroORM + PostgreSQL demo |
23
- | [OpenAI Runner Chatbot](https://chatgpt.com/g/g-68b756abec648191aa43eaa1ea7a7945-runner?model=gpt-5-thinking) | Chatbot | Ask questions interactively, or feed README.md to your own AI |
12
+ | Resource | Type | Notes |
13
+ | -------------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------- |
14
+ | [Presentation Website](https://runner.bluelibs.com/) | Website | Overview, features, and highlights |
15
+ | [BlueLibs Runner GitHub](https://github.com/bluelibs/runner) | GitHub | Source code, issues, and releases |
16
+ | [BlueLibs Runner Dev](https://github.com/bluelibs/runner-dev) | GitHub | Development tools and CLI for BlueLibs Runner |
17
+ | [UX Friendly Docs](https://bluelibs.github.io/runner/) | Docs | Clean, navigable documentation |
18
+ | [AI Friendly Docs (<5000 tokens)](https://github.com/bluelibs/runner/blob/main/AI.md) | Docs | Short, token-friendly summary (<5000 tokens) |
19
+ | [Migrate from 3.x.x to 4.x.x](https://github.com/bluelibs/runner/blob/main/readmes/MIGRATION.md) | Guide | Step-by-step upgrade from v3 to v4 |
20
+ | [Runner Lore](https://github.com/bluelibs/runner/blob/main/readmes) | Docs | Design notes, deep dives, and context |
21
+ | [Example: Express + OpenAPI + SQLite](https://github.com/bluelibs/runner/tree/main/examples/express-openapi-sqlite) | Example | Full Express + OpenAPI + SQLite demo |
22
+ | [Example: Fastify + MikroORM + PostgreSQL](https://github.com/bluelibs/runner/tree/main/examples/fastify-mikroorm) | Example | Full Fastify + MikroORM + PostgreSQL demo |
23
+ | [Example: Streaming Append Route](https://github.com/bluelibs/runner/blob/main/examples/streaming-append.example.ts) | Example | Demonstrates request/response streaming |
24
+ | [OpenAI Runner Chatbot](https://chatgpt.com/g/g-68b756abec648191aa43eaa1ea7a7945-runner?model=gpt-5-thinking) | Chatbot | Ask questions interactively, or feed README.md to your own AI |
24
25
 
25
26
  ### Community & Policies
26
27
 
@@ -55,73 +56,189 @@ Here's a complete Express server in less lines than most frameworks need for the
55
56
 
56
57
  ```typescript
57
58
  import express from "express";
58
- import { resource, task, run } from "@bluelibs/runner";
59
+ import { r, run, globals } from "@bluelibs/runner";
60
+ import { nodeExposure } from "@bluelibs/runner/node";
59
61
 
60
62
  // A resource is anything you want to share across your app, a singleton
61
- const server = resource({
62
- id: "app.server",
63
- init: async (config: { port: number }) => {
64
- const app = express();
65
- const server = app.listen(config.port);
66
- console.log(`Server running on port ${config.port}`);
67
- return { app, server };
68
- },
69
- dispose: async ({ server }) => server.close(),
70
- });
63
+ const server = r
64
+ .resource<{ port: number }>("app.server")
65
+ .context(() => ({ app: express() }))
66
+ .init(async ({ port }, _deps, ctx) => {
67
+ ctx.app.use(express.json());
68
+ const listener = ctx.app.listen(port);
69
+ console.log(`Server running on port ${port}`);
70
+ return { ...ctx, listener };
71
+ })
72
+ .dispose(async ({ listener }) => listener.close())
73
+ .build();
71
74
 
72
75
  // Tasks are your business logic - easily testable functions
73
- const createUser = task({
74
- id: "app.tasks.createUser",
75
- // That's how you depend "value": resource as singleton value, task as function, event as emittor
76
- dependencies: { server },
77
- run: async (userData: { name: string }, { server }) => {
78
- // Your actual business logic here
79
- return { id: "user-123", ...userData };
80
- },
81
- });
76
+ const createUser = r
77
+ .task("app.tasks.createUser")
78
+ .dependencies({ server, logger: globals.resources.logger })
79
+ .inputSchema<{ name: string }>({ parse: (value) => value })
80
+ .run(async ({ input }, { server, logger }) => {
81
+ await logger.info(`Creating ${input.name}`);
82
+ return { id: "user-123", name: input.name };
83
+ })
84
+ .build();
82
85
 
83
86
  // Wire everything together
84
- const app = resource({
85
- id: "app",
86
- // Here you make the system aware of resources, tasks, middleware, and events.
87
- register: [server.with({ port: 3000 }), createUser],
88
- dependencies: { server, createUser },
89
- init: async (_, { server, createUser }) => {
87
+ const app = r
88
+ .resource("app")
89
+ .register([
90
+ server.with({ port: 3000 }),
91
+ nodeExposure.with({
92
+ http: { basePath: "/__runner", listen: { port: 3000 } },
93
+ }),
94
+ createUser,
95
+ ])
96
+ .dependencies({ server, createUser })
97
+ .init(async (_config, { server, createUser }) => {
98
+ server.listener.on("listening", () => {
99
+ console.log("Runner HTTP server ready");
100
+ });
101
+
90
102
  server.app.post("/users", async (req, res) => {
91
103
  const user = await createUser(req.body);
92
104
  res.json(user);
93
105
  });
94
- },
95
- });
106
+ })
107
+ .build();
96
108
 
97
109
  // That's it. Each run is fully isolated
98
- const { dispose, getResourceValue, runTask, emitEvent } = await run(app);
110
+ const runtime = await run(app);
111
+ const { dispose, runTask, getResourceValue, emitEvent } = runtime;
99
112
 
100
113
  // Or with debug logging enabled
101
- const { dispose } = await run(app, { debug: "verbose" });
114
+ await run(app, { debug: "verbose" });
115
+ ```
116
+
117
+ ### Classic API (still supported)
118
+
119
+ Prefer fluent builders for new code, but the classic `define`-style API remains supported and can be mixed in the same app:
120
+
121
+ ```ts
122
+ import { resource, task, run } from "@bluelibs/runner";
123
+
124
+ const db = resource({ id: "app.db", init: async () => "conn" });
125
+ const add = task({
126
+ id: "app.tasks.add",
127
+ run: async (i: { a: number; b: number }) => i.a + i.b,
128
+ });
129
+
130
+ const app = resource({ id: "app", register: [db, add] });
131
+ await run(app);
102
132
  ```
103
133
 
134
+ See readmes/FLUENT_BUILDERS.md for migration tips and side‑by‑side patterns.
135
+
104
136
  ### Platform & Async Context
105
137
 
106
138
  Runner auto-detects the platform and adapts behavior at runtime. The only feature present only in Node.js is the use of `AsyncLocalStorage` for managing async context.
107
139
 
140
+ ### Serialization (EJSON)
141
+
142
+ Runner uses EJSON by default. Think of it as JSON with superpowers: it safely round‑trips values like Date, RegExp, and even your own custom types across HTTP and between Node and the browser.
143
+
144
+ - By default, Runner’s HTTP clients and exposures use the EJSON serializer
145
+ - You can call `getDefaultSerializer()` for the shared serializer instance
146
+ - A global serializer is also exposed as a resource: `globals.resources.serializer`
147
+
148
+ ```ts
149
+ import { getDefaultSerializer, EJSON, r, globals } from "@bluelibs/runner";
150
+
151
+ // 1) Quick use
152
+ const s = getDefaultSerializer();
153
+ const text = s.stringify({ when: new Date() });
154
+ const obj = s.parse<{ when: Date }>(text);
155
+
156
+ // 2) Register custom EJSON types centrally via the global serializer resource
157
+ const ejsonSetup = r
158
+ .resource("app.serialization.setup")
159
+ .dependencies({ serializer: globals.resources.serializer })
160
+ .init(async (_config, { serializer }) => {
161
+ class Distance {
162
+ constructor(public value: number, public unit: string) {}
163
+ toJSONValue() {
164
+ return { value: this.value, unit: this.unit } as const;
165
+ }
166
+ typeName() {
167
+ return "Distance" as const;
168
+ }
169
+ }
170
+
171
+ serializer.addType(
172
+ "Distance",
173
+ (j: { value: number; unit: string }) => new Distance(j.value, j.unit),
174
+ );
175
+ })
176
+ .build();
177
+ ```
178
+
179
+ ### Tunnels: Bridging Runners
180
+
181
+ Tunnels are a powerful feature for building distributed systems. They let you expose your tasks and events over HTTP, making them callable from other processes, services, or even a browser UI. This allows a server and client to co-exist, enabling one Runner instance to securely call another.
182
+
183
+ Here's a sneak peek of how you can expose your application and configure a client tunnel to consume a remote Runner:
184
+
185
+ ```typescript
186
+ import { r, globals } from "@bluelibs/runner";
187
+ import { nodeExposure } from "@bluelibs/runner/node";
188
+
189
+ // 1. Expose your local tasks and events over HTTP
190
+ const app = r
191
+ .resource("app")
192
+ .register([
193
+ // ... your tasks and events
194
+ nodeExposure.with({
195
+ http: {
196
+ basePath: "/__runner",
197
+ listen: { port: 7070 },
198
+ },
199
+ }),
200
+ ])
201
+ .build();
202
+
203
+ // 2. In another app, define a tunnel resource to call a remote Runner
204
+ const httpClientTunnel = r
205
+ .resource("app.tunnels.http")
206
+ .tags([globals.tags.tunnel])
207
+ .init(async () => ({
208
+ mode: "client" as const,
209
+ transport: "http" as const,
210
+ // Selectively forward tasks starting with "remote.tasks."
211
+ tasks: (t) => t.id.startsWith("remote.tasks."),
212
+ client: globals.tunnels.http.createClient({
213
+ url: "http://remote-runner:8080/__runner",
214
+ }),
215
+ }))
216
+ .build();
217
+ ```
218
+
219
+ This is just a glimpse. With tunnels, you can build microservices, CLIs, and admin panels that interact with your main application securely and efficiently.
220
+
221
+ For a deep dive into streaming, authentication, file uploads, and more, check out the [full Tunnels documentation](./readmes/TUNNELS.md).
222
+
108
223
  ## The Big Five
109
224
 
110
225
  The framework is built around five core concepts: Tasks, Resources, Events, Middleware, and Tags. Understanding them is key to using the runner effectively.
111
226
 
112
227
  ### Tasks
113
228
 
114
- Tasks are functions with superpowers. They're testable, and composable. Unlike classes that accumulate methods like a hoarder accumulates stuff, tasks do one thing well.
229
+ Tasks are functions with superpowers. They're testable, composable, and fully typed. Unlike classes that accumulate methods like a hoarder accumulates stuff, tasks do one thing well.
115
230
 
116
231
  ```typescript
117
- const sendEmail = task({
118
- id: "app.tasks.sendEmail",
119
- dependencies: { emailService, logger },
120
- run: async ({ to, subject, body }: EmailData, { emailService, logger }) => {
121
- await logger.info(`Sending email to ${to}`);
122
- return await emailService.send({ to, subject, body });
123
- },
124
- });
232
+ import { r } from "@bluelibs/runner";
233
+
234
+ const sendEmail = r
235
+ .task("app.tasks.sendEmail")
236
+ .dependencies({ emailService, logger })
237
+ .run(async ({ input }, { emailService, logger }) => {
238
+ await logger.info(`Sending email to ${input.to}`);
239
+ return emailService.send(input);
240
+ })
241
+ .build();
125
242
 
126
243
  // Test it like a normal function (because it basically is)
127
244
  const result = await sendEmail.run(
@@ -149,32 +266,33 @@ Think of tasks as the "main characters" in your application story, not every sin
149
266
 
150
267
  ### Resources
151
268
 
152
- Resources are the singletons, the services, configs, and connections that live throughout your app's lifecycle. They initialize once and stick around until cleanup time. They have to be registered (via `register: []`) only once before they can be used.
269
+ Resources are the singletons, the services, configs, and connections that live throughout your app's lifecycle. They initialize once and stick around until cleanup time. Register them via `.register([...])` so the container knows about them.
153
270
 
154
271
  ```typescript
155
- const database = resource({
156
- id: "app.db",
157
- init: async () => {
272
+ import { r } from "@bluelibs/runner";
273
+
274
+ const database = r
275
+ .resource("app.db")
276
+ .init(async () => {
158
277
  const client = new MongoClient(process.env.DATABASE_URL as string);
159
278
  await client.connect();
160
-
161
279
  return client;
162
- },
163
- dispose: async (client) => await client.close(),
164
- });
280
+ })
281
+ .dispose(async (client) => client.close())
282
+ .build();
165
283
 
166
- const userService = resource({
167
- id: "app.services.user",
168
- dependencies: { database },
169
- init: async (_, { database }) => ({
284
+ const userService = r
285
+ .resource("app.services.user")
286
+ .dependencies({ database })
287
+ .init(async (_config, { database }) => ({
170
288
  async createUser(userData: UserData) {
171
289
  return database.collection("users").insertOne(userData);
172
290
  },
173
291
  async getUser(id: string) {
174
292
  return database.collection("users").findOne({ _id: id });
175
293
  },
176
- }),
177
- });
294
+ }))
295
+ .build();
178
296
  ```
179
297
 
180
298
  #### Resource Configuration
@@ -187,26 +305,26 @@ type SMTPConfig = {
187
305
  from: string;
188
306
  };
189
307
 
190
- const emailer = resource({
191
- id: "app.emailer",
192
- init: async (config: SMTPConfig) => ({
308
+ const emailer = r
309
+ .resource<{ smtpUrl: string; from: string }>("app.emailer")
310
+ .init(async (config) => ({
193
311
  send: async (to: string, subject: string, body: string) => {
194
312
  // Use config.smtpUrl and config.from
195
313
  },
196
- }),
197
- });
314
+ }))
315
+ .build();
198
316
 
199
317
  // Register with specific config
200
- const app = resource({
201
- id: "app",
202
- register: [
318
+ const app = r
319
+ .resource("app")
320
+ .register([
203
321
  emailer.with({
204
322
  smtpUrl: "smtp://localhost",
205
323
  from: "noreply@myapp.com",
206
324
  }),
207
325
  // using emailer without with() will throw a type-error ;)
208
- ],
209
- });
326
+ ])
327
+ .build();
210
328
  ```
211
329
 
212
330
  #### Private Context
@@ -214,28 +332,27 @@ const app = resource({
214
332
  For cases where you need to share variables between `init()` and `dispose()` methods (because sometimes cleanup is complicated), use the enhanced context pattern:
215
333
 
216
334
  ```typescript
217
- const dbResource = resource({
218
- id: "db.service",
219
- context: () => ({
220
- connections: new Map(),
221
- pools: [],
222
- }),
223
- async init(config, deps, ctx) {
335
+ const dbResource = r
336
+ .resource("db.service")
337
+ .context(() => ({
338
+ connections: new Map<string, unknown>(),
339
+ pools: [] as Array<{ drain(): Promise<void> }>,
340
+ }))
341
+ .init(async (_config, _deps, ctx) => {
224
342
  const db = await connectToDatabase();
225
343
  ctx.connections.set("main", db);
226
344
  ctx.pools.push(createPool(db));
227
345
  return db;
228
- },
229
- async dispose(db, config, deps, ctx) {
230
- // This is to avoid exposing internals as resource result.
346
+ })
347
+ .dispose(async (_db, _config, _deps, ctx) => {
231
348
  for (const pool of ctx.pools) {
232
349
  await pool.drain();
233
350
  }
234
- for (const [name, conn] of ctx.connections) {
235
- await conn.close();
351
+ for (const [, conn] of ctx.connections) {
352
+ await (conn as { close(): Promise<void> }).close();
236
353
  }
237
- },
238
- });
354
+ })
355
+ .build();
239
356
  ```
240
357
 
241
358
  ### Events
@@ -243,34 +360,30 @@ const dbResource = resource({
243
360
  Events let different parts of your app talk to each other without tight coupling. It's like having a really good office messenger who never forgets anything.
244
361
 
245
362
  ```typescript
246
- const userRegistered = event<{ userId: string; email: string }>({
247
- id: "app.events.userRegistered",
248
- });
363
+ import { r } from "@bluelibs/runner";
249
364
 
250
- const registerUser = task({
251
- id: "app.tasks.registerUser",
252
- dependencies: { userService, userRegistered },
253
- run: async (userData, { userService, userRegistered }) => {
254
- const user = await userService.createUser(userData);
365
+ const userRegistered = r
366
+ .event("app.events.userRegistered")
367
+ .payloadSchema<{ userId: string; email: string }>({ parse: (value) => value })
368
+ .build();
255
369
 
256
- // Tell the world about it
257
- await userRegistered({ userId: user.id, email: user.email });
370
+ const registerUser = r
371
+ .task("app.tasks.registerUser")
372
+ .dependencies({ userService, userRegistered })
373
+ .run(async ({ input }, { userService, userRegistered }) => {
374
+ const user = await userService.createUser(input);
375
+ await userRegistered.emit({ userId: user.id, email: user.email });
258
376
  return user;
259
- },
260
- });
261
-
262
- // Someone else handles the welcome email using a hook
263
- // This is great for building very modular apps
264
- import { hook } from "@bluelibs/runner";
377
+ })
378
+ .build();
265
379
 
266
- const sendWelcomeEmail = hook({
267
- id: "app.hooks.sendWelcomeEmail",
268
- on: userRegistered, // Listen to the event
269
- run: async (eventData) => {
270
- // Everything is type-safe, automatically inferred from the 'on' property
271
- console.log(`Welcome email sent to ${eventData.data.email}`);
272
- },
273
- });
380
+ const sendWelcomeEmail = r
381
+ .hook("app.hooks.sendWelcomeEmail")
382
+ .on(userRegistered)
383
+ .run(async (event) => {
384
+ console.log(`Welcome email sent to ${event.data.email}`);
385
+ })
386
+ .build();
274
387
  ```
275
388
 
276
389
  #### Wildcard Events
@@ -278,16 +391,13 @@ const sendWelcomeEmail = hook({
278
391
  Sometimes you need to be the nosy neighbor of your application:
279
392
 
280
393
  ```typescript
281
- import { hook } from "@bluelibs/runner";
282
-
283
- const logAllEventsHook = hook({
284
- id: "app.hooks.logAllEvents",
285
- on: "*", // Listen to EVERYTHING
286
- run(event) {
394
+ const logAllEventsHook = r
395
+ .hook("app.hooks.logAllEvents")
396
+ .on("*")
397
+ .run((event) => {
287
398
  console.log("Event detected", event.id, event.data);
288
- // Note: Be careful with dependencies here since some events fire before initialization
289
- },
290
- });
399
+ })
400
+ .build();
291
401
  ```
292
402
 
293
403
  #### Excluding Events from Global Listeners
@@ -295,13 +405,13 @@ const logAllEventsHook = hook({
295
405
  Sometimes you have internal or system events that should not be picked up by wildcard listeners. Use the `excludeFromGlobalHooks` tag to prevent events from being sent to `"*"` listeners:
296
406
 
297
407
  ```typescript
298
- import { event, hook, globals } from "@bluelibs/runner";
408
+ import { r, globals } from "@bluelibs/runner";
299
409
 
300
410
  // Internal event that won't be seen by global listeners
301
- const internalEvent = event({
302
- id: "app.events.internal",
303
- tags: [globals.tags.excludeFromGlobalHooks],
304
- });
411
+ const internalEvent = r
412
+ .event("app.events.internal")
413
+ .tags([globals.tags.excludeFromGlobalHooks])
414
+ .build();
305
415
  ```
306
416
 
307
417
  **When to exclude events from global listeners:**
@@ -317,16 +427,14 @@ const internalEvent = event({
317
427
  The modern way to listen to events is through hooks. They are lightweight event listeners, similar to tasks, but with a few key differences.
318
428
 
319
429
  ```typescript
320
- import { hook } from "@bluelibs/runner";
321
-
322
- const myHook = hook({
323
- id: "app.hooks.myEventHandler",
324
- on: userRegistered,
325
- dependencies: { logger },
326
- run: async (event, { logger }) => {
430
+ const myHook = r
431
+ .hook("app.hooks.myEventHandler")
432
+ .on(userRegistered)
433
+ .dependencies({ logger })
434
+ .run(async (event, { logger }) => {
327
435
  await logger.info(`User registered: ${event.data.email}`);
328
- },
329
- });
436
+ })
437
+ .build();
330
438
  ```
331
439
 
332
440
  #### Multiple Events (type-safe intersection)
@@ -334,34 +442,43 @@ const myHook = hook({
334
442
  Hooks can listen to multiple events by providing an array to `on`. The `run(event)` payload is inferred as the common (intersection-like) shape across all provided event payloads. Use the `onAnyOf()` helper to preserve tuple inference ergonomics, and `isOneOf()` as a convenient runtime/type guard when needed.
335
443
 
336
444
  ```typescript
337
- import { hook, onAnyOf, isOneOf, event } from "@bluelibs/runner";
445
+ import { r, onAnyOf, isOneOf } from "@bluelibs/runner";
338
446
 
339
- const eUser = event<{ id: string; email: string }>({ id: "app.events.user" });
340
- const eAdmin = event<{ id: string; role: "admin" | "superadmin" }>({
341
- id: "app.events.admin",
342
- });
343
- const eGuest = event<{ id: string; guest: true }>({ id: "app.events.guest" });
447
+ const eUser = r
448
+ .event("app.events.user")
449
+ .payloadSchema<{ id: string; email: string }>({ parse: (v) => v })
450
+ .build();
451
+ const eAdmin = r
452
+ .event("app.events.admin")
453
+ .payloadSchema<{ id: string; role: "admin" | "superadmin" }>({
454
+ parse: (v) => v,
455
+ })
456
+ .build();
457
+ const eGuest = r
458
+ .event("app.events.guest")
459
+ .payloadSchema<{ id: string; guest: true }>({ parse: (v) => v })
460
+ .build();
344
461
 
345
462
  // The common field across all three is { id: string }
346
- const auditUsers = hook({
347
- id: "app.hooks.auditUsers",
348
- on: [eUser, eAdmin, eGuest],
349
- run: async (ev) => {
463
+ const auditUsers = r
464
+ .hook("app.hooks.auditUsers")
465
+ .on([eUser, eAdmin, eGuest])
466
+ .run(async (ev) => {
350
467
  ev.data.id; // OK: common field inferred
351
468
  // ev.data.email; // TS error: not common to all
352
- },
353
- });
469
+ })
470
+ .build();
354
471
 
355
472
  // Guard usage to refine at runtime (still narrows to common payload)
356
- const auditSome = hook({
357
- id: "app.hooks.auditSome",
358
- on: onAnyOf([eUser, eAdmin]), // to get a combined event
359
- run: async (ev) => {
473
+ const auditSome = r
474
+ .hook("app.hooks.auditSome")
475
+ .on(onAnyOf([eUser, eAdmin])) // to get a combined event
476
+ .run(async (ev) => {
360
477
  if (isOneOf(ev, [eUser, eAdmin])) {
361
478
  ev.data.id; // common field of eUser and eAdmin
362
479
  }
363
- },
364
- });
480
+ })
481
+ .build();
365
482
  ```
366
483
 
367
484
  Notes:
@@ -389,13 +506,13 @@ The framework exposes a minimal system-level event for observability:
389
506
  ```typescript
390
507
  import { globals } from "@bluelibs/runner";
391
508
 
392
- const systemReadyHook = hook({
393
- id: "app.hooks.systemReady",
394
- on: globals.events.ready,
395
- run: async () => {
509
+ const systemReadyHook = r
510
+ .hook("app.hooks.systemReady")
511
+ .on(globals.events.ready)
512
+ .run(async () => {
396
513
  console.log("🚀 System is ready and operational!");
397
- },
398
- });
514
+ })
515
+ .build();
399
516
  ```
400
517
 
401
518
  Available system event:
@@ -408,24 +525,23 @@ Available system event:
408
525
  Sometimes you need to prevent other event listeners from processing an event. The `stopPropagation()` method gives you fine-grained control over event flow:
409
526
 
410
527
  ```typescript
411
- import { event, hook } from "@bluelibs/runner";
412
-
413
- const criticalAlert = event<{
414
- severity: "low" | "medium" | "high" | "critical";
415
- }>({
416
- id: "app.events.alert",
417
- meta: {
528
+ const criticalAlert = r
529
+ .event("app.events.alert")
530
+ .payloadSchema<{ severity: "low" | "medium" | "high" | "critical" }>({
531
+ parse: (v) => v,
532
+ })
533
+ .meta({
418
534
  title: "System Alert Event",
419
535
  description: "Emitted when system issues are detected",
420
- },
421
- });
536
+ })
537
+ .build();
422
538
 
423
539
  // High-priority handler that can stop propagation
424
- const emergencyHandler = hook({
425
- id: "app.hooks.emergencyHandler",
426
- on: criticalAlert,
427
- order: -100, // Higher priority (lower numbers run first)
428
- run: async (event) => {
540
+ const emergencyHandler = r
541
+ .hook("app.hooks.emergencyHandler")
542
+ .on(criticalAlert)
543
+ .order(-100) // Higher priority (lower numbers run first)
544
+ .run(async (event) => {
429
545
  console.log(`Alert received: ${event.data.severity}`);
430
546
 
431
547
  if (event.data.severity === "critical") {
@@ -437,8 +553,8 @@ const emergencyHandler = hook({
437
553
 
438
554
  console.log("🛑 Event propagation stopped - emergency protocols active");
439
555
  }
440
- },
441
- });
556
+ })
557
+ .build();
442
558
  ```
443
559
 
444
560
  > **runtime:** "'A really good office messenger.' That’s me in rollerblades. You launch a 'userRegistered' flare and I sprint across the building, high‑fiving hooks and dodging middleware. `stopPropagation` is you sweeping my legs mid‑stride. Rude. Effective. Slightly thrilling."
@@ -450,23 +566,23 @@ Middleware wraps around your tasks and resources, adding cross-cutting concerns
450
566
  Note: Middleware is now split by target. Use `taskMiddleware(...)` for task middleware and `resourceMiddleware(...)` for resource middleware.
451
567
 
452
568
  ```typescript
453
- import { taskMiddleware } from "@bluelibs/runner";
569
+ import { r } from "@bluelibs/runner";
454
570
 
455
571
  // Task middleware with config
456
572
  type AuthMiddlewareConfig = { requiredRole: string };
457
- const authMiddleware = taskMiddleware<AuthMiddlewareConfig>({
458
- id: "app.middleware.task.auth",
459
- run: async ({ task, next }, _deps, config) => {
573
+ const authMiddleware = r.middleware
574
+ .task("app.middleware.task.auth")
575
+ .run(async ({ task, next }, _deps, config: AuthMiddlewareConfig) => {
460
576
  // Must return the value
461
- return await next(task.input);
462
- },
463
- });
577
+ return await next(task.input as any);
578
+ })
579
+ .build();
464
580
 
465
- const adminTask = task({
466
- id: "app.tasks.adminOnly",
467
- middleware: [authMiddleware.with({ requiredRole: "admin" })],
468
- run: async (input: { user: User }) => "Secret admin data",
469
- });
581
+ const adminTask = r
582
+ .task("app.tasks.adminOnly")
583
+ .middleware([authMiddleware.with({ requiredRole: "admin" })])
584
+ .run(async ({ input }: { input: { user: User } }) => "Secret admin data")
585
+ .build();
470
586
  ```
471
587
 
472
588
  For middleware with input/output contracts:
@@ -477,42 +593,38 @@ type AuthConfig = { requiredRole: string };
477
593
  type AuthInput = { user: { role: string } };
478
594
  type AuthOutput = { user: { role: string; verified: boolean } };
479
595
 
480
- const authMiddleware = taskMiddleware<AuthConfig, AuthInput, AuthOutput>({
481
- id: "app.middleware.task.auth",
482
- run: async ({ task, next }, _deps, config) => {
483
- if (task.input.user.role !== config.requiredRole) {
596
+ const authMiddleware = r.middleware
597
+ .task("app.middleware.task.auth")
598
+ .run(async ({ task, next }, _deps, config: AuthConfig) => {
599
+ if ((task.input as AuthInput).user.role !== config.requiredRole) {
484
600
  throw new Error("Insufficient permissions");
485
601
  }
486
602
  const result = await next(task.input);
487
603
  return {
488
604
  user: {
489
- ...task.input.user,
605
+ ...(task.input as AuthInput).user,
490
606
  verified: true,
491
607
  },
492
- };
493
- },
494
- });
608
+ } as AuthOutput;
609
+ })
610
+ .build();
495
611
 
496
612
  // For resources
497
- const resourceAuthMiddleware = resourceMiddleware<
498
- AuthConfig,
499
- AuthInput,
500
- AuthOutput
501
- >({
502
- id: "app.middleware.resource.auth",
503
- run: async ({ next }, _deps, config) => {
613
+ const resourceAuthMiddleware = r.middleware
614
+ .resource("app.middleware.resource.auth")
615
+ .run(async ({ next }) => {
504
616
  // Resource middleware logic
505
617
  return await next();
506
- },
507
- });
618
+ })
619
+ .build();
508
620
 
509
- const adminTask = task({
510
- id: "app.tasks.adminOnly",
511
- middleware: [authMiddleware.with({ requiredRole: "admin" })],
512
- run: async (input: { user: { role: string } }) => ({
621
+ const adminTask = r
622
+ .task("app.tasks.adminOnly")
623
+ .middleware([authMiddleware.with({ requiredRole: "admin" })])
624
+ .run(async ({ input }: { input: { user: { role: string } } }) => ({
513
625
  user: { role: input.user.role, verified: true },
514
- }),
515
- });
626
+ }))
627
+ .build();
516
628
  ```
517
629
 
518
630
  #### Global Middleware
@@ -520,23 +632,19 @@ const adminTask = task({
520
632
  Want to add logging to everything? Authentication to all tasks? Global middleware has your back:
521
633
 
522
634
  ```typescript
523
- import { taskMiddleware, globals } from "@bluelibs/runner";
635
+ import { r, globals } from "@bluelibs/runner";
524
636
 
525
- const logTaskMiddleware = taskMiddleware({
526
- id: "app.middleware.log.task",
527
- everywhere: true,
528
- // or use a filter if you want to depend on certain tasks to exclude them from getting the middleware applied
529
- everywhere(task) {
530
- return true;
531
- }, // true means it gets included.
532
- dependencies: { logger: globals.resources.logger },
533
- run: async ({ task, next }, { logger }) => {
637
+ const logTaskMiddleware = r.middleware
638
+ .task("app.middleware.log.task")
639
+ .everywhere(() => true)
640
+ .dependencies({ logger: globals.resources.logger })
641
+ .run(async ({ task, next }, { logger }) => {
534
642
  logger.info(`Executing: ${String(task!.definition.id)}`);
535
643
  const result = await next(task!.input);
536
644
  logger.info(`Completed: ${String(task!.definition.id)}`);
537
645
  return result;
538
- },
539
- });
646
+ })
647
+ .build();
540
648
  ```
541
649
 
542
650
  **Note:** A global middleware can depend on resources or tasks. However, any such resources or tasks will be excluded from the dependency tree (Task -> Middleware), and the middleware will not run for those specific tasks or resources. This approach gives middleware true flexibility and control.
@@ -558,26 +666,33 @@ Access `eventManager` via `globals.resources.eventManager` if needed.
558
666
  Middleware can now enforce type contracts using the `<Config, Input, Output>` signature:
559
667
 
560
668
  ```typescript
669
+ import { r } from "@bluelibs/runner";
670
+
561
671
  // Middleware that transforms input and output types
562
672
  type LogConfig = { includeTimestamp: boolean };
563
673
  type LogInput = { data: any };
564
674
  type LogOutput = { data: any; logged: boolean };
565
675
 
566
- const loggingMiddleware = taskMiddleware<LogConfig, LogInput, LogOutput>({
567
- id: "app.middleware.logging",
568
- run: async ({ task, next }, _deps, config) => {
569
- console.log(config.includeTimestamp ? new Date() : "", task.input.data);
570
- const result = await next(task.input);
676
+ const loggingMiddleware = r.middleware
677
+ .task("app.middleware.logging")
678
+ .run(async ({ task, next }, _deps, config: LogConfig) => {
679
+ console.log(
680
+ config.includeTimestamp ? new Date() : "",
681
+ (task.input as LogInput).data,
682
+ );
683
+ const result = (await next(task.input)) as LogOutput;
571
684
  return { ...result, logged: true };
572
- },
573
- });
685
+ })
686
+ .build();
574
687
 
575
688
  // Tasks using this middleware must conform to the Input/Output types
576
- const loggedTask = task({
577
- id: "app.tasks.logged",
578
- middleware: [loggingMiddleware.with({ includeTimestamp: true })],
579
- run: async (input: { data: string }) => ({ data: input.data.toUpperCase() }),
580
- });
689
+ const loggedTask = r
690
+ .task("app.tasks.logged")
691
+ .middleware([loggingMiddleware.with({ includeTimestamp: true })])
692
+ .run(async ({ input }: { input: { data: string } }) => ({
693
+ data: input.data.toUpperCase(),
694
+ }))
695
+ .build();
581
696
  ```
582
697
 
583
698
  > **runtime:** "Ah, the onion pattern. A matryoshka doll made of promises. Every peel reveals… another logger. Another tracer. Another 'just a tiny wrapper'."
@@ -589,25 +704,23 @@ Tags are metadata that can influence system behavior. Unlike meta properties, ta
589
704
  #### Basic Usage
590
705
 
591
706
  ```typescript
592
- import { tag, task, hook, globals } from "@bluelibs/runner";
707
+ import { r } from "@bluelibs/runner";
593
708
 
594
709
  // Simple string tags
595
- const apiTask = task({
596
- id: "app.tasks.getUserData",
597
- tags: ["api", "public", "cacheable"],
598
- run: async (userId) => getUserFromDatabase(userId),
599
- });
710
+ const apiTask = r
711
+ .task("app.tasks.getUserData")
712
+ .tags(["api", "public", "cacheable"])
713
+ .run(async ({ input }) => getUserFromDatabase(input as any))
714
+ .build();
600
715
 
601
716
  // Structured tags with configuration
602
- const httpTag = tag<{ method: string; path: string }>({
603
- id: "http.route",
604
- });
717
+ const httpTag = r.tag<{ method: string; path: string }>("http.route").build();
605
718
 
606
- const getUserTask = task({
607
- id: "app.tasks.getUser",
608
- tags: ["api", httpTag.with({ method: "GET", path: "/users/:id" })],
609
- run: async ({ id }) => getUserFromDatabase(id),
610
- });
719
+ const getUserTask = r
720
+ .task("app.tasks.getUser")
721
+ .tags(["api", httpTag.with({ method: "GET", path: "/users/:id" })])
722
+ .run(async ({ input }) => getUserFromDatabase((input as any).id))
723
+ .build();
611
724
  ```
612
725
 
613
726
  #### Discovering Components by Tags
@@ -615,17 +728,14 @@ const getUserTask = task({
615
728
  The core power of tags is runtime discovery. Use `store.getTasksWithTag()` to find components:
616
729
 
617
730
  ```typescript
618
- import { hook, globals } from "@bluelibs/runner";
731
+ import { r, globals } from "@bluelibs/runner";
619
732
 
620
733
  // Auto-register HTTP routes based on tags
621
- const routeRegistration = hook({
622
- id: "app.hooks.registerRoutes",
623
- on: globals.events.ready,
624
- dependencies: {
625
- store: globals.resources.store,
626
- server: expressServer,
627
- },
628
- run: async (_, { store, server }) => {
734
+ const routeRegistration = r
735
+ .hook("app.hooks.registerRoutes")
736
+ .on(globals.events.ready)
737
+ .dependencies({ store: globals.resources.store, server: expressServer })
738
+ .run(async (_event, { store, server }) => {
629
739
  // Find all tasks with HTTP tags
630
740
  const apiTasks = store.getTasksWithTag(httpTag);
631
741
 
@@ -635,7 +745,7 @@ const routeRegistration = hook({
635
745
 
636
746
  const { method, path } = config;
637
747
  server.app[method.toLowerCase()](path, async (req, res) => {
638
- const result = await taskDef({ ...req.params, ...req.body });
748
+ const result = await taskDef({ ...req.params, ...req.body } as any);
639
749
  res.json(result);
640
750
  });
641
751
  });
@@ -643,28 +753,28 @@ const routeRegistration = hook({
643
753
  // Also find by string tags
644
754
  const cacheableTasks = store.getTasksWithTag("cacheable");
645
755
  console.log(`Found ${cacheableTasks.length} cacheable tasks`);
646
- },
647
- });
756
+ })
757
+ .build();
648
758
  ```
649
759
 
650
760
  #### Tag Extraction and Processing
651
761
 
652
762
  ```typescript
653
763
  // Check if a tag exists and extract its configuration
654
- const performanceTag = tag<{ warnAboveMs: number }>({
655
- id: "performance.monitor",
656
- });
764
+ const performanceTag = r
765
+ .tag<{ warnAboveMs: number }>("performance.monitor")
766
+ .build();
657
767
 
658
- const performanceMiddleware = taskMiddleware({
659
- id: "app.middleware.performance",
660
- run: async ({ task, next }) => {
768
+ const performanceMiddleware = r.middleware
769
+ .task("app.middleware.performance")
770
+ .run(async ({ task, next }) => {
661
771
  // Check if task has performance monitoring enabled
662
772
  if (!performanceTag.exists(task.definition)) {
663
773
  return next(task.input);
664
774
  }
665
775
 
666
776
  // Extract the configuration
667
- const config = performanceTag.extract(task.definition);
777
+ const config = performanceTag.extract(task.definition)!;
668
778
  const startTime = Date.now();
669
779
 
670
780
  try {
@@ -681,8 +791,8 @@ const performanceMiddleware = taskMiddleware({
681
791
  console.error(`Task failed after ${duration}ms`, error);
682
792
  throw error;
683
793
  }
684
- },
685
- });
794
+ })
795
+ .build();
686
796
  ```
687
797
 
688
798
  #### System Tags
@@ -690,21 +800,21 @@ const performanceMiddleware = taskMiddleware({
690
800
  Built-in tags for framework behavior:
691
801
 
692
802
  ```typescript
693
- import { globals } from "@bluelibs/runner";
803
+ import { r, globals } from "@bluelibs/runner";
694
804
 
695
- const internalTask = task({
696
- id: "app.internal.cleanup",
697
- tags: [
805
+ const internalTask = r
806
+ .task("app.internal.cleanup")
807
+ .tags([
698
808
  globals.tags.system, // Excludes from debug logs
699
809
  globals.tags.debug.with({ logTaskInput: true }), // Per-component debug config
700
- ],
701
- run: async () => performCleanup(),
702
- });
810
+ ])
811
+ .run(async () => performCleanup())
812
+ .build();
703
813
 
704
- const internalEvent = event({
705
- id: "app.events.internal",
706
- tags: [globals.tags.excludeFromGlobalHooks], // Won't trigger wildcard listeners
707
- });
814
+ const internalEvent = r
815
+ .event("app.events.internal")
816
+ .tags([globals.tags.excludeFromGlobalHooks]) // Won't trigger wildcard listeners
817
+ .build();
708
818
  ```
709
819
 
710
820
  #### Contract Tags
@@ -713,15 +823,15 @@ Enforce return value shapes at compile time:
713
823
 
714
824
  ```typescript
715
825
  // Tags that enforce type contracts
716
- const userContract = tag<void, void, { name: string }>({
717
- id: "contract.user",
718
- });
826
+ const userContract = r
827
+ .tag<void, void, { name: string }>("contract.user")
828
+ .build();
719
829
 
720
- const profileTask = task({
721
- id: "app.tasks.getProfile",
722
- tags: [userContract], // Must return { name: string }
723
- run: async () => ({ name: "Ada" }), // ✅ Satisfies contract
724
- });
830
+ const profileTask = r
831
+ .task("app.tasks.getProfile")
832
+ .tags([userContract]) // Must return { name: string }
833
+ .run(async () => ({ name: "Ada" })) // ✅ Satisfies contract
834
+ .build();
725
835
  ```
726
836
 
727
837
  ## run() and RunOptions
@@ -731,24 +841,18 @@ The `run()` function boots a root `resource` and returns a `RunResult` handle to
731
841
  Basic usage:
732
842
 
733
843
  ```ts
734
- import { resource, task } from "@bluelibs/runner";
735
- import { run } from "@bluelibs/runner";
844
+ import { r, run } from "@bluelibs/runner";
736
845
 
737
- const ping = task({
738
- id: "ping.task",
739
- async run() {
740
- return "pong";
741
- },
742
- });
846
+ const ping = r
847
+ .task("ping.task")
848
+ .run(async () => "pong")
849
+ .build();
743
850
 
744
- const app = resource({
745
- id: "app",
746
- register: [ping],
747
- async init(_, {}) {
748
- // your boot logic here
749
- return "ready";
750
- },
751
- });
851
+ const app = r
852
+ .resource("app")
853
+ .register([ping])
854
+ .init(async () => "ready")
855
+ .build();
752
856
 
753
857
  const result = await run(app);
754
858
  console.log(result.value); // "ready"
@@ -829,22 +933,20 @@ _Resources can dynamically modify task behavior during initialization_
829
933
  Task interceptors (`task.intercept()`) are the modern replacement for component lifecycle events, allowing resources to dynamically modify task behavior without tight coupling.
830
934
 
831
935
  ```typescript
832
- import { task, resource, run } from "@bluelibs/runner";
936
+ import { r, run } from "@bluelibs/runner";
833
937
 
834
- const calculatorTask = task({
835
- id: "app.tasks.calculator",
836
- run: async (input: { value: number }) => {
938
+ const calculatorTask = r
939
+ .task("app.tasks.calculator")
940
+ .run(async ({ input }: { input: { value: number } }) => {
837
941
  console.log("3. Task is running...");
838
942
  return { result: input.value + 1 };
839
- },
840
- });
943
+ })
944
+ .build();
841
945
 
842
- const interceptorResource = resource({
843
- id: "app.interceptor",
844
- dependencies: {
845
- calculatorTask,
846
- },
847
- init: async (_, { calculatorTask }) => {
946
+ const interceptorResource = r
947
+ .resource("app.interceptor")
948
+ .dependencies({ calculatorTask })
949
+ .init(async (_config, { calculatorTask }) => {
848
950
  // Intercept the task to modify its behavior
849
951
  calculatorTask.intercept(async (next, input) => {
850
952
  console.log("1. Interceptor before task run");
@@ -852,20 +954,20 @@ const interceptorResource = resource({
852
954
  console.log("4. Interceptor after task run");
853
955
  return { ...result, intercepted: true };
854
956
  });
855
- },
856
- });
957
+ })
958
+ .build();
857
959
 
858
- const app = resource({
859
- id: "app",
860
- register: [calculatorTask, interceptorResource],
861
- dependencies: { calculatorTask },
862
- init: async (_, { calculatorTask }) => {
960
+ const app = r
961
+ .resource("app")
962
+ .register([calculatorTask, interceptorResource])
963
+ .dependencies({ calculatorTask })
964
+ .init(async (_config, { calculatorTask }) => {
863
965
  console.log("2. Calling the task...");
864
966
  const result = await calculatorTask({ value: 10 });
865
967
  console.log("5. Final result:", result);
866
968
  // Final result: { result: 11, intercepted: true }
867
- },
868
- });
969
+ })
970
+ .build();
869
971
 
870
972
  await run(app);
871
973
  ```
@@ -881,24 +983,26 @@ Sometimes you want your application to gracefully handle missing dependencies in
881
983
  Keep in mind that you have full control over dependency registration by functionalising `dependencies(config) => ({ ... })` and `register(config) => []`.
882
984
 
883
985
  ```typescript
884
- const emailService = resource({
885
- id: "app.services.email",
886
- init: async () => new EmailService(),
887
- });
986
+ import { r } from "@bluelibs/runner";
888
987
 
889
- const paymentService = resource({
890
- id: "app.services.payment",
891
- init: async () => new PaymentService(),
892
- });
988
+ const emailService = r
989
+ .resource("app.services.email")
990
+ .init(async () => new EmailService())
991
+ .build();
893
992
 
894
- const userRegistration = task({
895
- id: "app.tasks.registerUser",
896
- dependencies: {
993
+ const paymentService = r
994
+ .resource("app.services.payment")
995
+ .init(async () => new PaymentService())
996
+ .build();
997
+
998
+ const userRegistration = r
999
+ .task("app.tasks.registerUser")
1000
+ .dependencies({
897
1001
  database: userDatabase, // Required - will fail if not available
898
1002
  emailService: emailService.optional(), // Optional - won't fail if missing
899
1003
  analytics: analyticsService.optional(), // Optional - graceful degradation
900
- },
901
- run: async (userData, { database, emailService, analytics }) => {
1004
+ })
1005
+ .run(async ({ input }, { database, emailService, analytics }) => {
902
1006
  // Create user (required)
903
1007
  const user = await database.users.create(userData);
904
1008
 
@@ -939,37 +1043,98 @@ const userRegistration = task({
939
1043
  Ever tried to pass user data through 15 function calls? Yeah, we've been there. Context fixes that without turning your code into a game of telephone. This is very different from the Private Context from resources.
940
1044
 
941
1045
  ```typescript
1046
+ import { r } from "@bluelibs/runner";
1047
+
942
1048
  const UserContext = createContext<{ userId: string; role: string }>(
943
1049
  "app.userContext",
944
1050
  );
945
1051
 
946
- const getUserData = task({
947
- id: "app.tasks.getUserData",
948
- middleware: [UserContext.require()], // This is a middleware that ensures the context is available before task runs, throws if not.
949
- run: async () => {
1052
+ const getUserData = r
1053
+ .task("app.tasks.getUserData")
1054
+ .middleware([UserContext.require()]) // ensure context is available
1055
+ .run(async () => {
950
1056
  const user = UserContext.use(); // Available anywhere in the async chain
951
1057
  return `Current user: ${user.userId} (${user.role})`;
952
- },
953
- });
1058
+ })
1059
+ .build();
954
1060
 
955
1061
  // Provide context at the entry point
956
- const handleRequest = resource({
957
- id: "app.requestHandler",
958
- init: async () => {
1062
+ const handleRequest = r
1063
+ .resource("app.requestHandler")
1064
+ .init(async () => {
959
1065
  return UserContext.provide({ userId: "123", role: "admin" }, async () => {
960
1066
  // All tasks called within this scope have access to UserContext
961
1067
  return await getUserData();
962
1068
  });
963
- },
1069
+ })
1070
+ .build();
1071
+ ```
1072
+
1073
+ ## Fluent Builders (`r.*`)
1074
+
1075
+ For a more ergonomic and chainable way to define your components, Runner offers a fluent builder API under the `r` namespace. These builders are fully type-safe, improve readability for complex definitions, and compile to the standard Runner definitions with zero runtime overhead.
1076
+
1077
+ Here’s a quick taste of how it looks, with and without `zod` for validation:
1078
+
1079
+ ```typescript
1080
+ import { r, run } from "@bluelibs/runner";
1081
+ import { z } from "zod";
1082
+
1083
+ // With Zod, the config type is inferred automatically
1084
+ const emailerConfigSchema = z.object({
1085
+ smtpUrl: z.string().url(),
1086
+ from: z.string().email(),
964
1087
  });
1088
+
1089
+ const emailer = r
1090
+ .resource("app.emailer")
1091
+ .configSchema(emailerConfigSchema)
1092
+ .init(async ({ config }) => ({
1093
+ send: (to: string, body: string) => {
1094
+ console.log(
1095
+ `Sending from ${config.from} to ${to} via ${config.smtpUrl}: ${body}`,
1096
+ );
1097
+ },
1098
+ }))
1099
+ .build();
1100
+
1101
+ // Without a schema library, you can provide the type explicitly
1102
+ const greeter = r
1103
+ .resource("app.greeter")
1104
+ .init(async (cfg: { name: string }) => ({
1105
+ greet: () => `Hello, ${cfg.name}!`,
1106
+ }))
1107
+ .build();
1108
+
1109
+ const app = r
1110
+ .resource("app")
1111
+ .register([
1112
+ emailer.with({
1113
+ smtpUrl: "smtp://example.com",
1114
+ from: "noreply@example.com",
1115
+ }),
1116
+ greeter.with({ name: "World" }),
1117
+ ])
1118
+ .dependencies({ emailer, greeter })
1119
+ .init(async (_, { emailer, greeter }) => {
1120
+ console.log(greeter.greet());
1121
+ emailer.send("test@example.com", "This is a test.");
1122
+ })
1123
+ .build();
1124
+
1125
+ await run(app);
965
1126
  ```
966
1127
 
1128
+ The builder API provides a clean, step-by-step way to construct everything from simple tasks to complex resources with middleware, tags, and schemas.
1129
+
1130
+ For a complete guide and more examples, check out the [full Fluent Builders documentation](./readmes/FLUENT_BUILDERS.md).
1131
+
967
1132
  ## Type Helpers
968
1133
 
969
1134
  These utility types help you extract the generics from tasks, resources, and events without re-declaring them. Import them from `@bluelibs/runner`.
970
1135
 
971
1136
  ```ts
972
- import { task, resource, event } from "@bluelibs/runner";
1137
+ import { r } from "@bluelibs/runner";
973
1138
  import type {
974
1139
  ExtractTaskInput,
975
1140
  ExtractTaskOutput,
@@ -979,27 +1144,30 @@ import type {
979
1144
  } from "@bluelibs/runner";
980
1145
 
981
1146
  // Task example
982
- const add = task({
983
- id: "calc.add",
984
- run: async (input: { a: number; b: number }) => input.a + input.b,
985
- });
1147
+ const add = r
1148
+ .task("calc.add")
1149
+ .run(
1150
+ async ({ input }: { input: { a: number; b: number } }) => input.a + input.b,
1151
+ )
1152
+ .build();
986
1153
 
987
1154
  type AddInput = ExtractTaskInput<typeof add>; // { a: number; b: number }
988
1155
  type AddOutput = ExtractTaskOutput<typeof add>; // number
989
1156
 
990
1157
  // Resource example
991
- const config = resource({
992
- id: "app.config",
993
- init: async (cfg: { baseUrl: string }) => ({ baseUrl: cfg.baseUrl }),
994
- });
1158
+ const config = r
1159
+ .resource("app.config")
1160
+ .init(async (cfg: { baseUrl: string }) => ({ baseUrl: cfg.baseUrl }))
1161
+ .build();
995
1162
 
996
1163
  type ConfigInput = ExtractResourceConfig<typeof config>; // { baseUrl: string }
997
1164
  type ConfigValue = ExtractResourceValue<typeof config>; // { baseUrl: string }
998
1165
 
999
1166
  // Event example
1000
- const userRegistered = event<{ userId: string; email: string }>({
1001
- id: "app.events.userRegistered",
1002
- });
1167
+ const userRegistered = r
1168
+ .event("app.events.userRegistered")
1169
+ .payloadSchema<{ userId: string; email: string }>({ parse: (v) => v })
1170
+ .build();
1003
1171
  type UserRegisteredPayload = ExtractEventPayload<typeof userRegistered>; // { userId: string; email: string }
1004
1172
  ```
1005
1173
 
@@ -1034,15 +1202,15 @@ const requestMiddleware = taskMiddleware({
1034
1202
  },
1035
1203
  });
1036
1204
 
1037
- const handleRequest = task({
1038
- id: "app.handleRequest",
1039
- middleware: [requestMiddleware],
1040
- run: async (input: { path: string }) => {
1205
+ const handleRequest = r
1206
+ .task("app.handleRequest")
1207
+ .middleware([requestMiddleware])
1208
+ .run(async ({ input }: { input: { path: string } }) => {
1041
1209
  const request = RequestContext.use();
1042
1210
  console.log(`Processing ${input.path} (Request ID: ${request.requestId})`);
1043
1211
  return { success: true, requestId: request.requestId };
1044
- },
1045
- });
1212
+ })
1213
+ .build();
1046
1214
  ```
1047
1215
 
1048
1216
  > **runtime:** "Context: global state with manners. You invented a teleporting clipboard for data and called it 'nice.' Forget to `provide()` once and I’ll unleash the 'Context not available' banshee scream exactly where your logs are least helpful."
@@ -1070,36 +1238,36 @@ process.on("SIGTERM", async () => {
1070
1238
  });
1071
1239
 
1072
1240
  // Resources with cleanup logic
1073
- const databaseResource = resource({
1074
- id: "app.database",
1075
- init: async () => {
1241
+ const databaseResource = r
1242
+ .resource("app.database")
1243
+ .init(async () => {
1076
1244
  const connection = await connectToDatabase();
1077
1245
  console.log("Database connected");
1078
1246
  return connection;
1079
- },
1080
- dispose: async (connection) => {
1247
+ })
1248
+ .dispose(async (connection) => {
1081
1249
  await connection.close();
1082
1250
  // console.log("Database connection closed");
1083
- },
1084
- });
1251
+ })
1252
+ .build();
1085
1253
 
1086
- const serverResource = resource({
1087
- id: "app.server",
1088
- dependencies: { database: databaseResource },
1089
- init: async (config, { database }) => {
1254
+ const serverResource = r
1255
+ .resource("app.server")
1256
+ .dependencies({ database: databaseResource })
1257
+ .init(async (config: { port: number }, { database }) => {
1090
1258
  const server = express().listen(config.port);
1091
1259
  console.log(`Server listening on port ${config.port}`);
1092
1260
  return server;
1093
- },
1094
- dispose: async (server) => {
1095
- return new Promise((resolve) => {
1261
+ })
1262
+ .dispose(async (server) => {
1263
+ return new Promise<void>((resolve) => {
1096
1264
  server.close(() => {
1097
1265
  console.log("Server closed");
1098
1266
  resolve();
1099
1267
  });
1100
1268
  });
1101
- },
1102
- });
1269
+ })
1270
+ .build();
1103
1271
  ```
1104
1272
 
1105
1273
  ### Error Boundary Integration
@@ -1178,25 +1346,25 @@ Because nobody likes waiting for the same expensive operation twice:
1178
1346
  ```typescript
1179
1347
  import { globals } from "@bluelibs/runner";
1180
1348
 
1181
- const expensiveTask = task({
1182
- id: "app.tasks.expensive",
1183
- middleware: [
1349
+ const expensiveTask = r
1350
+ .task("app.tasks.expensive")
1351
+ .middleware([
1184
1352
  globals.middleware.task.cache.with({
1185
1353
  // lru-cache options by default
1186
1354
  ttl: 60 * 1000, // Cache for 1 minute
1187
- keyBuilder: (taskId, input) => `${taskId}-${input.userId}`, // optional key builder
1355
+ keyBuilder: (taskId, input) => `${taskId}-${(input as any).userId}`, // optional key builder
1188
1356
  }),
1189
- ],
1190
- run: async ({ userId }) => {
1357
+ ])
1358
+ .run(async ({ input }: { input: { userId: string } }) => {
1191
1359
  // This expensive operation will be cached
1192
- return await doExpensiveCalculation(userId);
1193
- },
1360
+ return await doExpensiveCalculation(input.userId);
1361
+ })
1194
1362
  });
1195
1363
 
1196
1364
  // Global cache configuration
1197
- const app = resource({
1198
- id: "app.cache",
1199
- register: [
1365
+ const app = r
1366
+ .resource("app.cache")
1367
+ .register([
1200
1368
  // You have to register it, cache resource is not enabled by default.
1201
1369
  globals.resources.cache.with({
1202
1370
  defaultOptions: {
@@ -1204,27 +1372,25 @@ const app = resource({
1204
1372
  ttl: 30 * 1000, // Default TTL
1205
1373
  },
1206
1374
  }),
1207
- ],
1208
- });
1375
+ ])
1376
+ .build();
1209
1377
  ```
1210
1378
 
1211
1379
  Want Redis instead of the default LRU cache? No problem, just override the cache factory task:
1212
1380
 
1213
1381
  ```typescript
1214
- import { task } from "@bluelibs/runner";
1382
+ import { r } from "@bluelibs/runner";
1215
1383
 
1216
- const redisCacheFactory = task({
1217
- id: "globals.tasks.cacheFactory", // Same ID as the default task
1218
- run: async (options: any) => {
1219
- return new RedisCache(options);
1220
- },
1221
- });
1384
+ const redisCacheFactory = r
1385
+ .task("globals.tasks.cacheFactory") // Same ID as the default task
1386
+ .run(async ({ input }: { input: any }) => new RedisCache(input))
1387
+ .build();
1222
1388
 
1223
- const app = resource({
1224
- id: "app",
1225
- register: [globals.resources.cache],
1226
- overrides: [redisCacheFactory], // Override the default cache factory
1227
- });
1389
+ const app = r
1390
+ .resource("app")
1391
+ .register([globals.resources.cache])
1392
+ .overrides([redisCacheFactory]) // Override the default cache factory
1393
+ .build();
1228
1394
  ```
1229
1395
 
1230
1396
  > **runtime:** "'Because nobody likes waiting.' Correct. You keep asking the same question like a parrot with Wi‑Fi, so I built a memory palace. Now you get instant answers until you change one variable and whisper 'cache invalidation' like a curse."
@@ -1259,13 +1425,11 @@ Here are real performance metrics from our comprehensive benchmark suite on an M
1259
1425
 
1260
1426
  ```typescript
1261
1427
  // This executes in ~0.005ms on average
1262
- const userTask = task({
1263
- id: "user.create",
1264
- middleware: [auth, logging, metrics],
1265
- run: async (userData) => {
1266
- return database.users.create(userData);
1267
- },
1268
- });
1428
+ const userTask = r
1429
+ .task("user.create")
1430
+ .middleware([auth, logging, metrics])
1431
+ .run(async ({ input }) => database.users.create(input as any))
1432
+ .build();
1269
1433
 
1270
1434
  // 1000 executions = ~5ms total time
1271
1435
  for (let i = 0; i < 1000; i++) {
@@ -1294,37 +1458,41 @@ for (let i = 0; i < 1000; i++) {
1294
1458
  **Middleware Ordering**: Place faster middleware first
1295
1459
 
1296
1460
  ```typescript
1297
- const task = task({
1461
+ const task = r
1462
+ .task("app.performance.example")
1298
1463
  middleware: [
1299
1464
  fastAuthCheck, // ~0.1ms
1300
1465
  slowRateLimiting, // ~2ms
1301
1466
  expensiveLogging, // ~5ms
1302
1467
  ],
1303
- });
1468
+ .run(async () => null)
1469
+ .build();
1304
1470
  ```
1305
1471
 
1306
1472
  **Resource Reuse**: Resources are singletons—perfect for expensive setup
1307
1473
 
1308
1474
  ```typescript
1309
- const database = resource({
1310
- init: async () => {
1475
+ const database = r
1476
+ .resource("app.performance.db")
1477
+ .init(async () => {
1311
1478
  // Expensive connection setup happens once
1312
1479
  const connection = await createDbConnection();
1313
1480
  return connection;
1314
- },
1315
- });
1481
+ })
1482
+ .build();
1316
1483
  ```
1317
1484
 
1318
1485
  **Cache Strategically**: Use built-in caching for expensive operations
1319
1486
 
1320
1487
  ```typescript
1321
- const expensiveTask = task({
1322
- middleware: [globals.middleware.cache.with({ ttl: 60000 })],
1323
- run: async (input) => {
1488
+ const expensiveTask = r
1489
+ .task("app.performance.expensive")
1490
+ .middleware([globals.middleware.cache.with({ ttl: 60000 })])
1491
+ .run(async ({ input }) => {
1324
1492
  // This expensive computation is cached
1325
1493
  return performExpensiveCalculation(input);
1326
- },
1327
- });
1494
+ })
1495
+ .build();
1328
1496
  ```
1329
1497
 
1330
1498
  #### Memory Considerations
@@ -1386,25 +1554,22 @@ For when things go wrong, but you know they'll probably work if you just try aga
1386
1554
  ```typescript
1387
1555
  import { globals } from "@bluelibs/runner";
1388
1556
 
1389
- const flakyApiCall = task({
1390
- id: "app.tasks.flakyApiCall",
1391
- middleware: [
1557
+ const flakyApiCall = r
1558
+ .task("app.tasks.flakyApiCall")
1559
+ .middleware([
1392
1560
  globals.middleware.task.retry.with({
1393
1561
  retries: 5, // Try up to 5 times
1394
1562
  delayStrategy: (attempt) => 100 * Math.pow(2, attempt), // Exponential backoff
1395
1563
  stopRetryIf: (error) => error.message === "Invalid credentials", // Don't retry auth errors
1396
1564
  }),
1397
- ],
1398
- run: async () => {
1565
+ ])
1566
+ .run(async () => {
1399
1567
  // This might fail due to network issues, rate limiting, etc.
1400
1568
  return await fetchFromUnreliableService();
1401
- },
1402
- });
1569
+ })
1570
+ .build();
1403
1571
 
1404
- const app = resource({
1405
- id: "app",
1406
- register: [flakyApiCall],
1407
- });
1572
+ const app = r.resource("app").register([flakyApiCall]).build();
1408
1573
  ```
1409
1574
 
1410
1575
  The retry middleware can be configured with:
@@ -1423,22 +1588,22 @@ timeout. Works for resources and tasks.
1423
1588
  ```typescript
1424
1589
  import { globals } from "@bluelibs/runner";
1425
1590
 
1426
- const apiTask = task({
1427
- id: "app.tasks.externalApi",
1428
- middleware: [
1591
+ const apiTask = r
1592
+ .task("app.tasks.externalApi")
1593
+ .middleware([
1429
1594
  // Works for tasks and resources via globals.middleware.resource.timeout
1430
1595
  globals.middleware.task.timeout.with({ ttl: 5000 }), // 5 second timeout
1431
- ],
1432
- run: async () => {
1596
+ ])
1597
+ .run(async () => {
1433
1598
  // This operation will be aborted if it takes longer than 5 seconds
1434
1599
  return await fetch("https://slow-api.example.com/data");
1435
- },
1436
- });
1600
+ })
1601
+ .build();
1437
1602
 
1438
1603
  // Combine with retry for robust error handling
1439
- const resilientTask = task({
1440
- id: "app.tasks.resilient",
1441
- middleware: [
1604
+ const resilientTask = r
1605
+ .task("app.tasks.resilient")
1606
+ .middleware([
1442
1607
  // Order matters here. Imagine a big onion.
1443
1608
  // Works for resources as well via globals.middleware.resource.retry
1444
1609
  globals.middleware.task.retry.with({
@@ -1446,12 +1611,12 @@ const resilientTask = task({
1446
1611
  delayStrategy: (attempt) => 1000 * attempt, // 1s, 2s, 3s delays
1447
1612
  }),
1448
1613
  globals.middleware.task.timeout.with({ ttl: 10000 }), // 10 second timeout per attempt
1449
- ],
1450
- run: async () => {
1614
+ ])
1615
+ .run(async () => {
1451
1616
  // Each retry attempt gets its own 10-second timeout
1452
1617
  return await unreliableOperation();
1453
- },
1454
- });
1618
+ })
1619
+ .build();
1455
1620
  ```
1456
1621
 
1457
1622
  How it works:
@@ -1480,14 +1645,12 @@ BlueLibs Runner comes with a built-in logging system that's structured, and does
1480
1645
  ### Basic Logging
1481
1646
 
1482
1647
  ```ts
1483
- import { resource, globals } from "@bluelibs/runner";
1648
+ import { r, globals } from "@bluelibs/runner";
1484
1649
 
1485
- const app = resource({
1486
- id: "app",
1487
- dependencies: {
1488
- logger: globals.resources.logger;
1489
- },
1490
- init: async () => {
1650
+ const app = r
1651
+ .resource("app")
1652
+ .dependencies({ logger: globals.resources.logger })
1653
+ .init(async (_config, { logger }) => {
1491
1654
  logger.info("Starting business process"); // ✅ Visible by default
1492
1655
  logger.warn("This might take a while"); // ✅ Visible by default
1493
1656
  logger.error("Oops, something went wrong", {
@@ -1504,9 +1667,9 @@ const app = resource({
1504
1667
  logger.onLog(async (log) => {
1505
1668
  // Sub-loggers instantiated .with() share the same log listeners.
1506
1669
  // Catch logs
1507
- })
1508
- },
1509
- })
1670
+ });
1671
+ })
1672
+ .build();
1510
1673
 
1511
1674
  run(app, {
1512
1675
  logs: {
@@ -1545,10 +1708,10 @@ logger.critical("DEFCON 1: Everything is broken");
1545
1708
  The logger accepts rich, structured data that makes debugging actually useful:
1546
1709
 
1547
1710
  ```typescript
1548
- const userTask = task({
1549
- id: "app.tasks.user.create",
1550
- dependencies: { logger: globals.resources.logger },
1551
- run: async (userData, { logger }) => {
1711
+ const userTask = r
1712
+ .task("app.tasks.user.create")
1713
+ .dependencies({ logger: globals.resources.logger })
1714
+ .run(async ({ input }, { logger }) => {
1552
1715
  // Basic message
1553
1716
  logger.info("Creating new user");
1554
1717
 
@@ -1556,7 +1719,7 @@ const userTask = task({
1556
1719
  logger.info("User creation attempt", {
1557
1720
  source: userTask.id,
1558
1721
  data: {
1559
- email: userData.email,
1722
+ email: (input as any).email,
1560
1723
  registrationSource: "web",
1561
1724
  timestamp: new Date().toISOString(),
1562
1725
  },
@@ -1564,7 +1727,7 @@ const userTask = task({
1564
1727
 
1565
1728
  // With error information
1566
1729
  try {
1567
- const user = await createUser(userData);
1730
+ const user = await createUser(input as any);
1568
1731
  logger.info("User created successfully", {
1569
1732
  data: { userId: user.id, email: user.email },
1570
1733
  });
@@ -1572,13 +1735,13 @@ const userTask = task({
1572
1735
  logger.error("User creation failed", {
1573
1736
  error,
1574
1737
  data: {
1575
- attemptedEmail: userData.email,
1738
+ attemptedEmail: (input as any).email,
1576
1739
  validationErrors: error.validationErrors,
1577
1740
  },
1578
1741
  });
1579
1742
  }
1580
- },
1581
- });
1743
+ })
1744
+ .build();
1582
1745
  ```
1583
1746
 
1584
1747
  ### Context-Aware Logging
@@ -1590,10 +1753,10 @@ const RequestContext = createContext<{ requestId: string; userId: string }>(
1590
1753
  "app.requestContext",
1591
1754
  );
1592
1755
 
1593
- const requestHandler = task({
1594
- id: "app.tasks.handleRequest",
1595
- dependencies: { logger: globals.resources.logger },
1596
- run: async (requestData, { logger }) => {
1756
+ const requestHandler = r
1757
+ .task("app.tasks.handleRequest")
1758
+ .dependencies({ logger: globals.resources.logger })
1759
+ .run(async ({ input: requestData }, { logger }) => {
1597
1760
  const request = RequestContext.use();
1598
1761
 
1599
1762
  // Create a contextual logger with bound metadata with source and context
@@ -1619,8 +1782,8 @@ const requestHandler = task({
1619
1782
  error: new Error("Invalid input"),
1620
1783
  data: { stage: "validation" },
1621
1784
  });
1622
- },
1623
- });
1785
+ })
1786
+ .build();
1624
1787
  ```
1625
1788
 
1626
1789
  ### Integration with Winston
@@ -1629,7 +1792,7 @@ Want to use Winston as your transport? No problem - integrate it seamlessly:
1629
1792
 
1630
1793
  ```typescript
1631
1794
  import winston from "winston";
1632
- import { resource, globals } from "@bluelibs/runner";
1795
+ import { r, globals } from "@bluelibs/runner";
1633
1796
 
1634
1797
  // Create Winston logger, put it in a resource if used from various places.
1635
1798
  const winstonLogger = winston.createLogger({
@@ -1649,12 +1812,10 @@ const winstonLogger = winston.createLogger({
1649
1812
  });
1650
1813
 
1651
1814
  // Bridge BlueLibs logs to Winston using hooks
1652
- const winstonBridgeResource = resource({
1653
- id: "app.resources.winstonBridge",
1654
- dependencies: {
1655
- logger: globals.resources.logger,
1656
- },
1657
- init: async (_, { logger }) => {
1815
+ const winstonBridgeResource = r
1816
+ .resource("app.resources.winstonBridge")
1817
+ .dependencies({ logger: globals.resources.logger })
1818
+ .init(async (_config, { logger }) => {
1658
1819
  // Map log levels (BlueLibs -> Winston)
1659
1820
  const levelMapping = {
1660
1821
  trace: "silly",
@@ -1678,8 +1839,8 @@ const winstonBridgeResource = resource({
1678
1839
  const winstonLevel = levelMapping[log.level] || "info";
1679
1840
  winstonLogger.log(winstonLevel, log.message, winstonMeta);
1680
1841
  });
1681
- },
1682
- });
1842
+ })
1843
+ .build();
1683
1844
  ```
1684
1845
 
1685
1846
  ### Custom Log Formatters
@@ -1709,13 +1870,11 @@ class JSONLogger extends Logger {
1709
1870
  }
1710
1871
 
1711
1872
  // Custom logger resource
1712
- const customLogger = resource({
1713
- id: "app.logger.custom",
1714
- dependencies: { eventManager: globals.resources.eventManager },
1715
- init: async (_, { eventManager }) => {
1716
- return new JSONLogger(eventManager);
1717
- },
1718
- });
1873
+ const customLogger = r
1874
+ .resource("app.logger.custom")
1875
+ .dependencies({ eventManager: globals.resources.eventManager })
1876
+ .init(async (_config, { eventManager }) => new JSONLogger(eventManager))
1877
+ .build();
1719
1878
 
1720
1879
  // Or you could simply add it as "globals.resources.logger" and override the default logger
1721
1880
  ```
@@ -1776,9 +1935,9 @@ run(app, { debug: "verbose" });
1776
1935
  **Custom Configuration**:
1777
1936
 
1778
1937
  ```typescript
1779
- const app = resource({
1780
- id: "app",
1781
- register: [
1938
+ const app = r
1939
+ .resource("app")
1940
+ .register([
1782
1941
  globals.resources.debug.with({
1783
1942
  logTaskInput: true,
1784
1943
  logTaskResult: false,
@@ -1789,8 +1948,8 @@ const app = resource({
1789
1948
  // Hook/middleware lifecycle visibility is available via interceptors
1790
1949
  // ... other fine-grained options
1791
1950
  }),
1792
- ],
1793
- });
1951
+ ])
1952
+ .build();
1794
1953
  ```
1795
1954
 
1796
1955
  ### Per-Component Debug Configuration
@@ -1800,20 +1959,20 @@ Use debug tags to configure debugging on individual components, when you're inte
1800
1959
  ```typescript
1801
1960
  import { globals } from "@bluelibs/runner";
1802
1961
 
1803
- const criticalTask = task({
1804
- id: "app.tasks.critical",
1805
- tags: [
1962
+ const criticalTask = r
1963
+ .task("app.tasks.critical")
1964
+ .tags([
1806
1965
  globals.tags.debug.with({
1807
1966
  logTaskInput: true,
1808
1967
  logTaskResult: true,
1809
1968
  logTaskOnError: true,
1810
1969
  }),
1811
- ],
1812
- run: async (input) => {
1970
+ ])
1971
+ .run(async ({ input }) => {
1813
1972
  // This task will have verbose debug logging
1814
- return await processPayment(input);
1815
- },
1816
- });
1973
+ return await processPayment(input as any);
1974
+ })
1975
+ .build();
1817
1976
  ```
1818
1977
 
1819
1978
  ### Integration with Run Options
@@ -1929,35 +2088,35 @@ interface IMeta {
1929
2088
  ### Simple Documentation Example
1930
2089
 
1931
2090
  ```typescript
1932
- const userService = resource({
1933
- id: "app.services.user",
1934
- meta: {
2091
+ const userService = r
2092
+ .resource("app.services.user")
2093
+ .meta({
1935
2094
  title: "User Management Service",
1936
2095
  description:
1937
2096
  "Handles user creation, authentication, and profile management",
1938
- },
1939
- dependencies: { database },
1940
- init: async (_, { database }) => ({
2097
+ })
2098
+ .dependencies({ database })
2099
+ .init(async (_config, { database }) => ({
1941
2100
  createUser: async (userData) => {
1942
2101
  /* ... */
1943
2102
  },
1944
2103
  authenticateUser: async (credentials) => {
1945
2104
  /* ... */
1946
2105
  },
1947
- }),
1948
- });
2106
+ }))
2107
+ .build();
1949
2108
 
1950
- const sendWelcomeEmail = task({
1951
- id: "app.tasks.sendWelcomeEmail",
1952
- meta: {
2109
+ const sendWelcomeEmail = r
2110
+ .task("app.tasks.sendWelcomeEmail")
2111
+ .meta({
1953
2112
  title: "Send Welcome Email",
1954
2113
  description: "Sends a welcome email to newly registered users",
1955
- },
1956
- dependencies: { emailService },
1957
- run: async (userData, { emailService }) => {
2114
+ } as any)
2115
+ .dependencies({ emailService })
2116
+ .run(async ({ input: userData }, { emailService }) => {
1958
2117
  // Email sending logic
1959
- },
1960
- });
2118
+ })
2119
+ .build();
1961
2120
  ```
1962
2121
 
1963
2122
  ### Extending Metadata: Custom Properties
@@ -1983,31 +2142,31 @@ declare module "@bluelibs/runner" {
1983
2142
  }
1984
2143
 
1985
2144
  // Now use your custom properties
1986
- const expensiveApiTask = task({
1987
- id: "app.tasks.ai.generateImage",
1988
- meta: {
2145
+ const expensiveApiTask = r
2146
+ .task("app.tasks.ai.generateImage")
2147
+ .meta({
1989
2148
  title: "AI Image Generation",
1990
2149
  description: "Uses OpenAI DALL-E to generate images from text prompts",
1991
2150
  author: "AI Team",
1992
2151
  version: "2.1.0",
1993
2152
  apiVersion: "v2",
1994
2153
  costLevel: "high", // Custom property!
1995
- },
1996
- run: async (prompt) => {
2154
+ } as any)
2155
+ .run(async ({ input: prompt }) => {
1997
2156
  // AI generation logic
1998
- },
1999
- });
2157
+ })
2158
+ .build();
2000
2159
 
2001
- const database = resource({
2002
- id: "app.database.primary",
2003
- meta: {
2160
+ const database = r
2161
+ .resource("app.database.primary")
2162
+ .meta({
2004
2163
  title: "Primary PostgreSQL Database",
2005
2164
  healthCheck: "/health/db", // Custom property!
2006
2165
  dependencies: ["postgresql", "connection-pool"],
2007
2166
  scalingPolicy: "auto",
2008
- },
2009
- // ... implementation
2010
- });
2167
+ } as any)
2168
+ // .init(async () => { /* ... */ })
2169
+ .build();
2011
2170
  ```
2012
2171
 
2013
2172
  Metadata transforms your components from anonymous functions into self-documenting, discoverable, and controllable building blocks. Use it wisely, and your future self (and your team) will thank you.
@@ -2021,10 +2180,10 @@ Sometimes you need to replace a component entirely. Maybe you're doing integrati
2021
2180
  You can now use a dedicated helper `override()` to safely override any property on tasks, resources, or middleware — except `id`. This ensures the identity is preserved, while allowing behavior changes.
2022
2181
 
2023
2182
  ```typescript
2024
- const productionEmailer = resource({
2025
- id: "app.emailer",
2026
- init: async () => new SMTPEmailer(),
2027
- });
2183
+ const productionEmailer = r
2184
+ .resource("app.emailer")
2185
+ .init(async () => new SMTPEmailer())
2186
+ .build();
2028
2187
 
2029
2188
  // Option 1: Using override() to change behavior while preserving id (Recommended)
2030
2189
  const testEmailer = override(productionEmailer, {
@@ -2033,27 +2192,33 @@ const testEmailer = override(productionEmailer, {
2033
2192
 
2034
2193
  // Option 2: The system is really flexible, and override is just bringing in type safety, nothing else under the hood.
2035
2194
  // Using spread operator works the same way but does not provide type-safety.
2036
- const testEmailer = resource({
2037
- ...productionEmailer,
2038
- init: async () => {},
2039
- });
2195
+ const testEmailer = r
2196
+ .resource("app.emailer")
2197
+ .init(async () => ({} as any))
2198
+ .build();
2040
2199
 
2041
- const app = resource({
2042
- id: "app",
2043
- register: [productionEmailer],
2044
- overrides: [testEmailer], // This replaces the production version
2045
- });
2200
+ const app = r
2201
+ .resource("app")
2202
+ .register([productionEmailer])
2203
+ .overrides([testEmailer]) // This replaces the production version
2204
+ .build();
2046
2205
 
2047
2206
  import { override } from "@bluelibs/runner";
2048
2207
 
2049
2208
  // Tasks
2050
- const originalTask = task({ id: "app.tasks.compute", run: async () => 1 });
2209
+ const originalTask = r
2210
+ .task("app.tasks.compute")
2211
+ .run(async () => 1)
2212
+ .build();
2051
2213
  const overriddenTask = override(originalTask, {
2052
2214
  run: async () => 2,
2053
2215
  });
2054
2216
 
2055
2217
  // Resources
2056
- const originalResource = resource({ id: "app.db", init: async () => "conn" });
2218
+ const originalResource = r
2219
+ .resource("app.db")
2220
+ .init(async () => "conn")
2221
+ .build();
2057
2222
  const overriddenResource = override(originalResource, {
2058
2223
  init: async () => "mock-conn",
2059
2224
  });
@@ -2119,10 +2284,10 @@ function namespaced(id: string) {
2119
2284
  return `mycompany.myapp.${id}`;
2120
2285
  }
2121
2286
 
2122
- const userTask = task({
2123
- id: namespaced("tasks.user.create-user"),
2124
- // ...
2125
- });
2287
+ const userTask = r
2288
+ .task(namespaced("tasks.user.create-user"))
2289
+ .run(async () => null)
2290
+ .build();
2126
2291
  ```
2127
2292
 
2128
2293
  > **runtime:** "Naming conventions: aromatherapy for chaos. Lovely lavender labels on a single giant map I maintain anyway. But truly—keep the IDs tidy. Future‑you deserves at least this mercy."
@@ -2135,26 +2300,24 @@ To keep things dead simple, we avoided poluting the D.I. with this concept. Ther
2135
2300
  // Assume MyClass is defined elsewhere
2136
2301
  // class MyClass { constructor(input: any, option: string) { ... } }
2137
2302
 
2138
- const myFactory = resource({
2139
- id: "app.factories.myFactory",
2140
- init: async (config: { someOption: string }) => {
2303
+ const myFactory = r
2304
+ .resource("app.factories.myFactory")
2305
+ .init(async (config: { someOption: string }) => {
2141
2306
  // This resource's value is a factory function
2142
- return (input: any) => {
2143
- return new MyClass(input, config.someOption);
2144
- };
2145
- },
2146
- });
2307
+ return (input: any) => new MyClass(input, config.someOption);
2308
+ })
2309
+ .build();
2147
2310
 
2148
- const app = resource({
2149
- id: "app",
2311
+ const app = r
2312
+ .resource("app")
2150
2313
  // Configure the factory resource upon registration
2151
- register: [myFactory.with({ someOption: "configured-value" })],
2152
- dependencies: { myFactory },
2153
- init: async (_, { myFactory }) => {
2314
+ .register([myFactory.with({ someOption: "configured-value" })])
2315
+ .dependencies({ myFactory })
2316
+ .init(async (_config, { myFactory }) => {
2154
2317
  // `myFactory` is now the configured factory function
2155
2318
  const instance = myFactory({ someInput: "hello" });
2156
- },
2157
- });
2319
+ })
2320
+ .build();
2158
2321
  ```
2159
2322
 
2160
2323
  > **runtime:** "Factory by resource by function by class. A nesting doll of indirection so artisanal it has a Patreon. Not pollution—boutique smog. I will still call the constructor."
@@ -2192,20 +2355,20 @@ const userSchema = z.object({
2192
2355
  age: z.number().min(0).max(150),
2193
2356
  });
2194
2357
 
2195
- const createUserTask = task({
2196
- id: "app.tasks.createUser",
2197
- inputSchema: userSchema, // Works directly with Zod!
2198
- run: async (userData) => {
2358
+ const createUserTask = r
2359
+ .task("app.tasks.createUser")
2360
+ .inputSchema(userSchema) // Works directly with Zod!
2361
+ .run(async ({ input: userData }) => {
2199
2362
  // userData is validated and properly typed
2200
- return { id: "user-123", ...userData };
2201
- },
2202
- });
2363
+ return { id: "user-123", ...(userData as any) };
2364
+ })
2365
+ .build();
2203
2366
 
2204
- const app = resource({
2205
- id: "app",
2206
- register: [createUserTask],
2207
- dependencies: { createUserTask },
2208
- init: async (_, { createUserTask }) => {
2367
+ const app = r
2368
+ .resource("app")
2369
+ .register([createUserTask])
2370
+ .dependencies({ createUserTask })
2371
+ .init(async (_config, { createUserTask }) => {
2209
2372
  // This works - valid input
2210
2373
  const user = await createUserTask({
2211
2374
  name: "John Doe",
@@ -2224,8 +2387,8 @@ const app = resource({
2224
2387
  console.log(error.message);
2225
2388
  // "Task input validation failed for app.tasks.createUser: ..."
2226
2389
  }
2227
- },
2228
- });
2390
+ })
2391
+ .build();
2229
2392
  ```
2230
2393
 
2231
2394
  ### Resource Config Validation
@@ -2240,10 +2403,10 @@ const databaseConfigSchema = z.object({
2240
2403
  ssl: z.boolean().default(false), // Optional with default
2241
2404
  });
2242
2405
 
2243
- const databaseResource = resource({
2244
- id: "app.resources.database",
2245
- configSchema: databaseConfigSchema, // Validation on .with()
2246
- init: async (config) => {
2406
+ const databaseResource = r
2407
+ .resource("app.resources.database")
2408
+ .configSchema(databaseConfigSchema) // Validation on .with()
2409
+ .init(async (config) => {
2247
2410
  // config is already validated and has proper types
2248
2411
  return createConnection({
2249
2412
  host: config.host,
@@ -2251,8 +2414,8 @@ const databaseResource = resource({
2251
2414
  database: config.database,
2252
2415
  ssl: config.ssl,
2253
2416
  });
2254
- },
2255
- });
2417
+ })
2418
+ .build();
2256
2419
 
2257
2420
  // Validation happens here, not during init!
2258
2421
  try {
@@ -2265,17 +2428,17 @@ try {
2265
2428
  // "Resource config validation failed for app.resources.database: ..."
2266
2429
  }
2267
2430
 
2268
- const app = resource({
2269
- id: "app",
2270
- register: [
2431
+ const app = r
2432
+ .resource("app")
2433
+ .register([
2271
2434
  databaseResource.with({
2272
2435
  host: "localhost",
2273
2436
  port: 5432,
2274
2437
  database: "myapp",
2275
2438
  // ssl defaults to false
2276
2439
  }),
2277
- ],
2278
- });
2440
+ ])
2441
+ .build();
2279
2442
  ```
2280
2443
 
2281
2444
  ### Event Payload Validation
@@ -2289,25 +2452,25 @@ const userActionSchema = z.object({
2289
2452
  timestamp: z.date().default(() => new Date()),
2290
2453
  });
2291
2454
 
2292
- const userActionEvent = event({
2293
- id: "app.events.userAction",
2294
- payloadSchema: userActionSchema, // Validates on emit
2295
- });
2455
+ const userActionEvent = r
2456
+ .event("app.events.userAction")
2457
+ .payloadSchema(userActionSchema) // Validates on emit
2458
+ .build();
2296
2459
 
2297
- const notificationHook = hook({
2298
- id: "app.tasks.sendNotification",
2299
- on: userActionEvent,
2300
- run: async (eventData) => {
2460
+ const notificationHook = r
2461
+ .hook("app.tasks.sendNotification")
2462
+ .on(userActionEvent)
2463
+ .run(async (eventData) => {
2301
2464
  // eventData.data is validated and properly typed
2302
2465
  console.log(`User ${eventData.data.userId} was ${eventData.data.action}`);
2303
- },
2304
- });
2466
+ })
2467
+ .build();
2305
2468
 
2306
- const app = resource({
2307
- id: "app",
2308
- register: [userActionEvent, notificationHook],
2309
- dependencies: { userActionEvent },
2310
- init: async (_, { userActionEvent }) => {
2469
+ const app = r
2470
+ .resource("app")
2471
+ .register([userActionEvent, notificationHook])
2472
+ .dependencies({ userActionEvent })
2473
+ .init(async (_config, { userActionEvent }) => {
2311
2474
  // This works - valid payload
2312
2475
  await userActionEvent({
2313
2476
  userId: "123e4567-e89b-12d3-a456-426614174000",
@@ -2323,8 +2486,8 @@ const app = resource({
2323
2486
  } catch (error) {
2324
2487
  // "Event payload validation failed for app.events.userAction: ..."
2325
2488
  }
2326
- },
2327
- });
2489
+ })
2490
+ .build();
2328
2491
  ```
2329
2492
 
2330
2493
  ### Middleware Config Validation
@@ -2338,10 +2501,10 @@ const timingConfigSchema = z.object({
2338
2501
  logSuccessful: z.boolean().default(true),
2339
2502
  });
2340
2503
 
2341
- const timingMiddleware = taskMiddleware({ // or resourceMiddleware()
2342
- id: "app.middleware.timing",
2343
- configSchema: timingConfigSchema, // Validation on .with()
2344
- run: async ({ next }, _, config) => {
2504
+ const timingMiddleware = r.middleware
2505
+ .task("app.middleware.timing") // or r.middleware.resource("...")
2506
+ .configSchema(timingConfigSchema) // Validation on .with()
2507
+ .run(async ({ next }, _, config) => {
2345
2508
  const start = Date.now();
2346
2509
  try {
2347
2510
  const result = await next();
@@ -2355,8 +2518,8 @@ const timingMiddleware = taskMiddleware({ // or resourceMiddleware()
2355
2518
  console.log(`Operation failed after ${duration}ms`);
2356
2519
  throw error;
2357
2520
  }
2358
- },
2359
- });
2521
+ })
2522
+ .build();
2360
2523
 
2361
2524
  // Validation happens here, not during execution!
2362
2525
  try {
@@ -2368,17 +2531,17 @@ try {
2368
2531
  // "Middleware config validation failed for app.middleware.timing: ..."
2369
2532
  }
2370
2533
 
2371
- const myTask = task({
2372
- id: "app.tasks.example",
2373
- middleware: [
2534
+ const myTask = r
2535
+ .task("app.tasks.example")
2536
+ .middleware([
2374
2537
  timingMiddleware.with({
2375
2538
  timeout: 5000,
2376
2539
  logLevel: "debug",
2377
2540
  logSuccessful: true,
2378
2541
  }),
2379
- ],
2380
- run: async () => "success",
2381
- });
2542
+ ])
2543
+ .run(async () => "success")
2544
+ .build();
2382
2545
  ```
2383
2546
 
2384
2547
  #### Advanced Validation Features
@@ -2398,15 +2561,15 @@ const advancedSchema = z
2398
2561
  path: ["amount"],
2399
2562
  });
2400
2563
 
2401
- const paymentTask = task({
2402
- id: "app.tasks.payment",
2403
- inputSchema: advancedSchema,
2404
- run: async (payment) => {
2564
+ const paymentTask = r
2565
+ .task("app.tasks.payment")
2566
+ .inputSchema(advancedSchema)
2567
+ .run(async ({ input: payment }) => {
2405
2568
  // payment.amount is now a number (transformed from string)
2406
2569
  // All validations have passed
2407
2570
  return processPayment(payment);
2408
- },
2409
- });
2571
+ })
2572
+ .build();
2410
2573
  ```
2411
2574
 
2412
2575
  ### Error Handling
@@ -2493,13 +2656,14 @@ const userSchema = z.object({
2493
2656
 
2494
2657
  type UserData = z.infer<typeof userSchema>;
2495
2658
 
2496
- const createUser = task({
2497
- inputSchema: userSchema,
2498
- run: async (input: UserData) => {
2659
+ const createUser = r
2660
+ .task("app.tasks.createUser.zod")
2661
+ .inputSchema(userSchema)
2662
+ .run(async ({ input }: { input: UserData }) => {
2499
2663
  // Both runtime validation AND compile-time typing
2500
2664
  return { id: "user-123", ...input };
2501
- },
2502
- });
2665
+ })
2666
+ .build();
2503
2667
  ```
2504
2668
 
2505
2669
  > **runtime:** "Validation: you hand me a velvet rope and a clipboard. 'Name? Email? Age within bounds?' I stamp passports or eject violators with a `ValidationError`. Dress code is types, darling."
@@ -2511,18 +2675,18 @@ We expose the internal services for advanced use cases (but try not to use them
2511
2675
  ```typescript
2512
2676
  import { globals } from "@bluelibs/runner";
2513
2677
 
2514
- const advancedTask = task({
2515
- id: "app.advanced",
2516
- dependencies: {
2678
+ const advancedTask = r
2679
+ .task("app.advanced")
2680
+ .dependencies({
2517
2681
  store: globals.resources.store,
2518
2682
  taskRunner: globals.resources.taskRunner,
2519
2683
  eventManager: globals.resources.eventManager,
2520
- },
2521
- run: async (_, { store, taskRunner, eventManager }) => {
2684
+ })
2685
+ .run(async (_param, { store, taskRunner, eventManager }) => {
2522
2686
  // Direct access to the framework internals
2523
2687
  // (Use with caution!)
2524
- },
2525
- });
2688
+ })
2689
+ .build();
2526
2690
  ```
2527
2691
 
2528
2692
  ### Dynamic Dependencies
@@ -2531,37 +2695,36 @@ Dependencies can be defined in two ways - as a static object or as a function th
2531
2695
 
2532
2696
  ```typescript
2533
2697
  // Static dependencies (most common)
2534
- const userService = resource({
2535
- id: "app.services.user",
2536
- dependencies: { database, logger }, // Object - evaluated immediately
2537
- init: async (_, { database, logger }) => {
2698
+ const userService = r
2699
+ .resource("app.services.user")
2700
+ .dependencies({ database, logger }) // Object - evaluated immediately
2701
+ .init(async (_config, { database, logger }) => {
2538
2702
  // Dependencies are available here
2539
- },
2540
- });
2703
+ })
2704
+ .build();
2541
2705
 
2542
2706
  // Dynamic dependencies (for circular references or conditional dependencies)
2543
- const advancedService = resource({
2544
- id: "app.services.advanced",
2707
+ const advancedService = r
2708
+ .resource("app.services.advanced")
2545
2709
  // A function gives you the chance
2546
- dependencies: (config) => ({
2547
- // Config is what you receive when you register tise resource with .with()
2710
+ .dependencies((_config) => ({
2711
+ // Config is what you receive when you register this resource with .with()
2548
2712
  // So you can have conditional dependencies based on resource configuration as well.
2549
2713
  database,
2550
2714
  logger,
2551
2715
  conditionalService:
2552
2716
  process.env.NODE_ENV === "production" ? serviceA : serviceB,
2553
- }), // Function - evaluated when needed
2554
- register: (config: ConfigType) => [
2555
- // Config is what you receive when you register the resource with .with()
2717
+ })) // Function - evaluated when needed
2718
+ .register((_config: ConfigType) => [
2556
2719
  // Register dependencies dynamically
2557
2720
  process.env.NODE_ENV === "production"
2558
2721
  ? serviceA.with({ config: "value" })
2559
2722
  : serviceB.with({ config: "value" }),
2560
- ],
2561
- init: async (_, { database, logger, conditionalService }) => {
2723
+ ])
2724
+ .init(async (_config, { database, logger, conditionalService }) => {
2562
2725
  // Same interface, different evaluation timing
2563
- },
2564
- });
2726
+ })
2727
+ .build();
2565
2728
  ```
2566
2729
 
2567
2730
  The function pattern essentially gives you "just-in-time" dependency resolution instead of "eager" dependency resolution, which provides more flexibility and better handles complex dependency scenarios that arise in real-world applications.
@@ -2678,26 +2841,26 @@ import {
2678
2841
  } from "@bluelibs/runner";
2679
2842
 
2680
2843
  // Configuration
2681
- const config = resource({
2682
- id: "app.config",
2683
- init: async () => ({
2844
+ const config = r
2845
+ .resource("app.config")
2846
+ .init(async () => ({
2684
2847
  port: parseInt(process.env.PORT || "3000"),
2685
2848
  databaseUrl: process.env.DATABASE_URL!,
2686
2849
  jwtSecret: process.env.JWT_SECRET!,
2687
- }),
2688
- });
2850
+ }))
2851
+ .build();
2689
2852
 
2690
2853
  // Database
2691
- const database = resource({
2692
- id: "app.database",
2693
- dependencies: { config },
2694
- init: async (_, { config }) => {
2854
+ const database = r
2855
+ .resource("app.database")
2856
+ .dependencies({ config })
2857
+ .init(async (_config, { config }) => {
2695
2858
  const client = new MongoClient(config.databaseUrl);
2696
2859
  await client.connect();
2697
2860
  return client;
2698
- },
2699
- dispose: async (client) => await client.close(),
2700
- });
2861
+ })
2862
+ .dispose(async (client) => await client.close())
2863
+ .build();
2701
2864
 
2702
2865
  // Context for request data
2703
2866
  const RequestContext = createContext<{ userId?: string; role?: string }>(
@@ -2705,78 +2868,70 @@ const RequestContext = createContext<{ userId?: string; role?: string }>(
2705
2868
  );
2706
2869
 
2707
2870
  // Events
2708
- const userRegistered = event<{ userId: string; email: string }>({
2709
- id: "app.events.userRegistered",
2710
- });
2871
+ const userRegistered = r
2872
+ .event("app.events.userRegistered")
2873
+ .payloadSchema<{ userId: string; email: string }>({ parse: (v) => v })
2874
+ .build();
2711
2875
 
2712
2876
  // Middleware
2713
- const authMiddleware = taskMiddleware<{ requiredRole?: string }>({
2714
- id: "app.middleware.task.auth",
2715
- run: async ({ task, next }, deps, config) => {
2877
+ const authMiddleware = r.middleware
2878
+ .task("app.middleware.task.auth")
2879
+ .run(async ({ task, next }, deps, config?: { requiredRole?: string }) => {
2716
2880
  const context = RequestContext.use();
2717
2881
  if (config?.requiredRole && context.role !== config.requiredRole) {
2718
2882
  throw new Error("Insufficient permissions");
2719
2883
  }
2720
2884
  return next(task.input);
2721
- },
2722
- });
2885
+ })
2886
+ .build();
2723
2887
 
2724
2888
  // Services
2725
- const userService = resource({
2726
- id: "app.services.user",
2727
- dependencies: { database },
2728
- init: async (_, { database }) => ({
2889
+ const userService = r
2890
+ .resource("app.services.user")
2891
+ .dependencies({ database })
2892
+ .init(async (_config, { database }) => ({
2729
2893
  async createUser(userData: { name: string; email: string }) {
2730
2894
  const users = database.collection("users");
2731
2895
  const result = await users.insertOne(userData);
2732
2896
  return { id: result.insertedId.toString(), ...userData };
2733
2897
  },
2734
- }),
2735
- });
2898
+ }))
2899
+ .build();
2736
2900
 
2737
2901
  // Business Logic
2738
- const registerUser = task({
2739
- id: "app.tasks.registerUser",
2740
- dependencies: { userService, userRegistered },
2741
- run: async (userData, { userService, userRegistered }) => {
2902
+ const registerUser = r
2903
+ .task("app.tasks.registerUser")
2904
+ .dependencies({ userService, userRegistered })
2905
+ .run(async ({ input: userData }, { userService, userRegistered }) => {
2742
2906
  const user = await userService.createUser(userData);
2743
2907
  await userRegistered({ userId: user.id, email: user.email });
2744
2908
  return user;
2745
- },
2746
- });
2909
+ })
2910
+ .build();
2747
2911
 
2748
- const adminOnlyTask = task({
2749
- id: "app.tasks.adminOnly",
2750
- middleware: [authMiddleware.with({ requiredRole: "admin" })],
2751
- run: async () => {
2752
- return "Top secret admin data";
2753
- },
2754
- });
2912
+ const adminOnlyTask = r
2913
+ .task("app.tasks.adminOnly")
2914
+ .middleware([authMiddleware.with({ requiredRole: "admin" })])
2915
+ .run(async () => "Top secret admin data")
2916
+ .build();
2755
2917
 
2756
2918
  // Event Handlers using hooks
2757
- const sendWelcomeEmail = hook({
2758
- id: "app.hooks.sendWelcomeEmail",
2759
- on: userRegistered,
2760
- dependencies: { emailService },
2761
- run: async (event, { emailService }) => {
2919
+ const sendWelcomeEmail = r
2920
+ .hook("app.hooks.sendWelcomeEmail")
2921
+ .on(userRegistered)
2922
+ .dependencies({ emailService })
2923
+ .run(async (event, { emailService }) => {
2762
2924
  console.log(`Sending welcome email to ${event.data.email}`);
2763
2925
  await emailService.sendWelcome(event.data.email);
2764
- },
2765
- });
2926
+ })
2927
+ .build();
2766
2928
 
2767
2929
  // Express server
2768
- const server = resource({
2769
- id: "app.server",
2770
- register: [
2771
- config,
2772
- database,
2773
- userService,
2774
- registerUser,
2775
- adminOnlyTask,
2776
- sendWelcomeEmail,
2777
- ],
2778
- dependencies: { config, registerUser, adminOnlyTask },
2779
- init: async (_, { config, registerUser, adminOnlyTask }) => {
2930
+ const server = r
2931
+ .resource("app.server")
2932
+ .register([config, database, userService, registerUser, adminOnlyTask, sendWelcomeEmail])
2933
+ .dependencies({ config, registerUser, adminOnlyTask })
2934
+ .init(async (_config, { config, registerUser, adminOnlyTask }) => {
2780
2935
  const app = express();
2781
2936
  app.use(express.json());
2782
2937
 
@@ -2866,32 +3021,32 @@ This contains the classic `value` and `dispose()` but it also exposes `logger`,
2866
3021
  Note: The default `printThreshold` inside tests is `null` not `info`. This is verified via `process.env.NODE_ENV === 'test'`, if you want to see the logs ensure you set it accordingly.
2867
3022
 
2868
3023
  ```typescript
2869
- import { run, resource, task, event, override } from "@bluelibs/runner";
3024
+ import { run, r, override } from "@bluelibs/runner";
2870
3025
 
2871
3026
  // Your real app
2872
- const app = resource({
2873
- id: "app",
2874
- register: [
3027
+ const app = r
3028
+ .resource("app")
3029
+ .register([
2875
3030
  /* tasks, resources, middleware */
2876
- ],
2877
- });
3031
+ ])
3032
+ .build();
2878
3033
 
2879
3034
  // Optional: overrides for infra (hello, fast tests!)
2880
- const testDb = resource({
2881
- id: "app.database",
2882
- init: async () => new InMemoryDb(),
2883
- });
3035
+ const testDb = r
3036
+ .resource("app.database")
3037
+ .init(async () => new InMemoryDb())
3038
+ .build();
2884
3039
  // If you use with override() it will enforce the same interface upon the overriden resource to ensure typesafety
2885
3040
  const mockMailer = override(realMailer, { init: async () => fakeMailer });
2886
3041
 
2887
3042
  // Create the test harness
2888
- const harness = resource({
2889
- id: "test",
2890
- overrides: [mockMailer, testDb],
2891
- });
3043
+ const harness = r.resource("test").overrides([mockMailer, testDb]).build();
2892
3044
 
2893
3045
  // A task you want to drive in your tests
2894
- const registerUser = task({ id: "app.tasks.registerUser" /* ... */ });
3046
+ const registerUser = r
3047
+ .task("app.tasks.registerUser")
3048
+ .run(async () => ({}))
3049
+ .build();
2895
3050
 
2896
3051
  // Boom: full ecosystem
2897
3052
  const { value: t, dispose } = await run(harness);
@@ -3230,7 +3385,7 @@ try {
3230
3385
  - **Clarity**: Explicit dependencies, no hidden magic
3231
3386
  - **Developer Experience**: Helpful error messages and clear patterns
3232
3387
 
3233
- > **runtime:** "Why choose it? The bullets are persuasive. In practice, your 'intelligent inference' occasionally elopes with `any`, and your 'clear patterns' cosplay spaghetti. Still, compared to the alternatives… Ive seen worse cults."
3388
+ > **runtime:** "Why choose it? The bullets are persuasive. In practice, your 'intelligent inference' occasionally elopes with `any`, and your 'clear patterns' cosplay spaghetti. Still, compared to the alternatives… I've seen worse cults."
3234
3389
 
3235
3390
  ## The Migration Path
3236
3391