@mastra/nestjs 0.2.26 → 0.2.27-alpha.10

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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "module";
2
2
  import { All, BadRequestException, Catch, Controller, ForbiddenException, Get, HttpException, HttpStatus, Inject, Injectable, Logger, Module, NotFoundException, PayloadTooLargeException, Req, Res, Scope, ServiceUnavailableException, SetMetadata, UnauthorizedException, UseFilters, UseGuards, UseInterceptors } from "@nestjs/common";
3
- import { MASTRA_CLIENT_TYPE_HEADER, MASTRA_IS_STUDIO_KEY, SERVER_ROUTES, isReservedRequestContextKey, isStudioClientTypeHeader, isZodError, normalizeQueryParams, redactStreamChunk, serializeStreamChunk } from "@mastra/server/server-adapter";
3
+ import { HTTPException, MASTRA_CLIENT_TYPE_HEADER, MASTRA_IS_STUDIO_KEY, SERVER_ROUTES, getCustomHTTPExceptionResponse, isReservedRequestContextKey, isStudioClientTypeHeader, isZodError, normalizeQueryParams, redactStreamChunk, serializeStreamChunk } from "@mastra/server/server-adapter";
4
4
  import { formatZodError, isZodError as isZodError$1 } from "@mastra/server/handlers/error";
5
5
  import { canAccessPublicly, checkRules, defaultAuthConfig, isDevPlaygroundRequest, isProtectedPath } from "@mastra/server/auth";
6
6
  import { RequestContext } from "@mastra/core/request-context";
@@ -64,6 +64,27 @@ function __decorate(decorators, target, key, desc) {
64
64
  //#endregion
65
65
  //#region src/services/route-handler.service.ts
66
66
  var _RouteHandlerService;
67
+ const VALIDATION_CONTEXT_LABELS = {
68
+ query: "query parameters",
69
+ body: "request body",
70
+ path: "path parameters"
71
+ };
72
+ function getSchemaTypeName(schema) {
73
+ if (!schema || typeof schema !== "object") return;
74
+ const definition = schema._def ?? schema.def;
75
+ return definition?.typeName ?? definition?.type;
76
+ }
77
+ function unwrapOptionalNullable(schema) {
78
+ let inner = schema;
79
+ let typeName = getSchemaTypeName(inner);
80
+ while (typeName === "ZodOptional" || typeName === "optional" || typeName === "ZodNullable" || typeName === "nullable") {
81
+ const definition = inner?._def ?? inner?.def;
82
+ if (!definition?.innerType) return inner;
83
+ inner = definition.innerType;
84
+ typeName = getSchemaTypeName(inner);
85
+ }
86
+ return inner;
87
+ }
67
88
  let RouteHandlerService = _RouteHandlerService = class RouteHandlerService {
68
89
  mastra;
69
90
  options;
@@ -81,10 +102,14 @@ let RouteHandlerService = _RouteHandlerService = class RouteHandlerService {
81
102
  this.mastra = mastra;
82
103
  this.options = options;
83
104
  this.routeMap = /* @__PURE__ */ new Map();
84
- for (const route of SERVER_ROUTES) {
85
- const key = this.getRouteKey(route.method, route.path);
86
- this.routeMap.set(key, route);
87
- }
105
+ for (const route of SERVER_ROUTES) this.registerRoute(route);
106
+ }
107
+ /**
108
+ * Register a route with the catch-all NestJS controller.
109
+ */
110
+ registerRoute(route) {
111
+ const key = this.getRouteKey(route.method, route.path);
112
+ this.routeMap.set(key, route);
88
113
  }
89
114
  /**
90
115
  * Find a route by exact method and path pattern.
@@ -111,7 +136,7 @@ let RouteHandlerService = _RouteHandlerService = class RouteHandlerService {
111
136
  route: exactRoute,
112
137
  pathParams: {}
113
138
  };
114
- for (const route of SERVER_ROUTES) {
139
+ for (const route of this.routeMap.values()) {
115
140
  if (route.method.toUpperCase() !== checkMethod) continue;
116
141
  const pathParams = this.matchPath(route.path, path);
117
142
  if (pathParams) return {
@@ -126,7 +151,7 @@ let RouteHandlerService = _RouteHandlerService = class RouteHandlerService {
126
151
  * Get all routes (for dynamic controller generation).
127
152
  */
128
153
  getAllRoutes() {
129
- return SERVER_ROUTES;
154
+ return Array.from(this.routeMap.values());
130
155
  }
131
156
  /**
132
157
  * Match a path against a route pattern.
@@ -160,21 +185,21 @@ let RouteHandlerService = _RouteHandlerService = class RouteHandlerService {
160
185
  if (route.pathParamSchema) try {
161
186
  validatedPathParams = await route.pathParamSchema.parseAsync(params.pathParams);
162
187
  } catch (error) {
163
- if (isZodError(error)) throw new ValidationError("Invalid path parameters", error);
188
+ if (isZodError(error)) throw this.createValidationError(route, error, "path");
164
189
  throw error;
165
190
  }
166
191
  let validatedQueryParams = params.queryParams;
167
192
  if (route.queryParamSchema) try {
168
193
  validatedQueryParams = await route.queryParamSchema.parseAsync(params.queryParams);
169
194
  } catch (error) {
170
- if (isZodError(error)) throw new ValidationError("Invalid query parameters", error);
195
+ if (isZodError(error)) throw this.createValidationError(route, error, "query");
171
196
  throw error;
172
197
  }
173
198
  let validatedBody = params.body;
174
- if (route.bodySchema && params.body !== void 0) try {
175
- validatedBody = await route.bodySchema.parseAsync(params.body);
199
+ if (route.bodySchema) try {
200
+ validatedBody = await this.parseBody(route, params.body);
176
201
  } catch (error) {
177
- if (isZodError(error)) throw new ValidationError("Invalid request body", error);
202
+ if (isZodError(error)) throw this.createValidationError(route, error, "body");
178
203
  throw error;
179
204
  }
180
205
  const context = {
@@ -199,6 +224,31 @@ let RouteHandlerService = _RouteHandlerService = class RouteHandlerService {
199
224
  sseFlushOnConnect: route.sseFlushOnConnect
200
225
  };
201
226
  }
227
+ async parseBody(route, body) {
228
+ const bodySchema = route.bodySchema;
229
+ if (!bodySchema) return body;
230
+ if (body === void 0) {
231
+ const omitted = await bodySchema.safeParseAsync(void 0);
232
+ if (omitted.success) return omitted.data;
233
+ const schemaType = getSchemaTypeName(unwrapOptionalNullable(bodySchema));
234
+ if (schemaType === "object" || schemaType === "ZodObject") {
235
+ const emptyObject = await bodySchema.safeParseAsync({});
236
+ if (emptyObject.success) return emptyObject.data;
237
+ }
238
+ throw omitted.error;
239
+ }
240
+ return bodySchema.parseAsync(body);
241
+ }
242
+ createValidationError(route, error, context) {
243
+ const hook = route.onValidationError ?? this.mastra.getServer()?.onValidationError;
244
+ if (hook) try {
245
+ const result = hook(error, context);
246
+ if (result) return new ValidationError(`Invalid ${VALIDATION_CONTEXT_LABELS[context]}`, error, result.status, result.body);
247
+ } catch (hookError) {
248
+ this.logger.error("Error in custom onValidationError hook", hookError);
249
+ }
250
+ return new ValidationError(`Invalid ${VALIDATION_CONTEXT_LABELS[context]}`, error, 400, formatZodError(error, VALIDATION_CONTEXT_LABELS[context]));
251
+ }
202
252
  getRouteKey(method, path) {
203
253
  return `${method.toUpperCase()}:${path}`;
204
254
  }
@@ -232,9 +282,13 @@ RouteHandlerService = _RouteHandlerService = __decorate([
232
282
  */
233
283
  var ValidationError = class extends Error {
234
284
  zodError;
235
- constructor(message, zodError) {
285
+ status;
286
+ body;
287
+ constructor(message, zodError, status = 400, body = formatZodError(zodError, "request")) {
236
288
  super(message);
237
289
  this.zodError = zodError;
290
+ this.status = status;
291
+ this.body = body;
238
292
  this.name = "ValidationError";
239
293
  }
240
294
  };
@@ -243,11 +297,29 @@ var ValidationError = class extends Error {
243
297
  var _MastraExceptionFilter;
244
298
  let MastraExceptionFilter = _MastraExceptionFilter = class MastraExceptionFilter {
245
299
  logger = new Logger(_MastraExceptionFilter.name);
246
- catch(exception, host) {
300
+ async catch(exception, host) {
247
301
  const ctx = host.switchToHttp();
248
302
  const response = ctx.getResponse();
249
303
  const request = ctx.getRequest();
250
304
  if (response.headersSent) return;
305
+ if (exception instanceof ValidationError) {
306
+ response.status(exception.status).json(exception.body);
307
+ return;
308
+ }
309
+ const customResponse = getCustomHTTPExceptionResponse(exception);
310
+ if (customResponse) {
311
+ customResponse.headers.forEach((value, name) => {
312
+ if (name.toLowerCase() !== "set-cookie") response.setHeader(name, value);
313
+ });
314
+ const setCookies = customResponse.headers.getSetCookie();
315
+ if (setCookies.length > 0) response.setHeader("set-cookie", setCookies);
316
+ response.status(customResponse.status).send(Buffer.from(await customResponse.arrayBuffer()));
317
+ return;
318
+ }
319
+ if (exception instanceof HTTPException) {
320
+ response.status(exception.status).json({ error: exception.message });
321
+ return;
322
+ }
251
323
  const normalized = this.normalizeError(exception);
252
324
  const rawRequestId = request.headers["x-request-id"];
253
325
  const requestId = (Array.isArray(rawRequestId) ? rawRequestId[0] : rawRequestId) || request.mastraRequestId;
@@ -10819,7 +10891,8 @@ let MastraController = class MastraController {
10819
10891
  if (![
10820
10892
  "POST",
10821
10893
  "PUT",
10822
- "PATCH"
10894
+ "PATCH",
10895
+ "DELETE"
10823
10896
  ].includes(req.method)) return;
10824
10897
  if ((req.headers["content-type"] || "").includes("multipart/form-data")) {
10825
10898
  const maxFileSize = route.maxBodySize ?? this.options.bodyLimitOptions?.maxFileSize;
@@ -10829,7 +10902,7 @@ let MastraController = class MastraController {
10829
10902
  allowedMimeTypes
10830
10903
  });
10831
10904
  }
10832
- return req.body;
10905
+ return req.body === void 0 ? {} : req.body;
10833
10906
  }
10834
10907
  };
10835
10908
  __decorate([
@@ -11029,15 +11102,20 @@ MastraAuthGuard = _MastraAuthGuard = __decorate([
11029
11102
  //#endregion
11030
11103
  //#region src/mastra-server.adapter.ts
11031
11104
  /**
11032
- * Minimal Mastra server adapter wrapper for NestJS.
11033
- * Provides MastraServerBase compatibility so getServerApp() works.
11105
+ * Mastra server adapter wrapper for NestJS.
11106
+ * Provides app access and delegates dynamic route registration to the catch-all controller.
11034
11107
  */
11035
11108
  var NestMastraServer = class extends MastraServerBase {
11036
- constructor(app) {
11109
+ routeHandler;
11110
+ constructor(app, routeHandler) {
11037
11111
  super({
11038
11112
  app,
11039
11113
  name: "NestMastraServer"
11040
11114
  });
11115
+ this.routeHandler = routeHandler;
11116
+ }
11117
+ async registerRoute(_app, route, _options) {
11118
+ this.routeHandler.registerRoute(route);
11041
11119
  }
11042
11120
  };
11043
11121
  //#endregion
@@ -11048,20 +11126,31 @@ let MastraService = _MastraService = class MastraService {
11048
11126
  options;
11049
11127
  shutdownService;
11050
11128
  httpAdapterHost;
11129
+ routeHandler;
11051
11130
  logger = new Logger(_MastraService.name);
11052
11131
  serverAdapter;
11053
- constructor(mastra, options, shutdownService, httpAdapterHost) {
11132
+ constructor(mastra, options, shutdownService, httpAdapterHost, routeHandler) {
11054
11133
  this.mastra = mastra;
11055
11134
  this.options = options;
11056
11135
  this.shutdownService = shutdownService;
11057
11136
  this.httpAdapterHost = httpAdapterHost;
11137
+ this.routeHandler = routeHandler;
11138
+ this.initializeServerAdapter();
11139
+ }
11140
+ onModuleInit() {
11141
+ this.initializeServerAdapter();
11142
+ }
11143
+ initializeServerAdapter() {
11144
+ if (this.serverAdapter) return;
11058
11145
  const adapterType = this.httpAdapterHost?.httpAdapter?.getType?.();
11059
11146
  if (adapterType && adapterType !== "express") throw new Error(`MastraModule requires NestJS to use the Express HTTP adapter. Received "${adapterType}". Install @nestjs/platform-express and bootstrap with the Express platform.`);
11060
11147
  const app = this.httpAdapterHost?.httpAdapter?.getInstance?.();
11061
- if (app) {
11062
- this.serverAdapter = new NestMastraServer(app);
11063
- this.mastra.setMastraServer(this.serverAdapter);
11064
- } else this.logger.warn("Unable to register Mastra server adapter: HTTP adapter instance not available");
11148
+ if (!app) {
11149
+ this.logger.warn("Unable to register Mastra server adapter: HTTP adapter instance not available");
11150
+ return;
11151
+ }
11152
+ this.serverAdapter = new NestMastraServer(app, this.routeHandler);
11153
+ this.mastra.setMastraServer(this.serverAdapter);
11065
11154
  }
11066
11155
  /**
11067
11156
  * Get the Mastra instance.
@@ -11100,11 +11189,13 @@ MastraService = _MastraService = __decorate([
11100
11189
  __decorateParam(1, Inject(MASTRA_OPTIONS)),
11101
11190
  __decorateParam(2, Inject(ShutdownService)),
11102
11191
  __decorateParam(3, Inject(HttpAdapterHost)),
11192
+ __decorateParam(4, Inject(RouteHandlerService)),
11103
11193
  __decorateMetadata("design:paramtypes", [
11104
11194
  Object,
11105
11195
  Object,
11106
11196
  typeof ShutdownService === "undefined" ? Object : ShutdownService,
11107
- typeof HttpAdapterHost === "undefined" ? Object : HttpAdapterHost
11197
+ typeof HttpAdapterHost === "undefined" ? Object : HttpAdapterHost,
11198
+ typeof RouteHandlerService === "undefined" ? Object : RouteHandlerService
11108
11199
  ])
11109
11200
  ], MastraService);
11110
11201
  //#endregion
@@ -11153,11 +11244,12 @@ let JsonBodyMiddleware = class JsonBodyMiddleware {
11153
11244
  const maxSize = options.bodyLimitOptions?.maxSize ?? 10485760;
11154
11245
  this.jsonParser = express.json({
11155
11246
  limit: maxSize,
11247
+ strict: false,
11156
11248
  type: ["application/json", "application/*+json"]
11157
11249
  });
11158
11250
  }
11159
11251
  use(req, res, next) {
11160
- if (req.body !== void 0 && Object.keys(req.body).length > 0) {
11252
+ if (req.body !== void 0) {
11161
11253
  next();
11162
11254
  return;
11163
11255
  }