@glossarist/concept-browser 0.7.114 → 0.7.116

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.
Files changed (103) hide show
  1. package/README.md +45 -2
  2. package/cli/index.mjs +10 -13
  3. package/cli/index.ts +9 -11
  4. package/data/concept-model/shapes/glossarist.concept.yaml.schema.json +255 -0
  5. package/package.json +18 -12
  6. package/scripts/__tests__/{bibliography.test.mjs → bibliography.test.ts} +1 -1
  7. package/scripts/__tests__/{cli-script-contract.test.mjs → cli-script-contract.test.ts} +3 -3
  8. package/scripts/__tests__/{concept-groups.test.mjs → concept-groups.test.ts} +1 -1
  9. package/scripts/__tests__/{doctor.test.mjs → doctor.test.ts} +1 -1
  10. package/scripts/__tests__/{fetch-datasets.test.mjs → fetch-datasets.test.ts} +1 -1
  11. package/scripts/__tests__/{first-non-empty.test.mjs → first-non-empty.test.ts} +1 -1
  12. package/scripts/__tests__/{process-about-pages.test.mjs → process-about-pages.test.ts} +1 -1
  13. package/scripts/bridge-to-astro.js +132 -0
  14. package/scripts/{bridge-to-astro.mjs → bridge-to-astro.ts} +2 -4
  15. package/scripts/build-edges.js +242 -336
  16. package/scripts/build-edges.ts +572 -0
  17. package/scripts/build-scripts.mjs +22 -0
  18. package/scripts/check-syntax.js +81 -0
  19. package/scripts/{check-syntax.mjs → check-syntax.ts} +20 -13
  20. package/scripts/doctor.js +278 -0
  21. package/scripts/extract-source-refs.js +16 -24
  22. package/scripts/extract-source-refs.ts +32 -0
  23. package/scripts/fetch-datasets.js +278 -0
  24. package/scripts/{fetch-datasets.mjs → fetch-datasets.ts} +2 -2
  25. package/scripts/generate-404.js +10 -11
  26. package/scripts/generate-404.ts +15 -0
  27. package/scripts/generate-data.js +2220 -0
  28. package/scripts/{generate-data.mjs → generate-data.ts} +73 -460
  29. package/scripts/generate-ontology-data.js +139 -0
  30. package/scripts/generate-ontology-schema.js +472 -0
  31. package/scripts/{generate-ontology-schema.mjs → generate-ontology-schema.ts} +2 -2
  32. package/scripts/lib/{build-cache.mjs → build-cache.ts} +3 -2
  33. package/scripts/lib/concept-formats.ts +206 -0
  34. package/scripts/lib/concept-uri.ts +14 -0
  35. package/scripts/lib/{dataset-turtle.mjs → dataset-turtle.ts} +4 -3
  36. package/scripts/lib/harmonize-concept.ts +86 -0
  37. package/scripts/lib/jsonld-emitter.ts +169 -0
  38. package/scripts/lib/{local-path-safety.mjs → local-path-safety.ts} +6 -2
  39. package/scripts/lib/yaml-types.ts +262 -0
  40. package/scripts/load-site-config.js +83 -0
  41. package/scripts/migrate-v1-to-v3.js +113 -0
  42. package/scripts/normalize-yaml.js +96 -0
  43. package/scripts/process-about-pages.js +241 -0
  44. package/scripts/{process-about-pages.mjs → process-about-pages.ts} +1 -1
  45. package/scripts/smoke.js +211 -0
  46. package/scripts/{smoke.mjs → smoke.ts} +4 -3
  47. package/scripts/sync-concept-model.js +63 -0
  48. package/scripts/validate-shacl.js +152 -0
  49. package/scripts/{validate-shacl.mjs → validate-shacl.ts} +5 -5
  50. package/scripts/validate-yaml.js +132 -0
  51. package/scripts/validate-yaml.ts +144 -0
  52. package/src/__tests__/extract-source-refs.test.ts +2 -3
  53. package/src/__tests__/perf/serialization-perf.test.ts +5 -5
  54. package/src/__tests__/published-package-contract.test.ts +84 -0
  55. package/src/__tests__/scripts/agents-turtle.test.ts +1 -1
  56. package/src/__tests__/scripts/bibliography-turtle.test.ts +1 -1
  57. package/src/__tests__/scripts/build-activity-turtle.test.ts +1 -1
  58. package/src/__tests__/scripts/build-cache.test.ts +1 -1
  59. package/src/__tests__/scripts/build-integration.test.ts +19 -19
  60. package/src/__tests__/scripts/dataset-turtle.test.ts +1 -1
  61. package/src/__tests__/scripts/migrate-v1-to-v3.test.ts +1 -1
  62. package/src/__tests__/scripts/normalize-yaml.test.ts +1 -1
  63. package/src/__tests__/scripts/turtle-escape.test.ts +1 -1
  64. package/src/__tests__/scripts/validate-yaml.test.ts +76 -0
  65. package/src/__tests__/scripts/version-turtle.test.ts +1 -1
  66. package/src/__tests__/scripts/vocab-turtle.test.ts +1 -1
  67. package/src/__tests__/scripts-syntax.test.ts +4 -4
  68. package/src/__tests__/use-rdf-document.test.ts +2 -3
  69. package/src/__tests__/utils/content-renderer-xss.test.ts +57 -0
  70. package/src/__tests__/validate-shacl.test.ts +3 -3
  71. package/src/adapters/wire/index.ts +69 -0
  72. package/src/data/ontology-schema.json +1336 -183
  73. package/src/utils/content-renderer.ts +7 -3
  74. package/src/utils/url-safety.ts +1 -5
  75. package/tsconfig.json +2 -2
  76. package/vite.config.ts +1 -1
  77. package/src/__tests__/composables/use-concept-edges-v2.test.ts +0 -167
  78. package/src/__tests__/composables/use-concept-edges-v2.test.ts.bak +0 -166
  79. package/src/types/agents-version-turtle.d.ts +0 -27
  80. package/src/types/bibliography-turtle.d.ts +0 -12
  81. package/src/types/build-activity-turtle.d.ts +0 -16
  82. package/src/types/build-cache.d.ts +0 -20
  83. package/src/types/dataset-turtle.d.ts +0 -32
  84. package/src/types/normalize-yaml.d.ts +0 -16
  85. package/src/types/turtle-escape.d.ts +0 -6
  86. package/src/types/vocab-turtle.d.ts +0 -13
  87. /package/scripts/{doctor.mjs → doctor.ts} +0 -0
  88. /package/scripts/{generate-ontology-data.mjs → generate-ontology-data.ts} +0 -0
  89. /package/scripts/lib/{agents-turtle.mjs → agents-turtle.ts} +0 -0
  90. /package/scripts/lib/{bibliography-turtle.mjs → bibliography-turtle.ts} +0 -0
  91. /package/scripts/lib/{bibliography.mjs → bibliography.ts} +0 -0
  92. /package/scripts/lib/build/{image-assets.mjs → image-assets.ts} +0 -0
  93. /package/scripts/lib/build/{non-verbal-consumer.mjs → non-verbal-consumer.ts} +0 -0
  94. /package/scripts/lib/{build-activity-turtle.mjs → build-activity-turtle.ts} +0 -0
  95. /package/scripts/lib/{concept-groups.mjs → concept-groups.ts} +0 -0
  96. /package/scripts/lib/{first-non-empty.mjs → first-non-empty.ts} +0 -0
  97. /package/scripts/lib/{turtle-escape.mjs → turtle-escape.ts} +0 -0
  98. /package/scripts/lib/{version-turtle.mjs → version-turtle.ts} +0 -0
  99. /package/scripts/lib/{vocab-turtle.mjs → vocab-turtle.ts} +0 -0
  100. /package/scripts/{load-site-config.mjs → load-site-config.ts} +0 -0
  101. /package/scripts/{migrate-v1-to-v3.mjs → migrate-v1-to-v3.ts} +0 -0
  102. /package/scripts/{normalize-yaml.mjs → normalize-yaml.ts} +0 -0
  103. /package/scripts/{sync-concept-model.mjs → sync-concept-model.ts} +0 -0
@@ -1,118 +1,106 @@
1
+ // scripts/lib/concept-uri.ts
1
2
  function buildConceptUri(uriBase, registerId, conceptId) {
2
3
  return `${uriBase}/${registerId}/concept/${conceptId}`;
3
4
  }
4
5
 
5
- const VALID_COMPLETENESS = new Set(['complete', 'partial']);
6
+ // scripts/extract-source-refs.js
7
+ function extractSourceRefs(concept, registerId) {
8
+ const refs = /* @__PURE__ */ new Set();
9
+ for (const src of concept["gl:source"] || []) {
10
+ const origin = src["gl:origin"];
11
+ if (origin) {
12
+ const ref = origin["gl:ref"];
13
+ if (ref?.["gl:source"]) refs.add(ref["gl:source"]);
14
+ }
15
+ }
16
+ for (const lc of Object.values(concept["gl:localizedConcept"] || {})) {
17
+ for (const src of lc["gl:source"] || []) {
18
+ const origin = src["gl:origin"];
19
+ if (origin) {
20
+ const ref = origin["gl:ref"];
21
+ if (ref?.["gl:source"]) refs.add(ref["gl:source"]);
22
+ }
23
+ }
24
+ }
25
+ return [...refs].map((source) => ({ source, registerId }));
26
+ }
6
27
 
28
+ // scripts/build-edges.ts
29
+ import { readFileSync, writeFileSync, readdirSync, existsSync, realpathSync } from "fs";
30
+ import { join, dirname } from "path";
31
+ import { fileURLToPath } from "url";
32
+ var VALID_COMPLETENESS = /* @__PURE__ */ new Set(["complete", "partial"]);
7
33
  function validateCompleteness(value) {
8
- if (value == null) return 'complete';
34
+ if (value == null) return "complete";
9
35
  if (!VALID_COMPLETENESS.has(value)) {
10
36
  throw new Error(
11
- `Invalid partitive relation completeness: "${value}". Allowed: complete, partial`,
37
+ `Invalid partitive relation completeness: "${value}". Allowed: complete, partial`
12
38
  );
13
39
  }
14
40
  return value;
15
41
  }
16
-
17
- /**
18
- * Resolve a partitive member's multiplicity from JSON-LD.
19
- *
20
- * Reads gl:multiplicity first (v3 wire shape, ISO 704:2022). Falls back
21
- * to migrating from gl:certainty (v2 wire shape) for one release cycle
22
- * until upstream emits multiplicity natively.
23
- *
24
- * confirmed → compulsory
25
- * possible → optional
26
- */
27
- const VALID_PRESENCE = new Set(['required', 'optional']);
28
- const VALID_COUNT = new Set(['exactly_one', 'at_least_one', 'multiple']);
29
- const LEGACY_MULTIPLICITY_MAP = {
30
- compulsory: { presence: 'required', count: 'exactly_one' },
31
- optional: { presence: 'optional', count: 'exactly_one' },
32
- compulsory_multiple: { presence: 'required', count: 'multiple' },
33
- optional_multiple: { presence: 'optional', count: 'multiple' },
34
- compulsory_at_least_one: { presence: 'required', count: 'at_least_one' },
42
+ var VALID_PRESENCE = /* @__PURE__ */ new Set(["required", "optional"]);
43
+ var VALID_COUNT = /* @__PURE__ */ new Set(["exactly_one", "at_least_one", "multiple"]);
44
+ var LEGACY_MULTIPLICITY_MAP = {
45
+ compulsory: { presence: "required", count: "exactly_one" },
46
+ optional: { presence: "optional", count: "exactly_one" },
47
+ compulsory_multiple: { presence: "required", count: "multiple" },
48
+ optional_multiple: { presence: "optional", count: "multiple" },
49
+ compulsory_at_least_one: { presence: "required", count: "at_least_one" }
35
50
  };
36
-
37
51
  function resolvePresence(member) {
38
- const p = member['gl:presence'] ?? member.presence;
52
+ const p = member["gl:presence"] ?? member.presence;
39
53
  if (p != null) {
40
54
  if (!VALID_PRESENCE.has(p)) throw new Error(`Invalid presence: "${p}"`);
41
55
  return p;
42
56
  }
43
57
  return migrateMultiplicity(member).presence;
44
58
  }
45
-
46
59
  function resolveCount(member) {
47
- const c = member['gl:count'] ?? member.count;
60
+ const c = member["gl:count"] ?? member.count;
48
61
  if (c != null) {
49
62
  if (!VALID_COUNT.has(c)) throw new Error(`Invalid count: "${c}"`);
50
63
  return c;
51
64
  }
52
65
  return migrateMultiplicity(member).count;
53
66
  }
54
-
55
67
  function migrateMultiplicity(member) {
56
- const m = member['gl:multiplicity'] ?? member.multiplicity;
68
+ const m = member["gl:multiplicity"] ?? member.multiplicity;
57
69
  if (m && LEGACY_MULTIPLICITY_MAP[m]) return LEGACY_MULTIPLICITY_MAP[m];
58
- const certainty = member['gl:certainty'] ?? member.certainty;
59
- if (certainty === 'possible') return { presence: 'optional', count: 'exactly_one' };
60
- return { presence: 'required', count: 'exactly_one' };
70
+ const certainty = member["gl:certainty"] ?? member.certainty;
71
+ if (certainty === "possible") return { presence: "optional", count: "exactly_one" };
72
+ return { presence: "required", count: "exactly_one" };
61
73
  }
62
-
63
74
  function resolveIsDelimiting(member) {
64
- return member['gl:isDelimiting'] === true || member.isDelimiting === true;
75
+ return member["gl:isDelimiting"] === true || member.isDelimiting === true;
65
76
  }
66
-
67
77
  function normalizeLocalizedString(content) {
68
- if (content == null) return undefined;
69
- if (typeof content === 'string') return { default: content };
78
+ if (content == null) return void 0;
79
+ if (typeof content === "string") return { default: content };
70
80
  return content;
71
81
  }
72
-
73
-
74
- /**
75
- * Pre-computes cross-reference and domain edges for each dataset.
76
- * Reads all concept JSON files, extracts structured references and
77
- * authoritative sources (domains), and writes edges.json + domain-nodes.json.
78
- *
79
- * Usage: node scripts/build-edges.js
80
- */
81
- import { extractSourceRefs } from './extract-source-refs.js';
82
- import { readFileSync, writeFileSync, readdirSync, existsSync, realpathSync } from 'fs';
83
- import { join, dirname, resolve } from 'path';
84
- import { fileURLToPath } from 'url';
85
- import yaml from 'js-yaml';
86
- import { Register } from 'glossarist';
87
-
88
- const __dirname = dirname(fileURLToPath(import.meta.url));
89
- const ROOT = process.cwd();
90
- const DATA_DIR = join(ROOT, 'public', 'data');
91
-
92
- // --- Normalization ---
93
-
82
+ var __dirname = dirname(fileURLToPath(import.meta.url));
83
+ var ROOT = process.cwd();
84
+ var DATA_DIR = join(ROOT, "public", "data");
94
85
  function slugify(text) {
95
- return text.toLowerCase().replace(/[^\w\s-]/g, '').replace(/[\s/]+/g, '-');
86
+ return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s/]+/g, "-");
96
87
  }
97
-
98
- // --- Extractors (open/closed: add new extractors to EXTRACTORS array) ---
99
-
100
88
  function extractReferences(concept, registerId) {
101
89
  const edges = [];
102
- const sourceUri = concept['@id'];
103
- for (const [lang, lc] of Object.entries(concept['gl:localizedConcept'] || {})) {
104
- if (lc['gl:references']) {
105
- for (const ref of lc['gl:references']) {
106
- if (ref['@id'] && ref['@id'] !== sourceUri) {
90
+ const sourceUri = concept["@id"];
91
+ for (const [lang, lc] of Object.entries(concept["gl:localizedConcept"] || {})) {
92
+ if (lc["gl:references"]) {
93
+ for (const ref of lc["gl:references"]) {
94
+ if (ref["@id"] && ref["@id"] !== sourceUri) {
107
95
  const edge = {
108
96
  source: sourceUri,
109
- target: ref['@id'],
110
- type: ref['@id'].startsWith('cite:') ? 'citation' : 'references',
111
- label: ref['gl:term'] || undefined,
97
+ target: ref["@id"],
98
+ type: ref["@id"].startsWith("cite:") ? "citation" : "references",
99
+ label: ref["gl:term"] || void 0,
112
100
  register: registerId,
113
- lang,
101
+ lang
114
102
  };
115
- if (ref['gl:sourceId']) edge.sourceId = ref['gl:sourceId'];
103
+ if (ref["gl:sourceId"]) edge.sourceId = ref["gl:sourceId"];
116
104
  edges.push(edge);
117
105
  }
118
106
  }
@@ -120,23 +108,20 @@ function extractReferences(concept, registerId) {
120
108
  }
121
109
  return edges;
122
110
  }
123
-
124
111
  function extractDomains(concept, registerId, uriBase) {
125
- if (!uriBase) throw new Error('build-edges: uriBase is required — set uriBase in site-config.yml');
112
+ if (!uriBase) throw new Error("build-edges: uriBase is required \u2014 set uriBase in site-config.yml");
126
113
  const base = uriBase;
127
114
  const edges = [];
128
- const sourceUri = concept['@id'];
129
- const seen = new Set();
130
-
131
- // v3 managed concept-level section/domain references
132
- const domains = concept['gl:domain'];
115
+ const sourceUri = concept["@id"];
116
+ const seen = /* @__PURE__ */ new Set();
117
+ const domains = concept["gl:domain"];
133
118
  if (Array.isArray(domains)) {
134
119
  for (const d of domains) {
135
- const conceptId = d['gl:conceptId'] || d.concept_id;
120
+ const conceptId = d["gl:conceptId"] || d.concept_id;
136
121
  if (conceptId) {
137
- const refType = d['gl:refType'] || d.ref_type || 'domain';
138
- const isSection = refType === 'section';
139
- const edgeType = isSection ? 'section' : 'domain';
122
+ const refType = d["gl:refType"] || d.ref_type || "domain";
123
+ const isSection = refType === "section";
124
+ const edgeType = isSection ? "section" : "domain";
140
125
  const domainUri = `${base}/${registerId}/domain/${conceptId}`;
141
126
  if (!seen.has(domainUri)) {
142
127
  seen.add(domainUri);
@@ -145,40 +130,37 @@ function extractDomains(concept, registerId, uriBase) {
145
130
  target: domainUri,
146
131
  type: edgeType,
147
132
  label: conceptId,
148
- register: registerId,
133
+ register: registerId
149
134
  });
150
135
  }
151
136
  }
152
137
  }
153
138
  }
154
-
155
- // Legacy: localized domain strings
156
- const lcs = concept['gl:localizedConcept'] || {};
139
+ const lcs = concept["gl:localizedConcept"] || {};
157
140
  const langs = Object.keys(lcs);
158
141
  for (const lang of langs) {
159
- const domain = lcs[lang]['gl:domain'];
142
+ const domain = lcs[lang]["gl:domain"];
160
143
  if (domain && !seen.has(domain)) {
161
144
  seen.add(domain);
162
145
  edges.push({
163
146
  source: sourceUri,
164
147
  target: `${base}/${registerId}/domain/${slugify(domain)}`,
165
- type: 'domain',
148
+ type: "domain",
166
149
  label: domain,
167
- register: registerId,
150
+ register: registerId
168
151
  });
169
152
  }
170
153
  }
171
154
  return edges;
172
155
  }
173
-
174
156
  function extractRelated(concept, registerId, uriBase, urnMap) {
175
157
  const edges = [];
176
- const sourceUri = concept['@id'];
177
- for (const r of concept['gl:related'] || []) {
178
- const ref = r['gl:ref'];
158
+ const sourceUri = concept["@id"];
159
+ for (const r of concept["gl:related"] || []) {
160
+ const ref = r["gl:ref"];
179
161
  if (!ref) continue;
180
- const source = ref['gl:source'] || ref['source'];
181
- const id = ref['gl:id'] || ref['id'];
162
+ const source = ref["gl:source"] || ref["source"];
163
+ const id = ref["gl:id"] || ref["id"];
182
164
  if (!source || !id) continue;
183
165
  const reg = urnMap.get(source) || source;
184
166
  const target = buildConceptUri(uriBase, reg, id);
@@ -186,90 +168,59 @@ function extractRelated(concept, registerId, uriBase, urnMap) {
186
168
  edges.push({
187
169
  source: sourceUri,
188
170
  target,
189
- type: r['gl:relationshipType'] || 'references',
190
- label: r['gl:term'] || undefined,
191
- register: reg,
171
+ type: r["gl:relationshipType"] || "references",
172
+ label: r["gl:term"] || void 0,
173
+ register: reg
192
174
  });
193
175
  }
194
176
  return edges;
195
177
  }
196
-
197
- /**
198
- * Extracts partitive relations from a concept's gl:partitiveRelations
199
- * array. Returns relation objects (NOT binary GraphEdges) so the UI
200
- * can render them with their full set-based semantics: comprehensive +
201
- * partitives (each with certainty), completeness, plurality, and
202
- * criterion.
203
- *
204
- * Relations are written to partitive_relations.json, keeping
205
- * edges.json binary-only.
206
- *
207
- * v2 shape per concept-model/TODO.partitive-relation-v2.
208
- */
209
178
  function extractPartitiveRelations(concept, registerId, uriBase, urnMap) {
210
179
  const relations = [];
211
- const sourceUri = concept['@id'];
212
- for (const rel of concept['gl:partitiveRelations'] || []) {
213
- const compRef = rel['gl:comprehensive'];
180
+ const sourceUri = concept["@id"];
181
+ for (const rel of concept["gl:partitiveRelations"] || []) {
182
+ const compRef = rel["gl:comprehensive"];
214
183
  if (!compRef) continue;
215
184
  const comprehensive = resolveConceptUri(compRef, uriBase, urnMap);
216
185
  if (!comprehensive) continue;
217
-
218
- const partitives = (rel['gl:hasPartitive'] || [])
219
- .map(p => {
220
- const memberRef = p['gl:ref'] || p;
221
- const uri = resolveConceptUri(memberRef, uriBase, urnMap);
222
- if (!uri || uri === sourceUri) return null;
223
- return {
224
- uri,
225
- presence: resolvePresence(p),
226
- count: resolveCount(p),
227
- isDelimiting: resolveIsDelimiting(p),
228
- };
229
- })
230
- .filter(p => p !== null);
231
-
186
+ const partitives = (rel["gl:hasPartitive"] || []).map((p) => {
187
+ const memberRef = p["gl:ref"] || p;
188
+ const uri = resolveConceptUri(memberRef, uriBase, urnMap);
189
+ if (!uri || uri === sourceUri) return null;
190
+ return {
191
+ uri,
192
+ presence: resolvePresence(p),
193
+ count: resolveCount(p),
194
+ isDelimiting: resolveIsDelimiting(p)
195
+ };
196
+ }).filter((p) => p !== null);
232
197
  if (partitives.length === 0) continue;
233
-
234
198
  relations.push({
235
199
  source: sourceUri,
236
200
  comprehensive,
237
201
  partitives,
238
- completeness: validateCompleteness(rel['gl:completeness']),
239
- criterion: normalizeLocalizedString(rel['gl:criterion']),
240
- register: registerId,
202
+ completeness: validateCompleteness(rel["gl:completeness"]),
203
+ criterion: normalizeLocalizedString(rel["gl:criterion"]),
204
+ register: registerId
241
205
  });
242
206
  }
243
207
  return relations;
244
208
  }
245
-
246
209
  function resolveConceptUri(ref, uriBase, urnMap) {
247
- const source = ref['gl:source'] || ref['source'];
248
- const id = ref['gl:id'] || ref['id'];
210
+ const source = ref["gl:source"] || ref["source"];
211
+ const id = ref["gl:id"] || ref["id"];
249
212
  if (!source || !id) return null;
250
213
  const reg = urnMap.get(source) || source;
251
214
  return buildConceptUri(uriBase, reg, id);
252
215
  }
253
-
254
- const EXTRACTORS = [extractReferences, extractRelated, extractDomains];
255
-
216
+ var EXTRACTORS = [extractReferences, extractRelated, extractDomains];
256
217
  function extractAllEdges(concept, registerId, uriBase, urnMap) {
257
- return EXTRACTORS.flatMap(fn => fn(concept, registerId, uriBase, urnMap));
218
+ return EXTRACTORS.flatMap((fn) => fn(concept, registerId, uriBase, urnMap));
258
219
  }
259
-
260
- /**
261
- * Per concept-model TODO.partitive-relation-v2 item 14:
262
- * a PartitiveRelation A → {b, c, d} subsumes binary has_part edges
263
- * for the same pairs. Warn (don't fail) when both encodings exist.
264
- *
265
- * Binary edge types considered partitive: has_part, is_part_of,
266
- * broader_partitive, narrower_partitive.
267
- */
268
- const BINARY_PARTITIVE_TYPES = new Set(['has_part', 'is_part_of', 'broader_partitive', 'narrower_partitive']);
269
-
220
+ var BINARY_PARTITIVE_TYPES = /* @__PURE__ */ new Set(["has_part", "is_part_of", "broader_partitive", "narrower_partitive"]);
270
221
  function warnBinaryPartitiveRedundancy(edges, relations, registerId) {
271
222
  if (!relations?.length) return;
272
- const partitivePairs = new Set();
223
+ const partitivePairs = /* @__PURE__ */ new Set();
273
224
  for (const rel of relations) {
274
225
  for (const member of rel.partitives ?? []) {
275
226
  const memberUri = member.uri ?? member;
@@ -277,47 +228,39 @@ function warnBinaryPartitiveRedundancy(edges, relations, registerId) {
277
228
  partitivePairs.add(`${memberUri}|${rel.comprehensive}`);
278
229
  }
279
230
  }
280
- const redundant = edges.filter(e =>
281
- BINARY_PARTITIVE_TYPES.has(e.type)
282
- && partitivePairs.has(`${e.source}|${e.target}`),
231
+ const redundant = edges.filter(
232
+ (e) => BINARY_PARTITIVE_TYPES.has(e.type) && partitivePairs.has(`${e.source}|${e.target}`)
283
233
  );
284
234
  if (redundant.length > 0) {
285
235
  console.warn(
286
- ` ⚠ ${registerId}: ${redundant.length} binary has_part/is_part_of edge(s) duplicate a PartitiveRelation member — prefer the PartitiveRelation encoding (concept-model TODO item 14).`,
236
+ ` \u26A0 ${registerId}: ${redundant.length} binary has_part/is_part_of edge(s) duplicate a PartitiveRelation member \u2014 prefer the PartitiveRelation encoding (concept-model TODO item 14).`
287
237
  );
288
238
  }
289
239
  }
290
-
291
- // --- Build ---
292
-
293
240
  function buildEdgesForDataset(datasetDir, registerId, uriBase, urnMap, manifest) {
294
- const conceptsDir = join(datasetDir, 'concepts');
241
+ const conceptsDir = join(datasetDir, "concepts");
295
242
  if (!existsSync(conceptsDir)) {
296
243
  console.log(` Skipping ${registerId}: no concepts directory`);
297
244
  return { edges: [], sourceRefs: [] };
298
245
  }
299
-
300
- const files = readdirSync(conceptsDir).filter(f => f.endsWith('.json'));
246
+ const files = readdirSync(conceptsDir).filter((f) => f.endsWith(".json"));
301
247
  console.log(` Processing ${files.length} concepts...`);
302
-
303
248
  const allEdges = [];
304
249
  const allPartitiveRelations = [];
305
250
  const allSourceRefs = [];
306
- const domainConceptCount = new Map();
251
+ const domainConceptCount = /* @__PURE__ */ new Map();
307
252
  let processed = 0;
308
-
309
253
  for (const file of files) {
310
254
  try {
311
- const data = JSON.parse(readFileSync(join(conceptsDir, file), 'utf-8'));
255
+ const data = JSON.parse(readFileSync(join(conceptsDir, file), "utf-8"));
312
256
  const edges = extractAllEdges(data, registerId, uriBase, urnMap);
313
257
  const relations = extractPartitiveRelations(data, registerId, uriBase, urnMap);
314
258
  warnBinaryPartitiveRedundancy(edges, relations, registerId);
315
259
  allEdges.push(...edges);
316
260
  allPartitiveRelations.push(...relations);
317
261
  allSourceRefs.push(...extractSourceRefs(data, registerId));
318
-
319
262
  for (const edge of edges) {
320
- if (edge.type === 'domain' || edge.type === 'section') {
263
+ if (edge.type === "domain" || edge.type === "section") {
321
264
  domainConceptCount.set(edge.target, (domainConceptCount.get(edge.target) || 0) + 1);
322
265
  }
323
266
  }
@@ -325,52 +268,40 @@ function buildEdgesForDataset(datasetDir, registerId, uriBase, urnMap, manifest)
325
268
  console.error(` Error processing ${file}: ${e.message}`);
326
269
  }
327
270
  processed++;
328
- if (processed % 5000 === 0) {
271
+ if (processed % 5e3 === 0) {
329
272
  console.log(` ... ${processed}/${files.length}`);
330
273
  }
331
274
  }
332
-
333
- // Deduplicate edges by source+target+type+lang
334
- const seen = new Set();
275
+ const seen = /* @__PURE__ */ new Set();
335
276
  const deduped = [];
336
277
  for (const edge of allEdges) {
337
- const key = `${edge.source}→${edge.target}→${edge.type}→${edge.lang || ''}`;
278
+ const key = `${edge.source}\u2192${edge.target}\u2192${edge.type}\u2192${edge.lang || ""}`;
338
279
  if (!seen.has(key)) {
339
280
  seen.add(key);
340
281
  deduped.push(edge);
341
282
  }
342
283
  }
343
-
344
284
  const output = {
345
285
  registerId,
346
286
  edgeCount: deduped.length,
347
- edges: deduped,
287
+ edges: deduped
348
288
  };
349
-
350
- const outputPath = join(datasetDir, 'edges.json');
289
+ const outputPath = join(datasetDir, "edges.json");
351
290
  writeFileSync(outputPath, JSON.stringify(output));
352
291
  console.log(` Written ${deduped.length} edges to edges.json (${(JSON.stringify(output).length / 1024).toFixed(1)} KB)`);
353
-
354
- // Write partitive_relations.json (parallel to edges.json) for one-to-many
355
- // partitive decompositions. See TODO.partitive-relation-v2.
356
292
  const relationsOutput = {
357
293
  registerId,
358
294
  relationCount: allPartitiveRelations.length,
359
- relations: allPartitiveRelations,
295
+ relations: allPartitiveRelations
360
296
  };
361
- const relationsPath = join(datasetDir, 'partitive_relations.json');
297
+ const relationsPath = join(datasetDir, "partitive_relations.json");
362
298
  writeFileSync(relationsPath, JSON.stringify(relationsOutput));
363
299
  console.log(` Written ${allPartitiveRelations.length} partitive relations to partitive_relations.json (${(JSON.stringify(relationsOutput).length / 1024).toFixed(1)} KB)`);
364
-
365
- // Build domain-nodes.json from manifest sections (authoritative source)
366
300
  const manifestSections = manifest.sections;
367
301
  if (manifestSections && manifestSections.length > 0) {
368
- const uriBase = manifest.uriBase;
369
- if (!uriBase) throw new Error('build-edges: manifest.uriBase is required');
370
-
371
- function buildSectionNode(section, idx) {
302
+ let buildSectionNode2 = function(section, idx) {
372
303
  const sectionId = `section-${section.id}`;
373
- const domainUri = `${uriBase}/${registerId}/domain/${sectionId}`;
304
+ const domainUri = `${uriBase2}/${registerId}/domain/${sectionId}`;
374
305
  const domainLabel = section.names?.eng || section.id;
375
306
  const node = {
376
307
  uri: domainUri,
@@ -379,196 +310,171 @@ function buildEdgesForDataset(datasetDir, registerId, uriBase, urnMap, manifest)
379
310
  label: domainLabel,
380
311
  registerId,
381
312
  conceptCount: domainConceptCount.get(domainUri) || 0,
382
- order: idx,
313
+ order: idx
383
314
  };
384
315
  if (section.children && section.children.length > 0) {
385
- node.children = section.children.map((child, childIdx) =>
386
- buildSectionNode(child, childIdx)
316
+ node.children = section.children.map(
317
+ (child, childIdx) => buildSectionNode2(child, childIdx)
387
318
  );
388
319
  }
389
320
  return node;
390
- }
391
-
392
- const domainNodes = manifestSections.map((section, idx) =>
393
- buildSectionNode(section, idx)
321
+ };
322
+ var buildSectionNode = buildSectionNode2;
323
+ const uriBase2 = manifest.uriBase;
324
+ if (!uriBase2) throw new Error("build-edges: manifest.uriBase is required");
325
+ const domainNodes = manifestSections.map(
326
+ (section, idx) => buildSectionNode2(section, idx)
394
327
  );
395
-
396
328
  const domainOutput = { registerId, domainNodes };
397
- const domainPath = join(datasetDir, 'domain-nodes.json');
329
+ const domainPath = join(datasetDir, "domain-nodes.json");
398
330
  writeFileSync(domainPath, JSON.stringify(domainOutput));
399
331
  console.log(` Written ${domainNodes.length} section-based domain nodes to domain-nodes.json`);
400
332
  } else {
401
- // Fallback: derive domain nodes from concept edges (legacy behavior)
402
- const domainEdgeMap = new Map();
333
+ const domainEdgeMap = /* @__PURE__ */ new Map();
403
334
  for (const edge of deduped) {
404
- if (edge.type === 'domain') {
335
+ if (edge.type === "domain") {
405
336
  const existing = domainEdgeMap.get(edge.target);
406
337
  if (existing) {
407
338
  existing.labels.add(edge.label);
408
339
  } else {
409
- domainEdgeMap.set(edge.target, { uri: edge.target, labels: new Set([edge.label]), registerId });
340
+ domainEdgeMap.set(edge.target, { uri: edge.target, labels: /* @__PURE__ */ new Set([edge.label]), registerId });
410
341
  }
411
342
  }
412
343
  }
413
-
414
- const domainNodes = [...domainEdgeMap.values()].map(d => ({
344
+ const domainNodes = [...domainEdgeMap.values()].map((d) => ({
415
345
  uri: d.uri,
416
346
  label: [...d.labels][0],
417
347
  registerId: d.registerId,
418
- conceptCount: domainConceptCount.get(d.uri) || 0,
348
+ conceptCount: domainConceptCount.get(d.uri) || 0
419
349
  })).sort((a, b) => b.conceptCount - a.conceptCount);
420
-
421
350
  const domainOutput = { registerId, domainNodes };
422
- const domainPath = join(datasetDir, 'domain-nodes.json');
351
+ const domainPath = join(datasetDir, "domain-nodes.json");
423
352
  writeFileSync(domainPath, JSON.stringify(domainOutput));
424
353
  console.log(` Written ${domainNodes.length} edge-derived domain nodes to domain-nodes.json`);
425
354
  }
426
-
427
355
  return { edges: deduped, sourceRefs: allSourceRefs };
428
356
  }
429
-
430
- // Main
431
- export function main() {
432
- console.log('Building edge indexes...\n');
433
-
434
- if (!existsSync(DATA_DIR)) {
435
- console.log('No data directory found. Nothing to do.');
436
- process.exit(0);
437
- }
438
-
439
- const datasets = readdirSync(DATA_DIR).filter(f => {
440
- try {
441
- return existsSync(join(DATA_DIR, f, 'manifest.json'));
442
- } catch {
443
- return false;
357
+ function main() {
358
+ console.log("Building edge indexes...\n");
359
+ if (!existsSync(DATA_DIR)) {
360
+ console.log("No data directory found. Nothing to do.");
361
+ process.exit(0);
444
362
  }
445
- });
446
-
447
- // Build URI→datasetId prefix map from all manifests
448
- const urnMap = new Map();
449
- const manifestCache = new Map();
450
- for (const ds of datasets) {
451
- const manifestPath = join(DATA_DIR, ds, 'manifest.json');
452
- try {
453
- const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
454
- manifestCache.set(ds, manifest);
363
+ const datasets = readdirSync(DATA_DIR).filter((f) => {
364
+ try {
365
+ return existsSync(join(DATA_DIR, f, "manifest.json"));
366
+ } catch {
367
+ return false;
368
+ }
369
+ });
370
+ const urnMap = /* @__PURE__ */ new Map();
371
+ const manifestCache = /* @__PURE__ */ new Map();
372
+ for (const ds of datasets) {
373
+ const manifestPath = join(DATA_DIR, ds, "manifest.json");
374
+ try {
375
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
376
+ manifestCache.set(ds, manifest);
377
+ if (manifest.datasetUri) {
378
+ const base = manifest.datasetUri.endsWith("*") ? manifest.datasetUri.slice(0, -1) : manifest.datasetUri;
379
+ if (base) urnMap.set(base, ds);
380
+ }
381
+ for (const alias of manifest.uriAliases ?? []) {
382
+ const base = alias.endsWith("*") ? alias.slice(0, -1) : alias;
383
+ if (base) urnMap.set(base, ds);
384
+ }
385
+ } catch {
386
+ }
387
+ }
388
+ console.log(`URI resolution map: ${[...urnMap.entries()].map(([k, v]) => `${k}\u2192${v}`).join(", ")}
389
+ `);
390
+ const allDatasetEdges = /* @__PURE__ */ new Map();
391
+ const allSourceRefs = [];
392
+ for (const ds of datasets) {
393
+ const manifest = manifestCache.get(ds);
394
+ if (!manifest) continue;
395
+ try {
396
+ console.log(`${manifest.title} (${ds}):`);
397
+ const uriBase = manifest.uriBase;
398
+ if (!uriBase) throw new Error("build-edges: manifest.uriBase is required");
399
+ const result = buildEdgesForDataset(join(DATA_DIR, ds), ds, uriBase, urnMap, manifest);
400
+ allDatasetEdges.set(ds, result.edges);
401
+ allSourceRefs.push(...result.sourceRefs);
402
+ } catch (e) {
403
+ console.error(`Error reading manifest for ${ds}: ${e.message}`);
404
+ }
405
+ console.log();
406
+ }
407
+ const knownSourceStrings = /* @__PURE__ */ new Set();
408
+ for (const [ds, manifest] of manifestCache) {
409
+ if (manifest.ref) knownSourceStrings.add(manifest.ref);
410
+ for (const alias of manifest.refAliases ?? []) {
411
+ knownSourceStrings.add(alias);
412
+ }
455
413
  if (manifest.datasetUri) {
456
- const base = manifest.datasetUri.endsWith('*') ? manifest.datasetUri.slice(0, -1) : manifest.datasetUri;
457
- if (base) urnMap.set(base, ds);
414
+ const base = manifest.datasetUri.endsWith("*") ? manifest.datasetUri.slice(0, -1) : manifest.datasetUri;
415
+ knownSourceStrings.add(base);
458
416
  }
459
417
  for (const alias of manifest.uriAliases ?? []) {
460
- const base = alias.endsWith('*') ? alias.slice(0, -1) : alias;
461
- if (base) urnMap.set(base, ds);
418
+ const base = alias.endsWith("*") ? alias.slice(0, -1) : alias;
419
+ knownSourceStrings.add(base);
462
420
  }
463
- } catch {}
464
- }
465
- console.log(`URI resolution map: ${[...urnMap.entries()].map(([k,v]) => `${k}→${v}`).join(', ')}\n`);
466
-
467
- const allDatasetEdges = new Map();
468
- const allSourceRefs = [];
469
-
470
- for (const ds of datasets) {
471
- const manifest = manifestCache.get(ds);
472
- if (!manifest) continue;
473
- try {
474
- console.log(`${manifest.title} (${ds}):`);
475
- const uriBase = manifest.uriBase;
476
- if (!uriBase) throw new Error('build-edges: manifest.uriBase is required');
477
- const result = buildEdgesForDataset(join(DATA_DIR, ds), ds, uriBase, urnMap, manifest);
478
- allDatasetEdges.set(ds, result.edges);
479
- allSourceRefs.push(...result.sourceRefs);
480
- } catch (e) {
481
- console.error(`Error reading manifest for ${ds}: ${e.message}`);
482
421
  }
483
- console.log();
484
- }
485
-
486
- // Audit — report concept source strings not covered by manifest refs.
487
- // Bibliography mapping (ref → URN) is declared in register.yaml and flows
488
- // through manifest.json to datasets.json at generate time. No separate
489
- // source-refs file needed — the registry IS the single source of truth.
490
-
491
- // Build a lookup of all known source strings from manifests
492
- const knownSourceStrings = new Set();
493
- for (const [ds, manifest] of manifestCache) {
494
- if (manifest.ref) knownSourceStrings.add(manifest.ref);
495
- for (const alias of manifest.refAliases ?? []) {
496
- knownSourceStrings.add(alias);
422
+ const auditUnmatched = /* @__PURE__ */ new Map();
423
+ for (const { source, registerId } of allSourceRefs) {
424
+ if (knownSourceStrings.has(source)) continue;
425
+ if (source.startsWith("urn:")) continue;
426
+ if (!auditUnmatched.has(source)) {
427
+ auditUnmatched.set(source, registerId);
428
+ }
497
429
  }
498
- if (manifest.datasetUri) {
499
- const base = manifest.datasetUri.endsWith('*') ? manifest.datasetUri.slice(0, -1) : manifest.datasetUri;
500
- knownSourceStrings.add(base);
430
+ if (auditUnmatched.size > 0) {
431
+ console.warn(`
432
+ \u26A0 ${auditUnmatched.size} source string(s) in concept data have no matching dataset ref:`);
433
+ for (const [source, fromDataset] of auditUnmatched) {
434
+ console.warn(` "${source}" (from ${fromDataset})`);
435
+ }
436
+ console.warn("Add refAliases to the target dataset manifest, or fix source strings to use URNs.\n");
501
437
  }
502
- for (const alias of manifest.uriAliases ?? []) {
503
- const base = alias.endsWith('*') ? alias.slice(0, -1) : alias;
504
- knownSourceStrings.add(base);
438
+ const datasetUriPrefixes = /* @__PURE__ */ new Map();
439
+ for (const [ds, manifest] of manifestCache) {
440
+ const uriBase = manifest.uriBase;
441
+ if (!uriBase) throw new Error("build-edges: manifest.uriBase is required");
442
+ datasetUriPrefixes.set(ds, `${uriBase}/${ds}/`);
505
443
  }
506
- }
507
-
508
- const auditUnmatched = new Map();
509
- for (const { source, registerId } of allSourceRefs) {
510
- if (knownSourceStrings.has(source)) continue;
511
- // URN source strings resolve via URI routing — not a bibliography concern
512
- if (source.startsWith('urn:')) continue;
513
- if (!auditUnmatched.has(source)) {
514
- auditUnmatched.set(source, registerId);
444
+ const crossRefIndex = {};
445
+ for (const ds of datasets) {
446
+ crossRefIndex[ds] = [];
515
447
  }
516
- }
517
-
518
- if (auditUnmatched.size > 0) {
519
- console.warn(`\n⚠ ${auditUnmatched.size} source string(s) in concept data have no matching dataset ref:`);
520
- for (const [source, fromDataset] of auditUnmatched) {
521
- console.warn(` "${source}" (from ${fromDataset})`);
522
- }
523
- console.warn('Add refAliases to the target dataset manifest, or fix source strings to use URNs.\n');
524
- }
525
-
526
- // Build cross-reference index: for each dataset, which other datasets'
527
- // edges.json contains edges targeting that dataset's URIs.
528
- const datasetUriPrefixes = new Map();
529
- for (const [ds, manifest] of manifestCache) {
530
- const uriBase = manifest.uriBase;
531
- if (!uriBase) throw new Error('build-edges: manifest.uriBase is required');
532
- datasetUriPrefixes.set(ds, `${uriBase}/${ds}/`);
533
- }
534
-
535
- const crossRefIndex = {};
536
- for (const ds of datasets) {
537
- crossRefIndex[ds] = [];
538
- }
539
-
540
- for (const [sourceDs, edges] of allDatasetEdges) {
541
- const targets = new Set();
542
- for (const edge of edges) {
543
- for (const [targetDs, prefix] of datasetUriPrefixes) {
544
- if (targetDs !== sourceDs && edge.target.startsWith(prefix)) {
545
- targets.add(targetDs);
448
+ for (const [sourceDs, edges] of allDatasetEdges) {
449
+ const targets = /* @__PURE__ */ new Set();
450
+ for (const edge of edges) {
451
+ for (const [targetDs, prefix] of datasetUriPrefixes) {
452
+ if (targetDs !== sourceDs && edge.target.startsWith(prefix)) {
453
+ targets.add(targetDs);
454
+ }
546
455
  }
547
- }
548
- // Also check source URIs targeting other datasets
549
- for (const [targetDs, prefix] of datasetUriPrefixes) {
550
- if (targetDs !== sourceDs && edge.source.startsWith(prefix)) {
551
- targets.add(targetDs);
456
+ for (const [targetDs, prefix] of datasetUriPrefixes) {
457
+ if (targetDs !== sourceDs && edge.source.startsWith(prefix)) {
458
+ targets.add(targetDs);
459
+ }
552
460
  }
553
461
  }
554
- }
555
- for (const targetDs of targets) {
556
- if (!crossRefIndex[targetDs].includes(sourceDs)) {
557
- crossRefIndex[targetDs].push(sourceDs);
462
+ for (const targetDs of targets) {
463
+ if (!crossRefIndex[targetDs].includes(sourceDs)) {
464
+ crossRefIndex[targetDs].push(sourceDs);
465
+ }
558
466
  }
559
467
  }
468
+ const crossRefPath = join(DATA_DIR, "cross-ref-index.json");
469
+ writeFileSync(crossRefPath, JSON.stringify(crossRefIndex));
470
+ const refCount = Object.values(crossRefIndex).reduce((sum, arr) => sum + arr.length, 0);
471
+ console.log(`Written cross-ref-index.json (${refCount} cross-references across ${datasets.length} datasets)`);
472
+ console.log("Done.");
560
473
  }
561
-
562
- const crossRefPath = join(DATA_DIR, 'cross-ref-index.json');
563
- writeFileSync(crossRefPath, JSON.stringify(crossRefIndex));
564
- const refCount = Object.values(crossRefIndex).reduce((sum, arr) => sum + arr.length, 0);
565
- console.log(`Written cross-ref-index.json (${refCount} cross-references across ${datasets.length} datasets)`);
566
-
567
- console.log('Done.');
568
- }
569
-
570
- const isDirectInvocation = process.argv[1]
571
- && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
474
+ var isDirectInvocation = process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
572
475
  if (isDirectInvocation) {
573
476
  main();
574
477
  }
478
+ export {
479
+ main
480
+ };