@pubid/pubid 0.1.3 → 0.1.4

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 (183) hide show
  1. package/artifact/corpus.json.gz +0 -0
  2. package/dist/conformance/cli.js +5 -1
  3. package/dist/flavors/amca/grammar.d.ts +4 -0
  4. package/dist/flavors/amca/grammar.js +87 -0
  5. package/dist/flavors/amca/index.d.ts +4 -0
  6. package/dist/flavors/amca/index.js +76 -0
  7. package/dist/flavors/amca/model.d.ts +29 -0
  8. package/dist/flavors/amca/model.js +146 -0
  9. package/dist/flavors/ansi/index.d.ts +24 -0
  10. package/dist/flavors/ansi/index.js +147 -0
  11. package/dist/flavors/api/grammar.d.ts +4 -0
  12. package/dist/flavors/api/grammar.js +79 -0
  13. package/dist/flavors/api/index.d.ts +4 -0
  14. package/dist/flavors/api/index.js +71 -0
  15. package/dist/flavors/api/model.d.ts +27 -0
  16. package/dist/flavors/api/model.js +114 -0
  17. package/dist/flavors/ashrae/grammar.d.ts +2 -0
  18. package/dist/flavors/ashrae/grammar.js +667 -0
  19. package/dist/flavors/ashrae/index.d.ts +4 -0
  20. package/dist/flavors/ashrae/index.js +306 -0
  21. package/dist/flavors/ashrae/model.d.ts +87 -0
  22. package/dist/flavors/ashrae/model.js +335 -0
  23. package/dist/flavors/ashrae/preprocessor.d.ts +7 -0
  24. package/dist/flavors/ashrae/preprocessor.js +44 -0
  25. package/dist/flavors/asme/grammar.d.ts +2 -0
  26. package/dist/flavors/asme/grammar.js +200 -0
  27. package/dist/flavors/asme/index.d.ts +4 -0
  28. package/dist/flavors/asme/index.js +138 -0
  29. package/dist/flavors/asme/model.d.ts +34 -0
  30. package/dist/flavors/asme/model.js +123 -0
  31. package/dist/flavors/astm/grammar.d.ts +2 -0
  32. package/dist/flavors/astm/grammar.js +110 -0
  33. package/dist/flavors/astm/index.d.ts +2 -0
  34. package/dist/flavors/astm/index.js +127 -0
  35. package/dist/flavors/astm/model.d.ts +42 -0
  36. package/dist/flavors/astm/model.js +263 -0
  37. package/dist/flavors/bipm/index.d.ts +30 -0
  38. package/dist/flavors/bipm/index.js +434 -0
  39. package/dist/flavors/bsi/grammar.d.ts +4 -0
  40. package/dist/flavors/bsi/grammar.js +535 -0
  41. package/dist/flavors/bsi/index.d.ts +4 -0
  42. package/dist/flavors/bsi/index.js +941 -0
  43. package/dist/flavors/bsi/model.d.ts +127 -0
  44. package/dist/flavors/bsi/model.js +863 -0
  45. package/dist/flavors/calconnect/index.d.ts +21 -0
  46. package/dist/flavors/calconnect/index.js +113 -0
  47. package/dist/flavors/cen_cenelec/grammar.d.ts +4 -0
  48. package/dist/flavors/cen_cenelec/grammar.js +107 -0
  49. package/dist/flavors/cen_cenelec/index.d.ts +5 -0
  50. package/dist/flavors/cen_cenelec/index.js +263 -0
  51. package/dist/flavors/cen_cenelec/model.d.ts +45 -0
  52. package/dist/flavors/cen_cenelec/model.js +490 -0
  53. package/dist/flavors/cie/grammar.d.ts +5 -0
  54. package/dist/flavors/cie/grammar.js +127 -0
  55. package/dist/flavors/cie/index.d.ts +2 -0
  56. package/dist/flavors/cie/index.js +311 -0
  57. package/dist/flavors/cie/model.d.ts +122 -0
  58. package/dist/flavors/cie/model.js +464 -0
  59. package/dist/flavors/csa/grammar.d.ts +6 -0
  60. package/dist/flavors/csa/grammar.js +239 -0
  61. package/dist/flavors/csa/index.d.ts +2 -0
  62. package/dist/flavors/csa/index.js +634 -0
  63. package/dist/flavors/csa/model.d.ts +33 -0
  64. package/dist/flavors/csa/model.js +505 -0
  65. package/dist/flavors/doi/index.d.ts +13 -0
  66. package/dist/flavors/doi/index.js +65 -0
  67. package/dist/flavors/easc/index.d.ts +4 -0
  68. package/dist/flavors/easc/index.js +107 -0
  69. package/dist/flavors/ecma/index.d.ts +4 -0
  70. package/dist/flavors/ecma/index.js +117 -0
  71. package/dist/flavors/etsi/index.d.ts +14 -0
  72. package/dist/flavors/etsi/index.js +307 -0
  73. package/dist/flavors/evs/grammar.d.ts +2 -0
  74. package/dist/flavors/evs/grammar.js +22 -0
  75. package/dist/flavors/evs/index.d.ts +2 -0
  76. package/dist/flavors/evs/index.js +24 -0
  77. package/dist/flavors/evs/model.d.ts +19 -0
  78. package/dist/flavors/evs/model.js +36 -0
  79. package/dist/flavors/gb/index.d.ts +24 -0
  80. package/dist/flavors/gb/index.js +111 -0
  81. package/dist/flavors/gost/grammar.d.ts +2 -0
  82. package/dist/flavors/gost/grammar.js +46 -0
  83. package/dist/flavors/gost/index.d.ts +2 -0
  84. package/dist/flavors/gost/index.js +165 -0
  85. package/dist/flavors/gost/model.d.ts +61 -0
  86. package/dist/flavors/gost/model.js +147 -0
  87. package/dist/flavors/gost/prefix_routes.d.ts +15 -0
  88. package/dist/flavors/gost/prefix_routes.js +192 -0
  89. package/dist/flavors/iala/index.d.ts +32 -0
  90. package/dist/flavors/iala/index.js +300 -0
  91. package/dist/flavors/iana/index.d.ts +19 -0
  92. package/dist/flavors/iana/index.js +71 -0
  93. package/dist/flavors/idf/grammar.d.ts +2 -0
  94. package/dist/flavors/idf/grammar.js +71 -0
  95. package/dist/flavors/idf/index.d.ts +2 -0
  96. package/dist/flavors/idf/index.js +100 -0
  97. package/dist/flavors/idf/model.d.ts +54 -0
  98. package/dist/flavors/idf/model.js +359 -0
  99. package/dist/flavors/iec/grammar.d.ts +3 -0
  100. package/dist/flavors/iec/grammar.js +187 -0
  101. package/dist/flavors/iec/index.d.ts +4 -0
  102. package/dist/flavors/iec/index.js +507 -0
  103. package/dist/flavors/iec/model.d.ts +54 -0
  104. package/dist/flavors/iec/model.js +437 -0
  105. package/dist/flavors/ieee/grammar.d.ts +2 -0
  106. package/dist/flavors/ieee/grammar.js +626 -0
  107. package/dist/flavors/ieee/index.d.ts +3 -0
  108. package/dist/flavors/ieee/index.js +1186 -0
  109. package/dist/flavors/ieee/model.d.ts +142 -0
  110. package/dist/flavors/ieee/model.js +1148 -0
  111. package/dist/flavors/ieee/preprocessor.d.ts +8 -0
  112. package/dist/flavors/ieee/preprocessor.js +410 -0
  113. package/dist/flavors/ieee/update_codes.d.ts +8 -0
  114. package/dist/flavors/ieee/update_codes.js +191 -0
  115. package/dist/flavors/ietf/index.d.ts +30 -0
  116. package/dist/flavors/ietf/index.js +151 -0
  117. package/dist/flavors/iho/index.d.ts +4 -0
  118. package/dist/flavors/iho/index.js +126 -0
  119. package/dist/flavors/index.d.ts +9 -0
  120. package/dist/flavors/index.js +136 -0
  121. package/dist/flavors/isbn/index.d.ts +13 -0
  122. package/dist/flavors/isbn/index.js +97 -0
  123. package/dist/flavors/iso/grammar.d.ts +2 -0
  124. package/dist/flavors/iso/grammar.js +207 -0
  125. package/dist/flavors/iso/index.d.ts +2 -0
  126. package/dist/flavors/iso/index.js +343 -0
  127. package/dist/flavors/iso/model.d.ts +159 -0
  128. package/dist/flavors/iso/model.js +802 -0
  129. package/dist/flavors/iso/stages.generated.d.ts +13 -0
  130. package/dist/flavors/iso/stages.generated.js +391 -0
  131. package/dist/flavors/itu/grammar.d.ts +4 -0
  132. package/dist/flavors/itu/grammar.js +205 -0
  133. package/dist/flavors/itu/index.d.ts +2 -0
  134. package/dist/flavors/itu/index.js +224 -0
  135. package/dist/flavors/itu/model.d.ts +184 -0
  136. package/dist/flavors/itu/model.js +599 -0
  137. package/dist/flavors/jcgm/index.d.ts +7 -0
  138. package/dist/flavors/jcgm/index.js +300 -0
  139. package/dist/flavors/jis/index.d.ts +4 -0
  140. package/dist/flavors/jis/index.js +267 -0
  141. package/dist/flavors/nist/grammar.d.ts +2 -0
  142. package/dist/flavors/nist/grammar.js +287 -0
  143. package/dist/flavors/nist/index.d.ts +2 -0
  144. package/dist/flavors/nist/index.js +1244 -0
  145. package/dist/flavors/nist/model.d.ts +155 -0
  146. package/dist/flavors/nist/model.js +823 -0
  147. package/dist/flavors/nist/preprocessor.d.ts +11 -0
  148. package/dist/flavors/nist/preprocessor.js +281 -0
  149. package/dist/flavors/oasis/index.d.ts +23 -0
  150. package/dist/flavors/oasis/index.js +110 -0
  151. package/dist/flavors/ogc/index.d.ts +20 -0
  152. package/dist/flavors/ogc/index.js +75 -0
  153. package/dist/flavors/oiml/grammar.d.ts +2 -0
  154. package/dist/flavors/oiml/grammar.js +180 -0
  155. package/dist/flavors/oiml/implementation.d.ts +9 -0
  156. package/dist/flavors/oiml/implementation.js +16 -0
  157. package/dist/flavors/oiml/model.d.ts +4 -0
  158. package/dist/flavors/oiml/model.js +462 -0
  159. package/dist/flavors/omg/index.d.ts +14 -0
  160. package/dist/flavors/omg/index.js +73 -0
  161. package/dist/flavors/plateau/index.d.ts +4 -0
  162. package/dist/flavors/plateau/index.js +97 -0
  163. package/dist/flavors/tgpp/index.d.ts +4 -0
  164. package/dist/flavors/tgpp/index.js +122 -0
  165. package/dist/flavors/un/index.d.ts +13 -0
  166. package/dist/flavors/un/index.js +90 -0
  167. package/dist/flavors/w3c/index.d.ts +14 -0
  168. package/dist/flavors/w3c/index.js +148 -0
  169. package/dist/flavors/xsf/index.d.ts +18 -0
  170. package/dist/flavors/xsf/index.js +65 -0
  171. package/dist/grammar/engine.d.ts +78 -0
  172. package/dist/grammar/engine.js +351 -0
  173. package/dist/model/attribute.d.ts +54 -0
  174. package/dist/model/attribute.js +53 -0
  175. package/dist/model/builder.d.ts +39 -0
  176. package/dist/model/builder.js +124 -0
  177. package/dist/model/component.d.ts +70 -0
  178. package/dist/model/component.js +170 -0
  179. package/dist/model/identifier.d.ts +83 -0
  180. package/dist/model/identifier.js +270 -0
  181. package/dist/model/urn-generator.d.ts +27 -0
  182. package/dist/model/urn-generator.js +91 -0
  183. package/package.json +1 -1
@@ -0,0 +1,124 @@
1
+ /**
2
+ * BaseBuilder — the TS mirror of Pubid::Builder::Base
3
+ * (TODO.unified/01 §5). The shared build loop: flatten the parslet tree
4
+ * (array of hashes → merged hash), ask selectClass for the concrete
5
+ * class, then assign each tree key through cast(); unknown keys are
6
+ * silently skipped; cast may return a hash to fan out.
7
+ */
8
+ import { PubidDate } from "./component.js";
9
+ import { Language } from "./component.js";
10
+ export class BaseBuilder {
11
+ /** Override to dispatch by data shape (type token, supplement marker…). */
12
+ selectClass(_data) {
13
+ return this.defaultIdentifierClass();
14
+ }
15
+ /**
16
+ * Override to convert raw parsed values to components. Return a hash to
17
+ * assign multiple attributes, a single value, or null/undefined to skip.
18
+ */
19
+ cast(_key, value) {
20
+ return value;
21
+ }
22
+ /** Override for non-attribute side effects; return true when consumed. */
23
+ handleKey(_identifier, _key, _value) {
24
+ return false;
25
+ }
26
+ build(data) {
27
+ const flat = this.flattenArray(data);
28
+ const IdentifierClass = this.selectClass(flat);
29
+ const identifier = new IdentifierClass();
30
+ this.assignAttributes(identifier, flat);
31
+ return identifier;
32
+ }
33
+ flattenArray(data) {
34
+ if (Array.isArray(data)) {
35
+ return Object.assign({}, ...data);
36
+ }
37
+ return data;
38
+ }
39
+ assignAttributes(identifier, data) {
40
+ const attrs = identifier.constructor.attributes;
41
+ for (const [key, value] of Object.entries(data)) {
42
+ const realized = this.cast(key, value);
43
+ if (realized === null || realized === undefined)
44
+ continue;
45
+ if (this.handleKey(identifier, key, realized))
46
+ continue;
47
+ const target = identifier;
48
+ if (typeof realized === "object" && !Array.isArray(realized)) {
49
+ for (const [k, v] of Object.entries(realized)) {
50
+ if (k in attrs)
51
+ target[k] = v;
52
+ }
53
+ }
54
+ else if (key in attrs) {
55
+ target[key] = realized;
56
+ }
57
+ }
58
+ }
59
+ // Shared helpers (Builder::Base private API).
60
+ parseDate(value) {
61
+ const str = String(value);
62
+ const m = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/.exec(str);
63
+ if (m) {
64
+ return new PubidDate({ year: m[1], month: m[2], day: m[3] });
65
+ }
66
+ if (/^\d{4}$/.test(str))
67
+ return new PubidDate({ year: str });
68
+ throw new Error(`Invalid date format: ${str}`);
69
+ }
70
+ parseLanguages(value) {
71
+ return String(value)
72
+ .replaceAll("/", ",")
73
+ .split(",")
74
+ .map((lang) => {
75
+ const original = lang.trim();
76
+ const code = Language.CHAR_MAP[original] ?? original;
77
+ return new Language({ code, originalCode: original });
78
+ });
79
+ }
80
+ static ROMAN = {
81
+ I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000,
82
+ };
83
+ convertRomanToInteger(roman) {
84
+ const str = String(roman);
85
+ if (!/^[IVXLCDM]+$/i.test(str))
86
+ return str;
87
+ let result = 0;
88
+ let prev = 0;
89
+ for (const char of str.toUpperCase().split("").reverse()) {
90
+ const value = BaseBuilder.ROMAN[char];
91
+ result += value < prev ? -value : value;
92
+ prev = value;
93
+ }
94
+ return String(result);
95
+ }
96
+ /**
97
+ * "1234-1-2" → {number, part?, subpart?}; roman parts convert to
98
+ * integers; normalizeNumberWithPart / extractLegacyYear are hooks.
99
+ */
100
+ parseNumberWithPart(value) {
101
+ const normalized = this.normalizeNumberWithPart(String(value));
102
+ const parts = normalized.split("-").filter((p) => p !== "");
103
+ const number = parts.shift();
104
+ let part = parts.shift()?.trim();
105
+ const subpart = parts.length > 0 ? parts.join("-") : undefined;
106
+ const legacy = this.extractLegacyYear(number, part);
107
+ if (legacy)
108
+ return legacy;
109
+ if (part !== undefined)
110
+ part = this.convertRomanToInteger(part);
111
+ const out = { number };
112
+ if (part !== undefined)
113
+ out["part"] = part;
114
+ if (subpart !== undefined)
115
+ out["subpart"] = subpart;
116
+ return out;
117
+ }
118
+ normalizeNumberWithPart(value) {
119
+ return value;
120
+ }
121
+ extractLegacyYear(_number, _part) {
122
+ return undefined;
123
+ }
124
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The unified model's Components — the TS mirror of lib/pubid/components/.
3
+ * Semantics pinned in TODO.unified/01-study-pubid-core.md §6-7.
4
+ *
5
+ * Every component carries: plain fields, `render(context)` (the
6
+ * RenderingContext seam: "human" default, "urn" lowercases / year-only),
7
+ * `toWire()` (its serialized hash form), and degenerate-scalar support
8
+ * (a component holding only its single significant field collapses to
9
+ * that scalar in the canonical hash).
10
+ */
11
+ export type RenderContext = "human" | "urn";
12
+ /** Render a value that may be a component or a bare scalar (Renderers::Base#render_component). */
13
+ export declare function renderComponent(value: unknown, context?: RenderContext): string | undefined;
14
+ export declare abstract class Component {
15
+ /** The serialized hash form (lutaml to_hash of the component). */
16
+ abstract toWire(): Record<string, unknown>;
17
+ /** The render seam (context: human default, urn lowercases etc.). */
18
+ abstract render(context?: RenderContext): string | undefined;
19
+ /**
20
+ * The scalar this component degenerates to when `field` is its only
21
+ * significant value, else undefined (Pubid::Identifier#degenerate_scalar).
22
+ * A field holding another component never degenerates.
23
+ */
24
+ degenerateScalar(field: string): string | undefined;
25
+ /** True when `value` equals the field's declared default. */
26
+ protected fieldIsDefaulted(_name: string, _value: unknown): boolean;
27
+ }
28
+ /** Publication date (Components::Date). Human "YYYY[-MM[-DD]]", URN year only, "--" undated. */
29
+ export declare class PubidDate extends Component {
30
+ readonly year?: string | undefined;
31
+ readonly month?: string | undefined;
32
+ readonly day?: string | undefined;
33
+ readonly undated: boolean;
34
+ constructor(attrs: Record<string, unknown>);
35
+ present(): boolean;
36
+ render(context?: RenderContext): string | undefined;
37
+ toWire(): Record<string, unknown>;
38
+ protected fieldIsDefaulted(name: string, value: unknown): boolean;
39
+ }
40
+ /** Publisher (Components::Publisher): body; URN render lowercases. */
41
+ export declare class Publisher extends Component {
42
+ readonly body: string;
43
+ constructor(attrs: Record<string, unknown>);
44
+ render(context?: RenderContext): string;
45
+ toWire(): Record<string, unknown>;
46
+ }
47
+ /** Language (Components::Language): single-char CHAR_MAP codes; URN lowercases. */
48
+ export declare class Language extends Component {
49
+ static readonly CHAR_MAP: Record<string, string>;
50
+ readonly code: string;
51
+ readonly originalCode?: string | undefined;
52
+ constructor(attrs: Record<string, unknown>);
53
+ render(context?: RenderContext): string;
54
+ toWire(): Record<string, unknown>;
55
+ }
56
+ /** Edition (Components::Edition): degenerates to its number. */
57
+ export declare class Edition extends Component {
58
+ readonly number: string;
59
+ readonly phase?: string | undefined;
60
+ constructor(attrs: Record<string, unknown>);
61
+ render(context?: RenderContext): string | undefined;
62
+ toWire(): Record<string, unknown>;
63
+ }
64
+ /** Iteration (Components::Iteration): degenerate by construction. */
65
+ export declare class Iteration extends Component {
66
+ readonly string: string;
67
+ constructor(attrs: Record<string, unknown>);
68
+ render(_context?: RenderContext): string | undefined;
69
+ toWire(): Record<string, unknown>;
70
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * The unified model's Components — the TS mirror of lib/pubid/components/.
3
+ * Semantics pinned in TODO.unified/01-study-pubid-core.md §6-7.
4
+ *
5
+ * Every component carries: plain fields, `render(context)` (the
6
+ * RenderingContext seam: "human" default, "urn" lowercases / year-only),
7
+ * `toWire()` (its serialized hash form), and degenerate-scalar support
8
+ * (a component holding only its single significant field collapses to
9
+ * that scalar in the canonical hash).
10
+ */
11
+ /** Render a value that may be a component or a bare scalar (Renderers::Base#render_component). */
12
+ export function renderComponent(value, context) {
13
+ if (value === undefined || value === null)
14
+ return undefined;
15
+ if (value instanceof Component)
16
+ return value.render(context);
17
+ return String(value);
18
+ }
19
+ export class Component {
20
+ /**
21
+ * The scalar this component degenerates to when `field` is its only
22
+ * significant value, else undefined (Pubid::Identifier#degenerate_scalar).
23
+ * A field holding another component never degenerates.
24
+ */
25
+ degenerateScalar(field) {
26
+ let found;
27
+ for (const [name, value] of Object.entries(this)) {
28
+ if (value === undefined || value === null || value === "")
29
+ continue;
30
+ if (this.fieldIsDefaulted(name, value))
31
+ continue;
32
+ if (name === field) {
33
+ if (value instanceof Component)
34
+ return undefined;
35
+ found = String(value);
36
+ }
37
+ else {
38
+ return undefined;
39
+ }
40
+ }
41
+ return found;
42
+ }
43
+ /** True when `value` equals the field's declared default. */
44
+ fieldIsDefaulted(_name, _value) {
45
+ return false;
46
+ }
47
+ }
48
+ const pad2 = (value) => value.padStart(2, "0");
49
+ /** Publication date (Components::Date). Human "YYYY[-MM[-DD]]", URN year only, "--" undated. */
50
+ export class PubidDate extends Component {
51
+ year;
52
+ month;
53
+ day;
54
+ undated;
55
+ constructor(attrs) {
56
+ super();
57
+ this.year = attrs["year"];
58
+ this.month = attrs["month"];
59
+ this.day = attrs["day"];
60
+ this.undated = attrs["undated"] ?? false;
61
+ }
62
+ present() {
63
+ if (this.undated)
64
+ return true;
65
+ return this.year !== undefined && this.year !== "";
66
+ }
67
+ render(context) {
68
+ if (this.undated && (this.year === undefined || this.year === ""))
69
+ return "--";
70
+ if (!this.present())
71
+ return undefined;
72
+ if (context === "urn")
73
+ return this.year;
74
+ if (this.month === undefined)
75
+ return this.year;
76
+ let result = `${this.year}-${pad2(this.month)}`;
77
+ if (this.day !== undefined)
78
+ result += `-${pad2(this.day)}`;
79
+ return result;
80
+ }
81
+ toWire() {
82
+ const wire = {};
83
+ if (this.year !== undefined)
84
+ wire["year"] = this.year;
85
+ if (this.month !== undefined)
86
+ wire["month"] = this.month;
87
+ if (this.day !== undefined)
88
+ wire["day"] = this.day;
89
+ if (this.undated)
90
+ wire["undated"] = true;
91
+ return wire;
92
+ }
93
+ fieldIsDefaulted(name, value) {
94
+ return name === "undated" && value === false;
95
+ }
96
+ }
97
+ /** Publisher (Components::Publisher): body; URN render lowercases. */
98
+ export class Publisher extends Component {
99
+ body;
100
+ constructor(attrs) {
101
+ super();
102
+ this.body = attrs["body"];
103
+ }
104
+ render(context) {
105
+ return context === "urn" ? this.body.toLowerCase() : this.body;
106
+ }
107
+ toWire() {
108
+ return { body: this.body };
109
+ }
110
+ }
111
+ /** Language (Components::Language): single-char CHAR_MAP codes; URN lowercases. */
112
+ export class Language extends Component {
113
+ static CHAR_MAP = {
114
+ R: "ru",
115
+ F: "fr",
116
+ E: "en",
117
+ A: "ar",
118
+ S: "es",
119
+ D: "de",
120
+ };
121
+ code;
122
+ originalCode;
123
+ constructor(attrs) {
124
+ super();
125
+ this.code = attrs["code"];
126
+ this.originalCode = (attrs["originalCode"] ?? attrs["original_code"]);
127
+ }
128
+ render(context) {
129
+ if (context === "urn")
130
+ return this.code.toLowerCase();
131
+ if (this.originalCode !== undefined) {
132
+ return this.originalCode.length === 1 ? this.code : this.originalCode;
133
+ }
134
+ return this.code;
135
+ }
136
+ toWire() {
137
+ return this.originalCode === undefined ? { code: this.code } : { code: this.code, original_code: this.originalCode };
138
+ }
139
+ }
140
+ /** Edition (Components::Edition): degenerates to its number. */
141
+ export class Edition extends Component {
142
+ number;
143
+ phase;
144
+ constructor(attrs) {
145
+ super();
146
+ this.number = attrs["number"];
147
+ this.phase = attrs["phase"];
148
+ }
149
+ render(context) {
150
+ void context;
151
+ return this.phase === undefined ? this.number : `${this.number}${this.phase}`;
152
+ }
153
+ toWire() {
154
+ return this.phase === undefined ? { number: this.number } : { number: this.number, phase: this.phase };
155
+ }
156
+ }
157
+ /** Iteration (Components::Iteration): degenerate by construction. */
158
+ export class Iteration extends Component {
159
+ string;
160
+ constructor(attrs) {
161
+ super();
162
+ this.string = attrs["string"];
163
+ }
164
+ render(_context) {
165
+ return this.string;
166
+ }
167
+ toWire() {
168
+ return { string: this.string };
169
+ }
170
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * BaseIdentifier — the TS mirror of Pubid::Identifier's declaration and
3
+ * canonical-serialization core. Semantics pinned in
4
+ * TODO.unified/01-study-pubid-core.md §2-3:
5
+ *
6
+ * toHash: `_type` first, mapped attributes, then canonicalization
7
+ * (drop empty / default-valued attributes, recursively), then
8
+ * degenerate-component flattening (`date: {year: "2024"}` → `year:
9
+ * "2024"`, `edition: {number: "2"}` → `edition: "2"`), then the class's
10
+ * compactHash hook.
11
+ *
12
+ * fromHash: dispatch on `_type` through the registered type map,
13
+ * re-inflate flat scalars into component hashes (unless the flavor has a
14
+ * converter for the key or declares the flat name as its own attribute),
15
+ * construct with type coercion.
16
+ *
17
+ * Flavors NEVER hand-roll a hash here — they declare attributes and
18
+ * mappings; the only imperative wire code is a converter pair.
19
+ */
20
+ import type { AttributeTable, FieldMapping } from "./attribute.js";
21
+ export interface IdentifierStatic {
22
+ new (attrs?: Record<string, unknown>): BaseIdentifier;
23
+ polymorphicName: string;
24
+ attributes: AttributeTable;
25
+ /** Optional mapping overrides; default = every attribute under its own name. */
26
+ mappings?: FieldMapping[];
27
+ /** Per-class shorter-wire hook (Ruby compact_hash); must invert in inflate. */
28
+ compactHash?: (model: BaseIdentifier, hash: Record<string, unknown>) => void;
29
+ /** Additional flat-scalar entries for this class. */
30
+ flatScalarComponents?: Record<string, string>;
31
+ /** The field each additional flat-scalar component degenerates to. */
32
+ flatScalarFields?: Record<string, string>;
33
+ fromHash(hash: Record<string, unknown>): BaseIdentifier;
34
+ /** The flavor's URN generator; undefined = the base template (Ruby resolve_urn_generator fallback). */
35
+ urnGenerator?: UrnGeneratorCtor;
36
+ }
37
+ export type UrnGeneratorCtor = new (identifier: BaseIdentifier) => {
38
+ generate(): string;
39
+ };
40
+ /** Register a concrete identifier class under its polymorphic _type (the *_TYPE_MAP role). */
41
+ export declare function registerType(klass: IdentifierStatic): void;
42
+ export declare abstract class BaseIdentifier {
43
+ static polymorphicName: string;
44
+ /** The human renderer body — the one method every flavor writes. */
45
+ abstract render(): string;
46
+ /** The root table; subclasses compose via extendAttributes(BaseIdentifier, …). */
47
+ static attributes: AttributeTable;
48
+ static mappings: FieldMapping[] | undefined;
49
+ static compactHash: ((model: BaseIdentifier, hash: Record<string, unknown>) => void) | undefined;
50
+ static flatScalarComponents: Record<string, string> | undefined;
51
+ ["constructor"]: IdentifierStatic;
52
+ constructor(attrs?: Record<string, unknown>);
53
+ /** The human form (Ruby render(format: :human) → the flavor renderer). */
54
+ toHuman(): string;
55
+ /** Ruby to_urn: the flavor's UrnGenerator, else the base template. */
56
+ toUrn(): string;
57
+ fromHash(hash: Record<string, unknown>): BaseIdentifier;
58
+ protected classAttributes(): AttributeTable;
59
+ /** Wire key for an attribute: custom mapping or the attribute name. */
60
+ protected wireKeyFor(name: string): {
61
+ wire: string;
62
+ toWire?: FieldMapping["toWire"];
63
+ };
64
+ protected attrValue(name: string): unknown;
65
+ /** Serialize one attribute's value (component → toWire, nested identifier → toHash, scalars as-is). */
66
+ protected serializeValue(value: unknown): unknown;
67
+ /** Nested serialization: same as toHash but with the unfiltered mappings. */
68
+ toHashNested(): Record<string, unknown>;
69
+ toHash(): Record<string, unknown>;
70
+ private toHashWith;
71
+ /**
72
+ * Degenerate single-field components collapse to their scalar
73
+ * (identifier.rb flatten_scalar_components): `date` RENAMES to `year`,
74
+ * `edition` keeps its name; guards: never overwrite an emitted wire
75
+ * key, never rename onto a declared attribute name.
76
+ */
77
+ protected flattenScalars(hash: Record<string, unknown>): void;
78
+ static fromHash(hash: Record<string, unknown>): BaseIdentifier;
79
+ /** Re-nest flat scalars into component hashes (identifier.rb inflate_scalar_components). */
80
+ protected static inflateScalarComponents(data: Record<string, unknown>): Record<string, unknown>;
81
+ /** Apply custom `fromWire` converters to the inflated hash. */
82
+ protected static applyMappings(data: Record<string, unknown>): Record<string, unknown>;
83
+ }