@stacksjs/defaults 0.75.361 → 0.75.366
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/ai/skills/stacks-ai/SKILL.md +22 -0
- package/ai/skills/stacks-native/CATALOG.md +5 -5
- package/ai/skills/stacks-payments/SKILL.md +10 -0
- package/ai/skills/stacks-technical-diagrams/LICENSE +202 -22
- package/ai/skills/stacks-technical-diagrams/LICENSE-MIT +22 -0
- package/ai/skills/stacks-technical-diagrams/NOTICE +26 -0
- package/ai/skills/stacks-technical-diagrams/SKILL.md +106 -266
- package/ai/skills/stacks-technical-diagrams/THIRD_PARTY_NOTICES.md +11 -2
- package/ai/skills/stacks-technical-diagrams/UPSTREAM.json +12 -0
- package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +2 -2
- package/ai/skills/stacks-technical-diagrams/assets/template.html +3655 -3798
- package/ai/skills/stacks-technical-diagrams/bin/archify.mjs +7290 -0
- package/ai/skills/stacks-technical-diagrams/bin/delivery-update.mjs +7 -0
- package/ai/skills/stacks-technical-diagrams/bin/diagrams +1 -0
- package/ai/skills/stacks-technical-diagrams/bin/finalize.mjs +1056 -0
- package/ai/skills/stacks-technical-diagrams/bin/open-artifact.mjs +72 -12
- package/ai/skills/stacks-technical-diagrams/bin/preview.mjs +348 -31
- package/ai/skills/stacks-technical-diagrams/bin/recover-output.mjs +41 -0
- package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +3 -2097
- package/ai/skills/stacks-technical-diagrams/bin/visual-check.mjs +2142 -157
- package/ai/skills/stacks-technical-diagrams/brand-marks/README.md +3 -3
- package/ai/skills/stacks-technical-diagrams/delta/architecture-delta.mjs +130 -33
- package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +25 -30
- package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +0 -20
- package/ai/skills/stacks-technical-diagrams/examples/archify-dev-activity.timeline.json +176 -0
- package/ai/skills/stacks-technical-diagrams/examples/archify-repository.tree.json +197 -0
- package/ai/skills/stacks-technical-diagrams/examples/async-job-roundtrip.sequence.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/brand-aware-delivery.architecture.json +3 -16
- package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +2 -7
- package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.base.architecture.json +2 -1
- package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.head.architecture.json +2 -1
- package/ai/skills/stacks-technical-diagrams/examples/checkout-request.waterfall.json +20 -0
- package/ai/skills/stacks-technical-diagrams/examples/class-payment-processors-rendered.html +14987 -0
- package/ai/skills/stacks-technical-diagrams/examples/class-payments-rendered.html +14787 -0
- package/ai/skills/stacks-technical-diagrams/examples/dataflow-product-analytics.html +14880 -0
- package/ai/skills/stacks-technical-diagrams/examples/delivery.explainer.json +155 -0
- package/ai/skills/stacks-technical-diagrams/examples/deployment-release.lifecycle.json +57 -34
- package/ai/skills/stacks-technical-diagrams/examples/erd-orders-rendered.html +15112 -0
- package/ai/skills/stacks-technical-diagrams/examples/event-stream.dataflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/example-rebuild.waterfall.json +212 -0
- package/ai/skills/stacks-technical-diagrams/examples/holiday-planning.workflow.json +143 -0
- package/ai/skills/stacks-technical-diagrams/examples/incident-response.workflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/lifecycle-agent-run.html +14892 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/de.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/fr.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/fr.partial.json +22 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/it.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/ja.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/pt.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/ru.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/orders.erd.json +337 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-incident.timeline.json +24 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-platform.tree.json +22 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-processors.class.json +177 -0
- package/ai/skills/stacks-technical-diagrams/examples/payments.class.json +85 -0
- package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/production-deployment.architecture.json +3 -23
- package/ai/skills/stacks-technical-diagrams/examples/release-delivery.workflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/sequence-cache-miss-request.html +14895 -0
- package/ai/skills/stacks-technical-diagrams/examples/subscription-billing-rendered.html +15369 -0
- package/ai/skills/stacks-technical-diagrams/examples/subscription-billing.erd.json +548 -0
- package/ai/skills/stacks-technical-diagrams/examples/timeline-archify-dev-activity-rendered.html +15002 -0
- package/ai/skills/stacks-technical-diagrams/examples/timeline-payment-incident-rendered.html +14772 -0
- package/ai/skills/stacks-technical-diagrams/examples/tree-archify-repository-rendered.html +15071 -0
- package/ai/skills/stacks-technical-diagrams/examples/tree-payment-platform-rendered.html +14810 -0
- package/ai/skills/stacks-technical-diagrams/examples/waterfall-checkout-request-rendered.html +14754 -0
- package/ai/skills/stacks-technical-diagrams/examples/waterfall-example-rebuild-rendered.html +14940 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app-rendered.html +14889 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/workflow-agent-tool-call-rendered.html +14907 -0
- package/ai/skills/stacks-technical-diagrams/explainer/browser-check.mjs +208 -0
- package/ai/skills/stacks-technical-diagrams/explainer/charts.mjs +70 -0
- package/ai/skills/stacks-technical-diagrams/explainer/check.mjs +168 -0
- package/ai/skills/stacks-technical-diagrams/explainer/diagrams.mjs +518 -0
- package/ai/skills/stacks-technical-diagrams/explainer/diff.mjs +124 -0
- package/ai/skills/stacks-technical-diagrams/explainer/inline.mjs +155 -0
- package/ai/skills/stacks-technical-diagrams/explainer/layout.mjs +35 -0
- package/ai/skills/stacks-technical-diagrams/explainer/page-bundle.mjs +16 -0
- package/ai/skills/stacks-technical-diagrams/explainer/parse.mjs +300 -0
- package/ai/skills/stacks-technical-diagrams/explainer/patch.mjs +117 -0
- package/ai/skills/stacks-technical-diagrams/explainer/prepare.mjs +131 -0
- package/ai/skills/stacks-technical-diagrams/explainer/reader.css +472 -0
- package/ai/skills/stacks-technical-diagrams/explainer/reader.js +667 -0
- package/ai/skills/stacks-technical-diagrams/explainer/render.mjs +224 -0
- package/ai/skills/stacks-technical-diagrams/explainer/revision.mjs +20 -0
- package/ai/skills/stacks-technical-diagrams/explainer/sources.mjs +318 -0
- package/ai/skills/stacks-technical-diagrams/explainer/validate.mjs +138 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/LICENSE +40 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/README.md +5 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/3DKXF73K.js +4 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/42UTT654.js +3 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/DKTLEL5E.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EEIX5ZYH.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EJ5KOXYD.js +25 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/GUJTEFZK.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/IZOKQ2GT.js +4 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/L4LY553T.js +3 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/MSKIJHB2.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/RVARI57O.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UKSIWDDN.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UPTXNAXN.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/XTDUIGUI.js +9 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/YAYTP6L5.js +9 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/core.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/pie.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/quadrantChart.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/radar.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/sankey.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/venn.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/xychart.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/writing.mjs +139 -0
- package/ai/skills/stacks-technical-diagrams/locales/README.md +40 -0
- package/ai/skills/stacks-technical-diagrams/locales/ar.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/en.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/es.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/ko.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/manifest.json +10 -0
- package/ai/skills/stacks-technical-diagrams/locales/zh-CN.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/zh-TW.json +488 -0
- package/ai/skills/stacks-technical-diagrams/migrations/workflow-v2.mjs +1 -0
- package/ai/skills/stacks-technical-diagrams/recipes/scenarios.mjs +115 -51
- package/ai/skills/stacks-technical-diagrams/references/architecture-layout-repair.md +18 -0
- package/ai/skills/stacks-technical-diagrams/references/authoring-contract.md +453 -51
- package/ai/skills/stacks-technical-diagrams/references/authoring-defaults.md +57 -0
- package/ai/skills/stacks-technical-diagrams/references/brand-marks.md +57 -3
- package/ai/skills/stacks-technical-diagrams/references/delivery-contract.md +603 -58
- package/ai/skills/stacks-technical-diagrams/references/explainer-authoring.md +274 -0
- package/ai/skills/stacks-technical-diagrams/references/repository-authoring.md +126 -0
- package/ai/skills/stacks-technical-diagrams/references/update-awareness.md +13 -0
- package/ai/skills/stacks-technical-diagrams/references/viewer-runtime.md +18 -11
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +1 -1
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/labels.mjs +2 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +550 -424
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/routing.mjs +1469 -0
- package/ai/skills/stacks-technical-diagrams/renderers/class/README.md +129 -0
- package/ai/skills/stacks-technical-diagrams/renderers/class/render-class.mjs +1095 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +89 -17
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/dataflow-compiler.mjs +1605 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/joint-label-routing.mjs +165 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/label-capacity.mjs +81 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +14 -474
- package/ai/skills/stacks-technical-diagrams/renderers/erd/README.md +246 -0
- package/ai/skills/stacks-technical-diagrams/renderers/erd/grid.mjs +150 -0
- package/ai/skills/stacks-technical-diagrams/renderers/erd/render-erd.mjs +1588 -0
- package/ai/skills/stacks-technical-diagrams/renderers/explainer/render-explainer.mjs +42 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +83 -94
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +990 -417
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +49 -17
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +735 -94
- package/ai/skills/stacks-technical-diagrams/renderers/shared/atomic-output.mjs +1982 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/automatic-labels.mjs +290 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/brand-marks.mjs +169 -26
- package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +266 -54
- package/ai/skills/stacks-technical-diagrams/renderers/shared/desktop-readability.mjs +106 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/diagnostics.mjs +0 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +268 -2
- package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +774 -124
- package/ai/skills/stacks-technical-diagrams/renderers/shared/i18n.mjs +289 -556
- package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +18 -1
- package/ai/skills/stacks-technical-diagrams/renderers/shared/legend.mjs +16 -6
- package/ai/skills/stacks-technical-diagrams/renderers/shared/node-card-style.mjs +32 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/output-path.mjs +329 -229
- package/ai/skills/stacks-technical-diagrams/renderers/shared/owned-temp-directory.mjs +82 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/path-semantics.mjs +1017 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/portable-path.mjs +408 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-evidence.mjs +235 -79
- package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-location.mjs +17 -4
- package/ai/skills/stacks-technical-diagrams/renderers/shared/route-quality.mjs +670 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/sidecar-path.mjs +38 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/software-notices.mjs +6 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/spatial-grid.mjs +66 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/svg-path-analysis.mjs +135 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/text-fit.mjs +59 -3
- package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +63 -35
- package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +2 -5
- package/ai/skills/stacks-technical-diagrams/renderers/timeline/README.md +76 -0
- package/ai/skills/stacks-technical-diagrams/renderers/timeline/render-timeline.mjs +576 -0
- package/ai/skills/stacks-technical-diagrams/renderers/tree/README.md +96 -0
- package/ai/skills/stacks-technical-diagrams/renderers/tree/render-tree.mjs +573 -0
- package/ai/skills/stacks-technical-diagrams/renderers/waterfall/README.md +73 -0
- package/ai/skills/stacks-technical-diagrams/renderers/waterfall/render-waterfall.mjs +413 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +137 -23
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +18 -6
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-compiler.mjs +2230 -710
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-migration-geometry.mjs +17 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-placement-repair.mjs +94 -0
- package/ai/skills/stacks-technical-diagrams/schemas/README.md +87 -40
- package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +7 -33
- package/ai/skills/stacks-technical-diagrams/schemas/class.schema.json +345 -0
- package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +68 -2
- package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +14 -3
- package/ai/skills/stacks-technical-diagrams/schemas/erd.schema.json +381 -0
- package/ai/skills/stacks-technical-diagrams/schemas/explainer.schema.json +927 -0
- package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +23 -110
- package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +15 -4
- package/ai/skills/stacks-technical-diagrams/schemas/timeline.schema.json +160 -0
- package/ai/skills/stacks-technical-diagrams/schemas/tree.schema.json +141 -0
- package/ai/skills/stacks-technical-diagrams/schemas/waterfall.schema.json +147 -0
- package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +14 -3
- package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +876 -243
- package/ai/skills/stacks-technical-diagrams/scripts/check-update.mjs +1717 -0
- package/ai/skills/stacks-technical-diagrams/scripts/delivery-update-child.mjs +26 -0
- package/ai/skills/stacks-technical-diagrams/scripts/generate-brand-marks.mjs +141 -0
- package/ai/skills/stacks-technical-diagrams/scripts/generate-validators.mjs +105 -0
- package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +11 -1
- package/ai/skills/stacks-technical-diagrams/scripts/update-contract.mjs +187 -0
- package/ai/skills/stacks-technical-diagrams/skill-release.json +10 -0
- package/ai/skills/stacks-testing/SKILL.md +8 -1
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Entity-Relationship Renderer
|
|
2
|
+
|
|
3
|
+
Render `diagram_type: "erd"` JSON files into the standard Archify HTML template.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
node archify/renderers/erd/render-erd.mjs input.erd.json output.html
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The renderer validates input against `archify/schemas/erd.schema.json` with the
|
|
10
|
+
bundled standalone validator. No dependency installation is required.
|
|
11
|
+
|
|
12
|
+
If `output.html` is omitted, the renderer uses `meta.output` from the JSON file
|
|
13
|
+
or falls back to `erd.html` in the current working directory.
|
|
14
|
+
|
|
15
|
+
After rendering, run the artifact checker:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
node archify/scripts/check-render-output.mjs output.html
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Input
|
|
22
|
+
|
|
23
|
+
ERD JSON files must set:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"schema_version": 1,
|
|
28
|
+
"diagram_type": "erd",
|
|
29
|
+
"meta": { "title": "Order management schema" },
|
|
30
|
+
"entities": [],
|
|
31
|
+
"relationships": [],
|
|
32
|
+
"cards": []
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
An entity carries the table name in `label`, its attributes in `attributes`, and a
|
|
37
|
+
`row`/`col` cell in the banded grid. The box height follows the declared attribute
|
|
38
|
+
count, so the layout never needs hand-measured sizes. An attribute's `key` accepts
|
|
39
|
+
`pk`, `fk`, or `uk`, or a list of them: a junction table's column is often part of
|
|
40
|
+
the primary key and a foreign key to the same parent, and stating one role would
|
|
41
|
+
hide the other. Each role draws its own badge, tinted in its legend's family, a
|
|
42
|
+
role named twice draws once, and the key column is measured from the widest badge
|
|
43
|
+
run in the table — never narrower than the shared width, wider when the badges
|
|
44
|
+
need it. Every `fk`
|
|
45
|
+
role — alone or inside a list — must also name its target as `"entity.attribute"`
|
|
46
|
+
in `references`, and that target is checked against the declared entities and
|
|
47
|
+
attributes.
|
|
48
|
+
|
|
49
|
+
A relationship reads `from` -> `to` and is drawn with crow's foot notation at
|
|
50
|
+
both ends. The foot opens toward the entity whose cardinality it describes: its
|
|
51
|
+
toes stand on that table's edge and its apex sits one foot back along the line,
|
|
52
|
+
so the glyph cannot be read as an arrowhead pointing at the table. Both ends
|
|
53
|
+
declare their own maximum: `fromCardinality`/`toCardinality` take `one` or
|
|
54
|
+
`many` and are required, because a maximum the author never stated is a fact the
|
|
55
|
+
diagram would be inventing; `fromOptional`/`toOptional` add the optional circle
|
|
56
|
+
past the apex and lower that end's minimum from one to zero.
|
|
57
|
+
`identifying: false` draws the non-identifying dashed line, and it owns the dash:
|
|
58
|
+
a `variant` that would draw the relationship solid alongside it is rejected by
|
|
59
|
+
the schema, because the solid line would state the opposite of the fact. The
|
|
60
|
+
variant still selects the accent for every identifying relationship.
|
|
61
|
+
|
|
62
|
+
## Layout and routing
|
|
63
|
+
|
|
64
|
+
`layout.mode: "grid"` places each entity by `row`/`col`. Every raw attribute index
|
|
65
|
+
owns the width of its widest entity and every raw row index owns the height of
|
|
66
|
+
its tallest, so boxes in one band line up without hand measurement and an empty
|
|
67
|
+
index leaves a routing channel. `pos` overrides the cell for a bounded
|
|
68
|
+
exception.
|
|
69
|
+
|
|
70
|
+
Relationships route through the orthogonal router the other typed renderers use
|
|
71
|
+
(`../architecture/routing.mjs`), so the same contracts apply: automatic port
|
|
72
|
+
spreading on a shared entity, the endpoint-side contract, the route-rhythm
|
|
73
|
+
floors, and the universal Clean Flow gate. The ERD is a caller, not a fork: it
|
|
74
|
+
passes `sideFor`, `maxPortSpacing`, `preferredCandidates`, and
|
|
75
|
+
`compositionFloors: false`, and reads the planning-free `inferredSides` and the
|
|
76
|
+
`ports` map. Each option is inert when omitted, which is how architecture and
|
|
77
|
+
lifecycle keep their routing byte for byte. Five differences are specific to this
|
|
78
|
+
renderer:
|
|
79
|
+
|
|
80
|
+
- Endpoints resolve horizontal-first, like every other non-architecture type: a
|
|
81
|
+
relationship leaves and enters on the left/right whenever the tables sit side
|
|
82
|
+
by side, and only falls back to a top/bottom port when they share a column. The
|
|
83
|
+
shared router's default is architecture's dominant-axis inference, which would
|
|
84
|
+
split one fan-in into two groups whenever a target happened to sit further away
|
|
85
|
+
vertically than horizontally.
|
|
86
|
+
- Automatic ports on one side spread up to 24 units apart, because a cardinality
|
|
87
|
+
glyph is 14 units tall: at the shared 14-unit spread two crow's feet on one table
|
|
88
|
+
edge read as a single smudge. That spacing is a cap, not a floor — a side only
|
|
89
|
+
fits `(extent - 32) / (ends - 1)` — so a table with more relationship ends than
|
|
90
|
+
its side has room for fails with `layout/marker-capacity`, which names the
|
|
91
|
+
table, the side, the spacing, and the three ways out.
|
|
92
|
+
- Relationships that would share one corridor get a deterministic lane offset,
|
|
93
|
+
so parallel foreign keys never sit on the identical channel line.
|
|
94
|
+
- Relationships that share one entity side (a fan-in of foreign keys, or a
|
|
95
|
+
fan-out parent) and one marker style bundle onto a single trunk just outside
|
|
96
|
+
that side: one bus line with a short branch per relationship, instead of a
|
|
97
|
+
sheaf of parallel lanes. The trunk bridges one port step of uncovered bus, so
|
|
98
|
+
the port spread above still reads as one line. The trunk is only a preferred
|
|
99
|
+
route — a branch that would break the endpoint contract or clip an entity falls
|
|
100
|
+
back to the ordinary families and stays unbundled, and anything authored
|
|
101
|
+
(`route`, `via`, `labelAt`) keeps its own line. Logical routes still traverse
|
|
102
|
+
the trunk, so every gate, label, and the layout report see the full geometry.
|
|
103
|
+
自动补出的主干桥接还会检查无关关系及已接受的其他桥接;出现交叉、接触或
|
|
104
|
+
重叠时保留分离的主干段,避免把独立关系画成同一条总线。检查按双方描边的
|
|
105
|
+
半宽之和留出间隔,并计入无关关系的显式 `width`;中心线未相交但描边接触
|
|
106
|
+
也会阻止桥接。这个回退不移动
|
|
107
|
+
实体、显式途经点或端口,也不改变关系基数;无障碍的同簇分支仍可共享主干。
|
|
108
|
+
- Entity boxes are opaque obstacles. When a third entity sits between two
|
|
109
|
+
aligned anchors, the shared obstacle grid routes around it, and a route that
|
|
110
|
+
still cannot clear an unrelated entity fails with
|
|
111
|
+
`clean-flow/edge-through-node` rather than being drawn through the box.
|
|
112
|
+
|
|
113
|
+
The renderer also reports ER-specific diagnostics: unknown entities in
|
|
114
|
+
`from`/`to`, unknown attribute names and unresolved `references`, duplicate attribute
|
|
115
|
+
names, self-referencing relationships, overlapping entity boxes, and rows whose
|
|
116
|
+
text cannot fit inside the declared entity width.
|
|
117
|
+
|
|
118
|
+
## Domain bands
|
|
119
|
+
|
|
120
|
+
`tag` names a table's functional domain, and a domain is drawn as a band when the
|
|
121
|
+
tables carrying that tag fill a solid rectangle of grid cells — every cell of
|
|
122
|
+
their bounding box holds one of them. A single row, a single column, and a filled
|
|
123
|
+
rectangular block are all that same claim (an L or diagonal is not); a one-table
|
|
124
|
+
domain is that claim at its smallest. The
|
|
125
|
+
band is measured from the placed boxes and carries the tag as its caption, which
|
|
126
|
+
is what makes a grouped schema readable at a glance: the reader sees blocks
|
|
127
|
+
instead of six equally weighted boxes.
|
|
128
|
+
|
|
129
|
+
A tag whose members do not fill one rectangle earns no band, because a band
|
|
130
|
+
around them would enclose cells the domain does not own and claim a grouping the
|
|
131
|
+
placement contradicts. The band is the only place a domain name is drawn, so the
|
|
132
|
+
renderer returns `erd/domain-not-drawn` rather than publishing a diagram whose
|
|
133
|
+
tables are complete and whose domain cannot be named. Move the members into one
|
|
134
|
+
rectangle, split the tag, or drop it. The same diagnostic covers the other ways the
|
|
135
|
+
name can go missing: a member placed outside the grid, because a band is measured
|
|
136
|
+
from cells and absolute coordinates have none; two members in one cell, which the
|
|
137
|
+
grid placement check reports instead so the author gets the pair that has to move;
|
|
138
|
+
a band whose padding would cover an unrelated table, which happens when
|
|
139
|
+
`layout.gapX`/`gapY` are smaller than the padding; and a caption the canvas does
|
|
140
|
+
not reach, which a `layout.origin` closer to the edge than the padding produces.
|
|
141
|
+
|
|
142
|
+
The header carries the table title alone. It is centered on the table's own
|
|
143
|
+
center line and held inside the interval it is measured against — the sigil porch
|
|
144
|
+
on the left, the padding on the right — so it does not run past either while the
|
|
145
|
+
shared text model's advance estimate holds. A title that cannot fit the header at
|
|
146
|
+
a readable size raises `erd/header-text-capacity`; widen the table or shorten the
|
|
147
|
+
title. Existing header and entity dimensions are preserved unless the author
|
|
148
|
+
changes them. An authored `sublabel` is not drawn: it stays on the node as
|
|
149
|
+
`data-node-sublabel` and in the accessible name.
|
|
150
|
+
|
|
151
|
+
## Cardinality ink
|
|
152
|
+
|
|
153
|
+
The crow's foot, the single bar, and the optional circle are drawn in the theme's
|
|
154
|
+
muted text ink rather than the shared arrow token: at the light theme's arrow
|
|
155
|
+
colour a glyph over a table fill sits near 2:1 contrast and reads as a smudge,
|
|
156
|
+
while the muted text ink clears the 3:1 non-text floor in both themes. The legend
|
|
157
|
+
repeats the same ink and stroke weight, and its labels render one step larger and
|
|
158
|
+
heavier than the shared legend default, so the legend shows the reader the exact
|
|
159
|
+
glyph the diagram draws.
|
|
160
|
+
|
|
161
|
+
## The legend band
|
|
162
|
+
|
|
163
|
+
The default legend is part of the generated content, so an automatic canvas is
|
|
164
|
+
sized to hold it: the shared measurement decides whether the reserved strip
|
|
165
|
+
fits, and the drawing area grows a band at a time until it does. A route or a
|
|
166
|
+
relationship label that still collides with the placed band is a real capacity
|
|
167
|
+
failure and raises the diagnostic instead of dropping the key, and an authored
|
|
168
|
+
`meta.viewBox` — a fixed drawing area — names the capacity it lacks
|
|
169
|
+
(`legend/vertical-overflow`, `legend/label-too-wide`, or `legend/content-overlap`,
|
|
170
|
+
each with the fix that resolves it).
|
|
171
|
+
|
|
172
|
+
The legend has two groups split by a divider. Key entries draw the same badge a
|
|
173
|
+
field row draws; relationship entries draw a short route meeting a table edge
|
|
174
|
+
with the marker's own glyph. ERD kinds are column keys and relationship ends,
|
|
175
|
+
not node kinds, so the entries stay out of the Viewer's counted node lens, which
|
|
176
|
+
would badge each one with a false zero. Hovering or keyboard-focusing an entry
|
|
177
|
+
highlights its matches in the SVG itself: a key fades the other field rows and
|
|
178
|
+
outlines the matching badges; a relationship end recolours that glyph wherever
|
|
179
|
+
it is drawn and fades routes that do not carry it. Static exports keep the plain
|
|
180
|
+
legend.
|
|
181
|
+
|
|
182
|
+
A canvas taller than one screen is a normal schema, not a defect. An automatic
|
|
183
|
+
canvas declares `data-reader-fit="intrinsic-height"` and
|
|
184
|
+
`data-reader-min-text="7.5"`, which is the Reader's permission to narrow a tall
|
|
185
|
+
schema down to the point where the page fits — never below that declared minimum,
|
|
186
|
+
so the field rows stay readable and anything taller than that scrolls; an
|
|
187
|
+
authored `meta.viewBox` keeps the authored fit contract. Field rows, their types,
|
|
188
|
+
and the key glyph carry `data-detail="context"` on the text itself, because the
|
|
189
|
+
Reader sizes the diagram from the texts that declare a level — a field that is
|
|
190
|
+
only context inside a row group would be shrunk past the readable floor on a tall
|
|
191
|
+
schema.
|
|
192
|
+
|
|
193
|
+
Narrowing is a floor, not a goal. The bundled eleven-table example measures, at
|
|
194
|
+
1440x900 and with the same tables and typography, as a 1230x678 canvas the Reader
|
|
195
|
+
renders at the full 1346px (scale 1.09), so its field types land at 11.5px. The
|
|
196
|
+
same tables grouped as four horizontal domains instead form a 910x874 canvas that
|
|
197
|
+
the Reader narrows to 777px (scale 0.85), landing the same types at 9.0px. A
|
|
198
|
+
horizontal arrangement that reaches the reader width instead of being narrowed
|
|
199
|
+
still trades text size for the columns it needs, which is why the measurement
|
|
200
|
+
here is the pair and not a rule about which axis is right.
|
|
201
|
+
|
|
202
|
+
## Typography
|
|
203
|
+
|
|
204
|
+
Table rows are text a reader scans, so the ERD's rhythm is looser than the generic
|
|
205
|
+
diagram rhythm: a 30-unit header, 20-unit rows, a 13-unit table name, 11-unit
|
|
206
|
+
field names, and a 10.5-unit type. The type keeps the same ink as the field name
|
|
207
|
+
because it is data, not an aside: at the muted token it measured 3.98:1 against a
|
|
208
|
+
table fill, under the 4.5:1 text floor, which is why it read as faint in review.
|
|
209
|
+
A row whose text cannot fit is a diagnostic rather than a silently truncated
|
|
210
|
+
cell, so a narrow table names the field it cannot show and asks for a wider box.
|
|
211
|
+
|
|
212
|
+
Field rows, their types, and the key glyph each carry `data-detail="context"` on
|
|
213
|
+
the text itself, not only on the row group: the Viewer's reader sizes the diagram
|
|
214
|
+
from the texts that declare a level, so a tall schema would otherwise be shrunk
|
|
215
|
+
past the readable floor. An automatic canvas also declares
|
|
216
|
+
`data-reader-fit="intrinsic-height"`, which is what lets a schema taller than one
|
|
217
|
+
screen scroll at a readable size instead of being squeezed to fit; an authored
|
|
218
|
+
`meta.viewBox` keeps the authored fit contract.
|
|
219
|
+
|
|
220
|
+
Relationship labels are set at 9 units in the relationship's own accent, with the
|
|
221
|
+
mask measured from the same factor, so a labelled schema keeps its semantic text
|
|
222
|
+
above the readable floor. They are the smallest text the diagram carries, and
|
|
223
|
+
they are semantic data: a collision is a repair (move the label, widen the
|
|
224
|
+
corridor), never a reason to drop the wording.
|
|
225
|
+
|
|
226
|
+
## Reading a schema
|
|
227
|
+
|
|
228
|
+
An ERD is also a table catalogue. For a schema with more than 8 tables or 50
|
|
229
|
+
fields, start with `meta.quality_profile: "standard"` so complete field
|
|
230
|
+
visibility remains the priority; promote to `showcase` only after the grouped
|
|
231
|
+
layout is genuinely readable. Include every physical column in
|
|
232
|
+
`entities[].attributes`, keep its SQL type, and preserve the source comment in the
|
|
233
|
+
type string as `SQL_TYPE|中文备注` when the schema has no separate comment field.
|
|
234
|
+
The renderer shows ERD attribute rows at the default `read` level, so fields do
|
|
235
|
+
not depend on hover or focus. Use cards only for supplementary constraints,
|
|
236
|
+
inferred relationships, and domain rules; never use them to hide the full table.
|
|
237
|
+
|
|
238
|
+
Group tables by functional domain before assigning grid cells. Give members of one
|
|
239
|
+
domain the same concise `tag` and keep them in one contiguous, non-interleaved run
|
|
240
|
+
— a row with adjacent columns or a column with adjacent rows; that run is what the
|
|
241
|
+
band is measured from. Give each domain a column of its own when the whole schema
|
|
242
|
+
has to be visible at once: the domain then reads top to bottom, the relationships
|
|
243
|
+
between neighbouring domains run left to right, and the canvas stays wide enough
|
|
244
|
+
for the Reader to show it at full text size. A wide entity is a taller wall for
|
|
245
|
+
the router to get around, and the projected text minimum still applies at
|
|
246
|
+
1440x900, so prefer a compact grouped grid over shrinking or hiding fields.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/** Banded grid placement for the ER IR. Not auto-layout: column widths and row
|
|
2
|
+
* band heights are measured from the boxes the author declares, so the same
|
|
3
|
+
* spec always produces the same coordinates. */
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_ER_GRID = {
|
|
6
|
+
mode: 'grid',
|
|
7
|
+
// The top origin leaves room for a domain caption above the first band, and
|
|
8
|
+
// the left origin starts the first column inside the page margin so the
|
|
9
|
+
// canvas is not edge-to-edge. Row bands are taller than the generic rhythm
|
|
10
|
+
// because a table row is text a reader has to scan, not a node label.
|
|
11
|
+
origin: [32, 40],
|
|
12
|
+
gapX: 56,
|
|
13
|
+
gapY: 44,
|
|
14
|
+
// Wide enough for a long field name plus its SQL type at the legibility
|
|
15
|
+
// sizes below (a 20-character name and a numeric(12,2) type).
|
|
16
|
+
entityW: 240,
|
|
17
|
+
// Header and row bands are sized for the field type legibility the reader
|
|
18
|
+
// asked for: an 11-unit field name and a 10.5-unit type sit on a 20-unit
|
|
19
|
+
// row, and the table name leads them on a 30-unit header.
|
|
20
|
+
headerH: 30,
|
|
21
|
+
rowH: 20,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function erGridLayout(er) {
|
|
25
|
+
const raw = er.layout;
|
|
26
|
+
// Grid cells without a layout block mean the default grid, not absolute
|
|
27
|
+
// coordinates that were never supplied.
|
|
28
|
+
if (!raw) {
|
|
29
|
+
const gridPlaced = (er.entities || []).some((entity) => Number.isFinite(entity?.row) && Number.isFinite(entity?.col));
|
|
30
|
+
return gridPlaced ? { ...DEFAULT_ER_GRID } : null;
|
|
31
|
+
}
|
|
32
|
+
if (raw.mode !== 'grid') return null;
|
|
33
|
+
return { ...DEFAULT_ER_GRID, ...raw };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// An entity may omit `width` and inherit the grid's default. Both the layout and
|
|
37
|
+
// the placement validator must resolve that the same way: reading `entity.width`
|
|
38
|
+
// directly turned the documented default into NaN and reported a placement
|
|
39
|
+
// failure for a box the schema accepts.
|
|
40
|
+
export function resolvedEntityWidth(entity, grid) {
|
|
41
|
+
return Number.isFinite(entity?.width) ? entity.width : (grid?.entityW ?? DEFAULT_ER_GRID.entityW);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// One header band plus one band per declared column. An entity with no columns
|
|
45
|
+
// still needs a readable body, so the minimum is a single row.
|
|
46
|
+
export function entityHeight(entity, grid) {
|
|
47
|
+
const rows = Math.max(1, Array.isArray(entity.attributes) ? entity.attributes.length : 0);
|
|
48
|
+
return grid.headerH + rows * grid.rowH + 6;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Every raw column index owns the widest box placed in it and every raw row
|
|
52
|
+
// index owns the tallest, so boxes in one band share a baseline without anyone
|
|
53
|
+
// measuring by hand. Indices with no box still contribute their gap, which is
|
|
54
|
+
// how an author asks for a routing channel between two bands. A box on a half
|
|
55
|
+
// column (a class grid's `col: 1.5`) is centred between two columns: it sizes
|
|
56
|
+
// its row but neither column.
|
|
57
|
+
const gridColumn = (col) => Number.isInteger(col * 2) && col >= 0;
|
|
58
|
+
// `columnGaps` raises the gap after a column to a measured floor (see the
|
|
59
|
+
// renderer's relationship-label demand); the authored or default gapX stays
|
|
60
|
+
// the minimum everywhere, so an absent map reproduces the plain grid.
|
|
61
|
+
export function bandedLayout(entities, grid, { columnGaps = null } = {}) {
|
|
62
|
+
const widths = new Map();
|
|
63
|
+
const heights = new Map();
|
|
64
|
+
let maxCol = -1;
|
|
65
|
+
for (const entity of entities) {
|
|
66
|
+
if (!Number.isInteger(entity.row) || !gridColumn(entity.col)) continue;
|
|
67
|
+
maxCol = Math.max(maxCol, Math.ceil(entity.col));
|
|
68
|
+
if (Number.isInteger(entity.col)) widths.set(entity.col, Math.max(widths.get(entity.col) || 0, resolvedEntityWidth(entity, grid)));
|
|
69
|
+
heights.set(entity.row, Math.max(heights.get(entity.row) || 0, entity.height));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const columnX = new Map();
|
|
73
|
+
const rowY = new Map();
|
|
74
|
+
let x = grid.origin[0];
|
|
75
|
+
for (let col = 0; col <= maxCol; col += 1) {
|
|
76
|
+
columnX.set(col, x);
|
|
77
|
+
x += (widths.get(col) || 0) + Math.max(grid.gapX, columnGaps?.get(col) || 0);
|
|
78
|
+
}
|
|
79
|
+
// A half-column box may be wider than its empty neighbouring bands. Keep
|
|
80
|
+
// every grid box inside the left margin without changing integer-only grids.
|
|
81
|
+
let leftInset = 0;
|
|
82
|
+
for (const entity of entities) {
|
|
83
|
+
if (!Number.isInteger(entity.row) || !gridColumn(entity.col) || Number.isInteger(entity.col)) continue;
|
|
84
|
+
const centre = (col) => columnX.get(col) + (widths.get(col) || 0) / 2;
|
|
85
|
+
const midpoint = (centre(Math.floor(entity.col)) + centre(Math.ceil(entity.col))) / 2;
|
|
86
|
+
leftInset = Math.max(leftInset, grid.origin[0] - midpoint + resolvedEntityWidth(entity, grid) / 2);
|
|
87
|
+
}
|
|
88
|
+
if (leftInset > 0) {
|
|
89
|
+
for (const [col, column] of columnX) columnX.set(col, column + leftInset);
|
|
90
|
+
x += leftInset;
|
|
91
|
+
}
|
|
92
|
+
let y = grid.origin[1];
|
|
93
|
+
const maxRow = Math.max(-1, ...heights.keys());
|
|
94
|
+
for (let row = 0; row <= maxRow; row += 1) {
|
|
95
|
+
rowY.set(row, y);
|
|
96
|
+
y += (heights.get(row) || 0) + grid.gapY;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { columnX, rowY, widths, heights, totalWidth: x, totalHeight: y };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function resolveEntityPos(entity, grid, bands) {
|
|
103
|
+
if (Array.isArray(entity.pos) && entity.pos.length === 2) return entity.pos;
|
|
104
|
+
if (!grid || !bands) return [NaN, NaN];
|
|
105
|
+
if (!Number.isInteger(entity.row) || !gridColumn(entity.col)) return [NaN, NaN];
|
|
106
|
+
const width = resolvedEntityWidth(entity, grid);
|
|
107
|
+
const rowY = bands.rowY.get(entity.row);
|
|
108
|
+
if (!Number.isInteger(entity.col)) {
|
|
109
|
+
const centre = (col) => bands.columnX.get(col) + (bands.widths.get(col) || 0) / 2;
|
|
110
|
+
const [left, right] = [Math.floor(entity.col), Math.ceil(entity.col)];
|
|
111
|
+
if (![bands.columnX.get(left), bands.columnX.get(right), rowY].every(Number.isFinite)) return [NaN, NaN];
|
|
112
|
+
return [(centre(left) + centre(right)) / 2 - width / 2, rowY];
|
|
113
|
+
}
|
|
114
|
+
const columnX = bands.columnX.get(entity.col);
|
|
115
|
+
if (!Number.isFinite(columnX) || !Number.isFinite(rowY)) return [NaN, NaN];
|
|
116
|
+
// Centre each box in its column so the gaps on both sides stay equal; the
|
|
117
|
+
// router reads those gaps as corridors.
|
|
118
|
+
const columnWidth = bands.widths.get(entity.col) || width;
|
|
119
|
+
return [columnX + (columnWidth - width) / 2, rowY];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function validateErGridPlacement(er, grid, bands, problems) {
|
|
123
|
+
const seen = new Map();
|
|
124
|
+
for (const entity of er.entities ?? []) {
|
|
125
|
+
const hasPos = Array.isArray(entity.pos) && entity.pos.length === 2;
|
|
126
|
+
if (!hasPos && !grid) {
|
|
127
|
+
problems.push(`Entity "${entity.id}" needs pos [x,y] or a grid layout with row/col.`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (hasPos) continue;
|
|
131
|
+
if (!Number.isInteger(entity.row) || !Number.isInteger(entity.col)) {
|
|
132
|
+
problems.push(`Entity "${entity.id}" needs pos [x,y] or grid row/col when layout.mode is "grid".`);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (entity.row < 0 || entity.col < 0) {
|
|
136
|
+
problems.push(`Entity "${entity.id}" row/col must be non-negative integers.`);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const key = `${entity.row},${entity.col}`;
|
|
140
|
+
if (seen.has(key)) {
|
|
141
|
+
problems.push(`Entities "${seen.get(key)}" and "${entity.id}" share grid cell row ${entity.row} col ${entity.col}.`);
|
|
142
|
+
} else {
|
|
143
|
+
seen.set(key, entity.id);
|
|
144
|
+
}
|
|
145
|
+
const [x, y] = resolveEntityPos(entity, grid, bands);
|
|
146
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
147
|
+
problems.push(`Entity "${entity.id}" could not be placed from row ${entity.row} col ${entity.col}.`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|