gedcom-ts 2026.6.0 → 2026.6.2

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/README.md CHANGED
@@ -28,7 +28,7 @@ A graphical demo showcasing the public API (import a `.ged` / `.zip`, browse the
28
28
  ## Package
29
29
 
30
30
  - NPM: [gedcom-ts](https://www.npmjs.com/package/gedcom-ts)
31
- - Current release **`2026.6.0`** — audio / OBJE / oral history. Version format **CalVer** `AAAA.M.micro` (e.g. `2026.6.0` = June 2026). See [CHANGELOG.md](CHANGELOG.md).
31
+ - Current release **`2026.6.2`** — parsing de dates GEDCOM (mois en minuscules). Version format **CalVer** `AAAA.M.micro` (e.g. `2026.6.2` = June 2026, micro release). See [CHANGELOG.md](CHANGELOG.md).
32
32
  - Entry points:
33
33
  - **`gedcom-ts`** — import, model, edit layer, export, geocoding
34
34
  - **`gedcom-ts/booklet`** — PDF livret (`pdf-lib` en peerDependency, chargé depuis `node_modules`)
@@ -85,13 +85,11 @@ You work with a `ReadGed` (after import) and a flat `Act[]` built from every per
85
85
 
86
86
  The library groups variants of the same place with **`citiesAreSimilar`**, not exact string equality.
87
87
 
88
-
89
88
  | Label A | Label B | Same place? |
90
89
  | ----------- | ------------------------------------ | --------------------------- |
91
90
  | `Pleurtuit` | `Pleurtuit, Ille-et-Vilaine, France` | Yes |
92
91
  | `Paris` | `Paris, TX, USA` | Depends on similarity rules |
93
92
 
94
-
95
93
  Use the functions in the tables below for grouping, geocoding, and maps. Do **not** compare raw strings yourself, and do **not** use `normalizeCityKey` for grouping (spelling normalization only).
96
94
 
97
95
  ### Prepare act list
@@ -112,7 +110,6 @@ function collectAllActs(ged: ReadGed): Act[] {
112
110
 
113
111
  When the user picks a city and a Nominatim result, update **every similar act that still has no GPS** — not only acts with the exact same label.
114
112
 
115
-
116
113
  | Step | Function |
117
114
  | ---------------------------------------------- | ----------------------------------------------------------- |
118
115
  | 1. Context from the tree (countries, centroid) | `inferGeocodeContext(ged)` |
@@ -120,7 +117,6 @@ When the user picks a city and a Nominatim result, update **every similar act th
120
117
  | 3. Acts to update | `actsNeedingGeocodeForCity(allActs, cityName)` |
121
118
  | 4. Write coordinates | `applyGeocodeCandidateToActs(targets, candidate, cityName)` |
122
119
 
123
-
124
120
  ```ts
125
121
  import {
126
122
  inferGeocodeContext,
@@ -150,7 +146,9 @@ One row per city; `withoutCoordCount` is how many acts still need GPS.
150
146
  ```ts
151
147
  import { groupActsBySimilarCity } from "gedcom-ts";
152
148
 
153
- const groups = groupActsBySimilarCity(allActs, { onlyWithoutCoordinates: true });
149
+ const groups = groupActsBySimilarCity(allActs, {
150
+ onlyWithoutCoordinates: true,
151
+ });
154
152
 
155
153
  for (const group of groups) {
156
154
  console.log(group.cityLabel, group.withoutCoordCount);
@@ -185,7 +183,6 @@ After a full geocode via `actsNeedingGeocodeForCity`, you should not get a clust
185
183
 
186
184
  ### Geocoding API cheat sheet
187
185
 
188
-
189
186
  | Goal | Call |
190
187
  | ------------------------- | ---------------------------------------------------------------- |
191
188
  | Search | `searchPlacesWithContext(city, inferGeocodeContext(ged))` |
@@ -195,7 +192,6 @@ After a full geocode via `actsNeedingGeocodeForCity`, you should not get a clust
195
192
  | Map marker id | `clusterKeyForCity(city)` |
196
193
  | Inconsistencies | `findHarmonizationClusters(ged)` |
197
194
 
198
-
199
195
  Network: `GET https://nominatim.openstreetmap.org/search?q=…&format=jsonv2` with a `User-Agent` (`gedcom-ts/<version> (genealogy library)`). For offline or mocked search, pass `fetchFn` in `searchPlaces` / `searchPlacesWithContext` options.
200
196
 
201
197
  The legacy callback `getCityCoordinates` is deprecated — use the flow above.
@@ -208,18 +204,18 @@ The **`gedcom-ts/booklet`** subpath builds a printable family booklet: cover pag
208
204
 
209
205
  Code is split so the host app only downloads what it uses. Approximate sizes after minify (obfuscation adds ~25–30 KiB on `gedcom-ts/booklet`).
210
206
 
211
- | Artifact | Size (typ.) | When loaded |
212
- | --- | ---: | --- |
213
- | `gedcom-ts` (`dist/index.mjs`) | ~180 KiB | GEDCOM import / edit / export |
214
- | `gedcom-ts/booklet` (`dist/booklet.mjs`) | ~100 KiB | livret (collect, estimate, PDF) |
215
- | `locale-chunks/booklet-locale-*` | 14–26 KiB | one UI language |
216
- | `feature-chunks/booklet-logo-draw` | ~11 KiB | cover logo (`coverLogo !== false`) |
217
- | `feature-chunks/booklet-timeline-raster` | ~3 KiB | `timelineStyle: "canvas"` |
218
- | `font-chunks/booklet-pdf-font-bytes-ar` | ~43 KiB | Arabic PDF |
219
- | `font-chunks/booklet-pdf-font-bytes-he` | ~13 KiB | Hebrew PDF |
220
- | `font-chunks/booklet-pdf-font-bytes-zh` | ~190 KiB | Chinese PDF (base subset) |
221
- | `font-chunks/booklet-pdf-font-bytes-zh-ext` | ~1.4 MiB | Chinese PDF when GEDCOM names need extra Han |
222
- | `pdf-lib` (peer, not in gedcom-ts) | ~500 KiB+ | any PDF generation |
207
+ | Artifact | Size (typ.) | When loaded |
208
+ | ------------------------------------------- | ----------: | -------------------------------------------- |
209
+ | `gedcom-ts` (`dist/index.mjs`) | ~180 KiB | GEDCOM import / edit / export |
210
+ | `gedcom-ts/booklet` (`dist/booklet.mjs`) | ~100 KiB | livret (collect, estimate, PDF) |
211
+ | `locale-chunks/booklet-locale-*` | 14–26 KiB | one UI language |
212
+ | `feature-chunks/booklet-logo-draw` | ~11 KiB | cover logo (`coverLogo !== false`) |
213
+ | `feature-chunks/booklet-timeline-raster` | ~3 KiB | `timelineStyle: "canvas"` |
214
+ | `font-chunks/booklet-pdf-font-bytes-ar` | ~43 KiB | Arabic PDF |
215
+ | `font-chunks/booklet-pdf-font-bytes-he` | ~13 KiB | Hebrew PDF |
216
+ | `font-chunks/booklet-pdf-font-bytes-zh` | ~190 KiB | Chinese PDF (base subset) |
217
+ | `font-chunks/booklet-pdf-font-bytes-zh-ext` | ~1.4 MiB | Chinese PDF when GEDCOM names need extra Han |
218
+ | `pdf-lib` (peer, not in gedcom-ts) | ~500 KiB+ | any PDF generation |
223
219
 
224
220
  **Not bundled in gedcom-ts:** `fflate` (main entry, external), `pdf-lib` (booklet peer). **`bidi-js`** and **`naqqash`** (Arabic shaping / RTL reordering) ship inside `gedcom-ts/booklet` today.
225
221
 
@@ -227,12 +223,12 @@ Call **`await ensureBookletLocale(locale)`** before sync APIs. `generateGenealog
227
223
 
228
224
  **Lazy chunk subpaths** (for bundler preload or explicit `import()` — normal apps should use `ensureBookletLocale()` instead):
229
225
 
230
- | Subpath | Role |
231
- | --- | --- |
226
+ | Subpath | Role |
227
+ | ----------------------------------------------------------------------------------- | --------------------------------------------------- |
232
228
  | `gedcom-ts/booklet/locale-chunks/booklet-locale-{en,fr,de,nl,es,zh,it,pt,pl,ar,he}` | Messages, narratives, date/place helpers per locale |
233
- | `gedcom-ts/booklet/font-chunks/booklet-pdf-font-bytes-{ar,he,zh,zh-ext}` | Subset Noto font bytes |
234
- | `gedcom-ts/booklet/feature-chunks/booklet-logo-draw` | Cover logo SVG paths |
235
- | `gedcom-ts/booklet/feature-chunks/booklet-timeline-raster` | Canvas timeline PNG rasterizer |
229
+ | `gedcom-ts/booklet/font-chunks/booklet-pdf-font-bytes-{ar,he,zh,zh-ext}` | Subset Noto font bytes |
230
+ | `gedcom-ts/booklet/feature-chunks/booklet-logo-draw` | Cover logo SVG paths |
231
+ | `gedcom-ts/booklet/feature-chunks/booklet-timeline-raster` | Canvas timeline PNG rasterizer |
236
232
 
237
233
  Each subpath is listed in `package.json` **`exports`** with matching **`types`** (`.d.ts` under `dist/booklet/…`) and runtime (`.mjs` / `.cjs` under `dist/…`). TypeScript resolves them via `exports.types` (`moduleResolution: bundler` / `node16`). Adding a locale updates `scripts/booklet-chunk-manifest.mjs`; `npm run build:js` regenerates chunk files and syncs `exports`.
238
234
 
@@ -289,7 +285,14 @@ async function exportBooklet(file: File) {
289
285
 
290
286
  const chapters = groupBookletIntoChapters(entries, locale);
291
287
  const families = chapters.reduce((n, ch) => n + ch.families.length, 0);
292
- const size = estimateBookletSize(chapters, entries.length, families, "summary", "canvas", locale);
288
+ const size = estimateBookletSize(
289
+ chapters,
290
+ entries.length,
291
+ families,
292
+ "summary",
293
+ "canvas",
294
+ locale,
295
+ );
293
296
  console.log(size.label);
294
297
 
295
298
  const pdf = await generateGenealogyBookletPdf(
@@ -316,25 +319,25 @@ Use `scope: "all"` and `referencePerson: null` to include every individual in th
316
319
 
317
320
  ### `collectBookletPersons` options
318
321
 
319
- | Option | Role |
320
- | --- | --- |
321
- | `ged` | `ReadGed` after import |
322
- | `scope` | `"all"` or `"from-reference"` (Sosa from `referencePerson`) |
323
- | `referencePerson` | Root person for `"from-reference"`; `null` if scope is `"all"` |
324
- | `maxGeneration` | Max Sosa generation (e.g. `6`); ignored when `scope === "all"` |
325
- | `locale` | `BookletLocale` (default `"en"`) — `en`, `fr`, `de`, `nl`, `es`, `zh`, `it`, `pt`, `pl`, `ar`, `he` |
322
+ | Option | Role |
323
+ | ----------------- | --------------------------------------------------------------------------------------------------- |
324
+ | `ged` | `ReadGed` after import |
325
+ | `scope` | `"all"` or `"from-reference"` (Sosa from `referencePerson`) |
326
+ | `referencePerson` | Root person for `"from-reference"`; `null` if scope is `"all"` |
327
+ | `maxGeneration` | Max Sosa generation (e.g. `6`); ignored when `scope === "all"` |
328
+ | `locale` | `BookletLocale` (default `"en"`) — `en`, `fr`, `de`, `nl`, `es`, `zh`, `it`, `pt`, `pl`, `ar`, `he` |
326
329
 
327
330
  Helpers: `personDisplayName`, `buildBookletPersonEntry`, `sortBookletEntries`, `bookletSexFromPerson`, `DEFAULT_BOOKLET_LOCALE`, `BOOKLET_LOCALES`, `isBookletLocaleRtl`.
328
331
 
329
332
  ### `generateGenealogyBookletPdf` options
330
333
 
331
- | Option | Values | Effect |
332
- | --- | --- | --- |
333
- | `detailLevel` | `"summary"` \| `"detailed"` | Short prose per person vs longer biographies |
334
- | `timelineStyle` | `"off"` \| `"canvas"` | Generation timeline pages per chapter |
335
- | `coverLogo` | `true` (default), `false`, or `DrawGedcomTsLogoOptions` | gedcom-ts vector logo on the cover |
336
- | `locale` | `BookletLocale` (default `"en"`) | Narrative, chapter titles, PDF chrome, timeline legend |
337
- | `unicodeFont` | `Uint8Array` (optional) | Replace bundled Noto font for `zh` / `ar` / `he` |
334
+ | Option | Values | Effect |
335
+ | --------------- | ------------------------------------------------------- | ------------------------------------------------------ |
336
+ | `detailLevel` | `"summary"` \| `"detailed"` | Short prose per person vs longer biographies |
337
+ | `timelineStyle` | `"off"` \| `"canvas"` | Generation timeline pages per chapter |
338
+ | `coverLogo` | `true` (default), `false`, or `DrawGedcomTsLogoOptions` | gedcom-ts vector logo on the cover |
339
+ | `locale` | `BookletLocale` (default `"en"`) | Narrative, chapter titles, PDF chrome, timeline legend |
340
+ | `unicodeFont` | `Uint8Array` (optional) | Replace bundled Noto font for `zh` / `ar` / `he` |
338
341
 
339
342
  `BookletPdfMeta`: `title`, `scopeLabel`, `personCount`, optional `referenceName` (usually translated in the host app).
340
343
 
@@ -358,17 +361,17 @@ await drawGedcomTsLogoOnPage(page, defaultCoverLogoOptions());
358
361
 
359
362
  ### Booklet API cheat sheet
360
363
 
361
- | Goal | Export |
362
- | --- | --- |
363
- | Persons for the booklet | `collectBookletPersons` |
364
- | Chapters / families | `groupBookletIntoChapters`, `partnerNamesLabel` |
364
+ | Goal | Export |
365
+ | -------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
366
+ | Persons for the booklet | `collectBookletPersons` |
367
+ | Chapters / families | `groupBookletIntoChapters`, `partnerNamesLabel` |
365
368
  | Localized narratives (custom UI) | `buildPersonSummaryNarrative`, `buildPersonDetailedNarratives`, `buildFamilyNarrative`, `buildChapterIntroNarrative` |
366
- | Page estimate | `estimateBookletSize`, `bookletSizeAdvice` |
367
- | Timeline data / PNG | `buildGenerationTimeline`, `rasterizeGenerationTimelinePng` |
368
- | PDF output | `generateGenealogyBookletPdf`, `downloadBookletPdf`, `toPdfText`, `preparePdfText` |
369
- | Locale helpers | `resolveBookletLocale`, `bookletLocaleToBcp47`, `bookletTextDirection`, `BOOKLET_RTL_LOCALES`, `ensureBookletLocale` |
370
- | Font preload (host) | `registerBookletPdfFontBytes`, `registerPdfFontkit`, `localeNeedsUnicodePdfFont` |
371
- | Logo | `ensureBookletLogo`, `drawGedcomTsLogoOnPage`, `getGedcomTsLogoPaths`, `defaultCoverLogoOptions` |
369
+ | Page estimate | `estimateBookletSize`, `bookletSizeAdvice` |
370
+ | Timeline data / PNG | `buildGenerationTimeline`, `rasterizeGenerationTimelinePng` |
371
+ | PDF output | `generateGenealogyBookletPdf`, `downloadBookletPdf`, `toPdfText`, `preparePdfText` |
372
+ | Locale helpers | `resolveBookletLocale`, `bookletLocaleToBcp47`, `bookletTextDirection`, `BOOKLET_RTL_LOCALES`, `ensureBookletLocale` |
373
+ | Font preload (host) | `registerBookletPdfFontBytes`, `registerPdfFontkit`, `localeNeedsUnicodePdfFont` |
374
+ | Logo | `ensureBookletLogo`, `drawGedcomTsLogoOnPage`, `getGedcomTsLogoPaths`, `defaultCoverLogoOptions` |
372
375
 
373
376
  ## Audio and oral history (GEDCOM 7)
374
377
 
@@ -416,18 +419,21 @@ async function addInterview(readGed: ReadGed, person: Person, mp3: File) {
416
419
  editAct(act)
417
420
  .asAudioInterview(readGed.persons, objeMap)
418
421
  .setTranscription("Suite de l'entretien…")
419
- .attachAudio({ audioUri: "https://example.org/rec.mp3", ownerIndi: person.INDI });
422
+ .attachAudio({
423
+ audioUri: "https://example.org/rec.mp3",
424
+ ownerIndi: person.INDI,
425
+ });
420
426
  ```
421
427
 
422
- | Goal | API |
423
- | --- | --- |
424
- | Create interview act | `createAudioInterviewAct` |
425
- | Attach audio to act | `attachAudioInterviewToAct`, `editAct(act).asAudioInterview()` |
426
- | Detect imported interviews | `isAudioInterviewAct`, `isAudioInterviewEvenType` |
427
- | Read transcription / media | `getAudioInterviewTranscription`, `primaryAudioInterviewUri` |
428
- | OBJE round-trip | `readGed.objeRecordsById` → `objeRecordsById` in export options |
429
- | Low-level OBJE parse/emit | `parseStandaloneObjeBlock`, `formatObjeRecordBlock`, `guessMediFromForm` |
430
- | EVEN type constants | `GEDCOM_7_EVEN_TYPE_AUDIO_INTERVIEW`, `GEDCOM_7_EVEN_TYPE_ORAL_HISTORY` |
428
+ | Goal | API |
429
+ | -------------------------- | ------------------------------------------------------------------------ |
430
+ | Create interview act | `createAudioInterviewAct` |
431
+ | Attach audio to act | `attachAudioInterviewToAct`, `editAct(act).asAudioInterview()` |
432
+ | Detect imported interviews | `isAudioInterviewAct`, `isAudioInterviewEvenType` |
433
+ | Read transcription / media | `getAudioInterviewTranscription`, `primaryAudioInterviewUri` |
434
+ | OBJE round-trip | `readGed.objeRecordsById` → `objeRecordsById` in export options |
435
+ | Low-level OBJE parse/emit | `parseStandaloneObjeBlock`, `formatObjeRecordBlock`, `guessMediFromForm` |
436
+ | EVEN type constants | `GEDCOM_7_EVEN_TYPE_AUDIO_INTERVIEW`, `GEDCOM_7_EVEN_TYPE_ORAL_HISTORY` |
431
437
 
432
438
  ## API reference
433
439
 
@@ -470,7 +476,10 @@ import { importGedFile, IMPORT_ERR_ZIP_GED_UNREADABLE } from "gedcom-ts";
470
476
  try {
471
477
  await importGedFile(file);
472
478
  } catch (error) {
473
- if (error instanceof Error && error.message === IMPORT_ERR_ZIP_GED_UNREADABLE) {
479
+ if (
480
+ error instanceof Error &&
481
+ error.message === IMPORT_ERR_ZIP_GED_UNREADABLE
482
+ ) {
474
483
  alert("Please unzip the archive manually and import the .ged file.");
475
484
  }
476
485
  }
@@ -482,25 +491,23 @@ Result of an import. Standalone `0 @O…@ OBJE` blocks with at least one `FILE`
482
491
 
483
492
  Notable members:
484
493
 
485
-
486
- | Member | Description |
487
- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
488
- | `persons: Person[]` | Imported individuals. |
489
- | `mapPersons: Map<number, Person>` | `INDI` (integer)`Person`. |
490
- | `partnersMap: Map<number, Person[]>` | Family id → spouses. |
491
- | `childsMap: Map<number, Person[]>` | Family idchildren. |
492
- | `placesMap: Map<string, Place>` | City name → `Place` (first occurrence wins). |
493
- | `mapFiles: Map<string, File>` | Relative path → media `File` (for ZIP imports). |
494
- | `datasetVersion: GedcomDatasetVersion` | `"7.0"`, `"5.5"` or `"unknown"`. |
495
- | `objeRecordsById: Map<number, ObjeRecord>` | Parsed `OBJE` records (audio, images, …) keyed by `@O{n}@`. |
496
- | `preservedTopLevelRecords: string[]` | Raw blocks for partial round-trip. |
497
- | `resolveIndividualPointer(raw)` | Resolves `@I12@`, `I12`, `@Homer_Simpson@`, `Homer_Simpson` to a `Person`. |
498
- | `getChildrenForParent(parent)` | All children attached to a parent (via `FAMS`). |
499
- | `getChildrenOfFamily(familyId)` | Children of a single family. |
500
- | `groupPartners()` | Rebuilds `partnersMap` / `childsMap` after editing links. |
501
- | `generateUniqueIndi()` | Next free `INDI` number for new persons. |
502
- | `rehydratePlacesFromActs()` | Rebuilds `placesMap` from act places (e.g. after manual graph edits). Use `editReadGed(readGed).addPerson(...)` to register persons so maps stay coherent. |
503
-
494
+ | Member | Description |
495
+ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
496
+ | `persons: Person[]` | Imported individuals. |
497
+ | `mapPersons: Map<number, Person>` | `INDI` (integer) → `Person`. |
498
+ | `partnersMap: Map<number, Person[]>` | Family idspouses. |
499
+ | `childsMap: Map<number, Person[]>` | Family id → children. |
500
+ | `placesMap: Map<string, Place>` | City name`Place` (first occurrence wins). |
501
+ | `mapFiles: Map<string, File>` | Relative pathmedia `File` (for ZIP imports). |
502
+ | `datasetVersion: GedcomDatasetVersion` | `"7.0"`, `"5.5"` or `"unknown"`. |
503
+ | `objeRecordsById: Map<number, ObjeRecord>` | Parsed `OBJE` records (audio, images, …) keyed by `@O{n}@`. |
504
+ | `preservedTopLevelRecords: string[]` | Raw blocks for partial round-trip. |
505
+ | `resolveIndividualPointer(raw)` | Resolves `@I12@`, `I12`, `@Homer_Simpson@`, `Homer_Simpson` to a `Person`. |
506
+ | `getChildrenForParent(parent)` | All children attached to a parent (via `FAMS`). |
507
+ | `getChildrenOfFamily(familyId)` | Children of a single family. |
508
+ | `groupPartners()` | Rebuilds `partnersMap` / `childsMap` after editing links. |
509
+ | `generateUniqueIndi()` | Next free `INDI` number for new persons. |
510
+ | `rehydratePlacesFromActs()` | Rebuilds `placesMap` from act places (e.g. after manual graph edits). Use `editReadGed(readGed).addPerson(...)` to register persons so maps stay coherent. |
504
511
 
505
512
  ```ts
506
513
  import { ReadGed } from "gedcom-ts";
@@ -545,7 +552,7 @@ function exportGed(readGed: ReadGed) {
545
552
 
546
553
  #### `ExportGedzipFile`
547
554
 
548
- Writes a `.zip` (GEDZIP) bundling the GEDCOM and all attached `MultimediaFile` payloads.
555
+ Writes a `.zip` (GEDZIP) bundling the GEDCOM and all attached `MultimediaFile` payloads. Compression is streamed per entry (`ZipWriter` + `BlobReader`) so large archives are not built entirely in memory.
549
556
 
550
557
  ```ts
551
558
  import { ExportGedzipFile, Person } from "gedcom-ts";
@@ -553,13 +560,16 @@ import { ExportGedzipFile, Person } from "gedcom-ts";
553
560
  async function exportZip(persons: Person[]) {
554
561
  await new ExportGedzipFile("my-tree", persons).download();
555
562
  }
563
+
564
+ async function exportZipBlob(persons: Person[]) {
565
+ return new ExportGedzipFile("my-tree", persons).gedzipBlob();
566
+ }
556
567
  ```
557
568
 
558
569
  #### `GedcomExportOptions`
559
570
 
560
571
  Options shared by both exporters:
561
572
 
562
-
563
573
  | Option | Effect |
564
574
  | ---------------------- | -------------------------------------------------------------------------------------------------------- |
565
575
  | `extraTopLevelRecords` | Raw `0 …` blocks re-emitted before `SUBM` / `TRLR` (round-trip with `readGed.preservedTopLevelRecords`). |
@@ -569,7 +579,6 @@ Options shared by both exporters:
569
579
  | `headDestination` | Value of `HEAD`.`DEST` (target app / URI). |
570
580
  | `headSchemaTagDefs` | Extension tag definitions emitted as `HEAD`.`SCHMA` / `2 TAG …`. |
571
581
 
572
-
573
582
  ### Domain model
574
583
 
575
584
  #### `Person`, `Sex`
@@ -673,17 +682,10 @@ const sorted = [...GEDCOM_7_EVENT_SORT_ORDER];
673
682
 
674
683
  #### `DateAct`, `Day`, `Month`, `dateToDateLine`, `days`, `months`, `TypeDateActSimpleQualifier`
675
684
 
676
- 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.
685
+ 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. Month tokens (`JAN`, `may`, …) and simple qualifiers are matched **case-insensitively** (since **2026.6.2**).
677
686
 
678
687
  ```ts
679
- import {
680
- DateAct,
681
- Day,
682
- Month,
683
- dateToDateLine,
684
- days,
685
- months,
686
- } from "gedcom-ts";
688
+ import { DateAct, Day, Month, dateToDateLine, days, months } from "gedcom-ts";
687
689
 
688
690
  const day: Day = 12;
689
691
  const month: Month = months[0]; // JAN
@@ -779,17 +781,15 @@ editPerson(person)
779
781
  editDateAct(person.acts.list[0].dateAct!).setExactDate(1900, "JAN", 1);
780
782
  ```
781
783
 
782
-
783
- | Helper / class | Purpose |
784
- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
785
- | `editPerson(person)` / `PersonEdit` | Identity, `FAMS` / `FAMC` / `removeFams*`, `nameVariants()` / `attributes()` / `multimedia()`, bulk `clear*`, entry points to `acts()` and `notes()`. |
786
- | `editActs(acts)` / `ActsEdit` | `add`, `addNew`, `insertAt`, `insertNewAt`, `replaceAt`, `removeAt`, `removeAct`, `removeLast`, `clear`, `sortByDate`, `at`, `indexOfAct`. |
787
- | `editAct(act)` / `ActEdit` | `setType`, `setIndis`, dates, place, EVEN fields, preserved lines, `notes()`, `multimedia()`, `asAudioInterview()`, `clearNotes`, `clearMultimedia`. |
788
- | `editAudioInterviewAct(act)` / `AudioInterviewActEdit` | Oral-history facade: `attachAudio`, `setTranscription`, `setDescription`, `registerPendingObje`, `isAudioInterview`, |
789
- | `editDateAct(dateAct)` / `DateActEdit` | `clear`, `applyGedcomPayload`, `setExactDate`, `setQualified`, `setBetween`, `setFromTo`, `setTime`, `setDatePhrase` / `appendDatePhrase`, `setVerbatimPayload`. |
790
- | `editPlace(place)` / `PlaceEdit` | `setFromGedcom7Payload`, `setCity`, `setCounty`, `setState`, `setCountry`, `setPlacPhrase` / `appendPlacPhrase` / `clearPlacPhrase`, `setCoordinates` / `clearCoordinates` / `replaceCoordinateModel`, `clearStructured`. |
791
- | `editNotes(notes)` / `NotesEdit` | `add`, `addNew`, `insertAt`, `insertNewAt`, `replaceAt`, `removeAt`, `removeNote`, `removeLast`, `clear`, `at`, `indexOfNote`. |
792
-
784
+ | Helper / class | Purpose |
785
+ | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
786
+ | `editPerson(person)` / `PersonEdit` | Identity, `FAMS` / `FAMC` / `removeFams*`, `nameVariants()` / `attributes()` / `multimedia()`, bulk `clear*`, entry points to `acts()` and `notes()`. |
787
+ | `editActs(acts)` / `ActsEdit` | `add`, `addNew`, `insertAt`, `insertNewAt`, `replaceAt`, `removeAt`, `removeAct`, `removeLast`, `clear`, `sortByDate`, `at`, `indexOfAct`. |
788
+ | `editAct(act)` / `ActEdit` | `setType`, `setIndis`, dates, place, EVEN fields, preserved lines, `notes()`, `multimedia()`, `asAudioInterview()`, `clearNotes`, `clearMultimedia`. |
789
+ | `editAudioInterviewAct(act)` / `AudioInterviewActEdit` | Oral-history facade: `attachAudio`, `setTranscription`, `setDescription`, `registerPendingObje`, `isAudioInterview`, |
790
+ | `editDateAct(dateAct)` / `DateActEdit` | `clear`, `applyGedcomPayload`, `setExactDate`, `setQualified`, `setBetween`, `setFromTo`, `setTime`, `setDatePhrase` / `appendDatePhrase`, `setVerbatimPayload`. |
791
+ | `editPlace(place)` / `PlaceEdit` | `setFromGedcom7Payload`, `setCity`, `setCounty`, `setState`, `setCountry`, `setPlacPhrase` / `appendPlacPhrase` / `clearPlacPhrase`, `setCoordinates` / `clearCoordinates` / `replaceCoordinateModel`, `clearStructured`. |
792
+ | `editNotes(notes)` / `NotesEdit` | `add`, `addNew`, `insertAt`, `insertNewAt`, `replaceAt`, `removeAt`, `removeNote`, `removeLast`, `clear`, `at`, `indexOfNote`. |
793
793
 
794
794
  ### Dataset editing: `ReadGed`, graph, clone, export options, validation
795
795
 
@@ -816,7 +816,11 @@ import {
816
816
  } from "gedcom-ts";
817
817
 
818
818
  const readGed = createEmptyReadGed();
819
- const p = createPersonStub(readGed.generateUniqueIndi(), { sex: Sex.M, firstnames: ["Jean"], lastname: "Dupont" });
819
+ const p = createPersonStub(readGed.generateUniqueIndi(), {
820
+ sex: Sex.M,
821
+ firstnames: ["Jean"],
822
+ lastname: "Dupont",
823
+ });
820
824
  editReadGed(readGed).addPerson(p);
821
825
  p.acts.add(new Act(Identifier.BIRT, new DateAct("1900")));
822
826
 
@@ -847,17 +851,15 @@ validateReadGed(readGed, {
847
851
 
848
852
  `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`.
849
853
 
850
-
851
- | Export | Role |
852
- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
853
- | `editReadGed(readGed)` / `ReadGedEdit` | `addPerson`, `removePersonByIndi`, `preserved()` `PreservedTopLevelEdit` (`append`, `insertAt`, `replaceAt`, `removeAt`, `clear`) on `preservedTopLevelRecords`. Low-level helpers: `addPersonToReadGed`, `removePersonFromReadGedByIndi`. |
854
- | `editGedcomExportOptions(opts)` / `GedcomExportOptionsEdit` | Fluent setters for `extraTopLevelRecords`, `objeRecordsById`, `headLanguageTag`, `headCopyright`, `headDestination`, `headSchemaTagDefs`. |
855
- | `clonePerson(person, newIndi)` / `cloneAct(act)` / `person.clone` / `act.clone` | Deep copies for templates or undo stacks. |
856
- | `nextFamilyId(persons)` | Next internal family id `F`. |
857
- | `createMarriageFamily`, `linkChildToFamily`, `unlinkChildFromFamily`, `removeFamilyReferencesFromDataset` | Create family `F` and spouse acts; optional `{ eventTag }` (default `MARR`, also `ENGA`, banns, contract, `EVEN` + `CreateActInit`, …). See `GEDCOM_7_PAIR_UNION_EVENT_TAGS`. |
854
+ | Export | Role |
855
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
856
+ | `editReadGed(readGed)` / `ReadGedEdit` | `addPerson`, `removePersonByIndi`, `preserved()` → `PreservedTopLevelEdit` (`append`, `insertAt`, `replaceAt`, `removeAt`, `clear`) on `preservedTopLevelRecords`. Low-level helpers: `addPersonToReadGed`, `removePersonFromReadGedByIndi`. |
857
+ | `editGedcomExportOptions(opts)` / `GedcomExportOptionsEdit` | Fluent setters for `extraTopLevelRecords`, `objeRecordsById`, `headLanguageTag`, `headCopyright`, `headDestination`, `headSchemaTagDefs`. |
858
+ | `clonePerson(person, newIndi)` / `cloneAct(act)` / `person.clone` / `act.clone` | Deep copies for templates or undo stacks. |
859
+ | `nextFamilyId(persons)` | Next internal family id `F`. |
860
+ | `createMarriageFamily`, `linkChildToFamily`, `unlinkChildFromFamily`, `removeFamilyReferencesFromDataset` | Create family `F` and spouse acts; optional `{ eventTag }` (default `MARR`, also `ENGA`, banns, contract, `EVEN` + `CreateActInit`, …). See `GEDCOM_7_PAIR_UNION_EVENT_TAGS`. |
858
861
  | `validateReadGed`, `assertReadGedConsistent`, `validatePerson`, `assertPersonConsistent` | Typed `code` / `severity` (`error` \| `warn`). Options: `checkMarrParticipants`, `checkDuplicateIndis`, `checkFamcWithoutSpouses`, `checkFamsWithoutSpouses`, `checkDuplicateFamsEntries`, `checkAncestorCycles`. Assertions: `failOn` (default: `error`). |
859
- | `tryAddPersonToReadGed`, `tryRemovePersonFromReadGedByIndi`, `tryLinkChildToFamily`, `tryUnlinkChildFromFamily`, `tryCreateMarriageFamily`, `commandBlockingIssues` | Command layer with `CommandResult` / `validate*Command` prechecks. |
860
-
862
+ | `tryAddPersonToReadGed`, `tryRemovePersonFromReadGedByIndi`, `tryLinkChildToFamily`, `tryUnlinkChildFromFamily`, `tryCreateMarriageFamily`, `commandBlockingIssues` | Command layer with `CommandResult` / `validate*Command` prechecks. |
861
863
 
862
864
  ### Utilities
863
865
 
@@ -898,7 +900,7 @@ const version = resolveDatasetVersion(headLines); // "7.0" | "5.5" | "unknown"
898
900
 
899
901
  #### `guessMediaFormFromUri(uri)`
900
902
 
901
- Best-effort media type detection from a path/URI or local `File` (`file.type` when set). Covers images, **audio** (mp3, **webm**, ogg, wav, m4a, flac, …), video, WebVTT, PDF. Used to fill `OBJE`.`FILE`.`FORM`. Pair with `guessMediFromForm` for `MEDI` (`AUDIO`, `PHOTO`, …). `refineGenericObjeForm` upgrades legacy `application/octet-stream` exports using the file extension.
903
+ Best-effort media type detection from a path/URI or local `File` (`file.type` when set). Covers images (incl. **tiff**, **heic**, svg), **audio** (mp3, **webm**, ogg, wav, m4a, flac, …), **video** (mp4, mov, mkv, …), subtitles (vtt, srt), and documents (pdf, djvu, docx, odt, xlsx, csv, html). Used to fill `OBJE`.`FILE`.`FORM`. Pair with `guessMediFromForm` for `MEDI` (`AUDIO`, `PHOTO`, `VIDEO`, `ELECTRONIC`, …). `refineGenericObjeForm` upgrades legacy `application/octet-stream` exports using the file extension.
902
904
 
903
905
  ```ts
904
906
  import { guessMediaFormFromUri, guessMediaFormFromFile } from "gedcom-ts";
@@ -930,7 +932,7 @@ const primary = selectPrimaryNameVariant(person.nameVariants);
930
932
 
931
933
  #### `GEDCOM_LIBRARY_VERSION`
932
934
 
933
- CalVer string written in exported `HEAD`.`SOUR`.`VERS` (same value as the npm package version, e.g. `2026.6.0`).
935
+ CalVer string written in exported `HEAD`.`SOUR`.`VERS` (same value as the npm package version, e.g. `2026.6.2`).
934
936
 
935
937
  ```ts
936
938
  import { GEDCOM_LIBRARY_VERSION } from "gedcom-ts";
@@ -947,11 +949,13 @@ try {
947
949
  const readGed = await importGedFile(file);
948
950
  console.log(readGed.persons.length);
949
951
  } catch (error) {
950
- if (error instanceof Error && error.message === IMPORT_ERR_ZIP_GED_UNREADABLE) {
952
+ if (
953
+ error instanceof Error &&
954
+ error.message === IMPORT_ERR_ZIP_GED_UNREADABLE
955
+ ) {
951
956
  console.error("Encrypted ZIP: please extract the .ged manually.");
952
957
  } else {
953
958
  console.error("GED import failed:", error);
954
959
  }
955
960
  }
956
961
  ```
957
-