@silkweave/nestjs 2.0.0 → 2.3.0
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 +15 -2
- package/build/index.d.mts +45 -5
- package/build/index.d.mts.map +1 -1
- package/build/index.mjs +61 -16
- package/build/index.mjs.map +1 -1
- package/package.json +18 -11
- package/.turbo/turbo-build.log +0 -16
- package/.turbo/turbo-check.log +0 -3
- package/.turbo/turbo-lint.log +0 -2
- package/.turbo/turbo-prepack.log +0 -18
- package/eslint.config.mjs +0 -93
- package/src/adapter/mcp.ts +0 -102
- package/src/decorator/mcp.ts +0 -39
- package/src/index.ts +0 -10
- package/src/lib/controllerDiscovery.ts +0 -201
- package/src/lib/executionContext.ts +0 -76
- package/src/lib/guards.ts +0 -70
- package/src/lib/metadata.ts +0 -38
- package/src/lib/rebind.ts +0 -117
- package/src/lib/reflect/classValidator.ts +0 -56
- package/src/lib/reflect/openapi.ts +0 -87
- package/src/lib/reflect/params.ts +0 -63
- package/src/lib/reflect/route.ts +0 -58
- package/src/lib/reflect/schema.ts +0 -270
- package/src/lib/reflect/swagger.ts +0 -39
- package/src/lib/silkweave.module.ts +0 -75
- package/src/lib/types.ts +0 -61
- package/tsconfig.json +0 -25
- package/tsconfig.tsbuildinfo +0 -1
- package/tsdown.config.ts +0 -7
package/README.md
CHANGED
|
@@ -117,6 +117,7 @@ Exposes the decorated controller route as an MCP tool. Every option is optional.
|
|
|
117
117
|
| `description` | `string` | `@ApiOperation` summary/description, else generated | Tool description |
|
|
118
118
|
| `input` | `Record<string, z.ZodType>` | - | Zod raw-shape override merged over the reflected fields (per-field). The escape hatch for shapes reflection can't express - discriminated unions, custom validators, `@Transform` |
|
|
119
119
|
| `pipes` | `'apply' \| 'skip'` | `'apply'` | Whether to run parameter-bound pipes (`@Param('id', ParseIntPipe)`) when re-binding |
|
|
120
|
+
| `result` | `'json' \| 'smart'` | `'smart'` | Default MCP result format - `'json'` returns compact JSON text (`jsonToolResult`); `'smart'` inlines small payloads and offloads large ones to an embedded resource (`smartToolResult`). A client that sends `_meta.disposition` on the call overrides it |
|
|
120
121
|
|
|
121
122
|
## How reflection works
|
|
122
123
|
|
|
@@ -174,15 +175,27 @@ Native NestJS `@UseGuards()` on `@Mcp` methods run before the handler is invoked
|
|
|
174
175
|
|
|
175
176
|
**Headers over MCP.** The inbound tool-call request is surfaced as a stand-in `{ headers, url, params, query }` object built from the MCP SDK's `extra.requestInfo`, so a header-based guard - e.g. one reading `getRequest().headers['x-api-key']` - works unchanged. `ExecutionContext.getType()` is `'http'` whenever a request is available (and `'rpc'` for transports with none, e.g. MCP stdio). Caveats:
|
|
176
177
|
|
|
177
|
-
-
|
|
178
|
+
- **Headers**, the request `url`, and **URL path params** cross the MCP boundary. `getRequest().params` is populated from the route's reflected `@Param` fields (as raw strings, like Express), so a path-scoped guard - e.g. one reading `getRequest().params['id']` to fence a key to one resource - works the same over MCP as over REST. `getRequest().query` is still empty (no query string over MCP).
|
|
178
179
|
- A guard that denies (returns `false` or throws) produces a clean MCP tool error (`ForbiddenException`), not an HTTP 500.
|
|
179
180
|
- For OAuth 2.1 / bearer-token MCP auth, prefer `mcp({ auth })` and read the resolved identity from the silkweave context (`ctx.get('auth')`); the header stand-in is for custom request-reading guards.
|
|
180
181
|
|
|
182
|
+
**Global guards (opt-in).** App-global guards - registered via `app.useGlobalGuards(new X())` or `{ provide: APP_GUARD, useClass }` - do **not** run on tool calls by default. Opt them in by class with `globalGuards`:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
SilkweaveModule.forRoot({
|
|
186
|
+
silkweave: { name: 'app', description: 'My App', version: '1.0.0' },
|
|
187
|
+
adapters: [mcp({ basePath: '/mcp' })],
|
|
188
|
+
globalGuards: [ApiKeyGuard] // runs before each method/class @UseGuards; throttler etc. deliberately excluded
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The allow-list is explicit-by-class on purpose - a blanket "run every global" would also fire unrelated globals (e.g. a `ThrottlerGuard`, which assumes a writable response MCP doesn't provide). Listed guards run **before** the method/class `@UseGuards`, mirroring Nest's request pipeline. They see the same request stand-in as method guards: headers, `url`, and path `params` (populated from reflected `@Param` fields); only `query`/IP-derived logic won't apply over MCP.
|
|
193
|
+
|
|
181
194
|
Controllers are normal Nest providers - inject services via the constructor as usual.
|
|
182
195
|
|
|
183
196
|
## What does *not* run
|
|
184
197
|
|
|
185
|
-
Because the handler is invoked directly (not through Nest's HTTP request pipeline), the following do **not** apply on a tool call - only `@UseGuards()` and parameter-bound pipes do:
|
|
198
|
+
Because the handler is invoked directly (not through Nest's HTTP request pipeline), the following do **not** apply on a tool call - only `@UseGuards()` (plus any opted-in `globalGuards`) and parameter-bound pipes do:
|
|
186
199
|
|
|
187
200
|
- Globally-registered `ValidationPipe` / interceptors / exception filters (MCP input is instead validated against the reflected Zod schema).
|
|
188
201
|
- DTO class instantiation - whole-DTO `@Body()`/`@Query()` arguments arrive as plain objects, so `@Transform` and DTO methods do not fire.
|
package/build/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CanActivate, DynamicModule, MiddlewareConsumer, NestModule, Type } from "@nestjs/common";
|
|
2
|
-
import { DiscoveryService, HttpAdapterHost, MetadataScanner, ModuleRef, Reflector } from "@nestjs/core";
|
|
2
|
+
import { ApplicationConfig, DiscoveryService, HttpAdapterHost, MetadataScanner, ModuleRef, Reflector } from "@nestjs/core";
|
|
3
3
|
import { Action, SilkweaveContext, SilkweaveOptions } from "@silkweave/core";
|
|
4
4
|
import { z } from "zod/v4";
|
|
5
5
|
import { AuthConfig } from "@silkweave/auth";
|
|
@@ -137,6 +137,19 @@ interface SilkweaveModuleOptions {
|
|
|
137
137
|
* reflection when an operation or field isn't present.
|
|
138
138
|
*/
|
|
139
139
|
openapi?: OpenApiDocument;
|
|
140
|
+
/**
|
|
141
|
+
* Opt-in allow-list of app-global guard classes (registered via
|
|
142
|
+
* `app.useGlobalGuards()` or `{ provide: APP_GUARD, useClass }`) to run on
|
|
143
|
+
* every MCP tool call, before each method/class `@UseGuards`. Listed by
|
|
144
|
+
* class - a blanket "run all globals" is deliberately not offered, since
|
|
145
|
+
* unrelated globals (e.g. a `ThrottlerGuard` that needs a writable response)
|
|
146
|
+
* would misbehave over MCP. Empty/omitted ⇒ no global guards run.
|
|
147
|
+
*
|
|
148
|
+
* Note: over MCP the request stand-in is headers-only (`params`/`query` are
|
|
149
|
+
* empty), so per-session or IP-derived guard logic won't apply; header-based
|
|
150
|
+
* authentication still works.
|
|
151
|
+
*/
|
|
152
|
+
globalGuards?: Type<CanActivate>[];
|
|
140
153
|
}
|
|
141
154
|
declare const SILKWEAVE_MODULE_OPTIONS = "__silkweave_module_options__";
|
|
142
155
|
//#endregion
|
|
@@ -204,6 +217,14 @@ interface McpMetadata {
|
|
|
204
217
|
* the method is invoked directly, not through Nest's HTTP request pipeline.
|
|
205
218
|
*/
|
|
206
219
|
pipes?: 'apply' | 'skip';
|
|
220
|
+
/**
|
|
221
|
+
* Default MCP result format for this tool. `'json'` returns compact JSON text
|
|
222
|
+
* (`jsonToolResult`); `'smart'` (the default when unset) inlines small
|
|
223
|
+
* payloads and offloads large ones to an embedded resource (`smartToolResult`).
|
|
224
|
+
* This is only a default - a client that sends `_meta.disposition` on the tool
|
|
225
|
+
* call overrides it.
|
|
226
|
+
*/
|
|
227
|
+
result?: 'json' | 'smart';
|
|
207
228
|
}
|
|
208
229
|
//#endregion
|
|
209
230
|
//#region src/decorator/mcp.d.ts
|
|
@@ -248,15 +269,17 @@ declare class ControllerDiscovery {
|
|
|
248
269
|
private readonly scanner;
|
|
249
270
|
private readonly reflector;
|
|
250
271
|
private readonly moduleRef;
|
|
251
|
-
|
|
272
|
+
private readonly appConfig;
|
|
273
|
+
constructor(discovery: DiscoveryService, scanner: MetadataScanner, reflector: Reflector, moduleRef: ModuleRef, appConfig: ApplicationConfig);
|
|
252
274
|
/**
|
|
253
275
|
* Walk every Nest provider/controller, find methods annotated with `@Mcp`,
|
|
254
276
|
* and build a core `Action` per method whose input schema is reflected from
|
|
255
277
|
* the route + parameter decorators (+ optional OpenAPI document) and whose
|
|
256
278
|
* `run` re-binds the validated input back into the method's positional
|
|
257
|
-
* arguments (with `@UseGuards` guards
|
|
279
|
+
* arguments (with `@UseGuards` guards - and any opted-in `globalGuards` -
|
|
280
|
+
* applied first).
|
|
258
281
|
*/
|
|
259
|
-
discover(openapi?: OpenApiDocument): Action[];
|
|
282
|
+
discover(openapi?: OpenApiDocument, globalGuards?: Type<CanActivate>[]): Action[];
|
|
260
283
|
private toAction;
|
|
261
284
|
/** Build the merged Zod input shape and the per-argument re-bind plan. */
|
|
262
285
|
private buildInput;
|
|
@@ -264,6 +287,23 @@ declare class ControllerDiscovery {
|
|
|
264
287
|
//#endregion
|
|
265
288
|
//#region src/lib/guards.d.ts
|
|
266
289
|
type GuardRef = Type<CanActivate> | CanActivate;
|
|
290
|
+
/**
|
|
291
|
+
* Collect the app's global guards that match an opt-in allow-list of classes.
|
|
292
|
+
*
|
|
293
|
+
* Reads both registration styles Nest exposes via `ApplicationConfig`:
|
|
294
|
+
* `useGlobalGuards(new X())` instances (`getGlobalGuards()`) and
|
|
295
|
+
* `{ provide: APP_GUARD, useClass }` DI guards (`getGlobalRequestGuards()`,
|
|
296
|
+
* which yields `InstanceWrapper`s - we read `.instance` off each).
|
|
297
|
+
*
|
|
298
|
+
* The allow-list is intentionally explicit-by-class: a blanket "run every
|
|
299
|
+
* global" would also fire unrelated globals (e.g. a `ThrottlerGuard` that
|
|
300
|
+
* assumes a writable response) on every tool call. An empty allow-list runs
|
|
301
|
+
* no globals, preserving the prior behavior.
|
|
302
|
+
*
|
|
303
|
+
* Call this at tool-call time, not at discovery time: `APP_GUARD` instances
|
|
304
|
+
* aren't populated until `app.init()` finishes.
|
|
305
|
+
*/
|
|
306
|
+
declare function collectGlobalGuards(appConfig: ApplicationConfig, allowList: Type<CanActivate>[]): CanActivate[];
|
|
267
307
|
/**
|
|
268
308
|
* Read `@UseGuards(...)` metadata for both the method and its class and merge
|
|
269
309
|
* the two lists. Method-level guards run AFTER class-level guards (matching
|
|
@@ -372,5 +412,5 @@ declare class SilkweaveModule implements NestModule {
|
|
|
372
412
|
configure(_consumer: MiddlewareConsumer): void;
|
|
373
413
|
}
|
|
374
414
|
//#endregion
|
|
375
|
-
export { Binding, ControllerDiscovery, FieldDesc, MCP_METADATA, Mcp, McpAdapterOptions, McpMetadata, NestAdapterRegisterContext, NestSilkweaveAdapter, OpenApiDocument, OpenApiLookup, SILKWEAVE_MODULE_OPTIONS, SilkweaveModule, SilkweaveModuleOptions, apiPropertyToField, buildOpenApiLookup, classValidatorToField, collectGuards, designTypeToField, fieldToZod, invokeRebound, mcp, mergeField, normalizeEnum, openApiFields, openapiSchemaToField, reflectDtoFields, runGuards, specialBinding, swaggerParamToField, typeTokenToBase };
|
|
415
|
+
export { Binding, ControllerDiscovery, FieldDesc, MCP_METADATA, Mcp, McpAdapterOptions, McpMetadata, NestAdapterRegisterContext, NestSilkweaveAdapter, OpenApiDocument, OpenApiLookup, SILKWEAVE_MODULE_OPTIONS, SilkweaveModule, SilkweaveModuleOptions, apiPropertyToField, buildOpenApiLookup, classValidatorToField, collectGlobalGuards, collectGuards, designTypeToField, fieldToZod, invokeRebound, mcp, mergeField, normalizeEnum, openApiFields, openapiSchemaToField, reflectDtoFields, runGuards, specialBinding, swaggerParamToField, typeTokenToBase };
|
|
376
416
|
//# sourceMappingURL=index.d.mts.map
|
package/build/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/lib/reflect/schema.ts","../src/lib/reflect/openapi.ts","../src/lib/types.ts","../src/adapter/mcp.ts","../src/lib/metadata.ts","../src/decorator/mcp.ts","../src/lib/controllerDiscovery.ts","../src/lib/guards.ts","../src/lib/rebind.ts","../src/lib/silkweave.module.ts"],"mappings":";;;;;;;;;;;;;;;UAeiB,SAAA;EACf,IAAA;EACA,QAAA;EACA,WAAA;EACA,IAAA;EACA,KAAA,GAAQ,SAAA;EACR,GAAA;EACA,GAAA;EACA,SAAA;EACA,SAAA;EACA,MAAA;EACA,OAAA;AAAA;;iBAac,UAAA,CAAW,IAAA,EAAM,SAAA,EAAW,IAAA,EAAM,SAAA,GAAY,SAAA;;iBA2C9C,UAAA,CAAW,CAAA,EAAG,SAAA,GAAY,CAAA,CAAE,OAAA;;iBAc5B,aAAA,CAAc,KAAA;;iBAWd,eAAA,CAAgB,IAAA,YAAgB,SAAA;;iBAkBhC,iBAAA,CAAkB,IAAA,YAAgB,SAAA;;iBAQlC,mBAAA,CAAoB,CAAA,EAAG,MAAA,gBAAsB,SAAA;;iBAgB7C,kBAAA,CAAmB,CAAA,EAAG,MAAA,gBAAsB,SAAA;;iBA4D5C,qBAAA,CAAsB,KAAA,EAAO,KAAA;EAAQ,IAAA;EAAe,IAAA;EAAe,WAAA;AAAA,KAA6B,SAAA;AA/HhH;AAAA,iBAsIgB,oBAAA,CAAqB,MAAA,EAAQ,MAAA,gBAAsB,SAAA;;;;;;;;iBA4BnD,gBAAA,CAAiB,OAAA,QAAe,MAAA,SAAe,SAAA;;;;;;;;UC5O9C,eAAA;EACf,KAAA,GAAQ,MAAA,SAAe,MAAA;EACvB,UAAA;IAAe,OAAA,GAAU,MAAA;EAAA;AAAA;AAAA,UAGV,aAAA;EACf,GAAA,EAAK,eAAA;EDIL;ECFA,UAAA,EAAY,GAAA;AAAA;;iBAIE,kBAAA,CAAmB,GAAA,EAAK,eAAA,GAAkB,aAAA;;;;;;;iBAwC1C,aAAA,CAAc,MAAA,EAAQ,aAAA,EAAe,MAAA,UAAgB,WAAA,WAAsB,MAAA,SAAe,SAAA
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/lib/reflect/schema.ts","../src/lib/reflect/openapi.ts","../src/lib/types.ts","../src/adapter/mcp.ts","../src/lib/metadata.ts","../src/decorator/mcp.ts","../src/lib/controllerDiscovery.ts","../src/lib/guards.ts","../src/lib/rebind.ts","../src/lib/silkweave.module.ts"],"mappings":"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"}
|
package/build/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { authMiddleware, mcpCors, mcpTransport, oauthRoutes, protectedResourceMetadata, sideloadResource } from "@silkweave/mcp";
|
|
3
3
|
import express from "express";
|
|
4
4
|
import { ForbiddenException, Inject, Injectable, Module, SetMetadata } from "@nestjs/common";
|
|
5
|
-
import { DiscoveryModule, DiscoveryService, HttpAdapterHost, MetadataScanner, ModuleRef, Reflector } from "@nestjs/core";
|
|
5
|
+
import { ApplicationConfig, DiscoveryModule, DiscoveryService, HttpAdapterHost, MetadataScanner, ModuleRef, Reflector } from "@nestjs/core";
|
|
6
6
|
import { createAction, createContext } from "@silkweave/core";
|
|
7
7
|
import { z } from "zod/v4";
|
|
8
8
|
import { GUARDS_METADATA, METHOD_METADATA, PATH_METADATA, ROUTE_ARGS_METADATA } from "@nestjs/common/constants.js";
|
|
@@ -8761,6 +8761,26 @@ var SilkweaveExecutionContext = class {
|
|
|
8761
8761
|
//#endregion
|
|
8762
8762
|
//#region src/lib/guards.ts
|
|
8763
8763
|
/**
|
|
8764
|
+
* Collect the app's global guards that match an opt-in allow-list of classes.
|
|
8765
|
+
*
|
|
8766
|
+
* Reads both registration styles Nest exposes via `ApplicationConfig`:
|
|
8767
|
+
* `useGlobalGuards(new X())` instances (`getGlobalGuards()`) and
|
|
8768
|
+
* `{ provide: APP_GUARD, useClass }` DI guards (`getGlobalRequestGuards()`,
|
|
8769
|
+
* which yields `InstanceWrapper`s - we read `.instance` off each).
|
|
8770
|
+
*
|
|
8771
|
+
* The allow-list is intentionally explicit-by-class: a blanket "run every
|
|
8772
|
+
* global" would also fire unrelated globals (e.g. a `ThrottlerGuard` that
|
|
8773
|
+
* assumes a writable response) on every tool call. An empty allow-list runs
|
|
8774
|
+
* no globals, preserving the prior behavior.
|
|
8775
|
+
*
|
|
8776
|
+
* Call this at tool-call time, not at discovery time: `APP_GUARD` instances
|
|
8777
|
+
* aren't populated until `app.init()` finishes.
|
|
8778
|
+
*/
|
|
8779
|
+
function collectGlobalGuards(appConfig, allowList) {
|
|
8780
|
+
if (allowList.length === 0) return [];
|
|
8781
|
+
return [...appConfig.getGlobalGuards(), ...appConfig.getGlobalRequestGuards().map((w) => w.instance)].filter((g) => g != null).filter((g) => allowList.some((c) => g instanceof c));
|
|
8782
|
+
}
|
|
8783
|
+
/**
|
|
8764
8784
|
* Read `@UseGuards(...)` metadata for both the method and its class and merge
|
|
8765
8785
|
* the two lists. Method-level guards run AFTER class-level guards (matching
|
|
8766
8786
|
* Nest's own behavior).
|
|
@@ -9340,22 +9360,24 @@ function __decorate(decorators, target, key, desc) {
|
|
|
9340
9360
|
}
|
|
9341
9361
|
//#endregion
|
|
9342
9362
|
//#region src/lib/controllerDiscovery.ts
|
|
9343
|
-
var _ref$1, _ref2$1, _ref3, _ref4;
|
|
9363
|
+
var _ref$1, _ref2$1, _ref3, _ref4, _ref5;
|
|
9344
9364
|
let ControllerDiscovery = class ControllerDiscovery {
|
|
9345
|
-
constructor(discovery, scanner, reflector, moduleRef) {
|
|
9365
|
+
constructor(discovery, scanner, reflector, moduleRef, appConfig) {
|
|
9346
9366
|
this.discovery = discovery;
|
|
9347
9367
|
this.scanner = scanner;
|
|
9348
9368
|
this.reflector = reflector;
|
|
9349
9369
|
this.moduleRef = moduleRef;
|
|
9370
|
+
this.appConfig = appConfig;
|
|
9350
9371
|
}
|
|
9351
9372
|
/**
|
|
9352
9373
|
* Walk every Nest provider/controller, find methods annotated with `@Mcp`,
|
|
9353
9374
|
* and build a core `Action` per method whose input schema is reflected from
|
|
9354
9375
|
* the route + parameter decorators (+ optional OpenAPI document) and whose
|
|
9355
9376
|
* `run` re-binds the validated input back into the method's positional
|
|
9356
|
-
* arguments (with `@UseGuards` guards
|
|
9377
|
+
* arguments (with `@UseGuards` guards - and any opted-in `globalGuards` -
|
|
9378
|
+
* applied first).
|
|
9357
9379
|
*/
|
|
9358
|
-
discover(openapi) {
|
|
9380
|
+
discover(openapi, globalGuards = []) {
|
|
9359
9381
|
const lookup = openapi ? buildOpenApiLookup(openapi) : void 0;
|
|
9360
9382
|
const discovered = [];
|
|
9361
9383
|
for (const wrapper of this.discovery.getProviders().concat(this.discovery.getControllers())) {
|
|
@@ -9378,9 +9400,9 @@ let ControllerDiscovery = class ControllerDiscovery {
|
|
|
9378
9400
|
});
|
|
9379
9401
|
}
|
|
9380
9402
|
}
|
|
9381
|
-
return discovered.map((d) => this.toAction(d, lookup));
|
|
9403
|
+
return discovered.map((d) => this.toAction(d, lookup, globalGuards));
|
|
9382
9404
|
}
|
|
9383
|
-
toAction(d, lookup) {
|
|
9405
|
+
toAction(d, lookup, globalGuards) {
|
|
9384
9406
|
const proto = Object.getPrototypeOf(d.instance);
|
|
9385
9407
|
const route = reflectRoute(d.classRef, d.method);
|
|
9386
9408
|
const slots = readParamSlots(d.classRef, d.methodName, proto);
|
|
@@ -9392,28 +9414,33 @@ let ControllerDiscovery = class ControllerDiscovery {
|
|
|
9392
9414
|
const description = d.meta.description ?? operation.description ?? `${d.methodName} (${route.method} /${route.path})`;
|
|
9393
9415
|
const applyParamPipes = d.meta.pipes !== "skip";
|
|
9394
9416
|
const guards = collectGuards(this.reflector, d.classRef, d.method);
|
|
9395
|
-
const
|
|
9417
|
+
const pathFields = pathParamFields(bindings);
|
|
9418
|
+
const { moduleRef, reflector, appConfig } = this;
|
|
9396
9419
|
const classRef = d.classRef;
|
|
9397
9420
|
const method = d.method;
|
|
9398
9421
|
const instance = d.instance;
|
|
9399
|
-
const applyGuards = async (context) => {
|
|
9400
|
-
|
|
9422
|
+
const applyGuards = async (context, input) => {
|
|
9423
|
+
const all = [...collectGlobalGuards(appConfig, globalGuards), ...guards];
|
|
9424
|
+
if (all.length === 0) return;
|
|
9401
9425
|
const request = context.getOptional("request");
|
|
9402
9426
|
const response = context.getOptional("response") ?? null;
|
|
9403
9427
|
const hasRequest = request != null;
|
|
9404
|
-
|
|
9428
|
+
const guardRequest = hasRequest ? request : {
|
|
9405
9429
|
headers: {},
|
|
9406
9430
|
params: {},
|
|
9407
9431
|
query: {}
|
|
9408
|
-
}
|
|
9432
|
+
};
|
|
9433
|
+
populatePathParams(guardRequest, pathFields, input);
|
|
9434
|
+
await runGuards(all, moduleRef, reflector, classRef, method, guardRequest, response, hasRequest ? "http" : "rpc");
|
|
9409
9435
|
};
|
|
9410
9436
|
return createAction({
|
|
9411
9437
|
name,
|
|
9412
9438
|
description,
|
|
9413
9439
|
input: z.object(shape),
|
|
9440
|
+
...d.meta.result ? { disposition: d.meta.result } : {},
|
|
9414
9441
|
isEnabled: (ctx) => ctx.getOptional("adapter") === "mcp",
|
|
9415
9442
|
run: async (input, context) => {
|
|
9416
|
-
await applyGuards(context);
|
|
9443
|
+
await applyGuards(context, input);
|
|
9417
9444
|
return await invokeRebound(method, instance, input, bindings, context.getOptional("request"), applyParamPipes) ?? {};
|
|
9418
9445
|
}
|
|
9419
9446
|
});
|
|
@@ -9448,8 +9475,26 @@ ControllerDiscovery = __decorate([Injectable(), __decorateMetadata("design:param
|
|
|
9448
9475
|
typeof (_ref$1 = typeof DiscoveryService !== "undefined" && DiscoveryService) === "function" ? _ref$1 : Object,
|
|
9449
9476
|
typeof (_ref2$1 = typeof MetadataScanner !== "undefined" && MetadataScanner) === "function" ? _ref2$1 : Object,
|
|
9450
9477
|
typeof (_ref3 = typeof Reflector !== "undefined" && Reflector) === "function" ? _ref3 : Object,
|
|
9451
|
-
typeof (_ref4 = typeof ModuleRef !== "undefined" && ModuleRef) === "function" ? _ref4 : Object
|
|
9478
|
+
typeof (_ref4 = typeof ModuleRef !== "undefined" && ModuleRef) === "function" ? _ref4 : Object,
|
|
9479
|
+
typeof (_ref5 = typeof ApplicationConfig !== "undefined" && ApplicationConfig) === "function" ? _ref5 : Object
|
|
9452
9480
|
])], ControllerDiscovery);
|
|
9481
|
+
/** Input field names that originate from the URL path (`@Param`), per the re-bind plan. */
|
|
9482
|
+
function pathParamFields(bindings) {
|
|
9483
|
+
const fields = [];
|
|
9484
|
+
for (const b of bindings) if (b.kind === "params") fields.push(...b.fields);
|
|
9485
|
+
else if (b.kind === "value" && b.source === "path") fields.push(b.field);
|
|
9486
|
+
return fields;
|
|
9487
|
+
}
|
|
9488
|
+
/**
|
|
9489
|
+
* Fill `request.params` with the reflected path fields from the validated input,
|
|
9490
|
+
* matching what Express would populate over REST (raw string values). Only adds
|
|
9491
|
+
* keys that are absent, so a real REST request's params are never overwritten.
|
|
9492
|
+
*/
|
|
9493
|
+
function populatePathParams(request, pathFields, input) {
|
|
9494
|
+
if (pathFields.length === 0 || typeof request !== "object" || request === null) return;
|
|
9495
|
+
const params = request.params ??= {};
|
|
9496
|
+
for (const field of pathFields) if (!(field in params) && input[field] !== void 0) params[field] = String(input[field]);
|
|
9497
|
+
}
|
|
9453
9498
|
function designTypeAt(designTypes, index) {
|
|
9454
9499
|
const ctor = designTypes[index];
|
|
9455
9500
|
if (ctor === String) return { type: "string" };
|
|
@@ -9555,7 +9600,7 @@ let SilkweaveModule = _SilkweaveModule = class SilkweaveModule {
|
|
|
9555
9600
|
configure(_consumer) {
|
|
9556
9601
|
const httpAdapter = this.httpAdapterHost.httpAdapter;
|
|
9557
9602
|
if (!httpAdapter) throw new Error("@silkweave/nestjs: HttpAdapterHost.httpAdapter is not available.");
|
|
9558
|
-
const allActions = this.discovery.discover(this.options.openapi);
|
|
9603
|
+
const allActions = this.discovery.discover(this.options.openapi, this.options.globalGuards);
|
|
9559
9604
|
for (const adapter of this.options.adapters) {
|
|
9560
9605
|
const baseContext = createContext({
|
|
9561
9606
|
...this.options.context ?? {},
|
|
@@ -9581,6 +9626,6 @@ SilkweaveModule = _SilkweaveModule = __decorate([
|
|
|
9581
9626
|
])
|
|
9582
9627
|
], SilkweaveModule);
|
|
9583
9628
|
//#endregion
|
|
9584
|
-
export { ControllerDiscovery, MCP_METADATA, Mcp, SILKWEAVE_MODULE_OPTIONS, SilkweaveModule, apiPropertyToField, buildOpenApiLookup, classValidatorToField, collectGuards, designTypeToField, fieldToZod, invokeRebound, mcp, mergeField, normalizeEnum, openApiFields, openapiSchemaToField, reflectDtoFields, runGuards, specialBinding, swaggerParamToField, typeTokenToBase };
|
|
9629
|
+
export { ControllerDiscovery, MCP_METADATA, Mcp, SILKWEAVE_MODULE_OPTIONS, SilkweaveModule, apiPropertyToField, buildOpenApiLookup, classValidatorToField, collectGlobalGuards, collectGuards, designTypeToField, fieldToZod, invokeRebound, mcp, mergeField, normalizeEnum, openApiFields, openapiSchemaToField, reflectDtoFields, runGuards, specialBinding, swaggerParamToField, typeTokenToBase };
|
|
9585
9630
|
|
|
9586
9631
|
//# sourceMappingURL=index.mjs.map
|