@bpmn-ld/bpmn-ld 0.0.0-stage → 0.1.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 +201 -0
- package/README.md +125 -2
- package/contexts/combined.jsonld +3 -0
- package/contexts/core.jsonld +170 -0
- package/contexts/di.jsonld +37 -0
- package/dist/analyze/analyze.d.ts +3 -0
- package/dist/analyze/analyze.js +11 -0
- package/dist/analyze/findings.d.ts +8 -0
- package/dist/analyze/findings.js +1 -0
- package/dist/analyze/report.d.ts +3 -0
- package/dist/analyze/report.js +9 -0
- package/dist/analyze/rules/ad-hoc-completion-condition.d.ts +2 -0
- package/dist/analyze/rules/ad-hoc-completion-condition.js +7 -0
- package/dist/analyze/rules/cannot-reach-end.d.ts +2 -0
- package/dist/analyze/rules/cannot-reach-end.js +28 -0
- package/dist/analyze/rules/compensation-association.d.ts +2 -0
- package/dist/analyze/rules/compensation-association.js +13 -0
- package/dist/analyze/rules/dead-end.d.ts +2 -0
- package/dist/analyze/rules/dead-end.js +13 -0
- package/dist/analyze/rules/end-event.d.ts +2 -0
- package/dist/analyze/rules/end-event.js +8 -0
- package/dist/analyze/rules/error-catch.d.ts +2 -0
- package/dist/analyze/rules/error-catch.js +14 -0
- package/dist/analyze/rules/escalation-catch.d.ts +2 -0
- package/dist/analyze/rules/escalation-catch.js +14 -0
- package/dist/analyze/rules/gateway-cardinality.d.ts +3 -0
- package/dist/analyze/rules/gateway-cardinality.js +17 -0
- package/dist/analyze/rules/index.d.ts +4 -0
- package/dist/analyze/rules/index.js +21 -0
- package/dist/analyze/rules/instantiating-gateway.d.ts +2 -0
- package/dist/analyze/rules/instantiating-gateway.js +14 -0
- package/dist/analyze/rules/non-interrupting-start.d.ts +2 -0
- package/dist/analyze/rules/non-interrupting-start.js +14 -0
- package/dist/analyze/rules/reachability.d.ts +2 -0
- package/dist/analyze/rules/reachability.js +25 -0
- package/dist/analyze/rules/rule.d.ts +7 -0
- package/dist/analyze/rules/rule.js +1 -0
- package/dist/analyze/rules/signal-catch.d.ts +2 -0
- package/dist/analyze/rules/signal-catch.js +12 -0
- package/dist/analyze/rules/split-join-pairing.d.ts +3 -0
- package/dist/analyze/rules/split-join-pairing.js +50 -0
- package/dist/analyze/rules/start-event.d.ts +2 -0
- package/dist/analyze/rules/start-event.js +8 -0
- package/dist/analyze/rules/task-message-flow.d.ts +2 -0
- package/dist/analyze/rules/task-message-flow.js +19 -0
- package/dist/analyze/scope-graph.d.ts +56 -0
- package/dist/analyze/scope-graph.js +89 -0
- package/dist/analyze-cli.d.ts +1 -0
- package/dist/analyze-cli.js +5 -0
- package/dist/analyze-run.d.ts +12 -0
- package/dist/analyze-run.js +85 -0
- package/dist/bpmn-ld-cli.d.ts +1 -0
- package/dist/bpmn-ld-cli.js +90 -0
- package/dist/build-cli.d.ts +1 -0
- package/dist/build-cli.js +2 -0
- package/dist/build.d.ts +3 -0
- package/dist/build.js +38 -0
- package/dist/check-xml/check.d.ts +3 -0
- package/dist/check-xml/check.js +11 -0
- package/dist/check-xml/lint.d.ts +2 -0
- package/dist/check-xml/lint.js +47 -0
- package/dist/check-xml/moddle.d.ts +6 -0
- package/dist/check-xml/moddle.js +23 -0
- package/dist/check-xml/types.d.ts +20 -0
- package/dist/check-xml/types.js +1 -0
- package/dist/check-xml/xsd.d.ts +2 -0
- package/dist/check-xml/xsd.js +25 -0
- package/dist/check-xml-cli.d.ts +1 -0
- package/dist/check-xml-cli.js +5 -0
- package/dist/check-xml-run.d.ts +10 -0
- package/dist/check-xml-run.js +77 -0
- package/dist/convert/analyze-output.d.ts +3 -0
- package/dist/convert/analyze-output.js +8 -0
- package/dist/convert/convert.d.ts +19 -0
- package/dist/convert/convert.js +96 -0
- package/dist/convert/errors.d.ts +5 -0
- package/dist/convert/errors.js +8 -0
- package/dist/convert/extensions.d.ts +23 -0
- package/dist/convert/extensions.js +103 -0
- package/dist/convert/mapping-check.d.ts +15 -0
- package/dist/convert/mapping-check.js +66 -0
- package/dist/convert/mapping.d.ts +52 -0
- package/dist/convert/mapping.js +273 -0
- package/dist/convert/namespaces.d.ts +2 -0
- package/dist/convert/namespaces.js +19 -0
- package/dist/convert/validate-output.d.ts +2 -0
- package/dist/convert/validate-output.js +28 -0
- package/dist/convert/warnings.d.ts +9 -0
- package/dist/convert/warnings.js +67 -0
- package/dist/convert/xml-reader.d.ts +11 -0
- package/dist/convert/xml-reader.js +84 -0
- package/dist/convert-cli.d.ts +1 -0
- package/dist/convert-cli.js +5 -0
- package/dist/convert-run.d.ts +10 -0
- package/dist/convert-run.js +125 -0
- package/dist/coverage-cli.d.ts +1 -0
- package/dist/coverage-cli.js +8 -0
- package/dist/coverage-mappings.d.ts +3 -0
- package/dist/coverage-mappings.js +34 -0
- package/dist/coverage-scope.d.ts +11 -0
- package/dist/coverage-scope.js +187 -0
- package/dist/coverage-xsd.d.ts +15 -0
- package/dist/coverage-xsd.js +55 -0
- package/dist/coverage.d.ts +35 -0
- package/dist/coverage.js +152 -0
- package/dist/docs-cli.d.ts +1 -0
- package/dist/docs-cli.js +18 -0
- package/dist/docs.d.ts +8 -0
- package/dist/docs.js +136 -0
- package/dist/eval/checks.d.ts +19 -0
- package/dist/eval/checks.js +252 -0
- package/dist/eval/regenerate.d.ts +10 -0
- package/dist/eval/regenerate.js +48 -0
- package/dist/eval-regenerate-cli.d.ts +1 -0
- package/dist/eval-regenerate-cli.js +16 -0
- package/dist/export/data.d.ts +10 -0
- package/dist/export/data.js +127 -0
- package/dist/export/diagram.d.ts +8 -0
- package/dist/export/diagram.js +112 -0
- package/dist/export/element.d.ts +27 -0
- package/dist/export/element.js +175 -0
- package/dist/export/export.d.ts +22 -0
- package/dist/export/export.js +124 -0
- package/dist/export/extensions.d.ts +18 -0
- package/dist/export/extensions.js +122 -0
- package/dist/export/layout-export.d.ts +4 -0
- package/dist/export/layout-export.js +25 -0
- package/dist/export/mapping-check.d.ts +39 -0
- package/dist/export/mapping-check.js +175 -0
- package/dist/export/normalize.d.ts +3 -0
- package/dist/export/normalize.js +67 -0
- package/dist/export-cli.d.ts +1 -0
- package/dist/export-cli.js +5 -0
- package/dist/export-run.d.ts +10 -0
- package/dist/export-run.js +157 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/install-skill-run.d.ts +8 -0
- package/dist/install-skill-run.js +30 -0
- package/dist/install-skill.d.ts +23 -0
- package/dist/install-skill.js +76 -0
- package/dist/layout/collaboration.d.ts +36 -0
- package/dist/layout/collaboration.js +152 -0
- package/dist/layout/di.d.ts +15 -0
- package/dist/layout/di.js +52 -0
- package/dist/layout/extract.d.ts +2 -0
- package/dist/layout/extract.js +99 -0
- package/dist/layout/lanes.d.ts +30 -0
- package/dist/layout/lanes.js +112 -0
- package/dist/layout/layering.d.ts +7 -0
- package/dist/layout/layering.js +53 -0
- package/dist/layout/layout.d.ts +12 -0
- package/dist/layout/layout.js +299 -0
- package/dist/layout/model.d.ts +52 -0
- package/dist/layout/model.js +10 -0
- package/dist/layout/placement.d.ts +25 -0
- package/dist/layout/placement.js +256 -0
- package/dist/layout/standalone.d.ts +8 -0
- package/dist/layout/standalone.js +50 -0
- package/dist/layout-cli.d.ts +1 -0
- package/dist/layout-cli.js +5 -0
- package/dist/layout-run.d.ts +9 -0
- package/dist/layout-run.js +105 -0
- package/dist/loader.d.ts +5 -0
- package/dist/loader.js +7 -0
- package/dist/namespace.d.ts +5 -0
- package/dist/namespace.js +5 -0
- package/dist/ontology.d.ts +19 -0
- package/dist/ontology.js +94 -0
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +2 -0
- package/dist/render/geometry.d.ts +40 -0
- package/dist/render/geometry.js +81 -0
- package/dist/render/renderer.d.ts +31 -0
- package/dist/render/renderer.js +369 -0
- package/dist/render/symbols.d.ts +10 -0
- package/dist/render/symbols.js +26 -0
- package/dist/render/text.d.ts +20 -0
- package/dist/render/text.js +28 -0
- package/dist/render-cli.d.ts +1 -0
- package/dist/render-cli.js +7 -0
- package/dist/render-run.d.ts +9 -0
- package/dist/render-run.js +161 -0
- package/dist/render.d.ts +4 -0
- package/dist/render.js +4 -0
- package/dist/schema-overrides.d.ts +14 -0
- package/dist/schema-overrides.js +36 -0
- package/dist/schema-validate.d.ts +5 -0
- package/dist/schema-validate.js +8 -0
- package/dist/schema.d.ts +6 -0
- package/dist/schema.js +114 -0
- package/dist/shacl.d.ts +15 -0
- package/dist/shacl.js +37 -0
- package/dist/validate-cli.d.ts +1 -0
- package/dist/validate-cli.js +5 -0
- package/dist/validate-run.d.ts +8 -0
- package/dist/validate-run.js +58 -0
- package/dist/validate.d.ts +11 -0
- package/dist/validate.js +94 -0
- package/dist/versioning-cli.d.ts +1 -0
- package/dist/versioning-cli.js +18 -0
- package/dist/versioning.d.ts +17 -0
- package/dist/versioning.js +45 -0
- package/ontology/core.ttl +993 -0
- package/ontology/di.ttl +167 -0
- package/package.json +76 -4
- package/schema/bpmn-ld.schema.json +15230 -0
- package/shapes/core.shapes.ttl +398 -0
- package/skills/bpmn-ld/SKILL.md +154 -0
- package/skills/bpmn-ld/evals/models/order-approval-di.jsonld +296 -0
- package/skills/bpmn-ld/evals/models/refund-di.bpmn +82 -0
- package/skills/bpmn-ld/evals/models/refund.bpmn +18 -0
- package/skills/bpmn-ld/evals/outputs/explain-collaboration.md +22 -0
- package/skills/bpmn-ld/evals/outputs/explain-order-approval.md +24 -0
- package/skills/bpmn-ld/evals/outputs/explain-refund.md +17 -0
- package/skills/bpmn-ld/evals/outputs/generate-activity-compensation.jsonld +39 -0
- package/skills/bpmn-ld/evals/outputs/generate-ad-hoc-sub-process.jsonld +52 -0
- package/skills/bpmn-ld/evals/outputs/generate-boundary-event.jsonld +27 -0
- package/skills/bpmn-ld/evals/outputs/generate-collaboration.jsonld +49 -0
- package/skills/bpmn-ld/evals/outputs/generate-conditions.jsonld +93 -0
- package/skills/bpmn-ld/evals/outputs/generate-diagram-properties.jsonld +340 -0
- package/skills/bpmn-ld/evals/outputs/generate-error.jsonld +217 -0
- package/skills/bpmn-ld/evals/outputs/generate-escalation.jsonld +231 -0
- package/skills/bpmn-ld/evals/outputs/generate-event-definition-ref.jsonld +67 -0
- package/skills/bpmn-ld/evals/outputs/generate-event-sub-process.jsonld +54 -0
- package/skills/bpmn-ld/evals/outputs/generate-exclusive-choice.jsonld +32 -0
- package/skills/bpmn-ld/evals/outputs/generate-instantiating-gateway.jsonld +24 -0
- package/skills/bpmn-ld/evals/outputs/generate-lane-partition.jsonld +46 -0
- package/skills/bpmn-ld/evals/outputs/generate-link-event.expect.json +11 -0
- package/skills/bpmn-ld/evals/outputs/generate-link-event.jsonld +57 -0
- package/skills/bpmn-ld/evals/outputs/generate-loop.jsonld +30 -0
- package/skills/bpmn-ld/evals/outputs/generate-message.jsonld +61 -0
- package/skills/bpmn-ld/evals/outputs/generate-parallel-branches.jsonld +25 -0
- package/skills/bpmn-ld/evals/outputs/generate-process-attributes.jsonld +49 -0
- package/skills/bpmn-ld/evals/outputs/generate-script-task.jsonld +18 -0
- package/skills/bpmn-ld/evals/outputs/generate-signal.jsonld +178 -0
- package/skills/bpmn-ld/evals/outputs/generate-sub-process.jsonld +31 -0
- package/skills/bpmn-ld/evals/outputs/generate-targeted-compensation.jsonld +40 -0
- package/skills/bpmn-ld/evals/outputs/generate-task-implementation.jsonld +18 -0
- package/skills/bpmn-ld/evals/outputs/generate-task-message-references.jsonld +49 -0
- package/skills/bpmn-ld/evals/outputs/generate-transaction.jsonld +48 -0
- package/skills/bpmn-ld/evals/outputs/modify-order-approval.jsonld +376 -0
- package/skills/bpmn-ld/evals/outputs/modify-refund.jsonld +373 -0
- package/skills/bpmn-ld/evals/outputs/order-approval.jsonld +29 -0
- package/skills/bpmn-ld/evals/outputs/refine-order-approval.jsonld +377 -0
- package/skills/bpmn-ld/evals/prompts/explain-collaboration.md +3 -0
- package/skills/bpmn-ld/evals/prompts/explain-order-approval.md +3 -0
- package/skills/bpmn-ld/evals/prompts/explain-refund.md +3 -0
- package/skills/bpmn-ld/evals/prompts/generate-activity-compensation.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-ad-hoc-sub-process.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-boundary-event.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-collaboration.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-conditions.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-diagram-properties.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-error.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-escalation.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-event-definition-ref.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-event-sub-process.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-exclusive-choice.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-instantiating-gateway.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-lane-partition.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-link-event.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-loop.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-message.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-parallel-branches.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-process-attributes.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-script-task.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-signal.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-sub-process.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-targeted-compensation.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-task-implementation.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-task-message-references.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-transaction.md +1 -0
- package/skills/bpmn-ld/evals/prompts/modify-order-approval.md +4 -0
- package/skills/bpmn-ld/evals/prompts/modify-refund.md +4 -0
- package/skills/bpmn-ld/evals/prompts/order-approval.md +1 -0
- package/skills/bpmn-ld/evals/prompts/refine-order-approval.md +8 -0
- package/skills/bpmn-ld/examples/positive/activity-compensation.jsonld +39 -0
- package/skills/bpmn-ld/examples/positive/ad-hoc-sub-process.jsonld +52 -0
- package/skills/bpmn-ld/examples/positive/boundary-event.jsonld +27 -0
- package/skills/bpmn-ld/examples/positive/collaboration.jsonld +49 -0
- package/skills/bpmn-ld/examples/positive/conditions.jsonld +93 -0
- package/skills/bpmn-ld/examples/positive/diagram-properties.jsonld +340 -0
- package/skills/bpmn-ld/examples/positive/error.jsonld +208 -0
- package/skills/bpmn-ld/examples/positive/escalation.jsonld +231 -0
- package/skills/bpmn-ld/examples/positive/event-definition-ref.jsonld +142 -0
- package/skills/bpmn-ld/examples/positive/event-sub-process.jsonld +54 -0
- package/skills/bpmn-ld/examples/positive/exclusive-choice.jsonld +32 -0
- package/skills/bpmn-ld/examples/positive/instantiating-gateway.jsonld +24 -0
- package/skills/bpmn-ld/examples/positive/lane-partition.jsonld +46 -0
- package/skills/bpmn-ld/examples/positive/link-event.expect.json +10 -0
- package/skills/bpmn-ld/examples/positive/link-event.jsonld +138 -0
- package/skills/bpmn-ld/examples/positive/loop.jsonld +30 -0
- package/skills/bpmn-ld/examples/positive/message.jsonld +60 -0
- package/skills/bpmn-ld/examples/positive/order-approval.jsonld +29 -0
- package/skills/bpmn-ld/examples/positive/parallel-branches.jsonld +25 -0
- package/skills/bpmn-ld/examples/positive/process-attributes.jsonld +49 -0
- package/skills/bpmn-ld/examples/positive/script-task.jsonld +18 -0
- package/skills/bpmn-ld/examples/positive/signal.jsonld +150 -0
- package/skills/bpmn-ld/examples/positive/sub-process.jsonld +31 -0
- package/skills/bpmn-ld/examples/positive/targeted-compensation.jsonld +40 -0
- package/skills/bpmn-ld/examples/positive/task-implementation.jsonld +18 -0
- package/skills/bpmn-ld/examples/positive/task-message-references.jsonld +151 -0
- package/skills/bpmn-ld/examples/positive/transaction.jsonld +48 -0
- package/skills/bpmn-ld/references/clarification-and-refinement.md +60 -0
- package/skills/bpmn-ld/references/common-mistakes.md +110 -0
- package/skills/bpmn-ld/references/explain-and-document.md +36 -0
- package/skills/bpmn-ld/references/modify-existing-model.md +49 -0
- package/skills/bpmn-ld/references/patterns.md +292 -0
- package/skills/bpmn-ld/references/render-output.md +1 -0
- package/skills/bpmn-ld/references/rule-catalogue.md +183 -0
- package/skills/bpmn-ld/references/vocabulary-summary.md +65 -0
- package/skills/bpmn-ld/template.jsonld +14 -0
- package/templates/combined.jsonld +3 -0
- package/templates/core.jsonld +170 -0
- package/templates/core.shapes.ttl +398 -0
- package/templates/core.ttl +993 -0
- package/templates/di.jsonld +37 -0
- package/templates/di.ttl +167 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { assignLayers } from "./layering.js";
|
|
2
|
+
export const ORIGIN = { x: 100, y: 100 };
|
|
3
|
+
const LAYER_GAP = 60;
|
|
4
|
+
export const ROW_GAP = 40;
|
|
5
|
+
const LOOP_CLEARANCE = 30;
|
|
6
|
+
const CONTAINER_MARGIN = 30;
|
|
7
|
+
const BOUNDARY_SIZE = 36;
|
|
8
|
+
const BOUNDARY_PITCH = 44;
|
|
9
|
+
const ACTIVITY_SIZE = { width: 100, height: 80 };
|
|
10
|
+
const DATA_GAP = 40;
|
|
11
|
+
const DATA_SPACING = 20;
|
|
12
|
+
const STUB_LENGTH = 40;
|
|
13
|
+
const boundariesOf = (model, hostId) => model.nodes.filter((node) => node.attachedTo === hostId);
|
|
14
|
+
export const reserveBelow = (model, hostId) => {
|
|
15
|
+
const count = boundariesOf(model, hostId).length;
|
|
16
|
+
return count === 0 ? 0 : BOUNDARY_SIZE / 2 + 10 + 8 * count;
|
|
17
|
+
};
|
|
18
|
+
export const boundaryBox = (model, node, host) => {
|
|
19
|
+
const siblings = boundariesOf(model, node.attachedTo);
|
|
20
|
+
const span = siblings.length * BOUNDARY_PITCH - (BOUNDARY_PITCH - BOUNDARY_SIZE);
|
|
21
|
+
return {
|
|
22
|
+
x: host.x + Math.round((host.width - span) / 2) + siblings.indexOf(node) * BOUNDARY_PITCH,
|
|
23
|
+
y: host.y + host.height - BOUNDARY_SIZE / 2,
|
|
24
|
+
width: BOUNDARY_SIZE,
|
|
25
|
+
height: BOUNDARY_SIZE,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export const placeNodes = (model, layering, origin = ORIGIN) => {
|
|
29
|
+
const sizes = new Map(model.nodes.map((node) => [node.id, node.size]));
|
|
30
|
+
const slot = (id) => sizes.get(id).height + reserveBelow(model, id);
|
|
31
|
+
const columnWidths = layering.layers.map((layer) => Math.max(0, ...layer.map((id) => sizes.get(id).width)));
|
|
32
|
+
const columnHeights = layering.layers.map((layer) => layer.reduce((sum, id) => sum + slot(id), 0) + ROW_GAP * Math.max(0, layer.length - 1));
|
|
33
|
+
const tallest = Math.max(0, ...columnHeights);
|
|
34
|
+
const boxes = new Map();
|
|
35
|
+
let x = origin.x;
|
|
36
|
+
layering.layers.forEach((layer, index) => {
|
|
37
|
+
let y = origin.y + (tallest - columnHeights[index]) / 2;
|
|
38
|
+
for (const id of layer) {
|
|
39
|
+
const { width, height } = sizes.get(id);
|
|
40
|
+
boxes.set(id, { x: x + (columnWidths[index] - width) / 2, y, width, height });
|
|
41
|
+
y += slot(id) + ROW_GAP;
|
|
42
|
+
}
|
|
43
|
+
x += columnWidths[index] + LAYER_GAP;
|
|
44
|
+
});
|
|
45
|
+
for (const node of model.nodes)
|
|
46
|
+
if (node.attachedTo)
|
|
47
|
+
boxes.set(node.id, boundaryBox(model, node, boxes.get(node.attachedTo)));
|
|
48
|
+
return boxes;
|
|
49
|
+
};
|
|
50
|
+
const centerY = (box) => box.y + box.height / 2;
|
|
51
|
+
const centerX = (box) => box.x + box.width / 2;
|
|
52
|
+
const dedupe = (points) => points.filter((point, index) => index === 0 || point.x !== points[index - 1].x || point.y !== points[index - 1].y);
|
|
53
|
+
const routeForward = (source, target) => {
|
|
54
|
+
const start = { x: source.x + source.width, y: centerY(source) };
|
|
55
|
+
const end = { x: target.x, y: centerY(target) };
|
|
56
|
+
if (start.y === end.y)
|
|
57
|
+
return [start, end];
|
|
58
|
+
const middle = Math.round((start.x + end.x) / 2);
|
|
59
|
+
return [start, { x: middle, y: start.y }, { x: middle, y: end.y }, end];
|
|
60
|
+
};
|
|
61
|
+
const routeBack = (source, target, floor) => {
|
|
62
|
+
const lane = floor + LOOP_CLEARANCE;
|
|
63
|
+
const start = { x: centerX(source), y: source.y + source.height };
|
|
64
|
+
const end = { x: centerX(target), y: target.y + target.height };
|
|
65
|
+
return [start, { x: start.x, y: lane }, { x: end.x, y: lane }, end];
|
|
66
|
+
};
|
|
67
|
+
const routeBoundary = (model, event, source, host, target) => {
|
|
68
|
+
const siblings = boundariesOf(model, event.attachedTo);
|
|
69
|
+
const index = siblings.indexOf(event);
|
|
70
|
+
const start = { x: centerX(source), y: source.y + source.height };
|
|
71
|
+
const lane = start.y + 8 + 8 * (siblings.length - 1 - index);
|
|
72
|
+
const end = { x: target.x, y: centerY(target) };
|
|
73
|
+
const middle = Math.round((host.x + host.width + target.x) / 2) + 6 * index;
|
|
74
|
+
return dedupe([start, { x: start.x, y: lane }, { x: middle, y: lane }, { x: middle, y: end.y }, end]);
|
|
75
|
+
};
|
|
76
|
+
export const routeFlows = (model, flows, layering, boxes) => {
|
|
77
|
+
const floor = Math.max(0, ...[...boxes.values()].map((box) => box.y + box.height));
|
|
78
|
+
const routes = new Map();
|
|
79
|
+
const nodes = new Map(model.nodes.map((node) => [node.id, node]));
|
|
80
|
+
for (const flow of flows) {
|
|
81
|
+
const source = boxes.get(flow.source);
|
|
82
|
+
const target = boxes.get(flow.target);
|
|
83
|
+
const back = layering.backFlows.has(flow.id) || layering.layerOf.get(flow.target) <= layering.layerOf.get(flow.source);
|
|
84
|
+
const event = nodes.get(flow.source);
|
|
85
|
+
if (back)
|
|
86
|
+
routes.set(flow.id, routeBack(source, target, floor));
|
|
87
|
+
else if (event.attachedTo)
|
|
88
|
+
routes.set(flow.id, routeBoundary(model, event, source, boxes.get(event.attachedTo), target));
|
|
89
|
+
else
|
|
90
|
+
routes.set(flow.id, routeForward(source, target));
|
|
91
|
+
}
|
|
92
|
+
return routes;
|
|
93
|
+
};
|
|
94
|
+
export const placeData = (model, boxes, routes, fixed = new Map()) => {
|
|
95
|
+
const placed = new Map();
|
|
96
|
+
const open = model.data.filter((item) => !fixed.has(item.id));
|
|
97
|
+
if (open.length === 0)
|
|
98
|
+
return placed;
|
|
99
|
+
const scope = model.nodes.map((node) => boxes.get(node.id)).filter((box) => box !== undefined);
|
|
100
|
+
const left = Math.min(...scope.map((box) => box.x));
|
|
101
|
+
const top = Math.max(...scope.map((box) => box.y + box.height), ...[...routes.values()].flat().map((point) => point.y)) + DATA_GAP;
|
|
102
|
+
const wanted = open.map((item) => {
|
|
103
|
+
const link = model.associations.find((association) => association.source === item.id || association.target === item.id);
|
|
104
|
+
const other = link ? boxes.get(link.source === item.id ? link.target : link.source) : undefined;
|
|
105
|
+
return { item, x: other ? other.x + other.width / 2 - item.size.width / 2 : left };
|
|
106
|
+
});
|
|
107
|
+
wanted.sort((a, b) => a.x - b.x);
|
|
108
|
+
let cursor = -Infinity;
|
|
109
|
+
for (const { item, x } of wanted) {
|
|
110
|
+
const at = Math.max(x, cursor);
|
|
111
|
+
placed.set(item.id, { x: at, y: top, width: item.size.width, height: item.size.height });
|
|
112
|
+
cursor = at + item.size.width + DATA_SPACING;
|
|
113
|
+
}
|
|
114
|
+
return placed;
|
|
115
|
+
};
|
|
116
|
+
const routeAssociation = (node, data) => {
|
|
117
|
+
const nx = node.x + node.width / 2;
|
|
118
|
+
const dx = data.x + data.width / 2;
|
|
119
|
+
if (data.y >= node.y + node.height) {
|
|
120
|
+
const start = { x: nx, y: node.y + node.height };
|
|
121
|
+
const end = { x: dx, y: data.y };
|
|
122
|
+
if (nx === dx)
|
|
123
|
+
return [start, end];
|
|
124
|
+
const middle = Math.round((start.y + end.y) / 2);
|
|
125
|
+
return [start, { x: nx, y: middle }, { x: dx, y: middle }, end];
|
|
126
|
+
}
|
|
127
|
+
if (data.y + data.height <= node.y) {
|
|
128
|
+
const start = { x: nx, y: node.y };
|
|
129
|
+
const end = { x: dx, y: data.y + data.height };
|
|
130
|
+
if (nx === dx)
|
|
131
|
+
return [start, end];
|
|
132
|
+
const middle = Math.round((start.y + end.y) / 2);
|
|
133
|
+
return [start, { x: nx, y: middle }, { x: dx, y: middle }, end];
|
|
134
|
+
}
|
|
135
|
+
return [{ x: nx, y: node.y + node.height / 2 }, { x: dx, y: data.y + data.height / 2 }];
|
|
136
|
+
};
|
|
137
|
+
export const routeExtras = (model, boxes, only) => {
|
|
138
|
+
const routes = new Map();
|
|
139
|
+
const dataIds = new Set(model.data.map((item) => item.id));
|
|
140
|
+
for (const stub of model.stubs) {
|
|
141
|
+
if (only && !only.has(stub.id))
|
|
142
|
+
continue;
|
|
143
|
+
const source = stub.source ? boxes.get(stub.source) : undefined;
|
|
144
|
+
const target = stub.target ? boxes.get(stub.target) : undefined;
|
|
145
|
+
if (source) {
|
|
146
|
+
const y = centerY(source);
|
|
147
|
+
routes.set(stub.id, [{ x: source.x + source.width, y }, { x: source.x + source.width + STUB_LENGTH, y }]);
|
|
148
|
+
}
|
|
149
|
+
else if (target) {
|
|
150
|
+
const y = centerY(target);
|
|
151
|
+
routes.set(stub.id, [{ x: target.x - STUB_LENGTH, y }, { x: target.x, y }]);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
for (const association of model.associations) {
|
|
155
|
+
if (only && !only.has(association.id))
|
|
156
|
+
continue;
|
|
157
|
+
const source = boxes.get(association.source);
|
|
158
|
+
const target = boxes.get(association.target);
|
|
159
|
+
if (!source || !target)
|
|
160
|
+
continue;
|
|
161
|
+
const route = dataIds.has(association.source) ? routeAssociation(target, source).reverse() : routeAssociation(source, target);
|
|
162
|
+
routes.set(association.id, route);
|
|
163
|
+
}
|
|
164
|
+
return routes;
|
|
165
|
+
};
|
|
166
|
+
export const measure = (model) => {
|
|
167
|
+
for (const node of model.nodes) {
|
|
168
|
+
if (node.content) {
|
|
169
|
+
measure(node.content);
|
|
170
|
+
const layering = assignLayers(node.content);
|
|
171
|
+
const origin = { x: node.content.lanes?.indent ?? 0, y: 0 };
|
|
172
|
+
const boxes = placeNodes(node.content, layering, origin);
|
|
173
|
+
node.content.lanes?.arrange(layering, boxes, origin);
|
|
174
|
+
const routes = routeFlows(node.content, node.content.flows, layering, boxes);
|
|
175
|
+
for (const [id, box] of placeData(node.content, boxes, routes))
|
|
176
|
+
boxes.set(id, box);
|
|
177
|
+
for (const [id, route] of routeExtras(node.content, boxes))
|
|
178
|
+
routes.set(id, route);
|
|
179
|
+
const points = [...routes.values()].flat();
|
|
180
|
+
const right = Math.max(0, ...[...boxes.values()].map((box) => box.x + box.width), ...points.map((point) => point.x));
|
|
181
|
+
const bottom = Math.max(0, ...[...boxes.values()].map((box) => box.y + box.height), ...points.map((point) => point.y));
|
|
182
|
+
node.size = {
|
|
183
|
+
width: Math.max(ACTIVITY_SIZE.width, right + 2 * CONTAINER_MARGIN),
|
|
184
|
+
height: Math.max(ACTIVITY_SIZE.height, bottom + 2 * CONTAINER_MARGIN),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
node.size = { ...node.size, width: Math.max(node.size.width, boundariesOf(model, node.id).length * BOUNDARY_PITCH) };
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const intersects = (a, b) => a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
|
|
191
|
+
export const placeScope = (model, origin, context) => {
|
|
192
|
+
const layering = assignLayers(model);
|
|
193
|
+
const layered = placeNodes(model, layering, origin);
|
|
194
|
+
context.arrange?.(model, layering, layered, origin);
|
|
195
|
+
model.lanes?.arrange(layering, layered, origin);
|
|
196
|
+
const anchor = model.nodes.find((node) => !node.attachedTo && context.fixed.has(node.id));
|
|
197
|
+
const dx = anchor ? context.fixed.get(anchor.id).x - layered.get(anchor.id).x : 0;
|
|
198
|
+
const dy = anchor ? context.fixed.get(anchor.id).y - layered.get(anchor.id).y : 0;
|
|
199
|
+
const taken = [...(context.obstacles.get(model.id) ?? [])];
|
|
200
|
+
const boxes = new Map();
|
|
201
|
+
for (const node of model.nodes) {
|
|
202
|
+
if (node.attachedTo)
|
|
203
|
+
continue;
|
|
204
|
+
const existing = context.fixed.get(node.id);
|
|
205
|
+
if (existing) {
|
|
206
|
+
boxes.set(node.id, existing);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const base = layered.get(node.id);
|
|
210
|
+
const box = { ...base, x: base.x + dx, y: base.y + dy };
|
|
211
|
+
for (let hit = taken.filter((other) => intersects(box, other)); hit.length > 0; hit = taken.filter((other) => intersects(box, other)))
|
|
212
|
+
box.y = Math.max(...hit.map((other) => other.y + other.height)) + ROW_GAP;
|
|
213
|
+
taken.push(box);
|
|
214
|
+
boxes.set(node.id, box);
|
|
215
|
+
}
|
|
216
|
+
for (const node of model.nodes)
|
|
217
|
+
if (node.attachedTo)
|
|
218
|
+
boxes.set(node.id, context.fixed.get(node.id) ?? boundaryBox(model, node, boxes.get(node.attachedTo)));
|
|
219
|
+
const flows = context.missingFlows ? model.flows.filter((flow) => context.missingFlows.has(flow.id)) : model.flows;
|
|
220
|
+
const routed = routeFlows(model, flows, layering, boxes);
|
|
221
|
+
for (const [id, route] of routed)
|
|
222
|
+
context.routes.set(id, route);
|
|
223
|
+
const scopeRoutes = new Map(routed);
|
|
224
|
+
for (const [id, box] of placeData(model, boxes, scopeRoutes, context.fixed)) {
|
|
225
|
+
const placed = { ...box };
|
|
226
|
+
for (let hit = taken.filter((other) => intersects(placed, other)); hit.length > 0; hit = taken.filter((other) => intersects(placed, other)))
|
|
227
|
+
placed.y = Math.max(...hit.map((other) => other.y + other.height)) + ROW_GAP;
|
|
228
|
+
taken.push(placed);
|
|
229
|
+
boxes.set(id, placed);
|
|
230
|
+
}
|
|
231
|
+
for (const item of model.data)
|
|
232
|
+
if (context.fixed.has(item.id))
|
|
233
|
+
boxes.set(item.id, context.fixed.get(item.id));
|
|
234
|
+
for (const [id, route] of routeExtras(model, boxes, context.missingFlows))
|
|
235
|
+
context.routes.set(id, route);
|
|
236
|
+
for (const [id, box] of boxes)
|
|
237
|
+
context.boxes.set(id, box);
|
|
238
|
+
for (const node of model.nodes) {
|
|
239
|
+
if (!node.content)
|
|
240
|
+
continue;
|
|
241
|
+
const frame = boxes.get(node.id);
|
|
242
|
+
placeScope(node.content, { x: frame.x + CONTAINER_MARGIN + (node.content.lanes?.indent ?? 0), y: frame.y + CONTAINER_MARGIN }, context);
|
|
243
|
+
if (node.content.lanes && context.lanes) {
|
|
244
|
+
const inner = { x: frame.x + CONTAINER_MARGIN, y: frame.y + CONTAINER_MARGIN, width: frame.width - 2 * CONTAINER_MARGIN, height: frame.height - 2 * CONTAINER_MARGIN };
|
|
245
|
+
const anchored = node.content.nodes.some((child) => context.fixed.has(child.id));
|
|
246
|
+
for (const [id, box] of node.content.lanes.boxes(inner, context.boxes, anchored))
|
|
247
|
+
context.lanes.set(id, box);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
export const place = (model) => {
|
|
252
|
+
measure(model);
|
|
253
|
+
const context = { fixed: new Map(), obstacles: new Map(), boxes: new Map(), routes: new Map() };
|
|
254
|
+
placeScope(model, ORIGIN, context);
|
|
255
|
+
return context;
|
|
256
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type LaneNode } from "./lanes.ts";
|
|
2
|
+
import { type Box, type Placement, type ProcessModel } from "./model.ts";
|
|
3
|
+
export interface StandaloneExisting {
|
|
4
|
+
fixed: Map<string, Box>;
|
|
5
|
+
obstacles: Map<string, Box[]>;
|
|
6
|
+
missingFlows?: Set<string>;
|
|
7
|
+
}
|
|
8
|
+
export declare const placeStandalone: (model: ProcessModel, lanes: LaneNode[] | undefined, existing?: StandaloneExisting) => Required<Placement>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { arrangeLanes, bandsBottom, buildLaneLayout, extendLastBand, laneBoxes, laneBoxesFromNodes, LANE_HEADER } from "./lanes.js";
|
|
2
|
+
import { allNodes } from "./model.js";
|
|
3
|
+
import { measure, ORIGIN, placeScope } from "./placement.js";
|
|
4
|
+
const MARGIN = 20;
|
|
5
|
+
export const placeStandalone = (model, lanes, existing = { fixed: new Map(), obstacles: new Map() }) => {
|
|
6
|
+
measure(model);
|
|
7
|
+
const laneLayout = lanes ? buildLaneLayout(lanes, model) : undefined;
|
|
8
|
+
const context = {
|
|
9
|
+
fixed: existing.fixed,
|
|
10
|
+
obstacles: existing.obstacles,
|
|
11
|
+
missingFlows: existing.missingFlows,
|
|
12
|
+
boxes: new Map(),
|
|
13
|
+
routes: new Map(),
|
|
14
|
+
lanes: new Map(),
|
|
15
|
+
arrange: laneLayout ? (scope, layering, layered, origin) => (scope === model ? arrangeLanes(laneLayout, scope, layering, layered, origin) : undefined) : undefined,
|
|
16
|
+
};
|
|
17
|
+
const indent = laneLayout ? LANE_HEADER * (laneLayout.depth + 1) + MARGIN : 0;
|
|
18
|
+
placeScope(model, { x: ORIGIN.x + indent, y: ORIGIN.y }, context);
|
|
19
|
+
const result = { boxes: context.boxes, routes: context.routes, lanes: new Map() };
|
|
20
|
+
const nested = () => {
|
|
21
|
+
for (const [id, box] of context.lanes)
|
|
22
|
+
result.lanes.set(id, existing.fixed.get(id) ?? box);
|
|
23
|
+
};
|
|
24
|
+
if (!laneLayout) {
|
|
25
|
+
nested();
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
const anchored = allNodes(model).some((node) => existing.fixed.has(node.id));
|
|
29
|
+
const boxes = [...context.boxes.values()];
|
|
30
|
+
const points = [...context.routes.values()].flat();
|
|
31
|
+
const right = Math.max(...boxes.map((box) => box.x + box.width), ...points.map((point) => point.x)) + MARGIN;
|
|
32
|
+
let frame;
|
|
33
|
+
let computed;
|
|
34
|
+
if (anchored) {
|
|
35
|
+
const x = Math.min(...boxes.map((box) => box.x)) - indent;
|
|
36
|
+
const y = Math.min(...boxes.map((box) => box.y)) - MARGIN;
|
|
37
|
+
const bottom = Math.max(...boxes.map((box) => box.y + box.height), ...points.map((point) => point.y)) + MARGIN;
|
|
38
|
+
frame = { x, y, width: right - x, height: bottom - y };
|
|
39
|
+
computed = laneBoxesFromNodes(laneLayout, frame, context.boxes, 0);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
extendLastBand(laneLayout, Math.max(...points.map((point) => point.y), ...boxes.map((box) => box.y + box.height), 0) + MARGIN / 2);
|
|
43
|
+
frame = { x: ORIGIN.x, y: ORIGIN.y, width: right - ORIGIN.x, height: bandsBottom(laneLayout) - ORIGIN.y };
|
|
44
|
+
computed = laneBoxes(laneLayout, frame, 0);
|
|
45
|
+
}
|
|
46
|
+
for (const lane of laneLayout.lanes)
|
|
47
|
+
result.lanes.set(lane.id, existing.fixed.get(lane.id) ?? computed.get(lane.id));
|
|
48
|
+
nested();
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const EXIT_OK = 0;
|
|
2
|
+
export declare const EXIT_BAD_INPUT = 2;
|
|
3
|
+
export interface CliResult {
|
|
4
|
+
exitCode: number;
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const USAGE = "Usage: layout <input.jsonld> [options]\n\nAdds BPMN DI (shapes, edges and waypoints) to a BPMN-LD document and writes the\nresult as JSON-LD. A process that already has a diagram is completed: shapes and\nedges are added only for flow nodes and sequence flows that lack them, placed\naround the existing shapes, and existing diagram content is never changed.\nWith --force the diagram of each process is replaced by a fresh full layout.\n\nOptions:\n -o, --output <file> Write the document to <file> instead of standard output\n --force Recompute all shapes and edges, replacing existing diagrams\n -h, --help Show this help\n\nExit codes:\n 0 Laid out successfully\n 2 Usage error or unusable input (missing file, invalid JSON, invalid BPMN-LD)\n";
|
|
9
|
+
export declare const runLayout: (argv: string[]) => CliResult;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { layoutDocument, LayoutInputError } from "./layout/layout.js";
|
|
3
|
+
export const EXIT_OK = 0;
|
|
4
|
+
export const EXIT_BAD_INPUT = 2;
|
|
5
|
+
const MAX_REPORTED_ERRORS = 10;
|
|
6
|
+
export const USAGE = `Usage: layout <input.jsonld> [options]
|
|
7
|
+
|
|
8
|
+
Adds BPMN DI (shapes, edges and waypoints) to a BPMN-LD document and writes the
|
|
9
|
+
result as JSON-LD. A process that already has a diagram is completed: shapes and
|
|
10
|
+
edges are added only for flow nodes and sequence flows that lack them, placed
|
|
11
|
+
around the existing shapes, and existing diagram content is never changed.
|
|
12
|
+
With --force the diagram of each process is replaced by a fresh full layout.
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
-o, --output <file> Write the document to <file> instead of standard output
|
|
16
|
+
--force Recompute all shapes and edges, replacing existing diagrams
|
|
17
|
+
-h, --help Show this help
|
|
18
|
+
|
|
19
|
+
Exit codes:
|
|
20
|
+
0 Laid out successfully
|
|
21
|
+
2 Usage error or unusable input (missing file, invalid JSON, invalid BPMN-LD)
|
|
22
|
+
`;
|
|
23
|
+
class UsageError extends Error {
|
|
24
|
+
}
|
|
25
|
+
const parseArgs = (argv) => {
|
|
26
|
+
let output;
|
|
27
|
+
let input;
|
|
28
|
+
let force = false;
|
|
29
|
+
for (let i = 0; i < argv.length; i++) {
|
|
30
|
+
const arg = argv[i];
|
|
31
|
+
if (arg === "-h" || arg === "--help")
|
|
32
|
+
return "help";
|
|
33
|
+
else if (arg === "--force")
|
|
34
|
+
force = true;
|
|
35
|
+
else if (arg === "-o" || arg === "--output") {
|
|
36
|
+
const value = argv[++i];
|
|
37
|
+
if (value === undefined || value.startsWith("-"))
|
|
38
|
+
throw new UsageError(`${arg} requires a value`);
|
|
39
|
+
output = value;
|
|
40
|
+
}
|
|
41
|
+
else if (arg.startsWith("-"))
|
|
42
|
+
throw new UsageError(`unknown option ${arg}`);
|
|
43
|
+
else if (input !== undefined)
|
|
44
|
+
throw new UsageError(`unexpected extra argument ${arg}`);
|
|
45
|
+
else
|
|
46
|
+
input = arg;
|
|
47
|
+
}
|
|
48
|
+
if (input === undefined)
|
|
49
|
+
throw new UsageError("missing input file");
|
|
50
|
+
return { input, output, force };
|
|
51
|
+
};
|
|
52
|
+
const failure = (stderr) => ({ exitCode: EXIT_BAD_INPUT, stdout: "", stderr });
|
|
53
|
+
const readJson = (path) => {
|
|
54
|
+
let text;
|
|
55
|
+
try {
|
|
56
|
+
text = readFileSync(path, "utf8");
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const reason = error.code === "ENOENT" ? "no such file" : (error.message ?? "cannot read");
|
|
60
|
+
throw new UsageError(`cannot read ${path}: ${reason}`);
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(text);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
throw new UsageError(`${path} is not valid JSON: ${error.message}`);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
export const runLayout = (argv) => {
|
|
70
|
+
let options;
|
|
71
|
+
let document;
|
|
72
|
+
try {
|
|
73
|
+
options = parseArgs(argv);
|
|
74
|
+
if (options === "help")
|
|
75
|
+
return { exitCode: EXIT_OK, stdout: USAGE, stderr: "" };
|
|
76
|
+
document = readJson(options.input);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error instanceof UsageError)
|
|
80
|
+
return failure(`error: ${error.message}\n\n${USAGE}`);
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
let text;
|
|
84
|
+
try {
|
|
85
|
+
text = `${JSON.stringify(layoutDocument(document, { force: options.force }).document, null, 2)}\n`;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (!(error instanceof LayoutInputError))
|
|
89
|
+
throw error;
|
|
90
|
+
const shown = error.errors.slice(0, MAX_REPORTED_ERRORS).map((e) => `error: ${options.input}: invalid BPMN-LD: ${e}`);
|
|
91
|
+
const more = error.errors.length - shown.length;
|
|
92
|
+
if (more > 0)
|
|
93
|
+
shown.push(`error: ... and ${more} more`);
|
|
94
|
+
return failure(`${shown.join("\n")}\n`);
|
|
95
|
+
}
|
|
96
|
+
if (options.output === undefined)
|
|
97
|
+
return { exitCode: EXIT_OK, stdout: text, stderr: "" };
|
|
98
|
+
try {
|
|
99
|
+
writeFileSync(options.output, text);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return failure(`error: cannot write ${options.output}: ${error.message}\n`);
|
|
103
|
+
}
|
|
104
|
+
return { exitCode: EXIT_OK, stdout: "", stderr: "" };
|
|
105
|
+
};
|
package/dist/loader.d.ts
ADDED
package/dist/loader.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
export const fileDocumentLoader = async (url) => {
|
|
4
|
+
if (!url.startsWith("file:"))
|
|
5
|
+
throw new Error(`Only file: URLs can be loaded, got ${url}`);
|
|
6
|
+
return { contextUrl: undefined, document: JSON.parse(await readFile(fileURLToPath(url), "utf8")), documentUrl: url };
|
|
7
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const BPMN_NAMESPACE = "https://bpmn-ld.example.org/ns#";
|
|
2
|
+
export declare const BPMN_ONTOLOGY_IRI: string;
|
|
3
|
+
export declare const BPMNDI_NAMESPACE = "https://bpmn-ld.example.org/di#";
|
|
4
|
+
export declare const BPMNDI_ONTOLOGY_IRI: string;
|
|
5
|
+
export declare const BPMN_SHAPES_NAMESPACE: string;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const BPMN_NAMESPACE = "https://bpmn-ld.example.org/ns#";
|
|
2
|
+
export const BPMN_ONTOLOGY_IRI = BPMN_NAMESPACE.replace(/#$/, "");
|
|
3
|
+
export const BPMNDI_NAMESPACE = "https://bpmn-ld.example.org/di#";
|
|
4
|
+
export const BPMNDI_ONTOLOGY_IRI = BPMNDI_NAMESPACE.replace(/#$/, "");
|
|
5
|
+
export const BPMN_SHAPES_NAMESPACE = `${BPMN_ONTOLOGY_IRI}/shapes#`;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Store } from "n3";
|
|
2
|
+
export declare const RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
|
|
3
|
+
export declare const RDFS_SUBCLASS_OF = "http://www.w3.org/2000/01/rdf-schema#subClassOf";
|
|
4
|
+
export declare const RDFS_DOMAIN = "http://www.w3.org/2000/01/rdf-schema#domain";
|
|
5
|
+
export declare const RDFS_RANGE = "http://www.w3.org/2000/01/rdf-schema#range";
|
|
6
|
+
export declare const parseOntology: (turtle: string) => Store<import("n3").Quad, import("n3").Quad, import("@rdfjs/types").Quad, import("@rdfjs/types").Quad>;
|
|
7
|
+
export declare const ONTOLOGY_FILES: string[];
|
|
8
|
+
export declare const isOwnedIri: (iri: string) => boolean;
|
|
9
|
+
export declare const loadOntology: () => Store<import("n3").Quad, import("n3").Quad, import("@rdfjs/types").Quad, import("@rdfjs/types").Quad>;
|
|
10
|
+
export declare const declaredClasses: (store: Store) => Set<string>;
|
|
11
|
+
export declare const declaredProperties: (store: Store) => Set<string>;
|
|
12
|
+
export declare const declaredIndividuals: (store: Store) => Set<string>;
|
|
13
|
+
export declare const declaredTerms: (store: Store) => Set<string>;
|
|
14
|
+
export declare const typesOf: (store: Store, subject: string) => string[];
|
|
15
|
+
export declare const superClasses: (store: Store, cls: string) => Set<string>;
|
|
16
|
+
export declare const isSubClassOf: (store: Store, cls: string, ancestor: string) => boolean;
|
|
17
|
+
export declare const domainAlternatives: (store: Store, property: string) => string[][];
|
|
18
|
+
export declare const rangeAlternatives: (store: Store, property: string) => string[][];
|
|
19
|
+
export declare const checkOntology: (store: Store) => string[];
|
package/dist/ontology.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { Parser, Store } from "n3";
|
|
4
|
+
import { ROOT } from "./paths.js";
|
|
5
|
+
import { BPMN_NAMESPACE, BPMNDI_NAMESPACE } from "./namespace.js";
|
|
6
|
+
export const RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
|
|
7
|
+
export const RDFS_SUBCLASS_OF = "http://www.w3.org/2000/01/rdf-schema#subClassOf";
|
|
8
|
+
export const RDFS_DOMAIN = "http://www.w3.org/2000/01/rdf-schema#domain";
|
|
9
|
+
export const RDFS_RANGE = "http://www.w3.org/2000/01/rdf-schema#range";
|
|
10
|
+
const OWL = "http://www.w3.org/2002/07/owl#";
|
|
11
|
+
const OWL_UNION_OF = `${OWL}unionOf`;
|
|
12
|
+
const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
|
|
13
|
+
const RDF_FIRST = `${RDF}first`;
|
|
14
|
+
const RDF_REST = `${RDF}rest`;
|
|
15
|
+
const RDF_NIL = `${RDF}nil`;
|
|
16
|
+
const XSD = "http://www.w3.org/2001/XMLSchema#";
|
|
17
|
+
const CLASS_TYPES = [`${OWL}Class`];
|
|
18
|
+
const PROPERTY_TYPES = [`${OWL}ObjectProperty`, `${OWL}DatatypeProperty`];
|
|
19
|
+
export const parseOntology = (turtle) => new Store(new Parser().parse(turtle));
|
|
20
|
+
export const ONTOLOGY_FILES = ["ontology/core.ttl", "ontology/di.ttl"];
|
|
21
|
+
export const isOwnedIri = (iri) => iri.startsWith(BPMN_NAMESPACE) || iri.startsWith(BPMNDI_NAMESPACE);
|
|
22
|
+
export const loadOntology = () => parseOntology(ONTOLOGY_FILES.map((file) => readFileSync(join(ROOT, file), "utf8")).join("\n"));
|
|
23
|
+
const subjectsOfTypes = (store, types) => types.flatMap((type) => store.getSubjects(RDF_TYPE, type, null).map((s) => s.value));
|
|
24
|
+
export const declaredClasses = (store) => new Set(subjectsOfTypes(store, CLASS_TYPES));
|
|
25
|
+
export const declaredProperties = (store) => new Set(subjectsOfTypes(store, PROPERTY_TYPES));
|
|
26
|
+
export const declaredIndividuals = (store) => new Set(subjectsOfTypes(store, [`${OWL}NamedIndividual`]));
|
|
27
|
+
export const declaredTerms = (store) => new Set([...declaredClasses(store), ...declaredProperties(store), ...declaredIndividuals(store)]);
|
|
28
|
+
export const typesOf = (store, subject) => store.getObjects(subject, RDF_TYPE, null).map((o) => o.value);
|
|
29
|
+
export const superClasses = (store, cls) => {
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
const pending = [cls];
|
|
32
|
+
while (pending.length) {
|
|
33
|
+
for (const parent of store.getObjects(pending.pop(), RDFS_SUBCLASS_OF, null)) {
|
|
34
|
+
if (!seen.has(parent.value)) {
|
|
35
|
+
seen.add(parent.value);
|
|
36
|
+
pending.push(parent.value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return seen;
|
|
41
|
+
};
|
|
42
|
+
export const isSubClassOf = (store, cls, ancestor) => cls === ancestor || superClasses(store, cls).has(ancestor);
|
|
43
|
+
const listMembers = (store, head) => {
|
|
44
|
+
const members = [];
|
|
45
|
+
let node = head;
|
|
46
|
+
while (node.value !== RDF_NIL) {
|
|
47
|
+
const [first] = store.getObjects(node, RDF_FIRST, null);
|
|
48
|
+
const [rest] = store.getObjects(node, RDF_REST, null);
|
|
49
|
+
if (!first || !rest)
|
|
50
|
+
break;
|
|
51
|
+
members.push(first.value);
|
|
52
|
+
node = rest;
|
|
53
|
+
}
|
|
54
|
+
return members;
|
|
55
|
+
};
|
|
56
|
+
const alternativesOf = (store, property, predicate) => store.getObjects(property, predicate, null).map((target) => {
|
|
57
|
+
const [union] = store.getObjects(target, OWL_UNION_OF, null);
|
|
58
|
+
return union ? listMembers(store, union) : [target.value];
|
|
59
|
+
});
|
|
60
|
+
export const domainAlternatives = (store, property) => alternativesOf(store, property, RDFS_DOMAIN);
|
|
61
|
+
export const rangeAlternatives = (store, property) => alternativesOf(store, property, RDFS_RANGE);
|
|
62
|
+
export const checkOntology = (store) => {
|
|
63
|
+
const errors = [];
|
|
64
|
+
const classes = declaredClasses(store);
|
|
65
|
+
const properties = declaredProperties(store);
|
|
66
|
+
const declared = declaredTerms(store);
|
|
67
|
+
for (const quad of store.getQuads(null, null, null, null)) {
|
|
68
|
+
for (const term of [quad.subject, quad.predicate, quad.object]) {
|
|
69
|
+
if (term.termType === "NamedNode" && isOwnedIri(term.value) && !declared.has(term.value)) {
|
|
70
|
+
errors.push(`${term.value} is used but not declared`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const property of properties) {
|
|
75
|
+
for (const alternatives of domainAlternatives(store, property)) {
|
|
76
|
+
for (const domain of alternatives) {
|
|
77
|
+
if (!classes.has(domain))
|
|
78
|
+
errors.push(`domain ${domain} of ${property} is not a declared class`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const alternatives of rangeAlternatives(store, property)) {
|
|
82
|
+
for (const range of alternatives) {
|
|
83
|
+
if (!classes.has(range) && !range.startsWith(XSD)) {
|
|
84
|
+
errors.push(`range ${range} of ${property} is not a declared class or datatype`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
for (const cls of classes) {
|
|
90
|
+
if (superClasses(store, cls).has(cls))
|
|
91
|
+
errors.push(`class hierarchy has a cycle through ${cls}`);
|
|
92
|
+
}
|
|
93
|
+
return [...new Set(errors)];
|
|
94
|
+
};
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ROOT: string;
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Json } from "../export/element.ts";
|
|
2
|
+
export interface Bounds {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
export interface Point {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
}
|
|
12
|
+
export interface DiagramLabel {
|
|
13
|
+
bounds?: Bounds;
|
|
14
|
+
}
|
|
15
|
+
export interface DiagramShape {
|
|
16
|
+
id: string;
|
|
17
|
+
bpmnElement: string;
|
|
18
|
+
element?: Json;
|
|
19
|
+
bounds: Bounds;
|
|
20
|
+
isExpanded?: boolean;
|
|
21
|
+
isHorizontal?: boolean;
|
|
22
|
+
isMarkerVisible?: boolean;
|
|
23
|
+
label?: DiagramLabel;
|
|
24
|
+
}
|
|
25
|
+
export interface DiagramEdge {
|
|
26
|
+
id: string;
|
|
27
|
+
bpmnElement: string;
|
|
28
|
+
element?: Json;
|
|
29
|
+
waypoints: Point[];
|
|
30
|
+
label?: DiagramLabel;
|
|
31
|
+
}
|
|
32
|
+
export interface Diagram {
|
|
33
|
+
id: string;
|
|
34
|
+
name?: string;
|
|
35
|
+
bpmnElement?: string;
|
|
36
|
+
resolution?: number;
|
|
37
|
+
shapes: DiagramShape[];
|
|
38
|
+
edges: DiagramEdge[];
|
|
39
|
+
}
|
|
40
|
+
export declare const geometryDiagrams: (document: Json) => Diagram[];
|