@treeviz/familysearch-catalog-sdk 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 idavidka and @treeviz contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,329 @@
1
+ # @treeviz/familysearch-catalog-sdk
2
+
3
+ > Part of the [@treeviz](https://www.npmjs.com/org/treeviz) organization - A collection of tools for genealogy data processing and visualization.
4
+
5
+ A modern TypeScript SDK for interacting with the FamilySearch Catalog and Places APIs. This package provides reusable utilities for resolving place names, looking up parish records, and extracting coverage periods from genealogical records.
6
+
7
+ ## Features
8
+
9
+ - πŸ—ΊοΈ **Places API** - Search and normalize place names
10
+ - πŸ“š **Catalog API** - Search parish and civil records
11
+ - πŸ” **Metadata Parsing** - Extract parish names, date ranges, and registry types
12
+ - πŸ’Ύ **Smart Caching** - In-memory LRU cache with TTL support
13
+ - πŸ” **OAuth Integration** - Reuses `@treeviz/familysearch-sdk` for authentication
14
+ - πŸ“˜ **TypeScript-first** - Full type definitions included
15
+ - ⚑ **Promise-based** - Modern async/await API
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @treeviz/familysearch-catalog-sdk
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```typescript
26
+ import { FamilySearchCatalog } from "@treeviz/familysearch-catalog-sdk";
27
+
28
+ // Initialize the resolver
29
+ const resolver = new FamilySearchCatalog({
30
+ clientId: process.env.FS_CLIENT_ID!,
31
+ clientSecret: process.env.FS_CLIENT_SECRET,
32
+ environment: "production",
33
+ enableCache: true,
34
+ });
35
+
36
+ // Resolve a place name
37
+ const result = await resolver.resolvePlace("Kismaros, Hungary");
38
+
39
+ console.log(result);
40
+ // Output:
41
+ // {
42
+ // placeId: "...",
43
+ // standardizedName: "Kismaros, Pest, Hungary",
44
+ // registry: "Roman Catholic Parish of Nagymaros",
45
+ // coverage: "1730-1895",
46
+ // sources: [...]
47
+ // }
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ ### Basic Place Resolution
53
+
54
+ ```typescript
55
+ const result = await resolver.resolvePlace("Budapest");
56
+
57
+ if (result) {
58
+ console.log(`Standardized: ${result.standardizedName}`);
59
+ console.log(`Registry: ${result.registry}`);
60
+ console.log(`Coverage: ${result.coverage}`);
61
+ }
62
+ ```
63
+
64
+ ### Search Places
65
+
66
+ ```typescript
67
+ const places = await resolver.searchPlaces("Kismaros", 5);
68
+
69
+ for (const place of places) {
70
+ console.log(`${place.name} - ${place.fullName}`);
71
+
72
+ // URLs are automatically generated for the configured environment
73
+ console.log(`Place details: ${place.url}`);
74
+ console.log(`Catalog search: ${place.catalogUrl}`);
75
+ console.log(`Records search: ${place.recordsUrl}`);
76
+ }
77
+ ```
78
+
79
+ **Note:** The SDK automatically generates FamilySearch web UI URLs (`url`, `catalogUrl`, `recordsUrl`) for each place result based on the configured environment (production/beta/integration).
80
+
81
+ ### Search Catalog
82
+
83
+ ```typescript
84
+ const records = await resolver.searchCatalog("Kismaros, Hungary");
85
+
86
+ for (const record of records) {
87
+ console.log(`${record.title} (${record.coverageYears})`);
88
+ console.log(`Author: ${record.author}`);
89
+ console.log(`URL: ${record.url}`);
90
+ }
91
+ ```
92
+
93
+ ### Using Individual Modules
94
+
95
+ ```typescript
96
+ import { CatalogPlacesClient, PlacesAPI, CatalogAPI } from "@treeviz/familysearch-catalog-sdk";
97
+
98
+ const client = new CatalogPlacesClient({
99
+ clientId: process.env.FS_CLIENT_ID!,
100
+ environment: "production",
101
+ });
102
+
103
+ const places = new PlacesAPI(client);
104
+ const catalog = new CatalogAPI(client);
105
+
106
+ // Search places
107
+ const placeResults = await places.searchPlace("Budapest");
108
+
109
+ // Search catalog
110
+ const catalogResults = await catalog.searchByPlace("Budapest");
111
+
112
+ // Extract coverage period
113
+ const coverage = catalog.getCoveragePeriod(catalogResults);
114
+ console.log(`Coverage: ${coverage?.startYear}-${coverage?.endYear}`);
115
+ ```
116
+
117
+ ### Parser Utilities
118
+
119
+ ```typescript
120
+ import * as Parser from "@treeviz/familysearch-catalog-sdk/parser";
121
+
122
+ // Extract parish name from title
123
+ const parish = Parser.extractParishName("Roman Catholic Parish of Nagymaros");
124
+ console.log(parish); // "Nagymaros"
125
+
126
+ // Parse date range
127
+ const range = Parser.parseDateRange("Records from 1730-1895");
128
+ console.log(range); // { start: 1730, end: 1895 }
129
+
130
+ // Extract registry type
131
+ const type = Parser.extractRegistryType("Roman Catholic Parish of Nagymaros");
132
+ console.log(type); // "Roman Catholic"
133
+
134
+ // Normalize place name
135
+ const normalized = Parser.normalizePlaceName("KismarΓ³s");
136
+ console.log(normalized); // "kismaros"
137
+ ```
138
+
139
+ ### Caching
140
+
141
+ ```typescript
142
+ const resolver = new FamilySearchCatalog({
143
+ clientId: process.env.FS_CLIENT_ID!,
144
+ enableCache: true,
145
+ cacheTTL: 3600, // 1 hour
146
+ });
147
+
148
+ // First call - makes API request
149
+ await resolver.resolvePlace("Budapest");
150
+
151
+ // Second call - uses cached result
152
+ await resolver.resolvePlace("Budapest");
153
+
154
+ // Clear cache
155
+ resolver.clearCache();
156
+
157
+ // Get cache stats
158
+ const stats = resolver.getCacheStats();
159
+ console.log(`Cache size: ${stats.size}, enabled: ${stats.enabled}`);
160
+ ```
161
+
162
+ ### Authentication
163
+
164
+ ```typescript
165
+ // Set access token for authenticated requests
166
+ resolver.setAccessToken("your-oauth-token");
167
+
168
+ // Now you can make authenticated requests
169
+ const result = await resolver.resolvePlace("Budapest");
170
+ ```
171
+
172
+ ## API Reference
173
+
174
+ ### FamilySearchCatalog
175
+
176
+ The main class for high-level operations.
177
+
178
+ #### Constructor
179
+
180
+ ```typescript
181
+ new FamilySearchCatalog(config: CatalogPlacesClientConfig)
182
+ ```
183
+
184
+ **Config options:**
185
+ - `clientId` (required) - FamilySearch OAuth client ID
186
+ - `clientSecret` (optional) - FamilySearch OAuth client secret
187
+ - `environment` (optional) - Environment: `"production"` (default), `"beta"`, or `"integration"`
188
+ - `enableCache` (optional) - Enable caching (default: `true`)
189
+ - `cacheTTL` (optional) - Cache TTL in seconds (default: `3600`)
190
+ - `debug` (optional) - Enable debug logging (default: `false`)
191
+
192
+ #### Methods
193
+
194
+ - `resolvePlace(placeName, options?)` - Resolve a place name
195
+ - `searchPlaces(query, count?)` - Search for places
196
+ - `searchCatalog(placeName, count?)` - Search catalog
197
+ - `getPlaceById(placeId)` - Get place details by ID
198
+ - `setAccessToken(token)` - Set OAuth access token
199
+ - `clearCache()` - Clear the cache
200
+ - `getCacheStats()` - Get cache statistics
201
+
202
+ ### PlacesAPI
203
+
204
+ Low-level Places API.
205
+
206
+ - `searchPlace(query, options?)` - Search for places
207
+ - `getPlaceById(placeId)` - Get place by ID
208
+ - `normalizePlace(placeName)` - Normalize a place name
209
+
210
+ ### CatalogAPI
211
+
212
+ Low-level Catalog API.
213
+
214
+ - `searchByPlace(placeName, options?)` - Search catalog by place
215
+ - `getRecordDetails(recordId)` - Get catalog record details
216
+ - `getCoveragePeriod(records)` - Extract coverage period from records
217
+ - `extractParishInfo(records)` - Extract parish information
218
+
219
+ ### Parser Utilities
220
+
221
+ - `extractParishName(title)` - Extract parish name from title
222
+ - `extractRegistryType(title)` - Extract registry type
223
+ - `parseDateRange(text)` - Parse date range from text
224
+ - `extractAuthor(attribution)` - Extract author name
225
+ - `formatYearRange(start, end)` - Format year range as string
226
+ - `normalizePlaceName(placeName)` - Normalize place name for comparison
227
+ - `calculateSimilarity(str1, str2)` - Calculate string similarity
228
+
229
+ ## Type Definitions
230
+
231
+ ### ResolvedPlace
232
+
233
+ ```typescript
234
+ interface ResolvedPlace {
235
+ placeId: string;
236
+ standardizedName: string;
237
+ registry: string;
238
+ coverage: string;
239
+ sources: CatalogSource[];
240
+ }
241
+ ```
242
+
243
+ ### CatalogSource
244
+
245
+ ```typescript
246
+ interface CatalogSource {
247
+ id: string;
248
+ title: string;
249
+ author: string;
250
+ years: string;
251
+ url: string;
252
+ }
253
+ ```
254
+
255
+ ### PlaceSearchResult
256
+
257
+ ```typescript
258
+ interface PlaceSearchResult {
259
+ id: string;
260
+ name: string;
261
+ fullName: string;
262
+ type?: string;
263
+ latitude?: number;
264
+ longitude?: number;
265
+ parent?: {
266
+ id: string;
267
+ name: string;
268
+ };
269
+ }
270
+ ```
271
+
272
+ ### CatalogSearchResult
273
+
274
+ ```typescript
275
+ interface CatalogSearchResult {
276
+ id: string;
277
+ title: string;
278
+ author: string;
279
+ coverageYears: string;
280
+ place: string;
281
+ type: string;
282
+ url: string;
283
+ metadata?: Record<string, unknown>;
284
+ }
285
+ ```
286
+
287
+ ## Dependencies
288
+
289
+ This package depends on:
290
+ - `@treeviz/familysearch-sdk` - For OAuth authentication and base HTTP client
291
+
292
+ ## Development
293
+
294
+ ```bash
295
+ # Install dependencies
296
+ npm install
297
+
298
+ # Build
299
+ npm run build
300
+
301
+ # Run tests
302
+ npm test
303
+
304
+ # Watch mode
305
+ npm run dev
306
+ ```
307
+
308
+ ## Related Packages
309
+
310
+ - [@treeviz/familysearch-sdk](https://www.npmjs.com/package/@treeviz/familysearch-sdk) - Core FamilySearch API SDK
311
+ - [@treeviz/gedcom-parser](https://www.npmjs.com/package/@treeviz/gedcom-parser) - GEDCOM file parser
312
+
313
+ ## Contributing
314
+
315
+ Contributions are welcome! Please feel free to submit a Pull Request.
316
+
317
+ ## License
318
+
319
+ MIT License - see [LICENSE](LICENSE) file for details
320
+
321
+ Copyright (c) 2026 idavidka and @treeviz contributors
322
+
323
+ ## Author
324
+
325
+ idavidka and @treeviz contributors
326
+
327
+ ## Repository
328
+
329
+ https://github.com/idavidka/familysearch-catalog-sdk
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ // src/cache/index.ts
4
+ var MemoryCache = class {
5
+ /**
6
+ * Create a new memory cache
7
+ * @param maxSize Maximum number of entries (default: 1000)
8
+ * @param defaultTTL Default TTL in seconds (default: 3600 = 1 hour)
9
+ */
10
+ constructor(maxSize = 1e3, defaultTTL = 3600) {
11
+ this.cache = /* @__PURE__ */ new Map();
12
+ this.maxSize = maxSize;
13
+ this.defaultTTL = defaultTTL;
14
+ }
15
+ /**
16
+ * Get value from cache
17
+ */
18
+ get(key) {
19
+ const entry = this.cache.get(key);
20
+ if (!entry) {
21
+ return void 0;
22
+ }
23
+ const now = Date.now();
24
+ const age = (now - entry.timestamp) / 1e3;
25
+ if (age > entry.ttl) {
26
+ this.cache.delete(key);
27
+ return void 0;
28
+ }
29
+ this.cache.delete(key);
30
+ this.cache.set(key, entry);
31
+ return entry.data;
32
+ }
33
+ /**
34
+ * Set value in cache
35
+ */
36
+ set(key, value, ttl) {
37
+ if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
38
+ const firstKey = this.cache.keys().next().value;
39
+ if (firstKey !== void 0) {
40
+ this.cache.delete(firstKey);
41
+ }
42
+ }
43
+ const entry = {
44
+ data: value,
45
+ timestamp: Date.now(),
46
+ ttl: ttl ?? this.defaultTTL
47
+ };
48
+ this.cache.set(key, entry);
49
+ }
50
+ /**
51
+ * Check if key exists in cache
52
+ */
53
+ has(key) {
54
+ const value = this.get(key);
55
+ return value !== void 0;
56
+ }
57
+ /**
58
+ * Delete key from cache
59
+ */
60
+ delete(key) {
61
+ return this.cache.delete(key);
62
+ }
63
+ /**
64
+ * Clear all cache entries
65
+ */
66
+ clear() {
67
+ this.cache.clear();
68
+ }
69
+ /**
70
+ * Get cache size
71
+ */
72
+ size() {
73
+ return this.cache.size;
74
+ }
75
+ /**
76
+ * Clean up expired entries
77
+ */
78
+ cleanup() {
79
+ const now = Date.now();
80
+ const keysToDelete = [];
81
+ for (const [key, entry] of this.cache.entries()) {
82
+ const age = (now - entry.timestamp) / 1e3;
83
+ if (age > entry.ttl) {
84
+ keysToDelete.push(key);
85
+ }
86
+ }
87
+ for (const key of keysToDelete) {
88
+ this.cache.delete(key);
89
+ }
90
+ }
91
+ };
92
+
93
+ exports.MemoryCache = MemoryCache;
@@ -0,0 +1,48 @@
1
+ import { c as Cache } from '../index-BaNFN7C2.cjs';
2
+ export { d as CacheEntry } from '../index-BaNFN7C2.cjs';
3
+
4
+ /**
5
+ * In-memory LRU cache with TTL support
6
+ */
7
+
8
+ declare class MemoryCache implements Cache {
9
+ private cache;
10
+ private readonly maxSize;
11
+ private readonly defaultTTL;
12
+ /**
13
+ * Create a new memory cache
14
+ * @param maxSize Maximum number of entries (default: 1000)
15
+ * @param defaultTTL Default TTL in seconds (default: 3600 = 1 hour)
16
+ */
17
+ constructor(maxSize?: number, defaultTTL?: number);
18
+ /**
19
+ * Get value from cache
20
+ */
21
+ get<T>(key: string): T | undefined;
22
+ /**
23
+ * Set value in cache
24
+ */
25
+ set<T>(key: string, value: T, ttl?: number): void;
26
+ /**
27
+ * Check if key exists in cache
28
+ */
29
+ has(key: string): boolean;
30
+ /**
31
+ * Delete key from cache
32
+ */
33
+ delete(key: string): boolean;
34
+ /**
35
+ * Clear all cache entries
36
+ */
37
+ clear(): void;
38
+ /**
39
+ * Get cache size
40
+ */
41
+ size(): number;
42
+ /**
43
+ * Clean up expired entries
44
+ */
45
+ cleanup(): void;
46
+ }
47
+
48
+ export { Cache, MemoryCache };
@@ -0,0 +1,48 @@
1
+ import { c as Cache } from '../index-BaNFN7C2.js';
2
+ export { d as CacheEntry } from '../index-BaNFN7C2.js';
3
+
4
+ /**
5
+ * In-memory LRU cache with TTL support
6
+ */
7
+
8
+ declare class MemoryCache implements Cache {
9
+ private cache;
10
+ private readonly maxSize;
11
+ private readonly defaultTTL;
12
+ /**
13
+ * Create a new memory cache
14
+ * @param maxSize Maximum number of entries (default: 1000)
15
+ * @param defaultTTL Default TTL in seconds (default: 3600 = 1 hour)
16
+ */
17
+ constructor(maxSize?: number, defaultTTL?: number);
18
+ /**
19
+ * Get value from cache
20
+ */
21
+ get<T>(key: string): T | undefined;
22
+ /**
23
+ * Set value in cache
24
+ */
25
+ set<T>(key: string, value: T, ttl?: number): void;
26
+ /**
27
+ * Check if key exists in cache
28
+ */
29
+ has(key: string): boolean;
30
+ /**
31
+ * Delete key from cache
32
+ */
33
+ delete(key: string): boolean;
34
+ /**
35
+ * Clear all cache entries
36
+ */
37
+ clear(): void;
38
+ /**
39
+ * Get cache size
40
+ */
41
+ size(): number;
42
+ /**
43
+ * Clean up expired entries
44
+ */
45
+ cleanup(): void;
46
+ }
47
+
48
+ export { Cache, MemoryCache };
@@ -0,0 +1,91 @@
1
+ // src/cache/index.ts
2
+ var MemoryCache = class {
3
+ /**
4
+ * Create a new memory cache
5
+ * @param maxSize Maximum number of entries (default: 1000)
6
+ * @param defaultTTL Default TTL in seconds (default: 3600 = 1 hour)
7
+ */
8
+ constructor(maxSize = 1e3, defaultTTL = 3600) {
9
+ this.cache = /* @__PURE__ */ new Map();
10
+ this.maxSize = maxSize;
11
+ this.defaultTTL = defaultTTL;
12
+ }
13
+ /**
14
+ * Get value from cache
15
+ */
16
+ get(key) {
17
+ const entry = this.cache.get(key);
18
+ if (!entry) {
19
+ return void 0;
20
+ }
21
+ const now = Date.now();
22
+ const age = (now - entry.timestamp) / 1e3;
23
+ if (age > entry.ttl) {
24
+ this.cache.delete(key);
25
+ return void 0;
26
+ }
27
+ this.cache.delete(key);
28
+ this.cache.set(key, entry);
29
+ return entry.data;
30
+ }
31
+ /**
32
+ * Set value in cache
33
+ */
34
+ set(key, value, ttl) {
35
+ if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
36
+ const firstKey = this.cache.keys().next().value;
37
+ if (firstKey !== void 0) {
38
+ this.cache.delete(firstKey);
39
+ }
40
+ }
41
+ const entry = {
42
+ data: value,
43
+ timestamp: Date.now(),
44
+ ttl: ttl ?? this.defaultTTL
45
+ };
46
+ this.cache.set(key, entry);
47
+ }
48
+ /**
49
+ * Check if key exists in cache
50
+ */
51
+ has(key) {
52
+ const value = this.get(key);
53
+ return value !== void 0;
54
+ }
55
+ /**
56
+ * Delete key from cache
57
+ */
58
+ delete(key) {
59
+ return this.cache.delete(key);
60
+ }
61
+ /**
62
+ * Clear all cache entries
63
+ */
64
+ clear() {
65
+ this.cache.clear();
66
+ }
67
+ /**
68
+ * Get cache size
69
+ */
70
+ size() {
71
+ return this.cache.size;
72
+ }
73
+ /**
74
+ * Clean up expired entries
75
+ */
76
+ cleanup() {
77
+ const now = Date.now();
78
+ const keysToDelete = [];
79
+ for (const [key, entry] of this.cache.entries()) {
80
+ const age = (now - entry.timestamp) / 1e3;
81
+ if (age > entry.ttl) {
82
+ keysToDelete.push(key);
83
+ }
84
+ }
85
+ for (const key of keysToDelete) {
86
+ this.cache.delete(key);
87
+ }
88
+ }
89
+ };
90
+
91
+ export { MemoryCache };