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