@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.
- package/package.json +6 -5
- package/schematics/add-auth-provider/index.d.ts +3 -0
- package/schematics/add-auth-provider/index.js +60 -0
- package/schematics/add-auth-provider/index.js.map +1 -0
- package/schematics/add-auth-provider/index.spec.ts +83 -0
- package/schematics/add-auth-provider/index.ts +83 -0
- package/schematics/add-auth-provider/schema.d.ts +18 -0
- package/schematics/add-auth-provider/schema.js +3 -0
- package/schematics/add-auth-provider/schema.js.map +1 -0
- package/schematics/add-auth-provider/schema.json +59 -0
- package/schematics/add-auth-provider/schema.ts +35 -0
- package/schematics/add-exchange/index.d.ts +3 -0
- package/schematics/add-exchange/index.js +49 -0
- package/schematics/add-exchange/index.js.map +1 -0
- package/schematics/add-exchange/index.spec.ts +65 -0
- package/schematics/add-exchange/index.ts +65 -0
- package/schematics/add-exchange/schema.d.ts +14 -0
- package/schematics/add-exchange/schema.js +3 -0
- package/schematics/add-exchange/schema.js.map +1 -0
- package/schematics/add-exchange/schema.json +50 -0
- package/schematics/add-exchange/schema.ts +26 -0
- package/schematics/add-exchange-binding/index.d.ts +3 -0
- package/schematics/add-exchange-binding/index.js +53 -0
- package/schematics/add-exchange-binding/index.js.map +1 -0
- package/schematics/add-exchange-binding/index.spec.ts +73 -0
- package/schematics/add-exchange-binding/index.ts +64 -0
- package/schematics/add-exchange-binding/schema.d.ts +8 -0
- package/schematics/add-exchange-binding/schema.js +3 -0
- package/schematics/add-exchange-binding/schema.js.map +1 -0
- package/schematics/add-exchange-binding/schema.json +36 -0
- package/schematics/add-exchange-binding/schema.ts +14 -0
- package/schematics/add-gateway/index.d.ts +4 -0
- package/schematics/add-gateway/index.js +105 -0
- package/schematics/add-gateway/index.js.map +1 -0
- package/schematics/add-gateway/index.spec.ts +163 -0
- package/schematics/add-gateway/index.ts +141 -0
- package/schematics/add-gateway/paths.d.ts +12 -0
- package/schematics/add-gateway/paths.js +87 -0
- package/schematics/add-gateway/paths.js.map +1 -0
- package/schematics/add-gateway/paths.ts +103 -0
- package/schematics/add-gateway/schema.d.ts +13 -0
- package/schematics/add-gateway/schema.js +3 -0
- package/schematics/add-gateway/schema.js.map +1 -0
- package/schematics/add-gateway/schema.json +63 -0
- package/schematics/add-gateway/schema.ts +24 -0
- package/schematics/add-gateway/wiring.d.ts +14 -0
- package/schematics/add-gateway/wiring.js +102 -0
- package/schematics/add-gateway/wiring.js.map +1 -0
- package/schematics/add-gateway/wiring.ts +115 -0
- package/schematics/add-metrics-topic/index.d.ts +3 -0
- package/schematics/add-metrics-topic/index.js +31 -0
- package/schematics/add-metrics-topic/index.js.map +1 -0
- package/schematics/add-metrics-topic/index.spec.ts +79 -0
- package/schematics/add-metrics-topic/index.ts +49 -0
- package/schematics/add-metrics-topic/schema.d.ts +10 -0
- package/schematics/add-metrics-topic/schema.js +3 -0
- package/schematics/add-metrics-topic/schema.js.map +1 -0
- package/schematics/add-metrics-topic/schema.json +48 -0
- package/schematics/add-metrics-topic/schema.ts +18 -0
- package/schematics/add-queue/index.d.ts +3 -0
- package/schematics/add-queue/index.js +48 -0
- package/schematics/add-queue/index.js.map +1 -0
- package/schematics/add-queue/index.spec.ts +75 -0
- package/schematics/add-queue/index.ts +74 -0
- package/schematics/add-queue/schema.d.ts +17 -0
- package/schematics/add-queue/schema.js +3 -0
- package/schematics/add-queue/schema.js.map +1 -0
- package/schematics/add-queue/schema.json +54 -0
- package/schematics/add-queue/schema.ts +32 -0
- package/schematics/add-reply-queue/index.d.ts +3 -0
- package/schematics/add-reply-queue/index.js +26 -0
- package/schematics/add-reply-queue/index.js.map +1 -0
- package/schematics/add-reply-queue/index.spec.ts +69 -0
- package/schematics/add-reply-queue/index.ts +33 -0
- package/schematics/add-reply-queue/schema.d.ts +6 -0
- package/schematics/add-reply-queue/schema.js +3 -0
- package/schematics/add-reply-queue/schema.js.map +1 -0
- package/schematics/add-reply-queue/schema.json +28 -0
- package/schematics/add-reply-queue/schema.ts +10 -0
- package/schematics/add-route/index.d.ts +3 -0
- package/schematics/add-route/index.js +57 -0
- package/schematics/add-route/index.js.map +1 -0
- package/schematics/add-route/index.spec.ts +75 -0
- package/schematics/add-route/index.ts +74 -0
- package/schematics/add-route/schema.d.ts +22 -0
- package/schematics/add-route/schema.js +3 -0
- package/schematics/add-route/schema.js.map +1 -0
- package/schematics/add-route/schema.json +74 -0
- package/schematics/add-route/schema.ts +43 -0
- package/schematics/add-topic/index.d.ts +3 -0
- package/schematics/add-topic/index.js +69 -0
- package/schematics/add-topic/index.js.map +1 -0
- package/schematics/add-topic/index.spec.ts +100 -0
- package/schematics/add-topic/index.ts +104 -0
- package/schematics/add-topic/schema.d.ts +18 -0
- package/schematics/add-topic/schema.js +3 -0
- package/schematics/add-topic/schema.js.map +1 -0
- package/schematics/add-topic/schema.json +63 -0
- package/schematics/add-topic/schema.ts +34 -0
- package/schematics/add-ws-event/index.d.ts +3 -0
- package/schematics/add-ws-event/index.js +53 -0
- package/schematics/add-ws-event/index.js.map +1 -0
- package/schematics/add-ws-event/index.spec.ts +85 -0
- package/schematics/add-ws-event/index.ts +72 -0
- package/schematics/add-ws-event/schema.d.ts +17 -0
- package/schematics/add-ws-event/schema.js +3 -0
- package/schematics/add-ws-event/schema.js.map +1 -0
- package/schematics/add-ws-event/schema.json +53 -0
- package/schematics/add-ws-event/schema.ts +33 -0
- package/schematics/collection.json +81 -1
- package/schematics/configure-broker/index.d.ts +3 -0
- package/schematics/configure-broker/index.js +41 -0
- package/schematics/configure-broker/index.js.map +1 -0
- package/schematics/configure-broker/index.spec.ts +62 -0
- package/schematics/configure-broker/index.ts +51 -0
- package/schematics/configure-broker/schema.d.ts +11 -0
- package/schematics/configure-broker/schema.js +3 -0
- package/schematics/configure-broker/schema.js.map +1 -0
- package/schematics/configure-broker/schema.json +47 -0
- package/schematics/configure-broker/schema.ts +20 -0
- package/schematics/enable-load-config/index.d.ts +3 -0
- package/schematics/enable-load-config/index.js +32 -0
- package/schematics/enable-load-config/index.js.map +1 -0
- package/schematics/enable-load-config/index.spec.ts +61 -0
- package/schematics/enable-load-config/index.ts +39 -0
- package/schematics/enable-load-config/schema.d.ts +8 -0
- package/schematics/enable-load-config/schema.js +3 -0
- package/schematics/enable-load-config/schema.js.map +1 -0
- package/schematics/enable-load-config/schema.json +36 -0
- package/schematics/enable-load-config/schema.ts +14 -0
- package/schematics/enable-retry/index.d.ts +3 -0
- package/schematics/enable-retry/index.js +51 -0
- package/schematics/enable-retry/index.js.map +1 -0
- package/schematics/enable-retry/index.spec.ts +73 -0
- package/schematics/enable-retry/index.ts +62 -0
- package/schematics/enable-retry/schema.d.ts +13 -0
- package/schematics/enable-retry/schema.js +3 -0
- package/schematics/enable-retry/schema.js.map +1 -0
- package/schematics/enable-retry/schema.json +64 -0
- package/schematics/enable-retry/schema.ts +24 -0
- package/schematics/enable-route-discovery/index.d.ts +3 -0
- package/schematics/enable-route-discovery/index.js +36 -0
- package/schematics/enable-route-discovery/index.js.map +1 -0
- package/schematics/enable-route-discovery/index.spec.ts +55 -0
- package/schematics/enable-route-discovery/index.ts +48 -0
- package/schematics/enable-route-discovery/schema.d.ts +9 -0
- package/schematics/enable-route-discovery/schema.js +3 -0
- package/schematics/enable-route-discovery/schema.js.map +1 -0
- package/schematics/enable-route-discovery/schema.json +44 -0
- package/schematics/enable-route-discovery/schema.ts +16 -0
- package/schematics/harden-gateway/index.d.ts +3 -0
- package/schematics/harden-gateway/index.js +93 -0
- package/schematics/harden-gateway/index.js.map +1 -0
- package/schematics/harden-gateway/index.spec.ts +105 -0
- package/schematics/harden-gateway/index.ts +108 -0
- package/schematics/harden-gateway/schema.d.ts +11 -0
- package/schematics/harden-gateway/schema.js +3 -0
- package/schematics/harden-gateway/schema.js.map +1 -0
- package/schematics/harden-gateway/schema.json +47 -0
- package/schematics/harden-gateway/schema.ts +20 -0
- package/schematics/nest-add/index.js +55 -832
- package/schematics/nest-add/index.js.map +1 -1
- package/schematics/nest-add/index.spec.ts +73 -0
- package/schematics/nest-add/index.ts +76 -917
- package/schematics/nest-add/init.schema.d.ts +1 -0
- package/schematics/nest-add/init.schema.ts +3 -0
- package/schematics/nest-add/schema.json +4 -43
- package/schematics/set-connection-name/index.d.ts +3 -0
- package/schematics/set-connection-name/index.js +34 -0
- package/schematics/set-connection-name/index.js.map +1 -0
- package/schematics/set-connection-name/index.spec.ts +48 -0
- package/schematics/set-connection-name/index.ts +42 -0
- package/schematics/set-connection-name/schema.d.ts +5 -0
- package/schematics/set-connection-name/schema.js +3 -0
- package/schematics/set-connection-name/schema.js.map +1 -0
- package/schematics/set-connection-name/schema.json +24 -0
- package/schematics/set-connection-name/schema.ts +8 -0
- package/schematics/utils/broker-yaml.util.d.ts +53 -0
- package/schematics/utils/broker-yaml.util.js +91 -0
- package/schematics/utils/broker-yaml.util.js.map +1 -0
- package/schematics/utils/broker-yaml.util.ts +172 -0
- package/schematics/utils/nest-wiring.util.d.ts +30 -0
- package/schematics/utils/nest-wiring.util.js +170 -0
- package/schematics/utils/nest-wiring.util.js.map +1 -0
- package/schematics/utils/nest-wiring.util.ts +211 -0
- package/schematics/utils/schematic-prompt.util.d.ts +7 -0
- package/schematics/utils/schematic-prompt.util.js +57 -0
- package/schematics/utils/schematic-prompt.util.js.map +1 -0
- package/schematics/utils/schematic-prompt.util.ts +82 -0
- package/schematics/utils/yaml-config.util.d.ts +22 -0
- package/schematics/utils/yaml-config.util.js +119 -0
- package/schematics/utils/yaml-config.util.js.map +1 -0
- package/schematics/utils/yaml-config.util.ts +167 -0
- /package/schematics/{nest-add → add-gateway}/files/acl/src/cache/in-memory-acl-store.ts +0 -0
- /package/schematics/{nest-add → add-gateway}/files/acl/src/modules/database/repository/acl.repository.ts +0 -0
- /package/schematics/{nest-add → add-gateway}/files/db-core/src/modules/database/repository/in-memory-collection.ts +0 -0
- /package/schematics/{nest-add → add-gateway}/files/gateway-admin/src/modules/database/repository/gateway.repository.ts +0 -0
- /package/schematics/{nest-add → add-gateway}/files/gateway-admin/src/modules/database/repository/route-sync.repository.ts +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
|
|
2
|
+
import { askText, loadPrompts, logOutcome } from '../utils/schematic-prompt.util';
|
|
3
|
+
import { findConfigYaml, readConfigDoc, setIn, writeConfigDoc } from '../utils/yaml-config.util';
|
|
4
|
+
import { EnableLoadConfigOptions } from './schema';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `enable-load-config` — add `gateway.loadConfig.paths` (and optionally `.events`) so the gateway
|
|
8
|
+
* pulls DB-managed routes/events and merges them with the YAML paths on every (re)load. Idempotent
|
|
9
|
+
* + comment-preserving.
|
|
10
|
+
*/
|
|
11
|
+
export function main(options: EnableLoadConfigOptions): Rule {
|
|
12
|
+
return async (tree: Tree, context: SchematicContext) => {
|
|
13
|
+
const flagsProvided = options.pathsTopic !== undefined || options.eventsTopic !== undefined;
|
|
14
|
+
const prompts = loadPrompts(context, flagsProvided);
|
|
15
|
+
|
|
16
|
+
const pathsTopic = options.pathsTopic || (await askText(prompts, 'Paths export topic?', 'rlb-gateway-admin'));
|
|
17
|
+
const pathsAction = options.pathsAction || (await askText(prompts, 'Paths export action?', 'gw-path-export'));
|
|
18
|
+
|
|
19
|
+
const configPath = findConfigYaml(tree, options.config);
|
|
20
|
+
const { doc, existed } = readConfigDoc(tree, configPath);
|
|
21
|
+
if (!existed) context.logger.info(`[rlb-amqp] ${configPath} not found — creating it.`);
|
|
22
|
+
|
|
23
|
+
const pOutcome = setIn(doc, ['gateway', 'loadConfig', 'paths'], { topic: pathsTopic, action: pathsAction });
|
|
24
|
+
logOutcome(context, 'gateway.loadConfig.paths', pOutcome);
|
|
25
|
+
|
|
26
|
+
if (options.eventsTopic && options.eventsAction) {
|
|
27
|
+
const eOutcome = setIn(doc, ['gateway', 'loadConfig', 'events'], {
|
|
28
|
+
topic: options.eventsTopic,
|
|
29
|
+
action: options.eventsAction,
|
|
30
|
+
});
|
|
31
|
+
logOutcome(context, 'gateway.loadConfig.events', eOutcome);
|
|
32
|
+
} else if (options.eventsTopic || options.eventsAction) {
|
|
33
|
+
context.logger.warn('[rlb-amqp] enable-load-config: events needs BOTH eventsTopic and eventsAction — skipped.');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
writeConfigDoc(tree, configPath, doc);
|
|
37
|
+
return tree;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../libs/rlb-nestjs-amqp/src/schematics/enable-load-config/schema.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "SchematicsRlbAmqpEnableLoadConfig",
|
|
4
|
+
"title": "Add gateway.loadConfig (pull DB-managed routes/events on (re)load)",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"pathsTopic": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"default": "rlb-gateway-admin",
|
|
10
|
+
"description": "Topic the gateway pulls DB-managed paths from."
|
|
11
|
+
},
|
|
12
|
+
"pathsAction": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"default": "gw-path-export",
|
|
15
|
+
"description": "Action for the paths export."
|
|
16
|
+
},
|
|
17
|
+
"eventsTopic": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Topic the gateway pulls DB-managed events from (optional; requires eventsAction)."
|
|
20
|
+
},
|
|
21
|
+
"eventsAction": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Action for the events export (optional; requires eventsTopic)."
|
|
24
|
+
},
|
|
25
|
+
"overwrite": {
|
|
26
|
+
"type": "boolean",
|
|
27
|
+
"default": false,
|
|
28
|
+
"description": "Update the entries if they already exist."
|
|
29
|
+
},
|
|
30
|
+
"config": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Path to config.yaml (default: auto-detected)."
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"required": []
|
|
36
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface EnableLoadConfigOptions {
|
|
2
|
+
/** Topic the gateway pulls DB-managed paths from. Default: 'rlb-gateway-admin'. */
|
|
3
|
+
pathsTopic?: string;
|
|
4
|
+
/** Action for the paths export. Default: 'gw-path-export'. */
|
|
5
|
+
pathsAction?: string;
|
|
6
|
+
/** Topic the gateway pulls DB-managed events from (optional; both events options required together). */
|
|
7
|
+
eventsTopic?: string;
|
|
8
|
+
/** Action for the events export (optional; both events options required together). */
|
|
9
|
+
eventsAction?: string;
|
|
10
|
+
/** Update the entries when they already exist (default: overwrite the scalar values). */
|
|
11
|
+
overwrite?: boolean;
|
|
12
|
+
/** Path to config.yaml (default: auto-detected, typically config/config.yaml). */
|
|
13
|
+
config?: string;
|
|
14
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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.scope !== undefined || options.maxAttempts !== undefined;
|
|
10
|
+
const prompts = (0, schematic_prompt_util_1.loadPrompts)(context, flagsProvided);
|
|
11
|
+
const scope = options.scope || (await (0, schematic_prompt_util_1.askSelect)(prompts, 'Retry scope?', ['broker', 'topic'], 'broker'));
|
|
12
|
+
const maxAttempts = options.maxAttempts ?? (await (0, schematic_prompt_util_1.askNumber)(prompts, 'Max attempts?', 5)) ?? 5;
|
|
13
|
+
const delayMs = options.delayMs ?? (await (0, schematic_prompt_util_1.askNumber)(prompts, 'Delay between attempts (ms)?', 0)) ?? 0;
|
|
14
|
+
const dlx = options.deadLetterExchange;
|
|
15
|
+
const onExhausted = options.onExhausted ?? (dlx ? 'dead-letter' : 'drop');
|
|
16
|
+
const retry = { maxAttempts, delayMs, onExhausted };
|
|
17
|
+
if (dlx) {
|
|
18
|
+
retry.deadLetter = { exchange: dlx, ...(options.deadLetterRoutingKey ? { routingKey: options.deadLetterRoutingKey } : {}) };
|
|
19
|
+
}
|
|
20
|
+
const configPath = (0, yaml_config_util_1.findConfigYaml)(tree, options.config);
|
|
21
|
+
const { doc, existed } = (0, yaml_config_util_1.readConfigDoc)(tree, configPath);
|
|
22
|
+
if (!existed)
|
|
23
|
+
context.logger.info(`[rlb-amqp] ${configPath} not found — creating it.`);
|
|
24
|
+
if (onExhausted === 'dead-letter' && dlx && (options.declareDlx ?? true)) {
|
|
25
|
+
const dlxOutcome = (0, broker_yaml_util_1.ensureExchange)(doc, { name: dlx, type: options.dlxType ?? 'topic' });
|
|
26
|
+
(0, schematic_prompt_util_1.logOutcome)(context, `dead-letter exchange '${dlx}'`, dlxOutcome);
|
|
27
|
+
}
|
|
28
|
+
if (scope === 'broker') {
|
|
29
|
+
const outcome = (0, yaml_config_util_1.setIn)(doc, ['broker', 'retry'], retry);
|
|
30
|
+
(0, schematic_prompt_util_1.logOutcome)(context, 'broker.retry', outcome);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const topic = options.topic;
|
|
34
|
+
if (!topic) {
|
|
35
|
+
context.logger.error('[rlb-amqp] enable-retry: --topic is required when scope=topic.');
|
|
36
|
+
return tree;
|
|
37
|
+
}
|
|
38
|
+
const item = (0, yaml_config_util_1.findSeqItemByKey)(doc, ['topics'], 'name', topic);
|
|
39
|
+
if (!item) {
|
|
40
|
+
context.logger.warn(`[rlb-amqp] enable-retry: topic ${topic} not found in topics[]`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
item.set('retry', doc.createNode(retry));
|
|
44
|
+
(0, schematic_prompt_util_1.logOutcome)(context, `topics[${topic}].retry`, 'updated');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
(0, yaml_config_util_1.writeConfigDoc)(tree, configPath, doc);
|
|
48
|
+
return tree;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../libs/rlb-nestjs-amqp/src/schematics/enable-retry/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
|
|
2
|
+
import { parseDocument } from 'yaml';
|
|
3
|
+
import { main } from './index';
|
|
4
|
+
import { EnableRetryOptions } from './schema';
|
|
5
|
+
|
|
6
|
+
/** Invoke the factory Rule directly (SchematicTestRunner eagerly loads ESM `ora`). */
|
|
7
|
+
const ctx = { logger: { info() {}, warn() {}, error() {} } } as unknown as SchematicContext;
|
|
8
|
+
|
|
9
|
+
async function run(options: EnableRetryOptions, tree: Tree): Promise<Tree> {
|
|
10
|
+
const rule = main(options) as Rule;
|
|
11
|
+
const result = await (rule as (t: Tree, c: SchematicContext) => Promise<Tree>)(tree, ctx);
|
|
12
|
+
return result || tree;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function read(tree: Tree, path = 'config/config.yaml'): string {
|
|
16
|
+
return tree.read(path)!.toString('utf-8');
|
|
17
|
+
}
|
|
18
|
+
function doc(tree: Tree) {
|
|
19
|
+
return parseDocument(read(tree));
|
|
20
|
+
}
|
|
21
|
+
function seedConfig(): Tree {
|
|
22
|
+
const tree = Tree.empty();
|
|
23
|
+
tree.create(
|
|
24
|
+
'config/config.yaml',
|
|
25
|
+
['# my config', 'broker:', ' exchanges:', ' - name: rlb', ' type: direct', 'topics:', ' - name: orders', ' mode: handle', ''].join('\n'),
|
|
26
|
+
);
|
|
27
|
+
return tree;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('enable-retry schematic', () => {
|
|
31
|
+
it('sets broker.retry with the resolved onExhausted default', async () => {
|
|
32
|
+
const tree = await run({ scope: 'broker', maxAttempts: 3, delayMs: 100 }, seedConfig());
|
|
33
|
+
const retry = doc(tree).getIn(['broker', 'retry']) as any;
|
|
34
|
+
expect(Number(retry.get('maxAttempts'))).toBe(3);
|
|
35
|
+
expect(Number(retry.get('delayMs'))).toBe(100);
|
|
36
|
+
expect(String(retry.get('onExhausted'))).toBe('drop'); // no DLX set
|
|
37
|
+
expect(read(tree)).toContain('# my config');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('is idempotent: re-running with the same values leaves broker.retry unchanged', async () => {
|
|
41
|
+
let tree = await run({ scope: 'broker', maxAttempts: 5 }, seedConfig());
|
|
42
|
+
const first = read(tree);
|
|
43
|
+
tree = await run({ scope: 'broker', maxAttempts: 5 }, tree);
|
|
44
|
+
expect(read(tree)).toBe(first);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('declares the dead-letter exchange in broker.exchanges when dead-lettering', async () => {
|
|
48
|
+
const tree = await run(
|
|
49
|
+
{ scope: 'broker', deadLetterExchange: 'rlb-dlx', deadLetterRoutingKey: 'dead' },
|
|
50
|
+
seedConfig(),
|
|
51
|
+
);
|
|
52
|
+
const exchanges = doc(tree).getIn(['broker', 'exchanges']) as any;
|
|
53
|
+
const names = exchanges.items.map((i: any) => String(i.get('name')));
|
|
54
|
+
expect(names).toContain('rlb-dlx');
|
|
55
|
+
const dlx = exchanges.items.find((i: any) => String(i.get('name')) === 'rlb-dlx');
|
|
56
|
+
expect(String(dlx.get('type'))).toBe('topic');
|
|
57
|
+
const retry = doc(tree).getIn(['broker', 'retry']) as any;
|
|
58
|
+
expect(String(retry.get('onExhausted'))).toBe('dead-letter');
|
|
59
|
+
expect(String(retry.getIn(['deadLetter', 'exchange']))).toBe('rlb-dlx');
|
|
60
|
+
expect(String(retry.getIn(['deadLetter', 'routingKey']))).toBe('dead');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('scope=topic sets topics[<name>].retry on the matching topic', async () => {
|
|
64
|
+
const tree = await run({ scope: 'topic', topic: 'orders', maxAttempts: 2 }, seedConfig());
|
|
65
|
+
const topic = (doc(tree).getIn(['topics']) as any).items.find((i: any) => String(i.get('name')) === 'orders');
|
|
66
|
+
expect(Number(topic.getIn(['retry', 'maxAttempts']))).toBe(2);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('scope=topic warns and makes no change when the topic is missing', async () => {
|
|
70
|
+
const tree = await run({ scope: 'topic', topic: 'ghost', maxAttempts: 2 }, seedConfig());
|
|
71
|
+
expect(doc(tree).getIn(['broker', 'retry'])).toBeUndefined();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
|
|
2
|
+
import { ensureExchange, ExchangeType, RetrySpec } from '../utils/broker-yaml.util';
|
|
3
|
+
import { askNumber, askSelect, loadPrompts, logOutcome } from '../utils/schematic-prompt.util';
|
|
4
|
+
import { findConfigYaml, findSeqItemByKey, readConfigDoc, setIn, writeConfigDoc } from '../utils/yaml-config.util';
|
|
5
|
+
import { EnableRetryOptions } from './schema';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `enable-retry` — configure the 2.1.x bounded retry policy that replaces the old infinite-requeue
|
|
9
|
+
* behaviour (built-in default is 5 attempts → drop). Its real value-add is DECLARING the dead-letter
|
|
10
|
+
* exchange in broker.exchanges automatically, since retry.deadLetter.exchange must resolve to a real
|
|
11
|
+
* declared exchange. Writes to broker.retry (broker-wide default) or topics[<name>].retry.
|
|
12
|
+
*/
|
|
13
|
+
export function main(options: EnableRetryOptions): Rule {
|
|
14
|
+
return async (tree: Tree, context: SchematicContext) => {
|
|
15
|
+
const flagsProvided = options.scope !== undefined || options.maxAttempts !== undefined;
|
|
16
|
+
const prompts = loadPrompts(context, flagsProvided);
|
|
17
|
+
|
|
18
|
+
const scope = options.scope || (await askSelect(prompts, 'Retry scope?', ['broker', 'topic'] as const, 'broker'));
|
|
19
|
+
const maxAttempts = options.maxAttempts ?? (await askNumber(prompts, 'Max attempts?', 5)) ?? 5;
|
|
20
|
+
const delayMs = options.delayMs ?? (await askNumber(prompts, 'Delay between attempts (ms)?', 0)) ?? 0;
|
|
21
|
+
|
|
22
|
+
// onExhausted defaults to dead-letter only when a DLX is actually configured, else drop.
|
|
23
|
+
const dlx = options.deadLetterExchange;
|
|
24
|
+
const onExhausted = options.onExhausted ?? (dlx ? 'dead-letter' : 'drop');
|
|
25
|
+
|
|
26
|
+
const retry: RetrySpec = { maxAttempts, delayMs, onExhausted };
|
|
27
|
+
if (dlx) {
|
|
28
|
+
retry.deadLetter = { exchange: dlx, ...(options.deadLetterRoutingKey ? { routingKey: options.deadLetterRoutingKey } : {}) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const configPath = findConfigYaml(tree, options.config);
|
|
32
|
+
const { doc, existed } = readConfigDoc(tree, configPath);
|
|
33
|
+
if (!existed) context.logger.info(`[rlb-amqp] ${configPath} not found — creating it.`);
|
|
34
|
+
|
|
35
|
+
// Declare the DLX so retry.deadLetter points at a real exchange (the schematic's main value).
|
|
36
|
+
if (onExhausted === 'dead-letter' && dlx && (options.declareDlx ?? true)) {
|
|
37
|
+
const dlxOutcome = ensureExchange(doc, { name: dlx, type: (options.dlxType as ExchangeType) ?? 'topic' });
|
|
38
|
+
logOutcome(context, `dead-letter exchange '${dlx}'`, dlxOutcome);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (scope === 'broker') {
|
|
42
|
+
const outcome = setIn(doc, ['broker', 'retry'], retry);
|
|
43
|
+
logOutcome(context, 'broker.retry', outcome);
|
|
44
|
+
} else {
|
|
45
|
+
const topic = options.topic;
|
|
46
|
+
if (!topic) {
|
|
47
|
+
context.logger.error('[rlb-amqp] enable-retry: --topic is required when scope=topic.');
|
|
48
|
+
return tree;
|
|
49
|
+
}
|
|
50
|
+
const item = findSeqItemByKey(doc, ['topics'], 'name', topic);
|
|
51
|
+
if (!item) {
|
|
52
|
+
context.logger.warn(`[rlb-amqp] enable-retry: topic ${topic} not found in topics[]`);
|
|
53
|
+
} else {
|
|
54
|
+
item.set('retry', doc.createNode(retry));
|
|
55
|
+
logOutcome(context, `topics[${topic}].retry`, 'updated');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
writeConfigDoc(tree, configPath, doc);
|
|
60
|
+
return tree;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface EnableRetryOptions {
|
|
2
|
+
scope?: 'broker' | 'topic';
|
|
3
|
+
topic?: string;
|
|
4
|
+
maxAttempts?: number;
|
|
5
|
+
delayMs?: number;
|
|
6
|
+
onExhausted?: 'dead-letter' | 'drop';
|
|
7
|
+
deadLetterExchange?: string;
|
|
8
|
+
deadLetterRoutingKey?: string;
|
|
9
|
+
declareDlx?: boolean;
|
|
10
|
+
dlxType?: 'direct' | 'topic' | 'fanout' | 'headers';
|
|
11
|
+
overwrite?: boolean;
|
|
12
|
+
config?: string;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../libs/rlb-nestjs-amqp/src/schematics/enable-retry/schema.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "SchematicsRlbAmqpEnableRetry",
|
|
4
|
+
"title": "Configure a bounded retry policy in config.yaml",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"scope": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"enum": ["broker", "topic"],
|
|
10
|
+
"default": "broker",
|
|
11
|
+
"description": "Broker-wide default (broker.retry) or a single topic (topics[].retry).",
|
|
12
|
+
"x-prompt": "Retry scope?",
|
|
13
|
+
"$default": { "$source": "argv", "index": 0 }
|
|
14
|
+
},
|
|
15
|
+
"topic": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Topic name (required when scope=topic)."
|
|
18
|
+
},
|
|
19
|
+
"maxAttempts": {
|
|
20
|
+
"type": "number",
|
|
21
|
+
"default": 5,
|
|
22
|
+
"description": "Bounded attempt count before dead-lettering/dropping."
|
|
23
|
+
},
|
|
24
|
+
"delayMs": {
|
|
25
|
+
"type": "number",
|
|
26
|
+
"default": 0,
|
|
27
|
+
"description": "Delay between attempts (ms)."
|
|
28
|
+
},
|
|
29
|
+
"onExhausted": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"enum": ["dead-letter", "drop"],
|
|
32
|
+
"description": "What to do once attempts are exhausted (default: dead-letter when a DLX is set, else drop)."
|
|
33
|
+
},
|
|
34
|
+
"deadLetterExchange": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Dead-letter exchange to route exhausted messages to."
|
|
37
|
+
},
|
|
38
|
+
"deadLetterRoutingKey": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "Routing key used when dead-lettering (omitted when unset)."
|
|
41
|
+
},
|
|
42
|
+
"declareDlx": {
|
|
43
|
+
"type": "boolean",
|
|
44
|
+
"default": true,
|
|
45
|
+
"description": "Also declare the DLX in broker.exchanges."
|
|
46
|
+
},
|
|
47
|
+
"dlxType": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"enum": ["direct", "topic", "fanout", "headers"],
|
|
50
|
+
"default": "topic",
|
|
51
|
+
"description": "Type of the declared DLX."
|
|
52
|
+
},
|
|
53
|
+
"overwrite": {
|
|
54
|
+
"type": "boolean",
|
|
55
|
+
"default": false,
|
|
56
|
+
"description": "Update the retry block if it already exists."
|
|
57
|
+
},
|
|
58
|
+
"config": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "Path to config.yaml (default: auto-detected)."
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"required": []
|
|
64
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface EnableRetryOptions {
|
|
2
|
+
/** Where the retry policy lives: broker-wide default or a single topic. Default: 'broker'. */
|
|
3
|
+
scope?: 'broker' | 'topic';
|
|
4
|
+
/** Topic name — required when scope=topic. */
|
|
5
|
+
topic?: string;
|
|
6
|
+
/** Bounded attempt count before the message is dead-lettered/dropped. Default: 5. */
|
|
7
|
+
maxAttempts?: number;
|
|
8
|
+
/** Delay between attempts, in ms. Default: 0. */
|
|
9
|
+
delayMs?: number;
|
|
10
|
+
/** What to do once attempts are exhausted. Default: 'dead-letter' when a DLX is set, else 'drop'. */
|
|
11
|
+
onExhausted?: 'dead-letter' | 'drop';
|
|
12
|
+
/** Dead-letter exchange to route exhausted messages to. */
|
|
13
|
+
deadLetterExchange?: string;
|
|
14
|
+
/** Optional routing key used when dead-lettering. */
|
|
15
|
+
deadLetterRoutingKey?: string;
|
|
16
|
+
/** Also declare the DLX in broker.exchanges (this schematic's main value-add). Default: true. */
|
|
17
|
+
declareDlx?: boolean;
|
|
18
|
+
/** Type of the declared DLX. Default: 'topic'. */
|
|
19
|
+
dlxType?: 'direct' | 'topic' | 'fanout' | 'headers';
|
|
20
|
+
/** Update the retry block when it already exists (default: leave it untouched). */
|
|
21
|
+
overwrite?: boolean;
|
|
22
|
+
/** Path to config.yaml (default: auto-detected, typically config/config.yaml). */
|
|
23
|
+
config?: string;
|
|
24
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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 formatting_1 = require("../utils/formatting");
|
|
6
|
+
const schematic_prompt_util_1 = require("../utils/schematic-prompt.util");
|
|
7
|
+
const yaml_config_util_1 = require("../utils/yaml-config.util");
|
|
8
|
+
function main(options) {
|
|
9
|
+
return async (tree, context) => {
|
|
10
|
+
const flagsProvided = options.serviceName !== undefined;
|
|
11
|
+
const prompts = (0, schematic_prompt_util_1.loadPrompts)(context, flagsProvided);
|
|
12
|
+
const rawName = options.serviceName || (await (0, schematic_prompt_util_1.askText)(prompts, 'Service name?', ''));
|
|
13
|
+
if (!rawName) {
|
|
14
|
+
context.logger.error('[rlb-amqp] enable-route-discovery: a serviceName is required.');
|
|
15
|
+
return tree;
|
|
16
|
+
}
|
|
17
|
+
const svc = (0, formatting_1.normalizeToKebabOrSnakeCase)(rawName);
|
|
18
|
+
const exchange = options.exchange || 'rlb-route-discovery';
|
|
19
|
+
const queue = options.queue || 'rlb-route-sync';
|
|
20
|
+
const publishOnBoot = options.publishOnBoot ?? true;
|
|
21
|
+
const configPath = (0, yaml_config_util_1.findConfigYaml)(tree, options.config);
|
|
22
|
+
const { doc, existed } = (0, yaml_config_util_1.readConfigDoc)(tree, configPath);
|
|
23
|
+
if (!existed)
|
|
24
|
+
context.logger.info(`[rlb-amqp] ${configPath} not found — creating it.`);
|
|
25
|
+
const outcome = (0, yaml_config_util_1.setIn)(doc, ['broker', 'routeDiscovery'], { serviceName: svc, publishOnBoot, exchange, queue });
|
|
26
|
+
(0, schematic_prompt_util_1.logOutcome)(context, 'broker.routeDiscovery', outcome);
|
|
27
|
+
if (options.declareExchange ?? true) {
|
|
28
|
+
const exOutcome = (0, broker_yaml_util_1.ensureExchange)(doc, { name: exchange, type: 'fanout' });
|
|
29
|
+
(0, schematic_prompt_util_1.logOutcome)(context, `discovery exchange '${exchange}'`, exOutcome);
|
|
30
|
+
}
|
|
31
|
+
context.logger.info(`[rlb-amqp] serviceName '${svc}' also fills connection_name when none is set (see set-connection-name).`);
|
|
32
|
+
(0, yaml_config_util_1.writeConfigDoc)(tree, configPath, doc);
|
|
33
|
+
return tree;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../libs/rlb-nestjs-amqp/src/schematics/enable-route-discovery/index.ts"],"names":[],"mappings":";;AAYA,oBAmCC;AA9CD,gEAA2D;AAC3D,oDAAkE;AAClE,0EAAkF;AAClF,gEAAiG;AAQjG,SAAgB,IAAI,CAAC,OAAoC;IACvD,OAAO,KAAK,EAAE,IAAU,EAAE,OAAyB,EAAE,EAAE;QACrD,MAAM,aAAa,GAAG,OAAO,CAAC,WAAW,KAAK,SAAS,CAAC;QACxD,MAAM,OAAO,GAAG,IAAA,mCAAW,EAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QAEpD,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,IAAI,CAAC,MAAM,IAAA,+BAAO,EAAC,OAAO,EAAE,eAAe,EAAE,EAAE,CAAC,CAAC,CAAC;QACrF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+DAA+D,CAAC,CAAC;YACtF,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,GAAG,GAAG,IAAA,wCAA2B,EAAC,OAAO,CAAC,CAAC;QACjD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,qBAAqB,CAAC;QAC3D,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,gBAAgB,CAAC;QAChD,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,IAAI,CAAC;QAEpD,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,wBAAK,EAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,gBAAgB,CAAC,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/G,IAAA,kCAAU,EAAC,OAAO,EAAE,uBAAuB,EAAE,OAAO,CAAC,CAAC;QAGtD,IAAI,OAAO,CAAC,eAAe,IAAI,IAAI,EAAE,CAAC;YACpC,MAAM,SAAS,GAAG,IAAA,iCAAc,EAAC,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC1E,IAAA,kCAAU,EAAC,OAAO,EAAE,uBAAuB,QAAQ,GAAG,EAAE,SAAS,CAAC,CAAC;QACrE,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,IAAI,CACjB,2BAA2B,GAAG,0EAA0E,CACzG,CAAC;QAEF,IAAA,iCAAc,EAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
|
|
2
|
+
import { parseDocument } from 'yaml';
|
|
3
|
+
import { main } from './index';
|
|
4
|
+
import { EnableRouteDiscoveryOptions } from './schema';
|
|
5
|
+
|
|
6
|
+
/** Invoke the factory Rule directly (SchematicTestRunner eagerly loads ESM `ora`). */
|
|
7
|
+
const ctx = { logger: { info() {}, warn() {}, error() {} } } as unknown as SchematicContext;
|
|
8
|
+
|
|
9
|
+
async function run(options: EnableRouteDiscoveryOptions, tree: Tree): Promise<Tree> {
|
|
10
|
+
const rule = main(options) as Rule;
|
|
11
|
+
const result = await (rule as (t: Tree, c: SchematicContext) => Promise<Tree>)(tree, ctx);
|
|
12
|
+
return result || tree;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function read(tree: Tree, path = 'config/config.yaml'): string {
|
|
16
|
+
return tree.read(path)!.toString('utf-8');
|
|
17
|
+
}
|
|
18
|
+
function doc(tree: Tree) {
|
|
19
|
+
return parseDocument(read(tree));
|
|
20
|
+
}
|
|
21
|
+
function seedConfig(): Tree {
|
|
22
|
+
const tree = Tree.empty();
|
|
23
|
+
tree.create('config/config.yaml', ['# my config', 'broker:', ' exchanges: []', 'topics: []', ''].join('\n'));
|
|
24
|
+
return tree;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('enable-route-discovery schematic', () => {
|
|
28
|
+
it('sets broker.routeDiscovery and kebab-normalizes the service name', async () => {
|
|
29
|
+
const tree = await run({ serviceName: 'OrderService' }, seedConfig());
|
|
30
|
+
const rd = doc(tree).getIn(['broker', 'routeDiscovery']) as any;
|
|
31
|
+
expect(String(rd.get('serviceName'))).toBe('order-service');
|
|
32
|
+
expect(String(rd.get('exchange'))).toBe('rlb-route-discovery');
|
|
33
|
+
expect(String(rd.get('queue'))).toBe('rlb-route-sync');
|
|
34
|
+
expect(rd.get('publishOnBoot')).toBe(true);
|
|
35
|
+
expect(read(tree)).toContain('# my config');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('declares the discovery exchange as fanout', async () => {
|
|
39
|
+
const tree = await run({ serviceName: 'billing' }, seedConfig());
|
|
40
|
+
const exchanges = doc(tree).getIn(['broker', 'exchanges']) as any;
|
|
41
|
+
const ex = exchanges.items.find((i: any) => String(i.get('name')) === 'rlb-route-discovery');
|
|
42
|
+
expect(ex).toBeDefined();
|
|
43
|
+
expect(String(ex.get('type'))).toBe('fanout');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('is idempotent: re-running with the same values does not duplicate the exchange', async () => {
|
|
47
|
+
let tree = await run({ serviceName: 'billing' }, seedConfig());
|
|
48
|
+
const first = read(tree);
|
|
49
|
+
tree = await run({ serviceName: 'billing' }, tree);
|
|
50
|
+
expect(read(tree)).toBe(first);
|
|
51
|
+
const exchanges = doc(tree).getIn(['broker', 'exchanges']) as any;
|
|
52
|
+
const count = exchanges.items.filter((i: any) => String(i.get('name')) === 'rlb-route-discovery').length;
|
|
53
|
+
expect(count).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
|
|
2
|
+
import { ensureExchange } from '../utils/broker-yaml.util';
|
|
3
|
+
import { normalizeToKebabOrSnakeCase } from '../utils/formatting';
|
|
4
|
+
import { askText, loadPrompts, logOutcome } from '../utils/schematic-prompt.util';
|
|
5
|
+
import { findConfigYaml, readConfigDoc, setIn, writeConfigDoc } from '../utils/yaml-config.util';
|
|
6
|
+
import { EnableRouteDiscoveryOptions } from './schema';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `enable-route-discovery` — publisher-side route auto-discovery: the service publishes its route
|
|
10
|
+
* manifest on a fanout exchange so a gateway can sync it. Declares that fanout exchange automatically
|
|
11
|
+
* so the discovery wiring is complete in one shot.
|
|
12
|
+
*/
|
|
13
|
+
export function main(options: EnableRouteDiscoveryOptions): Rule {
|
|
14
|
+
return async (tree: Tree, context: SchematicContext) => {
|
|
15
|
+
const flagsProvided = options.serviceName !== undefined;
|
|
16
|
+
const prompts = loadPrompts(context, flagsProvided);
|
|
17
|
+
|
|
18
|
+
const rawName = options.serviceName || (await askText(prompts, 'Service name?', ''));
|
|
19
|
+
if (!rawName) {
|
|
20
|
+
context.logger.error('[rlb-amqp] enable-route-discovery: a serviceName is required.');
|
|
21
|
+
return tree;
|
|
22
|
+
}
|
|
23
|
+
const svc = normalizeToKebabOrSnakeCase(rawName);
|
|
24
|
+
const exchange = options.exchange || 'rlb-route-discovery';
|
|
25
|
+
const queue = options.queue || 'rlb-route-sync';
|
|
26
|
+
const publishOnBoot = options.publishOnBoot ?? true;
|
|
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 = setIn(doc, ['broker', 'routeDiscovery'], { serviceName: svc, publishOnBoot, exchange, queue });
|
|
33
|
+
logOutcome(context, 'broker.routeDiscovery', outcome);
|
|
34
|
+
|
|
35
|
+
// A fanout exchange is the discovery bus; declare it so publish-on-boot has somewhere to go.
|
|
36
|
+
if (options.declareExchange ?? true) {
|
|
37
|
+
const exOutcome = ensureExchange(doc, { name: exchange, type: 'fanout' });
|
|
38
|
+
logOutcome(context, `discovery exchange '${exchange}'`, exOutcome);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
context.logger.info(
|
|
42
|
+
`[rlb-amqp] serviceName '${svc}' also fills connection_name when none is set (see set-connection-name).`,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
writeConfigDoc(tree, configPath, doc);
|
|
46
|
+
return tree;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../libs/rlb-nestjs-amqp/src/schematics/enable-route-discovery/schema.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "SchematicsRlbAmqpEnableRouteDiscovery",
|
|
4
|
+
"title": "Enable publisher-side route auto-discovery in config.yaml",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"serviceName": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "Logical service name published for auto-discovery (kebab/snake-normalized).",
|
|
10
|
+
"x-prompt": "Service name?",
|
|
11
|
+
"$default": { "$source": "argv", "index": 0 }
|
|
12
|
+
},
|
|
13
|
+
"exchange": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"default": "rlb-route-discovery",
|
|
16
|
+
"description": "Discovery fanout exchange."
|
|
17
|
+
},
|
|
18
|
+
"queue": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"default": "rlb-route-sync",
|
|
21
|
+
"description": "Queue the gateway syncs from."
|
|
22
|
+
},
|
|
23
|
+
"publishOnBoot": {
|
|
24
|
+
"type": "boolean",
|
|
25
|
+
"default": true,
|
|
26
|
+
"description": "Publish the route manifest on boot."
|
|
27
|
+
},
|
|
28
|
+
"declareExchange": {
|
|
29
|
+
"type": "boolean",
|
|
30
|
+
"default": true,
|
|
31
|
+
"description": "Also declare the discovery exchange in broker.exchanges."
|
|
32
|
+
},
|
|
33
|
+
"overwrite": {
|
|
34
|
+
"type": "boolean",
|
|
35
|
+
"default": false,
|
|
36
|
+
"description": "Update broker.routeDiscovery if it already exists."
|
|
37
|
+
},
|
|
38
|
+
"config": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "Path to config.yaml (default: auto-detected)."
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"required": []
|
|
44
|
+
}
|