UNPKG

nhb-toolbox

Version:

A versatile collection of smart, efficient, and reusable utility functions and classes for everyday development needs.

258 lines (257 loc) 11.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createControlledFormData = void 0; const guards_1 = require("../date/guards"); const non_primitives_1 = require("../guards/non-primitives"); const primitives_1 = require("../guards/primitives"); const guards_2 = require("./guards"); /** * * Utility to convert object into FormData in a controlled way. * - **N.B.** Be cautious when using this in SSR (Server-Side Rendering) environments (such as `Next.js` Server Components), as it may not work as expected. * * @param data - The source object to control and convert to FormData. * @param configs - Configuration options to control the formData. * @returns `FormData` instance containing the sanitized and transformed data. */ const createControlledFormData = (data, configs) => { const formData = new FormData(); const { stringifyNested = '*' } = configs || {}; /** - Helper to check if a key should be lowercase */ const _shouldLowercaseKeys = (key) => { return Array.isArray(configs?.lowerCaseKeys) ? configs?.lowerCaseKeys?.some((path) => key === path || key.startsWith(`${path}.`)) : configs?.lowerCaseKeys === '*'; }; /** - Helper to check if a key should be lowercase */ const _shouldLowercaseValue = (key) => { return Array.isArray(configs?.lowerCaseValues) ? configs.lowerCaseValues?.some((path) => key === path || key?.startsWith(`${path}.`)) : configs?.lowerCaseValues === '*'; }; /** - Transforms key to lowercase if needed */ const _transformKey = (key) => { return _shouldLowercaseKeys(key) ? key.toLowerCase() : key; }; /** - Helper function to check if a key matches a breakArray key. */ const _isRequiredKey = (key) => { const transformedKey = _transformKey(key); return Array.isArray(configs?.requiredKeys) ? configs?.requiredKeys?.some((path) => transformedKey === path || transformedKey?.startsWith(`${path}.`)) : configs?.requiredKeys === '*'; }; /** - Helper function to check if a key matches a dotNotation path to preserve. */ const _shouldDotNotate = (key) => { const transformedKey = _transformKey(key); return Array.isArray(configs?.dotNotateNested) ? configs.dotNotateNested.includes(transformedKey) : configs?.dotNotateNested === '*'; }; /** - Helper function to check if a key matches a stringifyNested key. */ const _shouldStringify = (key) => { const transformedKey = _transformKey(key); return Array.isArray(stringifyNested) ? stringifyNested.includes(transformedKey) : stringifyNested === '*'; }; /** - Helper function to check if a key matches a breakArray key. */ const _shouldBreakArray = (key) => { const transformedKey = _transformKey(key); return Array.isArray(configs?.breakArray) ? configs.breakArray.includes(transformedKey) : configs?.breakArray === '*'; }; /** - Helper to clean object by removing null/undefined/empty values while respecting required keys */ const _cleanObject = (obj, parentKey = '') => { return Object.entries(obj).reduce((acc, [key, value]) => { const transformedKey = _transformKey(key); const fullKey = parentKey ? `${parentKey}.${transformedKey}` : transformedKey; // * Skip ignored keys (don't include them in the cleaned object) if (configs?.ignoreKeys?.includes(fullKey)) { return acc; } const shouldKeep = (value != null && value !== '') || _isRequiredKey(fullKey) || (0, primitives_1.isNonEmptyString)(value) || (0, non_primitives_1.isValidArray)(value) || (0, non_primitives_1.isNotEmptyObject)(value); if (shouldKeep) { if ((0, non_primitives_1.isNotEmptyObject)(value)) { if ((0, guards_1.isDateLike)(value)) { acc[transformedKey] = value; } else { // * Recursively clean nested objects const cleaned = _cleanObject(value, fullKey); if (_isRequiredKey(fullKey) || (0, non_primitives_1.isNotEmptyObject)(cleaned)) { acc[transformedKey] = cleaned; } } } else { if (typeof value === 'string') { if ((0, primitives_1.isNonEmptyString)(value)) { let cleanString = value; if (configs?.trimStrings) { cleanString = cleanString?.trim(); } if (_shouldLowercaseValue(fullKey)) { cleanString = cleanString?.toLowerCase(); } acc[transformedKey] = cleanString; } else { acc[transformedKey] = value; } } else if (Array.isArray(value)) { if (_isRequiredKey(fullKey) || (0, non_primitives_1.isValidArray)(value)) { acc[transformedKey] = value; } } else if ((0, non_primitives_1.isEmptyObject)(value)) { if (_isRequiredKey(fullKey)) { acc[transformedKey] = value; } } else { acc[transformedKey] = value; } } } return acc; }, {}); }; /** * Helper function to add values to formData */ const _addToFormData = (key, value) => { const transformedKey = _transformKey(key); if ((0, guards_2.isCustomFileArray)(value)) { value?.forEach((file) => formData.append(transformedKey, file?.originFileObj)); } else if ((0, guards_2.isFileUpload)(value)) { if (value?.fileList) { value?.fileList.forEach((file) => formData.append(transformedKey, file?.originFileObj)); } else if (value?.file) { if ((0, guards_2.isCustomFile)(value?.file)) { formData.append(transformedKey, value?.file?.originFileObj); } else { formData.append(transformedKey, value?.file); } } } else if ((0, guards_2.isFileOrBlob)(value)) { formData.append(transformedKey, value); } else if ((0, guards_2.isFileList)(value)) { for (let i = 0; i < value?.length; i++) { formData.append(transformedKey, value.item(i)); } } else if (Array.isArray(value)) { if ((0, guards_2.isFileArray)(value)) { if (_shouldBreakArray(key)) { value?.forEach((item, index) => { _addToFormData(`${transformedKey}[${index}]`, item); }); } else { value?.forEach((item) => { formData.append(transformedKey, item); }); } } else if ((0, non_primitives_1.isValidArray)(value)) { if (_shouldBreakArray(key)) { value?.forEach((item, index) => { _addToFormData(`${transformedKey}[${index}]`, item); }); } else { formData.append(transformedKey, JSON.stringify(value)); } } else if (_isRequiredKey(key)) { formData.append(transformedKey, JSON.stringify(value)); } } else if ((0, guards_1.isDateLike)(value)) { formData.append(transformedKey, JSON.parse(JSON.stringify(value))); } else if ((0, non_primitives_1.isNotEmptyObject)(value)) { if (_shouldStringify(key) && !_shouldDotNotate(key)) { // * Clean object before stringifying, preserving required keys const cleanedValue = _cleanObject(value, key); if ((0, non_primitives_1.isNotEmptyObject)(cleanedValue) || _isRequiredKey(key)) { formData.append(transformedKey, JSON.stringify(cleanedValue)); } } else { Object.entries(value).forEach(([nestedKey, nestedValue]) => { _addToFormData(`${key}.${nestedKey}`, nestedValue); }); } } else { const isNotNullish = value != null && value !== ''; if (isNotNullish || _isRequiredKey(key)) { if (typeof value === 'string' && _shouldLowercaseValue(key)) { formData.append(transformedKey, value?.toLowerCase()); } else { formData.append(transformedKey, value); } } } }; /** - Helper to process object */ const _processObject = (obj, parentKey = '') => { Object.entries(obj).forEach(([key, value]) => { const transformedKey = _transformKey(key); const fullKey = parentKey ? `${parentKey}.${transformedKey}` : transformedKey; // * Skip keys that are in ignoreKeys if (configs?.ignoreKeys?.includes(fullKey)) { return; } // * Trim string values if trimStrings is enabled if (configs?.trimStrings && (0, primitives_1.isNonEmptyString)(value)) { value = value?.trim(); } // * Check if this key is preserved as dot-notation if (_shouldDotNotate(fullKey)) { _addToFormData(fullKey, value); } else if ((0, non_primitives_1.isNotEmptyObject)(value) && !_shouldStringify(fullKey)) { if ((0, guards_1.isDateLike)(value)) { _addToFormData(key, JSON.parse(JSON.stringify(value))); } else { // * Process nested objects _processObject(value, key); } } else if ((0, guards_2.isFileOrBlob)(value)) { _addToFormData(key, value); } else if ((0, guards_1.isDateLike)(value)) { _addToFormData(key, JSON.parse(JSON.stringify(value))); } else if ((0, non_primitives_1.isEmptyObject)(value)) { if (_isRequiredKey(fullKey)) { _addToFormData(key, JSON.stringify(value)); } } else { // * For other cases, just append as key-value _addToFormData(key, value); } }); }; if ((0, non_primitives_1.isNotEmptyObject)(data)) { _processObject(data); } return formData; }; exports.createControlledFormData = createControlledFormData;