@bluelibs/runner 3.4.1 → 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,10 +601,10 @@ 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
- ## Timeout Middleware: Never Wait Forever
604
+ ## Timeouts
605
605
 
606
606
  The built-in timeout middleware prevents operations from hanging indefinitely by racing them against a configurable
607
- timeout:
607
+ timeout. Works for resources and tasks.
608
608
 
609
609
  ```typescript
610
610
  import { globals } from "@bluelibs/runner";
@@ -653,13 +653,13 @@ Best practices:
653
653
  - Use longer timeouts for resource initialization than task execution
654
654
  - Consider network conditions when setting API call timeouts
655
655
 
656
- ## Logging: Because Console.log Isn't Professional
656
+ ## Logging
657
657
 
658
658
  _The structured logging system that actually makes debugging enjoyable_
659
659
 
660
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).
661
661
 
662
- ### Quick Start: Basic Logging
662
+ ### Basic Logging
663
663
 
664
664
  ```typescript
665
665
  import { globals } from "@bluelibs/runner";
@@ -694,7 +694,7 @@ RUNNER_LOG_LEVEL=debug node your-app.js
694
694
  RUNNER_DISABLE_LOGS=true node your-app.js
695
695
  ```
696
696
 
697
- ### Log Levels: From Whispers to Screams
697
+ ### Log Levels
698
698
 
699
699
  The logger supports six log levels with increasing severity:
700
700
 
@@ -717,7 +717,7 @@ logger.error("Houston, we have a problem");
717
717
  logger.critical("DEFCON 1: Everything is broken");
718
718
  ```
719
719
 
720
- ### Structured Logging: More Than Just Strings
720
+ ### Structured Logging
721
721
 
722
722
  The logger accepts rich, structured data that makes debugging actually useful:
723
723
 
@@ -757,7 +757,7 @@ const userTask = task({
757
757
  });
758
758
  ```
759
759
 
760
- ### Context-Aware Logging: The with() Method
760
+ ### Context-Aware Logging
761
761
 
762
762
  Create logger instances with bound context for consistent metadata across related operations:
763
763
 
@@ -797,11 +797,11 @@ const requestHandler = task({
797
797
  });
798
798
  ```
799
799
 
800
- ### Print Threshold: Control What Shows Up
800
+ ### Print Threshold
801
801
 
802
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:
803
803
 
804
- #### Environment Variable Controls
804
+ ### Environment Variables
805
805
 
806
806
  ```bash
807
807
  # Disable all logging output
@@ -812,7 +812,7 @@ RUNNER_LOG_LEVEL=debug node your-app.js
812
812
  RUNNER_LOG_LEVEL=error node your-app.js
813
813
  ```
814
814
 
815
- #### Programmatic Control
815
+ ### Programmatic Control
816
816
 
817
817
  ```typescript
818
818
  // Override the default print threshold programmatically
@@ -834,7 +834,7 @@ const setupLogging = task({
834
834
  });
835
835
  ```
836
836
 
837
- ### Event-Driven Log Handling: Ship Logs Anywhere
837
+ ### Event-Driven Log Handling
838
838
 
839
839
  Every log generates an event that you can listen to. This is where the real power comes in:
840
840
 
@@ -895,7 +895,7 @@ const databaseLogHandler = task({
895
895
  });
896
896
  ```
897
897
 
898
- ### Integration with Winston: Best of Both Worlds
898
+ ### Integration with Winston
899
899
 
900
900
  Want to use Winston as your transport? No problem - integrate it seamlessly:
901
901
 
@@ -951,7 +951,7 @@ const winstonBridge = task({
951
951
  });
952
952
  ```
953
953
 
954
- ### Advanced: Custom Log Formatters
954
+ ### Custom Log Formatters
955
955
 
956
956
  Want to customize how logs are printed? You can override the print behavior:
957
957
 
@@ -989,7 +989,7 @@ const customLogger = resource({
989
989
  // Or you could simply add it as "globals.resources.logger" and override the default logger
990
990
  ```
991
991
 
992
- ### Log Structure: What You Get
992
+ ### Log Structure
993
993
 
994
994
  Every log event contains:
995
995
 
@@ -1012,7 +1012,7 @@ interface ILog {
1012
1012
 
1013
1013
  ### Debugging Tips & Best Practices
1014
1014
 
1015
- #### 1. Use Structured Data Liberally
1015
+ Use Structured Data Liberally
1016
1016
 
1017
1017
  ```typescript
1018
1018
  // Bad - hard to search and filter
@@ -1029,7 +1029,7 @@ await logger.error("Order processing failed", {
1029
1029
  });
1030
1030
  ```
1031
1031
 
1032
- #### 2. Include Context in Errors
1032
+ Include Context in Errors
1033
1033
 
1034
1034
  ```typescript
1035
1035
  // Include relevant context with errors
@@ -1049,7 +1049,7 @@ try {
1049
1049
  }
1050
1050
  ```
1051
1051
 
1052
- #### 3. Use Different Log Levels Appropriately
1052
+ Use Different Log Levels Appropriately
1053
1053
 
1054
1054
  ```typescript
1055
1055
  // Good level usage
@@ -1065,7 +1065,7 @@ await logger.error("Database connection failed", {
1065
1065
  await logger.critical("System out of memory", { data: { available: "0MB" } });
1066
1066
  ```
1067
1067
 
1068
- #### 4. Create Domain-Specific Loggers
1068
+ Create Domain-Specific Loggers
1069
1069
 
1070
1070
  ```typescript
1071
1071
  // Create loggers with domain context
@@ -1078,13 +1078,13 @@ await paymentLogger.info("Processing payment", { data: paymentData });
1078
1078
  await authLogger.warn("Failed login attempt", { data: { email, ip } });
1079
1079
  ```
1080
1080
 
1081
- ## Meta: Documenting and Organizing Your Components
1081
+ ## Meta
1082
1082
 
1083
1083
  _The structured way to describe what your components do and control their behavior_
1084
1084
 
1085
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.
1086
1086
 
1087
- ### Basic Metadata Properties
1087
+ ### Metadata Properties
1088
1088
 
1089
1089
  Every component can have these basic metadata properties:
1090
1090
 
@@ -1132,9 +1132,9 @@ const sendWelcomeEmail = task({
1132
1132
  });
1133
1133
  ```
1134
1134
 
1135
- ### Tags: The Powerful Classification System
1135
+ ### Tags
1136
1136
 
1137
- 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.
1138
1138
 
1139
1139
  #### String Tags for Simple Classification
1140
1140
 
@@ -1245,7 +1245,7 @@ const apiEndpoint = task({
1245
1245
 
1246
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
1247
 
1248
- #### Smart Middleware Using Structured Tags
1248
+ #### Structured Tags
1249
1249
 
1250
1250
  ```typescript
1251
1251
  const performanceMiddleware = middleware({
@@ -1285,7 +1285,7 @@ const performanceMiddleware = middleware({
1285
1285
  });
1286
1286
  ```
1287
1287
 
1288
- #### Contract Tags: Enforcing Return Types
1288
+ #### Contract Tags
1289
1289
 
1290
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>`.
1291
1291
 
@@ -1347,60 +1347,6 @@ const badTask = task({
1347
1347
  });
1348
1348
  ```
1349
1349
 
1350
- ### When to Use Metadata
1351
-
1352
- Always strive to provide a title and a description.
1353
-
1354
- #### ✅ Great Use Cases
1355
-
1356
- **Documentation & Discovery**
1357
-
1358
- ```typescript
1359
- const paymentProcessor = resource({
1360
- meta: {
1361
- title: "Payment Processing Service",
1362
- description:
1363
- "Handles credit card payments via Stripe API with fraud detection",
1364
- tags: ["payment", "stripe", "pci-compliant", "critical"],
1365
- },
1366
- // ... implementation
1367
- });
1368
- ```
1369
-
1370
- **Conditional Behavior**
1371
-
1372
- ```typescript
1373
- const backgroundTask = task({
1374
- meta: {
1375
- tags: ["background", "low-priority", retryTag.with({ maxAttempts: 5 })],
1376
- },
1377
- // ... implementation
1378
- });
1379
- ```
1380
-
1381
- **Cross-Cutting Concerns**
1382
-
1383
- ```typescript
1384
- // All tasks tagged with "audit" get automatic logging
1385
- const sensitiveOperation = task({
1386
- meta: {
1387
- tags: ["audit", "sensitive", "admin-only"],
1388
- },
1389
- // ... implementation
1390
- });
1391
- ```
1392
-
1393
- **Environment-Specific Behavior**
1394
-
1395
- ```typescript
1396
- const developmentTask = task({
1397
- meta: {
1398
- tags: ["development-only", debugTag.with({ verbose: true })],
1399
- },
1400
- // ... implementation
1401
- });
1402
- ```
1403
-
1404
1350
  ### Extending Metadata: Custom Properties
1405
1351
 
1406
1352
  For advanced use cases, you can extend the metadata interfaces to add your own properties:
@@ -1453,9 +1399,7 @@ const database = resource({
1453
1399
  });
1454
1400
  ```
1455
1401
 
1456
- ### Advanced Patterns
1457
-
1458
- #### Dynamic Middleware Application
1402
+ #### Global Middleware Application
1459
1403
 
1460
1404
  ```typescript
1461
1405
  const app = resource({
@@ -1479,7 +1423,7 @@ Metadata transforms your components from anonymous functions into self-documenti
1479
1423
 
1480
1424
  ## Advanced Usage: When You Need More Power
1481
1425
 
1482
- ### Overrides: Swapping Components at Runtime
1426
+ ## Overrides
1483
1427
 
1484
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.
1485
1429
 
@@ -1537,7 +1481,7 @@ const overriddenMiddleware = override(originalMiddleware, {
1537
1481
 
1538
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.
1539
1483
 
1540
- ### Namespacing: Keeping Things Organized
1484
+ ## Namespacing
1541
1485
 
1542
1486
  As your app grows, you'll want consistent naming. Here's the convention that won't drive you crazy:
1543
1487
 
@@ -1561,7 +1505,7 @@ const userTask = task({
1561
1505
  });
1562
1506
  ```
1563
1507
 
1564
- ### Factory Pattern: For When You Need Instances
1508
+ ## Factory Pattern
1565
1509
 
1566
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:
1567
1511
 
@@ -1585,12 +1529,10 @@ const app = resource({
1585
1529
  });
1586
1530
  ```
1587
1531
 
1588
- ### Runtime Input and Config Validation
1532
+ ## Runtime Validation
1589
1533
 
1590
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.
1591
1535
 
1592
- #### The Validation Interface
1593
-
1594
1536
  The framework defines a simple `IValidationSchema<T>` interface that any validation library can implement:
1595
1537
 
1596
1538
  ```typescript
@@ -1606,7 +1548,7 @@ Popular validation libraries already implement this interface:
1606
1548
  - **Joi**: Use `.assert()` or create a wrapper
1607
1549
  - **Custom validators**: Implement the interface yourself
1608
1550
 
1609
- #### Task Input Validation
1551
+ ### Task Input Validation
1610
1552
 
1611
1553
  Add an `inputSchema` to any task to validate inputs before execution:
1612
1554
 
@@ -1656,7 +1598,7 @@ const app = resource({
1656
1598
  });
1657
1599
  ```
1658
1600
 
1659
- #### Resource Config Validation (Fail Fast)
1601
+ ### Resource Config Validation
1660
1602
 
1661
1603
  Add a `configSchema` to resources to validate configurations. **Validation happens immediately when `.with()` is called**, ensuring configuration errors are caught early:
1662
1604
 
@@ -1706,7 +1648,7 @@ const app = resource({
1706
1648
  });
1707
1649
  ```
1708
1650
 
1709
- #### Event Payload Validation
1651
+ ### Event Payload Validation
1710
1652
 
1711
1653
  Add a `payloadSchema` to events to validate payloads every time they're emitted:
1712
1654
 
@@ -1755,7 +1697,7 @@ const app = resource({
1755
1697
  });
1756
1698
  ```
1757
1699
 
1758
- #### Middleware Config Validation (Fail Fast)
1700
+ ### Middleware Config Validation
1759
1701
 
1760
1702
  Add a `configSchema` to middleware to validate configurations. Like resources, **validation happens immediately when `.with()` is called**:
1761
1703
 
@@ -1837,7 +1779,7 @@ const paymentTask = task({
1837
1779
  });
1838
1780
  ```
1839
1781
 
1840
- #### Error Handling
1782
+ ### Error Handling
1841
1783
 
1842
1784
  Validation errors are thrown with clear, descriptive messages that include the component ID:
1843
1785
 
@@ -1855,7 +1797,7 @@ Validation errors are thrown with clear, descriptive messages that include the c
1855
1797
  // "Middleware config validation failed for {middlewareId}: {validationErrorMessage}"
1856
1798
  ```
1857
1799
 
1858
- #### Using Different Validation Libraries
1800
+ #### Other Libraries
1859
1801
 
1860
1802
  The framework works with any validation library that implements the `IValidationSchema<T>` interface:
1861
1803
 
@@ -1913,13 +1855,14 @@ While runtime validation happens with your chosen library, TypeScript still enfo
1913
1855
 
1914
1856
  ```typescript
1915
1857
  // With Zod, define your type and schema together
1916
- type UserData = z.infer<typeof userSchema>;
1917
1858
 
1918
1859
  const userSchema = z.object({
1919
1860
  name: z.string(),
1920
1861
  email: z.string().email(),
1921
1862
  });
1922
1863
 
1864
+ type UserData = z.infer<typeof userSchema>;
1865
+
1923
1866
  const createUser = task({
1924
1867
  inputSchema: userSchema,
1925
1868
  run: async (input: UserData) => {
@@ -1929,7 +1872,7 @@ const createUser = task({
1929
1872
  });
1930
1873
  ```
1931
1874
 
1932
- ### Internal Services: For When You Need Direct Access
1875
+ ## Internal Services
1933
1876
 
1934
1877
  We expose the internal services for advanced use cases (but try not to use them unless you really need to):
1935
1878
 
@@ -1950,7 +1893,7 @@ const advancedTask = task({
1950
1893
  });
1951
1894
  ```
1952
1895
 
1953
- ### Dependency Patterns: Static vs Dynamic
1896
+ ### Dynamic Dependencies
1954
1897
 
1955
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:
1956
1899
 
@@ -1976,7 +1919,7 @@ const advancedService = resource({
1976
1919
  conditionalService:
1977
1920
  process.env.NODE_ENV === "production" ? serviceA : serviceB,
1978
1921
  }), // Function - evaluated when needed
1979
- register: (config) => [
1922
+ register: (config: ConfigType) => [
1980
1923
  // Config is what you receive when you register the resource with .with()
1981
1924
  // Register dependencies dynamically
1982
1925
  process.env.NODE_ENV === "production"
@@ -1993,11 +1936,11 @@ The function pattern essentially gives you "just-in-time" dependency resolution
1993
1936
 
1994
1937
  **Performance note**: Function-based dependencies have minimal overhead - they're only called once during dependency resolution.
1995
1938
 
1996
- ### Handling Circular "Type" Dependencies: Breaking the Chain
1939
+ ## Handling Circular Dependencies
1997
1940
 
1998
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.
1999
1942
 
2000
- #### The Problem: Self-Referencing Types
1943
+ ### The Problem
2001
1944
 
2002
1945
  Consider these resources that create a circular dependency:
2003
1946
 
@@ -2030,7 +1973,7 @@ export const cResource = defineResource({
2030
1973
 
2031
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.
2032
1975
 
2033
- #### The Solution: Type Annotations
1976
+ ### The Solution
2034
1977
 
2035
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:
2036
1979
 
@@ -2245,7 +2188,7 @@ process.on("SIGTERM", async () => {
2245
2188
  });
2246
2189
  ```
2247
2190
 
2248
- ## Testing: Actually Enjoyable
2191
+ ## Testing
2249
2192
 
2250
2193
  ### Unit Testing: Mock Everything, Test Everything
2251
2194
 
@@ -2343,8 +2286,6 @@ Ever had too many database connections competing for resources? Your connection
2343
2286
 
2344
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.
2345
2288
 
2346
- ### Quick Start
2347
-
2348
2289
  ```typescript
2349
2290
  import { Semaphore } from "@bluelibs/runner";
2350
2291
 
@@ -2362,8 +2303,6 @@ try {
2362
2303
  }
2363
2304
  ```
2364
2305
 
2365
- ### The Elegant Approach: withPermit()
2366
-
2367
2306
  Why manage permits manually when you can let the semaphore do the heavy lifting?
2368
2307
 
2369
2308
  ```typescript
@@ -2373,8 +2312,6 @@ const users = await dbSemaphore.withPermit(async () => {
2373
2312
  });
2374
2313
  ```
2375
2314
 
2376
- ### Timeout Support
2377
-
2378
2315
  Prevent operations from hanging indefinitely with configurable timeouts:
2379
2316
 
2380
2317
  ```typescript
@@ -2393,8 +2330,6 @@ const result = await dbSemaphore.withPermit(
2393
2330
  );
2394
2331
  ```
2395
2332
 
2396
- ### Cancellation Support
2397
-
2398
2333
  Operations can be cancelled using AbortSignal:
2399
2334
 
2400
2335
  ```typescript
@@ -2418,8 +2353,6 @@ try {
2418
2353
  }
2419
2354
  ```
2420
2355
 
2421
- ### Monitoring: Metrics & Debugging
2422
-
2423
2356
  Want to know what's happening under the hood?
2424
2357
 
2425
2358
  ```typescript
@@ -2439,8 +2372,6 @@ console.log(`Queue length: ${dbSemaphore.getWaitingCount()}`);
2439
2372
  console.log(`Is disposed: ${dbSemaphore.isDisposed()}`);
2440
2373
  ```
2441
2374
 
2442
- ### Resource Cleanup
2443
-
2444
2375
  Properly dispose of semaphores when finished:
2445
2376
 
2446
2377
  ```typescript
@@ -2457,23 +2388,15 @@ _The orderly guardian of chaos, the diplomatic bouncer of async operations._
2457
2388
 
2458
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.
2459
2390
 
2460
- ### **FIFO Ordering**
2461
-
2462
2391
  Tasks execute one after another in first-in, first-out order. No cutting, no exceptions, no drama.
2463
2392
 
2464
- ### **Deadlock Detective**
2465
-
2466
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.
2467
2394
 
2468
- ### **Graceful Disposal & Cancellation**
2469
-
2470
2395
  The Queue provides cooperative cancellation through the Web Standard `AbortController`:
2471
2396
 
2472
2397
  - **Patient mode** (default): Waits for all queued tasks to complete naturally
2473
2398
  - **Cancel mode**: Signals running tasks to abort via `AbortSignal`, enabling early termination
2474
2399
 
2475
- ### Basic Example
2476
-
2477
2400
  ```typescript
2478
2401
  import { Queue } from "@bluelibs/runner";
2479
2402
 
@@ -2493,7 +2416,7 @@ await queue.dispose();
2493
2416
 
2494
2417
  The Queue provides each task with an `AbortSignal` for cooperative cancellation. Tasks should periodically check this signal to enable early termination.
2495
2418
 
2496
- #### Example: Long-running Task with Cancellation Support
2419
+ #### Example: Long-running Task
2497
2420
 
2498
2421
  ```typescript
2499
2422
  const queue = new Queue();
@@ -2565,34 +2488,14 @@ const processFiles = queue.run(async (signal) => {
2565
2488
  });
2566
2489
  ```
2567
2490
 
2568
- ## The Magic Behind the Curtain
2569
-
2570
- ### Internal State
2491
+ #### The Magic Behind the Curtain
2571
2492
 
2572
2493
  - `tail`: The promise chain that maintains FIFO execution order
2573
2494
  - `disposed`: Boolean flag indicating whether the queue accepts new tasks
2574
2495
  - `abortController`: Centralized cancellation controller that provides `AbortSignal` to all tasks
2575
2496
  - `executionContext`: AsyncLocalStorage-based deadlock detection mechanism
2576
2497
 
2577
- ### Error Handling
2578
-
2579
- - **"Queue has been disposed"**: You tried to add work after closing time
2580
- - **"Dead-lock detected"**: A task tried to queue another task (infinite recursion prevention)
2581
-
2582
- ### Best Practices
2583
-
2584
- #### 1. Always Dispose Resources
2585
-
2586
- ```typescript
2587
- const queue = new Queue();
2588
- try {
2589
- await queue.run(task);
2590
- } finally {
2591
- await queue.dispose();
2592
- }
2593
- ```
2594
-
2595
- #### 2. Implement Cooperative Cancellation
2498
+ #### Implement Cooperative Cancellation
2596
2499
 
2597
2500
  Tasks should regularly check the `AbortSignal` and respond appropriately:
2598
2501
 
@@ -2607,7 +2510,7 @@ if (signal.aborted) {
2607
2510
  }
2608
2511
  ```
2609
2512
 
2610
- #### 3. Integrate with Native APIs
2513
+ ##### Integrate with Native APIs
2611
2514
 
2612
2515
  Many Web APIs accept `AbortSignal`:
2613
2516
 
@@ -2615,11 +2518,11 @@ Many Web APIs accept `AbortSignal`:
2615
2518
  - `setTimeout(callback, delay, { signal })`
2616
2519
  - Custom async operations
2617
2520
 
2618
- ### 4. Avoid Nested Queuing
2521
+ ##### Avoid Nested Queuing
2619
2522
 
2620
2523
  The Queue prevents deadlocks by rejecting attempts to queue tasks from within running tasks. Structure your code to avoid this pattern.
2621
2524
 
2622
- ### 5. Handle AbortError Gracefully
2525
+ ##### Handle AbortError Gracefully
2623
2526
 
2624
2527
  ```typescript
2625
2528
  try {
@@ -2637,7 +2540,7 @@ try {
2637
2540
 
2638
2541
  _Cooperative task scheduling with professional-grade cancellation support_
2639
2542
 
2640
- ## Real-World Examples
2543
+ ### Real-World Examples
2641
2544
 
2642
2545
  ### Database Connection Pool Manager
2643
2546
 
@@ -2684,54 +2587,7 @@ class APIClient {
2684
2587
  }
2685
2588
  ```
2686
2589
 
2687
- ### Batch Processing with Progress
2688
-
2689
- ```typescript
2690
- async function processBatch(items: any[]) {
2691
- const semaphore = new Semaphore(3); // Max 3 concurrent items
2692
- const results = [];
2693
-
2694
- console.log("Starting batch processing...");
2695
-
2696
- for (const [index, item] of items.entries()) {
2697
- const result = await semaphore.withPermit(async () => {
2698
- console.log(`Processing item ${index + 1}/${items.length}`);
2699
- return await processItem(item);
2700
- });
2701
-
2702
- results.push(result);
2703
-
2704
- // Show progress
2705
- const metrics = semaphore.getMetrics();
2706
- console.log(
2707
- `Active: ${metrics.maxPermits - metrics.availablePermits}, Waiting: ${
2708
- metrics.waitingCount
2709
- }`
2710
- );
2711
- }
2712
-
2713
- semaphore.dispose();
2714
- console.log("Batch processing complete!");
2715
- return results;
2716
- }
2717
- ```
2718
-
2719
- ## Best Practices
2720
-
2721
- 1. **Always dispose**: Clean up your semaphores when finished to prevent memory leaks
2722
- 2. **Use withPermit()**: It's cleaner and prevents resource leaks
2723
- 3. **Set timeouts**: Don't let operations hang forever
2724
- 4. **Monitor metrics**: Keep an eye on utilization to tune your permit count
2725
- 5. **Handle errors**: Timeouts and cancellations throw errors - catch them!
2726
-
2727
- ## Common Pitfalls
2728
-
2729
- - **Forgetting to release**: Manual acquire/release is error-prone - prefer `withPermit()`
2730
- - **No timeout**: Operations can hang forever without timeouts
2731
- - **Ignoring disposal**: Always dispose semaphores to prevent memory leaks
2732
- - **Wrong permit count**: Too few = slow, too many = defeats the purpose
2733
-
2734
- ## Anonymous IDs: Because Naming Things Is Hard
2590
+ ## Anonymous IDs
2735
2591
 
2736
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.
2737
2593
 
@@ -2759,63 +2615,11 @@ const createUser = task({
2759
2615
  ### Benefits of Anonymous IDs
2760
2616
 
2761
2617
  1. **Less Bikeshedding**: No more debates about naming conventions
2762
- 2. **Automatic Uniqueness**: Guaranteed collision-free identifiers
2618
+ 2. **Automatic Uniqueness**: Guaranteed collision-free identifiers folder based
2763
2619
  3. **Faster Prototyping**: Just write code, framework handles the rest
2764
2620
  4. **Refactor-Friendly**: Rename files/variables and IDs update automatically
2765
2621
  5. **Stack Trace Integration**: Error messages include helpful file locations
2766
2622
 
2767
- ### When to Use Manual vs Anonymous IDs
2768
-
2769
- | Use Case | Recommendation | Reason |
2770
- | ----------------------- | -------------- | --------------------------------------- |
2771
- | Internal tasks | Anonymous | No external references needed |
2772
- | Event definitions | Manual | Need predictable names for listeners |
2773
- | Public APIs | Manual | External modules need stable references |
2774
- | Middleware | Manual | Often reused across projects |
2775
- | Configuration resources | Anonymous | Usually internal infrastructure |
2776
- | Test doubles/mocks | Anonymous | One-off usage in tests |
2777
- | Cross-module services | Manual | Multiple files depend on them |
2778
-
2779
- ### Anonymous ID Examples by Pattern
2780
-
2781
- ```typescript
2782
- // ✅ Great for anonymous IDs
2783
- const database = resource({
2784
- init: async () => new Database(),
2785
- dispose: async (db) => db.close(),
2786
- });
2787
-
2788
- const processPayment = task({
2789
- dependencies: { database },
2790
- run: async (payment, { database }) => {
2791
- // Internal business logic
2792
- },
2793
- });
2794
-
2795
- // ✅ Better with manual IDs
2796
- const paymentProcessed = event<{ paymentId: string }>({
2797
- id: "app.events.paymentProcessed", // Other modules listen to this
2798
- });
2799
-
2800
- const authMiddleware = middleware({
2801
- id: "app.middleware.auth", // Reused across multiple tasks
2802
- run: async ({ task, next }) => {
2803
- // Auth logic
2804
- },
2805
- });
2806
-
2807
- // ✅ Mixed approach - totally fine!
2808
- const app = resource({
2809
- id: "app", // Main entry point gets manual ID
2810
- register: [
2811
- database, // Anonymous
2812
- processPayment, // Anonymous
2813
- paymentProcessed, // Manual
2814
- authMiddleware, // Manual
2815
- ],
2816
- });
2817
- ```
2818
-
2819
2623
  ### Debugging with Anonymous IDs
2820
2624
 
2821
2625
  Anonymous IDs show up clearly in error messages and logs:
@@ -2843,14 +2647,6 @@ logger.info("Processing payment", {
2843
2647
  - **Clarity**: Explicit dependencies, no hidden magic
2844
2648
  - **Developer Experience**: Helpful error messages and clear patterns
2845
2649
 
2846
- ### What You Don't Get
2847
-
2848
- - Complex configuration files that require a PhD to understand
2849
- - Decorator hell that makes your code look like a Christmas tree
2850
- - Hidden dependencies that break when you least expect it
2851
- - Framework lock-in that makes you feel trapped
2852
- - Mysterious behavior at runtime that makes you question reality
2853
-
2854
2650
  ## The Migration Path
2855
2651
 
2856
2652
  Coming from Express? No problem. Coming from NestJS? We feel your pain. Coming from Spring Boot? Welcome to the light side.