@mastra/nestjs 0.2.27-alpha.0 → 0.2.27-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,7 +35,7 @@ import { NestFactory } from '@nestjs/core';
35
35
  import { AppModule } from './app.module';
36
36
 
37
37
  async function bootstrap() {
38
- const app = await NestFactory.create(AppModule);
38
+ const app = await NestFactory.create(AppModule, { bodyParser: false });
39
39
  await app.listen(3000);
40
40
  }
41
41
 
@@ -48,6 +48,8 @@ bootstrap();
48
48
 
49
49
  The module registers Mastra routes under `/api` by default. Because it uses a catch-all NestJS controller, either import `MastraModule` last or assign a dedicated prefix such as `/api/mastra`.
50
50
 
51
+ Disable NestJS's default body parser when creating the application. `MastraModule` installs its own JSON parser so body limits, DELETE request bodies, scalar JSON, and route schema validation use the same adapter behavior.
52
+
51
53
  - [NestJS adapter reference](https://mastra.ai/reference/server/nestjs-adapter)
52
54
 
53
55
  ## Changelog
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -15,7 +15,7 @@ import type { ArgumentsHost, ExceptionFilter } from '@nestjs/common';
15
15
  */
16
16
  export declare class MastraExceptionFilter implements ExceptionFilter {
17
17
  private readonly logger;
18
- catch(exception: unknown, host: ArgumentsHost): void;
18
+ catch(exception: unknown, host: ArgumentsHost): Promise<void>;
19
19
  /**
20
20
  * Normalize any error type to a consistent format.
21
21
  */
@@ -1 +1 @@
1
- {"version":3,"file":"mastra-exception.filter.d.ts","sourceRoot":"","sources":["../../src/filters/mastra-exception.filter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAYrE;;;;;;;;;;;;;GAaG;AACH,qBACa,qBAAsB,YAAW,eAAe;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;IAEjE,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAgCnD;IAED;;OAEG;IACH,OAAO,CAAC,cAAc;IAgHtB;;OAEG;IACH,OAAO,CAAC,YAAY;CAkCrB"}
1
+ {"version":3,"file":"mastra-exception.filter.d.ts","sourceRoot":"","sources":["../../src/filters/mastra-exception.filter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAYrE;;;;;;;;;;;;;GAaG;AACH,qBACa,qBAAsB,YAAW,eAAe;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;IAE3D,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAyDlE;IAED;;OAEG;IACH,OAAO,CAAC,cAAc;IAgHtB;;OAEG;IACH,OAAO,CAAC,YAAY;CAkCrB"}
package/dist/index.cjs CHANGED
@@ -86,6 +86,27 @@ function __decorate(decorators, target, key, desc) {
86
86
  //#endregion
87
87
  //#region src/services/route-handler.service.ts
88
88
  var _RouteHandlerService;
89
+ const VALIDATION_CONTEXT_LABELS = {
90
+ query: "query parameters",
91
+ body: "request body",
92
+ path: "path parameters"
93
+ };
94
+ function getSchemaTypeName(schema) {
95
+ if (!schema || typeof schema !== "object") return;
96
+ const definition = schema._def ?? schema.def;
97
+ return definition?.typeName ?? definition?.type;
98
+ }
99
+ function unwrapOptionalNullable(schema) {
100
+ let inner = schema;
101
+ let typeName = getSchemaTypeName(inner);
102
+ while (typeName === "ZodOptional" || typeName === "optional" || typeName === "ZodNullable" || typeName === "nullable") {
103
+ const definition = inner?._def ?? inner?.def;
104
+ if (!definition?.innerType) return inner;
105
+ inner = definition.innerType;
106
+ typeName = getSchemaTypeName(inner);
107
+ }
108
+ return inner;
109
+ }
89
110
  let RouteHandlerService = _RouteHandlerService = class RouteHandlerService {
90
111
  mastra;
91
112
  options;
@@ -103,10 +124,14 @@ let RouteHandlerService = _RouteHandlerService = class RouteHandlerService {
103
124
  this.mastra = mastra;
104
125
  this.options = options;
105
126
  this.routeMap = /* @__PURE__ */ new Map();
106
- for (const route of _mastra_server_server_adapter.SERVER_ROUTES) {
107
- const key = this.getRouteKey(route.method, route.path);
108
- this.routeMap.set(key, route);
109
- }
127
+ for (const route of _mastra_server_server_adapter.SERVER_ROUTES) this.registerRoute(route);
128
+ }
129
+ /**
130
+ * Register a route with the catch-all NestJS controller.
131
+ */
132
+ registerRoute(route) {
133
+ const key = this.getRouteKey(route.method, route.path);
134
+ this.routeMap.set(key, route);
110
135
  }
111
136
  /**
112
137
  * Find a route by exact method and path pattern.
@@ -133,7 +158,7 @@ let RouteHandlerService = _RouteHandlerService = class RouteHandlerService {
133
158
  route: exactRoute,
134
159
  pathParams: {}
135
160
  };
136
- for (const route of _mastra_server_server_adapter.SERVER_ROUTES) {
161
+ for (const route of this.routeMap.values()) {
137
162
  if (route.method.toUpperCase() !== checkMethod) continue;
138
163
  const pathParams = this.matchPath(route.path, path);
139
164
  if (pathParams) return {
@@ -148,7 +173,7 @@ let RouteHandlerService = _RouteHandlerService = class RouteHandlerService {
148
173
  * Get all routes (for dynamic controller generation).
149
174
  */
150
175
  getAllRoutes() {
151
- return _mastra_server_server_adapter.SERVER_ROUTES;
176
+ return Array.from(this.routeMap.values());
152
177
  }
153
178
  /**
154
179
  * Match a path against a route pattern.
@@ -182,21 +207,21 @@ let RouteHandlerService = _RouteHandlerService = class RouteHandlerService {
182
207
  if (route.pathParamSchema) try {
183
208
  validatedPathParams = await route.pathParamSchema.parseAsync(params.pathParams);
184
209
  } catch (error) {
185
- if ((0, _mastra_server_server_adapter.isZodError)(error)) throw new ValidationError("Invalid path parameters", error);
210
+ if ((0, _mastra_server_server_adapter.isZodError)(error)) throw this.createValidationError(route, error, "path");
186
211
  throw error;
187
212
  }
188
213
  let validatedQueryParams = params.queryParams;
189
214
  if (route.queryParamSchema) try {
190
215
  validatedQueryParams = await route.queryParamSchema.parseAsync(params.queryParams);
191
216
  } catch (error) {
192
- if ((0, _mastra_server_server_adapter.isZodError)(error)) throw new ValidationError("Invalid query parameters", error);
217
+ if ((0, _mastra_server_server_adapter.isZodError)(error)) throw this.createValidationError(route, error, "query");
193
218
  throw error;
194
219
  }
195
220
  let validatedBody = params.body;
196
- if (route.bodySchema && params.body !== void 0) try {
197
- validatedBody = await route.bodySchema.parseAsync(params.body);
221
+ if (route.bodySchema) try {
222
+ validatedBody = await this.parseBody(route, params.body);
198
223
  } catch (error) {
199
- if ((0, _mastra_server_server_adapter.isZodError)(error)) throw new ValidationError("Invalid request body", error);
224
+ if ((0, _mastra_server_server_adapter.isZodError)(error)) throw this.createValidationError(route, error, "body");
200
225
  throw error;
201
226
  }
202
227
  const context = {
@@ -221,6 +246,31 @@ let RouteHandlerService = _RouteHandlerService = class RouteHandlerService {
221
246
  sseFlushOnConnect: route.sseFlushOnConnect
222
247
  };
223
248
  }
249
+ async parseBody(route, body) {
250
+ const bodySchema = route.bodySchema;
251
+ if (!bodySchema) return body;
252
+ if (body === void 0) {
253
+ const omitted = await bodySchema.safeParseAsync(void 0);
254
+ if (omitted.success) return omitted.data;
255
+ const schemaType = getSchemaTypeName(unwrapOptionalNullable(bodySchema));
256
+ if (schemaType === "object" || schemaType === "ZodObject") {
257
+ const emptyObject = await bodySchema.safeParseAsync({});
258
+ if (emptyObject.success) return emptyObject.data;
259
+ }
260
+ throw omitted.error;
261
+ }
262
+ return bodySchema.parseAsync(body);
263
+ }
264
+ createValidationError(route, error, context) {
265
+ const hook = route.onValidationError ?? this.mastra.getServer()?.onValidationError;
266
+ if (hook) try {
267
+ const result = hook(error, context);
268
+ if (result) return new ValidationError(`Invalid ${VALIDATION_CONTEXT_LABELS[context]}`, error, result.status, result.body);
269
+ } catch (hookError) {
270
+ this.logger.error("Error in custom onValidationError hook", hookError);
271
+ }
272
+ return new ValidationError(`Invalid ${VALIDATION_CONTEXT_LABELS[context]}`, error, 400, (0, _mastra_server_handlers_error.formatZodError)(error, VALIDATION_CONTEXT_LABELS[context]));
273
+ }
224
274
  getRouteKey(method, path) {
225
275
  return `${method.toUpperCase()}:${path}`;
226
276
  }
@@ -254,9 +304,13 @@ RouteHandlerService = _RouteHandlerService = __decorate([
254
304
  */
255
305
  var ValidationError = class extends Error {
256
306
  zodError;
257
- constructor(message, zodError) {
307
+ status;
308
+ body;
309
+ constructor(message, zodError, status = 400, body = (0, _mastra_server_handlers_error.formatZodError)(zodError, "request")) {
258
310
  super(message);
259
311
  this.zodError = zodError;
312
+ this.status = status;
313
+ this.body = body;
260
314
  this.name = "ValidationError";
261
315
  }
262
316
  };
@@ -265,11 +319,29 @@ var ValidationError = class extends Error {
265
319
  var _MastraExceptionFilter;
266
320
  let MastraExceptionFilter = _MastraExceptionFilter = class MastraExceptionFilter {
267
321
  logger = new _nestjs_common.Logger(_MastraExceptionFilter.name);
268
- catch(exception, host) {
322
+ async catch(exception, host) {
269
323
  const ctx = host.switchToHttp();
270
324
  const response = ctx.getResponse();
271
325
  const request = ctx.getRequest();
272
326
  if (response.headersSent) return;
327
+ if (exception instanceof ValidationError) {
328
+ response.status(exception.status).json(exception.body);
329
+ return;
330
+ }
331
+ const customResponse = (0, _mastra_server_server_adapter.getCustomHTTPExceptionResponse)(exception);
332
+ if (customResponse) {
333
+ customResponse.headers.forEach((value, name) => {
334
+ if (name.toLowerCase() !== "set-cookie") response.setHeader(name, value);
335
+ });
336
+ const setCookies = customResponse.headers.getSetCookie();
337
+ if (setCookies.length > 0) response.setHeader("set-cookie", setCookies);
338
+ response.status(customResponse.status).send(Buffer.from(await customResponse.arrayBuffer()));
339
+ return;
340
+ }
341
+ if (exception instanceof _mastra_server_server_adapter.HTTPException) {
342
+ response.status(exception.status).json({ error: exception.message });
343
+ return;
344
+ }
273
345
  const normalized = this.normalizeError(exception);
274
346
  const rawRequestId = request.headers["x-request-id"];
275
347
  const requestId = (Array.isArray(rawRequestId) ? rawRequestId[0] : rawRequestId) || request.mastraRequestId;
@@ -10841,7 +10913,8 @@ let MastraController = class MastraController {
10841
10913
  if (![
10842
10914
  "POST",
10843
10915
  "PUT",
10844
- "PATCH"
10916
+ "PATCH",
10917
+ "DELETE"
10845
10918
  ].includes(req.method)) return;
10846
10919
  if ((req.headers["content-type"] || "").includes("multipart/form-data")) {
10847
10920
  const maxFileSize = route.maxBodySize ?? this.options.bodyLimitOptions?.maxFileSize;
@@ -10851,7 +10924,7 @@ let MastraController = class MastraController {
10851
10924
  allowedMimeTypes
10852
10925
  });
10853
10926
  }
10854
- return req.body;
10927
+ return req.body === void 0 ? {} : req.body;
10855
10928
  }
10856
10929
  };
10857
10930
  __decorate([
@@ -11051,15 +11124,20 @@ MastraAuthGuard = _MastraAuthGuard = __decorate([
11051
11124
  //#endregion
11052
11125
  //#region src/mastra-server.adapter.ts
11053
11126
  /**
11054
- * Minimal Mastra server adapter wrapper for NestJS.
11055
- * Provides MastraServerBase compatibility so getServerApp() works.
11127
+ * Mastra server adapter wrapper for NestJS.
11128
+ * Provides app access and delegates dynamic route registration to the catch-all controller.
11056
11129
  */
11057
11130
  var NestMastraServer = class extends _mastra_core_server.MastraServerBase {
11058
- constructor(app) {
11131
+ routeHandler;
11132
+ constructor(app, routeHandler) {
11059
11133
  super({
11060
11134
  app,
11061
11135
  name: "NestMastraServer"
11062
11136
  });
11137
+ this.routeHandler = routeHandler;
11138
+ }
11139
+ async registerRoute(_app, route, _options) {
11140
+ this.routeHandler.registerRoute(route);
11063
11141
  }
11064
11142
  };
11065
11143
  //#endregion
@@ -11070,20 +11148,31 @@ let MastraService = _MastraService = class MastraService {
11070
11148
  options;
11071
11149
  shutdownService;
11072
11150
  httpAdapterHost;
11151
+ routeHandler;
11073
11152
  logger = new _nestjs_common.Logger(_MastraService.name);
11074
11153
  serverAdapter;
11075
- constructor(mastra, options, shutdownService, httpAdapterHost) {
11154
+ constructor(mastra, options, shutdownService, httpAdapterHost, routeHandler) {
11076
11155
  this.mastra = mastra;
11077
11156
  this.options = options;
11078
11157
  this.shutdownService = shutdownService;
11079
11158
  this.httpAdapterHost = httpAdapterHost;
11159
+ this.routeHandler = routeHandler;
11160
+ this.initializeServerAdapter();
11161
+ }
11162
+ onModuleInit() {
11163
+ this.initializeServerAdapter();
11164
+ }
11165
+ initializeServerAdapter() {
11166
+ if (this.serverAdapter) return;
11080
11167
  const adapterType = this.httpAdapterHost?.httpAdapter?.getType?.();
11081
11168
  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.`);
11082
11169
  const app = this.httpAdapterHost?.httpAdapter?.getInstance?.();
11083
- if (app) {
11084
- this.serverAdapter = new NestMastraServer(app);
11085
- this.mastra.setMastraServer(this.serverAdapter);
11086
- } else this.logger.warn("Unable to register Mastra server adapter: HTTP adapter instance not available");
11170
+ if (!app) {
11171
+ this.logger.warn("Unable to register Mastra server adapter: HTTP adapter instance not available");
11172
+ return;
11173
+ }
11174
+ this.serverAdapter = new NestMastraServer(app, this.routeHandler);
11175
+ this.mastra.setMastraServer(this.serverAdapter);
11087
11176
  }
11088
11177
  /**
11089
11178
  * Get the Mastra instance.
@@ -11122,11 +11211,13 @@ MastraService = _MastraService = __decorate([
11122
11211
  __decorateParam(1, (0, _nestjs_common.Inject)(MASTRA_OPTIONS)),
11123
11212
  __decorateParam(2, (0, _nestjs_common.Inject)(ShutdownService)),
11124
11213
  __decorateParam(3, (0, _nestjs_common.Inject)(_nestjs_core.HttpAdapterHost)),
11214
+ __decorateParam(4, (0, _nestjs_common.Inject)(RouteHandlerService)),
11125
11215
  __decorateMetadata("design:paramtypes", [
11126
11216
  Object,
11127
11217
  Object,
11128
11218
  typeof ShutdownService === "undefined" ? Object : ShutdownService,
11129
- typeof _nestjs_core.HttpAdapterHost === "undefined" ? Object : _nestjs_core.HttpAdapterHost
11219
+ typeof _nestjs_core.HttpAdapterHost === "undefined" ? Object : _nestjs_core.HttpAdapterHost,
11220
+ typeof RouteHandlerService === "undefined" ? Object : RouteHandlerService
11130
11221
  ])
11131
11222
  ], MastraService);
11132
11223
  //#endregion
@@ -11175,11 +11266,12 @@ let JsonBodyMiddleware = class JsonBodyMiddleware {
11175
11266
  const maxSize = options.bodyLimitOptions?.maxSize ?? 10485760;
11176
11267
  this.jsonParser = express.default.json({
11177
11268
  limit: maxSize,
11269
+ strict: false,
11178
11270
  type: ["application/json", "application/*+json"]
11179
11271
  });
11180
11272
  }
11181
11273
  use(req, res, next) {
11182
- if (req.body !== void 0 && Object.keys(req.body).length > 0) {
11274
+ if (req.body !== void 0) {
11183
11275
  next();
11184
11276
  return;
11185
11277
  }