@voxgig/sdkgen 0.25.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 +30 -11
  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 +1113 -525
  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,78 +982,134 @@ 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
- // No key at top.
760
- if (null == key) {
761
- return val
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
+ }
1002
+
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 {
1010
+
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) ? [] : {}
762
1018
  }
763
1019
 
764
- // Get the current value at the current path in obj.
765
- // NOTE: this is not exactly efficient, and should be optimised.
766
- let lenpath = path.length
767
- cI = lenpath - 1
768
- if (UNDEF === cur[cI]) {
769
- cur[cI] = getpath(out, slice(path, 0, lenpath - 1))
1020
+ // Matching override and destination so continue with their values.
1021
+ else if (typify(val) === typify(tval)) {
1022
+ cur[pI] = tval
770
1023
  }
771
1024
 
772
- // console.log('AAA', path, cur[cI])
1025
+ // Override wins.
1026
+ else {
1027
+ cur[pI] = val
773
1028
 
774
- // Create node if needed.
775
- if (!isnode(cur[cI])) {
776
- cur[cI] = islist(parent) ? [] : {}
1029
+ // No need to descend when override wins (destination is discarded).
1030
+ val = NONE
777
1031
  }
1032
+ }
778
1033
 
779
- // console.log('BBB', path, cur[cI])
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))
780
1037
 
781
- // console.log('VAL', path, val, isnode(val), isempty(val))
1038
+ return val
1039
+ }
782
1040
 
783
- // Node child is just ahead of us on the stack, since
784
- // `walk` traverses leaves before nodes.
785
- if (isnode(val)) {
786
- const missing = UNDEF === getprop(cur[cI], key)
787
- if (!isempty(val) || missing) { // || ) {
788
- // console.log('CCC')
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
1056
+ }
789
1057
 
790
- // if (missing) {
791
- // console.log('MISSING', key, val, cur[cI], cur[cI + 1])
792
- // }
1058
+ // Walk overriding node, creating paths in output as needed.
1059
+ out = walk(obj, before, after, maxdepth)
1060
+ // console.log('WALK-DONE', out, obj)
1061
+ }
1062
+ }
793
1063
 
794
- const mval = missing ? val : cur[cI + 1]
795
- setprop(cur[cI], key, mval)
796
- cur[cI + 1] = UNDEF
797
- }
798
- }
1064
+ if (0 === md) {
1065
+ out = getelem(list, -1)
1066
+ out = islist(out) ? [] : ismap(out) ? {} : out
1067
+ }
799
1068
 
800
- // Scalar child.
801
- else {
802
- // console.log('DDD', cur[cI], key, val)
803
- setprop(cur[cI], key, val)
804
- }
1069
+ return out
1070
+ }
805
1071
 
806
- return val
807
- }
808
1072
 
809
- // Walk overriding node, creating paths in output as needed.
810
- walk(obj, merger)
811
- }
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)
812
1101
  }
1102
+ parent = nextParent
813
1103
  }
814
1104
 
815
- return out
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
816
1113
  }
817
1114
 
818
1115
 
@@ -821,10 +1118,10 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial<
821
1118
  // Operate on a string array.
822
1119
  const parts = islist(path) ? path :
823
1120
  'string' === typeof path ? path.split(S_DT) :
824
- 'number' === typeof path ? [strkey(path)] : UNDEF
1121
+ 'number' === typeof path ? [strkey(path)] : NONE
825
1122
 
826
- if (UNDEF === parts) {
827
- return UNDEF
1123
+ if (NONE === parts) {
1124
+ return NONE
828
1125
  }
829
1126
 
830
1127
  // let root = store
@@ -856,7 +1153,7 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial<
856
1153
 
857
1154
  const dpath = getprop(injdef, 'dpath')
858
1155
 
859
- for (let pI = 0; UNDEF !== val && pI < parts.length; pI++) {
1156
+ for (let pI = 0; NONE !== val && pI < numparts; pI++) {
860
1157
  let part = parts[pI]
861
1158
 
862
1159
  if (injdef && S_DKEY === part) {
@@ -864,15 +1161,15 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial<
864
1161
  }
865
1162
  else if (injdef && part.startsWith('$GET:')) {
866
1163
  // $GET:path$ -> get store value, use as path part (string)
867
- part = stringify(getpath(src, part.substring(5, part.length - 1)))
1164
+ part = stringify(getpath(src, slice(part, 5, -1)))
868
1165
  }
869
1166
  else if (injdef && part.startsWith('$REF:')) {
870
1167
  // $REF:refpath$ -> get spec value, use as path part (string)
871
- 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)))
872
1169
  }
873
1170
  else if (injdef && part.startsWith('$META:')) {
874
1171
  // $META:metapath$ -> get meta value, use as path part (string)
875
- part = stringify(getpath(getprop(injdef, 'meta'), part.substring(6, part.length - 1)))
1172
+ part = stringify(getpath(getprop(injdef, 'meta'), slice(part, 6, -1)))
876
1173
  }
877
1174
 
878
1175
  // $$ escapes $
@@ -895,14 +1192,16 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial<
895
1192
  val = dparent
896
1193
  }
897
1194
  else {
898
- 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)])
899
1197
 
900
1198
  if (ascends <= size(dpath)) {
901
1199
  val = getpath(store, fullpath)
902
1200
  }
903
1201
  else {
904
- val = UNDEF
1202
+ val = NONE
905
1203
  }
1204
+
906
1205
  break
907
1206
  }
908
1207
  }
@@ -924,13 +1223,14 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial<
924
1223
  val = handler(injdef, val, ref, store)
925
1224
  }
926
1225
 
1226
+ // console.log('GETPATH', path, val)
1227
+
927
1228
  return val
928
1229
  }
929
1230
 
930
1231
 
931
-
932
1232
  // Inject values from a data store into a node recursively, resolving
933
- // 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
934
1234
  // argument allows custom modification of the result. The inj
935
1235
  // (Injection) argument is used to maintain recursive state.
936
1236
  function inject(
@@ -943,14 +1243,14 @@ function inject(
943
1243
 
944
1244
  // Create state if at root of injection. The input value is placed
945
1245
  // inside a virtual parent holder to simplify edge cases.
946
- if (UNDEF === injdef || null == injdef.mode) {
1246
+ if (NONE === injdef || null == injdef.mode) {
947
1247
  // Set up state assuming we are starting in the virtual parent.
948
1248
  inj = new Injection(val, { [S_DTOP]: val })
949
1249
  inj.dparent = store
950
1250
  inj.errs = getprop(store, S_DERRS, [])
951
1251
  inj.meta.__d = 0
952
1252
 
953
- if (UNDEF !== injdef) {
1253
+ if (NONE !== injdef) {
954
1254
  inj.modify = null == injdef.modify ? inj.modify : injdef.modify
955
1255
  inj.extra = null == injdef.extra ? inj.extra : injdef.extra
956
1256
  inj.meta = null == injdef.meta ? inj.meta : injdef.meta
@@ -960,6 +1260,9 @@ function inject(
960
1260
 
961
1261
  inj.descend()
962
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
+
963
1266
  // Descend into node.
964
1267
  if (isnode(val)) {
965
1268
 
@@ -967,21 +1270,29 @@ function inject(
967
1270
  // Injection transforms ($FOO) are processed *after* other keys.
968
1271
  // NOTE: the optional digits suffix of the transform can thus be
969
1272
  // used to order the transforms.
970
- let nodekeys = ismap(val) ? [
971
- ...Object.keys(val).filter(k => !k.includes(S_DS)).sort(),
972
- ...Object.keys(val).filter(k => k.includes(S_DS)).sort(),
973
- ] : (val as any).map((_n: any, i: number) => i)
974
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
+ }
975
1286
 
976
1287
  // Each child key-value pair is processed in three injection phases:
977
- // 1. inj.mode='key:pre' - Key string is injected, returning a possibly altered key.
978
- // 2. inj.mode='val' - The child value is injected.
979
- // 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.
980
1291
  for (let nkI = 0; nkI < nodekeys.length; nkI++) {
981
1292
 
982
1293
  const childinj = inj.child(nkI, nodekeys)
983
1294
  const nodekey = childinj.key
984
- childinj.mode = S_MKEYPRE
1295
+ childinj.mode = M_KEYPRE
985
1296
 
986
1297
  // Peform the key:pre mode injection on the child key.
987
1298
  const prekey = _injectstr(nodekey, store, childinj)
@@ -991,9 +1302,9 @@ function inject(
991
1302
  nodekeys = childinj.keys
992
1303
 
993
1304
  // Prevent further processing by returning an undefined prekey
994
- if (UNDEF !== prekey) {
1305
+ if (NONE !== prekey) {
995
1306
  childinj.val = getprop(val, prekey)
996
- childinj.mode = S_MVAL as InjectMode
1307
+ childinj.mode = M_VAL
997
1308
 
998
1309
  // Perform the val mode injection on the child value.
999
1310
  // NOTE: return value is not used.
@@ -1004,7 +1315,7 @@ function inject(
1004
1315
  nodekeys = childinj.keys
1005
1316
 
1006
1317
  // Peform the key:post mode injection on the child key.
1007
- childinj.mode = S_MKEYPOST as InjectMode
1318
+ childinj.mode = M_KEYPOST
1008
1319
  _injectstr(nodekey, store, childinj)
1009
1320
 
1010
1321
  // The injection may modify child processing.
@@ -1016,7 +1327,7 @@ function inject(
1016
1327
 
1017
1328
  // Inject paths into string scalars.
1018
1329
  else if (S_string === valtype) {
1019
- inj.mode = S_MVAL as InjectMode
1330
+ inj.mode = M_VAL
1020
1331
  val = _injectstr(val, store, inj)
1021
1332
  if (SKIP !== val) {
1022
1333
  inj.setval(val)
@@ -1038,6 +1349,8 @@ function inject(
1038
1349
  )
1039
1350
  }
1040
1351
 
1352
+ // console.log('INJ-VAL', val)
1353
+
1041
1354
  inj.val = val
1042
1355
 
1043
1356
  // Original val reference may no longer be correct.
@@ -1050,21 +1363,22 @@ function inject(
1050
1363
 
1051
1364
  // Delete a key from a map or list.
1052
1365
  const transform_DELETE: Injector = (inj: Injection) => {
1053
- inj.setval(UNDEF)
1054
- return UNDEF
1366
+ inj.setval(NONE)
1367
+ return NONE
1055
1368
  }
1056
1369
 
1057
1370
 
1058
1371
  // Copy value from source data.
1059
1372
  const transform_COPY: Injector = (inj: Injection, _val: any) => {
1060
- const { mode, key } = inj
1373
+ const ijname = 'COPY'
1061
1374
 
1062
- let out = key
1063
- if (!mode.startsWith(S_MKEY)) {
1064
- out = getprop(inj.dparent, key)
1065
- inj.setval(out)
1375
+ if (!checkPlacement(M_VAL, ijname, T_any, inj)) {
1376
+ return NONE
1066
1377
  }
1067
1378
 
1379
+ let out = getprop(inj.dparent, inj.key)
1380
+ inj.setval(out)
1381
+
1068
1382
  return out
1069
1383
  }
1070
1384
 
@@ -1074,29 +1388,30 @@ const transform_COPY: Injector = (inj: Injection, _val: any) => {
1074
1388
  const transform_KEY: Injector = (inj: Injection) => {
1075
1389
  const { mode, path, parent } = inj
1076
1390
 
1077
- // Do nothing in val mode.
1078
- if (S_MVAL !== mode) {
1079
- return UNDEF
1391
+ // Do nothing in val mode - not an error.
1392
+ if (M_VAL !== mode) {
1393
+ return NONE
1080
1394
  }
1081
1395
 
1082
1396
  // Key is defined by $KEY meta property.
1083
1397
  const keyspec = getprop(parent, S_BKEY)
1084
- if (UNDEF !== keyspec) {
1398
+ if (NONE !== keyspec) {
1085
1399
  delprop(parent, S_BKEY)
1086
1400
  return getprop(inj.dparent, keyspec)
1087
1401
  }
1088
1402
 
1089
1403
  // Key is defined within general purpose $META object.
1090
- 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))
1091
1406
  }
1092
1407
 
1093
1408
 
1094
- // Annotatea node. Does nothing itself, just used by
1409
+ // Annotate node. Does nothing itself, just used by
1095
1410
  // other injectors, and is removed when called.
1096
1411
  const transform_ANNO: Injector = (inj: Injection) => {
1097
1412
  const { parent } = inj
1098
1413
  delprop(parent, S_BANNO)
1099
- return UNDEF
1414
+ return NONE
1100
1415
  }
1101
1416
 
1102
1417
 
@@ -1109,29 +1424,27 @@ const transform_MERGE: Injector = (inj: Injection) => {
1109
1424
  const { mode, key, parent } = inj
1110
1425
 
1111
1426
  // Ensures $MERGE is removed from parent list (val mode).
1112
- let out: any = UNDEF
1427
+ let out: any = NONE
1113
1428
 
1114
- if (S_MKEYPRE === mode) {
1429
+ if (M_KEYPRE === mode) {
1115
1430
  out = key
1116
1431
  }
1117
1432
 
1118
1433
  // Operate after child values have been transformed.
1119
- else if (S_MKEYPOST === mode) {
1434
+ else if (M_KEYPOST === mode) {
1120
1435
  out = key
1121
1436
 
1122
1437
  let args = getprop(parent, key)
1123
1438
  args = Array.isArray(args) ? args : [args]
1124
1439
 
1125
1440
  // Remove the $MERGE command from a parent map.
1126
- inj.setval(UNDEF)
1441
+ inj.setval(NONE)
1127
1442
 
1128
1443
  // Literals in the parent have precedence, but we still merge onto
1129
1444
  // the parent object, so that node tree references are not changed.
1130
- const mergelist = [parent, ...args, clone(parent)]
1445
+ const mergelist = flatten([[parent], args, [clone(parent)]])
1131
1446
 
1132
1447
  merge(mergelist)
1133
-
1134
- // return key
1135
1448
  }
1136
1449
 
1137
1450
  return out
@@ -1146,65 +1459,63 @@ const transform_EACH: Injector = (
1146
1459
  _ref: string,
1147
1460
  store: any
1148
1461
  ) => {
1462
+ const ijname = 'EACH'
1149
1463
 
1150
- // Remove arguments to avoid spurious processing.
1151
- if (null != inj.keys) {
1152
- inj.keys.length = 1
1464
+ if (!checkPlacement(M_VAL, ijname, T_list, inj)) {
1465
+ return NONE
1153
1466
  }
1154
1467
 
1155
- if (S_MVAL !== inj.mode) {
1156
- return UNDEF
1157
- }
1468
+ // Remove remaining keys to avoid spurious processing.
1469
+ slice(inj.keys, 0, 1, true)
1158
1470
 
1159
- // Get arguments: ['`$EACH`', 'source-path', child-template].
1160
- const srcpath = getprop(inj.parent, 1)
1161
- 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
+ }
1162
1477
 
1163
1478
  // Source data.
1164
1479
  const srcstore = getprop(store, inj.base, store)
1165
1480
 
1166
1481
  const src = getpath(srcstore, srcpath, inj)
1482
+ const srctype = typify(src)
1167
1483
 
1168
1484
  // Create parallel data structures:
1169
1485
  // source entries :: child templates
1170
1486
  let tcur: any = []
1171
1487
  let tval: any = []
1172
1488
 
1173
- const tkey = inj.path[inj.path.length - 2]
1174
- 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))
1175
1491
 
1176
1492
  // Create clones of the child template for each value of the current soruce.
1177
- if (islist(src)) {
1178
- tval = src.map(() => clone(child))
1493
+ if (0 < (T_list & srctype)) {
1494
+ tval = items(src, () => clone(child))
1179
1495
  }
1180
- else if (ismap(src)) {
1181
- tval = Object.entries(src).map(n => ({
1182
- ...clone(child),
1183
-
1496
+ else if (0 < (T_map & srctype)) {
1497
+ tval = items(src, (n => merge([
1498
+ clone(child),
1184
1499
  // Make a note of the key for $KEY transforms.
1185
- [S_BANNO]: { KEY: n[0] }
1186
- }))
1500
+ { [S_BANNO]: { KEY: n[0] } }
1501
+ ], 1)))
1187
1502
  }
1188
1503
 
1189
1504
  let rval = []
1190
1505
 
1191
1506
  if (0 < size(tval)) {
1192
- tcur = null == src ? UNDEF : Object.values(src)
1507
+ tcur = null == src ? NONE : Object.values(src)
1193
1508
 
1194
1509
  const ckey = getelem(inj.path, -2)
1195
1510
 
1196
1511
  const tpath = slice(inj.path, -1)
1197
- const dpath = [S_DTOP, ...srcpath.split(S_DT), '$:' + ckey]
1198
-
1512
+ const dpath = flatten([S_DTOP, srcpath.split(S_DT), '$:' + ckey])
1199
1513
 
1200
1514
  // Parent structure.
1201
-
1202
- // const ckey = getelem(cpath, -1)
1203
1515
  tcur = { [ckey]: tcur }
1204
1516
 
1205
- if (1 < tpath.length) {
1517
+ if (1 < size(tpath)) {
1206
1518
  const pkey = getelem(inj.path, -3, S_DTOP)
1207
- // const pkey = getelem(cpath, -2, S_DTOP)
1208
1519
  tcur = { [pkey]: tcur }
1209
1520
  dpath.push('$:' + pkey)
1210
1521
  }
@@ -1224,7 +1535,8 @@ const transform_EACH: Injector = (
1224
1535
  rval = tinj.val
1225
1536
  }
1226
1537
 
1227
- _updateAncestors(inj, target, tkey, rval)
1538
+ // _updateAncestors(inj, target, tkey, rval)
1539
+ setprop(target, tkey, rval)
1228
1540
 
1229
1541
  // Prevent callee from damaging first list entry (since we are in `val` mode).
1230
1542
  return rval[0]
@@ -1232,7 +1544,7 @@ const transform_EACH: Injector = (
1232
1544
 
1233
1545
 
1234
1546
  // Convert a node to a map.
1235
- // Format: { '`$PACK`':['`source-path`', child-template]}
1547
+ // Format: { '`$PACK`':['source-path', child-template]}
1236
1548
  const transform_PACK: Injector = (
1237
1549
  inj: Injection,
1238
1550
  _val: any,
@@ -1241,78 +1553,105 @@ const transform_PACK: Injector = (
1241
1553
  ) => {
1242
1554
  const { mode, key, path, parent, nodes } = inj
1243
1555
 
1244
- // Defensive context checks.
1245
- if (S_MKEYPRE !== mode || S_string !== typeof key || null == path || null == nodes) {
1246
- return UNDEF
1556
+ const ijname = 'EACH'
1557
+
1558
+ if (!checkPlacement(M_KEYPRE, ijname, T_map, inj)) {
1559
+ return NONE
1247
1560
  }
1248
1561
 
1249
1562
  // Get arguments.
1250
- const args = parent[key]
1251
- const srcpath = args[0] // Path to source data.
1252
- 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
+ }
1253
1569
 
1254
1570
  // Find key and target node.
1255
- const keyprop = child[S_BKEY]
1256
1571
  const tkey = getelem(path, -2)
1257
- 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))
1258
1574
 
1259
1575
  // Source data
1260
1576
  const srcstore = getprop(store, inj.base, store)
1261
-
1262
1577
  let src = getpath(srcstore, srcpath, inj)
1263
1578
 
1264
1579
  // Prepare source as a list.
1265
- src = islist(src) ? src :
1266
- ismap(src) ? Object.entries(src)
1267
- .reduce((a: any[], n: any) =>
1268
- (n[1][S_BANNO] = { KEY: n[0] }, a.push(n[1]), a), []) :
1269
- 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
+ }
1270
1591
 
1271
1592
  if (null == src) {
1272
- return UNDEF
1593
+ return NONE
1273
1594
  }
1274
1595
 
1275
- // Get key if specified.
1276
- let childkey: PropKey | undefined = getprop(child, S_BKEY)
1277
- let keyname = UNDEF === childkey ? keyprop : childkey
1278
- 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)
1279
1601
 
1280
1602
  // Build parallel target object.
1281
1603
  let tval: any = {}
1282
- tval = src.reduce((a: any, n: any) => {
1283
- let kn = getprop(n, keyname)
1284
- setprop(a, kn, clone(child))
1285
- const nchild = getprop(a, kn)
1286
- const mval = getprop(n, S_BANNO)
1287
- if (UNDEF === mval) {
1288
- 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)
1289
1625
  }
1290
1626
  else {
1291
- setprop(nchild, S_BANNO, mval)
1627
+ setprop(tchild, S_BANNO, anno)
1292
1628
  }
1293
- return a
1294
- }, tval)
1629
+ })
1295
1630
 
1296
1631
  let rval = {}
1297
1632
 
1298
- if (0 < size(tval)) {
1633
+ if (!isempty(tval)) {
1299
1634
 
1300
1635
  // Build parallel source object.
1301
- let tcur: any = {}
1302
- src.reduce((a: any, n: any) => {
1303
- 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
+
1304
1643
  setprop(a, kn, n)
1305
1644
  return a
1306
- }, tcur)
1645
+ }, tsrc)
1307
1646
 
1308
1647
  const tpath = slice(inj.path, -1)
1309
1648
 
1310
1649
  const ckey = getelem(inj.path, -2)
1311
- const dpath = [S_DTOP, ...srcpath.split(S_DT), '$:' + ckey]
1650
+ const dpath = flatten([S_DTOP, srcpath.split(S_DT), '$:' + ckey])
1312
1651
 
1313
- tcur = { [ckey]: tcur }
1652
+ let tcur = { [ckey]: tsrc }
1314
1653
 
1315
- if (1 < tpath.length) {
1654
+ if (1 < size(tpath)) {
1316
1655
  const pkey = getelem(inj.path, -3, S_DTOP)
1317
1656
  tcur = { [pkey]: tcur }
1318
1657
  dpath.push('$:' + pkey)
@@ -1322,7 +1661,6 @@ const transform_PACK: Injector = (
1322
1661
  tinj.path = tpath
1323
1662
  tinj.nodes = slice(inj.nodes, -1)
1324
1663
 
1325
- // tinj.parent = tcur
1326
1664
  tinj.parent = getelem(tinj.nodes, -1)
1327
1665
  tinj.val = tval
1328
1666
 
@@ -1333,14 +1671,15 @@ const transform_PACK: Injector = (
1333
1671
  rval = tinj.val
1334
1672
  }
1335
1673
 
1336
- _updateAncestors(inj, target, tkey, rval)
1674
+ // _updateAncestors(inj, target, tkey, rval)
1675
+ setprop(target, tkey, rval)
1337
1676
 
1338
1677
  // Drop transform key.
1339
- return UNDEF
1678
+ return NONE
1340
1679
  }
1341
1680
 
1342
1681
 
1343
- // TODO: not found ref should removed key (setprop UNDEF)
1682
+ // TODO: not found ref should removed key (setprop NONE)
1344
1683
  // Reference original spec (enables recursice transformations)
1345
1684
  // Format: ['`$REF`', '`spec-path`']
1346
1685
  const transform_REF: Injector = (
@@ -1351,21 +1690,24 @@ const transform_REF: Injector = (
1351
1690
  ) => {
1352
1691
  const { nodes } = inj
1353
1692
 
1354
- if (S_MVAL !== inj.mode) {
1355
- return UNDEF
1693
+ if (M_VAL !== inj.mode) {
1694
+ return NONE
1356
1695
  }
1357
1696
 
1358
1697
  // Get arguments: ['`$REF`', 'ref-path'].
1359
1698
  const refpath = getprop(inj.parent, 1)
1360
- inj.keyI = inj.keys.length
1699
+ inj.keyI = size(inj.keys)
1361
1700
 
1362
1701
  // Spec reference.
1363
1702
  const spec = getprop(store, S_DSPEC)()
1364
1703
 
1704
+ const dpath = slice(inj.path, 1)
1365
1705
  const ref = getpath(spec, refpath, {
1366
1706
  // TODO: test relative refs
1367
- dpath: inj.path.slice(1),
1368
- 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),
1369
1711
  })
1370
1712
 
1371
1713
  let hasSubRef = false
@@ -1384,9 +1726,9 @@ const transform_REF: Injector = (
1384
1726
  const tpath = slice(inj.path, -1)
1385
1727
  let tcur = getpath(store, cpath)
1386
1728
  let tval = getpath(store, tpath)
1387
- let rval = UNDEF
1729
+ let rval = NONE
1388
1730
 
1389
- if (!hasSubRef || UNDEF !== tval) {
1731
+ if (!hasSubRef || NONE !== tval) {
1390
1732
  const tinj = inj.child(0, [getelem(tpath, -1)])
1391
1733
 
1392
1734
  tinj.path = tpath
@@ -1394,7 +1736,7 @@ const transform_REF: Injector = (
1394
1736
  tinj.parent = getelem(nodes, -2)
1395
1737
  tinj.val = tref
1396
1738
 
1397
- tinj.dpath = [...cpath]
1739
+ tinj.dpath = flatten([cpath])
1398
1740
  tinj.dparent = tcur
1399
1741
 
1400
1742
  inject(tref, store, tinj)
@@ -1402,7 +1744,7 @@ const transform_REF: Injector = (
1402
1744
  rval = tinj.val
1403
1745
  }
1404
1746
  else {
1405
- rval = UNDEF
1747
+ rval = NONE
1406
1748
  }
1407
1749
 
1408
1750
  const grandparent = inj.setval(rval, 2)
@@ -1415,6 +1757,117 @@ const transform_REF: Injector = (
1415
1757
  }
1416
1758
 
1417
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
+
1418
1871
  // Transform data using spec.
1419
1872
  // Only operates on static JSON-like data.
1420
1873
  // Arrays are treated as if they are objects with indices as keys.
@@ -1428,51 +1881,66 @@ function transform(
1428
1881
  spec = clone(origspec)
1429
1882
 
1430
1883
  const extra = injdef?.extra
1431
- // const modify = injdef?.modify
1884
+
1885
+ const collect = null != injdef?.errs
1886
+ const errs = injdef?.errs || []
1432
1887
 
1433
1888
  const extraTransforms: any = {}
1434
- const extraData = null == extra ? UNDEF : items(extra)
1889
+ const extraData = null == extra ? NONE : items(extra)
1435
1890
  .reduce((a: any, n: any[]) =>
1436
1891
  (n[0].startsWith(S_DS) ? extraTransforms[n[0]] = n[1] : (a[n[0]] = n[1]), a), {})
1437
1892
 
1438
1893
  const dataClone = merge([
1439
- isempty(extraData) ? UNDEF : clone(extraData),
1894
+ isempty(extraData) ? NONE : clone(extraData),
1440
1895
  clone(data),
1441
1896
  ])
1442
1897
 
1443
1898
  // Define a top level store that provides transform operations.
1444
- const store = {
1445
-
1446
- // The inject function recognises this special location for the root of the source data.
1447
- // NOTE: to escape data that contains "`$FOO`" keys at the top level,
1448
- // place that data inside a holding map: { myholder: mydata }.
1449
- $TOP: dataClone,
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
+ },
1450
1928
 
1451
- $SPEC: () => origspec,
1452
-
1453
- // Escape backtick (this also works inside backticks).
1454
- $BT: () => S_BT,
1455
-
1456
- // Escape dollar sign (this also works inside backticks).
1457
- $DS: () => S_DS,
1929
+ // Custom extra transforms, if any.
1930
+ extraTransforms,
1458
1931
 
1459
- // Insert current date and time as an ISO string.
1460
- $WHEN: () => new Date().toISOString(),
1932
+ {
1933
+ $ERRS: errs,
1934
+ }
1935
+ ], 1)
1461
1936
 
1462
- $DELETE: transform_DELETE,
1463
- $COPY: transform_COPY,
1464
- $KEY: transform_KEY,
1465
- $ANNO: transform_ANNO,
1466
- $MERGE: transform_MERGE,
1467
- $EACH: transform_EACH,
1468
- $PACK: transform_PACK,
1469
- $REF: transform_REF,
1937
+ const out = inject(spec, store, injdef)
1470
1938
 
1471
- // Custom extra transforms, if any.
1472
- ...extraTransforms,
1939
+ const generr = (0 < size(errs) && !collect)
1940
+ if (generr) {
1941
+ throw new Error(join(errs, ' | '))
1473
1942
  }
1474
1943
 
1475
- const out = inject(spec, store, injdef)
1476
1944
  return out
1477
1945
  }
1478
1946
 
@@ -1482,86 +1950,36 @@ const validate_STRING: Injector = (inj: Injection) => {
1482
1950
  let out = getprop(inj.dparent, inj.key)
1483
1951
 
1484
1952
  const t = typify(out)
1485
- if (S_string !== t) {
1953
+ if (0 === (T_string & t)) {
1486
1954
  let msg = _invalidTypeMsg(inj.path, S_string, t, out, 'V1010')
1487
1955
  inj.errs.push(msg)
1488
- return UNDEF
1956
+ return NONE
1489
1957
  }
1490
1958
 
1491
1959
  if (S_MT === out) {
1492
1960
  let msg = 'Empty string at ' + pathify(inj.path, 1)
1493
1961
  inj.errs.push(msg)
1494
- return UNDEF
1962
+ return NONE
1495
1963
  }
1496
1964
 
1497
1965
  return out
1498
1966
  }
1499
1967
 
1500
1968
 
1501
- // A required number value (int or float).
1502
- const validate_NUMBER: Injector = (inj: Injection) => {
1503
- let out = getprop(inj.dparent, inj.key)
1504
1969
 
1505
- const t = typify(out)
1506
- if (S_number !== t) {
1507
- inj.errs.push(_invalidTypeMsg(inj.path, S_number, t, out, 'V1020'))
1508
- return UNDEF
1509
- }
1510
1970
 
1511
- return out
1512
- }
1513
-
1514
-
1515
- // A required boolean value.
1516
- 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))
1517
1974
  let out = getprop(inj.dparent, inj.key)
1518
1975
 
1519
1976
  const t = typify(out)
1520
- if (S_boolean !== t) {
1521
- inj.errs.push(_invalidTypeMsg(inj.path, S_boolean, t, out, 'V1030'))
1522
- return UNDEF
1523
- }
1524
1977
 
1525
- return out
1526
- }
1527
-
1528
-
1529
- // A required object (map) value (contents not validated).
1530
- const validate_OBJECT: Injector = (inj: Injection) => {
1531
- let out = getprop(inj.dparent, inj.key)
1978
+ // console.log('TYPE', tname, typev, tn(typev), 'O=', t, tn(t), out, 'C=', t & typev)
1532
1979
 
1533
- const t = typify(out)
1534
- if (t !== S_object) {
1535
- inj.errs.push(_invalidTypeMsg(inj.path, S_object, t, out, 'V1040'))
1536
- return UNDEF
1537
- }
1538
-
1539
- return out
1540
- }
1541
-
1542
-
1543
- // A required array (list) value (contents not validated).
1544
- const validate_ARRAY: Injector = (inj: Injection) => {
1545
- let out = getprop(inj.dparent, inj.key)
1546
-
1547
- const t = typify(out)
1548
- if (t !== S_array) {
1549
- inj.errs.push(_invalidTypeMsg(inj.path, S_array, t, out, 'V1050'))
1550
- return UNDEF
1551
- }
1552
-
1553
- return out
1554
- }
1555
-
1556
-
1557
- // A required function value.
1558
- const validate_FUNCTION: Injector = (inj: Injection) => {
1559
- let out = getprop(inj.dparent, inj.key)
1560
-
1561
- const t = typify(out)
1562
- if (S_function !== t) {
1563
- inj.errs.push(_invalidTypeMsg(inj.path, S_function, t, out, 'V1060'))
1564
- return UNDEF
1980
+ if (0 === (t & typev)) {
1981
+ inj.errs.push(_invalidTypeMsg(inj.path, tname, t, out, 'V1001'))
1982
+ return NONE
1565
1983
  }
1566
1984
 
1567
1985
  return out
@@ -1585,20 +2003,20 @@ const validate_CHILD: Injector = (inj: Injection) => {
1585
2003
  // Setup data structures for validation by cloning child template.
1586
2004
 
1587
2005
  // Map syntax.
1588
- if (S_MKEYPRE === mode) {
2006
+ if (M_KEYPRE === mode) {
1589
2007
  const childtm = getprop(parent, key)
1590
2008
 
1591
2009
  // Get corresponding current object.
1592
- const pkey = getprop(path, path.length - 2)
2010
+ const pkey = getelem(path, -2)
1593
2011
  let tval = getprop(inj.dparent, pkey)
1594
2012
 
1595
- if (UNDEF == tval) {
2013
+ if (NONE == tval) {
1596
2014
  tval = {}
1597
2015
  }
1598
2016
  else if (!ismap(tval)) {
1599
2017
  inj.errs.push(_invalidTypeMsg(
1600
2018
  slice(inj.path, -1), S_object, typify(tval), tval), 'V0220')
1601
- return UNDEF
2019
+ return NONE
1602
2020
  }
1603
2021
 
1604
2022
  const ckeys = keysof(tval)
@@ -1610,52 +2028,56 @@ const validate_CHILD: Injector = (inj: Injection) => {
1610
2028
  }
1611
2029
 
1612
2030
  // Remove $CHILD to cleanup ouput.
1613
- inj.setval(UNDEF)
1614
- return UNDEF
2031
+ inj.setval(NONE)
2032
+ return NONE
1615
2033
  }
1616
2034
 
1617
2035
  // List syntax.
1618
- if (S_MVAL === mode) {
2036
+ if (M_VAL === mode) {
1619
2037
 
1620
2038
  if (!islist(parent)) {
1621
2039
  // $CHILD was not inside a list.
1622
2040
  inj.errs.push('Invalid $CHILD as value')
1623
- return UNDEF
2041
+ return NONE
1624
2042
  }
1625
2043
 
1626
2044
  const childtm = getprop(parent, 1)
1627
2045
 
1628
- if (UNDEF === inj.dparent) {
2046
+ if (NONE === inj.dparent) {
1629
2047
  // Empty list as default.
1630
- parent.length = 0
1631
- return UNDEF
2048
+ // parent.length = 0
2049
+ slice(parent, 0, 0, true)
2050
+ return NONE
1632
2051
  }
1633
2052
 
1634
2053
  if (!islist(inj.dparent)) {
1635
2054
  const msg = _invalidTypeMsg(
1636
- 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')
1637
2056
  inj.errs.push(msg)
1638
- inj.keyI = parent.length
2057
+ inj.keyI = size(parent)
1639
2058
  return inj.dparent
1640
2059
  }
1641
2060
 
1642
2061
  // Clone children abd reset inj key index.
1643
2062
  // The inject child loop will now iterate over the cloned children,
1644
2063
  // validating them againt the current list values.
1645
-
1646
- inj.dparent.map((_n, i) => parent[i] = clone(childtm))
1647
- parent.length = inj.dparent.length
2064
+ items(inj.dparent, (n) => setprop(parent, n[0], clone(childtm)))
2065
+ slice(parent, 0, inj.dparent.length, true)
1648
2066
  inj.keyI = 0
2067
+
1649
2068
  const out = getprop(inj.dparent, 0)
1650
2069
  return out
1651
2070
  }
1652
2071
 
1653
- return UNDEF
2072
+ return NONE
1654
2073
  }
1655
2074
 
1656
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
1657
2079
  // Match at least one of the specified shapes.
1658
- // Syntax: ['`$ONE`', alt0, alt1, ...]okI
2080
+ // Syntax: ['`$ONE`', alt0, alt1, ...]
1659
2081
  const validate_ONE: Injector = (
1660
2082
  inj: Injection,
1661
2083
  _val: any,
@@ -1665,7 +2087,7 @@ const validate_ONE: Injector = (
1665
2087
  const { mode, parent, keyI } = inj
1666
2088
 
1667
2089
  // Only operate in val mode, since parent is a list.
1668
- if (S_MVAL === mode) {
2090
+ if (M_VAL === mode) {
1669
2091
  if (!islist(parent) || 0 !== keyI) {
1670
2092
  inj.errs.push('The $ONE validator at field ' +
1671
2093
  pathify(inj.path, 1, 1) +
@@ -1673,7 +2095,7 @@ const validate_ONE: Injector = (
1673
2095
  return
1674
2096
  }
1675
2097
 
1676
- inj.keyI = inj.keys.length
2098
+ inj.keyI = size(inj.keys)
1677
2099
 
1678
2100
  // Clean up structure, replacing [$ONE, ...] with current
1679
2101
  inj.setval(inj.dparent, 2)
@@ -1682,7 +2104,7 @@ const validate_ONE: Injector = (
1682
2104
  inj.key = getelem(inj.path, -1)
1683
2105
 
1684
2106
  let tvals = slice(parent, 1)
1685
- if (0 === tvals.length) {
2107
+ if (0 === size(tvals)) {
1686
2108
  inj.errs.push('The $ONE validator at field ' +
1687
2109
  pathify(inj.path, 1, 1) +
1688
2110
  ' must have at least one argument.')
@@ -1695,7 +2117,7 @@ const validate_ONE: Injector = (
1695
2117
  // If match, then errs.length = 0
1696
2118
  let terrs: any[] = []
1697
2119
 
1698
- const vstore = { ...store }
2120
+ const vstore = merge([{}, store], 1)
1699
2121
  vstore.$TOP = inj.dparent
1700
2122
 
1701
2123
  const vcurrent = validate(inj.dparent, tval, {
@@ -1707,21 +2129,19 @@ const validate_ONE: Injector = (
1707
2129
  inj.setval(vcurrent, -2)
1708
2130
 
1709
2131
  // Accept current value if there was a match
1710
- if (0 === terrs.length) {
2132
+ if (0 === size(terrs)) {
1711
2133
  return
1712
2134
  }
1713
2135
  }
1714
2136
 
1715
2137
  // There was no match.
1716
-
1717
- const valdesc = tvals
1718
- .map((v: any) => stringify(v))
1719
- .join(', ')
1720
- .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())
1721
2141
 
1722
2142
  inj.errs.push(_invalidTypeMsg(
1723
2143
  inj.path,
1724
- (1 < tvals.length ? 'one of ' : '') + valdesc,
2144
+ (1 < size(tvals) ? 'one of ' : '') + valdesc,
1725
2145
  typify(inj.dparent), inj.dparent, 'V0210'))
1726
2146
  }
1727
2147
  }
@@ -1731,7 +2151,7 @@ const validate_EXACT: Injector = (inj: Injection) => {
1731
2151
  const { mode, parent, key, keyI } = inj
1732
2152
 
1733
2153
  // Only operate in val mode, since parent is a list.
1734
- if (S_MVAL === mode) {
2154
+ if (M_VAL === mode) {
1735
2155
  if (!islist(parent) || 0 !== keyI) {
1736
2156
  inj.errs.push('The $EXACT validator at field ' +
1737
2157
  pathify(inj.path, 1, 1) +
@@ -1739,16 +2159,17 @@ const validate_EXACT: Injector = (inj: Injection) => {
1739
2159
  return
1740
2160
  }
1741
2161
 
1742
- inj.keyI = inj.keys.length
2162
+ inj.keyI = size(inj.keys)
1743
2163
 
1744
2164
  // Clean up structure, replacing [$EXACT, ...] with current data parent
1745
2165
  inj.setval(inj.dparent, 2)
1746
2166
 
1747
- 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)
1748
2169
  inj.key = getelem(inj.path, -1)
1749
2170
 
1750
2171
  let tvals = slice(parent, 1)
1751
- if (0 === tvals.length) {
2172
+ if (0 === size(tvals)) {
1752
2173
  inj.errs.push('The $EXACT validator at field ' +
1753
2174
  pathify(inj.path, 1, 1) +
1754
2175
  ' must have at least one argument.')
@@ -1771,15 +2192,15 @@ const validate_EXACT: Injector = (inj: Injection) => {
1771
2192
  }
1772
2193
  }
1773
2194
 
1774
- const valdesc = tvals
1775
- .map((v: any) => stringify(v))
1776
- .join(', ')
1777
- .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())
1778
2199
 
1779
2200
  inj.errs.push(_invalidTypeMsg(
1780
2201
  inj.path,
1781
- (1 < inj.path.length ? '' : 'value ') +
1782
- '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,
1783
2204
  typify(inj.dparent), inj.dparent, 'V0110'))
1784
2205
  }
1785
2206
  else {
@@ -1797,7 +2218,7 @@ const _validation: Modify = (
1797
2218
  inj?: Injection,
1798
2219
  ) => {
1799
2220
 
1800
- if (UNDEF === inj) {
2221
+ if (NONE === inj) {
1801
2222
  return
1802
2223
  }
1803
2224
 
@@ -1811,28 +2232,28 @@ const _validation: Modify = (
1811
2232
  // Current val to verify.
1812
2233
  const cval = getprop(inj.dparent, key)
1813
2234
 
1814
- if (UNDEF === inj || (!exact && UNDEF === cval)) {
2235
+ if (NONE === inj || (!exact && NONE === cval)) {
1815
2236
  return
1816
2237
  }
1817
2238
 
1818
2239
  const ptype = typify(pval)
1819
2240
 
1820
2241
  // Delete any special commands remaining.
1821
- if (S_string === ptype && pval.includes(S_DS)) {
2242
+ if (0 < (T_string & ptype) && pval.includes(S_DS)) {
1822
2243
  return
1823
2244
  }
1824
2245
 
1825
2246
  const ctype = typify(cval)
1826
2247
 
1827
2248
  // Type mismatch.
1828
- if (ptype !== ctype && UNDEF !== pval) {
1829
- 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'))
1830
2251
  return
1831
2252
  }
1832
2253
 
1833
2254
  if (ismap(cval)) {
1834
2255
  if (!ismap(pval)) {
1835
- inj.errs.push(_invalidTypeMsg(inj.path, ptype, ctype, cval, 'V0020'))
2256
+ inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0020'))
1836
2257
  return
1837
2258
  }
1838
2259
 
@@ -1840,7 +2261,7 @@ const _validation: Modify = (
1840
2261
  const pkeys = keysof(pval)
1841
2262
 
1842
2263
  // Empty spec object {} means object can be open (any keys).
1843
- if (0 < pkeys.length && true !== getprop(pval, '`$OPEN`')) {
2264
+ if (0 < size(pkeys) && true !== getprop(pval, '`$OPEN`')) {
1844
2265
  const badkeys = []
1845
2266
  for (let ckey of ckeys) {
1846
2267
  if (!haskey(pval, ckey)) {
@@ -1849,9 +2270,9 @@ const _validation: Modify = (
1849
2270
  }
1850
2271
 
1851
2272
  // Closed object, so reject extra keys not in shape.
1852
- if (0 < badkeys.length) {
2273
+ if (0 < size(badkeys)) {
1853
2274
  const msg =
1854
- '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, ', ')
1855
2276
  inj.errs.push(msg)
1856
2277
  }
1857
2278
  }
@@ -1865,7 +2286,7 @@ const _validation: Modify = (
1865
2286
  }
1866
2287
  else if (islist(cval)) {
1867
2288
  if (!islist(pval)) {
1868
- inj.errs.push(_invalidTypeMsg(inj.path, ptype, ctype, cval, 'V0030'))
2289
+ inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0030'))
1869
2290
  }
1870
2291
  }
1871
2292
  else if (exact) {
@@ -1905,50 +2326,57 @@ function validate(
1905
2326
  const collect = null != injdef?.errs
1906
2327
  const errs = injdef?.errs || []
1907
2328
 
1908
- const store = {
1909
- // Remove the transform commands.
1910
- $DELETE: null,
1911
- $COPY: null,
1912
- $KEY: null,
1913
- $META: null,
1914
- $MERGE: null,
1915
- $EACH: null,
1916
- $PACK: null,
1917
-
1918
- $STRING: validate_STRING,
1919
- $NUMBER: validate_NUMBER,
1920
- $BOOLEAN: validate_BOOLEAN,
1921
- $OBJECT: validate_OBJECT,
1922
- $ARRAY: validate_ARRAY,
1923
- $FUNCTION: validate_FUNCTION,
1924
- $ANY: validate_ANY,
1925
- $CHILD: validate_CHILD,
1926
- $ONE: validate_ONE,
1927
- $EXACT: validate_EXACT,
1928
-
1929
- ...(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, {}),
1930
2358
 
1931
2359
  // A special top level value to collect errors.
1932
- // NOTE: collecterrs paramter always wins.
1933
- $ERRS: errs,
1934
- }
1935
-
1936
- let meta = { [S_BEXACT]: false }
2360
+ // NOTE: collecterrs parameter always wins.
2361
+ {
2362
+ $ERRS: errs,
2363
+ }
2364
+ ], 1)
1937
2365
 
1938
- if (injdef?.meta) {
1939
- meta = merge([meta, injdef.meta])
1940
- }
2366
+ let meta = getprop(injdef, 'meta', {})
2367
+ setprop(meta, S_BEXACT, getprop(meta, S_BEXACT, false))
1941
2368
 
1942
2369
  const out = transform(data, spec, {
1943
2370
  meta,
1944
2371
  extra: store,
1945
2372
  modify: _validation,
1946
- handler: _validatehandler
2373
+ handler: _validatehandler,
2374
+ errs,
1947
2375
  })
1948
2376
 
1949
- const generr = (0 < errs.length && !collect)
2377
+ const generr = (0 < size(errs) && !collect)
1950
2378
  if (generr) {
1951
- throw new Error('Invalid data: ' + errs.join(' | '))
2379
+ throw new Error(join(errs, ' | '))
1952
2380
  }
1953
2381
 
1954
2382
  return out
@@ -1956,18 +2384,16 @@ function validate(
1956
2384
 
1957
2385
 
1958
2386
  const select_AND: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
1959
- if (S_MKEYPRE === inj.mode) {
2387
+ if (M_KEYPRE === inj.mode) {
1960
2388
  const terms = getprop(inj.parent, inj.key)
1961
2389
 
1962
2390
  const ppath = slice(inj.path, -1)
1963
2391
  const point = getpath(store, ppath)
1964
2392
 
1965
- const vstore = { ...store }
2393
+ const vstore = merge([{}, store], 1)
1966
2394
  vstore.$TOP = point
1967
2395
 
1968
2396
  for (let term of terms) {
1969
- // setprop(term, '`$OPEN`', getprop(term, '`$OPEN`', true))
1970
-
1971
2397
  let terrs: any[] = []
1972
2398
 
1973
2399
  validate(point, term, {
@@ -1976,7 +2402,7 @@ const select_AND: Injector = (inj: Injection, _val: any, _ref: string, store: an
1976
2402
  meta: inj.meta,
1977
2403
  })
1978
2404
 
1979
- if (0 != terrs.length) {
2405
+ if (0 != size(terrs)) {
1980
2406
  inj.errs.push(
1981
2407
  'AND:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms))
1982
2408
  }
@@ -1990,13 +2416,13 @@ const select_AND: Injector = (inj: Injection, _val: any, _ref: string, store: an
1990
2416
 
1991
2417
 
1992
2418
  const select_OR: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
1993
- if (S_MKEYPRE === inj.mode) {
2419
+ if (M_KEYPRE === inj.mode) {
1994
2420
  const terms = getprop(inj.parent, inj.key)
1995
2421
 
1996
2422
  const ppath = slice(inj.path, -1)
1997
2423
  const point = getpath(store, ppath)
1998
2424
 
1999
- const vstore = { ...store }
2425
+ const vstore = merge([{}, store], 1)
2000
2426
  vstore.$TOP = point
2001
2427
 
2002
2428
  for (let term of terms) {
@@ -2008,7 +2434,7 @@ const select_OR: Injector = (inj: Injection, _val: any, _ref: string, store: any
2008
2434
  meta: inj.meta,
2009
2435
  })
2010
2436
 
2011
- if (0 === terrs.length) {
2437
+ if (0 === size(terrs)) {
2012
2438
  const gkey = getelem(inj.path, -2)
2013
2439
  const gp = getelem(inj.nodes, -2)
2014
2440
  setprop(gp, gkey, point)
@@ -2024,13 +2450,13 @@ const select_OR: Injector = (inj: Injection, _val: any, _ref: string, store: any
2024
2450
 
2025
2451
 
2026
2452
  const select_NOT: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
2027
- if (S_MKEYPRE === inj.mode) {
2453
+ if (M_KEYPRE === inj.mode) {
2028
2454
  const term = getprop(inj.parent, inj.key)
2029
2455
 
2030
2456
  const ppath = slice(inj.path, -1)
2031
2457
  const point = getpath(store, ppath)
2032
2458
 
2033
- const vstore = { ...store }
2459
+ const vstore = merge([{}, store], 1)
2034
2460
  vstore.$TOP = point
2035
2461
 
2036
2462
  let terrs: any[] = []
@@ -2041,7 +2467,7 @@ const select_NOT: Injector = (inj: Injection, _val: any, _ref: string, store: an
2041
2467
  meta: inj.meta,
2042
2468
  })
2043
2469
 
2044
- if (0 == terrs.length) {
2470
+ if (0 == size(terrs)) {
2045
2471
  inj.errs.push(
2046
2472
  'NOT:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(term))
2047
2473
  }
@@ -2054,7 +2480,7 @@ const select_NOT: Injector = (inj: Injection, _val: any, _ref: string, store: an
2054
2480
 
2055
2481
 
2056
2482
  const select_CMP: Injector = (inj: Injection, _val: any, ref: string, store: any) => {
2057
- if (S_MKEYPRE === inj.mode) {
2483
+ if (M_KEYPRE === inj.mode) {
2058
2484
  const term = getprop(inj.parent, inj.key)
2059
2485
  // const src = getprop(store, inj.base, store)
2060
2486
  const gkey = getelem(inj.path, -2)
@@ -2093,7 +2519,7 @@ const select_CMP: Injector = (inj: Injection, _val: any, ref: string, store: any
2093
2519
  }
2094
2520
  }
2095
2521
 
2096
- return UNDEF
2522
+ return NONE
2097
2523
  }
2098
2524
 
2099
2525
 
@@ -2107,10 +2533,13 @@ function select(children: any, query: any): any[] {
2107
2533
  }
2108
2534
 
2109
2535
  if (ismap(children)) {
2110
- 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
+ })
2111
2540
  }
2112
2541
  else {
2113
- 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]))
2114
2543
  }
2115
2544
 
2116
2545
  const results: any[] = []
@@ -2152,10 +2581,9 @@ function select(children: any, query: any): any[] {
2152
2581
  }
2153
2582
 
2154
2583
 
2155
-
2156
2584
  // Injection state used for recursive injection into JSON - like data structures.
2157
2585
  class Injection {
2158
- mode: InjectMode // Injection mode: key:pre, val, key:post.
2586
+ mode: InjectMode // Injection mode: M_KEYPRE, M_VAL, M_KEYPOST.
2159
2587
  full: boolean // Transform escape was full key name.
2160
2588
  keyI: number // Index of parent key in list of parent keys.
2161
2589
  keys: string[] // List of parent keys.
@@ -2166,7 +2594,7 @@ class Injection {
2166
2594
  nodes: any[] // Stack of ancestor nodes.
2167
2595
  handler: Injector // Custom handler for injections.
2168
2596
  errs: any[] // Error collector.
2169
- meta: Record<string, any> // Custom meta data.
2597
+ meta: Record<string, any> // Custom meta data. NOTE: do not merge, values must remain as-is.
2170
2598
  dparent: any // Current data parent node (contains current data value).
2171
2599
  dpath: string[] // Current data value path
2172
2600
  base?: string // Base key for data in store, if any.
@@ -2179,10 +2607,10 @@ class Injection {
2179
2607
  this.parent = parent
2180
2608
  this.errs = []
2181
2609
 
2182
- this.dparent = UNDEF
2610
+ this.dparent = NONE
2183
2611
  this.dpath = [S_DTOP]
2184
2612
 
2185
- this.mode = S_MVAL as InjectMode
2613
+ this.mode = M_VAL
2186
2614
  this.full = false
2187
2615
  this.keyI = 0
2188
2616
  this.keys = [S_DTOP]
@@ -2198,7 +2626,7 @@ class Injection {
2198
2626
  toString(prefix?: string) {
2199
2627
  return 'INJ' + (null == prefix ? '' : S_FS + prefix) + S_CN +
2200
2628
  pad(pathify(this.path, 1)) +
2201
- this.mode + (this.full ? '/full' : '') + S_CN +
2629
+ MODENAME[this.mode] + (this.full ? '/full' : '') + S_CN +
2202
2630
  'key=' + this.keyI + S_FS + this.key + S_FS + S_OS + this.keys + S_CS +
2203
2631
  ' p=' + stringify(this.parent, -1, 1) +
2204
2632
  ' m=' + stringify(this.meta, -1, 1) +
@@ -2212,12 +2640,12 @@ class Injection {
2212
2640
  const parentkey = getelem(this.path, -2)
2213
2641
 
2214
2642
  // Resolve current node in store for local paths.
2215
- if (UNDEF === this.dparent) {
2643
+ if (NONE === this.dparent) {
2216
2644
 
2217
2645
  // Even if there's no data, dpath should continue to match path, so that
2218
2646
  // relative paths work properly.
2219
- if (1 < this.dpath.length) {
2220
- this.dpath = [...this.dpath, parentkey]
2647
+ if (1 < size(this.dpath)) {
2648
+ this.dpath = flatten([this.dpath, parentkey])
2221
2649
  }
2222
2650
  }
2223
2651
  else {
@@ -2230,11 +2658,12 @@ class Injection {
2230
2658
  this.dpath = slice(this.dpath, -1)
2231
2659
  }
2232
2660
  else {
2233
- this.dpath = [...this.dpath, parentkey]
2661
+ this.dpath = flatten([this.dpath, parentkey])
2234
2662
  }
2235
2663
  }
2236
2664
  }
2237
2665
 
2666
+ // TODO: is this needed?
2238
2667
  return this.dparent
2239
2668
  }
2240
2669
 
@@ -2248,8 +2677,8 @@ class Injection {
2248
2677
  cinj.keys = keys
2249
2678
  cinj.key = key
2250
2679
 
2251
- cinj.path = [...(this.path || []), key]
2252
- cinj.nodes = [...(this.nodes || []), val]
2680
+ cinj.path = flatten([getdef(this.path, []), key])
2681
+ cinj.nodes = flatten([getdef(this.nodes, []), [val]])
2253
2682
 
2254
2683
  cinj.mode = this.mode
2255
2684
  cinj.handler = this.handler
@@ -2259,7 +2688,7 @@ class Injection {
2259
2688
  cinj.errs = this.errs
2260
2689
  cinj.prior = this
2261
2690
 
2262
- cinj.dpath = [...this.dpath]
2691
+ cinj.dpath = flatten([this.dpath])
2263
2692
  cinj.dparent = this.dparent
2264
2693
 
2265
2694
  return cinj
@@ -2267,18 +2696,22 @@ class Injection {
2267
2696
 
2268
2697
 
2269
2698
  setval(val: any, ancestor?: number) {
2699
+ let parent = NONE
2270
2700
  if (null == ancestor || ancestor < 2) {
2271
- return UNDEF === val ?
2272
- delprop(this.parent, this.key) :
2701
+ parent = NONE === val ?
2702
+ this.parent = delprop(this.parent, this.key) :
2273
2703
  setprop(this.parent, this.key, val)
2274
2704
  }
2275
2705
  else {
2276
2706
  const aval = getelem(this.nodes, 0 - ancestor)
2277
2707
  const akey = getelem(this.path, 0 - ancestor)
2278
- return UNDEF === val ?
2708
+ parent = NONE === val ?
2279
2709
  delprop(aval, akey) :
2280
2710
  setprop(aval, akey, val)
2281
2711
  }
2712
+
2713
+ // console.log('SETVAL', val, this.key, this.parent)
2714
+ return parent
2282
2715
  }
2283
2716
  }
2284
2717
 
@@ -2287,21 +2720,21 @@ class Injection {
2287
2720
  // ==================
2288
2721
 
2289
2722
 
2290
- // Update all references to target in inj.nodes.
2291
- function _updateAncestors(_inj: Injection, target: any, tkey: any, tval: any) {
2292
- // SetProp is sufficient in TypeScript as target reference remains consistent even for lists.
2293
- setprop(target, tkey, tval)
2294
- }
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
+ // }
2295
2728
 
2296
2729
 
2297
2730
  // Build a type validation error message.
2298
- 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) {
2299
2732
  let vs = null == v ? 'no value' : stringify(v)
2300
2733
 
2301
2734
  return 'Expected ' +
2302
- (1 < path.length ? ('field ' + pathify(path, 1) + ' to be ') : '') +
2735
+ (1 < size(path) ? ('field ' + pathify(path, 1) + ' to be ') : '') +
2303
2736
  needtype + ', but found ' +
2304
- (null != v ? vt + S_VIZ : '') + vs +
2737
+ (null != v ? typename(vt) + S_VIZ : '') + vs +
2305
2738
 
2306
2739
  // Uncomment to help debug validation errors.
2307
2740
  // ' [' + _whence + ']' +
@@ -2319,7 +2752,7 @@ const _injecthandler: Injector = (
2319
2752
  store: any
2320
2753
  ): any => {
2321
2754
  let out = val
2322
- const iscmd = isfunc(val) && (UNDEF === ref || ref.startsWith(S_DS))
2755
+ const iscmd = isfunc(val) && (NONE === ref || ref.startsWith(S_DS))
2323
2756
 
2324
2757
  // Only call val function if it is a special command ($NAME format).
2325
2758
  // TODO: OR if meta.'$CALL'
@@ -2329,7 +2762,7 @@ const _injecthandler: Injector = (
2329
2762
  }
2330
2763
 
2331
2764
  // Update parent with value. Ensures references remain in node tree.
2332
- else if (S_MVAL === inj.mode && inj.full) {
2765
+ else if (M_VAL === inj.mode && inj.full) {
2333
2766
  inj.setval(val)
2334
2767
  }
2335
2768
 
@@ -2399,9 +2832,9 @@ function _injectstr(
2399
2832
  let pathref = m[1]
2400
2833
 
2401
2834
  // Special escapes inside injection.
2402
- pathref = 3 < pathref.length ?
2403
- pathref.replace(R_BT_ESCAPE, S_BT).replace(R_DS_ESCAPE, S_DS) :
2404
- pathref
2835
+ if (3 < size(pathref)) {
2836
+ pathref = pathref.replace(R_BT_ESCAPE, S_BT).replace(R_DS_ESCAPE, S_DS)
2837
+ }
2405
2838
 
2406
2839
  // Get the extracted path reference.
2407
2840
  out = getpath(store, pathref, inj)
@@ -2411,14 +2844,19 @@ function _injectstr(
2411
2844
  // Check for injections within the string.
2412
2845
  const partial = (_m: string, ref: string) => {
2413
2846
  // Special escapes inside injection.
2414
- 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
+
2415
2852
  if (inj) {
2416
2853
  inj.full = false
2417
2854
  }
2855
+
2418
2856
  const found = getpath(store, ref, inj)
2419
2857
 
2420
2858
  // Ensure inject value is a string.
2421
- 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)
2422
2860
  }
2423
2861
 
2424
2862
  out = val.replace(R_INJECTION_PARTIAL, partial)
@@ -2435,11 +2873,101 @@ function _injectstr(
2435
2873
  }
2436
2874
 
2437
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
+
2438
2963
  class StructUtility {
2439
2964
  clone = clone
2440
2965
  delprop = delprop
2441
2966
  escre = escre
2442
2967
  escurl = escurl
2968
+ filter = filter
2969
+ flatten = flatten
2970
+ getdef = getdef
2443
2971
  getelem = getelem
2444
2972
  getpath = getpath
2445
2973
  getprop = getprop
@@ -2452,13 +2980,14 @@ class StructUtility {
2452
2980
  ismap = ismap
2453
2981
  isnode = isnode
2454
2982
  items = items
2455
- joinurl = joinurl
2983
+ join = join
2456
2984
  jsonify = jsonify
2457
2985
  keysof = keysof
2458
2986
  merge = merge
2459
2987
  pad = pad
2460
2988
  pathify = pathify
2461
2989
  select = select
2990
+ setpath = setpath
2462
2991
  setprop = setprop
2463
2992
  size = size
2464
2993
  slice = slice
@@ -2466,11 +2995,36 @@ class StructUtility {
2466
2995
  stringify = stringify
2467
2996
  transform = transform
2468
2997
  typify = typify
2998
+ typename = typename
2469
2999
  validate = validate
2470
3000
  walk = walk
2471
3001
 
2472
- jo = jo
2473
- 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
2474
3028
  }
2475
3029
 
2476
3030
  export {
@@ -2479,6 +3033,9 @@ export {
2479
3033
  delprop,
2480
3034
  escre,
2481
3035
  escurl,
3036
+ filter,
3037
+ flatten,
3038
+ getdef,
2482
3039
  getelem,
2483
3040
  getpath,
2484
3041
  getprop,
@@ -2491,13 +3048,14 @@ export {
2491
3048
  ismap,
2492
3049
  isnode,
2493
3050
  items,
2494
- joinurl,
3051
+ join,
2495
3052
  jsonify,
2496
3053
  keysof,
2497
3054
  merge,
2498
3055
  pad,
2499
3056
  pathify,
2500
3057
  select,
3058
+ setpath,
2501
3059
  setprop,
2502
3060
  size,
2503
3061
  slice,
@@ -2505,11 +3063,41 @@ export {
2505
3063
  stringify,
2506
3064
  transform,
2507
3065
  typify,
3066
+ typename,
2508
3067
  validate,
2509
3068
  walk,
2510
3069
 
2511
- jo,
2512
- 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,
2513
3101
  }
2514
3102
 
2515
3103
  export type {