@bluelibs/runner 4.8.1 → 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/AI.md +44 -5
- package/README.md +90 -68
- package/dist/browser/index.cjs +361 -174
- package/dist/browser/index.cjs.map +1 -1
- package/dist/browser/index.mjs +361 -174
- package/dist/browser/index.mjs.map +1 -1
- package/dist/definers/builders/middleware.d.ts +2 -2
- package/dist/edge/index.cjs +361 -174
- package/dist/edge/index.cjs.map +1 -1
- package/dist/edge/index.mjs +361 -174
- package/dist/edge/index.mjs.map +1 -1
- package/dist/globals/globalResources.d.ts +17 -8
- package/dist/globals/resources/httpClientFactory.resource.d.ts +28 -0
- package/dist/http-client.d.ts +1 -3
- package/dist/index.d.ts +15 -8
- package/dist/node/http-mixed-client.d.ts +3 -0
- package/dist/node/node.cjs +7323 -6962
- package/dist/node/node.cjs.map +1 -1
- package/dist/node/node.d.ts +93 -0
- package/dist/node/node.mjs +7346 -6986
- package/dist/node/node.mjs.map +1 -1
- package/dist/node/resources/http-mixed-client.factory.resource.d.ts +17 -0
- package/dist/node/resources/http-smart-client.factory.resource.d.ts +16 -0
- package/dist/universal/index.cjs +361 -174
- package/dist/universal/index.cjs.map +1 -1
- package/dist/universal/index.mjs +361 -174
- package/dist/universal/index.mjs.map +1 -1
- package/package.json +1 -1
package/AI.md
CHANGED
|
@@ -50,7 +50,7 @@ const createUser = r
|
|
|
50
50
|
.dependencies({ logger: globals.resources.logger })
|
|
51
51
|
.inputSchema<{ name: string }>({ parse: (value) => value })
|
|
52
52
|
.resultSchema<{ id: string; name: string }>({ parse: (value) => value })
|
|
53
|
-
.run(async (
|
|
53
|
+
.run(async (input, { logger }) => {
|
|
54
54
|
await logger.info(`Creating user ${input.name}`);
|
|
55
55
|
return { id: "user-1", name: input.name };
|
|
56
56
|
})
|
|
@@ -104,7 +104,7 @@ const sendEmail = r
|
|
|
104
104
|
loggingMiddleware.with({ label: "email" }),
|
|
105
105
|
tracingMiddleware,
|
|
106
106
|
])
|
|
107
|
-
.run(async (
|
|
107
|
+
.run(async (input, { emailer }) => {
|
|
108
108
|
await emailer.send(input);
|
|
109
109
|
return { delivered: true };
|
|
110
110
|
})
|
|
@@ -131,9 +131,9 @@ const userRegistered = r
|
|
|
131
131
|
const registerUser = r
|
|
132
132
|
.task("app.tasks.registerUser")
|
|
133
133
|
.dependencies({ userRegistered, userService })
|
|
134
|
-
.run(async (
|
|
134
|
+
.run(async (input, deps) => {
|
|
135
135
|
const user = await deps.userService.create(input);
|
|
136
|
-
await deps.userRegistered
|
|
136
|
+
await deps.userRegistered({ userId: user.id, email: user.email });
|
|
137
137
|
return user;
|
|
138
138
|
})
|
|
139
139
|
.build();
|
|
@@ -309,7 +309,46 @@ const root = r
|
|
|
309
309
|
.build();
|
|
310
310
|
```
|
|
311
311
|
|
|
312
|
-
|
|
312
|
+
### HTTP Client Factory (Recommended)
|
|
313
|
+
|
|
314
|
+
The `globals.resources.httpClientFactory` automatically injects serializer, error registry, and async contexts from the store:
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
import { r, globals } from "@bluelibs/runner";
|
|
318
|
+
|
|
319
|
+
const myTask = r
|
|
320
|
+
.task("app.tasks.callRemote")
|
|
321
|
+
.dependencies({ clientFactory: globals.resources.httpClientFactory })
|
|
322
|
+
.run(async (input, { clientFactory }) => {
|
|
323
|
+
// Client automatically has serializer, errors, and contexts injected
|
|
324
|
+
const client = clientFactory({
|
|
325
|
+
baseUrl: process.env.API_URL,
|
|
326
|
+
auth: { token: process.env.API_TOKEN },
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
return await client.task("remote.task", input);
|
|
330
|
+
})
|
|
331
|
+
.build();
|
|
332
|
+
|
|
333
|
+
// Node streaming clients via Node DI factories
|
|
334
|
+
import { globals as nodeGlobals } from "@bluelibs/runner/node";
|
|
335
|
+
|
|
336
|
+
const nodeTask = r
|
|
337
|
+
.task("app.tasks.streamingCall")
|
|
338
|
+
.dependencies({ smartFactory: nodeGlobals.resources.httpSmartClientFactory })
|
|
339
|
+
.run(async (input, { smartFactory }) => {
|
|
340
|
+
const client = smartFactory({
|
|
341
|
+
baseUrl: process.env.API_URL,
|
|
342
|
+
});
|
|
343
|
+
// Supports duplex streams and multipart uploads
|
|
344
|
+
return await client.task("remote.streaming.task", input);
|
|
345
|
+
})
|
|
346
|
+
.build();
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Direct Client Creation (Legacy)
|
|
350
|
+
|
|
351
|
+
You can also create clients directly without DI (manual serializer/error/context passing):
|
|
313
352
|
|
|
314
353
|
```ts
|
|
315
354
|
import { createHttpClient } from "@bluelibs/runner";
|
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ const createUser = r
|
|
|
76
76
|
.task("app.tasks.createUser")
|
|
77
77
|
.dependencies({ server, logger: globals.resources.logger })
|
|
78
78
|
.inputSchema<{ name: string }>({ parse: (value) => value })
|
|
79
|
-
.run(async (
|
|
79
|
+
.run(async (input, { server, logger }) => {
|
|
80
80
|
await logger.info(`Creating ${input.name}`);
|
|
81
81
|
return { id: "user-123", name: input.name };
|
|
82
82
|
})
|
|
@@ -144,7 +144,7 @@ import { r } from "@bluelibs/runner";
|
|
|
144
144
|
const sendEmail = r
|
|
145
145
|
.task("app.tasks.sendEmail")
|
|
146
146
|
.dependencies({ emailService, logger })
|
|
147
|
-
.run(async (
|
|
147
|
+
.run(async (input, { emailService, logger }) => {
|
|
148
148
|
await logger.info(`Sending email to ${input.to}`);
|
|
149
149
|
return emailService.send(input);
|
|
150
150
|
})
|
|
@@ -280,9 +280,9 @@ const userRegistered = r
|
|
|
280
280
|
const registerUser = r
|
|
281
281
|
.task("app.tasks.registerUser")
|
|
282
282
|
.dependencies({ userService, userRegistered })
|
|
283
|
-
.run(async (
|
|
283
|
+
.run(async (input, { userService, userRegistered }) => {
|
|
284
284
|
const user = await userService.createUser(input);
|
|
285
|
-
await userRegistered
|
|
285
|
+
await userRegistered({ userId: user.id, email: user.email });
|
|
286
286
|
return user;
|
|
287
287
|
})
|
|
288
288
|
.build();
|
|
@@ -484,14 +484,14 @@ 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
|
|
|
491
491
|
const adminTask = r
|
|
492
492
|
.task("app.tasks.adminOnly")
|
|
493
493
|
.middleware([authMiddleware.with({ requiredRole: "admin" })])
|
|
494
|
-
.run(async (
|
|
494
|
+
.run(async (input) => "Secret admin data")
|
|
495
495
|
.build();
|
|
496
496
|
```
|
|
497
497
|
|
|
@@ -531,7 +531,7 @@ const resourceAuthMiddleware = r.middleware
|
|
|
531
531
|
const adminTask = r
|
|
532
532
|
.task("app.tasks.adminOnly")
|
|
533
533
|
.middleware([authMiddleware.with({ requiredRole: "admin" })])
|
|
534
|
-
.run(async (
|
|
534
|
+
.run(async (input: { user: { role: string } }) => ({
|
|
535
535
|
user: { role: input.user.role, verified: true },
|
|
536
536
|
}))
|
|
537
537
|
.build();
|
|
@@ -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 (
|
|
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
|
|
|
@@ -760,7 +783,7 @@ const userNotFoundError = r
|
|
|
760
783
|
const getUser = r
|
|
761
784
|
.task("app.tasks.getUser")
|
|
762
785
|
.dependencies({ userNotFoundError })
|
|
763
|
-
.run(async (
|
|
786
|
+
.run(async (input, { userNotFoundError }) => {
|
|
764
787
|
userNotFoundError.throw({ code: 404, message: `User ${input} not found` });
|
|
765
788
|
})
|
|
766
789
|
.build();
|
|
@@ -884,7 +907,7 @@ import { r, run } from "@bluelibs/runner";
|
|
|
884
907
|
|
|
885
908
|
const calculatorTask = r
|
|
886
909
|
.task("app.tasks.calculator")
|
|
887
|
-
.run(async (
|
|
910
|
+
.run(async (input: { value: number }) => {
|
|
888
911
|
console.log("3. Task is running...");
|
|
889
912
|
return { result: input.value + 1 };
|
|
890
913
|
})
|
|
@@ -949,7 +972,7 @@ const userRegistration = r
|
|
|
949
972
|
emailService: emailService.optional(), // Optional - won't fail if missing
|
|
950
973
|
analytics: analyticsService.optional(), // Optional - graceful degradation
|
|
951
974
|
})
|
|
952
|
-
.run(async (
|
|
975
|
+
.run(async (input, { database, emailService, analytics }) => {
|
|
953
976
|
// Create user (required)
|
|
954
977
|
const user = await database.users.create(userData);
|
|
955
978
|
|
|
@@ -1051,12 +1074,13 @@ app = app.build();
|
|
|
1051
1074
|
const remoteTasksTunnel = r
|
|
1052
1075
|
.resource("app.tunnels.http")
|
|
1053
1076
|
.tags([globals.tags.tunnel])
|
|
1054
|
-
.
|
|
1077
|
+
.dependencies({ createClient: globals.resource.httpClientFactory })
|
|
1078
|
+
.init(async (_, { createClient }) => ({
|
|
1055
1079
|
mode: "client", // or "server", or "none", or "both" for emulating network infrastructure
|
|
1056
1080
|
transport: "http", // the only one supported for now
|
|
1057
1081
|
// Selectively forward tasks starting with "remote.tasks."
|
|
1058
1082
|
tasks: (t) => t.id.startsWith("remote.tasks."),
|
|
1059
|
-
client:
|
|
1083
|
+
client: createClient({
|
|
1060
1084
|
url: "http://remote-runner:8080/__runner",
|
|
1061
1085
|
}),
|
|
1062
1086
|
}))
|
|
@@ -1184,9 +1208,7 @@ import type {
|
|
|
1184
1208
|
// Task example
|
|
1185
1209
|
const add = r
|
|
1186
1210
|
.task("calc.add")
|
|
1187
|
-
.run(
|
|
1188
|
-
async ({ input }: { input: { a: number; b: number } }) => input.a + input.b,
|
|
1189
|
-
)
|
|
1211
|
+
.run(async (input: { a: number; b: number }) => input.a + input.b)
|
|
1190
1212
|
.build();
|
|
1191
1213
|
|
|
1192
1214
|
type AddInput = ExtractTaskInput<typeof add>; // { a: number; b: number }
|
|
@@ -1245,7 +1267,7 @@ const requestMiddleware = r.middleware
|
|
|
1245
1267
|
const handleRequest = r
|
|
1246
1268
|
.task("app.handleRequest")
|
|
1247
1269
|
.middleware([requestMiddleware])
|
|
1248
|
-
.run(async (
|
|
1270
|
+
.run(async (input: { path: string }) => {
|
|
1249
1271
|
const request = requestContext.use();
|
|
1250
1272
|
console.log(`Processing ${input.path} (Request ID: ${request.requestId})`);
|
|
1251
1273
|
return { success: true, requestId: request.requestId };
|
|
@@ -1392,10 +1414,10 @@ const expensiveTask = r
|
|
|
1392
1414
|
globals.middleware.task.cache.with({
|
|
1393
1415
|
// lru-cache options by default
|
|
1394
1416
|
ttl: 60 * 1000, // Cache for 1 minute
|
|
1395
|
-
keyBuilder: (taskId, input) => `${taskId}-${
|
|
1417
|
+
keyBuilder: (taskId, input: any) => `${taskId}-${input.userId}`, // optional key builder
|
|
1396
1418
|
}),
|
|
1397
1419
|
])
|
|
1398
|
-
.run(async (
|
|
1420
|
+
.run(async (input: { userId: string }) => {
|
|
1399
1421
|
// This expensive operation will be cached
|
|
1400
1422
|
return await doExpensiveCalculation(input.userId);
|
|
1401
1423
|
})
|
|
@@ -1423,7 +1445,7 @@ import { r } from "@bluelibs/runner";
|
|
|
1423
1445
|
|
|
1424
1446
|
const redisCacheFactory = r
|
|
1425
1447
|
.task("globals.tasks.cacheFactory") // Same ID as the default task
|
|
1426
|
-
.run(async (
|
|
1448
|
+
.run(async (input: { input: any }) => new RedisCache(input))
|
|
1427
1449
|
.build();
|
|
1428
1450
|
|
|
1429
1451
|
const app = r
|
|
@@ -1468,7 +1490,7 @@ Here are real performance metrics from our comprehensive benchmark suite on an M
|
|
|
1468
1490
|
const userTask = r
|
|
1469
1491
|
.task("user.create")
|
|
1470
1492
|
.middleware([auth, logging, metrics])
|
|
1471
|
-
.run(async (
|
|
1493
|
+
.run(async (input) => database.users.create(input))
|
|
1472
1494
|
.build();
|
|
1473
1495
|
|
|
1474
1496
|
// 1000 executions = ~5ms total time
|
|
@@ -1528,7 +1550,7 @@ const database = r
|
|
|
1528
1550
|
const expensiveTask = r
|
|
1529
1551
|
.task("app.performance.expensive")
|
|
1530
1552
|
.middleware([globals.middleware.cache.with({ ttl: 60000 })])
|
|
1531
|
-
.run(async (
|
|
1553
|
+
.run(async (input) => {
|
|
1532
1554
|
// This expensive computation is cached
|
|
1533
1555
|
return performExpensiveCalculation(input);
|
|
1534
1556
|
})
|
|
@@ -1751,7 +1773,7 @@ The logger accepts rich, structured data that makes debugging actually useful:
|
|
|
1751
1773
|
const userTask = r
|
|
1752
1774
|
.task("app.tasks.user.create")
|
|
1753
1775
|
.dependencies({ logger: globals.resources.logger })
|
|
1754
|
-
.run(async (
|
|
1776
|
+
.run(async (input, { logger }) => {
|
|
1755
1777
|
// Basic message
|
|
1756
1778
|
logger.info("Creating new user");
|
|
1757
1779
|
|
|
@@ -1759,7 +1781,7 @@ const userTask = r
|
|
|
1759
1781
|
logger.info("User creation attempt", {
|
|
1760
1782
|
source: userTask.id,
|
|
1761
1783
|
data: {
|
|
1762
|
-
email:
|
|
1784
|
+
email: input.email,
|
|
1763
1785
|
registrationSource: "web",
|
|
1764
1786
|
timestamp: new Date().toISOString(),
|
|
1765
1787
|
},
|
|
@@ -1767,7 +1789,7 @@ const userTask = r
|
|
|
1767
1789
|
|
|
1768
1790
|
// With error information
|
|
1769
1791
|
try {
|
|
1770
|
-
const user = await createUser(input
|
|
1792
|
+
const user = await createUser(input);
|
|
1771
1793
|
logger.info("User created successfully", {
|
|
1772
1794
|
data: { userId: user.id, email: user.email },
|
|
1773
1795
|
});
|
|
@@ -1775,7 +1797,7 @@ const userTask = r
|
|
|
1775
1797
|
logger.error("User creation failed", {
|
|
1776
1798
|
error,
|
|
1777
1799
|
data: {
|
|
1778
|
-
attemptedEmail:
|
|
1800
|
+
attemptedEmail: input.email,
|
|
1779
1801
|
validationErrors: error.validationErrors,
|
|
1780
1802
|
},
|
|
1781
1803
|
});
|
|
@@ -2028,9 +2050,9 @@ const criticalTask = r
|
|
|
2028
2050
|
logTaskOnError: true,
|
|
2029
2051
|
}),
|
|
2030
2052
|
])
|
|
2031
|
-
.run(async (
|
|
2053
|
+
.run(async (input) => {
|
|
2032
2054
|
// This task will have verbose debug logging
|
|
2033
|
-
return await processPayment(input
|
|
2055
|
+
return await processPayment(input);
|
|
2034
2056
|
})
|
|
2035
2057
|
.build();
|
|
2036
2058
|
```
|
|
@@ -2171,7 +2193,7 @@ const sendWelcomeEmail = r
|
|
|
2171
2193
|
.meta({
|
|
2172
2194
|
title: "Send Welcome Email",
|
|
2173
2195
|
description: "Sends a welcome email to newly registered users",
|
|
2174
|
-
}
|
|
2196
|
+
})
|
|
2175
2197
|
.dependencies({ emailService })
|
|
2176
2198
|
.run(async ({ input: userData }, { emailService }) => {
|
|
2177
2199
|
// Email sending logic
|
|
@@ -2211,7 +2233,7 @@ const expensiveApiTask = r
|
|
|
2211
2233
|
version: "2.1.0",
|
|
2212
2234
|
apiVersion: "v2",
|
|
2213
2235
|
costLevel: "high", // Custom property!
|
|
2214
|
-
}
|
|
2236
|
+
})
|
|
2215
2237
|
.run(async ({ input: prompt }) => {
|
|
2216
2238
|
// AI generation logic
|
|
2217
2239
|
})
|
|
@@ -2224,7 +2246,7 @@ const database = r
|
|
|
2224
2246
|
healthCheck: "/health/db", // Custom property!
|
|
2225
2247
|
dependencies: ["postgresql", "connection-pool"],
|
|
2226
2248
|
scalingPolicy: "auto",
|
|
2227
|
-
}
|
|
2249
|
+
})
|
|
2228
2250
|
// .init(async () => { /* ... */ })
|
|
2229
2251
|
.build();
|
|
2230
2252
|
```
|
|
@@ -2254,7 +2276,7 @@ const testEmailer = override(productionEmailer, {
|
|
|
2254
2276
|
// Using spread operator works the same way but does not provide type-safety.
|
|
2255
2277
|
const testEmailer = r
|
|
2256
2278
|
.resource("app.emailer")
|
|
2257
|
-
.init(async () => ({}
|
|
2279
|
+
.init(async () => ({}))
|
|
2258
2280
|
.build();
|
|
2259
2281
|
|
|
2260
2282
|
const app = r
|
|
@@ -2291,7 +2313,7 @@ const originalMiddleware = taskMiddleware({
|
|
|
2291
2313
|
const overriddenMiddleware = override(originalMiddleware, {
|
|
2292
2314
|
run: async ({ task, next }) => {
|
|
2293
2315
|
const result = await next(task?.input);
|
|
2294
|
-
return { wrapped: result }
|
|
2316
|
+
return { wrapped: result };
|
|
2295
2317
|
},
|
|
2296
2318
|
});
|
|
2297
2319
|
|
|
@@ -2420,7 +2442,7 @@ const createUserTask = r
|
|
|
2420
2442
|
.inputSchema(userSchema) // Works directly with Zod!
|
|
2421
2443
|
.run(async ({ input: userData }) => {
|
|
2422
2444
|
// userData is validated and properly typed
|
|
2423
|
-
return { id: "user-123", ...
|
|
2445
|
+
return { id: "user-123", ...userData };
|
|
2424
2446
|
})
|
|
2425
2447
|
.build();
|
|
2426
2448
|
|
|
@@ -2719,7 +2741,7 @@ type UserData = z.infer<typeof userSchema>;
|
|
|
2719
2741
|
const createUser = r
|
|
2720
2742
|
.task("app.tasks.createUser.zod")
|
|
2721
2743
|
.inputSchema(userSchema)
|
|
2722
|
-
.run(async (
|
|
2744
|
+
.run(async (input: { input: UserData }) => {
|
|
2723
2745
|
// Both runtime validation AND compile-time typing
|
|
2724
2746
|
return { id: "user-123", ...input };
|
|
2725
2747
|
})
|