@bluelibs/runner 4.8.2 → 4.8.3
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 +70 -47
- package/dist/browser/index.cjs.map +1 -1
- package/dist/browser/index.mjs.map +1 -1
- package/dist/definers/builders/middleware.d.ts +2 -2
- package/dist/edge/index.cjs.map +1 -1
- package/dist/edge/index.mjs.map +1 -1
- package/dist/node/node.cjs.map +1 -1
- package/dist/node/node.mjs.map +1 -1
- package/dist/universal/index.cjs.map +1 -1
- package/dist/universal/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
-
//
|
|
582
|
-
type
|
|
583
|
-
type
|
|
584
|
-
type
|
|
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
|
-
|
|
587
|
-
|
|
588
|
-
.
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
//
|
|
599
|
-
const
|
|
600
|
-
.task("app.tasks.
|
|
601
|
-
|
|
602
|
-
.
|
|
603
|
-
|
|
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([
|
|
632
|
-
.run(async (input) => getUserFromDatabase(
|
|
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 }
|
|
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
|
-
|
|
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}-${
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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 () => ({}
|
|
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 }
|
|
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", ...
|
|
2445
|
+
return { id: "user-123", ...userData };
|
|
2423
2446
|
})
|
|
2424
2447
|
.build();
|
|
2425
2448
|
|