hyper-html-api 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.
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Walk v2Rules pulling matching values from v1Data.
3
+ *
4
+ * - Scalar leaves: carry the v1 value if defined, else leave undefined so
5
+ * the engine keeps v2's template default on apply.
6
+ * - Object arrays ([sel, shape]): carry v1's items, trimmed to v1's count;
7
+ * each item shape-matches recursively. Fields v2 added that didn't exist
8
+ * in v1 are left undefined → template default.
9
+ * - Scalar arrays (`sel[]`): carry as-is.
10
+ * - Objects: recurse per key.
11
+ *
12
+ * Returns { data, summary } where summary counts scalar leaves carried,
13
+ * dropped, and total list items preserved.
14
+ */
15
+ export function shapeMatch(v1Data, v2Rules) {
16
+ const summary = { carriedOver: 0, discarded: 0, listItems: 0 }
17
+ const data = walk(v2Rules, v1Data, summary)
18
+ collectDiscards(v2Rules, v1Data, summary)
19
+ return { data, summary }
20
+ }
21
+
22
+ function walk(rule, v1, summary) {
23
+ if (typeof rule === 'string') {
24
+ if (rule.endsWith('[]')) {
25
+ if (!Array.isArray(v1)) return undefined
26
+ summary.listItems += v1.length
27
+ summary.carriedOver += v1.length
28
+ return v1
29
+ }
30
+ if (v1 === undefined || v1 === null) return undefined
31
+ summary.carriedOver++
32
+ return v1
33
+ }
34
+ if (Array.isArray(rule)) {
35
+ const [, shape] = rule
36
+ if (!Array.isArray(v1)) return undefined
37
+ summary.listItems += v1.length
38
+ return v1.map((item) => walk(shape, item, summary))
39
+ }
40
+ if (typeof rule === 'object' && rule !== null) {
41
+ const out = {}
42
+ for (const [k, sub] of Object.entries(rule)) {
43
+ const v = walk(sub, v1 == null ? undefined : v1[k], summary)
44
+ if (v !== undefined) out[k] = v
45
+ }
46
+ return out
47
+ }
48
+ return undefined
49
+ }
50
+
51
+ function collectDiscards(rule, v1, summary) {
52
+ if (v1 == null) return
53
+ if (typeof rule === 'object' && rule !== null && !Array.isArray(rule)) {
54
+ if (typeof v1 !== 'object' || Array.isArray(v1)) return
55
+ const known = new Set(Object.keys(rule))
56
+ for (const k of Object.keys(v1)) {
57
+ if (!known.has(k)) {
58
+ summary.discarded += countScalarLeaves(v1[k])
59
+ } else {
60
+ collectDiscards(rule[k], v1[k], summary)
61
+ }
62
+ }
63
+ return
64
+ }
65
+ if (Array.isArray(rule) && Array.isArray(v1)) {
66
+ const shape = rule[1]
67
+ v1.forEach((item) => collectDiscards(shape, item, summary))
68
+ return
69
+ }
70
+ if (
71
+ typeof rule === 'string' &&
72
+ !rule.endsWith('[]') &&
73
+ typeof v1 === 'object' &&
74
+ v1 !== null
75
+ ) {
76
+ summary.discarded += countScalarLeaves(v1)
77
+ }
78
+ }
79
+
80
+ function countScalarLeaves(v) {
81
+ if (v === null || v === undefined) return 0
82
+ if (Array.isArray(v)) return v.reduce((n, x) => n + countScalarLeaves(x), 0)
83
+ if (typeof v === 'object') {
84
+ return Object.values(v).reduce((n, x) => n + countScalarLeaves(x), 0)
85
+ }
86
+ return 1
87
+ }