@domorium/validator 1.8.2 → 2.0.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,149 @@
2
2
 
3
3
  All notable changes to `@domorium/validator` are documented here.
4
4
 
5
+ ## 2.0.1
6
+
7
+ - **A 5.5.1 date names its calendar where the specification puts it.** 5.5.1
8
+ writes the escape inside `<DATE>`, and `<DATE>` comes after the modifier:
9
+ `ABT <DATE>`, `BET <DATE> AND <DATE>`, `FROM <DATE> TO <DATE>`. The escape was
10
+ read only at the front of the whole payload, so `ABT @#DHEBREW@ TSH 5760` and
11
+ a Julian `FROM`/`TO` range were reported as invalid dates, while
12
+ `@#DJULIAN@ FROM 1 JAN 1700 TO 1 JAN 1710`, which the grammar does not admit,
13
+ passed. Each `<DATE>` now carries its own calendar and is read in
14
+ that calendar's months, so a range may cross calendars and neither half is
15
+ read in the other's vocabulary.
16
+ - **An exact date is Gregorian, and says so.** `DATE_EXACT` is
17
+ `<DAY> <MONTH> <YEAR_GREG>` with no slot for a calendar, but the escape was
18
+ stripped before the grammar ran, so `1 CHAN` / `2 DATE @#DHEBREW@ 1 TSH 5760`
19
+ validated clean — where the GEDCOM 7 rule for the same payload rejects it. The
20
+ two dialects agreed on every other date rule and disagreed on this one.
21
+ - **A day is measured against the calendar its own date names.** A calendar binds
22
+ to the date that follows it, which is what `FROM JULIAN 1670 TO 1800` says: two
23
+ dates, two calendars. The day-length check set the calendar once and carried it
24
+ to the end of the payload, so `FROM JULIAN 1 JAN 1700 TO 29 FEB 1900` reported
25
+ nothing — the second date is Gregorian, 1900 is not a Gregorian leap year, and
26
+ it was measured by the Julian rule, which has no exception for a century. The
27
+ same leading calendar suppressed the check for every later date in a range.
28
+ Each word that ends one date and begins the next now returns the calendar to
29
+ Gregorian, so a date names its own or is read as Gregorian.
30
+
31
+ ## 2.0.0 - 2026-08-20
32
+
33
+ Removes three members of `GedcomDocument` that no consumer read, which is what
34
+ makes this a major: `updateDocument`, which returned `this` and did nothing;
35
+ `xRefs`, a parse product nothing outside the package looked at; and the public
36
+ `pointers`, now private because writing to it poisoned the pointer-target cache
37
+ keyed on it. `getErrors` and `getNodes` lose parameters they never read — a
38
+ change to their types only, since JavaScript discards a surplus argument.
39
+
40
+ - **A 5.5.1 multimedia format is read without regard to case.** `2 FORM JPG` was
41
+ reported against a set the specification writes in lower case, so a file holding
42
+ the right value in the wrong spelling read as one holding a format 5.5.1 does not
43
+ have — and `FORM` is required under every `OBJE`.`FILE`, so an export writing
44
+ extensions in upper case earned an error per photograph. That set is now compared
45
+ without regard to case; `png` and `pdf`, which the specification has no spelling
46
+ for, are still reported. The LDS status sets, which 5.5.1 writes in upper case,
47
+ are unchanged: the case matters differently per set, so the set is named rather
48
+ than the comparison.
49
+ - **A tag typed in lower case is completed.** The lexer reads `1 sex` as the SEX
50
+ tag and the validator names the mistake — a tag is written in upper case, SEX —
51
+ but completion, which is the way out of it, matched upper case only: `1 se`
52
+ offered nothing where `1 SE` offered 62 tags, and a payload typed after `1 sex`
53
+ was offered nothing where after `1 SEX` it was offered the four values of the
54
+ enumeration. The typed tag is now read as the lexer reads it, and the schema is
55
+ asked about the tag it names rather than about the letters as they were typed.
56
+ - **A completion on a large document is answered in under a millisecond.** Every
57
+ request rebuilt the flattened node array and then filtered it, so a 300k-line
58
+ file paid 69 ms per keystroke — for a tree the last parse built and nothing has
59
+ touched since, where parsing and validating that whole file takes 492 ms once.
60
+ The walk is now cached against the array a parse replaces, and the line the
61
+ cursor sits on is found by halving the nodes rather than by reading the range of
62
+ every one before it: 6 ms for the first completion after a parse, 0.1 ms for
63
+ each one after that.
64
+ - **A second `createDocument` on one instance answers for the file it was given.**
65
+ A header naming a system before `GEDC`, a version no schema describes, and a
66
+ header naming no version at all each return before a schema is chosen, and the
67
+ schema, the extension tags and the GEDCOM 7 flag of the document read before
68
+ them stayed where they were: an instance that had read a 7.0 file offered
69
+ `SCHMA` while completing the header of a Personal Ancestral File, and labelled
70
+ a tag with a URI declared by a `HEAD`.`SCHMA` in another file. Parsing now
71
+ clears all three, so every call answers for its own text.
72
+ - **`GedcomDocument` no longer carries members nothing reads.** `updateDocument`
73
+ returned `this` and did nothing; `getErrors` took a language it never read and
74
+ `getNodes` a range it never read — and that `Range` was the DOM's, ambient
75
+ because the package pinned no `lib`, so `getNodes(new Range())` compiled in a
76
+ package that must also run in Node. `pointers` and `xRefs` were mutable parse
77
+ products no consumer outside the package read, and assigning to `pointers`
78
+ poisoned the pointer-target cache keyed on it. `getErrors()` and `getNodes()`
79
+ take no arguments, `pointers` is private and `updateDocument` and `xRefs` are
80
+ gone; the package compiles against `ES2023` alone, so a DOM global cannot be
81
+ named by accident again.
82
+ - **A 5.5.1 multimedia format and the four LDS ordinance statuses are read against
83
+ the value sets the specification spells out.** `3 FORM exe` beneath a FILE, and
84
+ `2 STAT nonsense` beneath BAPL, CONL, ENDL, SLGC or SLGS, were accepted: 5.5.1
85
+ states these closed sets in its primitive definitions rather than in an
86
+ enumeration vocabulary, so the scheme recorded only a type URI and the payload
87
+ was checked for one thing, that it was not empty. A multimedia FORM is now one
88
+ of `[bmp, gif, jpg, ole, pcx, tif, wav]` — 5.5 spelled two of those `jpeg` and
89
+ `tiff` — and each ordinance status one of the seven or eight the specification
90
+ lists for it. Their values are upper case where `PEDI` and `RESN` are lower, and
91
+ a file that mixes the two is now told so. Completion offers these values as well.
92
+ - **A payload type the validator does not describe is left alone, rather than
93
+ required to be a non-empty string.** Every payload type either schema declares is
94
+ now named, and a test over both says so. A type new to an upstream release used to
95
+ reach the same check as free text and report a missing value on every structure
96
+ that legitimately omits it; it now fails that test instead, where a person can see
97
+ it.
98
+ - **A 5.5.1 date is read against the calendar its escape names, and completion
99
+ offers one.** The twelve month tags lived in four places — the 5.5.1 date grammar,
100
+ the day-length table, the 7.0 `calendar` section and the completion reader's own
101
+ list — and 5.5.1, whose `calendar` section was empty, could use none of them. That
102
+ section now describes all six calendars 5.5.1 names, `validator/calendars.ts` is
103
+ the only reader of it, and the lengths stay in code with a test binding them to the
104
+ months both schemes name. `@#DHEBREW@ 45 XXX 5760` is reported rather than waved
105
+ through on a non-empty check, `@#DJULIAN@ 12 JAN 2000` is checked as a date rather
106
+ than accepted for being non-empty, and a date under `@#DROMAN@` or `@#DUNKNOWN@` —
107
+ calendars 5.5.1 names and gives no month — is still accepted, because an empty
108
+ month table says nothing rather than forbidding everything. Completion in a 5.5.1
109
+ file went from 8 candidates with the cursor after `2 DATE` to 26, the escapes and
110
+ the Gregorian months joining the modifiers; from none after `2 DATE 12` to 14; and
111
+ from none after `2 DATE 12 JAN 2000` to the two epoch spellings 5.5.1 writes.
112
+ Neither conformance corpus moved: 8 diagnostics over 23 official files, 14 795
113
+ over 14 vendor exports. See docs/adr/0012.
114
+
115
+ - **A document with more than about 125k diagnostics reports all of them instead of
116
+ none.** Diagnostics were gathered by spreading each level's array into its
117
+ parent's `push`, and a spread is one argument per element: V8 refuses somewhere
118
+ past 125k, so `createDocument` threw a `RangeError` for exceeding the call stack
119
+ and the caller got an exception where it had asked a question — not a truncated
120
+ list, none. A file of 130 000 records each carrying one tag the schema does not
121
+ define there now reports its 130 000 warnings. One array is carried down the walk
122
+ and pushed into, so nothing is capped and no array is allocated per node.
123
+ - **A line below one carrying a pointer but no tag answers nothing instead of
124
+ throwing.** `0 @I1@`, what a truncated export leaves behind, still becomes a node
125
+ and adopts the lines indented under it, so the walk from a node to the root met a
126
+ step with no tag and threw `TypeError: Cannot read properties of undefined`. Asking
127
+ for the type, the label or the pointer target of such a node — hovering
128
+ `1 NAME John /Doe/` is one way in — now gives the same empty answer already given
129
+ for a path the schema does not resolve.
130
+ - **A year followed by an extension epoch is a date.** `2 DATE 2000 _MYEPOCH` was
131
+ reported as an invalid date value. An extension tag is admissible as a month and
132
+ as an epoch alike, and the reader took the pair for a day and a month — leaving
133
+ the date with no year, which every date must have. A month is now read only when
134
+ the year it must precede follows it, so the epoch reading is the one left. The
135
+ same value under an extension calendar, `2 DATE _MYCAL 1900 _MYEPOCH`, was
136
+ rejected for the same reason and is now read too.
137
+
138
+ - **An unresolved pointer names the xref that failed, and lists nothing.** A `FAMC`
139
+ pointing at a family the file does not declare was reported as "Value for FAMC
140
+ should be in set [@F70@, @F75@, … 2853 more]" — the vocabulary of a closed set,
141
+ spent on ten families that were not the problem, ending in an admission that it
142
+ gave up. A pointer's set is the population of the document, so the diagnostic now
143
+ states the fact about the line: "No FAM record carries @F1450@". An enumeration
144
+ still lists its values, because for `SEX` the four of them are the whole answer.
145
+ Where the payload is not a pointer at all the message is unchanged — there the
146
+ shape is what is wrong, not the identifier.
147
+
5
148
  ## 1.8.2 - 2026-08-18
6
149
 
7
150
  - **A tag written in mixed case is read as written, and the message says which tag
package/README.md CHANGED
@@ -73,22 +73,27 @@ const unresolved = errors.filter(
73
73
  );
74
74
  ```
75
75
 
76
- | Code | Member | Reported when |
77
- | ----------------- | ---------------------- | ----------------------------------------------------------- |
78
- | `VAL001` | `UnknownTag` | a tag the schema does not define in this position |
79
- | `VAL002` | `MissingTag` | a structure the schema requires is absent |
80
- | `VAL003` | `MissingValue` | the payload is absent where one is required |
81
- | `VAL004` | `IncorrectValue` | the payload does not match its data type |
82
- | `VAL005` | `ShouldBeSetValue` | the payload is not one of the permitted values |
83
- | `VAL006` | `MissingRef` | a pointer payload that is not written as a pointer |
84
- | `VAL007` | `ManyOccurrences` | more occurrences than the cardinality allows |
85
- | `VAL008` | `UndocumentedTag` | a GEDCOM 7 extension tag with no `HEAD`.`SCHMA` declaration |
86
- | `VAL009` | `DuplicateDeclaration` | a tag declared more than once in `HEAD`.`SCHMA` |
87
- | `VAL010` | `EmptyEvent` | an event with neither a payload nor substructures |
88
- | `unresolved-xref` | `UnresolvedXref` | an xref naming no record of the required type |
89
- | `invalid-level` | `InvalidLevel` | a level that cannot follow the line above it |
90
- | `LEXER` | `Lexer` | the text could not be tokenized |
91
- | `PARSER` | `Parser` | the tokens could not be assembled into a tree |
76
+ | Code | Member | Reported when |
77
+ | ----------------- | ----------------------- | ------------------------------------------------------------------------------ |
78
+ | `VAL001` | `UnknownTag` | a tag the schema does not define in this position |
79
+ | `VAL002` | `MissingTag` | a structure the schema requires is absent |
80
+ | `VAL003` | `MissingValue` | the payload is absent where one is required |
81
+ | `VAL004` | `IncorrectValue` | the payload does not match its data type |
82
+ | `VAL005` | `ShouldBeSetValue` | the payload is not one of the permitted values |
83
+ | `VAL006` | `MissingRef` | a pointer payload that is not written as a pointer |
84
+ | `VAL007` | `ManyOccurrences` | more occurrences than the cardinality allows |
85
+ | `VAL008` | `UndocumentedTag` | a GEDCOM 7 extension tag with no `HEAD`.`SCHMA` declaration |
86
+ | `VAL009` | `DuplicateDeclaration` | a tag declared more than once in `HEAD`.`SCHMA` |
87
+ | `VAL010` | `EmptyEvent` | an event with neither a payload nor substructures |
88
+ | `VAL011` | `UnsupportedVersion` | the version is one no schema in this package describes |
89
+ | `VAL012` | `UndeterminedVersion` | no version could be read from `HEAD`.`GEDC`.`VERS` |
90
+ | `VAL013` | `SubstitutedVersion` | a version checked against a different version's schema |
91
+ | `VAL014` | `ImpossibleDay` | a date naming a day its calendar does not have, such as `31 FEB` |
92
+ | `VAL015` | `PersonalAncestralFile` | a header naming `SYST` before `GEDC`, so the file is a Personal Ancestral File |
93
+ | `unresolved-xref` | `UnresolvedXref` | an xref naming no record of the required type |
94
+ | `invalid-level` | `InvalidLevel` | a level that cannot follow the line above it |
95
+ | `LEXER` | `Lexer` | the text could not be tokenized |
96
+ | `PARSER` | `Parser` | the tokens could not be assembled into a tree |
92
97
 
93
98
  ## Scripts
94
99
 
@@ -3,6 +3,7 @@
3
3
  * nothing here: the months worth offering depend on the calendar named a few
4
4
  * tokens earlier, and the keywords depend on what the value opened with.
5
5
  */
6
+ import { GedcomScheme } from "../schemes/schema-types";
6
7
  export type DateGrammar = "value" | "period" | "exact";
7
8
  export interface DateSlot {
8
9
  /** A calendar may be named here. */
@@ -14,4 +15,4 @@ export interface DateSlot {
14
15
  /** Bare words the grammar allows here. */
15
16
  keywords: string[];
16
17
  }
17
- export declare function dateSlot(typed: string, grammar: DateGrammar): DateSlot;
18
+ export declare function dateSlot(scheme: GedcomScheme, typed: string, grammar: DateGrammar): DateSlot;
@@ -1,5 +1,5 @@
1
1
  import { GedcomError } from "../types/errors";
2
- import { ASTNode, ASTToken } from "../parser";
2
+ import { ASTNode } from "../parser";
3
3
  import { type GedcomDialect, type VersionResolution } from "../validator/versionRegistry";
4
4
  import { type GedcomCompletion } from "../completion/completion";
5
5
  import type { Position } from "../types/position";
@@ -11,8 +11,7 @@ export interface CreateDocumentOptions {
11
11
  }
12
12
  export declare class GedcomDocument {
13
13
  private nodes;
14
- pointers: Map<string, ASTNode[]>;
15
- xRefs: Map<string, ASTToken[]>;
14
+ private pointers;
16
15
  private errors;
17
16
  private scheme;
18
17
  private resolution;
@@ -29,7 +28,6 @@ export declare class GedcomDocument {
29
28
  getVersionResolution(): VersionResolution | undefined;
30
29
  getDialect(): GedcomDialect | undefined;
31
30
  getCompletions(position: Position, lineText: string): GedcomCompletion[];
32
- updateDocument(_text: string, _range: Range): GedcomDocument;
33
- getErrors(_lang?: string): GedcomError[];
34
- getNodes(_range?: Range): ASTNode[];
31
+ getErrors(): GedcomError[];
32
+ getNodes(): ASTNode[];
35
33
  }