@glossarist/concept-browser 0.7.87 → 0.7.88

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glossarist/concept-browser",
3
- "version": "0.7.87",
3
+ "version": "0.7.88",
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": {
@@ -103,4 +103,33 @@ describe('UriRouter', () => {
103
103
  expect(UriRouter.parseUri('https://example.com/other')).toBeNull();
104
104
  });
105
105
  });
106
+
107
+ describe('SSOT: UriRouter.buildConceptUri is the single concept-URI constructor', () => {
108
+ // Per AUDIT-2026-07-29 P1-1: ConceptIdentity, the conceptUri()
109
+ // bridge, and UriRouter.buildConceptUri must all funnel through
110
+ // UriRouter.buildConceptUri. No inline template literals for
111
+ // `${uriBase}/${registerId}/concept/${id}` outside UriRouter.
112
+
113
+ it('UriRouter.buildConceptUri constructs the canonical URI', () => {
114
+ expect(UriRouter.buildConceptUri('https://x.org', 'vim', '1.3'))
115
+ .toBe('https://x.org/vim/concept/1.3');
116
+ });
117
+
118
+ it('UriRouter.buildDomainUri constructs the canonical domain URI', () => {
119
+ expect(UriRouter.buildDomainUri('https://x.org', 'vim', 'physics'))
120
+ .toBe('https://x.org/vim/domain/physics');
121
+ });
122
+
123
+ it('ConceptIdentity.uri delegates to UriRouter.buildConceptUri', async () => {
124
+ const { ConceptIdentity } = await import('../adapters/concept-identity');
125
+ const id = new ConceptIdentity('1.3', 'vim', 'https://x.org');
126
+ expect(id.uri).toBe(UriRouter.buildConceptUri('https://x.org', 'vim', '1.3'));
127
+ });
128
+
129
+ it('ConceptIdentity.domainUri delegates to UriRouter.buildDomainUri', async () => {
130
+ const { ConceptIdentity } = await import('../adapters/concept-identity');
131
+ const id = new ConceptIdentity('1.3', 'vim', 'https://x.org');
132
+ expect(id.domainUri('physics')).toBe(UriRouter.buildDomainUri('https://x.org', 'vim', 'physics'));
133
+ });
134
+ });
106
135
  });
@@ -137,7 +137,7 @@ export class GraphDataSource {
137
137
  if (lc?.domain) {
138
138
  edges.push({
139
139
  source: sourceUri,
140
- target: `${this.uriBase}/${this.registerId}/domain/${slugify(lc.domain)}`,
140
+ target: UriRouter.buildDomainUri(this.uriBase, this.registerId, slugify(lc.domain)),
141
141
  type: 'domain',
142
142
  label: lc.domain,
143
143
  register: this.registerId,
@@ -1,5 +1,4 @@
1
1
  import type { Manifest } from './types';
2
- import { ConceptIdentity } from './concept-identity';
3
2
 
4
3
  // ── URI pattern matching ────────────────────────────────────────────────────
5
4
 
@@ -106,11 +105,16 @@ export class UriRouter {
106
105
  return `${uriBase}/${registerId}/concept/${conceptId}`;
107
106
  }
108
107
 
109
- /** Construct a canonical URI for a concept. */
108
+ /** Construct a canonical domain URI from components. */
109
+ static buildDomainUri(uriBase: string, registerId: string, domainSlug: string): string {
110
+ return `${uriBase}/${registerId}/domain/${domainSlug}`;
111
+ }
112
+
113
+ /** Construct a canonical URI for a concept from its register-id + concept-id. */
110
114
  buildUri(registerId: string, conceptId: string): string {
111
115
  const info = this.registerMap.get(registerId);
112
116
  const uriBase = info?.uriBase ?? '';
113
- return new ConceptIdentity(conceptId, registerId, uriBase).uri;
117
+ return UriRouter.buildConceptUri(uriBase, registerId, conceptId);
114
118
  }
115
119
 
116
120
  getRegisteredIds(): string[] {
@@ -1,4 +1,5 @@
1
1
  import { InvalidConceptIdentityError, InvalidConceptUriError } from '../errors';
2
+ import { UriRouter } from './UriRouter';
2
3
 
3
4
  export interface ConceptIdentityParts {
4
5
  readonly localId: string;
@@ -24,7 +25,10 @@ export class ConceptIdentity implements ConceptIdentityParts {
24
25
  { localId, registerId, uriBase },
25
26
  );
26
27
  }
27
- this._uri = `${uriBase}/${registerId}/concept/${localId}`;
28
+ // Single SSOT for concept URI construction: UriRouter.buildConceptUri.
29
+ // ConceptIdentity, the conceptUri() bridge, and UriRouter.buildConceptUri
30
+ // all funnel here — no inline template literals scattered across files.
31
+ this._uri = UriRouter.buildConceptUri(uriBase, registerId, localId);
28
32
  this._slug = localId;
29
33
  this._path = `${registerId}/concepts/${localId}`;
30
34
  }
@@ -52,7 +56,7 @@ export class ConceptIdentity implements ConceptIdentityParts {
52
56
  }
53
57
 
54
58
  domainUri(domainSlug: string): string {
55
- return `${this.uriBase}/${this.registerId}/domain/${domainSlug}`;
59
+ return UriRouter.buildDomainUri(this.uriBase, this.registerId, domainSlug);
56
60
  }
57
61
 
58
62
  static fromUri(uri: string): ConceptIdentity {
@@ -224,6 +224,12 @@ declare module 'glossarist' {
224
224
  // Re-declare the same exports under the 'glossarist/models' subpath so
225
225
  // deep-import callers can use them. Upstream's d.ts is stale on this
226
226
  // subpath too.
227
+ //
228
+ // The duplication between the 'glossarist' block above and this block
229
+ // is forced by upstream's broken d.ts (PR glossarist/glossarist-js#31).
230
+ // We cannot `export import` from a local namespace because TypeScript
231
+ // requires `declare module` blocks to be self-contained. When upstream
232
+ // ships proper d.ts, DELETE this entire file.
227
233
  declare module 'glossarist/models' {
228
234
  export type Completeness = 'complete' | 'partial';
229
235
  export const COMPLETENESS: { readonly COMPLETE: 'complete'; readonly PARTIAL: 'partial' };
@@ -252,23 +258,8 @@ declare module 'glossarist/models' {
252
258
  export function isValidMultiplicity(value: unknown): value is Multiplicity;
253
259
  export function multiplicityFromPair(presence: PartitivePresence, count: PartitiveCount): Multiplicity;
254
260
  export function pairFromMultiplicity(name: Multiplicity): { presence: PartitivePresence; count: PartitiveCount };
255
-
256
- export class TypeSharedPlurality extends GlossaristModel {
257
- constructor(data?: {
258
- isShared?: boolean;
259
- is_shared?: boolean;
260
- isUncertain?: boolean;
261
- is_uncertain?: boolean;
262
- sharedType?: ConceptRef;
263
- shared_type?: ConceptRef;
264
- });
265
- readonly isShared: boolean;
266
- readonly isUncertain: boolean;
267
- readonly sharedType: ConceptRef | null;
268
- hasSharedType(): boolean;
269
- toJSON(): { is_shared: boolean; is_uncertain?: boolean; shared_type?: ReturnType<ConceptRef['toJSON']> };
270
- static fromJSON(data: Record<string, unknown>): TypeSharedPlurality;
271
- }
261
+ // (TypeSharedPlurality removed — superseded by presence/count on
262
+ // PartitiveMember per MECE refactor. Was dead code.)
272
263
 
273
264
  // ── PartitiveRelation (glossarist 0.4.26 — MECE presence × count) ─
274
265