@land-catalyst/batch-data-sdk 1.1.12

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 ADDED
@@ -0,0 +1,1032 @@
1
+ # @land-catalyst/batch-data-sdk
2
+
3
+ TypeScript SDK for BatchData.io Property API - Complete type definitions, fluent query builders, and utilities.
4
+
5
+ ## Installation
6
+
7
+ This package is published to the public npm registry. To install it:
8
+
9
+ ```bash
10
+ npm install @land-catalyst/batch-data-sdk
11
+ ```
12
+
13
+ Or add to `package.json`:
14
+ ```json
15
+ {
16
+ "dependencies": {
17
+ "@land-catalyst/batch-data-sdk": "^1.0.0"
18
+ }
19
+ }
20
+ ```
21
+
22
+ ## Features
23
+
24
+ - ✅ **Complete TypeScript Types** - Full type definitions for all BatchData API endpoints and responses
25
+ - ✅ **Fluent Query Builders** - Type-safe, chainable API for constructing search criteria
26
+ - ✅ **Property Search Response Types** - Comprehensive types for property search results
27
+ - ✅ **HTTP Client** - Ready-to-use API client for BatchData endpoints
28
+ - ✅ **Custom Error Classes** - Specialized error handling for BatchData operations
29
+
30
+ ## Examples
31
+
32
+ ### Client Setup
33
+
34
+ ```typescript
35
+ import { BatchDataClient } from "@land-catalyst/batch-data-sdk";
36
+ import { ConsoleLogger } from "@land-catalyst/batch-data-sdk";
37
+
38
+ // Create a client instance
39
+ const client = new BatchDataClient({
40
+ apiKey: process.env.BATCHDATA_API_KEY!,
41
+ // Optional: provide custom logger
42
+ logger: new ConsoleLogger(),
43
+ });
44
+ ```
45
+
46
+ ### Basic Property Search
47
+
48
+ ```typescript
49
+ import {
50
+ SearchCriteriaBuilder,
51
+ PropertySearchRequestBuilder,
52
+ PropertyLookupOptionsBuilder,
53
+ } from "@land-catalyst/batch-data-sdk";
54
+
55
+ // Simple search by location
56
+ const request = new PropertySearchRequestBuilder()
57
+ .searchCriteria((c) => c.query("Maricopa County, AZ"))
58
+ .options((o) => o.take(10).skip(0))
59
+ .build();
60
+
61
+ const response = await client.searchProperties(request);
62
+ ```
63
+
64
+ ### Address-Based Search
65
+
66
+ ```typescript
67
+ import {
68
+ SearchCriteriaBuilder,
69
+ PropertySearchRequestBuilder,
70
+ } from "@land-catalyst/batch-data-sdk";
71
+
72
+ // Search by address with filters
73
+ const request = new PropertySearchRequestBuilder()
74
+ .searchCriteria((c) =>
75
+ c
76
+ .query("Maricopa County, AZ")
77
+ .address((a) =>
78
+ a
79
+ .city((city) => city.equals("Phoenix"))
80
+ .state((state) => state.equals("AZ"))
81
+ .zip((zip) => zip.inList(["85001", "85002", "85003"]))
82
+ .street((street) => street.contains("Main"))
83
+ )
84
+ )
85
+ .build();
86
+
87
+ const response = await client.searchProperties(request);
88
+ ```
89
+
90
+ ### String Filter Examples
91
+
92
+ ```typescript
93
+ import { StringFilterBuilder } from "@land-catalyst/batch-data-sdk";
94
+
95
+ // All string filter methods can be chained
96
+ const filter = new StringFilterBuilder()
97
+ .equals("Phoenix")
98
+ .contains("Phx")
99
+ .startsWith("Ph")
100
+ .endsWith("ix")
101
+ .matches(["Phoenix.*", ".*Arizona"])
102
+ .inList(["Phoenix", "Tucson", "Flagstaff"])
103
+ .build();
104
+
105
+ // Use in address search
106
+ const request = new PropertySearchRequestBuilder()
107
+ .searchCriteria((c) =>
108
+ c
109
+ .query("AZ")
110
+ .address((a) =>
111
+ a
112
+ .city((city) => city.equals("Phoenix").contains("Phx"))
113
+ .state((state) => state.inList(["AZ", "CA", "NV"]))
114
+ )
115
+ )
116
+ .build();
117
+ ```
118
+
119
+ ### Numeric Range Filter Examples
120
+
121
+ ```typescript
122
+ import { NumericRangeFilterBuilder } from "@land-catalyst/batch-data-sdk";
123
+
124
+ // Numeric ranges with min/max
125
+ const filter = new NumericRangeFilterBuilder()
126
+ .min(100000)
127
+ .max(500000)
128
+ .build();
129
+
130
+ // Use in assessment search
131
+ const request = new PropertySearchRequestBuilder()
132
+ .searchCriteria((c) =>
133
+ c
134
+ .query("Maricopa County, AZ")
135
+ .assessment((a) =>
136
+ a
137
+ .totalAssessedValue((v) => v.min(100000).max(500000))
138
+ .assessmentYear((y) => y.min(2020).max(2023))
139
+ )
140
+ )
141
+ .build();
142
+ ```
143
+
144
+ ### Date Range Filter Examples
145
+
146
+ ```typescript
147
+ import { DateRangeFilterBuilder } from "@land-catalyst/batch-data-sdk";
148
+
149
+ // Date ranges
150
+ const filter = new DateRangeFilterBuilder()
151
+ .minDate("2020-01-01")
152
+ .maxDate("2023-12-31")
153
+ .build();
154
+
155
+ // Use in sale search
156
+ const request = new PropertySearchRequestBuilder()
157
+ .searchCriteria((c) =>
158
+ c
159
+ .query("AZ")
160
+ .sale((s) =>
161
+ s.lastSaleDate((d) =>
162
+ d.minDate("2020-01-01").maxDate("2023-12-31")
163
+ )
164
+ )
165
+ )
166
+ .build();
167
+ ```
168
+
169
+ ### Building Criteria Search
170
+
171
+ ```typescript
172
+ const request = new PropertySearchRequestBuilder()
173
+ .searchCriteria((c) =>
174
+ c
175
+ .query("Maricopa County, AZ")
176
+ .building((b) =>
177
+ b
178
+ .yearBuilt((y) => y.min(1990).max(2020))
179
+ .bedroomCount((br) => br.min(3).max(5))
180
+ .bathroomCount((ba) => ba.min(2).max(4))
181
+ .totalBuildingAreaSquareFeet((area) => area.min(1500).max(3000))
182
+ .buildingType((type) => type.equals("Single Family"))
183
+ .pool((pool) => pool.equals("Yes"))
184
+ .airConditioningSource((ac) => ac.contains("Central"))
185
+ )
186
+ )
187
+ .build();
188
+ ```
189
+
190
+ ### Assessment Criteria Search
191
+
192
+ ```typescript
193
+ const request = new PropertySearchRequestBuilder()
194
+ .searchCriteria((c) =>
195
+ c
196
+ .query("Maricopa County, AZ")
197
+ .assessment((a) =>
198
+ a
199
+ .totalAssessedValue((v) => v.min(100000).max(500000))
200
+ .totalMarketValue((v) => v.min(200000).max(600000))
201
+ .assessmentYear((y) => y.min(2020).max(2023))
202
+ )
203
+ )
204
+ .build();
205
+ ```
206
+
207
+ ### Demographics Criteria Search
208
+
209
+ ```typescript
210
+ const request = new PropertySearchRequestBuilder()
211
+ .searchCriteria((c) =>
212
+ c
213
+ .query("Maricopa County, AZ")
214
+ .demographics((d) =>
215
+ d
216
+ .age((a) => a.min(25).max(65))
217
+ .income((i) => i.min(50000).max(150000))
218
+ .netWorth((nw) => nw.min(100000).max(1000000))
219
+ .householdSize((hs) => hs.min(2).max(5))
220
+ .homeownerRenter((hr) => hr.equals("Homeowner"))
221
+ .gender((g) => g.equals("M").inList(["M", "F"]))
222
+ .hasChildren(true)
223
+ .businessOwner((bo) => bo.equals("Yes"))
224
+ )
225
+ )
226
+ .build();
227
+ ```
228
+
229
+ ### Complex Multi-Criteria Search
230
+
231
+ ```typescript
232
+ const request = new PropertySearchRequestBuilder()
233
+ .searchCriteria((c) =>
234
+ c
235
+ .query("Maricopa County, AZ")
236
+ // Address filters
237
+ .address((a) =>
238
+ a
239
+ .city((city) => city.equals("Phoenix"))
240
+ .state((state) => state.equals("AZ"))
241
+ .zip((zip) => zip.startsWith("85"))
242
+ )
243
+ // Building filters
244
+ .building((b) =>
245
+ b
246
+ .yearBuilt((y) => y.min(1990).max(2020))
247
+ .bedroomCount((br) => br.min(3).max(5))
248
+ .bathroomCount((ba) => ba.min(2).max(4))
249
+ )
250
+ // Assessment filters
251
+ .assessment((a) =>
252
+ a
253
+ .totalAssessedValue((v) => v.min(100000).max(500000))
254
+ .assessmentYear((y) => y.min(2020).max(2023))
255
+ )
256
+ // Demographics filters
257
+ .demographics((d) =>
258
+ d
259
+ .income((i) => i.min(50000).max(150000))
260
+ .age((a) => a.min(25).max(65))
261
+ )
262
+ // Quick list
263
+ .quickList("vacant")
264
+ )
265
+ .options((o) =>
266
+ o
267
+ .take(50)
268
+ .skip(0)
269
+ .bedrooms(3, 5)
270
+ .bathrooms(2, 4)
271
+ .yearBuilt(1990, 2020)
272
+ .skipTrace(true)
273
+ .images(true)
274
+ .quicklistCounts(true)
275
+ )
276
+ .build();
277
+ ```
278
+
279
+ ### Property Search with Options
280
+
281
+ ```typescript
282
+ const request = new PropertySearchRequestBuilder()
283
+ .searchCriteria((c) => c.query("Maricopa County, AZ"))
284
+ .options((o) =>
285
+ o
286
+ // Pagination
287
+ .pagination(0, 50)
288
+ .skip(0)
289
+ .take(50)
290
+ // Distance filtering
291
+ .distance(5) // 5 miles
292
+ .distance(undefined, 8800, 26400) // yards and feet
293
+ .distance(undefined, undefined, undefined, 8, 8047) // km and meters
294
+ // Bounding box
295
+ .boundingBox(
296
+ { latitude: 33.5, longitude: -112.1 },
297
+ { latitude: 33.4, longitude: -112.0 }
298
+ )
299
+ // Property features
300
+ .bedrooms(3, 5)
301
+ .bathrooms(2, 4)
302
+ .stories(1, 2)
303
+ .area(80, 120) // percentage
304
+ .yearBuilt(1990, 2020)
305
+ .lotSize(90, 110) // percentage
306
+ // Flags
307
+ .skipTrace(true)
308
+ .aggregateLoanTypes(true)
309
+ .images(true)
310
+ .showRequests(true)
311
+ .areaPolygon(true)
312
+ .quicklistCounts(true)
313
+ .useSubdivision(true)
314
+ // Formatting
315
+ .dateFormat("YYYY-MM-DD")
316
+ // Sorting
317
+ .sort("totalAssessedValue", "desc", "session-12345")
318
+ .build()
319
+ )
320
+ .build();
321
+ ```
322
+
323
+ ### Property Subscription
324
+
325
+ ```typescript
326
+ import {
327
+ PropertySubscriptionBuilder,
328
+ DeliveryConfigBuilder,
329
+ } from "@land-catalyst/batch-data-sdk";
330
+
331
+ // Webhook subscription
332
+ const subscription = new PropertySubscriptionBuilder()
333
+ .searchCriteria((c) =>
334
+ c
335
+ .query("Maricopa County, AZ")
336
+ .address((a) =>
337
+ a
338
+ .city((city) => city.equals("Phoenix"))
339
+ .state((state) => state.equals("AZ"))
340
+ )
341
+ .quickList("vacant")
342
+ )
343
+ .deliveryConfig((dc) =>
344
+ dc.webhook("https://example.com/webhook", {
345
+ "X-API-Key": "secret",
346
+ })
347
+ )
348
+ .build();
349
+
350
+ const response = await client.createPropertySubscription(subscription);
351
+ ```
352
+
353
+ ### Property Subscription with Kinesis
354
+
355
+ ```typescript
356
+ const subscription = new PropertySubscriptionBuilder()
357
+ .searchCriteria((c) => c.query("US").quickList("owner-occupied"))
358
+ .deliveryConfig((dc) =>
359
+ dc.kinesis(
360
+ "my-stream",
361
+ "us-east-1",
362
+ "access-key-id",
363
+ "secret-access-key"
364
+ )
365
+ )
366
+ .build();
367
+ ```
368
+
369
+ ### Property Lookup by Address
370
+
371
+ ```typescript
372
+ import {
373
+ PropertyLookupRequestBuilder,
374
+ PropertyLookupRequestItemBuilder,
375
+ } from "@land-catalyst/batch-data-sdk";
376
+
377
+ // Single property lookup
378
+ const request = new PropertyLookupRequestBuilder()
379
+ .addItem(
380
+ new PropertyLookupRequestItemBuilder()
381
+ .address({
382
+ street: "2800 N 24th St",
383
+ city: "Phoenix",
384
+ state: "AZ",
385
+ zip: "85008",
386
+ })
387
+ .build()
388
+ )
389
+ .options((o) => o.take(1).skipTrace(true))
390
+ .build();
391
+
392
+ const response = await client.lookupProperty(request);
393
+ ```
394
+
395
+ ### Property Lookup by Multiple Identifiers
396
+
397
+ ```typescript
398
+ const request = new PropertyLookupRequestBuilder()
399
+ .addItem(
400
+ new PropertyLookupRequestItemBuilder()
401
+ .propertyId("12345")
402
+ .build()
403
+ )
404
+ .addItem(
405
+ new PropertyLookupRequestItemBuilder()
406
+ .apn("123-45-678")
407
+ .countyFipsCode("04013")
408
+ .build()
409
+ )
410
+ .addItem(
411
+ new PropertyLookupRequestItemBuilder()
412
+ .hash("abc123def456")
413
+ .build()
414
+ )
415
+ .options((o) =>
416
+ o
417
+ .take(10)
418
+ .images(true)
419
+ .skipTrace(true)
420
+ .showRequests(true)
421
+ )
422
+ .build();
423
+ ```
424
+
425
+ ### Async Property Lookup
426
+
427
+ ```typescript
428
+ import {
429
+ PropertyLookupAsyncRequestBuilder,
430
+ AsyncPropertyLookupOptionsBuilder,
431
+ } from "@land-catalyst/batch-data-sdk";
432
+
433
+ const request = new PropertyLookupAsyncRequestBuilder()
434
+ .addItem(
435
+ new PropertyLookupRequestItemBuilder()
436
+ .address({
437
+ street: "2800 N 24th St",
438
+ city: "Phoenix",
439
+ state: "AZ",
440
+ zip: "85008",
441
+ })
442
+ .build()
443
+ )
444
+ .options(
445
+ new AsyncPropertyLookupOptionsBuilder()
446
+ .webhook(
447
+ "https://example.com/webhook",
448
+ "https://example.com/error-webhook"
449
+ )
450
+ .build()
451
+ )
452
+ .build();
453
+
454
+ const response = await client.lookupPropertyAsync(request);
455
+ ```
456
+
457
+ ### Async Property Search
458
+
459
+ ```typescript
460
+ import { PropertySearchAsyncRequestBuilder } from "@land-catalyst/batch-data-sdk";
461
+
462
+ const request = new PropertySearchAsyncRequestBuilder()
463
+ .searchCriteria((c) =>
464
+ c
465
+ .query("Maricopa County, AZ")
466
+ .address((a) =>
467
+ a
468
+ .city((city) => city.equals("Phoenix"))
469
+ .state((state) => state.equals("AZ"))
470
+ )
471
+ )
472
+ .options(
473
+ new AsyncPropertyLookupOptionsBuilder()
474
+ .webhook("https://example.com/webhook")
475
+ .take(100)
476
+ .build()
477
+ )
478
+ .build();
479
+
480
+ const response = await client.searchPropertiesAsync(request);
481
+ ```
482
+
483
+ ### Address Verification
484
+
485
+ ```typescript
486
+ const request = {
487
+ requests: [
488
+ {
489
+ street: "2800 N 24th St",
490
+ city: "Phoenix",
491
+ state: "Arizona",
492
+ zip: "85008",
493
+ },
494
+ ],
495
+ };
496
+
497
+ const response = await client.verifyAddress(request);
498
+ ```
499
+
500
+ ### Address Autocomplete
501
+
502
+ ```typescript
503
+ const request = {
504
+ searchCriteria: {
505
+ query: "2800 N 24th St Phoenix",
506
+ },
507
+ options: {
508
+ skip: 0,
509
+ take: 5,
510
+ },
511
+ };
512
+
513
+ const response = await client.autocompleteAddress(request);
514
+ ```
515
+
516
+ ### Geocoding
517
+
518
+ ```typescript
519
+ // Geocode address to coordinates
520
+ const geocodeRequest = {
521
+ requests: [
522
+ {
523
+ address: "2800 N 24th St, Phoenix, AZ, 85008",
524
+ },
525
+ ],
526
+ };
527
+
528
+ const geocodeResponse = await client.geocodeAddress(geocodeRequest);
529
+
530
+ // Reverse geocode coordinates to address
531
+ const reverseGeocodeRequest = {
532
+ request: {
533
+ latitude: 33.47865,
534
+ longitude: -112.03029,
535
+ },
536
+ };
537
+
538
+ const reverseGeocodeResponse =
539
+ await client.reverseGeocodeAddress(reverseGeocodeRequest);
540
+ ```
541
+
542
+ ### Builder Accumulation Pattern
543
+
544
+ ```typescript
545
+ // Multiple calls to the same method accumulate changes
546
+ const criteria = new AddressSearchCriteriaBuilder()
547
+ .street((s) => s.equals("Main"))
548
+ .street((s) => s.contains("St"))
549
+ .street((s) => s.startsWith("M"))
550
+ .build();
551
+
552
+ // Result: { street: { equals: "Main", contains: "St", startsWith: "M" } }
553
+
554
+ // Direct values replace previous builder configuration
555
+ const criteria2 = new AddressSearchCriteriaBuilder()
556
+ .street((s) => s.equals("Main St"))
557
+ .street({ equals: "New Street" }) // Replaces previous
558
+ .build();
559
+
560
+ // Result: { street: { equals: "New Street" } }
561
+ ```
562
+
563
+ ### Using Direct Values vs Lambda Configurators
564
+
565
+ ```typescript
566
+ // Both patterns are supported - use whichever is more convenient
567
+
568
+ // Lambda pattern (recommended for complex configurations)
569
+ const request1 = new PropertySearchRequestBuilder()
570
+ .searchCriteria((c) =>
571
+ c
572
+ .query("AZ")
573
+ .address((a) =>
574
+ a
575
+ .city((city) => city.equals("Phoenix").contains("Phx"))
576
+ .state((state) => state.inList(["AZ", "CA"]))
577
+ )
578
+ )
579
+ .build();
580
+
581
+ // Direct value pattern (useful for simple cases)
582
+ const request2 = new PropertySearchRequestBuilder()
583
+ .searchCriteria({
584
+ query: "AZ",
585
+ address: {
586
+ city: { equals: "Phoenix" },
587
+ state: { equals: "AZ" },
588
+ },
589
+ })
590
+ .options({
591
+ take: 10,
592
+ skip: 0,
593
+ })
594
+ .build();
595
+ ```
596
+
597
+ ### Complex Nested Search
598
+
599
+ ```typescript
600
+ // Ultra-complex search with all criteria types
601
+ const request = new PropertySearchRequestBuilder()
602
+ .searchCriteria((c) =>
603
+ c
604
+ .query("Maricopa County, AZ")
605
+ // Address with multiple filters
606
+ .address((a) =>
607
+ a
608
+ .street((s) =>
609
+ s
610
+ .equals("Main")
611
+ .contains("Street")
612
+ .startsWith("M")
613
+ .endsWith("St")
614
+ .inList(["Main St", "Main Street", "Main Ave"])
615
+ )
616
+ .city((city) =>
617
+ city.equals("Phoenix").contains("Phx").matches(["Phoenix.*"])
618
+ )
619
+ .state((state) => state.equals("AZ"))
620
+ .zip((zip) => zip.startsWith("85").endsWith("01"))
621
+ )
622
+ // Assessment with accumulation
623
+ .assessment((a) =>
624
+ a
625
+ .assessmentYear((y) => y.min(2020).max(2023))
626
+ .assessmentYear((y) => y.min(2019)) // Accumulates
627
+ .totalAssessedValue((v) => v.min(100000).max(500000))
628
+ .totalAssessedValue((v) => v.min(150000)) // Accumulates
629
+ )
630
+ // Building criteria
631
+ .building((b) =>
632
+ b
633
+ .buildingType((t) =>
634
+ t.equals("Single Family").inList([
635
+ "Single Family",
636
+ "Townhouse",
637
+ "Condo",
638
+ ])
639
+ )
640
+ .yearBuilt((y) => y.min(1990).max(2020))
641
+ .bedroomCount((br) => br.min(3).max(5))
642
+ .bathroomCount((ba) => ba.min(2).max(4))
643
+ .pool((pool) => pool.equals("Yes"))
644
+ )
645
+ // Demographics
646
+ .demographics((d) =>
647
+ d
648
+ .age((a) => a.min(25).max(65))
649
+ .income((i) => i.min(50000).max(150000))
650
+ .income((i) => i.min(60000)) // Accumulates
651
+ .gender((g) => g.equals("M").inList(["M", "F"]))
652
+ .hasChildren(true)
653
+ )
654
+ // Quick lists
655
+ .quickList("vacant")
656
+ )
657
+ .options((o) =>
658
+ o
659
+ .pagination(100, 50)
660
+ .distance(5, 8800, 26400, 8, 8047)
661
+ .bedrooms(3, 5)
662
+ .bathrooms(2, 4)
663
+ .yearBuilt(1990, 2020)
664
+ .skipTrace(true)
665
+ .images(true)
666
+ .sort("totalAssessedValue", "desc", "session-12345")
667
+ .build()
668
+ )
669
+ .build();
670
+ ```
671
+
672
+ ### Error Handling
673
+
674
+ ```typescript
675
+ import { PropertyCountLimitExceededError } from "@land-catalyst/batch-data-sdk";
676
+
677
+ try {
678
+ const response = await client.searchProperties(request);
679
+ } catch (error) {
680
+ if (error instanceof PropertyCountLimitExceededError) {
681
+ console.log(`Limit exceeded: ${error.count} > ${error.limit}`);
682
+ console.log(`Limit type: ${error.limitType}`); // "sync" or "async"
683
+ } else if (error instanceof Error) {
684
+ console.error("API error:", error.message);
685
+ }
686
+ }
687
+ ```
688
+
689
+ ### Using Builders with Existing Data
690
+
691
+ ```typescript
692
+ // Create builder from existing criteria
693
+ const existingCriteria: SearchCriteria = {
694
+ query: "AZ",
695
+ address: {
696
+ city: { equals: "Phoenix" },
697
+ },
698
+ };
699
+
700
+ const builder = SearchCriteriaBuilder.from(existingCriteria);
701
+ builder.address((a) => a.state((s) => s.equals("AZ")));
702
+
703
+ const updatedCriteria = builder.build();
704
+ ```
705
+
706
+ ### GeoLocation Filters
707
+
708
+ ```typescript
709
+ import { GeoLocationFactory } from "@land-catalyst/batch-data-sdk";
710
+
711
+ // Distance-based search
712
+ const request = new PropertySearchRequestBuilder()
713
+ .searchCriteria((c) =>
714
+ c
715
+ .query("Phoenix, AZ")
716
+ .address((a) =>
717
+ a.geoLocationDistance(33.4484, -112.074, {
718
+ miles: "5",
719
+ })
720
+ )
721
+ )
722
+ .build();
723
+
724
+ // Bounding box search
725
+ const request2 = new PropertySearchRequestBuilder()
726
+ .searchCriteria((c) =>
727
+ c
728
+ .query("Phoenix, AZ")
729
+ .address((a) =>
730
+ a.geoLocationBoundingBox(
731
+ 33.5, // NW latitude
732
+ -112.1, // NW longitude
733
+ 33.4, // SE latitude
734
+ -112.0 // SE longitude
735
+ )
736
+ )
737
+ )
738
+ .build();
739
+
740
+ // Polygon search
741
+ const request3 = new PropertySearchRequestBuilder()
742
+ .searchCriteria((c) =>
743
+ c
744
+ .query("Phoenix, AZ")
745
+ .address((a) =>
746
+ a.geoLocationPolygon([
747
+ { latitude: 33.5, longitude: -112.1 },
748
+ { latitude: 33.4, longitude: -112.1 },
749
+ { latitude: 33.4, longitude: -112.0 },
750
+ { latitude: 33.5, longitude: -112.0 },
751
+ ])
752
+ )
753
+ )
754
+ .build();
755
+ ```
756
+
757
+ ### All Search Criteria Types
758
+
759
+ ```typescript
760
+ // Foreclosure search
761
+ .searchCriteria((c) =>
762
+ c
763
+ .query("AZ")
764
+ .foreclosure((f) =>
765
+ f
766
+ .status((s) => s.equals("Active"))
767
+ .recordingDate((d) => d.minDate("2020-01-01"))
768
+ )
769
+ )
770
+
771
+ // Listing search
772
+ .listing((l) =>
773
+ l
774
+ .price((p) => p.min(200000).max(500000))
775
+ .status((s) => s.equals("Active"))
776
+ .daysOnMarket((d) => d.min(0).max(90))
777
+ )
778
+
779
+ // Sale search
780
+ .sale((s) =>
781
+ s
782
+ .lastSalePrice((p) => p.min(100000).max(400000))
783
+ .lastSaleDate((d) => d.minDate("2020-01-01"))
784
+ )
785
+
786
+ // Owner search
787
+ .owner((o) =>
788
+ o
789
+ .firstName((f) => f.equals("John"))
790
+ .lastName((l) => l.equals("Doe"))
791
+ .ownerOccupied(true)
792
+ )
793
+
794
+ // Open lien search
795
+ .openLien((ol) =>
796
+ ol
797
+ .totalOpenLienCount((c) => c.min(1).max(5))
798
+ .totalOpenLienBalance((b) => b.min(50000).max(500000))
799
+ )
800
+
801
+ // Permit search
802
+ .permit((p) =>
803
+ p
804
+ .permitCount((c) => c.min(1).max(10))
805
+ .totalJobValue((v) => v.min(10000).max(100000))
806
+ )
807
+
808
+ // Tax search
809
+ .tax((t) => t.taxDelinquentYear((y) => y.min(2020).max(2023)))
810
+
811
+ // Valuation search
812
+ .valuation((v) =>
813
+ v
814
+ .estimatedValue((ev) => ev.min(200000).max(600000))
815
+ .ltv((ltv) => ltv.min(0).max(80))
816
+ )
817
+
818
+ // Intel search
819
+ .intel((i) =>
820
+ i
821
+ .lastSoldPrice((p) => p.min(150000).max(450000))
822
+ .lastSoldDate((d) => d.minDate("2020-01-01"))
823
+ .salePropensity((sp) => sp.min(50).max(100))
824
+ )
825
+ ```
826
+
827
+ ### Quick Lists
828
+
829
+ ```typescript
830
+ // Single quick list
831
+ .quickList("vacant")
832
+
833
+ // Multiple quick lists (AND)
834
+ .quickLists(["vacant", "owner-occupied", "high-equity"])
835
+
836
+ // Multiple quick lists (OR)
837
+ .orQuickLists(["vacant", "preforeclosure"])
838
+
839
+ // Exclude quick list
840
+ .quickList("not-vacant")
841
+ ```
842
+
843
+ ### Sorting and Pagination
844
+
845
+ ```typescript
846
+ .options((o) =>
847
+ o
848
+ // Pagination
849
+ .pagination(0, 50) // skip 0, take 50
850
+ .skip(100) // Override skip
851
+ .take(25) // Override take
852
+ // Sorting
853
+ .sort("totalAssessedValue", "desc")
854
+ .sort("yearBuilt", "asc", "session-12345") // With session ID
855
+ .build()
856
+ )
857
+ ```
858
+
859
+ ### Response Handling
860
+
861
+ ```typescript
862
+ const response = await client.searchProperties(request);
863
+
864
+ // Check status
865
+ if (response.status?.code === 200) {
866
+ console.log("Success!");
867
+ }
868
+
869
+ // Access properties
870
+ if (response.results?.properties) {
871
+ response.results.properties.forEach((property) => {
872
+ console.log(property.address?.street);
873
+ console.log(property.valuation?.estimatedValue);
874
+ console.log(property.demographics?.income);
875
+ console.log(property.building?.yearBuilt);
876
+ });
877
+ }
878
+
879
+ // Access metadata
880
+ if (response.results?.meta) {
881
+ console.log(
882
+ `Found ${response.results.meta.results?.resultsFound} properties`
883
+ );
884
+ console.log(
885
+ `Request took ${response.results.meta.performance?.totalRequestTime}ms`
886
+ );
887
+ }
888
+
889
+ // Quick list counts
890
+ if (response.results?.quicklistCounts) {
891
+ response.results.quicklistCounts.forEach((count) => {
892
+ console.log(`${count.name}: ${count.count}`);
893
+ });
894
+ }
895
+ ```
896
+
897
+ ## API Documentation
898
+
899
+ ### Types
900
+
901
+ - `SearchCriteria` - Complete search criteria structure
902
+ - `PropertySubscriptionRequest` - Subscription creation request
903
+ - `PropertySubscriptionResponse` - Subscription creation response
904
+ - `PropertySearchResponse` - Property search API response
905
+ - `Property` - Individual property object with all nested data
906
+ - `DeliveryConfig` - Webhook or Kinesis delivery configuration
907
+
908
+ ### Builders
909
+
910
+ All builders follow a fluent, chainable pattern:
911
+
912
+ - `SearchCriteriaBuilder` - Main search criteria builder
913
+ - `AddressSearchCriteriaBuilder` - Address filters
914
+ - `BuildingSearchCriteriaBuilder` - Building/property filters
915
+ - `StringFilterBuilder` - String comparison filters
916
+ - `NumericRangeFilterBuilder` - Numeric range filters
917
+ - `DateRangeFilterBuilder` - Date range filters
918
+ - And many more...
919
+
920
+ ### Errors
921
+
922
+ - `PropertyCountLimitExceededError` - Thrown when search results exceed configured limits
923
+
924
+ ## Development
925
+
926
+ ```bash
927
+ # Install dependencies
928
+ npm install
929
+
930
+ # Build the package
931
+ npm run build
932
+
933
+ # Run tests
934
+ npm test
935
+
936
+ # Run integration tests (requires BATCHDATA_API_KEY)
937
+ npm run test:integration
938
+
939
+ # The built files will be in ./dist
940
+ ```
941
+
942
+ ### Running Integration Tests
943
+
944
+ Integration tests make real API calls and require a valid API key:
945
+
946
+ ```bash
947
+ export BATCHDATA_API_KEY=your_api_key_here
948
+ npm run test:integration
949
+ ```
950
+
951
+ ## Publishing
952
+
953
+ This package is published to npm using trusted publishing (OIDC) via GitHub Actions. Publishing happens automatically when a version tag is pushed.
954
+
955
+ ### Manual Publishing (if needed)
956
+
957
+ If you need to publish manually, set up `.npmrc` to use your npm token:
958
+
959
+ 1. **Create `.npmrc` file** (add to `.gitignore` - already configured):
960
+ ```bash
961
+ # Option 1: Use environment variable (recommended)
962
+ echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > .npmrc
963
+
964
+ # Option 2: Use token directly (less secure)
965
+ echo "//registry.npmjs.org/:_authToken=your-token-here" > .npmrc
966
+ ```
967
+
968
+ 2. **Set your npm token as an environment variable**:
969
+ ```bash
970
+ export NPM_TOKEN=your-npm-token-here
971
+ ```
972
+
973
+ 3. **Publish**:
974
+ ```bash
975
+ npm run build
976
+ npm publish --access public
977
+ ```
978
+
979
+ **Note:** The `.npmrc` file is already in `.gitignore` so it won't be committed. See `.npmrc.example` for a template.
980
+
981
+ ### Setting Up GitHub Actions Publishing (Trusted Publishing)
982
+
983
+ The workflow uses npm trusted publishing (OIDC) - no tokens needed! To set it up:
984
+
985
+ 1. **Initial Manual Publish** (one-time, to create the package on npm):
986
+ ```bash
987
+ # Set up your .npmrc first (see Manual Publishing section above)
988
+ npm run build
989
+ npm publish --access public
990
+ ```
991
+
992
+ 2. **Enable Trusted Publishing on npm**:
993
+ - Go to [npmjs.com](https://www.npmjs.com) and log in
994
+ - Navigate to your package: `@land-catalyst/batch-data-sdk`
995
+ - Go to **Package Settings** → **Automation** → **Trusted Publishing**
996
+ - Click **Add GitHub Actions workflow**
997
+ - Select repository: `land-catalyst/batch-data-sdk`
998
+ - Select workflow file: `.github/workflows/cd.yml`
999
+ - Click **Approve**
1000
+
1001
+ 3. **Verify Setup**:
1002
+ - After enabling, the workflow will automatically publish to npm when you push a version tag
1003
+ - No authentication tokens needed - OIDC handles it automatically!
1004
+ - The package will show a verified checkmark on npm
1005
+
1006
+ For detailed setup instructions, see [SETUP_NPM_PUBLISHING.md](./SETUP_NPM_PUBLISHING.md).
1007
+
1008
+ ### Versioning
1009
+
1010
+ To create a new version:
1011
+
1012
+ ```bash
1013
+ # Patch version (1.1.12 -> 1.1.13)
1014
+ npm run v:patch
1015
+
1016
+ # Minor version (1.1.12 -> 1.2.0)
1017
+ npm run v:minor
1018
+
1019
+ # Major version (1.1.12 -> 2.0.0)
1020
+ npm run v:major
1021
+ ```
1022
+
1023
+ This will:
1024
+ 1. Update the version in `package.json`
1025
+ 2. Create a git tag
1026
+ 3. Push the tag to GitHub
1027
+ 4. Trigger the CD workflow to publish to npm
1028
+
1029
+ ## License
1030
+
1031
+ MIT
1032
+