@rse/gradia 0.9.8 → 1.0.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/dst/gradia-cli.js CHANGED
@@ -13,7 +13,6 @@ import { Command, Option } from "commander";
13
13
  /* internal dependencies */
14
14
  import { configDefaults } from "./gradia-api-config.js";
15
15
  import { Gradia, diagramTypes, diagramTypeDefault, diagramFormats, diagramFormatDefault } from "./gradia-api.js";
16
- import { serve as serveMCP } from "./gradia-mcp.js";
17
16
  /* internal package meta-information */
18
17
  const pkg = JSON.parse(fs.readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
19
18
  /* establish the command-line interface */
@@ -37,9 +36,9 @@ program.name("gradia")
37
36
  const m = /^([a-z][a-z0-9-]*)=(.*)$/.exec(nv);
38
37
  if (m === null)
39
38
  throw new Error(`invalid configuration option "${nv}" (expected "<name>=<value>")`);
40
- const key = m[1];
41
- if (!Object.hasOwn(configDefaults, key))
39
+ if (!Object.hasOwn(configDefaults, m[1]))
42
40
  throw new Error(`unknown configuration option "${m[1]}"`);
41
+ const key = m[1];
43
42
  if (typeof configDefaults[key] === "boolean") {
44
43
  if (m[2] !== "true" && m[2] !== "false")
45
44
  throw new Error(`invalid value "${m[2]}" for boolean configuration option "${key}" (expected "true" or "false")`);
@@ -51,6 +50,8 @@ program.name("gradia")
51
50
  throw new Error(`invalid value "${m[2]}" for numeric configuration option "${key}" (expected a non-negative number)`);
52
51
  store[key] = num;
53
52
  }
53
+ else if (m[2] === "")
54
+ throw new Error(`invalid empty value for string configuration option "${key}"`);
54
55
  else
55
56
  store[key] = m[2];
56
57
  }
@@ -63,6 +64,7 @@ program.name("gradia")
63
64
  throw new Error("option \"--mcp\" cannot be combined with an input graph description file");
64
65
  if (options.output !== undefined)
65
66
  throw new Error("option \"--mcp\" cannot be combined with option \"--output\"");
67
+ const { serve: serveMCP } = await import("./gradia-mcp.js");
66
68
  await serveMCP(pkg, { type: options.type, format: options.format, config });
67
69
  return;
68
70
  }
package/dst/gradia-mcp.js CHANGED
@@ -16,7 +16,7 @@ import { Gradia, diagramTypes, diagramFormats, diagramFormatDefault } from "./gr
16
16
  arbitrary local WOFF2 files) */
17
17
  const configOptions = Object.entries(configDefaults)
18
18
  .filter(([key]) => key !== "font-embed")
19
- .map(([key, val]) => ` - "${key}" (${typeof val === "number" ? "number" : "string"}, default: ${JSON.stringify(val)})`)
19
+ .map(([key, val]) => ` - "${key}" (${typeof val}, default: ${JSON.stringify(val)})`)
20
20
  .join("\n");
21
21
  /* the comprehensive description of the "gradia_render" tool, teaching
22
22
  the AI the complete input language and the available parameters */
@@ -28,11 +28,37 @@ component relationships, data flows, dependency graphs, and similar structures.
28
28
  ## Diagram Types
29
29
 
30
30
  - "graph": a loosely grid-snapped layered layout of the whole graph. This is
31
- the general-purpose type and the built-in default.
31
+ the general-purpose type and the built-in default. It accepts any topology,
32
+ including self-loops and free-standing nodes.
32
33
  - "hub": a hub graph, placing the single primary node (the node carrying the
33
34
  attribute "primary: true") in the center, its input nodes to the left and
34
- its output nodes to the right.
35
- - "grid": a compact grid of tiles for an edge-less graph.
35
+ its output nodes to the right. It accepts only the constrained topology
36
+ described below.
37
+ - "grid": a compact grid of tiles for an edge-less graph. The nodes are placed
38
+ in declaration order onto a roughly square, row-major grid.
39
+
40
+ ### Diagram Type Constraints
41
+
42
+ The "hub" type rejects the input with an error unless all of the following
43
+ conditions hold:
44
+
45
+ - Exactly one node carries the attribute "primary: true".
46
+ - Every edge either points to or originates from that primary node. An edge
47
+ between two non-primary nodes is rejected.
48
+ - The primary node carries no self-loop.
49
+ - Every non-primary node is an input or an output of the primary node, i.e.,
50
+ no node is free-standing.
51
+
52
+ A node which is both an input and an output of the primary node is placed
53
+ twice, once in the input column and once in the output column. The second
54
+ placement is rendered as a dashed "ghost" box, colored by the
55
+ "color-node-ghost-*" options.
56
+
57
+ The "grid" type rejects the input with an error as soon as the graph contains
58
+ at least one edge.
59
+
60
+ The "graph" type imposes no such constraints and hence is the safe choice
61
+ whenever the topology is not known to fit "hub" or "grid".
36
62
 
37
63
  ## Input Language
38
64
 
@@ -85,6 +111,10 @@ as <name>, terminals as "...", optional parts as [ ... ], repeatable parts as
85
111
  - A <string> cannot span lines. Inside it, "\\" escapes the following character.
86
112
  - A <directive> is a <comment> which occupies its entire line and starts with
87
113
  the keyword "#config" or "#type". All other comments are ignored.
114
+ - Of multiple "#type" directives the last one wins, and a directive naming an
115
+ invalid type is silently ignored. A "#config" directive naming an unknown
116
+ option or carrying an invalid value is silently ignored, too, whereas the
117
+ same mistake in the "config" tool argument is rejected with an error.
88
118
 
89
119
  ### Semantic Rules
90
120
 
@@ -100,14 +130,20 @@ as <name>, terminals as "...", optional parts as [ ... ], repeatable parts as
100
130
  its cardinality label.
101
131
  - Four attribute keys are reserved and consumed by the renderer instead of being
102
132
  displayed as a regular "<key>: <val>" line:
103
- - "url: <url>" renders the node box as a hyperlink,
133
+ - "url: <url>" renders the node box as a hyperlink. Only relative URLs and
134
+ the schemes "http", "https", and "mailto" are honored; any other URL is
135
+ silently dropped and the node box then simply stays unlinked,
104
136
  - "type: <name>" renders <name> as a smaller text above the node label,
105
137
  - "primary: true" renders the node in the primary node colors (and, for the
106
- diagram type "hub", marks the single central hub node),
138
+ diagram type "hub", marks the single central hub node). The value has to
139
+ be the literal string "true",
107
140
  - "group: <name>" places the node into the surrounding group box <name>.
108
141
  - A node cannot be a member of more than one group. As soon as at least one node
109
142
  carries a "group" attribute, all nodes without one implicitly belong to the
110
143
  group "default", and every edge has to stay within a single group.
144
+ - The groups are laid out individually with the selected diagram type and then
145
+ stacked vertically as decorated group boxes. For the type "hub" this means
146
+ that every single group needs its own primary node.
111
147
  - All remaining attributes are displayed as "<key>: <val>" lines inside the node
112
148
  box.
113
149
 
@@ -136,12 +172,37 @@ ${configOptions}
136
172
 
137
173
  The "size-*", "group-*", "graph-*", "hub-*", and "grid-*" options control the
138
174
  rendering geometry (canvas margin, node box sizing, edge routing, group box
139
- spacing, and the per-diagram-type layout) and take non-negative numbers. The
140
- "font-family" and "color-*" options are embedded into the generated SVG: when
141
- such an option is not explicitly configured, the SVG instead references the CSS
142
- custom property "--gradia-<option>" (definable by an embedding HTML document)
143
- and falls back to the built-in default. Leave them unset unless a particular
144
- look is explicitly requested.
175
+ spacing, and the per-diagram-type layout) and take non-negative numbers, except
176
+ the boolean "grid-node-width-equal", which forces all node boxes of a "grid"
177
+ diagram to the width of the widest one.
178
+
179
+ The "size-node-width-max" option additionally enables the word-wrapping of the
180
+ node box texts: given a positive value, the node name, its type, and its
181
+ attribute lines are greedily broken at whitespace, so that the node box stays
182
+ within the given width and grows in height instead. This is the only way to
183
+ produce multi-line node boxes. The default "0" disables the wrapping entirely
184
+ and hence lets a node box become as wide as its longest text. A single word
185
+ wider than the maximum is never broken and still widens the box beyond it.
186
+
187
+ The "grid-columns-min" and "grid-columns-max" options control the column count
188
+ of a "grid" diagram, which by default is derived from the node count as a
189
+ roughly square grid: "grid-columns-min" (default 3) raises the derived count,
190
+ so that a few nodes still share a single row instead of being stacked, while
191
+ "grid-columns-max" (default 4) caps it, so that larger diagrams grow in height
192
+ only. The column count never exceeds the node count and the maximum always
193
+ wins over the minimum.
194
+
195
+ The "font-family" and "color-*" options are embedded into the generated SVG:
196
+ when such an option is not explicitly configured, the SVG instead references
197
+ the CSS custom property "--gradia-<option>" (definable by an embedding HTML
198
+ document) and falls back to the built-in default. Leave them unset unless a
199
+ particular look is explicitly requested.
200
+
201
+ The "font-family" option is either the built-in font family "Source Sans 3" or
202
+ a plain font family name resolved by the displaying document. A path to a
203
+ WOFF2 file is rejected in this context, exactly like the option "font-embed":
204
+ both are available only through the (trusted) command-line options of the
205
+ service.
145
206
 
146
207
  ## Result
147
208
 
@@ -149,6 +210,10 @@ The tool returns the rendered diagram as a single text result, in the requested
149
210
  output format. For the "svg:*" formats this is the SVG/XML document, which is
150
211
  usually written to a "*.svg" file; for the "url:*" formats it is a
151
212
  "data:image/svg+xml" URL, usable as the "src" of an HTML "<img>" element.
213
+
214
+ On a malformed input, a violated diagram type constraint, or an invalid "config"
215
+ argument, the tool instead returns an error result whose text starts with
216
+ "gradia: ERROR: ", followed by the reason.
152
217
  `.trim();
153
218
  /* the arguments of the "gradia_render" tool */
154
219
  const toolArguments = {
@@ -176,11 +241,17 @@ const validateConfig = (raw) => {
176
241
  if (!Object.hasOwn(configDefaults, key))
177
242
  throw new Error(`unknown configuration option "${key}"`);
178
243
  const k = key;
179
- if (typeof configDefaults[k] === "boolean")
244
+ if (k === "font-embed")
180
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
+ }
181
252
  else if (typeof configDefaults[k] === "number") {
182
253
  const num = Number(val);
183
- if (typeof val === "boolean" || !Number.isFinite(num) || num < 0)
254
+ if (typeof val === "boolean" || val === "" || !Number.isFinite(num) || num < 0)
184
255
  throw new Error(`invalid value "${String(val)}" for numeric configuration option "${key}" ` +
185
256
  "(expected a non-negative number)");
186
257
  config[k] = num;
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": "0.9.8",
6
+ "version": "1.0.0",
7
7
  "license": "MIT",
8
8
  "author": {
9
9
  "name": "Dr. Ralf S. Engelschall",
@@ -26,7 +26,7 @@
26
26
  "dependencies": {
27
27
  "commander": "15.0.0",
28
28
  "@antv/layout": "2.0.0",
29
- "nanoid": "6.0.1",
29
+ "pure-uuid": "2.0.0",
30
30
  "source-sans": "3.52.0",
31
31
  "@modelcontextprotocol/sdk": "1.30.0",
32
32
  "zod": "4.4.3"