@silkweave/nestjs 1.9.0 → 1.10.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/src/lib/guards.ts CHANGED
@@ -24,7 +24,7 @@ export function collectGuards(
24
24
  async function resolveGuard(ref: GuardRef, moduleRef: ModuleRef): Promise<CanActivate> {
25
25
  if (typeof ref === 'function') {
26
26
  try {
27
- return moduleRef.get(ref, { strict: false })
27
+ return await moduleRef.get(ref, { strict: false })
28
28
  } catch {
29
29
  return moduleRef.create(ref)
30
30
  }
@@ -59,6 +59,6 @@ export async function runGuards(
59
59
  throw new ForbiddenException('Forbidden resource')
60
60
  }
61
61
  }
62
- // Reflector kept in the signature for future use (e.g., per-guard metadata) unused here.
62
+ // Reflector kept in the signature for future use (e.g., per-guard metadata) - unused here.
63
63
  void reflector
64
64
  }
@@ -6,7 +6,7 @@ export const ACTIONS_METADATA = '__silkweave_actions__'
6
6
 
7
7
  export type Transport = 'rest' | 'trpc' | 'mcp'
8
8
 
9
- export interface ActionMetadata<I extends object = object, O extends object = object> {
9
+ export interface ActionMetadata<I extends object = object, O extends object = object, C = unknown> {
10
10
  /** Action name. Defaults to the kebab-cased method name. */
11
11
  name?: string
12
12
  /** Human-readable description. Becomes the MCP tool description and REST OpenAPI summary. */
@@ -15,6 +15,13 @@ export interface ActionMetadata<I extends object = object, O extends object = ob
15
15
  input: z.ZodType<I> & { shape: Record<string, z.ZodTypeAny> }
16
16
  /** Optional Zod object schema for the action's output (used by tRPC type inference). */
17
17
  output?: z.ZodType<O> & { shape: Record<string, z.ZodTypeAny> }
18
+ /**
19
+ * Zod schema for individual chunks yielded by a streaming (async-generator)
20
+ * action method. Required when the decorated method is an `async function*`.
21
+ * Mirrors `Action.chunk` in @silkweave/core; typegen/trpc use it to expose
22
+ * the action as a tRPC subscription.
23
+ */
24
+ chunk?: z.ZodType<C>
18
25
  /** `'query'` (GET in REST, `.query()` in tRPC) or `'mutation'` (POST in REST, `.mutation()` in tRPC). Default: `'mutation'`. */
19
26
  kind?: ActionKind
20
27
  /** Allowlist of transports that should expose this action. Default: all registered transports. */
@@ -1,23 +1,22 @@
1
- import { Module, type DynamicModule } from '@nestjs/common'
2
- import { DiscoveryModule } from '@nestjs/core'
1
+ import { Inject, Module, type DynamicModule, type MiddlewareConsumer, type NestModule } from '@nestjs/common'
2
+ import { DiscoveryModule, HttpAdapterHost } from '@nestjs/core'
3
+ import { createContext } from '@silkweave/core'
3
4
  import { ActionDiscovery } from './discovery.js'
4
- import { SilkweaveService } from './silkweave.service.js'
5
5
  import { SILKWEAVE_MODULE_OPTIONS, type SilkweaveModuleOptions } from './types.js'
6
6
 
7
7
  /**
8
8
  * Root module for `@silkweave/nestjs`.
9
9
  *
10
- * Discovers every `@Action`-decorated method across the Nest app via
11
- * `DiscoveryService`, builds core `Action` objects from them, and mounts the
12
- * configured adapters (`rest()`, `trpc()`, `mcp()`) onto Nest's running HTTP
13
- * server during `OnApplicationBootstrap`.
10
+ * Discovers every `@Action`-decorated method via `DiscoveryService` and
11
+ * registers the configured adapters (`rest()`, `trpc()`, `mcp()`) directly on
12
+ * Nest's HTTP adapter inside `configure()`. Because `configure()` runs during
13
+ * `registerModules` - before Nest's `registerRouter()` step - Silkweave's
14
+ * routes always sit ahead of every controller in the Express stack. There is
15
+ * no slot middleware, no race with Nest's 404 catch-all, and every route
16
+ * shows up in Nest's `RoutesResolver` logger.
14
17
  *
15
18
  * @example
16
19
  * ```ts
17
- * import { Module } from '@nestjs/common'
18
- * import { SilkweaveModule, rest, trpc, mcp } from '@silkweave/nestjs'
19
- * import { UsersModule } from './users.module.js'
20
- *
21
20
  * @Module({
22
21
  * imports: [
23
22
  * SilkweaveModule.forRoot({
@@ -27,15 +26,20 @@ import { SILKWEAVE_MODULE_OPTIONS, type SilkweaveModuleOptions } from './types.j
27
26
  * trpc({ basePath: '/trpc' }),
28
27
  * mcp({ basePath: '/mcp' })
29
28
  * ]
30
- * }),
31
- * UsersModule
29
+ * })
32
30
  * ]
33
31
  * })
34
32
  * export class AppModule {}
35
33
  * ```
36
34
  */
37
35
  @Module({})
38
- export class SilkweaveModule {
36
+ export class SilkweaveModule implements NestModule {
37
+ constructor(
38
+ @Inject(SILKWEAVE_MODULE_OPTIONS) private readonly options: SilkweaveModuleOptions,
39
+ private readonly discovery: ActionDiscovery,
40
+ private readonly httpAdapterHost: HttpAdapterHost
41
+ ) { }
42
+
39
43
  static forRoot(options: SilkweaveModuleOptions): DynamicModule {
40
44
  return {
41
45
  module: SilkweaveModule,
@@ -43,10 +47,29 @@ export class SilkweaveModule {
43
47
  imports: [DiscoveryModule],
44
48
  providers: [
45
49
  { provide: SILKWEAVE_MODULE_OPTIONS, useValue: options },
46
- ActionDiscovery,
47
- SilkweaveService
50
+ ActionDiscovery
48
51
  ],
49
- exports: [SilkweaveService]
52
+ exports: []
53
+ }
54
+ }
55
+
56
+ configure(_consumer: MiddlewareConsumer): void {
57
+ const httpAdapter = this.httpAdapterHost.httpAdapter
58
+ if (!httpAdapter) {
59
+ throw new Error('@silkweave/nestjs: HttpAdapterHost.httpAdapter is not available.')
60
+ }
61
+ const allActions = this.discovery.discover()
62
+ for (const adapter of this.options.adapters) {
63
+ const baseContext = createContext({ ...(this.options.context ?? {}), adapter: adapter.name })
64
+ const actions = adapter.allActions
65
+ ? allActions
66
+ : allActions.filter((a) => !a.isEnabled || a.isEnabled(baseContext))
67
+ adapter.register({
68
+ httpAdapter,
69
+ silkweaveOptions: this.options.silkweave,
70
+ baseContext,
71
+ actions
72
+ })
50
73
  }
51
74
  }
52
75
  }
package/src/lib/types.ts CHANGED
@@ -1,36 +1,51 @@
1
1
  import type { HttpAdapterHost } from '@nestjs/core'
2
- import type { AdapterGenerator, SilkweaveOptions } from '@silkweave/core'
2
+ import type { Action, SilkweaveContext, SilkweaveOptions } from '@silkweave/core'
3
3
 
4
4
  /**
5
- * A Silkweave NestJS adapter. Each transport (REST, tRPC, MCP) implements this
6
- * shape: given a Nest `HttpAdapterHost`, it (a) immediately mounts a
7
- * placeholder middleware slot on Nest's running HTTP server during
8
- * `OnModuleInit` which is critical because Nest installs its 404 catch-all
9
- * later in `init()`, before `OnApplicationBootstrap` fires; routes registered
10
- * after the catch-all are unreachable — and (b) returns a core
11
- * `AdapterGenerator` whose `start(actions)` populates that slot with the real
12
- * handler.
13
- *
14
- * Adapters mount onto Nest's HTTP server instead of owning their own, so Nest
15
- * middleware, lifecycle hooks, and request scoping remain coherent.
5
+ * Context passed to a Nest Silkweave adapter when `SilkweaveModule` wires it
6
+ * up. Adapters register their routes directly on `httpAdapter` (no
7
+ * placeholder middleware, no `silkweave()` builder), so they only fire
8
+ * `register()` once and own the rest of their lifecycle implicitly through
9
+ * Nest.
10
+ */
11
+ export interface NestAdapterRegisterContext {
12
+ /** Nest's underlying HTTP adapter (Express or Fastify). */
13
+ httpAdapter: NonNullable<HttpAdapterHost['httpAdapter']>
14
+ /** Identity the adapter surfaces to clients (e.g. MCP server name). */
15
+ silkweaveOptions: SilkweaveOptions
16
+ /** Per-adapter context - already forked with `{ adapter: adapter.name, ...userContext }`. */
17
+ baseContext: SilkweaveContext
18
+ /** Actions filtered to those enabled on this adapter. */
19
+ actions: Action[]
20
+ }
21
+
22
+ /**
23
+ * A Silkweave Nest adapter. Each transport (REST, tRPC, MCP) implements this
24
+ * shape. `register()` is called from `SilkweaveModule.configure()` - which
25
+ * runs *before* Nest's controller routes are mapped - so adapter routes
26
+ * always sit ahead of any catch-all controllers in the framework's request
27
+ * pipeline.
16
28
  */
17
29
  export interface NestSilkweaveAdapter {
18
- /** Adapter discriminator set on the silkweave context as `ctx.get('adapter')`. */
19
- readonly name: 'rest' | 'trpc' | 'mcp'
30
+ /** Adapter discriminator - set on the silkweave context as `ctx.get('adapter')`. */
31
+ readonly name: 'rest' | 'trpc' | 'mcp' | 'typegen'
20
32
  /**
21
- * Reserve the adapter's route prefix on the Nest HTTP server *now* (before
22
- * Nest's 404 catch-all is installed) and return the core `AdapterGenerator`
23
- * that will populate the slot during `silkweave().start()`.
33
+ * When `true`, the adapter receives every discovered action regardless of
34
+ * each action's `transports` allowlist / `isEnabled` gate. Used by
35
+ * non-runtime adapters like `typegen()` that emit types for the entire
36
+ * action surface.
24
37
  */
25
- install(host: HttpAdapterHost): AdapterGenerator
38
+ readonly allActions?: boolean
39
+ /** Register this adapter's routes on Nest's HTTP server. */
40
+ register(ctx: NestAdapterRegisterContext): void
26
41
  }
27
42
 
28
43
  export interface SilkweaveModuleOptions {
29
- /** Identity for the silkweave instance surfaced to MCP clients, OpenAPI, etc. */
44
+ /** Identity for the silkweave instance - surfaced to MCP clients, OpenAPI, etc. */
30
45
  silkweave: SilkweaveOptions
31
46
  /** Adapters to mount. Examples: `rest()`, `trpc()`, `mcp()`. */
32
47
  adapters: NestSilkweaveAdapter[]
33
- /** Initial context keys (equivalent to chaining `.set(key, value)` on the builder). */
48
+ /** Initial context keys merged into every adapter's `baseContext`. */
34
49
  context?: Record<string, unknown>
35
50
  }
36
51