redis, postgres: full nox provider modules — client, tools, provisioner, events
redis provides redis-cache: a real admin client speaking RESP over a raw socket (node:net, no deps); provisioner makes a keyspace-scoped ACL user per grant. postgres provides postgres-database: admin client executing through psql (the consistent shell-out port, like minio's mc), full DDL for create/drop database+ role, a CSV row parser, read-only query tool. Both emit module.<x>.<thing>.provisioned/.deprovisioned from the provisioner. Both typecheck; manifests parse.
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
// redis's tools — redis's own code (novox/hq ADR 0044), importing redis's own RESP client. They
|
||||
// return structured data; the mesh serves them through the sdk's tool harness.
|
||||
|
||||
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
|
||||
import { RedisClient, type RespValue } from "../client.js";
|
||||
|
||||
export function getRedisTools(redis: RedisClient): ToolDefinition[] {
|
||||
return [
|
||||
{
|
||||
name: "redis_ping",
|
||||
description: "Check that the redis server is reachable and responding (PONG).",
|
||||
input: {},
|
||||
run: async () => ({ ok: await redis.ping() }),
|
||||
},
|
||||
{
|
||||
name: "redis_info",
|
||||
description: "Redis server info — memory, clients, keyspace. Omit section for everything.",
|
||||
input: { section: { type: "string", description: "an INFO section, e.g. 'memory', 'clients', 'keyspace'" } },
|
||||
run: async (args) => {
|
||||
const { raw, fields } = await redis.info(args.section ? String(args.section) : undefined);
|
||||
return { fields, raw };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "redis_command",
|
||||
description: "Run an arbitrary redis command, e.g. 'DBSIZE', 'GET key', 'ACL LIST'. Admin surface.",
|
||||
input: { command: { type: "string", description: "the command and its arguments, space-separated" } },
|
||||
run: async (args) => {
|
||||
const parts = tokenize(String(args.command ?? ""));
|
||||
if (parts.length === 0) throw new Error("redis_command: empty command");
|
||||
const reply: RespValue = await redis.command(...parts);
|
||||
return { command: parts.join(" "), reply };
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** Split a command line into arguments, honouring double-quoted spans. */
|
||||
function tokenize(command: string): string[] {
|
||||
const matches = command.match(/(?:[^\s"]+|"[^"]*")+/g) ?? [];
|
||||
return matches.map((p) => p.replace(/^"|"$/g, ""));
|
||||
}
|
||||
|
||||
// The tools exist only when the server can be reached from the environment; without it, redis
|
||||
// contributes none rather than failing the whole tool runtime.
|
||||
registerModuleTools("redis", (env) => {
|
||||
try {
|
||||
return getRedisTools(RedisClient.fromEnv(env));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user