@hkdigital/lib-core 0.3.8 → 0.3.10

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.
@@ -8,7 +8,7 @@ import { toArrayPath } from '../array/index.js';
8
8
 
9
9
  import { toStringPath } from '../string/index.js';
10
10
 
11
- import { isIterable } from '../is/index.js';
11
+ import * as is from '../is/index.js';
12
12
 
13
13
  import { iterateObjectPaths, iterateObjectEntries } from '../iterate/index.js';
14
14
 
@@ -42,54 +42,39 @@ export { PATH_SEPARATOR };
42
42
 
43
43
  /**
44
44
  * Returns true
45
- * - if the object has no (iterable) key value pairs
45
+ * - if the object has no enumerable key value pairs
46
46
  * - if the object is an empty array
47
47
  *
48
48
  * @param {object} obj
49
49
  *
50
50
  * @return {boolean}
51
51
  * true if the object has no key value pairs or the supplied object
52
- * is `falsey`
52
+ * is falsy
53
53
  */
54
54
  export function isEmpty(obj) {
55
55
  if (!obj) {
56
- // object is null or other falsey value
56
+ // object is null or other falsy value
57
57
  return true;
58
58
  }
59
59
 
60
60
  expect.object(obj);
61
61
 
62
- if (/*obj instanceof Array && */ 0 === obj.length) {
63
- return true;
64
- }
65
-
66
- for (const key in obj) {
67
- return false;
68
- }
69
-
70
- return true;
62
+ return Object.keys(obj).length === 0;
71
63
  }
72
64
 
73
65
  // -----------------------------------------------------------------------------
74
66
 
75
67
  /**
76
- * Get the number of (iterable) key value pairs in the specified object
68
+ * Get the number of enumerable key value pairs in the specified object
77
69
  *
78
70
  * @param {object} obj
79
71
  *
80
- * @return {number} number of iterable key value pairs
72
+ * @return {number} number of enumerable key value pairs
81
73
  */
82
- export function objectSize(obj) {
74
+ export function size(obj) {
83
75
  expect.object(obj);
84
76
 
85
- let count = 0;
86
-
87
- // eslint-disable-next-line no-unused-vars
88
- for (const _key in obj) {
89
- count = count + 1;
90
- }
91
-
92
- return count;
77
+ return Object.keys(obj).length;
93
78
  }
94
79
 
95
80
  // -----------------------------------------------------------------------------
@@ -108,46 +93,33 @@ export function objectSize(obj) {
108
93
  export function exportNotNull(obj, onlyKeys) {
109
94
  expect.object(obj);
110
95
 
111
- // if( onlyKeys )
112
- // {
113
- // expect.array( onlyKeys );
114
- // }
115
-
116
- const newObj = {};
117
-
118
96
  const onlyKeysSet = onlyKeys ? new Set(onlyKeys) : null;
119
97
 
120
- for (const key in obj) {
121
- const value = obj[key];
122
-
123
- if (value !== null && value !== undefined) {
124
- if (onlyKeysSet && !onlyKeysSet.has(key)) {
125
- continue;
126
- }
127
-
128
- newObj[key] = value;
129
- }
130
- } // end for
131
-
132
- return newObj;
98
+ return Object.fromEntries(
99
+ Object.entries(obj).filter(([key, value]) => {
100
+ if (value === null || value === undefined) return false;
101
+ return !onlyKeysSet || onlyKeysSet.has(key);
102
+ })
103
+ );
133
104
  }
134
105
 
135
106
  // -----------------------------------------------------------------------------
136
107
 
137
108
  /**
138
- * Returns a shallow copy of the object without the properties that
139
- * are `private`
140
- * - Private properties are properties that start with an underscore
141
- * `_`.
109
+ * Create a shallow copy of the object's public properties. Properties that
110
+ * start with an underscore are considered 'internal' properties and are not
111
+ * exported.
112
+ * - This method can e.g. be used to export a data object without it's
113
+ * 'internal' properties
142
114
  *
143
115
  * @param {object} obj
144
116
  *
145
117
  * @param {string[]} [keepKeys]
146
- * If specified, the sprecified private keys will be exported (e.g. `_id`)
118
+ * If specified, the specified private keys will be exported (e.g. `_id`)
147
119
  *
148
- * @returns {object} new object without the null properties
120
+ * @returns {object} new object without properties that start with an underscore
149
121
  */
150
- export function exportNotPrivate(obj, keepKeys) {
122
+ export function exportPublic(obj, keepKeys) {
151
123
  expect.object(obj);
152
124
 
153
125
  const newObj = {};
@@ -176,6 +148,83 @@ export function exportNotPrivate(obj, keepKeys) {
176
148
 
177
149
  // -----------------------------------------------------------------------------
178
150
 
151
+ /**
152
+ * Creates a copy of an object or array that contains only primitive values
153
+ * - Nested objects and arrays are completely removed
154
+ * - Only string, number, boolean, null, undefined values are kept
155
+ *
156
+ * @param {object|array} objectOrArray
157
+ *
158
+ * @returns {object|array} new object or array with only primitive values
159
+ */
160
+ export function exportNotNested(objectOrArray) {
161
+ expect.object(objectOrArray);
162
+
163
+ if (Array.isArray(objectOrArray)) {
164
+ // obj is an array
165
+
166
+ let isShallow = true;
167
+
168
+ for (let j = 0, n = objectOrArray.length; j < n; j = j + 1) {
169
+ const value = objectOrArray[j];
170
+
171
+ if (value instanceof Object) {
172
+ isShallow = false;
173
+ break;
174
+ }
175
+ } // end for
176
+
177
+ if (isShallow) {
178
+ // objectOrArray is already shallow -> nothing to do
179
+ return objectOrArray;
180
+ }
181
+
182
+ const outputArray = [];
183
+
184
+ for (let j = 0, n = objectOrArray.length; j < n; j = j + 1) {
185
+ const value = objectOrArray[j];
186
+
187
+ if (!(value instanceof Object)) {
188
+ outputArray.push(value);
189
+ }
190
+ } // end for
191
+
192
+ return outputArray;
193
+ } else {
194
+ // obj is a not an array
195
+
196
+ let isShallow = true;
197
+
198
+ for (const key in objectOrArray) {
199
+ const value = objectOrArray[key];
200
+
201
+ if (value instanceof Object) {
202
+ isShallow = false;
203
+ break;
204
+ }
205
+ } // end for
206
+
207
+ if (isShallow) {
208
+ // objectOrArray is already shallow -> nothing to do
209
+ return objectOrArray;
210
+ }
211
+
212
+ const outputObj = {};
213
+
214
+ for (const key in objectOrArray) {
215
+ const value = objectOrArray[key];
216
+
217
+ if (!(value instanceof Object)) {
218
+ outputObj[key] = value;
219
+ }
220
+ } // end for
221
+
222
+ return outputObj;
223
+ }
224
+ }
225
+
226
+ // -----------------------------------------------------------------------------
227
+
179
228
  // export function removeNull()
180
229
 
181
230
  // -----------------------------------------------------------------------------
@@ -511,7 +560,9 @@ export function objectGetWithThrow(obj, path, parseFn) {
511
560
  const { value: parsedValue, error } = parseFn(value);
512
561
 
513
562
  if (error) {
514
- throw new Error(`Invalid value found at path [${toStringPath(path)}]`, { cause: error });
563
+ throw new Error(`Invalid value found at path [${toStringPath(path)}]`, {
564
+ cause: error
565
+ });
515
566
  }
516
567
 
517
568
  value = parsedValue;
@@ -604,11 +655,14 @@ export function objectDiff(objBefore, objAfter, options = {}, _recursion) {
604
655
 
605
656
  const ignoreAdd = undefined === options.ignoreAdd ? false : options.ignoreAdd;
606
657
 
607
- const ignoreUpdate = undefined === options.ignoreUpdate ? false : options.ignoreUpdate;
658
+ const ignoreUpdate =
659
+ undefined === options.ignoreUpdate ? false : options.ignoreUpdate;
608
660
 
609
- const ignoreDelete = undefined === options.ignoreDelete ? false : options.ignoreDelete;
661
+ const ignoreDelete =
662
+ undefined === options.ignoreDelete ? false : options.ignoreDelete;
610
663
 
611
- const ignorePrivate = undefined === options.ignorePrivate ? true : options.ignorePrivate;
664
+ const ignorePrivate =
665
+ undefined === options.ignorePrivate ? true : options.ignorePrivate;
612
666
 
613
667
  let deleteValue = null;
614
668
 
@@ -686,11 +740,14 @@ export function patchObject(obj, changes, options = {}) {
686
740
 
687
741
  const ignoreAdd = undefined === options.ignoreAdd ? false : options.ignoreAdd;
688
742
 
689
- const ignoreUpdate = undefined === options.ignoreUpdate ? false : options.ignoreUpdate;
743
+ const ignoreUpdate =
744
+ undefined === options.ignoreUpdate ? false : options.ignoreUpdate;
690
745
 
691
- const ignoreDelete = undefined === options.ignoreDelete ? false : options.ignoreDelete;
746
+ const ignoreDelete =
747
+ undefined === options.ignoreDelete ? false : options.ignoreDelete;
692
748
 
693
- const ignorePrivate = undefined === options.ignorePrivate ? true : options.ignorePrivate;
749
+ const ignorePrivate =
750
+ undefined === options.ignorePrivate ? true : options.ignorePrivate;
694
751
 
695
752
  for (let j = 0, n = changes.length; j < n; j = j + 1) {
696
753
  const change = changes[j];
@@ -826,9 +883,13 @@ export function getPrototypeNames(obj) {
826
883
  * { someArray.0.name: 1 }
827
884
  *
828
885
  * @param {object} [options]
829
- * @param {object} [options.shallowLeaves=false]
830
- * If set to true, the values of the leaves of the tree will be converted
831
- * to shallow objects if the leaves are nested objects.
886
+ * @param {boolean} [options.shallowLeaves=false]
887
+ * If true, when extracted leaf values are objects, they are filtered to
888
+ * contain only primitive properties. Nested objects and arrays within
889
+ * the leaves are removed.
890
+ *
891
+ * Example: { profile: { name: "John", settings: {...} } }
892
+ * becomes: { profile: { name: "John" } }
832
893
  *
833
894
  * @return {object}
834
895
  * nested object with the values that were found or defaultValues
@@ -868,7 +929,7 @@ export function getTree(obj, tree, options) {
868
929
  } else {
869
930
  // Set shallow leave value instead of nested object
870
931
 
871
- const shallowLeaveValue = shallowClone(leaveValue);
932
+ const shallowLeaveValue = exportNotNested(leaveValue);
872
933
  objectSet(result, arrPath, shallowLeaveValue);
873
934
  }
874
935
  } // end for
@@ -880,20 +941,21 @@ export function getTree(obj, tree, options) {
880
941
 
881
942
  /**
882
943
  * Deep clone an object or any kind of other variable
883
- * - Recursively clone all nested properties
884
- * - Properties in the prototype chain are cloned too, but all copied into a
885
- * single prototype object
886
- * - This method works on objects, but also on any other JS variable type
887
- * - If a value cannot be cloned, a reference is returned. o.a. for
888
- * - Error objects
889
- * - Browser objects
890
- * - Functions
891
944
  *
892
945
  * @param {any} objectToBeCloned - Variable to clone
893
946
  *
894
947
  * @returns {any} cloned output
895
948
  */
896
949
  export function clone(objectToBeCloned, _seenObjects) {
950
+ if (!_seenObjects) {
951
+ try {
952
+ return structuredClone(objectToBeCloned);
953
+ // eslint-disable-next-line no-unused-vars
954
+ } catch (error) {
955
+ // Fall back to custom implementation for unsupported types
956
+ }
957
+ }
958
+
897
959
  // const startTime = Date.now();
898
960
 
899
961
  // -- Return references for all variables that are not objects
@@ -1035,91 +1097,6 @@ export function setReadOnlyProperty(obj, propertyName, value) {
1035
1097
 
1036
1098
  // -----------------------------------------------------------------------------
1037
1099
 
1038
- /**
1039
- * Returns a clone of a (nested) object that has a maximum depth
1040
- *
1041
- * @param {object|array} obj
1042
- *
1043
- * @returns {object|array} shallow object or array
1044
- */
1045
- export function shallowClone(objectOrArray) {
1046
- expect.object(objectOrArray);
1047
-
1048
- if (Array.isArray(objectOrArray)) {
1049
- // obj is an array
1050
-
1051
- let isShallow = true;
1052
-
1053
- for (let j = 0, n = objectOrArray.length; j < n; j = j + 1) {
1054
- const value = objectOrArray[j];
1055
-
1056
- if (value instanceof Object) {
1057
- isShallow = false;
1058
- break;
1059
- }
1060
- } // end for
1061
-
1062
- if (isShallow) {
1063
- // objectOrArray is already shallow -> nothing to do
1064
- return objectOrArray;
1065
- }
1066
-
1067
- const outputArray = [];
1068
-
1069
- for (let j = 0, n = objectOrArray.length; j < n; j = j + 1) {
1070
- const value = objectOrArray[j];
1071
-
1072
- if (!(value instanceof Object)) {
1073
- outputArray.push(value);
1074
- }
1075
- // else {
1076
- // //outputArray.push( null );
1077
- // // outputArray.push( $removed );
1078
- // }
1079
- } // end for
1080
-
1081
- return outputArray;
1082
- } else {
1083
- // obj is a not an array
1084
-
1085
- let isShallow = true;
1086
-
1087
- for (const key in objectOrArray) {
1088
- const value = objectOrArray[key];
1089
-
1090
- if (value instanceof Object) {
1091
- isShallow = false;
1092
- break;
1093
- }
1094
- // else {
1095
- // // outputObj[ key ] = null;
1096
- // // outputObj[ key ] = $removed;
1097
- // }
1098
- } // end for
1099
-
1100
- if (isShallow) {
1101
- // objectOrArray is already shallow -> nothing to do
1102
- return objectOrArray;
1103
- }
1104
-
1105
- const outputObj = {};
1106
-
1107
- for (const key in objectOrArray) {
1108
- const value = objectOrArray[key];
1109
-
1110
- if (!(value instanceof Object)) {
1111
- outputObj[key] = value;
1112
- }
1113
- // else {
1114
- // // outputObj[ key ] = null;
1115
- // // outputObj[ key ] = $removed;
1116
- // }
1117
- } // end for
1118
-
1119
- return outputObj;
1120
- }
1121
- }
1122
-
1123
1100
  // -----------------------------------------------------------------------------
1124
1101
 
1125
1102
  /**
@@ -1131,7 +1108,10 @@ export function shallowClone(objectOrArray) {
1131
1108
  *
1132
1109
  * @param {object} [obj] - Input object
1133
1110
  *
1134
- * @param {object|iterable} updateData - Data to update
1111
+ * @param {object|Iterable} updateData - Data to update
1112
+ *
1113
+ * @param {Object} [options]
1114
+ * @param {boolean} [options.replaceArrays=false]
1135
1115
  *
1136
1116
  * Note that if using path-value pairs, the order of the pairs is relevant:
1137
1117
  * {
@@ -1153,7 +1133,7 @@ export function updateObject(obj = null, updateData = null, options) {
1153
1133
 
1154
1134
  let pathValuePairs;
1155
1135
 
1156
- if (!isIterable(updateData)) {
1136
+ if (!is.iterable(updateData)) {
1157
1137
  // Convert updateData to path-value pairs (iterable)
1158
1138
 
1159
1139
  const walkArrays = options && options.replaceArrays ? true : false;
@@ -1243,7 +1223,9 @@ export function ensureArrayPath(path) {
1243
1223
  // Nothing to do
1244
1224
  return path;
1245
1225
  } else {
1246
- throw new Error('Missing or invalid parameter [path] (expected string or array)');
1226
+ throw new Error(
1227
+ 'Missing or invalid parameter [path] (expected string or array)'
1228
+ );
1247
1229
  }
1248
1230
  }
1249
1231
 
@@ -1260,11 +1242,11 @@ export function ensureArrayPath(path) {
1260
1242
  * Object to create the parent objects in
1261
1243
  *
1262
1244
  * @param {string[]} arrPath
1263
- * The path that specified which parent oobjects to create
1245
+ * The path that specified which parent objects to create
1264
1246
  *
1265
1247
  * @returns {object} the input object with the created object properties
1266
1248
  */
1267
- export function _ensureParent(obj, arrPath) {
1249
+ function _ensureParent(obj, arrPath) {
1268
1250
  // console.log("_ensureParent (1)", { obj, arrPath } );
1269
1251
 
1270
1252
  let current = obj;
@@ -1293,7 +1275,9 @@ export function _ensureParent(obj, arrPath) {
1293
1275
 
1294
1276
  if (Number.isNaN(nextKeyAsInt)) {
1295
1277
  // console.log("CHECK", { obj, arrPath, j, current, nextKey } );
1296
- throw new Error(`Cannot set property [${nextKey}] ` + 'on data node of type [Array]');
1278
+ throw new Error(
1279
+ `Cannot set property [${nextKey}] ` + 'on data node of type [Array]'
1280
+ );
1297
1281
  }
1298
1282
  }
1299
1283
  } else {
@@ -1322,7 +1306,7 @@ export function _ensureParent(obj, arrPath) {
1322
1306
  *
1323
1307
  * @returns {object|array|null} parent object or null if not found
1324
1308
  */
1325
- export function _getParent(obj, arrPath) {
1309
+ function _getParent(obj, arrPath) {
1326
1310
  let current = obj;
1327
1311
 
1328
1312
  for (let j = 0, n_1 = arrPath.length - 1; j < n_1; j = j + 1) {
@@ -33,7 +33,8 @@ import {
33
33
  MINUTE_MS,
34
34
  HOUR_MS,
35
35
  DAY_MS,
36
- TIME_2025_01_01 } from '../../constants/time/index.js';
36
+ TIME_2025_01_01
37
+ } from '../../constants/time/index.js';
37
38
 
38
39
  import * as expect from '../expect';
39
40
  import { HkPromise } from '../../classes/promise/index.js';
@@ -226,19 +227,21 @@ export function getWeekNumber(dateOrTimestamp) {
226
227
  *
227
228
  * @returns {string} name of the month in the specified locale
228
229
  */
229
- export function getMonthName(dateOrTimestamp, locale = 'nl-NL',
230
- options = { month: 'long' }) {
231
-
232
- const date = toDate(dateOrTimestamp);
230
+ export function getMonthName(
231
+ dateOrTimestamp,
232
+ locale = 'nl-NL',
233
+ options = { month: 'long' }
234
+ ) {
235
+ const date = toDate(dateOrTimestamp);
233
236
 
234
- // Create formatter with provided locale and options
235
- // @ts-ignore - TypeScript kan hier strikter zijn dan nodig met de options
236
- const formatter = new Intl.DateTimeFormat(locale, {
237
- month: options?.month || 'long',
238
- ...(options?.timeZone ? { timeZone: options.timeZone } : {})
239
- });
237
+ // Create formatter with provided locale and options
238
+ // @ts-ignore - TypeScript kan hier strikter zijn dan nodig met de options
239
+ const formatter = new Intl.DateTimeFormat(locale, {
240
+ month: options?.month || 'long',
241
+ ...(options?.timeZone ? { timeZone: options.timeZone } : {})
242
+ });
240
243
 
241
- return formatter.format(date);
244
+ return formatter.format(date);
242
245
  }
243
246
 
244
247
  /**
@@ -255,19 +258,21 @@ export function getMonthName(dateOrTimestamp, locale = 'nl-NL',
255
258
  *
256
259
  * @returns {string} name of the day in the specified locale
257
260
  */
258
- export function getDayName(dateOrTimestamp, locale = 'nl-NL',
259
- options = { weekday: 'long' }) {
260
-
261
- const date = toDate(dateOrTimestamp);
261
+ export function getDayName(
262
+ dateOrTimestamp,
263
+ locale = 'nl-NL',
264
+ options = { weekday: 'long' }
265
+ ) {
266
+ const date = toDate(dateOrTimestamp);
262
267
 
263
- // Create formatter with provided locale and options
264
- // @ts-ignore - TypeScript kan hier strikter zijn dan nodig met de options
265
- const formatter = new Intl.DateTimeFormat(locale, {
266
- weekday: options?.weekday || 'long',
267
- ...(options?.timeZone ? { timeZone: options.timeZone } : {})
268
- });
268
+ // Create formatter with provided locale and options
269
+ // @ts-ignore - TypeScript kan hier strikter zijn dan nodig met de options
270
+ const formatter = new Intl.DateTimeFormat(locale, {
271
+ weekday: options?.weekday || 'long',
272
+ ...(options?.timeZone ? { timeZone: options.timeZone } : {})
273
+ });
269
274
 
270
- return formatter.format(date);
275
+ return formatter.format(date);
271
276
  }
272
277
 
273
278
  /**