@mastra/elysia 0.0.0-knowledge-org-scope-20260902233451

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/dist/index.cjs ADDED
@@ -0,0 +1,822 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _mastra_server_auth = require("@mastra/server/auth");
3
+ let _mastra_server_server_adapter = require("@mastra/server/server-adapter");
4
+ let fetch_to_node = require("fetch-to-node");
5
+ let _mastra_core_request_context = require("@mastra/core/request-context");
6
+ //#region src/auth-middleware.ts
7
+ function createAuthMiddleware({ mastra, requiresAuth = true }) {
8
+ return async (ctx) => {
9
+ if (!requiresAuth) return;
10
+ const authConfig = mastra.getServer()?.auth;
11
+ if (!authConfig) return;
12
+ ctx.requestContext ??= new _mastra_core_request_context.RequestContext();
13
+ ctx.mastra ??= mastra;
14
+ const path = new URL(ctx.request.url).pathname;
15
+ const method = ctx.request.method;
16
+ const customRouteAuthConfig = new Map(ctx.customRouteAuthConfig ?? []);
17
+ customRouteAuthConfig.set(`${method}:${path}`, true);
18
+ const authHeader = ctx.request.headers.get("authorization");
19
+ let token = authHeader ? authHeader.replace("Bearer ", "") : null;
20
+ if (!token && ctx.query?.apiKey) token = ctx.query.apiKey;
21
+ const result = await (0, _mastra_server_auth.coreAuthMiddleware)({
22
+ path,
23
+ method,
24
+ getHeader: (name) => ctx.request.headers.get(name) || void 0,
25
+ mastra,
26
+ authConfig,
27
+ customRouteAuthConfig,
28
+ requestContext: ctx.requestContext,
29
+ rawRequest: ctx.request,
30
+ token,
31
+ buildAuthorizeContext: () => ctx
32
+ });
33
+ if (result.action === "error") return new Response(JSON.stringify(result.body), {
34
+ status: result.status,
35
+ headers: { "Content-Type": "application/json" }
36
+ });
37
+ };
38
+ }
39
+ //#endregion
40
+ //#region src/helper.ts
41
+ /**
42
+ * OpenAPI documentation cache to avoid regenerating on each call.
43
+ * Uses WeakMap to prevent cache collisions between server instances.
44
+ */
45
+ const openapiDocCache = /* @__PURE__ */ new WeakMap();
46
+ /**
47
+ * Get OpenAPI documentation from a MastraServer instance, formatted for Elysia's openapi plugin.
48
+ *
49
+ * This function:
50
+ * - Extracts all Mastra routes with their Zod schemas
51
+ * - Converts them to OpenAPI 3.1.0 format
52
+ * - Merges custom routes if registered
53
+ * - Applies the configured route prefix to all paths
54
+ * - Caches the result for performance
55
+ *
56
+ * @param server - The MastraServer instance (must be initialized with init())
57
+ * @param options - Optional configuration for OpenAPI metadata
58
+ * @returns OpenAPI documentation with paths, components, and info
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * import { getMastraOpenAPIDoc } from '@mastra/elysia';
63
+ *
64
+ * const srv = new MastraServer({ mastra, app, prefix: '/api' });
65
+ * await srv.init();
66
+ *
67
+ * const openAPIDoc = getMastraOpenAPIDoc(srv, {
68
+ * title: 'My Mastra API',
69
+ * version: '1.0.0',
70
+ * description: 'API with weather agent'
71
+ * });
72
+ *
73
+ * app.use(openapi({
74
+ * documentation: {
75
+ * info: openAPIDoc.info,
76
+ * paths: openAPIDoc.paths,
77
+ * components: openAPIDoc.components
78
+ * }
79
+ * }));
80
+ * ```
81
+ */
82
+ function getMastraOpenAPIDoc(server, options) {
83
+ if (options?.clearCache) openapiDocCache.delete(server);
84
+ const cached = openapiDocCache.get(server);
85
+ if (cached) {
86
+ if (options) return {
87
+ ...cached,
88
+ info: {
89
+ ...cached.info,
90
+ ...options.title && { title: options.title },
91
+ ...options.version && { version: options.version },
92
+ ...options.description && { description: options.description }
93
+ }
94
+ };
95
+ return cached;
96
+ }
97
+ const openApiSpec = (0, _mastra_server_server_adapter.generateOpenAPIDocument)(_mastra_server_server_adapter.SERVER_ROUTES, {
98
+ title: options?.title || "Mastra API",
99
+ version: options?.version || "1.0.0",
100
+ description: options?.description || "Mastra Server API"
101
+ });
102
+ const customApiRoutes = server.customApiRoutes;
103
+ if (customApiRoutes && customApiRoutes.length > 0) {
104
+ const customPaths = (0, _mastra_server_server_adapter.convertCustomRoutesToOpenAPIPaths)(customApiRoutes);
105
+ openApiSpec.paths = {
106
+ ...openApiSpec.paths,
107
+ ...customPaths
108
+ };
109
+ }
110
+ let paths = openApiSpec.paths || {};
111
+ const prefix = server.prefix;
112
+ if (prefix && prefix !== "/") {
113
+ const prefixedPaths = {};
114
+ for (const [path, pathItem] of Object.entries(paths)) {
115
+ const prefixedPath = `${prefix}${path}`.replace(/\/+/g, "/");
116
+ prefixedPaths[prefixedPath] = pathItem;
117
+ }
118
+ paths = prefixedPaths;
119
+ }
120
+ const result = {
121
+ paths,
122
+ components: openApiSpec.components,
123
+ info: openApiSpec.info
124
+ };
125
+ openapiDocCache.set(server, result);
126
+ return result;
127
+ }
128
+ /**
129
+ * Clear the OpenAPI documentation cache.
130
+ * Call this if routes are added dynamically after initialization.
131
+ *
132
+ * `@param` server - The MastraServer instance to clear cache for.
133
+ * Note: WeakMap does not support clearing all entries. Pass the specific server instance whose cache should be invalidated.
134
+ */
135
+ function clearMastraOpenAPICache(server) {
136
+ if (server) openapiDocCache.delete(server);
137
+ }
138
+ //#endregion
139
+ //#region src/index.ts
140
+ /**
141
+ * Normalizes route path parameters to position-based names (:p0, :p1, ...)
142
+ * to avoid Elysia router conflicts when different routes use different
143
+ * parameter names at the same path segment position (e.g. :agentId vs
144
+ * :storedAgentId under /stored/agents/...).
145
+ * Returns the normalized path and the original parameter names in order.
146
+ */
147
+ function normalizeRouteParams(path) {
148
+ const paramNames = [];
149
+ return {
150
+ normalizedPath: path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, paramName) => {
151
+ paramNames.push(paramName);
152
+ return `:p${paramNames.length - 1}`;
153
+ }),
154
+ paramNames
155
+ };
156
+ }
157
+ /**
158
+ * Remaps Elysia's parsed params from position-based names (p0, p1, ...)
159
+ * back to the original parameter names expected by the route handler.
160
+ */
161
+ function remapParams(rawParams, paramNames) {
162
+ if (paramNames.length === 0) return rawParams;
163
+ const result = {};
164
+ paramNames.forEach((paramName, i) => {
165
+ const key = `p${i}`;
166
+ if (key in rawParams) result[paramName] = rawParams[key];
167
+ });
168
+ return result;
169
+ }
170
+ async function createForwardRequest(ctx) {
171
+ const request = ctx.request;
172
+ const method = request.method;
173
+ if (method === "GET" || method === "HEAD") return request;
174
+ const headers = new Headers(request.headers);
175
+ let body;
176
+ if (ctx.body !== void 0) if (typeof ctx.body === "string" || ctx.body instanceof FormData || ctx.body instanceof Blob) body = ctx.body;
177
+ else {
178
+ body = JSON.stringify(ctx.body);
179
+ if (!headers.has("content-type")) headers.set("content-type", "application/json");
180
+ }
181
+ else if (!request.bodyUsed) {
182
+ const bodyBuffer = await request.clone().arrayBuffer();
183
+ if (bodyBuffer.byteLength > 0) body = bodyBuffer;
184
+ }
185
+ headers.delete("content-length");
186
+ return new Request(request.url, {
187
+ method,
188
+ headers,
189
+ body,
190
+ signal: request.signal,
191
+ ...body ? { duplex: "half" } : {}
192
+ });
193
+ }
194
+ function createSafeReadableStream(body) {
195
+ if (!body) return null;
196
+ return new ReadableStream({ async start(controller) {
197
+ const reader = body.getReader();
198
+ try {
199
+ while (true) {
200
+ const { done, value } = await reader.read();
201
+ if (done) break;
202
+ controller.enqueue(value);
203
+ }
204
+ } catch {} finally {
205
+ controller.close();
206
+ reader.releaseLock();
207
+ }
208
+ } });
209
+ }
210
+ let _hasPermissionPromise;
211
+ function loadHasPermission() {
212
+ if (!_hasPermissionPromise) _hasPermissionPromise = import("@mastra/core/auth/ee").then((m) => m.hasPermission).catch(() => {
213
+ console.error("[@mastra/elysia] Auth features require @mastra/core >= 1.6.0. Please upgrade: npm install @mastra/core@latest");
214
+ });
215
+ return _hasPermissionPromise;
216
+ }
217
+ var MastraServer = class extends _mastra_server_server_adapter.MastraServer {
218
+ createContextMiddleware() {
219
+ return async (ctx) => {
220
+ let bodyRequestContext;
221
+ let paramsRequestContext;
222
+ if (ctx.request.method === "POST" || ctx.request.method === "PUT" || ctx.request.method === "PATCH") {
223
+ if (ctx.request.headers.get("content-type")?.includes("application/json")) try {
224
+ const body = ctx.body ?? await ctx.request.clone().json();
225
+ if (body.requestContext) bodyRequestContext = body.requestContext;
226
+ } catch {}
227
+ }
228
+ if (ctx.request.method === "GET") try {
229
+ const encodedRequestContext = ctx.query?.requestContext;
230
+ if (encodedRequestContext) try {
231
+ paramsRequestContext = JSON.parse(encodedRequestContext);
232
+ } catch {
233
+ try {
234
+ const json = Buffer.from(encodedRequestContext, "base64").toString("utf-8");
235
+ paramsRequestContext = JSON.parse(json);
236
+ } catch {}
237
+ }
238
+ } catch {}
239
+ const requestContext = this.mergeRequestContext({
240
+ paramsRequestContext,
241
+ bodyRequestContext
242
+ });
243
+ this.applyRequestMetadataToContext({
244
+ requestContext,
245
+ getHeader: (name) => ctx.request.headers.get(name) || void 0
246
+ });
247
+ return {
248
+ requestContext,
249
+ mastra: this.mastra,
250
+ registeredTools: this.tools || {},
251
+ taskStore: this.taskStore,
252
+ abortSignal: ctx.request.signal,
253
+ customRouteAuthConfig: this.customRouteAuthConfig
254
+ };
255
+ };
256
+ }
257
+ async stream(route, _res, result) {
258
+ const streamFormat = route.streamFormat || "stream";
259
+ const encoder = new TextEncoder();
260
+ const headers = streamFormat === "sse" ? {
261
+ "Content-Type": "text/event-stream",
262
+ "Cache-Control": "no-cache",
263
+ Connection: "keep-alive",
264
+ "X-Accel-Buffering": "no"
265
+ } : { "Content-Type": "text/plain" };
266
+ const stream = new ReadableStream({ start: async (controller) => {
267
+ const reader = (result instanceof ReadableStream ? result : result.fullStream).getReader();
268
+ try {
269
+ if (streamFormat === "sse" && route.sseFlushOnConnect) controller.enqueue(encoder.encode(": connected\n\n"));
270
+ while (true) {
271
+ const { done, value } = await reader.read();
272
+ if (done) break;
273
+ if (value) {
274
+ if (streamFormat === "sse" && typeof value === "string" && value.startsWith(":")) {
275
+ controller.enqueue(encoder.encode(value));
276
+ continue;
277
+ }
278
+ const outputValue = this.streamOptions?.redact ?? true ? (0, _mastra_server_server_adapter.redactStreamChunk)(value) : value;
279
+ const serialized = (0, _mastra_server_server_adapter.serializeStreamChunk)(outputValue);
280
+ if (!serialized.ok) {
281
+ this.mastra.getLogger()?.error("Failed to serialize stream chunk, skipping", {
282
+ path: route.path,
283
+ chunkType: outputValue?.type,
284
+ error: serialized.error.message
285
+ });
286
+ continue;
287
+ }
288
+ if (streamFormat === "sse") controller.enqueue(encoder.encode(`data: ${serialized.json}\n\n`));
289
+ else controller.enqueue(encoder.encode(serialized.json + ""));
290
+ }
291
+ }
292
+ if (streamFormat === "sse") controller.enqueue(encoder.encode("data: [DONE]\n\n"));
293
+ controller.close();
294
+ } catch (error) {
295
+ this.mastra.getLogger()?.error("Error in stream processing", { error: error instanceof Error ? {
296
+ message: error.message,
297
+ stack: error.stack
298
+ } : error });
299
+ controller.error(error);
300
+ } finally {
301
+ await reader.cancel();
302
+ }
303
+ } });
304
+ return new Response(stream, { headers });
305
+ }
306
+ async getParams(route, request) {
307
+ const url = new URL(request.url);
308
+ const { paramNames } = normalizeRouteParams(route.path);
309
+ const rawUrlParams = request.params || request.ctx?.params || {};
310
+ const urlParams = paramNames.length > 0 ? remapParams(rawUrlParams, paramNames) : rawUrlParams;
311
+ const queryParams = (0, _mastra_server_server_adapter.normalizeQueryParams)(Object.fromEntries(url.searchParams));
312
+ let body;
313
+ let bodyParseError;
314
+ if (route.method === "POST" || route.method === "PUT" || route.method === "PATCH" || route.method === "DELETE") {
315
+ const contentType = request.headers.get("content-type") || "";
316
+ if (contentType.includes("multipart/form-data")) try {
317
+ const formData = await request.formData();
318
+ body = await this.parseFormData(formData);
319
+ } catch (error) {
320
+ this.mastra.getLogger()?.error("Failed to parse multipart form data", { error: error instanceof Error ? {
321
+ message: error.message,
322
+ stack: error.stack
323
+ } : error });
324
+ if (error instanceof Error && error.message.toLowerCase().includes("size")) throw error;
325
+ bodyParseError = { message: error instanceof Error ? error.message : "Failed to parse multipart form data" };
326
+ }
327
+ else if (contentType.includes("application/json")) {
328
+ const bodyText = await request.clone().text();
329
+ if (bodyText && bodyText.trim().length > 0) try {
330
+ body = JSON.parse(bodyText);
331
+ } catch (error) {
332
+ this.mastra.getLogger()?.error("Failed to parse JSON body", { error: error instanceof Error ? {
333
+ message: error.message,
334
+ stack: error.stack
335
+ } : error });
336
+ bodyParseError = { message: error instanceof Error ? error.message : "Invalid JSON in request body" };
337
+ }
338
+ }
339
+ }
340
+ return {
341
+ urlParams,
342
+ queryParams,
343
+ body,
344
+ bodyParseError
345
+ };
346
+ }
347
+ /**
348
+ * Parse FormData into a plain object, converting File objects to Buffers.
349
+ * Handles both native FormData and Elysia's pre-parsed object format.
350
+ */
351
+ async parseFormData(data) {
352
+ const result = {};
353
+ if (data instanceof FormData) {
354
+ for (const [key, value] of data.entries()) if (value instanceof File) {
355
+ const arrayBuffer = await value.arrayBuffer();
356
+ result[key] = Buffer.from(arrayBuffer);
357
+ } else if (typeof value === "string") try {
358
+ result[key] = JSON.parse(value);
359
+ } catch {
360
+ result[key] = value;
361
+ }
362
+ else result[key] = value;
363
+ return result;
364
+ }
365
+ for (const [key, value] of Object.entries(data)) if (value && typeof value === "object" && "arrayBuffer" in value && typeof value.arrayBuffer === "function") {
366
+ const arrayBuffer = await value.arrayBuffer();
367
+ result[key] = Buffer.from(arrayBuffer);
368
+ } else if (typeof value === "string") try {
369
+ result[key] = JSON.parse(value);
370
+ } catch {
371
+ result[key] = value;
372
+ }
373
+ else result[key] = value;
374
+ return result;
375
+ }
376
+ async sendResponse(route, response, result, prefix) {
377
+ const resolvedPrefix = prefix ?? this.prefix ?? "";
378
+ const refreshHeaders = {};
379
+ if (result && typeof result === "object" && "__refreshHeaders" in result) {
380
+ const headers = result.__refreshHeaders;
381
+ for (const [key, value] of Object.entries(headers)) refreshHeaders[key] = value;
382
+ delete result.__refreshHeaders;
383
+ }
384
+ if (route.responseType === "json") return new Response(JSON.stringify(result), {
385
+ status: 200,
386
+ headers: {
387
+ "Content-Type": "application/json",
388
+ ...refreshHeaders
389
+ }
390
+ });
391
+ else if (route.responseType === "stream") return this.stream(route, response, result);
392
+ else if (route.responseType === "datastream-response") {
393
+ const fetchResponse = result;
394
+ const headers = new Headers(fetchResponse.headers);
395
+ headers.delete("Transfer-Encoding");
396
+ return new Response(createSafeReadableStream(fetchResponse.body), {
397
+ status: fetchResponse.status,
398
+ statusText: fetchResponse.statusText,
399
+ headers
400
+ });
401
+ } else if (route.responseType === "mcp-http") {
402
+ const { server, httpPath, mcpOptions: routeMcpOptions } = result;
403
+ const forwardRequest = await createForwardRequest(response);
404
+ const { req, res } = (0, fetch_to_node.toReqRes)(forwardRequest);
405
+ const options = {
406
+ ...this.mcpOptions,
407
+ ...routeMcpOptions
408
+ };
409
+ server.startHTTP({
410
+ url: new URL(forwardRequest.url),
411
+ httpPath: `${resolvedPrefix}${httpPath}`,
412
+ req,
413
+ res,
414
+ options: Object.keys(options).length > 0 ? options : void 0
415
+ }).catch((e) => {
416
+ this.mastra.getLogger()?.error("[MCP HTTP] Error in background startHTTP:", { error: e instanceof Error ? {
417
+ message: e.message,
418
+ stack: e.stack
419
+ } : e });
420
+ });
421
+ const mcpResponse = await (0, fetch_to_node.toFetchResponse)(res);
422
+ const headers = new Headers(mcpResponse.headers);
423
+ headers.delete("Transfer-Encoding");
424
+ return new Response(createSafeReadableStream(mcpResponse.body), {
425
+ status: mcpResponse.status,
426
+ statusText: mcpResponse.statusText,
427
+ headers
428
+ });
429
+ } else if (route.responseType === "mcp-sse") {
430
+ const { server, ssePath, messagePath } = result;
431
+ const forwardRequest = await createForwardRequest(response);
432
+ const { req, res } = (0, fetch_to_node.toReqRes)(forwardRequest);
433
+ server.startSSE({
434
+ url: new URL(forwardRequest.url),
435
+ ssePath: `${resolvedPrefix}${ssePath}`,
436
+ messagePath: `${resolvedPrefix}${messagePath}`,
437
+ req,
438
+ res
439
+ }).catch((e) => {
440
+ this.mastra.getLogger()?.error("[MCP SSE] Error in background startSSE:", { error: e instanceof Error ? {
441
+ message: e.message,
442
+ stack: e.stack
443
+ } : e });
444
+ });
445
+ const sseResponse = await (0, fetch_to_node.toFetchResponse)(res);
446
+ const headers = new Headers(sseResponse.headers);
447
+ headers.delete("Transfer-Encoding");
448
+ return new Response(createSafeReadableStream(sseResponse.body), {
449
+ status: sseResponse.status,
450
+ statusText: sseResponse.statusText,
451
+ headers
452
+ });
453
+ } else return new Response(null, { status: 500 });
454
+ }
455
+ async registerRoute(app, route, { prefix: prefixParam } = {}) {
456
+ const prefix = prefixParam ?? this.prefix ?? "";
457
+ const { normalizedPath, paramNames } = normalizeRouteParams(`${prefix}${route.path}`);
458
+ const handler = async (ctx) => {
459
+ const authError = await this.checkRouteAuth(route, {
460
+ path: ctx.request.url ? new URL(ctx.request.url).pathname : ctx.path,
461
+ method: ctx.request.method,
462
+ getHeader: (name) => ctx.request.headers.get(name) || void 0,
463
+ getQuery: (name) => ctx.query?.[name],
464
+ requestContext: ctx.requestContext,
465
+ request: ctx.request,
466
+ buildAuthorizeContext: () => ctx
467
+ });
468
+ if (authError) return new Response(JSON.stringify({ error: authError.error }), {
469
+ status: authError.status,
470
+ headers: {
471
+ "Content-Type": "application/json",
472
+ ...authError.headers
473
+ }
474
+ });
475
+ if (this.mastra.getServer()?.auth) {
476
+ const hasPermission = await loadHasPermission();
477
+ if (hasPermission) {
478
+ const userPermissions = ctx.requestContext.get("userPermissions");
479
+ const permissionError = this.checkRoutePermission(route, userPermissions, hasPermission);
480
+ if (permissionError) return new Response(JSON.stringify({
481
+ error: permissionError.error,
482
+ message: permissionError.message
483
+ }), {
484
+ status: permissionError.status,
485
+ headers: { "Content-Type": "application/json" }
486
+ });
487
+ }
488
+ }
489
+ const rawParams = ctx.params || {};
490
+ const urlParams = paramNames.length > 0 ? remapParams(rawParams, paramNames) : rawParams;
491
+ const queryParams = (0, _mastra_server_server_adapter.normalizeQueryParams)(ctx.query || {});
492
+ let body;
493
+ let bodyParseError;
494
+ if (route.method === "POST" || route.method === "PUT" || route.method === "PATCH" || route.method === "DELETE") {
495
+ const maxSize = route.maxBodySize ?? this.bodyLimitOptions?.maxSize;
496
+ const contentLength = ctx.request.headers.get("content-length");
497
+ if (this.bodyLimitOptions && maxSize && contentLength && parseInt(contentLength, 10) > maxSize) {
498
+ let errorResponse = { error: "Request body too large" };
499
+ try {
500
+ errorResponse = this.bodyLimitOptions.onError({ error: "Request body too large" });
501
+ } catch {}
502
+ return new Response(JSON.stringify(errorResponse), {
503
+ status: 413,
504
+ headers: { "Content-Type": "application/json" }
505
+ });
506
+ }
507
+ const contentType = ctx.request.headers.get("content-type") || "";
508
+ if (contentType.includes("multipart/form-data")) try {
509
+ body = ctx.body ? await this.parseFormData(ctx.body) : void 0;
510
+ } catch (error) {
511
+ this.mastra.getLogger()?.error("Failed to parse multipart form data", { error: error instanceof Error ? {
512
+ message: error.message,
513
+ stack: error.stack
514
+ } : error });
515
+ if (error instanceof Error && error.message.toLowerCase().includes("size")) throw error;
516
+ bodyParseError = { message: error instanceof Error ? error.message : "Failed to parse multipart form data" };
517
+ }
518
+ else if (contentType.includes("application/json")) {
519
+ body = ctx.body;
520
+ if (body === void 0 && contentType.includes("application/json")) {
521
+ const bodyText = await ctx.request.clone().text();
522
+ if (bodyText && bodyText.trim().length > 0) try {
523
+ body = JSON.parse(bodyText);
524
+ } catch (error) {
525
+ this.mastra.getLogger()?.error("Failed to parse JSON body", { error: error instanceof Error ? {
526
+ message: error.message,
527
+ stack: error.stack
528
+ } : error });
529
+ bodyParseError = { message: error instanceof Error ? error.message : "Invalid JSON in request body" };
530
+ }
531
+ }
532
+ }
533
+ }
534
+ const params = {
535
+ urlParams,
536
+ queryParams,
537
+ body,
538
+ bodyParseError
539
+ };
540
+ if (params.bodyParseError) return new Response(JSON.stringify({
541
+ error: "Invalid request body",
542
+ issues: [{
543
+ field: "body",
544
+ message: params.bodyParseError.message
545
+ }]
546
+ }), {
547
+ status: 400,
548
+ headers: { "Content-Type": "application/json" }
549
+ });
550
+ if (params.queryParams) try {
551
+ params.queryParams = await this.parseQueryParams(route, params.queryParams);
552
+ } catch (error) {
553
+ this.mastra.getLogger()?.error("Error parsing query params", { error: error instanceof Error ? {
554
+ message: error.message,
555
+ stack: error.stack
556
+ } : error });
557
+ if ((0, _mastra_server_server_adapter.isZodError)(error)) {
558
+ const { status, body } = this.resolveValidationError(route, error, "query");
559
+ return new Response(JSON.stringify(body), {
560
+ status,
561
+ headers: { "Content-Type": "application/json" }
562
+ });
563
+ }
564
+ return new Response(JSON.stringify({
565
+ error: "Invalid query parameters",
566
+ issues: [{
567
+ field: "unknown",
568
+ message: error instanceof Error ? error.message : "Unknown error"
569
+ }]
570
+ }), {
571
+ status: 400,
572
+ headers: { "Content-Type": "application/json" }
573
+ });
574
+ }
575
+ if (params.body) try {
576
+ params.body = await this.parseBody(route, params.body);
577
+ } catch (error) {
578
+ this.mastra.getLogger()?.error("Error parsing body", { error: error instanceof Error ? {
579
+ message: error.message,
580
+ stack: error.stack
581
+ } : error });
582
+ if ((0, _mastra_server_server_adapter.isZodError)(error)) {
583
+ const { status, body } = this.resolveValidationError(route, error, "body");
584
+ return new Response(JSON.stringify(body), {
585
+ status,
586
+ headers: { "Content-Type": "application/json" }
587
+ });
588
+ }
589
+ return new Response(JSON.stringify({
590
+ error: "Invalid request body",
591
+ issues: [{
592
+ field: "unknown",
593
+ message: error instanceof Error ? error.message : "Unknown error"
594
+ }]
595
+ }), {
596
+ status: 400,
597
+ headers: { "Content-Type": "application/json" }
598
+ });
599
+ }
600
+ if (params.urlParams) try {
601
+ params.urlParams = await this.parsePathParams(route, params.urlParams);
602
+ } catch (error) {
603
+ this.mastra.getLogger()?.error("Error parsing path params", { error: error instanceof Error ? {
604
+ message: error.message,
605
+ stack: error.stack
606
+ } : error });
607
+ if ((0, _mastra_server_server_adapter.isZodError)(error)) {
608
+ const { status, body } = this.resolveValidationError(route, error, "path");
609
+ return new Response(JSON.stringify(body), {
610
+ status,
611
+ headers: { "Content-Type": "application/json" }
612
+ });
613
+ }
614
+ return new Response(JSON.stringify({
615
+ error: "Invalid path parameters",
616
+ issues: [{
617
+ field: "unknown",
618
+ message: error instanceof Error ? error.message : "Unknown error"
619
+ }]
620
+ }), {
621
+ status: 400,
622
+ headers: { "Content-Type": "application/json" }
623
+ });
624
+ }
625
+ const fgaError = await (0, _mastra_server_server_adapter.checkRouteFGA)(this.mastra, route, ctx.requestContext, {
626
+ ...params.urlParams,
627
+ ...params.queryParams,
628
+ ...typeof params.body === "object" ? params.body : {}
629
+ });
630
+ if (fgaError) return new Response(JSON.stringify({
631
+ error: fgaError.error,
632
+ message: fgaError.message
633
+ }), {
634
+ status: fgaError.status,
635
+ headers: { "Content-Type": "application/json" }
636
+ });
637
+ const handlerParams = {
638
+ ...params.urlParams,
639
+ ...params.queryParams,
640
+ ...typeof params.body === "object" ? params.body : {},
641
+ requestContext: ctx.requestContext,
642
+ mastra: ctx.mastra,
643
+ registeredTools: ctx.registeredTools,
644
+ taskStore: ctx.taskStore,
645
+ abortSignal: ctx.abortSignal,
646
+ routePrefix: prefix,
647
+ request: ctx.request
648
+ };
649
+ try {
650
+ const result = await route.handler(handlerParams);
651
+ return this.sendResponse(route, ctx, result, prefix);
652
+ } catch (error) {
653
+ this.mastra.getLogger()?.error("Error calling handler", {
654
+ error: error instanceof Error ? {
655
+ message: error.message,
656
+ stack: error.stack
657
+ } : error,
658
+ path: route.path,
659
+ method: route.method
660
+ });
661
+ if (error && typeof error === "object") {
662
+ if ("status" in error) {
663
+ const status = error.status;
664
+ return new Response(JSON.stringify({ error: error instanceof Error ? error.message : "Unknown error" }), {
665
+ status,
666
+ headers: { "Content-Type": "application/json" }
667
+ });
668
+ }
669
+ if ("details" in error && error.details && typeof error.details === "object" && "status" in error.details) {
670
+ const status = error.details.status;
671
+ return new Response(JSON.stringify({ error: error instanceof Error ? error.message : "Unknown error" }), {
672
+ status,
673
+ headers: { "Content-Type": "application/json" }
674
+ });
675
+ }
676
+ }
677
+ return new Response(JSON.stringify({ error: error instanceof Error ? error.message : "Unknown error" }), {
678
+ status: 500,
679
+ headers: { "Content-Type": "application/json" }
680
+ });
681
+ }
682
+ };
683
+ app[route.method.toLowerCase()](normalizedPath, handler);
684
+ }
685
+ async registerCustomApiRoutes() {
686
+ const routes = await this.registerSchemaApiRoutes();
687
+ if (!await this.buildCustomRouteHandler(routes)) return;
688
+ for (const route of routes) {
689
+ const method = route.method.toLowerCase();
690
+ const { normalizedPath: customNormalizedPath } = normalizeRouteParams(route.path);
691
+ this.app[method](customNormalizedPath, async (ctx) => {
692
+ const path = new URL(ctx.request.url).pathname;
693
+ const requestMethod = ctx.request.method;
694
+ const matchedRoute = (0, _mastra_server_auth.findMatchingCustomRoute)(path, requestMethod, this.customApiRoutes ?? this.mastra.getServer()?.apiRoutes);
695
+ const shouldRunCustomRouteAuth = (0, _mastra_server_auth.isProtectedCustomRoute)(path, requestMethod, this.customRouteAuthConfig);
696
+ const shouldRunCustomRouteFGA = !!matchedRoute?.route.fga;
697
+ if (shouldRunCustomRouteAuth || shouldRunCustomRouteFGA) {
698
+ const serverRoute = {
699
+ method: matchedRoute?.route.method ?? requestMethod,
700
+ path: matchedRoute?.route.path ?? path,
701
+ responseType: "json",
702
+ handler: async () => {},
703
+ requiresAuth: matchedRoute?.route.requiresAuth,
704
+ requiresPermission: matchedRoute?.route.requiresPermission,
705
+ fga: matchedRoute?.route.fga
706
+ };
707
+ if (shouldRunCustomRouteAuth) {
708
+ const authError = await this.checkRouteAuth(serverRoute, {
709
+ path,
710
+ method: requestMethod,
711
+ getHeader: (name) => ctx.request.headers.get(name) || void 0,
712
+ getQuery: (name) => ctx.query?.[name],
713
+ requestContext: ctx.requestContext,
714
+ request: ctx.request,
715
+ buildAuthorizeContext: () => ctx
716
+ });
717
+ if (authError) return new Response(JSON.stringify({ error: authError.error }), {
718
+ status: authError.status,
719
+ headers: {
720
+ "Content-Type": "application/json",
721
+ ...authError.headers
722
+ }
723
+ });
724
+ if (this.mastra.getServer()?.auth) {
725
+ const hasPermission = await loadHasPermission();
726
+ if (hasPermission) {
727
+ const userPermissions = ctx.requestContext.get("userPermissions");
728
+ const permissionError = this.checkRoutePermission(serverRoute, userPermissions, hasPermission);
729
+ if (permissionError) return new Response(JSON.stringify({
730
+ error: permissionError.error,
731
+ message: permissionError.message
732
+ }), {
733
+ status: permissionError.status,
734
+ headers: { "Content-Type": "application/json" }
735
+ });
736
+ }
737
+ }
738
+ }
739
+ const fgaError = await (0, _mastra_server_server_adapter.checkRouteFGA)(this.mastra, serverRoute, ctx.requestContext, {
740
+ ...matchedRoute?.params ?? {},
741
+ ...ctx.query,
742
+ ...typeof ctx.body === "object" && ctx.body !== null ? ctx.body : {}
743
+ });
744
+ if (fgaError) return new Response(JSON.stringify({
745
+ error: fgaError.error,
746
+ message: fgaError.message
747
+ }), {
748
+ status: fgaError.status,
749
+ headers: { "Content-Type": "application/json" }
750
+ });
751
+ }
752
+ const headers = {};
753
+ ctx.request.headers.forEach((value, key) => {
754
+ headers[key] = value;
755
+ });
756
+ const response = await this.handleCustomRouteRequest(ctx.request.url, ctx.request.method, headers, ctx.body, ctx.requestContext, ctx.request.signal);
757
+ if (!response) return new Response(JSON.stringify({ error: "Not Found" }), {
758
+ status: 404,
759
+ headers: { "Content-Type": "application/json" }
760
+ });
761
+ return response;
762
+ });
763
+ }
764
+ }
765
+ registerContextMiddleware() {
766
+ this.app.derive(this.createContextMiddleware());
767
+ }
768
+ registerAuthMiddleware() {
769
+ this.app.onError((ctx) => {
770
+ const error = ctx.error;
771
+ const status = error?.status ?? error?.code ?? 500;
772
+ const message = error?.message ?? "Internal server error";
773
+ const httpStatus = typeof status === "number" && status >= 400 && status < 500 ? status : 500;
774
+ return new Response(JSON.stringify({
775
+ error: httpStatus < 500 ? message : "Internal server error",
776
+ ...httpStatus < 500 ? { issues: [] } : {}
777
+ }), {
778
+ status: httpStatus,
779
+ headers: { "Content-Type": "application/json" }
780
+ });
781
+ });
782
+ }
783
+ registerHttpLoggingMiddleware() {
784
+ if (!this.httpLoggingConfig?.enabled) return;
785
+ this.app.derive(async (ctx) => {
786
+ const path = new URL(ctx.request.url).pathname;
787
+ if (!this.shouldLogRequest(path)) return {};
788
+ return { httpLogStart: Date.now() };
789
+ });
790
+ this.app.onAfterHandle((ctx) => {
791
+ const path = new URL(ctx.request.url).pathname;
792
+ if (!this.shouldLogRequest(path)) return;
793
+ const duration = Date.now() - (ctx.httpLogStart ?? Date.now());
794
+ const method = ctx.request.method;
795
+ const status = ctx.response?.status ?? ctx.set?.status ?? 200;
796
+ const level = this.httpLoggingConfig?.level || "info";
797
+ const logData = {
798
+ method,
799
+ path,
800
+ status,
801
+ duration: `${duration}ms`
802
+ };
803
+ if (this.httpLoggingConfig?.includeQueryParams) logData.query = ctx.query || {};
804
+ if (this.httpLoggingConfig?.includeHeaders) {
805
+ const headers = Object.fromEntries(ctx.request.headers.entries());
806
+ (this.httpLoggingConfig.redactHeaders || []).forEach((h) => {
807
+ const key = h.toLowerCase();
808
+ if (headers[key] !== void 0) headers[key] = "[REDACTED]";
809
+ });
810
+ logData.headers = headers;
811
+ }
812
+ this.logger[level](`${method} ${path} ${status} ${duration}ms`, logData);
813
+ });
814
+ }
815
+ };
816
+ //#endregion
817
+ exports.MastraServer = MastraServer;
818
+ exports.clearMastraOpenAPICache = clearMastraOpenAPICache;
819
+ exports.createAuthMiddleware = createAuthMiddleware;
820
+ exports.getMastraOpenAPIDoc = getMastraOpenAPIDoc;
821
+
822
+ //# sourceMappingURL=index.cjs.map