@glossarist/concept-browser 0.7.50 → 0.7.52

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 (170) hide show
  1. package/cli/index.mjs +32 -0
  2. package/env.d.ts +15 -0
  3. package/package.json +12 -2
  4. package/scripts/__tests__/doctor.test.mjs +147 -0
  5. package/scripts/doctor.mjs +327 -0
  6. package/scripts/generate-data.mjs +136 -0
  7. package/scripts/generate-ontology-data.mjs +3 -3
  8. package/scripts/generate-ontology-schema.mjs +3 -3
  9. package/scripts/lib/agents-turtle.mjs +64 -0
  10. package/scripts/lib/bibliography-turtle.mjs +54 -0
  11. package/scripts/lib/build-activity-turtle.mjs +92 -0
  12. package/scripts/lib/build-cache.mjs +70 -0
  13. package/scripts/lib/dataset-turtle.mjs +79 -0
  14. package/scripts/lib/turtle-escape.mjs +0 -0
  15. package/scripts/lib/version-turtle.mjs +56 -0
  16. package/scripts/lib/vocab-turtle.mjs +64 -0
  17. package/scripts/normalize-yaml.mjs +99 -0
  18. package/scripts/sync-concept-model.mjs +86 -0
  19. package/scripts/validate-shacl.mjs +194 -0
  20. package/src/__fixtures__/concept-shape.ttl +20 -0
  21. package/src/__fixtures__/shacl/bad/concept.ttl +7 -0
  22. package/src/__fixtures__/shacl/empty/.gitkeep +0 -0
  23. package/src/__fixtures__/shacl/good/concept.ttl +8 -0
  24. package/src/__tests__/__fixtures__/concepts.ts +221 -0
  25. package/src/__tests__/adapters/concept-identity.test.ts +76 -0
  26. package/src/__tests__/components/error-boundary.test.ts +109 -0
  27. package/src/__tests__/composables/use-dataset-series.test.ts +262 -0
  28. package/src/__tests__/concept-rdf/agents-emitter.test.ts +110 -0
  29. package/src/__tests__/concept-rdf/bibliography-emitter.test.ts +159 -0
  30. package/src/__tests__/concept-rdf/build-activity-emitter.test.ts +119 -0
  31. package/src/__tests__/concept-rdf/coerce-date.test.ts +97 -0
  32. package/src/__tests__/concept-rdf/concept-emitter.test.ts +258 -0
  33. package/src/__tests__/concept-rdf/dataset-emitter.test.ts +224 -0
  34. package/src/__tests__/concept-rdf/differential.test.ts +96 -0
  35. package/src/__tests__/concept-rdf/group-emitter.test.ts +109 -0
  36. package/src/__tests__/concept-rdf/image-variant-emitter.test.ts +135 -0
  37. package/src/__tests__/concept-rdf/jsonld-writer.test.ts +109 -0
  38. package/src/__tests__/concept-rdf/nonverbal-rep.test.ts +177 -0
  39. package/src/__tests__/concept-rdf/property-based.test.ts +179 -0
  40. package/src/__tests__/concept-rdf/provenance.test.ts +110 -0
  41. package/src/__tests__/concept-rdf/quad-isomorphism.test.ts +43 -0
  42. package/src/__tests__/concept-rdf/quad-isomorphism.ts +47 -0
  43. package/src/__tests__/concept-rdf/rdf-components.test.ts +145 -0
  44. package/src/__tests__/concept-rdf/rdf-graph.test.ts +115 -0
  45. package/src/__tests__/concept-rdf/round-trip.test.ts +243 -0
  46. package/src/__tests__/concept-rdf/scoped-examples.test.ts +126 -0
  47. package/src/__tests__/concept-rdf/sections-builder.test.ts +94 -0
  48. package/src/__tests__/concept-rdf/shacl-conformance.test.ts +110 -0
  49. package/src/__tests__/concept-rdf/shape-consistency.test.ts +138 -0
  50. package/src/__tests__/concept-rdf/snapshot-generation.test.ts +75 -0
  51. package/src/__tests__/concept-rdf/table-formula-emitter.test.ts +142 -0
  52. package/src/__tests__/concept-rdf/turtle-writer.test.ts +114 -0
  53. package/src/__tests__/concept-rdf/use-rdf-document.test.ts +246 -0
  54. package/src/__tests__/concept-rdf/version-emitter.test.ts +120 -0
  55. package/src/__tests__/concept-rdf/vocabulary-ssot.test.ts +100 -0
  56. package/src/__tests__/concept-rdf-view.test.ts +136 -0
  57. package/src/__tests__/dataset-style.test.ts +12 -7
  58. package/src/__tests__/errors/errors.test.ts +142 -0
  59. package/src/__tests__/format-downloads.test.ts +47 -65
  60. package/src/__tests__/markdown-lite.test.ts +19 -0
  61. package/src/__tests__/perf/bundle-layout.test.ts +50 -0
  62. package/src/__tests__/perf/serialization-perf.test.ts +121 -0
  63. package/src/__tests__/scripts/agents-turtle.test.ts +61 -0
  64. package/src/__tests__/scripts/bibliography-turtle.test.ts +59 -0
  65. package/src/__tests__/scripts/build-activity-turtle.test.ts +75 -0
  66. package/src/__tests__/scripts/build-cache.test.ts +78 -0
  67. package/src/__tests__/scripts/build-integration.test.ts +134 -0
  68. package/src/__tests__/scripts/dataset-turtle.test.ts +94 -0
  69. package/src/__tests__/scripts/normalize-yaml.test.ts +98 -0
  70. package/src/__tests__/scripts/stryker-config.test.ts +33 -0
  71. package/src/__tests__/scripts/turtle-escape.test.ts +63 -0
  72. package/src/__tests__/scripts/version-turtle.test.ts +72 -0
  73. package/src/__tests__/scripts/vocab-turtle.test.ts +63 -0
  74. package/src/__tests__/use-format-registry.test.ts +125 -0
  75. package/src/__tests__/utils/bcp47.test.ts +166 -0
  76. package/src/__tests__/utils/color-theme.test.ts +143 -0
  77. package/src/__tests__/utils/url-safety.test.ts +65 -0
  78. package/src/__tests__/validate-shacl.test.ts +100 -0
  79. package/src/adapters/DatasetAdapter.ts +11 -5
  80. package/src/adapters/GraphDataSource.ts +2 -1
  81. package/src/adapters/UriRouter.ts +2 -1
  82. package/src/adapters/concept-identity.ts +69 -0
  83. package/src/adapters/factory.ts +3 -2
  84. package/src/adapters/model-bridge.ts +2 -1
  85. package/src/adapters/non-verbal/figure-bridge.ts +22 -23
  86. package/src/adapters/non-verbal/formula-bridge.ts +11 -9
  87. package/src/adapters/non-verbal/glossarist-augment.d.ts +133 -0
  88. package/src/adapters/non-verbal/index.ts +12 -9
  89. package/src/adapters/non-verbal/kind.ts +2 -1
  90. package/src/adapters/non-verbal/table-bridge.ts +12 -10
  91. package/src/adapters/non-verbal/types.ts +36 -54
  92. package/src/adapters/non-verbal-resolver.ts +6 -3
  93. package/src/components/AppSidebar.vue +189 -93
  94. package/src/components/ConceptDetail.vue +8 -0
  95. package/src/components/ConceptEditionRail.vue +222 -0
  96. package/src/components/ConceptRdfView.vue +37 -377
  97. package/src/components/DatasetSeriesCard.vue +270 -0
  98. package/src/components/ErrorBoundary.vue +95 -0
  99. package/src/components/FormatDownloads.vue +17 -13
  100. package/src/components/HomeSeriesSection.vue +277 -0
  101. package/src/components/NonVerbalRepDisplay.vue +2 -2
  102. package/src/components/RelationSphere.vue +1672 -0
  103. package/src/components/SidebarSeriesSection.vue +239 -0
  104. package/src/components/concept-rdf/RdfInstanceHeader.vue +47 -0
  105. package/src/components/concept-rdf/RdfInstanceSection.vue +54 -0
  106. package/src/components/concept-rdf/RdfPrefixLegend.vue +27 -0
  107. package/src/components/concept-rdf/RdfSourcePanel.vue +72 -0
  108. package/src/components/concept-rdf/agents-emitter.ts +82 -0
  109. package/src/components/concept-rdf/bibliography-emitter.ts +83 -0
  110. package/src/components/concept-rdf/build-activity-emitter.ts +89 -0
  111. package/src/components/concept-rdf/concept-emitter.ts +443 -0
  112. package/src/components/concept-rdf/dataset-emitter.ts +95 -0
  113. package/src/components/concept-rdf/group-emitter.ts +69 -0
  114. package/src/components/concept-rdf/image-variant-emitter.ts +46 -0
  115. package/src/components/concept-rdf/jsonld-writer.ts +82 -0
  116. package/src/components/concept-rdf/predicates.ts +261 -0
  117. package/src/components/concept-rdf/provenance.ts +80 -0
  118. package/src/components/concept-rdf/rdf-graph.ts +211 -0
  119. package/src/components/concept-rdf/rdf-prefixes.ts +23 -0
  120. package/src/components/concept-rdf/sections-builder.ts +62 -0
  121. package/src/components/concept-rdf/table-formula-emitter.ts +101 -0
  122. package/src/components/concept-rdf/turtle-writer.ts +116 -0
  123. package/src/components/concept-rdf/use-rdf-document.ts +72 -0
  124. package/src/components/concept-rdf/version-emitter.ts +65 -0
  125. package/src/components/concept-rdf/vocabulary-emitter.ts +62 -0
  126. package/src/components/figure/FigureDisplay.vue +16 -15
  127. package/src/components/figure/FigureImages.vue +38 -16
  128. package/src/components/figure/figure-image-pick.ts +1 -1
  129. package/src/components/figure/figure-layout.ts +1 -1
  130. package/src/components/formula/FormulaDisplay.vue +11 -9
  131. package/src/components/formula/FormulaExpression.vue +4 -4
  132. package/src/components/non-verbal/NonVerbalCaption.vue +5 -5
  133. package/src/components/non-verbal/NonVerbalSources.vue +3 -11
  134. package/src/components/table/TableDisplay.vue +6 -4
  135. package/src/components/table/TableMarkup.vue +1 -1
  136. package/src/composables/use-color-theme.ts +82 -0
  137. package/src/composables/use-format-registry.ts +42 -0
  138. package/src/composables/use-non-verbal-entity.ts +2 -1
  139. package/src/composables/useDatasetSeries.ts +258 -0
  140. package/src/composables/useSphereProjection.ts +125 -0
  141. package/src/config/group-types.ts +92 -0
  142. package/src/config/types.ts +81 -2
  143. package/src/config/use-site-config.ts +2 -1
  144. package/src/errors.ts +136 -0
  145. package/src/i18n/locales/eng.yml +24 -0
  146. package/src/i18n/locales/fra.yml +24 -0
  147. package/src/stores/vocabulary.ts +3 -1
  148. package/src/style.css +17 -2
  149. package/src/types/agents-version-turtle.d.ts +27 -0
  150. package/src/types/bibliography-turtle.d.ts +12 -0
  151. package/src/types/build-activity-turtle.d.ts +16 -0
  152. package/src/types/build-cache.d.ts +20 -0
  153. package/src/types/dataset-turtle.d.ts +32 -0
  154. package/src/types/normalize-yaml.d.ts +16 -0
  155. package/src/types/turtle-escape.d.ts +6 -0
  156. package/src/types/vocab-turtle.d.ts +13 -0
  157. package/src/utils/asciidoc-lite.ts +11 -6
  158. package/src/utils/bcp47.ts +141 -0
  159. package/src/utils/color-theme-integration.ts +11 -0
  160. package/src/utils/color-theme.ts +129 -0
  161. package/src/utils/dataset-style.ts +31 -6
  162. package/src/utils/locale.ts +6 -14
  163. package/src/utils/markdown-lite.ts +6 -1
  164. package/src/utils/relation-sphere-styling.ts +63 -0
  165. package/src/utils/relationship-categories.ts +30 -0
  166. package/src/utils/url-safety.ts +30 -0
  167. package/src/views/ConceptView.vue +183 -9
  168. package/src/views/DatasetView.vue +6 -0
  169. package/src/views/HomeView.vue +5 -0
  170. package/vite.config.ts +7 -0
@@ -12,6 +12,12 @@ import type {
12
12
  import type { Concept } from 'glossarist';
13
13
  import { conceptFromJson } from './model-bridge';
14
14
  import { GraphDataSource } from './GraphDataSource';
15
+ import {
16
+ ChunkLoadError,
17
+ ConceptNotFoundError,
18
+ IndexLoadError,
19
+ ManifestLoadError,
20
+ } from '../errors';
15
21
 
16
22
  // ── Wire-format types for JSON responses ────────────────────────────────────
17
23
 
@@ -82,7 +88,7 @@ export class DatasetAdapter {
82
88
  async loadManifest(): Promise<Manifest> {
83
89
  if (this.manifestComplete && this.manifest) return this.manifest;
84
90
  const resp = await fetch(`${this.baseUrl}/manifest.json`);
85
- if (!resp.ok) throw new Error(`Failed to load manifest for ${this.registerId}: ${resp.status}`);
91
+ if (!resp.ok) throw ManifestLoadError.make(this.registerId, resp.status);
86
92
  this.manifest = (await resp.json()) as Manifest;
87
93
  this.manifestComplete = true;
88
94
  return this.manifest;
@@ -97,7 +103,7 @@ export class DatasetAdapter {
97
103
  }
98
104
 
99
105
  const resp = await fetch(`${this.baseUrl}/index.json`);
100
- if (!resp.ok) throw new Error(`Failed to load index for ${this.registerId}: ${resp.status}`);
106
+ if (!resp.ok) throw IndexLoadError.make(this.registerId, resp.status);
101
107
  const data = await resp.json();
102
108
 
103
109
  // Handle both old format (with eng/status fields) and new format (with designations map)
@@ -150,7 +156,7 @@ export class DatasetAdapter {
150
156
  meta = await metaResp.json();
151
157
  } else {
152
158
  const resp = await fetch(`${this.baseUrl}/index.json`);
153
- if (!resp.ok) throw new Error(`Failed to load index for ${this.registerId}`);
159
+ if (!resp.ok) throw IndexLoadError.make(this.registerId, resp.status);
154
160
  const data = await resp.json();
155
161
  this.index = this.normalizeIndex(data as IndexJson);
156
162
  this.buildSummaryIndex();
@@ -180,7 +186,7 @@ export class DatasetAdapter {
180
186
  if (this.loadedChunks.has(chunkIndex)) return [];
181
187
  const chunkFile = `index-${String(chunkIndex).padStart(4, '0')}.json`;
182
188
  const resp = await fetch(`${this.baseUrl}/chunks/${chunkFile}`);
183
- if (!resp.ok) throw new Error(`Failed to load chunk ${chunkIndex} for ${this.registerId}`);
189
+ if (!resp.ok) throw ChunkLoadError.make(this.registerId, chunkIndex, resp.status);
184
190
  const data = await resp.json();
185
191
  this.loadedChunks.add(chunkIndex);
186
192
  return data.concepts as ConceptEntry[];
@@ -255,7 +261,7 @@ export class DatasetAdapter {
255
261
  }
256
262
 
257
263
  const resp = await fetch(`${this.baseUrl}/concepts/${conceptId}.json`);
258
- if (!resp.ok) throw new Error(`Concept ${conceptId} not found in ${this.registerId}`);
264
+ if (!resp.ok) throw ConceptNotFoundError.make(this.registerId, conceptId);
259
265
  const json = await resp.json();
260
266
  const concept = conceptFromJson(json);
261
267
  this.conceptCache.set(conceptId, concept);
@@ -4,6 +4,7 @@ import type { DatasetAdapter } from './DatasetAdapter';
4
4
  import { UriRouter } from './UriRouter';
5
5
  import { slugify } from '../utils/slugify';
6
6
  import { toSectionNode, toSectionTree } from '../utils/section-tree';
7
+ import { ConceptIdentity } from './concept-identity';
7
8
 
8
9
  interface DomainNodeJson {
9
10
  uri?: string;
@@ -23,7 +24,7 @@ function resolveRefTarget(rc: RelatedConcept, uriBase: string, registerId: strin
23
24
  if (ref.source && !ref.source.startsWith('http')) {
24
25
  reg = urnMap?.get(ref.source) ?? ref.source;
25
26
  }
26
- return `${uriBase}/${reg}/concept/${ref.id}`;
27
+ return new ConceptIdentity(ref.id, reg, uriBase).uri;
27
28
  }
28
29
  if (ref.source && ref.source.startsWith('http')) return ref.source;
29
30
  return ref.source || '';
@@ -1,4 +1,5 @@
1
1
  import type { Manifest } from './types';
2
+ import { ConceptIdentity } from './concept-identity';
2
3
 
3
4
  // ── URI pattern matching ────────────────────────────────────────────────────
4
5
 
@@ -104,7 +105,7 @@ export class UriRouter {
104
105
  buildUri(registerId: string, conceptId: string): string {
105
106
  const info = this.registerMap.get(registerId);
106
107
  const uriBase = info?.uriBase ?? '';
107
- return `${uriBase}/${registerId}/concept/${conceptId}`;
108
+ return new ConceptIdentity(conceptId, registerId, uriBase).uri;
108
109
  }
109
110
 
110
111
  getRegisteredIds(): string[] {
@@ -0,0 +1,69 @@
1
+ import { InvalidConceptIdentityError, InvalidConceptUriError } from '../errors';
2
+
3
+ export interface ConceptIdentityParts {
4
+ readonly localId: string;
5
+ readonly registerId: string;
6
+ readonly uriBase: string;
7
+ }
8
+
9
+ const CONCEPT_URI_RE = /^(.+)\/([^/]+)\/concept\/(.+)$/;
10
+
11
+ export class ConceptIdentity implements ConceptIdentityParts {
12
+ private readonly _uri: string;
13
+ private readonly _slug: string;
14
+ private readonly _path: string;
15
+
16
+ constructor(
17
+ public readonly localId: string,
18
+ public readonly registerId: string,
19
+ public readonly uriBase: string,
20
+ ) {
21
+ if (!localId || !registerId || !uriBase) {
22
+ throw new InvalidConceptIdentityError(
23
+ 'ConceptIdentity requires non-empty localId, registerId, and uriBase',
24
+ { localId, registerId, uriBase },
25
+ );
26
+ }
27
+ this._uri = `${uriBase}/${registerId}/concept/${localId}`;
28
+ this._slug = localId;
29
+ this._path = `${registerId}/concepts/${localId}`;
30
+ }
31
+
32
+ get uri(): string { return this._uri; }
33
+ get slug(): string { return this._slug; }
34
+ get path(): string { return this._path; }
35
+
36
+ equals(other: ConceptIdentity): boolean {
37
+ return this._uri === other._uri;
38
+ }
39
+
40
+ toString(): string { return this._uri; }
41
+
42
+ toJSON(): ConceptIdentityParts {
43
+ return { localId: this.localId, registerId: this.registerId, uriBase: this.uriBase };
44
+ }
45
+
46
+ localizationUri(lang: string): string {
47
+ return `${this._uri}/${lang}`;
48
+ }
49
+
50
+ designationUri(lang: string, slug: string): string {
51
+ return `${this._uri}/${lang}/desig/${slug}`;
52
+ }
53
+
54
+ domainUri(domainSlug: string): string {
55
+ return `${this.uriBase}/${this.registerId}/domain/${domainSlug}`;
56
+ }
57
+
58
+ static fromUri(uri: string): ConceptIdentity {
59
+ const m = uri.match(CONCEPT_URI_RE);
60
+ if (!m) {
61
+ throw InvalidConceptUriError.make(uri);
62
+ }
63
+ return new ConceptIdentity(m[3], m[2], m[1]);
64
+ }
65
+
66
+ static isConceptUri(uri: string): boolean {
67
+ return CONCEPT_URI_RE.test(uri);
68
+ }
69
+ }
@@ -5,6 +5,7 @@ import { ReferenceResolver } from './ReferenceResolver';
5
5
  import { UriRouter } from './UriRouter';
6
6
  import { NonVerbalEntityResolver } from './non-verbal-resolver';
7
7
  import { BibliographyAdapter } from './bibliography-adapter';
8
+ import { DatasetRegistryLoadError, UnknownDatasetError } from '../errors';
8
9
 
9
10
  export class AdapterFactory {
10
11
  private adapters = new Map<string, DatasetAdapter>();
@@ -36,7 +37,7 @@ export class AdapterFactory {
36
37
  registry = JSON.parse(inline.textContent) as DatasetRegistry[];
37
38
  } else {
38
39
  const resp = await fetch(datasetsUrl);
39
- if (!resp.ok) throw new Error(`Failed to load dataset registry: ${resp.status}`);
40
+ if (!resp.ok) throw DatasetRegistryLoadError.make(resp.status, datasetsUrl);
40
41
  registry = await resp.json() as DatasetRegistry[];
41
42
  }
42
43
 
@@ -132,7 +133,7 @@ export class AdapterFactory {
132
133
 
133
134
  async loadDataset(registerId: string): Promise<DatasetAdapter> {
134
135
  const adapter = this.adapters.get(registerId);
135
- if (!adapter) throw new Error(`Unknown dataset: ${registerId}`);
136
+ if (!adapter) throw UnknownDatasetError.make(registerId);
136
137
 
137
138
  const manifest = await adapter.loadManifest();
138
139
  await adapter.loadIndex();
@@ -36,6 +36,7 @@ import {
36
36
  GRAMMAR_PARTS_OF_SPEECH,
37
37
  } from 'glossarist/models';
38
38
  import type { ConceptSummary } from './types';
39
+ import { ConceptIdentity } from './concept-identity';
39
40
 
40
41
  // ── JSON-LD wire-format types ─────────────────────────────────────────────
41
42
 
@@ -634,5 +635,5 @@ export function conceptToSummary(concept: Concept): ConceptSummary {
634
635
 
635
636
  export function conceptUri(concept: Concept, registerId: string, uriBase: string): string {
636
637
  if (concept.uri) return concept.uri;
637
- return `${uriBase}/${registerId}/concept/${concept.id}`;
638
+ return new ConceptIdentity(concept.id, registerId, uriBase).uri;
638
639
  }
@@ -23,8 +23,8 @@
23
23
  * ambiguity with the HTML `<img alt>` attribute.
24
24
  */
25
25
 
26
- import type { Figure, FigureImage, FigureImageFormat, FigureImageRole } from './types';
27
- import { isType, pickField, pickFieldArray, pickFieldRecord, localized } from './prefix';
26
+ import { Figure, FigureImage } from 'glossarist';
27
+ import { isType, pickField, pickFieldArray, localized } from './prefix';
28
28
  import { sourcesFromJsonLd } from './source-bridge';
29
29
 
30
30
  const FORMAT_SET: ReadonlySet<string> = new Set(['svg', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'avif']);
@@ -35,18 +35,20 @@ function imageFromJsonLd(raw: Record<string, unknown>): FigureImage | null {
35
35
  const src = pickField<string>(raw, 'src');
36
36
  if (!src) return null;
37
37
  const formatRaw = (pickField<string>(raw, 'format') ?? '').toLowerCase();
38
- const format = (FORMAT_SET.has(formatRaw) ? formatRaw : 'svg') as FigureImageFormat;
38
+ const format = FORMAT_SET.has(formatRaw) ? formatRaw : 'svg';
39
39
  const roleRaw = pickField<string>(raw, 'role');
40
- const role = roleRaw && ROLE_SET.has(roleRaw) ? (roleRaw as FigureImageRole) : undefined;
40
+ const role = roleRaw && ROLE_SET.has(roleRaw) ? roleRaw : undefined;
41
41
  const width = pickField<number>(raw, 'width');
42
42
  const height = pickField<number>(raw, 'height');
43
43
  const scale = pickField<number>(raw, 'scale');
44
- const img: FigureImage = { src, format };
45
- if (role) img.role = role;
46
- if (typeof width === 'number') img.width = width;
47
- if (typeof height === 'number') img.height = height;
48
- if (typeof scale === 'number') img.scale = scale;
49
- return img;
44
+ return new FigureImage({
45
+ src,
46
+ format,
47
+ ...(role !== undefined && { role }),
48
+ ...(typeof width === 'number' && { width }),
49
+ ...(typeof height === 'number' && { height }),
50
+ ...(typeof scale === 'number' && { scale }),
51
+ });
50
52
  }
51
53
 
52
54
  function imagesFromJsonLd(raw: unknown): FigureImage[] {
@@ -85,17 +87,14 @@ export function figureFromJsonLd(doc: Record<string, unknown>): Figure | null {
85
87
  const subfigures = subfiguresFromJsonLd(pickField(doc, 'subfigure'));
86
88
  const sources = sourcesFromJsonLd(pickField(doc, 'source'));
87
89
 
88
- const fig: Figure = { kind: 'figure', id, images };
89
- if (identifier) fig.identifier = identifier;
90
- if (caption) fig.caption = caption;
91
- if (alt) fig.alt = alt;
92
- if (description) fig.description = description;
93
- if (subfigures) fig.subfigures = subfigures;
94
- if (sources.length) fig.sources = sources;
95
-
96
- // pickFieldRecord is unused for figures; keep the import meaningful by
97
- // ensuring no stray image fields leak through (silent no-op).
98
- void pickFieldRecord;
99
-
100
- return fig;
90
+ return new Figure({
91
+ id,
92
+ images,
93
+ ...(identifier && { identifier }),
94
+ ...(caption && { caption }),
95
+ ...(alt && { alt }),
96
+ ...(description && { description }),
97
+ ...(subfigures && { subfigures }),
98
+ ...(sources.length && { sources }),
99
+ });
101
100
  }
@@ -15,7 +15,7 @@
15
15
  * }
16
16
  */
17
17
 
18
- import type { Formula, FormulaNotation } from './types';
18
+ import { Formula } from 'glossarist';
19
19
  import { isType, pickField, localized } from './prefix';
20
20
  import { sourcesFromJsonLd } from './source-bridge';
21
21
 
@@ -31,18 +31,20 @@ export function formulaFromJsonLd(doc: Record<string, unknown>): Formula | null
31
31
  if (!expression) return null;
32
32
 
33
33
  const notationRaw = (pickField<string>(doc, 'notation') ?? '').toLowerCase();
34
- const notation = NOTATION_SET.has(notationRaw) ? (notationRaw as FormulaNotation) : 'latex';
34
+ const notation = NOTATION_SET.has(notationRaw) ? notationRaw : 'latex';
35
35
 
36
36
  const identifier = pickField<string>(doc, 'identifier');
37
37
  const caption = localized(doc, 'caption');
38
38
  const description = localized(doc, 'description');
39
39
  const sources = sourcesFromJsonLd(pickField(doc, 'source'));
40
40
 
41
- const f: Formula = { kind: 'formula', id, expression, notation };
42
- if (identifier) f.identifier = identifier;
43
- if (caption) f.caption = caption;
44
- if (description) f.description = description;
45
- if (sources.length) f.sources = sources;
46
-
47
- return f;
41
+ return new Formula({
42
+ id,
43
+ expression,
44
+ notation,
45
+ ...(identifier && { identifier }),
46
+ ...(caption && { caption }),
47
+ ...(description && { description }),
48
+ ...(sources.length && { sources }),
49
+ });
48
50
  }
@@ -0,0 +1,133 @@
1
+ // Local module augmentation for glossarist 0.4.2.
2
+ //
3
+ // Upstream's published src/models/index.d.ts declares ZERO classes for the
4
+ // non-verbal hierarchy (Figure, Table, Formula, FigureImage, NonVerbalEntity,
5
+ // SharedNonVerbalEntity, NonVerbalReference + subclasses, BibliographyEntry,
6
+ // BibliographyData) plus the localized-string helpers. The top-level
7
+ // index.d.ts re-exports the names, so TypeScript silently resolves every
8
+ // consumer import to `any`.
9
+ //
10
+ // This file declares the runtime shape so consumer code can be type-checked.
11
+ // DELETE this file when upstream ships proper declarations — tracked by
12
+ // PR glossarist/glossarist-js#31 (targets v0.4.3+).
13
+
14
+ import type { ConceptSource, GlossaristModel } from 'glossarist';
15
+
16
+ declare module 'glossarist' {
17
+ class RegistrableModel extends GlossaristModel {
18
+ static register(type: string, cls: typeof RegistrableModel): void;
19
+ static fromData(data: Record<string, unknown>): RegistrableModel;
20
+ }
21
+
22
+ class FigureImage extends GlossaristModel {
23
+ constructor(data?: {
24
+ src?: string | null;
25
+ format?: string | null;
26
+ role?: string | null;
27
+ width?: number | null;
28
+ height?: number | null;
29
+ scale?: number | null;
30
+ });
31
+ readonly src: string | null;
32
+ readonly format: string | null;
33
+ readonly role: string | null;
34
+ readonly width: number | null;
35
+ readonly height: number | null;
36
+ readonly scale: number | null;
37
+ static fromJSON(data: Record<string, unknown>): FigureImage;
38
+ }
39
+
40
+ class NonVerbalEntity extends RegistrableModel {
41
+ constructor(data?: Record<string, unknown>);
42
+ readonly caption: Record<string, string> | null;
43
+ readonly description: Record<string, string> | null;
44
+ readonly alt: Record<string, string> | null;
45
+ readonly sources: ConceptSource[];
46
+ findById(targetId: string): NonVerbalEntity | null;
47
+ allIds(): string[];
48
+ static fromJSON(data: Record<string, unknown>): NonVerbalEntity;
49
+ }
50
+
51
+ class SharedNonVerbalEntity extends NonVerbalEntity {
52
+ constructor(data?: Record<string, unknown>);
53
+ readonly id: string | null;
54
+ readonly identifier: string | null;
55
+ findById(targetId: string): SharedNonVerbalEntity | null;
56
+ allIds(): string[];
57
+ static fromJSON(data: Record<string, unknown>): SharedNonVerbalEntity;
58
+ }
59
+
60
+ class Figure extends SharedNonVerbalEntity {
61
+ constructor(data?: Record<string, unknown>);
62
+ readonly images: FigureImage[];
63
+ readonly subfigures: Figure[];
64
+ findById(targetId: string): Figure | null;
65
+ allIds(): string[];
66
+ static fromJSON(data: Record<string, unknown>): Figure;
67
+ }
68
+
69
+ class Table extends SharedNonVerbalEntity {
70
+ constructor(data?: Record<string, unknown>);
71
+ readonly content: Record<string, unknown> | null;
72
+ readonly format: string | null;
73
+ static fromJSON(data: Record<string, unknown>): Table;
74
+ }
75
+
76
+ class Formula extends SharedNonVerbalEntity {
77
+ constructor(data?: Record<string, unknown>);
78
+ readonly expression: Record<string, string> | null;
79
+ readonly notation: string | null;
80
+ static fromJSON(data: Record<string, unknown>): Formula;
81
+ }
82
+
83
+ const NON_VERBAL_TYPES: readonly string[];
84
+
85
+ class NonVerbalReference extends RegistrableModel {
86
+ constructor(data?: Record<string, unknown>);
87
+ readonly entityId: string | null;
88
+ readonly display: string | null;
89
+ readonly dedupKey: readonly [string, string | null];
90
+ static fromJSON(data: Record<string, unknown> | string): NonVerbalReference;
91
+ static register(type: string, cls: typeof NonVerbalReference): void;
92
+ }
93
+
94
+ interface NonVerbRep {
95
+ readonly caption: string | null;
96
+ readonly description: string | null;
97
+ readonly alt: string | null;
98
+ readonly images: FigureImage[];
99
+ }
100
+
101
+ class FigureReference extends NonVerbalReference {
102
+ static fromJSON(data: Record<string, unknown> | string): FigureReference;
103
+ }
104
+
105
+ class TableReference extends NonVerbalReference {
106
+ static fromJSON(data: Record<string, unknown> | string): TableReference;
107
+ }
108
+
109
+ class FormulaReference extends NonVerbalReference {
110
+ static fromJSON(data: Record<string, unknown> | string): FormulaReference;
111
+ }
112
+
113
+ class BibliographyEntry extends GlossaristModel {
114
+ constructor(data?: Record<string, unknown>);
115
+ readonly id: string | null;
116
+ readonly reference: string | null;
117
+ readonly title: string | null;
118
+ readonly link: string | null;
119
+ readonly type: string | null;
120
+ static fromJSON(data: Record<string, unknown>): BibliographyEntry;
121
+ }
122
+
123
+ class BibliographyData extends GlossaristModel {
124
+ constructor(data?: Record<string, unknown>);
125
+ readonly entries: BibliographyEntry[];
126
+ find(id: string): BibliographyEntry | null;
127
+ readonly keys: string[];
128
+ toYAML(): string;
129
+ toJSON(): { bibliography: BibliographyEntry[] };
130
+ static fromYAML(yamlString: string): BibliographyData;
131
+ static fromJSON(data: Record<string, unknown>): BibliographyData;
132
+ }
133
+ }
@@ -1,33 +1,36 @@
1
1
  /**
2
2
  * Public API for the non-verbal entity model layer.
3
3
  *
4
- * Re-exports the types, bridges, and dispatch table. Components and
5
- * composables import from herenever from individual files so the
6
- * internal layout can evolve without breaking the public surface.
4
+ * Model classes (Figure, Table, Formula, FigureImage, NonVerbalEntity) are
5
+ * re-exported from `glossarist`upstream is the SSOT for the model.
6
+ * Consumer-owned types live in `./types`.
7
7
  */
8
8
 
9
9
  export type {
10
10
  LocalizedString,
11
11
  NonVerbalKind,
12
- FigureImage,
13
12
  FigureImageFormat,
14
13
  FigureImageRole,
15
14
  NonVerbalSource,
16
15
  NonVerbalSourceOrigin,
17
16
  NonVerbalSourceRef,
18
17
  NonVerbalSourceLocality,
19
- NonVerbalEntityBase,
20
- Figure,
21
- Table,
22
18
  TableContent,
23
19
  TableFormat,
24
- Formula,
25
20
  FormulaNotation,
26
- NonVerbalEntity,
27
21
  NonVerbRepV3,
28
22
  NonVerbalReference,
29
23
  } from './types';
30
24
 
25
+ export type {
26
+ Figure,
27
+ FigureImage,
28
+ Table,
29
+ Formula,
30
+ NonVerbalEntity,
31
+ SharedNonVerbalEntity,
32
+ } from 'glossarist';
33
+
31
34
  export { figureFromJsonLd } from './figure-bridge';
32
35
  export { tableFromJsonLd } from './table-bridge';
33
36
  export { formulaFromJsonLd } from './formula-bridge';
@@ -1,4 +1,5 @@
1
- import type { NonVerbalEntity, NonVerbalKind } from './types';
1
+ import type { NonVerbalKind } from './types';
2
+ import type { NonVerbalEntity } from 'glossarist';
2
3
  import {
3
4
  ENTITY_DIRECTORIES,
4
5
  ENTITY_TYPES,
@@ -20,7 +20,8 @@
20
20
  * }
21
21
  */
22
22
 
23
- import type { Table, TableContent, TableFormat } from './types';
23
+ import { Table } from 'glossarist';
24
+ import type { TableContent } from './types';
24
25
  import { isType, pickField, localized } from './prefix';
25
26
  import { sourcesFromJsonLd } from './source-bridge';
26
27
 
@@ -83,16 +84,17 @@ export function tableFromJsonLd(doc: Record<string, unknown>): Table | null {
83
84
  if (!content) return null;
84
85
 
85
86
  const formatRaw = (pickField<string>(doc, 'format') ?? '').toLowerCase();
86
- const format = FORMAT_SET.has(formatRaw) ? (formatRaw as TableFormat) : undefined;
87
+ const format = FORMAT_SET.has(formatRaw) ? formatRaw : undefined;
87
88
 
88
89
  const sources = sourcesFromJsonLd(pickField(doc, 'source'));
89
90
 
90
- const t: Table = { kind: 'table', id, content };
91
- if (identifier) t.identifier = identifier;
92
- if (caption) t.caption = caption;
93
- if (description) t.description = description;
94
- if (format) t.format = format;
95
- if (sources.length) t.sources = sources;
96
-
97
- return t;
91
+ return new Table({
92
+ id,
93
+ content: content as Record<string, unknown>,
94
+ ...(identifier && { identifier }),
95
+ ...(caption && { caption }),
96
+ ...(description && { description }),
97
+ ...(format && { format }),
98
+ ...(sources.length && { sources }),
99
+ });
98
100
  }
@@ -1,18 +1,23 @@
1
1
  /**
2
- * Non-verbal entity model TypeScript projection of the authoritative
3
- * glossarist-ruby model.
2
+ * Consumer-side types for non-verbal entities.
4
3
  *
5
- * The authoritative model lives in glossarist-ruby (Figure, Table, Formula
6
- * inherit from NonVerbalEntity). This file mirrors that model in TypeScript
7
- * for the consumer side. It does not redefine the model every field here
8
- * corresponds to a field in the authoritative source.
4
+ * Model classes (Figure, Table, Formula, FigureImage, NonVerbalEntity) are
5
+ * imported from `glossarist` the upstream library is the SSOT for the
6
+ * model. This file holds only what is genuinely consumer-owned:
9
7
  *
10
- * See:
11
- * ../glossarist-ruby/lib/glossarist/non_verbal_entity.rb
12
- * ../glossarist-ruby/lib/glossarist/figure.rb
13
- * ../glossarist-ruby/lib/glossarist/table.rb
14
- * ../glossarist-ruby/lib/glossarist/formula.rb
15
- * ../glossarist-ruby/lib/glossarist/figure_image.rb
8
+ * - `NonVerbalKind`: routing discriminator used by the resolver, the
9
+ * anchor scheme, the mention dispatcher, and the section router.
10
+ * - `NonVerbRepV3`: local view of NonVerbRep's V3 shape. Upstream's
11
+ * published .d.ts still describes the pre-V3 `ref`/`text` shape; this
12
+ * interface lets the consumer type-check against runtime reality.
13
+ * Delete when upstream ships a corrected declaration.
14
+ * - `NonVerbalReference`: consumer-side view of inline mentions like
15
+ * `{{fig:foo}}`. Carries a `kind` for UI routing.
16
+ * - `LocalizedString`, `FigureImageFormat`, `FigureImageRole`,
17
+ * `TableFormat`, `TableContent`, `FormulaNotation`: string-union
18
+ * refinements the consumer validates at bridge time.
19
+ * - `NonVerbalSource*`: wire shape for JSON-LD source entries. Stays
20
+ * consumer-side until upstream ships a V3 NonVerbalSource model.
16
21
  */
17
22
 
18
23
  export type LocalizedString = Record<string, string>;
@@ -23,15 +28,6 @@ export type FigureImageFormat = 'svg' | 'png' | 'jpg' | 'jpeg' | 'gif' | 'webp'
23
28
 
24
29
  export type FigureImageRole = 'vector' | 'raster' | 'dark' | 'light' | 'print';
25
30
 
26
- export interface FigureImage {
27
- src: string;
28
- format: FigureImageFormat;
29
- role?: FigureImageRole;
30
- width?: number;
31
- height?: number;
32
- scale?: number;
33
- }
34
-
35
31
  export interface NonVerbalSourceRef {
36
32
  source?: string;
37
33
  id?: string;
@@ -62,56 +58,42 @@ export interface NonVerbalSource {
62
58
  origin?: NonVerbalSourceOrigin;
63
59
  }
64
60
 
65
- export interface NonVerbalEntityBase {
66
- id: string;
67
- identifier?: string;
68
- caption?: LocalizedString;
69
- description?: LocalizedString;
70
- alt?: LocalizedString;
71
- sources?: NonVerbalSource[];
72
- }
73
-
74
- export interface Figure extends NonVerbalEntityBase {
75
- kind: 'figure';
76
- images: FigureImage[];
77
- subfigures?: Figure[];
78
- }
79
-
80
61
  export type TableFormat = 'html' | 'markdown' | 'asciidoc';
81
62
 
82
63
  export type TableContent =
83
64
  | { kind: 'structured'; headers: LocalizedString[]; rows: LocalizedString[][] }
84
65
  | { kind: 'markup'; markup: LocalizedString };
85
66
 
86
- export interface Table extends NonVerbalEntityBase {
87
- kind: 'table';
88
- content: TableContent;
89
- format?: TableFormat;
90
- }
91
-
92
67
  export type FormulaNotation = 'latex' | 'mathml' | 'asciimath';
93
68
 
94
- export interface Formula extends NonVerbalEntityBase {
95
- kind: 'formula';
96
- expression: LocalizedString;
97
- notation: FormulaNotation;
98
- }
99
-
100
- export type NonVerbalEntity = Figure | Table | Formula;
101
-
102
69
  /**
103
70
  * V3 NonVerbRep runtime shape.
104
71
  *
105
72
  * glossarist-js's runtime `NonVerbRep` (post-V3 reshape) holds the same
106
- * localized fields as `NonVerbalEntityBase` plus a `type` discriminator
73
+ * localized fields as the base NonVerbalEntity plus a `type` discriminator
107
74
  * and an `images[]` array. The published `.d.ts` (still stale at v0.4.2)
108
75
  * describes the pre-V3 `ref`/`text` shape; this local interface lets
109
76
  * consumer code type-check against reality. Drop when upstream ships a
110
- * corrected `.d.ts`.
77
+ * corrected `.d.ts` (glossarist/glossarist-js#31).
111
78
  */
112
- export interface NonVerbRepV3 extends NonVerbalEntityBase {
79
+ export interface NonVerbRepV3 {
80
+ id: string;
81
+ identifier?: string | null;
113
82
  type: string | null;
114
- images: FigureImage[];
83
+ caption?: LocalizedString | null;
84
+ description?: LocalizedString | null;
85
+ alt?: LocalizedString | null;
86
+ images: NonVerbRepImage[];
87
+ sources?: NonVerbalSource[];
88
+ }
89
+
90
+ export interface NonVerbRepImage {
91
+ src: string;
92
+ format?: string | null;
93
+ role?: string | null;
94
+ width?: number | null;
95
+ height?: number | null;
96
+ scale?: number | null;
115
97
  }
116
98
 
117
99
  export interface NonVerbalReference {