@voxgig/sdkgen 0.22.0 → 0.23.0

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 (119) hide show
  1. package/bin/voxgig-sdkgen +12 -4
  2. package/dist/action/action.d.ts +4 -0
  3. package/dist/action/action.js +35 -0
  4. package/dist/action/action.js.map +1 -0
  5. package/dist/action/feature.d.ts +4 -2
  6. package/dist/action/feature.js +57 -38
  7. package/dist/action/feature.js.map +1 -1
  8. package/dist/action/target.d.ts +4 -2
  9. package/dist/action/target.js +136 -45
  10. package/dist/action/target.js.map +1 -1
  11. package/dist/cmp/Entity.js +5 -0
  12. package/dist/cmp/Entity.js.map +1 -1
  13. package/dist/cmp/Feature.js +5 -0
  14. package/dist/cmp/Feature.js.map +1 -1
  15. package/dist/cmp/Main.js +4 -1
  16. package/dist/cmp/Main.js.map +1 -1
  17. package/dist/cmp/Top.d.ts +2 -0
  18. package/dist/cmp/Top.js +23 -0
  19. package/dist/cmp/Top.js.map +1 -0
  20. package/dist/sdkgen.d.ts +10 -3
  21. package/dist/sdkgen.js +53 -8
  22. package/dist/sdkgen.js.map +1 -1
  23. package/dist/tsconfig.tsbuildinfo +1 -1
  24. package/dist/types.d.ts +16 -0
  25. package/dist/types.js +3 -0
  26. package/dist/types.js.map +1 -0
  27. package/dist/utility.d.ts +3 -3
  28. package/dist/utility.js +7 -8
  29. package/dist/utility.js.map +1 -1
  30. package/package.json +6 -10
  31. package/project/.sdk/model/feature/README.md +2 -0
  32. package/project/.sdk/model/feature/log.jsonic +7 -4
  33. package/project/.sdk/model/feature/test.jsonic +26 -0
  34. package/project/.sdk/model/target/ts.jsonic +1 -0
  35. package/project/.sdk/src/cmp/ts/Config_ts.ts +7 -2
  36. package/project/.sdk/src/cmp/ts/EntityOperation_ts.ts +67 -0
  37. package/project/.sdk/src/cmp/ts/EntityTest_ts.ts +180 -0
  38. package/project/.sdk/src/cmp/ts/Entity_ts.ts +41 -66
  39. package/project/.sdk/src/cmp/ts/MainEntity_ts.ts +22 -0
  40. package/project/.sdk/src/cmp/ts/Main_ts.ts +52 -53
  41. package/project/.sdk/src/cmp/ts/Package_ts.ts +12 -7
  42. package/project/.sdk/src/cmp/ts/ReadmeInstall_ts.ts +19 -0
  43. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +24 -0
  44. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +13 -0
  45. package/project/.sdk/src/cmp/ts/TestMain_ts.ts +19 -0
  46. package/project/.sdk/src/cmp/ts/Test_ts.ts +20 -0
  47. package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +8 -1
  48. package/project/.sdk/src/cmp/ts/fragment/Entity.fragment.ts +164 -0
  49. package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +37 -0
  50. package/project/.sdk/src/cmp/ts/fragment/EntityCreateOp.fragment.ts +91 -0
  51. package/project/.sdk/src/cmp/ts/fragment/EntityListOp.fragment.ts +92 -0
  52. package/project/.sdk/src/cmp/ts/fragment/EntityLoadOp.fragment.ts +96 -0
  53. package/project/.sdk/src/cmp/ts/fragment/EntityRemoveOp.fragment.ts +96 -0
  54. package/project/.sdk/src/cmp/ts/fragment/EntityUpdateOp.fragment.ts +95 -0
  55. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +107 -0
  56. package/project/.sdk/src/cmp/ts/utility_ts.ts +10 -0
  57. package/project/.sdk/tm/ts/src/feature/base/BaseFeature.ts +43 -0
  58. package/project/.sdk/tm/ts/src/feature/log/LogFeature.ts +109 -0
  59. package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +159 -0
  60. package/project/.sdk/tm/ts/src/tsconfig.json +1 -1
  61. package/project/.sdk/tm/ts/src/types.ts +114 -0
  62. package/project/.sdk/tm/ts/src/utility/AddfeatureUtility.ts +47 -0
  63. package/project/.sdk/tm/ts/src/utility/AuthUtility.ts +42 -0
  64. package/project/.sdk/tm/ts/src/utility/BodyUtility.ts +29 -0
  65. package/project/.sdk/tm/ts/src/utility/CleanUtility.ts +50 -0
  66. package/project/.sdk/tm/ts/src/utility/ContextUtility.ts +67 -0
  67. package/project/.sdk/tm/ts/src/utility/DoneUtility.ts +28 -0
  68. package/project/.sdk/tm/ts/src/utility/ErrorUtility.ts +59 -0
  69. package/project/.sdk/tm/ts/src/utility/FeaturehookUtility.ts +26 -0
  70. package/project/.sdk/tm/ts/src/utility/FetcherUtility.ts +17 -0
  71. package/project/.sdk/tm/ts/src/utility/FindparamUtility.ts +54 -0
  72. package/project/.sdk/tm/ts/src/utility/FullurlUtility.ts +46 -0
  73. package/project/.sdk/tm/ts/src/utility/HeadersUtility.ts +24 -0
  74. package/project/.sdk/tm/ts/src/utility/InitfeatureUtility.ts +13 -0
  75. package/project/.sdk/tm/ts/src/utility/JoinurlUtility.ts +15 -0
  76. package/project/.sdk/tm/ts/src/utility/MethodUtility.ts +25 -0
  77. package/project/.sdk/tm/ts/src/utility/OperatorUtility.ts +90 -0
  78. package/project/.sdk/tm/ts/src/utility/OptionsUtility.ts +72 -0
  79. package/project/.sdk/tm/ts/src/utility/ParamsUtility.ts +37 -0
  80. package/project/.sdk/tm/ts/src/utility/QueryUtility.ts +27 -0
  81. package/project/.sdk/tm/ts/src/utility/ReqformUtility.ts +33 -0
  82. package/project/.sdk/tm/ts/src/utility/RequestUtility.ts +66 -0
  83. package/project/.sdk/tm/ts/src/utility/ResbasicUtility.ts +34 -0
  84. package/project/.sdk/tm/ts/src/utility/ResbodyUtility.ts +19 -0
  85. package/project/.sdk/tm/ts/src/utility/ResformUtility.ts +36 -0
  86. package/project/.sdk/tm/ts/src/utility/ResheadersUtility.ts +23 -0
  87. package/project/.sdk/tm/ts/src/utility/ResponseUtility.ts +30 -0
  88. package/project/.sdk/tm/ts/src/utility/ResultUtility.ts +36 -0
  89. package/project/.sdk/tm/ts/src/utility/SpecUtility.ts +61 -0
  90. package/project/.sdk/tm/ts/src/utility/StructUtility.ts +2499 -0
  91. package/project/.sdk/tm/ts/src/utility/Utility.ts +88 -0
  92. package/project/.sdk/tm/ts/test/exists.test.ts +17 -0
  93. package/project/.sdk/tm/ts/test/runner.ts +402 -0
  94. package/project/.sdk/tm/ts/test/tsconfig.json +1 -1
  95. package/project/.sdk/tm/ts/test/utility/Custom.test.ts +62 -0
  96. package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +244 -0
  97. package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +678 -0
  98. package/project/.sdk/tm/ts/test/utility/index.ts +9 -0
  99. package/project/.sdk/tm/ts/test/utility.ts +86 -0
  100. package/src/action/action.ts +54 -0
  101. package/src/action/feature.ts +83 -47
  102. package/src/action/target.ts +173 -53
  103. package/src/cmp/Entity.ts +6 -0
  104. package/src/cmp/Feature.ts +6 -3
  105. package/src/cmp/Main.ts +4 -1
  106. package/src/sdkgen.ts +86 -11
  107. package/src/types.ts +33 -0
  108. package/src/utility.ts +5 -3
  109. package/project/.sdk/model/feature/limit.jsonic +0 -12
  110. package/project/.sdk/model/feature/page.jsonic +0 -9
  111. package/project/.sdk/model/feature/telemetry.jsonic +0 -10
  112. package/project/.sdk/src/cmp/ts/fragment/Entity.fragment.js +0 -79
  113. package/project/.sdk/src/cmp/ts/fragment/EntityCreateOp.fragment.js +0 -61
  114. package/project/.sdk/src/cmp/ts/fragment/EntityListOp.fragment.js +0 -57
  115. package/project/.sdk/src/cmp/ts/fragment/EntityLoadOp.fragment.js +0 -61
  116. package/project/.sdk/src/cmp/ts/fragment/EntityRemoveOp.fragment.js +0 -61
  117. package/project/.sdk/src/cmp/ts/fragment/EntityUpdateOp.fragment.js +0 -61
  118. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.js +0 -67
  119. package/project/.sdk/tm/ts/test/README.md~ +0 -2
@@ -0,0 +1,2499 @@
1
+ /* Copyright (c) 2025 Voxgig Ltd. MIT LICENSE. */
2
+
3
+ /* Voxgig Struct
4
+ * =============
5
+ *
6
+ * Utility functions to manipulate in-memory JSON-like data
7
+ * structures. These structures assumed to be composed of nested
8
+ * "nodes", where a node is a list or map, and has named or indexed
9
+ * fields. The general design principle is "by-example". Transform
10
+ * specifications mirror the desired output. This implementation is
11
+ * designed for porting to multiple language, and to be tolerant of
12
+ * undefined values.
13
+ *
14
+ * Main utilities
15
+ * - getpath: get the value at a key path deep inside an object.
16
+ * - merge: merge multiple nodes, overriding values in earlier nodes.
17
+ * - walk: walk a node tree, applying a function at each node and leaf.
18
+ * - inject: inject values from a data store into a new data structure.
19
+ * - transform: transform a data structure to an example structure.
20
+ * - validate: valiate a data structure against a shape specification.
21
+ *
22
+ * Minor utilities
23
+ * - isnode, islist, ismap, iskey, isfunc: identify value kinds.
24
+ * - isempty: undefined values, or empty nodes.
25
+ * - keysof: sorted list of node keys (ascending).
26
+ * - haskey: true if key value is defined.
27
+ * - clone: create a copy of a JSON-like data structure.
28
+ * - items: list entries of a map or list as [key, value] pairs.
29
+ * - getprop: safely get a property value by key.
30
+ * - setprop: safely set a property value by key.
31
+ * - stringify: human-friendly string version of a value.
32
+ * - escre: escape a regular expresion string.
33
+ * - escurl: escape a url.
34
+ * - joinurl: join parts of a url, merging forward slashes.
35
+ *
36
+ * This set of functions and supporting utilities is designed to work
37
+ * uniformly across many languages, meaning that some code that may be
38
+ * functionally redundant in specific languages is still retained to
39
+ * keep the code human comparable.
40
+ *
41
+ * NOTE: In this code JSON nulls are in general *not* considered the
42
+ * same as the undefined value in the given language. However most
43
+ * JSON parsers do use the undefined value to represent JSON
44
+ * null. This is ambiguous as JSON null is a separate value, not an
45
+ * undefined value. You should convert such values to a special value
46
+ * to represent JSON null, if this ambiguity creates issues
47
+ * (thankfully in most APIs, JSON nulls are not used). For example,
48
+ * the unit tests use the string "__NULL__" where necessary.
49
+ *
50
+ */
51
+
52
+
53
+ // String constants are explicitly defined.
54
+
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'
60
+
61
+ // Special keys.
62
+ const S_BKEY = '`$KEY`'
63
+ const S_BANNO = '`$ANNO`'
64
+ const S_BEXACT = '`$EXACT`'
65
+
66
+ const S_DKEY = '$KEY'
67
+ const S_DTOP = '$TOP'
68
+ const S_DERRS = '$ERRS'
69
+ const S_DSPEC = '$SPEC'
70
+
71
+ // General strings.
72
+ const S_array = 'array'
73
+ const S_base = 'base'
74
+ const S_boolean = 'boolean'
75
+ const S_function = 'function'
76
+ const S_number = 'number'
77
+ const S_object = 'object'
78
+ const S_string = 'string'
79
+ const S_null = 'null'
80
+ const S_key = 'key'
81
+ const S_MT = ''
82
+ const S_BT = '`'
83
+ const S_DS = '$'
84
+ const S_DT = '.'
85
+ const S_CN = ':'
86
+ const S_FS = '/'
87
+ const S_OS = '['
88
+ const S_CS = ']'
89
+ const S_SP = ' '
90
+ const S_KEY = 'KEY'
91
+ const S_VIZ = ': '
92
+
93
+
94
+ // The standard undefined value for this language.
95
+ const UNDEF = undefined
96
+
97
+ // Private marker to indicate a skippable value.
98
+ const SKIP = { '`$SKIP`': true }
99
+
100
+ // Regular expression constants
101
+ const R_INTEGER_KEY = /^[-0-9]+$/ // Match integer keys (including <0).
102
+ const R_ESCAPE_REGEXP = /[.*+?^${}()|[\]\\]/g // Chars that need escaping in regexp.
103
+ const R_TRAILING_SLASH = /\/+$/ // Trailing slashes in URLs.
104
+ const R_LEADING_TRAILING_SLASH = /([^\/])\/+/ // Multiple slashes in URL middle.
105
+ const R_LEADING_SLASH = /^\/+/ // Leading slashes in URLs.
106
+ const R_QUOTES = /"/g // Double quotes for removal.
107
+ const R_DOT = /\./g // Dots in path strings.
108
+ const R_FUNCTION_REF = /^`\$FUNCTION:([0-9]+)`$/ // Function reference in clone.
109
+ const R_META_PATH = /^([^$]+)\$([=~])(.+)$/ // Meta path syntax.
110
+ const R_DOUBLE_DOLLAR = /\$\$/g // Double dollar escape sequence.
111
+ const R_TRANSFORM_NAME = /`\$([A-Z]+)`/g // Transform command names.
112
+ const R_INJECTION_FULL = /^`(\$[A-Z]+|[^`]*)[0-9]*`$/ // Full string injection pattern.
113
+ const R_BT_ESCAPE = /\$BT/g // Backtick escape sequence.
114
+ const R_DS_ESCAPE = /\$DS/g // Dollar sign escape sequence.
115
+ const R_INJECTION_PARTIAL = /`([^`]+)`/g // Partial string injection pattern.
116
+
117
+
118
+ // Keys are strings for maps, or integers for lists.
119
+ type PropKey = string | number
120
+
121
+ // Type that can be indexed by both string and number keys.
122
+ type Indexable = { [key: string]: any } & { [key: number]: any }
123
+
124
+
125
+ // For each key in a node (map or list), perform value injections in
126
+ // three phases: on key value, before child, and then on key value again.
127
+ // This mode is passed via the Injection structure.
128
+ type InjectMode = 'key:pre' | 'key:post' | 'val'
129
+
130
+
131
+ // Handle value injections using backtick escape sequences:
132
+ // - `a.b.c`: insert value at {a:{b:{c:1}}}
133
+ // - `$FOO`: apply transform FOO
134
+ type Injector = (
135
+ inj: Injection, // Injection state.
136
+ val: any, // Injection value specification.
137
+ ref: string, // Original injection reference string.
138
+ store: any, // Current source root value.
139
+ ) => any
140
+
141
+
142
+ // Apply a custom modification to injections.
143
+ type Modify = (
144
+ val: any, // Value.
145
+ key?: PropKey, // Value key, if any,
146
+ parent?: any, // Parent node, if any.
147
+ inj?: Injection, // Injection state, if any.
148
+ store?: any, // Store, if any
149
+ ) => void
150
+
151
+
152
+ // Function applied to each node and leaf when walking a node structure depth first.
153
+ // For {a:{b:1}} the call sequence args will be: b, 1, {b:1}, [a,b].
154
+ type WalkApply = (
155
+ // Map keys are strings, list keys are numbers, top key is UNDEF
156
+ key: string | number | undefined,
157
+ val: any,
158
+ parent: any,
159
+ path: string[]
160
+ ) => any
161
+
162
+
163
+
164
+ // Value is a node - defined, and a map (hash) or list (array).
165
+ // NOTE: typescript
166
+ // things
167
+ function isnode(val: any): val is Indexable {
168
+ return null != val && S_object == typeof val
169
+ }
170
+
171
+
172
+ // Value is a defined map (hash) with string keys.
173
+ function ismap(val: any): val is { [key: string]: any } {
174
+ return null != val && S_object == typeof val && !Array.isArray(val)
175
+ }
176
+
177
+
178
+ // Value is a defined list (array) with integer keys (indexes).
179
+ function islist(val: any): val is any[] {
180
+ return Array.isArray(val)
181
+ }
182
+
183
+
184
+ // Value is a defined string (non-empty) or integer key.
185
+ function iskey(key: any): key is PropKey {
186
+ const keytype = typeof key
187
+ return (S_string === keytype && S_MT !== key) || S_number === keytype
188
+ }
189
+
190
+
191
+ // Check for an "empty" value - undefined, empty string, array, object.
192
+ function isempty(val: any) {
193
+ return null == val || S_MT === val ||
194
+ (Array.isArray(val) && 0 === val.length) ||
195
+ (S_object === typeof val && 0 === Object.keys(val).length)
196
+ }
197
+
198
+
199
+ // Value is a function.
200
+ function isfunc(val: any): val is Function {
201
+ return S_function === typeof val
202
+ }
203
+
204
+
205
+ // The integer size of the value. For arrays and strings, the length,
206
+ // for numbers, the integer part, for boolean, true is 1 and falso 0, for all other values, 0.
207
+ function size(val: any): number {
208
+ if (islist(val)) {
209
+ return val.length
210
+ }
211
+ else if (ismap(val)) {
212
+ return Object.keys(val).length
213
+ }
214
+
215
+ const valtype = typeof val
216
+
217
+ if (S_string == valtype) {
218
+ return val.length
219
+ }
220
+ else if (S_number == typeof val) {
221
+ return Math.floor(val)
222
+ }
223
+ else if (S_boolean == typeof val) {
224
+ return true === val ? 1 : 0
225
+ }
226
+ else {
227
+ return 0
228
+ }
229
+ }
230
+
231
+
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 {
237
+ if (S_number === typeof val) {
238
+ start = null == start || S_number !== typeof start ? Number.MIN_SAFE_INTEGER : start
239
+ end = (null == end || S_number !== typeof end ? Number.MAX_SAFE_INTEGER : end) - 1
240
+ return Math.min(Math.max(val as number, start), end) as V
241
+ }
242
+
243
+ const vlen = size(val)
244
+
245
+ if (null != end && null == start) {
246
+ start = 0
247
+ }
248
+
249
+ if (null != start) {
250
+ if (start < 0) {
251
+ end = vlen + start
252
+ if (end < 0) {
253
+ end = 0
254
+ }
255
+ start = 0
256
+ }
257
+
258
+ else if (null != end) {
259
+ if (end < 0) {
260
+ end = vlen + end
261
+ if (end < 0) {
262
+ end = 0
263
+ }
264
+ }
265
+ else if (vlen < end) {
266
+ end = vlen
267
+ }
268
+ }
269
+
270
+ else {
271
+ end = vlen
272
+ }
273
+
274
+ if (vlen < start) {
275
+ start = vlen
276
+ }
277
+
278
+ if (-1 < start && start <= end && end <= vlen) {
279
+ if (islist(val)) {
280
+ val = val.slice(start, end) as V
281
+ }
282
+ else if (S_string === typeof val) {
283
+ val = (val as string).substring(start, end) as V
284
+ }
285
+ }
286
+ else {
287
+ if (islist(val)) {
288
+ val = [] as V
289
+ }
290
+ else if (S_string === typeof val) {
291
+ val = S_MT as V
292
+ }
293
+ }
294
+ }
295
+
296
+ return val
297
+ }
298
+
299
+
300
+ function pad(str: any, padding?: number, padchar?: string): string {
301
+ str = S_string === typeof str ? str : stringify(str)
302
+ padding = null == padding ? 44 : padding
303
+ padchar = null == padchar ? S_SP : ((padchar + S_SP)[0])
304
+ return -1 < padding ? str.padEnd(padding, padchar) : str.padStart(0 - padding, padchar)
305
+ }
306
+
307
+
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
314
+ }
315
+
316
+ const type = typeof value
317
+
318
+ if (Array.isArray(value)) {
319
+ return S_array
320
+ }
321
+
322
+ if (type === 'object') {
323
+ return S_object
324
+ }
325
+
326
+ return type
327
+ }
328
+
329
+
330
+ // Get a list element. The key should be an integer, or a string
331
+ // that can parse to an integer only. Negative integers count from the end of the list.
332
+ function getelem(val: any, key: any, alt?: any) {
333
+ let out = UNDEF
334
+
335
+ if (UNDEF === val || UNDEF === key) {
336
+ return alt
337
+ }
338
+
339
+ if (islist(val)) {
340
+ let nkey = parseInt(key)
341
+ if (Number.isInteger(nkey) && ('' + key).match(R_INTEGER_KEY)) {
342
+ if (nkey < 0) {
343
+ key = val.length + nkey
344
+ }
345
+ out = val[key]
346
+ }
347
+ }
348
+
349
+ if (UNDEF === out) {
350
+ return alt
351
+ }
352
+
353
+ return out
354
+ }
355
+
356
+
357
+ // Safely get a property of a node. Undefined arguments return undefined.
358
+ // If the key is not found, return the alternative value, if any.
359
+ function getprop(val: any, key: any, alt?: any) {
360
+ let out = alt
361
+
362
+ if (UNDEF === val || UNDEF === key) {
363
+ return alt
364
+ }
365
+
366
+ if (isnode(val)) {
367
+ out = val[key]
368
+ }
369
+
370
+ if (UNDEF === out) {
371
+ return alt
372
+ }
373
+
374
+ return out
375
+ }
376
+
377
+
378
+ // Convert different types of keys to string representation.
379
+ // String keys are returned as is.
380
+ // Number keys are converted to strings.
381
+ // Floats are truncated to integers.
382
+ // Booleans, objects, arrays, null, undefined all return empty string.
383
+ function strkey(key: any = UNDEF): string {
384
+ if (UNDEF === key) {
385
+ return S_MT
386
+ }
387
+
388
+ if (typeof key === S_string) {
389
+ return key
390
+ }
391
+
392
+ if (typeof key === S_boolean) {
393
+ return S_MT
394
+ }
395
+
396
+ if (typeof key === S_number) {
397
+ return key % 1 === 0 ? String(key) : String(Math.floor(key))
398
+ }
399
+
400
+ return S_MT
401
+ }
402
+
403
+
404
+ // Sorted keys of a map, or indexes of a list.
405
+ function keysof(val: any): string[] {
406
+ return !isnode(val) ? [] :
407
+ ismap(val) ? Object.keys(val).sort() : (val as any).map((_n: any, i: number) => S_MT + i)
408
+ }
409
+
410
+
411
+ // Value of property with name key in node val is defined.
412
+ function haskey(val: any, key: any) {
413
+ return UNDEF !== getprop(val, key)
414
+ }
415
+
416
+
417
+ // 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]])
421
+ }
422
+
423
+
424
+ // Escape regular expression.
425
+ function escre(s: string) {
426
+ s = null == s ? S_MT : s
427
+ return s.replace(R_ESCAPE_REGEXP, '\\$&')
428
+ }
429
+
430
+
431
+ // Escape URLs.
432
+ function escurl(s: string) {
433
+ s = null == s ? S_MT : s
434
+ return encodeURIComponent(s)
435
+ }
436
+
437
+
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)
448
+ }
449
+
450
+
451
+ // Output JSON in a "standard" format, with 2 space indents, each property on a new line,
452
+ // and spaces after {[: and before ]}. Any "wierd" values (NaN, etc) are output as null.
453
+ // In general, the behaivor of of JavaScript's JSON.stringify(val,null,2) is followed.
454
+ function jsonify(val: any, flags?: { indent?: number, offset?: number }) {
455
+ let str = S_null
456
+ 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
461
+ }
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')
469
+ }
470
+ }
471
+
472
+ return str
473
+ }
474
+
475
+
476
+ // Safely stringify a value for humans (NOT JSON!).
477
+ function stringify(val: any, maxlen?: number, pretty?: any): string {
478
+ let valstr = S_MT
479
+ pretty = !!pretty
480
+
481
+ if (UNDEF === val) {
482
+ return pretty ? '<>' : valstr
483
+ }
484
+
485
+ if (S_string === typeof val) {
486
+ valstr = val
487
+ }
488
+ else {
489
+ try {
490
+ valstr = JSON.stringify(val, function(_key: string, val: any) {
491
+ if (
492
+ val !== null &&
493
+ typeof val === "object" &&
494
+ !Array.isArray(val)
495
+ ) {
496
+ const sortedObj: any = {}
497
+ for (const k of Object.keys(val).sort()) {
498
+ sortedObj[k] = val[k]
499
+ }
500
+ return sortedObj
501
+ }
502
+ return val
503
+ })
504
+ valstr = valstr.replace(R_QUOTES, S_MT)
505
+ }
506
+ catch (err: any) {
507
+ valstr = S_MT + val
508
+ }
509
+ }
510
+
511
+ if (null != maxlen && -1 < maxlen) {
512
+ let js = valstr.substring(0, maxlen)
513
+ valstr = maxlen < valstr.length ? (js.substring(0, maxlen - 3) + '...') : valstr
514
+ }
515
+
516
+ if (pretty) {
517
+ // 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`),
520
+ r = '\x1b[0m', d = 0, o = c[0], t = o
521
+ for (const ch of valstr) {
522
+ if (ch === '{' || ch === '[') {
523
+ d++; o = c[d % c.length]; t += o + ch
524
+ } else if (ch === '}' || ch === ']') {
525
+ t += o + ch; d--; o = c[d % c.length]
526
+ } else {
527
+ t += o + ch
528
+ }
529
+ }
530
+ return t + r
531
+
532
+ }
533
+
534
+ return valstr
535
+ }
536
+
537
+
538
+ // Build a human friendly path string.
539
+ function pathify(val: any, startin?: number, endin?: number) {
540
+ let pathstr: string | undefined = UNDEF
541
+
542
+ let path: any[] | undefined = islist(val) ? val :
543
+ S_string == typeof val ? [val] :
544
+ S_number == typeof val ? [val] :
545
+ UNDEF
546
+
547
+ const start = null == startin ? 0 : -1 < startin ? startin : 0
548
+ const end = null == endin ? 0 : -1 < endin ? endin : 0
549
+
550
+ if (UNDEF != path && 0 <= start) {
551
+ path = slice(path, start, path.length - end)
552
+ if (0 === path.length) {
553
+ pathstr = '<root>'
554
+ }
555
+ 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)
563
+ }
564
+ }
565
+
566
+ if (UNDEF === pathstr) {
567
+ pathstr = '<unknown-path' + (UNDEF === val ? S_MT : S_CN + stringify(val, 47)) + '>'
568
+ }
569
+
570
+ return pathstr
571
+ }
572
+
573
+
574
+ // Clone a JSON-like data structure.
575
+ // NOTE: function value references are copied, *not* cloned.
576
+ function clone(val: any): any {
577
+ const refs: any[] = []
578
+ const replacer: any = (_k: any, v: any) => S_function === typeof v ?
579
+ (refs.push(v), '`$FUNCTION:' + (refs.length - 1) + '`') : v
580
+ 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)
583
+ }
584
+
585
+
586
+ // Define a JSON Object using function arguments.
587
+ function jo(...kv: any[]): Record<string, any> {
588
+ const kvsize = size(kv)
589
+ const o: any = {}
590
+ for (let i = 0; i < kvsize; i += 2) {
591
+ let k = getprop(kv, i, '$KEY' + i)
592
+ k = 'string' === typeof k ? k : stringify(k)
593
+ o[k] = getprop(kv, i + 1, null)
594
+ }
595
+ return o
596
+ }
597
+
598
+
599
+ // Define a JSON Array using function arguments.
600
+ function ja(...v: any[]): any[] {
601
+ const vsize = size(v)
602
+ const a: any = new Array(vsize)
603
+ for (let i = 0; i < vsize; i++) {
604
+ a[i] = getprop(v, i, null)
605
+ }
606
+ return a
607
+ }
608
+
609
+
610
+
611
+ // Safely delete a property from an object or array element.
612
+ // Undefined arguments and invalid keys are ignored.
613
+ // Returns the (possibly modified) parent.
614
+ // For objects, the property is deleted using the delete operator.
615
+ // For arrays, the element at the index is removed and remaining elements are shifted down.
616
+ function delprop<PARENT>(parent: PARENT, key: any): PARENT {
617
+ if (!iskey(key)) {
618
+ return parent
619
+ }
620
+
621
+ if (ismap(parent)) {
622
+ // key = S_MT + key
623
+ key = strkey(key)
624
+ delete (parent as any)[key]
625
+ }
626
+ else if (islist(parent)) {
627
+ // Ensure key is an integer.
628
+ let keyI = +key
629
+
630
+ if (isNaN(keyI)) {
631
+ return parent
632
+ }
633
+
634
+ keyI = Math.floor(keyI)
635
+
636
+ // 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++) {
639
+ parent[pI] = parent[pI + 1]
640
+ }
641
+ parent.length = parent.length - 1
642
+ }
643
+ }
644
+
645
+ return parent
646
+ }
647
+
648
+
649
+ // Safely set a property. Undefined arguments and invalid keys are ignored.
650
+ // Returns the (possibly modified) parent.
651
+ // If the parent is a list, and the key is negative, prepend the value.
652
+ // NOTE: If the key is above the list size, append the value; below, prepend.
653
+ function setprop<PARENT>(parent: PARENT, key: any, val: any): PARENT {
654
+ if (!iskey(key)) {
655
+ return parent
656
+ }
657
+
658
+ if (ismap(parent)) {
659
+ key = S_MT + key
660
+ const pany = parent as any
661
+ pany[key] = val
662
+ }
663
+ else if (islist(parent)) {
664
+ // Ensure key is an integer.
665
+ let keyI = +key
666
+
667
+ if (isNaN(keyI)) {
668
+ return parent
669
+ }
670
+
671
+ keyI = Math.floor(keyI)
672
+
673
+ // Set or append value at position keyI, or append if keyI out of bounds.
674
+ if (0 <= keyI) {
675
+ parent[parent.length < keyI ? parent.length : keyI] = val
676
+ }
677
+
678
+ // Prepend value if keyI is negative
679
+ else {
680
+ parent.unshift(val)
681
+ }
682
+ }
683
+
684
+ return parent
685
+ }
686
+
687
+
688
+ // Walk a data structure depth first, applying a function to each value.
689
+ function walk(
690
+ // These arguments are the public interface.
691
+ val: any,
692
+ apply: WalkApply,
693
+
694
+ // These areguments are used for recursive state.
695
+ key?: string | number,
696
+ parent?: any,
697
+ path?: string[]
698
+ ): 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]))
702
+ }
703
+ }
704
+
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 || [])
708
+ }
709
+
710
+
711
+ // Merge a list of values into each other. Later values have
712
+ // precedence. Nodes override scalars. Node kinds (list or map)
713
+ // override each other, and do *not* merge. The first element is
714
+ // modified.
715
+ function merge(val: any): any {
716
+ let out: any = UNDEF
717
+
718
+ // Handle edge cases.
719
+ if (!islist(val)) {
720
+ return val
721
+ }
722
+
723
+ const list = val as any[]
724
+ const lenlist = list.length
725
+
726
+ if (0 === lenlist) {
727
+ return UNDEF
728
+ }
729
+ else if (1 === lenlist) {
730
+ return list[0]
731
+ }
732
+
733
+ // Merge a list of values.
734
+ out = getprop(list, 0, {})
735
+
736
+ for (let oI = 1; oI < lenlist; oI++) {
737
+ let obj = list[oI]
738
+
739
+ if (!isnode(obj)) {
740
+ // Nodes win.
741
+ out = obj
742
+ }
743
+ else {
744
+ // Nodes win, also over nodes of a different kind.
745
+ if (!isnode(out) || (ismap(obj) && islist(out)) || (islist(obj) && ismap(out))) {
746
+ out = obj
747
+ }
748
+ else {
749
+ // Node stack. walking down the current obj.
750
+ let cur: any[] = [out]
751
+ let cI = 0
752
+
753
+ function merger(
754
+ key: string | number | undefined,
755
+ val: any,
756
+ parent: any,
757
+ path: string[]
758
+ ) {
759
+ if (null == key) {
760
+ return val
761
+ }
762
+
763
+ // Get the curent value at the current path in obj.
764
+ // NOTE: this is not exactly efficient, and should be optimised.
765
+ let lenpath = path.length
766
+ cI = lenpath - 1
767
+ if (UNDEF === cur[cI]) {
768
+ cur[cI] = getpath(out, slice(path, 0, lenpath - 1))
769
+ }
770
+
771
+ // Create node if needed.
772
+ if (!isnode(cur[cI])) {
773
+ cur[cI] = islist(parent) ? [] : {}
774
+ }
775
+
776
+ // Node child is just ahead of us on the stack, since
777
+ // `walk` traverses leaves before nodes.
778
+ if (isnode(val) && !isempty(val)) {
779
+ setprop(cur[cI], key, cur[cI + 1])
780
+ cur[cI + 1] = UNDEF
781
+ }
782
+
783
+ // Scalar child.
784
+ else {
785
+ setprop(cur[cI], key, val)
786
+ }
787
+
788
+ return val
789
+ }
790
+
791
+ // Walk overriding node, creating paths in output as needed.
792
+ walk(obj, merger)
793
+ }
794
+ }
795
+ }
796
+
797
+ return out
798
+ }
799
+
800
+
801
+ function getpath(store: any, path: number | string | string[], injdef?: Partial<Injection>) {
802
+
803
+ // Operate on a string array.
804
+ const parts = islist(path) ? path :
805
+ 'string' === typeof path ? path.split(S_DT) :
806
+ 'number' === typeof path ? [strkey(path)] : UNDEF
807
+
808
+ if (UNDEF === parts) {
809
+ return UNDEF
810
+ }
811
+
812
+ // let root = store
813
+ let val = store
814
+ const base = getprop(injdef, S_base)
815
+ const src = getprop(store, base, store)
816
+ const numparts = size(parts)
817
+ const dparent = getprop(injdef, 'dparent')
818
+
819
+ // An empty path (incl empty string) just finds the store.
820
+ if (null == path || null == store || (1 === numparts && S_MT === parts[0])) {
821
+ val = src
822
+ }
823
+ else if (0 < numparts) {
824
+
825
+ // Check for $ACTIONs
826
+ if (1 === numparts) {
827
+ val = getprop(store, parts[0])
828
+ }
829
+
830
+ if (!isfunc(val)) {
831
+ val = src
832
+
833
+ const m = parts[0].match(R_META_PATH)
834
+ if (m && injdef && injdef.meta) {
835
+ val = getprop(injdef.meta, m[1])
836
+ parts[0] = m[3]
837
+ }
838
+
839
+ const dpath = getprop(injdef, 'dpath')
840
+
841
+ for (let pI = 0; UNDEF !== val && pI < parts.length; pI++) {
842
+ let part = parts[pI]
843
+
844
+ if (injdef && S_DKEY === part) {
845
+ part = getprop(injdef, S_key)
846
+ }
847
+ else if (injdef && part.startsWith('$GET:')) {
848
+ // $GET:path$ -> get store value, use as path part (string)
849
+ part = stringify(getpath(src, part.substring(5, part.length - 1)))
850
+ }
851
+ else if (injdef && part.startsWith('$REF:')) {
852
+ // $REF:refpath$ -> get spec value, use as path part (string)
853
+ part = stringify(getpath(getprop(store, S_DSPEC), part.substring(5, part.length - 1)))
854
+ }
855
+ else if (injdef && part.startsWith('$META:')) {
856
+ // $META:metapath$ -> get meta value, use as path part (string)
857
+ part = stringify(getpath(getprop(injdef, 'meta'), part.substring(6, part.length - 1)))
858
+ }
859
+
860
+ // $$ escapes $
861
+ part = part.replace(R_DOUBLE_DOLLAR, '$')
862
+
863
+ if (S_MT === part) {
864
+
865
+ let ascends = 0
866
+ while (S_MT === parts[1 + pI]) {
867
+ ascends++
868
+ pI++
869
+ }
870
+
871
+ if (injdef && 0 < ascends) {
872
+ if (pI === parts.length - 1) {
873
+ ascends--
874
+ }
875
+
876
+ if (0 === ascends) {
877
+ val = dparent
878
+ }
879
+ else {
880
+ const fullpath = slice(dpath, 0 - ascends).concat(parts.slice(pI + 1))
881
+
882
+ if (ascends <= size(dpath)) {
883
+ val = getpath(store, fullpath)
884
+ }
885
+ else {
886
+ val = UNDEF
887
+ }
888
+ break
889
+ }
890
+ }
891
+ else {
892
+ val = dparent
893
+ }
894
+ }
895
+ else {
896
+ val = getprop(val, part)
897
+ }
898
+ }
899
+ }
900
+ }
901
+
902
+ // Inj may provide a custom handler to modify found value.
903
+ const handler = getprop(injdef, 'handler')
904
+ if (null != injdef && isfunc(handler)) {
905
+ const ref = pathify(path)
906
+ val = handler(injdef, val, ref, store)
907
+ }
908
+
909
+ return val
910
+ }
911
+
912
+
913
+
914
+ // Inject values from a data store into a node recursively, resolving
915
+ // paths against the store, or current if they are local. THe modify
916
+ // argument allows custom modification of the result. The inj
917
+ // (Injection) argument is used to maintain recursive state.
918
+ function inject(
919
+ val: any,
920
+ store: any,
921
+ injdef?: Partial<Injection>,
922
+ ) {
923
+ const valtype = typeof val
924
+ let inj: Injection = injdef as Injection
925
+
926
+ // Create state if at root of injection. The input value is placed
927
+ // inside a virtual parent holder to simplify edge cases.
928
+ if (UNDEF === injdef || null == injdef.mode) {
929
+ // Set up state assuming we are starting in the virtual parent.
930
+ inj = new Injection(val, { [S_DTOP]: val })
931
+ inj.dparent = store
932
+ inj.errs = getprop(store, S_DERRS, [])
933
+ inj.meta.__d = 0
934
+
935
+ if (UNDEF !== injdef) {
936
+ inj.modify = null == injdef.modify ? inj.modify : injdef.modify
937
+ inj.extra = null == injdef.extra ? inj.extra : injdef.extra
938
+ inj.meta = null == injdef.meta ? inj.meta : injdef.meta
939
+ inj.handler = null == injdef.handler ? inj.handler : injdef.handler
940
+ }
941
+ }
942
+
943
+ inj.descend()
944
+
945
+ // Descend into node.
946
+ if (isnode(val)) {
947
+
948
+ // Keys are sorted alphanumerically to ensure determinism.
949
+ // Injection transforms ($FOO) are processed *after* other keys.
950
+ // NOTE: the optional digits suffix of the transform can thus be
951
+ // used to order the transforms.
952
+ let nodekeys = ismap(val) ? [
953
+ ...Object.keys(val).filter(k => !k.includes(S_DS)).sort(),
954
+ ...Object.keys(val).filter(k => k.includes(S_DS)).sort(),
955
+ ] : (val as any).map((_n: any, i: number) => i)
956
+
957
+
958
+ // Each child key-value pair is processed in three injection phases:
959
+ // 1. inj.mode='key:pre' - Key string is injected, returning a possibly altered key.
960
+ // 2. inj.mode='val' - The child value is injected.
961
+ // 3. inj.mode='key:post' - Key string is injected again, allowing child mutation.
962
+ for (let nkI = 0; nkI < nodekeys.length; nkI++) {
963
+
964
+ const childinj = inj.child(nkI, nodekeys)
965
+ const nodekey = childinj.key
966
+ childinj.mode = S_MKEYPRE
967
+
968
+ // Peform the key:pre mode injection on the child key.
969
+ const prekey = _injectstr(nodekey, store, childinj)
970
+
971
+ // The injection may modify child processing.
972
+ nkI = childinj.keyI
973
+ nodekeys = childinj.keys
974
+
975
+ // Prevent further processing by returning an undefined prekey
976
+ if (UNDEF !== prekey) {
977
+ childinj.val = getprop(val, prekey)
978
+ childinj.mode = S_MVAL as InjectMode
979
+
980
+ // Perform the val mode injection on the child value.
981
+ // NOTE: return value is not used.
982
+ inject(childinj.val, store, childinj)
983
+
984
+ // The injection may modify child processing.
985
+ nkI = childinj.keyI
986
+ nodekeys = childinj.keys
987
+
988
+ // Peform the key:post mode injection on the child key.
989
+ childinj.mode = S_MKEYPOST as InjectMode
990
+ _injectstr(nodekey, store, childinj)
991
+
992
+ // The injection may modify child processing.
993
+ nkI = childinj.keyI
994
+ nodekeys = childinj.keys
995
+ }
996
+ }
997
+ }
998
+
999
+ // Inject paths into string scalars.
1000
+ else if (S_string === valtype) {
1001
+ inj.mode = S_MVAL as InjectMode
1002
+ val = _injectstr(val, store, inj)
1003
+ if (SKIP !== val) {
1004
+ inj.setval(val)
1005
+ }
1006
+ }
1007
+
1008
+ // Custom modification.
1009
+ if (inj.modify && SKIP !== val) {
1010
+ let mkey = inj.key
1011
+ let mparent = inj.parent
1012
+ let mval = getprop(mparent, mkey)
1013
+
1014
+ inj.modify(
1015
+ mval,
1016
+ mkey,
1017
+ mparent,
1018
+ inj,
1019
+ store
1020
+ )
1021
+ }
1022
+
1023
+ inj.val = val
1024
+
1025
+ // Original val reference may no longer be correct.
1026
+ // This return value is only used as the top level result.
1027
+ return getprop(inj.parent, S_DTOP)
1028
+ }
1029
+
1030
+
1031
+ // The transform_* functions are special command inject handlers (see Injector).
1032
+
1033
+ // Delete a key from a map or list.
1034
+ const transform_DELETE: Injector = (inj: Injection) => {
1035
+ inj.setval(UNDEF)
1036
+ return UNDEF
1037
+ }
1038
+
1039
+
1040
+ // Copy value from source data.
1041
+ const transform_COPY: Injector = (inj: Injection, _val: any) => {
1042
+ const { mode, key } = inj
1043
+
1044
+ let out = key
1045
+ if (!mode.startsWith(S_MKEY)) {
1046
+ out = getprop(inj.dparent, key)
1047
+ inj.setval(out)
1048
+ }
1049
+
1050
+ return out
1051
+ }
1052
+
1053
+
1054
+ // As a value, inject the key of the parent node.
1055
+ // As a key, defined the name of the key property in the source object.
1056
+ const transform_KEY: Injector = (inj: Injection) => {
1057
+ const { mode, path, parent } = inj
1058
+
1059
+ // Do nothing in val mode.
1060
+ if (S_MVAL !== mode) {
1061
+ return UNDEF
1062
+ }
1063
+
1064
+ // Key is defined by $KEY meta property.
1065
+ const keyspec = getprop(parent, S_BKEY)
1066
+ if (UNDEF !== keyspec) {
1067
+ delprop(parent, S_BKEY)
1068
+ return getprop(inj.dparent, keyspec)
1069
+ }
1070
+
1071
+ // Key is defined within general purpose $META object.
1072
+ return getprop(getprop(parent, S_BANNO), S_KEY, getprop(path, path.length - 2))
1073
+ }
1074
+
1075
+
1076
+ // Annotatea node. Does nothing itself, just used by
1077
+ // other injectors, and is removed when called.
1078
+ const transform_ANNO: Injector = (inj: Injection) => {
1079
+ const { parent } = inj
1080
+ delprop(parent, S_BANNO)
1081
+ return UNDEF
1082
+ }
1083
+
1084
+
1085
+ // Merge a list of objects into the current object.
1086
+ // Must be a key in an object. The value is merged over the current object.
1087
+ // If the value is an array, the elements are first merged using `merge`.
1088
+ // If the value is the empty string, merge the top level store.
1089
+ // Format: { '`$MERGE`': '`source-path`' | ['`source-paths`', ...] }
1090
+ const transform_MERGE: Injector = (inj: Injection) => {
1091
+ const { mode, key, parent } = inj
1092
+
1093
+ // Ensures $MERGE is removed from parent list (val mode).
1094
+ let out: any = UNDEF
1095
+
1096
+ if (S_MKEYPRE === mode) {
1097
+ out = key
1098
+ }
1099
+
1100
+ // Operate after child values have been transformed.
1101
+ else if (S_MKEYPOST === mode) {
1102
+ out = key
1103
+
1104
+ let args = getprop(parent, key)
1105
+ args = Array.isArray(args) ? args : [args]
1106
+
1107
+ // Remove the $MERGE command from a parent map.
1108
+ inj.setval(UNDEF)
1109
+
1110
+ // Literals in the parent have precedence, but we still merge onto
1111
+ // the parent object, so that node tree references are not changed.
1112
+ const mergelist = [parent, ...args, clone(parent)]
1113
+
1114
+ merge(mergelist)
1115
+
1116
+ // return key
1117
+ }
1118
+
1119
+ return out
1120
+ }
1121
+
1122
+
1123
+ // Convert a node to a list.
1124
+ // Format: ['`$EACH`', '`source-path-of-node`', child-template]
1125
+ const transform_EACH: Injector = (
1126
+ inj: Injection,
1127
+ _val: any,
1128
+ _ref: string,
1129
+ store: any
1130
+ ) => {
1131
+
1132
+ // Remove arguments to avoid spurious processing.
1133
+ if (null != inj.keys) {
1134
+ inj.keys.length = 1
1135
+ }
1136
+
1137
+ if (S_MVAL !== inj.mode) {
1138
+ return UNDEF
1139
+ }
1140
+
1141
+ // Get arguments: ['`$EACH`', 'source-path', child-template].
1142
+ const srcpath = getprop(inj.parent, 1)
1143
+ const child = clone(getprop(inj.parent, 2))
1144
+
1145
+ // Source data.
1146
+ const srcstore = getprop(store, inj.base, store)
1147
+
1148
+ const src = getpath(srcstore, srcpath, inj)
1149
+
1150
+ // Create parallel data structures:
1151
+ // source entries :: child templates
1152
+ let tcur: any = []
1153
+ let tval: any = []
1154
+
1155
+ const tkey = inj.path[inj.path.length - 2]
1156
+ const target = inj.nodes[inj.nodes.length - 2] || inj.nodes[inj.nodes.length - 1]
1157
+
1158
+ // Create clones of the child template for each value of the current soruce.
1159
+ if (islist(src)) {
1160
+ tval = src.map(() => clone(child))
1161
+ }
1162
+ else if (ismap(src)) {
1163
+ tval = Object.entries(src).map(n => ({
1164
+ ...clone(child),
1165
+
1166
+ // Make a note of the key for $KEY transforms.
1167
+ [S_BANNO]: { KEY: n[0] }
1168
+ }))
1169
+ }
1170
+
1171
+ let rval = []
1172
+
1173
+ if (0 < size(tval)) {
1174
+ tcur = null == src ? UNDEF : Object.values(src)
1175
+
1176
+ const ckey = getelem(inj.path, -2)
1177
+
1178
+ const tpath = slice(inj.path, -1)
1179
+ const dpath = [S_DTOP, ...srcpath.split(S_DT), '$:' + ckey]
1180
+
1181
+
1182
+ // Parent structure.
1183
+
1184
+ // const ckey = getelem(cpath, -1)
1185
+ tcur = { [ckey]: tcur }
1186
+
1187
+ if (1 < tpath.length) {
1188
+ const pkey = getelem(inj.path, -3, S_DTOP)
1189
+ // const pkey = getelem(cpath, -2, S_DTOP)
1190
+ tcur = { [pkey]: tcur }
1191
+ dpath.push('$:' + pkey)
1192
+ }
1193
+
1194
+ const tinj = inj.child(0, [ckey])
1195
+ tinj.path = tpath
1196
+ tinj.nodes = slice(inj.nodes, -1)
1197
+
1198
+ tinj.parent = getelem(tinj.nodes, -1)
1199
+ setprop(tinj.parent, ckey, tval)
1200
+
1201
+ tinj.val = tval
1202
+ tinj.dpath = dpath
1203
+ tinj.dparent = tcur
1204
+
1205
+ inject(tval, store, tinj)
1206
+ rval = tinj.val
1207
+ }
1208
+
1209
+ _updateAncestors(inj, target, tkey, rval)
1210
+
1211
+ // Prevent callee from damaging first list entry (since we are in `val` mode).
1212
+ return rval[0]
1213
+ }
1214
+
1215
+
1216
+ // Convert a node to a map.
1217
+ // Format: { '`$PACK`':['`source-path`', child-template]}
1218
+ const transform_PACK: Injector = (
1219
+ inj: Injection,
1220
+ _val: any,
1221
+ _ref: string,
1222
+ store: any
1223
+ ) => {
1224
+ const { mode, key, path, parent, nodes } = inj
1225
+
1226
+ // Defensive context checks.
1227
+ if (S_MKEYPRE !== mode || S_string !== typeof key || null == path || null == nodes) {
1228
+ return UNDEF
1229
+ }
1230
+
1231
+ // Get arguments.
1232
+ const args = parent[key]
1233
+ const srcpath = args[0] // Path to source data.
1234
+ const child = clone(args[1]) // Child template.
1235
+
1236
+ // Find key and target node.
1237
+ const keyprop = child[S_BKEY]
1238
+ const tkey = getelem(path, -2)
1239
+ const target = nodes[path.length - 2] || nodes[path.length - 1]
1240
+
1241
+ // Source data
1242
+ const srcstore = getprop(store, inj.base, store)
1243
+
1244
+ let src = getpath(srcstore, srcpath, inj)
1245
+
1246
+ // Prepare source as a list.
1247
+ src = islist(src) ? src :
1248
+ ismap(src) ? Object.entries(src)
1249
+ .reduce((a: any[], n: any) =>
1250
+ (n[1][S_BANNO] = { KEY: n[0] }, a.push(n[1]), a), []) :
1251
+ UNDEF
1252
+
1253
+ if (null == src) {
1254
+ return UNDEF
1255
+ }
1256
+
1257
+ // Get key if specified.
1258
+ let childkey: PropKey | undefined = getprop(child, S_BKEY)
1259
+ let keyname = UNDEF === childkey ? keyprop : childkey
1260
+ delprop(child, S_BKEY)
1261
+
1262
+ // Build parallel target object.
1263
+ let tval: any = {}
1264
+ tval = src.reduce((a: any, n: any) => {
1265
+ let kn = getprop(n, keyname)
1266
+ setprop(a, kn, clone(child))
1267
+ const nchild = getprop(a, kn)
1268
+ const mval = getprop(n, S_BANNO)
1269
+ if (UNDEF === mval) {
1270
+ delprop(nchild, S_BANNO)
1271
+ }
1272
+ else {
1273
+ setprop(nchild, S_BANNO, mval)
1274
+ }
1275
+ return a
1276
+ }, tval)
1277
+
1278
+ let rval = {}
1279
+
1280
+ if (0 < size(tval)) {
1281
+
1282
+ // Build parallel source object.
1283
+ let tcur: any = {}
1284
+ src.reduce((a: any, n: any) => {
1285
+ let kn = getprop(n, keyname)
1286
+ setprop(a, kn, n)
1287
+ return a
1288
+ }, tcur)
1289
+
1290
+ const tpath = slice(inj.path, -1)
1291
+
1292
+ const ckey = getelem(inj.path, -2)
1293
+ const dpath = [S_DTOP, ...srcpath.split(S_DT), '$:' + ckey]
1294
+
1295
+ tcur = { [ckey]: tcur }
1296
+
1297
+ if (1 < tpath.length) {
1298
+ const pkey = getelem(inj.path, -3, S_DTOP)
1299
+ tcur = { [pkey]: tcur }
1300
+ dpath.push('$:' + pkey)
1301
+ }
1302
+
1303
+ const tinj = inj.child(0, [ckey])
1304
+ tinj.path = tpath
1305
+ tinj.nodes = slice(inj.nodes, -1)
1306
+
1307
+ // tinj.parent = tcur
1308
+ tinj.parent = getelem(tinj.nodes, -1)
1309
+ tinj.val = tval
1310
+
1311
+ tinj.dpath = dpath
1312
+ tinj.dparent = tcur
1313
+
1314
+ inject(tval, store, tinj)
1315
+ rval = tinj.val
1316
+ }
1317
+
1318
+ _updateAncestors(inj, target, tkey, rval)
1319
+
1320
+ // Drop transform key.
1321
+ return UNDEF
1322
+ }
1323
+
1324
+
1325
+ // TODO: not found ref should removed key (setprop UNDEF)
1326
+ // Reference original spec (enables recursice transformations)
1327
+ // Format: ['`$REF`', '`spec-path`']
1328
+ const transform_REF: Injector = (
1329
+ inj: Injection,
1330
+ val: any,
1331
+ _ref: string,
1332
+ store: any
1333
+ ) => {
1334
+ const { nodes } = inj
1335
+
1336
+ if (S_MVAL !== inj.mode) {
1337
+ return UNDEF
1338
+ }
1339
+
1340
+ // Get arguments: ['`$REF`', 'ref-path'].
1341
+ const refpath = getprop(inj.parent, 1)
1342
+ inj.keyI = inj.keys.length
1343
+
1344
+ // Spec reference.
1345
+ const spec = getprop(store, S_DSPEC)()
1346
+
1347
+ const ref = getpath(spec, refpath, {
1348
+ // TODO: test relative refs
1349
+ dpath: inj.path.slice(1),
1350
+ dparent: getpath(spec, inj.path.slice(1))
1351
+ })
1352
+
1353
+ let hasSubRef = false
1354
+ if (isnode(ref)) {
1355
+ walk(ref, (_k: any, v: any) => {
1356
+ if ('`$REF`' === v) {
1357
+ hasSubRef = true
1358
+ }
1359
+ return v
1360
+ })
1361
+ }
1362
+
1363
+ let tref = clone(ref)
1364
+
1365
+ const cpath = slice(inj.path, -3)
1366
+ const tpath = slice(inj.path, -1)
1367
+ let tcur = getpath(store, cpath)
1368
+ let tval = getpath(store, tpath)
1369
+ let rval = UNDEF
1370
+
1371
+ if (!hasSubRef || UNDEF !== tval) {
1372
+ const tinj = inj.child(0, [getelem(tpath, -1)])
1373
+
1374
+ tinj.path = tpath
1375
+ tinj.nodes = slice(inj.nodes, -1)
1376
+ tinj.parent = getelem(nodes, -2)
1377
+ tinj.val = tref
1378
+
1379
+ tinj.dpath = [...cpath]
1380
+ tinj.dparent = tcur
1381
+
1382
+ inject(tref, store, tinj)
1383
+
1384
+ rval = tinj.val
1385
+ }
1386
+ else {
1387
+ rval = UNDEF
1388
+ }
1389
+
1390
+ const grandparent = inj.setval(rval, 2)
1391
+
1392
+ if (islist(grandparent) && inj.prior) {
1393
+ inj.prior.keyI--
1394
+ }
1395
+
1396
+ return val
1397
+ }
1398
+
1399
+
1400
+ // Transform data using spec.
1401
+ // Only operates on static JSON-like data.
1402
+ // Arrays are treated as if they are objects with indices as keys.
1403
+ function transform(
1404
+ data: any, // Source data to transform into new data (original not mutated)
1405
+ spec: any, // Transform specification; output follows this shape
1406
+ injdef?: Partial<Injection>
1407
+ ) {
1408
+ // Clone the spec so that the clone can be modified in place as the transform result.
1409
+ const origspec = spec
1410
+ spec = clone(origspec)
1411
+
1412
+ const extra = injdef?.extra
1413
+ // const modify = injdef?.modify
1414
+
1415
+ const extraTransforms: any = {}
1416
+ const extraData = null == extra ? UNDEF : items(extra)
1417
+ .reduce((a: any, n: any[]) =>
1418
+ (n[0].startsWith(S_DS) ? extraTransforms[n[0]] = n[1] : (a[n[0]] = n[1]), a), {})
1419
+
1420
+ const dataClone = merge([
1421
+ isempty(extraData) ? UNDEF : clone(extraData),
1422
+ clone(data),
1423
+ ])
1424
+
1425
+ // Define a top level store that provides transform operations.
1426
+ const store = {
1427
+
1428
+ // The inject function recognises this special location for the root of the source data.
1429
+ // NOTE: to escape data that contains "`$FOO`" keys at the top level,
1430
+ // place that data inside a holding map: { myholder: mydata }.
1431
+ $TOP: dataClone,
1432
+
1433
+ $SPEC: () => origspec,
1434
+
1435
+ // Escape backtick (this also works inside backticks).
1436
+ $BT: () => S_BT,
1437
+
1438
+ // Escape dollar sign (this also works inside backticks).
1439
+ $DS: () => S_DS,
1440
+
1441
+ // Insert current date and time as an ISO string.
1442
+ $WHEN: () => new Date().toISOString(),
1443
+
1444
+ $DELETE: transform_DELETE,
1445
+ $COPY: transform_COPY,
1446
+ $KEY: transform_KEY,
1447
+ $ANNO: transform_ANNO,
1448
+ $MERGE: transform_MERGE,
1449
+ $EACH: transform_EACH,
1450
+ $PACK: transform_PACK,
1451
+ $REF: transform_REF,
1452
+
1453
+ // Custom extra transforms, if any.
1454
+ ...extraTransforms,
1455
+ }
1456
+
1457
+ const out = inject(spec, store, injdef)
1458
+ return out
1459
+ }
1460
+
1461
+
1462
+ // A required string value. NOTE: Rejects empty strings.
1463
+ const validate_STRING: Injector = (inj: Injection) => {
1464
+ let out = getprop(inj.dparent, inj.key)
1465
+
1466
+ const t = typify(out)
1467
+ if (S_string !== t) {
1468
+ let msg = _invalidTypeMsg(inj.path, S_string, t, out, 'V1010')
1469
+ inj.errs.push(msg)
1470
+ return UNDEF
1471
+ }
1472
+
1473
+ if (S_MT === out) {
1474
+ let msg = 'Empty string at ' + pathify(inj.path, 1)
1475
+ inj.errs.push(msg)
1476
+ return UNDEF
1477
+ }
1478
+
1479
+ return out
1480
+ }
1481
+
1482
+
1483
+ // A required number value (int or float).
1484
+ const validate_NUMBER: Injector = (inj: Injection) => {
1485
+ let out = getprop(inj.dparent, inj.key)
1486
+
1487
+ const t = typify(out)
1488
+ if (S_number !== t) {
1489
+ inj.errs.push(_invalidTypeMsg(inj.path, S_number, t, out, 'V1020'))
1490
+ return UNDEF
1491
+ }
1492
+
1493
+ return out
1494
+ }
1495
+
1496
+
1497
+ // A required boolean value.
1498
+ const validate_BOOLEAN: Injector = (inj: Injection) => {
1499
+ let out = getprop(inj.dparent, inj.key)
1500
+
1501
+ const t = typify(out)
1502
+ if (S_boolean !== t) {
1503
+ inj.errs.push(_invalidTypeMsg(inj.path, S_boolean, t, out, 'V1030'))
1504
+ return UNDEF
1505
+ }
1506
+
1507
+ return out
1508
+ }
1509
+
1510
+
1511
+ // A required object (map) value (contents not validated).
1512
+ const validate_OBJECT: Injector = (inj: Injection) => {
1513
+ let out = getprop(inj.dparent, inj.key)
1514
+
1515
+ const t = typify(out)
1516
+ if (t !== S_object) {
1517
+ inj.errs.push(_invalidTypeMsg(inj.path, S_object, t, out, 'V1040'))
1518
+ return UNDEF
1519
+ }
1520
+
1521
+ return out
1522
+ }
1523
+
1524
+
1525
+ // A required array (list) value (contents not validated).
1526
+ const validate_ARRAY: Injector = (inj: Injection) => {
1527
+ let out = getprop(inj.dparent, inj.key)
1528
+
1529
+ const t = typify(out)
1530
+ if (t !== S_array) {
1531
+ inj.errs.push(_invalidTypeMsg(inj.path, S_array, t, out, 'V1050'))
1532
+ return UNDEF
1533
+ }
1534
+
1535
+ return out
1536
+ }
1537
+
1538
+
1539
+ // A required function value.
1540
+ const validate_FUNCTION: Injector = (inj: Injection) => {
1541
+ let out = getprop(inj.dparent, inj.key)
1542
+
1543
+ const t = typify(out)
1544
+ if (S_function !== t) {
1545
+ inj.errs.push(_invalidTypeMsg(inj.path, S_function, t, out, 'V1060'))
1546
+ return UNDEF
1547
+ }
1548
+
1549
+ return out
1550
+ }
1551
+
1552
+
1553
+ // Allow any value.
1554
+ const validate_ANY: Injector = (inj: Injection) => {
1555
+ let out = getprop(inj.dparent, inj.key)
1556
+ return out
1557
+ }
1558
+
1559
+
1560
+
1561
+ // Specify child values for map or list.
1562
+ // Map syntax: {'`$CHILD`': child-template }
1563
+ // List syntax: ['`$CHILD`', child-template ]
1564
+ const validate_CHILD: Injector = (inj: Injection) => {
1565
+ const { mode, key, parent, keys, path } = inj
1566
+
1567
+ // Setup data structures for validation by cloning child template.
1568
+
1569
+ // Map syntax.
1570
+ if (S_MKEYPRE === mode) {
1571
+ const childtm = getprop(parent, key)
1572
+
1573
+ // Get corresponding current object.
1574
+ const pkey = getprop(path, path.length - 2)
1575
+ let tval = getprop(inj.dparent, pkey)
1576
+
1577
+ if (UNDEF == tval) {
1578
+ tval = {}
1579
+ }
1580
+ else if (!ismap(tval)) {
1581
+ inj.errs.push(_invalidTypeMsg(
1582
+ slice(inj.path, -1), S_object, typify(tval), tval), 'V0220')
1583
+ return UNDEF
1584
+ }
1585
+
1586
+ const ckeys = keysof(tval)
1587
+ for (let ckey of ckeys) {
1588
+ setprop(parent, ckey, clone(childtm))
1589
+
1590
+ // NOTE: modifying inj! This extends the child value loop in inject.
1591
+ keys.push(ckey)
1592
+ }
1593
+
1594
+ // Remove $CHILD to cleanup ouput.
1595
+ inj.setval(UNDEF)
1596
+ return UNDEF
1597
+ }
1598
+
1599
+ // List syntax.
1600
+ if (S_MVAL === mode) {
1601
+
1602
+ if (!islist(parent)) {
1603
+ // $CHILD was not inside a list.
1604
+ inj.errs.push('Invalid $CHILD as value')
1605
+ return UNDEF
1606
+ }
1607
+
1608
+ const childtm = getprop(parent, 1)
1609
+
1610
+ if (UNDEF === inj.dparent) {
1611
+ // Empty list as default.
1612
+ parent.length = 0
1613
+ return UNDEF
1614
+ }
1615
+
1616
+ if (!islist(inj.dparent)) {
1617
+ const msg = _invalidTypeMsg(
1618
+ slice(inj.path, -1), S_array, typify(inj.dparent), inj.dparent, 'V0230')
1619
+ inj.errs.push(msg)
1620
+ inj.keyI = parent.length
1621
+ return inj.dparent
1622
+ }
1623
+
1624
+ // Clone children abd reset inj key index.
1625
+ // The inject child loop will now iterate over the cloned children,
1626
+ // validating them againt the current list values.
1627
+
1628
+ inj.dparent.map((_n, i) => parent[i] = clone(childtm))
1629
+ parent.length = inj.dparent.length
1630
+ inj.keyI = 0
1631
+ const out = getprop(inj.dparent, 0)
1632
+ return out
1633
+ }
1634
+
1635
+ return UNDEF
1636
+ }
1637
+
1638
+
1639
+ // Match at least one of the specified shapes.
1640
+ // Syntax: ['`$ONE`', alt0, alt1, ...]okI
1641
+ const validate_ONE: Injector = (
1642
+ inj: Injection,
1643
+ _val: any,
1644
+ _ref: string,
1645
+ store: any
1646
+ ) => {
1647
+ const { mode, parent, keyI } = inj
1648
+
1649
+ // Only operate in val mode, since parent is a list.
1650
+ if (S_MVAL === mode) {
1651
+ if (!islist(parent) || 0 !== keyI) {
1652
+ inj.errs.push('The $ONE validator at field ' +
1653
+ pathify(inj.path, 1, 1) +
1654
+ ' must be the first element of an array.')
1655
+ return
1656
+ }
1657
+
1658
+ inj.keyI = inj.keys.length
1659
+
1660
+ // Clean up structure, replacing [$ONE, ...] with current
1661
+ inj.setval(inj.dparent, 2)
1662
+
1663
+ inj.path = slice(inj.path, -1)
1664
+ inj.key = getelem(inj.path, -1)
1665
+
1666
+ let tvals = slice(parent, 1)
1667
+ if (0 === tvals.length) {
1668
+ inj.errs.push('The $ONE validator at field ' +
1669
+ pathify(inj.path, 1, 1) +
1670
+ ' must have at least one argument.')
1671
+ return
1672
+ }
1673
+
1674
+ // See if we can find a match.
1675
+ for (let tval of tvals) {
1676
+
1677
+ // If match, then errs.length = 0
1678
+ let terrs: any[] = []
1679
+
1680
+ const vstore = { ...store }
1681
+ vstore.$TOP = inj.dparent
1682
+
1683
+ const vcurrent = validate(inj.dparent, tval, {
1684
+ extra: vstore,
1685
+ errs: terrs,
1686
+ meta: inj.meta,
1687
+ })
1688
+
1689
+ inj.setval(vcurrent, -2)
1690
+
1691
+ // Accept current value if there was a match
1692
+ if (0 === terrs.length) {
1693
+ return
1694
+ }
1695
+ }
1696
+
1697
+ // There was no match.
1698
+
1699
+ const valdesc = tvals
1700
+ .map((v: any) => stringify(v))
1701
+ .join(', ')
1702
+ .replace(R_TRANSFORM_NAME, (_m: any, p1: string) => p1.toLowerCase())
1703
+
1704
+ inj.errs.push(_invalidTypeMsg(
1705
+ inj.path,
1706
+ (1 < tvals.length ? 'one of ' : '') + valdesc,
1707
+ typify(inj.dparent), inj.dparent, 'V0210'))
1708
+ }
1709
+ }
1710
+
1711
+
1712
+ const validate_EXACT: Injector = (inj: Injection) => {
1713
+ const { mode, parent, key, keyI } = inj
1714
+
1715
+ // Only operate in val mode, since parent is a list.
1716
+ if (S_MVAL === mode) {
1717
+ if (!islist(parent) || 0 !== keyI) {
1718
+ inj.errs.push('The $EXACT validator at field ' +
1719
+ pathify(inj.path, 1, 1) +
1720
+ ' must be the first element of an array.')
1721
+ return
1722
+ }
1723
+
1724
+ inj.keyI = inj.keys.length
1725
+
1726
+ // Clean up structure, replacing [$EXACT, ...] with current data parent
1727
+ inj.setval(inj.dparent, 2)
1728
+
1729
+ inj.path = slice(inj.path, 0, inj.path.length - 1)
1730
+ inj.key = getelem(inj.path, -1)
1731
+
1732
+ let tvals = slice(parent, 1)
1733
+ if (0 === tvals.length) {
1734
+ inj.errs.push('The $EXACT validator at field ' +
1735
+ pathify(inj.path, 1, 1) +
1736
+ ' must have at least one argument.')
1737
+ return
1738
+ }
1739
+
1740
+ // See if we can find an exact value match.
1741
+ let currentstr: string | undefined = undefined
1742
+ for (let tval of tvals) {
1743
+ let exactmatch = tval === inj.dparent
1744
+
1745
+ if (!exactmatch && isnode(tval)) {
1746
+ currentstr = undefined === currentstr ? stringify(inj.dparent) : currentstr
1747
+ const tvalstr = stringify(tval)
1748
+ exactmatch = tvalstr === currentstr
1749
+ }
1750
+
1751
+ if (exactmatch) {
1752
+ return
1753
+ }
1754
+ }
1755
+
1756
+ const valdesc = tvals
1757
+ .map((v: any) => stringify(v))
1758
+ .join(', ')
1759
+ .replace(R_TRANSFORM_NAME, (_m: any, p1: string) => p1.toLowerCase())
1760
+
1761
+ inj.errs.push(_invalidTypeMsg(
1762
+ inj.path,
1763
+ (1 < inj.path.length ? '' : 'value ') +
1764
+ 'exactly equal to ' + (1 === tvals.length ? '' : 'one of ') + valdesc,
1765
+ typify(inj.dparent), inj.dparent, 'V0110'))
1766
+ }
1767
+ else {
1768
+ delprop(parent, key)
1769
+ }
1770
+ }
1771
+
1772
+
1773
+ // This is the "modify" argument to inject. Use this to perform
1774
+ // generic validation. Runs *after* any special commands.
1775
+ const _validation: Modify = (
1776
+ pval: any,
1777
+ key?: any,
1778
+ parent?: any,
1779
+ inj?: Injection,
1780
+ ) => {
1781
+
1782
+ if (UNDEF === inj) {
1783
+ return
1784
+ }
1785
+
1786
+ if (SKIP === pval) {
1787
+ return
1788
+ }
1789
+
1790
+ // select needs exact matches
1791
+ const exact = getprop(inj.meta, S_BEXACT, false)
1792
+
1793
+ // Current val to verify.
1794
+ const cval = getprop(inj.dparent, key)
1795
+
1796
+ if (UNDEF === inj || (!exact && UNDEF === cval)) {
1797
+ return
1798
+ }
1799
+
1800
+ const ptype = typify(pval)
1801
+
1802
+ // Delete any special commands remaining.
1803
+ if (S_string === ptype && pval.includes(S_DS)) {
1804
+ return
1805
+ }
1806
+
1807
+ const ctype = typify(cval)
1808
+
1809
+ // Type mismatch.
1810
+ if (ptype !== ctype && UNDEF !== pval) {
1811
+ inj.errs.push(_invalidTypeMsg(inj.path, ptype, ctype, cval, 'V0010'))
1812
+ return
1813
+ }
1814
+
1815
+ if (ismap(cval)) {
1816
+ if (!ismap(pval)) {
1817
+ inj.errs.push(_invalidTypeMsg(inj.path, ptype, ctype, cval, 'V0020'))
1818
+ return
1819
+ }
1820
+
1821
+ const ckeys = keysof(cval)
1822
+ const pkeys = keysof(pval)
1823
+
1824
+ // Empty spec object {} means object can be open (any keys).
1825
+ if (0 < pkeys.length && true !== getprop(pval, '`$OPEN`')) {
1826
+ const badkeys = []
1827
+ for (let ckey of ckeys) {
1828
+ if (!haskey(pval, ckey)) {
1829
+ badkeys.push(ckey)
1830
+ }
1831
+ }
1832
+
1833
+ // Closed object, so reject extra keys not in shape.
1834
+ if (0 < badkeys.length) {
1835
+ const msg =
1836
+ 'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + badkeys.join(', ')
1837
+ inj.errs.push(msg)
1838
+ }
1839
+ }
1840
+ else {
1841
+ // Object is open, so merge in extra keys.
1842
+ merge([pval, cval])
1843
+ if (isnode(pval)) {
1844
+ delprop(pval, '`$OPEN`')
1845
+ }
1846
+ }
1847
+ }
1848
+ else if (islist(cval)) {
1849
+ if (!islist(pval)) {
1850
+ inj.errs.push(_invalidTypeMsg(inj.path, ptype, ctype, cval, 'V0030'))
1851
+ }
1852
+ }
1853
+ else if (exact) {
1854
+ if (cval !== pval) {
1855
+ const pathmsg = 1 < size(inj.path) ? 'at field ' + pathify(inj.path, 1) + S_VIZ : S_MT
1856
+ inj.errs.push('Value ' + pathmsg + cval +
1857
+ ' should equal ' + pval + S_DT)
1858
+ }
1859
+ }
1860
+ else {
1861
+ // Spec value was a default, copy over data
1862
+ setprop(parent, key, cval)
1863
+ }
1864
+
1865
+ return
1866
+ }
1867
+
1868
+
1869
+
1870
+ // Validate a data structure against a shape specification. The shape
1871
+ // specification follows the "by example" principle. Plain data in
1872
+ // teh shape is treated as default values that also specify the
1873
+ // required type. Thus shape {a:1} validates {a:2}, since the types
1874
+ // (number) match, but not {a:'A'}. Shape {a;1} against data {}
1875
+ // returns {a:1} as a=1 is the default value of the a key. Special
1876
+ // validation commands (in the same syntax as transform ) are also
1877
+ // provided to specify required values. Thus shape {a:'`$STRING`'}
1878
+ // validates {a:'A'} but not {a:1}. Empty map or list means the node
1879
+ // is open, and if missing an empty default is inserted.
1880
+ function validate(
1881
+ data: any, // Source data to transform into new data (original not mutated)
1882
+ spec: any, // Transform specification; output follows this shape
1883
+ injdef?: Partial<Injection>
1884
+ ) {
1885
+ const extra = injdef?.extra
1886
+
1887
+ const collect = null != injdef?.errs
1888
+ const errs = injdef?.errs || []
1889
+
1890
+ const store = {
1891
+ // Remove the transform commands.
1892
+ $DELETE: null,
1893
+ $COPY: null,
1894
+ $KEY: null,
1895
+ $META: null,
1896
+ $MERGE: null,
1897
+ $EACH: null,
1898
+ $PACK: null,
1899
+
1900
+ $STRING: validate_STRING,
1901
+ $NUMBER: validate_NUMBER,
1902
+ $BOOLEAN: validate_BOOLEAN,
1903
+ $OBJECT: validate_OBJECT,
1904
+ $ARRAY: validate_ARRAY,
1905
+ $FUNCTION: validate_FUNCTION,
1906
+ $ANY: validate_ANY,
1907
+ $CHILD: validate_CHILD,
1908
+ $ONE: validate_ONE,
1909
+ $EXACT: validate_EXACT,
1910
+
1911
+ ...(extra || {}),
1912
+
1913
+ // A special top level value to collect errors.
1914
+ // NOTE: collecterrs paramter always wins.
1915
+ $ERRS: errs,
1916
+ }
1917
+
1918
+ let meta = { [S_BEXACT]: false }
1919
+
1920
+ if (injdef?.meta) {
1921
+ meta = merge([meta, injdef.meta])
1922
+ }
1923
+
1924
+ const out = transform(data, spec, {
1925
+ meta,
1926
+ extra: store,
1927
+ modify: _validation,
1928
+ handler: _validatehandler
1929
+ })
1930
+
1931
+ const generr = (0 < errs.length && !collect)
1932
+ if (generr) {
1933
+ throw new Error('Invalid data: ' + errs.join(' | '))
1934
+ }
1935
+
1936
+ return out
1937
+ }
1938
+
1939
+
1940
+ const select_AND: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
1941
+ if (S_MKEYPRE === inj.mode) {
1942
+ const terms = getprop(inj.parent, inj.key)
1943
+
1944
+ const ppath = slice(inj.path, -1)
1945
+ const point = getpath(store, ppath)
1946
+
1947
+ const vstore = { ...store }
1948
+ vstore.$TOP = point
1949
+
1950
+ for (let term of terms) {
1951
+ // setprop(term, '`$OPEN`', getprop(term, '`$OPEN`', true))
1952
+
1953
+ let terrs: any[] = []
1954
+
1955
+ validate(point, term, {
1956
+ extra: vstore,
1957
+ errs: terrs,
1958
+ meta: inj.meta,
1959
+ })
1960
+
1961
+ if (0 != terrs.length) {
1962
+ inj.errs.push(
1963
+ 'AND:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms))
1964
+ }
1965
+ }
1966
+
1967
+ const gkey = getelem(inj.path, -2)
1968
+ const gp = getelem(inj.nodes, -2)
1969
+ setprop(gp, gkey, point)
1970
+ }
1971
+ }
1972
+
1973
+
1974
+ const select_OR: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
1975
+ if (S_MKEYPRE === inj.mode) {
1976
+ const terms = getprop(inj.parent, inj.key)
1977
+
1978
+ const ppath = slice(inj.path, -1)
1979
+ const point = getpath(store, ppath)
1980
+
1981
+ const vstore = { ...store }
1982
+ vstore.$TOP = point
1983
+
1984
+ for (let term of terms) {
1985
+ let terrs: any[] = []
1986
+
1987
+ validate(point, term, {
1988
+ extra: vstore,
1989
+ errs: terrs,
1990
+ meta: inj.meta,
1991
+ })
1992
+
1993
+ if (0 === terrs.length) {
1994
+ const gkey = getelem(inj.path, -2)
1995
+ const gp = getelem(inj.nodes, -2)
1996
+ setprop(gp, gkey, point)
1997
+
1998
+ return
1999
+ }
2000
+ }
2001
+
2002
+ inj.errs.push(
2003
+ 'OR:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms))
2004
+ }
2005
+ }
2006
+
2007
+
2008
+ const select_NOT: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
2009
+ if (S_MKEYPRE === inj.mode) {
2010
+ const term = getprop(inj.parent, inj.key)
2011
+
2012
+ const ppath = slice(inj.path, -1)
2013
+ const point = getpath(store, ppath)
2014
+
2015
+ const vstore = { ...store }
2016
+ vstore.$TOP = point
2017
+
2018
+ let terrs: any[] = []
2019
+
2020
+ validate(point, term, {
2021
+ extra: vstore,
2022
+ errs: terrs,
2023
+ meta: inj.meta,
2024
+ })
2025
+
2026
+ if (0 == terrs.length) {
2027
+ inj.errs.push(
2028
+ 'NOT:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(term))
2029
+ }
2030
+
2031
+ const gkey = getelem(inj.path, -2)
2032
+ const gp = getelem(inj.nodes, -2)
2033
+ setprop(gp, gkey, point)
2034
+ }
2035
+ }
2036
+
2037
+
2038
+ const select_CMP: Injector = (inj: Injection, _val: any, ref: string, store: any) => {
2039
+ if (S_MKEYPRE === inj.mode) {
2040
+ const term = getprop(inj.parent, inj.key)
2041
+ // const src = getprop(store, inj.base, store)
2042
+ const gkey = getelem(inj.path, -2)
2043
+
2044
+ // const tval = getprop(src, gkey)
2045
+
2046
+ const ppath = slice(inj.path, -1)
2047
+ const point = getpath(store, ppath)
2048
+
2049
+ let pass = false
2050
+
2051
+ if ('$GT' === ref && point > term) {
2052
+ pass = true
2053
+ }
2054
+ else if ('$LT' === ref && point < term) {
2055
+ pass = true
2056
+ }
2057
+ else if ('$GTE' === ref && point >= term) {
2058
+ pass = true
2059
+ }
2060
+ else if ('$LTE' === ref && point <= term) {
2061
+ pass = true
2062
+ }
2063
+ else if ('$LIKE' === ref && stringify(point).match(RegExp(term))) {
2064
+ pass = true
2065
+ }
2066
+
2067
+ if (pass) {
2068
+ // Update spec to match found value so that _validate does not complain.
2069
+ const gp = getelem(inj.nodes, -2)
2070
+ setprop(gp, gkey, point)
2071
+ }
2072
+ else {
2073
+ inj.errs.push('CMP: ' + pathify(ppath) + S_VIZ + stringify(point) +
2074
+ ' fail:' + ref + ' ' + stringify(term))
2075
+ }
2076
+ }
2077
+
2078
+ return UNDEF
2079
+ }
2080
+
2081
+
2082
+ // Select children from a top-level object that match a MongoDB-style query.
2083
+ // Supports $and, $or, and equality comparisons.
2084
+ // For arrays, children are elements; for objects, children are values.
2085
+ // TODO: swap arg order for consistency
2086
+ function select(children: any, query: any): any[] {
2087
+ if (!isnode(children)) {
2088
+ return []
2089
+ }
2090
+
2091
+ if (ismap(children)) {
2092
+ children = items(children).map(n => (n[1][S_DKEY] = n[0], n[1]))
2093
+ }
2094
+ else {
2095
+ children = (children as any[]).map((n, i) => ((ismap(n) ? n[S_DKEY] = i : null), n))
2096
+ }
2097
+
2098
+ const results: any[] = []
2099
+ const injdef = {
2100
+ errs: [],
2101
+ meta: { [S_BEXACT]: true },
2102
+ extra: {
2103
+ $AND: select_AND,
2104
+ $OR: select_OR,
2105
+ $NOT: select_NOT,
2106
+ $GT: select_CMP,
2107
+ $LT: select_CMP,
2108
+ $GTE: select_CMP,
2109
+ $LTE: select_CMP,
2110
+ $LIKE: select_CMP,
2111
+ }
2112
+ }
2113
+
2114
+ const q = clone(query)
2115
+
2116
+ walk(q, (_k: PropKey | undefined, v: any) => {
2117
+ if (ismap(v)) {
2118
+ setprop(v, '`$OPEN`', getprop(v, '`$OPEN`', true))
2119
+ }
2120
+ return v
2121
+ })
2122
+
2123
+ for (const child of children) {
2124
+ injdef.errs = []
2125
+
2126
+ validate(child, clone(q), injdef)
2127
+
2128
+ if (0 === size(injdef.errs)) {
2129
+ results.push(child)
2130
+ }
2131
+ }
2132
+
2133
+ return results
2134
+ }
2135
+
2136
+
2137
+
2138
+ // Injection state used for recursive injection into JSON - like data structures.
2139
+ class Injection {
2140
+ mode: InjectMode // Injection mode: key:pre, val, key:post.
2141
+ full: boolean // Transform escape was full key name.
2142
+ keyI: number // Index of parent key in list of parent keys.
2143
+ keys: string[] // List of parent keys.
2144
+ key: string // Current parent key.
2145
+ val: any // Current child value.
2146
+ parent: any // Current parent (in transform specification).
2147
+ path: string[] // Path to current node.
2148
+ nodes: any[] // Stack of ancestor nodes.
2149
+ handler: Injector // Custom handler for injections.
2150
+ errs: any[] // Error collector.
2151
+ meta: Record<string, any> // Custom meta data.
2152
+ dparent: any // Current data parent node (contains current data value).
2153
+ dpath: string[] // Current data value path
2154
+ base?: string // Base key for data in store, if any.
2155
+ modify?: Modify // Modify injection output.
2156
+ prior?: Injection // Parent (aka prior) injection.
2157
+ extra?: any
2158
+
2159
+ constructor(val: any, parent: any) {
2160
+ this.val = val
2161
+ this.parent = parent
2162
+ this.errs = []
2163
+
2164
+ this.dparent = UNDEF
2165
+ this.dpath = [S_DTOP]
2166
+
2167
+ this.mode = S_MVAL as InjectMode
2168
+ this.full = false
2169
+ this.keyI = 0
2170
+ this.keys = [S_DTOP]
2171
+ this.key = S_DTOP
2172
+ this.path = [S_DTOP]
2173
+ this.nodes = [parent]
2174
+ this.handler = _injecthandler
2175
+ this.base = S_DTOP
2176
+ this.meta = {}
2177
+ }
2178
+
2179
+
2180
+ toString(prefix?: string) {
2181
+ return 'INJ' + (null == prefix ? '' : S_FS + prefix) + S_CN +
2182
+ pad(pathify(this.path, 1)) +
2183
+ this.mode + (this.full ? '/full' : '') + S_CN +
2184
+ 'key=' + this.keyI + S_FS + this.key + S_FS + S_OS + this.keys + S_CS +
2185
+ ' p=' + stringify(this.parent, -1, 1) +
2186
+ ' m=' + stringify(this.meta, -1, 1) +
2187
+ ' d/' + pathify(this.dpath, 1) + '=' + stringify(this.dparent, -1, 1) +
2188
+ ' r=' + stringify(this.nodes[0]?.[S_DTOP], -1, 1)
2189
+ }
2190
+
2191
+
2192
+ descend() {
2193
+ this.meta.__d++
2194
+ const parentkey = getelem(this.path, -2)
2195
+
2196
+ // Resolve current node in store for local paths.
2197
+ if (UNDEF === this.dparent) {
2198
+
2199
+ // Even if there's no data, dpath should continue to match path, so that
2200
+ // relative paths work properly.
2201
+ if (1 < this.dpath.length) {
2202
+ this.dpath = [...this.dpath, parentkey]
2203
+ }
2204
+ }
2205
+ else {
2206
+ // this.dparent is the containing node of the current store value.
2207
+ if (null != parentkey) {
2208
+ this.dparent = getprop(this.dparent, parentkey)
2209
+
2210
+ let lastpart = getelem(this.dpath, -1)
2211
+ if (lastpart === '$:' + parentkey) {
2212
+ this.dpath = slice(this.dpath, -1)
2213
+ }
2214
+ else {
2215
+ this.dpath = [...this.dpath, parentkey]
2216
+ }
2217
+ }
2218
+ }
2219
+
2220
+ return this.dparent
2221
+ }
2222
+
2223
+
2224
+ child(keyI: number, keys: string[]) {
2225
+ const key = strkey(keys[keyI])
2226
+ const val = this.val
2227
+
2228
+ const cinj = new Injection(getprop(val, key), val)
2229
+ cinj.keyI = keyI
2230
+ cinj.keys = keys
2231
+ cinj.key = key
2232
+
2233
+ cinj.path = [...(this.path || []), key]
2234
+ cinj.nodes = [...(this.nodes || []), val]
2235
+
2236
+ cinj.mode = this.mode
2237
+ cinj.handler = this.handler
2238
+ cinj.modify = this.modify
2239
+ cinj.base = this.base
2240
+ cinj.meta = this.meta
2241
+ cinj.errs = this.errs
2242
+ cinj.prior = this
2243
+
2244
+ cinj.dpath = [...this.dpath]
2245
+ cinj.dparent = this.dparent
2246
+
2247
+ return cinj
2248
+ }
2249
+
2250
+
2251
+ setval(val: any, ancestor?: number) {
2252
+ if (null == ancestor || ancestor < 2) {
2253
+ return UNDEF === val ?
2254
+ delprop(this.parent, this.key) :
2255
+ setprop(this.parent, this.key, val)
2256
+ }
2257
+ else {
2258
+ const aval = getelem(this.nodes, 0 - ancestor)
2259
+ const akey = getelem(this.path, 0 - ancestor)
2260
+ return UNDEF === val ?
2261
+ delprop(aval, akey) :
2262
+ setprop(aval, akey, val)
2263
+ }
2264
+ }
2265
+ }
2266
+
2267
+
2268
+ // Internal utilities
2269
+ // ==================
2270
+
2271
+
2272
+ // Update all references to target in inj.nodes.
2273
+ function _updateAncestors(_inj: Injection, target: any, tkey: any, tval: any) {
2274
+ // SetProp is sufficient in TypeScript as target reference remains consistent even for lists.
2275
+ setprop(target, tkey, tval)
2276
+ }
2277
+
2278
+
2279
+ // Build a type validation error message.
2280
+ function _invalidTypeMsg(path: any, needtype: string, vt: string, v: any, _whence?: string) {
2281
+ let vs = null == v ? 'no value' : stringify(v)
2282
+
2283
+ return 'Expected ' +
2284
+ (1 < path.length ? ('field ' + pathify(path, 1) + ' to be ') : '') +
2285
+ needtype + ', but found ' +
2286
+ (null != v ? vt + S_VIZ : '') + vs +
2287
+
2288
+ // Uncomment to help debug validation errors.
2289
+ // ' [' + _whence + ']' +
2290
+
2291
+ '.'
2292
+ }
2293
+
2294
+
2295
+ // Default inject handler for transforms. If the path resolves to a function,
2296
+ // call the function passing the injection inj. This is how transforms operate.
2297
+ const _injecthandler: Injector = (
2298
+ inj: Injection,
2299
+ val: any,
2300
+ ref: string,
2301
+ store: any
2302
+ ): any => {
2303
+ let out = val
2304
+ const iscmd = isfunc(val) && (UNDEF === ref || ref.startsWith(S_DS))
2305
+
2306
+ // Only call val function if it is a special command ($NAME format).
2307
+ if (iscmd) {
2308
+ out = (val as Injector)(inj, val, ref, store)
2309
+ }
2310
+
2311
+ // Update parent with value. Ensures references remain in node tree.
2312
+ else if (S_MVAL === inj.mode && inj.full) {
2313
+ inj.setval(val)
2314
+ }
2315
+
2316
+ return out
2317
+ }
2318
+
2319
+
2320
+ const _validatehandler: Injector = (
2321
+ inj: Injection,
2322
+ val: any,
2323
+ ref: string,
2324
+ store: any
2325
+ ): any => {
2326
+ let out = val
2327
+
2328
+ const m = ref.match(R_META_PATH)
2329
+ const ismetapath = null != m
2330
+
2331
+ if (ismetapath) {
2332
+ if ('=' === m[2]) {
2333
+ inj.setval([S_BEXACT, val])
2334
+ }
2335
+ else {
2336
+ inj.setval(val)
2337
+ }
2338
+ inj.keyI = -1
2339
+
2340
+ out = SKIP
2341
+ }
2342
+ else {
2343
+ out = _injecthandler(inj, val, ref, store)
2344
+ }
2345
+
2346
+ return out
2347
+ }
2348
+
2349
+
2350
+ // Inject values from a data store into a string. Not a public utility - used by
2351
+ // `inject`. Inject are marked with `path` where path is resolved
2352
+ // with getpath against the store or current (if defined)
2353
+ // arguments. See `getpath`. Custom injection handling can be
2354
+ // provided by inj.handler (this is used for transform functions).
2355
+ // The path can also have the special syntax $NAME999 where NAME is
2356
+ // upper case letters only, and 999 is any digits, which are
2357
+ // discarded. This syntax specifies the name of a transform, and
2358
+ // optionally allows transforms to be ordered by alphanumeric sorting.
2359
+ function _injectstr(
2360
+ val: string,
2361
+ store: any,
2362
+ inj?: Injection
2363
+ ): any {
2364
+ // Can't inject into non-strings
2365
+ if (S_string !== typeof val || S_MT === val) {
2366
+ return S_MT
2367
+ }
2368
+
2369
+ let out: any = val
2370
+
2371
+ // Pattern examples: "`a.b.c`", "`$NAME`", "`$NAME1`"
2372
+ const m = val.match(R_INJECTION_FULL)
2373
+
2374
+ // Full string of the val is an injection.
2375
+ if (m) {
2376
+ if (null != inj) {
2377
+ inj.full = true
2378
+ }
2379
+ let pathref = m[1]
2380
+
2381
+ // Special escapes inside injection.
2382
+ pathref = 3 < pathref.length ?
2383
+ pathref.replace(R_BT_ESCAPE, S_BT).replace(R_DS_ESCAPE, S_DS) :
2384
+ pathref
2385
+
2386
+ // Get the extracted path reference.
2387
+ out = getpath(store, pathref, inj)
2388
+ }
2389
+
2390
+ else {
2391
+ // Check for injections within the string.
2392
+ const partial = (_m: string, ref: string) => {
2393
+ // Special escapes inside injection.
2394
+ ref = 3 < ref.length ? ref.replace(R_BT_ESCAPE, S_BT).replace(R_DS_ESCAPE, S_DS) : ref
2395
+ if (inj) {
2396
+ inj.full = false
2397
+ }
2398
+ const found = getpath(store, ref, inj)
2399
+
2400
+ // Ensure inject value is a string.
2401
+ return UNDEF === found ? S_MT : S_string === typeof found ? found : JSON.stringify(found)
2402
+ }
2403
+
2404
+ out = val.replace(R_INJECTION_PARTIAL, partial)
2405
+
2406
+ // Also call the inj handler on the entire string, providing the
2407
+ // option for custom injection.
2408
+ if (null != inj && isfunc(inj.handler)) {
2409
+ inj.full = true
2410
+ out = inj.handler(inj, out, val, store)
2411
+ }
2412
+ }
2413
+
2414
+ return out
2415
+ }
2416
+
2417
+
2418
+ class StructUtility {
2419
+ clone = clone
2420
+ delprop = delprop
2421
+ escre = escre
2422
+ escurl = escurl
2423
+ getelem = getelem
2424
+ getpath = getpath
2425
+ getprop = getprop
2426
+ haskey = haskey
2427
+ inject = inject
2428
+ isempty = isempty
2429
+ isfunc = isfunc
2430
+ iskey = iskey
2431
+ islist = islist
2432
+ ismap = ismap
2433
+ isnode = isnode
2434
+ items = items
2435
+ joinurl = joinurl
2436
+ jsonify = jsonify
2437
+ keysof = keysof
2438
+ merge = merge
2439
+ pad = pad
2440
+ pathify = pathify
2441
+ select = select
2442
+ setprop = setprop
2443
+ size = size
2444
+ slice = slice
2445
+ strkey = strkey
2446
+ stringify = stringify
2447
+ transform = transform
2448
+ typify = typify
2449
+ validate = validate
2450
+ walk = walk
2451
+
2452
+ jo = jo
2453
+ ja = ja
2454
+ }
2455
+
2456
+ export {
2457
+ StructUtility,
2458
+ clone,
2459
+ delprop,
2460
+ escre,
2461
+ escurl,
2462
+ getelem,
2463
+ getpath,
2464
+ getprop,
2465
+ haskey,
2466
+ inject,
2467
+ isempty,
2468
+ isfunc,
2469
+ iskey,
2470
+ islist,
2471
+ ismap,
2472
+ isnode,
2473
+ items,
2474
+ joinurl,
2475
+ jsonify,
2476
+ keysof,
2477
+ merge,
2478
+ pad,
2479
+ pathify,
2480
+ select,
2481
+ setprop,
2482
+ size,
2483
+ slice,
2484
+ strkey,
2485
+ stringify,
2486
+ transform,
2487
+ typify,
2488
+ validate,
2489
+ walk,
2490
+
2491
+ jo,
2492
+ ja,
2493
+ }
2494
+
2495
+ export type {
2496
+ Injection,
2497
+ Injector,
2498
+ WalkApply
2499
+ }