@krapwoo/ds-viewer 0.14.2 → 0.15.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,125 @@
1
+ /**
2
+ * Static, literal-only reader for `CatalogPageInput.tokenDefinition` — the same posture
3
+ * `cli/staticPage.ts`'s own `readStaticPage` already uses for every other field: no module
4
+ * resolution, no execution, a same-file `const` chase only (`resolve`, reused from
5
+ * `cli/staticPage.ts`). Reads structural metadata only (`kind`, section `id`/`title`/`desc`/`wide`,
6
+ * and each entry's own literal `id`, for duplicate detection) — never an entry's real value, which
7
+ * stays an opaque runtime import (design R4).
8
+ */
9
+ import * as ts from 'typescript';
10
+ import { findProp, propertyStringLiteral, readStringLiteralArray, resolve } from "./staticPage.js";
11
+ import { canonicalTokenTitle, isTokenKind } from "../native/catalog/tokenDefinition.js";
12
+ /** Reads `node` (a `tokenDefinition:` property initializer) down to `kind` and each section's
13
+ * `id`/`title`/`desc`/`wide`/entry-ids. Returns `undefined` — never a partial read — when `kind`
14
+ * isn't a literal, `sections` isn't a literal array, or any one section isn't a literal object
15
+ * with a literal `id` and `title`: the same "never a partial read" contract every other array
16
+ * reader in `cli/staticPage.ts` already follows (`readComposedOf`, `readSections`, …). An entry
17
+ * whose own `id` isn't literal is simply omitted from `entryIds` — informational leniency, since
18
+ * only the *structural* shape (kind/sections/ids) needs to be checkable, never an entry's real
19
+ * value (design R4: "no blanket uncheckability of otherwise literal page geometry"). */
20
+ export function readStaticTokenDefinition(node, consts) {
21
+ const resolved = resolve(node, consts, new Set());
22
+ if (!ts.isObjectLiteralExpression(resolved))
23
+ return undefined;
24
+ const kind = propertyStringLiteral(resolved, 'kind', consts);
25
+ if (kind === undefined)
26
+ return undefined;
27
+ const sectionsProp = findProp(resolved, 'sections');
28
+ if (!sectionsProp)
29
+ return undefined;
30
+ const sectionsResolved = resolve(sectionsProp.initializer, consts, new Set());
31
+ if (!ts.isArrayLiteralExpression(sectionsResolved))
32
+ return undefined;
33
+ const sections = [];
34
+ for (const element of sectionsResolved.elements) {
35
+ const sectionResolved = resolve(element, consts, new Set());
36
+ if (!ts.isObjectLiteralExpression(sectionResolved))
37
+ return undefined;
38
+ const id = propertyStringLiteral(sectionResolved, 'id', consts);
39
+ const title = propertyStringLiteral(sectionResolved, 'title', consts);
40
+ if (id === undefined || title === undefined)
41
+ return undefined;
42
+ const desc = propertyStringLiteral(sectionResolved, 'desc', consts);
43
+ const wideProp = findProp(sectionResolved, 'wide');
44
+ const wide = wideProp ? resolve(wideProp.initializer, consts, new Set()).kind === ts.SyntaxKind.TrueKeyword : false;
45
+ const entryIds = [];
46
+ let entriesOpaque = false;
47
+ const entriesProp = findProp(sectionResolved, 'entries');
48
+ if (entriesProp) {
49
+ const entriesResolved = resolve(entriesProp.initializer, consts, new Set());
50
+ if (ts.isArrayLiteralExpression(entriesResolved)) {
51
+ for (const entryElement of entriesResolved.elements) {
52
+ const entryResolved = resolve(entryElement, consts, new Set());
53
+ if (!ts.isObjectLiteralExpression(entryResolved)) {
54
+ entriesOpaque = true;
55
+ continue;
56
+ }
57
+ const entryId = propertyStringLiteral(entryResolved, 'id', consts);
58
+ if (entryId !== undefined)
59
+ entryIds.push(entryId);
60
+ else
61
+ entriesOpaque = true;
62
+ }
63
+ }
64
+ else {
65
+ entriesOpaque = true;
66
+ }
67
+ }
68
+ // #64 B1: this section's own sub-groups, read the same lenient way as its own entries above.
69
+ const groups = [];
70
+ let groupsOpaque = false;
71
+ const groupsProp = findProp(sectionResolved, 'groups');
72
+ if (groupsProp) {
73
+ const groupsResolved = resolve(groupsProp.initializer, consts, new Set());
74
+ if (ts.isArrayLiteralExpression(groupsResolved)) {
75
+ for (const groupElement of groupsResolved.elements) {
76
+ const groupResolved = resolve(groupElement, consts, new Set());
77
+ if (!ts.isObjectLiteralExpression(groupResolved)) {
78
+ groupsOpaque = true;
79
+ continue;
80
+ }
81
+ const groupId = propertyStringLiteral(groupResolved, 'id', consts);
82
+ const groupTitle = propertyStringLiteral(groupResolved, 'title', consts);
83
+ if (groupId === undefined || groupTitle === undefined) {
84
+ groupsOpaque = true;
85
+ continue;
86
+ }
87
+ const groupEntryIds = [];
88
+ let groupEntriesOpaque = false;
89
+ const groupEntriesProp = findProp(groupResolved, 'entries');
90
+ if (groupEntriesProp) {
91
+ const groupEntriesResolved = resolve(groupEntriesProp.initializer, consts, new Set());
92
+ if (ts.isArrayLiteralExpression(groupEntriesResolved)) {
93
+ for (const entryElement of groupEntriesResolved.elements) {
94
+ const entryResolved = resolve(entryElement, consts, new Set());
95
+ if (!ts.isObjectLiteralExpression(entryResolved)) {
96
+ groupEntriesOpaque = true;
97
+ continue;
98
+ }
99
+ const entryId = propertyStringLiteral(entryResolved, 'id', consts);
100
+ if (entryId !== undefined)
101
+ groupEntryIds.push(entryId);
102
+ else
103
+ groupEntriesOpaque = true;
104
+ }
105
+ }
106
+ else {
107
+ groupEntriesOpaque = true;
108
+ }
109
+ }
110
+ groups.push({ id: groupId, title: groupTitle, entryIds: groupEntryIds, entriesOpaque: groupEntriesOpaque });
111
+ }
112
+ }
113
+ else {
114
+ groupsOpaque = true;
115
+ }
116
+ }
117
+ // #64 B5: this section's own literal `aliases`, when every element resolves as a literal
118
+ // string — omitted (never a penalty) otherwise.
119
+ const aliasesProp = findProp(sectionResolved, 'aliases');
120
+ const aliases = aliasesProp ? readStringLiteralArray(aliasesProp.initializer, consts) : undefined;
121
+ sections.push({ id, title, desc, wide, entryIds, entriesOpaque, groups, groupsOpaque, ...(aliases !== undefined ? { aliases } : null) });
122
+ }
123
+ return { kind, sections };
124
+ }
125
+ export { canonicalTokenTitle, isTokenKind };
@@ -132,23 +132,37 @@ export function resolveActiveFromHash(hash, order) {
132
132
  return idFromHash(hash, order);
133
133
  }
134
134
  /** Every member id other than the whole, every member alias, and every family page's own
135
- * (page-level) alias, mapped to where it resolves (spec §5.3 "Old addresses"). Pages without a
136
- * `family` contribute nothing. */
135
+ * (page-level) alias, mapped to where it resolves (spec §5.3 "Old addresses"). #64 B5: also, a
136
+ * tokenDefinition page's own page-level aliases (`tokenDefinitionAliases`) and each of its
137
+ * sections' own `aliases` — the same routing a family page's aliases already get, extended to
138
+ * tokenDefinition pages. A page with neither contributes nothing. */
137
139
  export function aliasTargets(sections) {
138
140
  const targets = new Map();
139
141
  for (const section of sections) {
140
142
  const family = section.family;
141
- if (!family)
142
- continue;
143
- for (const member of family.members) {
144
- if (member.id === family.whole)
145
- continue;
146
- targets.set(member.id, { pageId: section.id, memberId: member.id });
147
- for (const alias of member.aliases ?? [])
148
- targets.set(alias, { pageId: section.id, memberId: member.id });
143
+ if (family) {
144
+ for (const member of family.members) {
145
+ if (member.id === family.whole) {
146
+ // C3: the whole's own id already resolves via `order` (it equals the page's own id), but
147
+ // its own aliases were never registered anywhere — routing one to the page, with no member
148
+ // focus (unlike a non-whole member's alias, which focuses that member).
149
+ for (const alias of member.aliases ?? [])
150
+ targets.set(alias, { pageId: section.id });
151
+ continue;
152
+ }
153
+ targets.set(member.id, { pageId: section.id, memberId: member.id });
154
+ for (const alias of member.aliases ?? [])
155
+ targets.set(alias, { pageId: section.id, memberId: member.id });
156
+ }
157
+ for (const alias of family.aliases ?? [])
158
+ targets.set(alias, { pageId: section.id });
149
159
  }
150
- for (const alias of family.aliases ?? [])
160
+ for (const alias of section.tokenDefinitionAliases ?? [])
151
161
  targets.set(alias, { pageId: section.id });
162
+ for (const tdSection of section.tokenDefinition?.sections ?? []) {
163
+ for (const alias of tdSection.aliases ?? [])
164
+ targets.set(alias, { pageId: section.id, sectionId: tdSection.id });
165
+ }
152
166
  }
153
167
  return targets;
154
168
  }
@@ -171,6 +185,8 @@ export function resolveRoute(hash, order, aliases) {
171
185
  if (target) {
172
186
  if (target.memberId !== undefined)
173
187
  return { kind: 'member', pageId: target.pageId, memberId: target.memberId };
188
+ if (target.sectionId !== undefined)
189
+ return { kind: 'page', id: target.pageId, sectionId: target.sectionId };
174
190
  return { kind: 'page', id: target.pageId };
175
191
  }
176
192
  return { kind: 'notFound', raw: decoded };