gedcom-ts 2.0.2 → 2026.5.0
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 +175 -0
- package/README.md +460 -88
- package/dist/commons/Act.d.ts +46 -7
- package/dist/commons/DateAct.d.ts +40 -2
- package/dist/commons/Identifier.enum.d.ts +3 -0
- package/dist/commons/IndiAttribute.d.ts +11 -0
- package/dist/commons/IndiGedcomSubLine.d.ts +6 -0
- package/dist/commons/MultimediaFile.d.ts +13 -2
- package/dist/commons/Person.d.ts +27 -4
- package/dist/commons/PersonNameVariant.d.ts +21 -0
- package/dist/commons/Place.d.ts +19 -0
- package/dist/commons/clonePrimitives.d.ts +17 -0
- package/dist/commons/gedcomEventTags.d.ts +17 -0
- package/dist/dataset/ReadGedEdit.d.ts +27 -0
- package/dist/dataset/cloneModels.d.ts +8 -0
- package/dist/dataset/graphOps.d.ts +29 -0
- package/dist/dataset/index.d.ts +9 -0
- package/dist/dataset/readGedCommands.d.ts +46 -0
- package/dist/dataset/readGedMutations.d.ts +12 -0
- package/dist/dataset/validation.d.ts +36 -0
- package/dist/edit/ActEdit.d.ts +37 -0
- package/dist/edit/ActMediaEdit.d.ts +21 -0
- package/dist/edit/ActsEdit.d.ts +34 -0
- package/dist/edit/DateActEdit.d.ts +37 -0
- package/dist/edit/GedcomExportOptionsEdit.d.ts +20 -0
- package/dist/edit/IndiAttributesEdit.d.ts +38 -0
- package/dist/edit/MultimediaFileEdit.d.ts +17 -0
- package/dist/edit/NameVariantsEdit.d.ts +43 -0
- package/dist/edit/NoteEdit.d.ts +18 -0
- package/dist/edit/NotesEdit.d.ts +25 -0
- package/dist/edit/PersonEdit.d.ts +47 -0
- package/dist/edit/PersonMediaEdit.d.ts +27 -0
- package/dist/edit/PlaceEdit.d.ts +23 -0
- package/dist/edit/factories.d.ts +35 -0
- package/dist/edit/index.d.ts +33 -0
- package/dist/export/GEDCOM.d.ts +33 -4
- package/dist/import/LoadFile.d.ts +14 -0
- package/dist/import/PreservedRecordsBuffer.d.ts +21 -0
- package/dist/import/ReadGed.d.ts +49 -0
- package/dist/import/SplitedInformations.d.ts +35 -3
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +17 -4
- package/dist/index.mjs +1 -1
- package/dist/utils/gedcom/actExtraction.d.ts +3 -1
- package/dist/utils/gedcom/datasetVersion.d.ts +7 -0
- package/dist/utils/gedcom/extractIndiNamesAndAttributes.d.ts +4 -0
- package/dist/utils/gedcom/importGedcomNote.d.ts +17 -0
- package/dist/utils/gedcom/labelKeyedRecords.d.ts +13 -0
- package/dist/utils/gedcom/mediaFormFromUri.d.ts +2 -0
- package/dist/utils/gedcom/parseStandaloneObje.d.ts +9 -0
- package/dist/utils/gedcom/personName.d.ts +7 -0
- package/dist/utils/gedcom/pointers.d.ts +16 -0
- package/dist/utils/gedcom/uriBasename.d.ts +2 -0
- package/dist/utils/multimedia/registerTrackedMedia.d.ts +10 -0
- package/dist/version.d.ts +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,13 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
`gedcom-ts` is a browser-oriented TypeScript library to:
|
|
4
4
|
|
|
5
|
-
- import genealogy data from GEDCOM (`.ged`) or ZIP (`.zip`)
|
|
6
|
-
- work with a typed JSON model (persons, acts, notes, media,
|
|
7
|
-
-
|
|
5
|
+
- import genealogy data from GEDCOM (`.ged`) or ZIP (`.zip` / `.gdz`)
|
|
6
|
+
- work with a typed JSON model (persons, acts, dates, places, notes, media, name variants, attributes)
|
|
7
|
+
- edit the model in-place through a fluent, chainable API (`editPerson`, `editAct`, …)
|
|
8
|
+
- export data back to GEDCOM (`.ged`) or GEDZIP (`.zip`)
|
|
9
|
+
|
|
10
|
+
## Live demo
|
|
11
|
+
|
|
12
|
+
A graphical demo showcasing the public API (import a `.ged` / `.zip`, browse the typed model, export back) is available at **[https://gedcomts.jaunet.me](https://gedcomts.jaunet.me)**
|
|
8
13
|
|
|
9
14
|
## Project
|
|
10
15
|
|
|
11
16
|
- NPM package: [gedcom-ts](https://www.npmjs.com/package/gedcom-ts)
|
|
17
|
+
- **Versioning (CalVer)** : releases use **`AAAA.M.micro`** (e.g. `2026.5.0` = May 2026). The npm `version`, `GEDCOM_LIBRARY_VERSION` (export `HEAD`.`SOUR`.`VERS`), and release branches share the same label. Older changelog entries still refer to semver (`2.1.0`, …). See [CHANGELOG.md](CHANGELOG.md).
|
|
18
|
+
- **GEDCOM 7 roadmap** (spec gedcom.io, coverage matrix, prioritized gaps): [docs/GEDCOM7-roadmap.md](docs/GEDCOM7-roadmap.md)
|
|
12
19
|
|
|
13
20
|
## Installation
|
|
14
21
|
|
|
@@ -19,58 +26,144 @@ npm install gedcom-ts
|
|
|
19
26
|
## Runtime Requirements
|
|
20
27
|
|
|
21
28
|
- modern browser runtime (`File`, `Blob`, `XMLHttpRequest`, `URL.createObjectURL`)
|
|
22
|
-
- for pure Node.js usage, DOM polyfills are required
|
|
29
|
+
- for pure Node.js usage, DOM polyfills are required (the library targets browsers)
|
|
23
30
|
|
|
24
|
-
##
|
|
31
|
+
## Quick start
|
|
25
32
|
|
|
26
33
|
```ts
|
|
27
|
-
import { importGedFile } from "gedcom-ts";
|
|
34
|
+
import { importGedFile, ExportGedzipFile } from "gedcom-ts";
|
|
28
35
|
|
|
29
|
-
async function
|
|
36
|
+
async function roundTrip(file: File) {
|
|
30
37
|
const readGed = await importGedFile(file);
|
|
31
|
-
|
|
38
|
+
const persons = readGed.persons;
|
|
39
|
+
|
|
40
|
+
if (persons.length > 0) {
|
|
41
|
+
persons[0].lastname = persons[0].lastname.toUpperCase();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await new ExportGedzipFile("updated-tree", persons).download();
|
|
32
45
|
}
|
|
33
46
|
```
|
|
34
47
|
|
|
35
|
-
|
|
48
|
+
Typical workflow:
|
|
49
|
+
|
|
50
|
+
1. import a file with `importGedFile` (or start from `createEmptyReadGed()`)
|
|
51
|
+
2. read / mutate the typed `Person`, `Act`, `Place`, `Note`, `MultimediaFile` objects (directly or through `editPerson` / `editAct` / `editPlace` / …)
|
|
52
|
+
3. export as `.ged` (`ExportGedcomFile`) or `.zip` (`ExportGedzipFile`)
|
|
53
|
+
|
|
54
|
+
## Public API reference
|
|
55
|
+
|
|
56
|
+
Everything exported from `gedcom-ts` is documented below with a short description and a minimal usage snippet. The full list mirrors the public exports of `src/index.ts`.
|
|
57
|
+
|
|
58
|
+
### Importing a file
|
|
36
59
|
|
|
37
|
-
|
|
38
|
-
- a ZIP containing one GED file + optional media files
|
|
60
|
+
#### `importGedFile(file: File): Promise<ReadGed>`
|
|
39
61
|
|
|
40
|
-
|
|
62
|
+
Detects the format from the file MIME / extension and dispatches to the right reader.
|
|
41
63
|
|
|
42
|
-
|
|
64
|
+
- accepts a single `.ged` file
|
|
65
|
+
- accepts a `.zip` / `.gdz` archive containing one `.ged` + optional media files
|
|
66
|
+
- throws `Error(IMPORT_ERR_ZIP_GED_UNREADABLE)` when the embedded `.ged` cannot be decoded (typical cause: password-protected ZIP)
|
|
43
67
|
|
|
44
68
|
```ts
|
|
45
|
-
import {
|
|
69
|
+
import { importGedFile } from "gedcom-ts";
|
|
46
70
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
71
|
+
const readGed = await importGedFile(fileInput.files![0]);
|
|
72
|
+
console.log(readGed.persons.length, readGed.datasetVersion);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### `createEmptyReadGed(options?): ReadGed`
|
|
76
|
+
|
|
77
|
+
Creates an empty graph with the same runtime shape as a successful import (empty `persons`, initialized `mapPersons` / `partnersMap` / `childsMap` / `placesMap` / `mapFiles`). Use it to start a brand-new tree without parsing a file. `datasetVersion` is set to `"7.0"`.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { createEmptyReadGed } from "gedcom-ts";
|
|
81
|
+
|
|
82
|
+
const readGed = createEmptyReadGed();
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### `IMPORT_ERR_ZIP_GED_UNREADABLE: string`
|
|
86
|
+
|
|
87
|
+
Sentinel error message thrown by `importGedFile` when a `.ged` inside a ZIP cannot be decoded (typically because the ZIP is password-protected). Compare with `error.message === IMPORT_ERR_ZIP_GED_UNREADABLE` to display a tailored message.
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { importGedFile, IMPORT_ERR_ZIP_GED_UNREADABLE } from "gedcom-ts";
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
await importGedFile(file);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error instanceof Error && error.message === IMPORT_ERR_ZIP_GED_UNREADABLE) {
|
|
96
|
+
alert("Please unzip the archive manually and import the .ged file.");
|
|
97
|
+
}
|
|
52
98
|
}
|
|
53
99
|
```
|
|
54
100
|
|
|
55
|
-
|
|
101
|
+
#### `ReadGed`
|
|
102
|
+
|
|
103
|
+
Result of an import. Top-level GEDCOM records not modeled into the typed graph (`OBJE`, `REPO`, `SOUR`, `SUBM`, …) are kept in order on `readGed.preservedTopLevelRecords` for round-trip export.
|
|
104
|
+
|
|
105
|
+
Notable members:
|
|
106
|
+
|
|
107
|
+
| Member | Description |
|
|
108
|
+
| --- | --- |
|
|
109
|
+
| `persons: Person[]` | Imported individuals. |
|
|
110
|
+
| `mapPersons: Map<number, Person>` | `INDI` (integer) → `Person`. |
|
|
111
|
+
| `partnersMap: Map<number, Person[]>` | Family id → spouses. |
|
|
112
|
+
| `childsMap: Map<number, Person[]>` | Family id → children. |
|
|
113
|
+
| `placesMap: Map<string, Place>` | City name → `Place` (first occurrence wins). |
|
|
114
|
+
| `mapFiles: Map<string, File>` | Relative path → media `File` (for ZIP imports). |
|
|
115
|
+
| `datasetVersion: GedcomDatasetVersion` | `"7.0"`, `"5.5"` or `"unknown"`. |
|
|
116
|
+
| `preservedTopLevelRecords: string[]` | Raw blocks for partial round-trip. |
|
|
117
|
+
| `resolveIndividualPointer(raw)` | Resolves `@I12@`, `I12`, `@Homer_Simpson@`, `Homer_Simpson` to a `Person`. |
|
|
118
|
+
| `getChildrenForParent(parent)` | All children attached to a parent (via `FAMS`). |
|
|
119
|
+
| `getChildrenOfFamily(familyId)` | Children of a single family. |
|
|
120
|
+
| `groupPartners()` | Rebuilds `partnersMap` / `childsMap` after editing links. |
|
|
121
|
+
| `generateUniqueIndi()` | Next free `INDI` number for new persons. |
|
|
122
|
+
| `rehydratePlacesFromActs()` | Rebuilds `placesMap` from act places (e.g. after manual graph edits). Use `editReadGed(readGed).addPerson(...)` to register persons so maps stay coherent. |
|
|
56
123
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
3. export as `.ged` or `.zip`
|
|
124
|
+
```ts
|
|
125
|
+
import { ReadGed } from "gedcom-ts";
|
|
60
126
|
|
|
61
|
-
|
|
127
|
+
function describe(readGed: ReadGed) {
|
|
128
|
+
return {
|
|
129
|
+
version: readGed.datasetVersion,
|
|
130
|
+
persons: readGed.persons.length,
|
|
131
|
+
places: readGed.placesMap.size,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
```
|
|
62
135
|
|
|
63
|
-
###
|
|
136
|
+
### Exporting
|
|
137
|
+
|
|
138
|
+
#### `ExportGedcomFile`
|
|
139
|
+
|
|
140
|
+
Writes a GEDCOM 7 (`.ged`) file. Three call signatures are supported:
|
|
64
141
|
|
|
65
142
|
```ts
|
|
66
|
-
|
|
143
|
+
new ExportGedcomFile(persons);
|
|
144
|
+
new ExportGedcomFile(title, persons);
|
|
145
|
+
new ExportGedcomFile(title, persons, options);
|
|
146
|
+
```
|
|
67
147
|
|
|
68
|
-
|
|
69
|
-
|
|
148
|
+
`.download()` triggers a browser download. `.toString()` returns the GEDCOM text.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { ExportGedcomFile, type ReadGed } from "gedcom-ts";
|
|
152
|
+
|
|
153
|
+
function exportGed(readGed: ReadGed) {
|
|
154
|
+
new ExportGedcomFile("my-tree", readGed.persons, {
|
|
155
|
+
extraTopLevelRecords: readGed.preservedTopLevelRecords,
|
|
156
|
+
headLanguageTag: "fr-FR",
|
|
157
|
+
headCopyright: "© 2026 Family Archive",
|
|
158
|
+
headDestination: "https://gedcom.io/",
|
|
159
|
+
headSchemaTagDefs: [{ tag: "_FOO", uri: "https://example.com/foo" }],
|
|
160
|
+
}).download();
|
|
70
161
|
}
|
|
71
162
|
```
|
|
72
163
|
|
|
73
|
-
|
|
164
|
+
#### `ExportGedzipFile`
|
|
165
|
+
|
|
166
|
+
Writes a `.zip` (GEDZIP) bundling the GEDCOM and all attached `MultimediaFile` payloads.
|
|
74
167
|
|
|
75
168
|
```ts
|
|
76
169
|
import { ExportGedzipFile, Person } from "gedcom-ts";
|
|
@@ -80,37 +173,132 @@ async function exportZip(persons: Person[]) {
|
|
|
80
173
|
}
|
|
81
174
|
```
|
|
82
175
|
|
|
83
|
-
|
|
176
|
+
#### `GedcomExportOptions`
|
|
177
|
+
|
|
178
|
+
Options shared by both exporters:
|
|
179
|
+
|
|
180
|
+
| Option | Effect |
|
|
181
|
+
| --- | --- |
|
|
182
|
+
| `extraTopLevelRecords` | Raw `0 …` blocks re-emitted before `SUBM` / `TRLR` (round-trip with `readGed.preservedTopLevelRecords`). |
|
|
183
|
+
| `headLanguageTag` | BCP 47 tag for `HEAD`.`LANG` (defaults to `en-US`). |
|
|
184
|
+
| `headCopyright` | One-line `1 COPR` notice. |
|
|
185
|
+
| `headDestination` | Value of `HEAD`.`DEST` (target app / URI). |
|
|
186
|
+
| `headSchemaTagDefs` | Extension tag definitions emitted as `HEAD`.`SCHMA` / `2 TAG …`. |
|
|
84
187
|
|
|
85
|
-
|
|
188
|
+
### Domain model
|
|
86
189
|
|
|
87
|
-
|
|
190
|
+
#### `Person`, `Sex`
|
|
88
191
|
|
|
89
192
|
```ts
|
|
90
193
|
import { Person, Sex } from "gedcom-ts";
|
|
91
194
|
|
|
92
195
|
const person = new Person();
|
|
93
196
|
person.INDI = 1;
|
|
94
|
-
person.SEX = Sex.M;
|
|
197
|
+
person.SEX = Sex.M; // M | F | U | X
|
|
95
198
|
person.firstnames = ["Jean"];
|
|
96
199
|
person.lastname = "DUPONT";
|
|
200
|
+
|
|
201
|
+
person.addMultimedia(/* MultimediaFile */);
|
|
202
|
+
person.deleteMultimedia("1/photo.jpg");
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Key fields: `INDI`, `sosa`, `SEX`, `firstnames`, `lastname`, `FAMC`, `FAMS`, `acts`, `notes`, `multimediaFiles`, `nameVariants`, `attributes`.
|
|
206
|
+
|
|
207
|
+
#### `PersonGedcomImportOptions` (type)
|
|
208
|
+
|
|
209
|
+
Optional hints consumed by `Person.createPersonJson` when re-parsing a single individual block (label-keyed pointers for `FAMS` / `FAMC` / `NOTE`, and standalone `OBJE` payloads). Useful when assembling a graph manually outside `ReadGed`.
|
|
210
|
+
|
|
211
|
+
#### `PersonNameVariant`, `PersonNameTranslation`
|
|
212
|
+
|
|
213
|
+
Lossless representation of every `1 NAME` block of an individual (type, `NPFX`/`GIVN`/`SURN`/… parts, `TRAN` translations). `Person.nameVariants` keeps them in order so alias / translation data survive an import → export round-trip.
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { PersonNameVariant, PersonNameTranslation } from "gedcom-ts";
|
|
217
|
+
|
|
218
|
+
const variant = new PersonNameVariant("Jean /Dupont/", "BIRTH");
|
|
219
|
+
variant.parts.push({ level: "2", tag: "GIVN", value: "Jean" });
|
|
220
|
+
variant.translations.push(new PersonNameTranslation("ジャン /デュポン/", "jp"));
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### `IndiAttribute`, `IndiGedcomSubLine`
|
|
224
|
+
|
|
225
|
+
Generic level-1 individual attributes (`FACT`, `DSCR`, `CAST`, `EDUC`, `OCCU`, `RELI`, `TITL`, `RESN`, …) with their sub-lines preserved (`IndiGedcomSubLine = { level; tag; value }`).
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
import { IndiAttribute } from "gedcom-ts";
|
|
229
|
+
|
|
230
|
+
const occ = new IndiAttribute("OCCU", "Blacksmith", [
|
|
231
|
+
{ level: "2", tag: "DATE", value: "1820" },
|
|
232
|
+
]);
|
|
97
233
|
```
|
|
98
234
|
|
|
99
|
-
|
|
235
|
+
#### `Act`, `Acts`, `TypeAct`, `ActConstructionOptions`
|
|
236
|
+
|
|
237
|
+
`Act` models an individual or family event (BIRT, MARR, etc.). `Acts` is the ordered collection on a `Person`. `TypeAct` is the union of every supported GEDCOM 7 event tag (= `Gedcom7EventTag`). Event-level notes live on `act.notes` (`2 NOTE` / `3 CONT` in GEDCOM); they are distinct from `person.notes` (`1 NOTE` on `INDI`) and round-trip through ZIP export/import.
|
|
100
238
|
|
|
101
239
|
```ts
|
|
102
|
-
import { Act, Acts,
|
|
240
|
+
import { Act, Acts, Identifier, type TypeAct } from "gedcom-ts";
|
|
103
241
|
|
|
104
|
-
const actType: TypeAct = Identifier.BIRT;
|
|
105
|
-
const act = new Act(actType);
|
|
106
242
|
const acts = new Acts();
|
|
107
|
-
|
|
243
|
+
const type: TypeAct = Identifier.BIRT;
|
|
244
|
+
acts.add(new Act(type));
|
|
245
|
+
acts.sortByDate();
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`ActConstructionOptions` controls the extras when building an `Act` manually:
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
import { Act, DateAct, Identifier } from "gedcom-ts";
|
|
252
|
+
|
|
253
|
+
const act = new Act(
|
|
254
|
+
Identifier.EVEN,
|
|
255
|
+
new DateAct("12 JAN 1901"),
|
|
256
|
+
null,
|
|
257
|
+
null,
|
|
258
|
+
null,
|
|
259
|
+
undefined,
|
|
260
|
+
undefined,
|
|
261
|
+
{
|
|
262
|
+
evenDescription: "Won a medal",
|
|
263
|
+
evenTypeLabel: "Award",
|
|
264
|
+
sdateAct: new DateAct("13 JAN 1901"),
|
|
265
|
+
eventPhrases: ["family gathering"],
|
|
266
|
+
preservedSubrecordPrefix: [],
|
|
267
|
+
preservedSubrecordSuffix: [],
|
|
268
|
+
},
|
|
269
|
+
);
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
#### `GEDCOM_7_ALL_EVENT_TAGS`, `GEDCOM_7_EVENT_SORT_ORDER`, `GEDCOM_7_EVENT_TAG_SET`, `GEDCOM_7_PAIR_UNION_EVENT_TAGS`, `Gedcom7EventTag`, `Gedcom7PairUnionEventTag`
|
|
273
|
+
|
|
274
|
+
Canonical lists of GEDCOM 7 event tags (`INDIVIDUAL_EVENT_STRUCTURE` ∪ `FAMILY_EVENT_STRUCTURE`, LDS ordinances excluded). Use them to build UI selects or guard custom logic. `GEDCOM_7_PAIR_UNION_EVENT_TAGS` lists tags commonly used when creating a family union via `createMarriageFamily` (`ENGA`, `MARB`, `MARC`, `MARL`, `MARR`, `MARS`).
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
import {
|
|
278
|
+
GEDCOM_7_ALL_EVENT_TAGS,
|
|
279
|
+
GEDCOM_7_EVENT_SORT_ORDER,
|
|
280
|
+
GEDCOM_7_EVENT_TAG_SET,
|
|
281
|
+
type Gedcom7EventTag,
|
|
282
|
+
} from "gedcom-ts";
|
|
283
|
+
|
|
284
|
+
const options: Gedcom7EventTag[] = [...GEDCOM_7_ALL_EVENT_TAGS];
|
|
285
|
+
const isEvent = GEDCOM_7_EVENT_TAG_SET.has("MARR");
|
|
286
|
+
const sorted = [...GEDCOM_7_EVENT_SORT_ORDER];
|
|
108
287
|
```
|
|
109
288
|
|
|
110
|
-
|
|
289
|
+
#### `DateAct`, `Day`, `Month`, `dateToDateLine`, `days`, `months`, `TypeDateActSimpleQualifier`
|
|
290
|
+
|
|
291
|
+
GEDCOM dates with full GEDCOM 7 / 5.5 support, including `INT`, `EST`, `CAL`, `BET … AND …`, `FROM … TO …`, ISO `YYYY-MM-DD`, `3 PHRASE` under `DATE`, `3 TIME`, and a verbatim fallback for unparseable payloads.
|
|
111
292
|
|
|
112
293
|
```ts
|
|
113
|
-
import {
|
|
294
|
+
import {
|
|
295
|
+
DateAct,
|
|
296
|
+
Day,
|
|
297
|
+
Month,
|
|
298
|
+
dateToDateLine,
|
|
299
|
+
days,
|
|
300
|
+
months,
|
|
301
|
+
} from "gedcom-ts";
|
|
114
302
|
|
|
115
303
|
const day: Day = 12;
|
|
116
304
|
const month: Month = months[0]; // JAN
|
|
@@ -120,38 +308,53 @@ const formattedDate = dateAct.date;
|
|
|
120
308
|
const knownDaysCount = days.length;
|
|
121
309
|
```
|
|
122
310
|
|
|
123
|
-
|
|
311
|
+
`TypeDateActSimpleQualifier` is the union of single-anchor qualifiers (`BEF | ABT | AFT | INT | EST | CAL`) accepted by `DateAct.updateQualifiedDate` and by `DateActEdit.setQualified`.
|
|
312
|
+
|
|
313
|
+
#### `Place`, `CoordinateGPS`
|
|
124
314
|
|
|
125
315
|
```ts
|
|
126
|
-
import {
|
|
316
|
+
import { Place, CoordinateGPS } from "gedcom-ts";
|
|
127
317
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
318
|
+
const place = new Place("Paris, FR", new CoordinateGPS(48.8566, 2.3522));
|
|
319
|
+
place.setFromGedcom7PlacPayload("Paris, Île-de-France, France");
|
|
320
|
+
const payload = place.toGedcom7PlacPayload(); // "Paris, , Île-de-France, France"
|
|
131
321
|
```
|
|
132
322
|
|
|
133
|
-
|
|
323
|
+
`Place` understands the `City, County, State, Country` GEDCOM 7 list (1 to 4+ segments) and exposes `placPhrase` for `3 PHRASE` under `PLAC` (import + export).
|
|
324
|
+
|
|
325
|
+
#### `MultimediaFile`, `MultimediaFiles`
|
|
326
|
+
|
|
327
|
+
Wraps either a local `File`, an external URI (`sourceUri`) or an OBJE pointer (`objeXrefId`) so the same model can survive a GED / ZIP round-trip.
|
|
134
328
|
|
|
135
329
|
```ts
|
|
136
|
-
import {
|
|
330
|
+
import { MultimediaFile, MultimediaFiles } from "gedcom-ts";
|
|
137
331
|
|
|
138
|
-
const
|
|
332
|
+
const bucket = new MultimediaFiles();
|
|
333
|
+
bucket.relativePath = "1/BIRT";
|
|
334
|
+
bucket.add(new MultimediaFile(new File(["img"], "birth.jpg")));
|
|
335
|
+
|
|
336
|
+
const remote = new MultimediaFile(undefined, "https://example.com/p.jpg");
|
|
337
|
+
const isExportable = remote.hasExportablePayload();
|
|
139
338
|
```
|
|
140
339
|
|
|
141
|
-
|
|
340
|
+
#### `Note`, `Notes`, `TypeNote`
|
|
142
341
|
|
|
143
342
|
```ts
|
|
144
|
-
import { Notes, Note,
|
|
343
|
+
import { Notes, Note, Identifier, type TypeNote } from "gedcom-ts";
|
|
145
344
|
|
|
146
345
|
const typeNote: TypeNote = Identifier.CONT;
|
|
147
346
|
const note = new Note();
|
|
148
347
|
note.updateType(typeNote);
|
|
149
348
|
note.updateLines(["first line", "second line"]);
|
|
349
|
+
|
|
150
350
|
const notes = new Notes();
|
|
151
351
|
notes.addNote(note);
|
|
352
|
+
notes.removeFromIndex(0);
|
|
152
353
|
```
|
|
153
354
|
|
|
154
|
-
|
|
355
|
+
#### `Identifier` (and deprecated `Identificator`)
|
|
356
|
+
|
|
357
|
+
Enum of every GEDCOM tag the library refers to (`INDI`, `BIRT`, `MARR`, `DATE`, `PLAC`, …). Prefer `Identifier`; `Identificator` is kept as a deprecated alias.
|
|
155
358
|
|
|
156
359
|
```ts
|
|
157
360
|
import { Identifier } from "gedcom-ts";
|
|
@@ -159,93 +362,262 @@ import { Identifier } from "gedcom-ts";
|
|
|
159
362
|
const birthTag = Identifier.BIRT;
|
|
160
363
|
```
|
|
161
364
|
|
|
162
|
-
|
|
365
|
+
#### `EventsByYears`, `ActsByYear`
|
|
366
|
+
|
|
367
|
+
Group acts by year, deduplicating per individual. Handy to build chronological timelines.
|
|
163
368
|
|
|
164
369
|
```ts
|
|
165
|
-
import {
|
|
370
|
+
import { EventsByYears, ActsByYear } from "gedcom-ts";
|
|
166
371
|
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
372
|
+
const grouped = new EventsByYears(person.acts.list);
|
|
373
|
+
for (const bucket of grouped.events) {
|
|
374
|
+
console.log(bucket.year, bucket.list.length);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const yearBucket = new ActsByYear(1901);
|
|
170
378
|
```
|
|
171
379
|
|
|
172
|
-
###
|
|
380
|
+
### Edit layer (in-place, chainable)
|
|
381
|
+
|
|
382
|
+
Wrap an existing model object to mutate it with a fluent API. Every method returns `this`, so you can chain. `.value` exposes the underlying target.
|
|
173
383
|
|
|
174
384
|
```ts
|
|
175
|
-
import {
|
|
385
|
+
import { editPerson, editDateAct, DateAct } from "gedcom-ts";
|
|
176
386
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
387
|
+
editPerson(person)
|
|
388
|
+
.setLastname("Dupont")
|
|
389
|
+
.setFirstnames(["Jean", "Marie"])
|
|
390
|
+
.acts()
|
|
391
|
+
.at(0)
|
|
392
|
+
.setDateAct(new DateAct("1 JAN 1900"));
|
|
393
|
+
|
|
394
|
+
editDateAct(person.acts.list[0].dateAct!).setExactDate(1900, "JAN", 1);
|
|
183
395
|
```
|
|
184
396
|
|
|
185
|
-
|
|
397
|
+
| Helper / class | Purpose |
|
|
398
|
+
| --- | --- |
|
|
399
|
+
| `editPerson(person)` / `PersonEdit` | Identity, `FAMS` / `FAMC` / `removeFams*`, `nameVariants()` / `attributes()` / `multimedia()`, bulk `clear*`, entry points to `acts()` and `notes()`. |
|
|
400
|
+
| `editActs(acts)` / `ActsEdit` | `add`, `addNew`, `insertAt`, `insertNewAt`, `replaceAt`, `removeAt`, `removeAct`, `removeLast`, `clear`, `sortByDate`, `at`, `indexOfAct`. |
|
|
401
|
+
| `editAct(act)` / `ActEdit` | `setType`, `setIndis`, dates, place, EVEN fields, preserved lines, `notes()`, `multimedia()`, `clearNotes`, `clearMultimedia`. |
|
|
402
|
+
| `editDateAct(dateAct)` / `DateActEdit` | `clear`, `applyGedcomPayload`, `setExactDate`, `setQualified`, `setBetween`, `setFromTo`, `setTime`, `setDatePhrase` / `appendDatePhrase`, `setVerbatimPayload`. |
|
|
403
|
+
| `editPlace(place)` / `PlaceEdit` | `setFromGedcom7Payload`, `setCity`, `setCounty`, `setState`, `setCountry`, `setPlacPhrase` / `appendPlacPhrase` / `clearPlacPhrase`, `setCoordinates` / `clearCoordinates` / `replaceCoordinateModel`, `clearStructured`. |
|
|
404
|
+
| `editNotes(notes)` / `NotesEdit` | `add`, `addNew`, `insertAt`, `insertNewAt`, `replaceAt`, `removeAt`, `removeNote`, `removeLast`, `clear`, `at`, `indexOfNote`. |
|
|
186
405
|
|
|
187
|
-
|
|
188
|
-
import { importGedFile } from "gedcom-ts";
|
|
406
|
+
### Dataset editing: `ReadGed`, graph, clone, export options, validation
|
|
189
407
|
|
|
190
|
-
|
|
408
|
+
Higher-level helpers complement the per-object edit facades:
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
import type { GedcomExportOptions } from "gedcom-ts";
|
|
412
|
+
import {
|
|
413
|
+
createEmptyReadGed,
|
|
414
|
+
createPersonStub,
|
|
415
|
+
DateAct,
|
|
416
|
+
Sex,
|
|
417
|
+
editReadGed,
|
|
418
|
+
editGedcomExportOptions,
|
|
419
|
+
clonePerson,
|
|
420
|
+
cloneAct,
|
|
421
|
+
createMarriageFamily,
|
|
422
|
+
linkChildToFamily,
|
|
423
|
+
removeFamilyReferencesFromDataset,
|
|
424
|
+
addPersonToReadGed,
|
|
425
|
+
validateReadGed,
|
|
426
|
+
Identifier,
|
|
427
|
+
Act,
|
|
428
|
+
} from "gedcom-ts";
|
|
429
|
+
|
|
430
|
+
const readGed = createEmptyReadGed();
|
|
431
|
+
const p = createPersonStub(readGed.generateUniqueIndi(), { sex: Sex.M, firstnames: ["Jean"], lastname: "Dupont" });
|
|
432
|
+
editReadGed(readGed).addPerson(p);
|
|
433
|
+
p.acts.add(new Act(Identifier.BIRT, new DateAct("1900")));
|
|
434
|
+
|
|
435
|
+
editReadGed(readGed).preserved().append("0 @S42@ SOUR Custom");
|
|
436
|
+
|
|
437
|
+
const opts: GedcomExportOptions = {};
|
|
438
|
+
editGedcomExportOptions(opts)
|
|
439
|
+
.setHeadCopyright("© 2026")
|
|
440
|
+
.setExtraTopLevelRecords([...readGed.preservedTopLevelRecords]);
|
|
441
|
+
|
|
442
|
+
const twin = clonePerson(p, readGed.generateUniqueIndi());
|
|
443
|
+
const birthCopy = cloneAct(p.acts.list[0]!);
|
|
444
|
+
|
|
445
|
+
// const fam = createMarriageFamily(readGed, spouseA, spouseB, { dateAct: new DateAct("1 JAN 2000") }, { eventTag: Identifier.ENGA });
|
|
446
|
+
// linkChildToFamily(readGed, child, fam);
|
|
447
|
+
// removeFamilyReferencesFromDataset(readGed, fam);
|
|
448
|
+
|
|
449
|
+
validateReadGed(readGed, {
|
|
450
|
+
checkMarrParticipants: true,
|
|
451
|
+
checkFamcWithoutSpouses: true,
|
|
452
|
+
checkFamsWithoutSpouses: true,
|
|
453
|
+
checkDuplicateFamsEntries: true,
|
|
454
|
+
checkAncestorCycles: true,
|
|
455
|
+
});
|
|
191
456
|
```
|
|
192
457
|
|
|
193
|
-
|
|
458
|
+
#### Commands (invariants before mutation)
|
|
459
|
+
|
|
460
|
+
`tryAddPersonToReadGed`, `tryRemovePersonFromReadGedByIndi`, `tryLinkChildToFamily`, `tryUnlinkChildFromFamily`, and `tryCreateMarriageFamily` return a `CommandResult` (`ok` + `issues` or `value` + optional `warnings`). `editReadGed(...).addPerson` uses these checks internally (throws on blocking errors). For UI or transactional flows, prefer the `try*` APIs and inspect `commandBlockingIssues`.
|
|
461
|
+
|
|
462
|
+
| Export | Role |
|
|
463
|
+
| --- | --- |
|
|
464
|
+
| `editReadGed(readGed)` / `ReadGedEdit` | `addPerson`, `removePersonByIndi`, `preserved()` → `PreservedTopLevelEdit` (`append`, `insertAt`, `replaceAt`, `removeAt`, `clear`) on `preservedTopLevelRecords`. Low-level helpers: `addPersonToReadGed`, `removePersonFromReadGedByIndi`. |
|
|
465
|
+
| `editGedcomExportOptions(opts)` / `GedcomExportOptionsEdit` | Fluent setters for `extraTopLevelRecords`, `headLanguageTag`, `headCopyright`, `headDestination`, `headSchemaTagDefs`. |
|
|
466
|
+
| `clonePerson(person, newIndi)` / `cloneAct(act)` / `person.clone` / `act.clone` | Deep copies for templates or undo stacks. |
|
|
467
|
+
| `nextFamilyId(persons)` | Next internal family id `F`. |
|
|
468
|
+
| `createMarriageFamily`, `linkChildToFamily`, `unlinkChildFromFamily`, `removeFamilyReferencesFromDataset` | Crée une `F` et des actes sur les deux conjoints. 5ᵉ argument : `{ eventTag }` (défaut `MARR`) pour `ENGA`, bans, contrat, `EVEN`+`CreateActInit`, etc. Voir `GEDCOM_7_PAIR_UNION_EVENT_TAGS`. |
|
|
469
|
+
| `validateReadGed`, `assertReadGedConsistent`, `validatePerson`, `assertPersonConsistent` | Typed `code` / `severity` (`error` \| `warn`). Options: `checkMarrParticipants`, `checkDuplicateIndis`, `checkFamcWithoutSpouses`, `checkFamsWithoutSpouses`, `checkDuplicateFamsEntries`, `checkAncestorCycles`. Assertions: `failOn` (default: `error`). |
|
|
470
|
+
| `tryAddPersonToReadGed`, `tryRemovePersonFromReadGedByIndi`, `tryLinkChildToFamily`, `tryUnlinkChildFromFamily`, `tryCreateMarriageFamily`, `commandBlockingIssues` | Command layer with `CommandResult` / `validate*Command` prechecks. |
|
|
471
|
+
|
|
472
|
+
### Utilities
|
|
473
|
+
|
|
474
|
+
#### `createSosaMap(root, partnersMap)`
|
|
475
|
+
|
|
476
|
+
Computes a Sosa numbering (Ahnentafel) starting at `root` (Sosa 1) and recursively walking ancestors via `partnersMap`.
|
|
194
477
|
|
|
195
478
|
```ts
|
|
196
479
|
import { createSosaMap, Person } from "gedcom-ts";
|
|
197
480
|
|
|
198
|
-
const
|
|
199
|
-
root.INDI
|
|
200
|
-
const sosaMap = createSosaMap(root, new Map());
|
|
481
|
+
const sosaMap = createSosaMap(root, readGed.partnersMap);
|
|
482
|
+
// e.g. sosaMap.get(root.INDI) === 1
|
|
201
483
|
```
|
|
202
484
|
|
|
203
|
-
|
|
485
|
+
#### `remainingTypesAct(acts, actToUpdate?)`
|
|
486
|
+
|
|
487
|
+
Returns the list of event types still allowed for a person’s `Acts`, enforcing the “unique per individual” rule for `BIRT` / `DEAT` / `BURI` / `CHR` while keeping every other tag selectable. Pass the currently edited act as `actToUpdate` so its own type stays in the list when re-opening a form.
|
|
204
488
|
|
|
205
489
|
```ts
|
|
206
490
|
import { remainingTypesAct, Acts } from "gedcom-ts";
|
|
207
491
|
|
|
208
|
-
const
|
|
492
|
+
const available = remainingTypesAct(new Acts());
|
|
209
493
|
```
|
|
210
494
|
|
|
211
|
-
|
|
495
|
+
#### `getCityCoordinates(cityName, callback)`
|
|
496
|
+
|
|
497
|
+
Queries OpenStreetMap’s Nominatim API and returns a list of candidate `Place` objects with coordinates. Browser-only (uses `XMLHttpRequest`).
|
|
212
498
|
|
|
213
499
|
```ts
|
|
214
|
-
import {
|
|
500
|
+
import { getCityCoordinates } from "gedcom-ts";
|
|
215
501
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const selectedYear = yearBucket.year;
|
|
502
|
+
getCityCoordinates("Paris", (places) => {
|
|
503
|
+
console.log(places[0]?.coordinate.latitude, places[0]?.coordinate.longitude);
|
|
504
|
+
});
|
|
220
505
|
```
|
|
221
506
|
|
|
222
|
-
|
|
507
|
+
#### `resolveDatasetVersion(headerLines)` / `GedcomDatasetVersion`
|
|
508
|
+
|
|
509
|
+
Inspects the lines of a `0 HEAD` block and returns `"7.0"`, `"5.5"` or `"unknown"`. Useful to branch UI behaviour for legacy datasets.
|
|
223
510
|
|
|
224
511
|
```ts
|
|
225
|
-
import {
|
|
512
|
+
import { resolveDatasetVersion } from "gedcom-ts";
|
|
513
|
+
|
|
514
|
+
const version = resolveDatasetVersion(headLines); // "7.0" | "5.5" | "unknown"
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
#### `guessMediaFormFromUri(uri)`
|
|
518
|
+
|
|
519
|
+
Best-effort media type detection from a path/URI (jpg, png, gif, webp, mp3, mp4, pdf, …). Used internally to fill `OBJE`.`FILE`.`FORM`.
|
|
520
|
+
|
|
521
|
+
```ts
|
|
522
|
+
import { guessMediaFormFromUri } from "gedcom-ts";
|
|
523
|
+
|
|
524
|
+
guessMediaFormFromUri("https://example.com/photo.jpg"); // "image/jpeg"
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
#### `extractPersonNameVariants(personLines)` / `extractIndiAttributes(personLines)`
|
|
528
|
+
|
|
529
|
+
Low-level parsers used by `Person.createPersonJson`. They turn the raw GEDCOM lines of a single `INDI` record into structured `PersonNameVariant[]` / `IndiAttribute[]`. Reuse them when parsing custom GEDCOM fragments outside `ReadGed`.
|
|
530
|
+
|
|
531
|
+
```ts
|
|
532
|
+
import { extractPersonNameVariants, extractIndiAttributes } from "gedcom-ts";
|
|
533
|
+
|
|
534
|
+
const variants = extractPersonNameVariants(rawIndiLines);
|
|
535
|
+
const attributes = extractIndiAttributes(rawIndiLines);
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
#### `selectPrimaryNameVariant(variants)`
|
|
539
|
+
|
|
540
|
+
Picks the most relevant `1 NAME` block: priority to `2 TYPE BIRTH`, then to a name with a `/surname/` payload, otherwise the first variant.
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
import { selectPrimaryNameVariant } from "gedcom-ts";
|
|
544
|
+
|
|
545
|
+
const primary = selectPrimaryNameVariant(person.nameVariants);
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
#### `GEDCOM_LIBRARY_VERSION`
|
|
549
|
+
|
|
550
|
+
CalVer string embedded in the exported `HEAD`.`SOUR`.`VERS` (same value as `package.json` `version`, e.g. `2026.5.0`). A test (`version-package-sync`) enforces the match on every CI run.
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
import { GEDCOM_LIBRARY_VERSION } from "gedcom-ts";
|
|
554
|
+
|
|
555
|
+
console.log(`gedcom-ts ${GEDCOM_LIBRARY_VERSION}`);
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
## End-to-end example
|
|
559
|
+
|
|
560
|
+
```ts
|
|
561
|
+
import {
|
|
562
|
+
importGedFile,
|
|
563
|
+
ExportGedzipFile,
|
|
564
|
+
editPerson,
|
|
565
|
+
DateAct,
|
|
566
|
+
Identifier,
|
|
567
|
+
} from "gedcom-ts";
|
|
226
568
|
|
|
227
569
|
async function importModifyExport(file: File) {
|
|
228
570
|
const readGed = await importGedFile(file);
|
|
229
571
|
const persons = readGed.persons;
|
|
230
572
|
|
|
231
573
|
if (persons.length > 0) {
|
|
232
|
-
persons[0]
|
|
574
|
+
editPerson(persons[0])
|
|
575
|
+
.setLastname(persons[0].lastname.toUpperCase())
|
|
576
|
+
.acts()
|
|
577
|
+
.at(0)
|
|
578
|
+
.setDateAct(new DateAct("1 JAN 1900"));
|
|
233
579
|
}
|
|
234
580
|
|
|
235
581
|
await new ExportGedzipFile("updated-tree", persons).download();
|
|
236
582
|
}
|
|
237
583
|
```
|
|
238
584
|
|
|
239
|
-
##
|
|
585
|
+
## Development
|
|
586
|
+
|
|
587
|
+
### Local scripts
|
|
588
|
+
|
|
589
|
+
| Script | Role |
|
|
590
|
+
| --- | --- |
|
|
591
|
+
| `npm run lint` | ESLint on the codebase |
|
|
592
|
+
| `npm run test` | Vitest test suite |
|
|
593
|
+
| `npm run build` | Production bundle + `.d.ts` |
|
|
594
|
+
| `npm run tgz` | `build` then `npm pack` (local `.tgz`) |
|
|
595
|
+
|
|
596
|
+
### GitLab CI (`.gitlab-ci.yml`)
|
|
597
|
+
|
|
598
|
+
Every pipeline runs **`InstallDependencies`** (`npm ci`, `node_modules` artifact) — required by all other jobs.
|
|
599
|
+
|
|
600
|
+
| Job | When it runs |
|
|
601
|
+
| --- | --- |
|
|
602
|
+
| `InstallDependencies` | Always |
|
|
603
|
+
| `Lint`, `Test` | Merge requests and branch pushes (not on `master` after a merge commit titled `Merge…`) |
|
|
604
|
+
| `BuildTgz`, `PublishNpm` | Push to `master` after merge only (`Merge…` commit title) |
|
|
605
|
+
|
|
606
|
+
Typical flow: open an MR from a release branch (e.g. `2026.5.0`) → lint + test run there → after merge to `master`, only package build and npm publish run (quality checks are not repeated).
|
|
607
|
+
|
|
608
|
+
## Error handling
|
|
240
609
|
|
|
241
610
|
```ts
|
|
242
|
-
import { importGedFile } from "gedcom-ts";
|
|
611
|
+
import { importGedFile, IMPORT_ERR_ZIP_GED_UNREADABLE } from "gedcom-ts";
|
|
243
612
|
|
|
244
613
|
try {
|
|
245
614
|
const readGed = await importGedFile(file);
|
|
246
|
-
|
|
615
|
+
console.log(readGed.persons.length);
|
|
247
616
|
} catch (error) {
|
|
248
|
-
|
|
617
|
+
if (error instanceof Error && error.message === IMPORT_ERR_ZIP_GED_UNREADABLE) {
|
|
618
|
+
console.error("Encrypted ZIP: please extract the .ged manually.");
|
|
619
|
+
} else {
|
|
620
|
+
console.error("GED import failed:", error);
|
|
621
|
+
}
|
|
249
622
|
}
|
|
250
623
|
```
|
|
251
|
-
|