@voxgig/sdkgen 0.24.0 → 0.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/bin/voxgig-sdkgen +6 -6
  2. package/dist/action/action.js +1 -2
  3. package/dist/action/action.js.map +1 -1
  4. package/dist/action/feature.js +4 -2
  5. package/dist/action/feature.js.map +1 -1
  6. package/dist/action/target.js +4 -2
  7. package/dist/action/target.js.map +1 -1
  8. package/dist/cmp/Entity.js +2 -1
  9. package/dist/cmp/Entity.js.map +1 -1
  10. package/dist/cmp/FeatureHook.js +2 -1
  11. package/dist/cmp/FeatureHook.js.map +1 -1
  12. package/dist/cmp/Main.js.map +1 -1
  13. package/dist/cmp/ReadmeEntity.js +2 -1
  14. package/dist/cmp/ReadmeEntity.js.map +1 -1
  15. package/dist/cmp/Test.d.ts +2 -0
  16. package/dist/cmp/Test.js +17 -0
  17. package/dist/cmp/Test.js.map +1 -0
  18. package/dist/sdkgen.d.ts +2 -1
  19. package/dist/sdkgen.js +17 -28
  20. package/dist/sdkgen.js.map +1 -1
  21. package/dist/tsconfig.tsbuildinfo +1 -1
  22. package/dist/types.d.ts +3 -1
  23. package/dist/types.js +4 -0
  24. package/dist/types.js.map +1 -1
  25. package/model/sdkgen.jsonic +8 -8
  26. package/package.json +8 -6
  27. package/project/.sdk/model/feature/log.jsonic +2 -2
  28. package/project/.sdk/model/feature/test.jsonic +7 -2
  29. package/project/.sdk/model/target/js.jsonic +2 -2
  30. package/project/.sdk/model/target/ts.jsonic +2 -2
  31. package/project/.sdk/src/cmp/js/Main_js.ts +3 -3
  32. package/project/.sdk/src/cmp/js/Quick_js.ts +1 -1
  33. package/project/.sdk/src/cmp/ts/Config_ts.ts +53 -6
  34. package/project/.sdk/src/cmp/ts/EntityOperation_ts.ts +3 -21
  35. package/project/.sdk/src/cmp/ts/Entity_ts.ts +3 -5
  36. package/project/.sdk/src/cmp/ts/Main_ts.ts +22 -12
  37. package/project/.sdk/src/cmp/ts/Package_ts.ts +32 -12
  38. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +341 -2
  39. package/project/.sdk/src/cmp/ts/TestMain_ts.ts +0 -3
  40. package/project/.sdk/src/cmp/ts/Test_ts.ts +21 -3
  41. package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +38 -5
  42. package/project/.sdk/src/cmp/ts/fragment/Entity.fragment.ts +50 -38
  43. package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +9 -7
  44. package/project/.sdk/src/cmp/ts/fragment/EntityCreateOp.fragment.ts +47 -31
  45. package/project/.sdk/src/cmp/ts/fragment/EntityListOp.fragment.ts +49 -31
  46. package/project/.sdk/src/cmp/ts/fragment/EntityLoadOp.fragment.ts +50 -33
  47. package/project/.sdk/src/cmp/ts/fragment/EntityRemoveOp.fragment.ts +50 -31
  48. package/project/.sdk/src/cmp/ts/fragment/EntityUpdateOp.fragment.ts +51 -31
  49. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +70 -34
  50. package/project/.sdk/src/cmp/ts/tsconfig.json +15 -0
  51. package/project/.sdk/src/cmp/ts/utility_ts.ts +55 -1
  52. package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +158 -104
  53. package/project/.sdk/tm/ts/src/types.ts +164 -34
  54. package/project/.sdk/tm/ts/src/utility/AuthUtility.ts +8 -2
  55. package/project/.sdk/tm/ts/src/utility/BodyUtility.ts +9 -6
  56. package/project/.sdk/tm/ts/src/utility/CleanUtility.ts +17 -31
  57. package/project/.sdk/tm/ts/src/utility/ContextUtility.ts +14 -54
  58. package/project/.sdk/tm/ts/src/utility/DoneUtility.ts +1 -1
  59. package/project/.sdk/tm/ts/src/utility/ErrorUtility.ts +7 -3
  60. package/project/.sdk/tm/ts/src/utility/FeaturehookUtility.ts +8 -5
  61. package/project/.sdk/tm/ts/src/utility/FetcherUtility.ts +18 -2
  62. package/project/.sdk/tm/ts/src/utility/FindparamUtility.ts +12 -5
  63. package/project/.sdk/tm/ts/src/utility/FullurlUtility.ts +21 -5
  64. package/project/.sdk/tm/ts/src/utility/InitfeatureUtility.ts +3 -1
  65. package/project/.sdk/tm/ts/src/utility/OperationUtility.ts +23 -0
  66. package/project/.sdk/tm/ts/src/utility/OptionsUtility.ts +25 -3
  67. package/project/.sdk/tm/ts/src/utility/ParamsUtility.ts +10 -10
  68. package/project/.sdk/tm/ts/src/utility/PreparePathUtility.ts +18 -0
  69. package/project/.sdk/tm/ts/src/utility/QueryUtility.ts +2 -2
  70. package/project/.sdk/tm/ts/src/utility/ReqformUtility.ts +2 -2
  71. package/project/.sdk/tm/ts/src/utility/RequestUtility.ts +20 -3
  72. package/project/.sdk/tm/ts/src/utility/ResbasicUtility.ts +1 -1
  73. package/project/.sdk/tm/ts/src/utility/ResbodyUtility.ts +5 -3
  74. package/project/.sdk/tm/ts/src/utility/ResformUtility.ts +3 -3
  75. package/project/.sdk/tm/ts/src/utility/ResheadersUtility.ts +9 -7
  76. package/project/.sdk/tm/ts/src/utility/ResponseUtility.ts +35 -4
  77. package/project/.sdk/tm/ts/src/utility/ResultUtility.ts +24 -4
  78. package/project/.sdk/tm/ts/src/utility/SelectionUtility.ts +78 -0
  79. package/project/.sdk/tm/ts/src/utility/SpecUtility.ts +34 -27
  80. package/project/.sdk/tm/ts/src/utility/StructUtility.ts +1117 -509
  81. package/project/.sdk/tm/ts/src/utility/Utility.ts +10 -8
  82. package/project/.sdk/tm/ts/test/exists.test.ts +0 -1
  83. package/project/.sdk/tm/ts/test/runner.ts +12 -10
  84. package/project/.sdk/tm/ts/test/utility/Custom.test.ts +30 -29
  85. package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +53 -47
  86. package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +433 -189
  87. package/src/action/action.ts +1 -2
  88. package/src/action/feature.ts +7 -2
  89. package/src/action/target.ts +7 -7
  90. package/src/cmp/Entity.ts +7 -1
  91. package/src/cmp/FeatureHook.ts +6 -1
  92. package/src/cmp/Main.ts +4 -0
  93. package/src/cmp/ReadmeEntity.ts +6 -1
  94. package/src/cmp/Test.ts +31 -0
  95. package/src/sdkgen.ts +19 -34
  96. package/src/types.ts +10 -1
  97. package/project/.sdk/src/cmp/ts/EntityTest_ts.ts +0 -180
  98. package/project/.sdk/tm/ts/src/utility/OperatorUtility.ts +0 -90
@@ -1,4 +1,6 @@
1
- /* Copyright (c) 2025 Voxgig Ltd. MIT LICENSE. */
1
+ /* Copyright (c) 2025-2026 Voxgig Ltd. MIT LICENSE. */
2
+
3
+ // VERSION: @voxgig/struct 0.0.10
2
4
 
3
5
  /* Voxgig Struct
4
6
  * =============
@@ -31,13 +33,15 @@
31
33
  * - stringify: human-friendly string version of a value.
32
34
  * - escre: escape a regular expresion string.
33
35
  * - escurl: escape a url.
34
- * - joinurl: join parts of a url, merging forward slashes.
36
+ * - join: join parts of a url, merging forward slashes.
35
37
  *
36
38
  * This set of functions and supporting utilities is designed to work
37
39
  * uniformly across many languages, meaning that some code that may be
38
40
  * functionally redundant in specific languages is still retained to
39
41
  * keep the code human comparable.
40
42
  *
43
+ * NOTE: Lists are assumed to be mutable and reference stable.
44
+ *
41
45
  * NOTE: In this code JSON nulls are in general *not* considered the
42
46
  * same as the undefined value in the given language. However most
43
47
  * JSON parsers do use the undefined value to represent JSON
@@ -52,16 +56,16 @@
52
56
 
53
57
  // String constants are explicitly defined.
54
58
 
55
- // Mode value for inject step.
56
- const S_MKEYPRE = 'key:pre'
57
- const S_MKEYPOST = 'key:post'
58
- const S_MVAL = 'val'
59
- const S_MKEY = 'key'
59
+ // Mode value for inject step (bitfield).
60
+ const M_KEYPRE = 1
61
+ const M_KEYPOST = 2
62
+ const M_VAL = 4
60
63
 
61
- // Special keys.
64
+ // Special strings.
62
65
  const S_BKEY = '`$KEY`'
63
66
  const S_BANNO = '`$ANNO`'
64
67
  const S_BEXACT = '`$EXACT`'
68
+ const S_BVAL = '`$VAL`'
65
69
 
66
70
  const S_DKEY = '$KEY'
67
71
  const S_DTOP = '$TOP'
@@ -69,33 +73,87 @@ const S_DERRS = '$ERRS'
69
73
  const S_DSPEC = '$SPEC'
70
74
 
71
75
  // General strings.
72
- const S_array = 'array'
76
+ const S_list = 'list'
73
77
  const S_base = 'base'
74
78
  const S_boolean = 'boolean'
75
79
  const S_function = 'function'
80
+ const S_symbol = 'symbol'
81
+ const S_instance = 'instance'
82
+ const S_key = 'key'
83
+ const S_any = 'any'
84
+ const S_nil = 'nil'
85
+ const S_null = 'null'
76
86
  const S_number = 'number'
77
87
  const S_object = 'object'
78
88
  const S_string = 'string'
79
- const S_null = 'null'
80
- const S_key = 'key'
81
- const S_MT = ''
89
+ const S_decimal = 'decimal'
90
+ const S_integer = 'integer'
91
+ const S_map = 'map'
92
+ const S_scalar = 'scalar'
93
+ const S_node = 'node'
94
+
95
+ // Character strings.
82
96
  const S_BT = '`'
97
+ const S_CN = ':'
98
+ const S_CS = ']'
83
99
  const S_DS = '$'
84
100
  const S_DT = '.'
85
- const S_CN = ':'
86
101
  const S_FS = '/'
102
+ const S_KEY = 'KEY'
103
+ const S_MT = ''
87
104
  const S_OS = '['
88
- const S_CS = ']'
89
105
  const S_SP = ' '
90
- const S_KEY = 'KEY'
106
+ const S_CM = ','
91
107
  const S_VIZ = ': '
92
108
 
109
+ // Types
110
+ let t = 31
111
+ const T_any = (1 << t--) - 1
112
+ const T_noval = 1 << t-- // Means property absent, undefined. Also NOT a scalar!
113
+ const T_boolean = 1 << t--
114
+ const T_decimal = 1 << t--
115
+ const T_integer = 1 << t--
116
+ const T_number = 1 << t--
117
+ const T_string = 1 << t--
118
+ const T_function = 1 << t--
119
+ const T_symbol = 1 << t--
120
+ const T_null = 1 << t-- // The actual JSON null value.
121
+ t -= 7
122
+ const T_list = 1 << t--
123
+ const T_map = 1 << t--
124
+ const T_instance = 1 << t--
125
+ t -= 4
126
+ const T_scalar = 1 << t--
127
+ const T_node = 1 << t--
128
+
129
+ const TYPENAME = [
130
+ S_any,
131
+ S_nil,
132
+ S_boolean,
133
+ S_decimal,
134
+ S_integer,
135
+ S_number,
136
+ S_string,
137
+ S_function,
138
+ S_symbol,
139
+ S_null,
140
+ '', '', '',
141
+ '', '', '', '',
142
+ S_list,
143
+ S_map,
144
+ S_instance,
145
+ '', '', '', '',
146
+ S_scalar,
147
+ S_node,
148
+ ]
93
149
 
94
150
  // The standard undefined value for this language.
95
- const UNDEF = undefined
151
+ const NONE = undefined
96
152
 
97
- // Private marker to indicate a skippable value.
153
+ // Private markers
98
154
  const SKIP = { '`$SKIP`': true }
155
+ const DELETE = { '`$DELETE`': true }
156
+
99
157
 
100
158
  // Regular expression constants
101
159
  const R_INTEGER_KEY = /^[-0-9]+$/ // Match integer keys (including <0).
@@ -105,7 +163,7 @@ const R_LEADING_TRAILING_SLASH = /([^\/])\/+/ // Multiple slashes in UR
105
163
  const R_LEADING_SLASH = /^\/+/ // Leading slashes in URLs.
106
164
  const R_QUOTES = /"/g // Double quotes for removal.
107
165
  const R_DOT = /\./g // Dots in path strings.
108
- const R_FUNCTION_REF = /^`\$FUNCTION:([0-9]+)`$/ // Function reference in clone.
166
+ const R_CLONE_REF = /^`\$REF:([0-9]+)`$/ // Copy reference in cloning.
109
167
  const R_META_PATH = /^([^$]+)\$([=~])(.+)$/ // Meta path syntax.
110
168
  const R_DOUBLE_DOLLAR = /\$\$/g // Double dollar escape sequence.
111
169
  const R_TRANSFORM_NAME = /`\$([A-Z]+)`/g // Transform command names.
@@ -114,6 +172,8 @@ const R_BT_ESCAPE = /\$BT/g // Backtick escape sequen
114
172
  const R_DS_ESCAPE = /\$DS/g // Dollar sign escape sequence.
115
173
  const R_INJECTION_PARTIAL = /`([^`]+)`/g // Partial string injection pattern.
116
174
 
175
+ // Default max depth (for walk etc).
176
+ const MAXDEPTH = 32
117
177
 
118
178
  // Keys are strings for maps, or integers for lists.
119
179
  type PropKey = string | number
@@ -125,8 +185,7 @@ type Indexable = { [key: string]: any } & { [key: number]: any }
125
185
  // For each key in a node (map or list), perform value injections in
126
186
  // three phases: on key value, before child, and then on key value again.
127
187
  // This mode is passed via the Injection structure.
128
- type InjectMode = 'key:pre' | 'key:post' | 'val'
129
-
188
+ type InjectMode = number
130
189
 
131
190
  // Handle value injections using backtick escape sequences:
132
191
  // - `a.b.c`: insert value at {a:{b:{c:1}}}
@@ -138,7 +197,6 @@ type Injector = (
138
197
  store: any, // Current source root value.
139
198
  ) => any
140
199
 
141
-
142
200
  // Apply a custom modification to injections.
143
201
  type Modify = (
144
202
  val: any, // Value.
@@ -148,11 +206,10 @@ type Modify = (
148
206
  store?: any, // Store, if any
149
207
  ) => void
150
208
 
151
-
152
209
  // Function applied to each node and leaf when walking a node structure depth first.
153
210
  // For {a:{b:1}} the call sequence args will be: b, 1, {b:1}, [a,b].
154
211
  type WalkApply = (
155
- // Map keys are strings, list keys are numbers, top key is UNDEF
212
+ // Map keys are strings, list keys are numbers, top key is NONE
156
213
  key: string | number | undefined,
157
214
  val: any,
158
215
  parent: any,
@@ -160,6 +217,20 @@ type WalkApply = (
160
217
  ) => any
161
218
 
162
219
 
220
+ // Return type string for narrowest type.
221
+ function typename(t: number) {
222
+ return getelem(TYPENAME, Math.clz32(t), TYPENAME[0])
223
+ }
224
+
225
+
226
+ // Get a defined value. Returns alt if val is undefined.
227
+ function getdef(val: any, alt: any) {
228
+ if (NONE === val) {
229
+ return alt
230
+ }
231
+ return val
232
+ }
233
+
163
234
 
164
235
  // Value is a node - defined, and a map (hash) or list (array).
165
236
  // NOTE: typescript
@@ -229,11 +300,14 @@ function size(val: any): number {
229
300
  }
230
301
 
231
302
 
232
- // Extract part of an array or string into a new value, from the start point to the end point.
233
- // If no end is specified, extract to the full length of the value. Negative arguments count
234
- // from the end of the value. For numbers, perform min and max bounding, where start is
235
- // inclusive, and end is *exclusive*.
236
- function slice<V extends any>(val: V, start?: number, end?: number): V {
303
+ // Extract part of an array or string into a new value, from the start
304
+ // point to the end point. If no end is specified, extract to the
305
+ // full length of the value. Negative arguments count from the end of
306
+ // the value. For numbers, perform min and max bounding, where start
307
+ // is inclusive, and end is *exclusive*.
308
+ // NOTE: input lists are not mutated by default. Use the mutate
309
+ // argument to mutate lists in place.
310
+ function slice<V extends any>(val: V, start?: number, end?: number, mutate?: boolean): V {
237
311
  if (S_number === typeof val) {
238
312
  start = null == start || S_number !== typeof start ? Number.MIN_SAFE_INTEGER : start
239
313
  end = (null == end || S_number !== typeof end ? Number.MAX_SAFE_INTEGER : end) - 1
@@ -277,7 +351,15 @@ function slice<V extends any>(val: V, start?: number, end?: number): V {
277
351
 
278
352
  if (-1 < start && start <= end && end <= vlen) {
279
353
  if (islist(val)) {
280
- val = val.slice(start, end) as V
354
+ if (mutate) {
355
+ for (let i = 0, j = start; j < end; i++, j++) {
356
+ val[i] = val[j]
357
+ }
358
+ val.length = (end - start)
359
+ }
360
+ else {
361
+ val = val.slice(start, end) as V
362
+ }
281
363
  }
282
364
  else if (S_string === typeof val) {
283
365
  val = (val as string).substring(start, end) as V
@@ -297,6 +379,7 @@ function slice<V extends any>(val: V, start?: number, end?: number): V {
297
379
  }
298
380
 
299
381
 
382
+ // String padding.
300
383
  function pad(str: any, padding?: number, padchar?: string): string {
301
384
  str = S_string === typeof str ? str : stringify(str)
302
385
  padding = null == padding ? 44 : padding
@@ -305,34 +388,71 @@ function pad(str: any, padding?: number, padchar?: string): string {
305
388
  }
306
389
 
307
390
 
308
- // Determine the type of a value as a string.
309
- // Returns one of: 'null', 'string', 'number', 'boolean', 'function', 'array', 'object'
310
- // Normalizes and simplifies JavaScript's type system for consistency.
311
- function typify(value: any): string {
312
- if (value === null || value === undefined) {
313
- return S_null
391
+ // Determine the type of a value as a bit code.
392
+ function typify(value: any): number {
393
+
394
+ if (undefined === value) {
395
+ return T_noval
314
396
  }
315
397
 
316
- const type = typeof value
398
+ const typestr = typeof value
399
+
400
+ if (null === value) {
401
+ return T_scalar | T_null
402
+ }
403
+ else if (S_number === typestr) {
404
+ if (Number.isInteger(value)) {
405
+ return T_scalar | T_number | T_integer
406
+ }
407
+ else if (isNaN(value)) {
408
+ return T_noval
409
+ }
410
+ else {
411
+ return T_scalar | T_number | T_decimal
412
+ }
413
+ }
414
+ else if (S_string === typestr) {
415
+ return T_scalar | T_string
416
+ }
417
+ else if (S_boolean === typestr) {
418
+ return T_scalar | T_boolean
419
+ }
420
+ else if (S_function === typestr) {
421
+ return T_scalar | T_function
422
+ }
317
423
 
318
- if (Array.isArray(value)) {
319
- return S_array
424
+ // For languages that have symbolic atoms.
425
+ else if (S_symbol === typestr) {
426
+ return T_scalar | T_symbol
320
427
  }
321
428
 
322
- if (type === 'object') {
323
- return S_object
429
+ else if (Array.isArray(value)) {
430
+ return T_node | T_list
324
431
  }
325
432
 
326
- return type
433
+ else if (S_object === typestr) {
434
+
435
+ if (value.constructor instanceof Function) {
436
+ let cname = value.constructor.name
437
+ if ('Object' !== cname && 'Array' !== cname) {
438
+ return T_node | T_instance
439
+ }
440
+ }
441
+
442
+ return T_node | T_map
443
+ }
444
+
445
+ // Anything else (e.g. bigint) is considered T_any
446
+ return T_any
327
447
  }
328
448
 
329
449
 
330
450
  // Get a list element. The key should be an integer, or a string
331
451
  // that can parse to an integer only. Negative integers count from the end of the list.
332
452
  function getelem(val: any, key: any, alt?: any) {
333
- let out = UNDEF
453
+ let out = NONE
334
454
 
335
- if (UNDEF === val || UNDEF === key) {
455
+ if (NONE === val || NONE === key) {
336
456
  return alt
337
457
  }
338
458
 
@@ -346,8 +466,8 @@ function getelem(val: any, key: any, alt?: any) {
346
466
  }
347
467
  }
348
468
 
349
- if (UNDEF === out) {
350
- return alt
469
+ if (NONE === out) {
470
+ return 0 < (T_function & typify(alt)) ? alt() : alt
351
471
  }
352
472
 
353
473
  return out
@@ -359,7 +479,7 @@ function getelem(val: any, key: any, alt?: any) {
359
479
  function getprop(val: any, key: any, alt?: any) {
360
480
  let out = alt
361
481
 
362
- if (UNDEF === val || UNDEF === key) {
482
+ if (NONE === val || NONE === key) {
363
483
  return alt
364
484
  }
365
485
 
@@ -367,7 +487,7 @@ function getprop(val: any, key: any, alt?: any) {
367
487
  out = val[key]
368
488
  }
369
489
 
370
- if (UNDEF === out) {
490
+ if (NONE === out) {
371
491
  return alt
372
492
  }
373
493
 
@@ -380,20 +500,20 @@ function getprop(val: any, key: any, alt?: any) {
380
500
  // Number keys are converted to strings.
381
501
  // Floats are truncated to integers.
382
502
  // Booleans, objects, arrays, null, undefined all return empty string.
383
- function strkey(key: any = UNDEF): string {
384
- if (UNDEF === key) {
503
+ function strkey(key: any = NONE): string {
504
+ if (NONE === key) {
385
505
  return S_MT
386
506
  }
387
507
 
388
- if (typeof key === S_string) {
508
+ const t = typify(key)
509
+
510
+ if (0 < (T_string & t)) {
389
511
  return key
390
512
  }
391
-
392
- if (typeof key === S_boolean) {
513
+ else if (0 < (T_boolean & t)) {
393
514
  return S_MT
394
515
  }
395
-
396
- if (typeof key === S_number) {
516
+ else if (0 < (T_number & t)) {
397
517
  return key % 1 === 0 ? String(key) : String(Math.floor(key))
398
518
  }
399
519
 
@@ -401,7 +521,8 @@ function strkey(key: any = UNDEF): string {
401
521
  }
402
522
 
403
523
 
404
- // Sorted keys of a map, or indexes of a list.
524
+ // Sorted keys of a map, or indexes (as strings) of a list.
525
+ // Root utility - only uses language facilities.
405
526
  function keysof(val: any): string[] {
406
527
  return !isnode(val) ? [] :
407
528
  ismap(val) ? Object.keys(val).sort() : (val as any).map((_n: any, i: number) => S_MT + i)
@@ -409,22 +530,60 @@ function keysof(val: any): string[] {
409
530
 
410
531
 
411
532
  // Value of property with name key in node val is defined.
533
+ // Root utility - only uses language facilities.
412
534
  function haskey(val: any, key: any) {
413
- return UNDEF !== getprop(val, key)
535
+ return NONE !== getprop(val, key)
414
536
  }
415
537
 
416
538
 
417
539
  // List the sorted keys of a map or list as an array of tuples of the form [key, value].
418
- // NOTE: Unlike keysof, list indexes are returned as numbers.
419
- function items(val: any): [number | string, any][] {
420
- return keysof(val).map((k: any) => [k, val[k]])
540
+ // As with keysof, list indexes are converted to strings.
541
+ // Root utility - only uses language facilities.
542
+ function items(val: any): [string, any][];
543
+ function items<T>(val: any, apply: (item: [string, any]) => T): T[];
544
+ function items(
545
+ val: any,
546
+ apply?: (item: [string, any]) => any
547
+ ): any[] {
548
+ let out: [string, any][] = keysof(val).map((k: any) => [k, val[k]])
549
+ if (null != apply) {
550
+ out = out.map(apply)
551
+ }
552
+ return out
553
+ }
554
+
555
+
556
+ // To replicate the array spread operator:
557
+ // a=1, b=[2,3], c=[4,5]
558
+ // [a,...b,c] -> [1,2,3,[4,5]]
559
+ // flatten([a,b,[c]]) -> [1,2,3,[4,5]]
560
+ // NOTE: [c] ensures c is not expanded
561
+ function flatten(list: any[], depth?: number) {
562
+ if (!islist(list)) {
563
+ return list
564
+ }
565
+ return list.flat(getdef(depth, 1))
566
+ }
567
+
568
+
569
+ // Filter item values using check function.
570
+ function filter(val: any, check: (item: [string, any]) => boolean): any[] {
571
+ let all = items(val)
572
+ let numall = size(all)
573
+ let out = []
574
+ for (let i = 0; i < numall; i++) {
575
+ if (check(all[i])) {
576
+ out.push(all[i][1])
577
+ }
578
+ }
579
+ return out
421
580
  }
422
581
 
423
582
 
424
583
  // Escape regular expression.
425
584
  function escre(s: string) {
426
- s = null == s ? S_MT : s
427
- return s.replace(R_ESCAPE_REGEXP, '\\$&')
585
+ // s = null == s ? S_MT : s
586
+ return replace(s, R_ESCAPE_REGEXP, '\\$&')
428
587
  }
429
588
 
430
589
 
@@ -435,16 +594,59 @@ function escurl(s: string) {
435
594
  }
436
595
 
437
596
 
438
- // Concatenate url part strings, merging forward slashes as needed.
439
- function joinurl(sarr: any[]) {
440
- return sarr
441
- .filter(s => null != s && S_MT !== s)
442
- .map((s, i) => 0 === i ? s.replace(R_TRAILING_SLASH, S_MT) :
443
- s.replace(R_LEADING_TRAILING_SLASH, '$1/')
444
- .replace(R_LEADING_SLASH, S_MT)
445
- .replace(R_TRAILING_SLASH, S_MT))
446
- .filter(s => S_MT !== s)
447
- .join(S_FS)
597
+ // Replace a search string (all), or a regexp, in a source string.
598
+ function replace(s: string, from: string | RegExp, to: any) {
599
+ let rs = s
600
+ let ts = typify(s)
601
+ if (0 === (T_string & ts)) {
602
+ rs = stringify(s)
603
+ }
604
+ else if (0 < ((T_noval | T_null) & ts)) {
605
+ rs = S_MT
606
+ }
607
+ else {
608
+ rs = stringify(s)
609
+ }
610
+ return rs.replace(from, to)
611
+ }
612
+
613
+
614
+ // Concatenate url part strings, merging sep char as needed.
615
+ function join(arr: any[], sep?: string, url?: boolean) {
616
+ const sarr = size(arr)
617
+ const sepdef = getdef(sep, S_CM)
618
+ const sepre = 1 === size(sepdef) ? escre(sepdef) : NONE
619
+ const out = filter(
620
+ items(
621
+ // filter(arr, (n) => null != n[1] && S_MT !== n[1]),
622
+ filter(arr, (n) => (0 < (T_string & typify(n[1]))) && S_MT !== n[1]),
623
+ (n) => {
624
+ let i = +n[0]
625
+ let s = n[1]
626
+
627
+ if (NONE !== sepre && S_MT !== sepre) {
628
+ if (url && 0 === i) {
629
+ s = replace(s, RegExp(sepre + '+$'), S_MT)
630
+ return s
631
+ }
632
+
633
+ if (0 < i) {
634
+ s = replace(s, RegExp('^' + sepre + '+'), S_MT)
635
+ }
636
+
637
+ if (i < sarr - 1 || !url) {
638
+ s = replace(s, RegExp(sepre + '+$'), S_MT)
639
+ }
640
+
641
+ s = replace(s, RegExp('([^' + sepre + '])' + sepre + '+([^' + sepre + '])'),
642
+ '$1' + sepdef + '$2')
643
+ }
644
+
645
+ return s
646
+ }), (n) => S_MT !== n[1])
647
+ .join(sepdef)
648
+
649
+ return out
448
650
  }
449
651
 
450
652
 
@@ -453,19 +655,27 @@ function joinurl(sarr: any[]) {
453
655
  // In general, the behaivor of of JavaScript's JSON.stringify(val,null,2) is followed.
454
656
  function jsonify(val: any, flags?: { indent?: number, offset?: number }) {
455
657
  let str = S_null
658
+
456
659
  if (null != val) {
457
- const indent = getprop(flags, 'indent', 2)
458
- str = JSON.stringify(val, null, indent)
459
- if (UNDEF === str) {
460
- str = S_null
660
+ try {
661
+ const indent = getprop(flags, 'indent', 2)
662
+ str = JSON.stringify(val, null, indent)
663
+ if (NONE === str) {
664
+ str = S_null
665
+ }
666
+ const offset = getprop(flags, 'offset', 0)
667
+ if (0 < offset) {
668
+ // Left offset entire indented JSON so that it aligns with surrounding code
669
+ // indented by offset. Assume first brace is on line with asignment, so not offset.
670
+ str = '{\n' +
671
+ join(
672
+ items(
673
+ slice(str.split('\n'), 1),
674
+ (n: any) => pad(n[1], 0 - offset - size(n[1]))), '\n')
675
+ }
461
676
  }
462
- const offset = getprop(flags, 'offset', 0)
463
- if (0 < offset) {
464
- // Left offset entire indented JSON so that it aligns with surrounding code
465
- // indented by offset.
466
- str = '{\n' + str.split('\n').slice(1)
467
- .map(n => pad(n, 0 - offset - size(n)))
468
- .join('\n')
677
+ catch (e: any) {
678
+ str = '__JSONIFY_FAILED__'
469
679
  }
470
680
  }
471
681
 
@@ -478,7 +688,7 @@ function stringify(val: any, maxlen?: number, pretty?: any): string {
478
688
  let valstr = S_MT
479
689
  pretty = !!pretty
480
690
 
481
- if (UNDEF === val) {
691
+ if (NONE === val) {
482
692
  return pretty ? '<>' : valstr
483
693
  }
484
694
 
@@ -494,9 +704,9 @@ function stringify(val: any, maxlen?: number, pretty?: any): string {
494
704
  !Array.isArray(val)
495
705
  ) {
496
706
  const sortedObj: any = {}
497
- for (const k of Object.keys(val).sort()) {
498
- sortedObj[k] = val[k]
499
- }
707
+ items(val, (n) => {
708
+ sortedObj[n[0]] = val[n[0]]
709
+ })
500
710
  return sortedObj
501
711
  }
502
712
  return val
@@ -504,7 +714,7 @@ function stringify(val: any, maxlen?: number, pretty?: any): string {
504
714
  valstr = valstr.replace(R_QUOTES, S_MT)
505
715
  }
506
716
  catch (err: any) {
507
- valstr = S_MT + val
717
+ valstr = '__STRINGIFY_FAILED__'
508
718
  }
509
719
  }
510
720
 
@@ -515,8 +725,9 @@ function stringify(val: any, maxlen?: number, pretty?: any): string {
515
725
 
516
726
  if (pretty) {
517
727
  // Indicate deeper JSON levels with different terminal colors (simplistic wrt strings).
518
- let c = [81, 118, 213, 39, 208, 201, 45, 190, 129, 51, 160, 121, 226, 33, 207, 69]
519
- .map(n => `\x1b[38;5;${n}m`),
728
+ let c = items(
729
+ [81, 118, 213, 39, 208, 201, 45, 190, 129, 51, 160, 121, 226, 33, 207, 69],
730
+ (n) => '\x1b[38;5;' + n[1] + 'm'),
520
731
  r = '\x1b[0m', d = 0, o = c[0], t = o
521
732
  for (const ch of valstr) {
522
733
  if (ch === '{' || ch === '[') {
@@ -537,34 +748,34 @@ function stringify(val: any, maxlen?: number, pretty?: any): string {
537
748
 
538
749
  // Build a human friendly path string.
539
750
  function pathify(val: any, startin?: number, endin?: number) {
540
- let pathstr: string | undefined = UNDEF
751
+ let pathstr: string | undefined = NONE
541
752
 
542
753
  let path: any[] | undefined = islist(val) ? val :
543
754
  S_string == typeof val ? [val] :
544
755
  S_number == typeof val ? [val] :
545
- UNDEF
756
+ NONE
546
757
 
547
758
  const start = null == startin ? 0 : -1 < startin ? startin : 0
548
759
  const end = null == endin ? 0 : -1 < endin ? endin : 0
549
760
 
550
- if (UNDEF != path && 0 <= start) {
761
+ if (NONE != path && 0 <= start) {
551
762
  path = slice(path, start, path.length - end)
552
763
  if (0 === path.length) {
553
764
  pathstr = '<root>'
554
765
  }
555
766
  else {
556
- pathstr = path
557
- // .filter((p: any, t: any) => (t = typeof p, S_string === t || S_number === t))
558
- .filter((p: any) => iskey(p))
559
- .map((p: any) =>
560
- S_number === typeof p ? S_MT + Math.floor(p) :
561
- p.replace(R_DOT, S_MT))
562
- .join(S_DT)
767
+ pathstr = join(
768
+ items(
769
+ filter(path, (n) => iskey(n[1])), (n) => {
770
+ let p = n[1]
771
+ return S_number === typeof p ? S_MT + Math.floor(p) :
772
+ p.replace(R_DOT, S_MT)
773
+ }), S_DT)
563
774
  }
564
775
  }
565
776
 
566
- if (UNDEF === pathstr) {
567
- pathstr = '<unknown-path' + (UNDEF === val ? S_MT : S_CN + stringify(val, 47)) + '>'
777
+ if (NONE === pathstr) {
778
+ pathstr = '<unknown-path' + (NONE === val ? S_MT : S_CN + stringify(val, 47)) + '>'
568
779
  }
569
780
 
570
781
  return pathstr
@@ -572,19 +783,21 @@ function pathify(val: any, startin?: number, endin?: number) {
572
783
 
573
784
 
574
785
  // Clone a JSON-like data structure.
575
- // NOTE: function value references are copied, *not* cloned.
786
+ // NOTE: function and instance values are copied, *not* cloned.
576
787
  function clone(val: any): any {
577
788
  const refs: any[] = []
578
- const replacer: any = (_k: any, v: any) => S_function === typeof v ?
579
- (refs.push(v), '`$FUNCTION:' + (refs.length - 1) + '`') : v
789
+ const reftype = T_function | T_instance
790
+ const replacer: any = (_k: any, v: any) => 0 < (reftype & typify(v)) ?
791
+ (refs.push(v), '`$REF:' + (refs.length - 1) + '`') : v
580
792
  const reviver: any = (_k: any, v: any, m: any) => S_string === typeof v ?
581
- (m = v.match(R_FUNCTION_REF), m ? refs[m[1]] : v) : v
582
- return UNDEF === val ? UNDEF : JSON.parse(JSON.stringify(val, replacer), reviver)
793
+ (m = v.match(R_CLONE_REF), m ? refs[m[1]] : v) : v
794
+ const out = NONE === val ? NONE : JSON.parse(JSON.stringify(val, replacer), reviver)
795
+ return out
583
796
  }
584
797
 
585
798
 
586
799
  // Define a JSON Object using function arguments.
587
- function jo(...kv: any[]): Record<string, any> {
800
+ function jm(...kv: any[]): Record<string, any> {
588
801
  const kvsize = size(kv)
589
802
  const o: any = {}
590
803
  for (let i = 0; i < kvsize; i += 2) {
@@ -597,7 +810,7 @@ function jo(...kv: any[]): Record<string, any> {
597
810
 
598
811
 
599
812
  // Define a JSON Array using function arguments.
600
- function ja(...v: any[]): any[] {
813
+ function jt(...v: any[]): any[] {
601
814
  const vsize = size(v)
602
815
  const a: any = new Array(vsize)
603
816
  for (let i = 0; i < vsize; i++) {
@@ -607,19 +820,18 @@ function ja(...v: any[]): any[] {
607
820
  }
608
821
 
609
822
 
610
-
611
823
  // Safely delete a property from an object or array element.
612
824
  // Undefined arguments and invalid keys are ignored.
613
825
  // Returns the (possibly modified) parent.
614
826
  // For objects, the property is deleted using the delete operator.
615
827
  // For arrays, the element at the index is removed and remaining elements are shifted down.
828
+ // NOTE: parent list may be new list, thus update references.
616
829
  function delprop<PARENT>(parent: PARENT, key: any): PARENT {
617
830
  if (!iskey(key)) {
618
831
  return parent
619
832
  }
620
833
 
621
834
  if (ismap(parent)) {
622
- // key = S_MT + key
623
835
  key = strkey(key)
624
836
  delete (parent as any)[key]
625
837
  }
@@ -634,10 +846,12 @@ function delprop<PARENT>(parent: PARENT, key: any): PARENT {
634
846
  keyI = Math.floor(keyI)
635
847
 
636
848
  // Delete list element at position keyI, shifting later elements down.
637
- if (0 <= keyI && keyI < parent.length) {
638
- for (let pI = keyI; pI < parent.length - 1; pI++) {
849
+ const psize = size(parent)
850
+ if (0 <= keyI && keyI < psize) {
851
+ for (let pI = keyI; pI < psize - 1; pI++) {
639
852
  parent[pI] = parent[pI + 1]
640
853
  }
854
+
641
855
  parent.length = parent.length - 1
642
856
  }
643
857
  }
@@ -650,6 +864,7 @@ function delprop<PARENT>(parent: PARENT, key: any): PARENT {
650
864
  // Returns the (possibly modified) parent.
651
865
  // If the parent is a list, and the key is negative, prepend the value.
652
866
  // NOTE: If the key is above the list size, append the value; below, prepend.
867
+ // NOTE: parent list may be new list, thus update references.
653
868
  function setprop<PARENT>(parent: PARENT, key: any, val: any): PARENT {
654
869
  if (!iskey(key)) {
655
870
  return parent
@@ -670,9 +885,11 @@ function setprop<PARENT>(parent: PARENT, key: any, val: any): PARENT {
670
885
 
671
886
  keyI = Math.floor(keyI)
672
887
 
888
+ // TODO: DELETE list element
889
+
673
890
  // Set or append value at position keyI, or append if keyI out of bounds.
674
891
  if (0 <= keyI) {
675
- parent[parent.length < keyI ? parent.length : keyI] = val
892
+ parent[slice(keyI, 0, size(parent) + 1)] = val
676
893
  }
677
894
 
678
895
  // Prepend value if keyI is negative
@@ -689,22 +906,44 @@ function setprop<PARENT>(parent: PARENT, key: any, val: any): PARENT {
689
906
  function walk(
690
907
  // These arguments are the public interface.
691
908
  val: any,
692
- apply: WalkApply,
909
+
910
+ // Before descending into a node.
911
+ before?: WalkApply,
912
+
913
+ // After descending into a node.
914
+ after?: WalkApply,
915
+
916
+ // Maximum recursive depth, default: 32. Use null for infinite depth.
917
+ maxdepth?: number,
693
918
 
694
919
  // These areguments are used for recursive state.
695
920
  key?: string | number,
696
921
  parent?: any,
697
922
  path?: string[]
698
923
  ): any {
699
- if (isnode(val)) {
700
- for (let [ckey, child] of items(val)) {
701
- setprop(val, ckey, walk(child, apply, ckey, val, [...(path || []), S_MT + ckey]))
924
+ if (NONE === path) {
925
+ path = []
926
+ }
927
+
928
+ let out = null == before ? val : before(key, val, parent, path)
929
+
930
+ maxdepth = null != maxdepth && 0 <= maxdepth ? maxdepth : MAXDEPTH
931
+ if (0 === maxdepth || (null != path && 0 < maxdepth && maxdepth <= path.length)) {
932
+ return out
933
+ }
934
+
935
+ if (isnode(out)) {
936
+ for (let [ckey, child] of items(out)) {
937
+ setprop(out, ckey, walk(
938
+ child, before, after, maxdepth, ckey, out,
939
+ flatten([getdef(path, []), S_MT + ckey])
940
+ ))
702
941
  }
703
942
  }
704
943
 
705
- // Nodes are applied *after* their children.
706
- // For the root node, key and parent will be undefined.
707
- return apply(key, val, parent, path || [])
944
+ out = null == after ? out : after(key, out, parent, path)
945
+
946
+ return out
708
947
  }
709
948
 
710
949
 
@@ -712,8 +951,10 @@ function walk(
712
951
  // precedence. Nodes override scalars. Node kinds (list or map)
713
952
  // override each other, and do *not* merge. The first element is
714
953
  // modified.
715
- function merge(val: any): any {
716
- let out: any = UNDEF
954
+ function merge(val: any, maxdepth?: number): any {
955
+ // const md: number = null == maxdepth ? MAXDEPTH : maxdepth < 0 ? 0 : maxdepth
956
+ const md: number = slice(maxdepth ?? MAXDEPTH, 0)
957
+ let out: any = NONE
717
958
 
718
959
  // Handle edge cases.
719
960
  if (!islist(val)) {
@@ -724,7 +965,7 @@ function merge(val: any): any {
724
965
  const lenlist = list.length
725
966
 
726
967
  if (0 === lenlist) {
727
- return UNDEF
968
+ return NONE
728
969
  }
729
970
  else if (1 === lenlist) {
730
971
  return list[0]
@@ -741,72 +982,146 @@ function merge(val: any): any {
741
982
  out = obj
742
983
  }
743
984
  else {
744
- // Nodes win, also over nodes of a different kind.
745
- if (!isnode(out) || (ismap(obj) && islist(out)) || (islist(obj) && ismap(out))) {
746
- out = obj
747
- }
748
- else {
749
- // Node stack. walking down the current obj.
750
- let cur: any[] = [out]
751
- let cI = 0
752
-
753
- function merger(
754
- key: string | number | undefined,
755
- val: any,
756
- parent: any,
757
- path: string[]
758
- ) {
759
- if (null == key) {
760
- return val
761
- }
985
+ // Current value at path end in overriding node.
986
+ let cur: any[] = [out]
987
+
988
+ // Current value at path end in destination node.
989
+ let dst: any[] = [out]
990
+
991
+ function before(
992
+ key: string | number | undefined,
993
+ val: any,
994
+ _parent: any,
995
+ path: string[]
996
+ ) {
997
+ const pI = size(path)
998
+
999
+ if (md <= pI) {
1000
+ setprop(cur[pI - 1], key, val)
1001
+ }
762
1002
 
763
- // Get the curent value at the current path in obj.
764
- // NOTE: this is not exactly efficient, and should be optimised.
765
- let lenpath = path.length
766
- cI = lenpath - 1
767
- if (UNDEF === cur[cI]) {
768
- cur[cI] = getpath(out, slice(path, 0, lenpath - 1))
769
- }
1003
+ // Scalars just override directly.
1004
+ else if (!isnode(val)) {
1005
+ cur[pI] = val
1006
+ }
1007
+
1008
+ // Descend into override node - Set up correct target in `after` function.
1009
+ else {
770
1010
 
771
- // Create node if needed.
772
- if (!isnode(cur[cI])) {
773
- cur[cI] = islist(parent) ? [] : {}
1011
+ // Descend into destination node using same key.
1012
+ dst[pI] = 0 < pI ? getprop(dst[pI - 1], key) : dst[pI]
1013
+ const tval = dst[pI]
1014
+
1015
+ // Destination empty, so create node (unless override is class instance).
1016
+ if (NONE === tval && 0 === (T_instance & typify(val))) {
1017
+ cur[pI] = islist(val) ? [] : {}
774
1018
  }
775
1019
 
776
- // Node child is just ahead of us on the stack, since
777
- // `walk` traverses leaves before nodes.
778
- if (isnode(val) && !isempty(val)) {
779
- setprop(cur[cI], key, cur[cI + 1])
780
- cur[cI + 1] = UNDEF
1020
+ // Matching override and destination so continue with their values.
1021
+ else if (typify(val) === typify(tval)) {
1022
+ cur[pI] = tval
781
1023
  }
782
1024
 
783
- // Scalar child.
1025
+ // Override wins.
784
1026
  else {
785
- setprop(cur[cI], key, val)
786
- }
1027
+ cur[pI] = val
787
1028
 
788
- return val
1029
+ // No need to descend when override wins (destination is discarded).
1030
+ val = NONE
1031
+ }
789
1032
  }
790
1033
 
791
- // Walk overriding node, creating paths in output as needed.
792
- walk(obj, merger)
1034
+ // console.log('BEFORE-END', pathify(path), '@', pI, key,
1035
+ // stringify(val, -1, 1), stringify(parent, -1, 1),
1036
+ // 'CUR=', stringify(cur, -1, 1), 'DST=', stringify(dst, -1, 1))
1037
+
1038
+ return val
1039
+ }
1040
+
1041
+ function after(
1042
+ key: string | number | undefined,
1043
+ _val: any,
1044
+ _parent: any,
1045
+ path: string[]
1046
+ ) {
1047
+ const cI = size(path)
1048
+ const target = cur[cI - 1]
1049
+ const value = cur[cI]
1050
+
1051
+ // console.log('AFTER-PREP', pathify(path), '@', cI, cur, '|',
1052
+ // stringify(key, -1, 1), stringify(value, -1, 1), 'T=', stringify(target, -1, 1))
1053
+
1054
+ setprop(target, key, value)
1055
+ return value
793
1056
  }
1057
+
1058
+ // Walk overriding node, creating paths in output as needed.
1059
+ out = walk(obj, before, after, maxdepth)
1060
+ // console.log('WALK-DONE', out, obj)
794
1061
  }
795
1062
  }
796
1063
 
1064
+ if (0 === md) {
1065
+ out = getelem(list, -1)
1066
+ out = islist(out) ? [] : ismap(out) ? {} : out
1067
+ }
1068
+
797
1069
  return out
798
1070
  }
799
1071
 
800
1072
 
1073
+ // Set a value using a path. Missing path parts are created.
1074
+ // String paths create only maps. Use a string list to create list parts.
1075
+ function setpath(
1076
+ store: any,
1077
+ path: number | string | string[],
1078
+ val: any,
1079
+ injdef?: Partial<Injection>
1080
+ ) {
1081
+ const pathType = typify(path)
1082
+
1083
+ const parts = 0 < (T_list & pathType) ? path :
1084
+ 0 < (T_string & pathType) ? (path as string).split(S_DT) :
1085
+ 0 < (T_number & pathType) ? [path] : NONE
1086
+
1087
+ if (NONE === parts) {
1088
+ return NONE
1089
+ }
1090
+
1091
+ const base = getprop(injdef, S_base)
1092
+ const numparts = size(parts)
1093
+ let parent = getprop(store, base, store)
1094
+
1095
+ for (let pI = 0; pI < numparts - 1; pI++) {
1096
+ const partKey = getelem(parts, pI)
1097
+ let nextParent = getprop(parent, partKey)
1098
+ if (!isnode(nextParent)) {
1099
+ nextParent = 0 < (T_number & typify(getelem(parts, pI + 1))) ? [] : {}
1100
+ setprop(parent, partKey, nextParent)
1101
+ }
1102
+ parent = nextParent
1103
+ }
1104
+
1105
+ if (DELETE === val) {
1106
+ delprop(parent, getelem(parts, -1))
1107
+ }
1108
+ else {
1109
+ setprop(parent, getelem(parts, -1), val)
1110
+ }
1111
+
1112
+ return parent
1113
+ }
1114
+
1115
+
801
1116
  function getpath(store: any, path: number | string | string[], injdef?: Partial<Injection>) {
802
1117
 
803
1118
  // Operate on a string array.
804
1119
  const parts = islist(path) ? path :
805
1120
  'string' === typeof path ? path.split(S_DT) :
806
- 'number' === typeof path ? [strkey(path)] : UNDEF
1121
+ 'number' === typeof path ? [strkey(path)] : NONE
807
1122
 
808
- if (UNDEF === parts) {
809
- return UNDEF
1123
+ if (NONE === parts) {
1124
+ return NONE
810
1125
  }
811
1126
 
812
1127
  // let root = store
@@ -838,7 +1153,7 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial<
838
1153
 
839
1154
  const dpath = getprop(injdef, 'dpath')
840
1155
 
841
- for (let pI = 0; UNDEF !== val && pI < parts.length; pI++) {
1156
+ for (let pI = 0; NONE !== val && pI < numparts; pI++) {
842
1157
  let part = parts[pI]
843
1158
 
844
1159
  if (injdef && S_DKEY === part) {
@@ -846,15 +1161,15 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial<
846
1161
  }
847
1162
  else if (injdef && part.startsWith('$GET:')) {
848
1163
  // $GET:path$ -> get store value, use as path part (string)
849
- part = stringify(getpath(src, part.substring(5, part.length - 1)))
1164
+ part = stringify(getpath(src, slice(part, 5, -1)))
850
1165
  }
851
1166
  else if (injdef && part.startsWith('$REF:')) {
852
1167
  // $REF:refpath$ -> get spec value, use as path part (string)
853
- part = stringify(getpath(getprop(store, S_DSPEC), part.substring(5, part.length - 1)))
1168
+ part = stringify(getpath(getprop(store, S_DSPEC), slice(part, 5, -1)))
854
1169
  }
855
1170
  else if (injdef && part.startsWith('$META:')) {
856
1171
  // $META:metapath$ -> get meta value, use as path part (string)
857
- part = stringify(getpath(getprop(injdef, 'meta'), part.substring(6, part.length - 1)))
1172
+ part = stringify(getpath(getprop(injdef, 'meta'), slice(part, 6, -1)))
858
1173
  }
859
1174
 
860
1175
  // $$ escapes $
@@ -877,14 +1192,16 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial<
877
1192
  val = dparent
878
1193
  }
879
1194
  else {
880
- const fullpath = slice(dpath, 0 - ascends).concat(parts.slice(pI + 1))
1195
+ // const fullpath = slice(dpath, 0 - ascends).concat(parts.slice(pI + 1))
1196
+ const fullpath = flatten([slice(dpath, 0 - ascends), parts.slice(pI + 1)])
881
1197
 
882
1198
  if (ascends <= size(dpath)) {
883
1199
  val = getpath(store, fullpath)
884
1200
  }
885
1201
  else {
886
- val = UNDEF
1202
+ val = NONE
887
1203
  }
1204
+
888
1205
  break
889
1206
  }
890
1207
  }
@@ -906,13 +1223,14 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial<
906
1223
  val = handler(injdef, val, ref, store)
907
1224
  }
908
1225
 
1226
+ // console.log('GETPATH', path, val)
1227
+
909
1228
  return val
910
1229
  }
911
1230
 
912
1231
 
913
-
914
1232
  // Inject values from a data store into a node recursively, resolving
915
- // paths against the store, or current if they are local. THe modify
1233
+ // paths against the store, or current if they are local. The modify
916
1234
  // argument allows custom modification of the result. The inj
917
1235
  // (Injection) argument is used to maintain recursive state.
918
1236
  function inject(
@@ -925,14 +1243,14 @@ function inject(
925
1243
 
926
1244
  // Create state if at root of injection. The input value is placed
927
1245
  // inside a virtual parent holder to simplify edge cases.
928
- if (UNDEF === injdef || null == injdef.mode) {
1246
+ if (NONE === injdef || null == injdef.mode) {
929
1247
  // Set up state assuming we are starting in the virtual parent.
930
1248
  inj = new Injection(val, { [S_DTOP]: val })
931
1249
  inj.dparent = store
932
1250
  inj.errs = getprop(store, S_DERRS, [])
933
1251
  inj.meta.__d = 0
934
1252
 
935
- if (UNDEF !== injdef) {
1253
+ if (NONE !== injdef) {
936
1254
  inj.modify = null == injdef.modify ? inj.modify : injdef.modify
937
1255
  inj.extra = null == injdef.extra ? inj.extra : injdef.extra
938
1256
  inj.meta = null == injdef.meta ? inj.meta : injdef.meta
@@ -942,6 +1260,9 @@ function inject(
942
1260
 
943
1261
  inj.descend()
944
1262
 
1263
+ // console.log('INJ-START', val, inj.mode, inj.key, inj.val,
1264
+ // 't=', inj.path, 'P=', inj.parent, 'dp=', inj.dparent, 'ST=', store.$TOP)
1265
+
945
1266
  // Descend into node.
946
1267
  if (isnode(val)) {
947
1268
 
@@ -949,21 +1270,29 @@ function inject(
949
1270
  // Injection transforms ($FOO) are processed *after* other keys.
950
1271
  // NOTE: the optional digits suffix of the transform can thus be
951
1272
  // used to order the transforms.
952
- let nodekeys = ismap(val) ? [
953
- ...Object.keys(val).filter(k => !k.includes(S_DS)).sort(),
954
- ...Object.keys(val).filter(k => k.includes(S_DS)).sort(),
955
- ] : (val as any).map((_n: any, i: number) => i)
956
1273
 
1274
+ let nodekeys: any[]
1275
+ nodekeys = keysof(val)
1276
+
1277
+ if (ismap(val)) {
1278
+ nodekeys = flatten([
1279
+ filter(nodekeys, (n => !n[1].includes(S_DS))),
1280
+ filter(nodekeys, (n => n[1].includes(S_DS))),
1281
+ ])
1282
+ }
1283
+ else {
1284
+ nodekeys = keysof(val)
1285
+ }
957
1286
 
958
1287
  // Each child key-value pair is processed in three injection phases:
959
- // 1. inj.mode='key:pre' - Key string is injected, returning a possibly altered key.
960
- // 2. inj.mode='val' - The child value is injected.
961
- // 3. inj.mode='key:post' - Key string is injected again, allowing child mutation.
1288
+ // 1. inj.mode=M_KEYPRE - Key string is injected, returning a possibly altered key.
1289
+ // 2. inj.mode=M_VAL - The child value is injected.
1290
+ // 3. inj.mode=M_KEYPOST - Key string is injected again, allowing child mutation.
962
1291
  for (let nkI = 0; nkI < nodekeys.length; nkI++) {
963
1292
 
964
1293
  const childinj = inj.child(nkI, nodekeys)
965
1294
  const nodekey = childinj.key
966
- childinj.mode = S_MKEYPRE
1295
+ childinj.mode = M_KEYPRE
967
1296
 
968
1297
  // Peform the key:pre mode injection on the child key.
969
1298
  const prekey = _injectstr(nodekey, store, childinj)
@@ -973,9 +1302,9 @@ function inject(
973
1302
  nodekeys = childinj.keys
974
1303
 
975
1304
  // Prevent further processing by returning an undefined prekey
976
- if (UNDEF !== prekey) {
1305
+ if (NONE !== prekey) {
977
1306
  childinj.val = getprop(val, prekey)
978
- childinj.mode = S_MVAL as InjectMode
1307
+ childinj.mode = M_VAL
979
1308
 
980
1309
  // Perform the val mode injection on the child value.
981
1310
  // NOTE: return value is not used.
@@ -986,7 +1315,7 @@ function inject(
986
1315
  nodekeys = childinj.keys
987
1316
 
988
1317
  // Peform the key:post mode injection on the child key.
989
- childinj.mode = S_MKEYPOST as InjectMode
1318
+ childinj.mode = M_KEYPOST
990
1319
  _injectstr(nodekey, store, childinj)
991
1320
 
992
1321
  // The injection may modify child processing.
@@ -998,7 +1327,7 @@ function inject(
998
1327
 
999
1328
  // Inject paths into string scalars.
1000
1329
  else if (S_string === valtype) {
1001
- inj.mode = S_MVAL as InjectMode
1330
+ inj.mode = M_VAL
1002
1331
  val = _injectstr(val, store, inj)
1003
1332
  if (SKIP !== val) {
1004
1333
  inj.setval(val)
@@ -1020,6 +1349,8 @@ function inject(
1020
1349
  )
1021
1350
  }
1022
1351
 
1352
+ // console.log('INJ-VAL', val)
1353
+
1023
1354
  inj.val = val
1024
1355
 
1025
1356
  // Original val reference may no longer be correct.
@@ -1032,21 +1363,22 @@ function inject(
1032
1363
 
1033
1364
  // Delete a key from a map or list.
1034
1365
  const transform_DELETE: Injector = (inj: Injection) => {
1035
- inj.setval(UNDEF)
1036
- return UNDEF
1366
+ inj.setval(NONE)
1367
+ return NONE
1037
1368
  }
1038
1369
 
1039
1370
 
1040
1371
  // Copy value from source data.
1041
1372
  const transform_COPY: Injector = (inj: Injection, _val: any) => {
1042
- const { mode, key } = inj
1373
+ const ijname = 'COPY'
1043
1374
 
1044
- let out = key
1045
- if (!mode.startsWith(S_MKEY)) {
1046
- out = getprop(inj.dparent, key)
1047
- inj.setval(out)
1375
+ if (!checkPlacement(M_VAL, ijname, T_any, inj)) {
1376
+ return NONE
1048
1377
  }
1049
1378
 
1379
+ let out = getprop(inj.dparent, inj.key)
1380
+ inj.setval(out)
1381
+
1050
1382
  return out
1051
1383
  }
1052
1384
 
@@ -1056,29 +1388,30 @@ const transform_COPY: Injector = (inj: Injection, _val: any) => {
1056
1388
  const transform_KEY: Injector = (inj: Injection) => {
1057
1389
  const { mode, path, parent } = inj
1058
1390
 
1059
- // Do nothing in val mode.
1060
- if (S_MVAL !== mode) {
1061
- return UNDEF
1391
+ // Do nothing in val mode - not an error.
1392
+ if (M_VAL !== mode) {
1393
+ return NONE
1062
1394
  }
1063
1395
 
1064
1396
  // Key is defined by $KEY meta property.
1065
1397
  const keyspec = getprop(parent, S_BKEY)
1066
- if (UNDEF !== keyspec) {
1398
+ if (NONE !== keyspec) {
1067
1399
  delprop(parent, S_BKEY)
1068
1400
  return getprop(inj.dparent, keyspec)
1069
1401
  }
1070
1402
 
1071
1403
  // Key is defined within general purpose $META object.
1072
- return getprop(getprop(parent, S_BANNO), S_KEY, getprop(path, path.length - 2))
1404
+ // return getprop(getprop(parent, S_BANNO), S_KEY, getprop(path, path.length - 2))
1405
+ return getprop(getprop(parent, S_BANNO), S_KEY, getelem(path, -2))
1073
1406
  }
1074
1407
 
1075
1408
 
1076
- // Annotatea node. Does nothing itself, just used by
1409
+ // Annotate node. Does nothing itself, just used by
1077
1410
  // other injectors, and is removed when called.
1078
1411
  const transform_ANNO: Injector = (inj: Injection) => {
1079
1412
  const { parent } = inj
1080
1413
  delprop(parent, S_BANNO)
1081
- return UNDEF
1414
+ return NONE
1082
1415
  }
1083
1416
 
1084
1417
 
@@ -1091,29 +1424,27 @@ const transform_MERGE: Injector = (inj: Injection) => {
1091
1424
  const { mode, key, parent } = inj
1092
1425
 
1093
1426
  // Ensures $MERGE is removed from parent list (val mode).
1094
- let out: any = UNDEF
1427
+ let out: any = NONE
1095
1428
 
1096
- if (S_MKEYPRE === mode) {
1429
+ if (M_KEYPRE === mode) {
1097
1430
  out = key
1098
1431
  }
1099
1432
 
1100
1433
  // Operate after child values have been transformed.
1101
- else if (S_MKEYPOST === mode) {
1434
+ else if (M_KEYPOST === mode) {
1102
1435
  out = key
1103
1436
 
1104
1437
  let args = getprop(parent, key)
1105
1438
  args = Array.isArray(args) ? args : [args]
1106
1439
 
1107
1440
  // Remove the $MERGE command from a parent map.
1108
- inj.setval(UNDEF)
1441
+ inj.setval(NONE)
1109
1442
 
1110
1443
  // Literals in the parent have precedence, but we still merge onto
1111
1444
  // the parent object, so that node tree references are not changed.
1112
- const mergelist = [parent, ...args, clone(parent)]
1445
+ const mergelist = flatten([[parent], args, [clone(parent)]])
1113
1446
 
1114
1447
  merge(mergelist)
1115
-
1116
- // return key
1117
1448
  }
1118
1449
 
1119
1450
  return out
@@ -1128,65 +1459,63 @@ const transform_EACH: Injector = (
1128
1459
  _ref: string,
1129
1460
  store: any
1130
1461
  ) => {
1462
+ const ijname = 'EACH'
1131
1463
 
1132
- // Remove arguments to avoid spurious processing.
1133
- if (null != inj.keys) {
1134
- inj.keys.length = 1
1464
+ if (!checkPlacement(M_VAL, ijname, T_list, inj)) {
1465
+ return NONE
1135
1466
  }
1136
1467
 
1137
- if (S_MVAL !== inj.mode) {
1138
- return UNDEF
1139
- }
1468
+ // Remove remaining keys to avoid spurious processing.
1469
+ slice(inj.keys, 0, 1, true)
1140
1470
 
1141
- // Get arguments: ['`$EACH`', 'source-path', child-template].
1142
- const srcpath = getprop(inj.parent, 1)
1143
- const child = clone(getprop(inj.parent, 2))
1471
+ // const [err, srcpath, child] = injectorArgs([T_string, T_any], inj)
1472
+ const [err, srcpath, child] = injectorArgs([T_string, T_any], slice(inj.parent, 1))
1473
+ if (NONE !== err) {
1474
+ inj.errs.push('$' + ijname + ': ' + err)
1475
+ return NONE
1476
+ }
1144
1477
 
1145
1478
  // Source data.
1146
1479
  const srcstore = getprop(store, inj.base, store)
1147
1480
 
1148
1481
  const src = getpath(srcstore, srcpath, inj)
1482
+ const srctype = typify(src)
1149
1483
 
1150
1484
  // Create parallel data structures:
1151
1485
  // source entries :: child templates
1152
1486
  let tcur: any = []
1153
1487
  let tval: any = []
1154
1488
 
1155
- const tkey = inj.path[inj.path.length - 2]
1156
- const target = inj.nodes[inj.nodes.length - 2] || inj.nodes[inj.nodes.length - 1]
1489
+ const tkey = getelem(inj.path, -2)
1490
+ const target = getelem(inj.nodes, - 2, () => getelem(inj.nodes, -1))
1157
1491
 
1158
1492
  // Create clones of the child template for each value of the current soruce.
1159
- if (islist(src)) {
1160
- tval = src.map(() => clone(child))
1493
+ if (0 < (T_list & srctype)) {
1494
+ tval = items(src, () => clone(child))
1161
1495
  }
1162
- else if (ismap(src)) {
1163
- tval = Object.entries(src).map(n => ({
1164
- ...clone(child),
1165
-
1496
+ else if (0 < (T_map & srctype)) {
1497
+ tval = items(src, (n => merge([
1498
+ clone(child),
1166
1499
  // Make a note of the key for $KEY transforms.
1167
- [S_BANNO]: { KEY: n[0] }
1168
- }))
1500
+ { [S_BANNO]: { KEY: n[0] } }
1501
+ ], 1)))
1169
1502
  }
1170
1503
 
1171
1504
  let rval = []
1172
1505
 
1173
1506
  if (0 < size(tval)) {
1174
- tcur = null == src ? UNDEF : Object.values(src)
1507
+ tcur = null == src ? NONE : Object.values(src)
1175
1508
 
1176
1509
  const ckey = getelem(inj.path, -2)
1177
1510
 
1178
1511
  const tpath = slice(inj.path, -1)
1179
- const dpath = [S_DTOP, ...srcpath.split(S_DT), '$:' + ckey]
1180
-
1512
+ const dpath = flatten([S_DTOP, srcpath.split(S_DT), '$:' + ckey])
1181
1513
 
1182
1514
  // Parent structure.
1183
-
1184
- // const ckey = getelem(cpath, -1)
1185
1515
  tcur = { [ckey]: tcur }
1186
1516
 
1187
- if (1 < tpath.length) {
1517
+ if (1 < size(tpath)) {
1188
1518
  const pkey = getelem(inj.path, -3, S_DTOP)
1189
- // const pkey = getelem(cpath, -2, S_DTOP)
1190
1519
  tcur = { [pkey]: tcur }
1191
1520
  dpath.push('$:' + pkey)
1192
1521
  }
@@ -1206,7 +1535,8 @@ const transform_EACH: Injector = (
1206
1535
  rval = tinj.val
1207
1536
  }
1208
1537
 
1209
- _updateAncestors(inj, target, tkey, rval)
1538
+ // _updateAncestors(inj, target, tkey, rval)
1539
+ setprop(target, tkey, rval)
1210
1540
 
1211
1541
  // Prevent callee from damaging first list entry (since we are in `val` mode).
1212
1542
  return rval[0]
@@ -1214,7 +1544,7 @@ const transform_EACH: Injector = (
1214
1544
 
1215
1545
 
1216
1546
  // Convert a node to a map.
1217
- // Format: { '`$PACK`':['`source-path`', child-template]}
1547
+ // Format: { '`$PACK`':['source-path', child-template]}
1218
1548
  const transform_PACK: Injector = (
1219
1549
  inj: Injection,
1220
1550
  _val: any,
@@ -1223,78 +1553,105 @@ const transform_PACK: Injector = (
1223
1553
  ) => {
1224
1554
  const { mode, key, path, parent, nodes } = inj
1225
1555
 
1226
- // Defensive context checks.
1227
- if (S_MKEYPRE !== mode || S_string !== typeof key || null == path || null == nodes) {
1228
- return UNDEF
1556
+ const ijname = 'EACH'
1557
+
1558
+ if (!checkPlacement(M_KEYPRE, ijname, T_map, inj)) {
1559
+ return NONE
1229
1560
  }
1230
1561
 
1231
1562
  // Get arguments.
1232
- const args = parent[key]
1233
- const srcpath = args[0] // Path to source data.
1234
- const child = clone(args[1]) // Child template.
1563
+ const args = getprop(parent, key)
1564
+ const [err, srcpath, origchildspec] = injectorArgs([T_string, T_any], args)
1565
+ if (NONE !== err) {
1566
+ inj.errs.push('$' + ijname + ': ' + err)
1567
+ return NONE
1568
+ }
1235
1569
 
1236
1570
  // Find key and target node.
1237
- const keyprop = child[S_BKEY]
1238
1571
  const tkey = getelem(path, -2)
1239
- const target = nodes[path.length - 2] || nodes[path.length - 1]
1572
+ const pathsize = size(path)
1573
+ const target = getelem(nodes, pathsize - 2, () => getelem(nodes, pathsize - 1))
1240
1574
 
1241
1575
  // Source data
1242
1576
  const srcstore = getprop(store, inj.base, store)
1243
-
1244
1577
  let src = getpath(srcstore, srcpath, inj)
1245
1578
 
1246
1579
  // Prepare source as a list.
1247
- src = islist(src) ? src :
1248
- ismap(src) ? Object.entries(src)
1249
- .reduce((a: any[], n: any) =>
1250
- (n[1][S_BANNO] = { KEY: n[0] }, a.push(n[1]), a), []) :
1251
- UNDEF
1580
+ if (!islist(src)) {
1581
+ if (ismap(src)) {
1582
+ src = items(src, (item: [string, any]) => {
1583
+ setprop(item[1], S_BANNO, { KEY: item[0] })
1584
+ return item[1]
1585
+ })
1586
+ }
1587
+ else {
1588
+ src = NONE
1589
+ }
1590
+ }
1252
1591
 
1253
1592
  if (null == src) {
1254
- return UNDEF
1593
+ return NONE
1255
1594
  }
1256
1595
 
1257
- // Get key if specified.
1258
- let childkey: PropKey | undefined = getprop(child, S_BKEY)
1259
- let keyname = UNDEF === childkey ? keyprop : childkey
1260
- delprop(child, S_BKEY)
1596
+ // Get keypath.
1597
+ const keypath = getprop(origchildspec, S_BKEY)
1598
+ const childspec = delprop(origchildspec, S_BKEY)
1599
+
1600
+ const child = getprop(childspec, S_BVAL, childspec)
1261
1601
 
1262
1602
  // Build parallel target object.
1263
1603
  let tval: any = {}
1264
- tval = src.reduce((a: any, n: any) => {
1265
- let kn = getprop(n, keyname)
1266
- setprop(a, kn, clone(child))
1267
- const nchild = getprop(a, kn)
1268
- const mval = getprop(n, S_BANNO)
1269
- if (UNDEF === mval) {
1270
- delprop(nchild, S_BANNO)
1604
+
1605
+ items(src, (item: [string, any]) => {
1606
+ const srckey = item[0]
1607
+ const srcnode = item[1]
1608
+
1609
+ let key: string = srckey
1610
+ if (NONE !== keypath) {
1611
+ if (keypath.startsWith('`')) {
1612
+ key = inject(keypath, merge([{}, store, { $TOP: srcnode }], 1))
1613
+ }
1614
+ else {
1615
+ key = getpath(srcnode, keypath, inj)
1616
+ }
1617
+ }
1618
+
1619
+ const tchild = clone(child)
1620
+ setprop(tval, key, tchild)
1621
+
1622
+ const anno = getprop(srcnode, S_BANNO)
1623
+ if (NONE === anno) {
1624
+ delprop(tchild, S_BANNO)
1271
1625
  }
1272
1626
  else {
1273
- setprop(nchild, S_BANNO, mval)
1627
+ setprop(tchild, S_BANNO, anno)
1274
1628
  }
1275
- return a
1276
- }, tval)
1629
+ })
1277
1630
 
1278
1631
  let rval = {}
1279
1632
 
1280
- if (0 < size(tval)) {
1633
+ if (!isempty(tval)) {
1281
1634
 
1282
1635
  // Build parallel source object.
1283
- let tcur: any = {}
1284
- src.reduce((a: any, n: any) => {
1285
- let kn = getprop(n, keyname)
1636
+ let tsrc: any = {}
1637
+ src.reduce((a: any, n: any, i: any) => {
1638
+ let kn = null == keypath ? i :
1639
+ keypath.startsWith('`') ?
1640
+ inject(keypath, merge([{}, store, { $TOP: n }], 1)) :
1641
+ getpath(n, keypath, inj)
1642
+
1286
1643
  setprop(a, kn, n)
1287
1644
  return a
1288
- }, tcur)
1645
+ }, tsrc)
1289
1646
 
1290
1647
  const tpath = slice(inj.path, -1)
1291
1648
 
1292
1649
  const ckey = getelem(inj.path, -2)
1293
- const dpath = [S_DTOP, ...srcpath.split(S_DT), '$:' + ckey]
1650
+ const dpath = flatten([S_DTOP, srcpath.split(S_DT), '$:' + ckey])
1294
1651
 
1295
- tcur = { [ckey]: tcur }
1652
+ let tcur = { [ckey]: tsrc }
1296
1653
 
1297
- if (1 < tpath.length) {
1654
+ if (1 < size(tpath)) {
1298
1655
  const pkey = getelem(inj.path, -3, S_DTOP)
1299
1656
  tcur = { [pkey]: tcur }
1300
1657
  dpath.push('$:' + pkey)
@@ -1304,7 +1661,6 @@ const transform_PACK: Injector = (
1304
1661
  tinj.path = tpath
1305
1662
  tinj.nodes = slice(inj.nodes, -1)
1306
1663
 
1307
- // tinj.parent = tcur
1308
1664
  tinj.parent = getelem(tinj.nodes, -1)
1309
1665
  tinj.val = tval
1310
1666
 
@@ -1315,14 +1671,15 @@ const transform_PACK: Injector = (
1315
1671
  rval = tinj.val
1316
1672
  }
1317
1673
 
1318
- _updateAncestors(inj, target, tkey, rval)
1674
+ // _updateAncestors(inj, target, tkey, rval)
1675
+ setprop(target, tkey, rval)
1319
1676
 
1320
1677
  // Drop transform key.
1321
- return UNDEF
1678
+ return NONE
1322
1679
  }
1323
1680
 
1324
1681
 
1325
- // TODO: not found ref should removed key (setprop UNDEF)
1682
+ // TODO: not found ref should removed key (setprop NONE)
1326
1683
  // Reference original spec (enables recursice transformations)
1327
1684
  // Format: ['`$REF`', '`spec-path`']
1328
1685
  const transform_REF: Injector = (
@@ -1333,21 +1690,24 @@ const transform_REF: Injector = (
1333
1690
  ) => {
1334
1691
  const { nodes } = inj
1335
1692
 
1336
- if (S_MVAL !== inj.mode) {
1337
- return UNDEF
1693
+ if (M_VAL !== inj.mode) {
1694
+ return NONE
1338
1695
  }
1339
1696
 
1340
1697
  // Get arguments: ['`$REF`', 'ref-path'].
1341
1698
  const refpath = getprop(inj.parent, 1)
1342
- inj.keyI = inj.keys.length
1699
+ inj.keyI = size(inj.keys)
1343
1700
 
1344
1701
  // Spec reference.
1345
1702
  const spec = getprop(store, S_DSPEC)()
1346
1703
 
1704
+ const dpath = slice(inj.path, 1)
1347
1705
  const ref = getpath(spec, refpath, {
1348
1706
  // TODO: test relative refs
1349
- dpath: inj.path.slice(1),
1350
- dparent: getpath(spec, inj.path.slice(1))
1707
+ // dpath: inj.path.slice(1),
1708
+ dpath,
1709
+ // dparent: getpath(spec, inj.path.slice(1))
1710
+ dparent: getpath(spec, dpath),
1351
1711
  })
1352
1712
 
1353
1713
  let hasSubRef = false
@@ -1366,9 +1726,9 @@ const transform_REF: Injector = (
1366
1726
  const tpath = slice(inj.path, -1)
1367
1727
  let tcur = getpath(store, cpath)
1368
1728
  let tval = getpath(store, tpath)
1369
- let rval = UNDEF
1729
+ let rval = NONE
1370
1730
 
1371
- if (!hasSubRef || UNDEF !== tval) {
1731
+ if (!hasSubRef || NONE !== tval) {
1372
1732
  const tinj = inj.child(0, [getelem(tpath, -1)])
1373
1733
 
1374
1734
  tinj.path = tpath
@@ -1376,7 +1736,7 @@ const transform_REF: Injector = (
1376
1736
  tinj.parent = getelem(nodes, -2)
1377
1737
  tinj.val = tref
1378
1738
 
1379
- tinj.dpath = [...cpath]
1739
+ tinj.dpath = flatten([cpath])
1380
1740
  tinj.dparent = tcur
1381
1741
 
1382
1742
  inject(tref, store, tinj)
@@ -1384,7 +1744,7 @@ const transform_REF: Injector = (
1384
1744
  rval = tinj.val
1385
1745
  }
1386
1746
  else {
1387
- rval = UNDEF
1747
+ rval = NONE
1388
1748
  }
1389
1749
 
1390
1750
  const grandparent = inj.setval(rval, 2)
@@ -1397,6 +1757,117 @@ const transform_REF: Injector = (
1397
1757
  }
1398
1758
 
1399
1759
 
1760
+ const transform_FORMAT: Injector = (
1761
+ inj: Injection,
1762
+ _val: any,
1763
+ _ref: string,
1764
+ store: any
1765
+ ) => {
1766
+ // console.log('FORMAT-START', inj, _val)
1767
+
1768
+ // Remove remaining keys to avoid spurious processing.
1769
+ slice(inj.keys, 0, 1, true)
1770
+
1771
+ if (M_VAL !== inj.mode) {
1772
+ return NONE
1773
+ }
1774
+
1775
+ // Get arguments: ['`$FORMAT`', 'name', child].
1776
+ // TODO: EACH and PACK should accept customm functions too
1777
+ const name = getprop(inj.parent, 1)
1778
+ const child = getprop(inj.parent, 2)
1779
+
1780
+ // Source data.
1781
+ const tkey = getelem(inj.path, -2)
1782
+ const target = getelem(inj.nodes, - 2, () => getelem(inj.nodes, -1))
1783
+
1784
+ const cinj = injectChild(child, store, inj)
1785
+ const resolved = cinj.val
1786
+
1787
+ let formatter = 0 < (T_function & typify(name)) ? name : getprop(FORMATTER, name)
1788
+
1789
+ if (NONE === formatter) {
1790
+ inj.errs.push('$FORMAT: unknown format: ' + name + '.')
1791
+ return NONE
1792
+ }
1793
+
1794
+ let out = walk(resolved, formatter)
1795
+
1796
+ setprop(target, tkey, out)
1797
+ // _updateAncestors(inj, target, tkey, out)
1798
+
1799
+ return out
1800
+ }
1801
+
1802
+
1803
+ const FORMATTER: Record<string, WalkApply> = {
1804
+ identity: (_k: any, v: any) => v,
1805
+ upper: (_k: any, v: any) => isnode(v) ? v : ('' + v).toUpperCase(),
1806
+ lower: (_k: any, v: any) => isnode(v) ? v : ('' + v).toLowerCase(),
1807
+ string: (_k: any, v: any) => isnode(v) ? v : ('' + v),
1808
+ number: (_k: any, v: any) => {
1809
+ if (isnode(v)) {
1810
+ return v
1811
+ }
1812
+ else {
1813
+ let n = Number(v)
1814
+ if (isNaN(n)) {
1815
+ n = 0
1816
+ }
1817
+ return n
1818
+ }
1819
+ },
1820
+ integer: (_k: any, v: any) => {
1821
+ if (isnode(v)) {
1822
+ return v
1823
+ }
1824
+ else {
1825
+ let n = Number(v)
1826
+ if (isNaN(n)) {
1827
+ n = 0
1828
+ }
1829
+ return n | 0
1830
+ }
1831
+ },
1832
+ concat: (k: any, v: any) =>
1833
+ null == k && islist(v) ? join(items(v, (n => isnode(n[1]) ? S_MT : (S_MT + n[1]))), S_MT) : v
1834
+ }
1835
+
1836
+
1837
+
1838
+ const transform_APPLY: Injector = (
1839
+ inj: Injection,
1840
+ _val: any,
1841
+ _ref: string,
1842
+ store: any
1843
+ ) => {
1844
+ const ijname = 'APPLY'
1845
+
1846
+ if (!checkPlacement(M_VAL, ijname, T_list, inj)) {
1847
+ return NONE
1848
+ }
1849
+
1850
+ // const [err, apply, child] = injectorArgs([T_function, T_any], inj)
1851
+ const [err, apply, child] = injectorArgs([T_function, T_any], slice(inj.parent, 1))
1852
+ if (NONE !== err) {
1853
+ inj.errs.push('$' + ijname + ': ' + err)
1854
+ return NONE
1855
+ }
1856
+
1857
+ const tkey = getelem(inj.path, -2)
1858
+ const target = getelem(inj.nodes, - 2, () => getelem(inj.nodes, -1))
1859
+
1860
+ const cinj = injectChild(child, store, inj)
1861
+ const resolved = cinj.val
1862
+
1863
+ const out = apply(resolved, store, cinj)
1864
+
1865
+ setprop(target, tkey, out)
1866
+
1867
+ return out
1868
+ }
1869
+
1870
+
1400
1871
  // Transform data using spec.
1401
1872
  // Only operates on static JSON-like data.
1402
1873
  // Arrays are treated as if they are objects with indices as keys.
@@ -1410,51 +1881,66 @@ function transform(
1410
1881
  spec = clone(origspec)
1411
1882
 
1412
1883
  const extra = injdef?.extra
1413
- // const modify = injdef?.modify
1884
+
1885
+ const collect = null != injdef?.errs
1886
+ const errs = injdef?.errs || []
1414
1887
 
1415
1888
  const extraTransforms: any = {}
1416
- const extraData = null == extra ? UNDEF : items(extra)
1889
+ const extraData = null == extra ? NONE : items(extra)
1417
1890
  .reduce((a: any, n: any[]) =>
1418
1891
  (n[0].startsWith(S_DS) ? extraTransforms[n[0]] = n[1] : (a[n[0]] = n[1]), a), {})
1419
1892
 
1420
1893
  const dataClone = merge([
1421
- isempty(extraData) ? UNDEF : clone(extraData),
1894
+ isempty(extraData) ? NONE : clone(extraData),
1422
1895
  clone(data),
1423
1896
  ])
1424
1897
 
1425
1898
  // Define a top level store that provides transform operations.
1426
- const store = {
1427
-
1428
- // The inject function recognises this special location for the root of the source data.
1429
- // NOTE: to escape data that contains "`$FOO`" keys at the top level,
1430
- // place that data inside a holding map: { myholder: mydata }.
1431
- $TOP: dataClone,
1432
-
1433
- $SPEC: () => origspec,
1899
+ const store = merge([
1900
+ {
1901
+ // The inject function recognises this special location for the root of the source data.
1902
+ // NOTE: to escape data that contains "`$FOO`" keys at the top level,
1903
+ // place that data inside a holding map: { myholder: mydata }.
1904
+ $TOP: dataClone,
1905
+
1906
+ $SPEC: () => origspec,
1907
+
1908
+ // Escape backtick (this also works inside backticks).
1909
+ $BT: () => S_BT,
1910
+
1911
+ // Escape dollar sign (this also works inside backticks).
1912
+ $DS: () => S_DS,
1913
+
1914
+ // Insert current date and time as an ISO string.
1915
+ $WHEN: () => new Date().toISOString(),
1916
+
1917
+ $DELETE: transform_DELETE,
1918
+ $COPY: transform_COPY,
1919
+ $KEY: transform_KEY,
1920
+ $ANNO: transform_ANNO,
1921
+ $MERGE: transform_MERGE,
1922
+ $EACH: transform_EACH,
1923
+ $PACK: transform_PACK,
1924
+ $REF: transform_REF,
1925
+ $FORMAT: transform_FORMAT,
1926
+ $APPLY: transform_APPLY,
1927
+ },
1434
1928
 
1435
- // Escape backtick (this also works inside backticks).
1436
- $BT: () => S_BT,
1437
-
1438
- // Escape dollar sign (this also works inside backticks).
1439
- $DS: () => S_DS,
1929
+ // Custom extra transforms, if any.
1930
+ extraTransforms,
1440
1931
 
1441
- // Insert current date and time as an ISO string.
1442
- $WHEN: () => new Date().toISOString(),
1932
+ {
1933
+ $ERRS: errs,
1934
+ }
1935
+ ], 1)
1443
1936
 
1444
- $DELETE: transform_DELETE,
1445
- $COPY: transform_COPY,
1446
- $KEY: transform_KEY,
1447
- $ANNO: transform_ANNO,
1448
- $MERGE: transform_MERGE,
1449
- $EACH: transform_EACH,
1450
- $PACK: transform_PACK,
1451
- $REF: transform_REF,
1937
+ const out = inject(spec, store, injdef)
1452
1938
 
1453
- // Custom extra transforms, if any.
1454
- ...extraTransforms,
1939
+ const generr = (0 < size(errs) && !collect)
1940
+ if (generr) {
1941
+ throw new Error(join(errs, ' | '))
1455
1942
  }
1456
1943
 
1457
- const out = inject(spec, store, injdef)
1458
1944
  return out
1459
1945
  }
1460
1946
 
@@ -1464,86 +1950,36 @@ const validate_STRING: Injector = (inj: Injection) => {
1464
1950
  let out = getprop(inj.dparent, inj.key)
1465
1951
 
1466
1952
  const t = typify(out)
1467
- if (S_string !== t) {
1953
+ if (0 === (T_string & t)) {
1468
1954
  let msg = _invalidTypeMsg(inj.path, S_string, t, out, 'V1010')
1469
1955
  inj.errs.push(msg)
1470
- return UNDEF
1956
+ return NONE
1471
1957
  }
1472
1958
 
1473
1959
  if (S_MT === out) {
1474
1960
  let msg = 'Empty string at ' + pathify(inj.path, 1)
1475
1961
  inj.errs.push(msg)
1476
- return UNDEF
1962
+ return NONE
1477
1963
  }
1478
1964
 
1479
1965
  return out
1480
1966
  }
1481
1967
 
1482
1968
 
1483
- // A required number value (int or float).
1484
- const validate_NUMBER: Injector = (inj: Injection) => {
1485
- let out = getprop(inj.dparent, inj.key)
1486
1969
 
1487
- const t = typify(out)
1488
- if (S_number !== t) {
1489
- inj.errs.push(_invalidTypeMsg(inj.path, S_number, t, out, 'V1020'))
1490
- return UNDEF
1491
- }
1492
1970
 
1493
- return out
1494
- }
1495
-
1496
-
1497
- // A required boolean value.
1498
- const validate_BOOLEAN: Injector = (inj: Injection) => {
1971
+ const validate_TYPE: Injector = (inj: Injection, _val: any, ref: string) => {
1972
+ const tname = slice(ref, 1).toLowerCase()
1973
+ const typev = 1 << (31 - TYPENAME.indexOf(tname))
1499
1974
  let out = getprop(inj.dparent, inj.key)
1500
1975
 
1501
1976
  const t = typify(out)
1502
- if (S_boolean !== t) {
1503
- inj.errs.push(_invalidTypeMsg(inj.path, S_boolean, t, out, 'V1030'))
1504
- return UNDEF
1505
- }
1506
-
1507
- return out
1508
- }
1509
1977
 
1978
+ // console.log('TYPE', tname, typev, tn(typev), 'O=', t, tn(t), out, 'C=', t & typev)
1510
1979
 
1511
- // A required object (map) value (contents not validated).
1512
- const validate_OBJECT: Injector = (inj: Injection) => {
1513
- let out = getprop(inj.dparent, inj.key)
1514
-
1515
- const t = typify(out)
1516
- if (t !== S_object) {
1517
- inj.errs.push(_invalidTypeMsg(inj.path, S_object, t, out, 'V1040'))
1518
- return UNDEF
1519
- }
1520
-
1521
- return out
1522
- }
1523
-
1524
-
1525
- // A required array (list) value (contents not validated).
1526
- const validate_ARRAY: Injector = (inj: Injection) => {
1527
- let out = getprop(inj.dparent, inj.key)
1528
-
1529
- const t = typify(out)
1530
- if (t !== S_array) {
1531
- inj.errs.push(_invalidTypeMsg(inj.path, S_array, t, out, 'V1050'))
1532
- return UNDEF
1533
- }
1534
-
1535
- return out
1536
- }
1537
-
1538
-
1539
- // A required function value.
1540
- const validate_FUNCTION: Injector = (inj: Injection) => {
1541
- let out = getprop(inj.dparent, inj.key)
1542
-
1543
- const t = typify(out)
1544
- if (S_function !== t) {
1545
- inj.errs.push(_invalidTypeMsg(inj.path, S_function, t, out, 'V1060'))
1546
- return UNDEF
1980
+ if (0 === (t & typev)) {
1981
+ inj.errs.push(_invalidTypeMsg(inj.path, tname, t, out, 'V1001'))
1982
+ return NONE
1547
1983
  }
1548
1984
 
1549
1985
  return out
@@ -1567,20 +2003,20 @@ const validate_CHILD: Injector = (inj: Injection) => {
1567
2003
  // Setup data structures for validation by cloning child template.
1568
2004
 
1569
2005
  // Map syntax.
1570
- if (S_MKEYPRE === mode) {
2006
+ if (M_KEYPRE === mode) {
1571
2007
  const childtm = getprop(parent, key)
1572
2008
 
1573
2009
  // Get corresponding current object.
1574
- const pkey = getprop(path, path.length - 2)
2010
+ const pkey = getelem(path, -2)
1575
2011
  let tval = getprop(inj.dparent, pkey)
1576
2012
 
1577
- if (UNDEF == tval) {
2013
+ if (NONE == tval) {
1578
2014
  tval = {}
1579
2015
  }
1580
2016
  else if (!ismap(tval)) {
1581
2017
  inj.errs.push(_invalidTypeMsg(
1582
2018
  slice(inj.path, -1), S_object, typify(tval), tval), 'V0220')
1583
- return UNDEF
2019
+ return NONE
1584
2020
  }
1585
2021
 
1586
2022
  const ckeys = keysof(tval)
@@ -1592,52 +2028,56 @@ const validate_CHILD: Injector = (inj: Injection) => {
1592
2028
  }
1593
2029
 
1594
2030
  // Remove $CHILD to cleanup ouput.
1595
- inj.setval(UNDEF)
1596
- return UNDEF
2031
+ inj.setval(NONE)
2032
+ return NONE
1597
2033
  }
1598
2034
 
1599
2035
  // List syntax.
1600
- if (S_MVAL === mode) {
2036
+ if (M_VAL === mode) {
1601
2037
 
1602
2038
  if (!islist(parent)) {
1603
2039
  // $CHILD was not inside a list.
1604
2040
  inj.errs.push('Invalid $CHILD as value')
1605
- return UNDEF
2041
+ return NONE
1606
2042
  }
1607
2043
 
1608
2044
  const childtm = getprop(parent, 1)
1609
2045
 
1610
- if (UNDEF === inj.dparent) {
2046
+ if (NONE === inj.dparent) {
1611
2047
  // Empty list as default.
1612
- parent.length = 0
1613
- return UNDEF
2048
+ // parent.length = 0
2049
+ slice(parent, 0, 0, true)
2050
+ return NONE
1614
2051
  }
1615
2052
 
1616
2053
  if (!islist(inj.dparent)) {
1617
2054
  const msg = _invalidTypeMsg(
1618
- slice(inj.path, -1), S_array, typify(inj.dparent), inj.dparent, 'V0230')
2055
+ slice(inj.path, -1), S_list, typify(inj.dparent), inj.dparent, 'V0230')
1619
2056
  inj.errs.push(msg)
1620
- inj.keyI = parent.length
2057
+ inj.keyI = size(parent)
1621
2058
  return inj.dparent
1622
2059
  }
1623
2060
 
1624
2061
  // Clone children abd reset inj key index.
1625
2062
  // The inject child loop will now iterate over the cloned children,
1626
2063
  // validating them againt the current list values.
1627
-
1628
- inj.dparent.map((_n, i) => parent[i] = clone(childtm))
1629
- parent.length = inj.dparent.length
2064
+ items(inj.dparent, (n) => setprop(parent, n[0], clone(childtm)))
2065
+ slice(parent, 0, inj.dparent.length, true)
1630
2066
  inj.keyI = 0
2067
+
1631
2068
  const out = getprop(inj.dparent, 0)
1632
2069
  return out
1633
2070
  }
1634
2071
 
1635
- return UNDEF
2072
+ return NONE
1636
2073
  }
1637
2074
 
1638
2075
 
2076
+ // TODO: implement SOME, ALL
2077
+ // FIX: ONE should mean exactly one, not at least one (=SOME)
2078
+ // TODO: implement a generate validate_ALT to do all of these
1639
2079
  // Match at least one of the specified shapes.
1640
- // Syntax: ['`$ONE`', alt0, alt1, ...]okI
2080
+ // Syntax: ['`$ONE`', alt0, alt1, ...]
1641
2081
  const validate_ONE: Injector = (
1642
2082
  inj: Injection,
1643
2083
  _val: any,
@@ -1647,7 +2087,7 @@ const validate_ONE: Injector = (
1647
2087
  const { mode, parent, keyI } = inj
1648
2088
 
1649
2089
  // Only operate in val mode, since parent is a list.
1650
- if (S_MVAL === mode) {
2090
+ if (M_VAL === mode) {
1651
2091
  if (!islist(parent) || 0 !== keyI) {
1652
2092
  inj.errs.push('The $ONE validator at field ' +
1653
2093
  pathify(inj.path, 1, 1) +
@@ -1655,7 +2095,7 @@ const validate_ONE: Injector = (
1655
2095
  return
1656
2096
  }
1657
2097
 
1658
- inj.keyI = inj.keys.length
2098
+ inj.keyI = size(inj.keys)
1659
2099
 
1660
2100
  // Clean up structure, replacing [$ONE, ...] with current
1661
2101
  inj.setval(inj.dparent, 2)
@@ -1664,7 +2104,7 @@ const validate_ONE: Injector = (
1664
2104
  inj.key = getelem(inj.path, -1)
1665
2105
 
1666
2106
  let tvals = slice(parent, 1)
1667
- if (0 === tvals.length) {
2107
+ if (0 === size(tvals)) {
1668
2108
  inj.errs.push('The $ONE validator at field ' +
1669
2109
  pathify(inj.path, 1, 1) +
1670
2110
  ' must have at least one argument.')
@@ -1677,7 +2117,7 @@ const validate_ONE: Injector = (
1677
2117
  // If match, then errs.length = 0
1678
2118
  let terrs: any[] = []
1679
2119
 
1680
- const vstore = { ...store }
2120
+ const vstore = merge([{}, store], 1)
1681
2121
  vstore.$TOP = inj.dparent
1682
2122
 
1683
2123
  const vcurrent = validate(inj.dparent, tval, {
@@ -1689,21 +2129,19 @@ const validate_ONE: Injector = (
1689
2129
  inj.setval(vcurrent, -2)
1690
2130
 
1691
2131
  // Accept current value if there was a match
1692
- if (0 === terrs.length) {
2132
+ if (0 === size(terrs)) {
1693
2133
  return
1694
2134
  }
1695
2135
  }
1696
2136
 
1697
2137
  // There was no match.
1698
-
1699
- const valdesc = tvals
1700
- .map((v: any) => stringify(v))
1701
- .join(', ')
1702
- .replace(R_TRANSFORM_NAME, (_m: any, p1: string) => p1.toLowerCase())
2138
+ const valdesc =
2139
+ replace(join(items(tvals, (n) => stringify(n[1])), ', '),
2140
+ R_TRANSFORM_NAME, (_m: any, p1: string) => p1.toLowerCase())
1703
2141
 
1704
2142
  inj.errs.push(_invalidTypeMsg(
1705
2143
  inj.path,
1706
- (1 < tvals.length ? 'one of ' : '') + valdesc,
2144
+ (1 < size(tvals) ? 'one of ' : '') + valdesc,
1707
2145
  typify(inj.dparent), inj.dparent, 'V0210'))
1708
2146
  }
1709
2147
  }
@@ -1713,7 +2151,7 @@ const validate_EXACT: Injector = (inj: Injection) => {
1713
2151
  const { mode, parent, key, keyI } = inj
1714
2152
 
1715
2153
  // Only operate in val mode, since parent is a list.
1716
- if (S_MVAL === mode) {
2154
+ if (M_VAL === mode) {
1717
2155
  if (!islist(parent) || 0 !== keyI) {
1718
2156
  inj.errs.push('The $EXACT validator at field ' +
1719
2157
  pathify(inj.path, 1, 1) +
@@ -1721,16 +2159,17 @@ const validate_EXACT: Injector = (inj: Injection) => {
1721
2159
  return
1722
2160
  }
1723
2161
 
1724
- inj.keyI = inj.keys.length
2162
+ inj.keyI = size(inj.keys)
1725
2163
 
1726
2164
  // Clean up structure, replacing [$EXACT, ...] with current data parent
1727
2165
  inj.setval(inj.dparent, 2)
1728
2166
 
1729
- inj.path = slice(inj.path, 0, inj.path.length - 1)
2167
+ // inj.path = slice(inj.path, 0, size(inj.path) - 1)
2168
+ inj.path = slice(inj.path, 0, -1)
1730
2169
  inj.key = getelem(inj.path, -1)
1731
2170
 
1732
2171
  let tvals = slice(parent, 1)
1733
- if (0 === tvals.length) {
2172
+ if (0 === size(tvals)) {
1734
2173
  inj.errs.push('The $EXACT validator at field ' +
1735
2174
  pathify(inj.path, 1, 1) +
1736
2175
  ' must have at least one argument.')
@@ -1753,15 +2192,15 @@ const validate_EXACT: Injector = (inj: Injection) => {
1753
2192
  }
1754
2193
  }
1755
2194
 
1756
- const valdesc = tvals
1757
- .map((v: any) => stringify(v))
1758
- .join(', ')
1759
- .replace(R_TRANSFORM_NAME, (_m: any, p1: string) => p1.toLowerCase())
2195
+ // There was no match.
2196
+ const valdesc =
2197
+ replace(join(items(tvals, (n) => stringify(n[1])), ', '),
2198
+ R_TRANSFORM_NAME, (_m: any, p1: string) => p1.toLowerCase())
1760
2199
 
1761
2200
  inj.errs.push(_invalidTypeMsg(
1762
2201
  inj.path,
1763
- (1 < inj.path.length ? '' : 'value ') +
1764
- 'exactly equal to ' + (1 === tvals.length ? '' : 'one of ') + valdesc,
2202
+ (1 < size(inj.path) ? '' : 'value ') +
2203
+ 'exactly equal to ' + (1 === size(tvals) ? '' : 'one of ') + valdesc,
1765
2204
  typify(inj.dparent), inj.dparent, 'V0110'))
1766
2205
  }
1767
2206
  else {
@@ -1779,7 +2218,7 @@ const _validation: Modify = (
1779
2218
  inj?: Injection,
1780
2219
  ) => {
1781
2220
 
1782
- if (UNDEF === inj) {
2221
+ if (NONE === inj) {
1783
2222
  return
1784
2223
  }
1785
2224
 
@@ -1793,28 +2232,28 @@ const _validation: Modify = (
1793
2232
  // Current val to verify.
1794
2233
  const cval = getprop(inj.dparent, key)
1795
2234
 
1796
- if (UNDEF === inj || (!exact && UNDEF === cval)) {
2235
+ if (NONE === inj || (!exact && NONE === cval)) {
1797
2236
  return
1798
2237
  }
1799
2238
 
1800
2239
  const ptype = typify(pval)
1801
2240
 
1802
2241
  // Delete any special commands remaining.
1803
- if (S_string === ptype && pval.includes(S_DS)) {
2242
+ if (0 < (T_string & ptype) && pval.includes(S_DS)) {
1804
2243
  return
1805
2244
  }
1806
2245
 
1807
2246
  const ctype = typify(cval)
1808
2247
 
1809
2248
  // Type mismatch.
1810
- if (ptype !== ctype && UNDEF !== pval) {
1811
- inj.errs.push(_invalidTypeMsg(inj.path, ptype, ctype, cval, 'V0010'))
2249
+ if (ptype !== ctype && NONE !== pval) {
2250
+ inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0010'))
1812
2251
  return
1813
2252
  }
1814
2253
 
1815
2254
  if (ismap(cval)) {
1816
2255
  if (!ismap(pval)) {
1817
- inj.errs.push(_invalidTypeMsg(inj.path, ptype, ctype, cval, 'V0020'))
2256
+ inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0020'))
1818
2257
  return
1819
2258
  }
1820
2259
 
@@ -1822,7 +2261,7 @@ const _validation: Modify = (
1822
2261
  const pkeys = keysof(pval)
1823
2262
 
1824
2263
  // Empty spec object {} means object can be open (any keys).
1825
- if (0 < pkeys.length && true !== getprop(pval, '`$OPEN`')) {
2264
+ if (0 < size(pkeys) && true !== getprop(pval, '`$OPEN`')) {
1826
2265
  const badkeys = []
1827
2266
  for (let ckey of ckeys) {
1828
2267
  if (!haskey(pval, ckey)) {
@@ -1831,9 +2270,9 @@ const _validation: Modify = (
1831
2270
  }
1832
2271
 
1833
2272
  // Closed object, so reject extra keys not in shape.
1834
- if (0 < badkeys.length) {
2273
+ if (0 < size(badkeys)) {
1835
2274
  const msg =
1836
- 'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + badkeys.join(', ')
2275
+ 'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + join(badkeys, ', ')
1837
2276
  inj.errs.push(msg)
1838
2277
  }
1839
2278
  }
@@ -1847,7 +2286,7 @@ const _validation: Modify = (
1847
2286
  }
1848
2287
  else if (islist(cval)) {
1849
2288
  if (!islist(pval)) {
1850
- inj.errs.push(_invalidTypeMsg(inj.path, ptype, ctype, cval, 'V0030'))
2289
+ inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0030'))
1851
2290
  }
1852
2291
  }
1853
2292
  else if (exact) {
@@ -1887,50 +2326,57 @@ function validate(
1887
2326
  const collect = null != injdef?.errs
1888
2327
  const errs = injdef?.errs || []
1889
2328
 
1890
- const store = {
1891
- // Remove the transform commands.
1892
- $DELETE: null,
1893
- $COPY: null,
1894
- $KEY: null,
1895
- $META: null,
1896
- $MERGE: null,
1897
- $EACH: null,
1898
- $PACK: null,
1899
-
1900
- $STRING: validate_STRING,
1901
- $NUMBER: validate_NUMBER,
1902
- $BOOLEAN: validate_BOOLEAN,
1903
- $OBJECT: validate_OBJECT,
1904
- $ARRAY: validate_ARRAY,
1905
- $FUNCTION: validate_FUNCTION,
1906
- $ANY: validate_ANY,
1907
- $CHILD: validate_CHILD,
1908
- $ONE: validate_ONE,
1909
- $EXACT: validate_EXACT,
1910
-
1911
- ...(extra || {}),
2329
+ const store = merge([
2330
+ {
2331
+ // Remove the transform commands.
2332
+ $DELETE: null,
2333
+ $COPY: null,
2334
+ $KEY: null,
2335
+ $META: null,
2336
+ $MERGE: null,
2337
+ $EACH: null,
2338
+ $PACK: null,
2339
+
2340
+ $STRING: validate_STRING,
2341
+ $NUMBER: validate_TYPE,
2342
+ $INTEGER: validate_TYPE,
2343
+ $DECIMAL: validate_TYPE,
2344
+ $BOOLEAN: validate_TYPE,
2345
+ $NULL: validate_TYPE,
2346
+ $NIL: validate_TYPE,
2347
+ $MAP: validate_TYPE,
2348
+ $LIST: validate_TYPE,
2349
+ $FUNCTION: validate_TYPE,
2350
+ $INSTANCE: validate_TYPE,
2351
+ $ANY: validate_ANY,
2352
+ $CHILD: validate_CHILD,
2353
+ $ONE: validate_ONE,
2354
+ $EXACT: validate_EXACT,
2355
+ },
2356
+
2357
+ getdef(extra, {}),
1912
2358
 
1913
2359
  // A special top level value to collect errors.
1914
- // NOTE: collecterrs paramter always wins.
1915
- $ERRS: errs,
1916
- }
1917
-
1918
- let meta = { [S_BEXACT]: false }
2360
+ // NOTE: collecterrs parameter always wins.
2361
+ {
2362
+ $ERRS: errs,
2363
+ }
2364
+ ], 1)
1919
2365
 
1920
- if (injdef?.meta) {
1921
- meta = merge([meta, injdef.meta])
1922
- }
2366
+ let meta = getprop(injdef, 'meta', {})
2367
+ setprop(meta, S_BEXACT, getprop(meta, S_BEXACT, false))
1923
2368
 
1924
2369
  const out = transform(data, spec, {
1925
2370
  meta,
1926
2371
  extra: store,
1927
2372
  modify: _validation,
1928
- handler: _validatehandler
2373
+ handler: _validatehandler,
2374
+ errs,
1929
2375
  })
1930
2376
 
1931
- const generr = (0 < errs.length && !collect)
2377
+ const generr = (0 < size(errs) && !collect)
1932
2378
  if (generr) {
1933
- throw new Error('Invalid data: ' + errs.join(' | '))
2379
+ throw new Error(join(errs, ' | '))
1934
2380
  }
1935
2381
 
1936
2382
  return out
@@ -1938,18 +2384,16 @@ function validate(
1938
2384
 
1939
2385
 
1940
2386
  const select_AND: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
1941
- if (S_MKEYPRE === inj.mode) {
2387
+ if (M_KEYPRE === inj.mode) {
1942
2388
  const terms = getprop(inj.parent, inj.key)
1943
2389
 
1944
2390
  const ppath = slice(inj.path, -1)
1945
2391
  const point = getpath(store, ppath)
1946
2392
 
1947
- const vstore = { ...store }
2393
+ const vstore = merge([{}, store], 1)
1948
2394
  vstore.$TOP = point
1949
2395
 
1950
2396
  for (let term of terms) {
1951
- // setprop(term, '`$OPEN`', getprop(term, '`$OPEN`', true))
1952
-
1953
2397
  let terrs: any[] = []
1954
2398
 
1955
2399
  validate(point, term, {
@@ -1958,7 +2402,7 @@ const select_AND: Injector = (inj: Injection, _val: any, _ref: string, store: an
1958
2402
  meta: inj.meta,
1959
2403
  })
1960
2404
 
1961
- if (0 != terrs.length) {
2405
+ if (0 != size(terrs)) {
1962
2406
  inj.errs.push(
1963
2407
  'AND:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms))
1964
2408
  }
@@ -1972,13 +2416,13 @@ const select_AND: Injector = (inj: Injection, _val: any, _ref: string, store: an
1972
2416
 
1973
2417
 
1974
2418
  const select_OR: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
1975
- if (S_MKEYPRE === inj.mode) {
2419
+ if (M_KEYPRE === inj.mode) {
1976
2420
  const terms = getprop(inj.parent, inj.key)
1977
2421
 
1978
2422
  const ppath = slice(inj.path, -1)
1979
2423
  const point = getpath(store, ppath)
1980
2424
 
1981
- const vstore = { ...store }
2425
+ const vstore = merge([{}, store], 1)
1982
2426
  vstore.$TOP = point
1983
2427
 
1984
2428
  for (let term of terms) {
@@ -1990,7 +2434,7 @@ const select_OR: Injector = (inj: Injection, _val: any, _ref: string, store: any
1990
2434
  meta: inj.meta,
1991
2435
  })
1992
2436
 
1993
- if (0 === terrs.length) {
2437
+ if (0 === size(terrs)) {
1994
2438
  const gkey = getelem(inj.path, -2)
1995
2439
  const gp = getelem(inj.nodes, -2)
1996
2440
  setprop(gp, gkey, point)
@@ -2006,13 +2450,13 @@ const select_OR: Injector = (inj: Injection, _val: any, _ref: string, store: any
2006
2450
 
2007
2451
 
2008
2452
  const select_NOT: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
2009
- if (S_MKEYPRE === inj.mode) {
2453
+ if (M_KEYPRE === inj.mode) {
2010
2454
  const term = getprop(inj.parent, inj.key)
2011
2455
 
2012
2456
  const ppath = slice(inj.path, -1)
2013
2457
  const point = getpath(store, ppath)
2014
2458
 
2015
- const vstore = { ...store }
2459
+ const vstore = merge([{}, store], 1)
2016
2460
  vstore.$TOP = point
2017
2461
 
2018
2462
  let terrs: any[] = []
@@ -2023,7 +2467,7 @@ const select_NOT: Injector = (inj: Injection, _val: any, _ref: string, store: an
2023
2467
  meta: inj.meta,
2024
2468
  })
2025
2469
 
2026
- if (0 == terrs.length) {
2470
+ if (0 == size(terrs)) {
2027
2471
  inj.errs.push(
2028
2472
  'NOT:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(term))
2029
2473
  }
@@ -2036,7 +2480,7 @@ const select_NOT: Injector = (inj: Injection, _val: any, _ref: string, store: an
2036
2480
 
2037
2481
 
2038
2482
  const select_CMP: Injector = (inj: Injection, _val: any, ref: string, store: any) => {
2039
- if (S_MKEYPRE === inj.mode) {
2483
+ if (M_KEYPRE === inj.mode) {
2040
2484
  const term = getprop(inj.parent, inj.key)
2041
2485
  // const src = getprop(store, inj.base, store)
2042
2486
  const gkey = getelem(inj.path, -2)
@@ -2075,7 +2519,7 @@ const select_CMP: Injector = (inj: Injection, _val: any, ref: string, store: any
2075
2519
  }
2076
2520
  }
2077
2521
 
2078
- return UNDEF
2522
+ return NONE
2079
2523
  }
2080
2524
 
2081
2525
 
@@ -2089,10 +2533,13 @@ function select(children: any, query: any): any[] {
2089
2533
  }
2090
2534
 
2091
2535
  if (ismap(children)) {
2092
- children = items(children).map(n => (n[1][S_DKEY] = n[0], n[1]))
2536
+ children = items(children, n => {
2537
+ setprop(n[1], S_DKEY, n[0])
2538
+ return n[1]
2539
+ })
2093
2540
  }
2094
2541
  else {
2095
- children = (children as any[]).map((n, i) => ((ismap(n) ? n[S_DKEY] = i : null), n))
2542
+ children = items(children, (n) => (setprop(n[1], S_DKEY, +n[0]), n[1]))
2096
2543
  }
2097
2544
 
2098
2545
  const results: any[] = []
@@ -2134,10 +2581,9 @@ function select(children: any, query: any): any[] {
2134
2581
  }
2135
2582
 
2136
2583
 
2137
-
2138
2584
  // Injection state used for recursive injection into JSON - like data structures.
2139
2585
  class Injection {
2140
- mode: InjectMode // Injection mode: key:pre, val, key:post.
2586
+ mode: InjectMode // Injection mode: M_KEYPRE, M_VAL, M_KEYPOST.
2141
2587
  full: boolean // Transform escape was full key name.
2142
2588
  keyI: number // Index of parent key in list of parent keys.
2143
2589
  keys: string[] // List of parent keys.
@@ -2148,7 +2594,7 @@ class Injection {
2148
2594
  nodes: any[] // Stack of ancestor nodes.
2149
2595
  handler: Injector // Custom handler for injections.
2150
2596
  errs: any[] // Error collector.
2151
- meta: Record<string, any> // Custom meta data.
2597
+ meta: Record<string, any> // Custom meta data. NOTE: do not merge, values must remain as-is.
2152
2598
  dparent: any // Current data parent node (contains current data value).
2153
2599
  dpath: string[] // Current data value path
2154
2600
  base?: string // Base key for data in store, if any.
@@ -2161,10 +2607,10 @@ class Injection {
2161
2607
  this.parent = parent
2162
2608
  this.errs = []
2163
2609
 
2164
- this.dparent = UNDEF
2610
+ this.dparent = NONE
2165
2611
  this.dpath = [S_DTOP]
2166
2612
 
2167
- this.mode = S_MVAL as InjectMode
2613
+ this.mode = M_VAL
2168
2614
  this.full = false
2169
2615
  this.keyI = 0
2170
2616
  this.keys = [S_DTOP]
@@ -2180,7 +2626,7 @@ class Injection {
2180
2626
  toString(prefix?: string) {
2181
2627
  return 'INJ' + (null == prefix ? '' : S_FS + prefix) + S_CN +
2182
2628
  pad(pathify(this.path, 1)) +
2183
- this.mode + (this.full ? '/full' : '') + S_CN +
2629
+ MODENAME[this.mode] + (this.full ? '/full' : '') + S_CN +
2184
2630
  'key=' + this.keyI + S_FS + this.key + S_FS + S_OS + this.keys + S_CS +
2185
2631
  ' p=' + stringify(this.parent, -1, 1) +
2186
2632
  ' m=' + stringify(this.meta, -1, 1) +
@@ -2194,12 +2640,12 @@ class Injection {
2194
2640
  const parentkey = getelem(this.path, -2)
2195
2641
 
2196
2642
  // Resolve current node in store for local paths.
2197
- if (UNDEF === this.dparent) {
2643
+ if (NONE === this.dparent) {
2198
2644
 
2199
2645
  // Even if there's no data, dpath should continue to match path, so that
2200
2646
  // relative paths work properly.
2201
- if (1 < this.dpath.length) {
2202
- this.dpath = [...this.dpath, parentkey]
2647
+ if (1 < size(this.dpath)) {
2648
+ this.dpath = flatten([this.dpath, parentkey])
2203
2649
  }
2204
2650
  }
2205
2651
  else {
@@ -2212,11 +2658,12 @@ class Injection {
2212
2658
  this.dpath = slice(this.dpath, -1)
2213
2659
  }
2214
2660
  else {
2215
- this.dpath = [...this.dpath, parentkey]
2661
+ this.dpath = flatten([this.dpath, parentkey])
2216
2662
  }
2217
2663
  }
2218
2664
  }
2219
2665
 
2666
+ // TODO: is this needed?
2220
2667
  return this.dparent
2221
2668
  }
2222
2669
 
@@ -2230,8 +2677,8 @@ class Injection {
2230
2677
  cinj.keys = keys
2231
2678
  cinj.key = key
2232
2679
 
2233
- cinj.path = [...(this.path || []), key]
2234
- cinj.nodes = [...(this.nodes || []), val]
2680
+ cinj.path = flatten([getdef(this.path, []), key])
2681
+ cinj.nodes = flatten([getdef(this.nodes, []), [val]])
2235
2682
 
2236
2683
  cinj.mode = this.mode
2237
2684
  cinj.handler = this.handler
@@ -2241,7 +2688,7 @@ class Injection {
2241
2688
  cinj.errs = this.errs
2242
2689
  cinj.prior = this
2243
2690
 
2244
- cinj.dpath = [...this.dpath]
2691
+ cinj.dpath = flatten([this.dpath])
2245
2692
  cinj.dparent = this.dparent
2246
2693
 
2247
2694
  return cinj
@@ -2249,18 +2696,22 @@ class Injection {
2249
2696
 
2250
2697
 
2251
2698
  setval(val: any, ancestor?: number) {
2699
+ let parent = NONE
2252
2700
  if (null == ancestor || ancestor < 2) {
2253
- return UNDEF === val ?
2254
- delprop(this.parent, this.key) :
2701
+ parent = NONE === val ?
2702
+ this.parent = delprop(this.parent, this.key) :
2255
2703
  setprop(this.parent, this.key, val)
2256
2704
  }
2257
2705
  else {
2258
2706
  const aval = getelem(this.nodes, 0 - ancestor)
2259
2707
  const akey = getelem(this.path, 0 - ancestor)
2260
- return UNDEF === val ?
2708
+ parent = NONE === val ?
2261
2709
  delprop(aval, akey) :
2262
2710
  setprop(aval, akey, val)
2263
2711
  }
2712
+
2713
+ // console.log('SETVAL', val, this.key, this.parent)
2714
+ return parent
2264
2715
  }
2265
2716
  }
2266
2717
 
@@ -2269,21 +2720,21 @@ class Injection {
2269
2720
  // ==================
2270
2721
 
2271
2722
 
2272
- // Update all references to target in inj.nodes.
2273
- function _updateAncestors(_inj: Injection, target: any, tkey: any, tval: any) {
2274
- // SetProp is sufficient in TypeScript as target reference remains consistent even for lists.
2275
- setprop(target, tkey, tval)
2276
- }
2723
+ // // Update all references to target in inj.nodes.
2724
+ // function _updateAncestors(_inj: Injection, target: any, tkey: any, tval: any) {
2725
+ // // SetProp is sufficient in TypeScript as target reference remains consistent even for lists.
2726
+ // setprop(target, tkey, tval)
2727
+ // }
2277
2728
 
2278
2729
 
2279
2730
  // Build a type validation error message.
2280
- function _invalidTypeMsg(path: any, needtype: string, vt: string, v: any, _whence?: string) {
2731
+ function _invalidTypeMsg(path: any, needtype: string, vt: number, v: any, _whence?: string) {
2281
2732
  let vs = null == v ? 'no value' : stringify(v)
2282
2733
 
2283
2734
  return 'Expected ' +
2284
- (1 < path.length ? ('field ' + pathify(path, 1) + ' to be ') : '') +
2735
+ (1 < size(path) ? ('field ' + pathify(path, 1) + ' to be ') : '') +
2285
2736
  needtype + ', but found ' +
2286
- (null != v ? vt + S_VIZ : '') + vs +
2737
+ (null != v ? typename(vt) + S_VIZ : '') + vs +
2287
2738
 
2288
2739
  // Uncomment to help debug validation errors.
2289
2740
  // ' [' + _whence + ']' +
@@ -2301,15 +2752,17 @@ const _injecthandler: Injector = (
2301
2752
  store: any
2302
2753
  ): any => {
2303
2754
  let out = val
2304
- const iscmd = isfunc(val) && (UNDEF === ref || ref.startsWith(S_DS))
2755
+ const iscmd = isfunc(val) && (NONE === ref || ref.startsWith(S_DS))
2305
2756
 
2306
2757
  // Only call val function if it is a special command ($NAME format).
2758
+ // TODO: OR if meta.'$CALL'
2759
+
2307
2760
  if (iscmd) {
2308
2761
  out = (val as Injector)(inj, val, ref, store)
2309
2762
  }
2310
2763
 
2311
2764
  // Update parent with value. Ensures references remain in node tree.
2312
- else if (S_MVAL === inj.mode && inj.full) {
2765
+ else if (M_VAL === inj.mode && inj.full) {
2313
2766
  inj.setval(val)
2314
2767
  }
2315
2768
 
@@ -2379,9 +2832,9 @@ function _injectstr(
2379
2832
  let pathref = m[1]
2380
2833
 
2381
2834
  // Special escapes inside injection.
2382
- pathref = 3 < pathref.length ?
2383
- pathref.replace(R_BT_ESCAPE, S_BT).replace(R_DS_ESCAPE, S_DS) :
2384
- pathref
2835
+ if (3 < size(pathref)) {
2836
+ pathref = pathref.replace(R_BT_ESCAPE, S_BT).replace(R_DS_ESCAPE, S_DS)
2837
+ }
2385
2838
 
2386
2839
  // Get the extracted path reference.
2387
2840
  out = getpath(store, pathref, inj)
@@ -2391,14 +2844,19 @@ function _injectstr(
2391
2844
  // Check for injections within the string.
2392
2845
  const partial = (_m: string, ref: string) => {
2393
2846
  // Special escapes inside injection.
2394
- ref = 3 < ref.length ? ref.replace(R_BT_ESCAPE, S_BT).replace(R_DS_ESCAPE, S_DS) : ref
2847
+
2848
+ if (3 < size(ref)) {
2849
+ ref = ref.replace(R_BT_ESCAPE, S_BT).replace(R_DS_ESCAPE, S_DS)
2850
+ }
2851
+
2395
2852
  if (inj) {
2396
2853
  inj.full = false
2397
2854
  }
2855
+
2398
2856
  const found = getpath(store, ref, inj)
2399
2857
 
2400
2858
  // Ensure inject value is a string.
2401
- return UNDEF === found ? S_MT : S_string === typeof found ? found : JSON.stringify(found)
2859
+ return NONE === found ? S_MT : S_string === typeof found ? found : JSON.stringify(found)
2402
2860
  }
2403
2861
 
2404
2862
  out = val.replace(R_INJECTION_PARTIAL, partial)
@@ -2415,11 +2873,101 @@ function _injectstr(
2415
2873
  }
2416
2874
 
2417
2875
 
2876
+ // Handler Utilities
2877
+ // =================
2878
+
2879
+
2880
+ const MODENAME: any = {
2881
+ [M_VAL]: 'val',
2882
+ [M_KEYPRE]: 'key:pre',
2883
+ [M_KEYPOST]: 'key:post',
2884
+ }
2885
+
2886
+ const PLACEMENT: any = {
2887
+ [M_VAL]: 'value',
2888
+ [M_KEYPRE]: S_key,
2889
+ [M_KEYPOST]: S_key,
2890
+ }
2891
+
2892
+ function checkPlacement(
2893
+ modes: InjectMode,
2894
+ ijname: string,
2895
+ parentTypes: number,
2896
+ inj: Injection
2897
+ ): boolean {
2898
+ if (0 === (modes & inj.mode)) {
2899
+ inj.errs.push('$' + ijname + ': invalid placement as ' + PLACEMENT[inj.mode] +
2900
+ ', expected: ' + join(items(
2901
+ [M_KEYPRE, M_KEYPOST, M_VAL].filter(m => modes & m),
2902
+ (n: any) => PLACEMENT[n[1]]), ',') + '.')
2903
+ return false
2904
+ }
2905
+ if (!isempty(parentTypes)) {
2906
+ const ptype = typify(inj.parent)
2907
+ if (0 === (parentTypes & ptype)) {
2908
+ inj.errs.push('$' + ijname + ': invalid placement in parent ' + typename(ptype) +
2909
+ ', expected: ' + typename(parentTypes) + '.')
2910
+ return false
2911
+
2912
+ }
2913
+ }
2914
+ return true
2915
+ }
2916
+
2917
+
2918
+ // function injectorArgs(argTypes: number[], inj: Injection): any {
2919
+ function injectorArgs(argTypes: number[], args: any[]): any {
2920
+ const numargs = size(argTypes)
2921
+ const found = new Array(1 + numargs)
2922
+ found[0] = NONE
2923
+ for (let argI = 0; argI < numargs; argI++) {
2924
+ // const arg = inj.parent[1 + argI]
2925
+ const arg = args[argI]
2926
+ const argType = typify(arg)
2927
+ if (0 === (argTypes[argI] & argType)) {
2928
+ found[0] = 'invalid argument: ' + stringify(arg, 22) +
2929
+ ' (' + typename(argType) + ' at position ' + (1 + argI) +
2930
+ ') is not of type: ' + typename(argTypes[argI]) + '.'
2931
+ break
2932
+ }
2933
+ found[1 + argI] = arg
2934
+ }
2935
+ return found
2936
+ }
2937
+
2938
+
2939
+ function injectChild(child: any, store: any, inj: Injection): Injection {
2940
+ let cinj = inj
2941
+
2942
+ // Replace ['`$FORMAT`',...] with child
2943
+ if (null != inj.prior) {
2944
+ if (null != inj.prior.prior) {
2945
+ cinj = inj.prior.prior.child(inj.prior.keyI, inj.prior.keys)
2946
+ cinj.val = child
2947
+ setprop(cinj.parent, inj.prior.key, child)
2948
+ }
2949
+ else {
2950
+ cinj = inj.prior.child(inj.keyI, inj.keys)
2951
+ cinj.val = child
2952
+ setprop(cinj.parent, inj.key, child)
2953
+ }
2954
+ }
2955
+
2956
+ // console.log('FORMAT-INJECT-CHILD', child)
2957
+ inject(child, store, cinj)
2958
+
2959
+ return cinj
2960
+ }
2961
+
2962
+
2418
2963
  class StructUtility {
2419
2964
  clone = clone
2420
2965
  delprop = delprop
2421
2966
  escre = escre
2422
2967
  escurl = escurl
2968
+ filter = filter
2969
+ flatten = flatten
2970
+ getdef = getdef
2423
2971
  getelem = getelem
2424
2972
  getpath = getpath
2425
2973
  getprop = getprop
@@ -2432,13 +2980,14 @@ class StructUtility {
2432
2980
  ismap = ismap
2433
2981
  isnode = isnode
2434
2982
  items = items
2435
- joinurl = joinurl
2983
+ join = join
2436
2984
  jsonify = jsonify
2437
2985
  keysof = keysof
2438
2986
  merge = merge
2439
2987
  pad = pad
2440
2988
  pathify = pathify
2441
2989
  select = select
2990
+ setpath = setpath
2442
2991
  setprop = setprop
2443
2992
  size = size
2444
2993
  slice = slice
@@ -2446,11 +2995,36 @@ class StructUtility {
2446
2995
  stringify = stringify
2447
2996
  transform = transform
2448
2997
  typify = typify
2998
+ typename = typename
2449
2999
  validate = validate
2450
3000
  walk = walk
2451
3001
 
2452
- jo = jo
2453
- ja = ja
3002
+ SKIP = SKIP
3003
+ DELETE = DELETE
3004
+
3005
+ jm = jm
3006
+ jt = jt
3007
+ tn = typename
3008
+
3009
+ T_any = T_any
3010
+ T_noval = T_noval
3011
+ T_boolean = T_boolean
3012
+ T_decimal = T_decimal
3013
+ T_integer = T_integer
3014
+ T_number = T_number
3015
+ T_string = T_string
3016
+ T_function = T_function
3017
+ T_symbol = T_symbol
3018
+ T_null = T_null
3019
+ T_list = T_list
3020
+ T_map = T_map
3021
+ T_instance = T_instance
3022
+ T_scalar = T_scalar
3023
+ T_node = T_node
3024
+
3025
+ checkPlacement = checkPlacement
3026
+ injectorArgs = injectorArgs
3027
+ injectChild = injectChild
2454
3028
  }
2455
3029
 
2456
3030
  export {
@@ -2459,6 +3033,9 @@ export {
2459
3033
  delprop,
2460
3034
  escre,
2461
3035
  escurl,
3036
+ filter,
3037
+ flatten,
3038
+ getdef,
2462
3039
  getelem,
2463
3040
  getpath,
2464
3041
  getprop,
@@ -2471,13 +3048,14 @@ export {
2471
3048
  ismap,
2472
3049
  isnode,
2473
3050
  items,
2474
- joinurl,
3051
+ join,
2475
3052
  jsonify,
2476
3053
  keysof,
2477
3054
  merge,
2478
3055
  pad,
2479
3056
  pathify,
2480
3057
  select,
3058
+ setpath,
2481
3059
  setprop,
2482
3060
  size,
2483
3061
  slice,
@@ -2485,11 +3063,41 @@ export {
2485
3063
  stringify,
2486
3064
  transform,
2487
3065
  typify,
3066
+ typename,
2488
3067
  validate,
2489
3068
  walk,
2490
3069
 
2491
- jo,
2492
- ja,
3070
+ SKIP,
3071
+ DELETE,
3072
+
3073
+ jm,
3074
+ jt,
3075
+
3076
+ T_any,
3077
+ T_noval,
3078
+ T_boolean,
3079
+ T_decimal,
3080
+ T_integer,
3081
+ T_number,
3082
+ T_string,
3083
+ T_function,
3084
+ T_symbol,
3085
+ T_null,
3086
+ T_list,
3087
+ T_map,
3088
+ T_instance,
3089
+ T_scalar,
3090
+ T_node,
3091
+
3092
+ M_KEYPRE,
3093
+ M_KEYPOST,
3094
+ M_VAL,
3095
+
3096
+ MODENAME,
3097
+
3098
+ checkPlacement,
3099
+ injectorArgs,
3100
+ injectChild,
2493
3101
  }
2494
3102
 
2495
3103
  export type {