@open-rlb/nestjs-amqp 2.1.0 → 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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-rlb/nestjs-amqp",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "",
5
5
  "author": "Giuseppe Riolo <riolo.giuseppe@gmail.com>",
6
6
  "private": false,
@@ -37,19 +37,19 @@
37
37
  "jwks-rsa": "^4.0.1",
38
38
  "reflect-metadata": "^0.2.2",
39
39
  "rxjs": "^7.8.2",
40
- "ws": "^8.21.0"
40
+ "ws": "^8.21.0",
41
+ "yaml": "^2.9.0"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@angular-devkit/core": "^22.0.1",
44
45
  "@angular-devkit/schematics": "^22.0.1",
45
- "@nestjs/mongoose": "^11.0.4",
46
- "@types/js-yaml": "^4.0.9",
47
- "mongoose": "^8.19.3",
48
46
  "@nestjs/cli": "^11.0.23",
47
+ "@nestjs/mongoose": "^11.0.4",
49
48
  "@nestjs/schematics": "^11.1.0",
50
49
  "@nestjs/testing": "^11.1.26",
51
50
  "@types/express": "^5.0.6",
52
51
  "@types/jest": "^30.0.0",
52
+ "@types/js-yaml": "^4.0.9",
53
53
  "@types/multer": "^2.1.0",
54
54
  "@types/node": "^25.9.3",
55
55
  "@types/supertest": "^7.2.0",
@@ -61,6 +61,7 @@
61
61
  "eslint-config-prettier": "^10.1.8",
62
62
  "eslint-plugin-prettier": "^5.5.6",
63
63
  "jest": "^30.4.2",
64
+ "mongoose": "^8.19.3",
64
65
  "prettier": "^3.8.4",
65
66
  "source-map-support": "^0.5.21",
66
67
  "supertest": "^7.2.2",
@@ -0,0 +1,3 @@
1
+ import { Rule } from '@angular-devkit/schematics';
2
+ import { AddAuthProviderOptions } from './schema';
3
+ export declare function main(options: AddAuthProviderOptions): Rule;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ const schematic_prompt_util_1 = require("../utils/schematic-prompt.util");
5
+ const yaml_config_util_1 = require("../utils/yaml-config.util");
6
+ const TYPES = ['jwt', 'jwks', 'basic', 'str-compare', 'none'];
7
+ const RELEVANT_FIELDS = {
8
+ jwt: ['issuer', 'algorithms', 'secret', 'audience', 'uidClaim', 'jwtMap'],
9
+ jwks: ['issuer', 'jwksUri', 'algorithms', 'audience', 'uidClaim', 'jwtMap', 'httpsAllowUnauthorized'],
10
+ basic: ['clientId', 'clientSecret', 'uidClaim'],
11
+ 'str-compare': ['secret', 'uidClaim'],
12
+ none: [],
13
+ };
14
+ function main(options) {
15
+ return async (tree, context) => {
16
+ const flagsProvided = options.name !== undefined;
17
+ const prompts = (0, schematic_prompt_util_1.loadPrompts)(context, flagsProvided);
18
+ const name = options.name || (await (0, schematic_prompt_util_1.askText)(prompts, 'Auth-provider name?', ''));
19
+ if (!name) {
20
+ context.logger.error('[rlb-amqp] add-auth-provider: a name is required.');
21
+ return tree;
22
+ }
23
+ const type = options.type || (await (0, schematic_prompt_util_1.askSelect)(prompts, 'Provider type?', TYPES, 'jwks'));
24
+ const headerPrefix = options.headerPrefix ?? 'X-GTW-AUTH-';
25
+ const configPath = (0, yaml_config_util_1.findConfigYaml)(tree, options.config);
26
+ const { doc, existed } = (0, yaml_config_util_1.readConfigDoc)(tree, configPath);
27
+ if (!existed)
28
+ context.logger.info(`[rlb-amqp] ${configPath} not found — creating it.`);
29
+ const candidates = {
30
+ uidClaim: options.uidClaim,
31
+ jwtMap: options.jwtMap,
32
+ algorithms: options.algorithms,
33
+ issuer: options.issuer,
34
+ jwksUri: options.jwksUri,
35
+ secret: options.secret,
36
+ audience: options.audience,
37
+ clientId: options.clientId,
38
+ clientSecret: options.clientSecret,
39
+ httpsAllowUnauthorized: options.httpsAllowUnauthorized,
40
+ };
41
+ const item = { name, type, headerPrefix };
42
+ for (const field of RELEVANT_FIELDS[type]) {
43
+ if (candidates[field] !== undefined)
44
+ item[field] = candidates[field];
45
+ }
46
+ if (type === 'jwt' || type === 'jwks') {
47
+ if (!options.algorithms || options.algorithms.length === 0) {
48
+ context.logger.warn('[rlb-amqp] add-auth-provider: algorithms is REQUIRED for jwt/jwks — verification will be DENIED until set (algorithm-confusion guard).');
49
+ }
50
+ if (!options.jwtMap || options.jwtMap.length === 0) {
51
+ context.logger.warn('[rlb-amqp] add-auth-provider: without jwtMap no identity claims are forwarded (uidClaim still needed for the action gate).');
52
+ }
53
+ }
54
+ const outcome = (0, yaml_config_util_1.upsertSeqItemByKey)(doc, ['auth-providers'], 'name', item, { overwrite: options.overwrite });
55
+ (0, schematic_prompt_util_1.logOutcome)(context, `auth-provider '${name}'`, outcome);
56
+ (0, yaml_config_util_1.writeConfigDoc)(tree, configPath, doc);
57
+ return tree;
58
+ };
59
+ }
60
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../libs/rlb-nestjs-amqp/src/schematics/add-auth-provider/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
2
+ import { parseDocument } from 'yaml';
3
+ import { main } from './index';
4
+ import { AddAuthProviderOptions } from './schema';
5
+
6
+ /**
7
+ * Schematics are tested by invoking the factory Rule directly against a Tree, NOT via
8
+ * SchematicTestRunner (its testing entrypoint eagerly loads `ora`, an ESM-only module jest cannot
9
+ * transform here). Passing `name` makes `flagsProvided` true, so no interactive prompt is attempted.
10
+ */
11
+ function makeCtx(warnings: string[] = []): SchematicContext {
12
+ return { logger: { info() {}, warn: (m: string) => warnings.push(m), error() {} } } as unknown as SchematicContext;
13
+ }
14
+
15
+ async function run(options: AddAuthProviderOptions, tree: Tree, ctx = makeCtx()): Promise<Tree> {
16
+ const rule = main(options) as Rule;
17
+ const result = await (rule as (t: Tree, c: SchematicContext) => Promise<Tree>)(tree, ctx);
18
+ return result || tree;
19
+ }
20
+
21
+ function read(tree: Tree, path = 'config/config.yaml'): string {
22
+ return tree.read(path)!.toString('utf-8');
23
+ }
24
+ function doc(tree: Tree) {
25
+ return parseDocument(read(tree));
26
+ }
27
+ function seedConfig(): Tree {
28
+ const tree = Tree.empty();
29
+ tree.create(
30
+ 'config/config.yaml',
31
+ ['# my config', 'auth-providers:', ' - name: existing', ' type: basic', 'gateway: {}', ''].join('\n'),
32
+ );
33
+ return tree;
34
+ }
35
+
36
+ describe('add-auth-provider schematic', () => {
37
+ it('appends a jwks provider with only type-relevant fields, preserving comments', async () => {
38
+ const tree = await run(
39
+ {
40
+ name: 'keycloak',
41
+ type: 'jwks',
42
+ jwksUri: 'https://kc/realms/x/protocol/openid-connect/certs',
43
+ algorithms: ['RS256'],
44
+ jwtMap: ['sub:userId'],
45
+ uidClaim: 'sub',
46
+ clientSecret: 'should-not-appear',
47
+ },
48
+ seedConfig(),
49
+ );
50
+ const providers = doc(tree).getIn(['auth-providers']) as any;
51
+ expect(providers.items.map((i: any) => String(i.get('name')))).toEqual(['existing', 'keycloak']);
52
+ const kc = providers.items[1];
53
+ expect(String(kc.get('type'))).toBe('jwks');
54
+ expect(String(kc.get('headerPrefix'))).toBe('X-GTW-AUTH-');
55
+ expect(kc.get('jwksUri')).toBeDefined();
56
+ // clientSecret is irrelevant for jwks and must be filtered out.
57
+ expect(kc.get('clientSecret')).toBeUndefined();
58
+ expect(read(tree)).toContain('# my config');
59
+ });
60
+
61
+ it('is idempotent: re-adding the same provider leaves a single entry', async () => {
62
+ let tree = await run({ name: 'demo', type: 'basic', clientSecret: 'secret' }, seedConfig());
63
+ tree = await run({ name: 'demo', type: 'basic', clientSecret: 'secret' }, tree);
64
+ const providers = doc(tree).getIn(['auth-providers']) as any;
65
+ const names = providers.items.map((i: any) => String(i.get('name')));
66
+ expect(names.filter((n: string) => n === 'demo')).toHaveLength(1);
67
+ });
68
+
69
+ it('warns when a jwt/jwks provider is missing algorithms and jwtMap (fails-closed invariant)', async () => {
70
+ const warnings: string[] = [];
71
+ await run({ name: 'insecure', type: 'jwt', issuer: 'https://issuer' }, seedConfig(), makeCtx(warnings));
72
+ expect(warnings.some((w) => w.includes('algorithms is REQUIRED'))).toBe(true);
73
+ expect(warnings.some((w) => w.includes('without jwtMap'))).toBe(true);
74
+ });
75
+
76
+ it('creates config.yaml when it does not exist', async () => {
77
+ const tree = await run({ name: 'demo', type: 'basic', clientSecret: 'secret', uidClaim: 'USERID' }, Tree.empty());
78
+ expect(tree.exists('config/config.yaml')).toBe(true);
79
+ const providers = doc(tree).getIn(['auth-providers']) as any;
80
+ expect(String(providers.items[0].get('name'))).toBe('demo');
81
+ expect(String(providers.items[0].get('uidClaim'))).toBe('USERID');
82
+ });
83
+ });
@@ -0,0 +1,83 @@
1
+ import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
2
+ import { askSelect, askText, loadPrompts, logOutcome } from '../utils/schematic-prompt.util';
3
+ import { findConfigYaml, readConfigDoc, upsertSeqItemByKey, writeConfigDoc } from '../utils/yaml-config.util';
4
+ import { AddAuthProviderOptions, AuthProviderType } from './schema';
5
+
6
+ const TYPES: readonly AuthProviderType[] = ['jwt', 'jwks', 'basic', 'str-compare', 'none'];
7
+
8
+ /**
9
+ * Optional fields that make sense per verification strategy. A field is only emitted when it is BOTH
10
+ * relevant to the chosen type AND provided — so a `basic` provider never carries a stray `jwksUri`,
11
+ * and a token provider never carries `clientSecret`. `name`/`type`/`headerPrefix` are always written.
12
+ */
13
+ const RELEVANT_FIELDS: Record<AuthProviderType, readonly string[]> = {
14
+ jwt: ['issuer', 'algorithms', 'secret', 'audience', 'uidClaim', 'jwtMap'],
15
+ jwks: ['issuer', 'jwksUri', 'algorithms', 'audience', 'uidClaim', 'jwtMap', 'httpsAllowUnauthorized'],
16
+ basic: ['clientId', 'clientSecret', 'uidClaim'],
17
+ 'str-compare': ['secret', 'uidClaim'],
18
+ none: [],
19
+ };
20
+
21
+ /**
22
+ * `add-auth-provider <name>` — idempotent upsert of a top-level `auth-providers[]` entry,
23
+ * comment-preserving. The provider name is what gateway paths/events reference via `auth`.
24
+ * Only the fields relevant to the selected verification `type` are written (see RELEVANT_FIELDS).
25
+ */
26
+ export function main(options: AddAuthProviderOptions): Rule {
27
+ return async (tree: Tree, context: SchematicContext) => {
28
+ const flagsProvided = options.name !== undefined;
29
+ const prompts = loadPrompts(context, flagsProvided);
30
+
31
+ const name = options.name || (await askText(prompts, 'Auth-provider name?', ''));
32
+ if (!name) {
33
+ context.logger.error('[rlb-amqp] add-auth-provider: a name is required.');
34
+ return tree;
35
+ }
36
+
37
+ const type = (options.type as AuthProviderType) || (await askSelect(prompts, 'Provider type?', TYPES, 'jwks'));
38
+ const headerPrefix = options.headerPrefix ?? 'X-GTW-AUTH-';
39
+
40
+ const configPath = findConfigYaml(tree, options.config);
41
+ const { doc, existed } = readConfigDoc(tree, configPath);
42
+ if (!existed) context.logger.info(`[rlb-amqp] ${configPath} not found — creating it.`);
43
+
44
+ // All candidate optional values; the per-type filter below keeps only the relevant, defined ones.
45
+ const candidates: Record<string, unknown> = {
46
+ uidClaim: options.uidClaim,
47
+ jwtMap: options.jwtMap,
48
+ algorithms: options.algorithms,
49
+ issuer: options.issuer,
50
+ jwksUri: options.jwksUri,
51
+ secret: options.secret,
52
+ audience: options.audience,
53
+ clientId: options.clientId,
54
+ clientSecret: options.clientSecret,
55
+ httpsAllowUnauthorized: options.httpsAllowUnauthorized,
56
+ };
57
+
58
+ const item: Record<string, unknown> = { name, type, headerPrefix };
59
+ for (const field of RELEVANT_FIELDS[type]) {
60
+ if (candidates[field] !== undefined) item[field] = candidates[field];
61
+ }
62
+
63
+ // Fails-closed invariants for token providers: without these the gateway can't safely verify.
64
+ if (type === 'jwt' || type === 'jwks') {
65
+ if (!options.algorithms || options.algorithms.length === 0) {
66
+ context.logger.warn(
67
+ '[rlb-amqp] add-auth-provider: algorithms is REQUIRED for jwt/jwks — verification will be DENIED until set (algorithm-confusion guard).',
68
+ );
69
+ }
70
+ if (!options.jwtMap || options.jwtMap.length === 0) {
71
+ context.logger.warn(
72
+ '[rlb-amqp] add-auth-provider: without jwtMap no identity claims are forwarded (uidClaim still needed for the action gate).',
73
+ );
74
+ }
75
+ }
76
+
77
+ const outcome = upsertSeqItemByKey(doc, ['auth-providers'], 'name', item, { overwrite: options.overwrite });
78
+ logOutcome(context, `auth-provider '${name}'`, outcome);
79
+
80
+ writeConfigDoc(tree, configPath, doc);
81
+ return tree;
82
+ };
83
+ }
@@ -0,0 +1,18 @@
1
+ export type AuthProviderType = 'jwt' | 'jwks' | 'basic' | 'str-compare' | 'none';
2
+ export interface AddAuthProviderOptions {
3
+ name?: string;
4
+ type?: AuthProviderType;
5
+ headerPrefix?: string;
6
+ uidClaim?: string;
7
+ jwtMap?: string[];
8
+ algorithms?: string[];
9
+ issuer?: string;
10
+ jwksUri?: string;
11
+ secret?: string;
12
+ audience?: string;
13
+ clientId?: string;
14
+ clientSecret?: string;
15
+ httpsAllowUnauthorized?: boolean;
16
+ overwrite?: boolean;
17
+ config?: string;
18
+ }
@@ -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-auth-provider/schema.ts"],"names":[],"mappings":""}
@@ -0,0 +1,59 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "SchematicsRlbAmqpAddAuthProvider",
4
+ "title": "Add an auth-providers[] entry to config.yaml",
5
+ "type": "object",
6
+ "properties": {
7
+ "name": {
8
+ "type": "string",
9
+ "description": "Provider name (the key gateway paths/events reference via auth).",
10
+ "x-prompt": "Auth-provider name?",
11
+ "$default": { "$source": "argv", "index": 0 }
12
+ },
13
+ "type": {
14
+ "type": "string",
15
+ "enum": ["jwt", "jwks", "basic", "str-compare", "none"],
16
+ "default": "jwks",
17
+ "description": "Verification strategy."
18
+ },
19
+ "headerPrefix": {
20
+ "type": "string",
21
+ "default": "X-GTW-AUTH-",
22
+ "description": "Prefix for the identity headers forwarded downstream."
23
+ },
24
+ "uidClaim": {
25
+ "type": "string",
26
+ "description": "Claim whose value becomes the authenticated userId (drives the ACL/action gate)."
27
+ },
28
+ "jwtMap": {
29
+ "type": "array",
30
+ "items": { "type": "string" },
31
+ "description": "Claim->header maps as src:dest (jwt/jwks)."
32
+ },
33
+ "algorithms": {
34
+ "type": "array",
35
+ "items": { "type": "string" },
36
+ "description": "Allowed signature algorithms (jwt/jwks) — REQUIRED to verify."
37
+ },
38
+ "issuer": { "type": "string", "description": "Expected token issuer (jwt/jwks)." },
39
+ "jwksUri": { "type": "string", "description": "JWKS endpoint to fetch signing keys from (jwks)." },
40
+ "secret": { "type": "string", "description": "HMAC/shared secret (jwt symmetric, or str-compare)." },
41
+ "audience": { "type": "string", "description": "Expected token audience (jwt/jwks)." },
42
+ "clientId": { "type": "string", "description": "Client id (basic)." },
43
+ "clientSecret": { "type": "string", "description": "Client secret / password (basic)." },
44
+ "httpsAllowUnauthorized": {
45
+ "type": "boolean",
46
+ "description": "Accept self-signed TLS when fetching the JWKS (jwks) — DEV ONLY."
47
+ },
48
+ "overwrite": {
49
+ "type": "boolean",
50
+ "default": false,
51
+ "description": "Update the entry if it already exists (default: leave it untouched)."
52
+ },
53
+ "config": {
54
+ "type": "string",
55
+ "description": "Path to config.yaml (default: auto-detected)."
56
+ }
57
+ },
58
+ "required": []
59
+ }
@@ -0,0 +1,35 @@
1
+ /** Auth-provider verification strategy. Mirrors HandlerAuthConfig['type']. */
2
+ export type AuthProviderType = 'jwt' | 'jwks' | 'basic' | 'str-compare' | 'none';
3
+
4
+ export interface AddAuthProviderOptions {
5
+ /** Provider name (the key gateway paths/events reference via `auth`). Prompted when omitted. */
6
+ name?: string;
7
+ /** Verification strategy. Default: jwks. */
8
+ type?: AuthProviderType;
9
+ /** Prefix for the identity headers the gateway forwards downstream. Default: X-GTW-AUTH-. */
10
+ headerPrefix?: string;
11
+ /** Claim whose value becomes the authenticated userId (drives the ACL/action gate). */
12
+ uidClaim?: string;
13
+ /** Claim→header maps as `src:dest` (jwt/jwks): which token claims are forwarded downstream. */
14
+ jwtMap?: string[];
15
+ /** Allowed signature algorithms (jwt/jwks) — REQUIRED to verify (algorithm-confusion guard). */
16
+ algorithms?: string[];
17
+ /** Expected token issuer (jwt/jwks). */
18
+ issuer?: string;
19
+ /** JWKS endpoint to fetch signing keys from (jwks). */
20
+ jwksUri?: string;
21
+ /** HMAC/shared secret (jwt with symmetric alg, or str-compare). */
22
+ secret?: string;
23
+ /** Expected token audience (jwt/jwks). */
24
+ audience?: string;
25
+ /** Client id (basic). */
26
+ clientId?: string;
27
+ /** Client secret / password (basic). */
28
+ clientSecret?: string;
29
+ /** Accept self-signed TLS when fetching the JWKS (jwks) — DEV ONLY. */
30
+ httpsAllowUnauthorized?: boolean;
31
+ /** Update the entry when it already exists (default: leave it untouched). */
32
+ overwrite?: boolean;
33
+ /** Path to config.yaml (default: auto-detected, typically config/config.yaml). */
34
+ config?: string;
35
+ }
@@ -0,0 +1,3 @@
1
+ import { Rule } from '@angular-devkit/schematics';
2
+ import { AddExchangeOptions } from './schema';
3
+ export declare function main(options: AddExchangeOptions): Rule;
@@ -0,0 +1,49 @@
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, 'Exchange name?', ''));
13
+ if (!name) {
14
+ context.logger.error('[rlb-amqp] add-exchange: a name is required.');
15
+ return tree;
16
+ }
17
+ const type = options.type || (await (0, schematic_prompt_util_1.askSelect)(prompts, 'Exchange type?', TYPES, 'direct'));
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
+ const outcome = (0, broker_yaml_util_1.ensureExchange)(doc, {
23
+ name,
24
+ type,
25
+ createExchangeIfNotExists: options.createIfNotExists ?? true,
26
+ durable: options.durable ?? true,
27
+ autoDelete: options.autoDelete ?? false,
28
+ internal: options.internal ?? false,
29
+ }, { overwrite: options.overwrite });
30
+ (0, schematic_prompt_util_1.logOutcome)(context, `exchange '${name}'`, outcome);
31
+ const withQueue = options.withQueue ?? (await (0, schematic_prompt_util_1.askConfirm)(prompts, `Create a queue bound to '${name}'?`, false));
32
+ if (withQueue) {
33
+ const queueName = options.queueName || (await (0, schematic_prompt_util_1.askText)(prompts, 'Queue name?', name));
34
+ const needsRoutingKey = type === 'topic';
35
+ const routingKey = needsRoutingKey ? options.routingKey || queueName : options.routingKey;
36
+ const qOutcome = (0, broker_yaml_util_1.ensureQueue)(doc, {
37
+ name: queueName,
38
+ exchange: name,
39
+ routingKey,
40
+ createQueueIfNotExists: true,
41
+ durable: true,
42
+ });
43
+ (0, schematic_prompt_util_1.logOutcome)(context, `queue '${queueName}'`, qOutcome);
44
+ }
45
+ (0, yaml_config_util_1.writeConfigDoc)(tree, configPath, doc);
46
+ return tree;
47
+ };
48
+ }
49
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../libs/rlb-nestjs-amqp/src/schematics/add-exchange/index.ts"],"names":[],"mappings":";;AAcA,oBAkDC;AA/DD,gEAAsF;AACtF,0EAAyG;AACzG,gEAA0F;AAG1F,MAAM,KAAK,GAA4B,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;AAQhF,SAAgB,IAAI,CAAC,OAA2B;IAC9C,OAAO,KAAK,EAAE,IAAU,EAAE,OAAyB,EAAE,EAAE;QACrD,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC;QACjD,MAAM,OAAO,GAAG,IAAA,mCAAW,EAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QAEpD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,CAAC,MAAM,IAAA,+BAAO,EAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,CAAC,CAAC,CAAC;QAC5E,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;YACrE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,IAAI,GAAI,OAAO,CAAC,IAAqB,IAAI,CAAC,MAAM,IAAA,iCAAS,EAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;QAE7G,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,OAAO,GAAG,IAAA,iCAAc,EAC5B,GAAG,EACH;YACE,IAAI;YACJ,IAAI;YACJ,yBAAyB,EAAE,OAAO,CAAC,iBAAiB,IAAI,IAAI;YAC5D,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,IAAI;YAChC,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,KAAK;YACvC,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,KAAK;SACpC,EACD,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CACjC,CAAC;QACF,IAAA,kCAAU,EAAC,OAAO,EAAE,aAAa,IAAI,GAAG,EAAE,OAAO,CAAC,CAAC;QAGnD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,CAAC,MAAM,IAAA,kCAAU,EAAC,OAAO,EAAE,4BAA4B,IAAI,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;QAChH,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,CAAC,MAAM,IAAA,+BAAO,EAAC,OAAO,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;YACrF,MAAM,eAAe,GAAG,IAAI,KAAK,OAAO,CAAC;YACzC,MAAM,UAAU,GAAG,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,IAAI,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;YAC1F,MAAM,QAAQ,GAAG,IAAA,8BAAW,EAAC,GAAG,EAAE;gBAChC,IAAI,EAAE,SAAS;gBACf,QAAQ,EAAE,IAAI;gBACd,UAAU;gBACV,sBAAsB,EAAE,IAAI;gBAC5B,OAAO,EAAE,IAAI;aACd,CAAC,CAAC;YACH,IAAA,kCAAU,EAAC,OAAO,EAAE,UAAU,SAAS,GAAG,EAAE,QAAQ,CAAC,CAAC;QACxD,CAAC;QAED,IAAA,iCAAc,EAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,65 @@
1
+ import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
2
+ import { parseDocument } from 'yaml';
3
+ import { main } from './index';
4
+ import { AddExchangeOptions } from './schema';
5
+
6
+ /**
7
+ * Schematics are tested by invoking the factory Rule directly against a Tree, NOT via
8
+ * SchematicTestRunner (its testing entrypoint eagerly loads `ora`, an ESM-only module jest cannot
9
+ * transform here). Passing `name` makes `flagsProvided` true, so no interactive prompt is attempted.
10
+ */
11
+ const ctx = { logger: { info() {}, warn() {}, error() {} } } as unknown as SchematicContext;
12
+
13
+ async function run(options: AddExchangeOptions, 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-exchange schematic', () => {
35
+ it('appends a new exchange, preserving the existing one and comments', async () => {
36
+ const tree = await run({ name: 'events', type: 'fanout' }, seedConfig());
37
+ const exchanges = doc(tree).getIn(['broker', 'exchanges']) as any;
38
+ expect(exchanges.items.map((i: any) => String(i.get('name')))).toEqual(['rlb', 'events']);
39
+ expect(String(exchanges.items[1].get('type'))).toBe('fanout');
40
+ expect(read(tree)).toContain('# my config');
41
+ });
42
+
43
+ it('is idempotent: re-adding the same exchange leaves a single entry', async () => {
44
+ let tree = await run({ name: 'events', type: 'fanout' }, seedConfig());
45
+ tree = await run({ name: 'events', type: 'fanout' }, tree);
46
+ const exchanges = doc(tree).getIn(['broker', 'exchanges']) as any;
47
+ const names = exchanges.items.map((i: any) => String(i.get('name')));
48
+ expect(names.filter((n: string) => n === 'events')).toHaveLength(1);
49
+ });
50
+
51
+ it('with withQueue creates a bound queue (routingKey only for topic exchanges)', async () => {
52
+ const tree = await run({ name: 'jobs', type: 'topic', withQueue: true, queueName: 'jobs-q' }, seedConfig());
53
+ const queue = (doc(tree).getIn(['broker', 'queues']) as any).items[0];
54
+ expect(String(queue.get('name'))).toBe('jobs-q');
55
+ expect(String(queue.get('exchange'))).toBe('jobs');
56
+ expect(String(queue.get('routingKey'))).toBe('jobs-q');
57
+ });
58
+
59
+ it('creates config.yaml when it does not exist', async () => {
60
+ const tree = await run({ name: 'rlb', type: 'direct' }, Tree.empty());
61
+ expect(tree.exists('config/config.yaml')).toBe(true);
62
+ const exchanges = doc(tree).getIn(['broker', 'exchanges']) as any;
63
+ expect(String(exchanges.items[0].get('name'))).toBe('rlb');
64
+ });
65
+ });
@@ -0,0 +1,65 @@
1
+ import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
2
+ import { ensureExchange, ensureQueue, ExchangeType } from '../utils/broker-yaml.util';
3
+ import { askConfirm, askSelect, askText, loadPrompts, logOutcome } from '../utils/schematic-prompt.util';
4
+ import { findConfigYaml, readConfigDoc, writeConfigDoc } from '../utils/yaml-config.util';
5
+ import { AddExchangeOptions } from './schema';
6
+
7
+ const TYPES: readonly ExchangeType[] = ['direct', 'topic', 'fanout', 'headers'];
8
+
9
+ /**
10
+ * `add-exchange <name>` — idempotent upsert of a `broker.exchanges[]` entry, comment-preserving.
11
+ * When interactive it also offers to create a queue bound to the exchange (default queue name =
12
+ * exchange name). Creating that queue calls the ensureQueue DATA helper directly — NOT the
13
+ * add-queue schematic — so there is no "create exchange? → create queue? → …" prompt ping-pong.
14
+ */
15
+ export function main(options: AddExchangeOptions): Rule {
16
+ return async (tree: Tree, context: SchematicContext) => {
17
+ const flagsProvided = options.name !== undefined;
18
+ const prompts = loadPrompts(context, flagsProvided);
19
+
20
+ const name = options.name || (await askText(prompts, 'Exchange name?', ''));
21
+ if (!name) {
22
+ context.logger.error('[rlb-amqp] add-exchange: a name is required.');
23
+ return tree;
24
+ }
25
+
26
+ const type = (options.type as ExchangeType) || (await askSelect(prompts, 'Exchange type?', TYPES, 'direct'));
27
+
28
+ const configPath = findConfigYaml(tree, options.config);
29
+ const { doc, existed } = readConfigDoc(tree, configPath);
30
+ if (!existed) context.logger.info(`[rlb-amqp] ${configPath} not found — creating it.`);
31
+
32
+ const outcome = ensureExchange(
33
+ doc,
34
+ {
35
+ name,
36
+ type,
37
+ createExchangeIfNotExists: options.createIfNotExists ?? true,
38
+ durable: options.durable ?? true,
39
+ autoDelete: options.autoDelete ?? false,
40
+ internal: options.internal ?? false,
41
+ },
42
+ { overwrite: options.overwrite },
43
+ );
44
+ logOutcome(context, `exchange '${name}'`, outcome);
45
+
46
+ // Offer to bind a queue. In non-interactive mode this fires only when --with-queue was passed.
47
+ const withQueue = options.withQueue ?? (await askConfirm(prompts, `Create a queue bound to '${name}'?`, false));
48
+ if (withQueue) {
49
+ const queueName = options.queueName || (await askText(prompts, 'Queue name?', name));
50
+ const needsRoutingKey = type === 'topic';
51
+ const routingKey = needsRoutingKey ? options.routingKey || queueName : options.routingKey;
52
+ const qOutcome = ensureQueue(doc, {
53
+ name: queueName,
54
+ exchange: name,
55
+ routingKey,
56
+ createQueueIfNotExists: true,
57
+ durable: true,
58
+ });
59
+ logOutcome(context, `queue '${queueName}'`, qOutcome);
60
+ }
61
+
62
+ writeConfigDoc(tree, configPath, doc);
63
+ return tree;
64
+ };
65
+ }
@@ -0,0 +1,14 @@
1
+ import { ExchangeType } from '../utils/broker-yaml.util';
2
+ export interface AddExchangeOptions {
3
+ name?: string;
4
+ type?: ExchangeType;
5
+ createIfNotExists?: boolean;
6
+ durable?: boolean;
7
+ autoDelete?: boolean;
8
+ internal?: boolean;
9
+ withQueue?: boolean;
10
+ queueName?: string;
11
+ routingKey?: string;
12
+ overwrite?: boolean;
13
+ config?: string;
14
+ }
@@ -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-exchange/schema.ts"],"names":[],"mappings":""}