@hkdigital/lib-core 0.3.11 → 0.3.13

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