reliable-node-utils 1.0.0

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 ADDED
@@ -0,0 +1,1013 @@
1
+ import { faker } from '@faker-js/faker';
2
+
3
+ // src/errors.ts
4
+ var UtilsError = class extends Error {
5
+ name = "UtilsError";
6
+ constructor(message, options) {
7
+ super(message, options);
8
+ Object.setPrototypeOf(this, new.target.prototype);
9
+ }
10
+ };
11
+ var AbortError = class extends UtilsError {
12
+ name = "AbortError";
13
+ };
14
+ var TimeoutError = class extends UtilsError {
15
+ name = "TimeoutError";
16
+ };
17
+ var RetryExhaustedError = class extends UtilsError {
18
+ constructor(message, attempt, lastError, options) {
19
+ super(message, { ...options, cause: lastError });
20
+ this.attempt = attempt;
21
+ this.lastError = lastError;
22
+ }
23
+ name = "RetryExhaustedError";
24
+ };
25
+
26
+ // src/async/retry.ts
27
+ var DEFAULT_OPTIONS = {
28
+ maxAttempts: 3,
29
+ baseDelayMs: 1e3,
30
+ maxDelayMs: 3e4,
31
+ jitter: true
32
+ };
33
+ function delay(ms, signal) {
34
+ if (signal?.aborted) return Promise.reject(new AbortError("Aborted"));
35
+ return new Promise((resolve, reject) => {
36
+ const onAbort = () => {
37
+ clearTimeout(id);
38
+ signal?.removeEventListener("abort", onAbort);
39
+ reject(new AbortError("Aborted"));
40
+ };
41
+ const id = setTimeout(() => {
42
+ signal?.removeEventListener("abort", onAbort);
43
+ resolve();
44
+ }, ms);
45
+ signal?.addEventListener("abort", onAbort, { once: true });
46
+ });
47
+ }
48
+ function assertValidRetryOptions(fn, options) {
49
+ if (typeof fn !== "function") {
50
+ throw new TypeError("retry: fn must be a function returning a Promise");
51
+ }
52
+ if (!Number.isInteger(options.maxAttempts) || options.maxAttempts < 1) {
53
+ throw new TypeError("retry: maxAttempts must be an integer >= 1");
54
+ }
55
+ if (!Number.isFinite(options.baseDelayMs) || options.baseDelayMs < 0) {
56
+ throw new TypeError("retry: baseDelayMs must be a finite number >= 0");
57
+ }
58
+ if (!Number.isFinite(options.maxDelayMs) || options.maxDelayMs < 0) {
59
+ throw new TypeError("retry: maxDelayMs must be a finite number >= 0");
60
+ }
61
+ if (options.maxDelayMs < options.baseDelayMs) {
62
+ throw new TypeError("retry: maxDelayMs must be >= baseDelayMs");
63
+ }
64
+ if (typeof options.jitter !== "boolean") {
65
+ throw new TypeError("retry: jitter must be a boolean");
66
+ }
67
+ if (options.onRetry !== void 0 && typeof options.onRetry !== "function") {
68
+ throw new TypeError("retry: onRetry must be a function");
69
+ }
70
+ }
71
+ function clamp(min, value, max) {
72
+ return Math.min(max, Math.max(min, value));
73
+ }
74
+ function computeDelay(attempt, baseDelayMs, maxDelayMs, jitter) {
75
+ const exponential = baseDelayMs * Math.pow(2, attempt - 1);
76
+ const capped = clamp(baseDelayMs, exponential, maxDelayMs);
77
+ if (!jitter) return capped;
78
+ return Math.random() * capped;
79
+ }
80
+ async function retry(fn, options = {}) {
81
+ const opts = { ...DEFAULT_OPTIONS, ...options };
82
+ assertValidRetryOptions(fn, opts);
83
+ const { maxAttempts, baseDelayMs, maxDelayMs, jitter, onRetry, signal } = opts;
84
+ let lastError;
85
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
86
+ if (signal?.aborted) {
87
+ throw new AbortError("Aborted");
88
+ }
89
+ try {
90
+ return await fn();
91
+ } catch (err) {
92
+ lastError = err;
93
+ if (attempt === maxAttempts) {
94
+ throw new RetryExhaustedError(
95
+ `Retry exhausted after ${maxAttempts} attempt(s)`,
96
+ attempt,
97
+ lastError
98
+ );
99
+ }
100
+ if (onRetry) {
101
+ await Promise.resolve(onRetry(attempt, err));
102
+ }
103
+ if (signal?.aborted) {
104
+ throw new AbortError("Aborted");
105
+ }
106
+ const waitMs = computeDelay(attempt, baseDelayMs, maxDelayMs, jitter);
107
+ await delay(waitMs, signal);
108
+ }
109
+ }
110
+ throw new RetryExhaustedError(
111
+ `Retry exhausted after ${maxAttempts} attempt(s)`,
112
+ maxAttempts,
113
+ lastError
114
+ );
115
+ }
116
+
117
+ // src/async/withTimeout.ts
118
+ async function withTimeout(promiseOrFn, ms, options = {}) {
119
+ const { signal } = options;
120
+ if (!Number.isFinite(ms) || ms < 0) {
121
+ throw new TypeError("withTimeout: ms must be a finite number >= 0");
122
+ }
123
+ if (signal?.aborted) {
124
+ throw new AbortError("Aborted");
125
+ }
126
+ const promise = typeof promiseOrFn === "function" ? promiseOrFn() : promiseOrFn;
127
+ let timeoutId;
128
+ const timeoutPromise = new Promise((_, reject2) => {
129
+ timeoutId = setTimeout(() => {
130
+ reject2(new TimeoutError(`Operation timed out after ${ms}ms`));
131
+ }, ms);
132
+ });
133
+ const abortHandler = () => {
134
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
135
+ reject(new AbortError("Aborted"));
136
+ };
137
+ let reject;
138
+ const abortPromise = new Promise((_, rej) => {
139
+ reject = rej;
140
+ });
141
+ signal?.addEventListener("abort", abortHandler, { once: true });
142
+ try {
143
+ const result = await Promise.race([promise, timeoutPromise, abortPromise]);
144
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
145
+ signal?.removeEventListener("abort", abortHandler);
146
+ return result;
147
+ } catch (err) {
148
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
149
+ signal?.removeEventListener("abort", abortHandler);
150
+ throw err;
151
+ }
152
+ }
153
+
154
+ // src/async/pLimit.ts
155
+ function pLimit(concurrency) {
156
+ if (typeof concurrency !== "number" || concurrency < 1 || !Number.isFinite(concurrency)) {
157
+ throw new TypeError("pLimit: concurrency must be a finite number >= 1");
158
+ }
159
+ let active = 0;
160
+ const queue = [];
161
+ const run = async (fn, ...args) => {
162
+ const wait = () => new Promise((resolve) => {
163
+ queue.push(resolve);
164
+ });
165
+ const next = () => {
166
+ active--;
167
+ const resolve = queue.shift();
168
+ if (resolve) resolve();
169
+ };
170
+ if (active >= concurrency) {
171
+ await wait();
172
+ }
173
+ active++;
174
+ try {
175
+ return await fn(...args);
176
+ } finally {
177
+ next();
178
+ }
179
+ };
180
+ return run;
181
+ }
182
+
183
+ // src/fn/memoize.ts
184
+ function memoize(fn, options = {}) {
185
+ const {
186
+ ttlMs,
187
+ maxSize,
188
+ keyFn = (...args) => JSON.stringify(args)
189
+ } = options;
190
+ const cache = /* @__PURE__ */ new Map();
191
+ const accessOrder = [];
192
+ function trimToMaxSize() {
193
+ if (maxSize === void 0 || accessOrder.length <= maxSize) return;
194
+ while (accessOrder.length > maxSize) {
195
+ const key = accessOrder.shift();
196
+ if (key !== void 0) cache.delete(key);
197
+ }
198
+ }
199
+ function getFresh(key) {
200
+ const entry = cache.get(key);
201
+ if (!entry) return { hit: false };
202
+ if (entry.expiresAt !== void 0 && Date.now() > entry.expiresAt) {
203
+ cache.delete(key);
204
+ const i = accessOrder.indexOf(key);
205
+ if (i !== -1) accessOrder.splice(i, 1);
206
+ return { hit: false };
207
+ }
208
+ if (maxSize !== void 0) {
209
+ const i = accessOrder.indexOf(key);
210
+ if (i !== -1) accessOrder.splice(i, 1);
211
+ accessOrder.push(key);
212
+ }
213
+ return { hit: true, value: entry.value };
214
+ }
215
+ const wrapped = function(...args) {
216
+ const key = keyFn(...args);
217
+ const cached = getFresh(key);
218
+ if (cached.hit) return cached.value;
219
+ const value = fn.apply(this, args);
220
+ const expiresAt = ttlMs !== void 0 ? Date.now() + ttlMs : void 0;
221
+ if (maxSize !== void 0) {
222
+ const existingIndex = accessOrder.indexOf(key);
223
+ if (existingIndex !== -1) accessOrder.splice(existingIndex, 1);
224
+ }
225
+ cache.set(key, { value, expiresAt });
226
+ if (maxSize !== void 0) {
227
+ accessOrder.push(key);
228
+ trimToMaxSize();
229
+ }
230
+ return value;
231
+ };
232
+ return wrapped;
233
+ }
234
+
235
+ // src/guards/index.ts
236
+ function isDefined(value) {
237
+ return value !== null && value !== void 0;
238
+ }
239
+ function isNonEmptyString(value) {
240
+ return typeof value === "string" && value.length > 0;
241
+ }
242
+ function isPlainObject(value) {
243
+ if (value === null || typeof value !== "object") return false;
244
+ const proto = Object.getPrototypeOf(value);
245
+ return proto === null || proto === Object.prototype;
246
+ }
247
+
248
+ // src/object/deepMerge.ts
249
+ var BLOCKED_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
250
+ function deepMerge(a, b) {
251
+ const result = { ...a };
252
+ for (const key of Object.keys(b)) {
253
+ const keyStr = String(key);
254
+ if (BLOCKED_KEYS.has(keyStr)) continue;
255
+ const bVal = b[key];
256
+ if (bVal === void 0) continue;
257
+ const aVal = result[key];
258
+ if (isPlainObject(aVal) && isPlainObject(bVal) && !Array.isArray(aVal) && !Array.isArray(bVal)) {
259
+ result[keyStr] = deepMerge(
260
+ aVal,
261
+ bVal
262
+ );
263
+ } else {
264
+ result[keyStr] = bVal;
265
+ }
266
+ }
267
+ return result;
268
+ }
269
+
270
+ // src/string/index.ts
271
+ function replaceTemplateValues(template, replacements) {
272
+ let result = template;
273
+ for (const [key, value] of Object.entries(replacements)) {
274
+ result = result.split(`{${key}}`).join(`"${String(value)}"`);
275
+ }
276
+ return result;
277
+ }
278
+ function splitString(input, delimiter) {
279
+ return input.split(delimiter);
280
+ }
281
+ function parseBooleanString(input) {
282
+ if (input === void 0 || input === null || input.trim() === "") {
283
+ throw new Error("No input provided.");
284
+ }
285
+ const normalized = input.trim().toLowerCase();
286
+ if (normalized === "true") return true;
287
+ if (normalized === "false") return false;
288
+ throw new Error(`Cannot convert string "${input}" to boolean.`);
289
+ }
290
+ function isNullOrEmptyString(value) {
291
+ return value === null || value === void 0 || value.trim() === "";
292
+ }
293
+ function snakeToPascalCase(input) {
294
+ return input.toLowerCase().replace(/_([a-z])/g, (_match, char) => char.toUpperCase()).replace(/^\w/, (char) => char.toUpperCase());
295
+ }
296
+ var convertToCamelCase = snakeToPascalCase;
297
+ var formatStringValue = replaceTemplateValues;
298
+
299
+ // src/random/index.ts
300
+ var ALPHANUMERIC = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
301
+ var ALPHABETIC = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
302
+ function assertPositiveLength(length, fnName) {
303
+ if (!Number.isInteger(length) || length <= 0) {
304
+ throw new Error(`${fnName}: length must be a positive integer.`);
305
+ }
306
+ }
307
+ function randomIndex(length, rng) {
308
+ return Math.floor(rng() * length);
309
+ }
310
+ function generateRandomAlphanumericString(length, options = {}) {
311
+ assertPositiveLength(length, "generateRandomAlphanumericString");
312
+ const rng = options.rng ?? Math.random;
313
+ let out = "";
314
+ for (let i = 0; i < length; i++) {
315
+ out += ALPHANUMERIC.charAt(randomIndex(ALPHANUMERIC.length, rng));
316
+ }
317
+ return options.uppercase ?? true ? out.toUpperCase() : out;
318
+ }
319
+ function generateRandomNDigitNumber(length, options = {}) {
320
+ assertPositiveLength(length, "generateRandomNDigitNumber");
321
+ if (length > 15) {
322
+ throw new Error(
323
+ "generateRandomNDigitNumber: length must be <= 15 for safe integers."
324
+ );
325
+ }
326
+ const rng = options.rng ?? Math.random;
327
+ const min = Math.pow(10, length - 1);
328
+ const max = Math.pow(10, length) - 1;
329
+ return Math.floor(rng() * (max - min + 1)) + min;
330
+ }
331
+ function generateRandomAlphabeticString(length, options = {}) {
332
+ assertPositiveLength(length, "generateRandomAlphabeticString");
333
+ const rng = options.rng ?? Math.random;
334
+ let out = "";
335
+ for (let i = 0; i < length; i++) {
336
+ out += ALPHABETIC.charAt(randomIndex(ALPHABETIC.length, rng));
337
+ }
338
+ return out;
339
+ }
340
+ var getAlphaNumericString = generateRandomAlphanumericString;
341
+ var generateRandomNumber = generateRandomNDigitNumber;
342
+ var generateRandomString = generateRandomAlphabeticString;
343
+
344
+ // src/json/index.ts
345
+ function updateAllKeysValues(target, updates) {
346
+ for (const [key, value] of Object.entries(target)) {
347
+ if (updates[key] !== void 0) {
348
+ target[key] = updates[key];
349
+ continue;
350
+ }
351
+ if (isPlainObject(value)) {
352
+ updateAllKeysValues(value, updates);
353
+ continue;
354
+ }
355
+ if (Array.isArray(value)) {
356
+ for (const item of value) {
357
+ if (isPlainObject(item)) {
358
+ updateAllKeysValues(item, updates);
359
+ }
360
+ }
361
+ }
362
+ }
363
+ }
364
+ function updateJsonValues(jsonText, updates) {
365
+ const parsed = JSON.parse(jsonText);
366
+ if (!isPlainObject(parsed) && !Array.isArray(parsed)) {
367
+ throw new Error("updateJsonValues: root must be a JSON object or array.");
368
+ }
369
+ if (isPlainObject(parsed)) {
370
+ updateAllKeysValues(parsed, updates);
371
+ } else {
372
+ for (const item of parsed) {
373
+ if (isPlainObject(item)) {
374
+ updateAllKeysValues(item, updates);
375
+ }
376
+ }
377
+ }
378
+ return JSON.stringify(parsed, null, 2);
379
+ }
380
+ var updateJsonData = updateJsonValues;
381
+
382
+ // src/date/index.ts
383
+ function pad2(value) {
384
+ return String(value).padStart(2, "0");
385
+ }
386
+ function formatDate(date, pattern) {
387
+ return pattern.replaceAll("yyyy", String(date.getFullYear())).replaceAll("MM", pad2(date.getMonth() + 1)).replaceAll("dd", pad2(date.getDate())).replaceAll("HH", pad2(date.getHours())).replaceAll("mm", pad2(date.getMinutes())).replaceAll("ss", pad2(date.getSeconds()));
388
+ }
389
+ function getRelativeDate(token, format = "yyyy-MM-dd", now = /* @__PURE__ */ new Date()) {
390
+ const outputFormat = isNullOrEmptyString(format) ? "yyyy-MM-dd" : format;
391
+ const date = new Date(now.getTime());
392
+ switch (token) {
393
+ case "Year":
394
+ date.setFullYear(date.getFullYear() - 1);
395
+ break;
396
+ case "Yesterday":
397
+ date.setDate(date.getDate() - 1);
398
+ break;
399
+ }
400
+ return formatDate(date, outputFormat);
401
+ }
402
+ var getDate = getRelativeDate;
403
+
404
+ // src/collections/index.ts
405
+ function renameMapKey(data, oldKey, newKey) {
406
+ if (!data.has(oldKey)) return false;
407
+ let value = data.get(oldKey) ?? "";
408
+ value = value.replace(/^"(.*)"$/, "$1");
409
+ data.delete(oldKey);
410
+ data.set(newKey, value);
411
+ return true;
412
+ }
413
+ var updateKey = renameMapKey;
414
+
415
+ // src/identity/uniqueStore.ts
416
+ var InMemoryUniqueValueStore = class {
417
+ values = /* @__PURE__ */ new Set();
418
+ has(value) {
419
+ return this.values.has(value);
420
+ }
421
+ add(value) {
422
+ this.values.add(value);
423
+ }
424
+ };
425
+ var scopedStores = /* @__PURE__ */ new Map();
426
+ function getScopedInMemoryStore(scope) {
427
+ const existing = scopedStores.get(scope);
428
+ if (existing) return existing;
429
+ const created = new InMemoryUniqueValueStore();
430
+ scopedStores.set(scope, created);
431
+ return created;
432
+ }
433
+ function clearScopedInMemoryStores() {
434
+ scopedStores.clear();
435
+ }
436
+
437
+ // src/identity/namesData.ts
438
+ var COMMON_FIRST_NAMES = [
439
+ "James",
440
+ "Mary",
441
+ "John",
442
+ "Patricia",
443
+ "Robert",
444
+ "Jennifer",
445
+ "Michael",
446
+ "Linda",
447
+ "William",
448
+ "Elizabeth",
449
+ "David",
450
+ "Barbara",
451
+ "Richard",
452
+ "Susan",
453
+ "Joseph",
454
+ "Jessica",
455
+ "Thomas",
456
+ "Sarah",
457
+ "Charles",
458
+ "Karen",
459
+ "Christopher",
460
+ "Nancy",
461
+ "Daniel",
462
+ "Lisa",
463
+ "Matthew",
464
+ "Betty",
465
+ "Anthony",
466
+ "Margaret",
467
+ "Mark",
468
+ "Sandra",
469
+ "Donald",
470
+ "Ashley",
471
+ "Steven",
472
+ "Kimberly",
473
+ "Andrew",
474
+ "Emily",
475
+ "Paul",
476
+ "Donna",
477
+ "Joshua",
478
+ "Michelle",
479
+ "Kenneth",
480
+ "Dorothy",
481
+ "Kevin",
482
+ "Carol",
483
+ "Brian",
484
+ "Amanda",
485
+ "George",
486
+ "Melissa",
487
+ "Timothy",
488
+ "Deborah"
489
+ ];
490
+ var COMMON_LAST_NAMES = [
491
+ "Smith",
492
+ "Johnson",
493
+ "Williams",
494
+ "Brown",
495
+ "Jones",
496
+ "Garcia",
497
+ "Miller",
498
+ "Davis",
499
+ "Rodriguez",
500
+ "Martinez",
501
+ "Hernandez",
502
+ "Lopez",
503
+ "Gonzalez",
504
+ "Wilson",
505
+ "Anderson",
506
+ "Thomas",
507
+ "Taylor",
508
+ "Moore",
509
+ "Jackson",
510
+ "Martin",
511
+ "Lee",
512
+ "Perez",
513
+ "Thompson",
514
+ "White",
515
+ "Harris",
516
+ "Sanchez",
517
+ "Clark",
518
+ "Ramirez",
519
+ "Lewis",
520
+ "Robinson",
521
+ "Walker",
522
+ "Young",
523
+ "Allen",
524
+ "King",
525
+ "Wright",
526
+ "Scott",
527
+ "Torres",
528
+ "Nguyen",
529
+ "Hill",
530
+ "Flores",
531
+ "Green",
532
+ "Adams",
533
+ "Nelson",
534
+ "Baker",
535
+ "Hall",
536
+ "Rivera",
537
+ "Campbell",
538
+ "Mitchell",
539
+ "Carter",
540
+ "Roberts"
541
+ ];
542
+
543
+ // src/identity/providers.ts
544
+ function pick(items, rng) {
545
+ const idx = Math.floor(rng() * items.length);
546
+ return items[Math.max(0, Math.min(items.length - 1, idx))];
547
+ }
548
+ var StaticDatasetNameProvider = class {
549
+ firstName(options = {}) {
550
+ const rng = options.rng ?? Math.random;
551
+ return pick(COMMON_FIRST_NAMES, rng);
552
+ }
553
+ lastName(options = {}) {
554
+ const rng = options.rng ?? Math.random;
555
+ return pick(COMMON_LAST_NAMES, rng);
556
+ }
557
+ };
558
+ var FakerNameProvider = class {
559
+ faker;
560
+ constructor(options = {}) {
561
+ this.faker = options.faker ?? faker;
562
+ }
563
+ firstName() {
564
+ return this.faker.person.firstName();
565
+ }
566
+ lastName() {
567
+ return this.faker.person.lastName();
568
+ }
569
+ };
570
+ var DEFAULT_NAME_PROVIDER = new StaticDatasetNameProvider();
571
+
572
+ // src/identity/nameGenerator.ts
573
+ function defaultNormalize(value) {
574
+ return value.trim().toLowerCase();
575
+ }
576
+ async function generateUnique(producer, options) {
577
+ const maxAttempts = options.maxAttempts ?? 1e3;
578
+ if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
579
+ throw new TypeError("unique name generation: maxAttempts must be an integer >= 1");
580
+ }
581
+ const normalize = options.normalize ?? defaultNormalize;
582
+ const store = options.store ?? getScopedInMemoryStore(options.scope ?? "default");
583
+ for (let i = 0; i < maxAttempts; i++) {
584
+ const candidate = producer();
585
+ const key = normalize(candidate);
586
+ if (!await store.has(key)) {
587
+ await store.add(key);
588
+ return candidate;
589
+ }
590
+ }
591
+ throw new Error(
592
+ `Unable to generate a unique value after ${maxAttempts} attempts. Use a larger name pool, custom normalize, or custom store.`
593
+ );
594
+ }
595
+ function generateFirstName(options = {}) {
596
+ const provider = options.provider ?? DEFAULT_NAME_PROVIDER;
597
+ return provider.firstName({ rng: options.rng });
598
+ }
599
+ function generateLastName(options = {}) {
600
+ const provider = options.provider ?? DEFAULT_NAME_PROVIDER;
601
+ return provider.lastName({ rng: options.rng });
602
+ }
603
+ function generateFullName(options = {}) {
604
+ return `${generateFirstName(options)} ${generateLastName(options)}`;
605
+ }
606
+ function generateUniqueFirstName(options = {}) {
607
+ return generateUnique(() => generateFirstName(options), options);
608
+ }
609
+ function generateUniqueLastName(options = {}) {
610
+ return generateUnique(() => generateLastName(options), options);
611
+ }
612
+ function generateUniqueFullName(options = {}) {
613
+ return generateUnique(() => generateFullName(options), options);
614
+ }
615
+
616
+ // src/address/errors.ts
617
+ var AddressProviderError = class extends UtilsError {
618
+ name = "AddressProviderError";
619
+ };
620
+ var AddressVerificationError = class extends UtilsError {
621
+ name = "AddressVerificationError";
622
+ };
623
+ var ProviderNotFoundError = class extends UtilsError {
624
+ name = "ProviderNotFoundError";
625
+ };
626
+
627
+ // src/address/service.ts
628
+ function pickProvider(providers, country, requireUspsForUS) {
629
+ if (country === "US" && requireUspsForUS) {
630
+ const usps = providers.find(
631
+ (provider2) => provider2.supportsCountry("US") && provider2.name === "USPS"
632
+ );
633
+ if (!usps) {
634
+ throw new ProviderNotFoundError(
635
+ "US lookup requires USPS provider, but none was configured"
636
+ );
637
+ }
638
+ return usps;
639
+ }
640
+ const provider = providers.find((p) => p.supportsCountry(country));
641
+ if (!provider) {
642
+ throw new ProviderNotFoundError(
643
+ `No address provider configured for country ${country}`
644
+ );
645
+ }
646
+ return provider;
647
+ }
648
+ async function getValidAddressByPostalCode(options) {
649
+ const provider = pickProvider(
650
+ options.providers,
651
+ options.country,
652
+ options.requireUspsForUS ?? true
653
+ );
654
+ return await provider.lookupPostalCode({
655
+ country: options.country,
656
+ postalCode: options.postalCode,
657
+ signal: options.signal
658
+ });
659
+ }
660
+ async function verifyAddress(options) {
661
+ const provider = pickProvider(
662
+ options.providers,
663
+ options.address.country,
664
+ options.requireUspsForUS ?? true
665
+ );
666
+ return await provider.verifyAddress(options.address);
667
+ }
668
+
669
+ // src/address/providers/providerUtils.ts
670
+ function assertProviderSupportsCountry(args) {
671
+ if (!args.supportsCountry(args.country)) {
672
+ throw new AddressProviderError(
673
+ `${args.providerName} provider does not support country ${args.country}`
674
+ );
675
+ }
676
+ }
677
+ function normalizeComparable(value) {
678
+ return value.trim().toUpperCase().replace(/\s+/g, " ");
679
+ }
680
+ function normalizePostalComparable(value) {
681
+ return value.replace(/\s+/g, "").toUpperCase();
682
+ }
683
+ async function fetchJsonOrThrow(url, options) {
684
+ const res = await fetch(url, { method: "GET", signal: options.signal });
685
+ if (!res.ok) {
686
+ throw new AddressProviderError(
687
+ `${options.errorPrefix} failed with status ${res.status}`
688
+ );
689
+ }
690
+ return await res.json();
691
+ }
692
+ async function fetchTextOrThrow(url, options) {
693
+ const res = await fetch(url, { method: "GET", signal: options.signal });
694
+ if (!res.ok) {
695
+ throw new AddressProviderError(
696
+ `${options.errorPrefix} failed with status ${res.status}`
697
+ );
698
+ }
699
+ return await res.text();
700
+ }
701
+
702
+ // src/address/providers/uspsProvider.ts
703
+ function escapeXml(value) {
704
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
705
+ }
706
+ function getTagValue(xml, tag) {
707
+ const match = new RegExp(`<${tag}>([^<]*)</${tag}>`, "i").exec(xml);
708
+ return match?.[1]?.trim();
709
+ }
710
+ function normalizeUsZip(value) {
711
+ const digits = value.replace(/\D/g, "").slice(0, 5);
712
+ if (digits.length !== 5) {
713
+ throw new TypeError("USPS provider: postalCode must contain at least 5 digits");
714
+ }
715
+ return digits;
716
+ }
717
+ var UspsAddressProvider = class {
718
+ name = "USPS";
719
+ endpoint;
720
+ userId;
721
+ constructor(options) {
722
+ if (!isNonEmptyString(options.userId)) {
723
+ throw new TypeError("USPS provider: userId is required");
724
+ }
725
+ this.userId = options.userId;
726
+ this.endpoint = options.endpoint ?? "https://secure.shippingapis.com/ShippingAPI.dll";
727
+ }
728
+ supportsCountry(country) {
729
+ return country === "US";
730
+ }
731
+ async lookupPostalCode(request) {
732
+ assertProviderSupportsCountry({
733
+ providerName: this.name,
734
+ country: request.country,
735
+ supportsCountry: this.supportsCountry.bind(this)
736
+ });
737
+ const zip5 = normalizeUsZip(request.postalCode);
738
+ const xml = `<CityStateLookupRequest USERID="${escapeXml(this.userId)}"><ZipCode ID="0"><Zip5>${zip5}</Zip5></ZipCode></CityStateLookupRequest>`;
739
+ const responseXml = await this.callApi("CityStateLookup", xml, request.signal);
740
+ const errorDescription = getTagValue(responseXml, "Description");
741
+ if (errorDescription) {
742
+ throw new AddressProviderError(`USPS CityStateLookup failed: ${errorDescription}`);
743
+ }
744
+ const city = getTagValue(responseXml, "City");
745
+ const state = getTagValue(responseXml, "State");
746
+ const canonicalZip = getTagValue(responseXml, "Zip5") ?? zip5;
747
+ if (!city || !state) {
748
+ throw new AddressProviderError("USPS CityStateLookup returned incomplete response");
749
+ }
750
+ return {
751
+ country: "US",
752
+ postalCode: canonicalZip,
753
+ city,
754
+ stateProvince: state,
755
+ source: this.name,
756
+ uspsVerified: true
757
+ };
758
+ }
759
+ async verifyAddress(request) {
760
+ assertProviderSupportsCountry({
761
+ providerName: this.name,
762
+ country: request.country,
763
+ supportsCountry: this.supportsCountry.bind(this)
764
+ });
765
+ if (!isNonEmptyString(request.addressLine1)) {
766
+ throw new TypeError("USPS provider: addressLine1 is required for verification");
767
+ }
768
+ const zip5 = normalizeUsZip(request.postalCode);
769
+ const xml = `<AddressValidateRequest USERID="${escapeXml(this.userId)}"><Address ID="0"><FirmName></FirmName><Address1>${escapeXml(request.addressLine2 ?? "")}</Address1><Address2>${escapeXml(request.addressLine1)}</Address2><City>${escapeXml(request.city ?? "")}</City><State>${escapeXml(request.stateProvince ?? "")}</State><Zip5>${zip5}</Zip5><Zip4></Zip4></Address></AddressValidateRequest>`;
770
+ const responseXml = await this.callApi("Verify", xml, request.signal);
771
+ const errorDescription = getTagValue(responseXml, "Description");
772
+ if (errorDescription) {
773
+ throw new AddressVerificationError(`USPS Verify failed: ${errorDescription}`);
774
+ }
775
+ const line1 = getTagValue(responseXml, "Address2");
776
+ const line2 = getTagValue(responseXml, "Address1");
777
+ const city = getTagValue(responseXml, "City");
778
+ const state = getTagValue(responseXml, "State");
779
+ const canonicalZip = getTagValue(responseXml, "Zip5");
780
+ const zip4 = getTagValue(responseXml, "Zip4");
781
+ if (!line1 || !city || !state || !canonicalZip) {
782
+ throw new AddressVerificationError("USPS Verify returned incomplete response");
783
+ }
784
+ return {
785
+ country: "US",
786
+ postalCode: zip4 ? `${canonicalZip}-${zip4}` : canonicalZip,
787
+ city,
788
+ stateProvince: state,
789
+ addressLine1: line1,
790
+ addressLine2: line2 || void 0,
791
+ source: this.name,
792
+ uspsVerified: true
793
+ };
794
+ }
795
+ async callApi(api, xml, signal) {
796
+ const url = `${this.endpoint}?API=${encodeURIComponent(api)}&XML=${encodeURIComponent(xml)}`;
797
+ return await fetchTextOrThrow(url, {
798
+ signal,
799
+ errorPrefix: "USPS request"
800
+ });
801
+ }
802
+ };
803
+
804
+ // src/address/providers/zippopotamProvider.ts
805
+ function canonicalCountryCode(value) {
806
+ return value.toLowerCase();
807
+ }
808
+ function normalizePostalCode(value) {
809
+ const cleaned = value.trim().toUpperCase();
810
+ if (!cleaned) throw new TypeError("postalCode is required");
811
+ return cleaned;
812
+ }
813
+ var ZippopotamAddressProvider = class {
814
+ name = "Zippopotam";
815
+ endpoint;
816
+ constructor(options = {}) {
817
+ this.endpoint = options.endpoint ?? "https://api.zippopotam.us";
818
+ }
819
+ supportsCountry(country) {
820
+ return country === "US" || country === "CA";
821
+ }
822
+ async lookupPostalCode(request) {
823
+ const postalCode = normalizePostalCode(request.postalCode);
824
+ assertProviderSupportsCountry({
825
+ providerName: this.name,
826
+ country: request.country,
827
+ supportsCountry: this.supportsCountry.bind(this)
828
+ });
829
+ const country = canonicalCountryCode(request.country);
830
+ const url = `${this.endpoint}/${country}/${encodeURIComponent(postalCode)}`;
831
+ const payload = await fetchJsonOrThrow(url, {
832
+ signal: request.signal,
833
+ errorPrefix: `Zippopotam lookup for ${request.country} ${postalCode}`
834
+ });
835
+ const first = payload.places?.[0];
836
+ if (!first) {
837
+ throw new AddressProviderError("Zippopotam lookup returned no places");
838
+ }
839
+ return {
840
+ country: request.country,
841
+ postalCode: payload["post code"],
842
+ city: first["place name"],
843
+ stateProvince: first["state abbreviation"] || first.state,
844
+ source: this.name,
845
+ uspsVerified: false
846
+ };
847
+ }
848
+ async verifyAddress(request) {
849
+ const lookedUp = await this.lookupPostalCode(request);
850
+ if (request.city && normalizeComparable(request.city) !== normalizeComparable(lookedUp.city)) {
851
+ throw new AddressVerificationError(
852
+ `City mismatch for postal code ${request.postalCode}: expected ${lookedUp.city}`
853
+ );
854
+ }
855
+ if (request.stateProvince && normalizeComparable(request.stateProvince) !== normalizeComparable(lookedUp.stateProvince)) {
856
+ throw new AddressVerificationError(
857
+ `State/Province mismatch for postal code ${request.postalCode}: expected ${lookedUp.stateProvince}`
858
+ );
859
+ }
860
+ return {
861
+ ...lookedUp,
862
+ addressLine1: request.addressLine1,
863
+ addressLine2: request.addressLine2
864
+ };
865
+ }
866
+ };
867
+
868
+ // src/address/providers/canadaPostProvider.ts
869
+ function normalizeCanadianPostalCode(value) {
870
+ const cleaned = value.replace(/\s+/g, "").toUpperCase();
871
+ if (!/^[A-Z]\d[A-Z]\d[A-Z]\d$/.test(cleaned)) {
872
+ throw new TypeError(
873
+ "CanadaPost provider: postalCode must be a valid Canadian postal code (A1A1A1)"
874
+ );
875
+ }
876
+ return `${cleaned.slice(0, 3)} ${cleaned.slice(3)}`;
877
+ }
878
+ var CanadaPostAddressProvider = class {
879
+ name = "CanadaPost";
880
+ key;
881
+ findEndpoint;
882
+ retrieveEndpoint;
883
+ constructor(options) {
884
+ if (!isNonEmptyString(options.key)) {
885
+ throw new TypeError("CanadaPost provider: key is required");
886
+ }
887
+ this.key = options.key;
888
+ this.findEndpoint = options.findEndpoint ?? "https://ws1.postescanada-canadapost.ca/AddressComplete/Interactive/Find/v2.10/json3.ws";
889
+ this.retrieveEndpoint = options.retrieveEndpoint ?? "https://ws1.postescanada-canadapost.ca/AddressComplete/Interactive/Retrieve/v2.11/json3.ws";
890
+ }
891
+ supportsCountry(country) {
892
+ return country === "CA";
893
+ }
894
+ async lookupPostalCode(request) {
895
+ assertProviderSupportsCountry({
896
+ providerName: this.name,
897
+ country: request.country,
898
+ supportsCountry: this.supportsCountry.bind(this)
899
+ });
900
+ const postalCode = normalizeCanadianPostalCode(request.postalCode);
901
+ const retrieved = await this.findAndRetrieve({
902
+ searchTerm: postalCode,
903
+ signal: request.signal
904
+ });
905
+ if (!retrieved.city || !retrieved.provinceCode || !retrieved.postalCode) {
906
+ throw new AddressProviderError("CanadaPost lookup returned incomplete response");
907
+ }
908
+ return {
909
+ country: "CA",
910
+ postalCode: normalizeCanadianPostalCode(retrieved.postalCode),
911
+ city: retrieved.city,
912
+ stateProvince: retrieved.provinceCode,
913
+ source: this.name,
914
+ uspsVerified: false
915
+ };
916
+ }
917
+ async verifyAddress(request) {
918
+ assertProviderSupportsCountry({
919
+ providerName: this.name,
920
+ country: request.country,
921
+ supportsCountry: this.supportsCountry.bind(this)
922
+ });
923
+ if (!isNonEmptyString(request.addressLine1)) {
924
+ throw new TypeError(
925
+ "CanadaPost provider: addressLine1 is required for verification"
926
+ );
927
+ }
928
+ const searchPostal = normalizeCanadianPostalCode(request.postalCode);
929
+ const searchTerm = [request.addressLine1, request.city, searchPostal].filter(isNonEmptyString).join(" ");
930
+ const retrieved = await this.findAndRetrieve({
931
+ searchTerm,
932
+ signal: request.signal
933
+ });
934
+ if (!retrieved.line1 || !retrieved.city || !retrieved.provinceCode || !retrieved.postalCode) {
935
+ throw new AddressVerificationError(
936
+ "CanadaPost verify returned incomplete response"
937
+ );
938
+ }
939
+ if (normalizePostalComparable(retrieved.postalCode) !== normalizePostalComparable(searchPostal)) {
940
+ throw new AddressVerificationError(
941
+ `Postal code mismatch: expected ${searchPostal}, got ${retrieved.postalCode}`
942
+ );
943
+ }
944
+ if (request.city && normalizeComparable(request.city) !== normalizeComparable(retrieved.city)) {
945
+ throw new AddressVerificationError(
946
+ `City mismatch: expected ${request.city}, got ${retrieved.city}`
947
+ );
948
+ }
949
+ if (request.stateProvince && normalizeComparable(request.stateProvince) !== normalizeComparable(retrieved.provinceCode)) {
950
+ throw new AddressVerificationError(
951
+ `Province mismatch: expected ${request.stateProvince}, got ${retrieved.provinceCode}`
952
+ );
953
+ }
954
+ return {
955
+ country: "CA",
956
+ postalCode: normalizeCanadianPostalCode(retrieved.postalCode),
957
+ city: retrieved.city,
958
+ stateProvince: retrieved.provinceCode,
959
+ addressLine1: retrieved.line1,
960
+ addressLine2: retrieved.line2,
961
+ source: this.name,
962
+ uspsVerified: false
963
+ };
964
+ }
965
+ async findAndRetrieve(args) {
966
+ const findPayload = await this.callFind(args.searchTerm, args.signal);
967
+ const firstId = findPayload.Items?.find((item) => isNonEmptyString(item.Id))?.Id;
968
+ if (!firstId) {
969
+ throw new AddressProviderError("CanadaPost find returned no candidate addresses");
970
+ }
971
+ const retrieved = await this.callRetrieve(firstId, args.signal);
972
+ const first = retrieved.Items?.[0];
973
+ if (!first) {
974
+ throw new AddressProviderError("CanadaPost retrieve returned no addresses");
975
+ }
976
+ return {
977
+ line1: first.Line1,
978
+ line2: first.Line2,
979
+ city: first.City,
980
+ provinceCode: first.ProvinceCode,
981
+ postalCode: first.PostalCode
982
+ };
983
+ }
984
+ async callFind(searchTerm, signal) {
985
+ const params = new URLSearchParams({
986
+ Key: this.key,
987
+ SearchTerm: searchTerm,
988
+ Country: "CAN",
989
+ Limit: "5"
990
+ });
991
+ const url = `${this.findEndpoint}?${params.toString()}`;
992
+ return await fetchJsonOrThrow(url, {
993
+ signal,
994
+ errorPrefix: "CanadaPost find"
995
+ });
996
+ }
997
+ async callRetrieve(id, signal) {
998
+ const params = new URLSearchParams({
999
+ Key: this.key,
1000
+ Id: id,
1001
+ Country: "CAN"
1002
+ });
1003
+ const url = `${this.retrieveEndpoint}?${params.toString()}`;
1004
+ return await fetchJsonOrThrow(url, {
1005
+ signal,
1006
+ errorPrefix: "CanadaPost retrieve"
1007
+ });
1008
+ }
1009
+ };
1010
+
1011
+ export { AbortError, AddressProviderError, AddressVerificationError, CanadaPostAddressProvider, DEFAULT_NAME_PROVIDER, FakerNameProvider, InMemoryUniqueValueStore, ProviderNotFoundError, RetryExhaustedError, StaticDatasetNameProvider, TimeoutError, UspsAddressProvider, ZippopotamAddressProvider, clearScopedInMemoryStores, convertToCamelCase, deepMerge, formatStringValue, generateFirstName, generateFullName, generateLastName, generateRandomAlphabeticString, generateRandomAlphanumericString, generateRandomNDigitNumber, generateRandomNumber, generateRandomString, generateUniqueFirstName, generateUniqueFullName, generateUniqueLastName, getAlphaNumericString, getDate, getRelativeDate, getScopedInMemoryStore, getValidAddressByPostalCode, isDefined, isNonEmptyString, isNullOrEmptyString, isPlainObject, memoize, pLimit, parseBooleanString, renameMapKey, replaceTemplateValues, retry, snakeToPascalCase, splitString, updateJsonData, updateJsonValues, updateKey, verifyAddress, withTimeout };
1012
+ //# sourceMappingURL=index.js.map
1013
+ //# sourceMappingURL=index.js.map