@voxgig/sdkgen 0.35.2 → 0.37.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 (498) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/Main.js +0 -12
  3. package/dist/cmp/Main.js.map +1 -1
  4. package/dist/cmp/Readme.js +5 -5
  5. package/dist/cmp/Readme.js.map +1 -1
  6. package/dist/cmp/ReadmeEntity.js +74 -24
  7. package/dist/cmp/ReadmeEntity.js.map +1 -1
  8. package/dist/cmp/ReadmeExplanation.d.ts +2 -0
  9. package/dist/cmp/ReadmeExplanation.js +308 -0
  10. package/dist/cmp/ReadmeExplanation.js.map +1 -0
  11. package/dist/cmp/ReadmeHowto.d.ts +2 -0
  12. package/dist/cmp/ReadmeHowto.js +18 -0
  13. package/dist/cmp/ReadmeHowto.js.map +1 -0
  14. package/dist/cmp/ReadmeIntro.js +8 -23
  15. package/dist/cmp/ReadmeIntro.js.map +1 -1
  16. package/dist/cmp/ReadmeModel.js +55 -91
  17. package/dist/cmp/ReadmeModel.js.map +1 -1
  18. package/dist/cmp/ReadmeOptions.js +35 -11
  19. package/dist/cmp/ReadmeOptions.js.map +1 -1
  20. package/dist/cmp/ReadmeQuick.js +4 -1
  21. package/dist/cmp/ReadmeQuick.js.map +1 -1
  22. package/dist/cmp/ReadmeRef.js +1042 -40
  23. package/dist/cmp/ReadmeRef.js.map +1 -1
  24. package/dist/cmp/ReadmeTop.d.ts +2 -0
  25. package/dist/cmp/ReadmeTop.js +171 -0
  26. package/dist/cmp/ReadmeTop.js.map +1 -0
  27. package/dist/sdkgen.d.ts +7 -1
  28. package/dist/sdkgen.js +13 -1
  29. package/dist/sdkgen.js.map +1 -1
  30. package/dist/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +2 -2
  32. package/project/.sdk/model/feature/feature-index.jsonic +5 -0
  33. package/project/.sdk/model/feature/log.jsonic +5 -1
  34. package/project/.sdk/model/feature/test.jsonic +1 -1
  35. package/project/.sdk/model/target/lua.jsonic +23 -0
  36. package/project/.sdk/model/target/php.jsonic +22 -0
  37. package/project/.sdk/model/target/py.jsonic +23 -0
  38. package/project/.sdk/model/target/rb.jsonic +23 -0
  39. package/project/.sdk/src/cmp/go/Config_go.ts +6 -2
  40. package/project/.sdk/src/cmp/go/Main_go.ts +2 -6
  41. package/project/.sdk/src/cmp/go/ReadmeExplanation_go.ts +42 -0
  42. package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +112 -0
  43. package/project/.sdk/src/cmp/go/ReadmeInstall_go.ts +29 -0
  44. package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +129 -0
  45. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +163 -0
  46. package/project/.sdk/src/cmp/go/ReadmeTopHowto_go.ts +24 -0
  47. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +67 -0
  48. package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +40 -0
  49. package/project/.sdk/src/cmp/go/TestDirect_go.ts +46 -2
  50. package/project/.sdk/src/cmp/go/TestEntity_go.ts +360 -160
  51. package/project/.sdk/src/cmp/go/Test_go.ts +24 -1
  52. package/project/.sdk/src/cmp/js/EntityBase_js.ts +34 -0
  53. package/project/.sdk/src/cmp/js/Main_js.ts +10 -0
  54. package/project/.sdk/src/cmp/js/ReadmeExplanation_js.ts +33 -0
  55. package/project/.sdk/src/cmp/js/ReadmeHowto_js.ts +123 -0
  56. package/project/.sdk/src/cmp/js/ReadmeModel_js.ts +152 -0
  57. package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +1 -1
  58. package/project/.sdk/src/cmp/js/ReadmeTopHowto_js.ts +25 -0
  59. package/project/.sdk/src/cmp/js/ReadmeTopQuick_js.ts +65 -0
  60. package/project/.sdk/src/cmp/js/ReadmeTopTest_js.ts +36 -0
  61. package/project/.sdk/src/cmp/js/TestDirect_js.ts +53 -5
  62. package/project/.sdk/src/cmp/js/TestEntity_js.ts +114 -20
  63. package/project/.sdk/src/cmp/js/fragment/Entity.fragment.js +7 -139
  64. package/project/.sdk/src/cmp/js/fragment/EntityBase.fragment.js +149 -0
  65. package/project/.sdk/src/cmp/js/fragment/EntityCreateOp.fragment.js +6 -10
  66. package/project/.sdk/src/cmp/js/fragment/EntityListOp.fragment.js +6 -10
  67. package/project/.sdk/src/cmp/js/fragment/EntityLoadOp.fragment.js +7 -11
  68. package/project/.sdk/src/cmp/js/fragment/EntityRemoveOp.fragment.js +7 -11
  69. package/project/.sdk/src/cmp/js/fragment/EntityUpdateOp.fragment.js +7 -11
  70. package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +2 -0
  71. package/project/.sdk/src/cmp/js/fragment/SdkError.fragment.js +0 -2
  72. package/project/.sdk/src/cmp/lua/Config_lua.ts +116 -0
  73. package/project/.sdk/src/cmp/lua/EntityOperation_lua.ts +44 -0
  74. package/project/.sdk/src/cmp/lua/Entity_lua.ts +62 -0
  75. package/project/.sdk/src/cmp/lua/MainEntity_lua.ts +23 -0
  76. package/project/.sdk/src/cmp/lua/Main_lua.ts +133 -0
  77. package/project/.sdk/src/cmp/lua/Package_lua.ts +80 -0
  78. package/project/.sdk/src/cmp/lua/ReadmeExplanation_lua.ts +42 -0
  79. package/project/.sdk/src/cmp/lua/ReadmeHowto_lua.ts +100 -0
  80. package/project/.sdk/src/cmp/lua/ReadmeInstall_lua.ts +26 -0
  81. package/project/.sdk/src/cmp/lua/ReadmeModel_lua.ts +129 -0
  82. package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +99 -0
  83. package/project/.sdk/src/cmp/lua/ReadmeTopHowto_lua.ts +24 -0
  84. package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +55 -0
  85. package/project/.sdk/src/cmp/lua/ReadmeTopTest_lua.ts +38 -0
  86. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +261 -0
  87. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +485 -0
  88. package/project/.sdk/src/cmp/lua/Test_lua.ts +49 -0
  89. package/project/.sdk/src/cmp/lua/fragment/Entity.fragment.lua +147 -0
  90. package/project/.sdk/src/cmp/lua/fragment/EntityCreateOp.fragment.lua +27 -0
  91. package/project/.sdk/src/cmp/lua/fragment/EntityListOp.fragment.lua +24 -0
  92. package/project/.sdk/src/cmp/lua/fragment/EntityLoadOp.fragment.lua +30 -0
  93. package/project/.sdk/src/cmp/lua/fragment/EntityRemoveOp.fragment.lua +30 -0
  94. package/project/.sdk/src/cmp/lua/fragment/EntityUpdateOp.fragment.lua +30 -0
  95. package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +256 -0
  96. package/project/.sdk/src/cmp/lua/fragment/SdkError.fragment.lua +26 -0
  97. package/project/.sdk/src/cmp/lua/tsconfig.json +15 -0
  98. package/project/.sdk/src/cmp/lua/utility_lua.ts +84 -0
  99. package/project/.sdk/src/cmp/php/Config_php.ts +111 -0
  100. package/project/.sdk/src/cmp/php/EntityOperation_php.ts +44 -0
  101. package/project/.sdk/src/cmp/php/Entity_php.ts +62 -0
  102. package/project/.sdk/src/cmp/php/MainEntity_php.ts +24 -0
  103. package/project/.sdk/src/cmp/php/Main_php.ts +135 -0
  104. package/project/.sdk/src/cmp/php/Package_php.ts +87 -0
  105. package/project/.sdk/src/cmp/php/ReadmeExplanation_php.ts +42 -0
  106. package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +101 -0
  107. package/project/.sdk/src/cmp/php/ReadmeInstall_php.ts +19 -0
  108. package/project/.sdk/src/cmp/php/ReadmeModel_php.ts +129 -0
  109. package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +100 -0
  110. package/project/.sdk/src/cmp/php/ReadmeTopHowto_php.ts +24 -0
  111. package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +56 -0
  112. package/project/.sdk/src/cmp/php/ReadmeTopTest_php.ts +38 -0
  113. package/project/.sdk/src/cmp/php/TestDirect_php.ts +270 -0
  114. package/project/.sdk/src/cmp/php/TestEntity_php.ts +479 -0
  115. package/project/.sdk/src/cmp/php/Test_php.ts +56 -0
  116. package/project/.sdk/src/cmp/php/fragment/Entity.fragment.php +146 -0
  117. package/project/.sdk/src/cmp/php/fragment/EntityCreateOp.fragment.php +26 -0
  118. package/project/.sdk/src/cmp/php/fragment/EntityListOp.fragment.php +23 -0
  119. package/project/.sdk/src/cmp/php/fragment/EntityLoadOp.fragment.php +29 -0
  120. package/project/.sdk/src/cmp/php/fragment/EntityRemoveOp.fragment.php +29 -0
  121. package/project/.sdk/src/cmp/php/fragment/EntityUpdateOp.fragment.php +29 -0
  122. package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +233 -0
  123. package/project/.sdk/src/cmp/php/fragment/SdkError.fragment.php +24 -0
  124. package/project/.sdk/src/cmp/php/tsconfig.json +15 -0
  125. package/project/.sdk/src/cmp/php/utility_php.ts +85 -0
  126. package/project/.sdk/src/cmp/py/Config_py.ts +97 -0
  127. package/project/.sdk/src/cmp/py/EntityOperation_py.ts +44 -0
  128. package/project/.sdk/src/cmp/py/Entity_py.ts +62 -0
  129. package/project/.sdk/src/cmp/py/MainEntity_py.ts +22 -0
  130. package/project/.sdk/src/cmp/py/Main_py.ts +156 -0
  131. package/project/.sdk/src/cmp/py/Package_py.ts +75 -0
  132. package/project/.sdk/src/cmp/py/ReadmeExplanation_py.ts +41 -0
  133. package/project/.sdk/src/cmp/py/ReadmeHowto_py.ts +98 -0
  134. package/project/.sdk/src/cmp/py/ReadmeInstall_py.ts +25 -0
  135. package/project/.sdk/src/cmp/py/ReadmeModel_py.ts +130 -0
  136. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +100 -0
  137. package/project/.sdk/src/cmp/py/ReadmeTopHowto_py.ts +24 -0
  138. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +56 -0
  139. package/project/.sdk/src/cmp/py/ReadmeTopTest_py.ts +38 -0
  140. package/project/.sdk/src/cmp/py/TestDirect_py.ts +248 -0
  141. package/project/.sdk/src/cmp/py/TestEntity_py.ts +477 -0
  142. package/project/.sdk/src/cmp/py/Test_py.ts +55 -0
  143. package/project/.sdk/src/cmp/py/fragment/Entity.fragment.py +112 -0
  144. package/project/.sdk/src/cmp/py/fragment/EntityCreateOp.fragment.py +25 -0
  145. package/project/.sdk/src/cmp/py/fragment/EntityListOp.fragment.py +22 -0
  146. package/project/.sdk/src/cmp/py/fragment/EntityLoadOp.fragment.py +27 -0
  147. package/project/.sdk/src/cmp/py/fragment/EntityRemoveOp.fragment.py +27 -0
  148. package/project/.sdk/src/cmp/py/fragment/EntityUpdateOp.fragment.py +27 -0
  149. package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +222 -0
  150. package/project/.sdk/src/cmp/py/fragment/SdkError.fragment.py +16 -0
  151. package/project/.sdk/src/cmp/py/tsconfig.json +15 -0
  152. package/project/.sdk/src/cmp/py/utility_py.ts +84 -0
  153. package/project/.sdk/src/cmp/rb/Config_rb.ts +105 -0
  154. package/project/.sdk/src/cmp/rb/EntityOperation_rb.ts +44 -0
  155. package/project/.sdk/src/cmp/rb/Entity_rb.ts +62 -0
  156. package/project/.sdk/src/cmp/rb/MainEntity_rb.ts +23 -0
  157. package/project/.sdk/src/cmp/rb/Main_rb.ts +130 -0
  158. package/project/.sdk/src/cmp/rb/Package_rb.ts +111 -0
  159. package/project/.sdk/src/cmp/rb/ReadmeExplanation_rb.ts +42 -0
  160. package/project/.sdk/src/cmp/rb/ReadmeHowto_rb.ts +98 -0
  161. package/project/.sdk/src/cmp/rb/ReadmeInstall_rb.ts +31 -0
  162. package/project/.sdk/src/cmp/rb/ReadmeModel_rb.ts +129 -0
  163. package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +99 -0
  164. package/project/.sdk/src/cmp/rb/ReadmeTopHowto_rb.ts +24 -0
  165. package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +55 -0
  166. package/project/.sdk/src/cmp/rb/ReadmeTopTest_rb.ts +38 -0
  167. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +260 -0
  168. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +476 -0
  169. package/project/.sdk/src/cmp/rb/Test_rb.ts +50 -0
  170. package/project/.sdk/src/cmp/rb/fragment/Entity.fragment.rb +116 -0
  171. package/project/.sdk/src/cmp/rb/fragment/EntityCreateOp.fragment.rb +25 -0
  172. package/project/.sdk/src/cmp/rb/fragment/EntityListOp.fragment.rb +20 -0
  173. package/project/.sdk/src/cmp/rb/fragment/EntityLoadOp.fragment.rb +26 -0
  174. package/project/.sdk/src/cmp/rb/fragment/EntityRemoveOp.fragment.rb +26 -0
  175. package/project/.sdk/src/cmp/rb/fragment/EntityUpdateOp.fragment.rb +26 -0
  176. package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +203 -0
  177. package/project/.sdk/src/cmp/rb/fragment/SdkError.fragment.rb +16 -0
  178. package/project/.sdk/src/cmp/rb/tsconfig.json +15 -0
  179. package/project/.sdk/src/cmp/rb/utility_rb.ts +84 -0
  180. package/project/.sdk/src/cmp/ts/Main_ts.ts +7 -0
  181. package/project/.sdk/src/cmp/ts/ReadmeExplanation_ts.ts +34 -0
  182. package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +123 -0
  183. package/project/.sdk/src/cmp/ts/ReadmeInstall_ts.ts +1 -1
  184. package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +159 -0
  185. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +69 -54
  186. package/project/.sdk/src/cmp/ts/ReadmeTopHowto_ts.ts +25 -0
  187. package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +65 -0
  188. package/project/.sdk/src/cmp/ts/ReadmeTopTest_ts.ts +36 -0
  189. package/project/.sdk/tm/go/feature/log_feature.go +133 -0
  190. package/project/.sdk/tm/go/src/feature/log/.gitkeep +0 -0
  191. package/project/.sdk/tm/go/test/runner_test.go +16 -2
  192. package/project/.sdk/tm/js/src/Context.js +142 -0
  193. package/project/.sdk/tm/js/src/Control.js +16 -0
  194. package/project/.sdk/tm/js/src/Operation.js +19 -0
  195. package/project/.sdk/tm/js/src/Point.js +24 -0
  196. package/project/.sdk/tm/js/src/README.md +1 -0
  197. package/project/.sdk/tm/js/src/Response.js +19 -0
  198. package/project/.sdk/tm/js/src/Result.js +21 -0
  199. package/project/.sdk/tm/js/src/Spec.js +26 -0
  200. package/project/.sdk/tm/js/src/feature/README.md +1 -0
  201. package/project/.sdk/tm/js/src/feature/base/BaseFeature.js +45 -0
  202. package/project/.sdk/tm/js/src/feature/log/LogFeature.js +46 -47
  203. package/project/.sdk/tm/js/src/feature/test/TestFeature.js +207 -0
  204. package/project/.sdk/tm/js/src/types.js +22 -0
  205. package/project/.sdk/tm/js/src/utility/CleanUtility.js +31 -0
  206. package/project/.sdk/tm/js/src/utility/DoneUtility.js +11 -4
  207. package/project/.sdk/tm/js/src/utility/FeatureAddUtility.js +42 -0
  208. package/project/.sdk/tm/js/src/utility/FeatureHookUtility.js +25 -0
  209. package/project/.sdk/tm/js/src/utility/FeatureInitUtility.js +11 -0
  210. package/project/.sdk/tm/js/src/utility/FetcherUtility.js +28 -0
  211. package/project/.sdk/tm/js/src/utility/MakeContextUtility.js +11 -0
  212. package/project/.sdk/tm/js/src/utility/MakeErrorUtility.js +55 -0
  213. package/project/.sdk/tm/js/src/utility/MakeFetchDefUtility.js +44 -0
  214. package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +93 -0
  215. package/project/.sdk/tm/js/src/utility/MakePointUtility.js +77 -0
  216. package/project/.sdk/tm/js/src/utility/MakeRequestUtility.js +63 -0
  217. package/project/.sdk/tm/js/src/utility/MakeResponseUtility.js +55 -0
  218. package/project/.sdk/tm/js/src/utility/MakeResultUtility.js +54 -0
  219. package/project/.sdk/tm/js/src/utility/MakeSpecUtility.js +58 -0
  220. package/project/.sdk/tm/js/src/utility/MakeUrlUtility.js +40 -0
  221. package/project/.sdk/tm/js/src/utility/ParamUtility.js +61 -0
  222. package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +41 -0
  223. package/project/.sdk/tm/js/src/utility/PrepareBodyUtility.js +25 -0
  224. package/project/.sdk/tm/js/src/utility/PrepareHeadersUtility.js +18 -0
  225. package/project/.sdk/tm/js/src/utility/{MethodUtility.js → PrepareMethodUtility.js} +7 -7
  226. package/project/.sdk/tm/js/src/utility/PrepareParamsUtility.js +25 -0
  227. package/project/.sdk/tm/js/src/utility/PreparePathUtility.js +13 -0
  228. package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +26 -0
  229. package/project/.sdk/tm/js/src/utility/README.md +1 -0
  230. package/project/.sdk/tm/js/src/utility/ResultBasicUtility.js +34 -0
  231. package/project/.sdk/tm/js/src/utility/ResultBodyUtility.js +18 -0
  232. package/project/.sdk/tm/js/src/utility/ResultHeadersUtility.js +22 -0
  233. package/project/.sdk/tm/js/src/utility/StructUtility.js +2219 -1078
  234. package/project/.sdk/tm/js/src/utility/TransformRequestUtility.js +28 -0
  235. package/project/.sdk/tm/js/src/utility/TransformResponseUtility.js +31 -0
  236. package/project/.sdk/tm/js/src/utility/Utility.js +61 -61
  237. package/project/.sdk/tm/js/test/README.md +1 -0
  238. package/project/.sdk/tm/js/test/exists.test.js +16 -0
  239. package/project/.sdk/tm/js/test/runner.js +323 -107
  240. package/project/.sdk/tm/js/test/utility/Custom.test.js +41 -63
  241. package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +390 -116
  242. package/project/.sdk/tm/js/test/utility/StructUtility.test.js +728 -175
  243. package/project/.sdk/tm/js/test/utility/index.js +9 -0
  244. package/project/.sdk/tm/js/test/utility.js +72 -0
  245. package/project/.sdk/tm/lua/LICENSE +22 -0
  246. package/project/.sdk/tm/lua/Makefile +10 -0
  247. package/project/.sdk/tm/lua/core/context.lua +208 -0
  248. package/project/.sdk/tm/lua/core/control.lua +17 -0
  249. package/project/.sdk/tm/lua/core/error.lua +30 -0
  250. package/project/.sdk/tm/lua/core/helpers.lua +30 -0
  251. package/project/.sdk/tm/lua/core/operation.lua +51 -0
  252. package/project/.sdk/tm/lua/core/response.lua +45 -0
  253. package/project/.sdk/tm/lua/core/result.lua +58 -0
  254. package/project/.sdk/tm/lua/core/spec.lua +29 -0
  255. package/project/.sdk/tm/lua/core/utility_type.lua +90 -0
  256. package/project/.sdk/tm/lua/feature/base_feature.lua +35 -0
  257. package/project/.sdk/tm/lua/feature/log_feature.lua +80 -0
  258. package/project/.sdk/tm/lua/feature/test_feature.lua +202 -0
  259. package/project/.sdk/tm/lua/src/feature/README.md +3 -0
  260. package/project/.sdk/tm/lua/src/feature/base/.gitkeep +0 -0
  261. package/project/.sdk/tm/lua/src/feature/log/.gitkeep +0 -0
  262. package/project/.sdk/tm/lua/src/feature/test/.gitkeep +0 -0
  263. package/project/.sdk/tm/lua/test/primary_utility_test.lua +1213 -0
  264. package/project/.sdk/tm/lua/test/runner.lua +86 -0
  265. package/project/.sdk/tm/lua/test/struct_runner.lua +602 -0
  266. package/project/.sdk/tm/lua/test/struct_utility_test.lua +959 -0
  267. package/project/.sdk/tm/lua/utility/clean.lua +7 -0
  268. package/project/.sdk/tm/lua/utility/done.lua +19 -0
  269. package/project/.sdk/tm/lua/utility/feature_add.lua +8 -0
  270. package/project/.sdk/tm/lua/utility/feature_hook.lua +21 -0
  271. package/project/.sdk/tm/lua/utility/feature_init.lua +24 -0
  272. package/project/.sdk/tm/lua/utility/fetcher.lua +96 -0
  273. package/project/.sdk/tm/lua/utility/make_context.lua +9 -0
  274. package/project/.sdk/tm/lua/utility/make_error.lua +73 -0
  275. package/project/.sdk/tm/lua/utility/make_fetch_def.lua +43 -0
  276. package/project/.sdk/tm/lua/utility/make_options.lua +116 -0
  277. package/project/.sdk/tm/lua/utility/make_point.lua +92 -0
  278. package/project/.sdk/tm/lua/utility/make_request.lua +58 -0
  279. package/project/.sdk/tm/lua/utility/make_response.lua +44 -0
  280. package/project/.sdk/tm/lua/utility/make_result.lua +51 -0
  281. package/project/.sdk/tm/lua/utility/make_spec.lua +72 -0
  282. package/project/.sdk/tm/lua/utility/make_url.lua +46 -0
  283. package/project/.sdk/tm/lua/utility/param.lua +68 -0
  284. package/project/.sdk/tm/lua/utility/prepare_auth.lua +39 -0
  285. package/project/.sdk/tm/lua/utility/prepare_body.lua +14 -0
  286. package/project/.sdk/tm/lua/utility/prepare_headers.lua +20 -0
  287. package/project/.sdk/tm/lua/utility/prepare_method.lua +17 -0
  288. package/project/.sdk/tm/lua/utility/prepare_params.lua +36 -0
  289. package/project/.sdk/tm/lua/utility/prepare_path.lua +19 -0
  290. package/project/.sdk/tm/lua/utility/prepare_query.lua +41 -0
  291. package/project/.sdk/tm/lua/utility/register.lua +71 -0
  292. package/project/.sdk/tm/lua/utility/result_basic.lua +32 -0
  293. package/project/.sdk/tm/lua/utility/result_body.lua +17 -0
  294. package/project/.sdk/tm/lua/utility/result_headers.lua +22 -0
  295. package/project/.sdk/tm/lua/utility/struct/struct.lua +3427 -0
  296. package/project/.sdk/tm/lua/utility/transform_request.lua +31 -0
  297. package/project/.sdk/tm/lua/utility/transform_response.lua +44 -0
  298. package/project/.sdk/tm/php/LICENSE +22 -0
  299. package/project/.sdk/tm/php/Makefile +10 -0
  300. package/project/.sdk/tm/php/core/Context.php +139 -0
  301. package/project/.sdk/tm/php/core/Control.php +18 -0
  302. package/project/.sdk/tm/php/core/Error.php +37 -0
  303. package/project/.sdk/tm/php/core/Helpers.php +25 -0
  304. package/project/.sdk/tm/php/core/Operation.php +36 -0
  305. package/project/.sdk/tm/php/core/Response.php +30 -0
  306. package/project/.sdk/tm/php/core/Result.php +35 -0
  307. package/project/.sdk/tm/php/core/Spec.php +38 -0
  308. package/project/.sdk/tm/php/core/UtilityType.php +89 -0
  309. package/project/.sdk/tm/php/feature/BaseFeature.php +37 -0
  310. package/project/.sdk/tm/php/feature/LogFeature.php +65 -0
  311. package/project/.sdk/tm/php/feature/TestFeature.php +156 -0
  312. package/project/.sdk/tm/php/src/feature/README.md +3 -0
  313. package/project/.sdk/tm/php/src/feature/base/.gitkeep +0 -0
  314. package/project/.sdk/tm/php/src/feature/log/.gitkeep +0 -0
  315. package/project/.sdk/tm/php/src/feature/test/.gitkeep +0 -0
  316. package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +1309 -0
  317. package/project/.sdk/tm/php/test/Runner.php +112 -0
  318. package/project/.sdk/tm/php/test/StructRunner.php +275 -0
  319. package/project/.sdk/tm/php/test/StructUtilityTest.php +1336 -0
  320. package/project/.sdk/tm/php/utility/Clean.php +12 -0
  321. package/project/.sdk/tm/php/utility/Done.php +26 -0
  322. package/project/.sdk/tm/php/utility/FeatureAdd.php +12 -0
  323. package/project/.sdk/tm/php/utility/FeatureHook.php +23 -0
  324. package/project/.sdk/tm/php/utility/FeatureInit.php +25 -0
  325. package/project/.sdk/tm/php/utility/Fetcher.php +105 -0
  326. package/project/.sdk/tm/php/utility/MakeContext.php +14 -0
  327. package/project/.sdk/tm/php/utility/MakeError.php +59 -0
  328. package/project/.sdk/tm/php/utility/MakeFetchDef.php +36 -0
  329. package/project/.sdk/tm/php/utility/MakeOptions.php +102 -0
  330. package/project/.sdk/tm/php/utility/MakePoint.php +87 -0
  331. package/project/.sdk/tm/php/utility/MakeRequest.php +57 -0
  332. package/project/.sdk/tm/php/utility/MakeResponse.php +43 -0
  333. package/project/.sdk/tm/php/utility/MakeResult.php +53 -0
  334. package/project/.sdk/tm/php/utility/MakeSpec.php +64 -0
  335. package/project/.sdk/tm/php/utility/MakeUrl.php +41 -0
  336. package/project/.sdk/tm/php/utility/Param.php +68 -0
  337. package/project/.sdk/tm/php/utility/PrepareAuth.php +33 -0
  338. package/project/.sdk/tm/php/utility/PrepareBody.php +15 -0
  339. package/project/.sdk/tm/php/utility/PrepareHeaders.php +18 -0
  340. package/project/.sdk/tm/php/utility/PrepareMethod.php +21 -0
  341. package/project/.sdk/tm/php/utility/PrepareParams.php +34 -0
  342. package/project/.sdk/tm/php/utility/PreparePath.php +20 -0
  343. package/project/.sdk/tm/php/utility/PrepareQuery.php +32 -0
  344. package/project/.sdk/tm/php/utility/Register.php +67 -0
  345. package/project/.sdk/tm/php/utility/ResultBasic.php +29 -0
  346. package/project/.sdk/tm/php/utility/ResultBody.php +17 -0
  347. package/project/.sdk/tm/php/utility/ResultHeaders.php +21 -0
  348. package/project/.sdk/tm/php/utility/TransformRequest.php +27 -0
  349. package/project/.sdk/tm/php/utility/TransformResponse.php +42 -0
  350. package/project/.sdk/tm/php/utility/struct/Struct.php +3535 -0
  351. package/project/.sdk/tm/py/Makefile +10 -0
  352. package/project/.sdk/tm/py/core/__init__.py +0 -0
  353. package/project/.sdk/tm/py/core/context.py +199 -0
  354. package/project/.sdk/tm/py/core/control.py +12 -0
  355. package/project/.sdk/tm/py/core/error.py +18 -0
  356. package/project/.sdk/tm/py/core/helpers.py +15 -0
  357. package/project/.sdk/tm/py/core/operation.py +37 -0
  358. package/project/.sdk/tm/py/core/response.py +34 -0
  359. package/project/.sdk/tm/py/core/result.py +44 -0
  360. package/project/.sdk/tm/py/core/spec.py +23 -0
  361. package/project/.sdk/tm/py/core/utility_type.py +82 -0
  362. package/project/.sdk/tm/py/entity/__init__.py +0 -0
  363. package/project/.sdk/tm/py/feature/__init__.py +0 -0
  364. package/project/.sdk/tm/py/feature/base_feature.py +61 -0
  365. package/project/.sdk/tm/py/feature/log_feature.py +84 -0
  366. package/project/.sdk/tm/py/feature/test_feature.py +164 -0
  367. package/project/.sdk/tm/py/src/feature/README.md +3 -0
  368. package/project/.sdk/tm/py/src/feature/base/.gitkeep +0 -0
  369. package/project/.sdk/tm/py/src/feature/log/.gitkeep +0 -0
  370. package/project/.sdk/tm/py/src/feature/test/.gitkeep +0 -0
  371. package/project/.sdk/tm/py/test/__init__.py +0 -0
  372. package/project/.sdk/tm/py/test/runner.py +90 -0
  373. package/project/.sdk/tm/py/test/struct_runner.py +411 -0
  374. package/project/.sdk/tm/py/test/test_primary_utility.py +1101 -0
  375. package/project/.sdk/tm/py/test/test_struct_utility.py +751 -0
  376. package/project/.sdk/tm/py/utility/__init__.py +0 -0
  377. package/project/.sdk/tm/py/utility/clean.py +5 -0
  378. package/project/.sdk/tm/py/utility/done.py +14 -0
  379. package/project/.sdk/tm/py/utility/feature_add.py +6 -0
  380. package/project/.sdk/tm/py/utility/feature_hook.py +15 -0
  381. package/project/.sdk/tm/py/utility/feature_init.py +18 -0
  382. package/project/.sdk/tm/py/utility/fetcher.py +95 -0
  383. package/project/.sdk/tm/py/utility/make_context.py +7 -0
  384. package/project/.sdk/tm/py/utility/make_error.py +64 -0
  385. package/project/.sdk/tm/py/utility/make_fetch_def.py +37 -0
  386. package/project/.sdk/tm/py/utility/make_options.py +103 -0
  387. package/project/.sdk/tm/py/utility/make_point.py +74 -0
  388. package/project/.sdk/tm/py/utility/make_request.py +52 -0
  389. package/project/.sdk/tm/py/utility/make_response.py +36 -0
  390. package/project/.sdk/tm/py/utility/make_result.py +41 -0
  391. package/project/.sdk/tm/py/utility/make_spec.py +68 -0
  392. package/project/.sdk/tm/py/utility/make_url.py +34 -0
  393. package/project/.sdk/tm/py/utility/param.py +55 -0
  394. package/project/.sdk/tm/py/utility/prepare_auth.py +34 -0
  395. package/project/.sdk/tm/py/utility/prepare_body.py +11 -0
  396. package/project/.sdk/tm/py/utility/prepare_headers.py +17 -0
  397. package/project/.sdk/tm/py/utility/prepare_method.py +15 -0
  398. package/project/.sdk/tm/py/utility/prepare_params.py +28 -0
  399. package/project/.sdk/tm/py/utility/prepare_path.py +16 -0
  400. package/project/.sdk/tm/py/utility/prepare_query.py +33 -0
  401. package/project/.sdk/tm/py/utility/register.py +68 -0
  402. package/project/.sdk/tm/py/utility/result_basic.py +26 -0
  403. package/project/.sdk/tm/py/utility/result_body.py +13 -0
  404. package/project/.sdk/tm/py/utility/result_headers.py +17 -0
  405. package/project/.sdk/tm/py/utility/transform_request.py +27 -0
  406. package/project/.sdk/tm/py/utility/transform_response.py +39 -0
  407. package/project/.sdk/tm/py/utility/voxgig_struct/__init__.py +72 -0
  408. package/project/.sdk/tm/py/utility/voxgig_struct/voxgig_struct.py +2770 -0
  409. package/project/.sdk/tm/rb/Gemfile +4 -0
  410. package/project/.sdk/tm/rb/LICENSE +22 -0
  411. package/project/.sdk/tm/rb/Makefile +10 -0
  412. package/project/.sdk/tm/rb/core/context.rb +105 -0
  413. package/project/.sdk/tm/rb/core/control.rb +11 -0
  414. package/project/.sdk/tm/rb/core/error.rb +24 -0
  415. package/project/.sdk/tm/rb/core/helpers.rb +16 -0
  416. package/project/.sdk/tm/rb/core/operation.rb +26 -0
  417. package/project/.sdk/tm/rb/core/response.rb +20 -0
  418. package/project/.sdk/tm/rb/core/result.rb +23 -0
  419. package/project/.sdk/tm/rb/core/spec.rb +23 -0
  420. package/project/.sdk/tm/rb/core/utility_type.rb +32 -0
  421. package/project/.sdk/tm/rb/feature/base_feature.rb +30 -0
  422. package/project/.sdk/tm/rb/feature/log_feature.rb +50 -0
  423. package/project/.sdk/tm/rb/feature/test_feature.rb +154 -0
  424. package/project/.sdk/tm/rb/src/feature/README.md +3 -0
  425. package/project/.sdk/tm/rb/src/feature/base/.gitkeep +0 -0
  426. package/project/.sdk/tm/rb/src/feature/log/.gitkeep +0 -0
  427. package/project/.sdk/tm/rb/src/feature/test/.gitkeep +0 -0
  428. package/project/.sdk/tm/rb/test/primary_utility_test.rb +1083 -0
  429. package/project/.sdk/tm/rb/test/runner.rb +70 -0
  430. package/project/.sdk/tm/rb/test/struct_runner.rb +309 -0
  431. package/project/.sdk/tm/rb/test/struct_utility_test.rb +670 -0
  432. package/project/.sdk/tm/rb/utility/clean.rb +4 -0
  433. package/project/.sdk/tm/rb/utility/done.rb +14 -0
  434. package/project/.sdk/tm/rb/utility/feature_add.rb +6 -0
  435. package/project/.sdk/tm/rb/utility/feature_hook.rb +11 -0
  436. package/project/.sdk/tm/rb/utility/feature_init.rb +16 -0
  437. package/project/.sdk/tm/rb/utility/fetcher.rb +67 -0
  438. package/project/.sdk/tm/rb/utility/make_context.rb +7 -0
  439. package/project/.sdk/tm/rb/utility/make_error.rb +44 -0
  440. package/project/.sdk/tm/rb/utility/make_fetch_def.rb +24 -0
  441. package/project/.sdk/tm/rb/utility/make_options.rb +57 -0
  442. package/project/.sdk/tm/rb/utility/make_point.rb +77 -0
  443. package/project/.sdk/tm/rb/utility/make_request.rb +44 -0
  444. package/project/.sdk/tm/rb/utility/make_response.rb +25 -0
  445. package/project/.sdk/tm/rb/utility/make_result.rb +33 -0
  446. package/project/.sdk/tm/rb/utility/make_spec.rb +50 -0
  447. package/project/.sdk/tm/rb/utility/make_url.rb +32 -0
  448. package/project/.sdk/tm/rb/utility/param.rb +48 -0
  449. package/project/.sdk/tm/rb/utility/prepare_auth.rb +26 -0
  450. package/project/.sdk/tm/rb/utility/prepare_body.rb +6 -0
  451. package/project/.sdk/tm/rb/utility/prepare_headers.rb +11 -0
  452. package/project/.sdk/tm/rb/utility/prepare_method.rb +5 -0
  453. package/project/.sdk/tm/rb/utility/prepare_params.rb +25 -0
  454. package/project/.sdk/tm/rb/utility/prepare_path.rb +13 -0
  455. package/project/.sdk/tm/rb/utility/prepare_query.rb +22 -0
  456. package/project/.sdk/tm/rb/utility/register.rb +63 -0
  457. package/project/.sdk/tm/rb/utility/result_basic.rb +23 -0
  458. package/project/.sdk/tm/rb/utility/result_body.rb +11 -0
  459. package/project/.sdk/tm/rb/utility/result_headers.rb +15 -0
  460. package/project/.sdk/tm/rb/utility/struct/voxgig_struct.rb +2256 -0
  461. package/project/.sdk/tm/rb/utility/transform_request.rb +15 -0
  462. package/project/.sdk/tm/rb/utility/transform_response.rb +23 -0
  463. package/src/cmp/Main.ts +1 -16
  464. package/src/cmp/Readme.ts +5 -5
  465. package/src/cmp/ReadmeEntity.ts +77 -25
  466. package/src/cmp/ReadmeExplanation.ts +333 -0
  467. package/src/cmp/ReadmeHowto.ts +28 -0
  468. package/src/cmp/ReadmeIntro.ts +10 -24
  469. package/src/cmp/ReadmeModel.ts +57 -88
  470. package/src/cmp/ReadmeOptions.ts +40 -11
  471. package/src/cmp/ReadmeQuick.ts +4 -1
  472. package/src/cmp/ReadmeRef.ts +1057 -40
  473. package/src/cmp/ReadmeTop.ts +213 -0
  474. package/src/sdkgen.ts +12 -0
  475. package/project/.sdk/src/cmp/js/Quick_js.ts +0 -78
  476. package/project/.sdk/src/cmp/js/TestAcceptEntity_js.ts +0 -13
  477. package/project/.sdk/src/cmp/js/TestAccept_js.ts +0 -18
  478. package/project/.sdk/tm/go/test/exists_test.go +0 -16
  479. package/project/.sdk/tm/js/src/utility/AuthUtility.js +0 -21
  480. package/project/.sdk/tm/js/src/utility/BodyUtility.js +0 -29
  481. package/project/.sdk/tm/js/src/utility/ErrorUtility.js +0 -33
  482. package/project/.sdk/tm/js/src/utility/FindparamUtility.js +0 -31
  483. package/project/.sdk/tm/js/src/utility/FullurlUtility.js +0 -39
  484. package/project/.sdk/tm/js/src/utility/HeadersUtility.js +0 -13
  485. package/project/.sdk/tm/js/src/utility/JoinurlUtility.js +0 -14
  486. package/project/.sdk/tm/js/src/utility/OperatorUtility.js +0 -44
  487. package/project/.sdk/tm/js/src/utility/OptionsUtility.js +0 -54
  488. package/project/.sdk/tm/js/src/utility/ParamsUtility.js +0 -21
  489. package/project/.sdk/tm/js/src/utility/QueryUtility.js +0 -21
  490. package/project/.sdk/tm/js/src/utility/ReqformUtility.js +0 -32
  491. package/project/.sdk/tm/js/src/utility/RequestUtility.js +0 -48
  492. package/project/.sdk/tm/js/src/utility/ResbasicUtility.js +0 -27
  493. package/project/.sdk/tm/js/src/utility/ResbodyUtility.js +0 -15
  494. package/project/.sdk/tm/js/src/utility/ResformUtility.js +0 -34
  495. package/project/.sdk/tm/js/src/utility/ResheadersUtility.js +0 -19
  496. package/project/.sdk/tm/js/src/utility/ResponseUtility.js +0 -37
  497. package/project/.sdk/tm/js/src/utility/ResultUtility.js +0 -28
  498. package/project/.sdk/tm/js/src/utility/SpecUtility.js +0 -35
@@ -1,1203 +1,2344 @@
1
-
2
- /* Copyright (c) 2025 Voxgig Ltd. MIT LICENSE. */
3
-
1
+ /* Copyright (c) 2025-2026 Voxgig Ltd. MIT LICENSE. */
2
+ // VERSION: @voxgig/struct 0.0.10
4
3
  /* Voxgig Struct
5
4
  * =============
6
5
  *
7
6
  * Utility functions to manipulate in-memory JSON-like data
8
- * structures. The general design principle is
9
- * "by-example". Transform specifications mirror the desired output.
10
- * This implementation is desgined for porting to multiple language.
7
+ * structures. These structures assumed to be composed of nested
8
+ * "nodes", where a node is a list or map, and has named or indexed
9
+ * fields. The general design principle is "by-example". Transform
10
+ * specifications mirror the desired output. This implementation is
11
+ * designed for porting to multiple language, and to be tolerant of
12
+ * undefined values.
11
13
  *
12
- * - isnode, islist, islist, iskey: identify value kinds
13
- * - clone: create a copy of a JSON-like data structure
14
- * - items: list entries of a map or list as [key, value] pairs
15
- * - getprop: safely get a property value by key
16
- * - setprop: safely set a property value by key
17
- * - getpath: get the value at a key path deep inside an object
14
+ * Main utilities
15
+ * - getpath: get the value at a key path deep inside an object.
18
16
  * - merge: merge multiple nodes, overriding values in earlier nodes.
19
17
  * - walk: walk a node tree, applying a function at each node and leaf.
20
18
  * - inject: inject values from a data store into a new data structure.
21
19
  * - transform: transform a data structure to an example structure.
20
+ * - validate: valiate a data structure against a shape specification.
21
+ *
22
+ * Minor utilities
23
+ * - isnode, islist, ismap, iskey, isfunc: identify value kinds.
24
+ * - isempty: undefined values, or empty nodes.
25
+ * - keysof: sorted list of node keys (ascending).
26
+ * - haskey: true if key value is defined.
27
+ * - clone: create a copy of a JSON-like data structure.
28
+ * - items: list entries of a map or list as [key, value] pairs.
29
+ * - getprop: safely get a property value by key.
30
+ * - setprop: safely set a property value by key.
31
+ * - stringify: human-friendly string version of a value.
32
+ * - escre: escape a regular expresion string.
33
+ * - escurl: escape a url.
34
+ * - join: join parts of a url, merging forward slashes.
35
+ *
36
+ * This set of functions and supporting utilities is designed to work
37
+ * uniformly across many languages, meaning that some code that may be
38
+ * functionally redundant in specific languages is still retained to
39
+ * keep the code human comparable.
40
+ *
41
+ * NOTE: Lists are assumed to be mutable and reference stable.
42
+ *
43
+ * NOTE: In this code JSON nulls are in general *not* considered the
44
+ * same as the undefined value in the given language. However most
45
+ * JSON parsers do use the undefined value to represent JSON
46
+ * null. This is ambiguous as JSON null is a separate value, not an
47
+ * undefined value. You should convert such values to a special value
48
+ * to represent JSON null, if this ambiguity creates issues
49
+ * (thankfully in most APIs, JSON nulls are not used). For example,
50
+ * the unit tests use the string "__NULL__" where necessary.
51
+ *
22
52
  */
23
-
24
-
25
- // String constants.
26
- const S = {
27
- MKEYPRE: 'key:pre',
28
- MKEYPOST: 'key:post',
29
- MVAL: 'val',
30
- MKEY: 'key',
31
-
32
- TKEY: '`$KEY`',
33
- TMETA: '`$META`',
34
-
35
- KEY: 'KEY',
36
-
37
- DTOP: '$TOP',
38
-
39
- object: 'object',
40
- array: 'array',
41
- number: 'number',
42
- boolean: 'boolean',
43
- string: 'string',
44
- function: 'function',
45
- empty: '',
46
- base: 'base',
47
-
48
- BT: '`',
49
- DS: '$',
50
- DT: '.',
53
+ // String constants are explicitly defined.
54
+ // Mode value for inject step (bitfield).
55
+ const M_KEYPRE = 1
56
+ const M_KEYPOST = 2
57
+ const M_VAL = 4
58
+ // Special strings.
59
+ const S_BKEY = '`$KEY`'
60
+ const S_BANNO = '`$ANNO`'
61
+ const S_BEXACT = '`$EXACT`'
62
+ const S_BVAL = '`$VAL`'
63
+ const S_DKEY = '$KEY'
64
+ const S_DTOP = '$TOP'
65
+ const S_DERRS = '$ERRS'
66
+ const S_DSPEC = '$SPEC'
67
+ // General strings.
68
+ const S_list = 'list'
69
+ const S_base = 'base'
70
+ const S_boolean = 'boolean'
71
+ const S_function = 'function'
72
+ const S_symbol = 'symbol'
73
+ const S_instance = 'instance'
74
+ const S_key = 'key'
75
+ const S_any = 'any'
76
+ const S_nil = 'nil'
77
+ const S_null = 'null'
78
+ const S_number = 'number'
79
+ const S_object = 'object'
80
+ const S_string = 'string'
81
+ const S_decimal = 'decimal'
82
+ const S_integer = 'integer'
83
+ const S_map = 'map'
84
+ const S_scalar = 'scalar'
85
+ const S_node = 'node'
86
+ // Character strings.
87
+ const S_BT = '`'
88
+ const S_CN = ':'
89
+ const S_CS = ']'
90
+ const S_DS = '$'
91
+ const S_DT = '.'
92
+ const S_FS = '/'
93
+ const S_KEY = 'KEY'
94
+ const S_MT = ''
95
+ const S_OS = '['
96
+ const S_SP = ' '
97
+ const S_CM = ','
98
+ const S_VIZ = ': '
99
+ // Types
100
+ let t = 31
101
+ const T_any = (1 << t--) - 1
102
+ const T_noval = 1 << t--; // Means property absent, undefined. Also NOT a scalar!
103
+ const T_boolean = 1 << t--
104
+ const T_decimal = 1 << t--
105
+ const T_integer = 1 << t--
106
+ const T_number = 1 << t--
107
+ const T_string = 1 << t--
108
+ const T_function = 1 << t--
109
+ const T_symbol = 1 << t--
110
+ const T_null = 1 << t--; // The actual JSON null value.
111
+ t -= 7
112
+ const T_list = 1 << t--
113
+ const T_map = 1 << t--
114
+ const T_instance = 1 << t--
115
+ t -= 4
116
+ const T_scalar = 1 << t--
117
+ const T_node = 1 << t--
118
+ 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,
137
+ ]
138
+ // The standard undefined value for this language.
139
+ const NONE = undefined
140
+ // Private markers
141
+ const SKIP = { '`$SKIP`': true }
142
+ const DELETE = { '`$DELETE`': true }
143
+ // 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.
159
+ // Default max depth (for walk etc).
160
+ const MAXDEPTH = 32
161
+ // Return type string for narrowest type.
162
+ function typename(t) {
163
+ return getelem(TYPENAME, Math.clz32(t), TYPENAME[0])
164
+ }
165
+ // Get a defined value. Returns alt if val is undefined.
166
+ function getdef(val, alt) {
167
+ if (NONE === val) {
168
+ return alt
169
+ }
170
+ return val
51
171
  }
52
-
53
- const UNDEF = undefined
54
-
55
-
56
172
  // Value is a node - defined, and a map (hash) or list (array).
173
+ // NOTE: typescript
174
+ // things
57
175
  function isnode(val) {
58
- return null != val && S.object == typeof val
176
+ return null != val && S_object == typeof val
59
177
  }
60
-
61
-
62
178
  // Value is a defined map (hash) with string keys.
63
179
  function ismap(val) {
64
- return null != val && S.object == typeof val && !Array.isArray(val)
180
+ return null != val && S_object == typeof val && !Array.isArray(val)
65
181
  }
66
-
67
-
68
182
  // Value is a defined list (array) with integer keys (indexes).
69
183
  function islist(val) {
70
- return Array.isArray(val)
184
+ return Array.isArray(val)
71
185
  }
72
-
73
-
74
186
  // Value is a defined string (non-empty) or integer key.
75
187
  function iskey(key) {
76
- const keytype = typeof key
77
- return (S.string === keytype && S.empty !== key) || S.number === keytype
188
+ const keytype = typeof key
189
+ return (S_string === keytype && S_MT !== key) || S_number === keytype
78
190
  }
79
-
80
-
81
- // Check for an "empty" value - undefined, null, empty string, array, object.
191
+ // Check for an "empty" value - undefined, empty string, array, object.
82
192
  function isempty(val) {
83
- return null == val || S.empty === val ||
84
- (Array.isArray(val) && 0 === val.length) ||
85
- (S.object === typeof val && 0 === Object.keys(val).length)
193
+ return null == val || S_MT === val ||
194
+ (Array.isArray(val) && 0 === val.length) ||
195
+ (S_object === typeof val && 0 === Object.keys(val).length)
86
196
  }
87
-
88
-
89
- // TOOD: TEST
197
+ // Value is a function.
90
198
  function isfunc(val) {
91
- return S.function === typeof val
199
+ return S_function === typeof val
200
+ }
201
+ // The integer size of the value. For arrays and strings, the length,
202
+ // for numbers, the integer part, for boolean, true is 1 and falso 0, for all other values, 0.
203
+ 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
+ }
223
+ }
224
+ // Extract part of an array or string into a new value, from the start
225
+ // point to the end point. If no end is specified, extract to the
226
+ // full length of the value. Negative arguments count from the end of
227
+ // the value. For numbers, perform min and max bounding, where start
228
+ // is inclusive, and end is *exclusive*.
229
+ // NOTE: input lists are not mutated by default. Use the mutate
230
+ // argument to mutate lists in place.
231
+ 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
292
+ }
293
+ // String padding.
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)
299
+ }
300
+ // Determine the type of a value as a bit code.
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
347
+ }
348
+ // Get a list element. The key should be an integer, or a string
349
+ // that can parse to an integer only. Negative integers count from the end of the list.
350
+ 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.
371
+ 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
383
+ }
384
+ // Convert different types of keys to string representation.
385
+ // String keys are returned as is.
386
+ // Number keys are converted to strings.
387
+ // Floats are truncated to integers.
388
+ // Booleans, objects, arrays, null, undefined all return empty string.
389
+ 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
+ }
403
+ return S_MT
404
+ }
405
+ // Sorted keys of a map, or indexes (as strings) of a list.
406
+ // Root utility - only uses language facilities.
407
+ function keysof(val) {
408
+ return !isnode(val) ? [] :
409
+ ismap(val) ? Object.keys(val).sort() : val.map((_n, i) => S_MT + i)
410
+ }
411
+ // Value of property with name key in node val is defined.
412
+ // Root utility - only uses language facilities.
413
+ function haskey(val, key) {
414
+ return NONE !== getprop(val, key)
415
+ }
416
+ 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
422
+ }
423
+ // To replicate the array spread operator:
424
+ // a=1, b=[2,3], c=[4,5]
425
+ // [a,...b,c] -> [1,2,3,[4,5]]
426
+ // flatten([a,b,[c]]) -> [1,2,3,[4,5]]
427
+ // NOTE: [c] ensures c is not expanded
428
+ function flatten(list, depth) {
429
+ if (!islist(list)) {
430
+ return list
431
+ }
432
+ return list.flat(getdef(depth, 1))
433
+ }
434
+ // Filter item values using check function.
435
+ 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
+ }
443
+ }
444
+ return out
92
445
  }
93
-
94
-
95
446
  // Escape regular expression.
96
447
  function escre(s) {
97
- s = null == s ? S.empty : s
98
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
448
+ // s = null == s ? S_MT : s
449
+ return replace(s, R_ESCAPE_REGEXP, '\\$&')
99
450
  }
100
-
101
-
102
- // Escape URL.
451
+ // Escape URLs.
103
452
  function escurl(s) {
104
- s = null == s ? S.empty : s
105
- return encodeURIComponent(s)
453
+ s = null == s ? S_MT : s
454
+ return encodeURIComponent(s)
106
455
  }
107
-
108
-
109
- function joinurl(sarr) {
110
- return sarr
111
- .filter(s=>null!=s&&''!==s)
112
- .map((s,i)=> 0===i ? s.replace(/([^\/])\/+/,'$1/').replace(/\/+$/,'') :
113
- s.replace(/([^\/])\/+/,'$1/').replace(/^\/+/,'').replace(/\/+$/,''))
114
- .filter(s=>''!==s)
115
- .join('/')
456
+ // Replace a search string (all), or a regexp, in a source string.
457
+ 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)
116
470
  }
117
-
118
-
119
- // List the keys of a map or list as an array of tuples of the form [key, value].
120
- function items(val) {
121
- return ismap(val) ? Object.entries(val) :
122
- islist(val) ? val.map((n, i) => [i, n]) :
123
- []
471
+ // Concatenate url part strings, merging sep char as needed.
472
+ 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) => {
479
+ let i = +n[0]
480
+ let s = n[1]
481
+ 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')
493
+ }
494
+ return s
495
+ }), (n) => S_MT !== n[1])
496
+ .join(sepdef)
497
+ return out
124
498
  }
125
-
126
-
127
- // Sorted keys of a map, or indexes of an array.
128
- function keysof(val) {
129
- return !isnode(val) ? [] : ismap(val) ? Object.keys(val).sort() : val.map((n,i)=>i)
499
+ // Output JSON in a "standard" format, with 2 space indents, each property on a new line,
500
+ // and spaces after {[: and before ]}. Any "wierd" values (NaN, etc) are output as null.
501
+ // In general, the behaivor of of JavaScript's JSON.stringify(val,null,2) is followed.
502
+ 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
130
524
  }
131
-
132
-
133
- function haskey(val, key) {
134
- return UNDEF !== getprop(val, key)
525
+ // Safely stringify a value for humans (NOT JSON!).
526
+ 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__'
553
+ }
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
135
580
  }
136
-
137
-
138
- // Safely stringify a value for printing (NOT JSON!).
139
- function stringify(val, maxlen) {
140
- let json = S.empty
141
-
142
- try {
143
- json = JSON.stringify(val)
144
- }
145
- catch (err) {
146
- json = S.empty + val
147
- }
148
-
149
- json = S.string !== typeof json ? S.empty + json : json
150
- json = json.replace(/"/g, '')
151
-
152
- if (null != maxlen) {
153
- let js = json.substring(0, maxlen)
154
- json = maxlen < json.length ? (js.substring(0, maxlen - 3) + '...') : json
155
- }
156
-
157
- return json
581
+ // Build a human friendly path string.
582
+ 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
158
607
  }
159
-
160
-
161
608
  // Clone a JSON-like data structure.
162
- // NOTE: function values are *not* cloned.
609
+ // NOTE: function and instance values are copied, *not* cloned.
163
610
  function clone(val) {
164
- const refs = []
165
- const replacer = (k,v)=>S.function === typeof v ?
166
- (refs.push(v),'`$FUNCTION:'+(refs.length-1)+'`') : v
167
- const reviver = (k,v,m)=>S.string === typeof v ?
168
- (m=v.match(/^`\$FUNCTION:([0-9]+)`$/),m?refs[m[1]]:v) : v
169
- return UNDEF === val ? UNDEF : JSON.parse(JSON.stringify(val,replacer),reviver)
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
170
619
  }
171
-
172
-
173
- // Safely get a property of a node. UNDEF arguments return UNDEF.
174
- // If the key is not found, return the alternative value.
175
- function getprop(val, key, alt) {
176
- let out = UNDEF === val ? alt : UNDEF === key ? alt : val[key]
177
- out = UNDEF === out ? alt : out
178
- return out
620
+ // Define a JSON Object using function arguments.
621
+ 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
179
630
  }
180
-
181
-
182
- // Safely set a property. UNDEF arguments and invalid keys are ignored.
183
- // Returns the (possible modified) parent.
184
- // If the value is UNDEF it the key will be deleted from the parent.
185
- // If the parent is a list, and the key is negative, prepend the value.
186
- // If the key is above the list size, append the value.
187
- // If the value is UNDEF, remove the list element at index key, and shift the
188
- // remaining elements down. These rules avoids "holes" in the list.
189
- function setprop(parent, key, val) {
190
- if (!iskey(key)) {
191
- return parent
192
- }
193
-
194
- if (ismap(parent)) {
195
- key = S.empty + key
196
- if (UNDEF === val) {
197
- delete parent[key]
631
+ // Define a JSON Array using function arguments.
632
+ 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)
198
637
  }
199
- else {
200
- parent[key] = val
638
+ return a
639
+ }
640
+ // Safely delete a property from an object or array element.
641
+ // Undefined arguments and invalid keys are ignored.
642
+ // Returns the (possibly modified) parent.
643
+ // For objects, the property is deleted using the delete operator.
644
+ // For arrays, the element at the index is removed and remaining elements are shifted down.
645
+ // NOTE: parent list may be new list, thus update references.
646
+ function delprop(parent, key) {
647
+ if (!iskey(key)) {
648
+ return parent
201
649
  }
202
- }
203
- else if (islist(parent)) {
204
- // Ensure key is an integer.
205
- let keyI = +key
206
-
207
- if (isNaN(keyI)) {
208
- return parent
650
+ if (ismap(parent)) {
651
+ key = strkey(key)
652
+ delete parent[key]
209
653
  }
210
-
211
- keyI = Math.floor(keyI)
212
-
213
- // Delete list element at position keyI, shifting later elements down.
214
- if (UNDEF === val) {
215
- if (0 <= keyI && keyI < parent.length) {
216
- for (let pI = keyI; pI < parent.length - 1; pI++) {
217
- parent[pI] = parent[pI + 1]
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
218
668
  }
219
- parent.length = parent.length - 1
220
- }
221
669
  }
222
-
223
- // Set or append value at position keyI, or append if keyI out of bounds.
224
- else if (0 <= keyI) {
225
- parent[parent.length < keyI ? parent.length : keyI] = val
670
+ return parent
671
+ }
672
+ // Safely set a property. Undefined arguments and invalid keys are ignored.
673
+ // Returns the (possibly modified) parent.
674
+ // If the parent is a list, and the key is negative, prepend the value.
675
+ // NOTE: If the key is above the list size, append the value; below, prepend.
676
+ // NOTE: parent list may be new list, thus update references.
677
+ function setprop(parent, key, val) {
678
+ if (!iskey(key)) {
679
+ return parent
226
680
  }
227
-
228
- // Prepend value if keyI is negative
229
- else {
230
- parent.unshift(val)
681
+ if (ismap(parent)) {
682
+ key = S_MT + key
683
+ const pany = parent
684
+ pany[key] = val
231
685
  }
232
- }
233
-
234
- return parent
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
+ }
703
+ return parent
235
704
  }
236
-
237
-
238
- // Walk a data structure depth first.
705
+ // Walk a data structure depth first, applying a function to each value.
239
706
  function walk(
240
- // These arguments are the public interface.
241
- val,
242
- apply,
243
-
244
- // These areguments are used for recursive state.
245
- key,
246
- parent,
247
- path
248
- ) {
249
- if (isnode(val)) {
250
- for (let [ckey, child] of items(val)) {
251
- setprop(val, ckey, walk(child, apply, ckey, val, [...(path || []), S.empty + ckey]))
252
- }
253
- }
254
-
255
- // Nodes are applied *after* their children.
256
- // For the root node, key and parent will be UNDEF.
257
- return apply(key, val, parent, path || [])
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)
731
+ return out
258
732
  }
259
-
260
-
261
- // Merge a list of values into each other. Later values have precedence.
262
- // Nodes override scalars. Node kinds (list or map) override each other.
263
- // The first element is modified.
264
- function merge(objs) {
265
- let out = UNDEF
266
-
267
- if (!islist(objs)) {
268
- out = objs
269
- }
270
- else if (0 === objs.length) {
271
- out = UNDEF
272
- }
273
- else if (1 === objs.length) {
274
- out = objs[0]
275
- }
276
- else {
277
-
278
- out = getprop(objs, 0, {})
279
-
280
- // Merge remaining down onto first.
281
- for (let oI = 1; oI < objs.length; oI++) {
282
- let obj = objs[oI]
283
-
284
- if (!isnode(obj)) {
285
- // Nodes win.
286
- out = obj
287
- }
288
- else {
289
- // Nodes win, also over nodes of a different kind.
290
- if (!isnode(out) || (ismap(obj) && islist(out)) || (islist(obj) && ismap(out))) {
291
- out = obj
733
+ // Merge a list of values into each other. Later values have
734
+ // precedence. Nodes override scalars. Node kinds (list or map)
735
+ // override each other, and do *not* merge. The first element is
736
+ // modified.
737
+ 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
292
760
  }
293
761
  else {
294
- let cur = [out] // Node stack
295
- let cI = 0
296
-
297
- // Walk overriding node, creating paths in output as needed.
298
- walk(obj, (key, val, parent, path) => {
299
- if (null == key) {
300
- return val
301
- }
302
-
303
- let lenpath = path.length
304
-
305
- cI = lenpath - 1
306
- if (UNDEF === cur[cI]) {
307
- cur[cI] = getpath(path.slice(0, lenpath - 1), out)
308
- }
309
-
310
- // Create node if needed.
311
- if (!isnode(cur[cI])) {
312
- cur[cI] = islist(parent) ? [] : {}
313
- }
314
-
315
- // Node child is just ahead of us on the stack.
316
- if (isnode(val) && !isempty(val)) {
317
- setprop(cur[cI], key, cur[cI + 1])
318
- cur[cI + 1] = UNDEF
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
319
799
  }
320
-
321
- // Scalar child or empty node.
322
- else {
323
- setprop(cur[cI], key, val)
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
324
808
  }
325
-
326
- return val
327
- })
809
+ // Walk overriding node, creating paths in output as needed.
810
+ out = walk(obj, before, after, maxdepth)
811
+ // console.log('WALK-DONE', out, obj)
328
812
  }
329
- }
330
813
  }
331
- }
332
-
333
- return out
814
+ if (0 === md) {
815
+ out = getelem(list, -1)
816
+ out = islist(out) ? [] : ismap(out) ? {} : out
817
+ }
818
+ return out
334
819
  }
335
-
336
-
337
- // Get a value deep inside a node using a key path.
338
- // For example the path `a.b` gets the value 1 from {a:{b:1}}.
339
- // The path can specified as a dotted string, or a string array.
340
- // If the path starts with a dot (or the first element is ''), the path is considered local,
341
- // and resolved against the `current` argument, if defined.
342
- // Integer path parts are used as array indexes.
343
- // The state argument allows for custom handling when called from `inject` or `transform`.
344
- function getpath(path, store, current, state) {
345
-
346
- const parts = islist(path) ? path : S.string === typeof path ? path.split(S.DT) : UNDEF
347
-
348
- if (UNDEF === parts) {
349
- return UNDEF
350
- }
351
-
352
- let root = store
353
- let val = store
354
-
355
- // An empty path (incl empty string) just finds the store.
356
- if (null == path || null == store || (1 === parts.length && S.empty === parts[0])) {
357
- // The actual store data may be in a store sub property, defined by state.base.
358
- val = getprop(store, getprop(state, S.base), store)
359
- }
360
- else if (0 < parts.length) {
361
- let pI = 0
362
-
363
- // Relative path uses `current` argument.
364
- if (S.empty === parts[0]) {
365
- pI = 1
366
- root = current
820
+ // Set a value using a path. Missing path parts are created.
821
+ // String paths create only maps. Use a string list to create list parts.
822
+ 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
367
829
  }
368
-
369
- let part = pI < parts.length ? parts[pI] : UNDEF
370
- let first = getprop(root, part)
371
-
372
- // At top level, check state.base, if provided
373
- val = (UNDEF === first && 0 === pI) ?
374
- getprop(getprop(root, getprop(state, S.base)), part) :
375
- first
376
-
377
- // Move along the path, trying to descend into the store.
378
- for (pI++; UNDEF !== val && pI < parts.length; pI++) {
379
- val = getprop(val, parts[pI])
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
380
841
  }
381
-
382
- }
383
-
384
- // State may provide a custom handler to modify found value.
385
- if (null != state && S.function === typeof state.handler) {
386
- val = state.handler(state, val, current, path, store)
387
- }
388
-
389
- return val
842
+ if (DELETE === val) {
843
+ delprop(parent, getelem(parts, -1))
844
+ }
845
+ else {
846
+ setprop(parent, getelem(parts, -1), val)
847
+ }
848
+ return parent
390
849
  }
391
-
392
-
393
- // Inject store values into a string. Not a public utility - used by `inject`.
394
- // Inject are marked with `path` where path is resolved with getpath against the
395
- // store or current (if defined) arguments. See `getpath`.
396
- // Custom injection handling can be provided by state.handler (this is used for
397
- // transform functions).
398
- // The path can also have the special syntax $NAME999 where NAME is upper case letters only,
399
- // and 999 is any digits, which are discarded. This syntax specifies the name of a transform,
400
- // and optionally allows transforms to be ordered by alphanumeric sorting.
401
- function injectstr(val, store, current, state) {
402
- if (S.string !== typeof val) {
403
- return S.empty
404
- }
405
-
406
- let out = val
407
- const m = val.match(/^`(\$[A-Z]+|[^`]+)[0-9]*`$/)
408
-
409
- // Full string is an injection.
410
- if (m) {
411
- if (state) {
412
- state.full = true
850
+ 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
413
857
  }
414
- let ref = m[1]
415
-
416
- // Special escapes inside injection.
417
- ref = 3 < ref.length ? ref.replace(/\$BT/g, S.BT).replace(/\$DS/g, S.DS) : ref
418
-
419
- out = getpath(ref, store, current, state)
420
- }
421
-
422
- // Check for injections within the string.
423
- else {
424
- out = val.replace(/`([^`]+)`/g,
425
- (_m, ref) => {
426
- ref = 3 < ref.length ? ref.replace(/\$BT/g, S.BT).replace(/\$DS/g, S.DS) : ref
427
- if (state) {
428
- state.full = false
429
- }
430
- const found = getpath(ref, store, current, state)
431
-
432
- return UNDEF === found ? S.empty :
433
- S.object === typeof found ? JSON.stringify(found) :
434
- found
435
- })
436
-
437
- // Also call the handler on the entire string.
438
- if (state.handler) {
439
- state.full = true
440
- out = state.handler(state, out, current, val, store)
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
441
867
  }
442
- }
443
-
444
- return out
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]
879
+ }
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
+ }
932
+ }
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
445
943
  }
446
-
447
-
448
- // Inject values from a data store into a node recursively, resolving paths against the store,
449
- // or current if they are local. THe modify argument allows custom modification of the result.
450
- // The state (InjectState) argument is used to maintain recursive state.
451
- function inject(
452
- val,
453
- store,
454
- modify,
455
- current,
456
- state,
457
- ) {
458
- const valtype = typeof val
459
-
460
- // Create state if at root of injection.
461
- // The input value is placed inside a virtual parent holder
462
- // to simplify edge cases.
463
- if (UNDEF === state) {
464
- const parent = { [S.DTOP]: val }
465
- state = {
466
- mode: S.MVAL,
467
- full: false,
468
- keyI: 0,
469
- keys: [S.DTOP],
470
- key: S.DTOP,
471
- val,
472
- parent,
473
- path: [S.DTOP],
474
- nodes: [parent],
475
- handler: injecthandler,
476
- base: S.DTOP,
477
- modify,
478
- errs: store.$ERRS || [],
479
- }
480
- }
481
-
482
- // Resolve current node in store for local paths.
483
- if (UNDEF === current) {
484
- current = { $TOP: store }
485
- }
486
- else {
487
- const parentkey = state.path[state.path.length - 2]
488
- current = null == parentkey ? current : getprop(current, parentkey)
489
- }
490
-
491
- // Desend into node.
492
- if (isnode(val)) {
493
-
494
- // Keys are sorted alphanumerically to ensure determinism.
495
- // Injection transforms ($FOO) are processed *after* other keys.
496
- // NOTE: the optional digits suffix of the transform can thsu be used to
497
- // order the transforms.
498
- const origkeys = ismap(val) ? [
499
- ...Object.keys(val).filter(k => !k.includes(S.DS)),
500
- ...Object.keys(val).filter(k => k.includes(S.DS)).sort(),
501
- ] : val.map((_n, i) => i)
502
-
503
-
504
- // Each child key-value pair is processed in three injection phases:
505
- // 1. state.mode='key:pre' - Key string is injected, returning a possibly altered key.
506
- // 2. state.mode='val' - The child value is injected.
507
- // 3. state.mode='key:post' - Key string is injected again, allowing child mutation.
508
- for (let okI = 0; okI < origkeys.length; okI++) {
509
- const origkey = S.empty + origkeys[okI]
510
-
511
- let childpath = [...(state.path || []), origkey]
512
- let childnodes = [...(state.nodes || []), val]
513
-
514
- const childstate = {
515
- mode: S.MKEYPRE,
516
- full: false,
517
- keyI: okI,
518
- keys: origkeys,
519
- key: origkey,
520
- val,
521
- parent: val,
522
- path: childpath,
523
- nodes: childnodes,
524
- handler: injecthandler,
525
- base: state.base,
526
- errs: store.$ERRS || [],
527
- }
528
-
529
- const prekey = injectstr(origkey, store, current, childstate)
530
- okI = childstate.keyI
531
-
532
- // Prevent further processing by returning an UNDEF prekey
533
- if (null != prekey) {
534
- let child = val[prekey]
535
- childstate.mode = S.MVAL
536
- inject(
537
- child,
538
- store,
539
- modify,
540
- current,
541
- childstate,
542
- )
543
- okI = childstate.keyI
544
-
545
- childstate.mode = S.MKEYPOST
546
- injectstr(origkey, store, current, childstate)
547
- okI = childstate.keyI
548
- }
549
- }
550
- }
551
-
552
- // Inject paths into string scalars.
553
- else if (S.string === valtype) {
554
- state.mode = S.MVAL
555
- const newval = injectstr(val, store, current, state)
556
- val = newval
557
-
558
- setprop(state.parent, state.key, newval)
559
- }
560
-
561
- // Other scalars are left in place unchanged.
562
-
563
- // Custom modification.
564
- if (modify) {
565
- modify(
566
- state.key,
567
- val,
568
- state.parent,
569
- state,
570
- current,
571
- store
572
- )
573
- }
574
-
575
- // Original val reference may no longer be correct.
576
- const out = getprop(state.parent, S.DTOP)
577
-
578
- // Output is only needed at the top level as the final result
579
- return out
580
- }
581
-
582
-
583
- // Default inject handler for transforms. If the path resolves to a function,
584
- // call the function passing the injection state. This is how transforms operate.
585
- const injecthandler = (state, val, current, ref, store) => {
586
- let out = val
587
-
588
- if (S.function === typeof val && ref.startsWith('$')) {
589
- out = val(state, val, current, store)
590
- }
591
- else if (S.MVAL === state.mode && state.full) {
592
- setprop(state.parent, state.key, val)
593
- }
594
-
595
- return out
944
+ // Inject values from a data store into a node recursively, resolving
945
+ // paths against the store, or current if they are local. The modify
946
+ // argument allows custom modification of the result. The inj
947
+ // (Injection) argument is used to maintain recursive state.
948
+ 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)
596
1038
  }
597
-
598
-
599
- // The transform_* functions are define inject handlers (see InjectHandler).
600
-
601
-
1039
+ // The transform_* functions are special command inject handlers (see Injector).
602
1040
  // Delete a key from a map or list.
603
- const transform_DELETE = (state) => {
604
- const { key, parent } = state
605
- setprop(parent, key, UNDEF)
606
- return UNDEF
1041
+ const transform_DELETE = (inj) => {
1042
+ inj.setval(NONE)
1043
+ return NONE
607
1044
  }
608
-
609
-
610
1045
  // Copy value from source data.
611
- const transform_COPY = (state, _val, current) => {
612
- const { mode, key, parent } = state
613
-
614
- let out
615
- if (mode.startsWith(S.MKEY)) {
616
- out = key
617
- }
618
- else {
619
- out = getprop(current, key)
620
- setprop(parent, key, out)
621
- }
622
-
623
- return out
1046
+ 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
624
1054
  }
625
-
626
-
627
1055
  // As a value, inject the key of the parent node.
628
1056
  // As a key, defined the name of the key property in the source object.
629
- const transform_KEY = (state, _val, current) => {
630
- const { mode, path, parent } = state
631
-
632
- if (S.MVAL !== mode) {
633
- return UNDEF
634
- }
635
-
636
- const keyspec = getprop(parent, S.TKEY)
637
- if (UNDEF !== keyspec) {
638
- setprop(parent, S.TKEY, UNDEF)
639
- return getprop(current, keyspec)
640
- }
641
-
642
- return getprop(getprop(parent, S.TMETA), S.KEY, getprop(path, path.length - 2))
1057
+ 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))
643
1072
  }
644
-
645
-
646
- // Store meta data about a node.
647
- const transform_META = (state) => {
648
- const { parent } = state
649
- setprop(parent, S.TMETA, UNDEF)
650
- return UNDEF
1073
+ // Annotate node. Does nothing itself, just used by
1074
+ // other injectors, and is removed when called.
1075
+ const transform_ANNO = (inj) => {
1076
+ const { parent } = inj
1077
+ delprop(parent, S_BANNO)
1078
+ return NONE
651
1079
  }
652
-
653
-
654
1080
  // Merge a list of objects into the current object.
655
1081
  // Must be a key in an object. The value is merged over the current object.
656
1082
  // If the value is an array, the elements are first merged using `merge`.
657
1083
  // If the value is the empty string, merge the top level store.
658
1084
  // Format: { '`$MERGE`': '`source-path`' | ['`source-paths`', ...] }
659
- const transform_MERGE = (
660
- state, _val, store
661
- ) => {
662
- const { mode, key, parent } = state
663
-
664
- if (S.MKEYPRE === mode) { return key }
665
-
666
- // Operate after child values have been transformed.
667
- if (S.MKEYPOST === mode) {
668
-
669
- let args = getprop(parent, key)
670
- args = S.empty === args ? [store.$TOP] : Array.isArray(args) ? args : [args]
671
-
672
- setprop(parent, key, UNDEF)
673
-
674
- // Literals in the parent have precedence.
675
- const mergelist = [parent, ...args, clone(parent)]
676
-
677
- merge(mergelist)
678
-
679
- return key
680
- }
681
-
682
- return UNDEF
1085
+ 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
683
1105
  }
684
-
685
-
686
1106
  // Convert a node to a list.
687
1107
  // Format: ['`$EACH`', '`source-path-of-node`', child-template]
688
- const transform_EACH = (
689
- state,
690
- _val,
691
- current,
692
- store
693
- ) => {
694
- const { mode, keys, path, parent, nodes } = state
695
-
696
- // Remove arguments to avoid spurious processing.
697
- if (keys) {
698
- keys.length = 1
699
- }
700
-
701
- // Defensive context checks.
702
- if (S.MVAL !== mode || null == path || null == nodes) {
703
- return UNDEF
704
- }
705
-
706
- // Get arguments.
707
- const srcpath = parent[1] // Path to source data.
708
- const child = clone(parent[2]) // Child template.
709
-
710
- // Source data
711
- const src = getpath(srcpath, store, current, state)
712
-
713
- // Create parallel data structures:
714
- // source entries :: child templates
715
- let tcurrent = []
716
- let tval = []
717
-
718
- const tkey = path[path.length - 2]
719
- const target = nodes[path.length - 2] || nodes[path.length - 1]
720
-
721
- if (isnode(src)) {
722
- if (islist(src)) {
723
- tval = src.map(() => clone(child))
1108
+ const transform_EACH = (inj, _val, _ref, store) => {
1109
+ const ijname = 'EACH'
1110
+ if (!checkPlacement(M_VAL, ijname, T_list, inj)) {
1111
+ return NONE
724
1112
  }
725
- else {
726
- tval = Object.entries(src).map(n => ({
727
- ...clone(child),
728
-
729
- // Make a note of the key for $KEY transforms
730
- [S.TMETA]: { KEY: n[0] }
731
- }))
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
732
1120
  }
733
-
734
- tcurrent = Object.values(src)
735
- }
736
-
737
- // Parent structure.
738
- tcurrent = { $TOP: tcurrent }
739
-
740
- // Build the substructure.
741
- tval = inject(
742
- tval,
743
- store,
744
- state.modify,
745
- tcurrent,
746
- )
747
-
748
- setprop(target, tkey, tval)
749
-
750
- // Prevent callee from damaging first list entry (since we are in `val` mode).
751
- return tval[0]
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]
752
1170
  }
753
-
754
-
755
-
756
1171
  // Convert a node to a map.
757
- // Format: { '`$PACK`':['`source-path`', child-template]}
758
- const transform_PACK = (
759
- state,
760
- _val,
761
- current,
762
- store
763
- ) => {
764
- const { mode, key, path, parent, nodes } = state
765
-
766
- // Defensive context checks.
767
- if (S.MKEYPRE !== mode || S.string !== typeof key || null == path || null == nodes) {
768
- return UNDEF
769
- }
770
-
771
- // Get arguments.
772
- const args = parent[key]
773
- const srcpath = args[0] // Path to source data.
774
- const child = clone(args[1]) // Child template.
775
-
776
- // Find key and target node.
777
- const keyprop = child[S.TKEY]
778
- const tkey = path[path.length - 2]
779
- const target = nodes[path.length - 2] || nodes[path.length - 1]
780
-
781
- // Source data
782
- let src = getpath(srcpath, store, current, state)
783
-
784
- // Prepare source as a list.
785
- src = islist(src) ? src :
786
- ismap(src) ? Object.entries(src)
787
- .reduce((a, n) =>
788
- (n[1][S.TMETA] = { KEY: n[0] }, a.push(n[1]), a), []) :
789
- UNDEF
790
-
791
- if (null == src) {
792
- return UNDEF
793
- }
794
-
795
- // Get key if specified.
796
- let childkey = getprop(child, S.TKEY)
797
- let keyname = UNDEF === childkey ? keyprop : childkey
798
- setprop(child, S.TKEY, UNDEF)
799
-
800
- // Build parallel target object.
801
- let tval = {}
802
- tval = src.reduce((a, n) => {
803
- let kn = getprop(n, keyname)
804
- setprop(a, kn, clone(child))
805
- const nchild = getprop(a, kn)
806
- setprop(nchild, S.TMETA, getprop(n, S.TMETA))
807
- return a
808
- }, tval)
809
-
810
- // Build parallel source object.
811
- let tcurrent = {}
812
- src.reduce((a, n) => {
813
- let kn = getprop(n, keyname)
814
- setprop(a, kn, n)
815
- return a
816
- }, tcurrent)
817
-
818
- tcurrent = { $TOP: tcurrent }
819
-
820
- // Build substructure.
821
- tval = inject(
822
- tval,
823
- store,
824
- state.modify,
825
- tcurrent,
826
- )
827
-
828
- setprop(target, tkey, tval)
829
-
830
- // Drop transform key.
831
- return UNDEF
1172
+ // Format: { '`$PACK`':['source-path', child-template]}
1173
+ 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.
1270
+ return NONE
1271
+ }
1272
+ // TODO: not found ref should removed key (setprop NONE)
1273
+ // Reference original spec (enables recursice transformations)
1274
+ // Format: ['`$REF`', '`spec-path`']
1275
+ 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),
1292
+ })
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
1327
+ }
1328
+ 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
1353
+ }
1354
+ 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
1384
+ }
1385
+ 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
832
1403
  }
833
-
834
-
835
1404
  // Transform data using spec.
836
- // Only operates on static JSON-like data (values can be functions however).
1405
+ // Only operates on static JSON-like data.
837
1406
  // Arrays are treated as if they are objects with indices as keys.
838
- function transform(
839
- data, // Source data to transform into new data (original not mutated)
840
- spec, // Transform specification; output follows this shape
841
- extra, // Additional store of data and transforms.
842
- modify // Optionally modify individual values.
843
- ) {
844
- // Clone the spec so that the clone can be modified in place as the transform result.
845
- spec = clone(spec)
846
-
847
- const extraTransforms = {}
848
- const extraData = null == extra ? {} : items(extra)
849
- .reduce((a, n) =>
850
- (n[0].startsWith(S.DS) ? extraTransforms[n[0]] = n[1] : (a[n[0]] = n[1]), a), {})
851
-
852
- const dataClone = merge([
853
- clone(UNDEF === extraData ? {} : extraData),
854
- clone(UNDEF === data ? {} : data),
855
- ])
856
-
857
- // Define a top level store that provides transform operations.
858
- const store = {
859
-
860
- // The inject function recognises this special location for the root of the source data.
861
- // NOTE: to escape data that contains "`$FOO`" keys at the top level,
862
- // place that data inside a holding map: { myholder: mydata }.
863
- $TOP: dataClone,
864
-
865
- // Escape backtick (this also works inside backticks).
866
- $BT: () => S.BT,
867
-
868
- // Escape dollar sign (this also works inside backticks).
869
- $DS: () => S.DS,
870
-
871
- // Insert current date and time as an ISO string.
872
- $WHEN: () => new Date().toISOString(),
873
-
874
- $DELETE: transform_DELETE,
875
- $COPY: transform_COPY,
876
- $KEY: transform_KEY,
877
- $META: transform_META,
878
- $MERGE: transform_MERGE,
879
- $EACH: transform_EACH,
880
- $PACK: transform_PACK,
881
-
882
- // Custom extra transforms, if any.
883
- ...extraTransforms,
884
- }
885
-
886
- const out = inject(spec, store, modify, store)
887
-
888
- return out
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
889
1460
  }
890
-
891
-
892
-
893
- function validate(
894
- data, // Source data to transform into new data (original not mutated)
895
- spec, // Transform specification; output follows this shape
896
-
897
- // TODO
898
- extra, // Additional custom checks
899
-
900
- // modify // Optionally modify individual values.
901
- collecterrs,
902
- ) {
903
- const errs = collecterrs || []
904
- const out = transform(
905
- data,
906
- spec,
907
- {
908
- $ERRS: errs,
909
-
910
- $DELETE: null,
911
- $COPY: null,
912
- $KEY: null,
913
- $META: null,
914
- $MERGE: null,
915
- $EACH: null,
916
- $PACK: null,
917
-
918
- $STRING: (state, val, current)=>{
919
- const { mode, key, parent } = state
920
- let out = getprop(current, key)
921
-
922
- let t = typeof out
923
- if(S.string === t) {
924
- if(S.empty === out) {
925
- state.errs.push('Empty string at '+pathify(state.path))
926
- }
927
- else {
928
- return out
929
- }
1461
+ // A required string value. NOTE: Rejects empty strings.
1462
+ 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
1476
+ }
1477
+ 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
1488
+ }
1489
+ // Allow any value.
1490
+ const validate_ANY = (inj) => {
1491
+ let out = getprop(inj.dparent, inj.key)
1492
+ return out
1493
+ }
1494
+ // Specify child values for map or list.
1495
+ // Map syntax: {'`$CHILD`': child-template }
1496
+ // List syntax: ['`$CHILD`', child-template ]
1497
+ 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 = {}
930
1508
  }
931
- else {
932
- state.errs.push(invalidTypeMsg(state.path,S.string,t,out))
933
- return
1509
+ else if (!ismap(tval)) {
1510
+ inj.errs.push(_invalidTypeMsg(slice(inj.path, -1), S_object, typify(tval), tval), 'V0220')
1511
+ return NONE
934
1512
  }
935
- },
936
-
937
- $NUMBER: (state, val, current)=>{
938
- const { mode, key, parent } = state
939
- let out = getprop(current, key)
940
-
941
- let t = typeof out
942
- if(S.number !== t) {
943
- state.errs.push(invalidTypeMsg(state.path,S.number,t,out))
944
- return
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)
945
1518
  }
946
-
947
- return out
948
- },
949
-
950
- $BOOLEAN: (state, val, current)=>{
951
- const { mode, key, parent } = state
952
- let out = getprop(current, key)
953
-
954
- let t = typeof out
955
- if(S.boolean !== t) {
956
- state.errs.push(invalidTypeMsg(state.path,S.boolean,t,out))
957
- return
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
958
1529
  }
959
-
960
- return out
961
- },
962
-
963
- $OBJECT: (state, val, current)=>{
964
- const { mode, key, parent } = state
965
- let out = getprop(current, key)
966
-
967
- let t = typeof out
968
-
969
- if(null == out || S.object !== t) {
970
- state.errs.push(invalidTypeMsg(state.path,S.object,t,out))
971
- return
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
972
1536
  }
973
-
974
- return out
975
- },
976
-
977
- $ARRAY: (state, val, current)=>{
978
- const { mode, key, parent } = state
979
- let out = getprop(current, key)
980
-
981
- let t = typeof out
982
- if(!Array.isArray(out)) {
983
- state.errs.push(invalidTypeMsg(state.path,S.array,t,out))
984
- return
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
985
1542
  }
986
-
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)
987
1550
  return out
988
- },
989
-
990
- $FUNCTION: (state, val, current)=>{
991
- const { mode, key, parent } = state
992
- let out = getprop(current, key)
993
-
994
- let t = typeof out
995
- if(S.function !== t) {
996
- state.errs.push(invalidTypeMsg(state.path,S.function,t,out))
997
- return
1551
+ }
1552
+ return NONE
1553
+ }
1554
+ // TODO: implement SOME, ALL
1555
+ // FIX: ONE should mean exactly one, not at least one (=SOME)
1556
+ // TODO: implement a generate validate_ALT to do all of these
1557
+ // Match at least one of the specified shapes.
1558
+ // Syntax: ['`$ONE`', alt0, alt1, ...]
1559
+ 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
998
1568
  }
999
-
1000
- return out
1001
- },
1002
-
1003
- $ANY: (state, val, current)=>{
1004
- const { mode, key, parent } = state
1005
- let out = getprop(current, key)
1006
- return out
1007
- },
1008
-
1009
- $CHILD: (state, val, current)=>{
1010
- const { mode, key, parent, keys, path } = state
1011
-
1012
- if(S.MKEYPRE === mode) {
1013
- const child = parent[key]
1014
- const pkey = path[path.length-2]
1015
- const tval = current[pkey]
1016
-
1017
- const ckeys = keysof(tval)
1018
- for(let ckey of ckeys) {
1019
- parent[ckey] = clone(child)
1020
- keys.push(ckey)
1021
- }
1022
-
1023
- delete parent[key]
1024
-
1025
- }
1026
- else if(S.MVAL === mode) {
1027
- if(!islist(parent)) {
1028
- state.errs.push('Invalid $CHILD as value')
1029
- }
1030
-
1031
- const child = parent[1]
1032
-
1033
- if(UNDEF === current) {
1034
- parent.length = 0
1035
- return UNDEF
1036
- }
1037
- else if(!islist(current)) {
1038
- state.errs.push(invalidTypeMsg(
1039
- state.path.slice(0,state.path.length-1),S.array,typeof current,current))
1040
- state.keyI = parent.length
1041
- return current
1042
- }
1043
- else {
1044
- current.map((n,i)=>parent[i]=clone(child))
1045
- parent.length = current.length
1046
- state.keyI = 0
1047
- return current[0]
1048
- }
1049
- }
1050
- },
1051
-
1052
- $ONE: (state, val, current)=>{
1053
- const { mode, key, parent, keys, path, nodes } = state
1054
-
1055
- if(S.MVAL === mode) {
1056
- state.keyI = state.keys.length
1057
-
1058
- let tvals = parent.slice(1)
1059
-
1060
- for(let tval of tvals) {
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
1061
1584
  let terrs = []
1062
- validate(current,tval,UNDEF,terrs)
1063
-
1064
- const grandparent = nodes[nodes.length-2]
1065
- const grandkey = path[path.length-2]
1066
-
1067
- if(isnode(grandparent)) {
1068
- if(0===terrs.length) {
1069
- setprop(grandparent, grandkey, current)
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)) {
1070
1595
  return
1071
- }
1072
- else {
1073
- setprop(grandparent, grandkey, UNDEF)
1074
- }
1075
1596
  }
1076
- }
1077
-
1078
- const valdesc = tvals
1079
- .map(v=>stringify(v))
1080
- .join(', ')
1081
- .replace(/`\$([A-Z]+)`/g, (m,p1)=>p1.toLowerCase())
1082
-
1083
- state.errs.push(invalidTypeMsg(
1084
- state.path.slice(0,state.path.length-1),
1085
- 'one of '+valdesc,
1086
- typeof current, current))
1087
- }
1088
- },
1089
-
1090
- ...(extra||{})
1091
- },
1092
-
1093
- (key,
1094
- val,
1095
- parent,
1096
- state,
1097
- current,
1098
- store)=>{
1099
- const cval = isnode(current) ? current[key] : UNDEF
1100
-
1101
- if(UNDEF === cval) {
1102
- return
1103
- }
1104
-
1105
- const pval = parent[key]
1106
-
1107
- const t = typeof pval
1108
-
1109
- if(S.string === t && pval.includes('$')) {
1110
- return
1111
- }
1112
-
1113
- //const t = typeof val
1114
- const ct = typeof cval
1115
-
1116
- if(t !== ct && UNDEF !== pval) {
1117
- state.errs.push(invalidTypeMsg(state.path,t,ct,cval))
1118
- }
1119
- else if(ismap(cval)) {
1120
- const ckeys = keysof(cval)
1121
- const pkeys = keysof(pval)
1122
-
1123
- // Empty spec object {} means object can be open (any keys).
1124
- if(0 < pkeys.length && true !== getprop(pval,'`$OPEN`')) {
1125
- const badkeys = []
1126
- for(ckey of ckeys) {
1127
- if(!haskey(val, ckey)) {
1128
- badkeys.push(ckey)
1129
- }
1130
- }
1131
- if(0 < badkeys.length) {
1132
- state.errs.push('Unexpected keys at '+pathify(state.path)+
1133
- ': '+badkeys.join(', '))
1134
- }
1135
- }
1136
- else {
1137
- merge([pval, cval])
1138
- if(isnode(pval)) {
1139
- delete pval['`$OPEN`']
1140
- }
1141
- }
1142
- }
1143
- else if(islist(cval)) {
1144
- if(!islist(val)) {
1145
- state.errs.push(invalidTypeMsg(state.path,t,ct,cval))
1146
- }
1147
- }
1148
- else {
1149
- // Spec value was a default, copy over data
1150
- parent[key] = cval
1151
- }
1152
- }
1153
- )
1154
-
1155
- if(0 < errs.length && null == collecterrs) {
1156
- throw new Error('Invalid data: '+errs.join('\n'))
1157
- }
1158
-
1159
- return out
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'))
1601
+ }
1160
1602
  }
1161
-
1162
-
1163
- function invalidTypeMsg(path, type, vt, v) {
1164
- return 'Expected '+type+' at '+pathify(path)+', found '+(null != v?vt+': ':'')+v
1603
+ 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)
1646
+ }
1165
1647
  }
1166
-
1167
- function pathify(val, from) {
1168
- from = null == from ? 1 : -1 < from ? from : 1
1169
- if(Array.isArray(val)) {
1170
- let path = val.slice(from)
1171
- if(0 === path.length) {
1172
- return '<root>'
1648
+ // This is the "modify" argument to inject. Use this to perform
1649
+ // generic validation. Runs *after* any special commands.
1650
+ 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
+ }
1720
+ return
1721
+ }
1722
+ // Validate a data structure against a shape specification. The shape
1723
+ // specification follows the "by example" principle. Plain data in
1724
+ // teh shape is treated as default values that also specify the
1725
+ // required type. Thus shape {a:1} validates {a:2}, since the types
1726
+ // (number) match, but not {a:'A'}. Shape {a;1} against data {}
1727
+ // returns {a:1} as a=1 is the default value of the a key. Special
1728
+ // validation commands (in the same syntax as transform ) are also
1729
+ // provided to specify required values. Thus shape {a:'`$STRING`'}
1730
+ // validates {a:'A'} but not {a:1}. Empty map or list means the node
1731
+ // 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
1785
+ }
1786
+ 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
+ }
1804
+ const gkey = getelem(inj.path, -2)
1805
+ const gp = getelem(inj.nodes, -2)
1806
+ setprop(gp, gkey, point)
1807
+ }
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
+ }
1832
+ }
1833
+ 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)
1852
+ }
1853
+ }
1854
+ 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
1889
+ }
1890
+ // Select children from a top-level object that match a MongoDB-style query.
1891
+ // Supports $and, $or, and equality comparisons.
1892
+ // For arrays, children are elements; for objects, children are values.
1893
+ // TODO: swap arg order for consistency
1894
+ 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
1928
+ })
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
1937
+ }
1938
+ // Injection state used for recursive injection into JSON - like data structures.
1939
+ 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
+ }
2031
+ }
2032
+ // Internal utilities
2033
+ // ==================
2034
+ // // Update all references to target in inj.nodes.
2035
+ // function _updateAncestors(_inj: Injection, target: any, tkey: any, tval: any) {
2036
+ // // SetProp is sufficient in TypeScript as target reference remains consistent even for lists.
2037
+ // setprop(target, tkey, tval)
2038
+ // }
2039
+ // Build a type validation error message.
2040
+ 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
+ '.'
2049
+ }
2050
+ // Default inject handler for transforms. If the path resolves to a function,
2051
+ // call the function passing the injection inj. This is how transforms operate.
2052
+ 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
2065
+ }
2066
+ 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
2084
+ }
2085
+ // Inject values from a data store into a string. Not a public utility - used by
2086
+ // `inject`. Inject are marked with `path` where path is resolved
2087
+ // with getpath against the store or current (if defined)
2088
+ // arguments. See `getpath`. Custom injection handling can be
2089
+ // provided by inj.handler (this is used for transform functions).
2090
+ // The path can also have the special syntax $NAME999 where NAME is
2091
+ // upper case letters only, and 999 is any digits, which are
2092
+ // discarded. This syntax specifies the name of a transform, and
2093
+ // optionally allows transforms to be ordered by alphanumeric sorting.
2094
+ 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
2138
+ }
2139
+ // Handler Utilities
2140
+ // =================
2141
+ const MODENAME = {
2142
+ [M_VAL]: 'val',
2143
+ [M_KEYPRE]: 'key:pre',
2144
+ [M_KEYPOST]: 'key:post',
2145
+ }
2146
+ const PLACEMENT = {
2147
+ [M_VAL]: 'value',
2148
+ [M_KEYPRE]: S_key,
2149
+ [M_KEYPOST]: S_key,
2150
+ }
2151
+ 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
2166
+ }
2167
+ // function injectorArgs(argTypes: number[], inj: Injection): any {
2168
+ 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
2183
+ }
2184
+ return found
2185
+ }
2186
+ 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
2204
+ }
2205
+ 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
1173
2267
  }
1174
- return path.join('.')
1175
- }
1176
- return '<unknown-path>'
1177
2268
  }
1178
2269
 
1179
2270
 
1180
2271
  module.exports = {
2272
+ StructUtility,
2273
+ Injection,
2274
+
1181
2275
  clone,
2276
+ delprop,
1182
2277
  escre,
1183
2278
  escurl,
2279
+ filter,
2280
+ flatten,
2281
+ getdef,
2282
+ getelem,
1184
2283
  getpath,
1185
2284
  getprop,
2285
+ haskey,
1186
2286
  inject,
1187
2287
  isempty,
2288
+ isfunc,
1188
2289
  iskey,
1189
2290
  islist,
1190
2291
  ismap,
1191
2292
  isnode,
1192
- isfunc,
1193
- haskey,
1194
- keysof,
1195
2293
  items,
2294
+ join,
2295
+ jsonify,
2296
+ keysof,
1196
2297
  merge,
2298
+ pad,
2299
+ pathify,
2300
+ select,
2301
+ setpath,
1197
2302
  setprop,
2303
+ size,
2304
+ slice,
2305
+ strkey,
1198
2306
  stringify,
1199
2307
  transform,
1200
- walk,
1201
- joinurl,
2308
+ typify,
2309
+ typename,
1202
2310
  validate,
2311
+ walk,
2312
+
2313
+ SKIP,
2314
+ DELETE,
2315
+
2316
+ jm,
2317
+ jt,
2318
+
2319
+ T_any,
2320
+ T_noval,
2321
+ T_boolean,
2322
+ T_decimal,
2323
+ T_integer,
2324
+ T_number,
2325
+ T_string,
2326
+ T_function,
2327
+ T_symbol,
2328
+ T_null,
2329
+ T_list,
2330
+ T_map,
2331
+ T_instance,
2332
+ T_scalar,
2333
+ T_node,
2334
+
2335
+ M_KEYPRE,
2336
+ M_KEYPOST,
2337
+ M_VAL,
2338
+
2339
+ MODENAME,
2340
+
2341
+ checkPlacement,
2342
+ injectorArgs,
2343
+ injectChild,
1203
2344
  }