Every module named its events the way the old bus spelled a routing key — `module.<module>.<verb>`. Design 29 says a module names an event locally and the mesh works out where it lands, so all 37 were stale against a rule already decided. On the new bus that derives into a namespace belonging to a module called "module", so no cross-module subscription in the mesh matched anything: nothing failed, nothing reacted (novox/hq 04-ISSUES/127). 36 manifests converted, and 43 files of module code with them. The code mattered as much as the manifests: the runtime builds the subject from what `emit()` is handed, so a converted manifest with unconverted code would have had the permission and the subject disagree. Three things the new check found on the way: - `photos` emitted an event its manifest never declared, which the new bus refuses outright. Declared. - `showcase` waited for an event nothing emits, so its demo could never be triggered — only `showcase` may publish under its own name. It emits both halves now. - `distribution` declared an event named after a different module. It emits `image.pushed` under its own name. An event about a *role* belongs on the seat, where the name outlives whoever holds it, but the sdk has no way to publish on a seat yet, so that stays recorded rather than declared. The audit logger's "everything" pattern is `**` rather than the old bus's `#`.
65 lines
2.7 KiB
TypeScript
65 lines
2.7 KiB
TypeScript
// nodered's tools — importing nodered's own admin-API client (novox/hq ADR 0039). They return
|
|
// structured data; the mesh serves them through the sdk's tool harness.
|
|
//
|
|
// The deploy tool is nodered's one event source (novox/hq ADR 0041/0042): a successful deploy
|
|
// emits module.nodered.flows.deployed. nodered has nothing to observe on a timer, so there is no
|
|
// separate events entrypoint — the emit rides the action that causes it. The emit is best-effort:
|
|
// if no broker is bound, the deploy still succeeds and the announcement is simply skipped.
|
|
|
|
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
|
|
import { emit } from "@novox/mesh-sdk/events";
|
|
import { NodeRedClient } from "../client.js";
|
|
|
|
export function getNodeRedTools(nodered: NodeRedClient): ToolDefinition[] {
|
|
return [
|
|
{
|
|
name: "nodered_list_flows",
|
|
description: "List Node-RED flows (tabs) — id, label, disabled state — and the total node count.",
|
|
input: {},
|
|
run: async () => nodered.listFlows(),
|
|
},
|
|
{
|
|
name: "nodered_list_nodes",
|
|
description: "List the Node-RED node modules installed in the runtime and their versions.",
|
|
input: {},
|
|
run: async () => {
|
|
const nodes = await nodered.listNodes();
|
|
return { count: nodes.length, nodes };
|
|
},
|
|
},
|
|
{
|
|
name: "nodered_deploy",
|
|
description:
|
|
"Replace the whole Node-RED flow configuration and deploy it. `flows` is the full node " +
|
|
"array (as GET /flows returns). Emits module.nodered.flows.deployed on success.",
|
|
input: {
|
|
flows: { type: "array", description: "the full flow configuration — every node across every tab" },
|
|
type: { type: "string", description: "deployment type: full (default), nodes, or flows" },
|
|
},
|
|
run: async (args) => {
|
|
const flows = args.flows as unknown[];
|
|
if (!Array.isArray(flows)) throw new Error("flows must be an array of Node-RED nodes");
|
|
const type = args.type ? String(args.type) : "full";
|
|
const result = await nodered.deployFlows(flows, type);
|
|
// Best-effort announcement — a deploy must not fail because the broker is unbound here.
|
|
try {
|
|
await emit("flows.deployed", { rev: result.rev, nodeCount: result.nodeCount, type });
|
|
} catch (err) {
|
|
console.error(`[nodered] deployed but could not emit: ${err}`);
|
|
}
|
|
return result;
|
|
},
|
|
},
|
|
];
|
|
}
|
|
|
|
// The tools exist only when a Node-RED URL is configured; without one, nodered contributes none
|
|
// rather than failing the whole tool runtime.
|
|
registerModuleTools("nodered", (env) => {
|
|
try {
|
|
return getNodeRedTools(NodeRedClient.fromEnv(env));
|
|
} catch {
|
|
return [];
|
|
}
|
|
});
|