@hkdigital/lib-sveltekit 0.1.5 → 0.1.6

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.
Files changed (204) hide show
  1. package/README.md +127 -127
  2. package/dist/classes/data/IterableTree.js +243 -243
  3. package/dist/classes/data/Selector.js +190 -190
  4. package/dist/classes/data/index.js +2 -2
  5. package/dist/classes/index.js +3 -3
  6. package/dist/classes/promise/HkPromise.js +377 -377
  7. package/dist/classes/promise/index.js +1 -1
  8. package/dist/classes/stores/SubscribersCount.js +107 -107
  9. package/dist/classes/stores/index.js +1 -1
  10. package/dist/classes/streams/LogTransformStream.js +19 -19
  11. package/dist/classes/streams/ServerEventsStore.js +110 -110
  12. package/dist/classes/streams/TimeStampSource.js +26 -26
  13. package/dist/classes/streams/index.js +3 -3
  14. package/dist/classes/svelte/audio/AudioLoader.svelte.js +58 -58
  15. package/dist/classes/svelte/audio/AudioScene.svelte.js +282 -282
  16. package/dist/classes/svelte/audio/mocks.js +35 -35
  17. package/dist/classes/svelte/final-state-machine/FiniteStateMachine.svelte.js +133 -133
  18. package/dist/classes/svelte/final-state-machine/index.js +1 -1
  19. package/dist/classes/svelte/image/ImageLoader.svelte.js +47 -47
  20. package/dist/classes/svelte/image/ImageScene.svelte.js +253 -253
  21. package/dist/classes/svelte/image/ImageVariantsLoader.svelte.js +152 -152
  22. package/dist/classes/svelte/image/index.js +4 -4
  23. package/dist/classes/svelte/image/mocks.js +35 -35
  24. package/dist/classes/svelte/image/typedef.js +8 -8
  25. package/dist/classes/svelte/loading-state-machine/LoadingStateMachine.svelte.js +109 -109
  26. package/dist/classes/svelte/loading-state-machine/constants.js +16 -16
  27. package/dist/classes/svelte/loading-state-machine/index.js +3 -3
  28. package/dist/classes/svelte/network-loader/NetworkLoader.svelte.js +331 -331
  29. package/dist/classes/svelte/network-loader/constants.js +3 -3
  30. package/dist/classes/svelte/network-loader/index.js +3 -3
  31. package/dist/classes/svelte/network-loader/mocks.js +30 -30
  32. package/dist/classes/svelte/network-loader/typedef.js +8 -8
  33. package/dist/components/area/HkArea.svelte +49 -49
  34. package/dist/components/area/HkArea.svelte.d.ts +0 -14
  35. package/dist/components/area/HkGridArea.svelte +77 -77
  36. package/dist/components/area/HkGridArea.svelte.d.ts +0 -22
  37. package/dist/components/area/index.js +2 -2
  38. package/dist/components/boxes/game-box/GameBox.svelte +112 -112
  39. package/dist/components/boxes/game-box/GameBox.svelte.d.ts +0 -15
  40. package/dist/components/boxes/game-box/gamebox.util.js +83 -83
  41. package/dist/components/boxes/index.js +2 -2
  42. package/dist/components/boxes/virtual-viewport/VirtualViewport.svelte +199 -199
  43. package/dist/components/boxes/virtual-viewport/VirtualViewport.svelte.d.ts +0 -22
  44. package/dist/components/buttons/button/Button.svelte +75 -75
  45. package/dist/components/buttons/button/Button.svelte.d.ts +0 -21
  46. package/dist/components/buttons/button-text/TextButton.svelte +21 -21
  47. package/dist/components/buttons/button-text/TextButton.svelte.d.ts +0 -7
  48. package/dist/components/buttons/index.js +2 -2
  49. package/dist/components/hkdev/blocks/TextBlock.svelte +46 -46
  50. package/dist/components/hkdev/blocks/TextBlock.svelte.d.ts +0 -13
  51. package/dist/components/hkdev/buttons/CheckButton.svelte +62 -62
  52. package/dist/components/hkdev/buttons/CheckButton.svelte.d.ts +0 -18
  53. package/dist/components/icon/HkIcon.svelte +86 -86
  54. package/dist/components/icon/HkIcon.svelte.d.ts +0 -12
  55. package/dist/components/icon/HkTabIcon.svelte +116 -116
  56. package/dist/components/icon/HkTabIcon.svelte.d.ts +0 -21
  57. package/dist/components/icon/index.js +4 -4
  58. package/dist/components/icon/typedef.js +16 -16
  59. package/dist/components/image/ImageBox.svelte +208 -208
  60. package/dist/components/image/ImageBox.svelte.d.ts +0 -19
  61. package/dist/components/image/index.js +5 -5
  62. package/dist/components/image/typedef.js +32 -32
  63. package/dist/components/index.js +2 -2
  64. package/dist/components/inputs/index.js +1 -1
  65. package/dist/components/inputs/text-input/TestTextInput.svelte__ +102 -102
  66. package/dist/components/inputs/text-input/TextInput.svelte +226 -226
  67. package/dist/components/inputs/text-input/TextInput.svelte.d.ts +0 -28
  68. package/dist/components/inputs/text-input/TextInput.svelte___ +83 -83
  69. package/dist/components/inputs/text-input/assets/IconInvalid.svelte +14 -14
  70. package/dist/components/inputs/text-input/assets/IconValid.svelte +12 -12
  71. package/dist/components/layout/HkAppLayout.state.svelte.js +25 -25
  72. package/dist/components/layout/HkAppLayout.svelte +251 -251
  73. package/dist/components/layout/HkAppLayout.svelte.d.ts +0 -11
  74. package/dist/components/layout/HkGridLayers.svelte +82 -82
  75. package/dist/components/layout/HkGridLayers.svelte.d.ts +0 -23
  76. package/dist/components/layout/index.js +9 -9
  77. package/dist/components/panels/index.js +1 -1
  78. package/dist/components/panels/plain-panel/PlainPanel.svelte +33 -33
  79. package/dist/components/panels/plain-panel/PlainPanel.svelte.d.ts +0 -12
  80. package/dist/components/rows/index.js +3 -3
  81. package/dist/components/rows/panel-grid-row/PanelGridRow.svelte +104 -104
  82. package/dist/components/rows/panel-grid-row/PanelGridRow.svelte.d.ts +0 -14
  83. package/dist/components/rows/panel-row-2/PanelRow2.svelte +40 -40
  84. package/dist/components/rows/panel-row-2/PanelRow2.svelte.d.ts +0 -14
  85. package/dist/components/tab-bar/HkTabBar.state.svelte.js +149 -149
  86. package/dist/components/tab-bar/HkTabBar.svelte +74 -74
  87. package/dist/components/tab-bar/HkTabBar.svelte.d.ts +0 -18
  88. package/dist/components/tab-bar/HkTabBarSelector.state.svelte.js +93 -93
  89. package/dist/components/tab-bar/HkTabBarSelector.svelte +49 -49
  90. package/dist/components/tab-bar/HkTabBarSelector.svelte.d.ts +0 -19
  91. package/dist/components/tab-bar/index.js +17 -17
  92. package/dist/components/tab-bar/typedef.js +8 -8
  93. package/dist/components/widgets/compare-left-right/CompareLeftRight.svelte +179 -179
  94. package/dist/components/widgets/compare-left-right/CompareLeftRight.svelte.d.ts +0 -10
  95. package/dist/components/widgets/compare-left-right/index.js +1 -1
  96. package/dist/components/widgets/scale-control/index.js +1 -1
  97. package/dist/config/imagetools-config.js +189 -189
  98. package/dist/config/imagetools.d.ts +71 -71
  99. package/dist/config/typedef.js +8 -8
  100. package/dist/constants/errors/api.js +9 -9
  101. package/dist/constants/errors/generic.js +5 -5
  102. package/dist/constants/errors/index.js +3 -3
  103. package/dist/constants/errors/jwt.js +5 -5
  104. package/dist/constants/http/headers.js +6 -6
  105. package/dist/constants/http/index.js +2 -2
  106. package/dist/constants/http/methods.js +2 -2
  107. package/dist/constants/index.js +3 -3
  108. package/dist/constants/mime/application.js +5 -5
  109. package/dist/constants/mime/audio.js +13 -13
  110. package/dist/constants/mime/image.js +3 -3
  111. package/dist/constants/mime/index.js +4 -4
  112. package/dist/constants/mime/text.js +2 -2
  113. package/dist/constants/regexp/index.js +31 -31
  114. package/dist/constants/regexp/inspiratie.js__ +95 -95
  115. package/dist/constants/regexp/text.js +49 -49
  116. package/dist/constants/regexp/user.js +32 -32
  117. package/dist/constants/regexp/web.js +3 -3
  118. package/dist/constants/state-labels/input-states.js +11 -11
  119. package/dist/constants/state-labels/submit-states.js +4 -4
  120. package/dist/constants/time.js +28 -28
  121. package/dist/css/tw-prose.postcss__ +259 -259
  122. package/dist/css/utilities.postcss +43 -43
  123. package/dist/design/design-config.js +73 -73
  124. package/dist/design/tailwind-theme-extend.d.ts +4 -4
  125. package/dist/design/tailwind-theme-extend.js +151 -151
  126. package/dist/schemas/index.js +1 -1
  127. package/dist/schemas/validate-url.js +180 -180
  128. package/dist/server/index.js +1 -1
  129. package/dist/server/logger.js +94 -94
  130. package/dist/states/index.js +1 -1
  131. package/dist/states/navigation.svelte.js +55 -55
  132. package/dist/stores/index.js +1 -1
  133. package/dist/stores/theme.js +80 -80
  134. package/dist/themes/hkdev/components/blocks/text-block.postcss +40 -40
  135. package/dist/themes/hkdev/components/boxes/game-box.postcss +13 -13
  136. package/dist/themes/hkdev/components/buttons/button-text.postcss +34 -34
  137. package/dist/themes/hkdev/components/buttons/button.postcss +138 -138
  138. package/dist/themes/hkdev/components/buttons/skip-button.postcss +8 -8
  139. package/dist/themes/hkdev/components/inputs/text-input.postcss +108 -108
  140. package/dist/themes/hkdev/components/panels/plain-panel.postcss +46 -46
  141. package/dist/themes/hkdev/components/panels/speech-bubble.postcss +52 -52
  142. package/dist/themes/hkdev/components/rows/panel-grid-row.postcss +7 -7
  143. package/dist/themes/hkdev/components/rows/panel-row-2.postcss +9 -9
  144. package/dist/themes/hkdev/components.postcss +55 -55
  145. package/dist/themes/hkdev/debug.postcss +1 -1
  146. package/dist/themes/hkdev/global/layout.postcss +39 -39
  147. package/dist/themes/hkdev/global/on-colors.postcss +53 -53
  148. package/dist/themes/hkdev/global/text.postcss__ +34 -34
  149. package/dist/themes/hkdev/global/vars.postcss__ +7 -7
  150. package/dist/themes/hkdev/globals.postcss +11 -11
  151. package/dist/themes/hkdev/responsive.postcss +12 -12
  152. package/dist/themes/hkdev/theme-ext.js +15 -15
  153. package/dist/themes/hkdev/theme.js +227 -227
  154. package/dist/themes/index.js +1 -1
  155. package/dist/util/array/index.js +455 -455
  156. package/dist/util/compare/index.js +247 -247
  157. package/dist/util/css/css-vars.js +83 -83
  158. package/dist/util/css/index.js +1 -1
  159. package/dist/util/design-system/components/states.js +22 -22
  160. package/dist/util/design-system/css/clamp.d.ts +2 -2
  161. package/dist/util/design-system/css/clamp.js +66 -66
  162. package/dist/util/design-system/css/root-design-vars.js +100 -100
  163. package/dist/util/design-system/index.js +5 -5
  164. package/dist/util/design-system/layout/scaling.js +97 -97
  165. package/dist/util/design-system/tailwind.js +289 -289
  166. package/dist/util/expect/arrays.js +47 -47
  167. package/dist/util/expect/index.js +259 -259
  168. package/dist/util/expect/primitives.js +55 -55
  169. package/dist/util/expect/url.js +60 -60
  170. package/dist/util/function/index.js +218 -218
  171. package/dist/util/http/errors.js +97 -97
  172. package/dist/util/http/headers.js +45 -45
  173. package/dist/util/http/http-request.js +273 -273
  174. package/dist/util/http/index.js +22 -22
  175. package/dist/util/http/json-request.js +143 -143
  176. package/dist/util/http/mocks.js +65 -65
  177. package/dist/util/http/response.js +228 -228
  178. package/dist/util/http/url.js +52 -52
  179. package/dist/util/image/index.js +86 -86
  180. package/dist/util/index.js +2 -2
  181. package/dist/util/is/index.js +140 -140
  182. package/dist/util/iterate/index.js +234 -234
  183. package/dist/util/object/index.js +1361 -1361
  184. package/dist/util/singleton/index.js +97 -97
  185. package/dist/util/string/index.js +184 -184
  186. package/dist/util/svelte/index.js +2 -2
  187. package/dist/util/svelte/observe/index.js +49 -49
  188. package/dist/util/svelte/state-context/index.js +83 -83
  189. package/dist/util/svelte/wait/index.js +38 -38
  190. package/dist/util/sveltekit/index.js +1 -1
  191. package/dist/util/sveltekit/route-folders/index.js +82 -82
  192. package/dist/util/time/index.js +339 -339
  193. package/dist/valibot/date.js__ +10 -10
  194. package/dist/valibot/index.js +9 -9
  195. package/dist/valibot/url.js +95 -95
  196. package/dist/valibot/user.js +23 -23
  197. package/dist/zod/all.js +33 -33
  198. package/dist/zod/generic.js +11 -11
  199. package/dist/zod/javascript.js +32 -32
  200. package/dist/zod/user.js +16 -16
  201. package/dist/zod/web.js +52 -52
  202. package/package.json +99 -99
  203. package/dist/themes/hkdev/components/buttons/button.postcss__ +0 -40
  204. package/dist/themes/hkdev/components/buttons/button.postcss___ +0 -91
@@ -1,1361 +1,1361 @@
1
- /* ------------------------------------------------------------------ Imports */
2
-
3
- import * as expect from '../expect/index.js';
4
-
5
- import { equals } from '../compare/index.js';
6
-
7
- import { toArrayPath } from '../array/index.js';
8
-
9
- import { toStringPath } from '../string/index.js';
10
-
11
- import { isIterable } from '../is/index.js';
12
-
13
- import { iterateObjectPaths, iterateObjectEntries } from '../iterate/index.js';
14
-
15
- // ------------------------------------------------------------------- Internals
16
-
17
- const PATH_SEPARATOR = '.';
18
-
19
- /**
20
- * Create a human friendly string representation of an array path
21
- * - Allows for removal of the last part of the array part
22
- *
23
- * @param {string[]} arr - Array path to join
24
- * @param {number} [lastIndex]
25
- * If specified, only parts up and including the last index will
26
- * be joined
27
- *
28
- * @returns {string} path as string
29
- */
30
- function display_array_path(arr, lastIndex) {
31
- return arr.slice(0, lastIndex).join(PATH_SEPARATOR);
32
- }
33
-
34
- const object_to_string = Object.prototype.toString;
35
- const has_own_property = Object.prototype.hasOwnProperty;
36
-
37
- /* ------------------------------------------------------------------ Exports */
38
-
39
- export { PATH_SEPARATOR };
40
-
41
- // -----------------------------------------------------------------------------
42
-
43
- /**
44
- * Returns true
45
- * - if the object has no (iterable) key value pairs
46
- * - if the object is an empty array
47
- *
48
- * @param {object} obj
49
- *
50
- * @return {boolean}
51
- * true if the object has no key value pairs or the supplied object
52
- * is `falsey`
53
- */
54
- export function isEmpty(obj) {
55
- if (!obj) {
56
- // object is null or other falsey value
57
- return true;
58
- }
59
-
60
- expect.object(obj);
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;
71
- }
72
-
73
- // -----------------------------------------------------------------------------
74
-
75
- /**
76
- * Get the number of (iterable) key value pairs in the specified object
77
- *
78
- * @param {object} obj
79
- *
80
- * @return {number} number of iterable key value pairs
81
- */
82
- export function objectSize(obj) {
83
- expect.object(obj);
84
-
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;
93
- }
94
-
95
- // -----------------------------------------------------------------------------
96
-
97
- /**
98
- * Returns a shallow copy of the object without the properties that
99
- * have the value [null] or [undefined]
100
- *
101
- * @param {object} obj
102
- *
103
- * @param {string[]} [onlyKeys]
104
- * If specified, only the specified keys will be exported
105
- *
106
- * @returns {object} new object without the null properties
107
- */
108
- export function exportNotNull(obj, onlyKeys) {
109
- expect.object(obj);
110
-
111
- // if( onlyKeys )
112
- // {
113
- // expect.array( onlyKeys );
114
- // }
115
-
116
- const newObj = {};
117
-
118
- const onlyKeysSet = onlyKeys ? new Set(onlyKeys) : null;
119
-
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;
133
- }
134
-
135
- // -----------------------------------------------------------------------------
136
-
137
- /**
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
- * `_`.
142
- *
143
- * @param {object} obj
144
- *
145
- * @param {string[]} [keepKeys]
146
- * If specified, the sprecified private keys will be exported (e.g. `_id`)
147
- *
148
- * @returns {object} new object without the null properties
149
- */
150
- export function exportNotPrivate(obj, keepKeys) {
151
- expect.object(obj);
152
-
153
- const newObj = {};
154
-
155
- const keepKeysSet = keepKeys ? new Set(keepKeys) : null;
156
-
157
- for (const key in obj) {
158
- const value = obj[key];
159
-
160
- if (!key.startsWith('_')) {
161
- newObj[key] = value;
162
- } else if (keepKeysSet && keepKeysSet.has(key)) {
163
- //
164
- // Add key to keep as read only property
165
- //
166
- Object.defineProperty(newObj, key, {
167
- value,
168
- writable: false,
169
- enumerable: true
170
- });
171
- }
172
- } // end for
173
-
174
- return newObj;
175
- }
176
-
177
- // -----------------------------------------------------------------------------
178
-
179
- // export function removeNull()
180
-
181
- // -----------------------------------------------------------------------------
182
-
183
- /**
184
- * Keep only the specified keys in the object
185
- * - deletes all other key-value pairs in the object
186
- *
187
- * @param {object} obj
188
- * @param {string[]|Set} keys
189
- * @param {boolean} [removeNullAndUndefined=true]
190
- *
191
- * @returns {object} object that only contains the specified keys
192
- */
193
- export function keep(obj, keys, removeNullAndUndefined = true) {
194
- expect.object(obj);
195
- expect.arrayOrSet(keys);
196
-
197
- const keep = keys instanceof Set ? keys : new Set(keys);
198
-
199
- for (const key in obj) {
200
- if (!keep.has(key)) {
201
- delete obj[key];
202
- continue;
203
- }
204
-
205
- const value = obj[key];
206
-
207
- if (removeNullAndUndefined) {
208
- if (value === null || value === undefined) {
209
- delete obj[key];
210
- }
211
- }
212
- } // end for
213
-
214
- return obj;
215
- }
216
-
217
- // -----------------------------------------------------------------------------
218
-
219
- /**
220
- * Freezes an object recursively
221
- * - Allows non-objects to be passed as input parameter (non-objects are
222
- * immutable by default).
223
- *
224
- * @param {any} value
225
- *
226
- * @returns {any}
227
- * recursively frozen object or original input value if a non-object was
228
- * supplied as input parameter
229
- */
230
- export function deepFreeze(value, _found) {
231
- if (!(value instanceof Object)) {
232
- return value;
233
- }
234
-
235
- if (!_found) {
236
- _found = new Set();
237
- } else if (_found.has(value)) {
238
- // Using recursion -> no need to return value
239
- return;
240
- }
241
-
242
- _found.add(value);
243
-
244
- Object.freeze(value);
245
-
246
- for (const key in value) {
247
- const childObj = value[key];
248
-
249
- if (childObj instanceof Object) {
250
- // Recurse into child objects
251
- deepFreeze(childObj, _found);
252
- }
253
- } // end for
254
-
255
- return value;
256
- }
257
-
258
- // -----------------------------------------------------------------------------
259
-
260
- /**
261
- * Set a value in an object using a path and value pair.
262
- * - Automatically creates parent objects
263
- *
264
- * @param {object} obj - Object to set the value in
265
- * @param {string|Array} path - Dot separated string path or array path
266
- * @param {any} value - value to set
267
- *
268
- * @returns {boolean} true if the value was changed
269
- */
270
- export function objectSet(obj, path, value) {
271
- expect.object(obj);
272
-
273
- const arrPath = toArrayPath(path);
274
-
275
- if (arguments.length < 3) {
276
- throw new Error('Missing or invalid parameter [value]');
277
- }
278
-
279
- let parentNode;
280
- const lastKey = arrPath[arrPath.length - 1];
281
-
282
- if (value !== undefined) {
283
- parentNode = _ensureParent(obj, arrPath);
284
- } else {
285
- //
286
- // value is undefined -> delete node
287
- //
288
- parentNode = _getParent(obj, arrPath);
289
-
290
- if (Array.isArray(parentNode)) {
291
- const keyAsInt = parseInt(lastKey, 10);
292
-
293
- if (Number.isNaN(keyAsInt)) {
294
- throw new Error(
295
- 'Cannot delete property [' +
296
- arrPath.join(PATH_SEPARATOR) +
297
- '] ' +
298
- 'from data node of type [Array]'
299
- );
300
- }
301
-
302
- if (keyAsInt < parentNode.length) {
303
- parentNode.splice(keyAsInt, 1);
304
- return true;
305
- }
306
-
307
- return false;
308
- } else if (parentNode) {
309
- if (lastKey in parentNode) {
310
- delete parentNode[lastKey];
311
- return true;
312
- }
313
- return false;
314
- }
315
- }
316
-
317
- // -- Set value
318
-
319
- const existingValue = parentNode[lastKey];
320
-
321
- if (!equals(value, existingValue)) {
322
- parentNode[lastKey] = value;
323
-
324
- return true;
325
- }
326
-
327
- return false;
328
- }
329
-
330
- // -----------------------------------------------------------------------------
331
-
332
- /**
333
- * Removes a value at the specified object path from the object.
334
- * - All parent objects that remain empty will be removed too (recursively)
335
- *
336
- * @param {object} obj - Object to set the value in
337
- * @param {string|Array} path - Dot separated string path or array path
338
- * @param {any} value - value to set
339
- */
340
- export function deletePath(obj, path) {
341
- expect.object(obj);
342
-
343
- const arrPath = toArrayPath(path);
344
-
345
- const n = arrPath.length;
346
- const n_1 = n - 1;
347
-
348
- if (!n) {
349
- // Path is empty ""
350
- return;
351
- }
352
-
353
- const lastKey = arrPath[n_1];
354
-
355
- if (1 === n) {
356
- // Path consist of a single key
357
- delete obj[lastKey];
358
- return;
359
- }
360
-
361
- // path is longer than a single key >>
362
-
363
- // -- Get parent objects
364
-
365
- const parents = [];
366
-
367
- let current = obj;
368
-
369
- let endValueFound = true;
370
-
371
- for (let j = 0; j < n; j = j + 1) {
372
- if (!(current instanceof Object)) {
373
- break;
374
- }
375
-
376
- parents.push(current);
377
-
378
- const key = arrPath[j];
379
-
380
- // console.log(
381
- // {
382
- // current,
383
- // key,
384
- // next: current[ key ]
385
- // } );
386
-
387
- if (!(key in current)) {
388
- // child not found -> no more parents
389
- endValueFound = false;
390
- break;
391
- }
392
-
393
- current = current[key];
394
- }
395
-
396
- // console.log( "parents", parents );
397
-
398
- // -- Delete value from direct parent
399
-
400
- const n_parents = parents.length - 1;
401
-
402
- if (endValueFound) {
403
- const lastParent = parents[n_parents];
404
-
405
- if (!Array.isArray(lastParent)) {
406
- delete lastParent[lastKey];
407
- } else {
408
- lastParent.splice(parseInt(lastKey, 10), 1);
409
- }
410
- }
411
-
412
- // -- Remove empty parents
413
-
414
- for (let j = n_parents - 1; j >= 0; j = j - 1) {
415
- const parent = parents[j];
416
- const key = arrPath[j];
417
- const child = parent[key];
418
-
419
- let childIsEmpty = false;
420
-
421
- if (Array.isArray(child)) {
422
- // Child is array
423
- if (0 === child.length) {
424
- childIsEmpty = true;
425
- }
426
- } else {
427
- // Child is object
428
- if (0 === Object.keys(child).length) {
429
- childIsEmpty = true;
430
- }
431
- }
432
-
433
- if (!childIsEmpty) {
434
- // done
435
- break;
436
- }
437
-
438
- // Remove empty child from parent
439
-
440
- if (!Array.isArray(parent)) {
441
- delete parent[key];
442
- break;
443
- } else {
444
- parent.splice(parseInt(key, 10), 1);
445
- }
446
- } // end for
447
- }
448
-
449
- // -----------------------------------------------------------------------------
450
-
451
- /**
452
- * Get a value from an object using a path
453
- * - Returns a default value if not found, with is [undefined] by default
454
- *
455
- * @param {object} obj - Object to get the value from
456
- * @param {string|Array} path - Dot separated string path or array path
457
- *
458
- * @param {any} [defaultValue=undefined]
459
- * Value to return if the value does not exist
460
- *
461
- * @return {any} value found at path, defaultValue or undefined
462
- */
463
- export function objectGet(obj, path, defaultValue) {
464
- expect.object(obj);
465
-
466
- const arrPath = toArrayPath(path);
467
-
468
- if (!path.length || (1 === path.length && !path[0].length)) {
469
- // "" or [""]
470
- return obj;
471
- }
472
-
473
- const parentNode = _getParent(obj, arrPath);
474
-
475
- if (!parentNode) {
476
- return defaultValue; // @note may be undefined
477
- }
478
-
479
- const lastKey = arrPath[arrPath.length - 1];
480
-
481
- const value = parentNode[lastKey];
482
-
483
- if (value === undefined) {
484
- return defaultValue; // @note may be undefined
485
- }
486
-
487
- return value;
488
- }
489
-
490
- // -----------------------------------------------------------------------------
491
-
492
- /**
493
- * Get a value from an object using a path
494
- * - Throws an exception if the path does not exist or the value is undefined
495
- *
496
- * @param {object} obj - Object to get the value from
497
- * @param {string|Array} path - Dot separated string path or array path
498
- *
499
- * @param {function} [parseFn]
500
- * Optional parser function that checks and converts the value
501
- *
502
- * @throws No value found at path
503
- * @throws Invalid value
504
- *
505
- * @return {any} value found at path
506
- */
507
- export function objectGetWithThrow(obj, path, parseFn) {
508
- let value = objectGet(obj, path);
509
-
510
- if (parseFn) {
511
- const { value: parsedValue, error } = parseFn(value);
512
-
513
- if (error) {
514
- throw new Error(`Invalid value found at path [${toStringPath(path)}]`, { cause: error });
515
- }
516
-
517
- value = parsedValue;
518
- }
519
-
520
- if (value === undefined) {
521
- throw new Error(`No value found at path [${toStringPath(path)}]`);
522
- }
523
-
524
- return value;
525
- }
526
-
527
- // -----------------------------------------------------------------------------
528
-
529
- // DEV >>>>
530
-
531
- /**
532
- * Get an iterator that returns the value of a path for each item (object)
533
- * in the list of objects
534
- *
535
- * @param {object[]} arr - Array of objects
536
- * @param {string|string[]} path - Dot separated string path or array path
537
- *
538
- * @param {object} [options] - options
539
- *
540
- * DEPRECEATED >>> NOT COMPATIBLE WITH LIGHTWEIGHT ITERATOR
541
- * @param {object} [options.unique=false] - Only return unique values
542
- *
543
- * @param {any} [options.defaultValue]
544
- * Value to return if the value does not exist
545
- *
546
- * @returns {Iterator<mixed>} value at the specified path for each item
547
- */
548
- // export function values( arr, path, options )
549
- // {
550
- // if( !Array.isArray(arr) )
551
- // {
552
- // throw new Error("Missing or invalid parameter [arr] (expected Array)");
553
- // }
554
-
555
- // if( typeof path !== "string" && !Array.isArray(path) )
556
- // {
557
- // throw new Error(
558
- // "Missing or invalid parameter [path] (expected string or Array");
559
- // }
560
-
561
- // options = Object.assign(
562
- // {
563
- // unique: false,
564
- // defaultValue: undefined
565
- // },
566
- // options );
567
-
568
- // if( options.unique )
569
- // {
570
- // // Keep track of all values to prevent duplicates
571
- // }
572
-
573
- // throw new Error("NOT IMPLEMENTED YET");
574
- // }
575
-
576
- // <<< DEV
577
-
578
- // -----------------------------------------------------------------------------
579
-
580
- /**
581
- * Returns a list of differences between the object before and the object
582
- * after the changes.
583
- * - By default, the function returns changes for added, updated and removed
584
- * properties
585
- *
586
- * @param {object} objBefore
587
- * @param {object} objAfter
588
- *
589
- * @param {object} options
590
- * @param {boolean} [options.ignoreAdd=false]
591
- * @param {boolean} [options.ignoreUpdate=false]
592
- * @param {boolean} [options.ignoreDelete=false]
593
- *
594
- * @param {boolean} [options.ignorePrivate=true]
595
- * Ignore properties that start with an underscore e.g. _id or _updatedAt
596
- *
597
- * @param {boolean} [options.deleteValue=null]
598
- *
599
- * @returns {array}
600
- * List of changes between the object before and object after
601
- */
602
- export function objectDiff(objBefore, objAfter, options = {}, _recursion) {
603
- const changes = [];
604
-
605
- const ignoreAdd = undefined === options.ignoreAdd ? false : options.ignoreAdd;
606
-
607
- const ignoreUpdate = undefined === options.ignoreUpdate ? false : options.ignoreUpdate;
608
-
609
- const ignoreDelete = undefined === options.ignoreDelete ? false : options.ignoreDelete;
610
-
611
- const ignorePrivate = undefined === options.ignorePrivate ? true : options.ignorePrivate;
612
-
613
- let deleteValue = null;
614
-
615
- if ('deletevalue' in options) {
616
- // Might be [undefined]
617
- deleteValue = options.deleteValue;
618
- }
619
-
620
- objBefore = objBefore || {};
621
-
622
- for (const key in objAfter) {
623
- if (ignorePrivate && key.startsWith('_')) {
624
- continue;
625
- }
626
-
627
- const newValue = objAfter[key];
628
-
629
- if (deleteValue !== newValue) {
630
- const previousValue = objBefore[key];
631
-
632
- if (newValue === previousValue) {
633
- // No change
634
- continue;
635
- }
636
-
637
- if (previousValue instanceof Object && newValue instanceof Object) {
638
- // TODO: _recursion
639
-
640
- const diff = objectDiff(newValue, previousValue, options, _recursion);
641
-
642
- if (0 === diff.length) {
643
- // No change
644
- continue;
645
- }
646
- }
647
-
648
- if (undefined !== previousValue) {
649
- if (!ignoreUpdate) {
650
- // update property value
651
- changes.push({ path: key, set: newValue });
652
- }
653
- } else if (!ignoreAdd) {
654
- // add property
655
- changes.push({ path: key, set: newValue });
656
- }
657
- } else if (!ignoreDelete) {
658
- // newValue === deleteValue && ignoreDelete=true
659
- changes.push({ path: key, unset: 1 });
660
- }
661
- } // end for
662
-
663
- return changes;
664
- }
665
-
666
- // -----------------------------------------------------------------------------
667
-
668
- /**
669
- * Applies a list of differences to the input object
670
- * - A list of changes can be generated by e.g. objectDiff
671
- *
672
- * @param {object} obj
673
- * @param {object[]} changes
674
- *
675
- * @param {object} options
676
- * @param {boolean} [options.ignoreAdd=false]
677
- * @param {boolean} [options.ignoreUpdate=false]
678
- * @param {boolean} [options.ignoreDelete=false]
679
- *
680
- * @param {boolean} [options.ignorePrivate=true]
681
- * Ignore properties that start with an underscore e.g. _id or _updatedAt
682
- */
683
- export function patchObject(obj, changes, options = {}) {
684
- expect.object(obj);
685
- expect.array(changes);
686
-
687
- const ignoreAdd = undefined === options.ignoreAdd ? false : options.ignoreAdd;
688
-
689
- const ignoreUpdate = undefined === options.ignoreUpdate ? false : options.ignoreUpdate;
690
-
691
- const ignoreDelete = undefined === options.ignoreDelete ? false : options.ignoreDelete;
692
-
693
- const ignorePrivate = undefined === options.ignorePrivate ? true : options.ignorePrivate;
694
-
695
- for (let j = 0, n = changes.length; j < n; j = j + 1) {
696
- const change = changes[j];
697
-
698
- const path = change.path;
699
-
700
- // FIXME: recursion
701
-
702
- if (ignorePrivate && path.startsWith('_')) {
703
- continue;
704
- }
705
-
706
- if ('unset' in change) {
707
- if (ignoreDelete) {
708
- continue;
709
- }
710
-
711
- // console.log( "DELETE", change );
712
- objectSet(obj, path, undefined); // undefined deletes property
713
- continue;
714
- }
715
-
716
- const newValue = change.set;
717
-
718
- if (undefined === newValue) {
719
- //logError("Invalid [change]", ignoreDelete, change);
720
- throw new Error(`Cannot set value [${path}=undefined]`);
721
- }
722
-
723
- if (ignoreAdd && Object._get(obj, path) === undefined) {
724
- // Ignore add
725
- continue;
726
- } else if (ignoreUpdate && objectGet(obj, path) !== undefined) {
727
- // Ignore update
728
- continue;
729
- }
730
-
731
- // Set new value
732
- objectSet(obj, path, newValue);
733
- } // end for
734
- }
735
-
736
- // -----------------------------------------------------------------------------
737
-
738
- /**
739
- * Extend the target object with methods and properties from the source
740
- * property object
741
- * - The target object will be extended by inserting the source property
742
- * object into it's property chain
743
-
744
- * - If the current target's prototype is not the same as the source
745
- * prototype, the source prototype will be cloned
746
- *
747
- * @param {object} target - Target object
748
- * @param {object} source
749
- * object to append to the target's prototype chain. The object and it's
750
- * prototype objects are cloned first
751
- */
752
- export function extend(target, source) {
753
- expect.objectNoFunction(target);
754
-
755
- if (Object.isFrozen(target)) {
756
- throw new Error('Invalid parameter [target] (object is immutable)');
757
- }
758
-
759
- expect.objectNoFunction(source);
760
-
761
- // let sourceProto = source.prototype;
762
-
763
- let targetProto = Object.getPrototypeOf(target);
764
-
765
- if (targetProto === Object.prototype || Object.isFrozen(targetProto)) {
766
- // FIXME: || Object.isFrozen(targetProto) should not be necessary!
767
-
768
- targetProto = target;
769
- }
770
-
771
- {
772
- let obj = source;
773
-
774
- while (obj && obj !== Object.prototype) {
775
- const next = Object.getPrototypeOf(obj);
776
-
777
- copyOwnProperties(obj, targetProto);
778
-
779
- obj = next;
780
- }
781
- }
782
-
783
- return;
784
- }
785
-
786
- // -----------------------------------------------------------------------------
787
-
788
- /**
789
- * Get a list of property names of the specified object
790
- *
791
- * @param {object} obj
792
- *
793
- * @returns {string[]} List of property names
794
- */
795
- export function getPrototypeNames(obj) {
796
- expect.object(obj);
797
-
798
- let proto = obj.prototype || obj;
799
-
800
- const names = [];
801
-
802
- while (proto) {
803
- // console.log( proto );
804
-
805
- names.push(proto.constructor.name);
806
-
807
- proto = Object.getPrototypeOf(proto);
808
- }
809
-
810
- return names;
811
- }
812
-
813
- // -----------------------------------------------------------------------------
814
-
815
- /**
816
- * Get a tree of values from an object
817
- * - Can returns default values if one or multiple values were not found
818
- *
819
- * @param {object} obj - Object to get the value from
820
- * @param {object} tree
821
- * Tree that contains the object paths to get.
822
- *
823
- * e.g. { path: 1 }
824
- * { some: { path: { to: 1 }, otherPath: { to: 1 } } }
825
- * { "some.path.to": 1, "some.otherPath.to": 1 }
826
- * { someArray.0.name: 1 }
827
- *
828
- * @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.
832
- *
833
- * @return {object}
834
- * nested object with the values that were found or defaultValues
835
- */
836
- export function getTree(obj, tree, options) {
837
- expect.object(obj);
838
- expect.object(tree);
839
-
840
- let shallowLeaves = false;
841
-
842
- if (options instanceof Object && options.shallowLeaves) {
843
- shallowLeaves = true;
844
- }
845
-
846
- const it = iterateObjectPaths(tree, { walkArrays: true });
847
-
848
- const result = {};
849
-
850
- for (let arrPath of it) {
851
- // console.log( { arrPath } );
852
-
853
- if (1 === arrPath.length && arrPath[0].includes(PATH_SEPARATOR)) {
854
- // Convert "short path syntax" to array path
855
- arrPath = arrPath[0].split(PATH_SEPARATOR);
856
- }
857
-
858
- // -- Get value from object at current path
859
-
860
- const leaveValue = objectGet(obj, arrPath);
861
-
862
- // -- Set value in result object at current path
863
-
864
- if (!shallowLeaves || !(leaveValue instanceof Object)) {
865
- // option: shallowLeaves=false OR value is not an object
866
- // -> no need to convert leaves to shallow objects
867
- objectSet(result, arrPath, leaveValue);
868
- } else {
869
- // Set shallow leave value instead of nested object
870
-
871
- const shallowLeaveValue = shallowClone(leaveValue);
872
- objectSet(result, arrPath, shallowLeaveValue);
873
- }
874
- } // end for
875
-
876
- return result;
877
- }
878
-
879
- // -----------------------------------------------------------------------------
880
-
881
- /**
882
- * 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
- *
892
- * @param {any} objectToBeCloned - Variable to clone
893
- *
894
- * @returns {any} cloned output
895
- */
896
- export function clone(objectToBeCloned, _seenObjects) {
897
- // const startTime = Date.now();
898
-
899
- // -- Return references for all variables that are not objects
900
-
901
- if (!(objectToBeCloned instanceof Object)) {
902
- return objectToBeCloned;
903
- }
904
-
905
- // --- Check if variable has already been cloned before
906
-
907
- if (!_seenObjects) {
908
- _seenObjects = { originals: [], clones: [] };
909
- } else {
910
- const originals = _seenObjects.originals;
911
-
912
- if (originals.length > 0) {
913
- const foundIndex = originals.indexOf(objectToBeCloned);
914
-
915
- if (-1 !== foundIndex) {
916
- //console.log("(recursion) return
917
- //from [_seenObjects]", _seenObjects.clones, foundIndex);
918
- return _seenObjects.clones[foundIndex];
919
- }
920
- }
921
- }
922
-
923
- // -- Handle array (like) objects
924
-
925
- const typeString = object_to_string.call(objectToBeCloned);
926
-
927
- if (Array.isArray(objectToBeCloned) || '[object Arguments]' === typeString) {
928
- const objectClone = [];
929
-
930
- for (let j = 0, n = objectToBeCloned.length; j < n; j = j + 1) {
931
- objectClone[j] = clone(objectToBeCloned[j], _seenObjects);
932
- }
933
-
934
- _seenObjects.originals.push(objectToBeCloned);
935
- _seenObjects.clones.push(objectClone);
936
-
937
- return objectClone;
938
- }
939
-
940
- // -- Handle not clonable objects
941
-
942
- if ('[object Object]' !== typeString || objectToBeCloned instanceof Error) {
943
- // Functions, Browser objects, ...
944
- // - Not clonable -> return reference
945
- // - No need to add to _seenObjects
946
- return objectToBeCloned;
947
- }
948
-
949
- // -- Handle special objects
950
- {
951
- // Filter out special objects.
952
- const Constructor = objectToBeCloned.constructor;
953
-
954
- let objectClone;
955
-
956
- switch (Constructor) {
957
- case RegExp:
958
- objectClone = new Constructor(objectToBeCloned);
959
- break;
960
-
961
- case Date:
962
- objectClone = new Constructor(objectToBeCloned.getTime());
963
- break;
964
-
965
- // TODO: other special objects ...
966
- }
967
-
968
- if (objectClone) {
969
- _seenObjects.originals.push(objectToBeCloned);
970
- _seenObjects.clones.push(objectClone);
971
-
972
- return objectClone;
973
- }
974
-
975
- if (objectToBeCloned.hkDoNotClone) {
976
- // Not clonable flag was set -> return reference
977
- // TODO: Other objects that should not be cloned?
978
- return objectToBeCloned;
979
- }
980
- }
981
-
982
- // -- Create new object and clone properties
983
-
984
- // objectClone = new Constructor();
985
- // const prototypeClone = Object.create(null);
986
-
987
- const prototypeClone = {};
988
- const objectClone = Object.create(prototypeClone);
989
-
990
- // Already push object to _seenObjects
991
- // (objectClone will still change as properties are being cloned)
992
- _seenObjects.originals.push(objectToBeCloned);
993
- _seenObjects.clones.push(objectClone);
994
-
995
- for (const prop in objectToBeCloned) {
996
- if (has_own_property.call(objectToBeCloned, prop)) {
997
- // Own property -> clone into object
998
- objectClone[prop] = clone(objectToBeCloned[prop], _seenObjects);
999
- } else {
1000
- // Inherited property -> clone into prototype
1001
-
1002
- // @warning
1003
- // known issue: all inherited properties are copied into the
1004
- // same prototype object!
1005
-
1006
- prototypeClone[prop] = clone(objectToBeCloned[prop], _seenObjects);
1007
- }
1008
- }
1009
-
1010
- return objectClone;
1011
- }
1012
-
1013
- // -----------------------------------------------------------------------------
1014
-
1015
- /**
1016
- * Set a read only property in an object
1017
- *
1018
- * @param {object} obj - Object to set the read only property in
1019
- * @param {string} propertyName - Name of the property to set
1020
- * @param {any} value - Value to set
1021
- */
1022
- export function setReadOnlyProperty(obj, propertyName, value) {
1023
- expect.object(obj);
1024
-
1025
- expect.string(propertyName);
1026
-
1027
- // expect.defined(value);
1028
-
1029
- Object.defineProperty(obj, propertyName, {
1030
- value,
1031
- writable: false,
1032
- enumerable: true
1033
- });
1034
- }
1035
-
1036
- // -----------------------------------------------------------------------------
1037
-
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
- // -----------------------------------------------------------------------------
1124
-
1125
- /**
1126
- * Update an object
1127
- * - Sets the path-value pairs from the updateData in the object
1128
- * - Existing values will be overwritten
1129
- * - Existing intermediate values (objects, arrays) will be overwritten too
1130
- * (if updateData is an object, not an iterable)
1131
- *
1132
- * @param {object} [obj] - Input object
1133
- *
1134
- * @param {object|iterable} updateData - Data to update
1135
- *
1136
- * Note that if using path-value pairs, the order of the pairs is relevant:
1137
- * {
1138
- * "some": {},
1139
- * "some.path": {},
1140
- * "some.path.to": 2,
1141
- * }
1142
- *
1143
- * @returns {object} updated object
1144
- */
1145
- export function updateObject(obj = null, updateData = null, options) {
1146
- // -- Check parameter [obj]
1147
-
1148
- expect.object(obj);
1149
-
1150
- expect.object(updateData);
1151
-
1152
- // -- Update cloned object
1153
-
1154
- let pathValuePairs;
1155
-
1156
- if (!isIterable(updateData)) {
1157
- // Convert updateData to path-value pairs (iterable)
1158
-
1159
- const walkArrays = options && options.replaceArrays ? true : false;
1160
-
1161
- pathValuePairs = iterateObjectEntries(updateData, {
1162
- expandPathKeys: true,
1163
- outputIntermediateNodes: true,
1164
- walkArrays
1165
- });
1166
-
1167
- // pathValuePairs = Array.from( pathValuePairs );
1168
- // console.log( "CHECK", { updateData, pathValuePairs } );
1169
- } else {
1170
- // Iterable -> assume iterable of path-value pairs
1171
- pathValuePairs = updateData;
1172
- }
1173
-
1174
- for (const [arrPath, value] of pathValuePairs) {
1175
- // console.log( "updateObject:set", arrPath, value );
1176
- objectSet(obj, arrPath, value);
1177
- }
1178
-
1179
- return obj;
1180
- }
1181
-
1182
- // -----------------------------------------------------------------------------
1183
-
1184
- /**
1185
- * Copy own properties from an object to another object if they do not
1186
- * exist yet.
1187
- *
1188
- * @param {object} from - Object ot copy properties from
1189
- * @param {object} to - Object to copy properties to
1190
- */
1191
- export function copyOwnProperties(from, to) {
1192
- const propertyNames = Object.getOwnPropertyNames(from);
1193
-
1194
- const nProperties = propertyNames.length;
1195
-
1196
- if (!nProperties) {
1197
- return;
1198
- }
1199
-
1200
- const firstIsConstructor = 'constructor' === propertyNames[0] ? true : false;
1201
-
1202
- if (1 === nProperties && firstIsConstructor) {
1203
- return;
1204
- }
1205
-
1206
- const startAt = firstIsConstructor ? 1 : 0;
1207
-
1208
- // console.log("propertyNames", from, propertyNames, startAt);
1209
-
1210
- for (let j = startAt; j < nProperties; j = j + 1) {
1211
- const key = propertyNames[j];
1212
-
1213
- const descriptor = Object.getOwnPropertyDescriptor(from, key);
1214
-
1215
- const targetDescriptor = Object.getOwnPropertyDescriptor(to, key);
1216
-
1217
- // Not needed, we copy an instance
1218
- // descriptor.value = clone( descriptor.value );
1219
-
1220
- if (!targetDescriptor) {
1221
- // Property does not yet exist
1222
- Object.defineProperty(to, key, descriptor);
1223
- }
1224
- } // end for
1225
-
1226
- // >>> FIXME? TODO? copy Symbols too? <<<
1227
- }
1228
-
1229
- // -----------------------------------------------------------------------------
1230
-
1231
- /**
1232
- * Convert string with dot separated values to a list of values
1233
- * - Accepts that the supplied path is already an array path
1234
- *
1235
- * @param {string|string[]} path
1236
- *
1237
- * @returns {string[]} list of path values
1238
- */
1239
- export function ensureArrayPath(path) {
1240
- if (typeof path === 'string') {
1241
- return path.split(PATH_SEPARATOR);
1242
- } else if (Array.isArray(path)) {
1243
- // Nothing to do
1244
- return path;
1245
- } else {
1246
- throw new Error('Missing or invalid parameter [path] (expected string or array)');
1247
- }
1248
- }
1249
-
1250
- /* ----------------------------------------------------- Internal methods */
1251
-
1252
- // -----------------------------------------------------------------------------
1253
-
1254
- /**
1255
- * Create all parent objects on the object path if they do not yet exist yet
1256
- * - This method will throw an exception if there is a non-object node in
1257
- * the path
1258
- *
1259
- * @param {object} obj
1260
- * Object to create the parent objects in
1261
- *
1262
- * @param {string[]} arrPath
1263
- * The path that specified which parent oobjects to create
1264
- *
1265
- * @returns {object} the input object with the created object properties
1266
- */
1267
- export function _ensureParent(obj, arrPath) {
1268
- // console.log("_ensureParent (1)", { obj, arrPath } );
1269
-
1270
- let current = obj;
1271
- let prev = current;
1272
-
1273
- for (let j = 0, n_1 = arrPath.length - 1; j < n_1; j = j + 1) {
1274
- const key = arrPath[j];
1275
-
1276
- current = current[key];
1277
-
1278
- if (current === undefined || current === null) {
1279
- current = prev[key] = {};
1280
- prev = current;
1281
- continue;
1282
- }
1283
-
1284
- const nextKey = arrPath[j + 1];
1285
-
1286
- // Check
1287
- // Check if current is an object
1288
- // If current is an array, check if the nextKey can be set on a array
1289
-
1290
- if (current instanceof Object) {
1291
- if (Array.isArray(current) && j < n_1) {
1292
- const nextKeyAsInt = parseInt(nextKey, 10);
1293
-
1294
- if (Number.isNaN(nextKeyAsInt)) {
1295
- // console.log("CHECK", { obj, arrPath, j, current, nextKey } );
1296
- throw new Error(`Cannot set property [${nextKey}] ` + 'on data node of type [Array]');
1297
- }
1298
- }
1299
- } else {
1300
- // console.log( { current, prev, key, arrPath, j } );
1301
-
1302
- throw new Error(
1303
- `Cannot set property [${nextKey}] from ` +
1304
- `path [${display_array_path(arrPath)}] on data node that is not ` +
1305
- 'an object or an array'
1306
- );
1307
- }
1308
-
1309
- prev = current;
1310
- } // end for
1311
-
1312
- return current;
1313
- }
1314
-
1315
- // -----------------------------------------------------------------------------
1316
-
1317
- /**
1318
- * Get parent object at the specified path
1319
- *
1320
- * @param {object} obj - Object to work in
1321
- * @param {string[]} arrPath - Path to get the parent object for
1322
- *
1323
- * @returns {object|array|null} parent object or null if not found
1324
- */
1325
- export function _getParent(obj, arrPath) {
1326
- let current = obj;
1327
-
1328
- for (let j = 0, n_1 = arrPath.length - 1; j < n_1; j = j + 1) {
1329
- let key = arrPath[j];
1330
-
1331
- current = current[key];
1332
-
1333
- if (typeof current === 'undefined') {
1334
- return null;
1335
- }
1336
-
1337
- if (current instanceof Object) {
1338
- if (Array.isArray(current) && j < n_1 - 1) {
1339
- // node is an array
1340
- // -> use next part of the array path as (numerical) array index
1341
-
1342
- key = arrPath[j + 1];
1343
- const keyAsInt = parseInt(key, 10);
1344
-
1345
- if (Number.isNaN(keyAsInt)) {
1346
- throw new Error(
1347
- `Cannot get property [${display_array_path(arrPath, j)}]` +
1348
- 'from data node of type [Array]'
1349
- );
1350
- }
1351
- }
1352
- } else {
1353
- throw new Error(
1354
- `Cannot get property [${display_array_path(arrPath, j)}]` +
1355
- 'from a data node that is not an object or an array'
1356
- );
1357
- }
1358
- } // end for
1359
-
1360
- return current;
1361
- }
1
+ /* ------------------------------------------------------------------ Imports */
2
+
3
+ import * as expect from '../expect/index.js';
4
+
5
+ import { equals } from '../compare/index.js';
6
+
7
+ import { toArrayPath } from '../array/index.js';
8
+
9
+ import { toStringPath } from '../string/index.js';
10
+
11
+ import { isIterable } from '../is/index.js';
12
+
13
+ import { iterateObjectPaths, iterateObjectEntries } from '../iterate/index.js';
14
+
15
+ // ------------------------------------------------------------------- Internals
16
+
17
+ const PATH_SEPARATOR = '.';
18
+
19
+ /**
20
+ * Create a human friendly string representation of an array path
21
+ * - Allows for removal of the last part of the array part
22
+ *
23
+ * @param {string[]} arr - Array path to join
24
+ * @param {number} [lastIndex]
25
+ * If specified, only parts up and including the last index will
26
+ * be joined
27
+ *
28
+ * @returns {string} path as string
29
+ */
30
+ function display_array_path(arr, lastIndex) {
31
+ return arr.slice(0, lastIndex).join(PATH_SEPARATOR);
32
+ }
33
+
34
+ const object_to_string = Object.prototype.toString;
35
+ const has_own_property = Object.prototype.hasOwnProperty;
36
+
37
+ /* ------------------------------------------------------------------ Exports */
38
+
39
+ export { PATH_SEPARATOR };
40
+
41
+ // -----------------------------------------------------------------------------
42
+
43
+ /**
44
+ * Returns true
45
+ * - if the object has no (iterable) key value pairs
46
+ * - if the object is an empty array
47
+ *
48
+ * @param {object} obj
49
+ *
50
+ * @return {boolean}
51
+ * true if the object has no key value pairs or the supplied object
52
+ * is `falsey`
53
+ */
54
+ export function isEmpty(obj) {
55
+ if (!obj) {
56
+ // object is null or other falsey value
57
+ return true;
58
+ }
59
+
60
+ expect.object(obj);
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;
71
+ }
72
+
73
+ // -----------------------------------------------------------------------------
74
+
75
+ /**
76
+ * Get the number of (iterable) key value pairs in the specified object
77
+ *
78
+ * @param {object} obj
79
+ *
80
+ * @return {number} number of iterable key value pairs
81
+ */
82
+ export function objectSize(obj) {
83
+ expect.object(obj);
84
+
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;
93
+ }
94
+
95
+ // -----------------------------------------------------------------------------
96
+
97
+ /**
98
+ * Returns a shallow copy of the object without the properties that
99
+ * have the value [null] or [undefined]
100
+ *
101
+ * @param {object} obj
102
+ *
103
+ * @param {string[]} [onlyKeys]
104
+ * If specified, only the specified keys will be exported
105
+ *
106
+ * @returns {object} new object without the null properties
107
+ */
108
+ export function exportNotNull(obj, onlyKeys) {
109
+ expect.object(obj);
110
+
111
+ // if( onlyKeys )
112
+ // {
113
+ // expect.array( onlyKeys );
114
+ // }
115
+
116
+ const newObj = {};
117
+
118
+ const onlyKeysSet = onlyKeys ? new Set(onlyKeys) : null;
119
+
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;
133
+ }
134
+
135
+ // -----------------------------------------------------------------------------
136
+
137
+ /**
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
+ * `_`.
142
+ *
143
+ * @param {object} obj
144
+ *
145
+ * @param {string[]} [keepKeys]
146
+ * If specified, the sprecified private keys will be exported (e.g. `_id`)
147
+ *
148
+ * @returns {object} new object without the null properties
149
+ */
150
+ export function exportNotPrivate(obj, keepKeys) {
151
+ expect.object(obj);
152
+
153
+ const newObj = {};
154
+
155
+ const keepKeysSet = keepKeys ? new Set(keepKeys) : null;
156
+
157
+ for (const key in obj) {
158
+ const value = obj[key];
159
+
160
+ if (!key.startsWith('_')) {
161
+ newObj[key] = value;
162
+ } else if (keepKeysSet && keepKeysSet.has(key)) {
163
+ //
164
+ // Add key to keep as read only property
165
+ //
166
+ Object.defineProperty(newObj, key, {
167
+ value,
168
+ writable: false,
169
+ enumerable: true
170
+ });
171
+ }
172
+ } // end for
173
+
174
+ return newObj;
175
+ }
176
+
177
+ // -----------------------------------------------------------------------------
178
+
179
+ // export function removeNull()
180
+
181
+ // -----------------------------------------------------------------------------
182
+
183
+ /**
184
+ * Keep only the specified keys in the object
185
+ * - deletes all other key-value pairs in the object
186
+ *
187
+ * @param {object} obj
188
+ * @param {string[]|Set} keys
189
+ * @param {boolean} [removeNullAndUndefined=true]
190
+ *
191
+ * @returns {object} object that only contains the specified keys
192
+ */
193
+ export function keep(obj, keys, removeNullAndUndefined = true) {
194
+ expect.object(obj);
195
+ expect.arrayOrSet(keys);
196
+
197
+ const keep = keys instanceof Set ? keys : new Set(keys);
198
+
199
+ for (const key in obj) {
200
+ if (!keep.has(key)) {
201
+ delete obj[key];
202
+ continue;
203
+ }
204
+
205
+ const value = obj[key];
206
+
207
+ if (removeNullAndUndefined) {
208
+ if (value === null || value === undefined) {
209
+ delete obj[key];
210
+ }
211
+ }
212
+ } // end for
213
+
214
+ return obj;
215
+ }
216
+
217
+ // -----------------------------------------------------------------------------
218
+
219
+ /**
220
+ * Freezes an object recursively
221
+ * - Allows non-objects to be passed as input parameter (non-objects are
222
+ * immutable by default).
223
+ *
224
+ * @param {any} value
225
+ *
226
+ * @returns {any}
227
+ * recursively frozen object or original input value if a non-object was
228
+ * supplied as input parameter
229
+ */
230
+ export function deepFreeze(value, _found) {
231
+ if (!(value instanceof Object)) {
232
+ return value;
233
+ }
234
+
235
+ if (!_found) {
236
+ _found = new Set();
237
+ } else if (_found.has(value)) {
238
+ // Using recursion -> no need to return value
239
+ return;
240
+ }
241
+
242
+ _found.add(value);
243
+
244
+ Object.freeze(value);
245
+
246
+ for (const key in value) {
247
+ const childObj = value[key];
248
+
249
+ if (childObj instanceof Object) {
250
+ // Recurse into child objects
251
+ deepFreeze(childObj, _found);
252
+ }
253
+ } // end for
254
+
255
+ return value;
256
+ }
257
+
258
+ // -----------------------------------------------------------------------------
259
+
260
+ /**
261
+ * Set a value in an object using a path and value pair.
262
+ * - Automatically creates parent objects
263
+ *
264
+ * @param {object} obj - Object to set the value in
265
+ * @param {string|Array} path - Dot separated string path or array path
266
+ * @param {any} value - value to set
267
+ *
268
+ * @returns {boolean} true if the value was changed
269
+ */
270
+ export function objectSet(obj, path, value) {
271
+ expect.object(obj);
272
+
273
+ const arrPath = toArrayPath(path);
274
+
275
+ if (arguments.length < 3) {
276
+ throw new Error('Missing or invalid parameter [value]');
277
+ }
278
+
279
+ let parentNode;
280
+ const lastKey = arrPath[arrPath.length - 1];
281
+
282
+ if (value !== undefined) {
283
+ parentNode = _ensureParent(obj, arrPath);
284
+ } else {
285
+ //
286
+ // value is undefined -> delete node
287
+ //
288
+ parentNode = _getParent(obj, arrPath);
289
+
290
+ if (Array.isArray(parentNode)) {
291
+ const keyAsInt = parseInt(lastKey, 10);
292
+
293
+ if (Number.isNaN(keyAsInt)) {
294
+ throw new Error(
295
+ 'Cannot delete property [' +
296
+ arrPath.join(PATH_SEPARATOR) +
297
+ '] ' +
298
+ 'from data node of type [Array]'
299
+ );
300
+ }
301
+
302
+ if (keyAsInt < parentNode.length) {
303
+ parentNode.splice(keyAsInt, 1);
304
+ return true;
305
+ }
306
+
307
+ return false;
308
+ } else if (parentNode) {
309
+ if (lastKey in parentNode) {
310
+ delete parentNode[lastKey];
311
+ return true;
312
+ }
313
+ return false;
314
+ }
315
+ }
316
+
317
+ // -- Set value
318
+
319
+ const existingValue = parentNode[lastKey];
320
+
321
+ if (!equals(value, existingValue)) {
322
+ parentNode[lastKey] = value;
323
+
324
+ return true;
325
+ }
326
+
327
+ return false;
328
+ }
329
+
330
+ // -----------------------------------------------------------------------------
331
+
332
+ /**
333
+ * Removes a value at the specified object path from the object.
334
+ * - All parent objects that remain empty will be removed too (recursively)
335
+ *
336
+ * @param {object} obj - Object to set the value in
337
+ * @param {string|Array} path - Dot separated string path or array path
338
+ * @param {any} value - value to set
339
+ */
340
+ export function deletePath(obj, path) {
341
+ expect.object(obj);
342
+
343
+ const arrPath = toArrayPath(path);
344
+
345
+ const n = arrPath.length;
346
+ const n_1 = n - 1;
347
+
348
+ if (!n) {
349
+ // Path is empty ""
350
+ return;
351
+ }
352
+
353
+ const lastKey = arrPath[n_1];
354
+
355
+ if (1 === n) {
356
+ // Path consist of a single key
357
+ delete obj[lastKey];
358
+ return;
359
+ }
360
+
361
+ // path is longer than a single key >>
362
+
363
+ // -- Get parent objects
364
+
365
+ const parents = [];
366
+
367
+ let current = obj;
368
+
369
+ let endValueFound = true;
370
+
371
+ for (let j = 0; j < n; j = j + 1) {
372
+ if (!(current instanceof Object)) {
373
+ break;
374
+ }
375
+
376
+ parents.push(current);
377
+
378
+ const key = arrPath[j];
379
+
380
+ // console.log(
381
+ // {
382
+ // current,
383
+ // key,
384
+ // next: current[ key ]
385
+ // } );
386
+
387
+ if (!(key in current)) {
388
+ // child not found -> no more parents
389
+ endValueFound = false;
390
+ break;
391
+ }
392
+
393
+ current = current[key];
394
+ }
395
+
396
+ // console.log( "parents", parents );
397
+
398
+ // -- Delete value from direct parent
399
+
400
+ const n_parents = parents.length - 1;
401
+
402
+ if (endValueFound) {
403
+ const lastParent = parents[n_parents];
404
+
405
+ if (!Array.isArray(lastParent)) {
406
+ delete lastParent[lastKey];
407
+ } else {
408
+ lastParent.splice(parseInt(lastKey, 10), 1);
409
+ }
410
+ }
411
+
412
+ // -- Remove empty parents
413
+
414
+ for (let j = n_parents - 1; j >= 0; j = j - 1) {
415
+ const parent = parents[j];
416
+ const key = arrPath[j];
417
+ const child = parent[key];
418
+
419
+ let childIsEmpty = false;
420
+
421
+ if (Array.isArray(child)) {
422
+ // Child is array
423
+ if (0 === child.length) {
424
+ childIsEmpty = true;
425
+ }
426
+ } else {
427
+ // Child is object
428
+ if (0 === Object.keys(child).length) {
429
+ childIsEmpty = true;
430
+ }
431
+ }
432
+
433
+ if (!childIsEmpty) {
434
+ // done
435
+ break;
436
+ }
437
+
438
+ // Remove empty child from parent
439
+
440
+ if (!Array.isArray(parent)) {
441
+ delete parent[key];
442
+ break;
443
+ } else {
444
+ parent.splice(parseInt(key, 10), 1);
445
+ }
446
+ } // end for
447
+ }
448
+
449
+ // -----------------------------------------------------------------------------
450
+
451
+ /**
452
+ * Get a value from an object using a path
453
+ * - Returns a default value if not found, with is [undefined] by default
454
+ *
455
+ * @param {object} obj - Object to get the value from
456
+ * @param {string|Array} path - Dot separated string path or array path
457
+ *
458
+ * @param {any} [defaultValue=undefined]
459
+ * Value to return if the value does not exist
460
+ *
461
+ * @return {any} value found at path, defaultValue or undefined
462
+ */
463
+ export function objectGet(obj, path, defaultValue) {
464
+ expect.object(obj);
465
+
466
+ const arrPath = toArrayPath(path);
467
+
468
+ if (!path.length || (1 === path.length && !path[0].length)) {
469
+ // "" or [""]
470
+ return obj;
471
+ }
472
+
473
+ const parentNode = _getParent(obj, arrPath);
474
+
475
+ if (!parentNode) {
476
+ return defaultValue; // @note may be undefined
477
+ }
478
+
479
+ const lastKey = arrPath[arrPath.length - 1];
480
+
481
+ const value = parentNode[lastKey];
482
+
483
+ if (value === undefined) {
484
+ return defaultValue; // @note may be undefined
485
+ }
486
+
487
+ return value;
488
+ }
489
+
490
+ // -----------------------------------------------------------------------------
491
+
492
+ /**
493
+ * Get a value from an object using a path
494
+ * - Throws an exception if the path does not exist or the value is undefined
495
+ *
496
+ * @param {object} obj - Object to get the value from
497
+ * @param {string|Array} path - Dot separated string path or array path
498
+ *
499
+ * @param {function} [parseFn]
500
+ * Optional parser function that checks and converts the value
501
+ *
502
+ * @throws No value found at path
503
+ * @throws Invalid value
504
+ *
505
+ * @return {any} value found at path
506
+ */
507
+ export function objectGetWithThrow(obj, path, parseFn) {
508
+ let value = objectGet(obj, path);
509
+
510
+ if (parseFn) {
511
+ const { value: parsedValue, error } = parseFn(value);
512
+
513
+ if (error) {
514
+ throw new Error(`Invalid value found at path [${toStringPath(path)}]`, { cause: error });
515
+ }
516
+
517
+ value = parsedValue;
518
+ }
519
+
520
+ if (value === undefined) {
521
+ throw new Error(`No value found at path [${toStringPath(path)}]`);
522
+ }
523
+
524
+ return value;
525
+ }
526
+
527
+ // -----------------------------------------------------------------------------
528
+
529
+ // DEV >>>>
530
+
531
+ /**
532
+ * Get an iterator that returns the value of a path for each item (object)
533
+ * in the list of objects
534
+ *
535
+ * @param {object[]} arr - Array of objects
536
+ * @param {string|string[]} path - Dot separated string path or array path
537
+ *
538
+ * @param {object} [options] - options
539
+ *
540
+ * DEPRECEATED >>> NOT COMPATIBLE WITH LIGHTWEIGHT ITERATOR
541
+ * @param {object} [options.unique=false] - Only return unique values
542
+ *
543
+ * @param {any} [options.defaultValue]
544
+ * Value to return if the value does not exist
545
+ *
546
+ * @returns {Iterator<mixed>} value at the specified path for each item
547
+ */
548
+ // export function values( arr, path, options )
549
+ // {
550
+ // if( !Array.isArray(arr) )
551
+ // {
552
+ // throw new Error("Missing or invalid parameter [arr] (expected Array)");
553
+ // }
554
+
555
+ // if( typeof path !== "string" && !Array.isArray(path) )
556
+ // {
557
+ // throw new Error(
558
+ // "Missing or invalid parameter [path] (expected string or Array");
559
+ // }
560
+
561
+ // options = Object.assign(
562
+ // {
563
+ // unique: false,
564
+ // defaultValue: undefined
565
+ // },
566
+ // options );
567
+
568
+ // if( options.unique )
569
+ // {
570
+ // // Keep track of all values to prevent duplicates
571
+ // }
572
+
573
+ // throw new Error("NOT IMPLEMENTED YET");
574
+ // }
575
+
576
+ // <<< DEV
577
+
578
+ // -----------------------------------------------------------------------------
579
+
580
+ /**
581
+ * Returns a list of differences between the object before and the object
582
+ * after the changes.
583
+ * - By default, the function returns changes for added, updated and removed
584
+ * properties
585
+ *
586
+ * @param {object} objBefore
587
+ * @param {object} objAfter
588
+ *
589
+ * @param {object} options
590
+ * @param {boolean} [options.ignoreAdd=false]
591
+ * @param {boolean} [options.ignoreUpdate=false]
592
+ * @param {boolean} [options.ignoreDelete=false]
593
+ *
594
+ * @param {boolean} [options.ignorePrivate=true]
595
+ * Ignore properties that start with an underscore e.g. _id or _updatedAt
596
+ *
597
+ * @param {boolean} [options.deleteValue=null]
598
+ *
599
+ * @returns {array}
600
+ * List of changes between the object before and object after
601
+ */
602
+ export function objectDiff(objBefore, objAfter, options = {}, _recursion) {
603
+ const changes = [];
604
+
605
+ const ignoreAdd = undefined === options.ignoreAdd ? false : options.ignoreAdd;
606
+
607
+ const ignoreUpdate = undefined === options.ignoreUpdate ? false : options.ignoreUpdate;
608
+
609
+ const ignoreDelete = undefined === options.ignoreDelete ? false : options.ignoreDelete;
610
+
611
+ const ignorePrivate = undefined === options.ignorePrivate ? true : options.ignorePrivate;
612
+
613
+ let deleteValue = null;
614
+
615
+ if ('deletevalue' in options) {
616
+ // Might be [undefined]
617
+ deleteValue = options.deleteValue;
618
+ }
619
+
620
+ objBefore = objBefore || {};
621
+
622
+ for (const key in objAfter) {
623
+ if (ignorePrivate && key.startsWith('_')) {
624
+ continue;
625
+ }
626
+
627
+ const newValue = objAfter[key];
628
+
629
+ if (deleteValue !== newValue) {
630
+ const previousValue = objBefore[key];
631
+
632
+ if (newValue === previousValue) {
633
+ // No change
634
+ continue;
635
+ }
636
+
637
+ if (previousValue instanceof Object && newValue instanceof Object) {
638
+ // TODO: _recursion
639
+
640
+ const diff = objectDiff(newValue, previousValue, options, _recursion);
641
+
642
+ if (0 === diff.length) {
643
+ // No change
644
+ continue;
645
+ }
646
+ }
647
+
648
+ if (undefined !== previousValue) {
649
+ if (!ignoreUpdate) {
650
+ // update property value
651
+ changes.push({ path: key, set: newValue });
652
+ }
653
+ } else if (!ignoreAdd) {
654
+ // add property
655
+ changes.push({ path: key, set: newValue });
656
+ }
657
+ } else if (!ignoreDelete) {
658
+ // newValue === deleteValue && ignoreDelete=true
659
+ changes.push({ path: key, unset: 1 });
660
+ }
661
+ } // end for
662
+
663
+ return changes;
664
+ }
665
+
666
+ // -----------------------------------------------------------------------------
667
+
668
+ /**
669
+ * Applies a list of differences to the input object
670
+ * - A list of changes can be generated by e.g. objectDiff
671
+ *
672
+ * @param {object} obj
673
+ * @param {object[]} changes
674
+ *
675
+ * @param {object} options
676
+ * @param {boolean} [options.ignoreAdd=false]
677
+ * @param {boolean} [options.ignoreUpdate=false]
678
+ * @param {boolean} [options.ignoreDelete=false]
679
+ *
680
+ * @param {boolean} [options.ignorePrivate=true]
681
+ * Ignore properties that start with an underscore e.g. _id or _updatedAt
682
+ */
683
+ export function patchObject(obj, changes, options = {}) {
684
+ expect.object(obj);
685
+ expect.array(changes);
686
+
687
+ const ignoreAdd = undefined === options.ignoreAdd ? false : options.ignoreAdd;
688
+
689
+ const ignoreUpdate = undefined === options.ignoreUpdate ? false : options.ignoreUpdate;
690
+
691
+ const ignoreDelete = undefined === options.ignoreDelete ? false : options.ignoreDelete;
692
+
693
+ const ignorePrivate = undefined === options.ignorePrivate ? true : options.ignorePrivate;
694
+
695
+ for (let j = 0, n = changes.length; j < n; j = j + 1) {
696
+ const change = changes[j];
697
+
698
+ const path = change.path;
699
+
700
+ // FIXME: recursion
701
+
702
+ if (ignorePrivate && path.startsWith('_')) {
703
+ continue;
704
+ }
705
+
706
+ if ('unset' in change) {
707
+ if (ignoreDelete) {
708
+ continue;
709
+ }
710
+
711
+ // console.log( "DELETE", change );
712
+ objectSet(obj, path, undefined); // undefined deletes property
713
+ continue;
714
+ }
715
+
716
+ const newValue = change.set;
717
+
718
+ if (undefined === newValue) {
719
+ //logError("Invalid [change]", ignoreDelete, change);
720
+ throw new Error(`Cannot set value [${path}=undefined]`);
721
+ }
722
+
723
+ if (ignoreAdd && Object._get(obj, path) === undefined) {
724
+ // Ignore add
725
+ continue;
726
+ } else if (ignoreUpdate && objectGet(obj, path) !== undefined) {
727
+ // Ignore update
728
+ continue;
729
+ }
730
+
731
+ // Set new value
732
+ objectSet(obj, path, newValue);
733
+ } // end for
734
+ }
735
+
736
+ // -----------------------------------------------------------------------------
737
+
738
+ /**
739
+ * Extend the target object with methods and properties from the source
740
+ * property object
741
+ * - The target object will be extended by inserting the source property
742
+ * object into it's property chain
743
+
744
+ * - If the current target's prototype is not the same as the source
745
+ * prototype, the source prototype will be cloned
746
+ *
747
+ * @param {object} target - Target object
748
+ * @param {object} source
749
+ * object to append to the target's prototype chain. The object and it's
750
+ * prototype objects are cloned first
751
+ */
752
+ export function extend(target, source) {
753
+ expect.objectNoFunction(target);
754
+
755
+ if (Object.isFrozen(target)) {
756
+ throw new Error('Invalid parameter [target] (object is immutable)');
757
+ }
758
+
759
+ expect.objectNoFunction(source);
760
+
761
+ // let sourceProto = source.prototype;
762
+
763
+ let targetProto = Object.getPrototypeOf(target);
764
+
765
+ if (targetProto === Object.prototype || Object.isFrozen(targetProto)) {
766
+ // FIXME: || Object.isFrozen(targetProto) should not be necessary!
767
+
768
+ targetProto = target;
769
+ }
770
+
771
+ {
772
+ let obj = source;
773
+
774
+ while (obj && obj !== Object.prototype) {
775
+ const next = Object.getPrototypeOf(obj);
776
+
777
+ copyOwnProperties(obj, targetProto);
778
+
779
+ obj = next;
780
+ }
781
+ }
782
+
783
+ return;
784
+ }
785
+
786
+ // -----------------------------------------------------------------------------
787
+
788
+ /**
789
+ * Get a list of property names of the specified object
790
+ *
791
+ * @param {object} obj
792
+ *
793
+ * @returns {string[]} List of property names
794
+ */
795
+ export function getPrototypeNames(obj) {
796
+ expect.object(obj);
797
+
798
+ let proto = obj.prototype || obj;
799
+
800
+ const names = [];
801
+
802
+ while (proto) {
803
+ // console.log( proto );
804
+
805
+ names.push(proto.constructor.name);
806
+
807
+ proto = Object.getPrototypeOf(proto);
808
+ }
809
+
810
+ return names;
811
+ }
812
+
813
+ // -----------------------------------------------------------------------------
814
+
815
+ /**
816
+ * Get a tree of values from an object
817
+ * - Can returns default values if one or multiple values were not found
818
+ *
819
+ * @param {object} obj - Object to get the value from
820
+ * @param {object} tree
821
+ * Tree that contains the object paths to get.
822
+ *
823
+ * e.g. { path: 1 }
824
+ * { some: { path: { to: 1 }, otherPath: { to: 1 } } }
825
+ * { "some.path.to": 1, "some.otherPath.to": 1 }
826
+ * { someArray.0.name: 1 }
827
+ *
828
+ * @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.
832
+ *
833
+ * @return {object}
834
+ * nested object with the values that were found or defaultValues
835
+ */
836
+ export function getTree(obj, tree, options) {
837
+ expect.object(obj);
838
+ expect.object(tree);
839
+
840
+ let shallowLeaves = false;
841
+
842
+ if (options instanceof Object && options.shallowLeaves) {
843
+ shallowLeaves = true;
844
+ }
845
+
846
+ const it = iterateObjectPaths(tree, { walkArrays: true });
847
+
848
+ const result = {};
849
+
850
+ for (let arrPath of it) {
851
+ // console.log( { arrPath } );
852
+
853
+ if (1 === arrPath.length && arrPath[0].includes(PATH_SEPARATOR)) {
854
+ // Convert "short path syntax" to array path
855
+ arrPath = arrPath[0].split(PATH_SEPARATOR);
856
+ }
857
+
858
+ // -- Get value from object at current path
859
+
860
+ const leaveValue = objectGet(obj, arrPath);
861
+
862
+ // -- Set value in result object at current path
863
+
864
+ if (!shallowLeaves || !(leaveValue instanceof Object)) {
865
+ // option: shallowLeaves=false OR value is not an object
866
+ // -> no need to convert leaves to shallow objects
867
+ objectSet(result, arrPath, leaveValue);
868
+ } else {
869
+ // Set shallow leave value instead of nested object
870
+
871
+ const shallowLeaveValue = shallowClone(leaveValue);
872
+ objectSet(result, arrPath, shallowLeaveValue);
873
+ }
874
+ } // end for
875
+
876
+ return result;
877
+ }
878
+
879
+ // -----------------------------------------------------------------------------
880
+
881
+ /**
882
+ * 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
+ *
892
+ * @param {any} objectToBeCloned - Variable to clone
893
+ *
894
+ * @returns {any} cloned output
895
+ */
896
+ export function clone(objectToBeCloned, _seenObjects) {
897
+ // const startTime = Date.now();
898
+
899
+ // -- Return references for all variables that are not objects
900
+
901
+ if (!(objectToBeCloned instanceof Object)) {
902
+ return objectToBeCloned;
903
+ }
904
+
905
+ // --- Check if variable has already been cloned before
906
+
907
+ if (!_seenObjects) {
908
+ _seenObjects = { originals: [], clones: [] };
909
+ } else {
910
+ const originals = _seenObjects.originals;
911
+
912
+ if (originals.length > 0) {
913
+ const foundIndex = originals.indexOf(objectToBeCloned);
914
+
915
+ if (-1 !== foundIndex) {
916
+ //console.log("(recursion) return
917
+ //from [_seenObjects]", _seenObjects.clones, foundIndex);
918
+ return _seenObjects.clones[foundIndex];
919
+ }
920
+ }
921
+ }
922
+
923
+ // -- Handle array (like) objects
924
+
925
+ const typeString = object_to_string.call(objectToBeCloned);
926
+
927
+ if (Array.isArray(objectToBeCloned) || '[object Arguments]' === typeString) {
928
+ const objectClone = [];
929
+
930
+ for (let j = 0, n = objectToBeCloned.length; j < n; j = j + 1) {
931
+ objectClone[j] = clone(objectToBeCloned[j], _seenObjects);
932
+ }
933
+
934
+ _seenObjects.originals.push(objectToBeCloned);
935
+ _seenObjects.clones.push(objectClone);
936
+
937
+ return objectClone;
938
+ }
939
+
940
+ // -- Handle not clonable objects
941
+
942
+ if ('[object Object]' !== typeString || objectToBeCloned instanceof Error) {
943
+ // Functions, Browser objects, ...
944
+ // - Not clonable -> return reference
945
+ // - No need to add to _seenObjects
946
+ return objectToBeCloned;
947
+ }
948
+
949
+ // -- Handle special objects
950
+ {
951
+ // Filter out special objects.
952
+ const Constructor = objectToBeCloned.constructor;
953
+
954
+ let objectClone;
955
+
956
+ switch (Constructor) {
957
+ case RegExp:
958
+ objectClone = new Constructor(objectToBeCloned);
959
+ break;
960
+
961
+ case Date:
962
+ objectClone = new Constructor(objectToBeCloned.getTime());
963
+ break;
964
+
965
+ // TODO: other special objects ...
966
+ }
967
+
968
+ if (objectClone) {
969
+ _seenObjects.originals.push(objectToBeCloned);
970
+ _seenObjects.clones.push(objectClone);
971
+
972
+ return objectClone;
973
+ }
974
+
975
+ if (objectToBeCloned.hkDoNotClone) {
976
+ // Not clonable flag was set -> return reference
977
+ // TODO: Other objects that should not be cloned?
978
+ return objectToBeCloned;
979
+ }
980
+ }
981
+
982
+ // -- Create new object and clone properties
983
+
984
+ // objectClone = new Constructor();
985
+ // const prototypeClone = Object.create(null);
986
+
987
+ const prototypeClone = {};
988
+ const objectClone = Object.create(prototypeClone);
989
+
990
+ // Already push object to _seenObjects
991
+ // (objectClone will still change as properties are being cloned)
992
+ _seenObjects.originals.push(objectToBeCloned);
993
+ _seenObjects.clones.push(objectClone);
994
+
995
+ for (const prop in objectToBeCloned) {
996
+ if (has_own_property.call(objectToBeCloned, prop)) {
997
+ // Own property -> clone into object
998
+ objectClone[prop] = clone(objectToBeCloned[prop], _seenObjects);
999
+ } else {
1000
+ // Inherited property -> clone into prototype
1001
+
1002
+ // @warning
1003
+ // known issue: all inherited properties are copied into the
1004
+ // same prototype object!
1005
+
1006
+ prototypeClone[prop] = clone(objectToBeCloned[prop], _seenObjects);
1007
+ }
1008
+ }
1009
+
1010
+ return objectClone;
1011
+ }
1012
+
1013
+ // -----------------------------------------------------------------------------
1014
+
1015
+ /**
1016
+ * Set a read only property in an object
1017
+ *
1018
+ * @param {object} obj - Object to set the read only property in
1019
+ * @param {string} propertyName - Name of the property to set
1020
+ * @param {any} value - Value to set
1021
+ */
1022
+ export function setReadOnlyProperty(obj, propertyName, value) {
1023
+ expect.object(obj);
1024
+
1025
+ expect.string(propertyName);
1026
+
1027
+ // expect.defined(value);
1028
+
1029
+ Object.defineProperty(obj, propertyName, {
1030
+ value,
1031
+ writable: false,
1032
+ enumerable: true
1033
+ });
1034
+ }
1035
+
1036
+ // -----------------------------------------------------------------------------
1037
+
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
+ // -----------------------------------------------------------------------------
1124
+
1125
+ /**
1126
+ * Update an object
1127
+ * - Sets the path-value pairs from the updateData in the object
1128
+ * - Existing values will be overwritten
1129
+ * - Existing intermediate values (objects, arrays) will be overwritten too
1130
+ * (if updateData is an object, not an iterable)
1131
+ *
1132
+ * @param {object} [obj] - Input object
1133
+ *
1134
+ * @param {object|iterable} updateData - Data to update
1135
+ *
1136
+ * Note that if using path-value pairs, the order of the pairs is relevant:
1137
+ * {
1138
+ * "some": {},
1139
+ * "some.path": {},
1140
+ * "some.path.to": 2,
1141
+ * }
1142
+ *
1143
+ * @returns {object} updated object
1144
+ */
1145
+ export function updateObject(obj = null, updateData = null, options) {
1146
+ // -- Check parameter [obj]
1147
+
1148
+ expect.object(obj);
1149
+
1150
+ expect.object(updateData);
1151
+
1152
+ // -- Update cloned object
1153
+
1154
+ let pathValuePairs;
1155
+
1156
+ if (!isIterable(updateData)) {
1157
+ // Convert updateData to path-value pairs (iterable)
1158
+
1159
+ const walkArrays = options && options.replaceArrays ? true : false;
1160
+
1161
+ pathValuePairs = iterateObjectEntries(updateData, {
1162
+ expandPathKeys: true,
1163
+ outputIntermediateNodes: true,
1164
+ walkArrays
1165
+ });
1166
+
1167
+ // pathValuePairs = Array.from( pathValuePairs );
1168
+ // console.log( "CHECK", { updateData, pathValuePairs } );
1169
+ } else {
1170
+ // Iterable -> assume iterable of path-value pairs
1171
+ pathValuePairs = updateData;
1172
+ }
1173
+
1174
+ for (const [arrPath, value] of pathValuePairs) {
1175
+ // console.log( "updateObject:set", arrPath, value );
1176
+ objectSet(obj, arrPath, value);
1177
+ }
1178
+
1179
+ return obj;
1180
+ }
1181
+
1182
+ // -----------------------------------------------------------------------------
1183
+
1184
+ /**
1185
+ * Copy own properties from an object to another object if they do not
1186
+ * exist yet.
1187
+ *
1188
+ * @param {object} from - Object ot copy properties from
1189
+ * @param {object} to - Object to copy properties to
1190
+ */
1191
+ export function copyOwnProperties(from, to) {
1192
+ const propertyNames = Object.getOwnPropertyNames(from);
1193
+
1194
+ const nProperties = propertyNames.length;
1195
+
1196
+ if (!nProperties) {
1197
+ return;
1198
+ }
1199
+
1200
+ const firstIsConstructor = 'constructor' === propertyNames[0] ? true : false;
1201
+
1202
+ if (1 === nProperties && firstIsConstructor) {
1203
+ return;
1204
+ }
1205
+
1206
+ const startAt = firstIsConstructor ? 1 : 0;
1207
+
1208
+ // console.log("propertyNames", from, propertyNames, startAt);
1209
+
1210
+ for (let j = startAt; j < nProperties; j = j + 1) {
1211
+ const key = propertyNames[j];
1212
+
1213
+ const descriptor = Object.getOwnPropertyDescriptor(from, key);
1214
+
1215
+ const targetDescriptor = Object.getOwnPropertyDescriptor(to, key);
1216
+
1217
+ // Not needed, we copy an instance
1218
+ // descriptor.value = clone( descriptor.value );
1219
+
1220
+ if (!targetDescriptor) {
1221
+ // Property does not yet exist
1222
+ Object.defineProperty(to, key, descriptor);
1223
+ }
1224
+ } // end for
1225
+
1226
+ // >>> FIXME? TODO? copy Symbols too? <<<
1227
+ }
1228
+
1229
+ // -----------------------------------------------------------------------------
1230
+
1231
+ /**
1232
+ * Convert string with dot separated values to a list of values
1233
+ * - Accepts that the supplied path is already an array path
1234
+ *
1235
+ * @param {string|string[]} path
1236
+ *
1237
+ * @returns {string[]} list of path values
1238
+ */
1239
+ export function ensureArrayPath(path) {
1240
+ if (typeof path === 'string') {
1241
+ return path.split(PATH_SEPARATOR);
1242
+ } else if (Array.isArray(path)) {
1243
+ // Nothing to do
1244
+ return path;
1245
+ } else {
1246
+ throw new Error('Missing or invalid parameter [path] (expected string or array)');
1247
+ }
1248
+ }
1249
+
1250
+ /* ----------------------------------------------------- Internal methods */
1251
+
1252
+ // -----------------------------------------------------------------------------
1253
+
1254
+ /**
1255
+ * Create all parent objects on the object path if they do not yet exist yet
1256
+ * - This method will throw an exception if there is a non-object node in
1257
+ * the path
1258
+ *
1259
+ * @param {object} obj
1260
+ * Object to create the parent objects in
1261
+ *
1262
+ * @param {string[]} arrPath
1263
+ * The path that specified which parent oobjects to create
1264
+ *
1265
+ * @returns {object} the input object with the created object properties
1266
+ */
1267
+ export function _ensureParent(obj, arrPath) {
1268
+ // console.log("_ensureParent (1)", { obj, arrPath } );
1269
+
1270
+ let current = obj;
1271
+ let prev = current;
1272
+
1273
+ for (let j = 0, n_1 = arrPath.length - 1; j < n_1; j = j + 1) {
1274
+ const key = arrPath[j];
1275
+
1276
+ current = current[key];
1277
+
1278
+ if (current === undefined || current === null) {
1279
+ current = prev[key] = {};
1280
+ prev = current;
1281
+ continue;
1282
+ }
1283
+
1284
+ const nextKey = arrPath[j + 1];
1285
+
1286
+ // Check
1287
+ // Check if current is an object
1288
+ // If current is an array, check if the nextKey can be set on a array
1289
+
1290
+ if (current instanceof Object) {
1291
+ if (Array.isArray(current) && j < n_1) {
1292
+ const nextKeyAsInt = parseInt(nextKey, 10);
1293
+
1294
+ if (Number.isNaN(nextKeyAsInt)) {
1295
+ // console.log("CHECK", { obj, arrPath, j, current, nextKey } );
1296
+ throw new Error(`Cannot set property [${nextKey}] ` + 'on data node of type [Array]');
1297
+ }
1298
+ }
1299
+ } else {
1300
+ // console.log( { current, prev, key, arrPath, j } );
1301
+
1302
+ throw new Error(
1303
+ `Cannot set property [${nextKey}] from ` +
1304
+ `path [${display_array_path(arrPath)}] on data node that is not ` +
1305
+ 'an object or an array'
1306
+ );
1307
+ }
1308
+
1309
+ prev = current;
1310
+ } // end for
1311
+
1312
+ return current;
1313
+ }
1314
+
1315
+ // -----------------------------------------------------------------------------
1316
+
1317
+ /**
1318
+ * Get parent object at the specified path
1319
+ *
1320
+ * @param {object} obj - Object to work in
1321
+ * @param {string[]} arrPath - Path to get the parent object for
1322
+ *
1323
+ * @returns {object|array|null} parent object or null if not found
1324
+ */
1325
+ export function _getParent(obj, arrPath) {
1326
+ let current = obj;
1327
+
1328
+ for (let j = 0, n_1 = arrPath.length - 1; j < n_1; j = j + 1) {
1329
+ let key = arrPath[j];
1330
+
1331
+ current = current[key];
1332
+
1333
+ if (typeof current === 'undefined') {
1334
+ return null;
1335
+ }
1336
+
1337
+ if (current instanceof Object) {
1338
+ if (Array.isArray(current) && j < n_1 - 1) {
1339
+ // node is an array
1340
+ // -> use next part of the array path as (numerical) array index
1341
+
1342
+ key = arrPath[j + 1];
1343
+ const keyAsInt = parseInt(key, 10);
1344
+
1345
+ if (Number.isNaN(keyAsInt)) {
1346
+ throw new Error(
1347
+ `Cannot get property [${display_array_path(arrPath, j)}]` +
1348
+ 'from data node of type [Array]'
1349
+ );
1350
+ }
1351
+ }
1352
+ } else {
1353
+ throw new Error(
1354
+ `Cannot get property [${display_array_path(arrPath, j)}]` +
1355
+ 'from a data node that is not an object or an array'
1356
+ );
1357
+ }
1358
+ } // end for
1359
+
1360
+ return current;
1361
+ }