glossarist 0.4.36 → 0.4.38

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 (29) hide show
  1. package/package.json +1 -1
  2. package/src/models/abstract-hyperedge.ts +164 -0
  3. package/src/models/bibliography-data.ts +64 -0
  4. package/src/models/{bibliography-entry.js → bibliography-entry.ts} +18 -4
  5. package/src/models/concept-system.ts +152 -0
  6. package/src/models/detailed-definition.ts +92 -0
  7. package/src/models/generic-hyperedge.ts +72 -0
  8. package/src/models/generic-member.ts +80 -0
  9. package/src/models/grammar-info.ts +78 -0
  10. package/src/models/{hyperedge-index.js → hyperedge-index.ts} +32 -18
  11. package/src/models/{hyperedge-member.js → hyperedge-member.ts} +38 -26
  12. package/src/models/{hyperedge-registry.js → hyperedge-registry.ts} +59 -29
  13. package/src/models/{locality.js → locality.ts} +16 -4
  14. package/src/models/localized-string.ts +32 -0
  15. package/src/models/partitive-hyperedge.ts +90 -0
  16. package/src/models/{partitive-member.js → partitive-member.ts} +18 -17
  17. package/src/models/{pronunciation.js → pronunciation.ts} +18 -4
  18. package/src/models/sequential-hyperedge.ts +70 -0
  19. package/src/models/{sequential-member.js → sequential-member.ts} +6 -3
  20. package/src/models/abstract-hyperedge.js +0 -119
  21. package/src/models/bibliography-data.js +0 -48
  22. package/src/models/concept-system.js +0 -117
  23. package/src/models/detailed-definition.js +0 -59
  24. package/src/models/generic-hyperedge.js +0 -55
  25. package/src/models/generic-member.js +0 -82
  26. package/src/models/grammar-info.js +0 -40
  27. package/src/models/localized-string.js +0 -19
  28. package/src/models/partitive-hyperedge.js +0 -91
  29. package/src/models/sequential-hyperedge.js +0 -56
@@ -1,7 +1,21 @@
1
1
  import { GlossaristModel } from './base.js';
2
2
 
3
+ export interface PronunciationJson {
4
+ content?: string | null;
5
+ language?: string | null;
6
+ script?: string | null;
7
+ system?: string | null;
8
+ country?: string | null;
9
+ }
10
+
3
11
  export class Pronunciation extends GlossaristModel {
4
- constructor(data = {}) {
12
+ readonly content: string | null;
13
+ readonly language: string | null;
14
+ readonly script: string | null;
15
+ readonly system: string | null;
16
+ readonly country: string | null;
17
+
18
+ constructor(data: PronunciationJson = {}) {
5
19
  super();
6
20
  this.content = data.content ?? null;
7
21
  this.language = data.language ?? null;
@@ -10,8 +24,8 @@ export class Pronunciation extends GlossaristModel {
10
24
  this.country = data.country ?? null;
11
25
  }
12
26
 
13
- toJSON() {
14
- const obj = {};
27
+ override toJSON(): PronunciationJson {
28
+ const obj: PronunciationJson = {};
15
29
  if (this.content != null) obj.content = this.content;
16
30
  if (this.language != null) obj.language = this.language;
17
31
  if (this.script != null) obj.script = this.script;
@@ -20,7 +34,7 @@ export class Pronunciation extends GlossaristModel {
20
34
  return obj;
21
35
  }
22
36
 
23
- static fromJSON(data) {
37
+ static override fromJSON(data: PronunciationJson): Pronunciation {
24
38
  return new Pronunciation(data);
25
39
  }
26
40
  }
@@ -0,0 +1,70 @@
1
+ // SequentialHyperedge — an ISO 12620 A.6.3 / ISO 704:2022 §5.5.5
2
+ // sequential relation connecting a comprehensive concept to two or
3
+ // more subordinate concepts in an ORDERED sequence (temporal,
4
+ // spatial, causal, developmental).
5
+ //
6
+ // Mirror of PartitiveHyperedge and GenericHyperedge. Member array
7
+ // order IS significant — reversing the array reverses the sequence.
8
+ // Use cases: manufacturing process steps, biological life cycles,
9
+ // software release lifecycles, supply chain stages, geological eras.
10
+
11
+ import { AbstractHyperedge } from './abstract-hyperedge.js';
12
+ import type { AbstractHyperedgeJson } from './abstract-hyperedge.js';
13
+ import { SequentialMember } from './sequential-member.js';
14
+ import type { SequentialMemberJson } from './sequential-member.js';
15
+ import { HyperedgeRegistry } from './hyperedge-registry.js';
16
+
17
+ export interface SequentialHyperedgeJson extends AbstractHyperedgeJson {
18
+ members: ReadonlyArray<SequentialMemberJson | SequentialMember>;
19
+ }
20
+
21
+ export class SequentialHyperedge extends AbstractHyperedge {
22
+ // Per-class metadata block — the OCP contract.
23
+ static wireKey = 'sequential_relations' as const;
24
+ static typeTag = 'sequential_relation' as const;
25
+ static rdfType = 'gloss:SequentialRelation' as const;
26
+ static memberClass = SequentialMember;
27
+ static v1WireKeys: ReadonlyArray<string> = [];
28
+ static kindLabel = 'SEQ';
29
+
30
+ declare readonly members: ReadonlyArray<SequentialMember>;
31
+
32
+ constructor(data: Partial<SequentialHyperedgeJson> = {}) {
33
+ super({
34
+ comprehensive: data.comprehensive ?? {},
35
+ members: data.members ?? [],
36
+ completeness: data.completeness ?? 'complete',
37
+ criterion: data.criterion,
38
+ sources: data.sources,
39
+ notes: data.notes,
40
+ status: data.status,
41
+ });
42
+ this.members = (Array.isArray(data.members) ? data.members : []).map((m) =>
43
+ m instanceof SequentialMember ? m : new SequentialMember(m as SequentialMemberJson),
44
+ );
45
+ }
46
+
47
+ static identityOf(
48
+ value: {
49
+ comprehensive?: { source?: string | null; id?: string | null } | null;
50
+ members?: ReadonlyArray<{ ref?: { source?: string | null; id?: string | null } | null } | null>;
51
+ } | null | undefined,
52
+ ): string {
53
+ const v = value ?? {};
54
+ const c = v.comprehensive ?? {};
55
+ // For sequential, member order IS significant — do NOT sort.
56
+ const memberKeys = (v.members ?? []).map((m) => {
57
+ const r = m?.ref ?? (m as { source?: string; id?: string } | null) ?? {};
58
+ return `${r.source ?? ''}:${r.id ?? ''}`;
59
+ });
60
+ return `${c.source ?? ''}:${c.id ?? ''}|${memberKeys.join('|')}`;
61
+ }
62
+
63
+ static override fromJSON(data: SequentialHyperedgeJson): SequentialHyperedge {
64
+ return new SequentialHyperedge(data);
65
+ }
66
+ }
67
+
68
+ export default SequentialHyperedge;
69
+
70
+ HyperedgeRegistry.register(SequentialHyperedge);
@@ -1,13 +1,16 @@
1
1
  // SequentialMember — one member of a SequentialHyperedge. Inherits
2
2
  // the MECE shape from HyperedgeMember. Distinct leaf class for type
3
3
  // safety. Member array order IS significant for sequential hyperedges.
4
- //
5
- // See docs/design/abstract-nary-relation.md (concept-model repo).
6
4
 
7
5
  import { HyperedgeMember } from './hyperedge-member.js';
6
+ import type { HyperedgeMemberJson } from './hyperedge-member.js';
7
+
8
+ export type SequentialMemberJson = HyperedgeMemberJson;
8
9
 
9
10
  export class SequentialMember extends HyperedgeMember {
10
- // Inherits all attributes and validations from HyperedgeMember.
11
+ static override fromJSON(data: SequentialMemberJson): SequentialMember {
12
+ return new SequentialMember(data);
13
+ }
11
14
  }
12
15
 
13
16
  export default SequentialMember;
@@ -1,119 +0,0 @@
1
- // AbstractHyperedge — shared base for directed 1→N hyperedges
2
- // relations (PartitiveHyperedge, GenericHyperedge, future
3
- // SequentialRelation, AssociativeRelation).
4
- //
5
- // Carries the common shape:
6
- // - comprehensive: ConceptRef
7
- // - members: 2+ MECE dimensioned concept refs (≥2 per ISO 704)
8
- // - completeness: complete | partial
9
- // - criterion: localized subdivision criterion (ISO 12620)
10
- // - sources / notes / status: administrative metadata
11
- //
12
- // Concrete leaves add type-specific fields if needed (currently none).
13
- // See docs/design/abstract-hyperedge.md (concept-model repo).
14
-
15
- import { GlossaristModel } from './base.js';
16
- import { ConceptRef } from './concept-ref.js';
17
- import {
18
- COMPLETENESS,
19
- DEFAULT_COMPLETENESS,
20
- isValidCompleteness,
21
- } from './completeness.js';
22
-
23
- export class AbstractHyperedge extends GlossaristModel {
24
- constructor(data = {}) {
25
- super();
26
- this.comprehensive = _ensureComprehensive(data.comprehensive);
27
- this.members = _ensureMembers(data.members, this.constructor.name);
28
- this.completeness = _resolveCompleteness(data.completeness);
29
- this.criterion = _normalizeCriterion(data.criterion);
30
- this.sources = Array.isArray(data.sources) ? [...data.sources] : [];
31
- this.notes = data.notes ?? null;
32
- this.status = data.status ?? null;
33
-
34
- _assertNoSelfLoop(this.comprehensive, this.members);
35
- }
36
-
37
- get isComplete() { return this.completeness === COMPLETENESS.COMPLETE; }
38
- get isPartial() { return this.completeness === COMPLETENESS.PARTIAL; }
39
- get isCoordinate() { return this.members.length >= 2; }
40
-
41
- hasCriterion() { return this.criterion != null; }
42
-
43
- identity() {
44
- return this.constructor.identityOf(this);
45
- }
46
-
47
- toJSON() {
48
- const obj = {
49
- comprehensive: this.comprehensive.toJSON(),
50
- members: this.members.map(m => m.toJSON()),
51
- completeness: this.completeness,
52
- };
53
- if (this.criterion != null) obj.criterion = { ...this.criterion };
54
- if (this.sources.length > 0) obj.sources = [...this.sources];
55
- if (this.notes != null) obj.notes = { ...this.notes };
56
- if (this.status != null) obj.status = this.status;
57
- return obj;
58
- }
59
- }
60
-
61
- function _ensureComprehensive(value) {
62
- const ref = value instanceof ConceptRef ? value : new ConceptRef(value ?? {});
63
- if (!ref.source && !ref.id && !ref.text) {
64
- throw new Error(
65
- 'AbstractHyperedge.comprehensive must be a non-empty ConceptReference ' +
66
- '(source, id, or text required)',
67
- );
68
- }
69
- return ref;
70
- }
71
-
72
- function _ensureMembers(values, typeName) {
73
- if (!Array.isArray(values)) {
74
- throw new Error(
75
- `${typeName}.members must be an array (got ${typeof values})`,
76
- );
77
- }
78
- if (values.length < 2) {
79
- throw new Error(
80
- `${typeName} requires ≥2 partitives (ISO 704); got ${values.length}`,
81
- );
82
- }
83
- return values;
84
- }
85
-
86
- function _resolveCompleteness(value) {
87
- const c = value ?? DEFAULT_COMPLETENESS;
88
- if (!isValidCompleteness(c)) {
89
- throw new Error(
90
- `${c === value ? 'completeness' : 'completeness'} has invalid value ${JSON.stringify(value)}`,
91
- );
92
- }
93
- return c;
94
- }
95
-
96
- function _normalizeCriterion(value) {
97
- if (value == null) return null;
98
- if (typeof value === 'string') {
99
- return value.length > 0 ? { default: value } : null;
100
- }
101
- if (typeof value === 'object') {
102
- const entries = Object.entries(value).filter(([, v]) => typeof v === 'string');
103
- return entries.length > 0 ? Object.fromEntries(entries) : null;
104
- }
105
- return null;
106
- }
107
-
108
- function _assertNoSelfLoop(comprehensive, members) {
109
- const cKey = `${comprehensive.source ?? ''}:${comprehensive.id ?? ''}`;
110
- for (const m of members) {
111
- const ref = m.ref;
112
- const pKey = `${ref?.source ?? ''}:${ref?.id ?? ''}`;
113
- if (pKey === cKey && (ref?.source || ref?.id)) {
114
- throw new Error(
115
- 'Relation.members cannot include the comprehensive (no self-loops)',
116
- );
117
- }
118
- }
119
- }
@@ -1,48 +0,0 @@
1
- import * as yaml from 'js-yaml';
2
- import { GlossaristModel } from './base.js';
3
- import { BibliographyEntry } from './bibliography-entry.js';
4
-
5
- export class BibliographyData extends GlossaristModel {
6
- constructor(data = {}) {
7
- super();
8
- let entriesData;
9
- if (Array.isArray(data)) {
10
- entriesData = data;
11
- } else {
12
- entriesData = data?.bibliography ?? data?.entries ?? [];
13
- }
14
- this._rawEntries = Array.isArray(entriesData) ? entriesData : [];
15
- this._entries = null;
16
- }
17
-
18
- get entries() {
19
- return this._lazy('_entries', '_rawEntries',
20
- e => e instanceof BibliographyEntry ? e : new BibliographyEntry(e));
21
- }
22
-
23
- find(id) {
24
- return this.entries.find(e => e.id === id) ?? null;
25
- }
26
-
27
- get keys() {
28
- return this.entries.map(e => e.id);
29
- }
30
-
31
- toJSON() {
32
- if (this.entries.length === 0) return { bibliography: [] };
33
- return { bibliography: this.entries.map(e => e.toJSON()) };
34
- }
35
-
36
- toYAML() {
37
- return yaml.dump(this.toJSON());
38
- }
39
-
40
- static fromYAML(yamlString) {
41
- const parsed = yaml.load(yamlString);
42
- return new BibliographyData(parsed ?? {});
43
- }
44
-
45
- static fromJSON(data) {
46
- return new BibliographyData(data);
47
- }
48
- }
@@ -1,117 +0,0 @@
1
- // ConceptSystem — first-class concept system entity per ISO 12620 A.7.
2
- //
3
- // Promotes the previously-implicit concept system (domains + tags +
4
- // relation graph) to an explicit first-class entity. Per-file at
5
- // `concept-systems/<id>.yaml`.
6
- //
7
- // See docs/design/concept-systems.md (concept-model repo).
8
-
9
- import { GlossaristModel } from './base.js';
10
- import { ConceptRef } from './concept-ref.js';
11
- import {
12
- CONCEPT_SYSTEM_TYPE,
13
- CONCEPT_SYSTEM_TYPE_VALUES,
14
- isValidConceptSystemType,
15
- } from './concept-system-type.js';
16
-
17
- export class ConceptSystem extends GlossaristModel {
18
- constructor(data = {}) {
19
- super();
20
- this.id = _ensureNonEmptyString(data.id, 'id');
21
- this.name = _ensureName(data.name);
22
- this.type = _resolveType(data.type);
23
- this.domain = data.domain ?? null;
24
- this.purpose = data.purpose ?? null;
25
- this.members = _ensureMembers(data.members);
26
- this.hyperedges = Array.isArray(data.hyperedges) ? [...data.hyperedges] : [];
27
- this.rootConcepts = Array.isArray(data.rootConcepts)
28
- ? data.rootConcepts.map(c => c instanceof ConceptRef ? c : new ConceptRef(c))
29
- : [];
30
- this.sources = Array.isArray(data.sources) ? [...data.sources] : [];
31
- this.status = data.status ?? null;
32
- }
33
-
34
- get isGeneric() { return this.type === CONCEPT_SYSTEM_TYPE.GENERIC; }
35
- get isPartitive() { return this.type === CONCEPT_SYSTEM_TYPE.PARTITIVE; }
36
- get isSequential() { return this.type === CONCEPT_SYSTEM_TYPE.SEQUENTIAL; }
37
- get isAssociative() { return this.type === CONCEPT_SYSTEM_TYPE.ASSOCIATIVE; }
38
- get isMixed() { return this.type === CONCEPT_SYSTEM_TYPE.MIXED; }
39
-
40
- hasHyperedge(edgeId) {
41
- return this.hyperedges.includes(edgeId);
42
- }
43
-
44
- hasMember(refOrUri) {
45
- const uri = typeof refOrUri === 'string' ? refOrUri : _refUri(refOrUri);
46
- return this.members.some(m => _refUri(m) === uri);
47
- }
48
-
49
- identity() {
50
- return this.id;
51
- }
52
-
53
- toJSON() {
54
- const obj = {
55
- id: this.id,
56
- name: { ...this.name },
57
- type: this.type,
58
- members: this.members.map(m => m.toJSON()),
59
- };
60
- if (this.domain != null) obj.domain = this.domain;
61
- if (this.purpose != null) obj.purpose = { ...this.purpose };
62
- if (this.hyperedges.length > 0) obj.hyperedges = [...this.hyperedges];
63
- if (this.rootConcepts.length > 0) obj.rootConcepts = this.rootConcepts.map(c => c.toJSON());
64
- if (this.sources.length > 0) obj.sources = [...this.sources];
65
- if (this.status != null) obj.status = this.status;
66
- return obj;
67
- }
68
-
69
- static fromJSON(data) {
70
- return new ConceptSystem(data);
71
- }
72
- }
73
-
74
- function _ensureNonEmptyString(value, fieldName) {
75
- if (typeof value !== 'string' || value.length === 0) {
76
- throw new Error(`ConceptSystem#${fieldName} must be a non-empty string`);
77
- }
78
- return value;
79
- }
80
-
81
- function _ensureName(value) {
82
- if (value == null) return {};
83
- if (typeof value !== 'object' || Array.isArray(value)) {
84
- throw new Error('ConceptSystem#name must be a localized object (e.g. { eng: "..." })');
85
- }
86
- return { ...value };
87
- }
88
-
89
- function _resolveType(value) {
90
- if (value == null) {
91
- throw new Error(
92
- 'ConceptSystem#type is required; one of ' +
93
- CONCEPT_SYSTEM_TYPE_VALUES.join(', '),
94
- );
95
- }
96
- if (!isValidConceptSystemType(value)) {
97
- throw new Error(
98
- `ConceptSystem#type ${JSON.stringify(value)} invalid; ` +
99
- `must be one of: ${CONCEPT_SYSTEM_TYPE_VALUES.join(', ')}`,
100
- );
101
- }
102
- return value;
103
- }
104
-
105
- function _ensureMembers(values) {
106
- if (!Array.isArray(values) || values.length === 0) {
107
- throw new Error('ConceptSystem#members must be a non-empty array');
108
- }
109
- return values.map(v => v instanceof ConceptRef ? v : new ConceptRef(v));
110
- }
111
-
112
- function _refUri(ref) {
113
- const r = ref?.ref ?? ref ?? {};
114
- return `${r.source ?? ''}:${r.id ?? ''}`;
115
- }
116
-
117
- export default ConceptSystem;
@@ -1,59 +0,0 @@
1
- import { GlossaristModel } from './base.js';
2
- import { Citation } from './citation.js';
3
- import { canonicalJson } from '../diff/canonical-json.js';
4
-
5
- export class DetailedDefinition extends GlossaristModel {
6
- constructor(data = {}) {
7
- super();
8
- this.content = data.content ?? '';
9
- this._rawSources = data.sources ?? [];
10
- this._rawExamples = data.examples ?? [];
11
- this._sources = null;
12
- this._examples = null;
13
- }
14
-
15
- get sources() {
16
- return this._lazy('_sources', '_rawSources',
17
- s => s instanceof Citation ? s : new Citation(s));
18
- }
19
-
20
- get examples() {
21
- return this._lazy('_examples', '_rawExamples',
22
- e => e instanceof DetailedDefinition ? e : new DetailedDefinition(e));
23
- }
24
-
25
- toJSON() {
26
- const obj = { content: this.content };
27
- this._serialize(obj, 'sources', '_sources', '_rawSources');
28
- this._serialize(obj, 'examples', '_examples', '_rawExamples');
29
- return obj;
30
- }
31
-
32
- /**
33
- * Yield this definition's content and the content of every nested
34
- * example (recursively). Each item carries `{ text, source }` where
35
- * `source` is `<path>.content` rooted at the `path` argument.
36
- */
37
- *walkTexts(path) {
38
- if (typeof this.content === 'string' && this.content.length > 0) {
39
- yield { text: this.content, source: `${path}.content` };
40
- }
41
- for (let i = 0; i < this.examples.length; i++) {
42
- yield* this.examples[i].walkTexts(`${path}.examples[${i}]`);
43
- }
44
- }
45
-
46
- static identityOf(value) {
47
- if (value == null) return '';
48
- if (typeof value.toJSON === 'function') return canonicalJson(value.toJSON());
49
- return canonicalJson(value);
50
- }
51
-
52
- identity() {
53
- return DetailedDefinition.identityOf(this);
54
- }
55
-
56
- static fromJSON(data) {
57
- return new DetailedDefinition(data);
58
- }
59
- }
@@ -1,55 +0,0 @@
1
- // GenericHyperedge — an ISO 704 / ISO 1087-1 / ISO 12620 generic
2
- // relation connecting a comprehensive concept (the genus) to two
3
- // or more specific concepts (the species) which together constitute
4
- // a decomposition by some criterion of subdivision.
5
- //
6
- // Mirror of PartitiveHyperedge. Multiple GenericRelations on the same
7
- // comprehensive distinguished by `criterion` is the OIML pattern
8
- // (5.1 measurement standard has 6 criterion groups).
9
- //
10
- // See docs/design/generic-relation.md (concept-model repo).
11
-
12
- import { AbstractHyperedge } from './abstract-hyperedge.js';
13
- import { GenericMember } from './generic-member.js';
14
- import { HyperedgeRegistry } from './hyperedge-registry.js';
15
-
16
- export class GenericHyperedge extends AbstractHyperedge {
17
- // Per-class metadata block — the OCP contract. See PartitiveHyperedge
18
- // for field documentation.
19
- static wireKey = 'generic_relations';
20
- static typeTag = 'generic_relation';
21
- static rdfType = 'gloss:GenericRelation';
22
- static memberClass = GenericMember;
23
- static v1WireKeys = [];
24
- static kindLabel = 'GEN';
25
-
26
- constructor(data = {}) {
27
- super(data);
28
- this.members = data?.members == null
29
- ? this.members
30
- : (Array.isArray(data.members)
31
- ? data.members.map(m =>
32
- m instanceof GenericMember ? m : new GenericMember(m))
33
- : this.members);
34
- }
35
-
36
- static identityOf(value) {
37
- const v = value ?? {};
38
- const c = v.comprehensive ?? {};
39
- const memberKeys = Array.isArray(v.members)
40
- ? v.members.map(m => {
41
- const r = m?.ref ?? m ?? {};
42
- return `${r.source ?? ''}:${r.id ?? ''}`;
43
- }).sort()
44
- : [];
45
- return `${c.source ?? ''}:${c.id ?? ''}|${memberKeys.join('|')}`;
46
- }
47
-
48
- static fromJSON(data) {
49
- return new GenericHyperedge(data);
50
- }
51
- }
52
-
53
- export default GenericHyperedge;
54
-
55
- HyperedgeRegistry.register(GenericHyperedge);
@@ -1,82 +0,0 @@
1
- // GenericMember — one member of a GenericHyperedge.
2
- //
3
- // Per ISO 704:2022 §5.5.4.2.1 (Generic relations and general concepts):
4
- //
5
- // "In a generic relation, the intension of the subordinate concept
6
- // includes the intension of the superordinate concept plus at least
7
- // one additional delimiting characteristic."
8
- //
9
- // In hyperedge terms: when a GenericHyperedge has comprehensive=computer mouse
10
- // and criterion=means of movement detection, each member (mechanical mouse,
11
- // optomechanical mouse, optical mouse) carries a delimitingCharacteristic —
12
- // the value of the criterion for that specific concept:
13
- //
14
- // mechanical mouse → 'detecting movement by means of rollers'
15
- // optomechanical mouse → 'detecting movement by means of rollers and light sensors'
16
- // optical mouse → 'detecting movement by means of light sensors'
17
- //
18
- // Multidimensionality (§5.6.3): the same comprehensive can be the
19
- // '1' side of multiple generic hyperedges simultaneously, each by a
20
- // different criterion. computer mouse is also comprehensive of
21
- // {wired mouse, wireless mouse} under criterion=computer connection.
22
- // That second hyperedge's members carry their own delimitingCharacteristic
23
- // values ('using a corded electrical connection', 'using a cordless
24
- // light or sound connection').
25
- //
26
- // delimitingCharacteristic is REQUIRED on GenericMember. Without it,
27
- // the member is just a coordinate concept with no semantic content
28
- // tying it to the criterion — ISO 704 says the relation is meaningless
29
- // without the delimiting characteristic.
30
- //
31
- // LocalizedString shape (mirrors criterion on the parent hyperedge):
32
- // { eng: 'detecting movement by means of light sensors' }
33
- // { eng: '...', fra: '...' } — multiple languages supported
34
-
35
- import { HyperedgeMember } from './hyperedge-member.js';
36
-
37
- export class GenericMember extends HyperedgeMember {
38
- constructor(data = {}) {
39
- super(data);
40
- this.delimitingCharacteristic = _resolveDelimitingCharacteristic(data.delimitingCharacteristic);
41
- }
42
-
43
- toJSON() {
44
- const base = super.toJSON();
45
- base.delimitingCharacteristic = { ...this.delimitingCharacteristic };
46
- return base;
47
- }
48
- }
49
-
50
- function _resolveDelimitingCharacteristic(value) {
51
- if (value == null) {
52
- throw new Error(
53
- 'GenericMember.delimitingCharacteristic is required per ISO 704:2022 §5.5.4.2.1 ' +
54
- '(the intension of a subordinate concept must include at least one additional ' +
55
- 'delimiting characteristic beyond the superordinate).',
56
- );
57
- }
58
- if (typeof value === 'string') {
59
- if (value.length === 0) {
60
- throw new Error(
61
- 'GenericMember.delimitingCharacteristic cannot be empty (ISO 704:2022 §5.5.4.2.1)',
62
- );
63
- }
64
- return { default: value };
65
- }
66
- if (typeof value === 'object') {
67
- const entries = Object.entries(value).filter(([, v]) => typeof v === 'string' && v.length > 0);
68
- if (entries.length === 0) {
69
- throw new Error(
70
- 'GenericMember.delimitingCharacteristic must have at least one non-empty string ' +
71
- 'value (ISO 704:2022 §5.5.4.2.1)',
72
- );
73
- }
74
- return Object.fromEntries(entries);
75
- }
76
- throw new Error(
77
- `GenericMember.delimitingCharacteristic must be a string or a language-keyed object; ` +
78
- `got ${typeof value}`,
79
- );
80
- }
81
-
82
- export default GenericMember;
@@ -1,40 +0,0 @@
1
- import { GlossaristModel } from './base.js';
2
-
3
- export const GRAMMAR_GENDERS = Object.freeze(['m', 'f', 'n', 'c']);
4
- export const GRAMMAR_NUMBERS = Object.freeze(['singular', 'dual', 'plural']);
5
- export const GRAMMAR_PARTS_OF_SPEECH = Object.freeze([
6
- 'noun', 'verb', 'adj', 'adverb', 'preposition', 'participle',
7
- ]);
8
-
9
- export class GrammarInfo extends GlossaristModel {
10
- constructor(data = {}) {
11
- super();
12
- this.gender = data.gender ?? null;
13
- this.number = data.number ?? null;
14
- this.partOfSpeech = data.part_of_speech ?? data.partOfSpeech ?? null;
15
- this.noun = data.noun ?? false;
16
- this.verb = data.verb ?? false;
17
- this.adj = data.adj ?? false;
18
- this.adverb = data.adverb ?? false;
19
- this.preposition = data.preposition ?? false;
20
- this.participle = data.participle ?? false;
21
- }
22
-
23
- toJSON() {
24
- const obj = {};
25
- if (this.gender != null) obj.gender = this.gender;
26
- if (this.number != null) obj.number = this.number;
27
- if (this.partOfSpeech != null) obj.part_of_speech = this.partOfSpeech;
28
- if (this.noun) obj.noun = true;
29
- if (this.verb) obj.verb = true;
30
- if (this.adj) obj.adj = true;
31
- if (this.adverb) obj.adverb = true;
32
- if (this.preposition) obj.preposition = true;
33
- if (this.participle) obj.participle = true;
34
- return obj;
35
- }
36
-
37
- static fromJSON(data) {
38
- return new GrammarInfo(data);
39
- }
40
- }
@@ -1,19 +0,0 @@
1
- const FALLBACK_LANG = 'eng';
2
-
3
- export function fetchLocalizedString(hash, lang, fallback = FALLBACK_LANG) {
4
- if (hash == null || typeof hash !== 'object') return null;
5
- const direct = hash[lang] ?? hash[String(lang)];
6
- if (direct != null) return direct;
7
- if (fallback != null && fallback !== lang) {
8
- return hash[fallback] ?? hash[String(fallback)] ?? null;
9
- }
10
- return null;
11
- }
12
-
13
- export function localizedStringIsEmpty(hash) {
14
- return hash == null || (typeof hash === 'object' && Object.keys(hash).length === 0);
15
- }
16
-
17
- export function localizedStringIsPresent(hash) {
18
- return !localizedStringIsEmpty(hash);
19
- }