astro-archify 0.3.4 → 0.3.6

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.
@@ -0,0 +1,144 @@
1
+ const TARGET_SCHEMA_VERSION = 2;
2
+
3
+ function clone(value) {
4
+ return JSON.parse(JSON.stringify(value));
5
+ }
6
+
7
+ /**
8
+ * Return the authored workflow as a schema-v2 document without its capacity
9
+ * override. The compiler can use this projection to discover the intrinsic v2
10
+ * rank plan before deciding whether an explicit viewBox needs to grow.
11
+ */
12
+ export function intrinsicWorkflow(workflow) {
13
+ const intrinsic = clone(workflow);
14
+ intrinsic.schema_version = TARGET_SCHEMA_VERSION;
15
+ intrinsic.meta = { ...intrinsic.meta };
16
+ delete intrinsic.meta.viewBox;
17
+ return intrinsic;
18
+ }
19
+
20
+ /**
21
+ * Return a schema-v2 planning projection that removes authored route geometry
22
+ * which may only become valid after its legacy X coordinates are remapped.
23
+ * Rank-affecting automatic and straight relationships remain in the projection.
24
+ */
25
+ export function planningWorkflow(workflow) {
26
+ const planned = intrinsicWorkflow(workflow);
27
+ planned.edges = planned.edges.flatMap((edge) => {
28
+ const hasRoutedGeometry = Array.isArray(edge.via)
29
+ || (edge.route && !['auto', 'straight'].includes(edge.route))
30
+ || edge.channelX !== undefined
31
+ || edge.channelY !== undefined;
32
+ if (hasRoutedGeometry) return [];
33
+
34
+ const automatic = {};
35
+ for (const property of ['id', 'from', 'to', 'variant', 'role', 'width']) {
36
+ if (edge[property] !== undefined) automatic[property] = edge[property];
37
+ }
38
+ if (edge.route === 'straight') automatic.route = 'straight';
39
+ if (edge.labelAt === undefined && edge.label !== undefined) automatic.label = edge.label;
40
+ return [automatic];
41
+ });
42
+
43
+ if (Array.isArray(planned.mainPath)) {
44
+ const projectedPairs = new Set(planned.edges.map((edge) => `${edge.from}\u0000${edge.to}`));
45
+ const projectionBreaksMainPath = planned.mainPath.some((from, index) => (
46
+ index < planned.mainPath.length - 1
47
+ && !projectedPairs.has(`${from}\u0000${planned.mainPath[index + 1]}`)
48
+ ));
49
+ if (projectionBreaksMainPath) delete planned.mainPath;
50
+ }
51
+
52
+ return planned;
53
+ }
54
+
55
+ function mappedNumber(value) {
56
+ return Number(value.toFixed(6));
57
+ }
58
+
59
+ /**
60
+ * Build a deterministic piecewise-linear mapping between corresponding legacy
61
+ * and readable rank centers. Coordinates outside the rank span are extrapolated
62
+ * using the nearest segment so explicitly authored outside corridors retain
63
+ * their relative offset.
64
+ */
65
+ export function createHorizontalRankMapper(oldColumns, newColumns) {
66
+ if (
67
+ !Array.isArray(oldColumns)
68
+ || !Array.isArray(newColumns)
69
+ || oldColumns.length !== newColumns.length
70
+ || oldColumns.length < 2
71
+ || !oldColumns.every(Number.isFinite)
72
+ || !newColumns.every(Number.isFinite)
73
+ ) {
74
+ throw new TypeError('Horizontal rank mapping requires matching finite column arrays.');
75
+ }
76
+ for (let index = 1; index < oldColumns.length; index += 1) {
77
+ if (oldColumns[index] <= oldColumns[index - 1] || newColumns[index] <= newColumns[index - 1]) {
78
+ throw new TypeError('Horizontal rank mapping requires strictly increasing columns.');
79
+ }
80
+ }
81
+
82
+ return (x) => {
83
+ if (!Number.isFinite(x)) throw new TypeError('Horizontal rank mapping requires a finite x coordinate.');
84
+ let segment = oldColumns.length - 2;
85
+ if (x <= oldColumns[0]) {
86
+ segment = 0;
87
+ } else {
88
+ for (let index = 0; index < oldColumns.length - 1; index += 1) {
89
+ if (x <= oldColumns[index + 1]) {
90
+ segment = index;
91
+ break;
92
+ }
93
+ }
94
+ }
95
+ const oldSpan = oldColumns[segment + 1] - oldColumns[segment];
96
+ const newSpan = newColumns[segment + 1] - newColumns[segment];
97
+ const ratio = (x - oldColumns[segment]) / oldSpan;
98
+ return mappedNumber(newColumns[segment] + ratio * newSpan);
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Apply one horizontal coordinate mapping to every schema-v1 absolute X pin.
104
+ * The caller owns the supplied workflow; this function reports an audit trail
105
+ * for each changed coordinate in stable document order.
106
+ */
107
+ export function mapExplicitCoordinates(workflow, mapX) {
108
+ const changedCoordinates = [];
109
+ const record = (path, owner, property) => {
110
+ const from = owner[property];
111
+ const to = mapX(from);
112
+ owner[property] = to;
113
+ if (to !== from) changedCoordinates.push({ path, from, to });
114
+ };
115
+
116
+ for (const [edgeIndex, edge] of workflow.edges.entries()) {
117
+ if (Array.isArray(edge.via)) {
118
+ for (const [pointIndex, point] of edge.via.entries()) {
119
+ if (Array.isArray(point) && Number.isFinite(point[0])) {
120
+ record(`/edges/${edgeIndex}/via/${pointIndex}/0`, point, 0);
121
+ }
122
+ }
123
+ }
124
+ if (Array.isArray(edge.labelAt) && Number.isFinite(edge.labelAt[0])) {
125
+ record(`/edges/${edgeIndex}/labelAt/0`, edge.labelAt, 0);
126
+ }
127
+ if (Number.isFinite(edge.channelX)) {
128
+ record(`/edges/${edgeIndex}/channelX`, edge, 'channelX');
129
+ }
130
+ }
131
+ return changedCoordinates;
132
+ }
133
+
134
+ /**
135
+ * Construct an independently owned schema-v2 candidate with all authored
136
+ * absolute X pins mapped to the readable rank plan.
137
+ */
138
+ export function createMappedWorkflowCandidate(workflow, oldColumns, newColumns) {
139
+ const document = clone(workflow);
140
+ document.schema_version = TARGET_SCHEMA_VERSION;
141
+ const mapX = createHorizontalRankMapper(oldColumns, newColumns);
142
+ const changedCoordinates = mapExplicitCoordinates(document, mapX);
143
+ return { document, changedCoordinates };
144
+ }