nicevg 0.16.0 → 0.17.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 CHANGED
@@ -1,139 +1,92 @@
1
- # nicevg for TypeScript (Bun and Node)
1
+ # nicevg
2
2
 
3
- nicevg で図を SVG に描く。TypeScript や JavaScript から、Bun では `bun:ffi`、Node では [koffi](https://koffi.dev/) でネイティブライブラリを呼ぶ。図はノードとコネクタの配列(Sketch)のほか、SVG の文字列でも渡せる。処理は Rust のネイティブコードで動くので、CLI と同じ速さで、出力も同じになる。
3
+ [![npm](https://img.shields.io/npm/v/nicevg)](https://www.npmjs.com/package/nicevg)
4
+ [![CI](https://github.com/takagiy/nicevg/actions/workflows/ci.yml/badge.svg)](https://github.com/takagiy/nicevg/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/takagiy/nicevg/blob/main/LICENSE)
4
6
 
5
- ## インストール
7
+ An SVG diagram tool for AI agents, for TypeScript and JavaScript on Bun and Node. It fixes a diagram's problems mechanically on the agent's behalf, and optimizes the layout while preserving the original intent.
6
8
 
7
- ```sh
8
- npm install nicevg # または bun add nicevg
9
- ```
10
-
11
- パッケージには、次の環境向けにビルド済みのネイティブライブラリが入っているので、インストール時にスクリプトは走らず、Rust も要らない。
12
-
13
- | 環境 | 対象 |
9
+ | Before | After |
14
10
  |---|---|
15
- | Linux | x64、arm64(glibc 2.17 以降) |
16
- | macOS | arm64、x64 |
17
- | Windows | x64 |
11
+ | ![A service diagram with overlapping boxes, an overflowing label, a diagonal line, lines through a box, a box cut off at the edge and labels far from their lines](https://cdn.jsdelivr.net/gh/takagiy/nicevg@main/docs/images/before.svg) | ![The same diagram repaired by nicevg](https://cdn.jsdelivr.net/gh/takagiy/nicevg@main/docs/images/after.svg) |
18
12
 
19
- ## 使い方
13
+ ## Install
20
14
 
21
- 入口は `render()` 1 つ。図を受け取り、配置の問題を直し、ノードを少し動かして折れ・交差・混雑を減らして、SVG を表す `Svg` を返す。
22
-
23
- ```ts
24
- import { render } from "nicevg";
25
-
26
- const svg = render({
27
- nodes: [
28
- { id: "order", x: 20, y: 20, width: 120, height: 56, label: "Order" },
29
- { id: "bill", x: 300, y: 160, width: 120, height: 56, label: "Bill" },
30
- { id: "user", x: 40, y: 200, width: 80, height: 80, shape: "circle", label: "User" },
31
- ],
32
- connectors: [
33
- { from: "order", to: "bill", label: "invoice" },
34
- { from: "user", to: "order" },
35
- ],
36
- });
37
-
38
- await svg.write("diagram.svg"); // ファイルに書く
39
- const text = svg.toString(); // SVG の文字列(`${svg}` でも同じ)
40
-
41
- for (const node of svg.report.diagram.nodes) {
42
- console.log(node.id, node.bounds); // 動いた後の位置と大きさ
43
- }
44
- for (const connector of svg.report.diagram.connectors) {
45
- console.log(connector.from, connector.to, connector.points); // 両端のノードと、直交する経路の折れ点
46
- }
47
- console.log(svg.report.issues); // 残った問題(なければ空)
48
- console.log(svg.changes); // 何を直し、何を動かしたか
15
+ ```sh
16
+ npm install nicevg
17
+ # or
18
+ bun add nicevg
49
19
  ```
50
20
 
51
- - `input` は `Sketch`、`{ svg: string }`、または前に返った `Svg`。
52
-
53
- ### Sketch
54
-
55
- | 項目 | 内容 |
56
- |---|---|
57
- | `nodes[].id`、`x`、`y`、`width`、`height` | ノードの id と、左上の位置と大きさ |
58
- | `nodes[].shape` | `"rect"`(既定)か `"circle"`。円は外接する正方形を `x`〜`height` で渡す |
59
- | `nodes[].label`、`fontSize` | 中に書く文字(`\n` で改行)。子を持つノードでは左上に、それ以外では中央に置く |
60
- | `nodes[].parent` | このノードを入れる親ノードの id |
61
- | `nodes[].allowOverlap` | 意図した重なりなら `true` |
62
- | `nodes[].class` | 描き方を `style` から選ぶ名前。付けたノードは `width`・`height`・`shape`・`label` を使わず、`style` が描く |
63
- | `edges[].from`、`to` | 両端のノードの id。`edges` は前の名前の `connectors` でも渡せる(両方は渡せない) |
64
- | `edges[].points` | 経路の折れ点。省くと、修正と配置調整が両端のノードの辺から辺へ経路を引く |
65
- | `edges[].label`、`fontSize` | コネクタの脇に置く文字 |
66
- | `style` | クラス名から、そのクラスのノードを描く関数への対応 |
67
- | `rootAttrs` | ルートの `<svg>` に付ける属性(`{ "font-family": "'Yu Gothic', sans-serif" }` など)。中の文字はこれを継承する。`xmlns`・`viewBox`・`width`・`height` は nicevg が書くので渡せない |
68
-
69
- ### style
70
-
71
- `style` の関数はノードを受け取り、ノードの左上を原点にして描いた SVG を `svg` で返す。ノードに足した項目(下の `no` など)も受け取れる。置く位置は `render` がノードの `x`、`y` と配置調整の移動から `translate` で付ける。
21
+ ## Example
72
22
 
73
23
  ```ts
74
24
  import { render, svg, text } from "nicevg";
75
25
 
76
26
  const image = render({
77
27
  nodes: [
78
- { id: "customer", x: 20, y: 40, width: 80, height: 40, label: "顧客" },
79
- { id: "orders", class: "store", x: 260, y: 40, no: "D1", label: "注文" },
28
+ { id: "order", x: 20, y: 20, width: 120, height: 56, label: "Order" },
29
+ { id: "orders", class: "pill", x: 300, y: 160, label: "Orders" },
80
30
  ],
81
- edges: [{ from: "customer", to: "orders", label: "注文" }],
31
+ edges: [{ from: "order", to: "orders", label: "save" }],
82
32
  style: {
83
- store: ({ no, label }) => {
84
- const id = text(String(no)), name = text(String(label));
85
- const sep = id.width + 24, w = sep + name.width + 24, h = Math.max(id.height, name.height) + 20;
33
+ pill: ({ label }) => {
34
+ const name = text(label);
35
+ const w = name.width + 32, h = name.height + 20;
86
36
  return svg`
87
- <g fill="none" stroke="#333">
88
- <path d="M ${w} 0 H 0 V ${h} H ${w}"/>
89
- <line x1="${sep}" y1="0" x2="${sep}" y2="${h}"/>
90
- </g>
91
- ${id.at({ cx: sep / 2, cy: h / 2 })}
92
- ${name.at({ left: sep + 12, cy: h / 2 })}`;
37
+ <rect width="${w}" height="${h}" rx="${h / 2}" fill="white" stroke="black"/>
38
+ ${name.at({ cx: w / 2, cy: h / 2 })}`;
93
39
  },
94
40
  },
41
+ rootAttrs: { "font-family": "sans-serif" },
95
42
  });
43
+ await image.write("diagram.svg");
96
44
  ```
97
45
 
98
- - `` svg`...` `` の中は SVG そのもの。埋め込んだ文字列はエスケープされ、`svg` の断片、`text`、それらの配列はそのまま入る。断片は `.at(x, y)` で原点をずらせる。
99
- - `text(文字列, 大きさ)` は 1 行の `<text>`。`width`・`height` は nicevg と同じ見積もり。`.at({ 横, 縦 })` で、横は `left`・`cx`・`right`、縦は `top`・`cy`・`bottom` から 1 つずつ選び、文字の箱のその点をその座標に置く。
100
- - ノードの範囲は、描いたもの(`rect`、`circle`、`ellipse`、`line`、`polyline`、`polygon`、`path`、`text`)を囲む矩形。コネクタはその辺から出入りする。曲線と円弧は端点だけで数える。
101
- - 修正と配置調整はノードを動かすが、大きさは変えない。中の `<text>` はラベルとして検査しないので、収まりは描く側が決める。
102
-
103
- `Svg` の `report.diagram` は、描いた図を読み直したもので、ノード(`bounds`、ラベルの範囲 `labelBounds`)、コネクタ(`points`)、ラベル(`bounds`、説明するコネクタの id `connector`)を持つ。コネクタは id を持たないので、`from` と `to` で見分ける。文字の幅は、全角を 1 文字あたりフォントサイズ、それ以外を 0.59 倍として見積もる。
104
-
105
- 形の書き方が違う入力、存在しないノードを指す `from`・`to`・`parent`、`style` にないクラス、整形式でない SVG を描いたスタイルは、どこが何を指しているか(`connectors[0].to`、`nodes[1].class` など)を書いた `NicevgError` を投げる。
106
-
107
- 描画は呼んだスレッドで最後まで走る。大きな図の配置調整は 1〜2 秒かかり、その間すべてのコアを使う。
108
-
109
- ## 開発
46
+ ## API
110
47
 
111
- Bun と Node(型を取り除いて .ts を動かせる 22.18 以降)、Rust のツールチェーンが要る。
48
+ ### `render(input)`
112
49
 
113
- ```sh
114
- bun install
115
- bun run build # cargo でネイティブライブラリを作って native/ に置き、tsc で src/ を dist/ に書き出す
116
- bun run test # Bun で src/ を、Node でビルドした dist/ をテストする
117
- bun run typecheck
118
- ```
50
+ Fixes the diagram, nudges its nodes into a cleaner layout and returns an `Svg`. `input` is a sketch (below), `{ svg: string }` with SVG text, or an `Svg` returned before. Bad input throws a `NicevgError` that says which field is wrong, such as `edges[0].to` or `nodes[1].class`.
119
51
 
120
- パッケージは Bun には `src/` の TypeScript を、Node には `dist/` の JavaScript と型定義を渡す(`package.json` の `exports`)。ネイティブライブラリは `NICEVG_LIBRARY` 環境変数、`native/<platform>-<arch>/`(`bun run build` が置く場所で、公開するパッケージでは全環境ぶん)、リポジトリの `target/release/` の順に探す。
52
+ | Option | |
53
+ |---|---|
54
+ | `nodes` | The boxes, circles and styled nodes (below). |
55
+ | `edges` | The lines between nodes (below). `connectors` is accepted as its old name. |
56
+ | `style` | For each `class` used by a node, a function drawing that node with `svg` and `text`. |
57
+ | `rootAttrs` | Attributes for the root `<svg>`, such as `{ "font-family": "sans-serif" }`. |
121
58
 
122
- `v*` のタグを push すると、`.github/workflows/npm.yml` が各環境のネイティブライブラリを作ってパッケージに入れ、Bun と Node でテストしてから npm に公開する。タグの版と `package.json` の `version` が違えば止まるので、リリースのときは Rust の `Cargo.toml`(ルートと `bindings/js`)と一緒に `package.json` の版も上げる。手で実行すると、公開せずにビルドとパックだけを確かめられる。
59
+ | Node field | |
60
+ |---|---|
61
+ | `id`, `x`, `y` | Its id and its top left corner. |
62
+ | `width`, `height` | Its size. Not used with `class`. |
63
+ | `label`, `fontSize` | Its text, lines split at `\n`. |
64
+ | `shape` | `"rect"` (the default) or `"circle"`. |
65
+ | `parent` | The id of the node it sits inside. |
66
+ | `class` | The `style` that draws it. Other fields you add are passed to that style. |
67
+ | `allowOverlap` | `true` if it overlaps other nodes on purpose. |
68
+
69
+ | Edge field | |
70
+ |---|---|
71
+ | `from`, `to` | The ids of the nodes it joins. |
72
+ | `label`, `fontSize` | Its text, placed beside it. |
73
+ | `points` | Its route. Without one, rendering routes it from side to side. |
123
74
 
124
- 公開は npm の Trusted Publishing(GitHub Actions の OIDC)で、npm のトークンを使わず、配布元の証明(provenance)も自動で付く。
75
+ ### `Svg`
125
76
 
126
- Trusted Publisher はすでに npm にあるパッケージにしか登録できないので、最初の 1 回だけは手元から公開する。npm のアカウントで 2FA を有効にしたうえで、次のようにする。
77
+ | Member | |
78
+ |---|---|
79
+ | `write(path)` | Writes the SVG to a file. |
80
+ | `toString()` | The SVG text. |
81
+ | `report.issues` | Problems left after rendering; empty when there are none. |
82
+ | `report.diagram` | The diagram read back: `nodes` with their `bounds`, `connectors` with their `points`, and `labels`. |
83
+ | `changes` | What rendering fixed and moved, in order. |
127
84
 
128
- 1. Actions の「Publish to npm」を手で実行する(手での実行は公開しない)。全環境のライブラリをそれぞれの環境でテストし、まとめたパッケージが、成果物 `npm-package` に残る。
129
- 2. それを手元に落として公開する(2FA のワンタイムコードを聞かれる)。手元からの公開なので、この版には配布元の証明が付かない。
130
- ```sh
131
- gh run download <run-id> -n npm-package
132
- npm publish ./nicevg-<version>.tgz
133
- ```
134
- 3. このリポジトリと `npm.yml` を Trusted Publisher に登録する(npmjs.com のパッケージの設定か、次のコマンド。どちらも 2FA が要る)。
135
- ```sh
136
- npm trust github nicevg --file npm.yml --repo takagiy/nicevg --allow-publish
137
- ```
85
+ ### Drawing a style
138
86
 
139
- 以後はタグの push だけで公開される。npm のトークンは作らない(2FA を回避するトークンは、npm が段階的に廃止している)。
87
+ | Function | |
88
+ |---|---|
89
+ | `` svg`...` `` | SVG markup. Embedded strings are escaped; `svg` fragments, texts and arrays of them go in as they are. |
90
+ | `text(content, size = 14)` | One line of text, with the `width` and `height` nicevg measures it at. |
91
+ | `text(...).at({ left \| cx \| right, top \| cy \| bottom })` | Places the text so that point of its box sits at those coordinates. |
92
+ | `` svg`...`.at(x, y) `` | The fragment with its origin moved to `x`, `y`. |
package/dist/index.d.ts CHANGED
@@ -79,15 +79,21 @@ export interface StyledNode {
79
79
  [field: string]: unknown;
80
80
  }
81
81
  /** Draws a styled node's picture, from its own origin, with `svg` and `text`. */
82
- export type Style = (node: StyledNode) => SvgFragment;
82
+ export type Style<Node extends StyledNode = StyledNode> = (node: Node) => SvgFragment;
83
+ /** A style for each class the nodes name, each given the nodes of its class. */
84
+ export type Styles<Node extends SketchNode | StyledNode> = {
85
+ [Class in Extract<Node, StyledNode>["class"]]: Style<Extract<Node, {
86
+ class: Class;
87
+ }>>;
88
+ };
83
89
  /** A diagram as nodes and the edges (connectors) between them. */
84
- export interface Sketch {
85
- nodes: (SketchNode | StyledNode)[];
90
+ export interface Sketch<Node extends SketchNode | StyledNode = SketchNode | StyledNode> {
91
+ nodes: readonly Node[];
86
92
  edges?: SketchConnector[];
87
93
  /** The same as `edges`, its name before; pass one or the other. */
88
94
  connectors?: SketchConnector[];
89
95
  /** How to draw the nodes of each class. */
90
- style?: Record<string, Style>;
96
+ style?: Styles<NoInfer<Node>>;
91
97
  /**
92
98
  * Attributes for the root `<svg>`, such as `font-family`, which its texts
93
99
  * inherit. nicevg writes `xmlns`, `viewBox`, `width` and `height` itself.
@@ -95,7 +101,7 @@ export interface Sketch {
95
101
  rootAttrs?: Record<string, string | number>;
96
102
  }
97
103
  /** A diagram to render: a sketch, SVG text following nicevg's SVG contract, or an Svg rendered before. */
98
- export type Input = Sketch | {
104
+ export type Input<Node extends SketchNode | StyledNode = SketchNode | StyledNode> = Sketch<Node> | {
99
105
  svg: string;
100
106
  } | Svg;
101
107
  export interface DiagramNode {
@@ -171,4 +177,4 @@ export declare class NicevgError extends Error {
171
177
  * routes through nodes, stray labels and more), routes the connectors given
172
178
  * no points, and moves nodes a little where that reads better.
173
179
  */
174
- export declare function render(input: Input): Svg;
180
+ export declare function render<const Node extends SketchNode | StyledNode>(input: Input<Node>): Svg;
package/dist/index.js CHANGED
@@ -64,7 +64,9 @@ export function render(input) {
64
64
  return new Svg(reply.ok.svg, reply.ok.changes, reply.ok.report);
65
65
  }
66
66
  /** The sketch as the native library reads it: styled nodes drawn, edges as connectors. */
67
- function sketchRequest({ nodes, edges, connectors, style = {}, rootAttrs }) {
67
+ function sketchRequest({ nodes, edges, connectors, style, rootAttrs }) {
68
+ // Each style is called only with nodes of its own class.
69
+ const styles = (style ?? {});
68
70
  if (edges && connectors) {
69
71
  throw new NicevgError("edges and connectors are the same; pass one of them");
70
72
  }
@@ -73,7 +75,7 @@ function sketchRequest({ nodes, edges, connectors, style = {}, rootAttrs }) {
73
75
  if (!("class" in node)) {
74
76
  return node;
75
77
  }
76
- const draw = Object.hasOwn(style, node.class) ? style[node.class] : undefined;
78
+ const draw = Object.hasOwn(styles, node.class) ? styles[node.class] : undefined;
77
79
  if (!draw) {
78
80
  throw new NicevgError(`nodes[${index}].class: no style named "${node.class}"`);
79
81
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nicevg",
3
- "version": "0.16.0",
3
+ "version": "0.17.1",
4
4
  "description": "Deterministic checks and repairs for diagrams, given as shapes or SVG, on Bun and Node through native FFI.",
5
5
  "keywords": [
6
6
  "diagram",
package/src/index.ts CHANGED
@@ -90,16 +90,21 @@ export interface StyledNode {
90
90
  }
91
91
 
92
92
  /** Draws a styled node's picture, from its own origin, with `svg` and `text`. */
93
- export type Style = (node: StyledNode) => SvgFragment;
93
+ export type Style<Node extends StyledNode = StyledNode> = (node: Node) => SvgFragment;
94
+
95
+ /** A style for each class the nodes name, each given the nodes of its class. */
96
+ export type Styles<Node extends SketchNode | StyledNode> = {
97
+ [Class in Extract<Node, StyledNode>["class"]]: Style<Extract<Node, { class: Class }>>;
98
+ };
94
99
 
95
100
  /** A diagram as nodes and the edges (connectors) between them. */
96
- export interface Sketch {
97
- nodes: (SketchNode | StyledNode)[];
101
+ export interface Sketch<Node extends SketchNode | StyledNode = SketchNode | StyledNode> {
102
+ nodes: readonly Node[];
98
103
  edges?: SketchConnector[];
99
104
  /** The same as `edges`, its name before; pass one or the other. */
100
105
  connectors?: SketchConnector[];
101
106
  /** How to draw the nodes of each class. */
102
- style?: Record<string, Style>;
107
+ style?: Styles<NoInfer<Node>>;
103
108
  /**
104
109
  * Attributes for the root `<svg>`, such as `font-family`, which its texts
105
110
  * inherit. nicevg writes `xmlns`, `viewBox`, `width` and `height` itself.
@@ -108,7 +113,7 @@ export interface Sketch {
108
113
  }
109
114
 
110
115
  /** A diagram to render: a sketch, SVG text following nicevg's SVG contract, or an Svg rendered before. */
111
- export type Input = Sketch | { svg: string } | Svg;
116
+ export type Input<Node extends SketchNode | StyledNode = SketchNode | StyledNode> = Sketch<Node> | { svg: string } | Svg;
112
117
 
113
118
  export interface DiagramNode {
114
119
  id: string;
@@ -196,7 +201,7 @@ export class NicevgError extends Error {
196
201
  * routes through nodes, stray labels and more), routes the connectors given
197
202
  * no points, and moves nodes a little where that reads better.
198
203
  */
199
- export function render(input: Input): Svg {
204
+ export function render<const Node extends SketchNode | StyledNode>(input: Input<Node>): Svg {
200
205
  const request = new TextEncoder().encode(
201
206
  JSON.stringify(input instanceof Svg ? { svg: input.toString() } : "svg" in input ? input : sketchRequest(input)),
202
207
  );
@@ -210,7 +215,9 @@ export function render(input: Input): Svg {
210
215
  }
211
216
 
212
217
  /** The sketch as the native library reads it: styled nodes drawn, edges as connectors. */
213
- function sketchRequest({ nodes, edges, connectors, style = {}, rootAttrs }: Sketch) {
218
+ function sketchRequest<Node extends SketchNode | StyledNode>({ nodes, edges, connectors, style, rootAttrs }: Sketch<Node>) {
219
+ // Each style is called only with nodes of its own class.
220
+ const styles = (style ?? {}) as Record<string, Style>;
214
221
  if (edges && connectors) {
215
222
  throw new NicevgError("edges and connectors are the same; pass one of them");
216
223
  }
@@ -219,7 +226,7 @@ function sketchRequest({ nodes, edges, connectors, style = {}, rootAttrs }: Sket
219
226
  if (!("class" in node)) {
220
227
  return node;
221
228
  }
222
- const draw = Object.hasOwn(style, node.class) ? style[node.class] : undefined;
229
+ const draw = Object.hasOwn(styles, node.class) ? styles[node.class] : undefined;
223
230
  if (!draw) {
224
231
  throw new NicevgError(`nodes[${index}].class: no style named "${node.class}"`);
225
232
  }