@bluelibs/runner 4.8.2 → 4.8.4

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
@@ -484,7 +484,7 @@ const authMiddleware = r.middleware
484
484
  .task("app.middleware.task.auth")
485
485
  .run(async ({ task, next }, _deps, config: AuthMiddlewareConfig) => {
486
486
  // Must return the value
487
- return await next(task.input as any);
487
+ return await next(task.input);
488
488
  })
489
489
  .build();
490
490
 
@@ -573,35 +573,62 @@ Access `eventManager` via `globals.resources.eventManager` if needed.
573
573
 
574
574
  #### Middleware Type Contracts
575
575
 
576
- Middleware can now enforce type contracts using the `<Config, Input, Output>` signature:
576
+ Middleware can enforce type contracts on the tasks that use them, ensuring data integrity as it flows through the system. This is achieved by defining `Input` and `Output` types within the middleware's implementation.
577
+
578
+ When a task uses this middleware, its own `run` method must conform to the `Input` and `Output` shapes defined by the middleware contract.
577
579
 
578
580
  ```typescript
579
581
  import { r } from "@bluelibs/runner";
580
582
 
581
- // Middleware that transforms input and output types
582
- type LogConfig = { includeTimestamp: boolean };
583
- type LogInput = { data: any };
584
- type LogOutput = { data: any; logged: boolean };
583
+ // 1. Define the contract types for the middleware.
584
+ type AuthConfig = { requiredRole: string };
585
+ type AuthInput = { user: { role: string } }; // Task's input must have this shape.
586
+ type AuthOutput = { executedBy: { role: string; verified: boolean } }; // Task's output must have this shape.
585
587
 
586
- const loggingMiddleware = r.middleware
587
- .task("app.middleware.logging")
588
- .run(async ({ task, next }, _deps, config: LogConfig) => {
589
- console.log(
590
- config.includeTimestamp ? new Date() : "",
591
- (task.input as LogInput).data,
592
- );
593
- const result = (await next(task.input)) as LogOutput;
594
- return { ...result, logged: true };
588
+ // 2. Create the middleware using these types in its `run` method.
589
+ const authMiddleware = r.middleware
590
+ .task<AuthConfig, AuthInput, AuthOutput>("app.middleware.auth")
591
+ .run(async ({ task, next }, _deps, config) => {
592
+ const input = task.input;
593
+ if (input.user.role !== config.requiredRole) {
594
+ throw new Error("Insufficient permissions");
595
+ }
596
+
597
+ // The task runs, and its result must match AuthOutput.
598
+ const result = await next(input);
599
+
600
+ // The middleware can further transform the output.
601
+ const output = result;
602
+ return {
603
+ ...output,
604
+ executedBy: {
605
+ ...output.executedBy,
606
+ verified: true, // The middleware adds its own data.
607
+ },
608
+ };
595
609
  })
596
610
  .build();
597
611
 
598
- // Tasks using this middleware must conform to the Input/Output types
599
- const loggedTask = r
600
- .task("app.tasks.logged")
601
- .middleware([loggingMiddleware.with({ includeTimestamp: true })])
602
- .run(async (input: { data: string }) => ({
603
- data: input.data.toUpperCase(),
604
- }))
612
+ // 3. Apply the middleware to a task.
613
+ const adminTask = r
614
+ .task("app.tasks.adminOnly")
615
+ // If you use multiple middleware with contracts they get combined.
616
+ .middleware([authMiddleware.with({ requiredRole: "admin" })])
617
+ // If you use .inputSchema() the input must contain the contract types otherwise you end-up with InputContractViolation error.
618
+ // The `run` method is now strictly typed by the middleware's contract.
619
+ // Its input must be `AuthInput`, and its return value must be `AuthOutput`.
620
+ .run(async (input) => {
621
+ // `input.user.role` is available and fully typed.
622
+ console.log(`Task executed by user with role: ${input.user.role}`);
623
+
624
+ // Returning a shape that doesn't match AuthOutput will cause a compile-time error.
625
+ // return { wrong: "shape" }; // This would fail!
626
+ return {
627
+ executedBy: {
628
+ role: input.user.role,
629
+ },
630
+ };
631
+ })
605
632
  .build();
606
633
  ```
607
634
 
@@ -616,20 +643,13 @@ Tags are metadata that can influence system behavior. Unlike meta properties, ta
616
643
  ```typescript
617
644
  import { r } from "@bluelibs/runner";
618
645
 
619
- // Simple string tags
620
- const apiTask = r
621
- .task("app.tasks.getUserData")
622
- .tags(["api", "public", "cacheable"])
623
- .run(async (input) => getUserFromDatabase(input as any))
624
- .build();
625
-
626
646
  // Structured tags with configuration
627
647
  const httpTag = r.tag<{ method: string; path: string }>("http.route").build();
628
648
 
629
649
  const getUserTask = r
630
650
  .task("app.tasks.getUser")
631
- .tags(["api", httpTag.with({ method: "GET", path: "/users/:id" })])
632
- .run(async (input) => getUserFromDatabase((input as any).id))
651
+ .tags([httpTag.with({ method: "GET", path: "/users/:id" })])
652
+ .run(async (input) => getUserFromDatabase(input.id))
633
653
  .build();
634
654
  ```
635
655
 
@@ -655,7 +675,7 @@ const routeRegistration = r
655
675
 
656
676
  const { method, path } = config;
657
677
  server.app[method.toLowerCase()](path, async (req, res) => {
658
- const result = await taskDef({ ...req.params, ...req.body } as any);
678
+ const result = await taskDef({ ...req.params, ...req.body });
659
679
  res.json(result);
660
680
  });
661
681
  });
@@ -732,15 +752,18 @@ const internalEvent = r
732
752
  Enforce return value shapes at compile time:
733
753
 
734
754
  ```typescript
735
- // Tags that enforce type contracts
755
+ // Tags that enforce type contracts input/output for tasks or config/value for resources
756
+ type InputType = { id: string };
757
+ type OutputType = { name: string };
736
758
  const userContract = r
737
- .tag<void, void, { name: string }>("contract.user")
759
+ // void = no config, no need for .with({ ... })
760
+ .tag<void, InputType, OutputType>("contract.user")
738
761
  .build();
739
762
 
740
763
  const profileTask = r
741
764
  .task("app.tasks.getProfile")
742
765
  .tags([userContract]) // Must return { name: string }
743
- .run(async () => ({ name: "Ada" })) // ✅ Satisfies contract
766
+ .run(async (input) => ({ name: input.id + "Ada" })) // ✅ Satisfies contract
744
767
  .build();
745
768
  ```
746
769
 
@@ -1391,7 +1414,7 @@ const expensiveTask = r
1391
1414
  globals.middleware.task.cache.with({
1392
1415
  // lru-cache options by default
1393
1416
  ttl: 60 * 1000, // Cache for 1 minute
1394
- keyBuilder: (taskId, input) => `${taskId}-${(input as any).userId}`, // optional key builder
1417
+ keyBuilder: (taskId, input: any) => `${taskId}-${input.userId}`, // optional key builder
1395
1418
  }),
1396
1419
  ])
1397
1420
  .run(async (input: { userId: string }) => {
@@ -1467,7 +1490,7 @@ Here are real performance metrics from our comprehensive benchmark suite on an M
1467
1490
  const userTask = r
1468
1491
  .task("user.create")
1469
1492
  .middleware([auth, logging, metrics])
1470
- .run(async (input) => database.users.create(input as any))
1493
+ .run(async (input) => database.users.create(input))
1471
1494
  .build();
1472
1495
 
1473
1496
  // 1000 executions = ~5ms total time
@@ -1758,7 +1781,7 @@ const userTask = r
1758
1781
  logger.info("User creation attempt", {
1759
1782
  source: userTask.id,
1760
1783
  data: {
1761
- email: (input as any).email,
1784
+ email: input.email,
1762
1785
  registrationSource: "web",
1763
1786
  timestamp: new Date().toISOString(),
1764
1787
  },
@@ -1766,7 +1789,7 @@ const userTask = r
1766
1789
 
1767
1790
  // With error information
1768
1791
  try {
1769
- const user = await createUser(input as any);
1792
+ const user = await createUser(input);
1770
1793
  logger.info("User created successfully", {
1771
1794
  data: { userId: user.id, email: user.email },
1772
1795
  });
@@ -1774,7 +1797,7 @@ const userTask = r
1774
1797
  logger.error("User creation failed", {
1775
1798
  error,
1776
1799
  data: {
1777
- attemptedEmail: (input as any).email,
1800
+ attemptedEmail: input.email,
1778
1801
  validationErrors: error.validationErrors,
1779
1802
  },
1780
1803
  });
@@ -2029,7 +2052,7 @@ const criticalTask = r
2029
2052
  ])
2030
2053
  .run(async (input) => {
2031
2054
  // This task will have verbose debug logging
2032
- return await processPayment(input as any);
2055
+ return await processPayment(input);
2033
2056
  })
2034
2057
  .build();
2035
2058
  ```
@@ -2170,7 +2193,7 @@ const sendWelcomeEmail = r
2170
2193
  .meta({
2171
2194
  title: "Send Welcome Email",
2172
2195
  description: "Sends a welcome email to newly registered users",
2173
- } as any)
2196
+ })
2174
2197
  .dependencies({ emailService })
2175
2198
  .run(async ({ input: userData }, { emailService }) => {
2176
2199
  // Email sending logic
@@ -2210,7 +2233,7 @@ const expensiveApiTask = r
2210
2233
  version: "2.1.0",
2211
2234
  apiVersion: "v2",
2212
2235
  costLevel: "high", // Custom property!
2213
- } as any)
2236
+ })
2214
2237
  .run(async ({ input: prompt }) => {
2215
2238
  // AI generation logic
2216
2239
  })
@@ -2223,7 +2246,7 @@ const database = r
2223
2246
  healthCheck: "/health/db", // Custom property!
2224
2247
  dependencies: ["postgresql", "connection-pool"],
2225
2248
  scalingPolicy: "auto",
2226
- } as any)
2249
+ })
2227
2250
  // .init(async () => { /* ... */ })
2228
2251
  .build();
2229
2252
  ```
@@ -2253,7 +2276,7 @@ const testEmailer = override(productionEmailer, {
2253
2276
  // Using spread operator works the same way but does not provide type-safety.
2254
2277
  const testEmailer = r
2255
2278
  .resource("app.emailer")
2256
- .init(async () => ({} as any))
2279
+ .init(async () => ({}))
2257
2280
  .build();
2258
2281
 
2259
2282
  const app = r
@@ -2290,7 +2313,7 @@ const originalMiddleware = taskMiddleware({
2290
2313
  const overriddenMiddleware = override(originalMiddleware, {
2291
2314
  run: async ({ task, next }) => {
2292
2315
  const result = await next(task?.input);
2293
- return { wrapped: result } as any;
2316
+ return { wrapped: result };
2294
2317
  },
2295
2318
  });
2296
2319
 
@@ -2419,7 +2442,7 @@ const createUserTask = r
2419
2442
  .inputSchema(userSchema) // Works directly with Zod!
2420
2443
  .run(async ({ input: userData }) => {
2421
2444
  // userData is validated and properly typed
2422
- return { id: "user-123", ...(userData as any) };
2445
+ return { id: "user-123", ...userData };
2423
2446
  })
2424
2447
  .build();
2425
2448