@valkyriestudios/utils 12.24.0 → 12.25.1

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/README.md CHANGED
@@ -30,7 +30,7 @@ isNotEmptyArray([]); // FALSE
30
30
  isNotEmptyArray([0, 1, 2]); // TRUE
31
31
  ```
32
32
 
33
- ### array/mapKey(val:Record[], key:string, opts:object={})
33
+ ### array/mapKey(val:Record[], key:string, opts?:{merge?:boolean;filter_fn?:(el:T) => boolean})
34
34
  Map a non-primitive object array into an object map by key
35
35
  ```typescript
36
36
  import mapKey from '@valkyriestudios/utils/array/mapKey';
@@ -39,9 +39,7 @@ mapKey([
39
39
  {uid: 15, name: 'Jonas'},
40
40
  {uid: 87, name: 'Josh'},
41
41
  ], 'uid');
42
-
43
- output:
44
-
42
+ /* Expected output: */
45
43
  {
46
44
  12: {uid: 12, name: 'Peter'},
47
45
  15: {uid: 15, name: 'Jonas'},
@@ -64,9 +62,7 @@ mapKey([
64
62
  new Date(),
65
63
  {uid: 87, name: 'Josh'},
66
64
  ], 'uid');
67
-
68
- output:
69
-
65
+ /* Expected output: */
70
66
  {
71
67
  12: {uid: 12, name: 'Peter'},
72
68
  15: {uid: 15, name: 'Jonas'},
@@ -92,9 +88,7 @@ mapKey([
92
88
  {uid: 87, name: 'Josh'},
93
89
  {uid: 12, name: 'Farah'},
94
90
  ], 'uid', {merge: true})
95
-
96
- output:
97
-
91
+ /* Expected output: */
98
92
  {
99
93
  12: {uid: 12, name: 'Farah'},
100
94
  15: {uid: 15, name: 'Bob', dob: '2022-02-07'},
@@ -102,6 +96,24 @@ output:
102
96
  }
103
97
  ```
104
98
 
99
+ allows filtering out objects with a custom filter_fn:
100
+ ```typescript
101
+ import mapKey from '@valkyriestudios/utils/array/mapKey';
102
+ mapKey([
103
+ {uid: 12, name: 'Peter', isActive: true},
104
+ {uid: 15, name: 'Jonas', dob: '2022-02-07', isActive: true},
105
+ {uid: 15, name: 'Bob', isActive: false},
106
+ {name: 'Alana', isActive: true},
107
+ {uid: 87, name: 'Josh', isActive: false},
108
+ {uid: 12, name: 'Farah', isActive: false},
109
+ ], 'uid', {merge: true})
110
+ /* Expected output: */
111
+ {
112
+ 12: {uid: 12, name: 'Peter', isActive: true},
113
+ 15: {uid: 15, name: 'Jonas', dob: '2022-02-07'},
114
+ }
115
+ ```
116
+
105
117
  ### array/mapFn(val:Record[], key:Function, opts:object={})
106
118
  Same behavior as mapKey but instead of a key, a function is passed to generate your own key. Eg:
107
119
 
@@ -112,9 +124,7 @@ mapFn([
112
124
  {uid: 15, name: 'Jonas'},
113
125
  {uid: 87, name: 'Josh'},
114
126
  ], el => el.uid)
115
-
116
- output:
117
-
127
+ /* Expected output: */
118
128
  {
119
129
  12: {uid: 12, name: 'Peter'},
120
130
  15: {uid: 15, name: 'Jonas'},
@@ -124,7 +134,7 @@ output:
124
134
 
125
135
  options are the same as the mapKey function
126
136
 
127
- ### array/mapPrimitive(val:any[], opts:object={valtrim:false,keyround:false,valround:false})
137
+ ### array/mapPrimitive(val:any[], opts?:{valtrim:false;keyround:false;valround:false;filter_fn:(el)=>boolean})
128
138
  Map an array of primitives (number/string)
129
139
  ```typescript
130
140
  import mapPrimitive from '@valkyriestudios/utils/array/mapPrimitive';
@@ -133,6 +143,12 @@ mapPrimitive(['hello', 'hello', 'foo', 'bar']); // {hello: 'hello', foo: 'foo',
133
143
  mapPrimitive(['hello', ' hello', 'foo', ' foo'], {valtrim: true}); // {hello: 'hello', foo: 'foo'}
134
144
  ```
135
145
 
146
+ Allows filtering out unwanted values:
147
+ ```typescript
148
+ import mapPrimitive from '@valkyriestudios/utils/array/mapPrimitive';
149
+ mapPrimitive([1,2,'bla', 3, false], {filter_fn: isNumber}); // {1: 1, 2: 2, 3: 3}
150
+ ```
151
+
136
152
  ### array/groupBy(val:Record[], handler:Function|string)
137
153
  Return a grouped object from an array. This function **will automatically filter out any non/empty objects**.
138
154
 
@@ -428,7 +444,15 @@ Available tokens for usage in spec:
428
444
  | `Z` | Zone, does not allow full zone names, only Z or offsets | `Z` `+02:00` |
429
445
  | `ISO` | Check for full iso date format, take note this enforces milliseconds as a requirement | 2024-02-03T10:28:30.000Z |
430
446
 
431
- Note: The `ISO` token is a shorthand for `YYYY-MM-DDTHH:mm:ss.SSSZ`
447
+ Allows for marking certain portions as optional by wrapping in `{...}`:
448
+ ```typescript
449
+ import isFormat from '@valkyriestudios/utils/date/isFormat';
450
+ isFormat('2024-02-07', 'YYYY-MM-DD{THH:mm:ss}'); // true
451
+ isFormat('2024-02-07T14:50', 'YYYY-MM-DD{THH:mm:ss}'); // false, optional part passed but invalid
452
+ isFormat('2024-02-07T14:50:30', 'YYYY-MM-DD{THH:mm:ss}'); // true
453
+ ```
454
+
455
+ Note: The `ISO` token is a shorthand for `YYYY-MM-DDTHH:mm:ss{.SSS}Z`
432
456
  Note: You can escape characters by surrounding them with `[...]` in your spec, eg: `YYYY-[Q]Q` would check for example `2024-Q1`
433
457
 
434
458
  ### date/diff(val_a:Date, val_b:Date, key:string)
@@ -787,7 +811,7 @@ isFormData(new FormData()); // TRUE
787
811
  isFormData({hi: 'there'}); // FALSE
788
812
  ```
789
813
 
790
- ### formdata/toObject(val:FormData, {raw?:string[]|true;single?:string[]} = {})
814
+ ### formdata/toObject(val:FormData, {raw?:string[]|true;single?:string[];normalize_bool?:boolean;normalize_date?:bool;normalize_number?:bool} = {})
791
815
  Converts an instance of FormData to an object
792
816
  ```typescript
793
817
  import toObject from '@valkyriestudios/utils/formdata/toObject';
@@ -800,7 +824,7 @@ form.append('emptyField', '');
800
824
  toObject(form); // {name: 'Alice', hobbies: ['reading', 'writing'], emptyField: ''}
801
825
  ```
802
826
 
803
- Automatically converts strings to numbers and booleans, and nests objects/arrays based on key structures:
827
+ Automatically converts strings to numbers, dates, booleans and nests objects/arrays based on key structures:
804
828
  ```typescript
805
829
  const form = new FormData();
806
830
  form.append('user[0].name', 'Alice');
@@ -853,6 +877,24 @@ formData.append('action', 'reset');
853
877
  toObject(formData, { single: ['status', 'action'] }) /* {status: 'inactive', action: 'reset'} */
854
878
  ```
855
879
 
880
+ Allows configuring what types of data to normalize:
881
+ ```typescript
882
+ const form = new FormData();
883
+ form.append('pincode', '0123');
884
+ form.append('enabled', 'false');
885
+ form.append('config.isGood', 'true');
886
+ form.append('config.amount', ' 50 ');
887
+
888
+ toObject(form, {raw: ['pincode'], normalize_bool: false}); /* {
889
+ pincode: '0123',
890
+ enabled: 'false',
891
+ config: {
892
+ isGood: 'true',
893
+ amount: 50,
894
+ },
895
+ } */
896
+ ```
897
+
856
898
  ### hash/guid()
857
899
  Generate a unique identifier (guid) according to RFC4122
858
900
  ```typescript
package/array/mapFn.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- interface mapOptions {
1
+ type MapOptions = {
2
2
  /**
3
3
  * Allow merging existing keys or not, if not keys will be overriden if they exist
4
4
  * (default=false)
@@ -11,8 +11,8 @@ interface mapOptions {
11
11
  * {12: {uid: 12, b: 'ho'}}
12
12
  */
13
13
  merge?: boolean;
14
- }
15
- type mapFn<T extends Record<string, any>> = (entry: T) => (string | number | boolean);
14
+ };
15
+ type MapFn<T extends Record<string, any>> = (entry: T) => (string | number | boolean);
16
16
  /**
17
17
  * Map an object array into a kv-object through a function that generates a key. Returning a non-string,
18
18
  * non-numeric value from the function (eg: false) will filter out the object.
@@ -23,10 +23,10 @@ type mapFn<T extends Record<string, any>> = (entry: T) => (string | number | boo
23
23
  * {12: {uid: 12, name: 'Peter'}, 15: {uid: 15, name: 'Jonas'}}
24
24
  *
25
25
  * @param {Record<string, any>[]} val - Array to map
26
- * @param {mapFn} fn - Handler function which is run for each of the objects and should return a string or number
27
- * @param {mapOptions?} opts - Options object to override built-in defaults
26
+ * @param {MapFn} fn - Handler function which is run for each of the objects and should return a string or number
27
+ * @param {MapOptions?} opts - Options object to override built-in defaults
28
28
  *
29
29
  * @returns {Record<string, T>} KV-Map object
30
30
  */
31
- declare function mapFn<T extends Record<string, any>>(arr: T[], fn: mapFn<T>, opts?: mapOptions): Record<string, T>;
31
+ declare function mapFn<T extends Record<string, any>>(arr: T[], fn: MapFn<T>, opts?: MapOptions): Record<string, T>;
32
32
  export { mapFn, mapFn as default };
package/array/mapFn.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mapFn = mapFn;
4
4
  exports.default = mapFn;
5
+ const merge_1 = require("../object/merge");
5
6
  function mapFn(arr, fn, opts) {
6
7
  if ((!Array.isArray(arr) || !arr.length) ||
7
8
  typeof fn !== 'function')
@@ -17,7 +18,7 @@ function mapFn(arr, fn, opts) {
17
18
  if (!Number.isFinite(hash) && !(typeof hash === 'string' && hash.trim().length))
18
19
  continue;
19
20
  hash = hash + '';
20
- map[hash] = MERGE && map[hash] !== undefined ? { ...map[hash], ...el } : el;
21
+ map[hash] = (MERGE && hash in map ? (0, merge_1.merge)(map[hash], el, { union: true }) : el);
21
22
  }
22
23
  return map;
23
24
  }
package/array/mapKey.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- interface mapOptions {
1
+ type MapOptions<T> = {
2
2
  /**
3
3
  * Allow merging existing keys or not, if not keys will be overriden if they exist
4
4
  * (default=false)
@@ -11,7 +11,11 @@ interface mapOptions {
11
11
  * {12: {uid: 12, b: 'ho'}}
12
12
  */
13
13
  merge?: boolean;
14
- }
14
+ /**
15
+ * Pass a custom filter function which will be run in O(n) while iterating
16
+ */
17
+ filter_fn?: (el: T) => boolean;
18
+ };
15
19
  /**
16
20
  * Map an object array into a kv-object by passing a common key that exists on the objects. Objects for
17
21
  * which the key doesn't exist will be filtered out automatically
@@ -23,9 +27,9 @@ interface mapOptions {
23
27
  *
24
28
  * @param {Record<string,any>[]} val - Array to map
25
29
  * @param {string} key - Key to map by
26
- * @param {mapOptions?} opts - Options object to override built-in defaults
30
+ * @param {MapOptions?} opts - Options object to override built-in defaults
27
31
  *
28
32
  * @returns {Record<string, T>} KV-Map object
29
33
  */
30
- declare function mapKey<T extends Record<string, any>>(arr: T[], key: string, opts?: mapOptions): Record<string, T>;
34
+ declare function mapKey<T extends Record<string, any>>(arr: T[], key: string, opts?: MapOptions<T>): Record<string, T>;
31
35
  export { mapKey, mapKey as default };
package/array/mapKey.js CHANGED
@@ -2,15 +2,17 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mapKey = mapKey;
4
4
  exports.default = mapKey;
5
+ const merge_1 = require("../object/merge");
5
6
  function mapKey(arr, key, opts) {
6
7
  if (!Array.isArray(arr) || typeof key !== 'string')
7
8
  return {};
8
- const key_s = key.trim();
9
- if (!key_s.length)
10
- return {};
11
9
  const len = arr.length;
12
10
  if (!len)
13
11
  return {};
12
+ const key_s = key.trim();
13
+ if (!key_s.length)
14
+ return {};
15
+ const FILTER_FN = opts?.filter_fn;
14
16
  const MERGE = opts?.merge === true;
15
17
  const map = {};
16
18
  for (let i = 0; i < len; i++) {
@@ -18,7 +20,9 @@ function mapKey(arr, key, opts) {
18
20
  const el_key = el?.[key_s];
19
21
  if (el_key === undefined)
20
22
  continue;
21
- map[el_key] = MERGE && el_key in map ? { ...map[el_key], ...el } : el;
23
+ if (FILTER_FN && !FILTER_FN(el))
24
+ continue;
25
+ map[el_key] = (MERGE && el_key in map ? (0, merge_1.merge)(map[el_key], el, { union: true }) : el);
22
26
  }
23
27
  return map;
24
28
  }
@@ -1,4 +1,4 @@
1
- interface mapOptions {
1
+ type MapOptions = {
2
2
  /**
3
3
  * Automatically trim all string values
4
4
  * (default=false)
@@ -18,7 +18,11 @@ interface mapOptions {
18
18
  * eg: mapPrimitive([5.432, 5.4, 5.43, 4.2, 4.1], {keyround: true}) -> {5: 5.43, 4: 4.1}
19
19
  */
20
20
  keyround?: boolean;
21
- }
21
+ /**
22
+ * Pass a custom filter function which will be run in O(n) while iterating
23
+ */
24
+ filter_fn?: (el: unknown) => boolean;
25
+ };
22
26
  type mapReturn = Record<string, string | number>;
23
27
  /**
24
28
  * Map an array of primitive values (numbers/strings) into a kv-object
@@ -30,7 +34,7 @@ type mapReturn = Record<string, string | number>;
30
34
  * {hello: 'hello', foo: 'foo', bar: 'bar'}
31
35
  *
32
36
  * @param {unknown[]} val - Array to map
33
- * @param {mapOptions?} opts - Options object to override built-in defaults
37
+ * @param {MapOptions?} opts - Options object to override built-in defaults
34
38
  */
35
- declare function mapPrimitive(arr: unknown[], opts?: mapOptions): mapReturn;
39
+ declare function mapPrimitive(arr: unknown[], opts?: MapOptions): mapReturn;
36
40
  export { mapPrimitive, mapPrimitive as default };
@@ -10,11 +10,16 @@ function mapPrimitive(arr, opts = {}) {
10
10
  const VALTRIM = opts?.valtrim === true;
11
11
  const VALROUND = (0, isIntegerAboveOrEqual_1.isIntegerAboveOrEqual)(opts?.valround, 0)
12
12
  ? opts?.valround
13
- : opts?.valround === true;
13
+ : opts?.valround === true
14
+ ? 0
15
+ : null;
14
16
  const KEYROUND = opts?.keyround === true;
17
+ const FILTER_FN = opts?.filter_fn;
15
18
  const map = {};
16
19
  for (let i = 0; i < arr.length; i++) {
17
20
  const el = arr[i];
21
+ if (FILTER_FN && !FILTER_FN(el))
22
+ continue;
18
23
  if (typeof el === 'string') {
19
24
  const trimmed = el.trim();
20
25
  if (!trimmed)
@@ -22,11 +27,9 @@ function mapPrimitive(arr, opts = {}) {
22
27
  map[trimmed] = VALTRIM ? trimmed : el;
23
28
  }
24
29
  else if (Number.isFinite(el)) {
25
- map[`${KEYROUND ? Math.round(el) : el}`] = VALROUND === false
30
+ map[`${KEYROUND ? Math.round(el) : el}`] = VALROUND === null
26
31
  ? el
27
- : VALROUND === true
28
- ? Math.round(el)
29
- : (0, round_1.round)(el, VALROUND);
32
+ : (0, round_1.round)(el, VALROUND);
30
33
  }
31
34
  }
32
35
  return map;
package/date/format.js CHANGED
@@ -59,7 +59,7 @@ function toZone(d, zone) {
59
59
  return new Date(time + zone_offset_cache[ckey]);
60
60
  let zone_time = null;
61
61
  try {
62
- zone_time = new Date(d.toLocaleString(DEFAULT_LOCALE, { timeZone: zone })).getTime();
62
+ zone_time = new Date(d.toLocaleString(DEFAULT_LOCALE, { timeZone: zone })).getTime() + d.getMilliseconds();
63
63
  }
64
64
  catch {
65
65
  throw new Error(`format: Invalid zone passed - ${zone}`);
@@ -149,7 +149,7 @@ function getSpecChain(spec) {
149
149
  let repl_len = 0;
150
150
  if (base.indexOf('[') >= 0) {
151
151
  base = base.replace(ESCAPE_RGX, match => {
152
- const escape_token = '$R' + repl_len++ + '$';
152
+ const escape_token = '$' + repl_len++ + '$';
153
153
  repl.push([escape_token, match.slice(1, -1)]);
154
154
  return escape_token;
155
155
  });
@@ -171,7 +171,7 @@ function getSpecChain(spec) {
171
171
  }
172
172
  }
173
173
  const chain_len = chain.length;
174
- const result = chain_len ? { base, chain, chain_len, repl, repl_len } : null;
174
+ const result = chain_len ? { base, chain, chain_len, repl } : null;
175
175
  spec_cache[spec] = result;
176
176
  return result;
177
177
  }
@@ -190,15 +190,17 @@ function format(val, spec, locale = DEFAULT_LOCALE, zone = DEFAULT_TZ, sow = DEF
190
190
  return n_val.toISOString();
191
191
  const d = toZone(n_val, zone);
192
192
  let base = n_spec.base;
193
- const { chain_len, chain, repl_len, repl } = n_spec;
194
- for (let i = 0; i < chain_len; i++) {
195
- let pos = base.indexOf(chain[i][0]);
196
- const formatted_val = chain[i][1](d, locale, sow);
193
+ const repl = [...n_spec.repl];
194
+ let repl_len = n_spec.repl.length;
195
+ for (let i = 0; i < n_spec.chain_len; i++) {
196
+ const el = n_spec.chain[i];
197
+ let pos = base.indexOf(el[0]);
198
+ const token_val = el[1](d, locale, sow);
197
199
  while (pos !== -1) {
198
- base = base.slice(0, pos) +
199
- formatted_val +
200
- base.slice(pos + chain[i][2]);
201
- pos = base.indexOf(chain[i][0], pos + chain[i][2]);
200
+ const key = '$' + repl_len++ + '$';
201
+ repl.push([key, token_val]);
202
+ base = base.slice(0, pos) + key + base.slice(pos + el[2]);
203
+ pos = base.indexOf(el[0], pos + el[2]);
202
204
  }
203
205
  }
204
206
  for (let i = 0; i < repl_len; i++) {
package/date/isFormat.js CHANGED
@@ -45,10 +45,10 @@ const TOKENS = [
45
45
  }],
46
46
  ];
47
47
  const SPEC_ALIASES = {
48
- ISO: 'YYYY-MM-DDTHH:mm:ss.SSSZ',
48
+ ISO: 'YYYY-MM-DDTHH:mm:ss{.SSS}Z',
49
49
  };
50
50
  const spec_pat_cache = {};
51
- function compileSpec(spec) {
51
+ function compileSpec(spec, is_chunk = false) {
52
52
  if (spec in spec_pat_cache)
53
53
  return spec_pat_cache[spec];
54
54
  const tokens = [];
@@ -62,6 +62,15 @@ function compileSpec(spec) {
62
62
  pat += spec.slice(cursor + 1, end_idx).replace(SPECIAL_CHARS, '\\$&');
63
63
  cursor = end_idx + 1;
64
64
  }
65
+ else if (spec[cursor] === '{') {
66
+ const end_idx = spec.indexOf('}', cursor);
67
+ if (end_idx === -1)
68
+ throw new Error('isDateFormat: Unmatched { in format string');
69
+ const compiled = compileSpec(spec.slice(cursor + 1, end_idx), true);
70
+ pat += '(?:' + compiled.rgx.source + ')?';
71
+ tokens.push(...compiled.tokens);
72
+ cursor = end_idx + 1;
73
+ }
65
74
  else {
66
75
  const token_idx = TOKENS.findIndex(([token_key]) => spec.startsWith(token_key, cursor));
67
76
  if (token_idx >= 0) {
@@ -76,7 +85,7 @@ function compileSpec(spec) {
76
85
  }
77
86
  }
78
87
  }
79
- spec_pat_cache[spec] = { rgx: RegExp('^' + pat + '$'), tokens };
88
+ spec_pat_cache[spec] = { rgx: is_chunk ? RegExp(pat) : RegExp('^' + pat + '$'), tokens };
80
89
  return spec_pat_cache[spec];
81
90
  }
82
91
  function isDateFormat(input, spec) {
@@ -95,7 +104,8 @@ function isDateFormat(input, spec) {
95
104
  const matches = patMatch.slice(1);
96
105
  const context = {};
97
106
  for (let i = 0; i < matches.length; i++) {
98
- if (!TOKENS[tokens[i]][2](matches[i], context))
107
+ const match = matches[i];
108
+ if (match !== undefined && !TOKENS[tokens[i]][2](match, context))
99
109
  return false;
100
110
  }
101
111
  const { is12, day, month, year } = context;
package/deep/set.js CHANGED
@@ -21,8 +21,6 @@ function deepSet(obj, path, value, define = false) {
21
21
  .split('.');
22
22
  const last_part_ix = parts.length - 1;
23
23
  for (let i = 0; i < last_part_ix; i++) {
24
- if (parts[i] === '')
25
- continue;
26
24
  if (Array.isArray(obj)) {
27
25
  const idx = parseInt(parts[i]);
28
26
  if (!Number.isInteger(idx) || idx < 0)
package/equal.js CHANGED
@@ -2,41 +2,69 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.equal = equal;
4
4
  exports.default = equal;
5
- const isNumericalNaN_1 = require("./number/isNumericalNaN");
6
5
  function isArrayEqual(a, b) {
7
6
  const a_len = a.length;
8
7
  if (a_len !== b.length)
9
8
  return false;
10
9
  for (let i = a_len - 1; i >= 0; i--) {
11
- if (equal(a[i], b[i]))
12
- continue;
13
- return false;
10
+ if (!equal(a[i], b[i]))
11
+ return false;
14
12
  }
15
13
  return true;
16
14
  }
17
15
  function isObjectEqual(a, b) {
18
- const keys_a = Object.keys(a);
19
- const keys_a_len = keys_a.length;
20
- if (keys_a_len !== Object.keys(b).length)
16
+ const a_keys = Object.keys(a);
17
+ const b_keys = Object.keys(b);
18
+ const a_len = a_keys.length;
19
+ if (a_len !== b_keys.length)
21
20
  return false;
22
- for (let i = keys_a_len - 1; i >= 0; i--) {
23
- if (equal(a[keys_a[i]], b[keys_a[i]]))
24
- continue;
21
+ for (let i = a_len - 1; i >= 0; i--) {
22
+ const key = a_keys[i];
23
+ if (!equal(a[key], b[key]))
24
+ return false;
25
+ }
26
+ return true;
27
+ }
28
+ function isMapEqual(a, b) {
29
+ if (a.size !== b.size)
25
30
  return false;
31
+ for (const [key, value] of a) {
32
+ if (!b.has(key) || !equal(value, b.get(key)))
33
+ return false;
26
34
  }
27
35
  return true;
28
36
  }
29
37
  function equal(a, b) {
30
- if (a instanceof Date)
31
- return b instanceof Date && a.valueOf() === b.valueOf();
32
- if (a instanceof RegExp)
33
- return b instanceof RegExp && String(a) === String(b);
34
- if (Array.isArray(a))
35
- return Array.isArray(b) && isArrayEqual(a, b);
36
- if (Object.prototype.toString.call(a) === '[object Object]') {
37
- return Object.prototype.toString.call(b) === '[object Object]' && isObjectEqual(a, b);
38
+ if (a === b)
39
+ return true;
40
+ switch (typeof a) {
41
+ case 'number':
42
+ return Number.isNaN(b) && Number.isNaN(a);
43
+ case 'object': {
44
+ if (a === null || b === null)
45
+ return false;
46
+ if (Array.isArray(a))
47
+ return Array.isArray(b) && isArrayEqual(a, b);
48
+ const proto_a = Object.prototype.toString.call(a);
49
+ const proto_b = Object.prototype.toString.call(b);
50
+ if (proto_a !== proto_b)
51
+ return false;
52
+ switch (proto_a) {
53
+ case '[object Date]':
54
+ return a.valueOf() === b.valueOf();
55
+ case '[object Object]':
56
+ return isObjectEqual(a, b);
57
+ case '[object Error]':
58
+ return a.name === b.name && a.message === b.message;
59
+ case '[object RegExp]':
60
+ return String(a) === String(b);
61
+ case '[object Map]':
62
+ return isMapEqual(a, b);
63
+ default:
64
+ return false;
65
+ }
66
+ }
67
+ default:
68
+ return false;
38
69
  }
39
- if ((0, isNumericalNaN_1.isNumericalNaN)(a))
40
- return (0, isNumericalNaN_1.isNumericalNaN)(b);
41
- return a === b;
42
70
  }
@@ -1,4 +1,4 @@
1
- type toObjectConfig = {
1
+ type ToObjectConfig = {
2
2
  /**
3
3
  * Pass array of keys that should not be normalized into number/bool when seen
4
4
  */
@@ -7,6 +7,18 @@ type toObjectConfig = {
7
7
  * Pass array of keys that should only have a single value (e.g., 'action')
8
8
  */
9
9
  single?: string[];
10
+ /**
11
+ * Whether or not we should normalize booleans, defaults to true if not set
12
+ */
13
+ normalize_bool?: boolean;
14
+ /**
15
+ * Whether or not we should normalize dates, defaults to true if not set
16
+ */
17
+ normalize_date?: boolean;
18
+ /**
19
+ * Whether or not we should normalize numbers, defaults to true if not set
20
+ */
21
+ normalize_number?: boolean;
10
22
  };
11
23
  /**
12
24
  * Converts a FormData instance to a json object
@@ -21,7 +33,7 @@ type toObjectConfig = {
21
33
  * {name: 'Alice', hobbies: ['reading', 'writing'], emptyField: ''}
22
34
  *
23
35
  * @param {FormData} val - FormData instance to convert to an object
24
- * @param {}
36
+ * @param {ToObjectConfig?} config - Config for conversion
25
37
  */
26
- declare function toObject<T extends Record<string, unknown>>(form: FormData, config?: toObjectConfig): T;
38
+ declare function toObject<T extends Record<string, unknown>>(form: FormData, config?: ToObjectConfig): T;
27
39
  export { toObject, toObject as default };
@@ -2,36 +2,33 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toObject = toObject;
4
4
  exports.default = toObject;
5
+ const isFormat_1 = require("../date/isFormat");
5
6
  const RGX_CLOSE = /\]/g;
6
7
  function assignValue(acc, rawkey, value, single) {
7
8
  let cursor = acc;
8
9
  const keys = rawkey.replace(RGX_CLOSE, '').split(/\[|\./);
9
- for (let i = 0; i < keys.length; i++) {
10
+ const keys_len = keys.length;
11
+ for (let i = 0; i < keys_len; i++) {
10
12
  const key = keys[i];
11
- if (i === keys.length - 1) {
12
- if (cursor[key] !== undefined && (!single || !single.has(key))) {
13
- if (Array.isArray(cursor[key])) {
13
+ if (i === keys_len - 1) {
14
+ const cursor_val = cursor[key];
15
+ if (cursor_val !== undefined && (!single || !single.has(key))) {
16
+ if (Array.isArray(cursor_val)) {
14
17
  cursor[key].push(value);
15
18
  }
16
19
  else {
17
- cursor[key] = [cursor[key], value];
20
+ cursor[key] = [cursor_val, value];
18
21
  }
19
22
  }
20
23
  else {
21
24
  cursor[key] = value;
22
25
  }
23
- return;
24
- }
25
- if (Array.isArray(cursor)) {
26
- const index = Number(key);
27
- if (!cursor[index])
28
- cursor[index] = isNaN(Number(keys[i + 1])) ? {} : [];
29
- cursor = cursor[index];
30
26
  }
31
27
  else {
32
- if (!cursor[key])
33
- cursor[key] = isNaN(Number(keys[i + 1])) ? {} : [];
34
- cursor = cursor[key];
28
+ const n_key = Array.isArray(cursor) ? Number(key) : key;
29
+ if (!cursor[n_key])
30
+ cursor[n_key] = isNaN(Number(keys[i + 1])) ? {} : [];
31
+ cursor = cursor[n_key];
35
32
  }
36
33
  }
37
34
  }
@@ -40,20 +37,36 @@ function toObject(form, config) {
40
37
  throw new Error('formdata/toObject: Value is not an instance of FormData');
41
38
  const set = config?.raw === true ? true : new Set(Array.isArray(config?.raw) ? config?.raw : []);
42
39
  const single = Array.isArray(config?.single) && config?.single.length ? new Set(config.single) : null;
40
+ const nBool = config?.normalize_bool !== false;
41
+ const nDate = config?.normalize_date !== false;
42
+ const nNumber = config?.normalize_number !== false;
43
43
  const acc = {};
44
44
  form.forEach((value, key) => {
45
- let normalizedValue = value;
46
45
  if (set !== true && typeof value === 'string' && !set.has(key)) {
47
- const lower = value.toLowerCase();
48
- normalizedValue = (lower === 'true'
49
- ? true
50
- : lower === 'false'
51
- ? false
52
- : !isNaN(Number(value)) && value.trim() !== ''
53
- ? Number(value)
54
- : value);
46
+ if (nBool) {
47
+ const lower = value.toLowerCase();
48
+ if (lower === 'true') {
49
+ assignValue(acc, key, true, single);
50
+ return;
51
+ }
52
+ else if (lower === 'false') {
53
+ assignValue(acc, key, false, single);
54
+ return;
55
+ }
56
+ }
57
+ const trimmed = value.trim();
58
+ if (trimmed.length) {
59
+ if (nNumber && !isNaN(Number(value))) {
60
+ assignValue(acc, key, Number(value), single);
61
+ return;
62
+ }
63
+ if (nDate && (0, isFormat_1.isDateFormat)(value, 'ISO')) {
64
+ assignValue(acc, key, new Date(value), single);
65
+ return;
66
+ }
67
+ }
55
68
  }
56
- assignValue(acc, key, normalizedValue, single);
69
+ assignValue(acc, key, value, single);
57
70
  });
58
71
  return acc;
59
72
  }
package/index.d.ts CHANGED
@@ -1,7 +1,3 @@
1
- declare module "number/isNumericalNaN" {
2
- function isNumericalNaN(val: unknown): boolean;
3
- export { isNumericalNaN, isNumericalNaN as default };
4
- }
5
1
  declare module "equal" {
6
2
  function equal(a: any, b: any): boolean;
7
3
  export { equal, equal as default };
@@ -40,29 +36,38 @@ declare module "array/join" {
40
36
  function join(val: unknown[], opts?: joinOptions): string;
41
37
  export { join, join as default };
42
38
  }
39
+ declare module "object/merge" {
40
+ type MergeOptions = {
41
+ union?: boolean;
42
+ };
43
+ function merge(target: Record<string, any>, source?: Record<string, any> | Record<string, any>[], opts?: MergeOptions): Record<string, any>;
44
+ export { merge, merge as default };
45
+ }
43
46
  declare module "array/mapFn" {
44
- interface mapOptions {
47
+ type MapOptions = {
45
48
  merge?: boolean;
46
- }
47
- type mapFn<T extends Record<string, any>> = (entry: T) => (string | number | boolean);
48
- function mapFn<T extends Record<string, any>>(arr: T[], fn: mapFn<T>, opts?: mapOptions): Record<string, T>;
49
+ };
50
+ type MapFn<T extends Record<string, any>> = (entry: T) => (string | number | boolean);
51
+ function mapFn<T extends Record<string, any>>(arr: T[], fn: MapFn<T>, opts?: MapOptions): Record<string, T>;
49
52
  export { mapFn, mapFn as default };
50
53
  }
51
54
  declare module "array/mapKey" {
52
- interface mapOptions {
55
+ type MapOptions<T> = {
53
56
  merge?: boolean;
54
- }
55
- function mapKey<T extends Record<string, any>>(arr: T[], key: string, opts?: mapOptions): Record<string, T>;
57
+ filter_fn?: (el: T) => boolean;
58
+ };
59
+ function mapKey<T extends Record<string, any>>(arr: T[], key: string, opts?: MapOptions<T>): Record<string, T>;
56
60
  export { mapKey, mapKey as default };
57
61
  }
58
62
  declare module "array/mapPrimitive" {
59
- interface mapOptions {
63
+ type MapOptions = {
60
64
  valtrim?: boolean;
61
65
  valround?: boolean | number;
62
66
  keyround?: boolean;
63
- }
67
+ filter_fn?: (el: unknown) => boolean;
68
+ };
64
69
  type mapReturn = Record<string, string | number>;
65
- function mapPrimitive(arr: unknown[], opts?: mapOptions): mapReturn;
70
+ function mapPrimitive(arr: unknown[], opts?: MapOptions): mapReturn;
66
71
  export { mapPrimitive, mapPrimitive as default };
67
72
  }
68
73
  declare module "object/isNotEmpty" {
@@ -229,11 +234,14 @@ declare module "formdata/is" {
229
234
  export { isFormData, isFormData as default };
230
235
  }
231
236
  declare module "formdata/toObject" {
232
- type toObjectConfig = {
237
+ type ToObjectConfig = {
233
238
  raw?: string[] | true;
234
239
  single?: string[];
240
+ normalize_bool?: boolean;
241
+ normalize_date?: boolean;
242
+ normalize_number?: boolean;
235
243
  };
236
- function toObject<T extends Record<string, unknown>>(form: FormData, config?: toObjectConfig): T;
244
+ function toObject<T extends Record<string, unknown>>(form: FormData, config?: ToObjectConfig): T;
237
245
  export { toObject, toObject as default };
238
246
  }
239
247
  declare module "formdata/index" {
@@ -309,13 +317,6 @@ declare module "object/define" {
309
317
  };
310
318
  export { define, define as default };
311
319
  }
312
- declare module "object/merge" {
313
- type MergeOptions = {
314
- union?: boolean;
315
- };
316
- function merge(target: Record<string, any>, source?: Record<string, any> | Record<string, any>[], opts?: MergeOptions): Record<string, any>;
317
- export { merge, merge as default };
318
- }
319
320
  declare module "deep/get" {
320
321
  type ObjectType = {
321
322
  [key: string]: any;
@@ -385,6 +386,10 @@ declare module "number/isIntegerBelowOrEqual" {
385
386
  function isIntegerBelowOrEqual(val: unknown, ref: number): val is number;
386
387
  export { isIntegerBelowOrEqual, isIntegerBelowOrEqual as default };
387
388
  }
389
+ declare module "number/isNumericalNaN" {
390
+ function isNumericalNaN(val: unknown): boolean;
391
+ export { isNumericalNaN, isNumericalNaN as default };
392
+ }
388
393
  declare module "number/randomBetween" {
389
394
  function randomBetween(min?: number, max?: number): number;
390
395
  export { randomBetween, randomBetween as default };
package/package.json CHANGED
@@ -1 +1 @@
1
- { "name": "@valkyriestudios/utils", "version": "12.24.0", "description": "A collection of single-function utilities for common tasks", "author": { "name": "Peter Vermeulen", "url": "https://www.linkedin.com/in/petervermeulen1/" }, "keywords": [ "utility", "library", "javascript", "js", "node", "bun" ], "license": "MIT", "repository": { "type": "git", "url": "git+https://github.com/ValkyrieStudios/utils.git" }, "bugs": { "url": "https://github.com/ValkyrieStudios/utils/issues" }, "homepage": "https://github.com/ValkyrieStudios/utils#readme", "types": "index.d.ts" }
1
+ { "name": "@valkyriestudios/utils", "version": "12.25.1", "description": "A collection of single-function utilities for common tasks", "author": { "name": "Peter Vermeulen", "url": "https://www.linkedin.com/in/petervermeulen1/" }, "keywords": [ "utility", "library", "javascript", "js", "node", "bun" ], "license": "MIT", "repository": { "type": "git", "url": "git+https://github.com/ValkyrieStudios/utils.git" }, "bugs": { "url": "https://github.com/ValkyrieStudios/utils/issues" }, "homepage": "https://github.com/ValkyrieStudios/utils#readme", "types": "index.d.ts" }