@cardenelabs/dragon 0.7.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/LICENSE +21 -0
- package/README.md +173 -0
- package/dist/index.cjs +4916 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1451 -0
- package/dist/index.d.ts +1451 -0
- package/dist/index.js +4870 -0
- package/dist/index.js.map +1 -0
- package/examples/quick-start.md +142 -0
- package/package.json +79 -0
- package/src/canvas-bounds.ts +52 -0
- package/src/color.ts +172 -0
- package/src/compile.ts +3739 -0
- package/src/focus.ts +46 -0
- package/src/index.ts +226 -0
- package/src/input-size.ts +154 -0
- package/src/json-parser.ts +434 -0
- package/src/keywords.ts +114 -0
- package/src/notation-lint.ts +304 -0
- package/src/parser.ts +354 -0
- package/src/relative-pos.ts +182 -0
- package/src/schema.ts +24 -0
- package/src/schemas/diagram.json +133 -0
- package/src/types.ts +294 -0
- package/src/v05/index.ts +9 -0
- package/src/v05/parser.ts +1678 -0
- package/src/write-position.ts +270 -0
package/src/focus.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 光らせる相手 (`focus:`) の書き方を読む。
|
|
3
|
+
*
|
|
4
|
+
* 書き方は 2 通り。 箱の名前 1 つか、 矢印 (`A -> B`) で 2 者を指す形。
|
|
5
|
+
*
|
|
6
|
+
* 読み方をここに 1 つだけ置く。 id の形は図種で違う (順序図は縦列の上端 / 下端 / 手順箱、
|
|
7
|
+
* 流れ図は箱 1 個) ため id への解決は図種ごとに残すが、 「どちらの書き方か」 の判断を
|
|
8
|
+
* 図種ごとに持つと、 同じ記述が図種によって別の意味になる。
|
|
9
|
+
*
|
|
10
|
+
* 以前は図種ごとに判定していて、 順序図と汎用の 2 経路が `-` 1 文字を矢印と見なしていた。
|
|
11
|
+
* その結果 `api-gateway` のような名前が「api から gateway への矢印」 と読まれ、 順序図では
|
|
12
|
+
* 光らず、 流れ図では名前に部分一致した別の矢印が光っていた (実測)。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** 光らせる相手の指定。 */
|
|
16
|
+
export type FocusEntry =
|
|
17
|
+
| { kind: "edge"; from: string; to: string }
|
|
18
|
+
| { kind: "node"; name: string };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 矢印の形。 `->` の 2 文字か `→` の 1 文字だけを矢印と見なす。
|
|
22
|
+
*
|
|
23
|
+
* `-` 1 文字を矢印に含めてはいけない。 名前に `-` を使う箱 (`api-gateway` / `shape-api-gateway`)
|
|
24
|
+
* が矢印として読まれる。
|
|
25
|
+
*/
|
|
26
|
+
const ARROW = /^(.+?)\s*(?:->|→)\s*(.+)$/;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 書かれた 1 件を読む。
|
|
30
|
+
*
|
|
31
|
+
* 矢印の両側は 1 文字以上を要求するので、 片側だけの形 (`-> API` / `API ->`) は矢印に
|
|
32
|
+
* ならず名前として読まれる。
|
|
33
|
+
*
|
|
34
|
+
* `knownNames` に一致する名前は矢印より先に名前として読む。 引用符付きで矢印を含む名前
|
|
35
|
+
* (`"A -> B"`) を書いた箱は実在し得るので、 常に矢印と読むと光らせられない (実測 = 名前が
|
|
36
|
+
* 一致する箱があるのに何も光らず、 「矢印が流れにありません」 と誤報した)。
|
|
37
|
+
*
|
|
38
|
+
* @param knownNames 実在する箱の名前。 渡さない場合は書き方だけで判断する
|
|
39
|
+
*/
|
|
40
|
+
export function parseFocusEntry(raw: string, knownNames?: ReadonlySet<string>): FocusEntry {
|
|
41
|
+
const item = raw.trim();
|
|
42
|
+
if (knownNames?.has(item)) return { kind: "node", name: item };
|
|
43
|
+
const m = item.match(ARROW);
|
|
44
|
+
if (!m) return { kind: "node", name: item };
|
|
45
|
+
return { kind: "edge", from: m[1]!.trim(), to: m[2]!.trim() };
|
|
46
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dragon — Text DSL public API
|
|
3
|
+
*
|
|
4
|
+
* Mermaid 感覚で animated SVG を生成する Text DSL。 cdl engine を呼出して CdlDiagram を返す。
|
|
5
|
+
*
|
|
6
|
+
* 使い方:
|
|
7
|
+
* const diagram = textDslToDiagram(source);
|
|
8
|
+
* // diagram は @cardenelabs/cdl の CdlDiagram、 そのまま CdlDiagramView 等に渡せる
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { parseTextDsl } from "./parser";
|
|
12
|
+
export { compileToCdl } from "./compile";
|
|
13
|
+
export type { CompileNotice } from "./compile";
|
|
14
|
+
export { parseTextDslV05 } from "./v05";
|
|
15
|
+
// 記法一覧が「実際に受け付ける値」 を実装から引くための公開。 手書きすると説明と実装がずれる。
|
|
16
|
+
export { PRESET_TYPES } from "./v05/parser";
|
|
17
|
+
export { TONE_ALIAS, NODE_KIND_ALIAS } from "./keywords";
|
|
18
|
+
export { lintDiagram, autoFix } from "./notation-lint";
|
|
19
|
+
export type { LintIssue, LintReport, LintSeverity } from "./notation-lint";
|
|
20
|
+
// canvas pivot 新 spec 図境界計算 helper (§diagram-boundary SSOT)
|
|
21
|
+
export {
|
|
22
|
+
computeDiagramBoundingBox,
|
|
23
|
+
rectsOverlap,
|
|
24
|
+
DIAGRAM_BOUNDARY_PADDING,
|
|
25
|
+
} from "./canvas-bounds";
|
|
26
|
+
export type { DiagramBoundingBox } from "./canvas-bounds";
|
|
27
|
+
// 位置を相対で書くための解決。 組み立て側と画面側の両方が同じ規則を使うために公開する。
|
|
28
|
+
export {
|
|
29
|
+
parseRelativePos,
|
|
30
|
+
resolveRelativePos,
|
|
31
|
+
orderByDependency,
|
|
32
|
+
RELATIVE_GAP_DEFAULT,
|
|
33
|
+
} from "./relative-pos";
|
|
34
|
+
export type { RelativePos, RelativeDirection, AnchorBox } from "./relative-pos";
|
|
35
|
+
// 光らせる相手の書き方の読み取り。 図種ごとの解決経路が同じ規則を共有する。
|
|
36
|
+
export { parseFocusEntry } from "./focus";
|
|
37
|
+
export type { FocusEntry } from "./focus";
|
|
38
|
+
// 画面で見えている座標を記法に落とすための書込み。 記法を知る側に置く。
|
|
39
|
+
export { writeActorPosition } from "./write-position";
|
|
40
|
+
// 図の上での位置を測る。 editor が現在位置を出すのと、 相対指定を解くので同じ規則を使う。
|
|
41
|
+
export { measureActorBoxes } from "./compile";
|
|
42
|
+
// パーツの見た目の大きさと、 位置を書かなかった時の格子。 画面側と組み立て側で同じ規則を使う。
|
|
43
|
+
// 記法が受理する種類の全体。 画面側が「本文が見本を使っているか」 を判定するのに使う (#1022)。
|
|
44
|
+
export { NODE_KIND_VALID } from "./v05/parser";
|
|
45
|
+
|
|
46
|
+
// パーツの大きさを測る 3 つ。 用途で使い分ける (取り違えると経路ごとに絵が変わる)。
|
|
47
|
+
//
|
|
48
|
+
// - `partRenderSize` = 図枠。 画面が描く大きさと、 格子が確保する場所に使う
|
|
49
|
+
// - `partBoxInFrame` = 図枠の中の箱 (パーツ自身の座標)。 画面が書いた座標と相対指定を解くのに使う
|
|
50
|
+
// - `partVisualSize` = 箱の外接矩形 (merge の座標)。 組み立て側が取り込んだ後の大きさに使う
|
|
51
|
+
//
|
|
52
|
+
// 格子だけ図枠なのは、 隣と重ならない幅を確保するのが目的で描く大きさそのものが要るため。
|
|
53
|
+
// 座標と間隔は見えている箱で測る (#937 / #1014)。
|
|
54
|
+
// `大きさ:` の倍率は `partTargetScale` が持つ。 画面側も同じ規則で拡大しないと、
|
|
55
|
+
// 書いた見本だけ経路で大きさが変わる (#1018)。
|
|
56
|
+
// `partDrawsInDiagram` = その見本が図の中に描かれる部品を持つか。 実体が操作パネルの
|
|
57
|
+
// 部品だけの見本 (catalog 80 件中 17 件) は重ねても図に出ないため、画面側が知らせる (#1017)。
|
|
58
|
+
// `倍率:` (`scale`) は図形の倍率として予約する (#1026)。 画面と組み立てで意味が違うと、
|
|
59
|
+
// 同じ本文が経路で別の絵になる。 正規化 (`normalizePartScale`) と `大きさ:` との掛け合わせ
|
|
60
|
+
// (`partTargetSize`) を engine 側に置き、画面側も同じ関数を呼ぶ。
|
|
61
|
+
export {
|
|
62
|
+
partRenderSize,
|
|
63
|
+
partVisualSize,
|
|
64
|
+
partBoxInFrame,
|
|
65
|
+
partTargetScale,
|
|
66
|
+
partTargetSize,
|
|
67
|
+
partScaleFactor,
|
|
68
|
+
normalizePartScale,
|
|
69
|
+
MAX_PART_SCALE,
|
|
70
|
+
partDrawsInDiagram,
|
|
71
|
+
partIsMeasurable,
|
|
72
|
+
partsGridCenters,
|
|
73
|
+
} from "./compile";
|
|
74
|
+
// 色として読めるかの判定と、 図の外を指す値かの判定。 状態の上書きを受け取る側 / 画面が色欄を
|
|
75
|
+
// 作る側 / 画面が背景色を直接書く側で同じ物差しを使う (別々に持つと、 片方だけ直した時に片方が通す)。
|
|
76
|
+
export { isColorValue, pointsOutside, stripExternalPaint } from "./color";
|
|
77
|
+
// 引用符の外し方 (#1028)。 画面側が本文から見本を抜く時に同じ判定を使う。
|
|
78
|
+
// 別々に持つと、片方だけが読める本文ができる (実測 = `位置: '300,200"` が
|
|
79
|
+
// 画面側では座標 300 として通り、組み立て側では読めない値として知らせが出た)
|
|
80
|
+
export { stripQuotes } from "./v05/parser";
|
|
81
|
+
// 大きすぎる入力を組み立てる前に止める上限 (#1005)。 画面側も同じ物差しで事前に知らせられるよう公開する。
|
|
82
|
+
export {
|
|
83
|
+
MAX_INPUT_ELEMENTS,
|
|
84
|
+
MAX_INPUT_BYTES,
|
|
85
|
+
countDocElements,
|
|
86
|
+
countDiagramElements,
|
|
87
|
+
countBytes,
|
|
88
|
+
describeOversize,
|
|
89
|
+
describeOversizeSource,
|
|
90
|
+
} from "./input-size";
|
|
91
|
+
export type { InputSize } from "./input-size";
|
|
92
|
+
|
|
93
|
+
// LLM 向け JSON DSL (Issue #208)
|
|
94
|
+
export { jsonToDiagram, validateDragonJson } from "./json-parser";
|
|
95
|
+
export { diagramJsonSchema } from "./schema";
|
|
96
|
+
export type {
|
|
97
|
+
DragonJson,
|
|
98
|
+
JsonActor,
|
|
99
|
+
JsonStep,
|
|
100
|
+
JsonPhase,
|
|
101
|
+
JsonDslError,
|
|
102
|
+
} from "./json-parser";
|
|
103
|
+
export type {
|
|
104
|
+
DslDocument,
|
|
105
|
+
DslActor,
|
|
106
|
+
DslStep,
|
|
107
|
+
DslAnimate,
|
|
108
|
+
DslState,
|
|
109
|
+
DslPhase,
|
|
110
|
+
DslTween,
|
|
111
|
+
DslSet,
|
|
112
|
+
DslError,
|
|
113
|
+
PresetType,
|
|
114
|
+
DslLane,
|
|
115
|
+
DslGroup,
|
|
116
|
+
DslViewport,
|
|
117
|
+
// CAR-1693 Phase 1: canvas pivot DSL 表面 pos + layout mode の public 型
|
|
118
|
+
LayoutPos,
|
|
119
|
+
LayoutMode,
|
|
120
|
+
} from "./types";
|
|
121
|
+
|
|
122
|
+
import { parseTextDsl } from "./parser";
|
|
123
|
+
import { parseTextDslV05 } from "./v05";
|
|
124
|
+
import { compileToCdl } from "./compile";
|
|
125
|
+
import { describeOversizeSource } from "./input-size";
|
|
126
|
+
import type { CompileNotice } from "./compile";
|
|
127
|
+
import type { CdlDiagram } from "@cardenelabs/cdl";
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* CAR-1657 = compile 時 parts identifier lookup 用 catalog。
|
|
131
|
+
* caller (CdlEditor 等) が loadPartsItems() の結果を Record<partId, CdlDiagram> で inject。
|
|
132
|
+
* 未渡し時 partId set actor は「未解決」 として skip + console.warn (diagram render は継続)。
|
|
133
|
+
*/
|
|
134
|
+
export interface CompileOpts {
|
|
135
|
+
partsCatalog?: Record<string, CdlDiagram>;
|
|
136
|
+
/**
|
|
137
|
+
* 図は出せるが書いた通りにならなかったことの受け取り口 (`位置: Web の下` が順序図で
|
|
138
|
+
* 効かない等)。 判定は組み立て側が持ち、 呼出側は受け取って表示するだけにする。
|
|
139
|
+
*/
|
|
140
|
+
onNotice?: (notice: CompileNotice) => void;
|
|
141
|
+
/**
|
|
142
|
+
* edge が DSL のどの行から来たかの受け取り口 (#998)。
|
|
143
|
+
*
|
|
144
|
+
* preset によっては書いた step と生成される edge が一致しない (`type: flow` は actor を鎖状に
|
|
145
|
+
* 繋ぐため `a -> c` と書いても `a -> b` になる)。 edge を起点に本文の行を直す機能 (自動修正の
|
|
146
|
+
* 書き戻し等) は、 この対応が無いと別の行を書き換える。
|
|
147
|
+
*
|
|
148
|
+
* **対応が取れない edge については呼ばれない**。 「対応が無い」 と「行 0」 を区別するため。
|
|
149
|
+
*/
|
|
150
|
+
onEdgeSource?: (edgeId: string, line: number) => void;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Dragon DSL から CdlDiagram に一発変換。 v0.5 (英語 keyword) / v0.4 (日本語 keyword) を auto-detect。
|
|
155
|
+
*
|
|
156
|
+
* 判定 ... src 内に v0.5 専用 syntax (`animation:` block / `- A -> B` flow / `step: "..."` ) を含むなら v0.5、
|
|
157
|
+
* 含まなければ v0.4 (deprecated、 console.warn を出す)。
|
|
158
|
+
*
|
|
159
|
+
* エラー時は throw、 詳細を取りたい場合は parseTextDslV05 / parseTextDsl を直接呼ぶ。
|
|
160
|
+
* opts.partsCatalog を渡すと CAR-1657 parts kind (arc-gauge 等) の actor が merge 展開される。
|
|
161
|
+
*/
|
|
162
|
+
export function textDslToDiagram(src: string, opts?: CompileOpts): CdlDiagram {
|
|
163
|
+
// 読み取る前に大きさを見る (#1005)。 要素数の上限は読み取った後にしか分からないため、
|
|
164
|
+
// 巨大な本文そのものによる待ちと記憶の消費はここでしか防げない
|
|
165
|
+
const oversize = describeOversizeSource(src);
|
|
166
|
+
if (oversize) throw new Error(oversize);
|
|
167
|
+
|
|
168
|
+
if (isV05Source(src)) {
|
|
169
|
+
const r = parseTextDslV05(src);
|
|
170
|
+
if (!r.ok) {
|
|
171
|
+
const msg = r.errors
|
|
172
|
+
.map((e) => ` L${e.line}: ${e.message}${e.hint ? `\n hint: ${e.hint}` : ""}`)
|
|
173
|
+
.join("\n");
|
|
174
|
+
throw new Error(`Dragon DSL v0.5 parse error:\n${msg}`);
|
|
175
|
+
}
|
|
176
|
+
return compileToCdl(r.doc, opts);
|
|
177
|
+
}
|
|
178
|
+
// v0.4 fallback (deprecated)
|
|
179
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
180
|
+
console.warn(
|
|
181
|
+
"[dragon] v0.4 syntax (Japanese keywords) is deprecated. Please migrate to v0.5 (English keywords) by 2026-12-31.",
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const r = parseTextDsl(src);
|
|
185
|
+
if (!r.ok) {
|
|
186
|
+
const msg = r.errors
|
|
187
|
+
.map((e) => ` L${e.line}: ${e.message}${e.hint ? `\n ヒント: ${e.hint}` : ""}`)
|
|
188
|
+
.join("\n");
|
|
189
|
+
throw new Error(`Dragon DSL parse error:\n${msg}`);
|
|
190
|
+
}
|
|
191
|
+
return compileToCdl(r.doc, opts);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function isV05Source(src: string): boolean {
|
|
195
|
+
// CAR-1657 (+ codex-review MAJOR fix) = v0.5 default + v0.4 marker detect。
|
|
196
|
+
//
|
|
197
|
+
// v0.4 は 2026-12-31 廃止予定、 新規 source は v0.5 前提で default を v0.5 に倒す。
|
|
198
|
+
// v0.4 marker (Japanese header + v0.4-specific English syntax) を 1 個でも検出したら v0.4 route。
|
|
199
|
+
//
|
|
200
|
+
// codex 指摘 = v0.4 も英語 alias `animate:` / `animation:` を受理するため、 header 名だけでは
|
|
201
|
+
// 判定不足。 v0.4-specific syntax (`step "..." Xs` = colon なし step / `^\d+\.\s+` = 番号 flow) を
|
|
202
|
+
// negative marker に追加。
|
|
203
|
+
const V04_KEYWORDS = [
|
|
204
|
+
// Japanese v0.4 専用 keyword (v0.5 は英語のみ)
|
|
205
|
+
/^\s*タイトル\s*[::]/, // title (JA)
|
|
206
|
+
/^\s*種類\s*[::]/, // type (JA)
|
|
207
|
+
/^\s*登場人物\s*[::]/, // actors (JA)
|
|
208
|
+
/^\s*流れ\s*[::]/, // flow (JA)
|
|
209
|
+
/^\s*アニメーション\s*[::]/, // animation (JA)
|
|
210
|
+
/^\s*動作\s*[::]/, // step (JA)
|
|
211
|
+
/^\s*状態\s*[::]/, // state (JA)
|
|
212
|
+
/^\s*ステップ\s*[「『]/, // step (JA)
|
|
213
|
+
// v0.4 English-specific syntax = colon なし step (v0.5 は step: 必須)
|
|
214
|
+
/^\s*step\s+"[^"]+"\s+[\d.]+\s*s\b/,
|
|
215
|
+
/^\s*step\s+'[^']+'\s+[\d.]+\s*s\b/,
|
|
216
|
+
// v0.4 numbered flow (`1. A → B`、 v0.5 は `- A -> B`)
|
|
217
|
+
/^\s*\d+\.\s+\S+\s*(?:→|->)\s*\S+/,
|
|
218
|
+
];
|
|
219
|
+
const lines = src.split("\n");
|
|
220
|
+
for (const ln of lines) {
|
|
221
|
+
for (const kw of V04_KEYWORDS) {
|
|
222
|
+
if (kw.test(ln)) return false; // v0.4 detected → false
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return true; // no v0.4 marker → v0.5 default
|
|
226
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { DslDocument } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 図を組み立てる前に、 大きすぎる入力を止める (#1005)。
|
|
5
|
+
*
|
|
6
|
+
* 組み立てにかかる時間は要素数の 2 乗で伸びる。 実測 (要素だけを並べた形)。
|
|
7
|
+
*
|
|
8
|
+
* | 要素 | 組み立て |
|
|
9
|
+
* |---|---|
|
|
10
|
+
* | 1,000 | 104ms |
|
|
11
|
+
* | 2,000 | 346ms |
|
|
12
|
+
* | 5,000 | 1,647ms |
|
|
13
|
+
* | 10,000 | 7,622ms |
|
|
14
|
+
*
|
|
15
|
+
* 待機 (500ms) の後に同じ流れの中で走るため、 この間 editor は操作を受け付けない。
|
|
16
|
+
* 貼ってしまうと tab を閉じるまで戻らない。
|
|
17
|
+
*
|
|
18
|
+
* 見本 (catalog) は 1 図あたり平均 4 要素、 最も多い file でも平均 7 要素。 実用の規模と
|
|
19
|
+
* 1 秒の境界 (約 3,000 要素) は桁が 2-3 つ違うため、 上限を置いても正当な図には当たらない。
|
|
20
|
+
*
|
|
21
|
+
* 既にある `dom-complexity-budget` (400 要素) とは別物。 あちらは組み立てた**後**に
|
|
22
|
+
* 「見やすさ」 の観点で警告を出すだけで、 組み立て自体は走る。 こちらは組み立てる**前**に
|
|
23
|
+
* 「固まらない」 ために止める。 400-2,000 の範囲は従来どおり警告が出て図も描ける。
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** 組み立てを止める要素数。 1 秒を明確に下回る水準に置く (2,000 要素で約 350ms) */
|
|
27
|
+
export const MAX_INPUT_ELEMENTS = 2000;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 組み立てを止める本文の大きさ (byte)。
|
|
31
|
+
*
|
|
32
|
+
* 要素数だけでは、 1 要素に極端に長い文字列を持つ形を捉えられない。 読み取り自体は速い
|
|
33
|
+
* (10,000 要素 98,936 byte で 2ms) ので、 実用の余裕を大きく取って置く。
|
|
34
|
+
*/
|
|
35
|
+
export const MAX_INPUT_BYTES = 512 * 1024;
|
|
36
|
+
|
|
37
|
+
/** 数えた結果。 超過した時に何がどれだけ超えたかを画面に出すために使う */
|
|
38
|
+
export interface InputSize {
|
|
39
|
+
elements: number;
|
|
40
|
+
bytes: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 記法の解析結果から要素数を数える。
|
|
45
|
+
*
|
|
46
|
+
* 数えるのは組み立ての重さに効くもの = 要素 / 流れ / 段 / 状態 / 段組み。
|
|
47
|
+
* 図の題名や表示の設定は数に入れない (何件あっても重さが変わらない)。
|
|
48
|
+
*
|
|
49
|
+
* **段の中身 (光らせる相手 / 遷移 / 即時変更) も数える**。 段の数だけを見ると、
|
|
50
|
+
* 1 段に 1,000 件の相手を書いた形が 1 件として通る。 実測ではこの形が組み立ての中で
|
|
51
|
+
* 約 100 万件に展開され、 呼び出しの深さが上限を超えて落ちた。
|
|
52
|
+
*/
|
|
53
|
+
export function countDocElements(doc: DslDocument): number {
|
|
54
|
+
const phases = doc.animate?.phases ?? [];
|
|
55
|
+
const phaseChildren = phases.reduce(
|
|
56
|
+
(acc, p) => acc + (p.highlight?.length ?? 0) + (p.tweens?.length ?? 0) + (p.sets?.length ?? 0),
|
|
57
|
+
0,
|
|
58
|
+
);
|
|
59
|
+
return (
|
|
60
|
+
doc.actors.length +
|
|
61
|
+
doc.flow.length +
|
|
62
|
+
phases.length +
|
|
63
|
+
phaseChildren +
|
|
64
|
+
(doc.animate?.states.length ?? 0) +
|
|
65
|
+
(doc.groups ? Object.keys(doc.groups).length : 0) +
|
|
66
|
+
(doc.lanes ? Object.keys(doc.lanes).length : 0)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 組み立て済みの図から要素数を数える。
|
|
72
|
+
*
|
|
73
|
+
* 記法を通らない入口 (本文に埋め込んだ図の定義) 用。 解析結果が無いため、 図の側で数える。
|
|
74
|
+
*
|
|
75
|
+
* **段の中身と読み取り部品も数える**。 段を 1 件として数えるだけだと、段の中に 10,000 件の
|
|
76
|
+
* 光らせる指定を持つ図が「1 要素」 と判定されて素通りする (実測)。 記法側の数え方
|
|
77
|
+
* (`countDocElements`) は段の中身を足しているので、こちらも揃える。
|
|
78
|
+
*/
|
|
79
|
+
export function countDiagramElements(diagram: {
|
|
80
|
+
nodes?: unknown[];
|
|
81
|
+
edges?: unknown[];
|
|
82
|
+
lanes?: unknown[];
|
|
83
|
+
states?: unknown[];
|
|
84
|
+
phases?: unknown[];
|
|
85
|
+
readouts?: unknown[];
|
|
86
|
+
inputs?: unknown[];
|
|
87
|
+
formulas?: unknown[];
|
|
88
|
+
scrollTriggers?: unknown[];
|
|
89
|
+
eventBindings?: unknown[];
|
|
90
|
+
}): number {
|
|
91
|
+
const phases = Array.isArray(diagram.phases) ? diagram.phases : [];
|
|
92
|
+
const phaseChildren = phases.reduce((acc: number, p) => {
|
|
93
|
+
const ph = p as { activate?: unknown[]; highlight?: unknown[]; tweens?: unknown[]; sets?: unknown[] };
|
|
94
|
+
return (
|
|
95
|
+
acc +
|
|
96
|
+
(Array.isArray(ph?.activate) ? ph.activate.length : 0) +
|
|
97
|
+
(Array.isArray(ph?.highlight) ? ph.highlight.length : 0) +
|
|
98
|
+
(Array.isArray(ph?.tweens) ? ph.tweens.length : 0) +
|
|
99
|
+
(Array.isArray(ph?.sets) ? ph.sets.length : 0)
|
|
100
|
+
);
|
|
101
|
+
}, 0);
|
|
102
|
+
return (
|
|
103
|
+
(diagram.nodes?.length ?? 0) +
|
|
104
|
+
(diagram.edges?.length ?? 0) +
|
|
105
|
+
(diagram.lanes?.length ?? 0) +
|
|
106
|
+
(diagram.states?.length ?? 0) +
|
|
107
|
+
phases.length +
|
|
108
|
+
phaseChildren +
|
|
109
|
+
// 図が持てる並びは全部数える。 1 つでも外すと、そこに寄せた図が素通りする
|
|
110
|
+
(diagram.readouts?.length ?? 0) +
|
|
111
|
+
(diagram.inputs?.length ?? 0) +
|
|
112
|
+
(diagram.formulas?.length ?? 0) +
|
|
113
|
+
(diagram.scrollTriggers?.length ?? 0) +
|
|
114
|
+
(diagram.eventBindings?.length ?? 0)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** 本文の大きさを byte で数える (文字数ではなく実際の大きさ) */
|
|
119
|
+
export function countBytes(src: string): number {
|
|
120
|
+
// Node にも browser にもある形で数える。 `Buffer` は browser に無い
|
|
121
|
+
return new TextEncoder().encode(src).length;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 大きすぎる入力なら、 その旨を伝える文を返す。 収まっていれば `null`。
|
|
126
|
+
*
|
|
127
|
+
* 投げるのではなく文を返すのは、 呼出側が「誤りとして表示する」 か「別の扱いにする」 かを
|
|
128
|
+
* 選べるようにするため。
|
|
129
|
+
*
|
|
130
|
+
* `bytes` に 0 を渡すと大きさの検査を飛ばす (要素数だけを見たい合流点で使う)。
|
|
131
|
+
*/
|
|
132
|
+
export function describeOversize(size: InputSize): string | null {
|
|
133
|
+
if (size.elements > MAX_INPUT_ELEMENTS) {
|
|
134
|
+
return `要素が ${size.elements.toLocaleString()} 件あります。 ${MAX_INPUT_ELEMENTS.toLocaleString()} 件までにしてください (これ以上は組み立てに時間がかかり、 画面が止まります)`;
|
|
135
|
+
}
|
|
136
|
+
if (size.bytes > MAX_INPUT_BYTES) {
|
|
137
|
+
// KB の整数で出す。 MB 小数 1 桁だと、 1 byte 超えただけの時に「0.5MB / 上限 0.5MB」 と
|
|
138
|
+
// 同じ数字が並び、 制限内なのに拒まれたように読める
|
|
139
|
+
const kb = Math.ceil(size.bytes / 1024);
|
|
140
|
+
const limitKb = Math.floor(MAX_INPUT_BYTES / 1024);
|
|
141
|
+
return `本文が ${kb.toLocaleString()}KB あります。 ${limitKb.toLocaleString()}KB までにしてください (これ以上は読み取りだけで待たされ、 記憶も大きく使います)`;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 本文が大きすぎるなら、 その旨を伝える文を返す。 収まっていれば `null`。
|
|
148
|
+
*
|
|
149
|
+
* **読み取る前に呼ぶ**。 要素数の上限は読み取った後にしか分からないため、 巨大な本文を
|
|
150
|
+
* 渡された時の読み取り自体 (11.9MB で記憶 200MB) を防げない。
|
|
151
|
+
*/
|
|
152
|
+
export function describeOversizeSource(src: string): string | null {
|
|
153
|
+
return describeOversize({ elements: 0, bytes: countBytes(src) });
|
|
154
|
+
}
|