findagrave-client
Version:
TypeScript client for FindAGrave GraphQL and REST APIs
493 lines (491 loc) • 14.6 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
FindAGraveClient: () => FindAGraveClient,
browseContinent: () => browseContinent,
browseCountry: () => browseCountry,
browseFromTop: () => browseFromTop,
createFindAGraveClient: () => createFindAGraveClient,
default: () => index_default,
getCemeteriesNear: () => getCemeteriesNear,
searchCemeteries: () => searchCemeteries,
searchLocations: () => searchLocations,
searchMemorials: () => searchMemorials,
searchMemorialsGeneral: () => searchMemorialsGeneral
});
module.exports = __toCommonJS(index_exports);
var import_core = require("@urql/core");
var PERSISTED_QUERIES = {
locationTypeahead: "48272a8b942641e1b3041ac159efd11d5ce4cda9d869a6f9f53468e04fab8ba5",
browse: "bfb78b85f6be61da441f58ab1e538e6b36ba50f5437b177f7826953eb9ee02c8",
browseStart: "aaba96c146d4145d9a29a44e13cf1a0db9f71276a24ec7a0e6d67d2f3968279e",
locationCoordinates: "4c83ce4a0faca6f9b4d38ba441a83eebc72fff8018d365093bc1960594825c8b",
getCemeteriesInBbox: "edb98695eaea673b2c57597571c51d2477019b5bb547d350b50393d5b9ee6dd4"
};
var FindAGraveClient = class {
client;
baseUrl = "https://www.findagrave.com";
constructor() {
this.client = new import_core.Client({
url: `${this.baseUrl}/orc/graphql`,
exchanges: [import_core.cacheExchange, import_core.fetchExchange],
fetchOptions: {
headers: {
"ancestry-clientpath": "findagrave-frontend",
"apollographql-client-name": "findagrave-cemetery-landing",
accept: "*/*",
"content-type": "application/json",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
}
}
});
}
/**
* Search for cemeteries by name
*/
async searchCemeteries(name, returnHighlight = true) {
const variables = {
search: {
name,
autocomplete: "name",
categories: ["cem"],
returnHighlight
}
};
const query = import_core.gql`
query LocationTypeahead($search: LocationSearchInput!) {
locationsByTypeahead(search: $search) {
total
locations {
id
names {
name
language
}
locations {
cemetery
cemeteryId
city
cityId
county
countyId
state
stateId
country
countryId
continent
continentId
}
coordinates {
lat
lon
}
highlight
... on Cemetery {
memorialCount
photographedCount
photoRequestCount
gpsCount
coordinatePrecision
}
}
}
}
`;
const result = await this.client.query(query, variables, {
fetchOptions: {
method: "GET"
}
});
if (result.error) {
throw new Error(`Cemetery search failed: ${result.error.message}`);
}
return result.data.locationsByTypeahead;
}
/**
* Search for locations (cities, counties, states, etc.)
*/
async searchLocations(name, returnHighlight = true) {
const variables = {
search: {
name,
autocomplete: "location",
categories: ["yes"],
returnHighlight
}
};
const query = import_core.gql`
query LocationTypeahead($search: LocationSearchInput!) {
locationsByTypeahead(search: $search) {
total
locations {
id
names {
name
language
}
locations {
city
cityId
county
countyId
state
stateId
country
countryId
continent
continentId
}
coordinates {
lat
lon
}
highlight
}
}
}
`;
const result = await this.client.query(query, variables, {
fetchOptions: {
method: "GET"
}
});
if (result.error) {
throw new Error(`Location search failed: ${result.error.message}`);
}
return result.data.locationsByTypeahead;
}
/**
* Get cemeteries within a geographic bounding box
*/
async getCemeteriesInBoundingBox(searchParams) {
const query = import_core.gql`
query GetCemeteriesInBbox($search: CemeterySearchInput!) {
bulkCemeterySearch(search: $search) {
cemeteries {
id
names {
name
language
}
locations {
cemetery
city
county
state
country
continent
}
coordinates {
lat
lon
}
coordinatePrecision
memorialCount
photographedCount
photoRequestCount
gpsCount
}
}
}
`;
const result = await this.client.query(
query,
{ search: searchParams },
{
fetchOptions: {
method: "GET"
}
}
);
if (result.error) {
throw new Error(`Bounding box search failed: ${result.error.message}`);
}
return result.data.bulkCemeterySearch;
}
/**
* Browse locations hierarchically (continents -> countries -> states -> counties -> cities)
*/
async browseLocations(parents, ignoreCemeteries = true, hasCemeteries = true) {
const query = import_core.gql`
query Browse($parents: [String!]!, $ignoreCemeteries: Boolean, $hasCemeteries: Boolean) {
browse(parents: $parents, ignoreCemeteries: $ignoreCemeteries, hasCemeteries: $hasCemeteries) {
id
locations {
id
names {
name
}
locations {
city
county
state
country
continent
}
coordinates {
lat
lon
}
}
}
}
`;
const result = await this.client.query(
query,
{ parents, ignoreCemeteries, hasCemeteries },
{
fetchOptions: {
method: "GET"
}
}
);
if (result.error) {
throw new Error(`Browse locations failed: ${result.error.message}`);
}
return result.data.browse;
}
/**
* Get location details by ID
*/
async getLocationById(ids) {
const query = import_core.gql`
query GetLocationById($ids: [String!]!) {
locationsById(ids: $ids) {
locations {
id
locations {
city
cityId
county
countyId
state
stateId
country
countryId
continentId
}
coordinates {
lat
lon
}
}
}
}
`;
const result = await this.client.query(
query,
{ ids },
{
fetchOptions: {
method: "GET"
}
}
);
if (result.error) {
throw new Error(`Get location by ID failed: ${result.error.message}`);
}
return result.data.locationsById;
}
/**
* Get coordinates for location IDs
*/
async getLocationCoordinates(ids) {
const query = import_core.gql`
query GetLocationCoordinates($ids: [String!]!) {
locations(ids: $ids) {
locations {
id
coordinates {
lat
lon
}
}
}
}
`;
const result = await this.client.query(
query,
{ ids },
{
fetchOptions: {
method: "GET"
}
}
);
if (result.error) {
throw new Error(`Get location coordinates failed: ${result.error.message}`);
}
return result.data.locations;
}
/**
* Search for memorials/individuals using the REST endpoint
* This uses the native fetch API since memorial searches use REST, not GraphQL
*/
async searchMemorials(cemeteryId, params) {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== void 0 && value !== null) {
searchParams.append(key, value.toString());
}
});
const url = `${this.baseUrl}/cemetery/${cemeteryId}/memorial-search?${searchParams.toString()}`;
const response = await fetch(url, {
method: "GET",
headers: {
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"accept-language": "en-US,en;q=0.5",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
dnt: "1"
}
});
if (!response.ok) {
throw new Error(`Memorial search failed: ${response.status} ${response.statusText}`);
}
return await response.text();
}
/**
* Search for memorials/individuals across all cemeteries (general search)
* This uses the main memorial search endpoint without specifying a cemetery
*/
async searchMemorialsGeneral(params) {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== void 0 && value !== null) {
searchParams.append(key, value.toString());
}
});
const url = `${this.baseUrl}/memorial-search?${searchParams.toString()}`;
const response = await fetch(url, {
method: "GET",
headers: {
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"accept-language": "en-US,en;q=0.5",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
dnt: "1"
}
});
if (!response.ok) {
throw new Error(`General memorial search failed: ${response.status} ${response.statusText}`);
}
return await response.text();
}
/**
* Alternative method using direct URL construction with persisted queries
* This mimics exactly how the browser makes requests
*/
async makePersistedQuery(operationName, variables) {
const encodedVariables = encodeURIComponent(JSON.stringify(variables));
const encodedExtensions = encodeURIComponent(
JSON.stringify({
persistedQuery: {
version: 1,
sha256Hash: PERSISTED_QUERIES[operationName]
}
})
);
const url = `${this.baseUrl}/orc/graphql?operationName=${operationName}&variables=${encodedVariables}&extensions=${encodedExtensions}`;
const response = await fetch(url, {
method: "GET",
headers: {
"ancestry-clientpath": "findagrave-frontend",
"apollographql-client-name": "findagrave-cemetery-landing",
accept: "*/*",
"content-type": "application/json",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
}
});
if (!response.ok) {
throw new Error(`Persisted query failed: ${response.status} ${response.statusText}`);
}
return await response.json();
}
};
function createFindAGraveClient() {
return new FindAGraveClient();
}
async function searchCemeteries(name) {
const client = createFindAGraveClient();
const result = await client.searchCemeteries(name);
return result.locations;
}
async function searchLocations(name) {
const client = createFindAGraveClient();
const result = await client.searchLocations(name);
return result.locations;
}
async function searchMemorials(cemeteryId, firstname, lastname, cemeteryName) {
const client = createFindAGraveClient();
return await client.searchMemorials(cemeteryId, {
firstname,
lastname,
cemeteryName
});
}
async function searchMemorialsGeneral(firstname, lastname, location) {
const client = createFindAGraveClient();
return await client.searchMemorialsGeneral({
firstname,
lastname,
location
});
}
async function getCemeteriesNear(centerLat, centerLon, radiusDegrees = 0.1) {
const client = createFindAGraveClient();
const result = await client.getCemeteriesInBoundingBox({
from: 0,
size: 9999,
boundingBox: {
top_left: { lat: centerLat + radiusDegrees, lon: centerLon - radiusDegrees },
bottom_right: { lat: centerLat - radiusDegrees, lon: centerLon + radiusDegrees }
}
});
return result.cemeteries;
}
async function browseFromTop() {
const client = createFindAGraveClient();
const result = await client.browseLocations(["top"]);
return result.length > 0 ? result[0].locations : [];
}
async function browseContinent(continentId) {
const client = createFindAGraveClient();
const result = await client.browseLocations(["top", continentId]);
return result.find((r) => r.id === continentId)?.locations || [];
}
async function browseCountry(continentId, countryId) {
const client = createFindAGraveClient();
const result = await client.browseLocations(["top", continentId, countryId]);
return result.find((r) => r.id === countryId)?.locations || [];
}
var index_default = FindAGraveClient;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FindAGraveClient,
browseContinent,
browseCountry,
browseFromTop,
createFindAGraveClient,
getCemeteriesNear,
searchCemeteries,
searchLocations,
searchMemorials,
searchMemorialsGeneral
});