@voxgig/sdkgen 0.16.0 → 0.18.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 (112) hide show
  1. package/dist/action/feature.js +3 -2
  2. package/dist/action/feature.js.map +1 -1
  3. package/dist/action/target.js +11 -6
  4. package/dist/action/target.js.map +1 -1
  5. package/dist/cmp/Entity.js +2 -1
  6. package/dist/cmp/Entity.js.map +1 -1
  7. package/dist/cmp/Main.js +23 -9
  8. package/dist/cmp/Main.js.map +1 -1
  9. package/dist/sdkgen.d.ts +7 -1
  10. package/dist/sdkgen.js +5 -2
  11. package/dist/sdkgen.js.map +1 -1
  12. package/dist/tsconfig.tsbuildinfo +1 -1
  13. package/dist/utility.js +1 -2
  14. package/dist/utility.js.map +1 -1
  15. package/model/sdkgen.jsonic +8 -5
  16. package/package.json +7 -5
  17. package/project/.sdk/model/feature/limit.jsonic +12 -0
  18. package/project/.sdk/model/feature/log.jsonic +32 -0
  19. package/project/.sdk/model/feature/page.jsonic +9 -0
  20. package/project/.sdk/model/feature/telemetry.jsonic +10 -0
  21. package/project/.sdk/model/target/go.jsonic +7 -0
  22. package/project/.sdk/model/target/js.jsonic +23 -0
  23. package/project/.sdk/model/target/ts.jsonic +25 -0
  24. package/project/.sdk/src/cmp/js/Config_js.ts +49 -0
  25. package/project/.sdk/src/cmp/js/Entity_js.ts +92 -0
  26. package/project/.sdk/src/cmp/js/MainEntity_js.ts +22 -0
  27. package/project/.sdk/src/cmp/js/Main_js.ts +88 -0
  28. package/project/.sdk/src/cmp/js/Package_js.ts +58 -0
  29. package/project/.sdk/src/cmp/js/Quick_js.ts +82 -0
  30. package/project/.sdk/src/cmp/js/ReadmeInstall_js.ts +19 -0
  31. package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +25 -0
  32. package/project/.sdk/src/cmp/js/TestAcceptEntity_js.ts +13 -0
  33. package/project/.sdk/src/cmp/js/TestAccept_js.ts +18 -0
  34. package/project/.sdk/src/cmp/js/TestEntity_js.ts +13 -0
  35. package/project/.sdk/src/cmp/js/TestMain_js.ts +19 -0
  36. package/project/.sdk/src/cmp/js/Test_js.ts +24 -0
  37. package/project/.sdk/src/cmp/js/fragment/Entity.fragment.js +79 -0
  38. package/project/.sdk/src/cmp/js/fragment/EntityCreateOp.fragment.js +61 -0
  39. package/project/.sdk/src/cmp/js/fragment/EntityListOp.fragment.js +57 -0
  40. package/project/.sdk/src/cmp/js/fragment/EntityLoadOp.fragment.js +61 -0
  41. package/project/.sdk/src/cmp/js/fragment/EntityRemoveOp.fragment.js +61 -0
  42. package/project/.sdk/src/cmp/js/fragment/EntityUpdateOp.fragment.js +61 -0
  43. package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +67 -0
  44. package/project/.sdk/src/cmp/ts/Config_ts.ts +36 -0
  45. package/project/.sdk/src/cmp/ts/Entity_ts.ts +93 -0
  46. package/project/.sdk/src/cmp/ts/MainEntity_ts.ts.off +22 -0
  47. package/project/.sdk/src/cmp/ts/Main_ts.ts +88 -0
  48. package/project/.sdk/src/cmp/ts/Main_ts.ts~ +88 -0
  49. package/project/.sdk/src/cmp/ts/Package_ts.ts +63 -0
  50. package/project/.sdk/src/cmp/ts/Package_ts.ts~ +58 -0
  51. package/project/.sdk/src/cmp/ts/Quick_ts.ts.off +82 -0
  52. package/project/.sdk/src/cmp/ts/ReadmeInstall_ts.ts.off +19 -0
  53. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts.off +25 -0
  54. package/project/.sdk/src/cmp/ts/TestAcceptEntity_ts.ts.off +13 -0
  55. package/project/.sdk/src/cmp/ts/TestAccept_ts.ts.off +18 -0
  56. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts.off +13 -0
  57. package/project/.sdk/src/cmp/ts/TestMain_ts.ts.off +19 -0
  58. package/project/.sdk/src/cmp/ts/Test_ts.ts.off +24 -0
  59. package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +15 -0
  60. package/project/.sdk/src/cmp/ts/fragment/Entity.fragment.js +79 -0
  61. package/project/.sdk/src/cmp/ts/fragment/EntityCreateOp.fragment.js +61 -0
  62. package/project/.sdk/src/cmp/ts/fragment/EntityListOp.fragment.js +57 -0
  63. package/project/.sdk/src/cmp/ts/fragment/EntityLoadOp.fragment.js +61 -0
  64. package/project/.sdk/src/cmp/ts/fragment/EntityRemoveOp.fragment.js +61 -0
  65. package/project/.sdk/src/cmp/ts/fragment/EntityUpdateOp.fragment.js +61 -0
  66. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.js +67 -0
  67. package/project/.sdk/tm/go/LICENSE +22 -0
  68. package/project/.sdk/tm/js/LICENSE +22 -0
  69. package/project/.sdk/tm/js/src/feature/log/LogFeature.js +108 -0
  70. package/project/.sdk/tm/js/src/utility/AuthUtility.js +21 -0
  71. package/project/.sdk/tm/js/src/utility/BodyUtility.js +29 -0
  72. package/project/.sdk/tm/js/src/utility/DoneUtility.js +15 -0
  73. package/project/.sdk/tm/js/src/utility/ErrorUtility.js +33 -0
  74. package/project/.sdk/tm/js/src/utility/FindparamUtility.js +31 -0
  75. package/project/.sdk/tm/js/src/utility/FullurlUtility.js +39 -0
  76. package/project/.sdk/tm/js/src/utility/HeadersUtility.js +13 -0
  77. package/project/.sdk/tm/js/src/utility/JoinurlUtility.js +14 -0
  78. package/project/.sdk/tm/js/src/utility/MethodUtility.js +22 -0
  79. package/project/.sdk/tm/js/src/utility/OperatorUtility.js +44 -0
  80. package/project/.sdk/tm/js/src/utility/OptionsUtility.js +54 -0
  81. package/project/.sdk/tm/js/src/utility/ParamsUtility.js +21 -0
  82. package/project/.sdk/tm/js/src/utility/QueryUtility.js +21 -0
  83. package/project/.sdk/tm/js/src/utility/ReqformUtility.js +32 -0
  84. package/project/.sdk/tm/js/src/utility/RequestUtility.js +48 -0
  85. package/project/.sdk/tm/js/src/utility/ResbasicUtility.js +27 -0
  86. package/project/.sdk/tm/js/src/utility/ResbodyUtility.js +15 -0
  87. package/project/.sdk/tm/js/src/utility/ResformUtility.js +34 -0
  88. package/project/.sdk/tm/js/src/utility/ResheadersUtility.js +19 -0
  89. package/project/.sdk/tm/js/src/utility/ResponseUtility.js +37 -0
  90. package/project/.sdk/tm/js/src/utility/ResultUtility.js +28 -0
  91. package/project/.sdk/tm/js/src/utility/SpecUtility.js +35 -0
  92. package/project/.sdk/tm/js/src/utility/StructUtility.js +1203 -0
  93. package/project/.sdk/tm/js/src/utility/Utility.js +74 -0
  94. package/project/.sdk/tm/js/test/runner.js +171 -0
  95. package/project/.sdk/tm/js/test/utility/Custom.test.js +85 -0
  96. package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +187 -0
  97. package/project/.sdk/tm/js/test/utility/StructUtility.test.js +367 -0
  98. package/project/.sdk/tm/py/LICENSE +22 -0
  99. package/project/.sdk/tm/ts/LICENSE +22 -0
  100. package/project/.sdk/tm/ts/src/README.md +1 -0
  101. package/project/.sdk/tm/ts/src/feature/README.md +1 -0
  102. package/project/.sdk/tm/ts/src/tsconfig.json +15 -0
  103. package/project/.sdk/tm/ts/src/utility/README.md +3 -0
  104. package/project/.sdk/tm/ts/test/README.md +2 -0
  105. package/project/.sdk/tm/ts/test/README.md~ +2 -0
  106. package/project/.sdk/tm/ts/test/tsconfig.json +13 -0
  107. package/src/action/feature.ts +4 -3
  108. package/src/action/target.ts +12 -6
  109. package/src/cmp/Entity.ts +3 -1
  110. package/src/cmp/Main.ts +30 -10
  111. package/src/sdkgen.ts +15 -5
  112. package/src/utility.ts +1 -2
@@ -0,0 +1,1203 @@
1
+
2
+ /* Copyright (c) 2025 Voxgig Ltd. MIT LICENSE. */
3
+
4
+ /* Voxgig Struct
5
+ * =============
6
+ *
7
+ * Utility functions to manipulate in-memory JSON-like data
8
+ * structures. The general design principle is
9
+ * "by-example". Transform specifications mirror the desired output.
10
+ * This implementation is desgined for porting to multiple language.
11
+ *
12
+ * - isnode, islist, islist, iskey: identify value kinds
13
+ * - clone: create a copy of a JSON-like data structure
14
+ * - items: list entries of a map or list as [key, value] pairs
15
+ * - getprop: safely get a property value by key
16
+ * - setprop: safely set a property value by key
17
+ * - getpath: get the value at a key path deep inside an object
18
+ * - merge: merge multiple nodes, overriding values in earlier nodes.
19
+ * - walk: walk a node tree, applying a function at each node and leaf.
20
+ * - inject: inject values from a data store into a new data structure.
21
+ * - transform: transform a data structure to an example structure.
22
+ */
23
+
24
+
25
+ // String constants.
26
+ const S = {
27
+ MKEYPRE: 'key:pre',
28
+ MKEYPOST: 'key:post',
29
+ MVAL: 'val',
30
+ MKEY: 'key',
31
+
32
+ TKEY: '`$KEY`',
33
+ TMETA: '`$META`',
34
+
35
+ KEY: 'KEY',
36
+
37
+ DTOP: '$TOP',
38
+
39
+ object: 'object',
40
+ array: 'array',
41
+ number: 'number',
42
+ boolean: 'boolean',
43
+ string: 'string',
44
+ function: 'function',
45
+ empty: '',
46
+ base: 'base',
47
+
48
+ BT: '`',
49
+ DS: '$',
50
+ DT: '.',
51
+ }
52
+
53
+ const UNDEF = undefined
54
+
55
+
56
+ // Value is a node - defined, and a map (hash) or list (array).
57
+ function isnode(val) {
58
+ return null != val && S.object == typeof val
59
+ }
60
+
61
+
62
+ // Value is a defined map (hash) with string keys.
63
+ function ismap(val) {
64
+ return null != val && S.object == typeof val && !Array.isArray(val)
65
+ }
66
+
67
+
68
+ // Value is a defined list (array) with integer keys (indexes).
69
+ function islist(val) {
70
+ return Array.isArray(val)
71
+ }
72
+
73
+
74
+ // Value is a defined string (non-empty) or integer key.
75
+ function iskey(key) {
76
+ const keytype = typeof key
77
+ return (S.string === keytype && S.empty !== key) || S.number === keytype
78
+ }
79
+
80
+
81
+ // Check for an "empty" value - undefined, null, empty string, array, object.
82
+ function isempty(val) {
83
+ return null == val || S.empty === val ||
84
+ (Array.isArray(val) && 0 === val.length) ||
85
+ (S.object === typeof val && 0 === Object.keys(val).length)
86
+ }
87
+
88
+
89
+ // TOOD: TEST
90
+ function isfunc(val) {
91
+ return S.function === typeof val
92
+ }
93
+
94
+
95
+ // Escape regular expression.
96
+ function escre(s) {
97
+ s = null == s ? S.empty : s
98
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
99
+ }
100
+
101
+
102
+ // Escape URL.
103
+ function escurl(s) {
104
+ s = null == s ? S.empty : s
105
+ return encodeURIComponent(s)
106
+ }
107
+
108
+
109
+ function joinurl(sarr) {
110
+ return sarr
111
+ .filter(s=>null!=s&&''!==s)
112
+ .map((s,i)=> 0===i ? s.replace(/([^\/])\/+/,'$1/').replace(/\/+$/,'') :
113
+ s.replace(/([^\/])\/+/,'$1/').replace(/^\/+/,'').replace(/\/+$/,''))
114
+ .filter(s=>''!==s)
115
+ .join('/')
116
+ }
117
+
118
+
119
+ // List the keys of a map or list as an array of tuples of the form [key, value].
120
+ function items(val) {
121
+ return ismap(val) ? Object.entries(val) :
122
+ islist(val) ? val.map((n, i) => [i, n]) :
123
+ []
124
+ }
125
+
126
+
127
+ // Sorted keys of a map, or indexes of an array.
128
+ function keysof(val) {
129
+ return !isnode(val) ? [] : ismap(val) ? Object.keys(val).sort() : val.map((n,i)=>i)
130
+ }
131
+
132
+
133
+ function haskey(val, key) {
134
+ return UNDEF !== getprop(val, key)
135
+ }
136
+
137
+
138
+ // Safely stringify a value for printing (NOT JSON!).
139
+ function stringify(val, maxlen) {
140
+ let json = S.empty
141
+
142
+ try {
143
+ json = JSON.stringify(val)
144
+ }
145
+ catch (err) {
146
+ json = S.empty + val
147
+ }
148
+
149
+ json = S.string !== typeof json ? S.empty + json : json
150
+ json = json.replace(/"/g, '')
151
+
152
+ if (null != maxlen) {
153
+ let js = json.substring(0, maxlen)
154
+ json = maxlen < json.length ? (js.substring(0, maxlen - 3) + '...') : json
155
+ }
156
+
157
+ return json
158
+ }
159
+
160
+
161
+ // Clone a JSON-like data structure.
162
+ // NOTE: function values are *not* cloned.
163
+ function clone(val) {
164
+ const refs = []
165
+ const replacer = (k,v)=>S.function === typeof v ?
166
+ (refs.push(v),'`$FUNCTION:'+(refs.length-1)+'`') : v
167
+ const reviver = (k,v,m)=>S.string === typeof v ?
168
+ (m=v.match(/^`\$FUNCTION:([0-9]+)`$/),m?refs[m[1]]:v) : v
169
+ return UNDEF === val ? UNDEF : JSON.parse(JSON.stringify(val,replacer),reviver)
170
+ }
171
+
172
+
173
+ // Safely get a property of a node. UNDEF arguments return UNDEF.
174
+ // If the key is not found, return the alternative value.
175
+ function getprop(val, key, alt) {
176
+ let out = UNDEF === val ? alt : UNDEF === key ? alt : val[key]
177
+ out = UNDEF === out ? alt : out
178
+ return out
179
+ }
180
+
181
+
182
+ // Safely set a property. UNDEF arguments and invalid keys are ignored.
183
+ // Returns the (possible modified) parent.
184
+ // If the value is UNDEF it the key will be deleted from the parent.
185
+ // If the parent is a list, and the key is negative, prepend the value.
186
+ // If the key is above the list size, append the value.
187
+ // If the value is UNDEF, remove the list element at index key, and shift the
188
+ // remaining elements down. These rules avoids "holes" in the list.
189
+ function setprop(parent, key, val) {
190
+ if (!iskey(key)) {
191
+ return parent
192
+ }
193
+
194
+ if (ismap(parent)) {
195
+ key = S.empty + key
196
+ if (UNDEF === val) {
197
+ delete parent[key]
198
+ }
199
+ else {
200
+ parent[key] = val
201
+ }
202
+ }
203
+ else if (islist(parent)) {
204
+ // Ensure key is an integer.
205
+ let keyI = +key
206
+
207
+ if (isNaN(keyI)) {
208
+ return parent
209
+ }
210
+
211
+ keyI = Math.floor(keyI)
212
+
213
+ // Delete list element at position keyI, shifting later elements down.
214
+ if (UNDEF === val) {
215
+ if (0 <= keyI && keyI < parent.length) {
216
+ for (let pI = keyI; pI < parent.length - 1; pI++) {
217
+ parent[pI] = parent[pI + 1]
218
+ }
219
+ parent.length = parent.length - 1
220
+ }
221
+ }
222
+
223
+ // Set or append value at position keyI, or append if keyI out of bounds.
224
+ else if (0 <= keyI) {
225
+ parent[parent.length < keyI ? parent.length : keyI] = val
226
+ }
227
+
228
+ // Prepend value if keyI is negative
229
+ else {
230
+ parent.unshift(val)
231
+ }
232
+ }
233
+
234
+ return parent
235
+ }
236
+
237
+
238
+ // Walk a data structure depth first.
239
+ function walk(
240
+ // These arguments are the public interface.
241
+ val,
242
+ apply,
243
+
244
+ // These areguments are used for recursive state.
245
+ key,
246
+ parent,
247
+ path
248
+ ) {
249
+ if (isnode(val)) {
250
+ for (let [ckey, child] of items(val)) {
251
+ setprop(val, ckey, walk(child, apply, ckey, val, [...(path || []), S.empty + ckey]))
252
+ }
253
+ }
254
+
255
+ // Nodes are applied *after* their children.
256
+ // For the root node, key and parent will be UNDEF.
257
+ return apply(key, val, parent, path || [])
258
+ }
259
+
260
+
261
+ // Merge a list of values into each other. Later values have precedence.
262
+ // Nodes override scalars. Node kinds (list or map) override each other.
263
+ // The first element is modified.
264
+ function merge(objs) {
265
+ let out = UNDEF
266
+
267
+ if (!islist(objs)) {
268
+ out = objs
269
+ }
270
+ else if (0 === objs.length) {
271
+ out = UNDEF
272
+ }
273
+ else if (1 === objs.length) {
274
+ out = objs[0]
275
+ }
276
+ else {
277
+
278
+ out = getprop(objs, 0, {})
279
+
280
+ // Merge remaining down onto first.
281
+ for (let oI = 1; oI < objs.length; oI++) {
282
+ let obj = objs[oI]
283
+
284
+ if (!isnode(obj)) {
285
+ // Nodes win.
286
+ out = obj
287
+ }
288
+ else {
289
+ // Nodes win, also over nodes of a different kind.
290
+ if (!isnode(out) || (ismap(obj) && islist(out)) || (islist(obj) && ismap(out))) {
291
+ out = obj
292
+ }
293
+ else {
294
+ let cur = [out] // Node stack
295
+ let cI = 0
296
+
297
+ // Walk overriding node, creating paths in output as needed.
298
+ walk(obj, (key, val, parent, path) => {
299
+ if (null == key) {
300
+ return val
301
+ }
302
+
303
+ let lenpath = path.length
304
+
305
+ cI = lenpath - 1
306
+ if (UNDEF === cur[cI]) {
307
+ cur[cI] = getpath(path.slice(0, lenpath - 1), out)
308
+ }
309
+
310
+ // Create node if needed.
311
+ if (!isnode(cur[cI])) {
312
+ cur[cI] = islist(parent) ? [] : {}
313
+ }
314
+
315
+ // Node child is just ahead of us on the stack.
316
+ if (isnode(val) && !isempty(val)) {
317
+ setprop(cur[cI], key, cur[cI + 1])
318
+ cur[cI + 1] = UNDEF
319
+ }
320
+
321
+ // Scalar child or empty node.
322
+ else {
323
+ setprop(cur[cI], key, val)
324
+ }
325
+
326
+ return val
327
+ })
328
+ }
329
+ }
330
+ }
331
+ }
332
+
333
+ return out
334
+ }
335
+
336
+
337
+ // Get a value deep inside a node using a key path.
338
+ // For example the path `a.b` gets the value 1 from {a:{b:1}}.
339
+ // The path can specified as a dotted string, or a string array.
340
+ // If the path starts with a dot (or the first element is ''), the path is considered local,
341
+ // and resolved against the `current` argument, if defined.
342
+ // Integer path parts are used as array indexes.
343
+ // The state argument allows for custom handling when called from `inject` or `transform`.
344
+ function getpath(path, store, current, state) {
345
+
346
+ const parts = islist(path) ? path : S.string === typeof path ? path.split(S.DT) : UNDEF
347
+
348
+ if (UNDEF === parts) {
349
+ return UNDEF
350
+ }
351
+
352
+ let root = store
353
+ let val = store
354
+
355
+ // An empty path (incl empty string) just finds the store.
356
+ if (null == path || null == store || (1 === parts.length && S.empty === parts[0])) {
357
+ // The actual store data may be in a store sub property, defined by state.base.
358
+ val = getprop(store, getprop(state, S.base), store)
359
+ }
360
+ else if (0 < parts.length) {
361
+ let pI = 0
362
+
363
+ // Relative path uses `current` argument.
364
+ if (S.empty === parts[0]) {
365
+ pI = 1
366
+ root = current
367
+ }
368
+
369
+ let part = pI < parts.length ? parts[pI] : UNDEF
370
+ let first = getprop(root, part)
371
+
372
+ // At top level, check state.base, if provided
373
+ val = (UNDEF === first && 0 === pI) ?
374
+ getprop(getprop(root, getprop(state, S.base)), part) :
375
+ first
376
+
377
+ // Move along the path, trying to descend into the store.
378
+ for (pI++; UNDEF !== val && pI < parts.length; pI++) {
379
+ val = getprop(val, parts[pI])
380
+ }
381
+
382
+ }
383
+
384
+ // State may provide a custom handler to modify found value.
385
+ if (null != state && S.function === typeof state.handler) {
386
+ val = state.handler(state, val, current, path, store)
387
+ }
388
+
389
+ return val
390
+ }
391
+
392
+
393
+ // Inject store values into a string. Not a public utility - used by `inject`.
394
+ // Inject are marked with `path` where path is resolved with getpath against the
395
+ // store or current (if defined) arguments. See `getpath`.
396
+ // Custom injection handling can be provided by state.handler (this is used for
397
+ // transform functions).
398
+ // The path can also have the special syntax $NAME999 where NAME is upper case letters only,
399
+ // and 999 is any digits, which are discarded. This syntax specifies the name of a transform,
400
+ // and optionally allows transforms to be ordered by alphanumeric sorting.
401
+ function injectstr(val, store, current, state) {
402
+ if (S.string !== typeof val) {
403
+ return S.empty
404
+ }
405
+
406
+ let out = val
407
+ const m = val.match(/^`(\$[A-Z]+|[^`]+)[0-9]*`$/)
408
+
409
+ // Full string is an injection.
410
+ if (m) {
411
+ if (state) {
412
+ state.full = true
413
+ }
414
+ let ref = m[1]
415
+
416
+ // Special escapes inside injection.
417
+ ref = 3 < ref.length ? ref.replace(/\$BT/g, S.BT).replace(/\$DS/g, S.DS) : ref
418
+
419
+ out = getpath(ref, store, current, state)
420
+ }
421
+
422
+ // Check for injections within the string.
423
+ else {
424
+ out = val.replace(/`([^`]+)`/g,
425
+ (_m, ref) => {
426
+ ref = 3 < ref.length ? ref.replace(/\$BT/g, S.BT).replace(/\$DS/g, S.DS) : ref
427
+ if (state) {
428
+ state.full = false
429
+ }
430
+ const found = getpath(ref, store, current, state)
431
+
432
+ return UNDEF === found ? S.empty :
433
+ S.object === typeof found ? JSON.stringify(found) :
434
+ found
435
+ })
436
+
437
+ // Also call the handler on the entire string.
438
+ if (state.handler) {
439
+ state.full = true
440
+ out = state.handler(state, out, current, val, store)
441
+ }
442
+ }
443
+
444
+ return out
445
+ }
446
+
447
+
448
+ // Inject values from a data store into a node recursively, resolving paths against the store,
449
+ // or current if they are local. THe modify argument allows custom modification of the result.
450
+ // The state (InjectState) argument is used to maintain recursive state.
451
+ function inject(
452
+ val,
453
+ store,
454
+ modify,
455
+ current,
456
+ state,
457
+ ) {
458
+ const valtype = typeof val
459
+
460
+ // Create state if at root of injection.
461
+ // The input value is placed inside a virtual parent holder
462
+ // to simplify edge cases.
463
+ if (UNDEF === state) {
464
+ const parent = { [S.DTOP]: val }
465
+ state = {
466
+ mode: S.MVAL,
467
+ full: false,
468
+ keyI: 0,
469
+ keys: [S.DTOP],
470
+ key: S.DTOP,
471
+ val,
472
+ parent,
473
+ path: [S.DTOP],
474
+ nodes: [parent],
475
+ handler: injecthandler,
476
+ base: S.DTOP,
477
+ modify,
478
+ errs: store.$ERRS || [],
479
+ }
480
+ }
481
+
482
+ // Resolve current node in store for local paths.
483
+ if (UNDEF === current) {
484
+ current = { $TOP: store }
485
+ }
486
+ else {
487
+ const parentkey = state.path[state.path.length - 2]
488
+ current = null == parentkey ? current : getprop(current, parentkey)
489
+ }
490
+
491
+ // Desend into node.
492
+ if (isnode(val)) {
493
+
494
+ // Keys are sorted alphanumerically to ensure determinism.
495
+ // Injection transforms ($FOO) are processed *after* other keys.
496
+ // NOTE: the optional digits suffix of the transform can thsu be used to
497
+ // order the transforms.
498
+ const origkeys = ismap(val) ? [
499
+ ...Object.keys(val).filter(k => !k.includes(S.DS)),
500
+ ...Object.keys(val).filter(k => k.includes(S.DS)).sort(),
501
+ ] : val.map((_n, i) => i)
502
+
503
+
504
+ // Each child key-value pair is processed in three injection phases:
505
+ // 1. state.mode='key:pre' - Key string is injected, returning a possibly altered key.
506
+ // 2. state.mode='val' - The child value is injected.
507
+ // 3. state.mode='key:post' - Key string is injected again, allowing child mutation.
508
+ for (let okI = 0; okI < origkeys.length; okI++) {
509
+ const origkey = S.empty + origkeys[okI]
510
+
511
+ let childpath = [...(state.path || []), origkey]
512
+ let childnodes = [...(state.nodes || []), val]
513
+
514
+ const childstate = {
515
+ mode: S.MKEYPRE,
516
+ full: false,
517
+ keyI: okI,
518
+ keys: origkeys,
519
+ key: origkey,
520
+ val,
521
+ parent: val,
522
+ path: childpath,
523
+ nodes: childnodes,
524
+ handler: injecthandler,
525
+ base: state.base,
526
+ errs: store.$ERRS || [],
527
+ }
528
+
529
+ const prekey = injectstr(origkey, store, current, childstate)
530
+ okI = childstate.keyI
531
+
532
+ // Prevent further processing by returning an UNDEF prekey
533
+ if (null != prekey) {
534
+ let child = val[prekey]
535
+ childstate.mode = S.MVAL
536
+ inject(
537
+ child,
538
+ store,
539
+ modify,
540
+ current,
541
+ childstate,
542
+ )
543
+ okI = childstate.keyI
544
+
545
+ childstate.mode = S.MKEYPOST
546
+ injectstr(origkey, store, current, childstate)
547
+ okI = childstate.keyI
548
+ }
549
+ }
550
+ }
551
+
552
+ // Inject paths into string scalars.
553
+ else if (S.string === valtype) {
554
+ state.mode = S.MVAL
555
+ const newval = injectstr(val, store, current, state)
556
+ val = newval
557
+
558
+ setprop(state.parent, state.key, newval)
559
+ }
560
+
561
+ // Other scalars are left in place unchanged.
562
+
563
+ // Custom modification.
564
+ if (modify) {
565
+ modify(
566
+ state.key,
567
+ val,
568
+ state.parent,
569
+ state,
570
+ current,
571
+ store
572
+ )
573
+ }
574
+
575
+ // Original val reference may no longer be correct.
576
+ const out = getprop(state.parent, S.DTOP)
577
+
578
+ // Output is only needed at the top level as the final result
579
+ return out
580
+ }
581
+
582
+
583
+ // Default inject handler for transforms. If the path resolves to a function,
584
+ // call the function passing the injection state. This is how transforms operate.
585
+ const injecthandler = (state, val, current, ref, store) => {
586
+ let out = val
587
+
588
+ if (S.function === typeof val && ref.startsWith('$')) {
589
+ out = val(state, val, current, store)
590
+ }
591
+ else if (S.MVAL === state.mode && state.full) {
592
+ setprop(state.parent, state.key, val)
593
+ }
594
+
595
+ return out
596
+ }
597
+
598
+
599
+ // The transform_* functions are define inject handlers (see InjectHandler).
600
+
601
+
602
+ // Delete a key from a map or list.
603
+ const transform_DELETE = (state) => {
604
+ const { key, parent } = state
605
+ setprop(parent, key, UNDEF)
606
+ return UNDEF
607
+ }
608
+
609
+
610
+ // Copy value from source data.
611
+ const transform_COPY = (state, _val, current) => {
612
+ const { mode, key, parent } = state
613
+
614
+ let out
615
+ if (mode.startsWith(S.MKEY)) {
616
+ out = key
617
+ }
618
+ else {
619
+ out = getprop(current, key)
620
+ setprop(parent, key, out)
621
+ }
622
+
623
+ return out
624
+ }
625
+
626
+
627
+ // As a value, inject the key of the parent node.
628
+ // As a key, defined the name of the key property in the source object.
629
+ const transform_KEY = (state, _val, current) => {
630
+ const { mode, path, parent } = state
631
+
632
+ if (S.MVAL !== mode) {
633
+ return UNDEF
634
+ }
635
+
636
+ const keyspec = getprop(parent, S.TKEY)
637
+ if (UNDEF !== keyspec) {
638
+ setprop(parent, S.TKEY, UNDEF)
639
+ return getprop(current, keyspec)
640
+ }
641
+
642
+ return getprop(getprop(parent, S.TMETA), S.KEY, getprop(path, path.length - 2))
643
+ }
644
+
645
+
646
+ // Store meta data about a node.
647
+ const transform_META = (state) => {
648
+ const { parent } = state
649
+ setprop(parent, S.TMETA, UNDEF)
650
+ return UNDEF
651
+ }
652
+
653
+
654
+ // Merge a list of objects into the current object.
655
+ // Must be a key in an object. The value is merged over the current object.
656
+ // If the value is an array, the elements are first merged using `merge`.
657
+ // If the value is the empty string, merge the top level store.
658
+ // Format: { '`$MERGE`': '`source-path`' | ['`source-paths`', ...] }
659
+ const transform_MERGE = (
660
+ state, _val, store
661
+ ) => {
662
+ const { mode, key, parent } = state
663
+
664
+ if (S.MKEYPRE === mode) { return key }
665
+
666
+ // Operate after child values have been transformed.
667
+ if (S.MKEYPOST === mode) {
668
+
669
+ let args = getprop(parent, key)
670
+ args = S.empty === args ? [store.$TOP] : Array.isArray(args) ? args : [args]
671
+
672
+ setprop(parent, key, UNDEF)
673
+
674
+ // Literals in the parent have precedence.
675
+ const mergelist = [parent, ...args, clone(parent)]
676
+
677
+ merge(mergelist)
678
+
679
+ return key
680
+ }
681
+
682
+ return UNDEF
683
+ }
684
+
685
+
686
+ // Convert a node to a list.
687
+ // Format: ['`$EACH`', '`source-path-of-node`', child-template]
688
+ const transform_EACH = (
689
+ state,
690
+ _val,
691
+ current,
692
+ store
693
+ ) => {
694
+ const { mode, keys, path, parent, nodes } = state
695
+
696
+ // Remove arguments to avoid spurious processing.
697
+ if (keys) {
698
+ keys.length = 1
699
+ }
700
+
701
+ // Defensive context checks.
702
+ if (S.MVAL !== mode || null == path || null == nodes) {
703
+ return UNDEF
704
+ }
705
+
706
+ // Get arguments.
707
+ const srcpath = parent[1] // Path to source data.
708
+ const child = clone(parent[2]) // Child template.
709
+
710
+ // Source data
711
+ const src = getpath(srcpath, store, current, state)
712
+
713
+ // Create parallel data structures:
714
+ // source entries :: child templates
715
+ let tcurrent = []
716
+ let tval = []
717
+
718
+ const tkey = path[path.length - 2]
719
+ const target = nodes[path.length - 2] || nodes[path.length - 1]
720
+
721
+ if (isnode(src)) {
722
+ if (islist(src)) {
723
+ tval = src.map(() => clone(child))
724
+ }
725
+ else {
726
+ tval = Object.entries(src).map(n => ({
727
+ ...clone(child),
728
+
729
+ // Make a note of the key for $KEY transforms
730
+ [S.TMETA]: { KEY: n[0] }
731
+ }))
732
+ }
733
+
734
+ tcurrent = Object.values(src)
735
+ }
736
+
737
+ // Parent structure.
738
+ tcurrent = { $TOP: tcurrent }
739
+
740
+ // Build the substructure.
741
+ tval = inject(
742
+ tval,
743
+ store,
744
+ state.modify,
745
+ tcurrent,
746
+ )
747
+
748
+ setprop(target, tkey, tval)
749
+
750
+ // Prevent callee from damaging first list entry (since we are in `val` mode).
751
+ return tval[0]
752
+ }
753
+
754
+
755
+
756
+ // Convert a node to a map.
757
+ // Format: { '`$PACK`':['`source-path`', child-template]}
758
+ const transform_PACK = (
759
+ state,
760
+ _val,
761
+ current,
762
+ store
763
+ ) => {
764
+ const { mode, key, path, parent, nodes } = state
765
+
766
+ // Defensive context checks.
767
+ if (S.MKEYPRE !== mode || S.string !== typeof key || null == path || null == nodes) {
768
+ return UNDEF
769
+ }
770
+
771
+ // Get arguments.
772
+ const args = parent[key]
773
+ const srcpath = args[0] // Path to source data.
774
+ const child = clone(args[1]) // Child template.
775
+
776
+ // Find key and target node.
777
+ const keyprop = child[S.TKEY]
778
+ const tkey = path[path.length - 2]
779
+ const target = nodes[path.length - 2] || nodes[path.length - 1]
780
+
781
+ // Source data
782
+ let src = getpath(srcpath, store, current, state)
783
+
784
+ // Prepare source as a list.
785
+ src = islist(src) ? src :
786
+ ismap(src) ? Object.entries(src)
787
+ .reduce((a, n) =>
788
+ (n[1][S.TMETA] = { KEY: n[0] }, a.push(n[1]), a), []) :
789
+ UNDEF
790
+
791
+ if (null == src) {
792
+ return UNDEF
793
+ }
794
+
795
+ // Get key if specified.
796
+ let childkey = getprop(child, S.TKEY)
797
+ let keyname = UNDEF === childkey ? keyprop : childkey
798
+ setprop(child, S.TKEY, UNDEF)
799
+
800
+ // Build parallel target object.
801
+ let tval = {}
802
+ tval = src.reduce((a, n) => {
803
+ let kn = getprop(n, keyname)
804
+ setprop(a, kn, clone(child))
805
+ const nchild = getprop(a, kn)
806
+ setprop(nchild, S.TMETA, getprop(n, S.TMETA))
807
+ return a
808
+ }, tval)
809
+
810
+ // Build parallel source object.
811
+ let tcurrent = {}
812
+ src.reduce((a, n) => {
813
+ let kn = getprop(n, keyname)
814
+ setprop(a, kn, n)
815
+ return a
816
+ }, tcurrent)
817
+
818
+ tcurrent = { $TOP: tcurrent }
819
+
820
+ // Build substructure.
821
+ tval = inject(
822
+ tval,
823
+ store,
824
+ state.modify,
825
+ tcurrent,
826
+ )
827
+
828
+ setprop(target, tkey, tval)
829
+
830
+ // Drop transform key.
831
+ return UNDEF
832
+ }
833
+
834
+
835
+ // Transform data using spec.
836
+ // Only operates on static JSON-like data (values can be functions however).
837
+ // Arrays are treated as if they are objects with indices as keys.
838
+ function transform(
839
+ data, // Source data to transform into new data (original not mutated)
840
+ spec, // Transform specification; output follows this shape
841
+ extra, // Additional store of data and transforms.
842
+ modify // Optionally modify individual values.
843
+ ) {
844
+ // Clone the spec so that the clone can be modified in place as the transform result.
845
+ spec = clone(spec)
846
+
847
+ const extraTransforms = {}
848
+ const extraData = null == extra ? {} : items(extra)
849
+ .reduce((a, n) =>
850
+ (n[0].startsWith(S.DS) ? extraTransforms[n[0]] = n[1] : (a[n[0]] = n[1]), a), {})
851
+
852
+ const dataClone = merge([
853
+ clone(UNDEF === extraData ? {} : extraData),
854
+ clone(UNDEF === data ? {} : data),
855
+ ])
856
+
857
+ // Define a top level store that provides transform operations.
858
+ const store = {
859
+
860
+ // The inject function recognises this special location for the root of the source data.
861
+ // NOTE: to escape data that contains "`$FOO`" keys at the top level,
862
+ // place that data inside a holding map: { myholder: mydata }.
863
+ $TOP: dataClone,
864
+
865
+ // Escape backtick (this also works inside backticks).
866
+ $BT: () => S.BT,
867
+
868
+ // Escape dollar sign (this also works inside backticks).
869
+ $DS: () => S.DS,
870
+
871
+ // Insert current date and time as an ISO string.
872
+ $WHEN: () => new Date().toISOString(),
873
+
874
+ $DELETE: transform_DELETE,
875
+ $COPY: transform_COPY,
876
+ $KEY: transform_KEY,
877
+ $META: transform_META,
878
+ $MERGE: transform_MERGE,
879
+ $EACH: transform_EACH,
880
+ $PACK: transform_PACK,
881
+
882
+ // Custom extra transforms, if any.
883
+ ...extraTransforms,
884
+ }
885
+
886
+ const out = inject(spec, store, modify, store)
887
+
888
+ return out
889
+ }
890
+
891
+
892
+
893
+ function validate(
894
+ data, // Source data to transform into new data (original not mutated)
895
+ spec, // Transform specification; output follows this shape
896
+
897
+ // TODO
898
+ extra, // Additional custom checks
899
+
900
+ // modify // Optionally modify individual values.
901
+ collecterrs,
902
+ ) {
903
+ const errs = collecterrs || []
904
+ const out = transform(
905
+ data,
906
+ spec,
907
+ {
908
+ $ERRS: errs,
909
+
910
+ $DELETE: null,
911
+ $COPY: null,
912
+ $KEY: null,
913
+ $META: null,
914
+ $MERGE: null,
915
+ $EACH: null,
916
+ $PACK: null,
917
+
918
+ $STRING: (state, val, current)=>{
919
+ const { mode, key, parent } = state
920
+ let out = getprop(current, key)
921
+
922
+ let t = typeof out
923
+ if(S.string === t) {
924
+ if(S.empty === out) {
925
+ state.errs.push('Empty string at '+pathify(state.path))
926
+ }
927
+ else {
928
+ return out
929
+ }
930
+ }
931
+ else {
932
+ state.errs.push(invalidTypeMsg(state.path,S.string,t,out))
933
+ return
934
+ }
935
+ },
936
+
937
+ $NUMBER: (state, val, current)=>{
938
+ const { mode, key, parent } = state
939
+ let out = getprop(current, key)
940
+
941
+ let t = typeof out
942
+ if(S.number !== t) {
943
+ state.errs.push(invalidTypeMsg(state.path,S.number,t,out))
944
+ return
945
+ }
946
+
947
+ return out
948
+ },
949
+
950
+ $BOOLEAN: (state, val, current)=>{
951
+ const { mode, key, parent } = state
952
+ let out = getprop(current, key)
953
+
954
+ let t = typeof out
955
+ if(S.boolean !== t) {
956
+ state.errs.push(invalidTypeMsg(state.path,S.boolean,t,out))
957
+ return
958
+ }
959
+
960
+ return out
961
+ },
962
+
963
+ $OBJECT: (state, val, current)=>{
964
+ const { mode, key, parent } = state
965
+ let out = getprop(current, key)
966
+
967
+ let t = typeof out
968
+
969
+ if(null == out || S.object !== t) {
970
+ state.errs.push(invalidTypeMsg(state.path,S.object,t,out))
971
+ return
972
+ }
973
+
974
+ return out
975
+ },
976
+
977
+ $ARRAY: (state, val, current)=>{
978
+ const { mode, key, parent } = state
979
+ let out = getprop(current, key)
980
+
981
+ let t = typeof out
982
+ if(!Array.isArray(out)) {
983
+ state.errs.push(invalidTypeMsg(state.path,S.array,t,out))
984
+ return
985
+ }
986
+
987
+ return out
988
+ },
989
+
990
+ $FUNCTION: (state, val, current)=>{
991
+ const { mode, key, parent } = state
992
+ let out = getprop(current, key)
993
+
994
+ let t = typeof out
995
+ if(S.function !== t) {
996
+ state.errs.push(invalidTypeMsg(state.path,S.function,t,out))
997
+ return
998
+ }
999
+
1000
+ return out
1001
+ },
1002
+
1003
+ $ANY: (state, val, current)=>{
1004
+ const { mode, key, parent } = state
1005
+ let out = getprop(current, key)
1006
+ return out
1007
+ },
1008
+
1009
+ $CHILD: (state, val, current)=>{
1010
+ const { mode, key, parent, keys, path } = state
1011
+
1012
+ if(S.MKEYPRE === mode) {
1013
+ const child = parent[key]
1014
+ const pkey = path[path.length-2]
1015
+ const tval = current[pkey]
1016
+
1017
+ const ckeys = keysof(tval)
1018
+ for(let ckey of ckeys) {
1019
+ parent[ckey] = clone(child)
1020
+ keys.push(ckey)
1021
+ }
1022
+
1023
+ delete parent[key]
1024
+
1025
+ }
1026
+ else if(S.MVAL === mode) {
1027
+ if(!islist(parent)) {
1028
+ state.errs.push('Invalid $CHILD as value')
1029
+ }
1030
+
1031
+ const child = parent[1]
1032
+
1033
+ if(UNDEF === current) {
1034
+ parent.length = 0
1035
+ return UNDEF
1036
+ }
1037
+ else if(!islist(current)) {
1038
+ state.errs.push(invalidTypeMsg(
1039
+ state.path.slice(0,state.path.length-1),S.array,typeof current,current))
1040
+ state.keyI = parent.length
1041
+ return current
1042
+ }
1043
+ else {
1044
+ current.map((n,i)=>parent[i]=clone(child))
1045
+ parent.length = current.length
1046
+ state.keyI = 0
1047
+ return current[0]
1048
+ }
1049
+ }
1050
+ },
1051
+
1052
+ $ONE: (state, val, current)=>{
1053
+ const { mode, key, parent, keys, path, nodes } = state
1054
+
1055
+ if(S.MVAL === mode) {
1056
+ state.keyI = state.keys.length
1057
+
1058
+ let tvals = parent.slice(1)
1059
+
1060
+ for(let tval of tvals) {
1061
+ let terrs = []
1062
+ validate(current,tval,UNDEF,terrs)
1063
+
1064
+ const grandparent = nodes[nodes.length-2]
1065
+ const grandkey = path[path.length-2]
1066
+
1067
+ if(isnode(grandparent)) {
1068
+ if(0===terrs.length) {
1069
+ setprop(grandparent, grandkey, current)
1070
+ return
1071
+ }
1072
+ else {
1073
+ setprop(grandparent, grandkey, UNDEF)
1074
+ }
1075
+ }
1076
+ }
1077
+
1078
+ const valdesc = tvals
1079
+ .map(v=>stringify(v))
1080
+ .join(', ')
1081
+ .replace(/`\$([A-Z]+)`/g, (m,p1)=>p1.toLowerCase())
1082
+
1083
+ state.errs.push(invalidTypeMsg(
1084
+ state.path.slice(0,state.path.length-1),
1085
+ 'one of '+valdesc,
1086
+ typeof current, current))
1087
+ }
1088
+ },
1089
+
1090
+ ...(extra||{})
1091
+ },
1092
+
1093
+ (key,
1094
+ val,
1095
+ parent,
1096
+ state,
1097
+ current,
1098
+ store)=>{
1099
+ const cval = isnode(current) ? current[key] : UNDEF
1100
+
1101
+ if(UNDEF === cval) {
1102
+ return
1103
+ }
1104
+
1105
+ const pval = parent[key]
1106
+
1107
+ const t = typeof pval
1108
+
1109
+ if(S.string === t && pval.includes('$')) {
1110
+ return
1111
+ }
1112
+
1113
+ //const t = typeof val
1114
+ const ct = typeof cval
1115
+
1116
+ if(t !== ct && UNDEF !== pval) {
1117
+ state.errs.push(invalidTypeMsg(state.path,t,ct,cval))
1118
+ }
1119
+ else if(ismap(cval)) {
1120
+ const ckeys = keysof(cval)
1121
+ const pkeys = keysof(pval)
1122
+
1123
+ // Empty spec object {} means object can be open (any keys).
1124
+ if(0 < pkeys.length && true !== getprop(pval,'`$OPEN`')) {
1125
+ const badkeys = []
1126
+ for(ckey of ckeys) {
1127
+ if(!haskey(val, ckey)) {
1128
+ badkeys.push(ckey)
1129
+ }
1130
+ }
1131
+ if(0 < badkeys.length) {
1132
+ state.errs.push('Unexpected keys at '+pathify(state.path)+
1133
+ ': '+badkeys.join(', '))
1134
+ }
1135
+ }
1136
+ else {
1137
+ merge([pval, cval])
1138
+ if(isnode(pval)) {
1139
+ delete pval['`$OPEN`']
1140
+ }
1141
+ }
1142
+ }
1143
+ else if(islist(cval)) {
1144
+ if(!islist(val)) {
1145
+ state.errs.push(invalidTypeMsg(state.path,t,ct,cval))
1146
+ }
1147
+ }
1148
+ else {
1149
+ // Spec value was a default, copy over data
1150
+ parent[key] = cval
1151
+ }
1152
+ }
1153
+ )
1154
+
1155
+ if(0 < errs.length && null == collecterrs) {
1156
+ throw new Error('Invalid data: '+errs.join('\n'))
1157
+ }
1158
+
1159
+ return out
1160
+ }
1161
+
1162
+
1163
+ function invalidTypeMsg(path, type, vt, v) {
1164
+ return 'Expected '+type+' at '+pathify(path)+', found '+(null != v?vt+': ':'')+v
1165
+ }
1166
+
1167
+ function pathify(val, from) {
1168
+ from = null == from ? 1 : -1 < from ? from : 1
1169
+ if(Array.isArray(val)) {
1170
+ let path = val.slice(from)
1171
+ if(0 === path.length) {
1172
+ return '<root>'
1173
+ }
1174
+ return path.join('.')
1175
+ }
1176
+ return '<unknown-path>'
1177
+ }
1178
+
1179
+
1180
+ module.exports = {
1181
+ clone,
1182
+ escre,
1183
+ escurl,
1184
+ getpath,
1185
+ getprop,
1186
+ inject,
1187
+ isempty,
1188
+ iskey,
1189
+ islist,
1190
+ ismap,
1191
+ isnode,
1192
+ isfunc,
1193
+ haskey,
1194
+ keysof,
1195
+ items,
1196
+ merge,
1197
+ setprop,
1198
+ stringify,
1199
+ transform,
1200
+ walk,
1201
+ joinurl,
1202
+ validate,
1203
+ }