@verifnow/sdk 1.2.0 → 1.4.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/README.md CHANGED
@@ -108,6 +108,8 @@ interface ValidationResult {
108
108
  emailDetails?: EmailDetails; // email only
109
109
  vatDetails?: VatDetails; // VAT only
110
110
  phoneDetails?: PhoneDetails; // phone only — country, lineType, formats
111
+ ibanDetails?: IbanDetails; // IBAN only — structure and checksum, separately
112
+ nasDetails?: NasDetails; // Canadian SIN only — temporary resident, series
111
113
  quota?: QuotaInfo; // from the X-RateLimit-* headers
112
114
  raw: Record<string, unknown>; // untouched response body
113
115
  }
package/dist/index.cjs CHANGED
@@ -76,7 +76,7 @@ var VerifNowResponseError = class extends VerifNowError {
76
76
  };
77
77
 
78
78
  // src/version.ts
79
- var VERSION = "1.2.0";
79
+ var VERSION = "1.4.0";
80
80
 
81
81
  // src/client.ts
82
82
  var DEFAULT_BASE_URL = "https://api.verifnow.io";
@@ -125,7 +125,12 @@ var VerifNow = class {
125
125
  validatePhone(value, options) {
126
126
  return this.validate("phone", value, options);
127
127
  }
128
- /** Validate an IBAN: country structure and check digits. */
128
+ /**
129
+ * Validate an IBAN against the SWIFT registry entry for its country, then its check digits.
130
+ *
131
+ * `ibanDetails` reports the two separately: check digits catch a typo, the registry catches an
132
+ * account number that could never exist in that country.
133
+ */
129
134
  validateIban(value, options) {
130
135
  return this.validate("iban", value, options);
131
136
  }
@@ -133,7 +138,12 @@ var VerifNow = class {
133
138
  validateVat(value, options) {
134
139
  return this.validate("vat", value, options);
135
140
  }
136
- /** Validate a Canadian Social Insurance Number. */
141
+ /**
142
+ * Validate a Canadian Social Insurance Number: format and Luhn check digit.
143
+ *
144
+ * `nasDetails` flags a temporary resident's number (it expires with their permit) and numbers
145
+ * from series not issued to individuals. Only collect a SIN where the law requires it.
146
+ */
137
147
  validateNas(value, options) {
138
148
  return this.validate("nas", value, options);
139
149
  }
@@ -397,6 +407,28 @@ function mapPhoneDetails(raw) {
397
407
  nationalFormat: asString(d.national_format)
398
408
  };
399
409
  }
410
+ function mapIbanDetails(raw) {
411
+ if (raw === null || typeof raw !== "object") return void 0;
412
+ const d = raw;
413
+ return {
414
+ countryCode: asString(d.country_code),
415
+ structureValid: asBoolean(d.structure_valid),
416
+ checksumValid: asBoolean(d.checksum_valid),
417
+ length: asNumber(d.length),
418
+ expectedLength: asNumber(d.expected_length),
419
+ formatted: asString(d.formatted)
420
+ };
421
+ }
422
+ function mapNasDetails(raw) {
423
+ if (raw === null || typeof raw !== "object") return void 0;
424
+ const d = raw;
425
+ return {
426
+ checksumValid: asBoolean(d.checksum_valid),
427
+ temporaryResident: asBoolean(d.temporary_resident),
428
+ individualSeries: asBoolean(d.individual_series),
429
+ formatted: asString(d.formatted)
430
+ };
431
+ }
400
432
  function mapResult(payload, quota) {
401
433
  return {
402
434
  valid: payload.valid === true,
@@ -407,6 +439,8 @@ function mapResult(payload, quota) {
407
439
  emailDetails: mapEmailDetails(payload.emailDetails),
408
440
  vatDetails: mapVatDetails(payload.vatDetails),
409
441
  phoneDetails: mapPhoneDetails(payload.phoneDetails),
442
+ ibanDetails: mapIbanDetails(payload.ibanDetails),
443
+ nasDetails: mapNasDetails(payload.nasDetails),
410
444
  quota,
411
445
  raw: payload
412
446
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/version.ts","../src/client.ts","../src/types.ts"],"sourcesContent":["export { VerifNow, type RequestOptions } from './client.js';\n\nexport {\n VerifNowError,\n VerifNowAuthError,\n VerifNowRequestError,\n VerifNowRateLimitError,\n VerifNowServerError,\n VerifNowConnectionError,\n VerifNowResponseError,\n} from './errors.js';\n\nexport {\n VALIDATION_RULES,\n type Deliverability,\n type EmailDetails,\n type EmailSignals,\n type PhoneDetails,\n type PhoneLineType,\n type QuotaInfo,\n type RetryOptions,\n type RiskLevel,\n type ValidationLevel,\n type ValidationResult,\n type ValidationRule,\n type VatDetails,\n type VatSource,\n type VerifNowOptions,\n} from './types.js';\n\nexport { VERSION } from './version.js';\n","import type { QuotaInfo } from './types.js';\n\n/**\n * Base class for every error this SDK throws.\n *\n * The SDK fails loudly on purpose. A validation client that swallows a network failure and\n * reports `valid: true` turns an outage into silently accepted bad data, and the outage stays\n * invisible until someone audits the database. Catch these and decide explicitly — accepting the\n * input on failure is a reasonable choice, but it should be a choice.\n *\n * @example\n * ```ts\n * try {\n * const result = await client.validateEmail(input);\n * return result.valid;\n * } catch (error) {\n * if (error instanceof VerifNowRateLimitError) throw error; // back-pressure, do not swallow\n * logger.warn({ error }, 'VerifNow unavailable, accepting input unverified');\n * return true;\n * }\n * ```\n */\nexport class VerifNowError extends Error {\n /** HTTP status, when the failure came back from the API rather than the network. */\n readonly status?: number;\n /** Correlation id from the `X-Request-Id` response header, useful in support requests. */\n readonly requestId?: string;\n\n constructor(\n message: string,\n options: { status?: number; requestId?: string; cause?: unknown } = {},\n ) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.status = options.status;\n this.requestId = options.requestId;\n Error.captureStackTrace?.(this, new.target);\n }\n}\n\n/** The API key is missing, malformed, revoked, or not authorised for this endpoint (401/403). */\nexport class VerifNowAuthError extends VerifNowError {}\n\n/**\n * The request was rejected as malformed (400).\n *\n * Retrying is pointless — the payload itself needs to change.\n */\nexport class VerifNowRequestError extends VerifNowError {}\n\n/** The monthly quota or the concurrency limit was exceeded (429). */\nexport class VerifNowRateLimitError extends VerifNowError {\n /** Quota counters from the response headers, when present. */\n readonly quota?: QuotaInfo;\n /** Seconds to wait before retrying, derived from `Retry-After` or `X-RateLimit-Reset`. */\n readonly retryAfterSeconds?: number;\n\n constructor(\n message: string,\n options: {\n status?: number;\n requestId?: string;\n cause?: unknown;\n quota?: QuotaInfo;\n retryAfterSeconds?: number;\n } = {},\n ) {\n super(message, options);\n this.quota = options.quota;\n this.retryAfterSeconds = options.retryAfterSeconds;\n }\n}\n\n/** The API failed to process the request (5xx). Retried automatically before surfacing. */\nexport class VerifNowServerError extends VerifNowError {}\n\n/**\n * The API could not be reached at all: DNS failure, refused connection, TLS error, or the\n * request exceeded `timeoutMs`.\n *\n * A wrong `baseUrl` surfaces here, which is why it names the URL it tried.\n */\nexport class VerifNowConnectionError extends VerifNowError {\n /** True when the failure was the client-side timeout rather than a transport error. */\n readonly timedOut: boolean;\n\n constructor(\n message: string,\n options: { cause?: unknown; timedOut?: boolean } = {},\n ) {\n super(message, { cause: options.cause });\n this.timedOut = options.timedOut ?? false;\n }\n}\n\n/** The API returned a success status with a body this SDK could not parse. */\nexport class VerifNowResponseError extends VerifNowError {}\n","/**\n * SDK version, sent to the API as `X-VerifNow-SDK: node/<version>` so calls made through an\n * official SDK can be told apart from hand-rolled integrations.\n *\n * Kept in sync with `package.json` by a test — bump both together.\n */\nexport const VERSION = '1.2.0';\n","import {\n VerifNowAuthError,\n VerifNowConnectionError,\n VerifNowError,\n VerifNowRateLimitError,\n VerifNowRequestError,\n VerifNowResponseError,\n VerifNowServerError,\n} from './errors.js';\nimport type {\n EmailDetails,\n EmailSignals,\n PhoneDetails,\n QuotaInfo,\n RetryOptions,\n ValidationResult,\n ValidationRule,\n VatDetails,\n VerifNowOptions,\n} from './types.js';\nimport { VERSION } from './version.js';\n\nconst DEFAULT_BASE_URL = 'https://api.verifnow.io';\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst DEFAULT_RETRY: Required<RetryOptions> = {\n attempts: 2,\n backoffMs: 200,\n maxBackoffMs: 2_000,\n};\n\n/** Per-call overrides. */\nexport interface RequestOptions {\n /** Override the client timeout for this call. */\n timeoutMs?: number;\n /** Cancel the call from your own controller. Combined with the timeout. */\n signal?: AbortSignal;\n}\n\n/**\n * Client for the VerifNow validation API.\n *\n * @example\n * ```ts\n * import { VerifNow } from '@verifnow/sdk';\n *\n * const client = new VerifNow({ apiKey: process.env.VERIFNOW_API_KEY! });\n * const result = await client.validateEmail('user@example.com');\n *\n * if (!result.valid) console.log(result.message);\n * if (result.emailDetails?.signals?.typoDetected) {\n * console.log('Did you mean', result.emailDetails.signals.suggestedDomain);\n * }\n * ```\n */\nexport class VerifNow {\n readonly #apiKey: string;\n readonly #baseUrl: string;\n readonly #timeoutMs: number;\n readonly #retry: Required<RetryOptions> | null;\n readonly #headers: Record<string, string>;\n readonly #fetch: typeof globalThis.fetch;\n\n constructor(options: VerifNowOptions) {\n if (!options?.apiKey || options.apiKey.trim() === '') {\n throw new VerifNowError(\n 'A VerifNow API key is required. Create one in the dashboard and pass it as `apiKey`.',\n );\n }\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new VerifNowError(\n 'No global fetch available. Use Node 18 or later, or pass a `fetch` implementation.',\n );\n }\n\n this.#apiKey = options.apiKey.trim();\n // Trailing slashes would produce `//api/v1/...`, which some proxies reject.\n this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.#retry =\n options.retry === false ? null : { ...DEFAULT_RETRY, ...(options.retry ?? {}) };\n this.#headers = options.headers ?? {};\n this.#fetch = fetchImpl.bind(globalThis);\n }\n\n /** Validate an email address: syntax, DNS/MX, typo, disposable, role-based, quality score. */\n validateEmail(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('email', value, options);\n }\n\n /**\n * Validate a phone number against its country's numbering plan.\n *\n * The number must include its country code (`+33…` or `0033…`). Valid numbers come back in\n * E.164 as `normalizedValue`, with country and line type in `phoneDetails`.\n */\n validatePhone(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('phone', value, options);\n }\n\n /** Validate an IBAN: country structure and check digits. */\n validateIban(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('iban', value, options);\n }\n\n /** Validate a VAT number. */\n validateVat(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('vat', value, options);\n }\n\n /** Validate a Canadian Social Insurance Number. */\n validateNas(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nas', value, options);\n }\n\n /** Validate a US Social Security Number. */\n validateSsn(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('ssn', value, options);\n }\n\n /** Validate a Spanish/Portuguese NIF. */\n validateNif(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nif', value, options);\n }\n\n /**\n * Validate a value against any rule.\n *\n * The typed helpers above call this. Use it directly when the rule is chosen at runtime.\n */\n async validate(\n rule: ValidationRule,\n value: string,\n options: RequestOptions = {},\n ): Promise<ValidationResult> {\n if (typeof value !== 'string' || value.trim() === '') {\n // Caught here rather than server-side: an empty value consumes quota and can only fail.\n throw new VerifNowRequestError(\n `Cannot validate an empty value for rule \"${rule}\".`,\n );\n }\n\n const url = `${this.#baseUrl}/api/v1/validate/${rule}`;\n const body = JSON.stringify({ value });\n const maxAttempts = this.#retry ? this.#retry.attempts + 1 : 1;\n\n let lastError: VerifNowError | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await this.#requestOnce(url, body, options);\n } catch (error) {\n if (!(error instanceof VerifNowError)) throw error;\n lastError = error;\n\n const isLastAttempt = attempt === maxAttempts - 1;\n if (isLastAttempt || !this.#retry) throw error;\n\n const delay = this.#retryDelay(error, attempt);\n if (delay === null) throw error;\n\n await sleep(delay);\n }\n }\n\n /* c8 ignore next -- the loop either returns or throws */\n throw lastError ?? new VerifNowError('Request failed');\n }\n\n /**\n * How long to wait before retrying, or `null` when the error should surface immediately.\n *\n * A 429 is retried only when the reset is close: the concurrency limit clears in\n * milliseconds, but a spent monthly quota does not, and sleeping on it helps nobody.\n */\n #retryDelay(error: VerifNowError, attempt: number): number | null {\n const retry = this.#retry!;\n const backoff = Math.min(retry.backoffMs * 2 ** attempt, retry.maxBackoffMs);\n\n if (error instanceof VerifNowRateLimitError) {\n const waitMs = (error.retryAfterSeconds ?? 0) * 1000;\n if (waitMs > retry.maxBackoffMs) return null;\n return Math.max(waitMs, backoff);\n }\n\n if (error instanceof VerifNowServerError) return backoff;\n // A timeout is retried: the deadline is ours, and the next attempt gets a fresh one.\n if (error instanceof VerifNowConnectionError) return backoff;\n\n // 400, 401 and unparseable bodies will fail identically on a second attempt.\n return null;\n }\n\n async #requestOnce(\n url: string,\n body: string,\n options: RequestOptions,\n ): Promise<ValidationResult> {\n const timeoutMs = options.timeoutMs ?? this.#timeoutMs;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const abortFromCaller = () => controller.abort();\n options.signal?.addEventListener('abort', abortFromCaller, { once: true });\n\n let response: Response;\n try {\n response = await this.#fetch(url, {\n method: 'POST',\n headers: {\n ...this.#headers,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'X-API-KEY': this.#apiKey,\n 'X-VerifNow-SDK': `node/${VERSION}`,\n },\n body,\n signal: controller.signal,\n });\n } catch (cause) {\n // The caller's own cancellation is theirs to handle, not a transport failure.\n if (options.signal?.aborted) throw cause;\n\n const timedOut = controller.signal.aborted;\n throw new VerifNowConnectionError(\n timedOut\n ? `VerifNow request to ${url} timed out after ${timeoutMs}ms.`\n : `Could not reach the VerifNow API at ${url}. Check \\`baseUrl\\` and network access.`,\n { cause, timedOut },\n );\n } finally {\n clearTimeout(timer);\n options.signal?.removeEventListener('abort', abortFromCaller);\n }\n\n return this.#handleResponse(response);\n }\n\n async #handleResponse(response: Response): Promise<ValidationResult> {\n const requestId = response.headers.get('X-Request-Id') ?? undefined;\n const quota = parseQuota(response.headers);\n\n if (response.ok) {\n let payload: unknown;\n try {\n payload = await response.json();\n } catch (cause) {\n throw new VerifNowResponseError(\n 'VerifNow returned a success status with a body that is not valid JSON.',\n { status: response.status, requestId, cause },\n );\n }\n\n if (payload === null || typeof payload !== 'object') {\n throw new VerifNowResponseError(\n 'VerifNow returned an unexpected response shape.',\n { status: response.status, requestId },\n );\n }\n\n return mapResult(payload as Record<string, unknown>, quota);\n }\n\n const message = await readErrorMessage(response);\n const context = { status: response.status, requestId };\n\n if (response.status === 401 || response.status === 403) {\n throw new VerifNowAuthError(\n `VerifNow rejected the API key (${response.status}): ${message}`,\n context,\n );\n }\n\n if (response.status === 429) {\n throw new VerifNowRateLimitError(`VerifNow rate limit reached: ${message}`, {\n ...context,\n quota,\n retryAfterSeconds: parseRetryAfter(response.headers, quota),\n });\n }\n\n if (response.status >= 500) {\n throw new VerifNowServerError(\n `VerifNow returned ${response.status}: ${message}`,\n context,\n );\n }\n\n throw new VerifNowRequestError(\n `VerifNow rejected the request (${response.status}): ${message}`,\n context,\n );\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction toNumber(value: string | null): number | undefined {\n if (value === null) return undefined;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction parseQuota(headers: Headers): QuotaInfo | undefined {\n const limit = toNumber(headers.get('X-RateLimit-Limit'));\n const remaining = toNumber(headers.get('X-RateLimit-Remaining'));\n const resetSeconds = toNumber(headers.get('X-RateLimit-Reset'));\n const overage = headers.get('X-Quota-Overage') === 'true';\n\n if (\n limit === undefined &&\n remaining === undefined &&\n resetSeconds === undefined &&\n !overage\n ) {\n return undefined;\n }\n\n return {\n limit,\n remaining,\n resetAt: resetSeconds === undefined ? undefined : new Date(resetSeconds * 1000),\n overage,\n };\n}\n\nfunction parseRetryAfter(headers: Headers, quota?: QuotaInfo): number | undefined {\n const retryAfter = headers.get('Retry-After');\n if (retryAfter !== null) {\n const seconds = Number(retryAfter);\n if (Number.isFinite(seconds)) return seconds;\n\n // RFC 7231 also allows an HTTP-date.\n const asDate = Date.parse(retryAfter);\n if (!Number.isNaN(asDate)) {\n return Math.max(0, Math.ceil((asDate - Date.now()) / 1000));\n }\n }\n\n if (quota?.resetAt) {\n return Math.max(0, Math.ceil((quota.resetAt.getTime() - Date.now()) / 1000));\n }\n\n return undefined;\n}\n\nasync function readErrorMessage(response: Response): Promise<string> {\n try {\n const text = await response.text();\n if (!text) return response.statusText || 'no details';\n\n try {\n const parsed = JSON.parse(text) as Record<string, unknown>;\n const message = parsed.message ?? parsed.error;\n if (typeof message === 'string' && message !== '') return message;\n } catch {\n // Not JSON — a proxy or the servlet container's default error page.\n }\n\n return text.slice(0, 500);\n } catch {\n return response.statusText || 'no details';\n }\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction asBoolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction mapSignals(raw: unknown): EmailSignals | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const s = raw as Record<string, unknown>;\n\n return {\n syntaxValid: asBoolean(s.syntax_valid),\n mxValid: asBoolean(s.mx_valid),\n typoDetected: asBoolean(s.typo_detected),\n suggestedDomain: asString(s.suggested_domain),\n disposable: asBoolean(s.disposable),\n roleBased: asBoolean(s.role_based),\n freeProvider: asBoolean(s.free_provider),\n domainAgeDays: asNumber(s.domain_age_days),\n mxProvider: asString(s.mx_provider),\n mxQualityScore: asNumber(s.mx_quality_score),\n };\n}\n\nfunction mapEmailDetails(raw: unknown): EmailDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n signals: mapSignals(d.signals),\n riskScore: asNumber(d.risk_score),\n riskLevel: asString(d.risk_level) as EmailDetails['riskLevel'],\n deliverability: asString(d.deliverability) as EmailDetails['deliverability'],\n appliedLevel: asString(d.applied_level) as EmailDetails['appliedLevel'],\n };\n}\n\n/**\n * Reads `registered`, preserving the difference between `false` and `null`.\n *\n * `asBoolean` cannot be used here: it maps `null` to `undefined`, which would erase the one\n * distinction the whole VAT design exists to carry. `false` means the registry answered and the\n * number is not there; `null` means nobody could ask. A caller that cannot tell them apart will\n * reject real businesses whenever VIES is down.\n *\n * A missing key is read as `null` for the same reason — unknown, not absent.\n */\nfunction asRegistered(value: unknown): boolean | null {\n return typeof value === 'boolean' ? value : null;\n}\n\nfunction asDate(value: unknown): Date | undefined {\n if (typeof value !== 'string') return undefined;\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? undefined : parsed;\n}\n\nfunction mapVatDetails(raw: unknown): VatDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n formatValid: asBoolean(d.format_valid),\n registered: asRegistered(d.registered),\n countryCode: asString(d.country_code),\n source: asString(d.source) as VatDetails['source'],\n checkedAt: asDate(d.checked_at),\n traderName: asString(d.trader_name),\n traderAddress: asString(d.trader_address),\n viesAvailable: asBoolean(d.vies_available),\n consultationNumber: asString(d.consultation_number),\n };\n}\n\nfunction mapPhoneDetails(raw: unknown): PhoneDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n countryCode: asString(d.country_code),\n callingCode: asNumber(d.calling_code),\n lineType: asString(d.line_type) as PhoneDetails['lineType'],\n internationalFormat: asString(d.international_format),\n nationalFormat: asString(d.national_format),\n };\n}\n\n/**\n * Maps the API's snake_case diagnostics onto camelCase, so a TypeScript caller is not switching\n * naming conventions mid-expression. The untouched body stays available on `raw`.\n */\nfunction mapResult(\n payload: Record<string, unknown>,\n quota: QuotaInfo | undefined,\n): ValidationResult {\n return {\n valid: payload.valid === true,\n message: asString(payload.message),\n normalizedValue: asString(payload.normalizedValue) ?? null,\n originalValue: asString(payload.originalValue),\n validationLevel: asString(payload.validationLevel) as ValidationResult['validationLevel'],\n emailDetails: mapEmailDetails(payload.emailDetails),\n vatDetails: mapVatDetails(payload.vatDetails),\n phoneDetails: mapPhoneDetails(payload.phoneDetails),\n quota,\n raw: payload,\n };\n}\n","/**\n * Validation rules exposed by the VerifNow API.\n *\n * Each maps to `POST /api/v1/validate/{rule}`.\n */\nexport type ValidationRule =\n | 'email'\n | 'phone'\n | 'iban'\n | 'vat'\n | 'nas'\n | 'ssn'\n | 'nif';\n\nexport const VALIDATION_RULES: readonly ValidationRule[] = [\n 'email',\n 'phone',\n 'iban',\n 'vat',\n 'nas',\n 'ssn',\n 'nif',\n] as const;\n\n/**\n * Depth of checks applied to a request, decided by the plan attached to the API key.\n *\n * `STANDARD` runs on the FREE and STARTER plans, `ADVANCED` on GROWTH, `PREMIUM` on PRO.\n * Branch on this rather than on the plan name: it is the only value that tells you which\n * signals are actually present in the response.\n */\nexport type ValidationLevel = 'BASIC' | 'STANDARD' | 'ADVANCED' | 'PREMIUM';\n\n/** Categorical risk assessment. Returned from `ADVANCED` depth upward. */\nexport type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH';\n\nexport type Deliverability =\n | 'DELIVERABLE'\n | 'RISKY'\n | 'UNDELIVERABLE'\n | 'UNKNOWN';\n\n/**\n * Per-signal breakdown behind an email verdict.\n *\n * Fields are `undefined` when the applied level does not compute them — the last four require\n * `ADVANCED` depth or higher. Check `ValidationResult.appliedLevel` before relying on one.\n */\nexport interface EmailSignals {\n /** The address matches the syntax pattern for the applied level. */\n syntaxValid?: boolean;\n /** The domain resolves and publishes MX (or fallback A) records. */\n mxValid?: boolean;\n /** A likely typo was found in the domain, e.g. `gmail.con`. */\n typoDetected?: boolean;\n /** The correction proposed when `typoDetected` is true. */\n suggestedDomain?: string;\n /** The domain belongs to a throwaway mailbox provider. */\n disposable?: boolean;\n /** The local part is a shared mailbox: `info@`, `admin@`, `noreply@`. */\n roleBased?: boolean;\n /** The domain is a consumer mailbox provider. Requires ADVANCED. */\n freeProvider?: boolean;\n /** Estimated age of the domain in days. Requires ADVANCED. */\n domainAgeDays?: number;\n /** Identified mail provider, e.g. `google`. Requires ADVANCED. */\n mxProvider?: string;\n /** Mail server quality between 0 and 1. Requires ADVANCED. */\n mxQualityScore?: number;\n}\n\n/** Email-specific diagnostics. Absent when the applied level is `BASIC`. */\nexport interface EmailDetails {\n signals?: EmailSignals;\n /** Aggregated risk on a 0–100 scale, where 0 is the lowest risk. */\n riskScore?: number;\n /** Categorical risk. Requires ADVANCED depth or higher. */\n riskLevel?: RiskLevel;\n deliverability?: Deliverability;\n /** The depth actually applied, echoed back by the API. */\n appliedLevel?: ValidationLevel;\n}\n\n/**\n * Where a VAT registration verdict came from.\n *\n * VIES publishes no SLA and drops member states several times a month, so a VAT answer is not\n * always a live one. Branch on this rather than on `ValidationResult.valid` whenever the\n * difference matters for your own compliance.\n */\nexport type VatSource =\n /** Confirmed against VIES during this request. */\n | 'LIVE'\n /** Served from a VIES answer less than 24 hours old. */\n | 'CACHE'\n /** VIES was unreachable, so an older cached answer was used. */\n | 'STALE'\n /** VIES was unreachable and nothing was cached. Registration is unknown. */\n | 'UNVERIFIED'\n /** The country is outside VIES, so no registry lookup is possible. */\n | 'NOT_APPLICABLE';\n\n/** VAT-specific diagnostics. Present on `vat` validations. */\nexport interface VatDetails {\n /** The number matches its member state's structure. Decided locally, never depends on VIES. */\n formatValid?: boolean;\n /**\n * Present in the member state's registry.\n *\n * **`null` means unknown, never \"not registered.\"** It is returned when VIES could not be\n * consulted. Treating `null` as `false` rejects legitimate customers during someone else's\n * outage — the single most expensive mistake available in VAT validation.\n */\n registered: boolean | null;\n /** Member state the number belongs to, e.g. `IE`. Greece is `EL`, Northern Ireland `XI`. */\n countryCode?: string;\n source?: VatSource;\n /** When the registration was last confirmed against VIES. */\n checkedAt?: Date;\n /** Registered trading name, when the member state discloses it. Germany does not. */\n traderName?: string;\n /** Registered address, when the member state discloses it. */\n traderAddress?: string;\n /** Whether VIES could answer for this country during the request. */\n viesAvailable?: boolean;\n /**\n * The consultation number VIES issued for this lookup — the receipt a tax authority accepts as\n * evidence that you checked. Present only when your account has its own VAT number configured,\n * because VIES issues one only to an identified requester.\n */\n consultationNumber?: string;\n}\n\n/**\n * Kind of line, according to the country's numbering plan.\n *\n * A `PREMIUM_RATE` or `VOIP` number is still `valid` — it exists. This is how you decide to\n * exclude one, rather than the API deciding for you.\n */\nexport type PhoneLineType =\n | 'MOBILE'\n | 'FIXED_LINE'\n /** The plan does not distinguish the two — the case for the US and Canada. */\n | 'FIXED_LINE_OR_MOBILE'\n | 'TOLL_FREE'\n | 'PREMIUM_RATE'\n | 'SHARED_COST'\n | 'VOIP'\n | 'PERSONAL_NUMBER'\n | 'PAGER'\n | 'UAN'\n | 'VOICEMAIL'\n | 'UNKNOWN';\n\n/**\n * Phone-specific diagnostics. Present whenever the input parsed as an international number —\n * including when it is invalid for its country, so you can tell the user which country it was\n * read as.\n */\nexport interface PhoneDetails {\n /** ISO 3166-1 alpha-2 country, e.g. `FR`. Absent when the calling code is shared by several. */\n countryCode?: string;\n /** International calling code without the plus sign, e.g. `33`. */\n callingCode?: number;\n /** Absent when the number is invalid. */\n lineType?: PhoneLineType;\n /** e.g. `+33 6 12 34 56 78`. Absent when the number is invalid. */\n internationalFormat?: string;\n /** e.g. `06 12 34 56 78`. Absent when the number is invalid. */\n nationalFormat?: string;\n}\n\n/** Outcome of a single validation call. */\nexport interface ValidationResult {\n /** Whether the value passed every check the applied level ran. */\n valid: boolean;\n /** Human-readable explanation of the verdict. */\n message?: string;\n /** Canonical form of the input — `null` when the value is invalid. */\n normalizedValue: string | null;\n /** The value exactly as submitted. */\n originalValue?: string;\n /** Depth applied to this request. */\n validationLevel?: ValidationLevel;\n /** Present for email validations from `STANDARD` depth upward. */\n emailDetails?: EmailDetails;\n /** Present for VAT validations. */\n vatDetails?: VatDetails;\n /** Present for phone validations. The E.164 form is `normalizedValue`. */\n phoneDetails?: PhoneDetails;\n /** Quota state reported by the response headers. */\n quota?: QuotaInfo;\n /** The unmodified JSON body, for fields this SDK version does not model yet. */\n raw: Record<string, unknown>;\n}\n\n/** Quota counters read from the `X-RateLimit-*` response headers. */\nexport interface QuotaInfo {\n /** Validations included in the current billing period. */\n limit?: number;\n /** Validations left before overage or blocking. */\n remaining?: number;\n /** When the current period resets. */\n resetAt?: Date;\n /** True once you are past the included quota and into per-unit billing. */\n overage?: boolean;\n}\n\nexport interface RetryOptions {\n /**\n * Retry attempts after the first failure. Defaults to 2, so up to 3 requests in total.\n * Only connection failures, 429 and 5xx are retried — never a 400 or 401, which will not\n * succeed on a second try.\n */\n attempts?: number;\n /** Delay before the first retry, in ms. Doubles each attempt. Defaults to 200. */\n backoffMs?: number;\n /** Upper bound on a single backoff delay, in ms. Defaults to 2000. */\n maxBackoffMs?: number;\n}\n\nexport interface VerifNowOptions {\n /** API key created in the VerifNow dashboard. Sent as the `X-API-KEY` header. */\n apiKey: string;\n /** Override the API origin. Defaults to `https://api.verifnow.io`. */\n baseUrl?: string;\n /** Abort a single request after this many ms. Defaults to 5000. */\n timeoutMs?: number;\n /** Retry policy, or `false` to disable retries entirely. */\n retry?: RetryOptions | false;\n /** Extra headers merged into every request. */\n headers?: Record<string, string>;\n /**\n * Replacement for the global `fetch`, for tests or a custom agent.\n * Defaults to `globalThis.fetch`.\n */\n fetch?: typeof globalThis.fetch;\n}\n"],"mappings":"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","names":["asDate"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/version.ts","../src/client.ts","../src/types.ts"],"sourcesContent":["export { VerifNow, type RequestOptions } from './client.js';\n\nexport {\n VerifNowError,\n VerifNowAuthError,\n VerifNowRequestError,\n VerifNowRateLimitError,\n VerifNowServerError,\n VerifNowConnectionError,\n VerifNowResponseError,\n} from './errors.js';\n\nexport {\n VALIDATION_RULES,\n type Deliverability,\n type EmailDetails,\n type EmailSignals,\n type IbanDetails,\n type NasDetails,\n type PhoneDetails,\n type PhoneLineType,\n type QuotaInfo,\n type RetryOptions,\n type RiskLevel,\n type ValidationLevel,\n type ValidationResult,\n type ValidationRule,\n type VatDetails,\n type VatSource,\n type VerifNowOptions,\n} from './types.js';\n\nexport { VERSION } from './version.js';\n","import type { QuotaInfo } from './types.js';\n\n/**\n * Base class for every error this SDK throws.\n *\n * The SDK fails loudly on purpose. A validation client that swallows a network failure and\n * reports `valid: true` turns an outage into silently accepted bad data, and the outage stays\n * invisible until someone audits the database. Catch these and decide explicitly — accepting the\n * input on failure is a reasonable choice, but it should be a choice.\n *\n * @example\n * ```ts\n * try {\n * const result = await client.validateEmail(input);\n * return result.valid;\n * } catch (error) {\n * if (error instanceof VerifNowRateLimitError) throw error; // back-pressure, do not swallow\n * logger.warn({ error }, 'VerifNow unavailable, accepting input unverified');\n * return true;\n * }\n * ```\n */\nexport class VerifNowError extends Error {\n /** HTTP status, when the failure came back from the API rather than the network. */\n readonly status?: number;\n /** Correlation id from the `X-Request-Id` response header, useful in support requests. */\n readonly requestId?: string;\n\n constructor(\n message: string,\n options: { status?: number; requestId?: string; cause?: unknown } = {},\n ) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.status = options.status;\n this.requestId = options.requestId;\n Error.captureStackTrace?.(this, new.target);\n }\n}\n\n/** The API key is missing, malformed, revoked, or not authorised for this endpoint (401/403). */\nexport class VerifNowAuthError extends VerifNowError {}\n\n/**\n * The request was rejected as malformed (400).\n *\n * Retrying is pointless — the payload itself needs to change.\n */\nexport class VerifNowRequestError extends VerifNowError {}\n\n/** The monthly quota or the concurrency limit was exceeded (429). */\nexport class VerifNowRateLimitError extends VerifNowError {\n /** Quota counters from the response headers, when present. */\n readonly quota?: QuotaInfo;\n /** Seconds to wait before retrying, derived from `Retry-After` or `X-RateLimit-Reset`. */\n readonly retryAfterSeconds?: number;\n\n constructor(\n message: string,\n options: {\n status?: number;\n requestId?: string;\n cause?: unknown;\n quota?: QuotaInfo;\n retryAfterSeconds?: number;\n } = {},\n ) {\n super(message, options);\n this.quota = options.quota;\n this.retryAfterSeconds = options.retryAfterSeconds;\n }\n}\n\n/** The API failed to process the request (5xx). Retried automatically before surfacing. */\nexport class VerifNowServerError extends VerifNowError {}\n\n/**\n * The API could not be reached at all: DNS failure, refused connection, TLS error, or the\n * request exceeded `timeoutMs`.\n *\n * A wrong `baseUrl` surfaces here, which is why it names the URL it tried.\n */\nexport class VerifNowConnectionError extends VerifNowError {\n /** True when the failure was the client-side timeout rather than a transport error. */\n readonly timedOut: boolean;\n\n constructor(\n message: string,\n options: { cause?: unknown; timedOut?: boolean } = {},\n ) {\n super(message, { cause: options.cause });\n this.timedOut = options.timedOut ?? false;\n }\n}\n\n/** The API returned a success status with a body this SDK could not parse. */\nexport class VerifNowResponseError extends VerifNowError {}\n","/**\n * SDK version, sent to the API as `X-VerifNow-SDK: node/<version>` so calls made through an\n * official SDK can be told apart from hand-rolled integrations.\n *\n * Kept in sync with `package.json` by a test — bump both together.\n */\nexport const VERSION = '1.4.0';\n","import {\n VerifNowAuthError,\n VerifNowConnectionError,\n VerifNowError,\n VerifNowRateLimitError,\n VerifNowRequestError,\n VerifNowResponseError,\n VerifNowServerError,\n} from './errors.js';\nimport type {\n EmailDetails,\n EmailSignals,\n IbanDetails,\n NasDetails,\n PhoneDetails,\n QuotaInfo,\n RetryOptions,\n ValidationResult,\n ValidationRule,\n VatDetails,\n VerifNowOptions,\n} from './types.js';\nimport { VERSION } from './version.js';\n\nconst DEFAULT_BASE_URL = 'https://api.verifnow.io';\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst DEFAULT_RETRY: Required<RetryOptions> = {\n attempts: 2,\n backoffMs: 200,\n maxBackoffMs: 2_000,\n};\n\n/** Per-call overrides. */\nexport interface RequestOptions {\n /** Override the client timeout for this call. */\n timeoutMs?: number;\n /** Cancel the call from your own controller. Combined with the timeout. */\n signal?: AbortSignal;\n}\n\n/**\n * Client for the VerifNow validation API.\n *\n * @example\n * ```ts\n * import { VerifNow } from '@verifnow/sdk';\n *\n * const client = new VerifNow({ apiKey: process.env.VERIFNOW_API_KEY! });\n * const result = await client.validateEmail('user@example.com');\n *\n * if (!result.valid) console.log(result.message);\n * if (result.emailDetails?.signals?.typoDetected) {\n * console.log('Did you mean', result.emailDetails.signals.suggestedDomain);\n * }\n * ```\n */\nexport class VerifNow {\n readonly #apiKey: string;\n readonly #baseUrl: string;\n readonly #timeoutMs: number;\n readonly #retry: Required<RetryOptions> | null;\n readonly #headers: Record<string, string>;\n readonly #fetch: typeof globalThis.fetch;\n\n constructor(options: VerifNowOptions) {\n if (!options?.apiKey || options.apiKey.trim() === '') {\n throw new VerifNowError(\n 'A VerifNow API key is required. Create one in the dashboard and pass it as `apiKey`.',\n );\n }\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new VerifNowError(\n 'No global fetch available. Use Node 18 or later, or pass a `fetch` implementation.',\n );\n }\n\n this.#apiKey = options.apiKey.trim();\n // Trailing slashes would produce `//api/v1/...`, which some proxies reject.\n this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.#retry =\n options.retry === false ? null : { ...DEFAULT_RETRY, ...(options.retry ?? {}) };\n this.#headers = options.headers ?? {};\n this.#fetch = fetchImpl.bind(globalThis);\n }\n\n /** Validate an email address: syntax, DNS/MX, typo, disposable, role-based, quality score. */\n validateEmail(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('email', value, options);\n }\n\n /**\n * Validate a phone number against its country's numbering plan.\n *\n * The number must include its country code (`+33…` or `0033…`). Valid numbers come back in\n * E.164 as `normalizedValue`, with country and line type in `phoneDetails`.\n */\n validatePhone(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('phone', value, options);\n }\n\n /**\n * Validate an IBAN against the SWIFT registry entry for its country, then its check digits.\n *\n * `ibanDetails` reports the two separately: check digits catch a typo, the registry catches an\n * account number that could never exist in that country.\n */\n validateIban(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('iban', value, options);\n }\n\n /** Validate a VAT number. */\n validateVat(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('vat', value, options);\n }\n\n /**\n * Validate a Canadian Social Insurance Number: format and Luhn check digit.\n *\n * `nasDetails` flags a temporary resident's number (it expires with their permit) and numbers\n * from series not issued to individuals. Only collect a SIN where the law requires it.\n */\n validateNas(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nas', value, options);\n }\n\n /** Validate a US Social Security Number. */\n validateSsn(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('ssn', value, options);\n }\n\n /** Validate a Spanish/Portuguese NIF. */\n validateNif(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nif', value, options);\n }\n\n /**\n * Validate a value against any rule.\n *\n * The typed helpers above call this. Use it directly when the rule is chosen at runtime.\n */\n async validate(\n rule: ValidationRule,\n value: string,\n options: RequestOptions = {},\n ): Promise<ValidationResult> {\n if (typeof value !== 'string' || value.trim() === '') {\n // Caught here rather than server-side: an empty value consumes quota and can only fail.\n throw new VerifNowRequestError(\n `Cannot validate an empty value for rule \"${rule}\".`,\n );\n }\n\n const url = `${this.#baseUrl}/api/v1/validate/${rule}`;\n const body = JSON.stringify({ value });\n const maxAttempts = this.#retry ? this.#retry.attempts + 1 : 1;\n\n let lastError: VerifNowError | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await this.#requestOnce(url, body, options);\n } catch (error) {\n if (!(error instanceof VerifNowError)) throw error;\n lastError = error;\n\n const isLastAttempt = attempt === maxAttempts - 1;\n if (isLastAttempt || !this.#retry) throw error;\n\n const delay = this.#retryDelay(error, attempt);\n if (delay === null) throw error;\n\n await sleep(delay);\n }\n }\n\n /* c8 ignore next -- the loop either returns or throws */\n throw lastError ?? new VerifNowError('Request failed');\n }\n\n /**\n * How long to wait before retrying, or `null` when the error should surface immediately.\n *\n * A 429 is retried only when the reset is close: the concurrency limit clears in\n * milliseconds, but a spent monthly quota does not, and sleeping on it helps nobody.\n */\n #retryDelay(error: VerifNowError, attempt: number): number | null {\n const retry = this.#retry!;\n const backoff = Math.min(retry.backoffMs * 2 ** attempt, retry.maxBackoffMs);\n\n if (error instanceof VerifNowRateLimitError) {\n const waitMs = (error.retryAfterSeconds ?? 0) * 1000;\n if (waitMs > retry.maxBackoffMs) return null;\n return Math.max(waitMs, backoff);\n }\n\n if (error instanceof VerifNowServerError) return backoff;\n // A timeout is retried: the deadline is ours, and the next attempt gets a fresh one.\n if (error instanceof VerifNowConnectionError) return backoff;\n\n // 400, 401 and unparseable bodies will fail identically on a second attempt.\n return null;\n }\n\n async #requestOnce(\n url: string,\n body: string,\n options: RequestOptions,\n ): Promise<ValidationResult> {\n const timeoutMs = options.timeoutMs ?? this.#timeoutMs;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const abortFromCaller = () => controller.abort();\n options.signal?.addEventListener('abort', abortFromCaller, { once: true });\n\n let response: Response;\n try {\n response = await this.#fetch(url, {\n method: 'POST',\n headers: {\n ...this.#headers,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'X-API-KEY': this.#apiKey,\n 'X-VerifNow-SDK': `node/${VERSION}`,\n },\n body,\n signal: controller.signal,\n });\n } catch (cause) {\n // The caller's own cancellation is theirs to handle, not a transport failure.\n if (options.signal?.aborted) throw cause;\n\n const timedOut = controller.signal.aborted;\n throw new VerifNowConnectionError(\n timedOut\n ? `VerifNow request to ${url} timed out after ${timeoutMs}ms.`\n : `Could not reach the VerifNow API at ${url}. Check \\`baseUrl\\` and network access.`,\n { cause, timedOut },\n );\n } finally {\n clearTimeout(timer);\n options.signal?.removeEventListener('abort', abortFromCaller);\n }\n\n return this.#handleResponse(response);\n }\n\n async #handleResponse(response: Response): Promise<ValidationResult> {\n const requestId = response.headers.get('X-Request-Id') ?? undefined;\n const quota = parseQuota(response.headers);\n\n if (response.ok) {\n let payload: unknown;\n try {\n payload = await response.json();\n } catch (cause) {\n throw new VerifNowResponseError(\n 'VerifNow returned a success status with a body that is not valid JSON.',\n { status: response.status, requestId, cause },\n );\n }\n\n if (payload === null || typeof payload !== 'object') {\n throw new VerifNowResponseError(\n 'VerifNow returned an unexpected response shape.',\n { status: response.status, requestId },\n );\n }\n\n return mapResult(payload as Record<string, unknown>, quota);\n }\n\n const message = await readErrorMessage(response);\n const context = { status: response.status, requestId };\n\n if (response.status === 401 || response.status === 403) {\n throw new VerifNowAuthError(\n `VerifNow rejected the API key (${response.status}): ${message}`,\n context,\n );\n }\n\n if (response.status === 429) {\n throw new VerifNowRateLimitError(`VerifNow rate limit reached: ${message}`, {\n ...context,\n quota,\n retryAfterSeconds: parseRetryAfter(response.headers, quota),\n });\n }\n\n if (response.status >= 500) {\n throw new VerifNowServerError(\n `VerifNow returned ${response.status}: ${message}`,\n context,\n );\n }\n\n throw new VerifNowRequestError(\n `VerifNow rejected the request (${response.status}): ${message}`,\n context,\n );\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction toNumber(value: string | null): number | undefined {\n if (value === null) return undefined;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction parseQuota(headers: Headers): QuotaInfo | undefined {\n const limit = toNumber(headers.get('X-RateLimit-Limit'));\n const remaining = toNumber(headers.get('X-RateLimit-Remaining'));\n const resetSeconds = toNumber(headers.get('X-RateLimit-Reset'));\n const overage = headers.get('X-Quota-Overage') === 'true';\n\n if (\n limit === undefined &&\n remaining === undefined &&\n resetSeconds === undefined &&\n !overage\n ) {\n return undefined;\n }\n\n return {\n limit,\n remaining,\n resetAt: resetSeconds === undefined ? undefined : new Date(resetSeconds * 1000),\n overage,\n };\n}\n\nfunction parseRetryAfter(headers: Headers, quota?: QuotaInfo): number | undefined {\n const retryAfter = headers.get('Retry-After');\n if (retryAfter !== null) {\n const seconds = Number(retryAfter);\n if (Number.isFinite(seconds)) return seconds;\n\n // RFC 7231 also allows an HTTP-date.\n const asDate = Date.parse(retryAfter);\n if (!Number.isNaN(asDate)) {\n return Math.max(0, Math.ceil((asDate - Date.now()) / 1000));\n }\n }\n\n if (quota?.resetAt) {\n return Math.max(0, Math.ceil((quota.resetAt.getTime() - Date.now()) / 1000));\n }\n\n return undefined;\n}\n\nasync function readErrorMessage(response: Response): Promise<string> {\n try {\n const text = await response.text();\n if (!text) return response.statusText || 'no details';\n\n try {\n const parsed = JSON.parse(text) as Record<string, unknown>;\n const message = parsed.message ?? parsed.error;\n if (typeof message === 'string' && message !== '') return message;\n } catch {\n // Not JSON — a proxy or the servlet container's default error page.\n }\n\n return text.slice(0, 500);\n } catch {\n return response.statusText || 'no details';\n }\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction asBoolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction mapSignals(raw: unknown): EmailSignals | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const s = raw as Record<string, unknown>;\n\n return {\n syntaxValid: asBoolean(s.syntax_valid),\n mxValid: asBoolean(s.mx_valid),\n typoDetected: asBoolean(s.typo_detected),\n suggestedDomain: asString(s.suggested_domain),\n disposable: asBoolean(s.disposable),\n roleBased: asBoolean(s.role_based),\n freeProvider: asBoolean(s.free_provider),\n domainAgeDays: asNumber(s.domain_age_days),\n mxProvider: asString(s.mx_provider),\n mxQualityScore: asNumber(s.mx_quality_score),\n };\n}\n\nfunction mapEmailDetails(raw: unknown): EmailDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n signals: mapSignals(d.signals),\n riskScore: asNumber(d.risk_score),\n riskLevel: asString(d.risk_level) as EmailDetails['riskLevel'],\n deliverability: asString(d.deliverability) as EmailDetails['deliverability'],\n appliedLevel: asString(d.applied_level) as EmailDetails['appliedLevel'],\n };\n}\n\n/**\n * Reads `registered`, preserving the difference between `false` and `null`.\n *\n * `asBoolean` cannot be used here: it maps `null` to `undefined`, which would erase the one\n * distinction the whole VAT design exists to carry. `false` means the registry answered and the\n * number is not there; `null` means nobody could ask. A caller that cannot tell them apart will\n * reject real businesses whenever VIES is down.\n *\n * A missing key is read as `null` for the same reason — unknown, not absent.\n */\nfunction asRegistered(value: unknown): boolean | null {\n return typeof value === 'boolean' ? value : null;\n}\n\nfunction asDate(value: unknown): Date | undefined {\n if (typeof value !== 'string') return undefined;\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? undefined : parsed;\n}\n\nfunction mapVatDetails(raw: unknown): VatDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n formatValid: asBoolean(d.format_valid),\n registered: asRegistered(d.registered),\n countryCode: asString(d.country_code),\n source: asString(d.source) as VatDetails['source'],\n checkedAt: asDate(d.checked_at),\n traderName: asString(d.trader_name),\n traderAddress: asString(d.trader_address),\n viesAvailable: asBoolean(d.vies_available),\n consultationNumber: asString(d.consultation_number),\n };\n}\n\nfunction mapPhoneDetails(raw: unknown): PhoneDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n countryCode: asString(d.country_code),\n callingCode: asNumber(d.calling_code),\n lineType: asString(d.line_type) as PhoneDetails['lineType'],\n internationalFormat: asString(d.international_format),\n nationalFormat: asString(d.national_format),\n };\n}\n\nfunction mapIbanDetails(raw: unknown): IbanDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n countryCode: asString(d.country_code),\n structureValid: asBoolean(d.structure_valid),\n checksumValid: asBoolean(d.checksum_valid),\n length: asNumber(d.length),\n expectedLength: asNumber(d.expected_length),\n formatted: asString(d.formatted),\n };\n}\n\nfunction mapNasDetails(raw: unknown): NasDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n checksumValid: asBoolean(d.checksum_valid),\n temporaryResident: asBoolean(d.temporary_resident),\n individualSeries: asBoolean(d.individual_series),\n formatted: asString(d.formatted),\n };\n}\n\n/**\n * Maps the API's snake_case diagnostics onto camelCase, so a TypeScript caller is not switching\n * naming conventions mid-expression. The untouched body stays available on `raw`.\n */\nfunction mapResult(\n payload: Record<string, unknown>,\n quota: QuotaInfo | undefined,\n): ValidationResult {\n return {\n valid: payload.valid === true,\n message: asString(payload.message),\n normalizedValue: asString(payload.normalizedValue) ?? null,\n originalValue: asString(payload.originalValue),\n validationLevel: asString(payload.validationLevel) as ValidationResult['validationLevel'],\n emailDetails: mapEmailDetails(payload.emailDetails),\n vatDetails: mapVatDetails(payload.vatDetails),\n phoneDetails: mapPhoneDetails(payload.phoneDetails),\n ibanDetails: mapIbanDetails(payload.ibanDetails),\n nasDetails: mapNasDetails(payload.nasDetails),\n quota,\n raw: payload,\n };\n}\n","/**\n * Validation rules exposed by the VerifNow API.\n *\n * Each maps to `POST /api/v1/validate/{rule}`.\n */\nexport type ValidationRule =\n | 'email'\n | 'phone'\n | 'iban'\n | 'vat'\n | 'nas'\n | 'ssn'\n | 'nif';\n\nexport const VALIDATION_RULES: readonly ValidationRule[] = [\n 'email',\n 'phone',\n 'iban',\n 'vat',\n 'nas',\n 'ssn',\n 'nif',\n] as const;\n\n/**\n * Depth of checks applied to a request, decided by the plan attached to the API key.\n *\n * `STANDARD` runs on the FREE and STARTER plans, `ADVANCED` on GROWTH, `PREMIUM` on PRO.\n * Branch on this rather than on the plan name: it is the only value that tells you which\n * signals are actually present in the response.\n */\nexport type ValidationLevel = 'BASIC' | 'STANDARD' | 'ADVANCED' | 'PREMIUM';\n\n/** Categorical risk assessment. Returned from `ADVANCED` depth upward. */\nexport type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH';\n\nexport type Deliverability =\n | 'DELIVERABLE'\n | 'RISKY'\n | 'UNDELIVERABLE'\n | 'UNKNOWN';\n\n/**\n * Per-signal breakdown behind an email verdict.\n *\n * Fields are `undefined` when the applied level does not compute them — the last four require\n * `ADVANCED` depth or higher. Check `ValidationResult.appliedLevel` before relying on one.\n */\nexport interface EmailSignals {\n /** The address matches the syntax pattern for the applied level. */\n syntaxValid?: boolean;\n /** The domain resolves and publishes MX (or fallback A) records. */\n mxValid?: boolean;\n /** A likely typo was found in the domain, e.g. `gmail.con`. */\n typoDetected?: boolean;\n /** The correction proposed when `typoDetected` is true. */\n suggestedDomain?: string;\n /** The domain belongs to a throwaway mailbox provider. */\n disposable?: boolean;\n /** The local part is a shared mailbox: `info@`, `admin@`, `noreply@`. */\n roleBased?: boolean;\n /** The domain is a consumer mailbox provider. Requires ADVANCED. */\n freeProvider?: boolean;\n /** Estimated age of the domain in days. Requires ADVANCED. */\n domainAgeDays?: number;\n /** Identified mail provider, e.g. `google`. Requires ADVANCED. */\n mxProvider?: string;\n /** Mail server quality between 0 and 1. Requires ADVANCED. */\n mxQualityScore?: number;\n}\n\n/** Email-specific diagnostics. Absent when the applied level is `BASIC`. */\nexport interface EmailDetails {\n signals?: EmailSignals;\n /** Aggregated risk on a 0–100 scale, where 0 is the lowest risk. */\n riskScore?: number;\n /** Categorical risk. Requires ADVANCED depth or higher. */\n riskLevel?: RiskLevel;\n deliverability?: Deliverability;\n /** The depth actually applied, echoed back by the API. */\n appliedLevel?: ValidationLevel;\n}\n\n/**\n * Where a VAT registration verdict came from.\n *\n * VIES publishes no SLA and drops member states several times a month, so a VAT answer is not\n * always a live one. Branch on this rather than on `ValidationResult.valid` whenever the\n * difference matters for your own compliance.\n */\nexport type VatSource =\n /** Confirmed against VIES during this request. */\n | 'LIVE'\n /** Served from a VIES answer less than 24 hours old. */\n | 'CACHE'\n /** VIES was unreachable, so an older cached answer was used. */\n | 'STALE'\n /** VIES was unreachable and nothing was cached. Registration is unknown. */\n | 'UNVERIFIED'\n /** The country is outside VIES, so no registry lookup is possible. */\n | 'NOT_APPLICABLE';\n\n/** VAT-specific diagnostics. Present on `vat` validations. */\nexport interface VatDetails {\n /** The number matches its member state's structure. Decided locally, never depends on VIES. */\n formatValid?: boolean;\n /**\n * Present in the member state's registry.\n *\n * **`null` means unknown, never \"not registered.\"** It is returned when VIES could not be\n * consulted. Treating `null` as `false` rejects legitimate customers during someone else's\n * outage — the single most expensive mistake available in VAT validation.\n */\n registered: boolean | null;\n /** Member state the number belongs to, e.g. `IE`. Greece is `EL`, Northern Ireland `XI`. */\n countryCode?: string;\n source?: VatSource;\n /** When the registration was last confirmed against VIES. */\n checkedAt?: Date;\n /** Registered trading name, when the member state discloses it. Germany does not. */\n traderName?: string;\n /** Registered address, when the member state discloses it. */\n traderAddress?: string;\n /** Whether VIES could answer for this country during the request. */\n viesAvailable?: boolean;\n /**\n * The consultation number VIES issued for this lookup — the receipt a tax authority accepts as\n * evidence that you checked. Present only when your account has its own VAT number configured,\n * because VIES issues one only to an identified requester.\n */\n consultationNumber?: string;\n}\n\n/**\n * Kind of line, according to the country's numbering plan.\n *\n * A `PREMIUM_RATE` or `VOIP` number is still `valid` — it exists. This is how you decide to\n * exclude one, rather than the API deciding for you.\n */\nexport type PhoneLineType =\n | 'MOBILE'\n | 'FIXED_LINE'\n /** The plan does not distinguish the two — the case for the US and Canada. */\n | 'FIXED_LINE_OR_MOBILE'\n | 'TOLL_FREE'\n | 'PREMIUM_RATE'\n | 'SHARED_COST'\n | 'VOIP'\n | 'PERSONAL_NUMBER'\n | 'PAGER'\n | 'UAN'\n | 'VOICEMAIL'\n | 'UNKNOWN';\n\n/**\n * Phone-specific diagnostics. Present whenever the input parsed as an international number —\n * including when it is invalid for its country, so you can tell the user which country it was\n * read as.\n */\nexport interface PhoneDetails {\n /** ISO 3166-1 alpha-2 country, e.g. `FR`. Absent when the calling code is shared by several. */\n countryCode?: string;\n /** International calling code without the plus sign, e.g. `33`. */\n callingCode?: number;\n /** Absent when the number is invalid. */\n lineType?: PhoneLineType;\n /** e.g. `+33 6 12 34 56 78`. Absent when the number is invalid. */\n internationalFormat?: string;\n /** e.g. `06 12 34 56 78`. Absent when the number is invalid. */\n nationalFormat?: string;\n}\n\n/**\n * IBAN-specific diagnostics. Present on `iban` validations.\n *\n * Structure and checksum are reported separately because they fail for different reasons:\n * `structureValid` answers \"could this be an account number in that country\" (the SWIFT\n * registry's length and layout), `checksumValid` answers \"was it typed correctly\" (mod-97).\n * There is no bank name or BIC — that needs a registry the API does not hold.\n */\nexport interface IbanDetails {\n /** The IBAN's country, from its first two characters. */\n countryCode?: string;\n /** Length and character layout match the registry entry for that country. */\n structureValid?: boolean;\n /** The mod-97 check digits are correct. */\n checksumValid?: boolean;\n /** Length of the value as submitted, spaces removed. */\n length?: number;\n /** Length the registry requires for that country; absent for an unknown country. */\n expectedLength?: number;\n /** Print format, in groups of four. Present only for a valid IBAN. */\n formatted?: string;\n}\n\n/**\n * Canadian Social Insurance Number diagnostics. Present on `nas` validations.\n *\n * There is no province and no expiry date: the first digit no longer reliably identifies a\n * province, and a temporary resident's SIN expires with their permit, which only the document\n * shows.\n */\nexport interface NasDetails {\n /** The Luhn check digit is correct. */\n checksumValid?: boolean;\n /**\n * A 9-series number, issued to temporary residents. It expires with the holder's permit, and the\n * number itself does not say when — check the document.\n */\n temporaryResident?: boolean;\n /**\n * The first digit belongs to a series issued to individuals. `false` for numbers starting with\n * 0 or 8 — including 046 454 286, the government's sample number, which is why it is safe to\n * use in tests.\n */\n individualSeries?: boolean;\n /** Printed form, e.g. `046 454 286`. */\n formatted?: string;\n}\n\n/** Outcome of a single validation call. */\nexport interface ValidationResult {\n /** Whether the value passed every check the applied level ran. */\n valid: boolean;\n /** Human-readable explanation of the verdict. */\n message?: string;\n /** Canonical form of the input — `null` when the value is invalid. */\n normalizedValue: string | null;\n /** The value exactly as submitted. */\n originalValue?: string;\n /** Depth applied to this request. */\n validationLevel?: ValidationLevel;\n /** Present for email validations from `STANDARD` depth upward. */\n emailDetails?: EmailDetails;\n /** Present for VAT validations. */\n vatDetails?: VatDetails;\n /** Present for phone validations. The E.164 form is `normalizedValue`. */\n phoneDetails?: PhoneDetails;\n /** Present for IBAN validations. */\n ibanDetails?: IbanDetails;\n /** Present for Canadian SIN (`nas`) validations. */\n nasDetails?: NasDetails;\n /** Quota state reported by the response headers. */\n quota?: QuotaInfo;\n /** The unmodified JSON body, for fields this SDK version does not model yet. */\n raw: Record<string, unknown>;\n}\n\n/** Quota counters read from the `X-RateLimit-*` response headers. */\nexport interface QuotaInfo {\n /** Validations included in the current billing period. */\n limit?: number;\n /** Validations left before overage or blocking. */\n remaining?: number;\n /** When the current period resets. */\n resetAt?: Date;\n /** True once you are past the included quota and into per-unit billing. */\n overage?: boolean;\n}\n\nexport interface RetryOptions {\n /**\n * Retry attempts after the first failure. Defaults to 2, so up to 3 requests in total.\n * Only connection failures, 429 and 5xx are retried — never a 400 or 401, which will not\n * succeed on a second try.\n */\n attempts?: number;\n /** Delay before the first retry, in ms. Doubles each attempt. Defaults to 200. */\n backoffMs?: number;\n /** Upper bound on a single backoff delay, in ms. Defaults to 2000. */\n maxBackoffMs?: number;\n}\n\nexport interface VerifNowOptions {\n /** API key created in the VerifNow dashboard. Sent as the `X-API-KEY` header. */\n apiKey: string;\n /** Override the API origin. Defaults to `https://api.verifnow.io`. */\n baseUrl?: string;\n /** Abort a single request after this many ms. Defaults to 5000. */\n timeoutMs?: number;\n /** Retry policy, or `false` to disable retries entirely. */\n retry?: RetryOptions | false;\n /** Extra headers merged into every request. */\n headers?: Record<string, string>;\n /**\n * Replacement for the global `fetch`, for tests or a custom agent.\n * Defaults to `globalThis.fetch`.\n */\n fetch?: typeof globalThis.fetch;\n}\n"],"mappings":"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","names":["asDate"]}
package/dist/index.d.cts CHANGED
@@ -129,6 +129,52 @@ interface PhoneDetails {
129
129
  /** e.g. `06 12 34 56 78`. Absent when the number is invalid. */
130
130
  nationalFormat?: string;
131
131
  }
132
+ /**
133
+ * IBAN-specific diagnostics. Present on `iban` validations.
134
+ *
135
+ * Structure and checksum are reported separately because they fail for different reasons:
136
+ * `structureValid` answers "could this be an account number in that country" (the SWIFT
137
+ * registry's length and layout), `checksumValid` answers "was it typed correctly" (mod-97).
138
+ * There is no bank name or BIC — that needs a registry the API does not hold.
139
+ */
140
+ interface IbanDetails {
141
+ /** The IBAN's country, from its first two characters. */
142
+ countryCode?: string;
143
+ /** Length and character layout match the registry entry for that country. */
144
+ structureValid?: boolean;
145
+ /** The mod-97 check digits are correct. */
146
+ checksumValid?: boolean;
147
+ /** Length of the value as submitted, spaces removed. */
148
+ length?: number;
149
+ /** Length the registry requires for that country; absent for an unknown country. */
150
+ expectedLength?: number;
151
+ /** Print format, in groups of four. Present only for a valid IBAN. */
152
+ formatted?: string;
153
+ }
154
+ /**
155
+ * Canadian Social Insurance Number diagnostics. Present on `nas` validations.
156
+ *
157
+ * There is no province and no expiry date: the first digit no longer reliably identifies a
158
+ * province, and a temporary resident's SIN expires with their permit, which only the document
159
+ * shows.
160
+ */
161
+ interface NasDetails {
162
+ /** The Luhn check digit is correct. */
163
+ checksumValid?: boolean;
164
+ /**
165
+ * A 9-series number, issued to temporary residents. It expires with the holder's permit, and the
166
+ * number itself does not say when — check the document.
167
+ */
168
+ temporaryResident?: boolean;
169
+ /**
170
+ * The first digit belongs to a series issued to individuals. `false` for numbers starting with
171
+ * 0 or 8 — including 046 454 286, the government's sample number, which is why it is safe to
172
+ * use in tests.
173
+ */
174
+ individualSeries?: boolean;
175
+ /** Printed form, e.g. `046 454 286`. */
176
+ formatted?: string;
177
+ }
132
178
  /** Outcome of a single validation call. */
133
179
  interface ValidationResult {
134
180
  /** Whether the value passed every check the applied level ran. */
@@ -147,6 +193,10 @@ interface ValidationResult {
147
193
  vatDetails?: VatDetails;
148
194
  /** Present for phone validations. The E.164 form is `normalizedValue`. */
149
195
  phoneDetails?: PhoneDetails;
196
+ /** Present for IBAN validations. */
197
+ ibanDetails?: IbanDetails;
198
+ /** Present for Canadian SIN (`nas`) validations. */
199
+ nasDetails?: NasDetails;
150
200
  /** Quota state reported by the response headers. */
151
201
  quota?: QuotaInfo;
152
202
  /** The unmodified JSON body, for fields this SDK version does not model yet. */
@@ -228,11 +278,21 @@ declare class VerifNow {
228
278
  * E.164 as `normalizedValue`, with country and line type in `phoneDetails`.
229
279
  */
230
280
  validatePhone(value: string, options?: RequestOptions): Promise<ValidationResult>;
231
- /** Validate an IBAN: country structure and check digits. */
281
+ /**
282
+ * Validate an IBAN against the SWIFT registry entry for its country, then its check digits.
283
+ *
284
+ * `ibanDetails` reports the two separately: check digits catch a typo, the registry catches an
285
+ * account number that could never exist in that country.
286
+ */
232
287
  validateIban(value: string, options?: RequestOptions): Promise<ValidationResult>;
233
288
  /** Validate a VAT number. */
234
289
  validateVat(value: string, options?: RequestOptions): Promise<ValidationResult>;
235
- /** Validate a Canadian Social Insurance Number. */
290
+ /**
291
+ * Validate a Canadian Social Insurance Number: format and Luhn check digit.
292
+ *
293
+ * `nasDetails` flags a temporary resident's number (it expires with their permit) and numbers
294
+ * from series not issued to individuals. Only collect a SIN where the law requires it.
295
+ */
236
296
  validateNas(value: string, options?: RequestOptions): Promise<ValidationResult>;
237
297
  /** Validate a US Social Security Number. */
238
298
  validateSsn(value: string, options?: RequestOptions): Promise<ValidationResult>;
@@ -328,6 +388,6 @@ declare class VerifNowResponseError extends VerifNowError {
328
388
  *
329
389
  * Kept in sync with `package.json` by a test — bump both together.
330
390
  */
331
- declare const VERSION = "1.2.0";
391
+ declare const VERSION = "1.4.0";
332
392
 
333
- export { type Deliverability, type EmailDetails, type EmailSignals, type PhoneDetails, type PhoneLineType, type QuotaInfo, type RequestOptions, type RetryOptions, type RiskLevel, VALIDATION_RULES, VERSION, type ValidationLevel, type ValidationResult, type ValidationRule, type VatDetails, type VatSource, VerifNow, VerifNowAuthError, VerifNowConnectionError, VerifNowError, type VerifNowOptions, VerifNowRateLimitError, VerifNowRequestError, VerifNowResponseError, VerifNowServerError };
393
+ export { type Deliverability, type EmailDetails, type EmailSignals, type IbanDetails, type NasDetails, type PhoneDetails, type PhoneLineType, type QuotaInfo, type RequestOptions, type RetryOptions, type RiskLevel, VALIDATION_RULES, VERSION, type ValidationLevel, type ValidationResult, type ValidationRule, type VatDetails, type VatSource, VerifNow, VerifNowAuthError, VerifNowConnectionError, VerifNowError, type VerifNowOptions, VerifNowRateLimitError, VerifNowRequestError, VerifNowResponseError, VerifNowServerError };
package/dist/index.d.ts CHANGED
@@ -129,6 +129,52 @@ interface PhoneDetails {
129
129
  /** e.g. `06 12 34 56 78`. Absent when the number is invalid. */
130
130
  nationalFormat?: string;
131
131
  }
132
+ /**
133
+ * IBAN-specific diagnostics. Present on `iban` validations.
134
+ *
135
+ * Structure and checksum are reported separately because they fail for different reasons:
136
+ * `structureValid` answers "could this be an account number in that country" (the SWIFT
137
+ * registry's length and layout), `checksumValid` answers "was it typed correctly" (mod-97).
138
+ * There is no bank name or BIC — that needs a registry the API does not hold.
139
+ */
140
+ interface IbanDetails {
141
+ /** The IBAN's country, from its first two characters. */
142
+ countryCode?: string;
143
+ /** Length and character layout match the registry entry for that country. */
144
+ structureValid?: boolean;
145
+ /** The mod-97 check digits are correct. */
146
+ checksumValid?: boolean;
147
+ /** Length of the value as submitted, spaces removed. */
148
+ length?: number;
149
+ /** Length the registry requires for that country; absent for an unknown country. */
150
+ expectedLength?: number;
151
+ /** Print format, in groups of four. Present only for a valid IBAN. */
152
+ formatted?: string;
153
+ }
154
+ /**
155
+ * Canadian Social Insurance Number diagnostics. Present on `nas` validations.
156
+ *
157
+ * There is no province and no expiry date: the first digit no longer reliably identifies a
158
+ * province, and a temporary resident's SIN expires with their permit, which only the document
159
+ * shows.
160
+ */
161
+ interface NasDetails {
162
+ /** The Luhn check digit is correct. */
163
+ checksumValid?: boolean;
164
+ /**
165
+ * A 9-series number, issued to temporary residents. It expires with the holder's permit, and the
166
+ * number itself does not say when — check the document.
167
+ */
168
+ temporaryResident?: boolean;
169
+ /**
170
+ * The first digit belongs to a series issued to individuals. `false` for numbers starting with
171
+ * 0 or 8 — including 046 454 286, the government's sample number, which is why it is safe to
172
+ * use in tests.
173
+ */
174
+ individualSeries?: boolean;
175
+ /** Printed form, e.g. `046 454 286`. */
176
+ formatted?: string;
177
+ }
132
178
  /** Outcome of a single validation call. */
133
179
  interface ValidationResult {
134
180
  /** Whether the value passed every check the applied level ran. */
@@ -147,6 +193,10 @@ interface ValidationResult {
147
193
  vatDetails?: VatDetails;
148
194
  /** Present for phone validations. The E.164 form is `normalizedValue`. */
149
195
  phoneDetails?: PhoneDetails;
196
+ /** Present for IBAN validations. */
197
+ ibanDetails?: IbanDetails;
198
+ /** Present for Canadian SIN (`nas`) validations. */
199
+ nasDetails?: NasDetails;
150
200
  /** Quota state reported by the response headers. */
151
201
  quota?: QuotaInfo;
152
202
  /** The unmodified JSON body, for fields this SDK version does not model yet. */
@@ -228,11 +278,21 @@ declare class VerifNow {
228
278
  * E.164 as `normalizedValue`, with country and line type in `phoneDetails`.
229
279
  */
230
280
  validatePhone(value: string, options?: RequestOptions): Promise<ValidationResult>;
231
- /** Validate an IBAN: country structure and check digits. */
281
+ /**
282
+ * Validate an IBAN against the SWIFT registry entry for its country, then its check digits.
283
+ *
284
+ * `ibanDetails` reports the two separately: check digits catch a typo, the registry catches an
285
+ * account number that could never exist in that country.
286
+ */
232
287
  validateIban(value: string, options?: RequestOptions): Promise<ValidationResult>;
233
288
  /** Validate a VAT number. */
234
289
  validateVat(value: string, options?: RequestOptions): Promise<ValidationResult>;
235
- /** Validate a Canadian Social Insurance Number. */
290
+ /**
291
+ * Validate a Canadian Social Insurance Number: format and Luhn check digit.
292
+ *
293
+ * `nasDetails` flags a temporary resident's number (it expires with their permit) and numbers
294
+ * from series not issued to individuals. Only collect a SIN where the law requires it.
295
+ */
236
296
  validateNas(value: string, options?: RequestOptions): Promise<ValidationResult>;
237
297
  /** Validate a US Social Security Number. */
238
298
  validateSsn(value: string, options?: RequestOptions): Promise<ValidationResult>;
@@ -328,6 +388,6 @@ declare class VerifNowResponseError extends VerifNowError {
328
388
  *
329
389
  * Kept in sync with `package.json` by a test — bump both together.
330
390
  */
331
- declare const VERSION = "1.2.0";
391
+ declare const VERSION = "1.4.0";
332
392
 
333
- export { type Deliverability, type EmailDetails, type EmailSignals, type PhoneDetails, type PhoneLineType, type QuotaInfo, type RequestOptions, type RetryOptions, type RiskLevel, VALIDATION_RULES, VERSION, type ValidationLevel, type ValidationResult, type ValidationRule, type VatDetails, type VatSource, VerifNow, VerifNowAuthError, VerifNowConnectionError, VerifNowError, type VerifNowOptions, VerifNowRateLimitError, VerifNowRequestError, VerifNowResponseError, VerifNowServerError };
393
+ export { type Deliverability, type EmailDetails, type EmailSignals, type IbanDetails, type NasDetails, type PhoneDetails, type PhoneLineType, type QuotaInfo, type RequestOptions, type RetryOptions, type RiskLevel, VALIDATION_RULES, VERSION, type ValidationLevel, type ValidationResult, type ValidationRule, type VatDetails, type VatSource, VerifNow, VerifNowAuthError, VerifNowConnectionError, VerifNowError, type VerifNowOptions, VerifNowRateLimitError, VerifNowRequestError, VerifNowResponseError, VerifNowServerError };
package/dist/index.js CHANGED
@@ -41,7 +41,7 @@ var VerifNowResponseError = class extends VerifNowError {
41
41
  };
42
42
 
43
43
  // src/version.ts
44
- var VERSION = "1.2.0";
44
+ var VERSION = "1.4.0";
45
45
 
46
46
  // src/client.ts
47
47
  var DEFAULT_BASE_URL = "https://api.verifnow.io";
@@ -90,7 +90,12 @@ var VerifNow = class {
90
90
  validatePhone(value, options) {
91
91
  return this.validate("phone", value, options);
92
92
  }
93
- /** Validate an IBAN: country structure and check digits. */
93
+ /**
94
+ * Validate an IBAN against the SWIFT registry entry for its country, then its check digits.
95
+ *
96
+ * `ibanDetails` reports the two separately: check digits catch a typo, the registry catches an
97
+ * account number that could never exist in that country.
98
+ */
94
99
  validateIban(value, options) {
95
100
  return this.validate("iban", value, options);
96
101
  }
@@ -98,7 +103,12 @@ var VerifNow = class {
98
103
  validateVat(value, options) {
99
104
  return this.validate("vat", value, options);
100
105
  }
101
- /** Validate a Canadian Social Insurance Number. */
106
+ /**
107
+ * Validate a Canadian Social Insurance Number: format and Luhn check digit.
108
+ *
109
+ * `nasDetails` flags a temporary resident's number (it expires with their permit) and numbers
110
+ * from series not issued to individuals. Only collect a SIN where the law requires it.
111
+ */
102
112
  validateNas(value, options) {
103
113
  return this.validate("nas", value, options);
104
114
  }
@@ -362,6 +372,28 @@ function mapPhoneDetails(raw) {
362
372
  nationalFormat: asString(d.national_format)
363
373
  };
364
374
  }
375
+ function mapIbanDetails(raw) {
376
+ if (raw === null || typeof raw !== "object") return void 0;
377
+ const d = raw;
378
+ return {
379
+ countryCode: asString(d.country_code),
380
+ structureValid: asBoolean(d.structure_valid),
381
+ checksumValid: asBoolean(d.checksum_valid),
382
+ length: asNumber(d.length),
383
+ expectedLength: asNumber(d.expected_length),
384
+ formatted: asString(d.formatted)
385
+ };
386
+ }
387
+ function mapNasDetails(raw) {
388
+ if (raw === null || typeof raw !== "object") return void 0;
389
+ const d = raw;
390
+ return {
391
+ checksumValid: asBoolean(d.checksum_valid),
392
+ temporaryResident: asBoolean(d.temporary_resident),
393
+ individualSeries: asBoolean(d.individual_series),
394
+ formatted: asString(d.formatted)
395
+ };
396
+ }
365
397
  function mapResult(payload, quota) {
366
398
  return {
367
399
  valid: payload.valid === true,
@@ -372,6 +404,8 @@ function mapResult(payload, quota) {
372
404
  emailDetails: mapEmailDetails(payload.emailDetails),
373
405
  vatDetails: mapVatDetails(payload.vatDetails),
374
406
  phoneDetails: mapPhoneDetails(payload.phoneDetails),
407
+ ibanDetails: mapIbanDetails(payload.ibanDetails),
408
+ nasDetails: mapNasDetails(payload.nasDetails),
375
409
  quota,
376
410
  raw: payload
377
411
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts","../src/types.ts"],"sourcesContent":["import type { QuotaInfo } from './types.js';\n\n/**\n * Base class for every error this SDK throws.\n *\n * The SDK fails loudly on purpose. A validation client that swallows a network failure and\n * reports `valid: true` turns an outage into silently accepted bad data, and the outage stays\n * invisible until someone audits the database. Catch these and decide explicitly — accepting the\n * input on failure is a reasonable choice, but it should be a choice.\n *\n * @example\n * ```ts\n * try {\n * const result = await client.validateEmail(input);\n * return result.valid;\n * } catch (error) {\n * if (error instanceof VerifNowRateLimitError) throw error; // back-pressure, do not swallow\n * logger.warn({ error }, 'VerifNow unavailable, accepting input unverified');\n * return true;\n * }\n * ```\n */\nexport class VerifNowError extends Error {\n /** HTTP status, when the failure came back from the API rather than the network. */\n readonly status?: number;\n /** Correlation id from the `X-Request-Id` response header, useful in support requests. */\n readonly requestId?: string;\n\n constructor(\n message: string,\n options: { status?: number; requestId?: string; cause?: unknown } = {},\n ) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.status = options.status;\n this.requestId = options.requestId;\n Error.captureStackTrace?.(this, new.target);\n }\n}\n\n/** The API key is missing, malformed, revoked, or not authorised for this endpoint (401/403). */\nexport class VerifNowAuthError extends VerifNowError {}\n\n/**\n * The request was rejected as malformed (400).\n *\n * Retrying is pointless — the payload itself needs to change.\n */\nexport class VerifNowRequestError extends VerifNowError {}\n\n/** The monthly quota or the concurrency limit was exceeded (429). */\nexport class VerifNowRateLimitError extends VerifNowError {\n /** Quota counters from the response headers, when present. */\n readonly quota?: QuotaInfo;\n /** Seconds to wait before retrying, derived from `Retry-After` or `X-RateLimit-Reset`. */\n readonly retryAfterSeconds?: number;\n\n constructor(\n message: string,\n options: {\n status?: number;\n requestId?: string;\n cause?: unknown;\n quota?: QuotaInfo;\n retryAfterSeconds?: number;\n } = {},\n ) {\n super(message, options);\n this.quota = options.quota;\n this.retryAfterSeconds = options.retryAfterSeconds;\n }\n}\n\n/** The API failed to process the request (5xx). Retried automatically before surfacing. */\nexport class VerifNowServerError extends VerifNowError {}\n\n/**\n * The API could not be reached at all: DNS failure, refused connection, TLS error, or the\n * request exceeded `timeoutMs`.\n *\n * A wrong `baseUrl` surfaces here, which is why it names the URL it tried.\n */\nexport class VerifNowConnectionError extends VerifNowError {\n /** True when the failure was the client-side timeout rather than a transport error. */\n readonly timedOut: boolean;\n\n constructor(\n message: string,\n options: { cause?: unknown; timedOut?: boolean } = {},\n ) {\n super(message, { cause: options.cause });\n this.timedOut = options.timedOut ?? false;\n }\n}\n\n/** The API returned a success status with a body this SDK could not parse. */\nexport class VerifNowResponseError extends VerifNowError {}\n","/**\n * SDK version, sent to the API as `X-VerifNow-SDK: node/<version>` so calls made through an\n * official SDK can be told apart from hand-rolled integrations.\n *\n * Kept in sync with `package.json` by a test — bump both together.\n */\nexport const VERSION = '1.2.0';\n","import {\n VerifNowAuthError,\n VerifNowConnectionError,\n VerifNowError,\n VerifNowRateLimitError,\n VerifNowRequestError,\n VerifNowResponseError,\n VerifNowServerError,\n} from './errors.js';\nimport type {\n EmailDetails,\n EmailSignals,\n PhoneDetails,\n QuotaInfo,\n RetryOptions,\n ValidationResult,\n ValidationRule,\n VatDetails,\n VerifNowOptions,\n} from './types.js';\nimport { VERSION } from './version.js';\n\nconst DEFAULT_BASE_URL = 'https://api.verifnow.io';\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst DEFAULT_RETRY: Required<RetryOptions> = {\n attempts: 2,\n backoffMs: 200,\n maxBackoffMs: 2_000,\n};\n\n/** Per-call overrides. */\nexport interface RequestOptions {\n /** Override the client timeout for this call. */\n timeoutMs?: number;\n /** Cancel the call from your own controller. Combined with the timeout. */\n signal?: AbortSignal;\n}\n\n/**\n * Client for the VerifNow validation API.\n *\n * @example\n * ```ts\n * import { VerifNow } from '@verifnow/sdk';\n *\n * const client = new VerifNow({ apiKey: process.env.VERIFNOW_API_KEY! });\n * const result = await client.validateEmail('user@example.com');\n *\n * if (!result.valid) console.log(result.message);\n * if (result.emailDetails?.signals?.typoDetected) {\n * console.log('Did you mean', result.emailDetails.signals.suggestedDomain);\n * }\n * ```\n */\nexport class VerifNow {\n readonly #apiKey: string;\n readonly #baseUrl: string;\n readonly #timeoutMs: number;\n readonly #retry: Required<RetryOptions> | null;\n readonly #headers: Record<string, string>;\n readonly #fetch: typeof globalThis.fetch;\n\n constructor(options: VerifNowOptions) {\n if (!options?.apiKey || options.apiKey.trim() === '') {\n throw new VerifNowError(\n 'A VerifNow API key is required. Create one in the dashboard and pass it as `apiKey`.',\n );\n }\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new VerifNowError(\n 'No global fetch available. Use Node 18 or later, or pass a `fetch` implementation.',\n );\n }\n\n this.#apiKey = options.apiKey.trim();\n // Trailing slashes would produce `//api/v1/...`, which some proxies reject.\n this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.#retry =\n options.retry === false ? null : { ...DEFAULT_RETRY, ...(options.retry ?? {}) };\n this.#headers = options.headers ?? {};\n this.#fetch = fetchImpl.bind(globalThis);\n }\n\n /** Validate an email address: syntax, DNS/MX, typo, disposable, role-based, quality score. */\n validateEmail(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('email', value, options);\n }\n\n /**\n * Validate a phone number against its country's numbering plan.\n *\n * The number must include its country code (`+33…` or `0033…`). Valid numbers come back in\n * E.164 as `normalizedValue`, with country and line type in `phoneDetails`.\n */\n validatePhone(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('phone', value, options);\n }\n\n /** Validate an IBAN: country structure and check digits. */\n validateIban(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('iban', value, options);\n }\n\n /** Validate a VAT number. */\n validateVat(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('vat', value, options);\n }\n\n /** Validate a Canadian Social Insurance Number. */\n validateNas(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nas', value, options);\n }\n\n /** Validate a US Social Security Number. */\n validateSsn(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('ssn', value, options);\n }\n\n /** Validate a Spanish/Portuguese NIF. */\n validateNif(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nif', value, options);\n }\n\n /**\n * Validate a value against any rule.\n *\n * The typed helpers above call this. Use it directly when the rule is chosen at runtime.\n */\n async validate(\n rule: ValidationRule,\n value: string,\n options: RequestOptions = {},\n ): Promise<ValidationResult> {\n if (typeof value !== 'string' || value.trim() === '') {\n // Caught here rather than server-side: an empty value consumes quota and can only fail.\n throw new VerifNowRequestError(\n `Cannot validate an empty value for rule \"${rule}\".`,\n );\n }\n\n const url = `${this.#baseUrl}/api/v1/validate/${rule}`;\n const body = JSON.stringify({ value });\n const maxAttempts = this.#retry ? this.#retry.attempts + 1 : 1;\n\n let lastError: VerifNowError | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await this.#requestOnce(url, body, options);\n } catch (error) {\n if (!(error instanceof VerifNowError)) throw error;\n lastError = error;\n\n const isLastAttempt = attempt === maxAttempts - 1;\n if (isLastAttempt || !this.#retry) throw error;\n\n const delay = this.#retryDelay(error, attempt);\n if (delay === null) throw error;\n\n await sleep(delay);\n }\n }\n\n /* c8 ignore next -- the loop either returns or throws */\n throw lastError ?? new VerifNowError('Request failed');\n }\n\n /**\n * How long to wait before retrying, or `null` when the error should surface immediately.\n *\n * A 429 is retried only when the reset is close: the concurrency limit clears in\n * milliseconds, but a spent monthly quota does not, and sleeping on it helps nobody.\n */\n #retryDelay(error: VerifNowError, attempt: number): number | null {\n const retry = this.#retry!;\n const backoff = Math.min(retry.backoffMs * 2 ** attempt, retry.maxBackoffMs);\n\n if (error instanceof VerifNowRateLimitError) {\n const waitMs = (error.retryAfterSeconds ?? 0) * 1000;\n if (waitMs > retry.maxBackoffMs) return null;\n return Math.max(waitMs, backoff);\n }\n\n if (error instanceof VerifNowServerError) return backoff;\n // A timeout is retried: the deadline is ours, and the next attempt gets a fresh one.\n if (error instanceof VerifNowConnectionError) return backoff;\n\n // 400, 401 and unparseable bodies will fail identically on a second attempt.\n return null;\n }\n\n async #requestOnce(\n url: string,\n body: string,\n options: RequestOptions,\n ): Promise<ValidationResult> {\n const timeoutMs = options.timeoutMs ?? this.#timeoutMs;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const abortFromCaller = () => controller.abort();\n options.signal?.addEventListener('abort', abortFromCaller, { once: true });\n\n let response: Response;\n try {\n response = await this.#fetch(url, {\n method: 'POST',\n headers: {\n ...this.#headers,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'X-API-KEY': this.#apiKey,\n 'X-VerifNow-SDK': `node/${VERSION}`,\n },\n body,\n signal: controller.signal,\n });\n } catch (cause) {\n // The caller's own cancellation is theirs to handle, not a transport failure.\n if (options.signal?.aborted) throw cause;\n\n const timedOut = controller.signal.aborted;\n throw new VerifNowConnectionError(\n timedOut\n ? `VerifNow request to ${url} timed out after ${timeoutMs}ms.`\n : `Could not reach the VerifNow API at ${url}. Check \\`baseUrl\\` and network access.`,\n { cause, timedOut },\n );\n } finally {\n clearTimeout(timer);\n options.signal?.removeEventListener('abort', abortFromCaller);\n }\n\n return this.#handleResponse(response);\n }\n\n async #handleResponse(response: Response): Promise<ValidationResult> {\n const requestId = response.headers.get('X-Request-Id') ?? undefined;\n const quota = parseQuota(response.headers);\n\n if (response.ok) {\n let payload: unknown;\n try {\n payload = await response.json();\n } catch (cause) {\n throw new VerifNowResponseError(\n 'VerifNow returned a success status with a body that is not valid JSON.',\n { status: response.status, requestId, cause },\n );\n }\n\n if (payload === null || typeof payload !== 'object') {\n throw new VerifNowResponseError(\n 'VerifNow returned an unexpected response shape.',\n { status: response.status, requestId },\n );\n }\n\n return mapResult(payload as Record<string, unknown>, quota);\n }\n\n const message = await readErrorMessage(response);\n const context = { status: response.status, requestId };\n\n if (response.status === 401 || response.status === 403) {\n throw new VerifNowAuthError(\n `VerifNow rejected the API key (${response.status}): ${message}`,\n context,\n );\n }\n\n if (response.status === 429) {\n throw new VerifNowRateLimitError(`VerifNow rate limit reached: ${message}`, {\n ...context,\n quota,\n retryAfterSeconds: parseRetryAfter(response.headers, quota),\n });\n }\n\n if (response.status >= 500) {\n throw new VerifNowServerError(\n `VerifNow returned ${response.status}: ${message}`,\n context,\n );\n }\n\n throw new VerifNowRequestError(\n `VerifNow rejected the request (${response.status}): ${message}`,\n context,\n );\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction toNumber(value: string | null): number | undefined {\n if (value === null) return undefined;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction parseQuota(headers: Headers): QuotaInfo | undefined {\n const limit = toNumber(headers.get('X-RateLimit-Limit'));\n const remaining = toNumber(headers.get('X-RateLimit-Remaining'));\n const resetSeconds = toNumber(headers.get('X-RateLimit-Reset'));\n const overage = headers.get('X-Quota-Overage') === 'true';\n\n if (\n limit === undefined &&\n remaining === undefined &&\n resetSeconds === undefined &&\n !overage\n ) {\n return undefined;\n }\n\n return {\n limit,\n remaining,\n resetAt: resetSeconds === undefined ? undefined : new Date(resetSeconds * 1000),\n overage,\n };\n}\n\nfunction parseRetryAfter(headers: Headers, quota?: QuotaInfo): number | undefined {\n const retryAfter = headers.get('Retry-After');\n if (retryAfter !== null) {\n const seconds = Number(retryAfter);\n if (Number.isFinite(seconds)) return seconds;\n\n // RFC 7231 also allows an HTTP-date.\n const asDate = Date.parse(retryAfter);\n if (!Number.isNaN(asDate)) {\n return Math.max(0, Math.ceil((asDate - Date.now()) / 1000));\n }\n }\n\n if (quota?.resetAt) {\n return Math.max(0, Math.ceil((quota.resetAt.getTime() - Date.now()) / 1000));\n }\n\n return undefined;\n}\n\nasync function readErrorMessage(response: Response): Promise<string> {\n try {\n const text = await response.text();\n if (!text) return response.statusText || 'no details';\n\n try {\n const parsed = JSON.parse(text) as Record<string, unknown>;\n const message = parsed.message ?? parsed.error;\n if (typeof message === 'string' && message !== '') return message;\n } catch {\n // Not JSON — a proxy or the servlet container's default error page.\n }\n\n return text.slice(0, 500);\n } catch {\n return response.statusText || 'no details';\n }\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction asBoolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction mapSignals(raw: unknown): EmailSignals | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const s = raw as Record<string, unknown>;\n\n return {\n syntaxValid: asBoolean(s.syntax_valid),\n mxValid: asBoolean(s.mx_valid),\n typoDetected: asBoolean(s.typo_detected),\n suggestedDomain: asString(s.suggested_domain),\n disposable: asBoolean(s.disposable),\n roleBased: asBoolean(s.role_based),\n freeProvider: asBoolean(s.free_provider),\n domainAgeDays: asNumber(s.domain_age_days),\n mxProvider: asString(s.mx_provider),\n mxQualityScore: asNumber(s.mx_quality_score),\n };\n}\n\nfunction mapEmailDetails(raw: unknown): EmailDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n signals: mapSignals(d.signals),\n riskScore: asNumber(d.risk_score),\n riskLevel: asString(d.risk_level) as EmailDetails['riskLevel'],\n deliverability: asString(d.deliverability) as EmailDetails['deliverability'],\n appliedLevel: asString(d.applied_level) as EmailDetails['appliedLevel'],\n };\n}\n\n/**\n * Reads `registered`, preserving the difference between `false` and `null`.\n *\n * `asBoolean` cannot be used here: it maps `null` to `undefined`, which would erase the one\n * distinction the whole VAT design exists to carry. `false` means the registry answered and the\n * number is not there; `null` means nobody could ask. A caller that cannot tell them apart will\n * reject real businesses whenever VIES is down.\n *\n * A missing key is read as `null` for the same reason — unknown, not absent.\n */\nfunction asRegistered(value: unknown): boolean | null {\n return typeof value === 'boolean' ? value : null;\n}\n\nfunction asDate(value: unknown): Date | undefined {\n if (typeof value !== 'string') return undefined;\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? undefined : parsed;\n}\n\nfunction mapVatDetails(raw: unknown): VatDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n formatValid: asBoolean(d.format_valid),\n registered: asRegistered(d.registered),\n countryCode: asString(d.country_code),\n source: asString(d.source) as VatDetails['source'],\n checkedAt: asDate(d.checked_at),\n traderName: asString(d.trader_name),\n traderAddress: asString(d.trader_address),\n viesAvailable: asBoolean(d.vies_available),\n consultationNumber: asString(d.consultation_number),\n };\n}\n\nfunction mapPhoneDetails(raw: unknown): PhoneDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n countryCode: asString(d.country_code),\n callingCode: asNumber(d.calling_code),\n lineType: asString(d.line_type) as PhoneDetails['lineType'],\n internationalFormat: asString(d.international_format),\n nationalFormat: asString(d.national_format),\n };\n}\n\n/**\n * Maps the API's snake_case diagnostics onto camelCase, so a TypeScript caller is not switching\n * naming conventions mid-expression. The untouched body stays available on `raw`.\n */\nfunction mapResult(\n payload: Record<string, unknown>,\n quota: QuotaInfo | undefined,\n): ValidationResult {\n return {\n valid: payload.valid === true,\n message: asString(payload.message),\n normalizedValue: asString(payload.normalizedValue) ?? null,\n originalValue: asString(payload.originalValue),\n validationLevel: asString(payload.validationLevel) as ValidationResult['validationLevel'],\n emailDetails: mapEmailDetails(payload.emailDetails),\n vatDetails: mapVatDetails(payload.vatDetails),\n phoneDetails: mapPhoneDetails(payload.phoneDetails),\n quota,\n raw: payload,\n };\n}\n","/**\n * Validation rules exposed by the VerifNow API.\n *\n * Each maps to `POST /api/v1/validate/{rule}`.\n */\nexport type ValidationRule =\n | 'email'\n | 'phone'\n | 'iban'\n | 'vat'\n | 'nas'\n | 'ssn'\n | 'nif';\n\nexport const VALIDATION_RULES: readonly ValidationRule[] = [\n 'email',\n 'phone',\n 'iban',\n 'vat',\n 'nas',\n 'ssn',\n 'nif',\n] as const;\n\n/**\n * Depth of checks applied to a request, decided by the plan attached to the API key.\n *\n * `STANDARD` runs on the FREE and STARTER plans, `ADVANCED` on GROWTH, `PREMIUM` on PRO.\n * Branch on this rather than on the plan name: it is the only value that tells you which\n * signals are actually present in the response.\n */\nexport type ValidationLevel = 'BASIC' | 'STANDARD' | 'ADVANCED' | 'PREMIUM';\n\n/** Categorical risk assessment. Returned from `ADVANCED` depth upward. */\nexport type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH';\n\nexport type Deliverability =\n | 'DELIVERABLE'\n | 'RISKY'\n | 'UNDELIVERABLE'\n | 'UNKNOWN';\n\n/**\n * Per-signal breakdown behind an email verdict.\n *\n * Fields are `undefined` when the applied level does not compute them — the last four require\n * `ADVANCED` depth or higher. Check `ValidationResult.appliedLevel` before relying on one.\n */\nexport interface EmailSignals {\n /** The address matches the syntax pattern for the applied level. */\n syntaxValid?: boolean;\n /** The domain resolves and publishes MX (or fallback A) records. */\n mxValid?: boolean;\n /** A likely typo was found in the domain, e.g. `gmail.con`. */\n typoDetected?: boolean;\n /** The correction proposed when `typoDetected` is true. */\n suggestedDomain?: string;\n /** The domain belongs to a throwaway mailbox provider. */\n disposable?: boolean;\n /** The local part is a shared mailbox: `info@`, `admin@`, `noreply@`. */\n roleBased?: boolean;\n /** The domain is a consumer mailbox provider. Requires ADVANCED. */\n freeProvider?: boolean;\n /** Estimated age of the domain in days. Requires ADVANCED. */\n domainAgeDays?: number;\n /** Identified mail provider, e.g. `google`. Requires ADVANCED. */\n mxProvider?: string;\n /** Mail server quality between 0 and 1. Requires ADVANCED. */\n mxQualityScore?: number;\n}\n\n/** Email-specific diagnostics. Absent when the applied level is `BASIC`. */\nexport interface EmailDetails {\n signals?: EmailSignals;\n /** Aggregated risk on a 0–100 scale, where 0 is the lowest risk. */\n riskScore?: number;\n /** Categorical risk. Requires ADVANCED depth or higher. */\n riskLevel?: RiskLevel;\n deliverability?: Deliverability;\n /** The depth actually applied, echoed back by the API. */\n appliedLevel?: ValidationLevel;\n}\n\n/**\n * Where a VAT registration verdict came from.\n *\n * VIES publishes no SLA and drops member states several times a month, so a VAT answer is not\n * always a live one. Branch on this rather than on `ValidationResult.valid` whenever the\n * difference matters for your own compliance.\n */\nexport type VatSource =\n /** Confirmed against VIES during this request. */\n | 'LIVE'\n /** Served from a VIES answer less than 24 hours old. */\n | 'CACHE'\n /** VIES was unreachable, so an older cached answer was used. */\n | 'STALE'\n /** VIES was unreachable and nothing was cached. Registration is unknown. */\n | 'UNVERIFIED'\n /** The country is outside VIES, so no registry lookup is possible. */\n | 'NOT_APPLICABLE';\n\n/** VAT-specific diagnostics. Present on `vat` validations. */\nexport interface VatDetails {\n /** The number matches its member state's structure. Decided locally, never depends on VIES. */\n formatValid?: boolean;\n /**\n * Present in the member state's registry.\n *\n * **`null` means unknown, never \"not registered.\"** It is returned when VIES could not be\n * consulted. Treating `null` as `false` rejects legitimate customers during someone else's\n * outage — the single most expensive mistake available in VAT validation.\n */\n registered: boolean | null;\n /** Member state the number belongs to, e.g. `IE`. Greece is `EL`, Northern Ireland `XI`. */\n countryCode?: string;\n source?: VatSource;\n /** When the registration was last confirmed against VIES. */\n checkedAt?: Date;\n /** Registered trading name, when the member state discloses it. Germany does not. */\n traderName?: string;\n /** Registered address, when the member state discloses it. */\n traderAddress?: string;\n /** Whether VIES could answer for this country during the request. */\n viesAvailable?: boolean;\n /**\n * The consultation number VIES issued for this lookup — the receipt a tax authority accepts as\n * evidence that you checked. Present only when your account has its own VAT number configured,\n * because VIES issues one only to an identified requester.\n */\n consultationNumber?: string;\n}\n\n/**\n * Kind of line, according to the country's numbering plan.\n *\n * A `PREMIUM_RATE` or `VOIP` number is still `valid` — it exists. This is how you decide to\n * exclude one, rather than the API deciding for you.\n */\nexport type PhoneLineType =\n | 'MOBILE'\n | 'FIXED_LINE'\n /** The plan does not distinguish the two — the case for the US and Canada. */\n | 'FIXED_LINE_OR_MOBILE'\n | 'TOLL_FREE'\n | 'PREMIUM_RATE'\n | 'SHARED_COST'\n | 'VOIP'\n | 'PERSONAL_NUMBER'\n | 'PAGER'\n | 'UAN'\n | 'VOICEMAIL'\n | 'UNKNOWN';\n\n/**\n * Phone-specific diagnostics. Present whenever the input parsed as an international number —\n * including when it is invalid for its country, so you can tell the user which country it was\n * read as.\n */\nexport interface PhoneDetails {\n /** ISO 3166-1 alpha-2 country, e.g. `FR`. Absent when the calling code is shared by several. */\n countryCode?: string;\n /** International calling code without the plus sign, e.g. `33`. */\n callingCode?: number;\n /** Absent when the number is invalid. */\n lineType?: PhoneLineType;\n /** e.g. `+33 6 12 34 56 78`. Absent when the number is invalid. */\n internationalFormat?: string;\n /** e.g. `06 12 34 56 78`. Absent when the number is invalid. */\n nationalFormat?: string;\n}\n\n/** Outcome of a single validation call. */\nexport interface ValidationResult {\n /** Whether the value passed every check the applied level ran. */\n valid: boolean;\n /** Human-readable explanation of the verdict. */\n message?: string;\n /** Canonical form of the input — `null` when the value is invalid. */\n normalizedValue: string | null;\n /** The value exactly as submitted. */\n originalValue?: string;\n /** Depth applied to this request. */\n validationLevel?: ValidationLevel;\n /** Present for email validations from `STANDARD` depth upward. */\n emailDetails?: EmailDetails;\n /** Present for VAT validations. */\n vatDetails?: VatDetails;\n /** Present for phone validations. The E.164 form is `normalizedValue`. */\n phoneDetails?: PhoneDetails;\n /** Quota state reported by the response headers. */\n quota?: QuotaInfo;\n /** The unmodified JSON body, for fields this SDK version does not model yet. */\n raw: Record<string, unknown>;\n}\n\n/** Quota counters read from the `X-RateLimit-*` response headers. */\nexport interface QuotaInfo {\n /** Validations included in the current billing period. */\n limit?: number;\n /** Validations left before overage or blocking. */\n remaining?: number;\n /** When the current period resets. */\n resetAt?: Date;\n /** True once you are past the included quota and into per-unit billing. */\n overage?: boolean;\n}\n\nexport interface RetryOptions {\n /**\n * Retry attempts after the first failure. Defaults to 2, so up to 3 requests in total.\n * Only connection failures, 429 and 5xx are retried — never a 400 or 401, which will not\n * succeed on a second try.\n */\n attempts?: number;\n /** Delay before the first retry, in ms. Doubles each attempt. Defaults to 200. */\n backoffMs?: number;\n /** Upper bound on a single backoff delay, in ms. Defaults to 2000. */\n maxBackoffMs?: number;\n}\n\nexport interface VerifNowOptions {\n /** API key created in the VerifNow dashboard. Sent as the `X-API-KEY` header. */\n apiKey: string;\n /** Override the API origin. Defaults to `https://api.verifnow.io`. */\n baseUrl?: string;\n /** Abort a single request after this many ms. Defaults to 5000. */\n timeoutMs?: number;\n /** Retry policy, or `false` to disable retries entirely. */\n retry?: RetryOptions | false;\n /** Extra headers merged into every request. */\n headers?: Record<string, string>;\n /**\n * Replacement for the global `fetch`, for tests or a custom agent.\n * Defaults to `globalThis.fetch`.\n */\n fetch?: typeof globalThis.fetch;\n}\n"],"mappings":";AAsBO,IAAM,gBAAN,cAA4B,MAAM;AAAA;AAAA,EAE9B;AAAA;AAAA,EAEA;AAAA,EAET,YACE,SACA,UAAoE,CAAC,GACrE;AACA,UAAM,SAAS,EAAE,OAAO,QAAQ,MAAM,CAAC;AACvC,SAAK,OAAO,WAAW;AACvB,SAAK,SAAS,QAAQ;AACtB,SAAK,YAAY,QAAQ;AACzB,UAAM,oBAAoB,MAAM,UAAU;AAAA,EAC5C;AACF;AAGO,IAAM,oBAAN,cAAgC,cAAc;AAAC;AAO/C,IAAM,uBAAN,cAAmC,cAAc;AAAC;AAGlD,IAAM,yBAAN,cAAqC,cAAc;AAAA;AAAA,EAE/C;AAAA;AAAA,EAEA;AAAA,EAET,YACE,SACA,UAMI,CAAC,GACL;AACA,UAAM,SAAS,OAAO;AACtB,SAAK,QAAQ,QAAQ;AACrB,SAAK,oBAAoB,QAAQ;AAAA,EACnC;AACF;AAGO,IAAM,sBAAN,cAAkC,cAAc;AAAC;AAQjD,IAAM,0BAAN,cAAsC,cAAc;AAAA;AAAA,EAEhD;AAAA,EAET,YACE,SACA,UAAmD,CAAC,GACpD;AACA,UAAM,SAAS,EAAE,OAAO,QAAQ,MAAM,CAAC;AACvC,SAAK,WAAW,QAAQ,YAAY;AAAA,EACtC;AACF;AAGO,IAAM,wBAAN,cAAoC,cAAc;AAAC;;;AC1FnD,IAAM,UAAU;;;ACgBvB,IAAM,mBAAmB;AACzB,IAAM,qBAAqB;AAC3B,IAAM,gBAAwC;AAAA,EAC5C,UAAU;AAAA,EACV,WAAW;AAAA,EACX,cAAc;AAChB;AA0BO,IAAM,WAAN,MAAe;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,SAA0B;AACpC,QAAI,CAAC,SAAS,UAAU,QAAQ,OAAO,KAAK,MAAM,IAAI;AACpD,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,YAAY,QAAQ,SAAS,WAAW;AAC9C,QAAI,OAAO,cAAc,YAAY;AACnC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,SAAK,UAAU,QAAQ,OAAO,KAAK;AAEnC,SAAK,YAAY,QAAQ,WAAW,kBAAkB,QAAQ,QAAQ,EAAE;AACxE,SAAK,aAAa,QAAQ,aAAa;AACvC,SAAK,SACH,QAAQ,UAAU,QAAQ,OAAO,EAAE,GAAG,eAAe,GAAI,QAAQ,SAAS,CAAC,EAAG;AAChF,SAAK,WAAW,QAAQ,WAAW,CAAC;AACpC,SAAK,SAAS,UAAU,KAAK,UAAU;AAAA,EACzC;AAAA;AAAA,EAGA,cAAc,OAAe,SAAqD;AAChF,WAAO,KAAK,SAAS,SAAS,OAAO,OAAO;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAc,OAAe,SAAqD;AAChF,WAAO,KAAK,SAAS,SAAS,OAAO,OAAO;AAAA,EAC9C;AAAA;AAAA,EAGA,aAAa,OAAe,SAAqD;AAC/E,WAAO,KAAK,SAAS,QAAQ,OAAO,OAAO;AAAA,EAC7C;AAAA;AAAA,EAGA,YAAY,OAAe,SAAqD;AAC9E,WAAO,KAAK,SAAS,OAAO,OAAO,OAAO;AAAA,EAC5C;AAAA;AAAA,EAGA,YAAY,OAAe,SAAqD;AAC9E,WAAO,KAAK,SAAS,OAAO,OAAO,OAAO;AAAA,EAC5C;AAAA;AAAA,EAGA,YAAY,OAAe,SAAqD;AAC9E,WAAO,KAAK,SAAS,OAAO,OAAO,OAAO;AAAA,EAC5C;AAAA;AAAA,EAGA,YAAY,OAAe,SAAqD;AAC9E,WAAO,KAAK,SAAS,OAAO,OAAO,OAAO;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,SACJ,MACA,OACA,UAA0B,CAAC,GACA;AAC3B,QAAI,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,IAAI;AAEpD,YAAM,IAAI;AAAA,QACR,4CAA4C,IAAI;AAAA,MAClD;AAAA,IACF;AAEA,UAAM,MAAM,GAAG,KAAK,QAAQ,oBAAoB,IAAI;AACpD,UAAM,OAAO,KAAK,UAAU,EAAE,MAAM,CAAC;AACrC,UAAM,cAAc,KAAK,SAAS,KAAK,OAAO,WAAW,IAAI;AAE7D,QAAI;AAEJ,aAAS,UAAU,GAAG,UAAU,aAAa,WAAW;AACtD,UAAI;AACF,eAAO,MAAM,KAAK,aAAa,KAAK,MAAM,OAAO;AAAA,MACnD,SAAS,OAAO;AACd,YAAI,EAAE,iBAAiB,eAAgB,OAAM;AAC7C,oBAAY;AAEZ,cAAM,gBAAgB,YAAY,cAAc;AAChD,YAAI,iBAAiB,CAAC,KAAK,OAAQ,OAAM;AAEzC,cAAM,QAAQ,KAAK,YAAY,OAAO,OAAO;AAC7C,YAAI,UAAU,KAAM,OAAM;AAE1B,cAAM,MAAM,KAAK;AAAA,MACnB;AAAA,IACF;AAGA,UAAM,aAAa,IAAI,cAAc,gBAAgB;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,OAAsB,SAAgC;AAChE,UAAM,QAAQ,KAAK;AACnB,UAAM,UAAU,KAAK,IAAI,MAAM,YAAY,KAAK,SAAS,MAAM,YAAY;AAE3E,QAAI,iBAAiB,wBAAwB;AAC3C,YAAM,UAAU,MAAM,qBAAqB,KAAK;AAChD,UAAI,SAAS,MAAM,aAAc,QAAO;AACxC,aAAO,KAAK,IAAI,QAAQ,OAAO;AAAA,IACjC;AAEA,QAAI,iBAAiB,oBAAqB,QAAO;AAEjD,QAAI,iBAAiB,wBAAyB,QAAO;AAGrD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,aACJ,KACA,MACA,SAC2B;AAC3B,UAAM,YAAY,QAAQ,aAAa,KAAK;AAC5C,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,SAAS;AAC5D,UAAM,kBAAkB,MAAM,WAAW,MAAM;AAC/C,YAAQ,QAAQ,iBAAiB,SAAS,iBAAiB,EAAE,MAAM,KAAK,CAAC;AAEzE,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,KAAK,OAAO,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,GAAG,KAAK;AAAA,UACR,gBAAgB;AAAA,UAChB,QAAQ;AAAA,UACR,aAAa,KAAK;AAAA,UAClB,kBAAkB,QAAQ,OAAO;AAAA,QACnC;AAAA,QACA;AAAA,QACA,QAAQ,WAAW;AAAA,MACrB,CAAC;AAAA,IACH,SAAS,OAAO;AAEd,UAAI,QAAQ,QAAQ,QAAS,OAAM;AAEnC,YAAM,WAAW,WAAW,OAAO;AACnC,YAAM,IAAI;AAAA,QACR,WACI,uBAAuB,GAAG,oBAAoB,SAAS,QACvD,uCAAuC,GAAG;AAAA,QAC9C,EAAE,OAAO,SAAS;AAAA,MACpB;AAAA,IACF,UAAE;AACA,mBAAa,KAAK;AAClB,cAAQ,QAAQ,oBAAoB,SAAS,eAAe;AAAA,IAC9D;AAEA,WAAO,KAAK,gBAAgB,QAAQ;AAAA,EACtC;AAAA,EAEA,MAAM,gBAAgB,UAA+C;AACnE,UAAM,YAAY,SAAS,QAAQ,IAAI,cAAc,KAAK;AAC1D,UAAM,QAAQ,WAAW,SAAS,OAAO;AAEzC,QAAI,SAAS,IAAI;AACf,UAAI;AACJ,UAAI;AACF,kBAAU,MAAM,SAAS,KAAK;AAAA,MAChC,SAAS,OAAO;AACd,cAAM,IAAI;AAAA,UACR;AAAA,UACA,EAAE,QAAQ,SAAS,QAAQ,WAAW,MAAM;AAAA,QAC9C;AAAA,MACF;AAEA,UAAI,YAAY,QAAQ,OAAO,YAAY,UAAU;AACnD,cAAM,IAAI;AAAA,UACR;AAAA,UACA,EAAE,QAAQ,SAAS,QAAQ,UAAU;AAAA,QACvC;AAAA,MACF;AAEA,aAAO,UAAU,SAAoC,KAAK;AAAA,IAC5D;AAEA,UAAM,UAAU,MAAM,iBAAiB,QAAQ;AAC/C,UAAM,UAAU,EAAE,QAAQ,SAAS,QAAQ,UAAU;AAErD,QAAI,SAAS,WAAW,OAAO,SAAS,WAAW,KAAK;AACtD,YAAM,IAAI;AAAA,QACR,kCAAkC,SAAS,MAAM,MAAM,OAAO;AAAA,QAC9D;AAAA,MACF;AAAA,IACF;AAEA,QAAI,SAAS,WAAW,KAAK;AAC3B,YAAM,IAAI,uBAAuB,gCAAgC,OAAO,IAAI;AAAA,QAC1E,GAAG;AAAA,QACH;AAAA,QACA,mBAAmB,gBAAgB,SAAS,SAAS,KAAK;AAAA,MAC5D,CAAC;AAAA,IACH;AAEA,QAAI,SAAS,UAAU,KAAK;AAC1B,YAAM,IAAI;AAAA,QACR,qBAAqB,SAAS,MAAM,KAAK,OAAO;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,IAAI;AAAA,MACR,kCAAkC,SAAS,MAAM,MAAM,OAAO;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAEA,SAAS,SAAS,OAA0C;AAC1D,MAAI,UAAU,KAAM,QAAO;AAC3B,QAAM,SAAS,OAAO,KAAK;AAC3B,SAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC5C;AAEA,SAAS,WAAW,SAAyC;AAC3D,QAAM,QAAQ,SAAS,QAAQ,IAAI,mBAAmB,CAAC;AACvD,QAAM,YAAY,SAAS,QAAQ,IAAI,uBAAuB,CAAC;AAC/D,QAAM,eAAe,SAAS,QAAQ,IAAI,mBAAmB,CAAC;AAC9D,QAAM,UAAU,QAAQ,IAAI,iBAAiB,MAAM;AAEnD,MACE,UAAU,UACV,cAAc,UACd,iBAAiB,UACjB,CAAC,SACD;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,iBAAiB,SAAY,SAAY,IAAI,KAAK,eAAe,GAAI;AAAA,IAC9E;AAAA,EACF;AACF;AAEA,SAAS,gBAAgB,SAAkB,OAAuC;AAChF,QAAM,aAAa,QAAQ,IAAI,aAAa;AAC5C,MAAI,eAAe,MAAM;AACvB,UAAM,UAAU,OAAO,UAAU;AACjC,QAAI,OAAO,SAAS,OAAO,EAAG,QAAO;AAGrC,UAAMA,UAAS,KAAK,MAAM,UAAU;AACpC,QAAI,CAAC,OAAO,MAAMA,OAAM,GAAG;AACzB,aAAO,KAAK,IAAI,GAAG,KAAK,MAAMA,UAAS,KAAK,IAAI,KAAK,GAAI,CAAC;AAAA,IAC5D;AAAA,EACF;AAEA,MAAI,OAAO,SAAS;AAClB,WAAO,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,QAAQ,QAAQ,IAAI,KAAK,IAAI,KAAK,GAAI,CAAC;AAAA,EAC7E;AAEA,SAAO;AACT;AAEA,eAAe,iBAAiB,UAAqC;AACnE,MAAI;AACF,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,CAAC,KAAM,QAAO,SAAS,cAAc;AAEzC,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,YAAM,UAAU,OAAO,WAAW,OAAO;AACzC,UAAI,OAAO,YAAY,YAAY,YAAY,GAAI,QAAO;AAAA,IAC5D,QAAQ;AAAA,IAER;AAEA,WAAO,KAAK,MAAM,GAAG,GAAG;AAAA,EAC1B,QAAQ;AACN,WAAO,SAAS,cAAc;AAAA,EAChC;AACF;AAEA,SAAS,SAAS,OAAoC;AACpD,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAEA,SAAS,UAAU,OAAqC;AACtD,SAAO,OAAO,UAAU,YAAY,QAAQ;AAC9C;AAEA,SAAS,SAAS,OAAoC;AACpD,SAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ;AACvE;AAEA,SAAS,WAAW,KAAwC;AAC1D,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,QAAM,IAAI;AAEV,SAAO;AAAA,IACL,aAAa,UAAU,EAAE,YAAY;AAAA,IACrC,SAAS,UAAU,EAAE,QAAQ;AAAA,IAC7B,cAAc,UAAU,EAAE,aAAa;AAAA,IACvC,iBAAiB,SAAS,EAAE,gBAAgB;AAAA,IAC5C,YAAY,UAAU,EAAE,UAAU;AAAA,IAClC,WAAW,UAAU,EAAE,UAAU;AAAA,IACjC,cAAc,UAAU,EAAE,aAAa;AAAA,IACvC,eAAe,SAAS,EAAE,eAAe;AAAA,IACzC,YAAY,SAAS,EAAE,WAAW;AAAA,IAClC,gBAAgB,SAAS,EAAE,gBAAgB;AAAA,EAC7C;AACF;AAEA,SAAS,gBAAgB,KAAwC;AAC/D,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,QAAM,IAAI;AAEV,SAAO;AAAA,IACL,SAAS,WAAW,EAAE,OAAO;AAAA,IAC7B,WAAW,SAAS,EAAE,UAAU;AAAA,IAChC,WAAW,SAAS,EAAE,UAAU;AAAA,IAChC,gBAAgB,SAAS,EAAE,cAAc;AAAA,IACzC,cAAc,SAAS,EAAE,aAAa;AAAA,EACxC;AACF;AAYA,SAAS,aAAa,OAAgC;AACpD,SAAO,OAAO,UAAU,YAAY,QAAQ;AAC9C;AAEA,SAAS,OAAO,OAAkC;AAChD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,SAAS,IAAI,KAAK,KAAK;AAC7B,SAAO,OAAO,MAAM,OAAO,QAAQ,CAAC,IAAI,SAAY;AACtD;AAEA,SAAS,cAAc,KAAsC;AAC3D,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,QAAM,IAAI;AAEV,SAAO;AAAA,IACL,aAAa,UAAU,EAAE,YAAY;AAAA,IACrC,YAAY,aAAa,EAAE,UAAU;AAAA,IACrC,aAAa,SAAS,EAAE,YAAY;AAAA,IACpC,QAAQ,SAAS,EAAE,MAAM;AAAA,IACzB,WAAW,OAAO,EAAE,UAAU;AAAA,IAC9B,YAAY,SAAS,EAAE,WAAW;AAAA,IAClC,eAAe,SAAS,EAAE,cAAc;AAAA,IACxC,eAAe,UAAU,EAAE,cAAc;AAAA,IACzC,oBAAoB,SAAS,EAAE,mBAAmB;AAAA,EACpD;AACF;AAEA,SAAS,gBAAgB,KAAwC;AAC/D,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,QAAM,IAAI;AAEV,SAAO;AAAA,IACL,aAAa,SAAS,EAAE,YAAY;AAAA,IACpC,aAAa,SAAS,EAAE,YAAY;AAAA,IACpC,UAAU,SAAS,EAAE,SAAS;AAAA,IAC9B,qBAAqB,SAAS,EAAE,oBAAoB;AAAA,IACpD,gBAAgB,SAAS,EAAE,eAAe;AAAA,EAC5C;AACF;AAMA,SAAS,UACP,SACA,OACkB;AAClB,SAAO;AAAA,IACL,OAAO,QAAQ,UAAU;AAAA,IACzB,SAAS,SAAS,QAAQ,OAAO;AAAA,IACjC,iBAAiB,SAAS,QAAQ,eAAe,KAAK;AAAA,IACtD,eAAe,SAAS,QAAQ,aAAa;AAAA,IAC7C,iBAAiB,SAAS,QAAQ,eAAe;AAAA,IACjD,cAAc,gBAAgB,QAAQ,YAAY;AAAA,IAClD,YAAY,cAAc,QAAQ,UAAU;AAAA,IAC5C,cAAc,gBAAgB,QAAQ,YAAY;AAAA,IAClD;AAAA,IACA,KAAK;AAAA,EACP;AACF;;;ACldO,IAAM,mBAA8C;AAAA,EACzD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":["asDate"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts","../src/types.ts"],"sourcesContent":["import type { QuotaInfo } from './types.js';\n\n/**\n * Base class for every error this SDK throws.\n *\n * The SDK fails loudly on purpose. A validation client that swallows a network failure and\n * reports `valid: true` turns an outage into silently accepted bad data, and the outage stays\n * invisible until someone audits the database. Catch these and decide explicitly — accepting the\n * input on failure is a reasonable choice, but it should be a choice.\n *\n * @example\n * ```ts\n * try {\n * const result = await client.validateEmail(input);\n * return result.valid;\n * } catch (error) {\n * if (error instanceof VerifNowRateLimitError) throw error; // back-pressure, do not swallow\n * logger.warn({ error }, 'VerifNow unavailable, accepting input unverified');\n * return true;\n * }\n * ```\n */\nexport class VerifNowError extends Error {\n /** HTTP status, when the failure came back from the API rather than the network. */\n readonly status?: number;\n /** Correlation id from the `X-Request-Id` response header, useful in support requests. */\n readonly requestId?: string;\n\n constructor(\n message: string,\n options: { status?: number; requestId?: string; cause?: unknown } = {},\n ) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.status = options.status;\n this.requestId = options.requestId;\n Error.captureStackTrace?.(this, new.target);\n }\n}\n\n/** The API key is missing, malformed, revoked, or not authorised for this endpoint (401/403). */\nexport class VerifNowAuthError extends VerifNowError {}\n\n/**\n * The request was rejected as malformed (400).\n *\n * Retrying is pointless — the payload itself needs to change.\n */\nexport class VerifNowRequestError extends VerifNowError {}\n\n/** The monthly quota or the concurrency limit was exceeded (429). */\nexport class VerifNowRateLimitError extends VerifNowError {\n /** Quota counters from the response headers, when present. */\n readonly quota?: QuotaInfo;\n /** Seconds to wait before retrying, derived from `Retry-After` or `X-RateLimit-Reset`. */\n readonly retryAfterSeconds?: number;\n\n constructor(\n message: string,\n options: {\n status?: number;\n requestId?: string;\n cause?: unknown;\n quota?: QuotaInfo;\n retryAfterSeconds?: number;\n } = {},\n ) {\n super(message, options);\n this.quota = options.quota;\n this.retryAfterSeconds = options.retryAfterSeconds;\n }\n}\n\n/** The API failed to process the request (5xx). Retried automatically before surfacing. */\nexport class VerifNowServerError extends VerifNowError {}\n\n/**\n * The API could not be reached at all: DNS failure, refused connection, TLS error, or the\n * request exceeded `timeoutMs`.\n *\n * A wrong `baseUrl` surfaces here, which is why it names the URL it tried.\n */\nexport class VerifNowConnectionError extends VerifNowError {\n /** True when the failure was the client-side timeout rather than a transport error. */\n readonly timedOut: boolean;\n\n constructor(\n message: string,\n options: { cause?: unknown; timedOut?: boolean } = {},\n ) {\n super(message, { cause: options.cause });\n this.timedOut = options.timedOut ?? false;\n }\n}\n\n/** The API returned a success status with a body this SDK could not parse. */\nexport class VerifNowResponseError extends VerifNowError {}\n","/**\n * SDK version, sent to the API as `X-VerifNow-SDK: node/<version>` so calls made through an\n * official SDK can be told apart from hand-rolled integrations.\n *\n * Kept in sync with `package.json` by a test — bump both together.\n */\nexport const VERSION = '1.4.0';\n","import {\n VerifNowAuthError,\n VerifNowConnectionError,\n VerifNowError,\n VerifNowRateLimitError,\n VerifNowRequestError,\n VerifNowResponseError,\n VerifNowServerError,\n} from './errors.js';\nimport type {\n EmailDetails,\n EmailSignals,\n IbanDetails,\n NasDetails,\n PhoneDetails,\n QuotaInfo,\n RetryOptions,\n ValidationResult,\n ValidationRule,\n VatDetails,\n VerifNowOptions,\n} from './types.js';\nimport { VERSION } from './version.js';\n\nconst DEFAULT_BASE_URL = 'https://api.verifnow.io';\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst DEFAULT_RETRY: Required<RetryOptions> = {\n attempts: 2,\n backoffMs: 200,\n maxBackoffMs: 2_000,\n};\n\n/** Per-call overrides. */\nexport interface RequestOptions {\n /** Override the client timeout for this call. */\n timeoutMs?: number;\n /** Cancel the call from your own controller. Combined with the timeout. */\n signal?: AbortSignal;\n}\n\n/**\n * Client for the VerifNow validation API.\n *\n * @example\n * ```ts\n * import { VerifNow } from '@verifnow/sdk';\n *\n * const client = new VerifNow({ apiKey: process.env.VERIFNOW_API_KEY! });\n * const result = await client.validateEmail('user@example.com');\n *\n * if (!result.valid) console.log(result.message);\n * if (result.emailDetails?.signals?.typoDetected) {\n * console.log('Did you mean', result.emailDetails.signals.suggestedDomain);\n * }\n * ```\n */\nexport class VerifNow {\n readonly #apiKey: string;\n readonly #baseUrl: string;\n readonly #timeoutMs: number;\n readonly #retry: Required<RetryOptions> | null;\n readonly #headers: Record<string, string>;\n readonly #fetch: typeof globalThis.fetch;\n\n constructor(options: VerifNowOptions) {\n if (!options?.apiKey || options.apiKey.trim() === '') {\n throw new VerifNowError(\n 'A VerifNow API key is required. Create one in the dashboard and pass it as `apiKey`.',\n );\n }\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new VerifNowError(\n 'No global fetch available. Use Node 18 or later, or pass a `fetch` implementation.',\n );\n }\n\n this.#apiKey = options.apiKey.trim();\n // Trailing slashes would produce `//api/v1/...`, which some proxies reject.\n this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.#retry =\n options.retry === false ? null : { ...DEFAULT_RETRY, ...(options.retry ?? {}) };\n this.#headers = options.headers ?? {};\n this.#fetch = fetchImpl.bind(globalThis);\n }\n\n /** Validate an email address: syntax, DNS/MX, typo, disposable, role-based, quality score. */\n validateEmail(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('email', value, options);\n }\n\n /**\n * Validate a phone number against its country's numbering plan.\n *\n * The number must include its country code (`+33…` or `0033…`). Valid numbers come back in\n * E.164 as `normalizedValue`, with country and line type in `phoneDetails`.\n */\n validatePhone(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('phone', value, options);\n }\n\n /**\n * Validate an IBAN against the SWIFT registry entry for its country, then its check digits.\n *\n * `ibanDetails` reports the two separately: check digits catch a typo, the registry catches an\n * account number that could never exist in that country.\n */\n validateIban(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('iban', value, options);\n }\n\n /** Validate a VAT number. */\n validateVat(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('vat', value, options);\n }\n\n /**\n * Validate a Canadian Social Insurance Number: format and Luhn check digit.\n *\n * `nasDetails` flags a temporary resident's number (it expires with their permit) and numbers\n * from series not issued to individuals. Only collect a SIN where the law requires it.\n */\n validateNas(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nas', value, options);\n }\n\n /** Validate a US Social Security Number. */\n validateSsn(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('ssn', value, options);\n }\n\n /** Validate a Spanish/Portuguese NIF. */\n validateNif(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nif', value, options);\n }\n\n /**\n * Validate a value against any rule.\n *\n * The typed helpers above call this. Use it directly when the rule is chosen at runtime.\n */\n async validate(\n rule: ValidationRule,\n value: string,\n options: RequestOptions = {},\n ): Promise<ValidationResult> {\n if (typeof value !== 'string' || value.trim() === '') {\n // Caught here rather than server-side: an empty value consumes quota and can only fail.\n throw new VerifNowRequestError(\n `Cannot validate an empty value for rule \"${rule}\".`,\n );\n }\n\n const url = `${this.#baseUrl}/api/v1/validate/${rule}`;\n const body = JSON.stringify({ value });\n const maxAttempts = this.#retry ? this.#retry.attempts + 1 : 1;\n\n let lastError: VerifNowError | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await this.#requestOnce(url, body, options);\n } catch (error) {\n if (!(error instanceof VerifNowError)) throw error;\n lastError = error;\n\n const isLastAttempt = attempt === maxAttempts - 1;\n if (isLastAttempt || !this.#retry) throw error;\n\n const delay = this.#retryDelay(error, attempt);\n if (delay === null) throw error;\n\n await sleep(delay);\n }\n }\n\n /* c8 ignore next -- the loop either returns or throws */\n throw lastError ?? new VerifNowError('Request failed');\n }\n\n /**\n * How long to wait before retrying, or `null` when the error should surface immediately.\n *\n * A 429 is retried only when the reset is close: the concurrency limit clears in\n * milliseconds, but a spent monthly quota does not, and sleeping on it helps nobody.\n */\n #retryDelay(error: VerifNowError, attempt: number): number | null {\n const retry = this.#retry!;\n const backoff = Math.min(retry.backoffMs * 2 ** attempt, retry.maxBackoffMs);\n\n if (error instanceof VerifNowRateLimitError) {\n const waitMs = (error.retryAfterSeconds ?? 0) * 1000;\n if (waitMs > retry.maxBackoffMs) return null;\n return Math.max(waitMs, backoff);\n }\n\n if (error instanceof VerifNowServerError) return backoff;\n // A timeout is retried: the deadline is ours, and the next attempt gets a fresh one.\n if (error instanceof VerifNowConnectionError) return backoff;\n\n // 400, 401 and unparseable bodies will fail identically on a second attempt.\n return null;\n }\n\n async #requestOnce(\n url: string,\n body: string,\n options: RequestOptions,\n ): Promise<ValidationResult> {\n const timeoutMs = options.timeoutMs ?? this.#timeoutMs;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const abortFromCaller = () => controller.abort();\n options.signal?.addEventListener('abort', abortFromCaller, { once: true });\n\n let response: Response;\n try {\n response = await this.#fetch(url, {\n method: 'POST',\n headers: {\n ...this.#headers,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'X-API-KEY': this.#apiKey,\n 'X-VerifNow-SDK': `node/${VERSION}`,\n },\n body,\n signal: controller.signal,\n });\n } catch (cause) {\n // The caller's own cancellation is theirs to handle, not a transport failure.\n if (options.signal?.aborted) throw cause;\n\n const timedOut = controller.signal.aborted;\n throw new VerifNowConnectionError(\n timedOut\n ? `VerifNow request to ${url} timed out after ${timeoutMs}ms.`\n : `Could not reach the VerifNow API at ${url}. Check \\`baseUrl\\` and network access.`,\n { cause, timedOut },\n );\n } finally {\n clearTimeout(timer);\n options.signal?.removeEventListener('abort', abortFromCaller);\n }\n\n return this.#handleResponse(response);\n }\n\n async #handleResponse(response: Response): Promise<ValidationResult> {\n const requestId = response.headers.get('X-Request-Id') ?? undefined;\n const quota = parseQuota(response.headers);\n\n if (response.ok) {\n let payload: unknown;\n try {\n payload = await response.json();\n } catch (cause) {\n throw new VerifNowResponseError(\n 'VerifNow returned a success status with a body that is not valid JSON.',\n { status: response.status, requestId, cause },\n );\n }\n\n if (payload === null || typeof payload !== 'object') {\n throw new VerifNowResponseError(\n 'VerifNow returned an unexpected response shape.',\n { status: response.status, requestId },\n );\n }\n\n return mapResult(payload as Record<string, unknown>, quota);\n }\n\n const message = await readErrorMessage(response);\n const context = { status: response.status, requestId };\n\n if (response.status === 401 || response.status === 403) {\n throw new VerifNowAuthError(\n `VerifNow rejected the API key (${response.status}): ${message}`,\n context,\n );\n }\n\n if (response.status === 429) {\n throw new VerifNowRateLimitError(`VerifNow rate limit reached: ${message}`, {\n ...context,\n quota,\n retryAfterSeconds: parseRetryAfter(response.headers, quota),\n });\n }\n\n if (response.status >= 500) {\n throw new VerifNowServerError(\n `VerifNow returned ${response.status}: ${message}`,\n context,\n );\n }\n\n throw new VerifNowRequestError(\n `VerifNow rejected the request (${response.status}): ${message}`,\n context,\n );\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction toNumber(value: string | null): number | undefined {\n if (value === null) return undefined;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction parseQuota(headers: Headers): QuotaInfo | undefined {\n const limit = toNumber(headers.get('X-RateLimit-Limit'));\n const remaining = toNumber(headers.get('X-RateLimit-Remaining'));\n const resetSeconds = toNumber(headers.get('X-RateLimit-Reset'));\n const overage = headers.get('X-Quota-Overage') === 'true';\n\n if (\n limit === undefined &&\n remaining === undefined &&\n resetSeconds === undefined &&\n !overage\n ) {\n return undefined;\n }\n\n return {\n limit,\n remaining,\n resetAt: resetSeconds === undefined ? undefined : new Date(resetSeconds * 1000),\n overage,\n };\n}\n\nfunction parseRetryAfter(headers: Headers, quota?: QuotaInfo): number | undefined {\n const retryAfter = headers.get('Retry-After');\n if (retryAfter !== null) {\n const seconds = Number(retryAfter);\n if (Number.isFinite(seconds)) return seconds;\n\n // RFC 7231 also allows an HTTP-date.\n const asDate = Date.parse(retryAfter);\n if (!Number.isNaN(asDate)) {\n return Math.max(0, Math.ceil((asDate - Date.now()) / 1000));\n }\n }\n\n if (quota?.resetAt) {\n return Math.max(0, Math.ceil((quota.resetAt.getTime() - Date.now()) / 1000));\n }\n\n return undefined;\n}\n\nasync function readErrorMessage(response: Response): Promise<string> {\n try {\n const text = await response.text();\n if (!text) return response.statusText || 'no details';\n\n try {\n const parsed = JSON.parse(text) as Record<string, unknown>;\n const message = parsed.message ?? parsed.error;\n if (typeof message === 'string' && message !== '') return message;\n } catch {\n // Not JSON — a proxy or the servlet container's default error page.\n }\n\n return text.slice(0, 500);\n } catch {\n return response.statusText || 'no details';\n }\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction asBoolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction mapSignals(raw: unknown): EmailSignals | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const s = raw as Record<string, unknown>;\n\n return {\n syntaxValid: asBoolean(s.syntax_valid),\n mxValid: asBoolean(s.mx_valid),\n typoDetected: asBoolean(s.typo_detected),\n suggestedDomain: asString(s.suggested_domain),\n disposable: asBoolean(s.disposable),\n roleBased: asBoolean(s.role_based),\n freeProvider: asBoolean(s.free_provider),\n domainAgeDays: asNumber(s.domain_age_days),\n mxProvider: asString(s.mx_provider),\n mxQualityScore: asNumber(s.mx_quality_score),\n };\n}\n\nfunction mapEmailDetails(raw: unknown): EmailDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n signals: mapSignals(d.signals),\n riskScore: asNumber(d.risk_score),\n riskLevel: asString(d.risk_level) as EmailDetails['riskLevel'],\n deliverability: asString(d.deliverability) as EmailDetails['deliverability'],\n appliedLevel: asString(d.applied_level) as EmailDetails['appliedLevel'],\n };\n}\n\n/**\n * Reads `registered`, preserving the difference between `false` and `null`.\n *\n * `asBoolean` cannot be used here: it maps `null` to `undefined`, which would erase the one\n * distinction the whole VAT design exists to carry. `false` means the registry answered and the\n * number is not there; `null` means nobody could ask. A caller that cannot tell them apart will\n * reject real businesses whenever VIES is down.\n *\n * A missing key is read as `null` for the same reason — unknown, not absent.\n */\nfunction asRegistered(value: unknown): boolean | null {\n return typeof value === 'boolean' ? value : null;\n}\n\nfunction asDate(value: unknown): Date | undefined {\n if (typeof value !== 'string') return undefined;\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? undefined : parsed;\n}\n\nfunction mapVatDetails(raw: unknown): VatDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n formatValid: asBoolean(d.format_valid),\n registered: asRegistered(d.registered),\n countryCode: asString(d.country_code),\n source: asString(d.source) as VatDetails['source'],\n checkedAt: asDate(d.checked_at),\n traderName: asString(d.trader_name),\n traderAddress: asString(d.trader_address),\n viesAvailable: asBoolean(d.vies_available),\n consultationNumber: asString(d.consultation_number),\n };\n}\n\nfunction mapPhoneDetails(raw: unknown): PhoneDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n countryCode: asString(d.country_code),\n callingCode: asNumber(d.calling_code),\n lineType: asString(d.line_type) as PhoneDetails['lineType'],\n internationalFormat: asString(d.international_format),\n nationalFormat: asString(d.national_format),\n };\n}\n\nfunction mapIbanDetails(raw: unknown): IbanDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n countryCode: asString(d.country_code),\n structureValid: asBoolean(d.structure_valid),\n checksumValid: asBoolean(d.checksum_valid),\n length: asNumber(d.length),\n expectedLength: asNumber(d.expected_length),\n formatted: asString(d.formatted),\n };\n}\n\nfunction mapNasDetails(raw: unknown): NasDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n checksumValid: asBoolean(d.checksum_valid),\n temporaryResident: asBoolean(d.temporary_resident),\n individualSeries: asBoolean(d.individual_series),\n formatted: asString(d.formatted),\n };\n}\n\n/**\n * Maps the API's snake_case diagnostics onto camelCase, so a TypeScript caller is not switching\n * naming conventions mid-expression. The untouched body stays available on `raw`.\n */\nfunction mapResult(\n payload: Record<string, unknown>,\n quota: QuotaInfo | undefined,\n): ValidationResult {\n return {\n valid: payload.valid === true,\n message: asString(payload.message),\n normalizedValue: asString(payload.normalizedValue) ?? null,\n originalValue: asString(payload.originalValue),\n validationLevel: asString(payload.validationLevel) as ValidationResult['validationLevel'],\n emailDetails: mapEmailDetails(payload.emailDetails),\n vatDetails: mapVatDetails(payload.vatDetails),\n phoneDetails: mapPhoneDetails(payload.phoneDetails),\n ibanDetails: mapIbanDetails(payload.ibanDetails),\n nasDetails: mapNasDetails(payload.nasDetails),\n quota,\n raw: payload,\n };\n}\n","/**\n * Validation rules exposed by the VerifNow API.\n *\n * Each maps to `POST /api/v1/validate/{rule}`.\n */\nexport type ValidationRule =\n | 'email'\n | 'phone'\n | 'iban'\n | 'vat'\n | 'nas'\n | 'ssn'\n | 'nif';\n\nexport const VALIDATION_RULES: readonly ValidationRule[] = [\n 'email',\n 'phone',\n 'iban',\n 'vat',\n 'nas',\n 'ssn',\n 'nif',\n] as const;\n\n/**\n * Depth of checks applied to a request, decided by the plan attached to the API key.\n *\n * `STANDARD` runs on the FREE and STARTER plans, `ADVANCED` on GROWTH, `PREMIUM` on PRO.\n * Branch on this rather than on the plan name: it is the only value that tells you which\n * signals are actually present in the response.\n */\nexport type ValidationLevel = 'BASIC' | 'STANDARD' | 'ADVANCED' | 'PREMIUM';\n\n/** Categorical risk assessment. Returned from `ADVANCED` depth upward. */\nexport type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH';\n\nexport type Deliverability =\n | 'DELIVERABLE'\n | 'RISKY'\n | 'UNDELIVERABLE'\n | 'UNKNOWN';\n\n/**\n * Per-signal breakdown behind an email verdict.\n *\n * Fields are `undefined` when the applied level does not compute them — the last four require\n * `ADVANCED` depth or higher. Check `ValidationResult.appliedLevel` before relying on one.\n */\nexport interface EmailSignals {\n /** The address matches the syntax pattern for the applied level. */\n syntaxValid?: boolean;\n /** The domain resolves and publishes MX (or fallback A) records. */\n mxValid?: boolean;\n /** A likely typo was found in the domain, e.g. `gmail.con`. */\n typoDetected?: boolean;\n /** The correction proposed when `typoDetected` is true. */\n suggestedDomain?: string;\n /** The domain belongs to a throwaway mailbox provider. */\n disposable?: boolean;\n /** The local part is a shared mailbox: `info@`, `admin@`, `noreply@`. */\n roleBased?: boolean;\n /** The domain is a consumer mailbox provider. Requires ADVANCED. */\n freeProvider?: boolean;\n /** Estimated age of the domain in days. Requires ADVANCED. */\n domainAgeDays?: number;\n /** Identified mail provider, e.g. `google`. Requires ADVANCED. */\n mxProvider?: string;\n /** Mail server quality between 0 and 1. Requires ADVANCED. */\n mxQualityScore?: number;\n}\n\n/** Email-specific diagnostics. Absent when the applied level is `BASIC`. */\nexport interface EmailDetails {\n signals?: EmailSignals;\n /** Aggregated risk on a 0–100 scale, where 0 is the lowest risk. */\n riskScore?: number;\n /** Categorical risk. Requires ADVANCED depth or higher. */\n riskLevel?: RiskLevel;\n deliverability?: Deliverability;\n /** The depth actually applied, echoed back by the API. */\n appliedLevel?: ValidationLevel;\n}\n\n/**\n * Where a VAT registration verdict came from.\n *\n * VIES publishes no SLA and drops member states several times a month, so a VAT answer is not\n * always a live one. Branch on this rather than on `ValidationResult.valid` whenever the\n * difference matters for your own compliance.\n */\nexport type VatSource =\n /** Confirmed against VIES during this request. */\n | 'LIVE'\n /** Served from a VIES answer less than 24 hours old. */\n | 'CACHE'\n /** VIES was unreachable, so an older cached answer was used. */\n | 'STALE'\n /** VIES was unreachable and nothing was cached. Registration is unknown. */\n | 'UNVERIFIED'\n /** The country is outside VIES, so no registry lookup is possible. */\n | 'NOT_APPLICABLE';\n\n/** VAT-specific diagnostics. Present on `vat` validations. */\nexport interface VatDetails {\n /** The number matches its member state's structure. Decided locally, never depends on VIES. */\n formatValid?: boolean;\n /**\n * Present in the member state's registry.\n *\n * **`null` means unknown, never \"not registered.\"** It is returned when VIES could not be\n * consulted. Treating `null` as `false` rejects legitimate customers during someone else's\n * outage — the single most expensive mistake available in VAT validation.\n */\n registered: boolean | null;\n /** Member state the number belongs to, e.g. `IE`. Greece is `EL`, Northern Ireland `XI`. */\n countryCode?: string;\n source?: VatSource;\n /** When the registration was last confirmed against VIES. */\n checkedAt?: Date;\n /** Registered trading name, when the member state discloses it. Germany does not. */\n traderName?: string;\n /** Registered address, when the member state discloses it. */\n traderAddress?: string;\n /** Whether VIES could answer for this country during the request. */\n viesAvailable?: boolean;\n /**\n * The consultation number VIES issued for this lookup — the receipt a tax authority accepts as\n * evidence that you checked. Present only when your account has its own VAT number configured,\n * because VIES issues one only to an identified requester.\n */\n consultationNumber?: string;\n}\n\n/**\n * Kind of line, according to the country's numbering plan.\n *\n * A `PREMIUM_RATE` or `VOIP` number is still `valid` — it exists. This is how you decide to\n * exclude one, rather than the API deciding for you.\n */\nexport type PhoneLineType =\n | 'MOBILE'\n | 'FIXED_LINE'\n /** The plan does not distinguish the two — the case for the US and Canada. */\n | 'FIXED_LINE_OR_MOBILE'\n | 'TOLL_FREE'\n | 'PREMIUM_RATE'\n | 'SHARED_COST'\n | 'VOIP'\n | 'PERSONAL_NUMBER'\n | 'PAGER'\n | 'UAN'\n | 'VOICEMAIL'\n | 'UNKNOWN';\n\n/**\n * Phone-specific diagnostics. Present whenever the input parsed as an international number —\n * including when it is invalid for its country, so you can tell the user which country it was\n * read as.\n */\nexport interface PhoneDetails {\n /** ISO 3166-1 alpha-2 country, e.g. `FR`. Absent when the calling code is shared by several. */\n countryCode?: string;\n /** International calling code without the plus sign, e.g. `33`. */\n callingCode?: number;\n /** Absent when the number is invalid. */\n lineType?: PhoneLineType;\n /** e.g. `+33 6 12 34 56 78`. Absent when the number is invalid. */\n internationalFormat?: string;\n /** e.g. `06 12 34 56 78`. Absent when the number is invalid. */\n nationalFormat?: string;\n}\n\n/**\n * IBAN-specific diagnostics. Present on `iban` validations.\n *\n * Structure and checksum are reported separately because they fail for different reasons:\n * `structureValid` answers \"could this be an account number in that country\" (the SWIFT\n * registry's length and layout), `checksumValid` answers \"was it typed correctly\" (mod-97).\n * There is no bank name or BIC — that needs a registry the API does not hold.\n */\nexport interface IbanDetails {\n /** The IBAN's country, from its first two characters. */\n countryCode?: string;\n /** Length and character layout match the registry entry for that country. */\n structureValid?: boolean;\n /** The mod-97 check digits are correct. */\n checksumValid?: boolean;\n /** Length of the value as submitted, spaces removed. */\n length?: number;\n /** Length the registry requires for that country; absent for an unknown country. */\n expectedLength?: number;\n /** Print format, in groups of four. Present only for a valid IBAN. */\n formatted?: string;\n}\n\n/**\n * Canadian Social Insurance Number diagnostics. Present on `nas` validations.\n *\n * There is no province and no expiry date: the first digit no longer reliably identifies a\n * province, and a temporary resident's SIN expires with their permit, which only the document\n * shows.\n */\nexport interface NasDetails {\n /** The Luhn check digit is correct. */\n checksumValid?: boolean;\n /**\n * A 9-series number, issued to temporary residents. It expires with the holder's permit, and the\n * number itself does not say when — check the document.\n */\n temporaryResident?: boolean;\n /**\n * The first digit belongs to a series issued to individuals. `false` for numbers starting with\n * 0 or 8 — including 046 454 286, the government's sample number, which is why it is safe to\n * use in tests.\n */\n individualSeries?: boolean;\n /** Printed form, e.g. `046 454 286`. */\n formatted?: string;\n}\n\n/** Outcome of a single validation call. */\nexport interface ValidationResult {\n /** Whether the value passed every check the applied level ran. */\n valid: boolean;\n /** Human-readable explanation of the verdict. */\n message?: string;\n /** Canonical form of the input — `null` when the value is invalid. */\n normalizedValue: string | null;\n /** The value exactly as submitted. */\n originalValue?: string;\n /** Depth applied to this request. */\n validationLevel?: ValidationLevel;\n /** Present for email validations from `STANDARD` depth upward. */\n emailDetails?: EmailDetails;\n /** Present for VAT validations. */\n vatDetails?: VatDetails;\n /** Present for phone validations. The E.164 form is `normalizedValue`. */\n phoneDetails?: PhoneDetails;\n /** Present for IBAN validations. */\n ibanDetails?: IbanDetails;\n /** Present for Canadian SIN (`nas`) validations. */\n nasDetails?: NasDetails;\n /** Quota state reported by the response headers. */\n quota?: QuotaInfo;\n /** The unmodified JSON body, for fields this SDK version does not model yet. */\n raw: Record<string, unknown>;\n}\n\n/** Quota counters read from the `X-RateLimit-*` response headers. */\nexport interface QuotaInfo {\n /** Validations included in the current billing period. */\n limit?: number;\n /** Validations left before overage or blocking. */\n remaining?: number;\n /** When the current period resets. */\n resetAt?: Date;\n /** True once you are past the included quota and into per-unit billing. */\n overage?: boolean;\n}\n\nexport interface RetryOptions {\n /**\n * Retry attempts after the first failure. Defaults to 2, so up to 3 requests in total.\n * Only connection failures, 429 and 5xx are retried — never a 400 or 401, which will not\n * succeed on a second try.\n */\n attempts?: number;\n /** Delay before the first retry, in ms. Doubles each attempt. Defaults to 200. */\n backoffMs?: number;\n /** Upper bound on a single backoff delay, in ms. Defaults to 2000. */\n maxBackoffMs?: number;\n}\n\nexport interface VerifNowOptions {\n /** API key created in the VerifNow dashboard. Sent as the `X-API-KEY` header. */\n apiKey: string;\n /** Override the API origin. Defaults to `https://api.verifnow.io`. */\n baseUrl?: string;\n /** Abort a single request after this many ms. Defaults to 5000. */\n timeoutMs?: number;\n /** Retry policy, or `false` to disable retries entirely. */\n retry?: RetryOptions | false;\n /** Extra headers merged into every request. */\n headers?: Record<string, string>;\n /**\n * Replacement for the global `fetch`, for tests or a custom agent.\n * Defaults to `globalThis.fetch`.\n */\n fetch?: typeof globalThis.fetch;\n}\n"],"mappings":"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","names":["asDate"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verifnow/sdk",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Official Node.js SDK for the VerifNow validation API — email, phone, IBAN, VAT, SSN, SIN and NIF validation in one call.",
5
5
  "keywords": [
6
6
  "verifnow",