structured-fw 0.7.2

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 (129) hide show
  1. package/Config.ts +47 -0
  2. package/LICENSE +21 -0
  3. package/README.md +332 -0
  4. package/app/Types.ts +1 -0
  5. package/app/models/README.md +9 -0
  6. package/app/routes/README.md +19 -0
  7. package/app/views/README.md +1 -0
  8. package/app/views/layout.html +1 -0
  9. package/bin/structured +114 -0
  10. package/build/Config.d.ts +2 -0
  11. package/build/Config.js +31 -0
  12. package/build/app/Types.d.ts +1 -0
  13. package/build/app/Types.js +1 -0
  14. package/build/app/models/Users.d.ts +0 -0
  15. package/build/app/models/Users.js +1 -0
  16. package/build/app/routes/Auth.d.ts +0 -0
  17. package/build/app/routes/Auth.js +1 -0
  18. package/build/app/routes/Test.d.ts +2 -0
  19. package/build/app/routes/Test.js +101 -0
  20. package/build/app/routes/Todo.d.ts +0 -0
  21. package/build/app/routes/Todo.js +1 -0
  22. package/build/app/routes/Upload.d.ts +0 -0
  23. package/build/app/routes/Upload.js +1 -0
  24. package/build/app/routes/Validation.d.ts +2 -0
  25. package/build/app/routes/Validation.js +34 -0
  26. package/build/app/views/components/ClientImport/ClientImport.client.d.ts +2 -0
  27. package/build/app/views/components/ClientImport/ClientImport.client.js +4 -0
  28. package/build/app/views/components/ClientImport/Export.d.ts +1 -0
  29. package/build/app/views/components/ClientImport/Export.js +1 -0
  30. package/build/app/views/components/Conditionals/Conditionals.client.d.ts +2 -0
  31. package/build/app/views/components/Conditionals/Conditionals.client.js +43 -0
  32. package/build/app/views/components/FormTest/FormTestNested/FormTestNested.d.ts +8 -0
  33. package/build/app/views/components/FormTest/FormTestNested/FormTestNested.js +7 -0
  34. package/build/app/views/components/ModelsTest/ModelsTest.client.d.ts +2 -0
  35. package/build/app/views/components/ModelsTest/ModelsTest.client.js +5 -0
  36. package/build/app/views/components/MultipartForm/MultipartForm.client.d.ts +0 -0
  37. package/build/app/views/components/MultipartForm/MultipartForm.client.js +1 -0
  38. package/build/app/views/components/PassObject/PassObject.d.ts +10 -0
  39. package/build/app/views/components/PassObject/PassObject.js +10 -0
  40. package/build/app/views/components/PassObject/ReceiveObj/ReceiveObj.d.ts +6 -0
  41. package/build/app/views/components/PassObject/ReceiveObj/ReceiveObj.js +6 -0
  42. package/build/app/views/components/RedrawAbort/RedrawAbort.client.d.ts +2 -0
  43. package/build/app/views/components/RedrawAbort/RedrawAbort.client.js +6 -0
  44. package/build/app/views/components/RedrawAbort/RedrawAbort.d.ts +8 -0
  45. package/build/app/views/components/RedrawAbort/RedrawAbort.js +8 -0
  46. package/build/app/views/components/ServerSideContext/ServerSideContext.d.ts +7 -0
  47. package/build/app/views/components/ServerSideContext/ServerSideContext.js +10 -0
  48. package/build/assets/ts/Export.d.ts +1 -0
  49. package/build/assets/ts/Export.js +1 -0
  50. package/build/index.d.ts +1 -0
  51. package/build/index.js +3 -0
  52. package/build/system/Helpers.d.ts +3 -0
  53. package/build/system/Helpers.js +72 -0
  54. package/build/system/Symbols.d.ts +3 -0
  55. package/build/system/Symbols.js +3 -0
  56. package/build/system/Types.d.ts +171 -0
  57. package/build/system/Types.js +1 -0
  58. package/build/system/Util.d.ts +20 -0
  59. package/build/system/Util.js +336 -0
  60. package/build/system/client/App.d.ts +7 -0
  61. package/build/system/client/App.js +8 -0
  62. package/build/system/client/Client.d.ts +6 -0
  63. package/build/system/client/Client.js +9 -0
  64. package/build/system/client/ClientComponent.d.ts +68 -0
  65. package/build/system/client/ClientComponent.js +734 -0
  66. package/build/system/client/DataStore.d.ts +22 -0
  67. package/build/system/client/DataStore.js +64 -0
  68. package/build/system/client/DataStoreView.d.ts +19 -0
  69. package/build/system/client/DataStoreView.js +56 -0
  70. package/build/system/client/EventEmitter.d.ts +7 -0
  71. package/build/system/client/EventEmitter.js +31 -0
  72. package/build/system/client/Net.d.ts +13 -0
  73. package/build/system/client/Net.js +39 -0
  74. package/build/system/client/NetRequest.d.ts +13 -0
  75. package/build/system/client/NetRequest.js +45 -0
  76. package/build/system/server/Application.d.ts +31 -0
  77. package/build/system/server/Application.js +171 -0
  78. package/build/system/server/Component.d.ts +27 -0
  79. package/build/system/server/Component.js +249 -0
  80. package/build/system/server/Components.d.ts +12 -0
  81. package/build/system/server/Components.js +77 -0
  82. package/build/system/server/Cookies.d.ts +6 -0
  83. package/build/system/server/Cookies.js +19 -0
  84. package/build/system/server/Document.d.ts +24 -0
  85. package/build/system/server/Document.js +107 -0
  86. package/build/system/server/DocumentHead.d.ts +32 -0
  87. package/build/system/server/DocumentHead.js +118 -0
  88. package/build/system/server/FormValidation.d.ts +16 -0
  89. package/build/system/server/FormValidation.js +197 -0
  90. package/build/system/server/Handlebars.d.ts +11 -0
  91. package/build/system/server/Handlebars.js +34 -0
  92. package/build/system/server/Request.d.ts +21 -0
  93. package/build/system/server/Request.js +356 -0
  94. package/build/system/server/Session.d.ts +23 -0
  95. package/build/system/server/Session.js +114 -0
  96. package/build/system/server/dom/DOMFragment.d.ts +4 -0
  97. package/build/system/server/dom/DOMFragment.js +6 -0
  98. package/build/system/server/dom/DOMNode.d.ts +31 -0
  99. package/build/system/server/dom/DOMNode.js +110 -0
  100. package/build/system/server/dom/HTMLParser.d.ts +21 -0
  101. package/build/system/server/dom/HTMLParser.js +204 -0
  102. package/index.ts +4 -0
  103. package/package.json +31 -0
  104. package/system/Helpers.ts +97 -0
  105. package/system/Symbols.ts +6 -0
  106. package/system/Types.ts +234 -0
  107. package/system/Util.ts +488 -0
  108. package/system/client/App.ts +11 -0
  109. package/system/client/Client.ts +9 -0
  110. package/system/client/ClientComponent.ts +1117 -0
  111. package/system/client/DataStore.ts +101 -0
  112. package/system/client/DataStoreView.ts +82 -0
  113. package/system/client/EventEmitter.ts +38 -0
  114. package/system/client/Net.ts +58 -0
  115. package/system/client/NetRequest.ts +64 -0
  116. package/system/server/Application.ts +230 -0
  117. package/system/server/Component.ts +404 -0
  118. package/system/server/Components.ts +111 -0
  119. package/system/server/Cookies.ts +29 -0
  120. package/system/server/Document.ts +163 -0
  121. package/system/server/DocumentHead.ts +150 -0
  122. package/system/server/FormValidation.ts +231 -0
  123. package/system/server/Handlebars.ts +51 -0
  124. package/system/server/Request.ts +497 -0
  125. package/system/server/Session.ts +151 -0
  126. package/system/server/dom/DOMFragment.ts +7 -0
  127. package/system/server/dom/DOMNode.ts +140 -0
  128. package/system/server/dom/HTMLParser.ts +238 -0
  129. package/tsconfig.json +35 -0
package/system/Util.ts ADDED
@@ -0,0 +1,488 @@
1
+ import { LooseObject, PostedDataDecoded } from "./Types.js";
2
+
3
+
4
+ // process given query string into an object
5
+ // optionally accepts initialValue
6
+ export function queryStringDecode(queryString: string, initialValue: PostedDataDecoded = {}, trimValues: boolean = true): PostedDataDecoded {
7
+ // replace + with space and split string at & to produce key=value pairs
8
+ const pairsRaw = queryString.replaceAll('+', ' ').split('&');
9
+
10
+ // produce an array of {key, value, isArray, isObject, keyRaw}
11
+ let pairs = pairsRaw.map((pair) => {
12
+
13
+ // parts could have:
14
+ // 1 element - key with no value
15
+ // 2 elements - key & value
16
+ // more than 2 elements if the value had "=" in it
17
+ let parts = pair.split('=');
18
+ if (parts.length > 2) {
19
+ // value contained a "=", join all elements 1 and above with "="
20
+ // this makes parts have exactly 2 elements (key and value)
21
+ parts = [parts[0]].concat(parts.slice(1).join('='));
22
+ }
23
+
24
+ // part now holds 1 (key without value) or 2 elements (key and value)
25
+ const hasValue = parts.length > 1;
26
+
27
+ // decode key
28
+ const keyRaw = decodeURIComponent(parts[0]);
29
+
30
+ // if key has a value, decode it, otherwise value is true (as in, key is set)
31
+ const value = hasValue ? (trimValues ? decodeURIComponent(parts[1]).trim() : decodeURIComponent(parts[1])) : true;
32
+
33
+ // if key includes "[.*]" then it is an array or object
34
+ const arrayOrObject = /\[[^\[\]]*\]/.test(keyRaw);
35
+
36
+ // pathStart is "[.*]" or null if the key contains no [.*]
37
+ const pathStart = arrayOrObject ? (/\[(.*?)\]/.exec(keyRaw) as RegExpExecArray)[1] : null;
38
+
39
+ // to be an object it has to contain a string within [], otherwise it is an array
40
+ const isObject = pathStart !== null && /[^\[\]]+/.test(pathStart) && ! /^\s+$/.test(pathStart) && ! /^\d+$/.test(pathStart);
41
+
42
+ // it is an array if it's not an object
43
+ const isArray = pathStart !== null && ! isObject;
44
+
45
+ // the actual key is, if array or object - anything before "[", if not then same as keyRaw
46
+ const key = isArray || isObject ? keyRaw.substring(0, keyRaw.indexOf('[')) : keyRaw;
47
+
48
+ return {
49
+ key,
50
+ value,
51
+ isArray,
52
+ isObject,
53
+ path: pathStart,
54
+ keyRaw
55
+ }
56
+ });
57
+
58
+ while (pairs.length > 0) {
59
+ const item = pairs.shift();
60
+ if (item) {
61
+ if (! item.isArray && ! item.isObject) {
62
+ // simple value
63
+ initialValue[item.key] = item.value;
64
+ } else if (item.isObject) {
65
+ // object
66
+ // return all properties of this object
67
+ const properties = [item].concat(pairs.filter((pair) => {
68
+ return pair.isObject && pair.key === item.key;
69
+ }));
70
+ // remove properties of this same object from pairs
71
+ pairs = pairs.filter((pair) => {
72
+ return ! (pair.isObject && pair.key === item.key);
73
+ });
74
+
75
+ let obj: Record<string, string | boolean > = {}
76
+ const simpleProperties = properties.filter((prop) => {
77
+ return prop.keyRaw === `${prop.key}[${prop.path}]`;
78
+ });
79
+ const complexProperties = properties.filter((prop) => {
80
+ return prop.keyRaw !== `${prop.key}[${prop.path}]`;
81
+ });
82
+
83
+ // handle simple properties
84
+ for (const property of simpleProperties) {
85
+ if (! property.path) {continue;}
86
+ obj[property.path] = property.value;
87
+ }
88
+
89
+ // handle complex properties
90
+ const complexPropertyPathsResolved: Array<string> = [];
91
+ for (const property of complexProperties) {
92
+ if (! property.path) {continue;}
93
+
94
+ // property done
95
+ if (complexPropertyPathsResolved.includes(property.path)) {continue;}
96
+
97
+ const objectProperties = complexProperties.filter((prop) => {
98
+ return prop.path === property.path;
99
+ });
100
+
101
+ // mark as solved
102
+ complexPropertyPathsResolved.push(property.path as string);
103
+
104
+ obj[property.path] = queryStringDecode(objectProperties.map((prop) => {
105
+ const pathRemaining = prop.keyRaw.substring(prop.key.length + (prop.path?.length || 0) + 3);
106
+ const val = prop.value === true ? '' : `=${encodeURIComponent(prop.value)}`;
107
+ return `value[${pathRemaining.substring(0, pathRemaining.length - 1)}]${val}`;
108
+ }).join('&')).value as any;
109
+ }
110
+
111
+ initialValue[item.key] = obj;
112
+ } else if (item.isArray) {
113
+ // array
114
+ // gather values
115
+ let arrayValues = [item].concat(pairs.filter((pair) => {
116
+ return pair.isArray && pair.key === item.key;
117
+ }));
118
+ // remove values gathered from pairs
119
+ pairs = pairs.filter((pair) => {
120
+ return !(pair.isArray && pair.key === item.key);
121
+ });
122
+
123
+ // order the array values, this only has effect if it is an ordered array (arr[0]=val&arr[1]=val)
124
+ arrayValues.sort((a, b) => {
125
+ if (a.path === b.path) {return 0;}
126
+ if (a.path && b.path && a.path.trim().length > 0 && b.path.trim().length > 0) {
127
+ const aIndex = parseInt(a.path);
128
+ const bIndex = parseInt(b.path);
129
+ return aIndex - bIndex;
130
+ }
131
+
132
+ return 0;
133
+ });
134
+
135
+ // arrayValues is either filled with simple values, or filled with object properties
136
+ const complexPropertyPathsResolved: Array<string> = [];
137
+ const arrayItems = arrayValues.map((value) => {
138
+ const simpleValue = value.keyRaw === `${value.key}[${value.path}]`;
139
+ if (simpleValue) {
140
+ return value.value;
141
+ }
142
+ // complex value (object)
143
+ if (complexPropertyPathsResolved.includes(value.path as string)) {
144
+ // already solved
145
+ return null;
146
+ }
147
+ const objectProperties = arrayValues.filter((prop) => {
148
+ return prop.path === value.path;
149
+ });
150
+
151
+ // mark as solved
152
+ complexPropertyPathsResolved.push(value.path as string);
153
+
154
+ return queryStringDecode(objectProperties.map((prop) => {
155
+ const pathRemaining = prop.keyRaw.substring(prop.key.length + (prop.path?.length || 0) + 3);
156
+ return `value${pathRemaining.length > 0 ? `[${pathRemaining.substring(0, pathRemaining.length - 1)}]` : ''}=${encodeURIComponent(prop.value)}`;
157
+ }).join('&')).value as PostedDataDecoded;
158
+ }).filter((val) => {
159
+ return val !== null;
160
+ });
161
+
162
+ // console.log(arrayValues);
163
+
164
+ initialValue[item.key] = arrayItems;
165
+ }
166
+ }
167
+ }
168
+
169
+ return initialValue;
170
+ }
171
+
172
+ // sometimes we want to use queryStringDeocde simply to parse a complex query string
173
+ // with nested keys into an object, with intent to assign a non-primitive value to it later
174
+ // for example decoding "obj[nested][key]" would produce { obj: { nested: { key: true } } }
175
+ // we might want to set the "key" to something other than true, or another primitive value
176
+ // so doing obj[nested][key]=[desiredValue] is impossible
177
+ // as the type of the value can't be coerced to string
178
+ // this function takes an object produced by queryStringDecode or a query string itself
179
+ // and the value to be populated, populates the key with value = true with given value
180
+ // and returns the populated object
181
+ export function queryStringDecodedSetValue(obj: PostedDataDecoded | string, value: any): LooseObject {
182
+ // obj given as string, decode it first to PostedDataDecoded
183
+ if (typeof obj === 'string') {
184
+ obj = queryStringDecode(obj);
185
+ }
186
+
187
+ // obj is either { key: true } or a nested object eventually ending with a value = true
188
+ // replace true with given value
189
+ // recursively search the object and find value = true, replace it with given file
190
+ const setValue = (obj: LooseObject) => {
191
+ for (const key in obj) {
192
+ if (typeof obj[key] === 'object' && obj[key] !== null) {
193
+ // not here, resume recursively
194
+ setValue(obj[key]);
195
+ } else if (obj[key] === true) {
196
+ // found the place for the file, populate it
197
+ obj[key] = value;
198
+ }
199
+ }
200
+ }
201
+
202
+ setValue(obj);
203
+ return obj;
204
+ }
205
+
206
+ // loop through given object, for each key, runs callbackEach providing the key and value to it
207
+ // this is a basic for ... in loop, but makes it more TS friendly
208
+ // object keys are always a string according to TS, which requires type casting
209
+ export function objectEach<T>(obj: T, callbackEach: (key: keyof T, value: T[keyof T]) => void): void {
210
+ for (const key in obj) {
211
+ callbackEach(key, obj[key]);
212
+ }
213
+ }
214
+
215
+ // converts string-with-dashes to stringWithDashes
216
+ export function toCamelCase(dataKey: string, separator: string = '-'): string {
217
+ let index: number;
218
+ do {
219
+ index = dataKey.indexOf(separator);
220
+ if (index > -1) {
221
+ dataKey = dataKey.substring(0, index) + dataKey.substring(index + 1, index + 2).toUpperCase() + dataKey.substring(index + 2);
222
+ }
223
+ } while(index > -1);
224
+ return dataKey;
225
+ }
226
+
227
+ // camelCase to snake_case
228
+ export function toSnakeCase(str: string, joinWith: string = '_'): string {
229
+ let start = 0;
230
+ const parts = [];
231
+ if (str.length < 2) {
232
+ return str.toLowerCase();
233
+ }
234
+
235
+ // split in parts at capital letters
236
+ for (let i = 1; i < str.length; i++) {
237
+ if (str[i] !== str[i].toLowerCase()) {
238
+ // a capital letter
239
+ parts.push(str.substring(start, i).toLowerCase());
240
+ start = i;
241
+ }
242
+ }
243
+
244
+ // didn't do anything useful
245
+ if (start === 0) {
246
+ return str.toLowerCase();
247
+ }
248
+
249
+ // add last part
250
+ parts.push(str.substring(start).toLowerCase());
251
+
252
+ return parts.join(joinWith);
253
+ }
254
+
255
+ export function capitalize(str: string): string {
256
+ return str.substring(0, 1).toUpperCase() + str.substring(1);
257
+ }
258
+
259
+ export function isAsync(fn: Function): boolean {
260
+ return fn.constructor.name === 'AsyncFunction';
261
+ }
262
+
263
+ export function randomString(len: number): string {
264
+ const charCodes: Uint8Array = new Uint8Array(len);
265
+ const generators = [
266
+ // uppercase letters
267
+ function(): number {
268
+ return 65 + Math.floor(Math.random() * 26);
269
+ },
270
+ // lowercase letters
271
+ function(): number {
272
+ return 97 + Math.floor(Math.random() * 26);
273
+ },
274
+ // numbers
275
+ function(): number {
276
+ return 48 + Math.floor(Math.random() * 10);
277
+ }
278
+ ];
279
+
280
+ for (let i = 0; i < len; i++) {
281
+ charCodes[i] = generators[Math.floor(Math.random() * generators.length)]();
282
+ }
283
+
284
+ return String.fromCodePoint(...charCodes);
285
+ }
286
+
287
+ export function unique<T>(arr: Array<T>): Array<T> {
288
+ return arr.reduce((prev, curr) => {
289
+ if (! prev.includes(curr)) {
290
+ prev.push(curr);
291
+ }
292
+ return prev;
293
+ }, [] as Array<T>);
294
+ }
295
+
296
+ export function stripTags(contentWithHTML: string, keepTags: Array<string> = []): string {
297
+ if (contentWithHTML === undefined) {return ''};
298
+ return contentWithHTML.replaceAll(/<\s*\/?\s*[a-zA-Z]+[^>]*?>/g, (sub, index) => {
299
+ const keep = keepTags.some((kept) => {
300
+ const match = new RegExp(`^<\s*\/?\s*${kept}`);
301
+ return match.test(sub);
302
+ });
303
+ if (keep) {
304
+ return sub;
305
+ }
306
+ return sub.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
307
+ });
308
+ }
309
+
310
+ function base64ToBytes(base64: string) {
311
+ const binString = atob(base64);
312
+ // @ts-ignore
313
+ return Uint8Array.from(binString, (m) => m.codePointAt(0));
314
+ }
315
+
316
+ function bytesToBase64(bytes: Uint8Array) {
317
+ const binString = String.fromCodePoint(...bytes);
318
+ return btoa(binString);
319
+ }
320
+
321
+ export function attributeValueToString(key: string, value: any): string {
322
+ return 'base64:' + bytesToBase64(new TextEncoder().encode(JSON.stringify({key, value})));
323
+ }
324
+
325
+ export function attributeValueFromString(attributeValue: string): string|{
326
+ key: string,
327
+ value: any
328
+ } {
329
+
330
+ if (attributeValue.indexOf('base64:') === 0) {
331
+ try {
332
+ const decoded = new TextDecoder().decode(base64ToBytes(attributeValue.substring(7)));
333
+
334
+ if (decoded.indexOf('{') !== 0) {
335
+ // expected to start with "{", if not return as is
336
+ return attributeValue;
337
+ }
338
+
339
+ const valObj = JSON.parse(decoded);
340
+
341
+ if (! ('value' in valObj) || ! ('key' in valObj)) {
342
+ // unrecognized object
343
+ return decoded;
344
+ }
345
+
346
+ return valObj;
347
+
348
+ } catch (e) {
349
+ return attributeValue;
350
+ }
351
+ }
352
+ return attributeValue;
353
+ }
354
+
355
+ export function attributeValueEscape(str: string): string {
356
+ return str.replaceAll('"', '&quot;');
357
+ }
358
+
359
+ export function isObject(item: any): boolean {
360
+ if (typeof window === 'undefined') {
361
+ return (item && typeof item === 'object' && !Array.isArray(item)) && ! Buffer.isBuffer(item);
362
+ }
363
+ return (item && typeof item === 'object' && !Array.isArray(item));
364
+ }
365
+
366
+ // deep comparison of 2 objects
367
+ export function equalDeep(a: LooseObject, b: LooseObject): boolean {
368
+ if (a === b) {
369
+ // same object
370
+ return true;
371
+ }
372
+
373
+ const keysA = Object.keys(a);
374
+ const keysB = Object.keys(b);
375
+
376
+ if (keysA.length !== keysB.length) {
377
+ // key count different, not same
378
+ return false;
379
+ }
380
+
381
+ // same number of keys
382
+ const keyDifferent = keysA.some((keyA) => {
383
+ return ! (keyA in b);
384
+ });
385
+
386
+ if (keyDifferent) {
387
+ // some of the keys are different, not same
388
+ return false;
389
+ }
390
+
391
+ // objects have the same keys, make sure values are also the same
392
+ for (let i = 0; i < keysA.length; i++) {
393
+ const key = keysA[i];
394
+ const valA = a[key];
395
+ const valB = b[key];
396
+ const typeA = typeof valA;
397
+ const typeB = typeof valB;
398
+
399
+ if (valA === valB) {
400
+ // same value, continue
401
+ continue;
402
+ }
403
+
404
+ if (typeA !== typeB) {
405
+ // value type different, not same
406
+ return false;
407
+ } else {
408
+ // same type, if primitive, comapre values
409
+ if (typeA !== 'object') {
410
+ if (valA !== valB) {
411
+ return false;
412
+ }
413
+ }
414
+ }
415
+
416
+ // values have the same type, if they were exact same they would already pass this iteration
417
+ // at this point primitives have been compared in the first check in the itration
418
+ // both types should be "object" at this point (object, array or null)
419
+ // null is also an object, make sure either both are null, or none is null
420
+ if ((valA === null && valB !== null) || (valA !== null && valB === null)) {
421
+ // one is null, other is not
422
+ return false;
423
+ }
424
+
425
+
426
+ // make sure either both are array, or none is array
427
+ const isArrayA = Array.isArray(valA);
428
+ const isArrayB = Array.isArray(valB);
429
+ if ((isArrayA && ! isArrayB) || (! isArrayA && isArrayB)) {
430
+ // one is array, other is not
431
+ return false;
432
+ }
433
+
434
+ // at this point both values are either array or object
435
+ if (! isArrayA && ! isArrayB) {
436
+ // neither is an array, both are objects
437
+ // pass the values through equalsDeep, if not the same, objects are not the same either
438
+ if (! equalDeep(valA, valB)) {
439
+ return false;
440
+ }
441
+ }
442
+
443
+ if (isArrayA && isArrayB) {
444
+ // both are arrays
445
+ if (valA.length !== valB.length) {
446
+ // arrays of different length
447
+ return false;
448
+ }
449
+
450
+
451
+ for (let j = 0; j < valA.length; j++) {
452
+ if (! equalDeep({
453
+ value: valA[j]
454
+ }, {
455
+ value: valB[j]
456
+ })) {
457
+ // different value at index, not the same array, objects not same
458
+ return false;
459
+ }
460
+ }
461
+ }
462
+ }
463
+
464
+ // all checks passed, same objects
465
+ return true;
466
+ }
467
+
468
+ export function mergeDeep(target: any, ...sources: Array<any>): LooseObject {
469
+ if (!sources.length) return target;
470
+ const source = sources.shift();
471
+
472
+ if (isObject(target) && isObject(source)) {
473
+ for (const key in source) {
474
+ if (isObject(source[key])) {
475
+ if (!target[key]) Object.assign(target, { [key]: {} });
476
+ mergeDeep(target[key], source[key]);
477
+ } else {
478
+ Object.assign(target, { [key]: source[key] });
479
+ }
480
+ }
481
+ }
482
+
483
+ if (Array.isArray(target) && Array.isArray(source)) {
484
+ return target.concat(source);
485
+ }
486
+
487
+ return mergeDeep(target, ...sources);
488
+ }
@@ -0,0 +1,11 @@
1
+ import { DataStore } from './DataStore.js';
2
+ import { ClientComponent } from './ClientComponent.js';
3
+
4
+ export class App {
5
+ root: ClientComponent;
6
+ store: DataStore = new DataStore();
7
+
8
+ constructor() {
9
+ this.root = new ClientComponent(null, 'root', document.body, this.store);
10
+ }
11
+ }
@@ -0,0 +1,9 @@
1
+ import { App } from './App.js';
2
+ import { Net } from './Net.js';
3
+
4
+ export class Client {
5
+ Components : App = new App();
6
+ Net : Net = new Net();
7
+ }
8
+
9
+ new App();