UNPKG

splitwise

Version:

A TypeScript SDK for the Splitwise API.

173 lines 7.24 kB
"use strict"; /** * Typed error hierarchy for the Splitwise SDK, following the Stripe pattern. * * SplitwiseError (base) * ├── SplitwiseApiError (HTTP errors from the API) * │ ├── SplitwiseAuthenticationError (401) * │ ├── SplitwiseForbiddenError (403) * │ ├── SplitwiseNotFoundError (404) * │ ├── SplitwiseValidationError (400) * │ ├── SplitwiseRateLimitError (429) * │ ├── SplitwiseServerError (5xx) * │ └── SplitwiseConstraintError (200 with success:false / non-empty errors) * └── SplitwiseConnectionError (network failures) */ Object.defineProperty(exports, "__esModule", { value: true }); exports.SplitwiseConnectionError = exports.SplitwiseConstraintError = exports.SplitwiseServerError = exports.SplitwiseRateLimitError = exports.SplitwiseValidationError = exports.SplitwiseNotFoundError = exports.SplitwiseForbiddenError = exports.SplitwiseAuthenticationError = exports.SplitwiseApiError = exports.SplitwiseError = void 0; exports.parseRetryAfter = parseRetryAfter; exports.createApiError = createApiError; class SplitwiseError extends Error { constructor(message) { super(message); this.name = 'SplitwiseError'; } } exports.SplitwiseError = SplitwiseError; class SplitwiseApiError extends SplitwiseError { statusCode; code; raw; constructor(statusCode, message, code, raw) { super(message); this.name = 'SplitwiseApiError'; this.statusCode = statusCode; this.code = code; this.raw = raw; } } exports.SplitwiseApiError = SplitwiseApiError; class SplitwiseAuthenticationError extends SplitwiseApiError { constructor(message, code, raw) { super(401, message, code, raw); this.name = 'SplitwiseAuthenticationError'; } } exports.SplitwiseAuthenticationError = SplitwiseAuthenticationError; class SplitwiseForbiddenError extends SplitwiseApiError { constructor(message, code, raw) { super(403, message, code, raw); this.name = 'SplitwiseForbiddenError'; } } exports.SplitwiseForbiddenError = SplitwiseForbiddenError; class SplitwiseNotFoundError extends SplitwiseApiError { constructor(message, code, raw) { super(404, message, code, raw); this.name = 'SplitwiseNotFoundError'; } } exports.SplitwiseNotFoundError = SplitwiseNotFoundError; class SplitwiseValidationError extends SplitwiseApiError { constructor(message, code, raw) { super(400, message, code, raw); this.name = 'SplitwiseValidationError'; } } exports.SplitwiseValidationError = SplitwiseValidationError; class SplitwiseRateLimitError extends SplitwiseApiError { /** * Server-suggested wait time in seconds, parsed from the Retry-After * header. Handles both delta-seconds (e.g. "120") and HTTP-date formats. * Undefined when the server didn't send the header or it was malformed. */ retryAfter; constructor(message, code, raw, retryAfter) { super(429, message, code, raw); this.name = 'SplitwiseRateLimitError'; this.retryAfter = retryAfter; } } exports.SplitwiseRateLimitError = SplitwiseRateLimitError; class SplitwiseServerError extends SplitwiseApiError { constructor(statusCode, message, code, raw) { super(statusCode, message, code, raw); this.name = 'SplitwiseServerError'; } } exports.SplitwiseServerError = SplitwiseServerError; /** * Splitwise's "destructive" endpoints (delete_*, undelete_*, add_user_to_group, * remove_user_from_group) and some create/update endpoints can return HTTP 200 * with `success: false` or a non-empty `errors` field when the operation * couldn't complete for a domain reason (e.g. trying to delete a friend with a * non-zero balance). The SDK surfaces these as a typed exception following the * Stripe model -- failures are always thrown, never returned as data. */ class SplitwiseConstraintError extends SplitwiseApiError { constructor(message, code, raw) { super(200, message, code, raw); this.name = 'SplitwiseConstraintError'; } } exports.SplitwiseConstraintError = SplitwiseConstraintError; class SplitwiseConnectionError extends SplitwiseError { cause; constructor(message, cause) { super(message); this.name = 'SplitwiseConnectionError'; this.cause = cause; } } exports.SplitwiseConnectionError = SplitwiseConnectionError; /** * Parse a Retry-After header value, returning the delay in seconds. * * Per RFC 7231 § 7.1.3 the value can be either: * - a non-negative integer (delta-seconds), e.g. "120" * - an HTTP-date, e.g. "Wed, 21 Oct 2026 07:28:00 GMT" * * Returns undefined if the value is missing, malformed, or in the past. */ function parseRetryAfter(raw, now = () => Date.now()) { if (raw === null || raw === undefined || raw.length === 0) return undefined; const trimmed = raw.trim(); // Try delta-seconds first. Use a regex to reject mixed input like "12abc" // that Number() would otherwise coerce to NaN-but-then-misleading. if (/^\d+(?:\.\d+)?$/.test(trimmed)) { const seconds = Number(trimmed); return seconds >= 0 ? seconds : undefined; } // Fall back to HTTP-date. Date.parse() is too permissive (e.g. it accepts // bare strings like "-5" as years), so we require the input to look like a // proper HTTP-date. RFC 7231 allows three formats: // IMF-fixdate : "Sun, 06 Nov 1994 08:49:37 GMT" (weekday + comma) // obsolete RFC 850: "Sunday, 06-Nov-94 08:49:37 GMT" (weekday + comma) // ANSI C asctime : "Sun Nov 6 08:49:37 1994" (weekday + space + month name) // All three start with a weekday name; the next non-space char is either // a comma or another letter (the month name in asctime). if (!/^[A-Za-z]+(?:,|\s+[A-Za-z])/.test(trimmed)) return undefined; const epochMs = Date.parse(trimmed); if (Number.isNaN(epochMs)) return undefined; const deltaSeconds = Math.max(0, Math.ceil((epochMs - now()) / 1000)); return deltaSeconds; } /** * Maps an HTTP status code to the appropriate SplitwiseApiError subclass. */ function createApiError(statusCode, message, code, raw, headers) { switch (statusCode) { case 400: return new SplitwiseValidationError(message, code, raw); case 401: return new SplitwiseAuthenticationError(message, code, raw); case 403: return new SplitwiseForbiddenError(message, code, raw); case 404: return new SplitwiseNotFoundError(message, code, raw); case 429: { const retryAfterHeader = headers?.get('retry-after'); const retryAfter = parseRetryAfter(retryAfterHeader, () => Date.now()); return new SplitwiseRateLimitError(message, code, raw, retryAfter); } default: if (statusCode >= 500 && statusCode < 600) { return new SplitwiseServerError(statusCode, message, code, raw); } return new SplitwiseApiError(statusCode, message, code, raw); } } //# sourceMappingURL=errors.js.map