glossarist 0.4.38 → 0.4.39

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",
3
- "version": "0.4.38",
3
+ "version": "0.4.39",
4
4
  "description": "JavaScript SDK for Glossarist GCR packages — read, write, validate, and manage terminology concepts",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -7,9 +7,26 @@
7
7
  * override for performance.
8
8
  */
9
9
 
10
+ /**
11
+ * Any value that can be the output of `JSON.stringify`. Used as the
12
+ * return type of {@link GlossaristModel.toJSON} so subclasses with
13
+ * non-object JSON output (e.g. `NonVerbalReference.toJSON()` returns
14
+ * a bare string for the no-display case) can override without
15
+ * breaking variance. The object case accepts any object to avoid
16
+ * forcing `<Model>Json` interfaces to declare explicit string index
17
+ * signatures (TS interfaces don't have implicit index signatures).
18
+ */
19
+ export type JsonValue =
20
+ | string
21
+ | number
22
+ | boolean
23
+ | null
24
+ | JsonValue[]
25
+ | object;
26
+
10
27
  export abstract class GlossaristModel {
11
28
  /** Wire-shape serialization. Subclasses MUST override. */
12
- toJSON(): object {
29
+ toJSON(): JsonValue {
13
30
  throw new Error(`${this.constructor.name} must implement toJSON()`);
14
31
  }
15
32
 
@@ -0,0 +1,98 @@
1
+ import { GlossaristModel } from './base.js';
2
+ import { Locality } from './locality.js';
3
+ import type { LocalityJson } from './locality.js';
4
+
5
+ export interface CitationRefJson {
6
+ source?: string | null;
7
+ id?: string | null;
8
+ version?: string | null;
9
+ }
10
+
11
+ export interface CitationJson {
12
+ ref?: CitationRefJson | CitationRef | null;
13
+ locality?: LocalityJson | Locality | null;
14
+ link?: string | null;
15
+ original?: string | null;
16
+ custom_locality?: string | null;
17
+ customLocality?: string | null;
18
+ }
19
+
20
+ export class CitationRef extends GlossaristModel {
21
+ readonly source: string | null;
22
+ readonly id: string | null;
23
+ readonly version: string | null;
24
+
25
+ constructor(data: CitationRefJson = {}) {
26
+ super();
27
+ this.source = data.source ?? null;
28
+ this.id = data.id ?? null;
29
+ this.version = data.version ?? null;
30
+ }
31
+
32
+ override toString(): string {
33
+ const parts: string[] = [];
34
+ if (this.source) parts.push(this.source);
35
+ if (this.id) parts.push(this.id);
36
+ return parts.join(' ');
37
+ }
38
+
39
+ override toJSON(): CitationRefJson {
40
+ const obj: CitationRefJson = {};
41
+ if (this.source != null) obj.source = this.source;
42
+ if (this.id != null) obj.id = this.id;
43
+ if (this.version != null) obj.version = this.version;
44
+ return obj;
45
+ }
46
+
47
+ static override fromJSON(data: CitationRefJson): CitationRef {
48
+ return new CitationRef(data);
49
+ }
50
+ }
51
+
52
+ export class Citation extends GlossaristModel {
53
+ /** Backward-compat alias for {@link CitationRef}. Original codebase
54
+ * exposed the Ref class via `Citation.Ref`; keep that working. */
55
+ static readonly Ref = CitationRef;
56
+
57
+ readonly ref: CitationRef | null;
58
+ readonly locality: Locality | null;
59
+ readonly link: string | null;
60
+ readonly original: string | null;
61
+ readonly customLocality: string | null;
62
+
63
+ constructor(data: CitationJson | null = {}) {
64
+ super();
65
+ const d = data ?? {};
66
+ this.ref = d.ref
67
+ ? d.ref instanceof CitationRef
68
+ ? d.ref
69
+ : new CitationRef(d.ref)
70
+ : null;
71
+ this.locality = d.locality
72
+ ? d.locality instanceof Locality
73
+ ? d.locality
74
+ : new Locality(d.locality)
75
+ : null;
76
+ this.link = d.link ?? null;
77
+ this.original = d.original ?? null;
78
+ this.customLocality = d.custom_locality ?? d.customLocality ?? null;
79
+ }
80
+
81
+ override toString(): string {
82
+ return this.ref ? this.ref.toString() : '';
83
+ }
84
+
85
+ override toJSON(): CitationJson {
86
+ const obj: CitationJson = {};
87
+ if (this.ref != null) obj.ref = this.ref.toJSON();
88
+ if (this.locality != null) obj.locality = this.locality.toJSON();
89
+ if (this.link != null) obj.link = this.link;
90
+ if (this.original != null) obj.original = this.original;
91
+ if (this.customLocality != null) obj.custom_locality = this.customLocality;
92
+ return obj;
93
+ }
94
+
95
+ static override fromJSON(data: CitationJson): Citation {
96
+ return new Citation(data);
97
+ }
98
+ }
@@ -1,7 +1,21 @@
1
1
  import { GlossaristModel } from './base.js';
2
2
 
3
+ export interface ConceptReferenceJson {
4
+ concept_id?: string | null;
5
+ conceptId?: string | null;
6
+ ref_type?: string | null;
7
+ refType?: string | null;
8
+ source?: string | null;
9
+ urn?: string | null;
10
+ }
11
+
3
12
  export class ConceptReference extends GlossaristModel {
4
- constructor(data = {}) {
13
+ readonly conceptId: string | null;
14
+ readonly refType: string | null;
15
+ readonly source: string | null;
16
+ readonly urn: string | null;
17
+
18
+ constructor(data: ConceptReferenceJson = {}) {
5
19
  super();
6
20
  this.conceptId = data.concept_id ?? data.conceptId ?? null;
7
21
  this.refType = data.ref_type ?? data.refType ?? null;
@@ -9,20 +23,20 @@ export class ConceptReference extends GlossaristModel {
9
23
  this.urn = data.urn ?? null;
10
24
  }
11
25
 
12
- get isLocal() {
26
+ get isLocal(): boolean {
13
27
  return this.urn == null && this.source == null;
14
28
  }
15
29
 
16
- get isExternal() {
30
+ get isExternal(): boolean {
17
31
  return !this.isLocal;
18
32
  }
19
33
 
20
- static domain(conceptId) {
34
+ static domain(conceptId: string): ConceptReference {
21
35
  return new ConceptReference({ concept_id: conceptId, ref_type: 'domain' });
22
36
  }
23
37
 
24
- toJSON() {
25
- const obj = {};
38
+ override toJSON(): ConceptReferenceJson {
39
+ const obj: ConceptReferenceJson = {};
26
40
  if (this.conceptId != null) obj.concept_id = this.conceptId;
27
41
  if (this.refType != null) obj.ref_type = this.refType;
28
42
  if (this.source != null) obj.source = this.source;
@@ -30,7 +44,7 @@ export class ConceptReference extends GlossaristModel {
30
44
  return obj;
31
45
  }
32
46
 
33
- static fromJSON(data) {
47
+ static override fromJSON(data: ConceptReferenceJson): ConceptReference {
34
48
  return new ConceptReference(data);
35
49
  }
36
50
  }
@@ -0,0 +1,39 @@
1
+ import { GlossaristModel } from './base.js';
2
+
3
+ export const DESIGNATION_RELATIONSHIP_TYPES: ReadonlyArray<string> = Object.freeze([
4
+ // TBX (ISO 30042) / ISO 12620 term-level relationships
5
+ 'abbreviated_form_for', 'short_form_for',
6
+ ]);
7
+
8
+ export type DesignationRelationshipType = string;
9
+
10
+ export interface DesignationRelationshipJson {
11
+ type?: DesignationRelationshipType | null;
12
+ content?: string | null;
13
+ target?: string | null;
14
+ }
15
+
16
+ export class DesignationRelationship extends GlossaristModel {
17
+ readonly type: DesignationRelationshipType | null;
18
+ readonly content: string | null;
19
+ readonly target: string | null;
20
+
21
+ constructor(data: DesignationRelationshipJson = {}) {
22
+ super();
23
+ this.type = data.type ?? null;
24
+ this.content = data.content ?? null;
25
+ this.target = data.target ?? null;
26
+ }
27
+
28
+ override toJSON(): DesignationRelationshipJson {
29
+ const obj: DesignationRelationshipJson = {};
30
+ if (this.type != null) obj.type = this.type;
31
+ if (this.content != null) obj.content = this.content;
32
+ if (this.target != null) obj.target = this.target;
33
+ return obj;
34
+ }
35
+
36
+ static override fromJSON(data: DesignationRelationshipJson): DesignationRelationship {
37
+ return new DesignationRelationship(data);
38
+ }
39
+ }
@@ -0,0 +1,110 @@
1
+ import { SharedNonVerbalEntity } from './shared-non-verbal-entity.js';
2
+ import type { SharedNonVerbalEntityJson } from './shared-non-verbal-entity.js';
3
+ import { NonVerbalEntity } from './non-verbal-entity.js';
4
+
5
+ export interface FigureImageJson {
6
+ src?: string | null;
7
+ format?: string | null;
8
+ role?: string | null;
9
+ width?: number | string | null;
10
+ height?: number | string | null;
11
+ scale?: number | string | null;
12
+ }
13
+
14
+ export class FigureImage {
15
+ readonly src: string | null;
16
+ readonly format: string | null;
17
+ readonly role: string | null;
18
+ readonly width: number | string | null;
19
+ readonly height: number | string | null;
20
+ readonly scale: number | string | null;
21
+
22
+ constructor(data: FigureImageJson = {}) {
23
+ this.src = data.src ?? null;
24
+ this.format = data.format ?? null;
25
+ this.role = data.role ?? null;
26
+ this.width = data.width ?? null;
27
+ this.height = data.height ?? null;
28
+ this.scale = data.scale ?? null;
29
+ }
30
+
31
+ toJSON(): FigureImageJson {
32
+ const obj: FigureImageJson = {};
33
+ if (this.src != null) obj.src = this.src;
34
+ if (this.format != null) obj.format = this.format;
35
+ if (this.role != null) obj.role = this.role;
36
+ if (this.width != null) obj.width = this.width;
37
+ if (this.height != null) obj.height = this.height;
38
+ if (this.scale != null) obj.scale = this.scale;
39
+ return obj;
40
+ }
41
+
42
+ static fromJSON(data: FigureImageJson): FigureImage {
43
+ return new FigureImage(data);
44
+ }
45
+ }
46
+
47
+ export interface FigureJson extends SharedNonVerbalEntityJson {
48
+ images?: ReadonlyArray<FigureImageJson>;
49
+ subfigures?: ReadonlyArray<FigureJson | Figure>;
50
+ }
51
+
52
+ export class Figure extends SharedNonVerbalEntity {
53
+ protected readonly _rawImages: ReadonlyArray<FigureImageJson | FigureImage>;
54
+ protected readonly _rawSubfigures: ReadonlyArray<FigureJson | Figure>;
55
+ protected _images: ReadonlyArray<FigureImage> | null = null;
56
+ protected _subfigures: ReadonlyArray<Figure> | null = null;
57
+
58
+ constructor(data: FigureJson = {}) {
59
+ super(data);
60
+ this._rawImages = data.images ?? [];
61
+ this._rawSubfigures = data.subfigures ?? [];
62
+ }
63
+
64
+ override rdfClass(): string {
65
+ return 'Figure';
66
+ }
67
+
68
+ get images(): ReadonlyArray<FigureImage> {
69
+ return this._lazy<FigureImage>(
70
+ '_images',
71
+ '_rawImages',
72
+ (i) => (i instanceof FigureImage ? i : new FigureImage(i as FigureImageJson)),
73
+ );
74
+ }
75
+
76
+ get subfigures(): ReadonlyArray<Figure> {
77
+ return this._lazy<Figure>(
78
+ '_subfigures',
79
+ '_rawSubfigures',
80
+ (s) => (s instanceof Figure ? s : new Figure(s as FigureJson)),
81
+ );
82
+ }
83
+
84
+ override findById(targetId: string): this | null {
85
+ if (this.id === targetId) return this;
86
+ for (const sub of this.subfigures) {
87
+ const found = sub.findById(targetId);
88
+ if (found) return found as this;
89
+ }
90
+ return null;
91
+ }
92
+
93
+ override allIds(): string[] {
94
+ const ids = this.id != null ? [this.id] : [];
95
+ return [...ids, ...this.subfigures.flatMap((s) => s.allIds())];
96
+ }
97
+
98
+ override toJSON(): FigureJson {
99
+ const obj = super.toJSON() as FigureJson;
100
+ this._serialize(obj as unknown as Record<string, unknown>, 'images', '_images', '_rawImages');
101
+ this._serialize(obj as unknown as Record<string, unknown>, 'subfigures', '_subfigures', '_rawSubfigures');
102
+ return obj;
103
+ }
104
+
105
+ static override fromJSON(data: FigureJson): Figure {
106
+ return new Figure(data);
107
+ }
108
+ }
109
+
110
+ NonVerbalEntity.register('figure', Figure);
@@ -0,0 +1,36 @@
1
+ import { SharedNonVerbalEntity } from './shared-non-verbal-entity.js';
2
+ import type { SharedNonVerbalEntityJson } from './shared-non-verbal-entity.js';
3
+ import { NonVerbalEntity } from './non-verbal-entity.js';
4
+
5
+ export interface FormulaJson extends SharedNonVerbalEntityJson {
6
+ expression?: string | null;
7
+ notation?: string | null;
8
+ }
9
+
10
+ export class Formula extends SharedNonVerbalEntity {
11
+ readonly expression: string | null;
12
+ readonly notation: string | null;
13
+
14
+ constructor(data: FormulaJson = {}) {
15
+ super(data);
16
+ this.expression = data.expression ?? null;
17
+ this.notation = data.notation ?? null;
18
+ }
19
+
20
+ override rdfClass(): string {
21
+ return 'Formula';
22
+ }
23
+
24
+ override toJSON(): FormulaJson {
25
+ const obj = super.toJSON() as FormulaJson;
26
+ if (this.expression != null) obj.expression = this.expression;
27
+ if (this.notation != null) obj.notation = this.notation;
28
+ return obj;
29
+ }
30
+
31
+ static override fromJSON(data: FormulaJson): Formula {
32
+ return new Formula(data);
33
+ }
34
+ }
35
+
36
+ NonVerbalEntity.register('formula', Formula);
@@ -0,0 +1,48 @@
1
+ import { NonVerbalEntity } from './non-verbal-entity.js';
2
+ import type { NonVerbalEntityJson } from './non-verbal-entity.js';
3
+ import { FigureImage } from './figure.js';
4
+ import type { FigureImageJson } from './figure.js';
5
+
6
+ export const NON_VERBAL_TYPES: ReadonlyArray<string> = Object.freeze([
7
+ 'image', 'table', 'formula',
8
+ ]);
9
+
10
+ export interface NonVerbRepJson extends NonVerbalEntityJson {
11
+ type?: string | null;
12
+ images?: ReadonlyArray<FigureImageJson | FigureImage>;
13
+ }
14
+
15
+ export class NonVerbRep extends NonVerbalEntity {
16
+ readonly type: string | null;
17
+ protected readonly _rawImages: ReadonlyArray<FigureImageJson | FigureImage>;
18
+ protected _images: ReadonlyArray<FigureImage> | null = null;
19
+
20
+ constructor(data: NonVerbRepJson = {}) {
21
+ super(data);
22
+ this.type = data.type ?? null;
23
+ this._rawImages = data.images ?? [];
24
+ }
25
+
26
+ override rdfClass(): string {
27
+ return 'NonVerbalRepresentation';
28
+ }
29
+
30
+ get images(): ReadonlyArray<FigureImage> {
31
+ return this._lazy<FigureImage>(
32
+ '_images',
33
+ '_rawImages',
34
+ (i) => (i instanceof FigureImage ? i : new FigureImage(i as FigureImageJson)),
35
+ );
36
+ }
37
+
38
+ override toJSON(): NonVerbRepJson {
39
+ const obj = super.toJSON() as NonVerbRepJson;
40
+ if (this.type != null) obj.type = this.type;
41
+ this._serialize(obj as unknown as Record<string, unknown>, 'images', '_images', '_rawImages');
42
+ return obj;
43
+ }
44
+
45
+ static override fromJSON(data: NonVerbRepJson): NonVerbRep {
46
+ return new NonVerbRep(data);
47
+ }
48
+ }
@@ -0,0 +1,65 @@
1
+ import { RegistrableModel } from './registrable.js';
2
+ import { ConceptSource } from './concept-source.js';
3
+ import type { ConceptSourceJson } from './concept-source.js';
4
+
5
+ export interface NonVerbalEntityJson {
6
+ caption?: string | null;
7
+ description?: string | null;
8
+ alt?: string | null;
9
+ sources?: ReadonlyArray<ConceptSourceJson | ConceptSource>;
10
+ type?: string | null;
11
+ }
12
+
13
+ export class NonVerbalEntity extends RegistrableModel {
14
+ readonly caption: string | null;
15
+ readonly description: string | null;
16
+ readonly alt: string | null;
17
+ protected readonly _rawSources: ReadonlyArray<ConceptSourceJson | ConceptSource>;
18
+ protected _sources: ReadonlyArray<ConceptSource> | null = null;
19
+
20
+ constructor(data: NonVerbalEntityJson = {}) {
21
+ super();
22
+ this.caption = data.caption ?? null;
23
+ this.description = data.description ?? null;
24
+ this.alt = data.alt ?? null;
25
+ this._rawSources = data.sources ?? [];
26
+ }
27
+
28
+ get sources(): ReadonlyArray<ConceptSource> {
29
+ return this._lazy<ConceptSource>(
30
+ '_sources',
31
+ '_rawSources',
32
+ (s) => (s instanceof ConceptSource ? s : new ConceptSource(s as ConceptSourceJson)),
33
+ );
34
+ }
35
+
36
+ /**
37
+ * Ontology class local-name. Subtypes override to return their specific
38
+ * class (Figure/Table/Formula). Used by the RDF emitter so a new subtype
39
+ * registers without editing the emitter (OCP).
40
+ */
41
+ rdfClass(): string {
42
+ return 'NonVerbalEntity';
43
+ }
44
+
45
+ findById(_targetId: string): NonVerbalEntity | null {
46
+ return null;
47
+ }
48
+
49
+ allIds(): string[] {
50
+ return [];
51
+ }
52
+
53
+ override toJSON(): NonVerbalEntityJson {
54
+ const obj: NonVerbalEntityJson = {};
55
+ if (this.caption != null) obj.caption = this.caption;
56
+ if (this.description != null) obj.description = this.description;
57
+ if (this.alt != null) obj.alt = this.alt;
58
+ this._serialize(obj as unknown as Record<string, unknown>, 'sources', '_sources', '_rawSources');
59
+ return obj;
60
+ }
61
+
62
+ static override fromJSON(data: NonVerbalEntityJson): NonVerbalEntity {
63
+ return NonVerbalEntity.fromData(data);
64
+ }
65
+ }
@@ -0,0 +1,73 @@
1
+ import { RegistrableModel } from './registrable.js';
2
+
3
+ export interface NonVerbalReferenceJson {
4
+ entityId?: string | null;
5
+ entity_id?: string | null;
6
+ ref?: string | null;
7
+ id?: string | null;
8
+ display?: string | null;
9
+ type?: string;
10
+ }
11
+
12
+ /**
13
+ * Polymorphic JSON for NonVerbalReference: either a bare string (the
14
+ * entity id) or an object with `{ ref, display?, type? }`.
15
+ */
16
+ export type NonVerbalReferenceInput =
17
+ | NonVerbalReferenceJson
18
+ | string
19
+ | NonVerbalReference;
20
+
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ type AnyCtor = new (...args: any[]) => any;
23
+
24
+ export class NonVerbalReference extends RegistrableModel {
25
+ readonly entityId: string | null;
26
+ readonly display: string | null;
27
+
28
+ constructor(data: NonVerbalReferenceJson = {}) {
29
+ super();
30
+ this.entityId = data.entityId ?? data.entity_id ?? data.ref ?? data.id ?? null;
31
+ this.display = data.display ?? null;
32
+ }
33
+
34
+ get dedupKey(): [string, string | null] {
35
+ return [this.constructor.name, this.entityId];
36
+ }
37
+
38
+ override toJSON(): NonVerbalReferenceJson | string {
39
+ if (this.display != null) {
40
+ return { ref: this.entityId ?? '', display: this.display };
41
+ }
42
+ return this.entityId ?? '';
43
+ }
44
+
45
+ static override fromJSON<T extends NonVerbalReference>(
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ this: new (...args: any[]) => T,
48
+ data: NonVerbalReferenceInput,
49
+ ): T | NonVerbalReference {
50
+ if (data instanceof NonVerbalReference) return data;
51
+ if (typeof data === 'string') {
52
+ return new (this as new (d: NonVerbalReferenceJson) => T)({ entityId: data });
53
+ }
54
+ const entityId =
55
+ data.entityId ?? data.entity_id ?? data.ref ?? data.id ?? null;
56
+ const display = data.display ?? null;
57
+ const type = data.type;
58
+ const Self = this as unknown as { _registry(): Map<string, AnyCtor> };
59
+ if (type) {
60
+ const Cls = Self._registry().get(type);
61
+ if (Cls) {
62
+ return new (Cls as new (d: NonVerbalReferenceJson) => T)({
63
+ entityId: entityId ?? undefined,
64
+ display: display ?? undefined,
65
+ });
66
+ }
67
+ }
68
+ return new (this as new (d: NonVerbalReferenceJson) => T)({
69
+ entityId: entityId ?? undefined,
70
+ display: display ?? undefined,
71
+ });
72
+ }
73
+ }
@@ -0,0 +1,29 @@
1
+ import { NonVerbalReference } from './non-verbal-reference.js';
2
+ import type { NonVerbalReferenceInput } from './non-verbal-reference.js';
3
+
4
+ export class FigureReference extends NonVerbalReference {
5
+ static override fromJSON(
6
+ data: NonVerbalReferenceInput,
7
+ ): FigureReference | NonVerbalReference {
8
+ return NonVerbalReference.fromJSON(data) as FigureReference | NonVerbalReference;
9
+ }
10
+ }
11
+ NonVerbalReference.register('figure', FigureReference);
12
+
13
+ export class TableReference extends NonVerbalReference {
14
+ static override fromJSON(
15
+ data: NonVerbalReferenceInput,
16
+ ): TableReference | NonVerbalReference {
17
+ return NonVerbalReference.fromJSON(data) as TableReference | NonVerbalReference;
18
+ }
19
+ }
20
+ NonVerbalReference.register('table', TableReference);
21
+
22
+ export class FormulaReference extends NonVerbalReference {
23
+ static override fromJSON(
24
+ data: NonVerbalReferenceInput,
25
+ ): FormulaReference | NonVerbalReference {
26
+ return NonVerbalReference.fromJSON(data) as FormulaReference | NonVerbalReference;
27
+ }
28
+ }
29
+ NonVerbalReference.register('formula', FormulaReference);
@@ -0,0 +1,48 @@
1
+ import { GlossaristModel } from './base.js';
2
+
3
+ /**
4
+ * Per-class registry of named subtypes. The registry is keyed by
5
+ * `type` discriminator and maps to the constructor function. Used by
6
+ * NonVerbalEntity (Figure/Table/Formula), NonVerbalReference
7
+ * (Figure/Table/Formula references), and any future RegistrableModel
8
+ * subclass that needs polymorphic dispatch on a string discriminator.
9
+ *
10
+ * Stored as a WeakMap keyed by the base class function, so each base
11
+ * gets its own namespace (NonVerbalEntity.register does not collide
12
+ * with NonVerbalReference.register).
13
+ *
14
+ * The registry stores constructors as `Function` because TS's strict
15
+ * variance on constructor params makes a typed constraint reject
16
+ * subclasses with narrower param types. The dispatch site casts the
17
+ * result back to the specific subclass.
18
+ */
19
+
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- see docstring.
21
+ type AnyCtor = new (...args: any[]) => any;
22
+
23
+ const _registries = new WeakMap<object, Map<string, AnyCtor>>();
24
+
25
+ export class RegistrableModel extends GlossaristModel {
26
+ static _registry(): Map<string, AnyCtor> {
27
+ const existing = _registries.get(this) ?? new Map();
28
+ if (!_registries.has(this)) _registries.set(this, existing);
29
+ return existing;
30
+ }
31
+
32
+ static register(type: string, cls: AnyCtor): void {
33
+ this._registry().set(type, cls);
34
+ }
35
+
36
+ static fromData<T extends RegistrableModel>(
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- any ctor signature accepted.
38
+ this: new (...args: any[]) => T,
39
+ data: unknown,
40
+ ): T {
41
+ if (data instanceof this) return data;
42
+ const type = (data as { type?: string } | null)?.type;
43
+ const Self = this as unknown as { _registry(): Map<string, AnyCtor> };
44
+ const registry = Self._registry();
45
+ const Cls = type ? (registry.get(type) ?? (this as AnyCtor)) : (this as AnyCtor);
46
+ return new (Cls as new (data: unknown) => T)(data);
47
+ }
48
+ }
@@ -1,7 +1,9 @@
1
1
  import { GlossaristModel } from './base.js';
2
2
  import { ConceptRef } from './concept-ref.js';
3
+ import type { ConceptRefJson } from './concept-ref.js';
4
+ import type { LocalizedString } from './generic-member.js';
3
5
 
4
- export const RELATIONSHIP_TYPES = Object.freeze([
6
+ export const RELATIONSHIP_TYPES: ReadonlyArray<string> = Object.freeze([
5
7
  // Lifecycle (ISO 10241-1)
6
8
  'deprecates', 'deprecated_by', 'supersedes', 'superseded_by',
7
9
  'replaces', 'replaced_by', 'invalidates', 'invalidated_by',
@@ -25,10 +27,9 @@ export const RELATIONSHIP_TYPES = Object.freeze([
25
27
  // Associative (ISO 10241-1 / ISO 25964)
26
28
  'see', 'related_concept', 'related_concept_broader', 'related_concept_narrower',
27
29
  'references',
28
- // ExternalConcept resolution (Glossarist extension — v2 partitive-relation)
29
- // Direction: real concept `provides` substance for an external concept;
30
- // external concept `provided_by` real concept. Stored on the
31
- // ExternalConcept. See TODO.partitive-relation-v2 item 07.
30
+ // ExternalConcept resolution (Glossarist extension). Direction: real
31
+ // concept `provides` substance for an external concept; external concept
32
+ // `provided_by` real concept. Stored on the ExternalConcept.
32
33
  'provides', 'provided_by',
33
34
  // Comparative (ISO 10241-1)
34
35
  'compare', 'contrast',
@@ -38,56 +39,79 @@ export const RELATIONSHIP_TYPES = Object.freeze([
38
39
  'homograph', 'false_friend',
39
40
  ]);
40
41
 
42
+ export type RelationshipType = string;
43
+
44
+ export interface RelatedConceptJson {
45
+ type?: RelationshipType;
46
+ content?: LocalizedString | string | null;
47
+ ref?: ConceptRefJson | ConceptRef | null;
48
+ }
49
+
41
50
  export class RelatedConcept extends GlossaristModel {
42
- constructor(data = {}) {
51
+ readonly type: RelationshipType;
52
+ readonly content: LocalizedString | null;
53
+ readonly ref: ConceptRef | null;
54
+
55
+ constructor(data: RelatedConceptJson = {}) {
43
56
  super();
44
57
  this.type = data.type ?? 'see';
45
- this.content = normalizeContent(data.content);
58
+ this.content = _normalizeContent(data.content);
46
59
  this.ref = data.ref
47
- ? (data.ref instanceof ConceptRef ? data.ref : new ConceptRef(data.ref))
60
+ ? data.ref instanceof ConceptRef
61
+ ? data.ref
62
+ : new ConceptRef(data.ref)
48
63
  : null;
49
64
  }
50
65
 
51
- // Convenience reader: returns the first available string value
52
- // from the localized hash, or null if absent. Mirrors
53
- // PartitiveHyperedge#contentString.
54
- get contentString() {
66
+ /**
67
+ * Convenience reader: returns the first available string value from
68
+ * the localized hash, or null if absent.
69
+ */
70
+ get contentString(): string | null {
55
71
  if (!this.content) return null;
56
72
  const values = Object.values(this.content);
57
- return values.length > 0 ? values[0] : null;
73
+ return values.length > 0 ? (values[0] ?? null) : null;
58
74
  }
59
75
 
60
- toJSON() {
61
- const obj = { type: this.type };
76
+ override toJSON(): RelatedConceptJson {
77
+ const obj: RelatedConceptJson = { type: this.type };
62
78
  if (this.content != null) obj.content = this.content;
63
79
  if (this.ref != null) obj.ref = this.ref.toJSON();
64
80
  return obj;
65
81
  }
66
82
 
67
- static identityOf(value) {
83
+ static identityOf(
84
+ value: {
85
+ type?: string;
86
+ ref?: { source?: string | null; id?: string | null } | null;
87
+ } | null | undefined,
88
+ ): string {
68
89
  const v = value ?? {};
69
- const ref = v?.ref;
90
+ const ref = v.ref;
70
91
  return `${v.type ?? ''}|${ref?.source ?? ''}|${ref?.id ?? ''}`;
71
92
  }
72
93
 
73
- identity() {
94
+ override identity(): string {
74
95
  return RelatedConcept.identityOf(this);
75
96
  }
76
97
 
77
- static fromJSON(data) {
98
+ static override fromJSON(data: RelatedConceptJson): RelatedConcept {
78
99
  return new RelatedConcept(data);
79
100
  }
80
101
  }
81
102
 
82
- // Content is a localized string (language → text). Plain strings on
83
- // input are normalized to `{ default: '...' }` so callers always see
84
- // the same shape. Mirrors PartitiveHyperedge's normalizeContent.
85
- function normalizeContent(value) {
103
+ function _normalizeContent(
104
+ value: LocalizedString | string | null | undefined,
105
+ ): LocalizedString | null {
86
106
  if (value == null) return null;
87
107
  if (typeof value === 'string') return { default: value };
88
108
  if (typeof value === 'object') {
89
- const entries = Object.entries(value).filter(([, v]) => typeof v === 'string');
90
- return entries.length > 0 ? Object.fromEntries(entries) : null;
109
+ const entries = Object.entries(value).filter(
110
+ ([, v]) => typeof v === 'string',
111
+ );
112
+ return entries.length > 0
113
+ ? (Object.fromEntries(entries) as LocalizedString)
114
+ : null;
91
115
  }
92
116
  return null;
93
117
  }
@@ -0,0 +1,36 @@
1
+ import { NonVerbalEntity } from './non-verbal-entity.js';
2
+ import type { NonVerbalEntityJson } from './non-verbal-entity.js';
3
+
4
+ export interface SharedNonVerbalEntityJson extends NonVerbalEntityJson {
5
+ id?: string | null;
6
+ identifier?: string | null;
7
+ }
8
+
9
+ export class SharedNonVerbalEntity extends NonVerbalEntity {
10
+ readonly id: string | null;
11
+ readonly identifier: string | null;
12
+
13
+ constructor(data: SharedNonVerbalEntityJson = {}) {
14
+ super(data);
15
+ this.id = data.id ?? null;
16
+ this.identifier = data.identifier ?? null;
17
+ }
18
+
19
+ override findById(targetId: string): this | null {
20
+ return this.id === targetId ? this : null;
21
+ }
22
+ override allIds(): string[] {
23
+ return this.id != null ? [this.id] : [];
24
+ }
25
+
26
+ override toJSON(): SharedNonVerbalEntityJson {
27
+ const obj = super.toJSON() as SharedNonVerbalEntityJson;
28
+ if (this.id != null) obj.id = this.id;
29
+ if (this.identifier != null) obj.identifier = this.identifier;
30
+ return obj;
31
+ }
32
+
33
+ static override fromJSON(data: SharedNonVerbalEntityJson): SharedNonVerbalEntity {
34
+ return SharedNonVerbalEntity.fromData(data);
35
+ }
36
+ }
@@ -0,0 +1,36 @@
1
+ import { SharedNonVerbalEntity } from './shared-non-verbal-entity.js';
2
+ import type { SharedNonVerbalEntityJson } from './shared-non-verbal-entity.js';
3
+ import { NonVerbalEntity } from './non-verbal-entity.js';
4
+
5
+ export interface TableJson extends SharedNonVerbalEntityJson {
6
+ content?: string | null;
7
+ format?: string | null;
8
+ }
9
+
10
+ export class Table extends SharedNonVerbalEntity {
11
+ readonly content: string | null;
12
+ readonly format: string | null;
13
+
14
+ constructor(data: TableJson = {}) {
15
+ super(data);
16
+ this.content = data.content ?? null;
17
+ this.format = data.format ?? null;
18
+ }
19
+
20
+ override rdfClass(): string {
21
+ return 'Table';
22
+ }
23
+
24
+ override toJSON(): TableJson {
25
+ const obj = super.toJSON() as TableJson;
26
+ if (this.content != null) obj.content = this.content;
27
+ if (this.format != null) obj.format = this.format;
28
+ return obj;
29
+ }
30
+
31
+ static override fromJSON(data: TableJson): Table {
32
+ return new Table(data);
33
+ }
34
+ }
35
+
36
+ NonVerbalEntity.register('table', Table);
@@ -1,68 +0,0 @@
1
- import { GlossaristModel } from './base.js';
2
- import { Locality } from './locality.js';
3
-
4
- export class Citation extends GlossaristModel {
5
- constructor(data = {}) {
6
- super();
7
- const d = data ?? {};
8
-
9
- this.ref = d.ref
10
- ? (d.ref instanceof Citation.Ref ? d.ref : new Citation.Ref(d.ref))
11
- : null;
12
-
13
- this.locality = d.locality
14
- ? (d.locality instanceof Locality ? d.locality : new Locality(d.locality))
15
- : null;
16
- this.link = d.link ?? null;
17
- this.original = d.original ?? null;
18
- this.customLocality = d.custom_locality ?? d.customLocality ?? null;
19
- }
20
-
21
- toString() {
22
- if (this.ref) return this.ref.toString();
23
- return '';
24
- }
25
-
26
- toJSON() {
27
- const obj = {};
28
- if (this.ref != null) obj.ref = this.ref.toJSON();
29
- if (this.locality != null) obj.locality = this.locality.toJSON();
30
- if (this.link != null) obj.link = this.link;
31
- if (this.original != null) obj.original = this.original;
32
- if (this.customLocality != null) obj.custom_locality = this.customLocality;
33
- return obj;
34
- }
35
-
36
- static fromJSON(data) {
37
- return new Citation(data);
38
- }
39
- }
40
-
41
- Citation.Ref = class Ref extends GlossaristModel {
42
- constructor(data = {}) {
43
- super();
44
- const d = data ?? {};
45
- this.source = d.source ?? null;
46
- this.id = d.id ?? null;
47
- this.version = d.version ?? null;
48
- }
49
-
50
- toString() {
51
- const parts = [];
52
- if (this.source) parts.push(this.source);
53
- if (this.id) parts.push(this.id);
54
- return parts.join(' ');
55
- }
56
-
57
- toJSON() {
58
- const obj = {};
59
- if (this.source != null) obj.source = this.source;
60
- if (this.id != null) obj.id = this.id;
61
- if (this.version != null) obj.version = this.version;
62
- return obj;
63
- }
64
-
65
- static fromJSON(data) {
66
- return new Citation.Ref(data);
67
- }
68
- };
@@ -1,27 +0,0 @@
1
- import { GlossaristModel } from './base.js';
2
-
3
- export const DESIGNATION_RELATIONSHIP_TYPES = Object.freeze([
4
- // TBX (ISO 30042) / ISO 12620 term-level relationships
5
- 'abbreviated_form_for', 'short_form_for',
6
- ]);
7
-
8
- export class DesignationRelationship extends GlossaristModel {
9
- constructor(data = {}) {
10
- super();
11
- this.type = data.type ?? null;
12
- this.content = data.content ?? null;
13
- this.target = data.target ?? null;
14
- }
15
-
16
- toJSON() {
17
- const obj = {};
18
- if (this.type != null) obj.type = this.type;
19
- if (this.content != null) obj.content = this.content;
20
- if (this.target != null) obj.target = this.target;
21
- return obj;
22
- }
23
-
24
- static fromJSON(data) {
25
- return new DesignationRelationship(data);
26
- }
27
- }
@@ -1,75 +0,0 @@
1
- import { SharedNonVerbalEntity } from './shared-non-verbal-entity.js';
2
- import { NonVerbalEntity } from './non-verbal-entity.js';
3
-
4
- export class FigureImage {
5
- constructor(data = {}) {
6
- this.src = data.src ?? null;
7
- this.format = data.format ?? null;
8
- this.role = data.role ?? null;
9
- this.width = data.width ?? null;
10
- this.height = data.height ?? null;
11
- this.scale = data.scale ?? null;
12
- }
13
-
14
- toJSON() {
15
- const obj = {};
16
- if (this.src != null) obj.src = this.src;
17
- if (this.format != null) obj.format = this.format;
18
- if (this.role != null) obj.role = this.role;
19
- if (this.width != null) obj.width = this.width;
20
- if (this.height != null) obj.height = this.height;
21
- if (this.scale != null) obj.scale = this.scale;
22
- return obj;
23
- }
24
-
25
- static fromJSON(data) { return new FigureImage(data); }
26
- }
27
-
28
- export class Figure extends SharedNonVerbalEntity {
29
- constructor(data = {}) {
30
- super(data);
31
- this._rawImages = data.images ?? [];
32
- this._rawSubfigures = data.subfigures ?? [];
33
- this._images = null;
34
- this._subfigures = null;
35
- }
36
-
37
- rdfClass() {
38
- return 'Figure';
39
- }
40
-
41
- get images() {
42
- return this._lazy('_images', '_rawImages',
43
- i => i instanceof FigureImage ? i : new FigureImage(i));
44
- }
45
-
46
- get subfigures() {
47
- return this._lazy('_subfigures', '_rawSubfigures',
48
- s => s instanceof Figure ? s : new Figure(s));
49
- }
50
-
51
- findById(targetId) {
52
- if (this.id === targetId) return this;
53
- for (const sub of this.subfigures) {
54
- const found = sub.findById(targetId);
55
- if (found) return found;
56
- }
57
- return null;
58
- }
59
-
60
- allIds() {
61
- const ids = this.id != null ? [this.id] : [];
62
- return [...ids, ...this.subfigures.flatMap(s => s.allIds())];
63
- }
64
-
65
- toJSON() {
66
- const obj = super.toJSON();
67
- this._serialize(obj, 'images', '_images', '_rawImages');
68
- this._serialize(obj, 'subfigures', '_subfigures', '_rawSubfigures');
69
- return obj;
70
- }
71
-
72
- static fromJSON(data) { return new Figure(data); }
73
- }
74
-
75
- NonVerbalEntity.register('figure', Figure);
@@ -1,25 +0,0 @@
1
- import { SharedNonVerbalEntity } from './shared-non-verbal-entity.js';
2
- import { NonVerbalEntity } from './non-verbal-entity.js';
3
-
4
- export class Formula extends SharedNonVerbalEntity {
5
- constructor(data = {}) {
6
- super(data);
7
- this.expression = data.expression ?? null;
8
- this.notation = data.notation ?? null;
9
- }
10
-
11
- rdfClass() {
12
- return 'Formula';
13
- }
14
-
15
- toJSON() {
16
- const obj = super.toJSON();
17
- if (this.expression != null) obj.expression = this.expression;
18
- if (this.notation != null) obj.notation = this.notation;
19
- return obj;
20
- }
21
-
22
- static fromJSON(data) { return new Formula(data); }
23
- }
24
-
25
- NonVerbalEntity.register('formula', Formula);
@@ -1,33 +0,0 @@
1
- import { NonVerbalEntity } from './non-verbal-entity.js';
2
- import { FigureImage } from './figure.js';
3
-
4
- export const NON_VERBAL_TYPES = Object.freeze(['image', 'table', 'formula']);
5
-
6
- export class NonVerbRep extends NonVerbalEntity {
7
- constructor(data = {}) {
8
- super(data);
9
- this.type = data.type ?? null;
10
- this._rawImages = data.images ?? [];
11
- this._images = null;
12
- }
13
-
14
- rdfClass() {
15
- return 'NonVerbalRepresentation';
16
- }
17
-
18
- get images() {
19
- return this._lazy('_images', '_rawImages',
20
- i => i instanceof FigureImage ? i : new FigureImage(i));
21
- }
22
-
23
- toJSON() {
24
- const obj = super.toJSON();
25
- if (this.type != null) obj.type = this.type;
26
- this._serialize(obj, 'images', '_images', '_rawImages');
27
- return obj;
28
- }
29
-
30
- static fromJSON(data) {
31
- return new NonVerbRep(data);
32
- }
33
- }
@@ -1,46 +0,0 @@
1
- import { RegistrableModel } from './registrable.js';
2
- import { ConceptSource } from './concept-source.js';
3
-
4
- export class NonVerbalEntity extends RegistrableModel {
5
- constructor(data = {}) {
6
- super();
7
- this.caption = data.caption ?? null;
8
- this.description = data.description ?? null;
9
- this.alt = data.alt ?? null;
10
- this._rawSources = data.sources ?? [];
11
- this._sources = null;
12
- }
13
-
14
- get sources() {
15
- return this._lazy('_sources', '_rawSources',
16
- s => s instanceof ConceptSource ? s : new ConceptSource(s));
17
- }
18
-
19
- // Ontology class local-name. Subtypes override to return their specific
20
- // class (Figure/Table/Formula). Used by the RDF emitter so a new subtype
21
- // registers without editing the emitter (OCP).
22
- rdfClass() {
23
- return 'NonVerbalEntity';
24
- }
25
-
26
- findById(_targetId) {
27
- return null;
28
- }
29
-
30
- allIds() {
31
- return [];
32
- }
33
-
34
- toJSON() {
35
- const obj = {};
36
- if (this.caption != null) obj.caption = this.caption;
37
- if (this.description != null) obj.description = this.description;
38
- if (this.alt != null) obj.alt = this.alt;
39
- this._serialize(obj, 'sources', '_sources', '_rawSources');
40
- return obj;
41
- }
42
-
43
- static fromJSON(data) {
44
- return NonVerbalEntity.fromData(data);
45
- }
46
- }
@@ -1,35 +0,0 @@
1
- import { RegistrableModel } from './registrable.js';
2
-
3
- export class NonVerbalReference extends RegistrableModel {
4
- constructor(data = {}) {
5
- super();
6
- this.entityId = data.entityId ?? data.entity_id ?? data.ref ?? data.id ?? null;
7
- this.display = data.display ?? null;
8
- }
9
-
10
- get dedupKey() {
11
- return [this.constructor.name, this.entityId];
12
- }
13
-
14
- toJSON() {
15
- if (this.display != null) {
16
- return { ref: this.entityId, display: this.display };
17
- }
18
- return this.entityId;
19
- }
20
-
21
- static fromJSON(data) {
22
- if (data instanceof NonVerbalReference) return data;
23
- if (typeof data === 'string') {
24
- return new NonVerbalReference({ entityId: data });
25
- }
26
- const entityId = data.entityId ?? data.entity_id ?? data.ref ?? data.id ?? null;
27
- const display = data.display ?? null;
28
- const type = data.type;
29
- if (type && this._registry().has(type)) {
30
- const Cls = this._registry().get(type);
31
- return new Cls({ entityId, display });
32
- }
33
- return new NonVerbalReference({ entityId, display });
34
- }
35
- }
@@ -1,16 +0,0 @@
1
- import { NonVerbalReference } from './non-verbal-reference.js';
2
-
3
- export class FigureReference extends NonVerbalReference {
4
- static fromJSON(data) { return NonVerbalReference.fromJSON(data); }
5
- }
6
- NonVerbalReference.register('figure', FigureReference);
7
-
8
- export class TableReference extends NonVerbalReference {
9
- static fromJSON(data) { return NonVerbalReference.fromJSON(data); }
10
- }
11
- NonVerbalReference.register('table', TableReference);
12
-
13
- export class FormulaReference extends NonVerbalReference {
14
- static fromJSON(data) { return NonVerbalReference.fromJSON(data); }
15
- }
16
- NonVerbalReference.register('formula', FormulaReference);
@@ -1,25 +0,0 @@
1
- import { GlossaristModel } from './base.js';
2
-
3
- const _registries = new WeakMap();
4
-
5
- export class RegistrableModel extends GlossaristModel {
6
- static _registry() {
7
- let map = _registries.get(this);
8
- if (!map) {
9
- map = new Map();
10
- _registries.set(this, map);
11
- }
12
- return map;
13
- }
14
-
15
- static register(type, cls) {
16
- this._registry().set(type, cls);
17
- }
18
-
19
- static fromData(data) {
20
- if (data instanceof this) return data;
21
- const type = data?.type;
22
- const Cls = type ? this._registry().get(type) ?? this : this;
23
- return new Cls(data);
24
- }
25
- }
@@ -1,28 +0,0 @@
1
- import { NonVerbalEntity } from './non-verbal-entity.js';
2
-
3
- export class SharedNonVerbalEntity extends NonVerbalEntity {
4
- constructor(data = {}) {
5
- super(data);
6
- this.id = data.id ?? null;
7
- this.identifier = data.identifier ?? null;
8
- }
9
-
10
- findById(targetId) {
11
- return this.id === targetId ? this : null;
12
- }
13
-
14
- allIds() {
15
- return this.id != null ? [this.id] : [];
16
- }
17
-
18
- toJSON() {
19
- const obj = super.toJSON();
20
- if (this.id != null) obj.id = this.id;
21
- if (this.identifier != null) obj.identifier = this.identifier;
22
- return obj;
23
- }
24
-
25
- static fromJSON(data) {
26
- return SharedNonVerbalEntity.fromData(data);
27
- }
28
- }
@@ -1,25 +0,0 @@
1
- import { SharedNonVerbalEntity } from './shared-non-verbal-entity.js';
2
- import { NonVerbalEntity } from './non-verbal-entity.js';
3
-
4
- export class Table extends SharedNonVerbalEntity {
5
- constructor(data = {}) {
6
- super(data);
7
- this.content = data.content ?? null;
8
- this.format = data.format ?? null;
9
- }
10
-
11
- rdfClass() {
12
- return 'Table';
13
- }
14
-
15
- toJSON() {
16
- const obj = super.toJSON();
17
- if (this.content != null) obj.content = this.content;
18
- if (this.format != null) obj.format = this.format;
19
- return obj;
20
- }
21
-
22
- static fromJSON(data) { return new Table(data); }
23
- }
24
-
25
- NonVerbalEntity.register('table', Table);