@treeviz/familysearch-sdk 2.0.6 → 2.0.8

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/dist/index.js CHANGED
@@ -1666,6 +1666,10 @@ __export(tree_exports, {
1666
1666
  });
1667
1667
 
1668
1668
  // src/api/tree/persons.ts
1669
+ var PERSON_WRITE_HEADERS = {
1670
+ "Content-Type": "application/x-fs-v1+json",
1671
+ Accept: "application/x-fs-v1+json"
1672
+ };
1669
1673
  async function readPerson(sdk, personId) {
1670
1674
  try {
1671
1675
  const response = await sdk.get(
@@ -1698,7 +1702,7 @@ async function readPersonWithDetails(sdk, personId, options = {}) {
1698
1702
  }
1699
1703
  async function createPerson(sdk, person, reason) {
1700
1704
  try {
1701
- const headers = {};
1705
+ const headers = { ...PERSON_WRITE_HEADERS };
1702
1706
  if (reason) {
1703
1707
  headers["X-Reason"] = reason;
1704
1708
  }
@@ -1715,7 +1719,7 @@ async function createPerson(sdk, person, reason) {
1715
1719
  }
1716
1720
  async function updatePerson(sdk, personId, person, reason) {
1717
1721
  try {
1718
- const headers = {};
1722
+ const headers = { ...PERSON_WRITE_HEADERS };
1719
1723
  if (reason) {
1720
1724
  headers["X-Reason"] = reason;
1721
1725
  }
@@ -6216,7 +6220,7 @@ var FamilySearchSDK = class {
6216
6220
  });
6217
6221
  let data;
6218
6222
  const contentType = response.headers.get("content-type");
6219
- if (contentType && contentType.includes("application/json")) {
6223
+ if (contentType && contentType.includes("json")) {
6220
6224
  try {
6221
6225
  data = await response.json();
6222
6226
  } catch (error) {
@@ -6372,6 +6376,40 @@ function resetFamilySearchSDK() {
6372
6376
  sdkInstance = null;
6373
6377
  }
6374
6378
 
6379
+ // src/places/gazetteer-id.ts
6380
+ var numericPlaceIdFromRef = (ref) => {
6381
+ if (!ref) {
6382
+ return void 0;
6383
+ }
6384
+ if (ref.includes("/places/description/") || ref.includes("ark:/")) {
6385
+ return void 0;
6386
+ }
6387
+ const match = ref.match(/\/places\/(\d+)/);
6388
+ return match?.[1];
6389
+ };
6390
+ var extractGazetteerPlaceId = (place) => {
6391
+ const persistent = place.identifiers?.["http://gedcomx.org/Persistent"]?.[0];
6392
+ const fromPersistent = numericPlaceIdFromRef(persistent);
6393
+ if (fromPersistent) {
6394
+ return fromPersistent;
6395
+ }
6396
+ return numericPlaceIdFromRef(place.place?.description);
6397
+ };
6398
+ var familySearchResearchPlaceUrl = (placeId, baseUrl = "https://www.familysearch.org") => `${baseUrl}/en/research/places/?focusedId=${placeId}`;
6399
+ var pickBestPlaceSearchResult = (results, query) => {
6400
+ if (results.length === 0) {
6401
+ return void 0;
6402
+ }
6403
+ const town = query.split(",")[0]?.trim().toLowerCase();
6404
+ if (!town) {
6405
+ return results[0];
6406
+ }
6407
+ const label = (result) => (result.fullName || result.name || result.title || "").toLowerCase();
6408
+ return results.find(
6409
+ (result) => label(result) === town || label(result).startsWith(`${town},`)
6410
+ ) || results.find((result) => label(result).includes(town)) || results[0];
6411
+ };
6412
+
6375
6413
  // src/utils/gedcom-converter.ts
6376
6414
  function transformFamilySearchUrl(url) {
6377
6415
  if (url.includes("/platform/tree/persons/https://")) {
@@ -6416,6 +6454,64 @@ function transformSourceUrl(url) {
6416
6454
  }
6417
6455
  return `${baseUrl}/${arkId}`;
6418
6456
  }
6457
+ function sanitizeGedcomValue(value) {
6458
+ return value.replace(/[\r\n\t]+/g, " ").trim();
6459
+ }
6460
+ var GEDCOM_MONTHS = [
6461
+ "JAN",
6462
+ "FEB",
6463
+ "MAR",
6464
+ "APR",
6465
+ "MAY",
6466
+ "JUN",
6467
+ "JUL",
6468
+ "AUG",
6469
+ "SEP",
6470
+ "OCT",
6471
+ "NOV",
6472
+ "DEC"
6473
+ ];
6474
+ function formatSimpleFormalDate(value) {
6475
+ const match = value.match(/^([+-])(\d{1,6})(?:-(\d{2}))?(?:-(\d{2}))?/);
6476
+ if (!match) return void 0;
6477
+ const [, sign, yearPart, monthPart, dayPart] = match;
6478
+ if (sign === "-") return void 0;
6479
+ const year = String(parseInt(yearPart, 10));
6480
+ if (!monthPart) return year;
6481
+ const month = GEDCOM_MONTHS[parseInt(monthPart, 10) - 1];
6482
+ if (!month) return void 0;
6483
+ if (!dayPart) return `${month} ${year}`;
6484
+ return `${parseInt(dayPart, 10)} ${month} ${year}`;
6485
+ }
6486
+ function convertFormalDateToGedcom(formal) {
6487
+ if (!formal) return void 0;
6488
+ let value = formal.trim();
6489
+ let approximate = false;
6490
+ if (value.startsWith("A")) {
6491
+ approximate = true;
6492
+ value = value.slice(1);
6493
+ }
6494
+ if (value.includes("/")) {
6495
+ const slashIndex = value.indexOf("/");
6496
+ const start = value.slice(0, slashIndex);
6497
+ const end = value.slice(slashIndex + 1);
6498
+ const startDate = start ? formatSimpleFormalDate(start) : void 0;
6499
+ const endDate = end ? formatSimpleFormalDate(end) : void 0;
6500
+ if (start && !startDate) return void 0;
6501
+ if (end && !endDate) return void 0;
6502
+ if (startDate && endDate) return `BET ${startDate} AND ${endDate}`;
6503
+ if (startDate) return `FROM ${startDate}`;
6504
+ if (endDate) return `TO ${endDate}`;
6505
+ return void 0;
6506
+ }
6507
+ const simple = formatSimpleFormalDate(value);
6508
+ if (!simple) return void 0;
6509
+ return approximate ? `ABT ${simple}` : simple;
6510
+ }
6511
+ function resolveFactDate(date) {
6512
+ if (!date) return void 0;
6513
+ return convertFormalDateToGedcom(date.formal) || date.original;
6514
+ }
6419
6515
  function extractName(person) {
6420
6516
  if (!person) return null;
6421
6517
  if (person.names?.[0]?.nameForms?.[0]) {
@@ -6423,8 +6519,14 @@ function extractName(person) {
6423
6519
  const parts = nameForm.parts || [];
6424
6520
  const given = parts.find((p) => p.type?.includes("Given"))?.value || "";
6425
6521
  const surname = parts.find((p) => p.type?.includes("Surname"))?.value || "";
6426
- if (given || surname) {
6427
- return `${given} /${surname}/`.trim();
6522
+ if (given && surname) {
6523
+ return `${given} /${surname}/`;
6524
+ }
6525
+ if (surname) {
6526
+ return `/${surname}/`;
6527
+ }
6528
+ if (given) {
6529
+ return given;
6428
6530
  }
6429
6531
  if (nameForm.fullText) {
6430
6532
  return nameForm.fullText;
@@ -6454,11 +6556,9 @@ function extractFact(person, factType) {
6454
6556
  const factTypeUrl = `http://gedcomx.org/${factType === "BIRTH" ? "Birth" : "Death"}`;
6455
6557
  const fact = person.facts?.find((f) => f.type === factTypeUrl);
6456
6558
  if (fact) {
6457
- if (fact.date?.formal || fact.date?.original) {
6458
- result.date = (fact.date?.formal || fact.date?.original)?.replace(
6459
- /^(-|\+)/,
6460
- ""
6461
- );
6559
+ const resolvedDate = resolveFactDate(fact.date);
6560
+ if (resolvedDate) {
6561
+ result.date = resolvedDate;
6462
6562
  }
6463
6563
  if (fact.place?.original) {
6464
6564
  result.place = fact.place.original;
@@ -6511,25 +6611,30 @@ function convertFactToGedcom(fact) {
6511
6611
  };
6512
6612
  const gedcomTag = typeMap[fact.type] || "EVEN";
6513
6613
  if (gedcomTag === "OCCU" && fact.value) {
6514
- lines.push(`1 OCCU ${fact.value}`);
6614
+ lines.push(`1 OCCU ${sanitizeGedcomValue(fact.value)}`);
6515
6615
  } else {
6516
6616
  lines.push(`1 ${gedcomTag}`);
6517
6617
  }
6618
+ if (gedcomTag === "EVEN") {
6619
+ const typeName = fact.type.split("/").pop();
6620
+ if (typeName) {
6621
+ lines.push(`2 TYPE ${sanitizeGedcomValue(typeName)}`);
6622
+ }
6623
+ }
6518
6624
  if (fact.links?.conclusion?.href) {
6519
6625
  const webUrl = transformFamilySearchUrl(fact.links.conclusion.href);
6520
6626
  lines.push(`2 WWW ${webUrl}`);
6521
6627
  lines.push(`3 _IS_FS Y`);
6522
6628
  }
6523
- if (fact.date?.formal || fact.date?.original) {
6524
- lines.push(
6525
- `2 DATE ${(fact.date?.formal || fact.date?.original)?.replace(/^(-|\+)/, "")}`
6526
- );
6629
+ const resolvedDate = resolveFactDate(fact.date);
6630
+ if (resolvedDate) {
6631
+ lines.push(`2 DATE ${sanitizeGedcomValue(resolvedDate)}`);
6527
6632
  }
6528
6633
  if (fact.place?.original) {
6529
- lines.push(`2 PLAC ${fact.place.original}`);
6634
+ lines.push(`2 PLAC ${sanitizeGedcomValue(fact.place.original)}`);
6530
6635
  }
6531
6636
  if (fact.value && gedcomTag !== "OCCU") {
6532
- lines.push(`2 NOTE ${fact.value}`);
6637
+ lines.push(`2 NOTE ${sanitizeGedcomValue(fact.value)}`);
6533
6638
  }
6534
6639
  return lines;
6535
6640
  }
@@ -6549,14 +6654,14 @@ function convertToGedcom(pedigreeData, options = {}) {
6549
6654
  lines.push("2 VERS 1.0");
6550
6655
  lines.push("2 NAME FamilySearch API");
6551
6656
  if (treeName) {
6552
- lines.push(`2 _TREE ${treeName}`);
6657
+ lines.push(`2 _TREE ${sanitizeGedcomValue(treeName)}`);
6553
6658
  const randomRIN = Math.floor(Math.random() * 1e8);
6554
6659
  lines.push(`3 RIN ${randomRIN}`);
6555
6660
  }
6556
6661
  lines.push("1 DEST ANY");
6557
6662
  lines.push("1 DATE " + formatDateForGedcom(/* @__PURE__ */ new Date()));
6558
6663
  lines.push("1 SUBM @SUBM1@");
6559
- lines.push("1 FILE " + treeName);
6664
+ lines.push("1 FILE " + sanitizeGedcomValue(treeName));
6560
6665
  lines.push("1 GEDC");
6561
6666
  lines.push("2 VERS 5.5");
6562
6667
  lines.push("2 FORM LINEAGE-LINKED");
@@ -6578,20 +6683,22 @@ function convertToGedcom(pedigreeData, options = {}) {
6578
6683
  const sourceId = `@S${sourceCounter++}@`;
6579
6684
  lines.push(`0 ${sourceId} SOUR`);
6580
6685
  const title = source.titles?.[0]?.value || "FamilySearch Source";
6581
- lines.push(`1 TITL ${title}`);
6686
+ lines.push(`1 TITL ${sanitizeGedcomValue(title)}`);
6582
6687
  if (source.citations?.[0]?.value) {
6583
- lines.push(`1 TEXT ${source.citations[0].value}`);
6688
+ lines.push(`1 TEXT ${sanitizeGedcomValue(source.citations[0].value)}`);
6584
6689
  }
6585
6690
  if (source.about) {
6586
6691
  const webUrl = transformSourceUrl(source.about);
6587
6692
  lines.push(`1 WWW ${webUrl}`);
6588
6693
  }
6589
6694
  if (source.resourceType) {
6590
- lines.push(`1 NOTE Resource Type: ${source.resourceType}`);
6695
+ lines.push(
6696
+ `1 NOTE Resource Type: ${sanitizeGedcomValue(source.resourceType)}`
6697
+ );
6591
6698
  }
6592
6699
  });
6593
6700
  const fsSourMap = /* @__PURE__ */ new Map();
6594
- let fsSourCounter = 1;
6701
+ let fsSourCounter = sourceCounter;
6595
6702
  pedigreeData.persons.forEach((person) => {
6596
6703
  if (person.sources?.persons?.[0]?.sources) {
6597
6704
  const sourceDescMap = /* @__PURE__ */ new Map();
@@ -6641,20 +6748,22 @@ function convertToGedcom(pedigreeData, options = {}) {
6641
6748
  lines.push(`1 _IS_FS Y`);
6642
6749
  lines.push(`1 _FS_ID ${id}`);
6643
6750
  if (source.titles?.[0]?.value) {
6644
- lines.push(`1 TITL ${source.titles[0].value}`);
6751
+ lines.push(`1 TITL ${sanitizeGedcomValue(source.titles[0].value)}`);
6645
6752
  }
6646
6753
  if (source.citations?.[0]?.value) {
6647
- lines.push(`1 TEXT ${source.citations[0].value}`);
6754
+ lines.push(`1 TEXT ${sanitizeGedcomValue(source.citations[0].value)}`);
6648
6755
  }
6649
6756
  if (source.about) {
6650
6757
  const webUrl = transformSourceUrl(source.about);
6651
6758
  lines.push(`1 WWW ${webUrl}`);
6652
6759
  }
6653
6760
  if (source.resourceType) {
6654
- lines.push(`1 NOTE Resource Type: ${source.resourceType}`);
6761
+ lines.push(
6762
+ `1 NOTE Resource Type: ${sanitizeGedcomValue(source.resourceType)}`
6763
+ );
6655
6764
  }
6656
6765
  notes.forEach((note) => {
6657
- lines.push(`1 NOTE ${note}`);
6766
+ lines.push(`1 NOTE ${sanitizeGedcomValue(note)}`);
6658
6767
  });
6659
6768
  });
6660
6769
  const fsMatchMap = /* @__PURE__ */ new Map();
@@ -6674,7 +6783,7 @@ function convertToGedcom(pedigreeData, options = {}) {
6674
6783
  lines.push(`1 _IS_FS Y`);
6675
6784
  lines.push(`1 _FS_ID ${id}`);
6676
6785
  if (entry.title) {
6677
- lines.push(`1 TITL ${entry.title}`);
6786
+ lines.push(`1 TITL ${sanitizeGedcomValue(entry.title)}`);
6678
6787
  }
6679
6788
  if (entry.content?.score !== void 0) {
6680
6789
  lines.push(`1 SCORE ${entry.content.score}`);
@@ -6695,7 +6804,7 @@ function convertToGedcom(pedigreeData, options = {}) {
6695
6804
  }
6696
6805
  if (matchInfo.status) {
6697
6806
  const statusName = matchInfo.status.includes("/") ? matchInfo.status.split("/").pop() : matchInfo.status;
6698
- lines.push(`1 NOTE Status: ${statusName}`);
6807
+ lines.push(`1 NOTE Status: ${sanitizeGedcomValue(statusName || "")}`);
6699
6808
  }
6700
6809
  }
6701
6810
  lines.push(`1 TYPE ${collectionType}`);
@@ -6707,42 +6816,52 @@ function convertToGedcom(pedigreeData, options = {}) {
6707
6816
  if (entry.content?.gedcomx?.persons && entry.content.gedcomx.persons.length > 0) {
6708
6817
  const matchedPerson = entry.content.gedcomx.persons[0];
6709
6818
  if (matchedPerson.display?.name) {
6710
- lines.push(`1 TEXT ${matchedPerson.display.name}`);
6819
+ lines.push(`1 TEXT ${sanitizeGedcomValue(matchedPerson.display.name)}`);
6711
6820
  }
6712
6821
  if (matchedPerson.display?.lifespan) {
6713
6822
  lines.push(
6714
- `1 NOTE Lifespan: ${matchedPerson.display.lifespan}`
6823
+ `1 NOTE Lifespan: ${sanitizeGedcomValue(matchedPerson.display.lifespan)}`
6715
6824
  );
6716
6825
  }
6717
6826
  if (matchedPerson.display?.birthDate || matchedPerson.display?.birthPlace) {
6718
- const birthInfo = [
6719
- matchedPerson.display.birthDate,
6720
- matchedPerson.display.birthPlace
6721
- ].filter(Boolean).join(", ");
6827
+ const birthInfo = sanitizeGedcomValue(
6828
+ [
6829
+ matchedPerson.display.birthDate,
6830
+ matchedPerson.display.birthPlace
6831
+ ].filter(Boolean).join(", ")
6832
+ );
6722
6833
  if (birthInfo) {
6723
6834
  lines.push(`1 NOTE Birth: ${birthInfo}`);
6724
6835
  }
6725
6836
  }
6726
6837
  if (matchedPerson.display?.deathDate || matchedPerson.display?.deathPlace) {
6727
- const deathInfo = [
6728
- matchedPerson.display.deathDate,
6729
- matchedPerson.display.deathPlace
6730
- ].filter(Boolean).join(", ");
6838
+ const deathInfo = sanitizeGedcomValue(
6839
+ [
6840
+ matchedPerson.display.deathDate,
6841
+ matchedPerson.display.deathPlace
6842
+ ].filter(Boolean).join(", ")
6843
+ );
6731
6844
  if (deathInfo) {
6732
6845
  lines.push(`1 NOTE Death: ${deathInfo}`);
6733
6846
  }
6734
6847
  }
6735
6848
  if (matchedPerson.display?.gender) {
6736
- lines.push(`1 NOTE Gender: ${matchedPerson.display.gender}`);
6849
+ lines.push(
6850
+ `1 NOTE Gender: ${sanitizeGedcomValue(matchedPerson.display.gender)}`
6851
+ );
6737
6852
  }
6738
6853
  }
6739
6854
  if (entry.content?.sourceDescription) {
6740
6855
  const sourceDesc = entry.content.sourceDescription;
6741
6856
  if (sourceDesc.citations?.[0]?.value) {
6742
- lines.push(`1 NOTE Citation: ${sourceDesc.citations[0].value}`);
6857
+ lines.push(
6858
+ `1 NOTE Citation: ${sanitizeGedcomValue(sourceDesc.citations[0].value)}`
6859
+ );
6743
6860
  }
6744
6861
  if (sourceDesc.resourceType) {
6745
- lines.push(`1 NOTE Resource Type: ${sourceDesc.resourceType}`);
6862
+ lines.push(
6863
+ `1 NOTE Resource Type: ${sanitizeGedcomValue(sourceDesc.resourceType)}`
6864
+ );
6746
6865
  }
6747
6866
  if (sourceDesc.about) {
6748
6867
  const webUrl = transformSourceUrl(sourceDesc.about);
@@ -6985,7 +7104,7 @@ function convertToGedcom(pedigreeData, options = {}) {
6985
7104
  const personData = person.fullDetails?.persons?.[0] || person;
6986
7105
  const name = extractName(personData);
6987
7106
  if (name) {
6988
- lines.push(`1 NAME ${name}`);
7107
+ lines.push(`1 NAME ${sanitizeGedcomValue(name)}`);
6989
7108
  }
6990
7109
  const gender = extractGender(personData);
6991
7110
  if (gender) {
@@ -6995,20 +7114,20 @@ function convertToGedcom(pedigreeData, options = {}) {
6995
7114
  if (birth) {
6996
7115
  lines.push("1 BIRT");
6997
7116
  if (birth.date) {
6998
- lines.push(`2 DATE ${birth.date}`);
7117
+ lines.push(`2 DATE ${sanitizeGedcomValue(birth.date)}`);
6999
7118
  }
7000
7119
  if (birth.place) {
7001
- lines.push(`2 PLAC ${birth.place}`);
7120
+ lines.push(`2 PLAC ${sanitizeGedcomValue(birth.place)}`);
7002
7121
  }
7003
7122
  }
7004
7123
  const death = extractFact(personData, "DEATH");
7005
7124
  if (death) {
7006
7125
  lines.push("1 DEAT");
7007
7126
  if (death.date) {
7008
- lines.push(`2 DATE ${death.date}`);
7127
+ lines.push(`2 DATE ${sanitizeGedcomValue(death.date)}`);
7009
7128
  }
7010
7129
  if (death.place) {
7011
- lines.push(`2 PLAC ${death.place}`);
7130
+ lines.push(`2 PLAC ${sanitizeGedcomValue(death.place)}`);
7012
7131
  }
7013
7132
  }
7014
7133
  personData.facts?.forEach((fact) => {
@@ -7020,8 +7139,7 @@ function convertToGedcom(pedigreeData, options = {}) {
7020
7139
  if (includeNotes && person.notes?.persons?.[0]?.notes) {
7021
7140
  person.notes.persons[0].notes.forEach((note) => {
7022
7141
  if (note.text) {
7023
- const noteText = note.text.replace(/\n/g, " ");
7024
- lines.push(`1 NOTE ${noteText}`);
7142
+ lines.push(`1 NOTE ${sanitizeGedcomValue(note.text)}`);
7025
7143
  }
7026
7144
  });
7027
7145
  }
@@ -7039,7 +7157,7 @@ function convertToGedcom(pedigreeData, options = {}) {
7039
7157
  sourceRef.qualifiers.forEach((qualifier) => {
7040
7158
  if (qualifier.name && qualifier.value) {
7041
7159
  lines.push(
7042
- `2 NOTE ${qualifier.name}: ${qualifier.value}`
7160
+ `2 NOTE ${sanitizeGedcomValue(`${qualifier.name}: ${qualifier.value}`)}`
7043
7161
  );
7044
7162
  }
7045
7163
  });
@@ -7193,35 +7311,40 @@ function addMarriageFacts(lines, relationships, person1, person2) {
7193
7311
  return [a, b].sort().join("-") === relKey;
7194
7312
  });
7195
7313
  if (!rel) return;
7314
+ const isMarriageEvent = (type) => !!type && (type.includes("Marriage") || type.includes("Divorce"));
7196
7315
  const marriageFacts = [];
7197
7316
  if (rel.facts && Array.isArray(rel.facts)) {
7198
7317
  rel.facts.forEach((f) => {
7199
- if (f.type?.includes("Marriage")) {
7318
+ if (isMarriageEvent(f.type)) {
7200
7319
  marriageFacts.push(f);
7201
7320
  }
7202
7321
  });
7203
7322
  }
7204
7323
  if (rel.details?.facts && Array.isArray(rel.details.facts)) {
7205
7324
  marriageFacts.push(
7206
- ...rel.details.facts.filter((f) => f.type?.includes("Marriage"))
7325
+ ...rel.details.facts.filter((f) => isMarriageEvent(f.type))
7207
7326
  );
7208
7327
  }
7209
7328
  if (rel.details?.persons && Array.isArray(rel.details.persons)) {
7210
7329
  rel.details.persons.forEach((p) => {
7211
7330
  if (p.facts && Array.isArray(p.facts)) {
7212
7331
  p.facts.forEach((f) => {
7213
- if (f.type?.includes("Marriage")) {
7332
+ if (isMarriageEvent(f.type)) {
7214
7333
  marriageFacts.push(f);
7215
7334
  }
7216
7335
  });
7217
7336
  }
7218
7337
  });
7219
7338
  }
7339
+ const seenFacts = /* @__PURE__ */ new Set();
7220
7340
  marriageFacts.forEach((mf) => {
7341
+ const key = `${mf.type}|${resolveFactDate(mf.date) || ""}|${mf.place?.original || ""}`;
7342
+ if (seenFacts.has(key)) return;
7343
+ seenFacts.add(key);
7221
7344
  const factLines = convertFactToGedcom(mf);
7222
7345
  factLines.forEach((line) => lines.push(line));
7223
7346
  });
7224
7347
  }
7225
7348
  var convertFamilySearchToGedcom = convertToGedcom;
7226
7349
 
7227
- export { AuthenticationError, ENVIRONMENT_CONFIGS, FamilySearchError, FamilySearchSDK, genealogies_exports as GenealogiesAPI, memories_exports as MemoriesAPI, NetworkError, NotFoundError, OAUTH_ENDPOINTS, OAUTH_STORAGE_KEYS, OAuthAPI, PedigreeAPI, PlacesAPI, RateLimitError, ServerError, standards_exports as StandardsAPI, tree_exports as TreeAPI, user_exports as UserAPI, ValidationError, vocab_exports as VocabAPI, buildAuthorizationUrl, checkPlaceIsChild, clearAllTokens, clearStoredTokens, convertFamilySearchToGedcom, convertToGedcom, createErrorFromResponse, createFamilySearchSDK, createNetworkError, exchangeCodeForToken, fetchMultiplePersons, fetchPedigree, generateOAuthState, getFamilySearchSDK, getOAuthEndpoints, getStoredAccessToken, getStoredRefreshToken, getTokenStorageKey, getUserInfo, initFamilySearchSDK, isRetryableError, openOAuthPopup, parseCallbackParams, readCurrentUser2 as readCurrentUser, readPlaceAttributes, readPlaceChildren, readPlaceDescription, readPlaceDescriptionWithRelated, readPlaceDescriptions, readPlaceDescriptionsGroup, readPlaceDetails, readPlaceType, readPlaceTypeGroup, readPlaceTypeGroups, readPlaceTypes, refreshAccessToken, resetFamilySearchSDK, searchParentPlaces, searchPlaces, storeOAuthState, storeTokens, transformFamilySearchUrl, validateAccessToken, validateOAuthState };
7350
+ export { AuthenticationError, ENVIRONMENT_CONFIGS, FamilySearchError, FamilySearchSDK, genealogies_exports as GenealogiesAPI, memories_exports as MemoriesAPI, NetworkError, NotFoundError, OAUTH_ENDPOINTS, OAUTH_STORAGE_KEYS, OAuthAPI, PedigreeAPI, PlacesAPI, RateLimitError, ServerError, standards_exports as StandardsAPI, tree_exports as TreeAPI, user_exports as UserAPI, ValidationError, vocab_exports as VocabAPI, buildAuthorizationUrl, checkPlaceIsChild, clearAllTokens, clearStoredTokens, convertFamilySearchToGedcom, convertToGedcom, createErrorFromResponse, createFamilySearchSDK, createNetworkError, exchangeCodeForToken, extractGazetteerPlaceId, familySearchResearchPlaceUrl, fetchMultiplePersons, fetchPedigree, generateOAuthState, getFamilySearchSDK, getOAuthEndpoints, getStoredAccessToken, getStoredRefreshToken, getTokenStorageKey, getUserInfo, initFamilySearchSDK, isRetryableError, numericPlaceIdFromRef, openOAuthPopup, parseCallbackParams, pickBestPlaceSearchResult, readCurrentUser2 as readCurrentUser, readPlaceAttributes, readPlaceChildren, readPlaceDescription, readPlaceDescriptionWithRelated, readPlaceDescriptions, readPlaceDescriptionsGroup, readPlaceDetails, readPlaceType, readPlaceTypeGroup, readPlaceTypeGroups, readPlaceTypes, refreshAccessToken, resetFamilySearchSDK, searchParentPlaces, searchPlaces, storeOAuthState, storeTokens, transformFamilySearchUrl, validateAccessToken, validateOAuthState };
@@ -298,8 +298,46 @@ var PlacesAPI = class {
298
298
  }
299
299
  };
300
300
 
301
+ // src/places/gazetteer-id.ts
302
+ var numericPlaceIdFromRef = (ref) => {
303
+ if (!ref) {
304
+ return void 0;
305
+ }
306
+ if (ref.includes("/places/description/") || ref.includes("ark:/")) {
307
+ return void 0;
308
+ }
309
+ const match = ref.match(/\/places\/(\d+)/);
310
+ return match?.[1];
311
+ };
312
+ var extractGazetteerPlaceId = (place) => {
313
+ const persistent = place.identifiers?.["http://gedcomx.org/Persistent"]?.[0];
314
+ const fromPersistent = numericPlaceIdFromRef(persistent);
315
+ if (fromPersistent) {
316
+ return fromPersistent;
317
+ }
318
+ return numericPlaceIdFromRef(place.place?.description);
319
+ };
320
+ var familySearchResearchPlaceUrl = (placeId, baseUrl = "https://www.familysearch.org") => `${baseUrl}/en/research/places/?focusedId=${placeId}`;
321
+ var pickBestPlaceSearchResult = (results, query) => {
322
+ if (results.length === 0) {
323
+ return void 0;
324
+ }
325
+ const town = query.split(",")[0]?.trim().toLowerCase();
326
+ if (!town) {
327
+ return results[0];
328
+ }
329
+ const label = (result) => (result.fullName || result.name || result.title || "").toLowerCase();
330
+ return results.find(
331
+ (result) => label(result) === town || label(result).startsWith(`${town},`)
332
+ ) || results.find((result) => label(result).includes(town)) || results[0];
333
+ };
334
+
301
335
  exports.PlacesAPI = PlacesAPI;
302
336
  exports.checkPlaceIsChild = checkPlaceIsChild;
337
+ exports.extractGazetteerPlaceId = extractGazetteerPlaceId;
338
+ exports.familySearchResearchPlaceUrl = familySearchResearchPlaceUrl;
339
+ exports.numericPlaceIdFromRef = numericPlaceIdFromRef;
340
+ exports.pickBestPlaceSearchResult = pickBestPlaceSearchResult;
303
341
  exports.readPlaceAttributes = readPlaceAttributes;
304
342
  exports.readPlaceChildren = readPlaceChildren;
305
343
  exports.readPlaceDescription = readPlaceDescription;
@@ -1,3 +1,42 @@
1
- export { bW as PlacesAPI, bY as checkPlaceIsChild, b$ as readPlaceAttributes, c0 as readPlaceChildren, c1 as readPlaceDescription, c2 as readPlaceDescriptionWithRelated, c3 as readPlaceDescriptions, c4 as readPlaceDescriptionsGroup, c5 as readPlaceDetails, c6 as readPlaceType, c7 as readPlaceTypeGroup, c8 as readPlaceTypeGroups, c9 as readPlaceTypes, cd as searchParentPlaces, ce as searchPlaces } from '../index-CLkFtjZD.cjs';
1
+ export { bW as PlacesAPI, bY as checkPlaceIsChild, b$ as readPlaceAttributes, c0 as readPlaceChildren, c1 as readPlaceDescription, c2 as readPlaceDescriptionWithRelated, c3 as readPlaceDescriptions, c4 as readPlaceDescriptionsGroup, c5 as readPlaceDetails, c6 as readPlaceType, c7 as readPlaceTypeGroup, c8 as readPlaceTypeGroups, c9 as readPlaceTypes, cd as searchParentPlaces, ce as searchPlaces } from '../tree-changes-e0UMhVha.cjs';
2
2
  import '../tree-D7X-beAQ.cjs';
3
3
  import '../index-0PpnEPuZ.cjs';
4
+
5
+ /**
6
+ * FamilySearch Places search returns Place Descriptions.
7
+ * `place.id` is a Description ID. The Places web UI `focusedId` is a
8
+ * gazetteer Place ID (different namespace). The Place ID lives on
9
+ * `identifiers["http://gedcomx.org/Persistent"]` or `place.place`.
10
+ *
11
+ * @see https://developers.familysearch.org/main/docs/places
12
+ */
13
+ type FamilySearchPlacePayload = {
14
+ id?: string;
15
+ identifiers?: Record<string, string[]>;
16
+ place?: {
17
+ original?: string;
18
+ description?: string;
19
+ };
20
+ display?: {
21
+ name?: string;
22
+ fullName?: string;
23
+ type?: string;
24
+ };
25
+ names?: Array<{
26
+ value?: string;
27
+ }>;
28
+ type?: string;
29
+ latitude?: number;
30
+ longitude?: number;
31
+ };
32
+ /** Numeric ID from a Place authority URL, never from /places/description/. */
33
+ declare const numericPlaceIdFromRef: (ref?: string) => string | undefined;
34
+ declare const extractGazetteerPlaceId: (place: FamilySearchPlacePayload) => string | undefined;
35
+ declare const familySearchResearchPlaceUrl: (placeId: string, baseUrl?: string) => string;
36
+ declare const pickBestPlaceSearchResult: <T extends {
37
+ name?: string;
38
+ fullName?: string;
39
+ title?: string;
40
+ }>(results: T[], query: string) => T | undefined;
41
+
42
+ export { type FamilySearchPlacePayload, extractGazetteerPlaceId, familySearchResearchPlaceUrl, numericPlaceIdFromRef, pickBestPlaceSearchResult };
@@ -1,3 +1,42 @@
1
- export { bW as PlacesAPI, bY as checkPlaceIsChild, b$ as readPlaceAttributes, c0 as readPlaceChildren, c1 as readPlaceDescription, c2 as readPlaceDescriptionWithRelated, c3 as readPlaceDescriptions, c4 as readPlaceDescriptionsGroup, c5 as readPlaceDetails, c6 as readPlaceType, c7 as readPlaceTypeGroup, c8 as readPlaceTypeGroups, c9 as readPlaceTypes, cd as searchParentPlaces, ce as searchPlaces } from '../index-CKi9wd-C.js';
1
+ export { bW as PlacesAPI, bY as checkPlaceIsChild, b$ as readPlaceAttributes, c0 as readPlaceChildren, c1 as readPlaceDescription, c2 as readPlaceDescriptionWithRelated, c3 as readPlaceDescriptions, c4 as readPlaceDescriptionsGroup, c5 as readPlaceDetails, c6 as readPlaceType, c7 as readPlaceTypeGroup, c8 as readPlaceTypeGroups, c9 as readPlaceTypes, cd as searchParentPlaces, ce as searchPlaces } from '../tree-changes-CFwJtBcK.js';
2
2
  import '../tree-D7X-beAQ.js';
3
3
  import '../index-0PpnEPuZ.js';
4
+
5
+ /**
6
+ * FamilySearch Places search returns Place Descriptions.
7
+ * `place.id` is a Description ID. The Places web UI `focusedId` is a
8
+ * gazetteer Place ID (different namespace). The Place ID lives on
9
+ * `identifiers["http://gedcomx.org/Persistent"]` or `place.place`.
10
+ *
11
+ * @see https://developers.familysearch.org/main/docs/places
12
+ */
13
+ type FamilySearchPlacePayload = {
14
+ id?: string;
15
+ identifiers?: Record<string, string[]>;
16
+ place?: {
17
+ original?: string;
18
+ description?: string;
19
+ };
20
+ display?: {
21
+ name?: string;
22
+ fullName?: string;
23
+ type?: string;
24
+ };
25
+ names?: Array<{
26
+ value?: string;
27
+ }>;
28
+ type?: string;
29
+ latitude?: number;
30
+ longitude?: number;
31
+ };
32
+ /** Numeric ID from a Place authority URL, never from /places/description/. */
33
+ declare const numericPlaceIdFromRef: (ref?: string) => string | undefined;
34
+ declare const extractGazetteerPlaceId: (place: FamilySearchPlacePayload) => string | undefined;
35
+ declare const familySearchResearchPlaceUrl: (placeId: string, baseUrl?: string) => string;
36
+ declare const pickBestPlaceSearchResult: <T extends {
37
+ name?: string;
38
+ fullName?: string;
39
+ title?: string;
40
+ }>(results: T[], query: string) => T | undefined;
41
+
42
+ export { type FamilySearchPlacePayload, extractGazetteerPlaceId, familySearchResearchPlaceUrl, numericPlaceIdFromRef, pickBestPlaceSearchResult };
@@ -296,4 +296,38 @@ var PlacesAPI = class {
296
296
  }
297
297
  };
298
298
 
299
- export { PlacesAPI, checkPlaceIsChild, readPlaceAttributes, readPlaceChildren, readPlaceDescription, readPlaceDescriptionWithRelated, readPlaceDescriptions, readPlaceDescriptionsGroup, readPlaceDetails, readPlaceType, readPlaceTypeGroup, readPlaceTypeGroups, readPlaceTypes, searchParentPlaces, searchPlaces };
299
+ // src/places/gazetteer-id.ts
300
+ var numericPlaceIdFromRef = (ref) => {
301
+ if (!ref) {
302
+ return void 0;
303
+ }
304
+ if (ref.includes("/places/description/") || ref.includes("ark:/")) {
305
+ return void 0;
306
+ }
307
+ const match = ref.match(/\/places\/(\d+)/);
308
+ return match?.[1];
309
+ };
310
+ var extractGazetteerPlaceId = (place) => {
311
+ const persistent = place.identifiers?.["http://gedcomx.org/Persistent"]?.[0];
312
+ const fromPersistent = numericPlaceIdFromRef(persistent);
313
+ if (fromPersistent) {
314
+ return fromPersistent;
315
+ }
316
+ return numericPlaceIdFromRef(place.place?.description);
317
+ };
318
+ var familySearchResearchPlaceUrl = (placeId, baseUrl = "https://www.familysearch.org") => `${baseUrl}/en/research/places/?focusedId=${placeId}`;
319
+ var pickBestPlaceSearchResult = (results, query) => {
320
+ if (results.length === 0) {
321
+ return void 0;
322
+ }
323
+ const town = query.split(",")[0]?.trim().toLowerCase();
324
+ if (!town) {
325
+ return results[0];
326
+ }
327
+ const label = (result) => (result.fullName || result.name || result.title || "").toLowerCase();
328
+ return results.find(
329
+ (result) => label(result) === town || label(result).startsWith(`${town},`)
330
+ ) || results.find((result) => label(result).includes(town)) || results[0];
331
+ };
332
+
333
+ export { PlacesAPI, checkPlaceIsChild, extractGazetteerPlaceId, familySearchResearchPlaceUrl, numericPlaceIdFromRef, pickBestPlaceSearchResult, readPlaceAttributes, readPlaceChildren, readPlaceDescription, readPlaceDescriptionWithRelated, readPlaceDescriptions, readPlaceDescriptionsGroup, readPlaceDetails, readPlaceType, readPlaceTypeGroup, readPlaceTypeGroups, readPlaceTypes, searchParentPlaces, searchPlaces };
@@ -1,6 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  // src/api/tree/persons.ts
4
+ var PERSON_WRITE_HEADERS = {
5
+ "Content-Type": "application/x-fs-v1+json",
6
+ Accept: "application/x-fs-v1+json"
7
+ };
4
8
  async function readPerson(sdk, personId) {
5
9
  try {
6
10
  const response = await sdk.get(
@@ -33,7 +37,7 @@ async function readPersonWithDetails(sdk, personId, options = {}) {
33
37
  }
34
38
  async function createPerson(sdk, person, reason) {
35
39
  try {
36
- const headers = {};
40
+ const headers = { ...PERSON_WRITE_HEADERS };
37
41
  if (reason) {
38
42
  headers["X-Reason"] = reason;
39
43
  }
@@ -50,7 +54,7 @@ async function createPerson(sdk, person, reason) {
50
54
  }
51
55
  async function updatePerson(sdk, personId, person, reason) {
52
56
  try {
53
- const headers = {};
57
+ const headers = { ...PERSON_WRITE_HEADERS };
54
58
  if (reason) {
55
59
  headers["X-Reason"] = reason;
56
60
  }