@glossarist/concept-browser 0.7.63 → 0.7.64
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 +6 -4
- 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.64",
|
|
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)
|
|
@@ -375,7 +376,8 @@ function buildRefMaps(config, registerCache) {
|
|
|
375
376
|
const xref = config.crossReferences || {};
|
|
376
377
|
if (xref.refPrefixMap) Object.assign(refPrefixMap, xref.refPrefixMap);
|
|
377
378
|
|
|
378
|
-
const uriBase = config.uriBase
|
|
379
|
+
const uriBase = config.uriBase;
|
|
380
|
+
if (!uriBase) throw new Error('site-config.yml: uriBase is required');
|
|
379
381
|
return { patternIndex, refPrefixMap, uriBase, register: null };
|
|
380
382
|
}
|
|
381
383
|
|
|
@@ -1617,7 +1619,7 @@ console.log('Emitted vocabulary graph: data/_vocab.ttl');
|
|
|
1617
1619
|
|
|
1618
1620
|
const contributors = config.contributors ?? [];
|
|
1619
1621
|
if (contributors.length > 0) {
|
|
1620
|
-
fs.writeFileSync(path.join(DATA, 'agents.ttl'), await buildAgentsTurtle(contributors));
|
|
1622
|
+
fs.writeFileSync(path.join(DATA, 'agents.ttl'), await buildAgentsTurtle(contributors, refMaps.uriBase + '/agent'));
|
|
1621
1623
|
console.log(`Emitted agents graph: data/agents.ttl (${contributors.length} contributors)`);
|
|
1622
1624
|
}
|
|
1623
1625
|
|
|
@@ -1626,7 +1628,7 @@ for (const ds of registry) {
|
|
|
1626
1628
|
const bibPath = path.join(DATA, ds.id, 'bibliography.json');
|
|
1627
1629
|
if (fs.existsSync(bibPath)) {
|
|
1628
1630
|
const bibJson = JSON.parse(fs.readFileSync(bibPath, 'utf8'));
|
|
1629
|
-
const bibTtl = await buildBibliographyTurtle(ds.id, bibJson);
|
|
1631
|
+
const bibTtl = await buildBibliographyTurtle(ds.id, bibJson, refMaps.uriBase);
|
|
1630
1632
|
fs.writeFileSync(path.join(DATA, ds.id, 'bib.ttl'), bibTtl);
|
|
1631
1633
|
}
|
|
1632
1634
|
}
|
|
@@ -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> = {};
|