@glossarist/concept-browser 0.7.63 → 0.7.65
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.
- package/package.json +1 -1
- package/scripts/build-edges.js +8 -4
- package/scripts/generate-data.mjs +9 -6
- package/scripts/lib/agents-turtle.mjs +2 -1
- package/scripts/lib/bibliography-turtle.mjs +3 -2
- package/src/__tests__/dataset-adapter.test.ts +1 -0
- package/src/__tests__/scripts/agents-turtle.test.ts +5 -5
- package/src/__tests__/scripts/bibliography-turtle.test.ts +5 -5
- package/src/__tests__/scripts/build-integration.test.ts +4 -4
- package/src/__tests__/test-helpers.ts +2 -1
- package/src/adapters/GraphDataSource.ts +3 -1
- package/src/components/RelationSphere.vue +2 -1
- package/src/stores/vocabulary.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.65",
|
|
4
4
|
"description": "Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/scripts/build-edges.js
CHANGED
|
@@ -49,7 +49,8 @@ function extractReferences(concept, registerId) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function extractDomains(concept, registerId, uriBase) {
|
|
52
|
-
|
|
52
|
+
if (!uriBase) throw new Error('build-edges: uriBase is required — set uriBase in site-config.yml');
|
|
53
|
+
const base = uriBase;
|
|
53
54
|
const edges = [];
|
|
54
55
|
const sourceUri = concept['@id'];
|
|
55
56
|
const seen = new Set();
|
|
@@ -188,7 +189,8 @@ function buildEdgesForDataset(datasetDir, registerId, uriBase, urnMap, manifest)
|
|
|
188
189
|
// Build domain-nodes.json from manifest sections (authoritative source)
|
|
189
190
|
const manifestSections = manifest.sections;
|
|
190
191
|
if (manifestSections && manifestSections.length > 0) {
|
|
191
|
-
const uriBase = manifest.uriBase
|
|
192
|
+
const uriBase = manifest.uriBase;
|
|
193
|
+
if (!uriBase) throw new Error('build-edges: manifest.uriBase is required');
|
|
192
194
|
|
|
193
195
|
function buildSectionNode(section, idx) {
|
|
194
196
|
const sectionId = `section-${section.id}`;
|
|
@@ -293,7 +295,8 @@ for (const ds of datasets) {
|
|
|
293
295
|
if (!manifest) continue;
|
|
294
296
|
try {
|
|
295
297
|
console.log(`${manifest.title} (${ds}):`);
|
|
296
|
-
const uriBase = manifest.uriBase
|
|
298
|
+
const uriBase = manifest.uriBase;
|
|
299
|
+
if (!uriBase) throw new Error('build-edges: manifest.uriBase is required');
|
|
297
300
|
const result = buildEdgesForDataset(join(DATA_DIR, ds), ds, uriBase, urnMap, manifest);
|
|
298
301
|
allDatasetEdges.set(ds, result.edges);
|
|
299
302
|
allSourceRefs.push(...result.sourceRefs);
|
|
@@ -347,7 +350,8 @@ if (auditUnmatched.size > 0) {
|
|
|
347
350
|
// edges.json contains edges targeting that dataset's URIs.
|
|
348
351
|
const datasetUriPrefixes = new Map();
|
|
349
352
|
for (const [ds, manifest] of manifestCache) {
|
|
350
|
-
const uriBase = manifest.uriBase
|
|
353
|
+
const uriBase = manifest.uriBase;
|
|
354
|
+
if (!uriBase) throw new Error('build-edges: manifest.uriBase is required');
|
|
351
355
|
datasetUriPrefixes.set(ds, `${uriBase}/${ds}/`);
|
|
352
356
|
}
|
|
353
357
|
|
|
@@ -92,7 +92,8 @@ function writeJson(filePath, data) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
async function writeDatasetRdf(register, manifest, concepts, refMaps, opts) {
|
|
95
|
-
const uriBase = refMaps?.uriBase
|
|
95
|
+
const uriBase = refMaps?.uriBase;
|
|
96
|
+
if (!uriBase) throw new Error('generate-data: uriBase is required — set uriBase in site-config.yml');
|
|
96
97
|
const datasetIri = `${uriBase}/${register}/`;
|
|
97
98
|
const topConceptUris = concepts
|
|
98
99
|
.slice(0, 32)
|
|
@@ -139,7 +140,7 @@ async function writeDatasetRdf(register, manifest, concepts, refMaps, opts) {
|
|
|
139
140
|
fs.writeFileSync(path.join(DATA, register, `${register}.ttl`), ttl);
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
async function writeBuildActivity(conceptCount, datasetRegisters) {
|
|
143
|
+
async function writeBuildActivity(conceptCount, datasetRegisters, baseUri) {
|
|
143
144
|
const runId = process.env.GITHUB_RUN_ID
|
|
144
145
|
? `${process.env.GITHUB_RUN_ID}-${process.env.GITHUB_RUN_ATTEMPT ?? '1'}`
|
|
145
146
|
: `local-${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
@@ -161,6 +162,7 @@ async function writeBuildActivity(conceptCount, datasetRegisters) {
|
|
|
161
162
|
datasetRegisters,
|
|
162
163
|
conceptCount,
|
|
163
164
|
associatedAgentIri: agentIri,
|
|
165
|
+
baseUri,
|
|
164
166
|
});
|
|
165
167
|
const activityDir = path.join(DATA, 'activity');
|
|
166
168
|
fs.mkdirSync(activityDir, { recursive: true });
|
|
@@ -375,7 +377,8 @@ function buildRefMaps(config, registerCache) {
|
|
|
375
377
|
const xref = config.crossReferences || {};
|
|
376
378
|
if (xref.refPrefixMap) Object.assign(refPrefixMap, xref.refPrefixMap);
|
|
377
379
|
|
|
378
|
-
const uriBase = config.uriBase
|
|
380
|
+
const uriBase = config.uriBase;
|
|
381
|
+
if (!uriBase) throw new Error('site-config.yml: uriBase is required');
|
|
379
382
|
return { patternIndex, refPrefixMap, uriBase, register: null };
|
|
380
383
|
}
|
|
381
384
|
|
|
@@ -1610,14 +1613,14 @@ for (const [id, count] of Object.entries(counts)) {
|
|
|
1610
1613
|
console.log(` ${id}: ${count} concepts`);
|
|
1611
1614
|
}
|
|
1612
1615
|
|
|
1613
|
-
await writeBuildActivity(total, registry.map(r => r.id));
|
|
1616
|
+
await writeBuildActivity(total, registry.map(r => r.id), refMaps.uriBase);
|
|
1614
1617
|
|
|
1615
1618
|
fs.writeFileSync(path.join(DATA, '_vocab.ttl'), await buildVocabularyTurtle());
|
|
1616
1619
|
console.log('Emitted vocabulary graph: data/_vocab.ttl');
|
|
1617
1620
|
|
|
1618
1621
|
const contributors = config.contributors ?? [];
|
|
1619
1622
|
if (contributors.length > 0) {
|
|
1620
|
-
fs.writeFileSync(path.join(DATA, 'agents.ttl'), await buildAgentsTurtle(contributors));
|
|
1623
|
+
fs.writeFileSync(path.join(DATA, 'agents.ttl'), await buildAgentsTurtle(contributors, refMaps.uriBase + '/agent'));
|
|
1621
1624
|
console.log(`Emitted agents graph: data/agents.ttl (${contributors.length} contributors)`);
|
|
1622
1625
|
}
|
|
1623
1626
|
|
|
@@ -1626,7 +1629,7 @@ for (const ds of registry) {
|
|
|
1626
1629
|
const bibPath = path.join(DATA, ds.id, 'bibliography.json');
|
|
1627
1630
|
if (fs.existsSync(bibPath)) {
|
|
1628
1631
|
const bibJson = JSON.parse(fs.readFileSync(bibPath, 'utf8'));
|
|
1629
|
-
const bibTtl = await buildBibliographyTurtle(ds.id, bibJson);
|
|
1632
|
+
const bibTtl = await buildBibliographyTurtle(ds.id, bibJson, refMaps.uriBase);
|
|
1630
1633
|
fs.writeFileSync(path.join(DATA, ds.id, 'bib.ttl'), bibTtl);
|
|
1631
1634
|
}
|
|
1632
1635
|
}
|
|
@@ -19,7 +19,8 @@ function writerPrefixes() {
|
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export async function buildAgentsTurtle(contributors, agentBase
|
|
22
|
+
export async function buildAgentsTurtle(contributors, agentBase) {
|
|
23
|
+
if (!agentBase) throw new Error('buildAgentsTurtle requires agentBase');
|
|
23
24
|
const agents = agentsFromContributors(contributors ?? [], agentBase);
|
|
24
25
|
const quads = collectQuads(agentsToQuads(agents));
|
|
25
26
|
return writeTurtle(quads, { prefixes: writerPrefixes() });
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
writeTurtle,
|
|
11
11
|
} from 'glossarist/rdf';
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// No hardcoded default base URI — callers MUST pass it.
|
|
14
14
|
|
|
15
15
|
function writerPrefixes() {
|
|
16
16
|
return {
|
|
@@ -22,7 +22,8 @@ function writerPrefixes() {
|
|
|
22
22
|
|
|
23
23
|
export { normalizeBibliographyData };
|
|
24
24
|
|
|
25
|
-
export async function buildBibliographyTurtle(register, bibliographyJson, baseUri
|
|
25
|
+
export async function buildBibliographyTurtle(register, bibliographyJson, baseUri) {
|
|
26
|
+
if (!baseUri) throw new Error('buildBibliographyTurtle requires baseUri');
|
|
26
27
|
const entries = normalizeBibliographyData(bibliographyJson);
|
|
27
28
|
const quads = collectQuads(bibliographyToQuads({ registerId: register, entries, baseUri }));
|
|
28
29
|
return writeTurtle(quads, { prefixes: writerPrefixes() });
|
|
@@ -16,13 +16,13 @@ const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
|
16
16
|
|
|
17
17
|
describe('buildAgentsTurtle (mjs)', () => {
|
|
18
18
|
it('parses without errors', async () => {
|
|
19
|
-
const ttl = await buildAgentsTurtle([{ name: 'Ada Lovelace' }]);
|
|
19
|
+
const ttl = await buildAgentsTurtle([{ name: 'Ada Lovelace' }], 'https://glossarist.org/agent');
|
|
20
20
|
const store = parse(ttl);
|
|
21
21
|
expect(store.size).toBeGreaterThan(0);
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
it('types each person as foaf:Person, prov:Person, prov:Agent', async () => {
|
|
25
|
-
const ttl = await buildAgentsTurtle([{ name: 'Ada Lovelace' }]);
|
|
25
|
+
const ttl = await buildAgentsTurtle([{ name: 'Ada Lovelace' }], 'https://glossarist.org/agent');
|
|
26
26
|
const store = parse(ttl);
|
|
27
27
|
const types = store.getObjects('https://glossarist.org/agent/ada-lovelace', RDF_TYPE, null).map(q => q.value);
|
|
28
28
|
expect(types).toContain(`${FOAF}Person`);
|
|
@@ -33,7 +33,7 @@ describe('buildAgentsTurtle (mjs)', () => {
|
|
|
33
33
|
it('records name, mailto mbox, role, and seeAlso', async () => {
|
|
34
34
|
const ttl = await buildAgentsTurtle([
|
|
35
35
|
{ name: 'Ada Lovelace', email: 'ada@example.org', role: 'Editor', url: 'https://example.org/ada' },
|
|
36
|
-
]);
|
|
36
|
+
], 'https://glossarist.org/agent');
|
|
37
37
|
const store = parse(ttl);
|
|
38
38
|
const iri = 'https://glossarist.org/agent/ada-lovelace';
|
|
39
39
|
expect(store.getObjects(iri, `${FOAF}name`, null).map(q => q.value)).toContain('Ada Lovelace');
|
|
@@ -46,7 +46,7 @@ describe('buildAgentsTurtle (mjs)', () => {
|
|
|
46
46
|
const ttl = await buildAgentsTurtle([
|
|
47
47
|
{ name: 'Alice', organization: 'ISO' },
|
|
48
48
|
{ name: 'Bob', organization: 'ISO' },
|
|
49
|
-
]);
|
|
49
|
+
], 'https://glossarist.org/agent');
|
|
50
50
|
const store = parse(ttl);
|
|
51
51
|
const types = store.getObjects('https://glossarist.org/org/iso', RDF_TYPE, null).map(q => q.value);
|
|
52
52
|
expect(types).toContain(`${FOAF}Organization`);
|
|
@@ -54,7 +54,7 @@ describe('buildAgentsTurtle (mjs)', () => {
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
it('emits only prefix declarations for an empty input', async () => {
|
|
57
|
-
const ttl = await buildAgentsTurtle([]);
|
|
57
|
+
const ttl = await buildAgentsTurtle([], 'https://glossarist.org/agent');
|
|
58
58
|
expect(ttl).toContain('@prefix foaf:');
|
|
59
59
|
expect(ttl).not.toContain('<https://glossarist.org/agent/');
|
|
60
60
|
});
|
|
@@ -15,13 +15,13 @@ function parse(turtle: string): Store {
|
|
|
15
15
|
|
|
16
16
|
describe('buildBibliographyTurtle (mjs)', () => {
|
|
17
17
|
it('parses without errors', async () => {
|
|
18
|
-
const ttl = await buildBibliographyTurtle('iso-geodetic', { iso704: { reference: 'ISO 704' } });
|
|
18
|
+
const ttl = await buildBibliographyTurtle('iso-geodetic', { iso704: { reference: 'ISO 704' } }, 'https://glossarist.org');
|
|
19
19
|
const store = parse(ttl);
|
|
20
20
|
expect(store.size).toBeGreaterThan(0);
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
it('types each entry as dcterms:BibliographicResource', async () => {
|
|
24
|
-
const ttl = await buildBibliographyTurtle('iso-geodetic', { iso704: { reference: 'ISO 704' } });
|
|
24
|
+
const ttl = await buildBibliographyTurtle('iso-geodetic', { iso704: { reference: 'ISO 704' } }, 'https://glossarist.org');
|
|
25
25
|
const store = parse(ttl);
|
|
26
26
|
const types = store.getObjects('https://glossarist.org/iso-geodetic/bib/iso704', RDF_TYPE, null).map(q => q.value);
|
|
27
27
|
expect(types).toContain(`${DCTERMS}BibliographicResource`);
|
|
@@ -30,7 +30,7 @@ describe('buildBibliographyTurtle (mjs)', () => {
|
|
|
30
30
|
it('emits identifier, bibliographicCitation, and title', async () => {
|
|
31
31
|
const ttl = await buildBibliographyTurtle('iso-geodetic', {
|
|
32
32
|
iso704: { reference: 'ISO 704', title: 'Terminology work — Principles and methods' },
|
|
33
|
-
});
|
|
33
|
+
}, 'https://glossarist.org');
|
|
34
34
|
const store = parse(ttl);
|
|
35
35
|
const iri = 'https://glossarist.org/iso-geodetic/bib/iso704';
|
|
36
36
|
expect(store.getObjects(iri, `${DCTERMS}identifier`, null).map(q => q.value)).toContain('iso704');
|
|
@@ -41,7 +41,7 @@ describe('buildBibliographyTurtle (mjs)', () => {
|
|
|
41
41
|
it('emits foaf:page when link is provided', async () => {
|
|
42
42
|
const ttl = await buildBibliographyTurtle('iso-geodetic', {
|
|
43
43
|
ref: { reference: 'X', link: 'https://example.org/x' },
|
|
44
|
-
});
|
|
44
|
+
}, 'https://glossarist.org');
|
|
45
45
|
const store = parse(ttl);
|
|
46
46
|
const iri = 'https://glossarist.org/iso-geodetic/bib/ref';
|
|
47
47
|
expect(store.getObjects(iri, `${FOAF}page`, null).map(q => q.value)).toContain('https://example.org/x');
|
|
@@ -51,7 +51,7 @@ describe('buildBibliographyTurtle (mjs)', () => {
|
|
|
51
51
|
const ttl = await buildBibliographyTurtle('iso-geodetic', {
|
|
52
52
|
a: { reference: 'A' },
|
|
53
53
|
b: { reference: 'B' },
|
|
54
|
-
});
|
|
54
|
+
}, 'https://glossarist.org');
|
|
55
55
|
const store = parse(ttl);
|
|
56
56
|
expect(store.getObjects('https://glossarist.org/iso-geodetic/bib/a', `${DCTERMS}bibliographicCitation`, null).map(q => q.value)).toContain('A');
|
|
57
57
|
expect(store.getObjects('https://glossarist.org/iso-geodetic/bib/b', `${DCTERMS}bibliographicCitation`, null).map(q => q.value)).toContain('B');
|
|
@@ -61,7 +61,7 @@ describe('WS F Layer 8 — build pipeline integration', () => {
|
|
|
61
61
|
it('agents-turtle.mjs produces parseable foaf:Person', () => {
|
|
62
62
|
const out = runInline(`
|
|
63
63
|
import { buildAgentsTurtle } from './scripts/lib/agents-turtle.mjs';
|
|
64
|
-
console.log(await buildAgentsTurtle([{ name: 'Ada Lovelace', role: 'Editor', organization: 'Royal Society' }]));
|
|
64
|
+
console.log(await buildAgentsTurtle([{ name: 'Ada Lovelace', role: 'Editor', organization: 'Royal Society' }], 'https://glossarist.org/agent'));
|
|
65
65
|
`);
|
|
66
66
|
const store = parseTurtle(out);
|
|
67
67
|
const persons = [...store].filter(q =>
|
|
@@ -74,7 +74,7 @@ describe('WS F Layer 8 — build pipeline integration', () => {
|
|
|
74
74
|
it('bibliography-turtle.mjs produces parseable dcterms:BibliographicResource', () => {
|
|
75
75
|
const out = runInline(`
|
|
76
76
|
import { buildBibliographyTurtle } from './scripts/lib/bibliography-turtle.mjs';
|
|
77
|
-
console.log(await buildBibliographyTurtle('test', { iso704: { reference: 'ISO 704' } }));
|
|
77
|
+
console.log(await buildBibliographyTurtle('test', { iso704: { reference: 'ISO 704' } }, 'https://glossarist.org'));
|
|
78
78
|
`);
|
|
79
79
|
const store = parseTurtle(out);
|
|
80
80
|
const bibs = [...store].filter(q =>
|
|
@@ -122,8 +122,8 @@ describe('WS F Layer 8 — build pipeline integration', () => {
|
|
|
122
122
|
await buildVocabularyTurtle(),
|
|
123
123
|
await buildDatasetTurtle({ datasetIri: 'https://glossarist.org/test/', registerId: 'test', title: 't', modified: '2026-01-01', languages: ['eng'], distributions: [], topConceptUris: [], sections: [] }),
|
|
124
124
|
await buildActivityTurtle({ runId: 'r', startedAt: '2026-01-01T00:00:00Z', endedAt: '2026-01-01T00:05:00Z', toolId: 'cb', toolVersion: '0', datasetRegisters: [], conceptCount: 0 }),
|
|
125
|
-
await buildAgentsTurtle([{ name: 'Ada' }]),
|
|
126
|
-
await buildBibliographyTurtle('test', { x: { reference: 'X' } }),
|
|
125
|
+
await buildAgentsTurtle([{ name: 'Ada' }], 'https://glossarist.org/agent'),
|
|
126
|
+
await buildBibliographyTurtle('test', { x: { reference: 'X' } }, 'https://glossarist.org'),
|
|
127
127
|
await buildVersionHistoryTurtle({ registerId: 't', datasetIri: 'https://glossarist.org/t/', versions: [{ version: '1', generatedAt: '2026-01-01' }] }),
|
|
128
128
|
];
|
|
129
129
|
console.log(parts.join('\\n'));
|
|
@@ -38,6 +38,7 @@ export function makeManifest(overrides: Partial<Manifest> = {}): Manifest {
|
|
|
38
38
|
|
|
39
39
|
export interface AdapterStubOptions {
|
|
40
40
|
concepts?: ConceptSummary[];
|
|
41
|
+
manifest?: Manifest | null;
|
|
41
42
|
search?: () => SearchHit[];
|
|
42
43
|
fetchConcept?: () => Promise<any>;
|
|
43
44
|
getAdjacentConcepts?: () => { prev: string | null; next: string | null };
|
|
@@ -55,7 +56,7 @@ export interface AdapterStubOptions {
|
|
|
55
56
|
export function makeAdapterStub(options: AdapterStubOptions = {}): any {
|
|
56
57
|
return {
|
|
57
58
|
index: options.concepts ?? [],
|
|
58
|
-
manifest:
|
|
59
|
+
manifest: options.manifest ?? makeManifest(),
|
|
59
60
|
registerId: 'test',
|
|
60
61
|
getConceptCount: () => (options.concepts ?? []).length,
|
|
61
62
|
getConcepts: () => options.concepts ?? [],
|
|
@@ -42,7 +42,9 @@ export class GraphDataSource {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
private get uriBase(): string {
|
|
45
|
-
|
|
45
|
+
const uriBase = this.adapter.manifest?.uriBase;
|
|
46
|
+
if (!uriBase) throw new Error('GraphDataSource: manifest.uriBase is required');
|
|
47
|
+
return uriBase;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
private get urnMap(): ReadonlyMap<string, string> {
|
|
@@ -113,7 +113,8 @@ let navDuration = 2200; /* mutable — shorter for expand changes than for conc
|
|
|
113
113
|
async function loadNeighborTerms() {
|
|
114
114
|
const factory = getFactory();
|
|
115
115
|
const lang = sphereLang.value;
|
|
116
|
-
const uriBase = props.manifest.uriBase
|
|
116
|
+
const uriBase = props.manifest.uriBase;
|
|
117
|
+
if (!uriBase) throw new Error('RelationSphere: manifest.uriBase is required');
|
|
117
118
|
const neighborRegisters = new Set<string>();
|
|
118
119
|
|
|
119
120
|
/* Phase 1: collect which neighbor datasets we need to load */
|
package/src/stores/vocabulary.ts
CHANGED
|
@@ -207,7 +207,8 @@ export const useVocabularyStore = defineStore('vocabulary', () => {
|
|
|
207
207
|
// We do NOT call adapter.extractEdges(concept) here because that would duplicate
|
|
208
208
|
// edges already present in edges.json, causing double-rendering of relations.
|
|
209
209
|
const domainEdges = adapter.extractDomainEdges(concept);
|
|
210
|
-
const uriBase = adapter.manifest?.uriBase
|
|
210
|
+
const uriBase = adapter.manifest?.uriBase;
|
|
211
|
+
if (!uriBase) throw new Error('vocabulary store: manifest.uriBase is required — set uriBase in site-config.yml');
|
|
211
212
|
const uri = conceptUri(concept, registerId, uriBase);
|
|
212
213
|
|
|
213
214
|
const designations: Record<string, string> = {};
|