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
JavaScript
;
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;