@open-rlb/nestjs-amqp 2.1.1 → 2.2.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.
Files changed (198) hide show
  1. package/package.json +6 -5
  2. package/schematics/add-auth-provider/index.d.ts +3 -0
  3. package/schematics/add-auth-provider/index.js +60 -0
  4. package/schematics/add-auth-provider/index.js.map +1 -0
  5. package/schematics/add-auth-provider/index.spec.ts +83 -0
  6. package/schematics/add-auth-provider/index.ts +83 -0
  7. package/schematics/add-auth-provider/schema.d.ts +18 -0
  8. package/schematics/add-auth-provider/schema.js +3 -0
  9. package/schematics/add-auth-provider/schema.js.map +1 -0
  10. package/schematics/add-auth-provider/schema.json +59 -0
  11. package/schematics/add-auth-provider/schema.ts +35 -0
  12. package/schematics/add-exchange/index.d.ts +3 -0
  13. package/schematics/add-exchange/index.js +49 -0
  14. package/schematics/add-exchange/index.js.map +1 -0
  15. package/schematics/add-exchange/index.spec.ts +65 -0
  16. package/schematics/add-exchange/index.ts +65 -0
  17. package/schematics/add-exchange/schema.d.ts +14 -0
  18. package/schematics/add-exchange/schema.js +3 -0
  19. package/schematics/add-exchange/schema.js.map +1 -0
  20. package/schematics/add-exchange/schema.json +50 -0
  21. package/schematics/add-exchange/schema.ts +26 -0
  22. package/schematics/add-exchange-binding/index.d.ts +3 -0
  23. package/schematics/add-exchange-binding/index.js +53 -0
  24. package/schematics/add-exchange-binding/index.js.map +1 -0
  25. package/schematics/add-exchange-binding/index.spec.ts +73 -0
  26. package/schematics/add-exchange-binding/index.ts +64 -0
  27. package/schematics/add-exchange-binding/schema.d.ts +8 -0
  28. package/schematics/add-exchange-binding/schema.js +3 -0
  29. package/schematics/add-exchange-binding/schema.js.map +1 -0
  30. package/schematics/add-exchange-binding/schema.json +36 -0
  31. package/schematics/add-exchange-binding/schema.ts +14 -0
  32. package/schematics/add-gateway/index.d.ts +4 -0
  33. package/schematics/add-gateway/index.js +105 -0
  34. package/schematics/add-gateway/index.js.map +1 -0
  35. package/schematics/add-gateway/index.spec.ts +163 -0
  36. package/schematics/add-gateway/index.ts +141 -0
  37. package/schematics/add-gateway/paths.d.ts +12 -0
  38. package/schematics/add-gateway/paths.js +87 -0
  39. package/schematics/add-gateway/paths.js.map +1 -0
  40. package/schematics/add-gateway/paths.ts +103 -0
  41. package/schematics/add-gateway/schema.d.ts +13 -0
  42. package/schematics/add-gateway/schema.js +3 -0
  43. package/schematics/add-gateway/schema.js.map +1 -0
  44. package/schematics/add-gateway/schema.json +63 -0
  45. package/schematics/add-gateway/schema.ts +24 -0
  46. package/schematics/add-gateway/wiring.d.ts +14 -0
  47. package/schematics/add-gateway/wiring.js +102 -0
  48. package/schematics/add-gateway/wiring.js.map +1 -0
  49. package/schematics/add-gateway/wiring.ts +115 -0
  50. package/schematics/add-metrics-topic/index.d.ts +3 -0
  51. package/schematics/add-metrics-topic/index.js +31 -0
  52. package/schematics/add-metrics-topic/index.js.map +1 -0
  53. package/schematics/add-metrics-topic/index.spec.ts +79 -0
  54. package/schematics/add-metrics-topic/index.ts +49 -0
  55. package/schematics/add-metrics-topic/schema.d.ts +10 -0
  56. package/schematics/add-metrics-topic/schema.js +3 -0
  57. package/schematics/add-metrics-topic/schema.js.map +1 -0
  58. package/schematics/add-metrics-topic/schema.json +48 -0
  59. package/schematics/add-metrics-topic/schema.ts +18 -0
  60. package/schematics/add-queue/index.d.ts +3 -0
  61. package/schematics/add-queue/index.js +48 -0
  62. package/schematics/add-queue/index.js.map +1 -0
  63. package/schematics/add-queue/index.spec.ts +75 -0
  64. package/schematics/add-queue/index.ts +74 -0
  65. package/schematics/add-queue/schema.d.ts +17 -0
  66. package/schematics/add-queue/schema.js +3 -0
  67. package/schematics/add-queue/schema.js.map +1 -0
  68. package/schematics/add-queue/schema.json +54 -0
  69. package/schematics/add-queue/schema.ts +32 -0
  70. package/schematics/add-reply-queue/index.d.ts +3 -0
  71. package/schematics/add-reply-queue/index.js +26 -0
  72. package/schematics/add-reply-queue/index.js.map +1 -0
  73. package/schematics/add-reply-queue/index.spec.ts +69 -0
  74. package/schematics/add-reply-queue/index.ts +33 -0
  75. package/schematics/add-reply-queue/schema.d.ts +6 -0
  76. package/schematics/add-reply-queue/schema.js +3 -0
  77. package/schematics/add-reply-queue/schema.js.map +1 -0
  78. package/schematics/add-reply-queue/schema.json +28 -0
  79. package/schematics/add-reply-queue/schema.ts +10 -0
  80. package/schematics/add-route/index.d.ts +3 -0
  81. package/schematics/add-route/index.js +57 -0
  82. package/schematics/add-route/index.js.map +1 -0
  83. package/schematics/add-route/index.spec.ts +75 -0
  84. package/schematics/add-route/index.ts +74 -0
  85. package/schematics/add-route/schema.d.ts +22 -0
  86. package/schematics/add-route/schema.js +3 -0
  87. package/schematics/add-route/schema.js.map +1 -0
  88. package/schematics/add-route/schema.json +74 -0
  89. package/schematics/add-route/schema.ts +43 -0
  90. package/schematics/add-topic/index.d.ts +3 -0
  91. package/schematics/add-topic/index.js +69 -0
  92. package/schematics/add-topic/index.js.map +1 -0
  93. package/schematics/add-topic/index.spec.ts +100 -0
  94. package/schematics/add-topic/index.ts +104 -0
  95. package/schematics/add-topic/schema.d.ts +18 -0
  96. package/schematics/add-topic/schema.js +3 -0
  97. package/schematics/add-topic/schema.js.map +1 -0
  98. package/schematics/add-topic/schema.json +63 -0
  99. package/schematics/add-topic/schema.ts +34 -0
  100. package/schematics/add-ws-event/index.d.ts +3 -0
  101. package/schematics/add-ws-event/index.js +53 -0
  102. package/schematics/add-ws-event/index.js.map +1 -0
  103. package/schematics/add-ws-event/index.spec.ts +85 -0
  104. package/schematics/add-ws-event/index.ts +72 -0
  105. package/schematics/add-ws-event/schema.d.ts +17 -0
  106. package/schematics/add-ws-event/schema.js +3 -0
  107. package/schematics/add-ws-event/schema.js.map +1 -0
  108. package/schematics/add-ws-event/schema.json +53 -0
  109. package/schematics/add-ws-event/schema.ts +33 -0
  110. package/schematics/collection.json +81 -1
  111. package/schematics/configure-broker/index.d.ts +3 -0
  112. package/schematics/configure-broker/index.js +41 -0
  113. package/schematics/configure-broker/index.js.map +1 -0
  114. package/schematics/configure-broker/index.spec.ts +62 -0
  115. package/schematics/configure-broker/index.ts +51 -0
  116. package/schematics/configure-broker/schema.d.ts +11 -0
  117. package/schematics/configure-broker/schema.js +3 -0
  118. package/schematics/configure-broker/schema.js.map +1 -0
  119. package/schematics/configure-broker/schema.json +47 -0
  120. package/schematics/configure-broker/schema.ts +20 -0
  121. package/schematics/enable-load-config/index.d.ts +3 -0
  122. package/schematics/enable-load-config/index.js +32 -0
  123. package/schematics/enable-load-config/index.js.map +1 -0
  124. package/schematics/enable-load-config/index.spec.ts +61 -0
  125. package/schematics/enable-load-config/index.ts +39 -0
  126. package/schematics/enable-load-config/schema.d.ts +8 -0
  127. package/schematics/enable-load-config/schema.js +3 -0
  128. package/schematics/enable-load-config/schema.js.map +1 -0
  129. package/schematics/enable-load-config/schema.json +36 -0
  130. package/schematics/enable-load-config/schema.ts +14 -0
  131. package/schematics/enable-retry/index.d.ts +3 -0
  132. package/schematics/enable-retry/index.js +51 -0
  133. package/schematics/enable-retry/index.js.map +1 -0
  134. package/schematics/enable-retry/index.spec.ts +73 -0
  135. package/schematics/enable-retry/index.ts +62 -0
  136. package/schematics/enable-retry/schema.d.ts +13 -0
  137. package/schematics/enable-retry/schema.js +3 -0
  138. package/schematics/enable-retry/schema.js.map +1 -0
  139. package/schematics/enable-retry/schema.json +64 -0
  140. package/schematics/enable-retry/schema.ts +24 -0
  141. package/schematics/enable-route-discovery/index.d.ts +3 -0
  142. package/schematics/enable-route-discovery/index.js +36 -0
  143. package/schematics/enable-route-discovery/index.js.map +1 -0
  144. package/schematics/enable-route-discovery/index.spec.ts +55 -0
  145. package/schematics/enable-route-discovery/index.ts +48 -0
  146. package/schematics/enable-route-discovery/schema.d.ts +9 -0
  147. package/schematics/enable-route-discovery/schema.js +3 -0
  148. package/schematics/enable-route-discovery/schema.js.map +1 -0
  149. package/schematics/enable-route-discovery/schema.json +44 -0
  150. package/schematics/enable-route-discovery/schema.ts +16 -0
  151. package/schematics/harden-gateway/index.d.ts +3 -0
  152. package/schematics/harden-gateway/index.js +93 -0
  153. package/schematics/harden-gateway/index.js.map +1 -0
  154. package/schematics/harden-gateway/index.spec.ts +105 -0
  155. package/schematics/harden-gateway/index.ts +108 -0
  156. package/schematics/harden-gateway/schema.d.ts +11 -0
  157. package/schematics/harden-gateway/schema.js +3 -0
  158. package/schematics/harden-gateway/schema.js.map +1 -0
  159. package/schematics/harden-gateway/schema.json +47 -0
  160. package/schematics/harden-gateway/schema.ts +20 -0
  161. package/schematics/nest-add/index.js +55 -832
  162. package/schematics/nest-add/index.js.map +1 -1
  163. package/schematics/nest-add/index.spec.ts +73 -0
  164. package/schematics/nest-add/index.ts +76 -917
  165. package/schematics/nest-add/init.schema.d.ts +1 -0
  166. package/schematics/nest-add/init.schema.ts +3 -0
  167. package/schematics/nest-add/schema.json +4 -43
  168. package/schematics/set-connection-name/index.d.ts +3 -0
  169. package/schematics/set-connection-name/index.js +34 -0
  170. package/schematics/set-connection-name/index.js.map +1 -0
  171. package/schematics/set-connection-name/index.spec.ts +48 -0
  172. package/schematics/set-connection-name/index.ts +42 -0
  173. package/schematics/set-connection-name/schema.d.ts +5 -0
  174. package/schematics/set-connection-name/schema.js +3 -0
  175. package/schematics/set-connection-name/schema.js.map +1 -0
  176. package/schematics/set-connection-name/schema.json +24 -0
  177. package/schematics/set-connection-name/schema.ts +8 -0
  178. package/schematics/utils/broker-yaml.util.d.ts +53 -0
  179. package/schematics/utils/broker-yaml.util.js +91 -0
  180. package/schematics/utils/broker-yaml.util.js.map +1 -0
  181. package/schematics/utils/broker-yaml.util.ts +172 -0
  182. package/schematics/utils/nest-wiring.util.d.ts +30 -0
  183. package/schematics/utils/nest-wiring.util.js +170 -0
  184. package/schematics/utils/nest-wiring.util.js.map +1 -0
  185. package/schematics/utils/nest-wiring.util.ts +211 -0
  186. package/schematics/utils/schematic-prompt.util.d.ts +7 -0
  187. package/schematics/utils/schematic-prompt.util.js +57 -0
  188. package/schematics/utils/schematic-prompt.util.js.map +1 -0
  189. package/schematics/utils/schematic-prompt.util.ts +82 -0
  190. package/schematics/utils/yaml-config.util.d.ts +22 -0
  191. package/schematics/utils/yaml-config.util.js +119 -0
  192. package/schematics/utils/yaml-config.util.js.map +1 -0
  193. package/schematics/utils/yaml-config.util.ts +167 -0
  194. /package/schematics/{nest-add → add-gateway}/files/acl/src/cache/in-memory-acl-store.ts +0 -0
  195. /package/schematics/{nest-add → add-gateway}/files/acl/src/modules/database/repository/acl.repository.ts +0 -0
  196. /package/schematics/{nest-add → add-gateway}/files/db-core/src/modules/database/repository/in-memory-collection.ts +0 -0
  197. /package/schematics/{nest-add → add-gateway}/files/gateway-admin/src/modules/database/repository/gateway.repository.ts +0 -0
  198. /package/schematics/{nest-add → add-gateway}/files/gateway-admin/src/modules/database/repository/route-sync.repository.ts +0 -0
@@ -0,0 +1,115 @@
1
+ import { brokerModuleEntry, configModuleEntry, WireEntry } from '../utils/nest-wiring.util';
2
+
3
+ /**
4
+ * The AppModule wiring for a gateway: the lib symbols to import, the helper import lines, and the
5
+ * `@Module` imports[] entries with idempotency sentinels. Mirrors what nest-add generated for the
6
+ * gateway case, now owned by add-gateway so a promote-to-gateway run wires a working app.
7
+ * HttpModule / ProxyModule share the `ProxyModule.forRootAsync` sentinel (the gateway pair), so both
8
+ * are added together when absent and skipped together on a re-run.
9
+ */
10
+
11
+ export interface GwFeatures {
12
+ acl: boolean;
13
+ admin: boolean;
14
+ routeReception: boolean;
15
+ routeExchange: string;
16
+ routeQueue: string;
17
+ }
18
+
19
+ export function libSymbols(f: GwFeatures): string[] {
20
+ const s = ['AppConfig', 'BrokerModule', 'BrokerTopic', 'GatewayConfig', 'HandlerAuthConfig', 'ProxyModule', 'RabbitMQConfig'];
21
+ if (f.acl) s.push('AclActionRepository', 'AclGrantRepository', 'AclModule', 'AclRoleRepository', 'AclService', 'RLB_ACL_CACHE_STORE', 'RLB_GTW_ACL_ROLE_SERVICE');
22
+ if (f.admin || f.routeReception) s.push('AuthProviderRepository', 'GatewayAdminModule', 'HttpMetricRepository', 'HttpPathRepository', 'RouteSyncLogRepository');
23
+ if (f.admin) s.push('RLB_GTW_AUTH_PROVIDER_SOURCE');
24
+ return s;
25
+ }
26
+
27
+ export function importLines(f: GwFeatures): { line: string; marker: string }[] {
28
+ const lines = [
29
+ { line: `import { ConfigModule, ConfigService } from '@nestjs/config';`, marker: '@nestjs/config' },
30
+ { line: `import yamlConfig from './config/config.loader';`, marker: './config/config.loader' },
31
+ { line: `import { HttpModule } from '@nestjs/axios';`, marker: '@nestjs/axios' },
32
+ ];
33
+ if (f.acl) {
34
+ lines.push({ line: `import { InMemoryAclActionRepository, InMemoryAclGrantRepository, InMemoryAclRoleRepository } from './modules/database/repository/acl.repository';`, marker: './modules/database/repository/acl.repository' });
35
+ lines.push({ line: `import { InMemoryAclStore } from './cache/in-memory-acl-store';`, marker: './cache/in-memory-acl-store' });
36
+ }
37
+ if (f.admin || f.routeReception) {
38
+ lines.push({ line: `import { InMemoryAuthProviderRepository, InMemoryHttpMetricRepository, InMemoryHttpPathRepository } from './modules/database/repository/gateway.repository';`, marker: './modules/database/repository/gateway.repository' });
39
+ lines.push({ line: `import { InMemoryRouteSyncLogRepository } from './modules/database/repository/route-sync.repository';`, marker: './modules/database/repository/route-sync.repository' });
40
+ }
41
+ return lines;
42
+ }
43
+
44
+ const GATEWAY_SENTINEL = 'ProxyModule.forRootAsync';
45
+
46
+ export function moduleEntries(f: GwFeatures): WireEntry[] {
47
+ // ConfigModule + BrokerModule come from the shared core builders (single source of truth with nest-add).
48
+ const entries: WireEntry[] = [
49
+ configModuleEntry(),
50
+ brokerModuleEntry(),
51
+ { code: `HttpModule`, sentinel: GATEWAY_SENTINEL },
52
+ { code: proxyForRootAsync(f), sentinel: GATEWAY_SENTINEL },
53
+ ];
54
+ if (f.acl) entries.push({ code: aclForRoot(), sentinel: 'AclModule.forRoot' });
55
+ if (f.admin || f.routeReception) entries.push({ code: gatewayAdminForRoot(f), sentinel: 'GatewayAdminModule.forRoot' });
56
+ return entries;
57
+ }
58
+
59
+ function proxyForRootAsync(f: GwFeatures): string {
60
+ const provs: string[] = [];
61
+ if (f.acl)
62
+ provs.push(` // Action-gated paths resolve the caller's identity via AclService (in-process, no broker hop).
63
+ { provide: RLB_GTW_ACL_ROLE_SERVICE, useExisting: AclService },`);
64
+ if (f.admin)
65
+ provs.push(` // DB auth-provider source for the runtime registry; activated by a deliberate gw-auth-reload.
66
+ { provide: RLB_GTW_AUTH_PROVIDER_SOURCE, useExisting: InMemoryAuthProviderRepository },`);
67
+ const providers = provs.length ? `[\n${provs.join('\n')}\n ]` : `[]`;
68
+ return `ProxyModule.forRootAsync({
69
+ imports: [ConfigModule],
70
+ inject: [ConfigService],
71
+ useFactory: (configService: ConfigService) => ({
72
+ authOptions: configService.get<HandlerAuthConfig[]>('auth-providers'),
73
+ gatewayOptions: configService.get<GatewayConfig>('gateway'),
74
+ }),
75
+ providers: ${providers},
76
+ })`;
77
+ }
78
+
79
+ function aclForRoot(): string {
80
+ return `AclModule.forRoot(
81
+ [
82
+ InMemoryAclActionRepository,
83
+ { provide: AclActionRepository, useExisting: InMemoryAclActionRepository },
84
+ InMemoryAclRoleRepository,
85
+ { provide: AclRoleRepository, useExisting: InMemoryAclRoleRepository },
86
+ InMemoryAclGrantRepository,
87
+ { provide: AclGrantRepository, useExisting: InMemoryAclGrantRepository },
88
+ InMemoryAclStore,
89
+ { provide: RLB_ACL_CACHE_STORE, useExisting: InMemoryAclStore },
90
+ ],
91
+ { cache: { ramTtlMs: 30000, l2TtlSec: 600 } },
92
+ )`;
93
+ }
94
+
95
+ function gatewayAdminForRoot(f: GwFeatures): string {
96
+ const options = f.routeReception
97
+ ? `,
98
+ {
99
+ // Consumer-side route-discovery — names MUST match the publishers' broker.routeDiscovery.
100
+ routeDiscovery: { exchange: '${f.routeExchange}', queue: '${f.routeQueue}' },
101
+ }`
102
+ : '';
103
+ return `GatewayAdminModule.forRoot(
104
+ [
105
+ InMemoryHttpPathRepository,
106
+ { provide: HttpPathRepository, useExisting: InMemoryHttpPathRepository },
107
+ InMemoryAuthProviderRepository,
108
+ { provide: AuthProviderRepository, useExisting: InMemoryAuthProviderRepository },
109
+ InMemoryHttpMetricRepository,
110
+ { provide: HttpMetricRepository, useExisting: InMemoryHttpMetricRepository },
111
+ InMemoryRouteSyncLogRepository,
112
+ { provide: RouteSyncLogRepository, useExisting: InMemoryRouteSyncLogRepository },
113
+ ]${options},
114
+ )`;
115
+ }
@@ -0,0 +1,3 @@
1
+ import { Rule } from '@angular-devkit/schematics';
2
+ import { AddMetricsTopicOptions } from './schema';
3
+ export declare function main(options: AddMetricsTopicOptions): Rule;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ const broker_yaml_util_1 = require("../utils/broker-yaml.util");
5
+ const schematic_prompt_util_1 = require("../utils/schematic-prompt.util");
6
+ const yaml_config_util_1 = require("../utils/yaml-config.util");
7
+ function main(options) {
8
+ return async (tree, context) => {
9
+ const flagsProvided = options.topic !== undefined;
10
+ const prompts = (0, schematic_prompt_util_1.loadPrompts)(context, flagsProvided);
11
+ const topic = options.topic || (await (0, schematic_prompt_util_1.askText)(prompts, 'Metrics topic name?', 'rlb-gateway-metrics'));
12
+ const queue = options.queue || (await (0, schematic_prompt_util_1.askText)(prompts, 'Queue backing the topic?', topic));
13
+ const exchange = options.exchange || (await (0, schematic_prompt_util_1.askText)(prompts, 'Exchange?', 'rlb'));
14
+ const messageTtl = options.messageTtl ?? (await (0, schematic_prompt_util_1.askNumber)(prompts, 'Queue messageTtl (ms)?', 3600000));
15
+ const maxLength = options.maxLength ?? (await (0, schematic_prompt_util_1.askNumber)(prompts, 'Queue maxLength?', 500000));
16
+ const action = options.action || (await (0, schematic_prompt_util_1.askText)(prompts, 'Metrics action?', 'gw-metrics-track'));
17
+ const configPath = (0, yaml_config_util_1.findConfigYaml)(tree, options.config);
18
+ const { doc, existed } = (0, yaml_config_util_1.readConfigDoc)(tree, configPath);
19
+ if (!existed)
20
+ context.logger.info(`[rlb-amqp] ${configPath} not found — creating it.`);
21
+ const qOutcome = (0, broker_yaml_util_1.ensureQueue)(doc, { name: queue, exchange, routingKey: queue, createQueueIfNotExists: true, durable: true, messageTtl, maxLength }, { overwrite: options.overwrite });
22
+ (0, schematic_prompt_util_1.logOutcome)(context, `queue '${queue}'`, qOutcome);
23
+ const tOutcome = (0, broker_yaml_util_1.ensureTopic)(doc, { name: topic, mode: 'handle', queue, exchange, routingKey: queue }, { overwrite: options.overwrite });
24
+ (0, schematic_prompt_util_1.logOutcome)(context, `topic '${topic}'`, tOutcome);
25
+ const gOutcome = (0, yaml_config_util_1.setIn)(doc, ['gateway', 'metrics'], { topic, action });
26
+ (0, schematic_prompt_util_1.logOutcome)(context, 'gateway.metrics', gOutcome);
27
+ (0, yaml_config_util_1.writeConfigDoc)(tree, configPath, doc);
28
+ return tree;
29
+ };
30
+ }
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../libs/rlb-nestjs-amqp/src/schematics/add-metrics-topic/index.ts"],"names":[],"mappings":";;AAYA,oBAoCC;AA/CD,gEAAqE;AACrE,0EAA6F;AAC7F,gEAAiG;AASjG,SAAgB,IAAI,CAAC,OAA+B;IAClD,OAAO,KAAK,EAAE,IAAU,EAAE,OAAyB,EAAE,EAAE;QACrD,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC;QAClD,MAAM,OAAO,GAAG,IAAA,mCAAW,EAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QAEpD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,MAAM,IAAA,+BAAO,EAAC,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,CAAC,CAAC,CAAC;QACtG,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,MAAM,IAAA,+BAAO,EAAC,OAAO,EAAE,0BAA0B,EAAE,KAAK,CAAC,CAAC,CAAC;QAC3F,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,MAAM,IAAA,+BAAO,EAAC,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC;QAClF,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,CAAC,MAAM,IAAA,iCAAS,EAAC,OAAO,EAAE,wBAAwB,EAAE,OAAO,CAAC,CAAC,CAAC;QACvG,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,CAAC,MAAM,IAAA,iCAAS,EAAC,OAAO,EAAE,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAAC;QAC9F,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,IAAA,+BAAO,EAAC,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,CAAC,CAAC,CAAC;QAEjG,MAAM,UAAU,GAAG,IAAA,iCAAc,EAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QACxD,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,IAAA,gCAAa,EAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACzD,IAAI,CAAC,OAAO;YAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,UAAU,2BAA2B,CAAC,CAAC;QAEvF,MAAM,QAAQ,GAAG,IAAA,8BAAW,EAC1B,GAAG,EACH,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,sBAAsB,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,EAChH,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CACjC,CAAC;QACF,IAAA,kCAAU,EAAC,OAAO,EAAE,UAAU,KAAK,GAAG,EAAE,QAAQ,CAAC,CAAC;QAElD,MAAM,QAAQ,GAAG,IAAA,8BAAW,EAC1B,GAAG,EACH,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,EACnE,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CACjC,CAAC;QACF,IAAA,kCAAU,EAAC,OAAO,EAAE,UAAU,KAAK,GAAG,EAAE,QAAQ,CAAC,CAAC;QAElD,MAAM,QAAQ,GAAG,IAAA,wBAAK,EAAC,GAAG,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QACvE,IAAA,kCAAU,EAAC,OAAO,EAAE,iBAAiB,EAAE,QAAQ,CAAC,CAAC;QAEjD,IAAA,iCAAc,EAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,79 @@
1
+ import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
2
+ import { parseDocument } from 'yaml';
3
+ import { main } from './index';
4
+ import { AddMetricsTopicOptions } from './schema';
5
+
6
+ /**
7
+ * Factory is invoked directly against a Tree (not SchematicTestRunner, whose testing entrypoint
8
+ * eagerly loads the ESM-only `ora`). Passing `topic` makes `flagsProvided` true → no prompts.
9
+ */
10
+ const ctx = { logger: { info() {}, warn() {}, error() {} } } as unknown as SchematicContext;
11
+
12
+ async function run(options: AddMetricsTopicOptions, tree: Tree): Promise<Tree> {
13
+ const rule = main(options) as Rule;
14
+ const result = await (rule as (t: Tree, c: SchematicContext) => Promise<Tree>)(tree, ctx);
15
+ return result || tree;
16
+ }
17
+
18
+ function read(tree: Tree, path = 'config/config.yaml'): string {
19
+ return tree.read(path)!.toString('utf-8');
20
+ }
21
+ function doc(tree: Tree) {
22
+ return parseDocument(read(tree));
23
+ }
24
+ function seedConfig(): Tree {
25
+ const tree = Tree.empty();
26
+ tree.create(
27
+ 'config/config.yaml',
28
+ ['# my config', 'broker:', ' queues: []', 'topics: []', 'gateway:', ' paths: []', ''].join('\n'),
29
+ );
30
+ return tree;
31
+ }
32
+
33
+ describe('add-metrics-topic schematic', () => {
34
+ it('creates a bounded queue, a handle topic and points gateway.metrics at it', async () => {
35
+ const tree = await run({ topic: 'rlb-gateway-metrics' }, seedConfig());
36
+ const d = doc(tree);
37
+
38
+ const queue = (d.getIn(['broker', 'queues']) as any).items[0];
39
+ expect(String(queue.get('name'))).toBe('rlb-gateway-metrics');
40
+ expect(String(queue.get('exchange'))).toBe('rlb');
41
+ expect(String(queue.get('routingKey'))).toBe('rlb-gateway-metrics');
42
+ const qOpts = queue.get('options');
43
+ expect(Number(qOpts.get('messageTtl'))).toBe(3600000);
44
+ expect(Number(qOpts.get('maxLength'))).toBe(500000);
45
+
46
+ const topic = (d.getIn(['topics']) as any).items[0];
47
+ expect(String(topic.get('name'))).toBe('rlb-gateway-metrics');
48
+ expect(String(topic.get('mode'))).toBe('handle');
49
+ expect(String(topic.get('queue'))).toBe('rlb-gateway-metrics');
50
+
51
+ expect(String(d.getIn(['gateway', 'metrics', 'topic']))).toBe('rlb-gateway-metrics');
52
+ expect(String(d.getIn(['gateway', 'metrics', 'action']))).toBe('gw-metrics-track');
53
+ expect(read(tree)).toContain('# my config');
54
+ });
55
+
56
+ it('honours an explicit queue name and custom bounds/action', async () => {
57
+ const tree = await run(
58
+ { topic: 'metrics-t', queue: 'metrics-q', exchange: 'ex', messageTtl: 60000, maxLength: 100, action: 'track' },
59
+ seedConfig(),
60
+ );
61
+ const d = doc(tree);
62
+ const queue = (d.getIn(['broker', 'queues']) as any).items[0];
63
+ expect(String(queue.get('name'))).toBe('metrics-q');
64
+ expect(String(queue.get('exchange'))).toBe('ex');
65
+ expect(Number(queue.get('options').get('messageTtl'))).toBe(60000);
66
+ expect(String(d.getIn(['topics'] as any) && (d.getIn(['topics']) as any).items[0].get('queue'))).toBe('metrics-q');
67
+ expect(String(d.getIn(['gateway', 'metrics', 'action']))).toBe('track');
68
+ });
69
+
70
+ it('is idempotent: re-running leaves a single queue/topic entry', async () => {
71
+ let tree = await run({ topic: 'rlb-gateway-metrics' }, seedConfig());
72
+ tree = await run({ topic: 'rlb-gateway-metrics' }, tree);
73
+ const d = doc(tree);
74
+ const queues = (d.getIn(['broker', 'queues']) as any).items.map((i: any) => String(i.get('name')));
75
+ const topics = (d.getIn(['topics']) as any).items.map((i: any) => String(i.get('name')));
76
+ expect(queues.filter((n: string) => n === 'rlb-gateway-metrics')).toHaveLength(1);
77
+ expect(topics.filter((n: string) => n === 'rlb-gateway-metrics')).toHaveLength(1);
78
+ });
79
+ });
@@ -0,0 +1,49 @@
1
+ import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
2
+ import { ensureQueue, ensureTopic } from '../utils/broker-yaml.util';
3
+ import { askNumber, askText, loadPrompts, logOutcome } from '../utils/schematic-prompt.util';
4
+ import { findConfigYaml, readConfigDoc, setIn, writeConfigDoc } from '../utils/yaml-config.util';
5
+ import { AddMetricsTopicOptions } from './schema';
6
+
7
+ /**
8
+ * `add-metrics-topic` — move the gateway's per-request metrics off the shared rlb-gateway-admin
9
+ * queue onto a dedicated, growth-bounded topic/queue (messageTtl + maxLength) and repoint
10
+ * `gateway.metrics` at it. Idempotent + comment-preserving; the queue/topic upserts go through the
11
+ * broker DATA helpers directly (no prompt ping-pong).
12
+ */
13
+ export function main(options: AddMetricsTopicOptions): Rule {
14
+ return async (tree: Tree, context: SchematicContext) => {
15
+ const flagsProvided = options.topic !== undefined;
16
+ const prompts = loadPrompts(context, flagsProvided);
17
+
18
+ const topic = options.topic || (await askText(prompts, 'Metrics topic name?', 'rlb-gateway-metrics'));
19
+ const queue = options.queue || (await askText(prompts, 'Queue backing the topic?', topic));
20
+ const exchange = options.exchange || (await askText(prompts, 'Exchange?', 'rlb'));
21
+ const messageTtl = options.messageTtl ?? (await askNumber(prompts, 'Queue messageTtl (ms)?', 3600000));
22
+ const maxLength = options.maxLength ?? (await askNumber(prompts, 'Queue maxLength?', 500000));
23
+ const action = options.action || (await askText(prompts, 'Metrics action?', 'gw-metrics-track'));
24
+
25
+ const configPath = findConfigYaml(tree, options.config);
26
+ const { doc, existed } = readConfigDoc(tree, configPath);
27
+ if (!existed) context.logger.info(`[rlb-amqp] ${configPath} not found — creating it.`);
28
+
29
+ const qOutcome = ensureQueue(
30
+ doc,
31
+ { name: queue, exchange, routingKey: queue, createQueueIfNotExists: true, durable: true, messageTtl, maxLength },
32
+ { overwrite: options.overwrite },
33
+ );
34
+ logOutcome(context, `queue '${queue}'`, qOutcome);
35
+
36
+ const tOutcome = ensureTopic(
37
+ doc,
38
+ { name: topic, mode: 'handle', queue, exchange, routingKey: queue },
39
+ { overwrite: options.overwrite },
40
+ );
41
+ logOutcome(context, `topic '${topic}'`, tOutcome);
42
+
43
+ const gOutcome = setIn(doc, ['gateway', 'metrics'], { topic, action });
44
+ logOutcome(context, 'gateway.metrics', gOutcome);
45
+
46
+ writeConfigDoc(tree, configPath, doc);
47
+ return tree;
48
+ };
49
+ }
@@ -0,0 +1,10 @@
1
+ export interface AddMetricsTopicOptions {
2
+ topic?: string;
3
+ queue?: string;
4
+ exchange?: string;
5
+ messageTtl?: number;
6
+ maxLength?: number;
7
+ action?: string;
8
+ overwrite?: boolean;
9
+ config?: string;
10
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../libs/rlb-nestjs-amqp/src/schematics/add-metrics-topic/schema.ts"],"names":[],"mappings":""}
@@ -0,0 +1,48 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "SchematicsRlbAmqpAddMetricsTopic",
4
+ "title": "Isolate gateway metrics on a dedicated bounded topic/queue",
5
+ "type": "object",
6
+ "properties": {
7
+ "topic": {
8
+ "type": "string",
9
+ "default": "rlb-gateway-metrics",
10
+ "description": "Metrics topic name.",
11
+ "$default": { "$source": "argv", "index": 0 }
12
+ },
13
+ "queue": {
14
+ "type": "string",
15
+ "description": "Queue backing the topic (default: the topic value)."
16
+ },
17
+ "exchange": {
18
+ "type": "string",
19
+ "default": "rlb",
20
+ "description": "Exchange the queue binds to."
21
+ },
22
+ "messageTtl": {
23
+ "type": "number",
24
+ "default": 3600000,
25
+ "description": "Queue options.messageTtl (ms)."
26
+ },
27
+ "maxLength": {
28
+ "type": "number",
29
+ "default": 500000,
30
+ "description": "Queue options.maxLength."
31
+ },
32
+ "action": {
33
+ "type": "string",
34
+ "default": "gw-metrics-track",
35
+ "description": "Action the gateway emits metrics with."
36
+ },
37
+ "overwrite": {
38
+ "type": "boolean",
39
+ "default": false,
40
+ "description": "Update the entries if they already exist (default: leave them untouched)."
41
+ },
42
+ "config": {
43
+ "type": "string",
44
+ "description": "Path to config.yaml (default: auto-detected)."
45
+ }
46
+ },
47
+ "required": []
48
+ }
@@ -0,0 +1,18 @@
1
+ export interface AddMetricsTopicOptions {
2
+ /** Metrics topic name. Default: 'rlb-gateway-metrics'. */
3
+ topic?: string;
4
+ /** Queue backing the topic. Default: the topic value. */
5
+ queue?: string;
6
+ /** Exchange the queue binds to. Default: 'rlb'. */
7
+ exchange?: string;
8
+ /** Queue options.messageTtl (ms). Default: 3600000. */
9
+ messageTtl?: number;
10
+ /** Queue options.maxLength. Default: 500000. */
11
+ maxLength?: number;
12
+ /** Action the gateway emits metrics with. Default: 'gw-metrics-track'. */
13
+ action?: string;
14
+ /** Update the entries when they already exist (default: leave them untouched). */
15
+ overwrite?: boolean;
16
+ /** Path to config.yaml (default: auto-detected, typically config/config.yaml). */
17
+ config?: string;
18
+ }
@@ -0,0 +1,3 @@
1
+ import { Rule } from '@angular-devkit/schematics';
2
+ import { AddQueueOptions } from './schema';
3
+ export declare function main(options: AddQueueOptions): Rule;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ const broker_yaml_util_1 = require("../utils/broker-yaml.util");
5
+ const schematic_prompt_util_1 = require("../utils/schematic-prompt.util");
6
+ const yaml_config_util_1 = require("../utils/yaml-config.util");
7
+ const TYPES = ['direct', 'topic', 'fanout', 'headers'];
8
+ function main(options) {
9
+ return async (tree, context) => {
10
+ const flagsProvided = options.name !== undefined;
11
+ const prompts = (0, schematic_prompt_util_1.loadPrompts)(context, flagsProvided);
12
+ const name = options.name || (await (0, schematic_prompt_util_1.askText)(prompts, 'Queue name?', ''));
13
+ if (!name) {
14
+ context.logger.error('[rlb-amqp] add-queue: a name is required.');
15
+ return tree;
16
+ }
17
+ const exchange = options.exchange || (await (0, schematic_prompt_util_1.askText)(prompts, 'Bind to which exchange?', 'rlb'));
18
+ const configPath = (0, yaml_config_util_1.findConfigYaml)(tree, options.config);
19
+ const { doc, existed } = (0, yaml_config_util_1.readConfigDoc)(tree, configPath);
20
+ if (!existed)
21
+ context.logger.info(`[rlb-amqp] ${configPath} not found — creating it.`);
22
+ if (!(0, broker_yaml_util_1.exchangeExists)(doc, exchange)) {
23
+ const createExchange = options.createExchange ?? (await (0, schematic_prompt_util_1.askConfirm)(prompts, `Exchange '${exchange}' is missing — create it?`, false));
24
+ if (createExchange) {
25
+ const exchangeType = options.exchangeType || (await (0, schematic_prompt_util_1.askSelect)(prompts, `Type for exchange '${exchange}'?`, TYPES, 'direct'));
26
+ const exOutcome = (0, broker_yaml_util_1.ensureExchange)(doc, { name: exchange, type: exchangeType });
27
+ (0, schematic_prompt_util_1.logOutcome)(context, `exchange '${exchange}'`, exOutcome);
28
+ }
29
+ }
30
+ const routingKey = (0, broker_yaml_util_1.routingKeyRequired)(doc, exchange) ? options.routingKey || name : options.routingKey;
31
+ const outcome = (0, broker_yaml_util_1.ensureQueue)(doc, {
32
+ name,
33
+ exchange,
34
+ routingKey,
35
+ createQueueIfNotExists: options.createIfNotExists ?? true,
36
+ durable: options.durable ?? true,
37
+ exclusive: options.exclusive ?? false,
38
+ autoDelete: options.autoDelete ?? false,
39
+ messageTtl: options.messageTtl,
40
+ maxLength: options.maxLength,
41
+ expires: options.expires,
42
+ }, { overwrite: options.overwrite });
43
+ (0, schematic_prompt_util_1.logOutcome)(context, `queue '${name}'`, outcome);
44
+ (0, yaml_config_util_1.writeConfigDoc)(tree, configPath, doc);
45
+ return tree;
46
+ };
47
+ }
48
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../libs/rlb-nestjs-amqp/src/schematics/add-queue/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
2
+ import { parseDocument } from 'yaml';
3
+ import { main } from './index';
4
+ import { AddQueueOptions } from './schema';
5
+
6
+ /**
7
+ * Invoke the factory Rule directly against a Tree (NOT SchematicTestRunner — it eagerly loads `ora`,
8
+ * an ESM-only module jest cannot transform here). Passing `name` makes `flagsProvided` true, so no
9
+ * interactive prompt is attempted.
10
+ */
11
+ const ctx = { logger: { info() {}, warn() {}, error() {} } } as unknown as SchematicContext;
12
+
13
+ async function run(options: AddQueueOptions, tree: Tree): Promise<Tree> {
14
+ const rule = main(options) as Rule;
15
+ const result = await (rule as (t: Tree, c: SchematicContext) => Promise<Tree>)(tree, ctx);
16
+ return result || tree;
17
+ }
18
+
19
+ function read(tree: Tree, path = 'config/config.yaml'): string {
20
+ return tree.read(path)!.toString('utf-8');
21
+ }
22
+ function doc(tree: Tree) {
23
+ return parseDocument(read(tree));
24
+ }
25
+ function seedConfig(): Tree {
26
+ const tree = Tree.empty();
27
+ tree.create(
28
+ 'config/config.yaml',
29
+ ['# my config', 'broker:', ' exchanges:', ' - name: rlb', ' type: direct', 'topics: []', ''].join('\n'),
30
+ );
31
+ return tree;
32
+ }
33
+
34
+ describe('add-queue schematic', () => {
35
+ it('appends a new queue, preserving comments', async () => {
36
+ const tree = await run({ name: 'orders', exchange: 'rlb' }, seedConfig());
37
+ const queues = doc(tree).getIn(['broker', 'queues']) as any;
38
+ expect(queues.items.map((i: any) => String(i.get('name')))).toEqual(['orders']);
39
+ expect(String(queues.items[0].get('exchange'))).toBe('rlb');
40
+ expect(read(tree)).toContain('# my config');
41
+ });
42
+
43
+ it('is idempotent: re-adding the same queue leaves a single entry', async () => {
44
+ let tree = await run({ name: 'orders', exchange: 'rlb' }, seedConfig());
45
+ tree = await run({ name: 'orders', exchange: 'rlb' }, tree);
46
+ const queues = doc(tree).getIn(['broker', 'queues']) as any;
47
+ const names = queues.items.map((i: any) => String(i.get('name')));
48
+ expect(names.filter((n: string) => n === 'orders')).toHaveLength(1);
49
+ });
50
+
51
+ it('creates the missing exchange when createExchange is set', async () => {
52
+ const tree = await run({ name: 'jobs', exchange: 'jobs-ex', createExchange: true, exchangeType: 'topic' }, seedConfig());
53
+ const exchanges = doc(tree).getIn(['broker', 'exchanges']) as any;
54
+ const created = exchanges.items.find((i: any) => String(i.get('name')) === 'jobs-ex');
55
+ expect(created).toBeDefined();
56
+ expect(String(created.get('type'))).toBe('topic');
57
+ });
58
+
59
+ it('defaults routingKey to the queue name for topic exchanges', async () => {
60
+ const tree = await run({ name: 'jobs', exchange: 'jobs-ex', createExchange: true, exchangeType: 'topic' }, seedConfig());
61
+ const queue = (doc(tree).getIn(['broker', 'queues']) as any).items[0];
62
+ expect(String(queue.get('routingKey'))).toBe('jobs');
63
+ });
64
+
65
+ it('does not emit a routingKey for direct exchanges when none is given', async () => {
66
+ const tree = await run({ name: 'orders', exchange: 'rlb' }, seedConfig());
67
+ const queue = (doc(tree).getIn(['broker', 'queues']) as any).items[0];
68
+ expect(queue.get('routingKey')).toBeUndefined();
69
+ });
70
+
71
+ it('creates config.yaml when it does not exist', async () => {
72
+ const tree = await run({ name: 'orders', exchange: 'rlb' }, Tree.empty());
73
+ expect(tree.exists('config/config.yaml')).toBe(true);
74
+ });
75
+ });
@@ -0,0 +1,74 @@
1
+ import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
2
+ import {
3
+ ensureExchange,
4
+ ensureQueue,
5
+ exchangeExists,
6
+ ExchangeType,
7
+ routingKeyRequired,
8
+ } from '../utils/broker-yaml.util';
9
+ import { askConfirm, askSelect, askText, loadPrompts, logOutcome } from '../utils/schematic-prompt.util';
10
+ import { findConfigYaml, readConfigDoc, writeConfigDoc } from '../utils/yaml-config.util';
11
+ import { AddQueueOptions } from './schema';
12
+
13
+ const TYPES: readonly ExchangeType[] = ['direct', 'topic', 'fanout', 'headers'];
14
+
15
+ /**
16
+ * `add-queue <name>` — idempotent upsert of a `broker.queues[]` entry, comment-preserving.
17
+ * When the target exchange is missing it can create it via the ensureExchange DATA helper (never the
18
+ * add-exchange schematic) so there is no cross-schematic prompt ping-pong. Topic exchanges require a
19
+ * routingKey, so we default it to the queue name when the exchange is (or is being created as) topic.
20
+ */
21
+ export function main(options: AddQueueOptions): Rule {
22
+ return async (tree: Tree, context: SchematicContext) => {
23
+ const flagsProvided = options.name !== undefined;
24
+ const prompts = loadPrompts(context, flagsProvided);
25
+
26
+ const name = options.name || (await askText(prompts, 'Queue name?', ''));
27
+ if (!name) {
28
+ context.logger.error('[rlb-amqp] add-queue: a name is required.');
29
+ return tree;
30
+ }
31
+
32
+ const exchange = options.exchange || (await askText(prompts, 'Bind to which exchange?', 'rlb'));
33
+
34
+ const configPath = findConfigYaml(tree, options.config);
35
+ const { doc, existed } = readConfigDoc(tree, configPath);
36
+ if (!existed) context.logger.info(`[rlb-amqp] ${configPath} not found — creating it.`);
37
+
38
+ // Create the exchange first when missing so the queue never dangles. Offer defaults to false
39
+ // interactively; passing --create-exchange (or its type) opts in non-interactively.
40
+ if (!exchangeExists(doc, exchange)) {
41
+ const createExchange = options.createExchange ?? (await askConfirm(prompts, `Exchange '${exchange}' is missing — create it?`, false));
42
+ if (createExchange) {
43
+ const exchangeType =
44
+ (options.exchangeType as ExchangeType) || (await askSelect(prompts, `Type for exchange '${exchange}'?`, TYPES, 'direct'));
45
+ const exOutcome = ensureExchange(doc, { name: exchange, type: exchangeType });
46
+ logOutcome(context, `exchange '${exchange}'`, exOutcome);
47
+ }
48
+ }
49
+
50
+ // Topic exchanges MUST carry a routingKey; default it to the queue name when unspecified.
51
+ const routingKey = routingKeyRequired(doc, exchange) ? options.routingKey || name : options.routingKey;
52
+
53
+ const outcome = ensureQueue(
54
+ doc,
55
+ {
56
+ name,
57
+ exchange,
58
+ routingKey,
59
+ createQueueIfNotExists: options.createIfNotExists ?? true,
60
+ durable: options.durable ?? true,
61
+ exclusive: options.exclusive ?? false,
62
+ autoDelete: options.autoDelete ?? false,
63
+ messageTtl: options.messageTtl,
64
+ maxLength: options.maxLength,
65
+ expires: options.expires,
66
+ },
67
+ { overwrite: options.overwrite },
68
+ );
69
+ logOutcome(context, `queue '${name}'`, outcome);
70
+
71
+ writeConfigDoc(tree, configPath, doc);
72
+ return tree;
73
+ };
74
+ }
@@ -0,0 +1,17 @@
1
+ import { ExchangeType } from '../utils/broker-yaml.util';
2
+ export interface AddQueueOptions {
3
+ name?: string;
4
+ exchange?: string;
5
+ routingKey?: string;
6
+ createIfNotExists?: boolean;
7
+ durable?: boolean;
8
+ exclusive?: boolean;
9
+ autoDelete?: boolean;
10
+ messageTtl?: number;
11
+ maxLength?: number;
12
+ expires?: number;
13
+ createExchange?: boolean;
14
+ exchangeType?: ExchangeType;
15
+ overwrite?: boolean;
16
+ config?: string;
17
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../libs/rlb-nestjs-amqp/src/schematics/add-queue/schema.ts"],"names":[],"mappings":""}