UNPKG

splitwise

Version:

A TypeScript SDK for the Splitwise API.

154 lines 5.55 kB
"use strict"; /** * Parameter conversion utilities for the Splitwise API. * * The SDK accepts camelCase params (idiomatic JS) and converts them to the * snake_case + double-underscore-flattened format the Splitwise API expects. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.toSnakeCase = toSnakeCase; exports.toCamelCase = toCamelCase; exports.keysToSnakeCase = keysToSnakeCase; exports.keysToCamelCase = keysToCamelCase; exports.flattenParams = flattenParams; // --------------------------------------------------------------------------- // String case conversion // --------------------------------------------------------------------------- /** * Converts a camelCase or PascalCase string to snake_case. * Already-snake_case strings pass through unchanged. */ function toSnakeCase(str) { return str // Insert underscore between a lowercase/digit and an uppercase letter .replace(/([a-z0-9])([A-Z])/g, '$1_$2') // Insert underscore between consecutive uppercase letters followed by lowercase .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') .toLowerCase(); } /** * Converts a snake_case string to camelCase. * Already-camelCase strings pass through unchanged. */ function toCamelCase(str) { return str.replace(/_([a-z0-9])/g, (_, char) => char.toUpperCase()); } // --------------------------------------------------------------------------- // Recursive key conversion // --------------------------------------------------------------------------- /** * True only for plain Object or Object.create(null) instances. Built-in * classes (Date, Blob, URL, Map, Set, etc.) intentionally return false so * the recursive helpers don't try to walk into them and replace them with * `{}` (Object.entries returns [] for most built-ins, which would silently * destroy the value). */ function isPlainObject(value) { if (typeof value !== 'object' || value === null) return false; const proto = Object.getPrototypeOf(value); return proto === null || proto === Object.prototype; } /** * Recursively converts all object keys from camelCase to snake_case. * Plain objects and arrays are walked; everything else (primitives, * Date, Blob, URL, Map, Set, custom classes, etc.) passes through * untouched so the caller / JSON.stringify can handle them. */ function keysToSnakeCase(obj) { if (Array.isArray(obj)) { return obj.map(keysToSnakeCase); } if (!isPlainObject(obj)) { return obj; } const result = {}; for (const [key, value] of Object.entries(obj)) { result[toSnakeCase(key)] = keysToSnakeCase(value); } return result; } /** * Recursively converts all object keys from snake_case to camelCase. * Plain objects and arrays are walked; everything else passes through. */ function keysToCamelCase(obj) { if (Array.isArray(obj)) { return obj.map(keysToCamelCase); } if (!isPlainObject(obj)) { return obj; } const result = {}; for (const [key, value] of Object.entries(obj)) { result[toCamelCase(key)] = keysToCamelCase(value); } return result; } /** * Flattens nested objects/arrays into Splitwise's double-underscore notation * and converts booleans to 0/1 integers. All keys are converted to snake_case. * * Notable conversions: * - Date → ISO string (otherwise Date has no enumerable own properties and * would silently disappear) * - Blob/File → preserved as-is so multipart construction can pick them up * (the caller must check for Blob values before form-urlencoding) * - URL → string form * - Anything else non-plain-object that has a sensible `toString()` is * stringified rather than silently dropped * * Example: * { users: [{ userId: 1, paidShare: "10" }] } * → { "users__0__user_id": 1, "users__0__paid_share": "10" } */ function flattenParams(obj) { const result = {}; function walk(value, prefix) { if (value === null || value === undefined) { return; } if (typeof value === 'boolean') { result[prefix] = value ? 1 : 0; return; } if (typeof value === 'string' || typeof value === 'number') { result[prefix] = value; return; } if (value instanceof Date) { result[prefix] = value.toISOString(); return; } if (value instanceof Blob) { result[prefix] = value; return; } if (typeof URL !== 'undefined' && value instanceof URL) { result[prefix] = value.toString(); return; } if (Array.isArray(value)) { for (let i = 0; i < value.length; i++) { walk(value[i], `${prefix}__${i}`); } return; } if (isPlainObject(value)) { for (const [key, child] of Object.entries(value)) { const snakeKey = toSnakeCase(key); walk(child, prefix ? `${prefix}__${snakeKey}` : snakeKey); } return; } // Fallback: anything else (BigInt, custom class, etc.) gets stringified // rather than silently dropped. Better a "[object Foo]" in the request // than a missing field that's hard to diagnose. result[prefix] = String(value); } for (const [key, value] of Object.entries(obj)) { walk(value, toSnakeCase(key)); } return result; } //# sourceMappingURL=params.js.map