@bluelibs/runner 3.4.0 → 3.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # BlueLibs Runner: The Framework That Actually Makes Sense
1
+ # BlueLibs Runner
2
2
 
3
3
  _Or: How I Learned to Stop Worrying and Love Dependency Injection_
4
4
 
@@ -17,7 +17,7 @@ Welcome to BlueLibs Runner, where we've taken the chaos of modern application ar
17
17
 
18
18
  BlueLibs Runner is a TypeScript-first framework that embraces functional programming principles while keeping dependency injection simple enough that you won't need a flowchart to understand your own code. Think of it as the anti-framework framework – it gets out of your way and lets you build stuff that actually works.
19
19
 
20
- ### The Core Philosophy (AKA Why We Built This)
20
+ ### The Core
21
21
 
22
22
  - **Tasks are functions** - Not classes with 47 methods you'll never use
23
23
  - **Resources are singletons** - Database connections, configs, services - the usual suspects
@@ -25,7 +25,7 @@ BlueLibs Runner is a TypeScript-first framework that embraces functional program
25
25
  - **Everything is async** - Because it's 2025 and blocking code is so 2005
26
26
  - **Explicit beats implicit** - No magic, no surprises, no "how the hell does this work?"
27
27
 
28
- ## Quick Start (The "Just Show Me Code" Section)
28
+ ## Quick Start
29
29
 
30
30
  ```bash
31
31
  npm install @bluelibs/runner
@@ -77,9 +77,11 @@ const app = resource({
77
77
  const { dispose } = await run(app);
78
78
  ```
79
79
 
80
- ## The Big Four: Your New Building Blocks
80
+ ## The Big Four
81
81
 
82
- ### 1. Tasks: The Heart of Your Business Logic
82
+ Another term to define them would be TERM. (tasks, events, resources, middleware)
83
+
84
+ ### Tasks
83
85
 
84
86
  Tasks are functions with superpowers. They're pure-ish, testable, and composable. Unlike classes that accumulate methods like a hoarder accumulates stuff, tasks do one thing well.
85
87
 
@@ -100,8 +102,6 @@ const result = await sendEmail.run(
100
102
  );
101
103
  ```
102
104
 
103
- #### When to Task and When Not to Task
104
-
105
105
  Look, we get it. You could turn every function into a task, but that's like using a sledgehammer to crack nuts. Here's the deal:
106
106
 
107
107
  **Make it a task when:**
@@ -119,9 +119,9 @@ Look, we get it. You could turn every function into a task, but that's like usin
119
119
 
120
120
  Think of tasks as the "main characters" in your application story, not every single line of dialogue.
121
121
 
122
- ### 2. Resources: Your Singletons That Don't Suck
122
+ ### Resources
123
123
 
124
- Resources are 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.
124
+ 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.
125
125
 
126
126
  ```typescript
127
127
  const database = resource({
@@ -149,7 +149,7 @@ const userService = resource({
149
149
  });
150
150
  ```
151
151
 
152
- #### Resource Configuration: Because Hardcoding Is for Amateurs
152
+ #### Resource Configuration
153
153
 
154
154
  Resources can be configured with type-safe options. No more "config object of unknown shape" nonsense.
155
155
 
@@ -181,7 +181,7 @@ const app = resource({
181
181
  });
182
182
  ```
183
183
 
184
- #### Shared Context Between Init and Dispose
184
+ #### Private Context
185
185
 
186
186
  For cases where you need to share variables between `init()` and `dispose()` methods (because sometimes cleanup is complicated), use the enhanced context pattern:
187
187
 
@@ -210,7 +210,7 @@ const dbResource = resource({
210
210
  });
211
211
  ```
212
212
 
213
- ### 3. Events: Async Communication That Actually Works
213
+ ### Events
214
214
 
215
215
  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.
216
216
 
@@ -242,7 +242,7 @@ const sendWelcomeEmail = task({
242
242
  });
243
243
  ```
244
244
 
245
- #### Wildcard Events: For When You Want to Know Everything
245
+ #### Wildcard Events
246
246
 
247
247
  Sometimes you need to be the nosy neighbor of your application:
248
248
 
@@ -257,7 +257,7 @@ const logAllEventsTask = task({
257
257
  });
258
258
  ```
259
259
 
260
- #### Tasks and Resources built-in events
260
+ #### Built-in Events
261
261
 
262
262
  Tasks and resources have their own lifecycle events that you can hook into:
263
263
 
@@ -275,7 +275,7 @@ const myResource = resource({ ... });
275
275
 
276
276
  Each event has its own utilities and functions.
277
277
 
278
- #### Global Events: The System's Built-in Gossip Network
278
+ #### Global Events
279
279
 
280
280
  The framework comes with its own set of events that fire during the lifecycle. Think of them as the system's way of keeping you informed:
281
281
 
@@ -296,7 +296,7 @@ const taskLogger = task({
296
296
  });
297
297
  ```
298
298
 
299
- #### Event Propagation Control with stopPropagation()
299
+ #### stopPropagation()
300
300
 
301
301
  Sometimes you need to prevent other event listeners from processing an event. The `stopPropagation()` method gives you fine-grained control over event flow:
302
302
 
@@ -333,7 +333,7 @@ const emergencyHandler = task({
333
333
  });
334
334
  ```
335
335
 
336
- ### 4. Middleware: The Interceptor Pattern Done Right
336
+ ### Middleware
337
337
 
338
338
  Middleware wraps around your tasks and resources, adding cross-cutting concerns without polluting your business logic.
339
339
 
@@ -365,7 +365,7 @@ const adminTask = task({
365
365
  });
366
366
  ```
367
367
 
368
- #### Global Middleware: Apply Everywhere, Configure Once
368
+ #### Global Middleware
369
369
 
370
370
  Want to add logging to everything? Authentication to all tasks? Global middleware has your back:
371
371
 
@@ -388,9 +388,9 @@ const app = resource({
388
388
  });
389
389
  ```
390
390
 
391
- ## Context: Request-Scoped Data That Doesn't Drive You Insane
391
+ ## Context
392
392
 
393
- 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.
393
+ 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.
394
394
 
395
395
  ```typescript
396
396
  const UserContext = createContext<{ userId: string; role: string }>(
@@ -418,7 +418,7 @@ const handleRequest = resource({
418
418
  });
419
419
  ```
420
420
 
421
- ### Context with Middleware: The Power Couple
421
+ ### Context with Middleware
422
422
 
423
423
  Context shines when combined with middleware for request-scoped data:
424
424
 
@@ -457,7 +457,7 @@ const handleRequest = task({
457
457
  });
458
458
  ```
459
459
 
460
- ## Dependency Management: The Index Pattern
460
+ ## The Index Pattern
461
461
 
462
462
  When your app grows beyond "hello world", you'll want to group related dependencies. The `index()` helper is your friend - it's basically a 3-in-1 resource that registers, depends on, and returns everything you give it.
463
463
 
@@ -482,7 +482,7 @@ const app = resource({
482
482
  });
483
483
  ```
484
484
 
485
- ## Error Handling: Graceful Failures
485
+ ## Error Handling
486
486
 
487
487
  Errors happen. When they do, you can listen for them and decide what to do. No more unhandled promise rejections ruining your day.
488
488
 
@@ -507,7 +507,7 @@ const errorHandler = task({
507
507
  });
508
508
  ```
509
509
 
510
- ## Caching: Built-in Performance
510
+ ## Caching
511
511
 
512
512
  Because nobody likes waiting for the same expensive operation twice:
513
513
 
@@ -601,13 +601,65 @@ The retry middleware can be configured with:
601
601
  - `delayStrategy`: A function that returns the delay in milliseconds before the next attempt.
602
602
  - `stopRetryIf`: A function to prevent retries for certain types of errors.
603
603
 
604
- ## Logging: Because Console.log Isn't Professional
604
+ ## Timeouts
605
+
606
+ The built-in timeout middleware prevents operations from hanging indefinitely by racing them against a configurable
607
+ timeout. Works for resources and tasks.
608
+
609
+ ```typescript
610
+ import { globals } from "@bluelibs/runner";
611
+
612
+ const apiTask = task({
613
+ id: "app.tasks.externalApi",
614
+ middleware: [
615
+ globals.middleware.timeout.with({ ttl: 5000 }), // 5 second timeout
616
+ ],
617
+ run: async () => {
618
+ // This operation will be aborted if it takes longer than 5 seconds
619
+ return await fetch("https://slow-api.example.com/data");
620
+ },
621
+ });
622
+
623
+ // Combine with retry for robust error handling
624
+ const resilientTask = task({
625
+ id: "app.tasks.resilient",
626
+ middleware: [
627
+ // Order matters here. Imagine a big onion.
628
+ globals.middleware.retry.with({
629
+ retries: 3,
630
+ delayStrategy: (attempt) => 1000 * attempt, // 1s, 2s, 3s delays
631
+ }),
632
+ globals.middleware.timeout.with({ ttl: 10000 }), // 10 second timeout per attempt
633
+ ],
634
+ run: async () => {
635
+ // Each retry attempt gets its own 10-second timeout
636
+ return await unreliableOperation();
637
+ },
638
+ });
639
+ ```
640
+
641
+ How it works:
642
+
643
+ - Uses AbortController and Promise.race() for clean cancellation
644
+ - Throws TimeoutError when the timeout is reached
645
+ - Works with any async operation in tasks and resources
646
+ - Integrates seamlessly with retry middleware for layered resilience
647
+ - Zero timeout (ttl: 0) throws immediately for testing edge cases
648
+
649
+ Best practices:
650
+
651
+ - Set timeouts based on expected operation duration plus buffer
652
+ - Combine with retry middleware for transient failures
653
+ - Use longer timeouts for resource initialization than task execution
654
+ - Consider network conditions when setting API call timeouts
655
+
656
+ ## Logging
605
657
 
606
658
  _The structured logging system that actually makes debugging enjoyable_
607
659
 
608
660
  BlueLibs Runner comes with a built-in logging system that's event-driven, structured, and doesn't make you hate your life when you're trying to debug at 2 AM. It emits events for everything, so you can handle logs however you want - ship them to your favorite log warehouse, pretty-print them to console, or ignore them entirely (we won't judge).
609
661
 
610
- ### Quick Start: Basic Logging
662
+ ### Basic Logging
611
663
 
612
664
  ```typescript
613
665
  import { globals } from "@bluelibs/runner";
@@ -642,7 +694,7 @@ RUNNER_LOG_LEVEL=debug node your-app.js
642
694
  RUNNER_DISABLE_LOGS=true node your-app.js
643
695
  ```
644
696
 
645
- ### Log Levels: From Whispers to Screams
697
+ ### Log Levels
646
698
 
647
699
  The logger supports six log levels with increasing severity:
648
700
 
@@ -665,7 +717,7 @@ logger.error("Houston, we have a problem");
665
717
  logger.critical("DEFCON 1: Everything is broken");
666
718
  ```
667
719
 
668
- ### Structured Logging: More Than Just Strings
720
+ ### Structured Logging
669
721
 
670
722
  The logger accepts rich, structured data that makes debugging actually useful:
671
723
 
@@ -705,7 +757,7 @@ const userTask = task({
705
757
  });
706
758
  ```
707
759
 
708
- ### Context-Aware Logging: The with() Method
760
+ ### Context-Aware Logging
709
761
 
710
762
  Create logger instances with bound context for consistent metadata across related operations:
711
763
 
@@ -745,11 +797,11 @@ const requestHandler = task({
745
797
  });
746
798
  ```
747
799
 
748
- ### Print Threshold: Control What Shows Up
800
+ ### Print Threshold
749
801
 
750
802
  By default, logs at `info` level and above are automatically printed to console for better developer experience. You can easily control this behavior through environment variables or by setting a print threshold programmatically:
751
803
 
752
- #### Environment Variable Controls
804
+ ### Environment Variables
753
805
 
754
806
  ```bash
755
807
  # Disable all logging output
@@ -760,7 +812,7 @@ RUNNER_LOG_LEVEL=debug node your-app.js
760
812
  RUNNER_LOG_LEVEL=error node your-app.js
761
813
  ```
762
814
 
763
- #### Programmatic Control
815
+ ### Programmatic Control
764
816
 
765
817
  ```typescript
766
818
  // Override the default print threshold programmatically
@@ -782,7 +834,7 @@ const setupLogging = task({
782
834
  });
783
835
  ```
784
836
 
785
- ### Event-Driven Log Handling: Ship Logs Anywhere
837
+ ### Event-Driven Log Handling
786
838
 
787
839
  Every log generates an event that you can listen to. This is where the real power comes in:
788
840
 
@@ -843,7 +895,7 @@ const databaseLogHandler = task({
843
895
  });
844
896
  ```
845
897
 
846
- ### Integration with Winston: Best of Both Worlds
898
+ ### Integration with Winston
847
899
 
848
900
  Want to use Winston as your transport? No problem - integrate it seamlessly:
849
901
 
@@ -899,7 +951,7 @@ const winstonBridge = task({
899
951
  });
900
952
  ```
901
953
 
902
- ### Advanced: Custom Log Formatters
954
+ ### Custom Log Formatters
903
955
 
904
956
  Want to customize how logs are printed? You can override the print behavior:
905
957
 
@@ -937,7 +989,7 @@ const customLogger = resource({
937
989
  // Or you could simply add it as "globals.resources.logger" and override the default logger
938
990
  ```
939
991
 
940
- ### Log Structure: What You Get
992
+ ### Log Structure
941
993
 
942
994
  Every log event contains:
943
995
 
@@ -960,7 +1012,7 @@ interface ILog {
960
1012
 
961
1013
  ### Debugging Tips & Best Practices
962
1014
 
963
- #### 1. Use Structured Data Liberally
1015
+ Use Structured Data Liberally
964
1016
 
965
1017
  ```typescript
966
1018
  // Bad - hard to search and filter
@@ -977,7 +1029,7 @@ await logger.error("Order processing failed", {
977
1029
  });
978
1030
  ```
979
1031
 
980
- #### 2. Include Context in Errors
1032
+ Include Context in Errors
981
1033
 
982
1034
  ```typescript
983
1035
  // Include relevant context with errors
@@ -997,7 +1049,7 @@ try {
997
1049
  }
998
1050
  ```
999
1051
 
1000
- #### 3. Use Different Log Levels Appropriately
1052
+ Use Different Log Levels Appropriately
1001
1053
 
1002
1054
  ```typescript
1003
1055
  // Good level usage
@@ -1013,7 +1065,7 @@ await logger.error("Database connection failed", {
1013
1065
  await logger.critical("System out of memory", { data: { available: "0MB" } });
1014
1066
  ```
1015
1067
 
1016
- #### 4. Create Domain-Specific Loggers
1068
+ Create Domain-Specific Loggers
1017
1069
 
1018
1070
  ```typescript
1019
1071
  // Create loggers with domain context
@@ -1026,13 +1078,13 @@ await paymentLogger.info("Processing payment", { data: paymentData });
1026
1078
  await authLogger.warn("Failed login attempt", { data: { email, ip } });
1027
1079
  ```
1028
1080
 
1029
- ## Meta: Documenting and Organizing Your Components
1081
+ ## Meta
1030
1082
 
1031
1083
  _The structured way to describe what your components do and control their behavior_
1032
1084
 
1033
1085
  Metadata in BlueLibs Runner provides a systematic way to document, categorize, and control the behavior of your tasks, resources, events, and middleware. Think of it as your component's passport - it tells you and your tools everything they need to know about what this component does and how it should be treated.
1034
1086
 
1035
- ### Basic Metadata Properties
1087
+ ### Metadata Properties
1036
1088
 
1037
1089
  Every component can have these basic metadata properties:
1038
1090
 
@@ -1080,9 +1132,9 @@ const sendWelcomeEmail = task({
1080
1132
  });
1081
1133
  ```
1082
1134
 
1083
- ### Tags: The Powerful Classification System
1135
+ ### Tags
1084
1136
 
1085
- Tags are the most powerful part of the metadata system. They can be simple strings or sophisticated configuration objects that control component behavior.
1137
+ Tags are the most powerful part of the metadata system used for classification. They can be simple strings or sophisticated configuration objects that control component behavior.
1086
1138
 
1087
1139
  #### String Tags for Simple Classification
1088
1140
 
@@ -1191,7 +1243,9 @@ const apiEndpoint = task({
1191
1243
  });
1192
1244
  ```
1193
1245
 
1194
- #### Smart Middleware Using Structured Tags
1246
+ To process these tags you can hook into `globals.events.afterInit`, use the global store as dependency and use the `getTasksWithTag()` and `getResourcesWithTag()` functionality.
1247
+
1248
+ #### Structured Tags
1195
1249
 
1196
1250
  ```typescript
1197
1251
  const performanceMiddleware = middleware({
@@ -1231,7 +1285,7 @@ const performanceMiddleware = middleware({
1231
1285
  });
1232
1286
  ```
1233
1287
 
1234
- #### Contract Tags: Enforcing Return Types
1288
+ #### Contract Tags
1235
1289
 
1236
1290
  You can attach contracts to tags to enforce the shape of a task's returned value and a resource's `init()` value at compile time. Contracts are specified via the second generic of `defineTag<TConfig, TContract>`.
1237
1291
 
@@ -1293,85 +1347,6 @@ const badTask = task({
1293
1347
  });
1294
1348
  ```
1295
1349
 
1296
- ### When to Use Metadata
1297
-
1298
- #### ✅ Great Use Cases
1299
-
1300
- **Documentation & Discovery**
1301
-
1302
- ```typescript
1303
- const paymentProcessor = resource({
1304
- meta: {
1305
- title: "Payment Processing Service",
1306
- description:
1307
- "Handles credit card payments via Stripe API with fraud detection",
1308
- tags: ["payment", "stripe", "pci-compliant", "critical"],
1309
- },
1310
- // ... implementation
1311
- });
1312
- ```
1313
-
1314
- **Conditional Behavior**
1315
-
1316
- ```typescript
1317
- const backgroundTask = task({
1318
- meta: {
1319
- tags: ["background", "low-priority", retryTag.with({ maxAttempts: 5 })],
1320
- },
1321
- // ... implementation
1322
- });
1323
- ```
1324
-
1325
- **Cross-Cutting Concerns**
1326
-
1327
- ```typescript
1328
- // All tasks tagged with "audit" get automatic logging
1329
- const sensitiveOperation = task({
1330
- meta: {
1331
- tags: ["audit", "sensitive", "admin-only"],
1332
- },
1333
- // ... implementation
1334
- });
1335
- ```
1336
-
1337
- **Environment-Specific Behavior**
1338
-
1339
- ```typescript
1340
- const developmentTask = task({
1341
- meta: {
1342
- tags: ["development-only", debugTag.with({ verbose: true })],
1343
- },
1344
- // ... implementation
1345
- });
1346
- ```
1347
-
1348
- #### ❌ When NOT to Use Metadata
1349
-
1350
- **Simple Internal Logic** - Don't overcomplicate straightforward code:
1351
-
1352
- ```typescript
1353
- // ❌ Overkill
1354
- const simple = task({
1355
- meta: { tags: ["internal", "utility"] },
1356
- run: () => Math.random(),
1357
- });
1358
-
1359
- // ✅ Better
1360
- const generateId = () => Math.random().toString(36);
1361
- ```
1362
-
1363
- **One-Off Tasks** - If it's used once, metadata won't help:
1364
-
1365
- ```typescript
1366
- // ❌ Unnecessary
1367
- const oneTimeScript = task({
1368
- meta: { title: "Migration Script", tags: ["migration"] },
1369
- run: () => {
1370
- /* run once and forget */
1371
- },
1372
- });
1373
- ```
1374
-
1375
1350
  ### Extending Metadata: Custom Properties
1376
1351
 
1377
1352
  For advanced use cases, you can extend the metadata interfaces to add your own properties:
@@ -1424,25 +1399,7 @@ const database = resource({
1424
1399
  });
1425
1400
  ```
1426
1401
 
1427
- ### Advanced Patterns
1428
-
1429
- #### Tag-Based Component Selection
1430
-
1431
- ```typescript
1432
- // Find all API endpoints
1433
- function getApiTasks(store: Store) {
1434
- return store.getAllTasks().filter((task) => task.meta?.tags?.includes("api"));
1435
- }
1436
-
1437
- // Find all tasks with specific performance requirements
1438
- function getPerformanceCriticalTasks(store: Store) {
1439
- return store.tasks.values().filter(({ task }) => {
1440
- return performanceTag.extract(task) !== null;
1441
- });
1442
- }
1443
- ```
1444
-
1445
- #### Dynamic Middleware Application
1402
+ #### Global Middleware Application
1446
1403
 
1447
1404
  ```typescript
1448
1405
  const app = resource({
@@ -1466,7 +1423,7 @@ Metadata transforms your components from anonymous functions into self-documenti
1466
1423
 
1467
1424
  ## Advanced Usage: When You Need More Power
1468
1425
 
1469
- ### Overrides: Swapping Components at Runtime
1426
+ ## Overrides
1470
1427
 
1471
1428
  Sometimes you need to replace a component entirely. Maybe you're doing integration testing or you want to override a library from an external package.
1472
1429
 
@@ -1524,7 +1481,7 @@ const overriddenMiddleware = override(originalMiddleware, {
1524
1481
 
1525
1482
  Overrides are applied after everything is registered. If multiple overrides target the same id, the one defined higher in the resource tree (closer to the root) wins, because it’s applied last. Conflicting overrides are allowed; overriding something that wasn’t registered throws. Use override() to change behavior safely while preserving the original id.
1526
1483
 
1527
- ### Namespacing: Keeping Things Organized
1484
+ ## Namespacing
1528
1485
 
1529
1486
  As your app grows, you'll want consistent naming. Here's the convention that won't drive you crazy:
1530
1487
 
@@ -1548,7 +1505,7 @@ const userTask = task({
1548
1505
  });
1549
1506
  ```
1550
1507
 
1551
- ### Factory Pattern: For When You Need Instances
1508
+ ## Factory Pattern
1552
1509
 
1553
1510
  To keep things dead simple, we avoided poluting the D.I. with this concept. Therefore, we recommend using a resource with a factory function to create instances of your classes:
1554
1511
 
@@ -1572,12 +1529,10 @@ const app = resource({
1572
1529
  });
1573
1530
  ```
1574
1531
 
1575
- ### Runtime Input and Config Validation
1532
+ ## Runtime Validation
1576
1533
 
1577
1534
  BlueLibs Runner includes a generic validation interface that works with any validation library, including [Zod](https://zod.dev/), [Yup](https://github.com/jquense/yup), [Joi](https://joi.dev/), and others. The framework provides runtime validation with excellent TypeScript inference while remaining library-agnostic.
1578
1535
 
1579
- #### The Validation Interface
1580
-
1581
1536
  The framework defines a simple `IValidationSchema<T>` interface that any validation library can implement:
1582
1537
 
1583
1538
  ```typescript
@@ -1593,7 +1548,7 @@ Popular validation libraries already implement this interface:
1593
1548
  - **Joi**: Use `.assert()` or create a wrapper
1594
1549
  - **Custom validators**: Implement the interface yourself
1595
1550
 
1596
- #### Task Input Validation
1551
+ ### Task Input Validation
1597
1552
 
1598
1553
  Add an `inputSchema` to any task to validate inputs before execution:
1599
1554
 
@@ -1643,7 +1598,7 @@ const app = resource({
1643
1598
  });
1644
1599
  ```
1645
1600
 
1646
- #### Resource Config Validation (Fail Fast)
1601
+ ### Resource Config Validation
1647
1602
 
1648
1603
  Add a `configSchema` to resources to validate configurations. **Validation happens immediately when `.with()` is called**, ensuring configuration errors are caught early:
1649
1604
 
@@ -1693,7 +1648,7 @@ const app = resource({
1693
1648
  });
1694
1649
  ```
1695
1650
 
1696
- #### Event Payload Validation
1651
+ ### Event Payload Validation
1697
1652
 
1698
1653
  Add a `payloadSchema` to events to validate payloads every time they're emitted:
1699
1654
 
@@ -1742,7 +1697,7 @@ const app = resource({
1742
1697
  });
1743
1698
  ```
1744
1699
 
1745
- #### Middleware Config Validation (Fail Fast)
1700
+ ### Middleware Config Validation
1746
1701
 
1747
1702
  Add a `configSchema` to middleware to validate configurations. Like resources, **validation happens immediately when `.with()` is called**:
1748
1703
 
@@ -1824,7 +1779,7 @@ const paymentTask = task({
1824
1779
  });
1825
1780
  ```
1826
1781
 
1827
- #### Error Handling
1782
+ ### Error Handling
1828
1783
 
1829
1784
  Validation errors are thrown with clear, descriptive messages that include the component ID:
1830
1785
 
@@ -1842,7 +1797,7 @@ Validation errors are thrown with clear, descriptive messages that include the c
1842
1797
  // "Middleware config validation failed for {middlewareId}: {validationErrorMessage}"
1843
1798
  ```
1844
1799
 
1845
- #### Using Different Validation Libraries
1800
+ #### Other Libraries
1846
1801
 
1847
1802
  The framework works with any validation library that implements the `IValidationSchema<T>` interface:
1848
1803
 
@@ -1900,13 +1855,14 @@ While runtime validation happens with your chosen library, TypeScript still enfo
1900
1855
 
1901
1856
  ```typescript
1902
1857
  // With Zod, define your type and schema together
1903
- type UserData = z.infer<typeof userSchema>;
1904
1858
 
1905
1859
  const userSchema = z.object({
1906
1860
  name: z.string(),
1907
1861
  email: z.string().email(),
1908
1862
  });
1909
1863
 
1864
+ type UserData = z.infer<typeof userSchema>;
1865
+
1910
1866
  const createUser = task({
1911
1867
  inputSchema: userSchema,
1912
1868
  run: async (input: UserData) => {
@@ -1916,7 +1872,7 @@ const createUser = task({
1916
1872
  });
1917
1873
  ```
1918
1874
 
1919
- ### Internal Services: For When You Need Direct Access
1875
+ ## Internal Services
1920
1876
 
1921
1877
  We expose the internal services for advanced use cases (but try not to use them unless you really need to):
1922
1878
 
@@ -1937,7 +1893,7 @@ const advancedTask = task({
1937
1893
  });
1938
1894
  ```
1939
1895
 
1940
- ### Dependency Patterns: Static vs Dynamic
1896
+ ### Dynamic Dependencies
1941
1897
 
1942
1898
  Dependencies can be defined in two ways - as a static object or as a function that returns an object. Each approach has its use cases:
1943
1899
 
@@ -1963,7 +1919,7 @@ const advancedService = resource({
1963
1919
  conditionalService:
1964
1920
  process.env.NODE_ENV === "production" ? serviceA : serviceB,
1965
1921
  }), // Function - evaluated when needed
1966
- register: (config) => [
1922
+ register: (config: ConfigType) => [
1967
1923
  // Config is what you receive when you register the resource with .with()
1968
1924
  // Register dependencies dynamically
1969
1925
  process.env.NODE_ENV === "production"
@@ -1980,11 +1936,11 @@ The function pattern essentially gives you "just-in-time" dependency resolution
1980
1936
 
1981
1937
  **Performance note**: Function-based dependencies have minimal overhead - they're only called once during dependency resolution.
1982
1938
 
1983
- ### Handling Circular "Type" Dependencies: Breaking the Chain
1939
+ ## Handling Circular Dependencies
1984
1940
 
1985
1941
  Sometimes you'll run into circular type dependencies because of your file structure not necessarily because of a real circular dependency. TypeScript struggles with these, but there's a way to handle it gracefully.
1986
1942
 
1987
- #### The Problem: Self-Referencing Types
1943
+ ### The Problem
1988
1944
 
1989
1945
  Consider these resources that create a circular dependency:
1990
1946
 
@@ -2017,7 +1973,7 @@ export const cResource = defineResource({
2017
1973
 
2018
1974
  A depends B depends C depends ATask. No circular dependency, yet Typescript struggles with these, but there's a way to handle it gracefully.
2019
1975
 
2020
- #### The Solution: Type Annotations
1976
+ ### The Solution
2021
1977
 
2022
1978
  The fix is to explicitly type the resource that completes the circle using a simple assertion `IResource<Config, ReturnType>`. This breaks the TypeScript inference chain while maintaining runtime functionality:
2023
1979
 
@@ -2232,7 +2188,7 @@ process.on("SIGTERM", async () => {
2232
2188
  });
2233
2189
  ```
2234
2190
 
2235
- ## Testing: Actually Enjoyable
2191
+ ## Testing
2236
2192
 
2237
2193
  ### Unit Testing: Mock Everything, Test Everything
2238
2194
 
@@ -2330,8 +2286,6 @@ Ever had too many database connections competing for resources? Your connection
2330
2286
 
2331
2287
  Think of it as a VIP rope at an exclusive venue. Only a limited number of operations can proceed at once. The rest wait in an orderly queue like well-behaved async functions.
2332
2288
 
2333
- ### Quick Start
2334
-
2335
2289
  ```typescript
2336
2290
  import { Semaphore } from "@bluelibs/runner";
2337
2291
 
@@ -2349,8 +2303,6 @@ try {
2349
2303
  }
2350
2304
  ```
2351
2305
 
2352
- ### The Elegant Approach: withPermit()
2353
-
2354
2306
  Why manage permits manually when you can let the semaphore do the heavy lifting?
2355
2307
 
2356
2308
  ```typescript
@@ -2360,8 +2312,6 @@ const users = await dbSemaphore.withPermit(async () => {
2360
2312
  });
2361
2313
  ```
2362
2314
 
2363
- ### Timeout Support
2364
-
2365
2315
  Prevent operations from hanging indefinitely with configurable timeouts:
2366
2316
 
2367
2317
  ```typescript
@@ -2380,8 +2330,6 @@ const result = await dbSemaphore.withPermit(
2380
2330
  );
2381
2331
  ```
2382
2332
 
2383
- ### Cancellation Support
2384
-
2385
2333
  Operations can be cancelled using AbortSignal:
2386
2334
 
2387
2335
  ```typescript
@@ -2405,8 +2353,6 @@ try {
2405
2353
  }
2406
2354
  ```
2407
2355
 
2408
- ### Monitoring: Metrics & Debugging
2409
-
2410
2356
  Want to know what's happening under the hood?
2411
2357
 
2412
2358
  ```typescript
@@ -2426,8 +2372,6 @@ console.log(`Queue length: ${dbSemaphore.getWaitingCount()}`);
2426
2372
  console.log(`Is disposed: ${dbSemaphore.isDisposed()}`);
2427
2373
  ```
2428
2374
 
2429
- ### Resource Cleanup
2430
-
2431
2375
  Properly dispose of semaphores when finished:
2432
2376
 
2433
2377
  ```typescript
@@ -2444,23 +2388,15 @@ _The orderly guardian of chaos, the diplomatic bouncer of async operations._
2444
2388
 
2445
2389
  The `Queue` class is your friendly neighborhood task coordinator. Think of it as a very polite but firm British queue-master who ensures everyone waits their turn, prevents cutting in line, and gracefully handles when it's time to close shop.
2446
2390
 
2447
- ### **FIFO Ordering**
2448
-
2449
2391
  Tasks execute one after another in first-in, first-out order. No cutting, no exceptions, no drama.
2450
2392
 
2451
- ### **Deadlock Detective**
2452
-
2453
2393
  Using the clever `AsyncLocalStorage`, our Queue can detect when a task tries to queue another task (the async equivalent of "yo dawg, I heard you like queues..."). When caught red-handed, it politely but firmly rejects with a deadlock error.
2454
2394
 
2455
- ### **Graceful Disposal & Cancellation**
2456
-
2457
2395
  The Queue provides cooperative cancellation through the Web Standard `AbortController`:
2458
2396
 
2459
2397
  - **Patient mode** (default): Waits for all queued tasks to complete naturally
2460
2398
  - **Cancel mode**: Signals running tasks to abort via `AbortSignal`, enabling early termination
2461
2399
 
2462
- ### Basic Example
2463
-
2464
2400
  ```typescript
2465
2401
  import { Queue } from "@bluelibs/runner";
2466
2402
 
@@ -2480,7 +2416,7 @@ await queue.dispose();
2480
2416
 
2481
2417
  The Queue provides each task with an `AbortSignal` for cooperative cancellation. Tasks should periodically check this signal to enable early termination.
2482
2418
 
2483
- #### Example: Long-running Task with Cancellation Support
2419
+ #### Example: Long-running Task
2484
2420
 
2485
2421
  ```typescript
2486
2422
  const queue = new Queue();
@@ -2552,34 +2488,14 @@ const processFiles = queue.run(async (signal) => {
2552
2488
  });
2553
2489
  ```
2554
2490
 
2555
- ## The Magic Behind the Curtain
2556
-
2557
- ### Internal State
2491
+ #### The Magic Behind the Curtain
2558
2492
 
2559
2493
  - `tail`: The promise chain that maintains FIFO execution order
2560
2494
  - `disposed`: Boolean flag indicating whether the queue accepts new tasks
2561
2495
  - `abortController`: Centralized cancellation controller that provides `AbortSignal` to all tasks
2562
2496
  - `executionContext`: AsyncLocalStorage-based deadlock detection mechanism
2563
2497
 
2564
- ### Error Handling
2565
-
2566
- - **"Queue has been disposed"**: You tried to add work after closing time
2567
- - **"Dead-lock detected"**: A task tried to queue another task (infinite recursion prevention)
2568
-
2569
- ### Best Practices
2570
-
2571
- #### 1. Always Dispose Resources
2572
-
2573
- ```typescript
2574
- const queue = new Queue();
2575
- try {
2576
- await queue.run(task);
2577
- } finally {
2578
- await queue.dispose();
2579
- }
2580
- ```
2581
-
2582
- #### 2. Implement Cooperative Cancellation
2498
+ #### Implement Cooperative Cancellation
2583
2499
 
2584
2500
  Tasks should regularly check the `AbortSignal` and respond appropriately:
2585
2501
 
@@ -2594,7 +2510,7 @@ if (signal.aborted) {
2594
2510
  }
2595
2511
  ```
2596
2512
 
2597
- #### 3. Integrate with Native APIs
2513
+ ##### Integrate with Native APIs
2598
2514
 
2599
2515
  Many Web APIs accept `AbortSignal`:
2600
2516
 
@@ -2602,11 +2518,11 @@ Many Web APIs accept `AbortSignal`:
2602
2518
  - `setTimeout(callback, delay, { signal })`
2603
2519
  - Custom async operations
2604
2520
 
2605
- ### 4. Avoid Nested Queuing
2521
+ ##### Avoid Nested Queuing
2606
2522
 
2607
2523
  The Queue prevents deadlocks by rejecting attempts to queue tasks from within running tasks. Structure your code to avoid this pattern.
2608
2524
 
2609
- ### 5. Handle AbortError Gracefully
2525
+ ##### Handle AbortError Gracefully
2610
2526
 
2611
2527
  ```typescript
2612
2528
  try {
@@ -2624,7 +2540,7 @@ try {
2624
2540
 
2625
2541
  _Cooperative task scheduling with professional-grade cancellation support_
2626
2542
 
2627
- ## Real-World Examples
2543
+ ### Real-World Examples
2628
2544
 
2629
2545
  ### Database Connection Pool Manager
2630
2546
 
@@ -2671,54 +2587,7 @@ class APIClient {
2671
2587
  }
2672
2588
  ```
2673
2589
 
2674
- ### Batch Processing with Progress
2675
-
2676
- ```typescript
2677
- async function processBatch(items: any[]) {
2678
- const semaphore = new Semaphore(3); // Max 3 concurrent items
2679
- const results = [];
2680
-
2681
- console.log("Starting batch processing...");
2682
-
2683
- for (const [index, item] of items.entries()) {
2684
- const result = await semaphore.withPermit(async () => {
2685
- console.log(`Processing item ${index + 1}/${items.length}`);
2686
- return await processItem(item);
2687
- });
2688
-
2689
- results.push(result);
2690
-
2691
- // Show progress
2692
- const metrics = semaphore.getMetrics();
2693
- console.log(
2694
- `Active: ${metrics.maxPermits - metrics.availablePermits}, Waiting: ${
2695
- metrics.waitingCount
2696
- }`
2697
- );
2698
- }
2699
-
2700
- semaphore.dispose();
2701
- console.log("Batch processing complete!");
2702
- return results;
2703
- }
2704
- ```
2705
-
2706
- ## Best Practices
2707
-
2708
- 1. **Always dispose**: Clean up your semaphores when finished to prevent memory leaks
2709
- 2. **Use withPermit()**: It's cleaner and prevents resource leaks
2710
- 3. **Set timeouts**: Don't let operations hang forever
2711
- 4. **Monitor metrics**: Keep an eye on utilization to tune your permit count
2712
- 5. **Handle errors**: Timeouts and cancellations throw errors - catch them!
2713
-
2714
- ## Common Pitfalls
2715
-
2716
- - **Forgetting to release**: Manual acquire/release is error-prone - prefer `withPermit()`
2717
- - **No timeout**: Operations can hang forever without timeouts
2718
- - **Ignoring disposal**: Always dispose semaphores to prevent memory leaks
2719
- - **Wrong permit count**: Too few = slow, too many = defeats the purpose
2720
-
2721
- ## Anonymous IDs: Because Naming Things Is Hard
2590
+ ## Anonymous IDs
2722
2591
 
2723
2592
  One of our favorite quality-of-life features: **anonymous IDs**. Instead of manually naming every component, the framework can generate unique symbol-based identifiers based on your file path and variable name. It's like having a really good naming assistant who never gets tired.
2724
2593
 
@@ -2746,63 +2615,11 @@ const createUser = task({
2746
2615
  ### Benefits of Anonymous IDs
2747
2616
 
2748
2617
  1. **Less Bikeshedding**: No more debates about naming conventions
2749
- 2. **Automatic Uniqueness**: Guaranteed collision-free identifiers
2618
+ 2. **Automatic Uniqueness**: Guaranteed collision-free identifiers folder based
2750
2619
  3. **Faster Prototyping**: Just write code, framework handles the rest
2751
2620
  4. **Refactor-Friendly**: Rename files/variables and IDs update automatically
2752
2621
  5. **Stack Trace Integration**: Error messages include helpful file locations
2753
2622
 
2754
- ### When to Use Manual vs Anonymous IDs
2755
-
2756
- | Use Case | Recommendation | Reason |
2757
- | ----------------------- | -------------- | --------------------------------------- |
2758
- | Internal tasks | Anonymous | No external references needed |
2759
- | Event definitions | Manual | Need predictable names for listeners |
2760
- | Public APIs | Manual | External modules need stable references |
2761
- | Middleware | Manual | Often reused across projects |
2762
- | Configuration resources | Anonymous | Usually internal infrastructure |
2763
- | Test doubles/mocks | Anonymous | One-off usage in tests |
2764
- | Cross-module services | Manual | Multiple files depend on them |
2765
-
2766
- ### Anonymous ID Examples by Pattern
2767
-
2768
- ```typescript
2769
- // ✅ Great for anonymous IDs
2770
- const database = resource({
2771
- init: async () => new Database(),
2772
- dispose: async (db) => db.close(),
2773
- });
2774
-
2775
- const processPayment = task({
2776
- dependencies: { database },
2777
- run: async (payment, { database }) => {
2778
- // Internal business logic
2779
- },
2780
- });
2781
-
2782
- // ✅ Better with manual IDs
2783
- const paymentProcessed = event<{ paymentId: string }>({
2784
- id: "app.events.paymentProcessed", // Other modules listen to this
2785
- });
2786
-
2787
- const authMiddleware = middleware({
2788
- id: "app.middleware.auth", // Reused across multiple tasks
2789
- run: async ({ task, next }) => {
2790
- // Auth logic
2791
- },
2792
- });
2793
-
2794
- // ✅ Mixed approach - totally fine!
2795
- const app = resource({
2796
- id: "app", // Main entry point gets manual ID
2797
- register: [
2798
- database, // Anonymous
2799
- processPayment, // Anonymous
2800
- paymentProcessed, // Manual
2801
- authMiddleware, // Manual
2802
- ],
2803
- });
2804
- ```
2805
-
2806
2623
  ### Debugging with Anonymous IDs
2807
2624
 
2808
2625
  Anonymous IDs show up clearly in error messages and logs:
@@ -2830,14 +2647,6 @@ logger.info("Processing payment", {
2830
2647
  - **Clarity**: Explicit dependencies, no hidden magic
2831
2648
  - **Developer Experience**: Helpful error messages and clear patterns
2832
2649
 
2833
- ### What You Don't Get
2834
-
2835
- - Complex configuration files that require a PhD to understand
2836
- - Decorator hell that makes your code look like a Christmas tree
2837
- - Hidden dependencies that break when you least expect it
2838
- - Framework lock-in that makes you feel trapped
2839
- - Mysterious behavior at runtime that makes you question reality
2840
-
2841
2650
  ## The Migration Path
2842
2651
 
2843
2652
  Coming from Express? No problem. Coming from NestJS? We feel your pain. Coming from Spring Boot? Welcome to the light side.