@mastra/koa 1.6.11-alpha.0 → 1.6.11-alpha.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/CHANGELOG.md +16 -0
- package/dist/index.cjs +765 -867
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +763 -864
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -1,874 +1,773 @@
|
|
|
1
|
-
import { Busboy } from
|
|
2
|
-
import { coreAuthMiddleware, findMatchingCustomRoute, isProtectedCustomRoute } from
|
|
3
|
-
import { MastraServer as MastraServer$1, isZodError,
|
|
4
|
-
import { RequestContext } from
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return new globalThis.Request(url, {
|
|
21
|
-
method: ctx.method,
|
|
22
|
-
headers
|
|
23
|
-
});
|
|
1
|
+
import { Busboy } from "@fastify/busboy";
|
|
2
|
+
import { coreAuthMiddleware, findMatchingCustomRoute, isProtectedCustomRoute } from "@mastra/server/auth";
|
|
3
|
+
import { MastraServer as MastraServer$1, checkRouteFGA, isZodError, normalizeQueryParams, redactStreamChunk, serializeStreamChunk } from "@mastra/server/server-adapter";
|
|
4
|
+
import { RequestContext } from "@mastra/core/request-context";
|
|
5
|
+
//#region src/auth-middleware.ts
|
|
6
|
+
function toWebRequest$1(ctx) {
|
|
7
|
+
const url = `${ctx.protocol || "http"}://${ctx.host || "localhost"}${ctx.url}`;
|
|
8
|
+
const headers = new Headers();
|
|
9
|
+
for (const [key, value] of Object.entries(ctx.headers)) {
|
|
10
|
+
if (!value) continue;
|
|
11
|
+
if (Array.isArray(value)) value.forEach((v) => headers.append(key, v));
|
|
12
|
+
else headers.set(key, value);
|
|
13
|
+
}
|
|
14
|
+
return new globalThis.Request(url, {
|
|
15
|
+
method: ctx.method,
|
|
16
|
+
headers
|
|
17
|
+
});
|
|
24
18
|
}
|
|
25
19
|
function createAuthMiddleware({ mastra, requiresAuth = true }) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
ctx.body = result.body;
|
|
65
|
-
};
|
|
20
|
+
return async (ctx, next) => {
|
|
21
|
+
if (!requiresAuth) {
|
|
22
|
+
await next();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const authConfig = mastra.getServer()?.auth;
|
|
26
|
+
if (!authConfig) {
|
|
27
|
+
await next();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
ctx.state.requestContext ??= new RequestContext();
|
|
31
|
+
ctx.state.mastra ??= mastra;
|
|
32
|
+
const path = String(ctx.path || "/");
|
|
33
|
+
const method = String(ctx.method || "GET");
|
|
34
|
+
const customRouteAuthConfig = new Map(ctx.state.customRouteAuthConfig ?? []);
|
|
35
|
+
customRouteAuthConfig.set(`${method}:${path}`, true);
|
|
36
|
+
const authHeader = ctx.headers.authorization;
|
|
37
|
+
let token = authHeader ? authHeader.replace("Bearer ", "") : null;
|
|
38
|
+
if (!token) token = ctx.query.apiKey || null;
|
|
39
|
+
const result = await coreAuthMiddleware({
|
|
40
|
+
path,
|
|
41
|
+
method,
|
|
42
|
+
getHeader: (name) => ctx.headers[name.toLowerCase()],
|
|
43
|
+
mastra,
|
|
44
|
+
authConfig,
|
|
45
|
+
customRouteAuthConfig,
|
|
46
|
+
requestContext: ctx.state.requestContext,
|
|
47
|
+
rawRequest: toWebRequest$1(ctx),
|
|
48
|
+
token,
|
|
49
|
+
buildAuthorizeContext: () => toWebRequest$1(ctx)
|
|
50
|
+
});
|
|
51
|
+
if (result.action === "next") {
|
|
52
|
+
await next();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
ctx.status = result.status;
|
|
56
|
+
ctx.body = result.body;
|
|
57
|
+
};
|
|
66
58
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/index.ts
|
|
61
|
+
let _hasPermissionPromise;
|
|
70
62
|
function loadHasPermission() {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
);
|
|
76
|
-
return void 0;
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
return _hasPermissionPromise;
|
|
63
|
+
if (!_hasPermissionPromise) _hasPermissionPromise = import("@mastra/core/auth/ee").then((m) => m.hasPermission).catch(() => {
|
|
64
|
+
console.error("[@mastra/koa] Auth features require @mastra/core >= 1.6.0. Please upgrade: npm install @mastra/core@latest");
|
|
65
|
+
});
|
|
66
|
+
return _hasPermissionPromise;
|
|
80
67
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return new globalThis.Request(url, {
|
|
96
|
-
method: ctx.method,
|
|
97
|
-
headers
|
|
98
|
-
});
|
|
68
|
+
/**
|
|
69
|
+
* Convert Koa context to Web API Request for cookie-based auth providers.
|
|
70
|
+
*/
|
|
71
|
+
function toWebRequest(ctx) {
|
|
72
|
+
const url = `${ctx.protocol || "http"}://${ctx.host || "localhost"}${ctx.url}`;
|
|
73
|
+
const headers = new Headers();
|
|
74
|
+
for (const [key, value] of Object.entries(ctx.headers)) if (value) if (Array.isArray(value)) value.forEach((v) => headers.append(key, v));
|
|
75
|
+
else headers.set(key, value);
|
|
76
|
+
return new globalThis.Request(url, {
|
|
77
|
+
method: ctx.method,
|
|
78
|
+
headers
|
|
79
|
+
});
|
|
99
80
|
}
|
|
100
81
|
var MastraServer = class extends MastraServer$1 {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
if (permissionError) {
|
|
789
|
-
ctx.status = permissionError.status;
|
|
790
|
-
ctx.body = {
|
|
791
|
-
error: permissionError.error,
|
|
792
|
-
message: permissionError.message
|
|
793
|
-
};
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
const fgaError = await checkRouteFGA(server.mastra, serverRoute, ctx.state.requestContext, {
|
|
799
|
-
...matchedRoute?.params ?? {},
|
|
800
|
-
...ctx.query,
|
|
801
|
-
...typeof ctx.request.body === "object" && ctx.request.body !== null ? ctx.request.body : {}
|
|
802
|
-
});
|
|
803
|
-
if (fgaError) {
|
|
804
|
-
ctx.status = fgaError.status;
|
|
805
|
-
ctx.body = { error: fgaError.error, message: fgaError.message };
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
const response = await server.handleCustomRouteRequest(
|
|
810
|
-
`${ctx.protocol}://${ctx.host}${ctx.originalUrl || ctx.url}`,
|
|
811
|
-
ctx.method,
|
|
812
|
-
ctx.headers,
|
|
813
|
-
ctx.request.body,
|
|
814
|
-
ctx.state.requestContext,
|
|
815
|
-
customRouteAbortController.signal
|
|
816
|
-
);
|
|
817
|
-
if (!response) return next();
|
|
818
|
-
ctx.respond = false;
|
|
819
|
-
await server.writeCustomRouteResponse(response, ctx.res, customRouteAbortController.signal);
|
|
820
|
-
} finally {
|
|
821
|
-
ctx.res.off("close", abortCustomRouteIfOpen);
|
|
822
|
-
ctx.res.off("error", abortCustomRouteIfOpen);
|
|
823
|
-
}
|
|
824
|
-
});
|
|
825
|
-
}
|
|
826
|
-
registerContextMiddleware() {
|
|
827
|
-
this.app.use(this.createContextMiddleware());
|
|
828
|
-
}
|
|
829
|
-
registerAuthMiddleware() {
|
|
830
|
-
}
|
|
831
|
-
registerHttpLoggingMiddleware() {
|
|
832
|
-
if (!this.httpLoggingConfig?.enabled) {
|
|
833
|
-
return;
|
|
834
|
-
}
|
|
835
|
-
const server = this;
|
|
836
|
-
this.app.use(async function mastraHttpLogger(ctx, next) {
|
|
837
|
-
if (!server.shouldLogRequest(ctx.path)) {
|
|
838
|
-
return next();
|
|
839
|
-
}
|
|
840
|
-
const start = Date.now();
|
|
841
|
-
const method = ctx.method;
|
|
842
|
-
const path = ctx.path;
|
|
843
|
-
await next();
|
|
844
|
-
const duration = Date.now() - start;
|
|
845
|
-
const status = ctx.status;
|
|
846
|
-
const level = server.httpLoggingConfig?.level || "info";
|
|
847
|
-
const logData = {
|
|
848
|
-
method,
|
|
849
|
-
path,
|
|
850
|
-
status,
|
|
851
|
-
duration: `${duration}ms`
|
|
852
|
-
};
|
|
853
|
-
if (server.httpLoggingConfig?.includeQueryParams) {
|
|
854
|
-
logData.query = ctx.query;
|
|
855
|
-
}
|
|
856
|
-
if (server.httpLoggingConfig?.includeHeaders) {
|
|
857
|
-
const headers = { ...ctx.headers };
|
|
858
|
-
const redactHeaders = server.httpLoggingConfig.redactHeaders || [];
|
|
859
|
-
redactHeaders.forEach((h) => {
|
|
860
|
-
const key = h.toLowerCase();
|
|
861
|
-
if (headers[key] !== void 0) {
|
|
862
|
-
headers[key] = "[REDACTED]";
|
|
863
|
-
}
|
|
864
|
-
});
|
|
865
|
-
logData.headers = headers;
|
|
866
|
-
}
|
|
867
|
-
server.logger[level](`${method} ${path} ${status} ${duration}ms`, logData);
|
|
868
|
-
});
|
|
869
|
-
}
|
|
82
|
+
activeRouteDispatchers = /* @__PURE__ */ new WeakMap();
|
|
83
|
+
async init() {
|
|
84
|
+
this.registerErrorMiddleware();
|
|
85
|
+
await super.init();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Register a global error-handling middleware at the top of the middleware chain.
|
|
89
|
+
* This acts as a safety net for errors that propagate past route handlers
|
|
90
|
+
* (e.g., from auth middleware, context middleware, or when route handlers re-throw).
|
|
91
|
+
*
|
|
92
|
+
* When `server.onError` is configured, calls it and uses the response.
|
|
93
|
+
* Otherwise provides a default JSON error response.
|
|
94
|
+
*
|
|
95
|
+
* Errors are emitted on the app for logging (Koa convention) but NOT re-thrown,
|
|
96
|
+
* so this middleware is the final error boundary. Users who need custom error handling
|
|
97
|
+
* should use `server.onError` or register their own middleware between this and the routes.
|
|
98
|
+
*/
|
|
99
|
+
registerErrorMiddleware() {
|
|
100
|
+
const server = this;
|
|
101
|
+
this.app.use(async function mastraErrorBoundary(ctx, next) {
|
|
102
|
+
try {
|
|
103
|
+
await next();
|
|
104
|
+
} catch (err) {
|
|
105
|
+
if (await server.handleOnError(err, ctx)) return;
|
|
106
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
107
|
+
let status = 500;
|
|
108
|
+
if (err && typeof err === "object") {
|
|
109
|
+
if ("status" in err) status = err.status;
|
|
110
|
+
else if ("details" in err && err.details && typeof err.details === "object" && "status" in err.details) status = err.details.status;
|
|
111
|
+
}
|
|
112
|
+
ctx.status = status;
|
|
113
|
+
ctx.body = { error: error.message || "Unknown error" };
|
|
114
|
+
ctx.app.emit("error", err, ctx);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Try to handle an error using the `server.onError` hook.
|
|
120
|
+
* Creates a minimal context shim compatible with the Hono-style onError signature.
|
|
121
|
+
*
|
|
122
|
+
* @returns true if the error was handled and the response was set on ctx
|
|
123
|
+
*/
|
|
124
|
+
async handleOnError(err, ctx) {
|
|
125
|
+
if (ctx._mastraOnErrorAttempted) return false;
|
|
126
|
+
ctx._mastraOnErrorAttempted = true;
|
|
127
|
+
const onError = this.mastra.getServer()?.onError;
|
|
128
|
+
if (!onError) return false;
|
|
129
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
130
|
+
const shimContext = {
|
|
131
|
+
json: (data, status = 200) => new Response(JSON.stringify(data), {
|
|
132
|
+
status,
|
|
133
|
+
headers: { "Content-Type": "application/json" }
|
|
134
|
+
}),
|
|
135
|
+
req: {
|
|
136
|
+
path: ctx.path,
|
|
137
|
+
method: ctx.method,
|
|
138
|
+
header: (name) => {
|
|
139
|
+
const value = ctx.headers[name.toLowerCase()];
|
|
140
|
+
if (Array.isArray(value)) return value.join(", ");
|
|
141
|
+
return value;
|
|
142
|
+
},
|
|
143
|
+
url: ctx.url
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
try {
|
|
147
|
+
const response = await onError(error, shimContext);
|
|
148
|
+
ctx.status = response.status;
|
|
149
|
+
if ((response.headers.get("content-type") || "").includes("application/json")) ctx.body = await response.json();
|
|
150
|
+
else ctx.body = await response.text();
|
|
151
|
+
return true;
|
|
152
|
+
} catch (onErrorErr) {
|
|
153
|
+
this.mastra.getLogger()?.error("Error in custom onError handler", { error: onErrorErr instanceof Error ? {
|
|
154
|
+
message: onErrorErr.message,
|
|
155
|
+
stack: onErrorErr.stack
|
|
156
|
+
} : onErrorErr });
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
createContextMiddleware() {
|
|
161
|
+
const server = this;
|
|
162
|
+
return async function mastraRequestContext(ctx, next) {
|
|
163
|
+
let bodyRequestContext;
|
|
164
|
+
let paramsRequestContext;
|
|
165
|
+
if (ctx.method === "POST" || ctx.method === "PUT") {
|
|
166
|
+
if (ctx.headers["content-type"]?.includes("application/json") && ctx.request.body) {
|
|
167
|
+
const body = ctx.request.body;
|
|
168
|
+
if (body.requestContext) bodyRequestContext = body.requestContext;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (ctx.method === "GET") try {
|
|
172
|
+
const encodedRequestContext = ctx.query.requestContext;
|
|
173
|
+
if (typeof encodedRequestContext === "string") try {
|
|
174
|
+
paramsRequestContext = JSON.parse(encodedRequestContext);
|
|
175
|
+
} catch {
|
|
176
|
+
try {
|
|
177
|
+
const json = Buffer.from(encodedRequestContext, "base64").toString("utf-8");
|
|
178
|
+
paramsRequestContext = JSON.parse(json);
|
|
179
|
+
} catch {}
|
|
180
|
+
}
|
|
181
|
+
} catch {}
|
|
182
|
+
const requestContext = server.mergeRequestContext({
|
|
183
|
+
paramsRequestContext,
|
|
184
|
+
bodyRequestContext
|
|
185
|
+
});
|
|
186
|
+
server.applyRequestMetadataToContext({
|
|
187
|
+
requestContext,
|
|
188
|
+
getHeader: (name) => ctx.get(name)
|
|
189
|
+
});
|
|
190
|
+
ctx.state.requestContext = requestContext;
|
|
191
|
+
ctx.state.mastra = server.mastra;
|
|
192
|
+
ctx.state.tools = server.tools || {};
|
|
193
|
+
if (server.taskStore) ctx.state.taskStore = server.taskStore;
|
|
194
|
+
ctx.state.customRouteAuthConfig = server.customRouteAuthConfig;
|
|
195
|
+
const controller = new AbortController();
|
|
196
|
+
ctx.req.on("close", () => {
|
|
197
|
+
if (!ctx.res.writableEnded) controller.abort();
|
|
198
|
+
});
|
|
199
|
+
ctx.state.abortSignal = controller.signal;
|
|
200
|
+
await next();
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
getRouteDispatcherGroup(app) {
|
|
204
|
+
const middlewareStack = app.middleware;
|
|
205
|
+
const supportsReuse = Array.isArray(middlewareStack);
|
|
206
|
+
if (supportsReuse) {
|
|
207
|
+
const activeGroup = this.activeRouteDispatchers.get(app);
|
|
208
|
+
if (activeGroup && middlewareStack.length === activeGroup.stackLengthAfterRegistration) return activeGroup;
|
|
209
|
+
}
|
|
210
|
+
const group = {
|
|
211
|
+
routes: [],
|
|
212
|
+
stackLengthAfterRegistration: 0
|
|
213
|
+
};
|
|
214
|
+
app.use(this.createRouteDispatcherMiddleware(group));
|
|
215
|
+
if (supportsReuse) {
|
|
216
|
+
group.stackLengthAfterRegistration = app.middleware.length;
|
|
217
|
+
this.activeRouteDispatchers.set(app, group);
|
|
218
|
+
}
|
|
219
|
+
return group;
|
|
220
|
+
}
|
|
221
|
+
createRouteDispatcherMiddleware(group) {
|
|
222
|
+
const server = this;
|
|
223
|
+
return async function mastraRouteDispatcher(ctx, next) {
|
|
224
|
+
const matchedRoute = server.findRegisteredRoute(group.routes, ctx);
|
|
225
|
+
if (!matchedRoute) {
|
|
226
|
+
await next();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
await server.handleMatchedRoute(matchedRoute, ctx);
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
findRegisteredRoute(routes, ctx) {
|
|
233
|
+
const method = ctx.method.toUpperCase();
|
|
234
|
+
for (const registeredRoute of routes) {
|
|
235
|
+
if (registeredRoute.route.method.toUpperCase() !== "ALL" && method !== registeredRoute.route.method.toUpperCase()) continue;
|
|
236
|
+
const match = registeredRoute.pathRegex.exec(ctx.path);
|
|
237
|
+
if (!match) continue;
|
|
238
|
+
ctx.params = {};
|
|
239
|
+
registeredRoute.paramNames.forEach((name, index) => {
|
|
240
|
+
ctx.params[name] = match[index + 1];
|
|
241
|
+
});
|
|
242
|
+
return registeredRoute;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async handleMatchedRoute(registeredRoute, ctx) {
|
|
246
|
+
const { route, prefix } = registeredRoute;
|
|
247
|
+
const authError = await this.checkRouteAuth(route, {
|
|
248
|
+
path: String(ctx.path || "/"),
|
|
249
|
+
method: String(ctx.method || "GET"),
|
|
250
|
+
getHeader: (name) => ctx.headers[name.toLowerCase()],
|
|
251
|
+
getQuery: (name) => ctx.query[name],
|
|
252
|
+
requestContext: ctx.state.requestContext,
|
|
253
|
+
request: toWebRequest(ctx),
|
|
254
|
+
buildAuthorizeContext: () => toWebRequest(ctx)
|
|
255
|
+
});
|
|
256
|
+
if (authError) {
|
|
257
|
+
if (authError.headers) for (const [key, value] of Object.entries(authError.headers)) ctx.set(key, value);
|
|
258
|
+
if (authError.error) {
|
|
259
|
+
ctx.status = authError.status;
|
|
260
|
+
ctx.body = { error: authError.error };
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const params = await this.getParams(route, ctx);
|
|
265
|
+
if (params.bodyParseError) {
|
|
266
|
+
ctx.status = 400;
|
|
267
|
+
ctx.body = {
|
|
268
|
+
error: "Invalid request body",
|
|
269
|
+
issues: [{
|
|
270
|
+
field: "body",
|
|
271
|
+
message: params.bodyParseError.message
|
|
272
|
+
}]
|
|
273
|
+
};
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (params.queryParams) try {
|
|
277
|
+
params.queryParams = await this.parseQueryParams(route, params.queryParams);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
this.mastra.getLogger()?.error("Error parsing query params", { error: error instanceof Error ? {
|
|
280
|
+
message: error.message,
|
|
281
|
+
stack: error.stack
|
|
282
|
+
} : error });
|
|
283
|
+
if (isZodError(error)) {
|
|
284
|
+
const resolved = this.resolveValidationError(route, error, "query");
|
|
285
|
+
ctx.status = resolved.status;
|
|
286
|
+
ctx.body = resolved.body;
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
ctx.status = 400;
|
|
290
|
+
ctx.body = {
|
|
291
|
+
error: "Invalid query parameters",
|
|
292
|
+
issues: [{
|
|
293
|
+
field: "unknown",
|
|
294
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
295
|
+
}]
|
|
296
|
+
};
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (params.body) try {
|
|
300
|
+
params.body = await this.parseBody(route, params.body);
|
|
301
|
+
} catch (error) {
|
|
302
|
+
this.mastra.getLogger()?.error("Error parsing body", { error: error instanceof Error ? {
|
|
303
|
+
message: error.message,
|
|
304
|
+
stack: error.stack
|
|
305
|
+
} : error });
|
|
306
|
+
if (isZodError(error)) {
|
|
307
|
+
const resolved = this.resolveValidationError(route, error, "body");
|
|
308
|
+
ctx.status = resolved.status;
|
|
309
|
+
ctx.body = resolved.body;
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
ctx.status = 400;
|
|
313
|
+
ctx.body = {
|
|
314
|
+
error: "Invalid request body",
|
|
315
|
+
issues: [{
|
|
316
|
+
field: "unknown",
|
|
317
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
318
|
+
}]
|
|
319
|
+
};
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (params.urlParams) try {
|
|
323
|
+
params.urlParams = await this.parsePathParams(route, params.urlParams);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
this.mastra.getLogger()?.error("Error parsing path params", { error: error instanceof Error ? {
|
|
326
|
+
message: error.message,
|
|
327
|
+
stack: error.stack
|
|
328
|
+
} : error });
|
|
329
|
+
if (isZodError(error)) {
|
|
330
|
+
const resolved = this.resolveValidationError(route, error, "path");
|
|
331
|
+
ctx.status = resolved.status;
|
|
332
|
+
ctx.body = resolved.body;
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
ctx.status = 400;
|
|
336
|
+
ctx.body = {
|
|
337
|
+
error: "Invalid path parameters",
|
|
338
|
+
issues: [{
|
|
339
|
+
field: "unknown",
|
|
340
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
341
|
+
}]
|
|
342
|
+
};
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const handlerParams = {
|
|
346
|
+
...params.urlParams,
|
|
347
|
+
...params.queryParams,
|
|
348
|
+
...typeof params.body === "object" ? params.body : {},
|
|
349
|
+
requestContext: ctx.state.requestContext,
|
|
350
|
+
mastra: this.mastra,
|
|
351
|
+
tools: ctx.state.tools,
|
|
352
|
+
taskStore: ctx.state.taskStore,
|
|
353
|
+
abortSignal: ctx.state.abortSignal,
|
|
354
|
+
routePrefix: prefix,
|
|
355
|
+
request: toWebRequest(ctx)
|
|
356
|
+
};
|
|
357
|
+
const requestContext = ctx.state.requestContext;
|
|
358
|
+
if (this.mastra.getStudio?.()?.auth || this.mastra.getServer()?.auth) {
|
|
359
|
+
const hasPermission = await loadHasPermission();
|
|
360
|
+
if (hasPermission) {
|
|
361
|
+
const userPermissions = requestContext.get("mastra__userPermissions");
|
|
362
|
+
const permissionError = this.checkRoutePermission(route, userPermissions, hasPermission, requestContext);
|
|
363
|
+
if (permissionError) {
|
|
364
|
+
ctx.status = permissionError.status;
|
|
365
|
+
ctx.body = {
|
|
366
|
+
error: permissionError.error,
|
|
367
|
+
message: permissionError.message
|
|
368
|
+
};
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const fgaError = await checkRouteFGA(this.mastra, route, requestContext, {
|
|
374
|
+
...params.urlParams,
|
|
375
|
+
...params.queryParams,
|
|
376
|
+
...typeof params.body === "object" ? params.body : {}
|
|
377
|
+
});
|
|
378
|
+
if (fgaError) {
|
|
379
|
+
ctx.status = fgaError.status;
|
|
380
|
+
ctx.body = {
|
|
381
|
+
error: fgaError.error,
|
|
382
|
+
message: fgaError.message
|
|
383
|
+
};
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
const result = await route.handler(handlerParams);
|
|
388
|
+
await this.sendResponse(route, ctx, result, prefix);
|
|
389
|
+
} catch (error) {
|
|
390
|
+
const httpStatus = error && typeof error === "object" && "status" in error ? error.status : void 0;
|
|
391
|
+
if (!(typeof httpStatus === "number" && httpStatus >= 400 && httpStatus < 500)) this.mastra.getLogger()?.error("Error calling handler", {
|
|
392
|
+
error: error instanceof Error ? {
|
|
393
|
+
message: error.message,
|
|
394
|
+
stack: error.stack
|
|
395
|
+
} : error,
|
|
396
|
+
path: route.path,
|
|
397
|
+
method: route.method
|
|
398
|
+
});
|
|
399
|
+
if (error && typeof error === "object") {
|
|
400
|
+
if (!("status" in error)) {
|
|
401
|
+
if ("details" in error && error.details && typeof error.details === "object" && "status" in error.details) error.status = error.details.status;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (await this.handleOnError(error, ctx)) return;
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async stream(route, ctx, result) {
|
|
409
|
+
ctx.respond = false;
|
|
410
|
+
const streamFormat = route.streamFormat || "stream";
|
|
411
|
+
const sseHeaders = streamFormat === "sse" ? {
|
|
412
|
+
"Content-Type": "text/event-stream",
|
|
413
|
+
"Cache-Control": "no-cache",
|
|
414
|
+
Connection: "keep-alive",
|
|
415
|
+
"X-Accel-Buffering": "no"
|
|
416
|
+
} : { "Content-Type": "text/plain" };
|
|
417
|
+
ctx.res.writeHead(200, {
|
|
418
|
+
...sseHeaders,
|
|
419
|
+
"Transfer-Encoding": "chunked"
|
|
420
|
+
});
|
|
421
|
+
if (streamFormat === "sse" && route.sseFlushOnConnect) ctx.res.write(": connected\n\n");
|
|
422
|
+
const reader = (result instanceof ReadableStream ? result : result.fullStream).getReader();
|
|
423
|
+
ctx.res.on("close", () => {
|
|
424
|
+
reader.cancel("request aborted");
|
|
425
|
+
});
|
|
426
|
+
try {
|
|
427
|
+
while (true) {
|
|
428
|
+
const { done, value } = await reader.read();
|
|
429
|
+
if (done) break;
|
|
430
|
+
if (value) {
|
|
431
|
+
if (streamFormat === "sse" && typeof value === "string" && value.startsWith(":")) {
|
|
432
|
+
ctx.res.write(value);
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const outputValue = this.streamOptions?.redact ?? true ? redactStreamChunk(value) : value;
|
|
436
|
+
const serialized = serializeStreamChunk(outputValue);
|
|
437
|
+
if (!serialized.ok) {
|
|
438
|
+
this.mastra.getLogger()?.error("Failed to serialize stream chunk, skipping", {
|
|
439
|
+
path: route.path,
|
|
440
|
+
chunkType: outputValue?.type,
|
|
441
|
+
error: serialized.error.message
|
|
442
|
+
});
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (streamFormat === "sse") ctx.res.write(`data: ${serialized.json}\n\n`);
|
|
446
|
+
else ctx.res.write(serialized.json + "");
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
} catch (error) {
|
|
450
|
+
this.mastra.getLogger()?.error("Error in stream processing", { error: error instanceof Error ? {
|
|
451
|
+
message: error.message,
|
|
452
|
+
stack: error.stack
|
|
453
|
+
} : error });
|
|
454
|
+
} finally {
|
|
455
|
+
ctx.res.end();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
async getParams(route, ctx) {
|
|
459
|
+
const urlParams = ctx.params || {};
|
|
460
|
+
const queryParams = normalizeQueryParams(ctx.query || {});
|
|
461
|
+
let body;
|
|
462
|
+
let bodyParseError;
|
|
463
|
+
if (route.method === "POST" || route.method === "PUT" || route.method === "PATCH" || route.method === "DELETE") if ((ctx.headers["content-type"] || "").includes("multipart/form-data")) try {
|
|
464
|
+
const maxFileSize = route.maxBodySize ?? this.bodyLimitOptions?.maxSize;
|
|
465
|
+
body = await this.parseMultipartFormData(ctx, maxFileSize);
|
|
466
|
+
} catch (error) {
|
|
467
|
+
this.mastra.getLogger()?.error("Failed to parse multipart form data", { error: error instanceof Error ? {
|
|
468
|
+
message: error.message,
|
|
469
|
+
stack: error.stack
|
|
470
|
+
} : error });
|
|
471
|
+
if (error instanceof Error && error.message.toLowerCase().includes("size")) throw error;
|
|
472
|
+
bodyParseError = { message: error instanceof Error ? error.message : "Failed to parse multipart form data" };
|
|
473
|
+
}
|
|
474
|
+
else body = ctx.request.body;
|
|
475
|
+
return {
|
|
476
|
+
urlParams,
|
|
477
|
+
queryParams,
|
|
478
|
+
body,
|
|
479
|
+
bodyParseError
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Parse multipart/form-data using @fastify/busboy.
|
|
484
|
+
* Converts file uploads to Buffers and parses JSON field values.
|
|
485
|
+
*
|
|
486
|
+
* @param ctx - The Koa context object
|
|
487
|
+
* @param maxFileSize - Optional maximum file size in bytes
|
|
488
|
+
*/
|
|
489
|
+
parseMultipartFormData(ctx, maxFileSize) {
|
|
490
|
+
return new Promise((resolve, reject) => {
|
|
491
|
+
const result = {};
|
|
492
|
+
const busboy = new Busboy({
|
|
493
|
+
headers: { "content-type": ctx.headers["content-type"] },
|
|
494
|
+
limits: maxFileSize ? { fileSize: maxFileSize } : void 0
|
|
495
|
+
});
|
|
496
|
+
busboy.on("file", (fieldname, file) => {
|
|
497
|
+
const chunks = [];
|
|
498
|
+
let limitExceeded = false;
|
|
499
|
+
file.on("data", (chunk) => {
|
|
500
|
+
chunks.push(chunk);
|
|
501
|
+
});
|
|
502
|
+
file.on("limit", () => {
|
|
503
|
+
limitExceeded = true;
|
|
504
|
+
reject(/* @__PURE__ */ new Error(`File size limit exceeded${maxFileSize ? ` (max: ${maxFileSize} bytes)` : ""}`));
|
|
505
|
+
});
|
|
506
|
+
file.on("end", () => {
|
|
507
|
+
if (!limitExceeded) result[fieldname] = Buffer.concat(chunks);
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
busboy.on("field", (fieldname, value) => {
|
|
511
|
+
try {
|
|
512
|
+
result[fieldname] = JSON.parse(value);
|
|
513
|
+
} catch {
|
|
514
|
+
result[fieldname] = value;
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
busboy.on("finish", () => {
|
|
518
|
+
resolve(result);
|
|
519
|
+
});
|
|
520
|
+
busboy.on("error", (error) => {
|
|
521
|
+
reject(error);
|
|
522
|
+
});
|
|
523
|
+
ctx.req.pipe(busboy);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
async sendResponse(route, ctx, result, prefix) {
|
|
527
|
+
const resolvedPrefix = prefix ?? this.prefix ?? "";
|
|
528
|
+
if (result && typeof result === "object" && "__refreshHeaders" in result) {
|
|
529
|
+
const refreshHeaders = result.__refreshHeaders;
|
|
530
|
+
for (const [key, value] of Object.entries(refreshHeaders)) ctx.set(key, value);
|
|
531
|
+
delete result.__refreshHeaders;
|
|
532
|
+
}
|
|
533
|
+
if (route.responseType === "json") {
|
|
534
|
+
ctx.type = "application/json";
|
|
535
|
+
ctx.body = result === null || result === void 0 ? JSON.stringify(null) : result;
|
|
536
|
+
} else if (route.responseType === "stream") await this.stream(route, ctx, result);
|
|
537
|
+
else if (route.responseType === "datastream-response") {
|
|
538
|
+
ctx.respond = false;
|
|
539
|
+
const fetchResponse = result;
|
|
540
|
+
const headers = {};
|
|
541
|
+
fetchResponse.headers.forEach((value, key) => {
|
|
542
|
+
headers[key] = value;
|
|
543
|
+
});
|
|
544
|
+
ctx.res.writeHead(fetchResponse.status, headers);
|
|
545
|
+
if (fetchResponse.body) {
|
|
546
|
+
const reader = fetchResponse.body.getReader();
|
|
547
|
+
const onResError = (err) => {
|
|
548
|
+
this.mastra.getLogger()?.error("Error writing datastream response", { error: err instanceof Error ? {
|
|
549
|
+
message: err.message,
|
|
550
|
+
stack: err.stack
|
|
551
|
+
} : err });
|
|
552
|
+
reader.cancel("response write error");
|
|
553
|
+
};
|
|
554
|
+
ctx.res.once("error", onResError);
|
|
555
|
+
try {
|
|
556
|
+
while (true) {
|
|
557
|
+
const { done, value } = await reader.read();
|
|
558
|
+
if (done) break;
|
|
559
|
+
ctx.res.write(value);
|
|
560
|
+
}
|
|
561
|
+
} catch (error) {
|
|
562
|
+
this.mastra.getLogger()?.error("Error in datastream processing", { error: error instanceof Error ? {
|
|
563
|
+
message: error.message,
|
|
564
|
+
stack: error.stack
|
|
565
|
+
} : error });
|
|
566
|
+
} finally {
|
|
567
|
+
ctx.res.off("error", onResError);
|
|
568
|
+
ctx.res.end();
|
|
569
|
+
}
|
|
570
|
+
} else ctx.res.end();
|
|
571
|
+
} else if (route.responseType === "mcp-http") {
|
|
572
|
+
ctx.respond = false;
|
|
573
|
+
const { server, httpPath, mcpOptions: routeMcpOptions } = result;
|
|
574
|
+
try {
|
|
575
|
+
const rawReq = ctx.req;
|
|
576
|
+
if (ctx.request.body !== void 0) rawReq.body = ctx.request.body;
|
|
577
|
+
const options = {
|
|
578
|
+
...this.mcpOptions,
|
|
579
|
+
...routeMcpOptions
|
|
580
|
+
};
|
|
581
|
+
await server.startHTTP({
|
|
582
|
+
url: new URL(ctx.url, `http://${ctx.headers.host}`),
|
|
583
|
+
httpPath: `${resolvedPrefix}${httpPath}`,
|
|
584
|
+
req: rawReq,
|
|
585
|
+
res: ctx.res,
|
|
586
|
+
options: Object.keys(options).length > 0 ? options : void 0
|
|
587
|
+
});
|
|
588
|
+
} catch {
|
|
589
|
+
if (!ctx.res.headersSent) {
|
|
590
|
+
ctx.res.writeHead(500, { "Content-Type": "application/json" });
|
|
591
|
+
ctx.res.end(JSON.stringify({
|
|
592
|
+
jsonrpc: "2.0",
|
|
593
|
+
error: {
|
|
594
|
+
code: -32603,
|
|
595
|
+
message: "Internal server error"
|
|
596
|
+
},
|
|
597
|
+
id: null
|
|
598
|
+
}));
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
} else if (route.responseType === "mcp-sse") {
|
|
602
|
+
ctx.respond = false;
|
|
603
|
+
const { server, ssePath, messagePath } = result;
|
|
604
|
+
try {
|
|
605
|
+
const rawReq = ctx.req;
|
|
606
|
+
if (ctx.request.body !== void 0) rawReq.body = ctx.request.body;
|
|
607
|
+
await server.startSSE({
|
|
608
|
+
url: new URL(ctx.url, `http://${ctx.headers.host}`),
|
|
609
|
+
ssePath: `${resolvedPrefix}${ssePath}`,
|
|
610
|
+
messagePath: `${resolvedPrefix}${messagePath}`,
|
|
611
|
+
req: rawReq,
|
|
612
|
+
res: ctx.res
|
|
613
|
+
});
|
|
614
|
+
} catch {
|
|
615
|
+
if (!ctx.res.headersSent) {
|
|
616
|
+
ctx.res.writeHead(500, { "Content-Type": "application/json" });
|
|
617
|
+
ctx.res.end(JSON.stringify({ error: "Error handling MCP SSE request" }));
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
} else ctx.status = 500;
|
|
621
|
+
}
|
|
622
|
+
async registerRoute(app, route, { prefix: prefixParam } = {}) {
|
|
623
|
+
const prefix = prefixParam ?? this.prefix ?? "";
|
|
624
|
+
const koaPath = `${prefix}${route.path}`;
|
|
625
|
+
this.getRouteDispatcherGroup(app).routes.push({
|
|
626
|
+
route,
|
|
627
|
+
prefix,
|
|
628
|
+
koaPath,
|
|
629
|
+
pathRegex: this.pathToRegex(koaPath),
|
|
630
|
+
paramNames: this.extractParamNames(koaPath)
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Convert Express-style path to regex for matching
|
|
635
|
+
*/
|
|
636
|
+
pathToRegex(path) {
|
|
637
|
+
const PARAM_PLACEHOLDER = "\0PARAM\0";
|
|
638
|
+
const regexPath = path.replace(/:[^/]+/g, PARAM_PLACEHOLDER).replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(new RegExp(PARAM_PLACEHOLDER, "g"), "([^/]+)").replace(/\//g, "\\/");
|
|
639
|
+
return new RegExp(`^${regexPath}$`);
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Extract parameter names from path
|
|
643
|
+
*/
|
|
644
|
+
extractParamNames(path) {
|
|
645
|
+
return (path.match(/:[^/]+/g) || []).map((m) => m.slice(1));
|
|
646
|
+
}
|
|
647
|
+
async registerCustomApiRoutes() {
|
|
648
|
+
if (!await this.buildCustomRouteHandler()) return;
|
|
649
|
+
const server = this;
|
|
650
|
+
this.app.use(async function mastraCustomRouteDispatcher(ctx, next) {
|
|
651
|
+
const path = String(ctx.path || "/");
|
|
652
|
+
const method = String(ctx.method || "GET");
|
|
653
|
+
const matchedRoute = findMatchingCustomRoute(path, method, server.customApiRoutes ?? server.mastra.getServer()?.apiRoutes);
|
|
654
|
+
const shouldRunCustomRouteAuth = isProtectedCustomRoute(path, method, server.customRouteAuthConfig);
|
|
655
|
+
const shouldRunCustomRouteFGA = !!matchedRoute?.route.fga;
|
|
656
|
+
const customRouteAbortController = new AbortController();
|
|
657
|
+
const abortCustomRoute = () => {
|
|
658
|
+
customRouteAbortController.abort();
|
|
659
|
+
};
|
|
660
|
+
const abortCustomRouteIfOpen = () => {
|
|
661
|
+
if (!ctx.res.writableEnded) abortCustomRoute();
|
|
662
|
+
};
|
|
663
|
+
ctx.res.once("close", abortCustomRouteIfOpen);
|
|
664
|
+
ctx.res.once("error", abortCustomRouteIfOpen);
|
|
665
|
+
try {
|
|
666
|
+
if (shouldRunCustomRouteAuth || shouldRunCustomRouteFGA) {
|
|
667
|
+
const serverRoute = {
|
|
668
|
+
method: matchedRoute?.route.method ?? method,
|
|
669
|
+
path: matchedRoute?.route.path ?? path,
|
|
670
|
+
responseType: "json",
|
|
671
|
+
handler: async () => {},
|
|
672
|
+
requiresAuth: matchedRoute?.route.requiresAuth,
|
|
673
|
+
requiresPermission: matchedRoute?.route.requiresPermission,
|
|
674
|
+
fga: matchedRoute?.route.fga
|
|
675
|
+
};
|
|
676
|
+
if (shouldRunCustomRouteAuth) {
|
|
677
|
+
const authError = await server.checkRouteAuth(serverRoute, {
|
|
678
|
+
path,
|
|
679
|
+
method,
|
|
680
|
+
getHeader: (name) => ctx.headers[name.toLowerCase()],
|
|
681
|
+
getQuery: (name) => ctx.query[name],
|
|
682
|
+
requestContext: ctx.state.requestContext,
|
|
683
|
+
request: toWebRequest(ctx),
|
|
684
|
+
buildAuthorizeContext: () => toWebRequest(ctx)
|
|
685
|
+
});
|
|
686
|
+
if (authError) {
|
|
687
|
+
if (authError.headers) for (const [key, value] of Object.entries(authError.headers)) ctx.set(key, value);
|
|
688
|
+
if (authError.error) {
|
|
689
|
+
ctx.status = authError.status;
|
|
690
|
+
ctx.body = { error: authError.error };
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
const requestContext = ctx.state.requestContext;
|
|
696
|
+
if (server.mastra.getStudio?.()?.auth || server.mastra.getServer()?.auth) {
|
|
697
|
+
const hasPermission = await loadHasPermission();
|
|
698
|
+
if (hasPermission) {
|
|
699
|
+
const userPermissions = requestContext.get("mastra__userPermissions");
|
|
700
|
+
const permissionError = server.checkRoutePermission(serverRoute, userPermissions, hasPermission, requestContext);
|
|
701
|
+
if (permissionError) {
|
|
702
|
+
ctx.status = permissionError.status;
|
|
703
|
+
ctx.body = {
|
|
704
|
+
error: permissionError.error,
|
|
705
|
+
message: permissionError.message
|
|
706
|
+
};
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const fgaError = await checkRouteFGA(server.mastra, serverRoute, ctx.state.requestContext, {
|
|
712
|
+
...matchedRoute?.params ?? {},
|
|
713
|
+
...ctx.query,
|
|
714
|
+
...typeof ctx.request.body === "object" && ctx.request.body !== null ? ctx.request.body : {}
|
|
715
|
+
});
|
|
716
|
+
if (fgaError) {
|
|
717
|
+
ctx.status = fgaError.status;
|
|
718
|
+
ctx.body = {
|
|
719
|
+
error: fgaError.error,
|
|
720
|
+
message: fgaError.message
|
|
721
|
+
};
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
const response = await server.handleCustomRouteRequest(`${ctx.protocol}://${ctx.host}${ctx.originalUrl || ctx.url}`, ctx.method, ctx.headers, ctx.request.body, ctx.state.requestContext, customRouteAbortController.signal);
|
|
726
|
+
if (!response) return next();
|
|
727
|
+
ctx.respond = false;
|
|
728
|
+
await server.writeCustomRouteResponse(response, ctx.res, customRouteAbortController.signal);
|
|
729
|
+
} finally {
|
|
730
|
+
ctx.res.off("close", abortCustomRouteIfOpen);
|
|
731
|
+
ctx.res.off("error", abortCustomRouteIfOpen);
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
registerContextMiddleware() {
|
|
736
|
+
this.app.use(this.createContextMiddleware());
|
|
737
|
+
}
|
|
738
|
+
registerAuthMiddleware() {}
|
|
739
|
+
registerHttpLoggingMiddleware() {
|
|
740
|
+
if (!this.httpLoggingConfig?.enabled) return;
|
|
741
|
+
const server = this;
|
|
742
|
+
this.app.use(async function mastraHttpLogger(ctx, next) {
|
|
743
|
+
if (!server.shouldLogRequest(ctx.path)) return next();
|
|
744
|
+
const start = Date.now();
|
|
745
|
+
const method = ctx.method;
|
|
746
|
+
const path = ctx.path;
|
|
747
|
+
await next();
|
|
748
|
+
const duration = Date.now() - start;
|
|
749
|
+
const status = ctx.status;
|
|
750
|
+
const level = server.httpLoggingConfig?.level || "info";
|
|
751
|
+
const logData = {
|
|
752
|
+
method,
|
|
753
|
+
path,
|
|
754
|
+
status,
|
|
755
|
+
duration: `${duration}ms`
|
|
756
|
+
};
|
|
757
|
+
if (server.httpLoggingConfig?.includeQueryParams) logData.query = ctx.query;
|
|
758
|
+
if (server.httpLoggingConfig?.includeHeaders) {
|
|
759
|
+
const headers = { ...ctx.headers };
|
|
760
|
+
(server.httpLoggingConfig.redactHeaders || []).forEach((h) => {
|
|
761
|
+
const key = h.toLowerCase();
|
|
762
|
+
if (headers[key] !== void 0) headers[key] = "[REDACTED]";
|
|
763
|
+
});
|
|
764
|
+
logData.headers = headers;
|
|
765
|
+
}
|
|
766
|
+
server.logger[level](`${method} ${path} ${status} ${duration}ms`, logData);
|
|
767
|
+
});
|
|
768
|
+
}
|
|
870
769
|
};
|
|
871
|
-
|
|
770
|
+
//#endregion
|
|
872
771
|
export { MastraServer, createAuthMiddleware };
|
|
873
|
-
|
|
772
|
+
|
|
874
773
|
//# sourceMappingURL=index.js.map
|