@rse/gradia 1.1.9 → 1.2.1
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/README.md +89 -36
- package/dst/gradia-api-config.d.ts +7 -0
- package/dst/gradia-api-config.js +54 -24
- package/dst/gradia-api-parser.js +10 -4
- package/dst/gradia-api-render-base.d.ts +28 -0
- package/dst/gradia-api-render-base.js +3 -2
- package/dst/gradia-api-render-container.d.ts +31 -0
- package/dst/gradia-api-render-container.js +300 -0
- package/dst/gradia-api-render-edge.d.ts +1 -1
- package/dst/gradia-api-render-edge.js +106 -37
- package/dst/gradia-api-render-group.d.ts +3 -2
- package/dst/gradia-api-render-group.js +16 -27
- package/dst/gradia-api-render-node.d.ts +10 -1
- package/dst/gradia-api-render-node.js +35 -14
- package/dst/gradia-api-render-svg.d.ts +1 -21
- package/dst/gradia-api-render-svg.js +82 -20
- package/dst/gradia-api-type-graph.d.ts +3 -2
- package/dst/gradia-api-type-graph.js +280 -135
- package/dst/gradia-api-type-grid.d.ts +3 -2
- package/dst/gradia-api-type-grid.js +10 -5
- package/dst/gradia-api-type-hub.d.ts +3 -2
- package/dst/gradia-api-type-hub.js +20 -10
- package/dst/gradia-api.d.ts +12 -4
- package/dst/gradia-api.js +33 -13
- package/dst/gradia-api.umd.js +2 -2
- package/dst/gradia-cli.js +4 -21
- package/dst/gradia-mcp.js +58 -54
- package/package.json +1 -1
package/dst/gradia-cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from "node:url";
|
|
|
11
11
|
/* external dependencies */
|
|
12
12
|
import { Command, Option } from "commander";
|
|
13
13
|
/* internal dependencies */
|
|
14
|
-
import {
|
|
14
|
+
import { validateConfig } from "./gradia-api-config.js";
|
|
15
15
|
import { Gradia, diagramTypes, diagramTypeDefault, diagramFormats, diagramFormatDefault } from "./gradia-api.js";
|
|
16
16
|
/* internal package meta-information */
|
|
17
17
|
const pkg = JSON.parse(fs.readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
|
|
@@ -30,31 +30,14 @@ program.name("gradia")
|
|
|
30
30
|
.argument("[input]", "input graph description file (omitted in MCP service mode)")
|
|
31
31
|
.action(async (input, options) => {
|
|
32
32
|
/* parse and validate the rendering configuration options */
|
|
33
|
-
const
|
|
34
|
-
const store = config;
|
|
33
|
+
const store = {};
|
|
35
34
|
for (const nv of options.config) {
|
|
36
35
|
const m = /^([a-z][a-z0-9-]*)=(.*)$/.exec(nv);
|
|
37
36
|
if (m === null)
|
|
38
37
|
throw new Error(`invalid configuration option "${nv}" (expected "<name>=<value>")`);
|
|
39
|
-
|
|
40
|
-
throw new Error(`unknown configuration option "${m[1]}"`);
|
|
41
|
-
const key = m[1];
|
|
42
|
-
if (typeof configDefaults[key] === "boolean") {
|
|
43
|
-
if (m[2] !== "true" && m[2] !== "false")
|
|
44
|
-
throw new Error(`invalid value "${m[2]}" for boolean configuration option "${key}" (expected "true" or "false")`);
|
|
45
|
-
store[key] = m[2] === "true";
|
|
46
|
-
}
|
|
47
|
-
else if (typeof configDefaults[key] === "number") {
|
|
48
|
-
const num = Number(m[2]);
|
|
49
|
-
if (m[2] === "" || !Number.isFinite(num) || num < 0)
|
|
50
|
-
throw new Error(`invalid value "${m[2]}" for numeric configuration option "${key}" (expected a non-negative number)`);
|
|
51
|
-
store[key] = num;
|
|
52
|
-
}
|
|
53
|
-
else if (m[2] === "")
|
|
54
|
-
throw new Error(`invalid empty value for string configuration option "${key}"`);
|
|
55
|
-
else
|
|
56
|
-
store[key] = m[2];
|
|
38
|
+
store[m[1]] = m[2];
|
|
57
39
|
}
|
|
40
|
+
const config = validateConfig(store, { trusted: true });
|
|
58
41
|
/* optionally run as an MCP service instead of rendering once,
|
|
59
42
|
with the command-line options acting as the defaults of the
|
|
60
43
|
tool calls (and hence being the only trusted source of the
|
package/dst/gradia-mcp.js
CHANGED
|
@@ -8,8 +8,8 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
8
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
/* internal dependencies */
|
|
11
|
-
import { configDefaults } from "./gradia-api-config.js";
|
|
12
|
-
import { Gradia, diagramTypes, diagramFormats, diagramFormatDefault } from "./gradia-api.js";
|
|
11
|
+
import { configDefaults, validateConfig } from "./gradia-api-config.js";
|
|
12
|
+
import { Gradia, diagramTypes, diagramTypeDefault, diagramFormats, diagramFormatDefault } from "./gradia-api.js";
|
|
13
13
|
/* the rendering configuration options offered to the MCP client
|
|
14
14
|
("font-embed" is intentionally omitted, as the arguments of a tool
|
|
15
15
|
call are treated as untrusted and it would allow the reading of
|
|
@@ -128,8 +128,8 @@ as <name>, terminals as "...", optional parts as [ ... ], repeatable parts as
|
|
|
128
128
|
following node, i.e., the chain is equivalent to "a --> b" and "b --> c".
|
|
129
129
|
- The <edge-name> is rendered as the label of the edge and the <edge-arity> as
|
|
130
130
|
its cardinality label.
|
|
131
|
-
-
|
|
132
|
-
displayed as a regular "<key>: <val>" line:
|
|
131
|
+
- Seven attribute keys are reserved and consumed by the renderer instead of
|
|
132
|
+
being displayed as a regular "<key>: <val>" line:
|
|
133
133
|
- "url: <url>" renders the node box as a hyperlink. Only relative URLs and
|
|
134
134
|
the schemes "http", "https", and "mailto" are honored; any other URL is
|
|
135
135
|
silently dropped and the node box then simply stays unlinked,
|
|
@@ -137,13 +137,47 @@ as <name>, terminals as "...", optional parts as [ ... ], repeatable parts as
|
|
|
137
137
|
- "primary: true" renders the node in the primary node colors (and, for the
|
|
138
138
|
diagram type "hub", marks the single central hub node). The value has to
|
|
139
139
|
be the literal string "true",
|
|
140
|
-
- "group: <name>" places the node into the surrounding group box <name
|
|
140
|
+
- "group: <name>" places the node into the surrounding group box <name>,
|
|
141
|
+
- "parent: <id>" nests the node into the container node <id> (a node which
|
|
142
|
+
is implicitly declared if never referenced otherwise),
|
|
143
|
+
- "container: <type>" marks the node as a container whose members are laid
|
|
144
|
+
out with the diagram type <type> ("graph", "hub", or "grid"), even if it
|
|
145
|
+
has no members at all,
|
|
146
|
+
- "order: <number>" places the node explicitly (see below).
|
|
147
|
+
- A "graph" (a "graph" container level, too) with at least one node carrying an
|
|
148
|
+
"order" attribute is laid out in that explicit order instead of by the layered
|
|
149
|
+
algorithm: the distinct order values become the rows, top-down in ascending
|
|
150
|
+
order, the nodes of one value fill their row left-to-right in declaration
|
|
151
|
+
order (wrapped into further rows beyond "graph-columns-max" nodes), and the
|
|
152
|
+
nodes without an order follow in the trailing rows. A "grid" places its tiles
|
|
153
|
+
in the order of the attribute, too, the tiles without one trailing. Use it to
|
|
154
|
+
impose a reading order (e.g. the order of a document), which lets every
|
|
155
|
+
reference to an earlier item point upward or leftward.
|
|
141
156
|
- A node cannot be a member of more than one group. As soon as at least one node
|
|
142
157
|
carries a "group" attribute, all nodes without one implicitly belong to the
|
|
143
|
-
group "default", and every edge has to stay within a single group.
|
|
158
|
+
group "default", and every edge has to stay within a single group. A nested
|
|
159
|
+
node belongs to the group of its outermost container.
|
|
144
160
|
- The groups are laid out individually with the selected diagram type and then
|
|
145
161
|
stacked vertically as decorated group boxes. For the type "hub" this means
|
|
146
162
|
that every single group needs its own primary node.
|
|
163
|
+
- A node referenced as the "parent" of another node is a container: it is
|
|
164
|
+
rendered as a dashed grey box surrounding its members instead of as a node
|
|
165
|
+
box, with its label, "type", and "url" in the box head (its remaining
|
|
166
|
+
attributes are not rendered). Containers nest to any depth, but a node cannot
|
|
167
|
+
be a member of more than one container, and cannot be nested into itself.
|
|
168
|
+
- The members of a container are laid out as a diagram of their own, with the
|
|
169
|
+
diagram type of the enclosing level unless overridden by "container: <type>",
|
|
170
|
+
and the container then takes part in the layout of the enclosing level as a
|
|
171
|
+
single box. Edges may connect any two nodes, across container boundaries and
|
|
172
|
+
to container nodes themselves, except a container with one of its own
|
|
173
|
+
members. An edge crossing the boundary of a container laid out as "graph" is
|
|
174
|
+
routed through the box border to the inner node (entering on the west and
|
|
175
|
+
leaving on the east side), while an edge crossing the boundary of a container
|
|
176
|
+
laid out as "hub" or "grid" ends at the box border instead. The diagram type
|
|
177
|
+
constraints apply to every container level (e.g., a "hub" container needs its
|
|
178
|
+
own primary node, and a "grid" container permits no edges among its members).
|
|
179
|
+
- Use containers (not groups) to visualize zones, tiers, deployment units, or
|
|
180
|
+
packages whose members are connected to the outside world.
|
|
147
181
|
- All remaining attributes are displayed as "<key>: <val>" lines inside the node
|
|
148
182
|
box.
|
|
149
183
|
|
|
@@ -155,8 +189,9 @@ as <name>, terminals as "...", optional parts as [ ... ], repeatable parts as
|
|
|
155
189
|
#config color-edge-line #999999
|
|
156
190
|
|
|
157
191
|
Animal: animal [ type: "abstract class", kind: base, url: "#animal", primary: true ]
|
|
158
|
-
Dog: dog
|
|
159
|
-
Cat: cat
|
|
192
|
+
Dog: dog [ parent: Pets ]
|
|
193
|
+
Cat: cat [ parent: Pets ]
|
|
194
|
+
Pets: "Domestic Pets" [ type: package ]
|
|
160
195
|
|
|
161
196
|
Dog --(isa)--> Animal
|
|
162
197
|
Cat --(isa)--> Animal
|
|
@@ -170,11 +205,11 @@ over them. The recognized options, with their types and built-in defaults, are:
|
|
|
170
205
|
|
|
171
206
|
${configOptions}
|
|
172
207
|
|
|
173
|
-
The "size-*", "group-*", "graph-*", "hub-*", and "grid-*" options
|
|
174
|
-
rendering geometry (canvas margin, node box sizing, edge routing,
|
|
175
|
-
spacing, and the per-diagram-type layout) and take
|
|
176
|
-
the boolean "grid-node-width-equal", which forces
|
|
177
|
-
diagram to the width of the widest one.
|
|
208
|
+
The "size-*", "group-*", "container-*", "graph-*", "hub-*", and "grid-*" options
|
|
209
|
+
control the rendering geometry (canvas margin, node box sizing, edge routing,
|
|
210
|
+
group and container box spacing, and the per-diagram-type layout) and take
|
|
211
|
+
non-negative numbers, except the boolean "grid-node-width-equal", which forces
|
|
212
|
+
all node boxes of a "grid" diagram to the width of the widest one.
|
|
178
213
|
|
|
179
214
|
The "size-node-width-max" option additionally enables the word-wrapping of the
|
|
180
215
|
node box texts: given a positive value, the node name, its type, and its
|
|
@@ -215,57 +250,26 @@ On a malformed input, a violated diagram type constraint, or an invalid "config"
|
|
|
215
250
|
argument, the tool instead returns an error result whose text starts with
|
|
216
251
|
"gradia: ERROR: ", followed by the reason.
|
|
217
252
|
`.trim();
|
|
218
|
-
/* the arguments of the "gradia_render" tool
|
|
219
|
-
|
|
253
|
+
/* the arguments of the "gradia_render" tool (with their descriptions
|
|
254
|
+
reflecting the service defaults underlying the arguments) */
|
|
255
|
+
const toolArgumentsOf = (defaults) => ({
|
|
220
256
|
input: z.string()
|
|
221
257
|
.describe("The graph description in the Gradia input language (see the tool description for its grammar)."),
|
|
222
258
|
type: z.enum(diagramTypes).optional()
|
|
223
259
|
.describe("The diagram type. Takes precedence over a \"#type\" directive inside the input. " +
|
|
224
|
-
|
|
260
|
+
(defaults.type !== undefined ?
|
|
261
|
+
`Defaults to "${defaults.type}".` :
|
|
262
|
+
`Defaults to the "#type" directive of the input, else to "${diagramTypeDefault}".`)),
|
|
225
263
|
format: z.enum(diagramFormats).optional()
|
|
226
264
|
.describe("The output format: \"svg:standalone\" (a standalone SVG/XML document), " +
|
|
227
265
|
"\"svg:embedded\" (the SVG without the \"<?xml?>\" declaration, for embedding into HTML), " +
|
|
228
266
|
"\"url:xml\" (a \"data:image/svg+xml\" URL with URL-encoded XML), or " +
|
|
229
267
|
"\"url:base64\" (a \"data:image/svg+xml\" URL with Base64-encoded XML). " +
|
|
230
|
-
`Defaults to "${diagramFormatDefault}".`),
|
|
268
|
+
`Defaults to "${defaults.format ?? diagramFormatDefault}".`),
|
|
231
269
|
config: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional()
|
|
232
270
|
.describe("The rendering configuration options (see the tool description for their names, " +
|
|
233
271
|
"types, and defaults). Takes precedence over the \"#config\" directives inside the input.")
|
|
234
|
-
};
|
|
235
|
-
/* validate and coerce the rendering configuration options of a tool
|
|
236
|
-
call (the arguments are treated as untrusted, so an unknown option
|
|
237
|
-
or an invalid value is rejected instead of being silently applied) */
|
|
238
|
-
const validateConfig = (raw) => {
|
|
239
|
-
const config = {};
|
|
240
|
-
for (const [key, val] of Object.entries(raw)) {
|
|
241
|
-
if (!Object.hasOwn(configDefaults, key))
|
|
242
|
-
throw new Error(`unknown configuration option "${key}"`);
|
|
243
|
-
const k = key;
|
|
244
|
-
if (k === "font-embed")
|
|
245
|
-
throw new Error(`configuration option "${key}" is not available in this context`);
|
|
246
|
-
else if (typeof configDefaults[k] === "boolean") {
|
|
247
|
-
if (typeof val !== "boolean" && val !== "true" && val !== "false")
|
|
248
|
-
throw new Error(`invalid value "${String(val)}" for boolean configuration option "${key}" ` +
|
|
249
|
-
"(expected \"true\" or \"false\")");
|
|
250
|
-
config[k] = typeof val === "boolean" ? val : val === "true";
|
|
251
|
-
}
|
|
252
|
-
else if (typeof configDefaults[k] === "number") {
|
|
253
|
-
const num = Number(val);
|
|
254
|
-
if (typeof val === "boolean" || val === "" || !Number.isFinite(num) || num < 0)
|
|
255
|
-
throw new Error(`invalid value "${String(val)}" for numeric configuration option "${key}" ` +
|
|
256
|
-
"(expected a non-negative number)");
|
|
257
|
-
config[k] = num;
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
if (typeof val !== "string")
|
|
261
|
-
throw new Error(`invalid value "${String(val)}" for string configuration option "${key}" (expected a string)`);
|
|
262
|
-
if (k === "font-family" && val.endsWith(".woff2"))
|
|
263
|
-
throw new Error("configuration option \"font-family\" must not be the path to a WOFF2 file in this context");
|
|
264
|
-
config[k] = val;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
return config;
|
|
268
|
-
};
|
|
272
|
+
});
|
|
269
273
|
/* run the MCP service on stdio */
|
|
270
274
|
export const serve = async (meta, defaults = {}) => {
|
|
271
275
|
/* establish the MCP server and its single tool */
|
|
@@ -277,11 +281,11 @@ export const serve = async (meta, defaults = {}) => {
|
|
|
277
281
|
server.registerTool("gradia_render", {
|
|
278
282
|
title: "Render an object graph as an SVG diagram",
|
|
279
283
|
description: toolDescription,
|
|
280
|
-
inputSchema:
|
|
284
|
+
inputSchema: toolArgumentsOf(defaults),
|
|
281
285
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
282
286
|
}, async (args) => {
|
|
283
287
|
try {
|
|
284
|
-
const config = { ...defaults.config, ...validateConfig(args.config ?? {}) };
|
|
288
|
+
const config = { ...defaults.config, ...validateConfig(args.config ?? {}, { trusted: false }) };
|
|
285
289
|
const out = await Gradia.render(args.input, {
|
|
286
290
|
type: args.type ?? defaults.type,
|
|
287
291
|
format: args.format ?? defaults.format,
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Object Graph Diagram Rendering",
|
|
4
4
|
"keywords": [ "object", "graph", "diagram", "rendering", "svg", "layout", "api", "cli" ],
|
|
5
5
|
"publishConfig": { "access": "public" },
|
|
6
|
-
"version": "1.1
|
|
6
|
+
"version": "1.2.1",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Dr. Ralf S. Engelschall",
|