@open-rlb/nestjs-amqp 2.0.4 → 2.0.6

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.
Files changed (77) hide show
  1. package/README.md +4 -2
  2. package/amqp-lib/config/rabbitmq.config.d.ts +6 -0
  3. package/modules/acl/const.d.ts +0 -1
  4. package/modules/acl/const.js +0 -1
  5. package/modules/acl/const.js.map +1 -1
  6. package/modules/acl/models.d.ts +5 -7
  7. package/modules/acl/repository/acl-action.repository.d.ts +1 -5
  8. package/modules/acl/repository/acl-action.repository.js.map +1 -1
  9. package/modules/acl/repository/acl-role.repository.d.ts +1 -5
  10. package/modules/acl/repository/acl-role.repository.js.map +1 -1
  11. package/modules/acl/services/acl-management.service.d.ts +2 -2
  12. package/modules/acl/services/acl-management.service.js +17 -20
  13. package/modules/acl/services/acl-management.service.js.map +1 -1
  14. package/modules/acl/services/acl.service.d.ts +1 -2
  15. package/modules/acl/services/acl.service.js +5 -21
  16. package/modules/acl/services/acl.service.js.map +1 -1
  17. package/modules/broker/broker.module.d.ts +2 -4
  18. package/modules/broker/broker.module.js +23 -5
  19. package/modules/broker/broker.module.js.map +1 -1
  20. package/modules/broker/config/decorator-paths.d.ts +1 -0
  21. package/modules/broker/config/decorator-paths.js +34 -4
  22. package/modules/broker/config/decorator-paths.js.map +1 -1
  23. package/modules/broker/config/route-discovery.config.d.ts +2 -0
  24. package/modules/broker/const.d.ts +1 -0
  25. package/modules/broker/const.js +2 -1
  26. package/modules/broker/const.js.map +1 -1
  27. package/modules/broker/decorators/broker-action.decorator.d.ts +2 -1
  28. package/modules/broker/decorators/broker-action.decorator.js +2 -2
  29. package/modules/broker/decorators/broker-action.decorator.js.map +1 -1
  30. package/modules/broker/services/broker.service.js +1 -1
  31. package/modules/broker/services/broker.service.js.map +1 -1
  32. package/modules/broker/services/metadata-scanner.service.js +11 -2
  33. package/modules/broker/services/metadata-scanner.service.js.map +1 -1
  34. package/modules/broker/services/route-discovery-publisher.service.js +7 -5
  35. package/modules/broker/services/route-discovery-publisher.service.js.map +1 -1
  36. package/modules/gateway-admin/config/gateway-admin.config.d.ts +4 -0
  37. package/modules/gateway-admin/const.d.ts +1 -1
  38. package/modules/gateway-admin/const.js +1 -1
  39. package/modules/gateway-admin/const.js.map +1 -1
  40. package/modules/gateway-admin/gateway-admin.module.d.ts +7 -1
  41. package/modules/gateway-admin/gateway-admin.module.js +13 -0
  42. package/modules/gateway-admin/gateway-admin.module.js.map +1 -1
  43. package/modules/gateway-admin/repository/auth-provider.repository.d.ts +3 -4
  44. package/modules/gateway-admin/repository/auth-provider.repository.js.map +1 -1
  45. package/modules/gateway-admin/services/gateway-auth.service.d.ts +3 -4
  46. package/modules/gateway-admin/services/gateway-auth.service.js +15 -23
  47. package/modules/gateway-admin/services/gateway-auth.service.js.map +1 -1
  48. package/modules/gateway-admin/services/gateway-metrics.service.d.ts +3 -0
  49. package/modules/gateway-admin/services/gateway-metrics.service.js +9 -0
  50. package/modules/gateway-admin/services/gateway-metrics.service.js.map +1 -1
  51. package/modules/gateway-admin/services/route-sync.service.d.ts +3 -1
  52. package/modules/gateway-admin/services/route-sync.service.js +14 -8
  53. package/modules/gateway-admin/services/route-sync.service.js.map +1 -1
  54. package/modules/proxy/services/http-handler.service.d.ts +3 -0
  55. package/modules/proxy/services/http-handler.service.js +27 -3
  56. package/modules/proxy/services/http-handler.service.js.map +1 -1
  57. package/package.json +5 -1
  58. package/schematics/nest-add/files/acl/src/cache/in-memory-acl-store.ts +54 -0
  59. package/schematics/nest-add/files/acl/src/modules/database/repository/acl.repository.ts +74 -0
  60. package/schematics/nest-add/files/db-core/src/modules/database/repository/in-memory-collection.ts +122 -0
  61. package/schematics/nest-add/files/gateway-admin/src/modules/database/repository/gateway.repository.ts +151 -0
  62. package/schematics/nest-add/files/gateway-admin/src/modules/database/repository/route-sync.repository.ts +15 -0
  63. package/schematics/nest-add/files/skills/rlb-amqp/SKILL.md +33 -5
  64. package/schematics/nest-add/files/skills/rlb-amqp/references/config-schema.md +182 -93
  65. package/schematics/nest-add/files/skills/rlb-amqp/references/gotchas.md +129 -79
  66. package/schematics/nest-add/files/skills/rlb-amqp-acl/SKILL.md +185 -0
  67. package/schematics/nest-add/files/skills/rlb-amqp-add-action/SKILL.md +87 -2
  68. package/schematics/nest-add/files/skills/rlb-amqp-add-route/SKILL.md +82 -19
  69. package/schematics/nest-add/files/skills/rlb-amqp-add-ws-event/SKILL.md +40 -18
  70. package/schematics/nest-add/files/skills/rlb-amqp-gateway-admin/SKILL.md +244 -0
  71. package/schematics/nest-add/files/skills/rlb-amqp-scaffold/SKILL.md +177 -42
  72. package/schematics/nest-add/index.js +612 -142
  73. package/schematics/nest-add/index.js.map +1 -1
  74. package/schematics/nest-add/index.ts +673 -241
  75. package/schematics/nest-add/init.schema.d.ts +10 -1
  76. package/schematics/nest-add/init.schema.ts +29 -3
  77. package/schematics/nest-add/schema.json +37 -8
@@ -9,10 +9,83 @@ Read first:
9
9
  - `.claude/skills/rlb-amqp/references/config-schema.md`
10
10
  - `.claude/skills/rlb-amqp/references/gotchas.md`
11
11
 
12
- Decide the role: a **microservice** (only `@BrokerAction` handlers), a **gateway**
13
- (HTTP/WS exposure), or **both** in one app. Generate only the pieces needed.
12
+ Decide the role: a **microservice** (only `@BrokerAction` / `@BrokerHTTP` handlers, no
13
+ HTTP server) or a **gateway** (HTTP/WS exposure in front of microservices). Two paths:
14
+ the `nest add` schematic (fast, patches in place) or manual wiring. Canonical runnable
15
+ examples live under `sample/config-sample/` (`gateway-in-memory`, `gateway-db`,
16
+ `calculator.ms`) — mirror those, not the retired `apps/gateway-2`.
14
17
 
15
- ## 1. `src/config/config.loader.ts`
18
+ ---
19
+
20
+ ## Path A — `nest add` schematic (preferred)
21
+
22
+ From a NestJS project root:
23
+
24
+ ```bash
25
+ nest add @open-rlb/nestjs-amqp
26
+ ```
27
+
28
+ It **patches in place** (does not scaffold a new app): edits `src/app.module.ts` and
29
+ `src/main.ts`, creates `src/config/config.loader.ts` + `config/config.yaml`, copies
30
+ RUNNABLE in-memory repositories for the selected features, adds deps, and copies the
31
+ Claude skills.
32
+
33
+ ### Interactive flow
34
+
35
+ 1. **"Create a gateway (HTTP/WebSocket) configuration? y/N"**
36
+ 2. **YES** → checkbox of gateway features:
37
+ - `acl` — `AclModule` + ACL management/grant/check paths
38
+ - `gateway-admin` — `GatewayAdminModule` + DB-managed routes/auth-providers/metrics paths
39
+ - `route-reception` — gateway consumes routes auto-published by microservices
40
+ Then prompts for names (defaults shown): exchange `rlb`, ACL queue `rlb-acl`,
41
+ admin queue `rlb-gateway-admin`, control topic `rlb-gateway-control`,
42
+ route exchange `rlb-route-discovery`, route queue `rlb-route-sync`.
43
+ 3. **NO** (plain microservice) → checkbox:
44
+ - `auto-config-publish` — publish this service's `@BrokerHTTP` routes to the gateway
45
+ on boot (adds `broker.routeDiscovery`). Prompts service name + route exchange/queue.
46
+ 4. "Copy the Claude skills into .claude/skills? Y/n"
47
+
48
+ ### Non-interactive flags (CI / scripted)
49
+
50
+ Passing `--gatewayConfig` or any `--features` skips the prompts; everything else falls
51
+ back to `rlb-*` defaults.
52
+
53
+ ```bash
54
+ # Gateway with ACL + admin + route reception
55
+ nest add @open-rlb/nestjs-amqp \
56
+ --gatewayConfig --features acl --features gateway-admin --features route-reception
57
+
58
+ # Plain microservice that auto-publishes its @BrokerHTTP routes on boot
59
+ nest add @open-rlb/nestjs-amqp \
60
+ --gatewayConfig=false --features auto-config-publish \
61
+ --serviceName my-service --routeExchange rlb-route-discovery --routeQueue rlb-route-sync
62
+ ```
63
+
64
+ | Flag | Purpose |
65
+ | --- | --- |
66
+ | `--gatewayConfig` | `true` = gateway, `false` = microservice (default false non-interactive). |
67
+ | `--features <f>` | Repeatable. Gateway: `acl`, `gateway-admin`, `route-reception`. MS: `auto-config-publish`. |
68
+ | `--exchange` | Main AMQP exchange backing acl/admin queues. Default `rlb`. |
69
+ | `--aclQueue` / `--adminQueue` | Queues backing the fixed `rlb-acl` / `rlb-gateway-admin` topics. |
70
+ | `--controlTopic` | Broadcast control/reload topic. Default `rlb-gateway-control`. |
71
+ | `--routeExchange` / `--routeQueue` | Route-discovery exchange/queue. Defaults `rlb-route-discovery` / `rlb-route-sync` — **must match both publisher and gateway**. |
72
+ | `--serviceName` | Route-publish ownership key + AMQP `connection_name`. Default = project name. |
73
+ | `--skills` | Copy Claude skills. Default `true`; `--skills=false` to skip. |
74
+
75
+ > `app.module.ts` patch is idempotent (keys off `BrokerModule.forRootAsync`). If it can't
76
+ > locate `app.module.ts` / `main.ts` it warns and leaves you the manual wiring below.
77
+
78
+ After scaffolding, edit `config/config.yaml` (fill `<AMQP_URI>` / credentials) and replace
79
+ the `<APP_NAME>` `connection_name` placeholder. Then use `rlb-amqp-add-action` /
80
+ `rlb-amqp-add-route` / `rlb-amqp-add-ws-event` to grow the service.
81
+
82
+ ---
83
+
84
+ ## Path B — manual wiring
85
+
86
+ Mirrors `sample/config-sample/gateway-in-memory` (gateway) and `calculator.ms` (pure MS).
87
+
88
+ ### 1. `src/config/config.loader.ts`
16
89
 
17
90
  ```ts
18
91
  import { readFileSync } from 'fs';
@@ -20,23 +93,25 @@ import * as yaml from 'js-yaml';
20
93
  import { join } from 'path';
21
94
 
22
95
  const YAML_CONFIG_FILENAME = 'config/config.yaml';
96
+
23
97
  export default () =>
24
98
  yaml.load(readFileSync(join(process.cwd(), YAML_CONFIG_FILENAME), 'utf8')) as Record<string, any>;
25
99
  ```
26
100
 
27
- (`js-yaml` is a dependency of the config loader; ensure it's installed.)
101
+ (`js-yaml` + `@nestjs/config` are deps; the gateway also needs `@nestjs/axios`,
102
+ `@nestjs/platform-ws`, `@nestjs/websockets`, `ws`.)
28
103
 
29
- ## 2. `src/app.module.ts`
104
+ ### 2. `src/app.module.ts`
30
105
 
31
106
  ```ts
32
- import { HttpModule } from '@nestjs/axios';
107
+ import { HttpModule } from '@nestjs/axios'; // gateway only
33
108
  import { Module } from '@nestjs/common';
34
109
  import { ConfigModule, ConfigService } from '@nestjs/config';
35
- import { AppConfig, BrokerModule, BrokerTopic, GatewayConfig, ProxyModule } from '@open-rlb/nestjs-amqp';
36
- import { RabbitMQConfig } from '@open-rlb/nestjs-amqp/amqp-lib/config/rabbitmq.config';
37
- import { HandlerAuthConfig } from '@open-rlb/nestjs-amqp/modules/broker/config/handler-auth.config';
110
+ import {
111
+ AppConfig, BrokerModule, BrokerTopic, RabbitMQConfig,
112
+ GatewayConfig, HandlerAuthConfig, ProxyModule, // gateway only
113
+ } from '@open-rlb/nestjs-amqp';
38
114
  import yamlConfig from './config/config.loader';
39
- // import { MyActionService } from './my-action.service';
40
115
 
41
116
  @Module({
42
117
  imports: [
@@ -45,13 +120,15 @@ import yamlConfig from './config/config.loader';
45
120
  imports: [ConfigModule],
46
121
  inject: [ConfigService],
47
122
  useFactory: async (config: ConfigService) => ({
48
- options: config.get<RabbitMQConfig>('broker'),
49
- topics: config.get<BrokerTopic[]>('topics'),
123
+ // broker.routeDiscovery (publisher side), when used, lives INSIDE this `broker` block.
124
+ options: config.get<RabbitMQConfig>('broker')!,
125
+ topics: config.get<BrokerTopic[]>('topics')!,
50
126
  appOptions: config.get<AppConfig>('app'),
51
127
  }),
52
128
  }),
129
+
130
+ // --- gateway only: omit ProxyModule + HttpModule for a pure microservice ---
53
131
  HttpModule,
54
- // Gateway: auth-providers + gateway config live in ProxyModule (NOT BrokerModule).
55
132
  ProxyModule.forRootAsync({
56
133
  imports: [ConfigModule],
57
134
  inject: [ConfigService],
@@ -60,33 +137,59 @@ import yamlConfig from './config/config.loader';
60
137
  gatewayOptions: config.get<GatewayConfig>('gateway'),
61
138
  }),
62
139
  providers: [
63
- // { provide: RLB_GTW_ACL_ROLE_SERVICE, useClass: MyAclService }, // only if using `roles`
140
+ // Role-gated paths resolve the caller's roles in-process via this token (NO broker hop):
141
+ // { provide: RLB_GTW_ACL_ROLE_SERVICE, useExisting: AclService }, // required if any path uses `roles`
142
+ // Optional in-proxy per-request metrics hook (independent of gateway.metrics):
143
+ // { provide: RLB_GTW_METRICS_HOOK, useClass: InfluxMetricsHook },
64
144
  ],
65
145
  }),
66
146
  ],
67
- providers: [/* MyActionService */],
147
+ providers: [/* AppService — your @BrokerAction / @BrokerHTTP handlers */],
68
148
  })
69
149
  export class AppModule {}
70
150
  ```
71
151
 
72
- > Omit `ProxyModule`/`HttpModule` for a pure microservice with no HTTP/WS gateway.
152
+ > The gateway's `auth-providers` + `gateway` config belong to **`ProxyModule`**, not
153
+ > `BrokerModule`. For ACL add `AclModule.forRoot([...repos], { cache })` and bind
154
+ > `RLB_GTW_ACL_ROLE_SERVICE`; for DB routes/auth/metrics add
155
+ > `GatewayAdminModule.forRoot([...repos])` — see the `gateway-in-memory` sample and the
156
+ > ACL / Gateway Admin docs.
157
+
158
+ ### 3. `src/main.ts`
73
159
 
74
- ## 3. `src/main.ts`
160
+ **Gateway** (needs `rawBody` + `WsAdapter`):
75
161
 
76
162
  ```ts
163
+ import { ConfigService } from '@nestjs/config';
77
164
  import { NestFactory } from '@nestjs/core';
78
165
  import { WsAdapter } from '@nestjs/platform-ws';
79
166
  import { AppModule } from './app.module';
80
167
 
81
168
  async function bootstrap() {
82
- const app = await NestFactory.create(AppModule, { rawBody: true }); // rawBody needed if any path uses parseRaw
83
- app.useWebSocketAdapter(new WsAdapter(app)); // only if using the WS gateway
84
- await app.listen(3000, '0.0.0.0');
169
+ const app = await NestFactory.create(AppModule, { rawBody: true }); // rawBody: raw-body/webhook routes
170
+ app.useWebSocketAdapter(new WsAdapter(app)); // WS events
171
+ app.enableShutdownHooks();
172
+ const cfg = app.get(ConfigService).get<{ port?: number; host?: string }>('app');
173
+ await app.listen(Number(process.env.PORT) || cfg?.port || 3000, cfg?.host || '0.0.0.0');
85
174
  }
86
175
  bootstrap();
87
176
  ```
88
177
 
89
- ## 4. `config/config.yaml` (starter)
178
+ **Pure microservice** (no HTTP server — `init()`, not `listen()`):
179
+
180
+ ```ts
181
+ import { NestFactory } from '@nestjs/core';
182
+ import { AppModule } from './app.module';
183
+
184
+ async function bootstrap() {
185
+ const app = await NestFactory.create(AppModule);
186
+ app.enableShutdownHooks(); // drain in-flight RPC + close AMQP cleanly on SIGINT/SIGTERM
187
+ await app.init(); // @BrokerAction handlers start consuming once initialized
188
+ }
189
+ bootstrap();
190
+ ```
191
+
192
+ ### 4. `config/config.yaml` (starter)
90
193
 
91
194
  ```yaml
92
195
  app:
@@ -94,28 +197,29 @@ app:
94
197
  host: 0.0.0.0
95
198
  environment: development
96
199
 
97
- auth-providers: []
200
+ auth-providers: [] # gateway only — JWT/JWKS providers
98
201
 
99
202
  broker:
203
+ name: rabbitmq
100
204
  uri: "amqp://guest:guest@localhost:5672/"
101
- defaultRpcTimeout: 10000
102
205
  defaultSubscribeErrorBehavior: ack
206
+ defaultPublishErrorBehavior: reject
103
207
  connectionManagerOptions:
104
208
  heartbeatIntervalInSeconds: 60
105
209
  reconnectTimeInSeconds: 60
106
210
  connectionOptions:
107
211
  clientProperties:
108
- connection_name: my-service # REQUIRED for broadcast/WebSocket
212
+ connection_name: my-service # distinct per instance; needed for broadcast/WebSocket
109
213
  credentials: { mechanism: PLAIN, username: guest, password: guest }
110
214
  exchanges:
111
- - name: my-ex
215
+ - name: rlb
112
216
  type: direct
113
217
  createExchangeIfNotExists: true
114
218
  options: { durable: true }
115
219
  queues:
116
220
  - name: my-rpc-q
117
- exchange: my-ex
118
- routingKey: my.rpc
221
+ exchange: rlb
222
+ routingKey: my-rpc-q
119
223
  createQueueIfNotExists: true
120
224
  options: { durable: true }
121
225
 
@@ -123,38 +227,69 @@ topics:
123
227
  - name: my-rpc
124
228
  mode: rpc
125
229
  queue: my-rpc-q
230
+ exchange: rlb
231
+ routingKey: my-rpc-q
126
232
 
233
+ # --- gateway only ---
127
234
  gateway:
128
235
  mode: gateway
236
+ events: []
129
237
  paths:
130
- - name: ping
238
+ - name: health
131
239
  method: GET
132
- path: /ping
240
+ path: /health
133
241
  dataSource: query
134
- topic: my-rpc
135
- action: ping
242
+ topic: rlb-gateway-admin # gateway-admin gw-health → 200 { status: 'ok' }
243
+ action: gw-health
136
244
  mode: rpc
137
- events: []
138
245
  ```
139
246
 
140
- ## 5. Sample handler (optional)
247
+ ### Route auto-discovery (publisher side, optional)
248
+
249
+ A microservice can announce its `@BrokerHTTP` routes on boot. Add INSIDE the `broker`
250
+ block — `serviceName` is the ownership key AND fills `connection_name` when none is set
251
+ explicitly. `exchange`/`queue` default to `rlb-route-discovery` / `rlb-route-sync` and
252
+ must match the gateway's `GatewayAdminModule` `routeDiscovery { exchange, queue }`.
253
+
254
+ ```yaml
255
+ broker:
256
+ # ...
257
+ routeDiscovery:
258
+ serviceName: my-service
259
+ publishOnBoot: true
260
+ # exchange: rlb-route-discovery # override to namespace per env (must match the gateway)
261
+ # queue: rlb-route-sync
262
+ ```
263
+
264
+ ### 5. Sample handler (one method, both transports)
141
265
 
142
266
  ```ts
143
267
  import { Injectable } from '@nestjs/common';
144
- import { BrokerAction, BrokerParam } from '@open-rlb/nestjs-amqp';
268
+ import { BrokerAction, BrokerHTTP, BrokerParam } from '@open-rlb/nestjs-amqp';
145
269
 
146
270
  @Injectable()
147
- export class MyActionService {
148
- @BrokerAction('my-rpc', 'ping', 'rpc')
149
- async ping(@BrokerParam('body-full') data: any) {
150
- return { pong: true, echo: data };
271
+ export class AppService {
272
+ @BrokerAction('my-rpc', 'ping') // AMQP RPC on topic my-rpc, action ping
273
+ @BrokerHTTP('POST', '/ping', 'body') // route metadata for gateway auto-discovery
274
+ async ping(@BrokerParam('body', 'name') name: string) { // flat params, one decorator each
275
+ return { pong: true, name };
151
276
  }
152
277
  }
153
278
  ```
154
279
 
280
+ Add auth with `@BrokerAuth(authName, allowAnonymous?, roles?, httpName?)` — decoupled from
281
+ `@BrokerHTTP`. With one `@BrokerHTTP` it auto-pairs (no `httpName` needed); with multiple,
282
+ each `@BrokerHTTP` sets a `name` and each `@BrokerAuth` targets it via `httpName`. A route
283
+ with no `@BrokerAuth` is public.
284
+
155
285
  ## Verify
156
- - topic/queue/exchange names line up (gotchas 5–7); `connection_name` set if needed (8).
157
- - `npm run build`, start the app with a reachable RabbitMQ, hit `/ping`.
286
+ - topic/queue/exchange names line up across `broker`/`topics`/paths (gotchas 5–7);
287
+ `connection_name` set if using broadcast/WebSocket (8).
288
+ - Route-discovery `exchange`/`queue` identical on publisher and gateway.
289
+ - Reload DB routes at runtime via the `gw-reload` action on the broadcast control topic
290
+ (default `rlb-gateway-control`).
291
+ - `npm run build`, start with a reachable RabbitMQ, hit `/health` (gateway) or publish to
292
+ the RPC topic (microservice).
158
293
 
159
- After scaffolding, use `rlb-amqp-add-action` / `rlb-amqp-add-route` / `rlb-amqp-add-ws-event`
160
- to grow the service.
294
+ After scaffolding, use `rlb-amqp-add-action` / `rlb-amqp-add-route` /
295
+ `rlb-amqp-add-ws-event` to grow the service.