@silkweave/nestjs 1.11.0 → 1.12.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.
@@ -1,17 +1,16 @@
1
- $ tsdown
2
- ℹ tsdown v0.21.10 powered by rolldown v1.0.0-rc.17
3
- ℹ config file: /Users/atomic/projects/silkweave/silkweave/packages/nestjs/tsdown.config.ts
4
- ℹ entry: src/index.ts
5
- ℹ tsconfig: tsconfig.json
6
- ℹ Build start
7
- ℹ Cleaning 4 files
8
- ℹ Hint: consider adding deps.onlyBundle option to avoid unintended bundling of dependencies, or set deps.onlyBundle: false to disable this hint.
9
- See more at https://tsdown.dev/options/dependencies#deps-onlybundle
10
- Detected dependencies in bundle:
11
- - rxjs
12
- ℹ build/index.mjs 338.49 kB │ gzip: 49.77 kB
13
- ℹ build/index.mjs.map 577.61 kB │ gzip: 87.77 kB
14
- ℹ build/index.d.mts.map  2.99 kB │ gzip: 1.07 kB
15
- ℹ build/index.d.mts  16.35 kB │ gzip: 5.75 kB
16
- ℹ 4 files, total: 935.45 kB
17
- ✔ Build complete in 742ms
1
+ $ tsdown
2
+ ℹ tsdown v0.21.10 powered by rolldown v1.0.0-rc.17
3
+ ℹ config file: /Users/atomic/projects/silkweave/silkweave/packages/nestjs/tsdown.config.ts
4
+ ℹ entry: src/index.ts
5
+ ℹ tsconfig: tsconfig.json
6
+ ℹ Build start
7
+ ℹ Hint: consider adding deps.onlyBundle option to avoid unintended bundling of dependencies, or set deps.onlyBundle: false to disable this hint.
8
+ See more at https://tsdown.dev/options/dependencies#deps-onlybundle
9
+ Detected dependencies in bundle:
10
+ - rxjs
11
+ ℹ build/index.mjs 339.14 kB │ gzip: 49.99 kB
12
+ ℹ build/index.mjs.map 579.22 kB │ gzip: 88.28 kB
13
+ ℹ build/index.d.mts.map  3.06 kB │ gzip: 1.10 kB
14
+ ℹ build/index.d.mts  17.08 kB │ gzip: 6.02 kB
15
+ ℹ 4 files, total: 938.50 kB
16
+ ✔ Build complete in 1034ms
@@ -1,5 +1,4 @@
1
-
2
- $ pnpm clean && pnpm build
1
+ $ pnpm clean && pnpm build
3
2
  $ rimraf build
4
3
  $ tsdown
5
4
  ℹ tsdown v0.21.10 powered by rolldown v1.0.0-rc.17
@@ -11,9 +10,9 @@
11
10
  See more at https://tsdown.dev/options/dependencies#deps-onlybundle
12
11
  Detected dependencies in bundle:
13
12
  - rxjs
14
- ℹ build/index.mjs 337.92 kB │ gzip: 49.52 kB
15
- ℹ build/index.mjs.map 576.49 kB │ gzip: 87.36 kB
16
- ℹ build/index.d.mts.map  2.98 kB │ gzip: 1.07 kB
17
- ℹ build/index.d.mts  15.96 kB │ gzip: 5.58 kB
18
- ℹ 4 files, total: 933.34 kB
19
- ✔ Build complete in 828ms
13
+ ℹ build/index.mjs 344.18 kB │ gzip: 51.78 kB
14
+ ℹ build/index.mjs.map 589.05 kB │ gzip: 91.22 kB
15
+ ℹ build/index.d.mts.map  3.45 kB │ gzip: 1.22 kB
16
+ ℹ build/index.d.mts  20.10 kB │ gzip: 6.89 kB
17
+ ℹ 4 files, total: 956.79 kB
18
+ ✔ Build complete in 822ms
package/README.md CHANGED
@@ -93,7 +93,7 @@ await app.listen(8080)
93
93
 
94
94
  The `users.list` and `users.get` actions are now reachable via:
95
95
 
96
- - **REST:** `GET /api/users/list?activeOnly=true` and `GET /api/users/get?id=1`
96
+ - **REST:** `GET /api/users/list?activeOnly=true` (query param) and `GET /api/users/1` (path param, via `path: 'users/:id'`)
97
97
  - **tRPC:** `client.usersList.query({ activeOnly: true })` and `client.usersGet.query({ id: '1' })`
98
98
  - **MCP:** tools `UsersList` and `UsersGet`
99
99
 
@@ -111,6 +111,9 @@ The `users.list` and `users.get` actions are now reachable via:
111
111
  | `output` | `z.ZodObject` | - | Optional output schema (used by tRPC type inference) |
112
112
  | `chunk` | `z.ZodType` | - | Schema for chunks yielded by a streaming (`async function*`) method. **Required** when the method is an async generator |
113
113
  | `kind` | `'query' \| 'mutation'` | `'mutation'` | `'query'` → GET in REST, `.query()` in tRPC. `'mutation'` → POST / `.mutation()` |
114
+ | `method` | `'GET' \| 'POST' \| 'PUT' \| 'DELETE'` | `POST` (or `GET` when `kind: 'query'`) | REST HTTP verb. Overrides the `kind`-derived default |
115
+ | `path` | `string` | action name with dots as slashes | REST route, optionally with `:param` placeholders (e.g. `'spaces/:spaceId/users'`). Each placeholder must be a key of `input` and is resolved from the URL path |
116
+ | `queryParams` | `(keyof input)[]` | - | Input fields read from the URL query string instead of the body (e.g. `['offset', 'limit']`). On a bodyless GET every non-path field is read from the query string automatically |
114
117
  | `transports` | `('rest' \| 'trpc' \| 'mcp')[]` | all | Allowlist of transports that expose this action |
115
118
  | `isEnabled` | `(ctx) => boolean` | - | Dynamic gate (AND-combined with `transports`) |
116
119
  | `toolResult` | `(response, ctx) => CallToolResult` | - | Custom MCP `CallToolResult` formatter |
@@ -140,12 +143,39 @@ Maps actions to REST routes on the Nest HTTP server.
140
143
  | `basePath` | `string` | `'/api'` | URL prefix joined to each action's path |
141
144
  | `auth` | `AuthConfig` | - | `@silkweave/auth` bearer-token config |
142
145
 
143
- Routes follow `{basePath}/{action-name-with-slashes}` where dots in action names become slashes:
146
+ By default routes follow `{basePath}/{action-name-with-slashes}` where dots in action names become slashes, and the verb comes from `kind`:
144
147
 
145
148
  - `users.list` (query) → `GET /api/users/list`
146
149
  - `users.ban` (mutation) → `POST /api/users/ban`
147
150
 
148
- Each action is registered as an individual route on Nest's HTTP adapter (not a sub-app). Input is parsed from `req.query` (queries) or `req.body` (mutations) and validated against the action's Zod schema; validation failures return HTTP 400 with the Zod issues.
151
+ The action's `method`, `path`, and `queryParams` fields override this (see the `@Action` options table above):
152
+
153
+ - `path: 'users/:id'`, `kind: 'query'` → `GET /api/users/:id` (`id` from the path)
154
+ - `queryParams: ['activeOnly']`, `kind: 'query'` → `GET /api/users/list?activeOnly=true`
155
+
156
+ Each action is registered as an individual route on Nest's HTTP adapter (not a sub-app). Input is merged from the request body, `queryParams` query-string fields, and `:param` path placeholders (path/query strings are coerced to the schema's primitive), then validated against the action's Zod schema; validation failures return HTTP 400 with the Zod issues.
157
+
158
+ #### Swagger / OpenAPI
159
+
160
+ `@nestjs/swagger` builds its document by scanning **controllers**, but Silkweave registers action routes directly on the HTTP adapter - so the scanner never sees them. `addSilkweaveActions(app, document, options?)` closes the gap: it discovers the actions through the same `ActionDiscovery` provider the `rest()` adapter uses, builds OpenAPI paths with the same routing logic, and merges them into the document. The result stays in sync with the live routes without any dynamic controllers. Call it between `createDocument()` and `setup()`:
161
+
162
+ ```ts
163
+ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
164
+ import { addSilkweaveActions } from '@silkweave/nestjs'
165
+
166
+ const config = new DocumentBuilder().setTitle('My API').setVersion('1.0.0').build()
167
+ const document = SwaggerModule.createDocument(app, config) // your controllers
168
+ addSilkweaveActions(app, document) // + silkweave actions
169
+ SwaggerModule.setup('api/docs', app, document)
170
+ ```
171
+
172
+ | Option | Type | Default | Description |
173
+ |--------|------|---------|-------------|
174
+ | `basePath` | `string` | the `rest()` adapter's `basePath`, else `'/api'` | URL prefix the action routes are mounted on |
175
+ | `tag` | `string` | `'Actions'` | OpenAPI tag the actions are grouped under |
176
+ | `includeDisabled` | `boolean` | `false` | Include actions gated off the REST transport (via `transports` / `isEnabled`) |
177
+
178
+ `@nestjs/swagger` is an **optional** peer dependency - install it alongside `@silkweave/nestjs` to use this helper. Path params, `queryParams`, request bodies, and `output`/`chunk` response schemas are all derived from the action's Zod schemas.
149
179
 
150
180
  ### `trpc(options?)`
151
181
 
package/build/index.d.mts CHANGED
@@ -1,11 +1,12 @@
1
- import { CanActivate, DynamicModule, MiddlewareConsumer, NestModule, Type } from "@nestjs/common";
1
+ import { CanActivate, DynamicModule, INestApplication, MiddlewareConsumer, NestModule, Type } from "@nestjs/common";
2
2
  import { AuthConfig } from "@silkweave/auth";
3
- import { Action as Action$1, ActionKind, SilkweaveContext, SilkweaveOptions } from "@silkweave/core";
3
+ import { Action as Action$1, ActionKind, HttpMethod, SilkweaveContext, SilkweaveOptions } from "@silkweave/core";
4
4
  import z$1 from "zod/v4";
5
5
  import { InferTrpcRouter } from "@silkweave/trpc";
6
6
  import { TypegenFormat } from "@silkweave/typegen";
7
7
  import { DiscoveryService, HttpAdapterHost, MetadataScanner, ModuleRef, Reflector } from "@nestjs/core";
8
8
  import { CorsOptions } from "cors";
9
+ import { OpenAPIObject } from "@nestjs/swagger";
9
10
 
10
11
  //#region src/lib/types.d.ts
11
12
  /**
@@ -35,6 +36,12 @@ interface NestAdapterRegisterContext {
35
36
  interface NestSilkweaveAdapter {
36
37
  /** Adapter discriminator - set on the silkweave context as `ctx.get('adapter')`. */
37
38
  readonly name: 'rest' | 'trpc' | 'mcp' | 'typegen';
39
+ /**
40
+ * URL prefix the adapter mounts on (e.g. `'/api'`). Surfaced for introspection
41
+ * tooling such as `addSilkweaveActions()` (OpenAPI/Swagger), which reads it to
42
+ * keep generated docs aligned with the live routes.
43
+ */
44
+ readonly basePath?: string;
38
45
  /**
39
46
  * When `true`, the adapter receives every discovered action regardless of
40
47
  * each action's `transports` allowlist / `isEnabled` gate. Used by
@@ -95,12 +102,17 @@ interface RestAdapterOptions {
95
102
  * REST adapter for `@silkweave/nestjs`. Registers each discovered `@Action`
96
103
  * as an individual route on Nest's HTTP adapter:
97
104
  *
98
- * - `kind: 'query'` `GET ${basePath}/${actionName-with-slashes}` (input from query string)
99
- * - `kind: 'mutation'` `POST ${basePath}/${actionName-with-slashes}` (input from JSON body)
105
+ * - HTTP verb comes from `method`, else `GET` for `kind: 'query'`, else `POST`.
106
+ * - Path comes from `path` (joined to `basePath`, may contain `:param`
107
+ * placeholders), else `${basePath}/${actionName-with-slashes}`.
108
+ * - Input is merged from the request body, the `queryParams` query-string
109
+ * fields, and any `:param` path placeholders (see `resolveActionInput`).
100
110
  *
101
- * Routes show up in Nest's `RoutesResolver` logger and are eligible for
102
- * `@nestjs/swagger` scanning. Works on `@nestjs/platform-express` out of the
103
- * box. For `@nestjs/platform-fastify`, register `@fastify/express` first.
111
+ * Routes show up in Nest's `RoutesResolver` logger. They are registered
112
+ * directly on the HTTP adapter (not as Nest controllers), so `@nestjs/swagger`'s
113
+ * controller scanner does not see them - use `addSilkweaveActions()` to add them
114
+ * to the OpenAPI document. Works on `@nestjs/platform-express` out of the box.
115
+ * For `@nestjs/platform-fastify`, register `@fastify/express` first.
104
116
  */
105
117
  declare function rest(options?: RestAdapterOptions): NestSilkweaveAdapter;
106
118
  //#endregion
@@ -179,6 +191,12 @@ interface ActionMetadata<I extends object = object, O extends object = object, C
179
191
  chunk?: z$1.ZodType<C>;
180
192
  /** `'query'` (GET in REST, `.query()` in tRPC) or `'mutation'` (POST in REST, `.mutation()` in tRPC). Default: `'mutation'`. */
181
193
  kind?: ActionKind;
194
+ /** HTTP verb for the REST route. Defaults to `POST` (or `GET` when `kind` is `'query'`). Overrides the `kind`-derived default. */
195
+ method?: HttpMethod;
196
+ /** REST route path, optionally with `:param` placeholders (e.g. `'spaces/:spaceId/users'`). Each placeholder must be a key of `input`. Defaults to the action name with dots as slashes. */
197
+ path?: string;
198
+ /** Input fields read from the URL query string instead of the request body (e.g. `['offset', 'limit']`). Each must be a key of `input`. */
199
+ queryParams?: (keyof I)[];
182
200
  /** Allowlist of transports that should expose this action. Default: all registered transports. */
183
201
  transports?: Transport[];
184
202
  /** Dynamic enable check (in addition to `transports`). AND-combined with the transports filter. */
@@ -309,6 +327,22 @@ declare function collectGuards(reflector: Reflector, classRef: Type<unknown>, ha
309
327
  */
310
328
  declare function runGuards(guards: GuardRef[], moduleRef: ModuleRef, reflector: Reflector, classRef: Type<unknown>, handler: (...args: unknown[]) => unknown, request: unknown, response: unknown, contextType?: 'http' | 'rpc'): Promise<void>;
311
329
  //#endregion
330
+ //#region src/lib/openapi.d.ts
331
+ interface ActionPathsOptions {
332
+ /** URL prefix the `rest()` adapter mounts on. Default `'/api'`. */
333
+ basePath?: string;
334
+ /** OpenAPI tag the actions are grouped under. Default `'Actions'`. */
335
+ tag?: string;
336
+ }
337
+ /**
338
+ * Build an OpenAPI `paths` fragment for a set of Silkweave actions, mirroring
339
+ * exactly how the `rest()` adapter routes them: the same HTTP verb (`method` ??
340
+ * `kind`), the same `path`/`name`-derived route, and the same path/query/body
341
+ * field split (`pathParamNames` + `queryParams`). Schemas are inlined from the
342
+ * Zod input/output via `z.toJSONSchema`; no shared components are emitted.
343
+ */
344
+ declare function buildActionPaths(actions: Action$1[], options?: ActionPathsOptions): Record<string, Record<string, unknown>>;
345
+ //#endregion
312
346
  //#region src/lib/silkweave.module.d.ts
313
347
  /**
314
348
  * Root module for `@silkweave/nestjs`.
@@ -347,5 +381,45 @@ declare class SilkweaveModule implements NestModule {
347
381
  configure(_consumer: MiddlewareConsumer): void;
348
382
  }
349
383
  //#endregion
350
- export { ACTIONS_METADATA, ACTION_METADATA, ACTION_RESPONSE_KEY, Action, ActionDiscovery, ActionMetadata, Actions, type ActionsClassMetadata, AnyActionMetadata, type InferTrpcRouter, McpAdapterOptions, NestAdapterRegisterContext, NestSilkweaveAdapter, RestAdapterOptions, ResultToolResult, SILKWEAVE_MODULE_OPTIONS, SilkweaveModule, SilkweaveModuleOptions, type Transport, TrpcAdapterOptions, TypegenAdapterOptions, buildIsEnabled, collectGuards, mcp, rest, runGuards, trpc, typegen };
384
+ //#region src/lib/swagger.d.ts
385
+ interface SilkweaveSwaggerOptions {
386
+ /**
387
+ * URL prefix the `rest()` adapter mounts on. Defaults to the configured
388
+ * `rest()` adapter's `basePath`, falling back to `'/api'`.
389
+ */
390
+ basePath?: string;
391
+ /** OpenAPI tag the actions are grouped under. Default `'Actions'`. */
392
+ tag?: string;
393
+ /**
394
+ * Include actions that are *not* enabled on the REST transport (gated out via
395
+ * `transports` / `isEnabled`). Default `false` - the document mirrors the
396
+ * routes the `rest()` adapter actually registers.
397
+ */
398
+ includeDisabled?: boolean;
399
+ }
400
+ /**
401
+ * Merge every REST-exposed Silkweave `@Action` into a NestJS Swagger
402
+ * `OpenAPIObject`.
403
+ *
404
+ * `@nestjs/swagger` builds its document by scanning **controllers**, but
405
+ * Silkweave registers action routes directly on the HTTP adapter (so they sit
406
+ * ahead of controllers in the request pipeline) - which means the scanner never
407
+ * sees them. This helper closes that gap: it discovers the actions through the
408
+ * same `ActionDiscovery` provider the `rest()` adapter uses, builds OpenAPI
409
+ * paths with the same routing logic (`buildActionPaths`), and merges them into
410
+ * the document. The result stays in sync with the live routes without any
411
+ * dynamic controllers.
412
+ *
413
+ * Call it between `SwaggerModule.createDocument()` and `SwaggerModule.setup()`:
414
+ *
415
+ * @example
416
+ * ```ts
417
+ * const document = SwaggerModule.createDocument(app, config)
418
+ * addSilkweaveActions(app, document)
419
+ * SwaggerModule.setup('api/docs', app, document)
420
+ * ```
421
+ */
422
+ declare function addSilkweaveActions(app: INestApplication, document: OpenAPIObject, options?: SilkweaveSwaggerOptions): OpenAPIObject;
423
+ //#endregion
424
+ export { ACTIONS_METADATA, ACTION_METADATA, ACTION_RESPONSE_KEY, Action, ActionDiscovery, ActionMetadata, ActionPathsOptions, Actions, type ActionsClassMetadata, AnyActionMetadata, type InferTrpcRouter, McpAdapterOptions, NestAdapterRegisterContext, NestSilkweaveAdapter, RestAdapterOptions, ResultToolResult, SILKWEAVE_MODULE_OPTIONS, SilkweaveModule, SilkweaveModuleOptions, SilkweaveSwaggerOptions, type Transport, TrpcAdapterOptions, TypegenAdapterOptions, addSilkweaveActions, buildActionPaths, buildIsEnabled, collectGuards, mcp, rest, runGuards, trpc, typegen };
351
425
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/lib/types.ts","../src/adapter/mcp.ts","../src/adapter/rest.ts","../src/adapter/trpc.ts","../src/adapter/typegen.ts","../src/lib/metadata.ts","../src/decorator/action.ts","../src/decorator/actions.ts","../src/lib/discovery.ts","../src/lib/filter.ts","../src/lib/guards.ts","../src/lib/silkweave.module.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/lib/types.ts","../src/adapter/mcp.ts","../src/adapter/rest.ts","../src/adapter/trpc.ts","../src/adapter/typegen.ts","../src/lib/metadata.ts","../src/decorator/action.ts","../src/decorator/actions.ts","../src/lib/discovery.ts","../src/lib/filter.ts","../src/lib/guards.ts","../src/lib/openapi.ts","../src/lib/silkweave.module.ts","../src/lib/swagger.ts"],"mappings":"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"}
package/build/index.mjs CHANGED
@@ -2,7 +2,7 @@ import { authMiddleware, mcpCors, mcpTransport, oauthRoutes, protectedResourceMe
2
2
  import express from "express";
3
3
  import { ForbiddenException, HttpException, Inject, Injectable, Module, SetMetadata } from "@nestjs/common";
4
4
  import { validateToken } from "@silkweave/auth";
5
- import { SilkweaveError, createAction, createContext } from "@silkweave/core";
5
+ import { SilkweaveError, actionMethod, createAction, createContext, methodHasBody, pathParamNames, resolveActionInput, validateActionRouting } from "@silkweave/core";
6
6
  import { buildLogLevels } from "@silkweave/logger";
7
7
  import { z } from "zod/v4";
8
8
  import { buildRouter, createActionLogger, resolveAuth } from "@silkweave/trpc";
@@ -11,7 +11,7 @@ import { renderTypegen } from "@silkweave/typegen";
11
11
  import { mkdir, writeFile } from "node:fs/promises";
12
12
  import { dirname, resolve } from "node:path";
13
13
  import { DiscoveryModule, DiscoveryService, HttpAdapterHost, MetadataScanner, ModuleRef, Reflector } from "@nestjs/core";
14
- import { kebabCase } from "change-case";
14
+ import { camelCase, kebabCase } from "change-case";
15
15
  import { GUARDS_METADATA } from "@nestjs/common/constants.js";
16
16
  //#region \0rolldown/runtime.js
17
17
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
@@ -167,7 +167,13 @@ function createActionHandler(action, context, auth, logger) {
167
167
  }
168
168
  authInfo = result.auth;
169
169
  }
170
- const raw = action.kind === "query" ? reqLike.query ?? parseQuery(req.url) : reqLike.body ?? await readJsonBody(req);
170
+ const query = reqLike.query ?? parseQuery(req.url);
171
+ const body = methodHasBody(actionMethod(action)) ? reqLike.body ?? await readJsonBody(req) : void 0;
172
+ const raw = resolveActionInput(action, {
173
+ params: reqLike.params,
174
+ query,
175
+ body
176
+ });
171
177
  const input = action.input.parse(raw);
172
178
  sendJson(res, await action.run(input, context.fork({
173
179
  logger,
@@ -184,22 +190,29 @@ function createActionHandler(action, context, auth, logger) {
184
190
  * REST adapter for `@silkweave/nestjs`. Registers each discovered `@Action`
185
191
  * as an individual route on Nest's HTTP adapter:
186
192
  *
187
- * - `kind: 'query'` `GET ${basePath}/${actionName-with-slashes}` (input from query string)
188
- * - `kind: 'mutation'` `POST ${basePath}/${actionName-with-slashes}` (input from JSON body)
193
+ * - HTTP verb comes from `method`, else `GET` for `kind: 'query'`, else `POST`.
194
+ * - Path comes from `path` (joined to `basePath`, may contain `:param`
195
+ * placeholders), else `${basePath}/${actionName-with-slashes}`.
196
+ * - Input is merged from the request body, the `queryParams` query-string
197
+ * fields, and any `:param` path placeholders (see `resolveActionInput`).
189
198
  *
190
- * Routes show up in Nest's `RoutesResolver` logger and are eligible for
191
- * `@nestjs/swagger` scanning. Works on `@nestjs/platform-express` out of the
192
- * box. For `@nestjs/platform-fastify`, register `@fastify/express` first.
199
+ * Routes show up in Nest's `RoutesResolver` logger. They are registered
200
+ * directly on the HTTP adapter (not as Nest controllers), so `@nestjs/swagger`'s
201
+ * controller scanner does not see them - use `addSilkweaveActions()` to add them
202
+ * to the OpenAPI document. Works on `@nestjs/platform-express` out of the box.
203
+ * For `@nestjs/platform-fastify`, register `@fastify/express` first.
193
204
  */
194
205
  function rest(options = {}) {
206
+ const basePath = (options.basePath ?? "/api").replace(/\/$/, "");
195
207
  return {
196
208
  name: "rest",
209
+ basePath,
197
210
  register({ httpAdapter, baseContext, actions }) {
198
- const basePath = (options.basePath ?? "/api").replace(/\/$/, "");
199
211
  const logger = createRestLogger();
200
212
  for (const action of actions) {
201
- const path = `${basePath}${actionNameToPath(action.name)}`;
202
- const method = action.kind === "query" ? "get" : "post";
213
+ validateActionRouting(action);
214
+ const path = action.path ? `${basePath}/${action.path.replace(/^\//, "")}` : `${basePath}${actionNameToPath(action.name)}`;
215
+ const method = actionMethod(action).toLowerCase();
203
216
  const handler = createActionHandler(action, baseContext, options.auth, logger);
204
217
  httpAdapter[method](path, handler);
205
218
  }
@@ -9144,6 +9157,9 @@ let ActionDiscovery = class ActionDiscovery {
9144
9157
  input: d.meta.input,
9145
9158
  chunk: d.meta.chunk,
9146
9159
  kind: d.meta.kind ?? "mutation",
9160
+ method: d.meta.method,
9161
+ path: d.meta.path,
9162
+ queryParams: d.meta.queryParams,
9147
9163
  args: d.meta.args,
9148
9164
  isEnabled,
9149
9165
  toolResult: d.meta.toolResult,
@@ -9159,6 +9175,9 @@ let ActionDiscovery = class ActionDiscovery {
9159
9175
  input: d.meta.input,
9160
9176
  output: d.meta.output,
9161
9177
  kind: d.meta.kind ?? "mutation",
9178
+ method: d.meta.method,
9179
+ path: d.meta.path,
9180
+ queryParams: d.meta.queryParams,
9162
9181
  args: d.meta.args,
9163
9182
  isEnabled,
9164
9183
  toolResult: d.meta.toolResult,
@@ -9176,6 +9195,103 @@ ActionDiscovery = __decorate([Injectable(), __decorateMetadata("design:paramtype
9176
9195
  typeof (_ref4 = typeof ModuleRef !== "undefined" && ModuleRef) === "function" ? _ref4 : Object
9177
9196
  ])], ActionDiscovery);
9178
9197
  //#endregion
9198
+ //#region src/lib/openapi.ts
9199
+ /** Convert a route template's `:param` placeholders to OpenAPI `{param}` form. */
9200
+ function toOpenApiPath(template) {
9201
+ return template.replace(/:([A-Za-z0-9_]+)/g, "{$1}");
9202
+ }
9203
+ /** The OpenAPI path key for an action - mirrors the `rest()` adapter's routing. */
9204
+ function actionRoute(action, basePath) {
9205
+ return toOpenApiPath(`${basePath}/${action.path ? action.path.replace(/^\//, "") : action.name.replace(/\./g, "/")}`.replace(/\/{2,}/g, "/"));
9206
+ }
9207
+ /** `z.toJSONSchema` tags a top-level `$schema`; drop it for a tidy OpenAPI doc. */
9208
+ function toJsonSchema(schema) {
9209
+ const { $schema: _schema, ...rest } = z.toJSONSchema(schema);
9210
+ return rest;
9211
+ }
9212
+ function responseSchema(action) {
9213
+ if (action.output) return toJsonSchema(action.output);
9214
+ if (action.chunk) return {
9215
+ type: "array",
9216
+ items: toJsonSchema(action.chunk)
9217
+ };
9218
+ }
9219
+ /** Split an action's input fields into OpenAPI path/query parameters and a body, matching the `rest()` adapter. */
9220
+ function splitInputSources(action, hasBody) {
9221
+ const json = z.toJSONSchema(action.input);
9222
+ const required = new Set(json.required ?? []);
9223
+ const pathParams = new Set(pathParamNames(action.path));
9224
+ const queryKeys = new Set((action.queryParams ?? []).map(String));
9225
+ const split = {
9226
+ parameters: [],
9227
+ bodyProps: {},
9228
+ bodyRequired: []
9229
+ };
9230
+ for (const [key, schema] of Object.entries(json.properties ?? {})) if (pathParams.has(key)) split.parameters.push({
9231
+ name: key,
9232
+ in: "path",
9233
+ required: true,
9234
+ schema
9235
+ });
9236
+ else if (!hasBody || queryKeys.has(key)) split.parameters.push({
9237
+ name: key,
9238
+ in: "query",
9239
+ required: required.has(key),
9240
+ schema
9241
+ });
9242
+ else {
9243
+ split.bodyProps[key] = schema;
9244
+ if (required.has(key)) split.bodyRequired.push(key);
9245
+ }
9246
+ return split;
9247
+ }
9248
+ /** Build the OpenAPI operation object for a single action. */
9249
+ function buildOperation(action, tag) {
9250
+ const hasBody = methodHasBody(actionMethod(action));
9251
+ const { parameters, bodyProps, bodyRequired } = splitInputSources(action, hasBody);
9252
+ const response = responseSchema(action);
9253
+ const operation = {
9254
+ tags: [tag],
9255
+ summary: action.description,
9256
+ operationId: camelCase(action.name),
9257
+ responses: { 200: {
9258
+ description: "Successful response",
9259
+ ...response ? { content: { "application/json": { schema: response } } } : {}
9260
+ } }
9261
+ };
9262
+ if (parameters.length) operation.parameters = parameters;
9263
+ if (hasBody && Object.keys(bodyProps).length) operation.requestBody = {
9264
+ required: bodyRequired.length > 0,
9265
+ content: { "application/json": { schema: {
9266
+ type: "object",
9267
+ properties: bodyProps,
9268
+ required: bodyRequired
9269
+ } } }
9270
+ };
9271
+ return operation;
9272
+ }
9273
+ /**
9274
+ * Build an OpenAPI `paths` fragment for a set of Silkweave actions, mirroring
9275
+ * exactly how the `rest()` adapter routes them: the same HTTP verb (`method` ??
9276
+ * `kind`), the same `path`/`name`-derived route, and the same path/query/body
9277
+ * field split (`pathParamNames` + `queryParams`). Schemas are inlined from the
9278
+ * Zod input/output via `z.toJSONSchema`; no shared components are emitted.
9279
+ */
9280
+ function buildActionPaths(actions, options = {}) {
9281
+ const basePath = (options.basePath ?? "/api").replace(/\/$/, "");
9282
+ const tag = options.tag ?? "Actions";
9283
+ const paths = {};
9284
+ for (const action of actions) {
9285
+ const method = actionMethod(action).toLowerCase();
9286
+ const route = actionRoute(action, basePath);
9287
+ paths[route] = {
9288
+ ...paths[route] ?? {},
9289
+ [method]: buildOperation(action, tag)
9290
+ };
9291
+ }
9292
+ return paths;
9293
+ }
9294
+ //#endregion
9179
9295
  //#region src/lib/types.ts
9180
9296
  const SILKWEAVE_MODULE_OPTIONS = "__silkweave_module_options__";
9181
9297
  //#endregion
@@ -9235,6 +9351,55 @@ SilkweaveModule = _SilkweaveModule = __decorate([
9235
9351
  ])
9236
9352
  ], SilkweaveModule);
9237
9353
  //#endregion
9238
- export { ACTIONS_METADATA, ACTION_METADATA, ACTION_RESPONSE_KEY, Action, ActionDiscovery, Actions, SILKWEAVE_MODULE_OPTIONS, SilkweaveModule, buildIsEnabled, collectGuards, mcp, rest, runGuards, trpc, typegen };
9354
+ //#region src/lib/swagger.ts
9355
+ /**
9356
+ * Merge every REST-exposed Silkweave `@Action` into a NestJS Swagger
9357
+ * `OpenAPIObject`.
9358
+ *
9359
+ * `@nestjs/swagger` builds its document by scanning **controllers**, but
9360
+ * Silkweave registers action routes directly on the HTTP adapter (so they sit
9361
+ * ahead of controllers in the request pipeline) - which means the scanner never
9362
+ * sees them. This helper closes that gap: it discovers the actions through the
9363
+ * same `ActionDiscovery` provider the `rest()` adapter uses, builds OpenAPI
9364
+ * paths with the same routing logic (`buildActionPaths`), and merges them into
9365
+ * the document. The result stays in sync with the live routes without any
9366
+ * dynamic controllers.
9367
+ *
9368
+ * Call it between `SwaggerModule.createDocument()` and `SwaggerModule.setup()`:
9369
+ *
9370
+ * @example
9371
+ * ```ts
9372
+ * const document = SwaggerModule.createDocument(app, config)
9373
+ * addSilkweaveActions(app, document)
9374
+ * SwaggerModule.setup('api/docs', app, document)
9375
+ * ```
9376
+ */
9377
+ function addSilkweaveActions(app, document, options = {}) {
9378
+ const discovery = app.get(ActionDiscovery);
9379
+ const moduleOptions = app.get(SILKWEAVE_MODULE_OPTIONS);
9380
+ const restAdapter = moduleOptions.adapters.find((adapter) => adapter.name === "rest");
9381
+ const basePath = options.basePath ?? restAdapter?.basePath ?? "/api";
9382
+ const allActions = discovery.discover();
9383
+ const paths = buildActionPaths(options.includeDisabled ? allActions : allActions.filter((action) => isRestEnabled(action, moduleOptions)), {
9384
+ basePath,
9385
+ tag: options.tag
9386
+ });
9387
+ document.paths ??= {};
9388
+ for (const [route, item] of Object.entries(paths)) document.paths[route] = {
9389
+ ...document.paths[route] ?? {},
9390
+ ...item
9391
+ };
9392
+ return document;
9393
+ }
9394
+ /** Whether an action would be registered by the `rest()` adapter (adapter: 'rest'). */
9395
+ function isRestEnabled(action, moduleOptions) {
9396
+ if (!action.isEnabled) return true;
9397
+ return action.isEnabled(createContext({
9398
+ ...moduleOptions.context ?? {},
9399
+ adapter: "rest"
9400
+ }));
9401
+ }
9402
+ //#endregion
9403
+ export { ACTIONS_METADATA, ACTION_METADATA, ACTION_RESPONSE_KEY, Action, ActionDiscovery, Actions, SILKWEAVE_MODULE_OPTIONS, SilkweaveModule, addSilkweaveActions, buildActionPaths, buildIsEnabled, collectGuards, mcp, rest, runGuards, trpc, typegen };
9239
9404
 
9240
9405
  //# sourceMappingURL=index.mjs.map