nhb-toolbox 4.30.15 → 4.30.16

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/CHANGELOG.md CHANGED
@@ -4,6 +4,11 @@
4
4
 
5
5
  All notable changes to the package will be documented here.
6
6
 
7
+ ## [4.30.16] - 2026-05-24
8
+
9
+ - **Fixed** multiple issues with `createFormData`: Now handles configuration options properly and it is more reliable now.
10
+ - **Updated** `isDateLike` utility to correctly identify `Temporal` instances while making the return type `value is DateLike` to improve type-safety, previously it was returning `boolean`.
11
+
7
12
  ## [4.30.14] - 2026-05-19
8
13
 
9
14
  - **Updated** *signature* of `Chronos` plugin method `isPalindromeDate()`: now accepts `shortYear` parameter as optional (default `false`).
package/README.md CHANGED
@@ -351,17 +351,19 @@ getColorForInitial('Banana', 50); // '#00376E80' (50% opacity)
351
351
  ```typescript
352
352
  import { createFormData } from 'nhb-toolbox';
353
353
 
354
- const formData = createFormData({
354
+ const data = {
355
355
  user: {
356
356
  name: ' John Doe ',
357
357
  age: 30,
358
358
  preferences: { theme: 'dark' }
359
359
  },
360
360
  files: [file1, file2]
361
- }, {
361
+ };
362
+
363
+ const formData = createFormData(data, {
362
364
  trimStrings: true,
363
365
  lowerCaseValues: ['user.name'],
364
- dotNotateNested: ['user.preferences'],
366
+ dotNotateNested: ['user'],
365
367
  breakArray: ['files']
366
368
  });
367
369
 
@@ -53,9 +53,19 @@ function isDateLike(value) {
53
53
  (0, non_primitives_1.isFunction)(value.getClass)) {
54
54
  return true;
55
55
  }
56
+ const temporals = [
57
+ 'Instant',
58
+ 'Duration',
59
+ 'PlainDate',
60
+ 'PlainTime',
61
+ 'PlainDateTime',
62
+ 'PlainMonthDay',
63
+ 'PlainYearMonth',
64
+ 'ZonedDateTime',
65
+ ];
56
66
  if ((0, non_primitives_1.isFunction)(value.toJSON) &&
57
67
  (0, non_primitives_1.isFunction)(value.toString) &&
58
- ['PlainDate', 'ZonedDateTime', 'Instant'].includes(value.constructor?.name ?? '')) {
68
+ temporals.includes(value.constructor?.name ?? '')) {
59
69
  return true;
60
70
  }
61
71
  }
@@ -4,52 +4,62 @@ exports.createControlledFormData = void 0;
4
4
  const guards_1 = require("../date/guards");
5
5
  const non_primitives_1 = require("../guards/non-primitives");
6
6
  const primitives_1 = require("../guards/primitives");
7
+ const basics_1 = require("../string/basics");
7
8
  const guards_2 = require("./guards");
8
9
  const createControlledFormData = (data, configs) => {
10
+ if (typeof FormData === 'undefined') {
11
+ throw new Error('FormData is not available! Please make sure your environment supports FormData.');
12
+ }
9
13
  const formData = new FormData();
10
- const { stringifyNested = '*' } = configs || {};
14
+ const { stringifyNested = '*', ignoreKeys = [], breakArray, lowerCaseKeys, dotNotateNested, lowerCaseValues, requiredKeys, } = configs || {};
15
+ const _compareKeyPaths = (key, paths) => {
16
+ return paths.some((path) => key === path || key.startsWith(`${path}.`));
17
+ };
11
18
  const _shouldLowercaseKeys = (key) => {
12
- return Array.isArray(configs?.lowerCaseKeys)
13
- ? configs?.lowerCaseKeys?.some((path) => key === path || key.startsWith(`${path}.`))
14
- : configs?.lowerCaseKeys === '*';
19
+ return Array.isArray(lowerCaseKeys)
20
+ ? _compareKeyPaths(key, lowerCaseKeys)
21
+ : lowerCaseKeys === '*';
15
22
  };
16
23
  const _shouldLowercaseValue = (key) => {
17
- return Array.isArray(configs?.lowerCaseValues)
18
- ? configs.lowerCaseValues?.some((path) => key === path || key?.startsWith(`${path}.`))
19
- : configs?.lowerCaseValues === '*';
24
+ return Array.isArray(lowerCaseValues)
25
+ ? _compareKeyPaths(key, lowerCaseValues)
26
+ : lowerCaseValues === '*';
20
27
  };
21
28
  const _transformKey = (key) => {
22
- return _shouldLowercaseKeys(key) ? key.toLowerCase() : key;
29
+ return (_shouldLowercaseKeys(key) ? key.toLowerCase() : key);
30
+ };
31
+ const _parseDateLike = (value) => {
32
+ return String(JSON.parse(JSON.stringify(value)));
23
33
  };
24
34
  const _isRequiredKey = (key) => {
25
35
  const transformedKey = _transformKey(key);
26
- return Array.isArray(configs?.requiredKeys)
27
- ? configs?.requiredKeys?.some((path) => transformedKey === path || transformedKey?.startsWith(`${path}.`))
28
- : configs?.requiredKeys === '*';
36
+ return Array.isArray(requiredKeys)
37
+ ? _compareKeyPaths(transformedKey, requiredKeys)
38
+ : requiredKeys === '*';
29
39
  };
30
40
  const _shouldDotNotate = (key) => {
31
41
  const transformedKey = _transformKey(key);
32
- return Array.isArray(configs?.dotNotateNested)
33
- ? configs.dotNotateNested.includes(transformedKey)
34
- : configs?.dotNotateNested === '*';
42
+ return Array.isArray(dotNotateNested)
43
+ ? _compareKeyPaths(transformedKey, dotNotateNested)
44
+ : dotNotateNested === '*';
35
45
  };
36
46
  const _shouldStringify = (key) => {
37
47
  const transformedKey = _transformKey(key);
38
48
  return Array.isArray(stringifyNested)
39
- ? stringifyNested.includes(transformedKey)
49
+ ? _compareKeyPaths(transformedKey, stringifyNested)
40
50
  : stringifyNested === '*';
41
51
  };
42
52
  const _shouldBreakArray = (key) => {
43
53
  const transformedKey = _transformKey(key);
44
- return Array.isArray(configs?.breakArray)
45
- ? configs.breakArray.includes(transformedKey)
46
- : configs?.breakArray === '*';
54
+ return Array.isArray(breakArray)
55
+ ? _compareKeyPaths(transformedKey, breakArray)
56
+ : breakArray === '*';
47
57
  };
48
58
  const _cleanObject = (obj, parentKey = '') => {
49
59
  return Object.entries(obj).reduce((acc, [key, value]) => {
50
60
  const transformedKey = _transformKey(key);
51
61
  const fullKey = parentKey ? `${parentKey}.${transformedKey}` : transformedKey;
52
- if (configs?.ignoreKeys?.includes(fullKey)) {
62
+ if (_compareKeyPaths(fullKey, ignoreKeys)) {
53
63
  return acc;
54
64
  }
55
65
  const shouldKeep = (value != null && value !== '') ||
@@ -58,7 +68,10 @@ const createControlledFormData = (data, configs) => {
58
68
  (0, non_primitives_1.isValidArray)(value) ||
59
69
  (0, non_primitives_1.isNotEmptyObject)(value);
60
70
  if (shouldKeep) {
61
- if ((0, non_primitives_1.isNotEmptyObject)(value)) {
71
+ if ((0, guards_1.isDateLike)(value)) {
72
+ acc[transformedKey] = value;
73
+ }
74
+ else if ((0, non_primitives_1.isNotEmptyObject)(value)) {
62
75
  if ((0, guards_1.isDateLike)(value)) {
63
76
  acc[transformedKey] = value;
64
77
  }
@@ -74,7 +87,7 @@ const createControlledFormData = (data, configs) => {
74
87
  if ((0, primitives_1.isNonEmptyString)(value)) {
75
88
  let cleanString = value;
76
89
  if (configs?.trimStrings) {
77
- cleanString = cleanString?.trim();
90
+ cleanString = (0, basics_1.trimString)(cleanString);
78
91
  }
79
92
  if (_shouldLowercaseValue(fullKey)) {
80
93
  cleanString = cleanString?.toLowerCase();
@@ -105,6 +118,9 @@ const createControlledFormData = (data, configs) => {
105
118
  };
106
119
  const _addToFormData = (key, value) => {
107
120
  const transformedKey = _transformKey(key);
121
+ if (_compareKeyPaths(transformedKey, ignoreKeys)) {
122
+ return;
123
+ }
108
124
  if ((0, guards_2.isCustomFileArray)(value)) {
109
125
  value?.forEach((file) => formData.append(transformedKey, file?.originFileObj));
110
126
  }
@@ -157,7 +173,7 @@ const createControlledFormData = (data, configs) => {
157
173
  }
158
174
  }
159
175
  else if ((0, guards_1.isDateLike)(value)) {
160
- formData.append(transformedKey, JSON.parse(JSON.stringify(value)));
176
+ formData.append(transformedKey, _parseDateLike(value));
161
177
  }
162
178
  else if ((0, non_primitives_1.isNotEmptyObject)(value)) {
163
179
  if (_shouldStringify(key) && !_shouldDotNotate(key)) {
@@ -175,8 +191,15 @@ const createControlledFormData = (data, configs) => {
175
191
  else {
176
192
  const isNotNullish = value != null && value !== '';
177
193
  if (isNotNullish || _isRequiredKey(key)) {
178
- if ((0, primitives_1.isString)(value) && _shouldLowercaseValue(key)) {
179
- formData.append(transformedKey, value?.toLowerCase());
194
+ if ((0, primitives_1.isString)(value)) {
195
+ let processedValue = value;
196
+ if (configs?.trimStrings) {
197
+ processedValue = (0, basics_1.trimString)(processedValue);
198
+ }
199
+ if (_shouldLowercaseValue(key)) {
200
+ processedValue = processedValue.toLowerCase();
201
+ }
202
+ formData.append(transformedKey, processedValue);
180
203
  }
181
204
  else {
182
205
  formData.append(transformedKey, value);
@@ -188,18 +211,18 @@ const createControlledFormData = (data, configs) => {
188
211
  Object.entries(obj).forEach(([key, value]) => {
189
212
  const transformedKey = _transformKey(key);
190
213
  const fullKey = parentKey ? `${parentKey}.${transformedKey}` : transformedKey;
191
- if (configs?.ignoreKeys?.includes(fullKey)) {
214
+ if (_compareKeyPaths(fullKey, ignoreKeys)) {
192
215
  return;
193
216
  }
194
217
  if (configs?.trimStrings && (0, primitives_1.isNonEmptyString)(value)) {
195
- value = value?.trim();
218
+ value = (0, basics_1.trimString)(value);
196
219
  }
197
220
  if (_shouldDotNotate(fullKey)) {
198
221
  _addToFormData(fullKey, value);
199
222
  }
200
223
  else if ((0, non_primitives_1.isNotEmptyObject)(value) && !_shouldStringify(fullKey)) {
201
224
  if ((0, guards_1.isDateLike)(value)) {
202
- _addToFormData(key, JSON.parse(JSON.stringify(value)));
225
+ _addToFormData(key, _parseDateLike(value));
203
226
  }
204
227
  else {
205
228
  _processObject(value, key);
@@ -209,7 +232,7 @@ const createControlledFormData = (data, configs) => {
209
232
  _addToFormData(key, value);
210
233
  }
211
234
  else if ((0, guards_1.isDateLike)(value)) {
212
- _addToFormData(key, JSON.parse(JSON.stringify(value)));
235
+ _addToFormData(key, _parseDateLike(value));
213
236
  }
214
237
  else if ((0, non_primitives_1.isEmptyObject)(value)) {
215
238
  if (_isRequiredKey(fullKey)) {
@@ -1,5 +1,5 @@
1
1
  import type { Numeric } from '../types/index';
2
- import type { $TimeZoneIdentifier, ClockTime, TimeWithUnit, TimeZoneIdNative, UTCOffset } from './types';
2
+ import type { $TimeZoneIdentifier, ClockTime, DateLike, TimeWithUnit, TimeZoneIdNative, UTCOffset } from './types';
3
3
  /**
4
4
  * * Checks if the provided value is a valid time string in "HH:MM" format.
5
5
  *
@@ -51,6 +51,6 @@ export declare function isLeapYear(year: Numeric): boolean;
51
51
  * @param value Value to check if it is date-like object.
52
52
  * @returns `true` if the value is date-like object, otherwise `false`.
53
53
  */
54
- export declare function isDateLike(value: unknown): boolean;
54
+ export declare function isDateLike(value: unknown): value is DateLike;
55
55
  /** Checks if a value represents time value (number) with different forms of {@link TimeWithUnit units} */
56
56
  export declare function isTimeWithUnit(value: unknown): value is TimeWithUnit;
@@ -44,9 +44,19 @@ export function isDateLike(value) {
44
44
  isFunction(value.getClass)) {
45
45
  return true;
46
46
  }
47
+ const temporals = [
48
+ 'Instant',
49
+ 'Duration',
50
+ 'PlainDate',
51
+ 'PlainTime',
52
+ 'PlainDateTime',
53
+ 'PlainMonthDay',
54
+ 'PlainYearMonth',
55
+ 'ZonedDateTime',
56
+ ];
47
57
  if (isFunction(value.toJSON) &&
48
58
  isFunction(value.toString) &&
49
- ['PlainDate', 'ZonedDateTime', 'Instant'].includes(value.constructor?.name ?? '')) {
59
+ temporals.includes(value.constructor?.name ?? '')) {
50
60
  return true;
51
61
  }
52
62
  }
@@ -1,52 +1,62 @@
1
1
  import { isDateLike } from '../date/guards.js';
2
2
  import { isEmptyObject, isNotEmptyObject, isValidArray } from '../guards/non-primitives.js';
3
3
  import { isNonEmptyString, isString } from '../guards/primitives.js';
4
+ import { trimString } from '../string/basics.js';
4
5
  import { isCustomFile, isCustomFileArray, isFileArray, isFileList, isFileOrBlob, isFileUpload, } from './guards.js';
5
6
  export const createControlledFormData = (data, configs) => {
7
+ if (typeof FormData === 'undefined') {
8
+ throw new Error('FormData is not available! Please make sure your environment supports FormData.');
9
+ }
6
10
  const formData = new FormData();
7
- const { stringifyNested = '*' } = configs || {};
11
+ const { stringifyNested = '*', ignoreKeys = [], breakArray, lowerCaseKeys, dotNotateNested, lowerCaseValues, requiredKeys, } = configs || {};
12
+ const _compareKeyPaths = (key, paths) => {
13
+ return paths.some((path) => key === path || key.startsWith(`${path}.`));
14
+ };
8
15
  const _shouldLowercaseKeys = (key) => {
9
- return Array.isArray(configs?.lowerCaseKeys)
10
- ? configs?.lowerCaseKeys?.some((path) => key === path || key.startsWith(`${path}.`))
11
- : configs?.lowerCaseKeys === '*';
16
+ return Array.isArray(lowerCaseKeys)
17
+ ? _compareKeyPaths(key, lowerCaseKeys)
18
+ : lowerCaseKeys === '*';
12
19
  };
13
20
  const _shouldLowercaseValue = (key) => {
14
- return Array.isArray(configs?.lowerCaseValues)
15
- ? configs.lowerCaseValues?.some((path) => key === path || key?.startsWith(`${path}.`))
16
- : configs?.lowerCaseValues === '*';
21
+ return Array.isArray(lowerCaseValues)
22
+ ? _compareKeyPaths(key, lowerCaseValues)
23
+ : lowerCaseValues === '*';
17
24
  };
18
25
  const _transformKey = (key) => {
19
- return _shouldLowercaseKeys(key) ? key.toLowerCase() : key;
26
+ return (_shouldLowercaseKeys(key) ? key.toLowerCase() : key);
27
+ };
28
+ const _parseDateLike = (value) => {
29
+ return String(JSON.parse(JSON.stringify(value)));
20
30
  };
21
31
  const _isRequiredKey = (key) => {
22
32
  const transformedKey = _transformKey(key);
23
- return Array.isArray(configs?.requiredKeys)
24
- ? configs?.requiredKeys?.some((path) => transformedKey === path || transformedKey?.startsWith(`${path}.`))
25
- : configs?.requiredKeys === '*';
33
+ return Array.isArray(requiredKeys)
34
+ ? _compareKeyPaths(transformedKey, requiredKeys)
35
+ : requiredKeys === '*';
26
36
  };
27
37
  const _shouldDotNotate = (key) => {
28
38
  const transformedKey = _transformKey(key);
29
- return Array.isArray(configs?.dotNotateNested)
30
- ? configs.dotNotateNested.includes(transformedKey)
31
- : configs?.dotNotateNested === '*';
39
+ return Array.isArray(dotNotateNested)
40
+ ? _compareKeyPaths(transformedKey, dotNotateNested)
41
+ : dotNotateNested === '*';
32
42
  };
33
43
  const _shouldStringify = (key) => {
34
44
  const transformedKey = _transformKey(key);
35
45
  return Array.isArray(stringifyNested)
36
- ? stringifyNested.includes(transformedKey)
46
+ ? _compareKeyPaths(transformedKey, stringifyNested)
37
47
  : stringifyNested === '*';
38
48
  };
39
49
  const _shouldBreakArray = (key) => {
40
50
  const transformedKey = _transformKey(key);
41
- return Array.isArray(configs?.breakArray)
42
- ? configs.breakArray.includes(transformedKey)
43
- : configs?.breakArray === '*';
51
+ return Array.isArray(breakArray)
52
+ ? _compareKeyPaths(transformedKey, breakArray)
53
+ : breakArray === '*';
44
54
  };
45
55
  const _cleanObject = (obj, parentKey = '') => {
46
56
  return Object.entries(obj).reduce((acc, [key, value]) => {
47
57
  const transformedKey = _transformKey(key);
48
58
  const fullKey = parentKey ? `${parentKey}.${transformedKey}` : transformedKey;
49
- if (configs?.ignoreKeys?.includes(fullKey)) {
59
+ if (_compareKeyPaths(fullKey, ignoreKeys)) {
50
60
  return acc;
51
61
  }
52
62
  const shouldKeep = (value != null && value !== '') ||
@@ -55,7 +65,10 @@ export const createControlledFormData = (data, configs) => {
55
65
  isValidArray(value) ||
56
66
  isNotEmptyObject(value);
57
67
  if (shouldKeep) {
58
- if (isNotEmptyObject(value)) {
68
+ if (isDateLike(value)) {
69
+ acc[transformedKey] = value;
70
+ }
71
+ else if (isNotEmptyObject(value)) {
59
72
  if (isDateLike(value)) {
60
73
  acc[transformedKey] = value;
61
74
  }
@@ -71,7 +84,7 @@ export const createControlledFormData = (data, configs) => {
71
84
  if (isNonEmptyString(value)) {
72
85
  let cleanString = value;
73
86
  if (configs?.trimStrings) {
74
- cleanString = cleanString?.trim();
87
+ cleanString = trimString(cleanString);
75
88
  }
76
89
  if (_shouldLowercaseValue(fullKey)) {
77
90
  cleanString = cleanString?.toLowerCase();
@@ -102,6 +115,9 @@ export const createControlledFormData = (data, configs) => {
102
115
  };
103
116
  const _addToFormData = (key, value) => {
104
117
  const transformedKey = _transformKey(key);
118
+ if (_compareKeyPaths(transformedKey, ignoreKeys)) {
119
+ return;
120
+ }
105
121
  if (isCustomFileArray(value)) {
106
122
  value?.forEach((file) => formData.append(transformedKey, file?.originFileObj));
107
123
  }
@@ -154,7 +170,7 @@ export const createControlledFormData = (data, configs) => {
154
170
  }
155
171
  }
156
172
  else if (isDateLike(value)) {
157
- formData.append(transformedKey, JSON.parse(JSON.stringify(value)));
173
+ formData.append(transformedKey, _parseDateLike(value));
158
174
  }
159
175
  else if (isNotEmptyObject(value)) {
160
176
  if (_shouldStringify(key) && !_shouldDotNotate(key)) {
@@ -172,8 +188,15 @@ export const createControlledFormData = (data, configs) => {
172
188
  else {
173
189
  const isNotNullish = value != null && value !== '';
174
190
  if (isNotNullish || _isRequiredKey(key)) {
175
- if (isString(value) && _shouldLowercaseValue(key)) {
176
- formData.append(transformedKey, value?.toLowerCase());
191
+ if (isString(value)) {
192
+ let processedValue = value;
193
+ if (configs?.trimStrings) {
194
+ processedValue = trimString(processedValue);
195
+ }
196
+ if (_shouldLowercaseValue(key)) {
197
+ processedValue = processedValue.toLowerCase();
198
+ }
199
+ formData.append(transformedKey, processedValue);
177
200
  }
178
201
  else {
179
202
  formData.append(transformedKey, value);
@@ -185,18 +208,18 @@ export const createControlledFormData = (data, configs) => {
185
208
  Object.entries(obj).forEach(([key, value]) => {
186
209
  const transformedKey = _transformKey(key);
187
210
  const fullKey = parentKey ? `${parentKey}.${transformedKey}` : transformedKey;
188
- if (configs?.ignoreKeys?.includes(fullKey)) {
211
+ if (_compareKeyPaths(fullKey, ignoreKeys)) {
189
212
  return;
190
213
  }
191
214
  if (configs?.trimStrings && isNonEmptyString(value)) {
192
- value = value?.trim();
215
+ value = trimString(value);
193
216
  }
194
217
  if (_shouldDotNotate(fullKey)) {
195
218
  _addToFormData(fullKey, value);
196
219
  }
197
220
  else if (isNotEmptyObject(value) && !_shouldStringify(fullKey)) {
198
221
  if (isDateLike(value)) {
199
- _addToFormData(key, JSON.parse(JSON.stringify(value)));
222
+ _addToFormData(key, _parseDateLike(value));
200
223
  }
201
224
  else {
202
225
  _processObject(value, key);
@@ -206,7 +229,7 @@ export const createControlledFormData = (data, configs) => {
206
229
  _addToFormData(key, value);
207
230
  }
208
231
  else if (isDateLike(value)) {
209
- _addToFormData(key, JSON.parse(JSON.stringify(value)));
232
+ _addToFormData(key, _parseDateLike(value));
210
233
  }
211
234
  else if (isEmptyObject(value)) {
212
235
  if (_isRequiredKey(fullKey)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nhb-toolbox",
3
- "version": "4.30.15",
3
+ "version": "4.30.16",
4
4
  "description": "A versatile collection of smart, efficient, and reusable utility functions, classes and types for everyday development needs.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -46,12 +46,12 @@
46
46
  "devDependencies": {
47
47
  "@biomejs/biome": "^2.4.15",
48
48
  "@types/jest": "^30.0.0",
49
- "@types/node": "^25.9.0",
49
+ "@types/node": "^25.9.1",
50
50
  "husky": "^9.1.7",
51
51
  "jest": "^30.4.2",
52
52
  "lint-staged": "^17.0.5",
53
53
  "nhb-scripts": "^1.9.2",
54
- "ts-jest": "^29.4.9",
54
+ "ts-jest": "^29.4.11",
55
55
  "typescript": "^6.0.3"
56
56
  },
57
57
  "keywords": [