@versionzero/schema 1.0.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 (401) hide show
  1. package/LICENSE +177 -0
  2. package/README.md +246 -0
  3. package/package.json +84 -0
  4. package/src/compilation/handler-compilation.js +28 -0
  5. package/src/compilation/metadata-compilation.js +35 -0
  6. package/src/compilation/schema-compilation.js +142 -0
  7. package/src/compilation/selection-compilation.js +84 -0
  8. package/src/compilation/union-compilation.js +510 -0
  9. package/src/compilation/values-compilation.js +35 -0
  10. package/src/compiled-schema.js +1709 -0
  11. package/src/constants.js +1 -0
  12. package/src/core-library/index.js +32 -0
  13. package/src/core-library/processors/aggregation-operators.js +75 -0
  14. package/src/core-library/processors/alpha-constraint.js +20 -0
  15. package/src/core-library/processors/alphanum-constraint.js +20 -0
  16. package/src/core-library/processors/array-operator.js +51 -0
  17. package/src/core-library/processors/assert-constraint.js +75 -0
  18. package/src/core-library/processors/base64-constraint.js +26 -0
  19. package/src/core-library/processors/camel-case-operator.js +24 -0
  20. package/src/core-library/processors/capitalize-operator.js +16 -0
  21. package/src/core-library/processors/cardnum-constraint.js +193 -0
  22. package/src/core-library/processors/ceil-operator.js +44 -0
  23. package/src/core-library/processors/collapse-operator.js +29 -0
  24. package/src/core-library/processors/compact-operator.js +34 -0
  25. package/src/core-library/processors/compile-operator.js +65 -0
  26. package/src/core-library/processors/concat-operator.js +51 -0
  27. package/src/core-library/processors/conditional-operators.js +301 -0
  28. package/src/core-library/processors/constant-case-operator.js +16 -0
  29. package/src/core-library/processors/data-size-operator.js +86 -0
  30. package/src/core-library/processors/date-object-operator.js +54 -0
  31. package/src/core-library/processors/date-operator.js +67 -0
  32. package/src/core-library/processors/date-range-constraint.js +76 -0
  33. package/src/core-library/processors/defined-constraint.js +30 -0
  34. package/src/core-library/processors/each-operator.js +57 -0
  35. package/src/core-library/processors/email-constraint.js +112 -0
  36. package/src/core-library/processors/entries-operator.js +25 -0
  37. package/src/core-library/processors/eq-constraint.js +37 -0
  38. package/src/core-library/processors/filter-operator.js +74 -0
  39. package/src/core-library/processors/find-schema-operator.js +45 -0
  40. package/src/core-library/processors/flatten-operator.js +40 -0
  41. package/src/core-library/processors/floor-operator.js +47 -0
  42. package/src/core-library/processors/get-operator.js +44 -0
  43. package/src/core-library/processors/group-by-operator.js +84 -0
  44. package/src/core-library/processors/has-prefix-constraint.js +37 -0
  45. package/src/core-library/processors/has-suffix-constraint.js +35 -0
  46. package/src/core-library/processors/hex-constraint.js +20 -0
  47. package/src/core-library/processors/hostname-constraint.js +22 -0
  48. package/src/core-library/processors/http-url-constraint.js +27 -0
  49. package/src/core-library/processors/in-constraint.js +66 -0
  50. package/src/core-library/processors/index-by-operator.js +98 -0
  51. package/src/core-library/processors/index.js +131 -0
  52. package/src/core-library/processors/input-operator.js +23 -0
  53. package/src/core-library/processors/instanceof-constraint.js +38 -0
  54. package/src/core-library/processors/integer-constraint.js +22 -0
  55. package/src/core-library/processors/invoke-operator.js +33 -0
  56. package/src/core-library/processors/ipv4-constraint.js +188 -0
  57. package/src/core-library/processors/ipv6-constraint.js +205 -0
  58. package/src/core-library/processors/is-array-constraint.js +21 -0
  59. package/src/core-library/processors/is-date-constraint.js +22 -0
  60. package/src/core-library/processors/is-number-constraint.js +21 -0
  61. package/src/core-library/processors/is-object-constraint.js +21 -0
  62. package/src/core-library/processors/is-string-constraint.js +21 -0
  63. package/src/core-library/processors/join-operator.js +41 -0
  64. package/src/core-library/processors/json-constraint.js +22 -0
  65. package/src/core-library/processors/json-decode-operator.js +25 -0
  66. package/src/core-library/processors/json-encode-operator.js +35 -0
  67. package/src/core-library/processors/kebab-case-operator.js +23 -0
  68. package/src/core-library/processors/keys-operator.js +20 -0
  69. package/src/core-library/processors/length-constraint.js +85 -0
  70. package/src/core-library/processors/lookup-operator.js +84 -0
  71. package/src/core-library/processors/lowercase-operator.js +14 -0
  72. package/src/core-library/processors/map-operator.js +84 -0
  73. package/src/core-library/processors/match-operator.js +64 -0
  74. package/src/core-library/processors/matches-constraint.js +54 -0
  75. package/src/core-library/processors/math-operators.js +151 -0
  76. package/src/core-library/processors/merge-deep-operator.js +61 -0
  77. package/src/core-library/processors/merge-operator.js +54 -0
  78. package/src/core-library/processors/metadata-operator.js +100 -0
  79. package/src/core-library/processors/negative-constraint.js +23 -0
  80. package/src/core-library/processors/never-constraint.js +69 -0
  81. package/src/core-library/processors/non-empty-constraint.js +59 -0
  82. package/src/core-library/processors/not-constraint.js +71 -0
  83. package/src/core-library/processors/number-operator.js +24 -0
  84. package/src/core-library/processors/numeric-constraint.js +22 -0
  85. package/src/core-library/processors/object-operator.js +38 -0
  86. package/src/core-library/processors/omit-operator.js +57 -0
  87. package/src/core-library/processors/parallel-operator.js +64 -0
  88. package/src/core-library/processors/pascal-case-operator.js +16 -0
  89. package/src/core-library/processors/phone-constraint.js +235 -0
  90. package/src/core-library/processors/pick-operator.js +62 -0
  91. package/src/core-library/processors/pipeline-operator.js +63 -0
  92. package/src/core-library/processors/port-constraint.js +22 -0
  93. package/src/core-library/processors/positive-constraint.js +23 -0
  94. package/src/core-library/processors/process-operator.js +55 -0
  95. package/src/core-library/processors/property-operator.js +49 -0
  96. package/src/core-library/processors/range-constraint.js +72 -0
  97. package/src/core-library/processors/reference-operator.js +79 -0
  98. package/src/core-library/processors/require-constraint.js +74 -0
  99. package/src/core-library/processors/reverse-operator.js +20 -0
  100. package/src/core-library/processors/round-operator.js +53 -0
  101. package/src/core-library/processors/schema-handler-operators.js +54 -0
  102. package/src/core-library/processors/semver-constraint.js +282 -0
  103. package/src/core-library/processors/sequence-processors.js +406 -0
  104. package/src/core-library/processors/sort-operator.js +52 -0
  105. package/src/core-library/processors/split-operator.js +43 -0
  106. package/src/core-library/processors/string-extra-operators.js +141 -0
  107. package/src/core-library/processors/string-operator.js +34 -0
  108. package/src/core-library/processors/target-operator.js +30 -0
  109. package/src/core-library/processors/template-operator.js +60 -0
  110. package/src/core-library/processors/title-case-operator.js +17 -0
  111. package/src/core-library/processors/trim-operator.js +14 -0
  112. package/src/core-library/processors/truthy-constraint.js +35 -0
  113. package/src/core-library/processors/type-operator.js +24 -0
  114. package/src/core-library/processors/unique-operator.js +21 -0
  115. package/src/core-library/processors/uppercase-operator.js +14 -0
  116. package/src/core-library/processors/url-constraint.js +31 -0
  117. package/src/core-library/processors/url-decode-operator.js +50 -0
  118. package/src/core-library/processors/url-encode-operator.js +44 -0
  119. package/src/core-library/processors/uuid-constraint.js +31 -0
  120. package/src/core-library/processors/values-operator.js +20 -0
  121. package/src/core-library/schemas/any-schema.js +23 -0
  122. package/src/core-library/schemas/array-schema.js +8 -0
  123. package/src/core-library/schemas/boolean-schema.js +10 -0
  124. package/src/core-library/schemas/date-schema.js +12 -0
  125. package/src/core-library/schemas/function-schema.js +40 -0
  126. package/src/core-library/schemas/number-schema.js +9 -0
  127. package/src/core-library/schemas/object-schema.js +10 -0
  128. package/src/core-library/schemas/root-schema.js +21 -0
  129. package/src/core-library/schemas/string-schema.js +9 -0
  130. package/src/core-library-node/index.js +47 -0
  131. package/src/core-library-node/processors/base64-decode-operator.js +20 -0
  132. package/src/core-library-node/processors/base64-encode-operator.js +20 -0
  133. package/src/core-library-node/processors/buffer-operator.js +39 -0
  134. package/src/core-library-node/processors/directory-constraint.js +35 -0
  135. package/src/core-library-node/processors/executable-constraint.js +34 -0
  136. package/src/core-library-node/processors/file-constraint.js +34 -0
  137. package/src/core-library-node/processors/file-size-constraint.js +94 -0
  138. package/src/core-library-node/processors/is-buffer-constraint.js +21 -0
  139. package/src/core-library-node/processors/reachable-constraint.js +28 -0
  140. package/src/core-library-node/processors/readable-constraint.js +34 -0
  141. package/src/core-library-node/processors/writable-constraint.js +59 -0
  142. package/src/core-library-node/schemas/buffer-schema.js +10 -0
  143. package/src/errors.js +209 -0
  144. package/src/executor/array-executor.js +78 -0
  145. package/src/executor/conditional-executor.js +134 -0
  146. package/src/executor/each-executor.js +68 -0
  147. package/src/executor/executor.js +123 -0
  148. package/src/executor/object-executor.js +98 -0
  149. package/src/executor/parallel-executor.js +43 -0
  150. package/src/executor/pipeline-executor.js +65 -0
  151. package/src/executor/sequence-executor.js +206 -0
  152. package/src/executor/serial-executor.js +24 -0
  153. package/src/executor/step-executor.js +68 -0
  154. package/src/helpers/case.js +124 -0
  155. package/src/helpers/data-size.js +144 -0
  156. package/src/helpers/debug-sink.js +15 -0
  157. package/src/helpers/deep.js +280 -0
  158. package/src/helpers/format.js +121 -0
  159. package/src/helpers/has-string-properties.js +30 -0
  160. package/src/helpers/index.js +16 -0
  161. package/src/helpers/object.js +115 -0
  162. package/src/helpers/parse-date.js +75 -0
  163. package/src/helpers/path.js +28 -0
  164. package/src/helpers/regex.js +18 -0
  165. package/src/helpers/stringify.js +309 -0
  166. package/src/helpers/to-data.js +64 -0
  167. package/src/helpers/truthy.js +55 -0
  168. package/src/index.js +29 -0
  169. package/src/schema-compiler.js +531 -0
  170. package/src/schema-location.js +200 -0
  171. package/src/schema-resolver.js +546 -0
  172. package/src/schema.js +1182 -0
  173. package/src/traversal/executors/check-condition.js +42 -0
  174. package/src/traversal/executors/check-input.js +27 -0
  175. package/src/traversal/executors/check-required.js +19 -0
  176. package/src/traversal/executors/check-schema.js +45 -0
  177. package/src/traversal/executors/defaults.js +21 -0
  178. package/src/traversal/executors/enter-existing.js +25 -0
  179. package/src/traversal/executors/enter-input.js +25 -0
  180. package/src/traversal/executors/enter.js +37 -0
  181. package/src/traversal/executors/exit.js +74 -0
  182. package/src/traversal/executors/finalize.js +64 -0
  183. package/src/traversal/executors/index.js +42 -0
  184. package/src/traversal/executors/normalize.js +38 -0
  185. package/src/traversal/executors/prepare-existing.js +27 -0
  186. package/src/traversal/executors/prepare-pending.js +54 -0
  187. package/src/traversal/executors/resolve-union.js +50 -0
  188. package/src/traversal/executors/serialize.js +48 -0
  189. package/src/traversal/executors/transform-early.js +51 -0
  190. package/src/traversal/executors/transform.js +68 -0
  191. package/src/traversal/executors/traversal-state-executor.js +46 -0
  192. package/src/traversal/executors/validate.js +63 -0
  193. package/src/traversal/traversal-context.js +231 -0
  194. package/src/traversal/traversal-state.js +809 -0
  195. package/src/types.js +102 -0
  196. package/src/value-processor/composed-value-processor.js +43 -0
  197. package/src/value-processor/defined-value-processor.js +72 -0
  198. package/src/value-processor/function-value-processor.js +68 -0
  199. package/src/value-processor/parameterized-value-processor.js +45 -0
  200. package/src/value-processor/parameters-value-processor.js +178 -0
  201. package/src/value-processor/spec.js +89 -0
  202. package/src/value-processor/value-processor.js +105 -0
  203. package/types/compilation/handler-compilation.d.ts +13 -0
  204. package/types/compilation/metadata-compilation.d.ts +6 -0
  205. package/types/compilation/schema-compilation.d.ts +32 -0
  206. package/types/compilation/selection-compilation.d.ts +9 -0
  207. package/types/compilation/union-compilation.d.ts +42 -0
  208. package/types/compilation/values-compilation.d.ts +12 -0
  209. package/types/compiled-schema.d.ts +883 -0
  210. package/types/constants.d.ts +1 -0
  211. package/types/core-library/index.d.ts +7 -0
  212. package/types/core-library/processors/aggregation-operators.d.ts +24 -0
  213. package/types/core-library/processors/alpha-constraint.d.ts +9 -0
  214. package/types/core-library/processors/alphanum-constraint.d.ts +9 -0
  215. package/types/core-library/processors/array-operator.d.ts +12 -0
  216. package/types/core-library/processors/assert-constraint.d.ts +30 -0
  217. package/types/core-library/processors/base64-constraint.d.ts +11 -0
  218. package/types/core-library/processors/camel-case-operator.d.ts +17 -0
  219. package/types/core-library/processors/capitalize-operator.d.ts +11 -0
  220. package/types/core-library/processors/cardnum-constraint.d.ts +51 -0
  221. package/types/core-library/processors/ceil-operator.d.ts +30 -0
  222. package/types/core-library/processors/collapse-operator.d.ts +24 -0
  223. package/types/core-library/processors/compact-operator.d.ts +29 -0
  224. package/types/core-library/processors/compile-operator.d.ts +34 -0
  225. package/types/core-library/processors/concat-operator.d.ts +23 -0
  226. package/types/core-library/processors/conditional-operators.d.ts +219 -0
  227. package/types/core-library/processors/constant-case-operator.d.ts +9 -0
  228. package/types/core-library/processors/data-size-operator.d.ts +31 -0
  229. package/types/core-library/processors/date-object-operator.d.ts +16 -0
  230. package/types/core-library/processors/date-operator.d.ts +21 -0
  231. package/types/core-library/processors/date-range-constraint.d.ts +26 -0
  232. package/types/core-library/processors/defined-constraint.d.ts +20 -0
  233. package/types/core-library/processors/each-operator.d.ts +34 -0
  234. package/types/core-library/processors/email-constraint.d.ts +54 -0
  235. package/types/core-library/processors/entries-operator.d.ts +13 -0
  236. package/types/core-library/processors/eq-constraint.d.ts +20 -0
  237. package/types/core-library/processors/filter-operator.d.ts +35 -0
  238. package/types/core-library/processors/find-schema-operator.d.ts +28 -0
  239. package/types/core-library/processors/flatten-operator.d.ts +26 -0
  240. package/types/core-library/processors/floor-operator.d.ts +33 -0
  241. package/types/core-library/processors/get-operator.d.ts +31 -0
  242. package/types/core-library/processors/group-by-operator.d.ts +36 -0
  243. package/types/core-library/processors/has-prefix-constraint.d.ts +22 -0
  244. package/types/core-library/processors/has-suffix-constraint.d.ts +20 -0
  245. package/types/core-library/processors/hex-constraint.d.ts +9 -0
  246. package/types/core-library/processors/hostname-constraint.d.ts +11 -0
  247. package/types/core-library/processors/http-url-constraint.d.ts +9 -0
  248. package/types/core-library/processors/in-constraint.d.ts +27 -0
  249. package/types/core-library/processors/index-by-operator.d.ts +26 -0
  250. package/types/core-library/processors/index.d.ts +8 -0
  251. package/types/core-library/processors/input-operator.d.ts +20 -0
  252. package/types/core-library/processors/instanceof-constraint.d.ts +23 -0
  253. package/types/core-library/processors/integer-constraint.d.ts +9 -0
  254. package/types/core-library/processors/invoke-operator.d.ts +12 -0
  255. package/types/core-library/processors/ipv4-constraint.d.ts +37 -0
  256. package/types/core-library/processors/ipv6-constraint.d.ts +34 -0
  257. package/types/core-library/processors/is-array-constraint.d.ts +10 -0
  258. package/types/core-library/processors/is-date-constraint.d.ts +10 -0
  259. package/types/core-library/processors/is-number-constraint.d.ts +10 -0
  260. package/types/core-library/processors/is-object-constraint.d.ts +10 -0
  261. package/types/core-library/processors/is-string-constraint.d.ts +10 -0
  262. package/types/core-library/processors/join-operator.d.ts +29 -0
  263. package/types/core-library/processors/json-constraint.d.ts +10 -0
  264. package/types/core-library/processors/json-decode-operator.d.ts +9 -0
  265. package/types/core-library/processors/json-encode-operator.d.ts +27 -0
  266. package/types/core-library/processors/kebab-case-operator.d.ts +16 -0
  267. package/types/core-library/processors/keys-operator.d.ts +9 -0
  268. package/types/core-library/processors/length-constraint.d.ts +34 -0
  269. package/types/core-library/processors/lookup-operator.d.ts +36 -0
  270. package/types/core-library/processors/lowercase-operator.d.ts +9 -0
  271. package/types/core-library/processors/map-operator.d.ts +38 -0
  272. package/types/core-library/processors/match-operator.d.ts +34 -0
  273. package/types/core-library/processors/matches-constraint.d.ts +29 -0
  274. package/types/core-library/processors/math-operators.d.ts +91 -0
  275. package/types/core-library/processors/merge-deep-operator.d.ts +32 -0
  276. package/types/core-library/processors/merge-operator.d.ts +26 -0
  277. package/types/core-library/processors/metadata-operator.d.ts +56 -0
  278. package/types/core-library/processors/negative-constraint.d.ts +13 -0
  279. package/types/core-library/processors/never-constraint.d.ts +26 -0
  280. package/types/core-library/processors/non-empty-constraint.d.ts +28 -0
  281. package/types/core-library/processors/not-constraint.d.ts +28 -0
  282. package/types/core-library/processors/number-operator.d.ts +9 -0
  283. package/types/core-library/processors/numeric-constraint.d.ts +10 -0
  284. package/types/core-library/processors/object-operator.d.ts +10 -0
  285. package/types/core-library/processors/omit-operator.d.ts +24 -0
  286. package/types/core-library/processors/parallel-operator.d.ts +41 -0
  287. package/types/core-library/processors/pascal-case-operator.d.ts +9 -0
  288. package/types/core-library/processors/phone-constraint.d.ts +65 -0
  289. package/types/core-library/processors/pick-operator.d.ts +27 -0
  290. package/types/core-library/processors/pipeline-operator.d.ts +40 -0
  291. package/types/core-library/processors/port-constraint.d.ts +11 -0
  292. package/types/core-library/processors/positive-constraint.d.ts +13 -0
  293. package/types/core-library/processors/process-operator.d.ts +37 -0
  294. package/types/core-library/processors/property-operator.d.ts +34 -0
  295. package/types/core-library/processors/range-constraint.d.ts +30 -0
  296. package/types/core-library/processors/reference-operator.d.ts +38 -0
  297. package/types/core-library/processors/require-constraint.d.ts +29 -0
  298. package/types/core-library/processors/reverse-operator.d.ts +9 -0
  299. package/types/core-library/processors/round-operator.d.ts +34 -0
  300. package/types/core-library/processors/schema-handler-operators.d.ts +28 -0
  301. package/types/core-library/processors/semver-constraint.d.ts +43 -0
  302. package/types/core-library/processors/sequence-processors.d.ts +213 -0
  303. package/types/core-library/processors/sort-operator.d.ts +31 -0
  304. package/types/core-library/processors/split-operator.d.ts +33 -0
  305. package/types/core-library/processors/string-extra-operators.d.ts +83 -0
  306. package/types/core-library/processors/string-operator.d.ts +10 -0
  307. package/types/core-library/processors/target-operator.d.ts +27 -0
  308. package/types/core-library/processors/template-operator.d.ts +31 -0
  309. package/types/core-library/processors/title-case-operator.d.ts +12 -0
  310. package/types/core-library/processors/trim-operator.d.ts +9 -0
  311. package/types/core-library/processors/truthy-constraint.d.ts +23 -0
  312. package/types/core-library/processors/type-operator.d.ts +11 -0
  313. package/types/core-library/processors/unique-operator.d.ts +10 -0
  314. package/types/core-library/processors/uppercase-operator.d.ts +9 -0
  315. package/types/core-library/processors/url-constraint.d.ts +20 -0
  316. package/types/core-library/processors/url-decode-operator.d.ts +31 -0
  317. package/types/core-library/processors/url-encode-operator.d.ts +36 -0
  318. package/types/core-library/processors/uuid-constraint.d.ts +20 -0
  319. package/types/core-library/processors/values-operator.d.ts +9 -0
  320. package/types/core-library/schemas/any-schema.d.ts +2 -0
  321. package/types/core-library/schemas/array-schema.d.ts +2 -0
  322. package/types/core-library/schemas/boolean-schema.d.ts +2 -0
  323. package/types/core-library/schemas/date-schema.d.ts +2 -0
  324. package/types/core-library/schemas/function-schema.d.ts +2 -0
  325. package/types/core-library/schemas/number-schema.d.ts +2 -0
  326. package/types/core-library/schemas/object-schema.d.ts +2 -0
  327. package/types/core-library/schemas/root-schema.d.ts +2 -0
  328. package/types/core-library/schemas/string-schema.d.ts +2 -0
  329. package/types/core-library-node/index.d.ts +12 -0
  330. package/types/core-library-node/processors/base64-decode-operator.d.ts +9 -0
  331. package/types/core-library-node/processors/base64-encode-operator.d.ts +9 -0
  332. package/types/core-library-node/processors/buffer-operator.d.ts +15 -0
  333. package/types/core-library-node/processors/directory-constraint.d.ts +14 -0
  334. package/types/core-library-node/processors/executable-constraint.d.ts +17 -0
  335. package/types/core-library-node/processors/file-constraint.d.ts +13 -0
  336. package/types/core-library-node/processors/file-size-constraint.d.ts +43 -0
  337. package/types/core-library-node/processors/is-buffer-constraint.d.ts +10 -0
  338. package/types/core-library-node/processors/reachable-constraint.d.ts +13 -0
  339. package/types/core-library-node/processors/readable-constraint.d.ts +17 -0
  340. package/types/core-library-node/processors/writable-constraint.d.ts +18 -0
  341. package/types/core-library-node/schemas/buffer-schema.d.ts +2 -0
  342. package/types/errors.d.ts +58 -0
  343. package/types/executor/array-executor.d.ts +17 -0
  344. package/types/executor/conditional-executor.d.ts +45 -0
  345. package/types/executor/each-executor.d.ts +15 -0
  346. package/types/executor/executor.d.ts +84 -0
  347. package/types/executor/object-executor.d.ts +14 -0
  348. package/types/executor/parallel-executor.d.ts +27 -0
  349. package/types/executor/pipeline-executor.d.ts +11 -0
  350. package/types/executor/sequence-executor.d.ts +32 -0
  351. package/types/executor/serial-executor.d.ts +16 -0
  352. package/types/executor/step-executor.d.ts +14 -0
  353. package/types/helpers/case.d.ts +30 -0
  354. package/types/helpers/data-size.d.ts +25 -0
  355. package/types/helpers/debug-sink.d.ts +9 -0
  356. package/types/helpers/deep.d.ts +33 -0
  357. package/types/helpers/format.d.ts +14 -0
  358. package/types/helpers/has-string-properties.d.ts +5 -0
  359. package/types/helpers/index.d.ts +13 -0
  360. package/types/helpers/object.d.ts +46 -0
  361. package/types/helpers/parse-date.d.ts +6 -0
  362. package/types/helpers/path.d.ts +13 -0
  363. package/types/helpers/regex.d.ts +7 -0
  364. package/types/helpers/stringify.d.ts +33 -0
  365. package/types/helpers/to-data.d.ts +13 -0
  366. package/types/helpers/truthy.d.ts +26 -0
  367. package/types/index.d.ts +6 -0
  368. package/types/schema-compiler.d.ts +49 -0
  369. package/types/schema-location.d.ts +64 -0
  370. package/types/schema-resolver.d.ts +145 -0
  371. package/types/schema.d.ts +586 -0
  372. package/types/traversal/executors/check-condition.d.ts +8 -0
  373. package/types/traversal/executors/check-input.d.ts +6 -0
  374. package/types/traversal/executors/check-required.d.ts +6 -0
  375. package/types/traversal/executors/check-schema.d.ts +7 -0
  376. package/types/traversal/executors/defaults.d.ts +8 -0
  377. package/types/traversal/executors/enter-existing.d.ts +6 -0
  378. package/types/traversal/executors/enter-input.d.ts +8 -0
  379. package/types/traversal/executors/enter.d.ts +7 -0
  380. package/types/traversal/executors/exit.d.ts +6 -0
  381. package/types/traversal/executors/finalize.d.ts +6 -0
  382. package/types/traversal/executors/index.d.ts +15 -0
  383. package/types/traversal/executors/normalize.d.ts +7 -0
  384. package/types/traversal/executors/prepare-existing.d.ts +6 -0
  385. package/types/traversal/executors/prepare-pending.d.ts +6 -0
  386. package/types/traversal/executors/resolve-union.d.ts +6 -0
  387. package/types/traversal/executors/serialize.d.ts +11 -0
  388. package/types/traversal/executors/transform-early.d.ts +6 -0
  389. package/types/traversal/executors/transform.d.ts +6 -0
  390. package/types/traversal/executors/traversal-state-executor.d.ts +19 -0
  391. package/types/traversal/executors/validate.d.ts +6 -0
  392. package/types/traversal/traversal-context.d.ts +67 -0
  393. package/types/traversal/traversal-state.d.ts +97 -0
  394. package/types/types.d.ts +218 -0
  395. package/types/value-processor/composed-value-processor.d.ts +17 -0
  396. package/types/value-processor/defined-value-processor.d.ts +16 -0
  397. package/types/value-processor/function-value-processor.d.ts +15 -0
  398. package/types/value-processor/parameterized-value-processor.d.ts +14 -0
  399. package/types/value-processor/parameters-value-processor.d.ts +28 -0
  400. package/types/value-processor/spec.d.ts +22 -0
  401. package/types/value-processor/value-processor.d.ts +92 -0
@@ -0,0 +1,1709 @@
1
+ import { toData } from './helpers/to-data.js';
2
+ import { SchemaLocation } from './schema-location.js';
3
+ import { TraversalContext } from './traversal/traversal-context.js';
4
+ import { PipelineExecutor } from "./executor/pipeline-executor.js";
5
+ import { ValueProcessor } from './value-processor/value-processor.js';
6
+ import { EMPTY } from './constants.js';
7
+ import {
8
+ PROCESS_EXECUTOR,
9
+ PRELOAD_EXECUTOR,
10
+ SERIALIZE_EXECUTOR,
11
+ VALIDATE_EXECUTOR
12
+ } from './traversal/executors/index.js';
13
+
14
+ import {
15
+ FinalizeError,
16
+ NormalizeError,
17
+ SchemaError,
18
+ SerializeError,
19
+ TransformError,
20
+ ValidationError
21
+ } from './errors.js';
22
+ import { deepEquals, deepPrune } from './helpers/deep.js';
23
+ import { isTruthy } from './helpers/truthy.js';
24
+ import { isPlainObject } from './helpers/object.js';
25
+ import { formatValue } from './helpers/format.js';
26
+
27
+
28
+ /** @import { TraversalContextOptions } from './traversal/traversal-context.js' */
29
+ /** @import { ISchema, ISchemaOptions, ISchemaMetadata, SchemaData } from './types.js' */
30
+
31
+ /** @typedef {ISchemaMetadata} CompiledSchemaMetadata */
32
+ /** @typedef {ISchemaOptions} CompiledSchemaOptions */
33
+
34
+ /**
35
+ * @typedef {object} CompiledSchemaHandlers
36
+ * @property {Array<ValueProcessor>} [normalizers]
37
+ * @property {Array<ValueProcessor>} [conditions]
38
+ * @property {Array<ValueProcessor>} [transformers]
39
+ * @property {Array<ValueProcessor>} [finalizers]
40
+ * @property {Array<ValueProcessor>} [validators]
41
+ * @property {Array<ValueProcessor>} [serializers]
42
+ * @property {Array<ValueProcessor>} [discriminators]
43
+ */
44
+
45
+ /** @typedef {{[key:string]:CompiledSchema}} CompiledSchemaProperties */
46
+ /** @typedef {{[key:string]:CompiledSchema}} CompiledSchemaUnionSchemas */
47
+
48
+ /**
49
+ * @typedef {object} SharedOptions
50
+ * @property {SchemaLocation} [location]
51
+ * @property {TraversalContext|TraversalContextOptions} [context]
52
+ */
53
+
54
+ /** @typedef {SharedOptions & {[key:string]: any}} ValidateOptions */
55
+ /** @typedef {SharedOptions & {[key:string]: any}} SerializeOptions */
56
+ /** @typedef {SharedOptions & {assignments?:Map<string,any>} & {[key:string]: any}} ProcessOptions */
57
+ /** @typedef {SharedOptions & {[key:string]: any}} ProcessAssignmentsOptions */
58
+
59
+ /**
60
+ * CompiledSchema - the resolved version of a schema usable for processing input values into output values
61
+ *
62
+ * The SchemaResolver compiler takes an input Schema and constructs a CompiledSchema:
63
+ * - The base schema hierarchy is resolved and flattened.
64
+ * - Handlers have their input specifications converted into value processor executors.
65
+ * - Unions may trigger property hoisting and discriminator synthesis.
66
+ * - Core options are converted to standardized forms.
67
+ * - Metadata is expanded by introspecting the resolved schema.
68
+ * - Errors are thrown if the input Schema is invalid, inconsistent, or missing required data.
69
+ *
70
+ * @augments {ISchema}
71
+ */
72
+ export class CompiledSchema
73
+ {
74
+ /** @internal */
75
+ static __TOKEN = Symbol('CONSTRUCT_USING_COMPILER')
76
+
77
+ /** @type {Map<string,CompiledSchema>} */
78
+ #propertiesMap = new Map();
79
+ /** @type {CompiledSchemaProperties|undefined} */
80
+ #properties;
81
+ /** @type {Map<string,CompiledSchema>} */
82
+ #unionSchemasMap = new Map();
83
+ /** @type {CompiledSchemaUnionSchemas|undefined} */
84
+ #unionSchemas;
85
+ /** @type {Map<string, ValueProcessor>} */
86
+ #valueProcessorMap = new Map();
87
+
88
+ /** @type {CompiledSchemaHandlers} */
89
+ #handlers = {};
90
+ /** @type {CompiledSchemaOptions} */
91
+ #options = {};
92
+ /** @type {CompiledSchemaMetadata} */
93
+ #metadata = {};
94
+ /** @type {boolean} */
95
+ #frozen = false;
96
+
97
+ /**
98
+ * CompiledSchema constructor - do not call directly (use SchemaResolver.compile())
99
+ *
100
+ * @param {symbol} token - magic to reduce shenanigans
101
+ */
102
+ constructor(token) {
103
+ if (token !== CompiledSchema.__TOKEN) {
104
+ throw new SchemaError('CompiledSchema must be created via compilation');
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Options contains information that changes schema parsing and processing.
110
+ *
111
+ * @type {CompiledSchemaOptions}
112
+ */
113
+ get options() {
114
+ return this.#options;
115
+ }
116
+
117
+ /**
118
+ * Metadata contains information for describing the schema behavior to users and hints for tools.
119
+ *
120
+ * @type {CompiledSchemaMetadata}
121
+ */
122
+ get metadata() {
123
+ return this.#metadata;
124
+ }
125
+
126
+ /**
127
+ * Properties are named child schemas, defining a hierarchical schema structure.
128
+ *
129
+ * This is an (inefficient) cache for compatibility with the ISchema "interface".
130
+ *
131
+ * @type {CompiledSchemaProperties}
132
+ */
133
+ get properties() {
134
+ return this.#properties ??=
135
+ Object.freeze(Object.fromEntries(this.#propertiesMap));
136
+ }
137
+
138
+ /**
139
+ *
140
+ * @type {IteratorObject<[string, CompiledSchema]>}
141
+ */
142
+ get propertyEntries() {
143
+ return this.#propertiesMap.entries();
144
+ }
145
+
146
+ /**
147
+ * Handlers are associated with asynchronous value processors.
148
+ *
149
+ * The "friendly" handler definitions from the source Schema are each compiled into asynchronous functions
150
+ * that run as a pipeline.
151
+ *
152
+ * All handlers have the same async signature, receiving:
153
+ * 1. a value to be processed by the current schema
154
+ * 2. a reference to the top-level aggregate target being built or processed by the entire schema hierarchy
155
+ * 3. a location defining the current schema and the traversal path to where it was encountered
156
+ * 5. any (unmanaged / developer defined) options passed to whatever invoked the handler processing
157
+ *
158
+ * The compiled handlers may vary in their return types and exception handling behavior.
159
+ *
160
+ * @type {CompiledSchemaHandlers}
161
+ */
162
+ get handlers() {
163
+ return this.#handlers;
164
+ }
165
+
166
+ /**
167
+ * Get a value processor by handler name
168
+ *
169
+ * Handler definitions are compiled into a single value processor pipeline per handler name.
170
+ *
171
+ * @param {string} handlerName
172
+ * @returns {ValueProcessor|undefined}
173
+ * @internal
174
+ */
175
+ getValueProcessor(handlerName) {
176
+ return this.#valueProcessorMap.get(handlerName);
177
+ }
178
+
179
+ /**
180
+ *
181
+ * @param {string} handlerName
182
+ * @param {ValueProcessor} valueProcessor
183
+ * @returns {ValueProcessor}
184
+ * @internal
185
+ */
186
+ _setValueProcessor(handlerName, valueProcessor) {
187
+ this.#valueProcessorMap.set(handlerName, valueProcessor);
188
+ return valueProcessor;
189
+ }
190
+
191
+ /**
192
+ * If this schema defines a union, return the schema member elements of the union.
193
+ *
194
+ * Unions use a discriminator handler to attempt to resolve to one of the unionSchema member elements
195
+ * based on either the value being processed or the overall aggregated data. The key of the
196
+ * unionSchema in this collection is sometimes used as part of this process.
197
+ *
198
+ * Once the discriminator succeeds, the active schema switches to the resolved unionSchema.
199
+ *
200
+ * @type {CompiledSchemaUnionSchemas}
201
+ */
202
+ get unionSchemas() {
203
+ return this.#unionSchemas ??=
204
+ Object.freeze(Object.fromEntries(this.#unionSchemasMap));
205
+ }
206
+
207
+ /**
208
+ *
209
+ * @type {IteratorObject<[string, CompiledSchema]>}
210
+ */
211
+ get unionSchemaEntries() {
212
+ return this.#unionSchemasMap.entries();
213
+ }
214
+
215
+ /**
216
+ * Extract this schema as a raw data object usable for cloning this schema.
217
+ *
218
+ * Note that the data may include handler functions, so it cannot be assumed to be serializable to JSON!
219
+ *
220
+ * @returns {SchemaData|undefined}
221
+ */
222
+ toData() {
223
+ return toData(this);
224
+ }
225
+
226
+ /**
227
+ * Return true if this schema has any child schemas.
228
+ *
229
+ * @type {boolean}
230
+ */
231
+ get hasChildren() {
232
+ return this.#propertiesMap.size > 0;
233
+ }
234
+
235
+ /**
236
+ * Return true if this schema should be treated as a container.
237
+ *
238
+ * Any schema that has children is a container, but also those explicitly marked as a container.
239
+ *
240
+ * @returns {boolean}
241
+ */
242
+ get isContainer() {
243
+ return this.hasChildren || Boolean(this.options.container);
244
+ }
245
+
246
+ /**
247
+ * Return true if this schema supports wildcard properties.
248
+ *
249
+ * @type {boolean}
250
+ */
251
+ get hasWildcard() {
252
+ return this.#propertiesMap.has('*');
253
+ }
254
+
255
+ /**
256
+ * Return true if this schema defines an array.
257
+ *
258
+ * Arrays sometimes need special treatment; the built-in 'array' base schema sets this option.
259
+ *
260
+ * @type {boolean}
261
+ */
262
+ get isArray() {
263
+ return this.options.type === 'array';
264
+ }
265
+
266
+ /**
267
+ * Return true if this schema defines a function value.
268
+ *
269
+ * Functions passed to most operations are interpreted as dynamic values (called to retrieve actual value).
270
+ * This setting overrides that behavior, and forces a passed function to be treated as a simple value.
271
+ *
272
+ * @type {boolean}
273
+ */
274
+ get isFunction() {
275
+ return this.options.type === 'function'
276
+ }
277
+
278
+
279
+ /**
280
+ * Return true if this schema defines a union.
281
+ * Unions adopt the behavior of one of their unionSchema member elements based on a discriminator handler function.
282
+ *
283
+ * @type {boolean}
284
+ */
285
+ get isUnion() {
286
+ return this.#unionSchemasMap.size > 0
287
+ }
288
+
289
+ /**
290
+ * Return true if this schema is used to select union keys.
291
+ *
292
+ * (This doesn't guarantee that there is a matching discriminator on the parent that uses it!)
293
+ * todo - convert this option into a generated normalizer that enforces union key values at runtime?
294
+ *
295
+ * @type {boolean}
296
+ */
297
+ get isUnionKey() {
298
+ return !!this.options.unionKey;
299
+ }
300
+
301
+ /**
302
+ * Return true if the schema acts as a selector.
303
+ *
304
+ * Selectors control the activation or deactivation of peer selection schemas using a conditional handler
305
+ * synthesized during compilation.
306
+ *
307
+ * @type {boolean}
308
+ */
309
+ get isSelector() {
310
+ return !!this.options.selector;
311
+ }
312
+
313
+ /**
314
+ * Return true if this schema contains a selector as a child.
315
+ * @type {boolean}
316
+ */
317
+ get hasChildSelector() {
318
+ for (const propertySchema of this.#propertiesMap.values()) {
319
+ if (propertySchema.isSelector) {
320
+ return true;
321
+ }
322
+ }
323
+ return false;
324
+ }
325
+
326
+ /**
327
+ * Return true if this schema is a selection conditionally activated by a peer selector.
328
+ *
329
+ * @type {boolean}
330
+ */
331
+ get isSelection() {
332
+ return this.options.selection !== undefined && this.options.selection !== false; // todo - use a symbol to trigger name rather than "true"...
333
+ }
334
+
335
+ /**
336
+ * Return true if this schema contains a selection as a child.
337
+ * @type {boolean}
338
+ */
339
+ get hasChildSelection() {
340
+ for (const propertySchema of this.#propertiesMap.values()) {
341
+ if (propertySchema.isSelection) {
342
+ return true;
343
+ }
344
+ }
345
+ return false;
346
+ }
347
+
348
+ /**
349
+ * Get the selector value that triggers this selection. The default value for a selection is its own property name.
350
+ *
351
+ * @type {string|boolean|undefined}
352
+ */
353
+ get selection() {
354
+ return this.options.selection;
355
+ }
356
+
357
+ /**
358
+ * Return the legal (normalized) values this schema accepts, if defined.
359
+ *
360
+ * (This acts as an "upstream" check before calling any transform handlers.)
361
+ *
362
+ * @type {Array<NonNullable<any>>|undefined}
363
+ */
364
+ get values() {
365
+ return this.options.values;
366
+ }
367
+
368
+ /**
369
+ * Returns true if this schema defines any values it accepts.
370
+ *
371
+ * @type {boolean}
372
+ */
373
+ get hasValues() {
374
+ const v = this.options.values;
375
+ return Array.isArray(v) && v.length > 0;
376
+ }
377
+
378
+ /**
379
+ * Returns whether this schema enforces strict/lax checking.
380
+ *
381
+ * - returns true if the schema uses strict checking
382
+ * - returns false if the schema uses lax checking
383
+ * - if undefined, it depends on the traversal context and the behavior of individual processors
384
+ *
385
+ * This setting is useful for preventing validation errors when transforms return new objects
386
+ * that contain extra properties that don't match the schema.
387
+ *
388
+ * In most contexts (e.g. assignment processing and validation) strict processing is the default.
389
+ * Setting lax mode changes several behaviors:
390
+ * - If a union is marked lax, it is not an error to fail to resolve the union
391
+ * - If a leaf schema is marked lax, it is not an error if a value fails to assign to it
392
+ *
393
+ * Lax mode does *not* mean that errors during normalization
394
+ *
395
+ * @type {boolean|undefined}
396
+ */
397
+ get strict() {
398
+ return this.options.strict;
399
+ }
400
+
401
+ /**
402
+ * Returns true if the value defined by this schema is required to exist in the output to be valid.
403
+ *
404
+ * Under the normal pathways, this is a shallow requirement:
405
+ * - Each schema checks its own input; if the required flag is set, the input must not be undefined.
406
+ * - If the input value is defined and the schema defines child properties, the input will be traversed
407
+ * and recursively checked against the child property schemas.
408
+ * - If the input value is undefined, child property schemas are NOT checked.
409
+ *
410
+ * If the "deep" flag is set on the schema, this behavior changes:
411
+ * - If the input value is undefined on a schema with "deep" set, child properties ARE checked.
412
+ *
413
+ * If a schema sets a default value, it will generally satisfy the required setting (unless set to a
414
+ * value function that returns undefined!)
415
+ *
416
+ * @type {boolean}
417
+ */
418
+ get required() {
419
+ return this.options.required ?? false;
420
+ }
421
+
422
+ /**
423
+ * Returns the default value this schema provides if the input is undefined.
424
+ *
425
+ * This is normally interpreted as a "shallow" default;
426
+ * - If the schema being actively processed has a default value and the input value is undefined, the default is used.
427
+ * - If the schema being actively processed defines child properties, and is passed an input value that does not
428
+ * contain a value for a specific child that defines a default, the child's default will be used.
429
+ * - If the schema being actively processed defines child properties, but is passed an undefined input value,
430
+ * the child property schemas will not be traversed, and thus any child defaults will not take effect.
431
+ *
432
+ * This last behavior changes if the "deep" schema option is set:
433
+ * - If the schema being actively processed defines child properties is passed an undefined input value when
434
+ * the "deep" option is enabled, child property schemas will be traversed and any defaults will be used.
435
+ *
436
+ * The default may also be set to a value function:
437
+ * `async (value: any, target: any, location:SchemaLocation, options: Object): any`
438
+ * that will be called during the normalization phase. This can be useful for late binding, remote lookups,
439
+ * side effects, or lazy evaluation of expensive values.
440
+ *
441
+ * @type {any|undefined}
442
+ */
443
+ get default() {
444
+ return this.options.default;
445
+ }
446
+
447
+ /**
448
+ * Returns whether this schema should be deeply traversed even when the input is empty.
449
+ *
450
+ * This is useful for triggering required/defaults settings. (Note that a schema with
451
+ * child properties that is marked required is deep by default, because that's almost
452
+ * always what is wanted/expected.)
453
+ *
454
+ * Returns undefined if unset, signaling that the traversal context will decide.
455
+ *
456
+ * @type {boolean|undefined}
457
+ */
458
+ get deep() {
459
+ if (this.options.deep !== undefined) {
460
+ return this.options.deep;
461
+ }
462
+ if (this.hasChildren && this.required) {
463
+ return true;
464
+ }
465
+ return undefined;
466
+ }
467
+
468
+ /**
469
+ * Return true if the schema always returns an inherited or referenced value.
470
+ *
471
+ * Referenced properties never accept a direct assignment, and will always return the value
472
+ * corresponding to the first matching property name found higher in the schema.
473
+ *
474
+ * @type {boolean}
475
+ */
476
+ get isReference() {
477
+ return this.options.reference ?? false;
478
+ }
479
+
480
+ /**
481
+ * Returns true if this schema defines a value that can be assumed to always exist and be valid.
482
+ *
483
+ * The implicit setting implies that values passed to this schema should not be visited or validated.
484
+ *
485
+ * @type {boolean}
486
+ */
487
+ get isImplicit() {
488
+ return this.options.implicit ?? false;
489
+ }
490
+
491
+ /**
492
+ * Returns true if the container allows incremental assignment to children.
493
+ * Deprecated - use "opaque" as a more accurate signal of intent.
494
+ *
495
+ * @type {boolean}
496
+ * @deprecated
497
+ */
498
+ get allowIncremental() {
499
+ return this.options.allowIncremental ?? this.hasChildren;
500
+ }
501
+
502
+ /**
503
+ * Returns true if the schema defines a value whose internals are hidden after transformation.
504
+ *
505
+ * Opaque schemas will typically need a custom validator that understands the post-transform contract.
506
+ *
507
+ * Transformation of opaque schemas is delayed until all known child assignments have been staged into a
508
+ * pending container. Once an opaque schema is transformed, it no longer accepts assignments. (This
509
+ * may cause sequencing issues with late-resolved conditional assignments!)
510
+ *
511
+ * @type {boolean}
512
+ */
513
+ get isOpaque() {
514
+ return !this.hasChildren || this.options.allowIncremental === false;
515
+ }
516
+
517
+ /**
518
+ * Check if the provided value (and/or current output target) passes the schema conditional check.
519
+ *
520
+ * Schemas that don't have a condition handler defined will always succeed.
521
+ *
522
+ * Failed conditions will be repeatedly re-checked during assignment processing until the final pass.
523
+ * Errors encountered while checking conditions are caught and simply result in a failed condition.
524
+ *
525
+ * (This is an executor function that may return synchronous or asynchronous results.)
526
+ *
527
+ * @param {any} value
528
+ * @param {any} [target]
529
+ * @param {SchemaLocation} [location]
530
+ * @param {object} [options]
531
+ * @returns {boolean|Promise<boolean>}
532
+ * @internal
533
+ */
534
+ _checkCondition(value, target, location = new SchemaLocation(this), options) {
535
+ if (location.schema !== this) {
536
+ return location.schema._checkCondition(value, target, location, options);
537
+ }
538
+ if (value instanceof Error && !this.options.allowErrors) {
539
+ return false;
540
+ }
541
+
542
+ const conditional = this.getValueProcessor('conditions');
543
+
544
+ if (!conditional) {
545
+ return true;
546
+ }
547
+ try {
548
+ const result = conditional.execute(value, target, location, options);
549
+ if (result instanceof Promise) {
550
+ return result.then(
551
+ resolved => (resolved instanceof Error && !this.options.allowErrors)? false : isTruthy(resolved),
552
+ _ => false
553
+ );
554
+ }
555
+ if (result instanceof Error && !this.options.allowErrors) {
556
+ return false;
557
+ }
558
+ return isTruthy(result);
559
+ }
560
+ catch (error) {
561
+ return false;
562
+ }
563
+ }
564
+ /**
565
+ * Check if the provided value (and/or current output target) passes the schema conditional check.
566
+ *
567
+ * Failed conditions will be repeatedly re-checked during assignment processing until the final pass.
568
+ * Errors encountered while checking conditions are caught and simply result in a failed condition.
569
+ *
570
+ * (This an async wrapper around the internal `_checkCondition` executor function.)
571
+ *
572
+ * @param {any} value
573
+ * @param {any} [target]
574
+ * @param {SchemaLocation} [location]
575
+ * @param {object} [options]
576
+ * @returns {Promise<boolean>}
577
+ * @internal
578
+ */
579
+ async checkCondition(value, target, location = new SchemaLocation(this), options) {
580
+ return this._checkCondition(value, target, location, options);
581
+ }
582
+
583
+ /**
584
+ * Return true if this schema is conditional
585
+ *
586
+ * @returns {boolean}
587
+ */
588
+ get hasConditions() {
589
+ const conditions = this.handlers.conditions;
590
+ return Boolean(conditions && Array.isArray(conditions) && conditions.length);
591
+ }
592
+
593
+ /**
594
+ * Return true if this schema requires finalization
595
+ *
596
+ * @returns {boolean}
597
+ */
598
+ get requiresFinalization() {
599
+ const finalizers = this.handlers.finalizers;
600
+ return Boolean(finalizers && Array.isArray(finalizers) && finalizers.length);
601
+ }
602
+
603
+ _checkValue(value, ErrorClass) {
604
+ if (value instanceof Error && !this.options.allowErrors) {
605
+ if (ErrorClass && value instanceof ErrorClass) {
606
+ throw value;
607
+ }
608
+ else {
609
+ const err = new ErrorClass(value.message, {cause: value});
610
+ Error?.captureStackTrace(err, this._checkValue);
611
+ throw err;
612
+ }
613
+
614
+ }
615
+ return value;
616
+ }
617
+
618
+ /**
619
+ * Use the registered discriminator to return a matching union schema, or undefined if the union cannot be resolved.
620
+ * Discriminator functions must return either one of the unionSchema members, a unionSchema key, or undefined.
621
+ *
622
+ * (This is an executor function that may return synchronous or asynchronous results.)
623
+ *
624
+ * @param {any} value
625
+ * @param {any} [target]
626
+ * @param {SchemaLocation} [location]
627
+ * @param {object} [options]
628
+ * @returns {CompiledSchema|undefined|Promise<CompiledSchema|undefined>}
629
+ * @internal
630
+ */
631
+ _discriminateUnion(value, target, location = new SchemaLocation(this), options = {}) {
632
+ if (location.schema !== this) {
633
+ return location.schema._discriminateUnion(value, target, location, options);
634
+ }
635
+ const discriminator = this.getValueProcessor('discriminators');
636
+
637
+ if (!this.isUnion || !discriminator) {
638
+ return undefined;
639
+ }
640
+
641
+ if (value instanceof Error && !this.options.allowErrors) {
642
+ if (options?.strict) {
643
+ throw value;
644
+ }
645
+ return undefined;
646
+ }
647
+
648
+
649
+ let result;
650
+ try {
651
+ result = discriminator.execute(value, target, location, options);
652
+ }
653
+ catch (error) {
654
+ if (options?.strict) {
655
+ throw error;
656
+ }
657
+ return undefined;
658
+ }
659
+ if (result instanceof Promise) {
660
+ return result.then(
661
+ resolved => this.getUnionSchema(resolved),
662
+ rejected => {
663
+ if (options.strict) { throw rejected; }
664
+ return undefined;
665
+ });
666
+ }
667
+ return this.getUnionSchema(result);
668
+ }
669
+ /**
670
+ * Use the registered discriminator to return a matching union schema, or undefined if the union cannot be resolved.
671
+ * Discriminator functions must return either one of the unionSchema members, a unionSchema key, or undefined.
672
+ *
673
+ * (This an async wrapper around the internal `_discriminateUnion` executor function.)
674
+ *
675
+ * @param {any} value
676
+ * @param {any} [target]
677
+ * @param {SchemaLocation} [location]
678
+ * @param {object} [options]
679
+ * @returns {Promise<CompiledSchema|undefined>}
680
+ * @internal
681
+ */
682
+ async discriminateUnion(value, target, location = new SchemaLocation(this), options) {
683
+ return this._discriminateUnion(value, target, location, options);
684
+ }
685
+
686
+ /**
687
+ * Ensure the input is of an expected shape that can be handled by this schema.
688
+ *
689
+ * Runs all normalizer value processors in a pipeline until completion or an error is thrown.
690
+ * As external data may originate in the form of strings or JSON structures, the main task of
691
+ * a normalizer is to "canonicalize" these inputs:
692
+ * - The normalized output should be accepted by the transformer handler.
693
+ * - Normalizers should usually pass through valid transformed values unchanged.
694
+ * - By contract, when passed "true", a container schema should construct an "empty"
695
+ * container (e.g. {} or []). (Even a schema that defines transformation to a
696
+ * complex class should have a normalized empty container format to use for construction).
697
+ *
698
+ * The normalize process will throw an exception if the input is incompatible.
699
+ *
700
+ * Unlike other handlers, normalizers should generally not depend on the overall
701
+ * target state, as they are sometimes invoked in isolation (even during compilation!)
702
+ * and thus shouldn't assume the "undefined means retry later" behavior of other handlers.
703
+ *
704
+ * Also note that normalizeValue does not recursively examine child properties.
705
+ *
706
+ * (This is an executor function that may return synchronous or asynchronous results.)
707
+ *
708
+ * @param {any} value - value to normalize
709
+ * @param {any} [target] - top level output target being built (avoid using for normalizers!)
710
+ * @param {SchemaLocation} [location] - path to this value in the output target
711
+ * @param {object} [options] - optional tweaks to normalizer behavior
712
+ * @returns {any|Promise<any>}
713
+ * @internal
714
+ */
715
+
716
+ _normalizeValue(value, target, location = new SchemaLocation(this), options = {}) {
717
+ if (location.schema !== this) {
718
+ return location.schema._normalizeValue(value, target, location, options);
719
+ }
720
+ if (value instanceof Error && !this.options.allowErrors) {
721
+ throw new NormalizeError(value.message, {cause: value});
722
+ }
723
+ const sync = options?.sync ?? false;
724
+ const dynamic = options?.dynamic ?? this.options?.dynamic ?? true; // todo - make dynamic default to false?
725
+
726
+ if ((typeof value === 'function' || value instanceof ValueProcessor) && dynamic && !options.compiling) {
727
+ const result = (value instanceof ValueProcessor)?
728
+ value.execute(true, target, location, options) : value(true, target, location, options);
729
+
730
+ if (result instanceof Promise) {
731
+ if (sync) {
732
+ throw new NormalizeError('Encountered an async value function during a forced-sync normalization', {location});
733
+ }
734
+ return result.then(resolved => {
735
+ if (resolved === undefined || resolved === null) {
736
+ return resolved; // a dynamic function must not fall back to defaults
737
+ }
738
+ // recurse so that we pick up the normal flow.
739
+ return this._normalizeValue(resolved, target, location, {...options, dynamic: false});
740
+ })
741
+ }
742
+ value = result; // fall through...
743
+ }
744
+
745
+ if (value === null || (value === undefined && !this.options.allowUndefined)) {
746
+ return value;
747
+ }
748
+
749
+ const normalizer = this.getValueProcessor('normalizers');
750
+ if (normalizer === undefined) {
751
+ // If we are a container type without a normalizer, create a default container if passed EMPTY
752
+ // todo - consider making this a compilation error! all container schemas need a normalizer of some sort!
753
+ if (this.isContainer && value === EMPTY) {
754
+ return this.isArray? [] : {}
755
+ }
756
+ return value;
757
+ }
758
+ let result;
759
+ try {
760
+ result = normalizer.execute(value, target, location, options);
761
+ }
762
+ catch (error) {
763
+ throw new NormalizeError('Unable to normalize', {location, cause: error});
764
+ }
765
+
766
+ if (result instanceof Promise) {
767
+ if (sync) {
768
+ throw new NormalizeError('Encountered an async processor during a forced-sync normalization', {location});
769
+ }
770
+ return result.then(
771
+ resolved => this._checkValue(resolved, NormalizeError),
772
+ rejected => { throw new NormalizeError('Unable to normalize', {value, location, cause: rejected})}
773
+ );
774
+ }
775
+ return this._checkValue(result);
776
+ }
777
+
778
+ /**
779
+ * Ensure the input is of an expected shape that can be handled by this schema.
780
+ *
781
+ * Runs all normalizer value processors in a pipeline until completion or an error is thrown.
782
+ * As many external input values will originate in the form of strings or JSON
783
+ * structures, the main task of a normalizer is to "canonicalize" these inputs:
784
+ * - The normalized output should be accepted by the transformer handler.
785
+ * - Normalizers should usually pass through valid transformed values unchanged.
786
+ * - By contract, when passed "true", a container schema should construct an "empty"
787
+ * container (e.g. {} or []). (Even a schema that defines transformation to a
788
+ * complex class should have a normalized empty container format to use for construction).
789
+ *
790
+ * The normalize process will throw an exception if the input is incompatible.
791
+ *
792
+ * Unlike other handlers, normalizers should generally not depend on the overall
793
+ * target state, as they are sometimes invoked in isolation (even during compilation!)
794
+ * and thus shouldn't assume the "undefined means retry later" behavior of other handlers.
795
+ *
796
+ * Also note that normalizeValue does not recursively examine child properties.
797
+ *
798
+ * (This an async wrapper around the internal `_normalizeValue` executor function.)
799
+ *
800
+ * @param {any} value - value to normalize
801
+ * @param {any} [target] - top level output target being built (avoid using for normalizers!)
802
+ * @param {SchemaLocation} [location] - path to this value in the output target
803
+ * @param {object} [options] - optional tweaks to normalizer behavior
804
+ * @returns {Promise<any>}
805
+ * @internal
806
+ */
807
+
808
+ async normalizeValue(value, target, location = new SchemaLocation(this), options) {
809
+ return this._normalizeValue(value, target, location, options)
810
+ }
811
+
812
+ /**
813
+ * Transform a normalized input value for the final target based on this schema and provided context.
814
+ *
815
+ * Runs all transformer value processors in a pipeline until one returns undefined or throws an error.
816
+ * - The input to the pipeline is assumed to be normalized.
817
+ * - An error may be thrown if the input cannot be transformed.
818
+ * - If a transformer depends upon the overall target, it may return undefined to signal
819
+ * that the transform should be retried when the target is updated.
820
+ *
821
+ * A schema's transform is allowed to enforce validation internally, or it can delegate this to
822
+ * a finalizer or validator. In any case, the output from the transform is not guaranteed to be valid.
823
+ * (Note that conditions and unions are not checked if you call this directly.)
824
+ *
825
+ * Child properties are not traversed in this call, and are presumed to already have been transformed.
826
+ *
827
+ * (This is an executor function that may return synchronous or asynchronous results.)
828
+ *
829
+ * @param {any} value - input value to transform
830
+ * @param {any} [target] - global target in case the transformer depends on it
831
+ * @param {SchemaLocation} [location] - the traversal location of the schema
832
+ * @param {object} [options] - any tweaks to the transformer behavior
833
+ * @returns {any|Promise<any>} - transformed value
834
+ * @internal
835
+ */
836
+ _transformValue(value, target, location = new SchemaLocation(this), options = {}) {
837
+ if (location.schema !== this) {
838
+ return location.schema._transformValue(value, target, location, options);
839
+ }
840
+ if (value instanceof Error && !this.options.allowErrors) {
841
+ throw new TransformError(value.message, {cause: value});
842
+ }
843
+
844
+ if (this.isImplicit) {
845
+ throw new TransformError('Cannot transform a value for an implicit schema', {location});
846
+ }
847
+ if (value === null || (value === undefined && !this.options.allowUndefined)) {
848
+ return value;
849
+ }
850
+ let result;
851
+ try {
852
+ this.ensureAccepts(value);
853
+ const transformer = this.getValueProcessor('transformers');
854
+ if (transformer === undefined) {
855
+ return value;
856
+ }
857
+ result = transformer.execute(value, target, location, options);
858
+ }
859
+ catch (error) {
860
+ throw new TransformError('Unable to transform', {value, location, cause: error});
861
+ }
862
+ if (result instanceof Promise) {
863
+ return result.then(
864
+ resolved => this._checkValue(resolved, TransformError),
865
+ rejected => { throw new TransformError('Unable to transform', {value, location, cause: rejected}) }
866
+ );
867
+ }
868
+ return this._checkValue(result, TransformError);
869
+ }
870
+ /**
871
+ * Transform a normalized input value for the final target based on this schema and provided context.
872
+ *
873
+ * Runs all transformer value processors in a pipeline until one returns undefined or throws an error.
874
+ * - The input to the pipeline is assumed to be normalized.
875
+ * - An error may be thrown if the input cannot be transformed.
876
+ * - If a transformer depends upon the overall target, it may return undefined to signal
877
+ * that the transform should be retried when the target is updated.
878
+ *
879
+ * A schema's transform is allowed to enforce validation internally, or it can delegate this to
880
+ * a finalizer or validator. In any case, the output from the transform is not guaranteed to be valid.
881
+ * (Note that conditions and unions are not checked if you call this directly.)
882
+ *
883
+ * Child properties are not traversed in this call, and are presumed to already have been transformed.
884
+ *
885
+ * (This an async wrapper around the internal `_transformValue` executor function.)
886
+ *
887
+ * @param {any} value - input value to transform
888
+ * @param {any} [target] - global target in case the transformer depends on it
889
+ * @param {SchemaLocation} [location] - the traversal location of the schema
890
+ * @param {object} [options] - any tweaks to the transformer behavior
891
+ * @returns {Promise<any>} - transformed value
892
+ * @internal
893
+ */
894
+ async transformValue(value, target, location = new SchemaLocation(this), options) {
895
+ return this._transformValue(value, target, location, options);
896
+ }
897
+
898
+ /**
899
+ * Finalize a transformed input value by running any necessary post-processing steps.
900
+ *
901
+ * Runs all finalizer value processors in a pipeline until one returns undefined or throws an error.
902
+ * - The input to the pipeline is be assumed to be transformed.
903
+ * - Finalizers are generally only required for incremental schemas that need to check child values.
904
+ * - A finalizer on the root schema (or that checks for the root path, if the root schema is shared)
905
+ * can act as an "entire output" finalizer.
906
+ *
907
+ * (This is an executor function that may return synchronous or asynchronous results.)
908
+ *
909
+ * @param {any} value - input value to finalize
910
+ * @param {any} [target] - global target in case the finalizer depends on it
911
+ * @param {SchemaLocation} [location] - the traversal location of the schema
912
+ * @param {object} [options] - any tweaks to the finalizer behavior
913
+ * @returns {any|Promise<any>} - finalized value
914
+ * @internal
915
+ */
916
+ _finalizeValue(value, target, location = new SchemaLocation(this), options = {}) {
917
+ if (location.schema !== this) {
918
+ return location.schema._finalizeValue(value, target, location, options);
919
+ }
920
+ if (value instanceof Error && !this.options.allowErrors) {
921
+ throw new FinalizeError(value.message, {cause: value});
922
+ }
923
+
924
+ if (this.isImplicit) {
925
+ throw new TransformError('Cannot finalize a value for an implicit schema', {location});
926
+ }
927
+ if (value === null || (value === undefined && !this.options.allowUndefined)) {
928
+ return value;
929
+ }
930
+ let result;
931
+ try {
932
+ const finalizer = this.getValueProcessor('finalizers');
933
+ if (finalizer === undefined) {
934
+ return value;
935
+ }
936
+ result = finalizer.execute(value, target, location, options);
937
+ }
938
+ catch (error) {
939
+ throw new FinalizeError('Unable to finalize', {value, location, cause: error});
940
+ }
941
+ if (result instanceof Promise) {
942
+ return result.then(
943
+ resolved => this._checkValue(resolved, FinalizeError),
944
+ rejected => { throw new FinalizeError('Unable to finalize', {value, location, cause: rejected}) }
945
+ );
946
+ }
947
+ return this._checkValue(result, FinalizeError);
948
+ }
949
+ /**
950
+ * Finalize a transformed input value by running any necessary post-processing steps.
951
+ *
952
+ * Runs all finalizer value processors in a pipeline until one returns undefined or throws an error.
953
+ * - The input to the pipeline is be assumed to be transformed.
954
+ * - Finalizers are generally only required for incremental schemas that need to check child values.
955
+ * - A finalizer on the root schema (or that checks for the root path, if the root schema is shared)
956
+ * can act as an "entire output" finalizer.
957
+ *
958
+ * (This an async wrapper around the internal `_finalizeValue` executor function.)
959
+ *
960
+ * @param {any} value - input value to transform
961
+ * @param {any} [target] - global target in case the transformer depends on it
962
+ * @param {SchemaLocation} [location] - the traversal location of the schema
963
+ * @param {object} [options] - any tweaks to the transformer behavior
964
+ * @returns {Promise<any>} - transformed value
965
+ * @internal
966
+ */
967
+ async finalizeValue(value, target, location = new SchemaLocation(this), options) {
968
+ return this._finalizeValue(value, target, location, options);
969
+ }
970
+
971
+ /**
972
+ * Validate the provided input value.
973
+ *
974
+ * Runs all validator value processors in a pipeline until one throws an error.
975
+ * Validators can return a different value from the input (presumably "more valid",
976
+ * e.g. strings trimmed, case made consistent, etc.) but must throw if the input is invalid.
977
+ *
978
+ * Children are not traversed in this call.
979
+ *
980
+ * (This is an executor function that may return synchronous or asynchronous results.)
981
+ *
982
+ * @param {any} value - value to validate
983
+ * @param {any} [target] - complete output target
984
+ * @param {SchemaLocation} [location] - traversal location of this schema
985
+ * @param {object} [options] - options to tweak validation behavior
986
+ * @returns {any|Promise<any>}
987
+ * @internal
988
+ */
989
+ _validateValue(value, target, location = new SchemaLocation(this), options = {}) {
990
+ if (location.schema !== this) {
991
+ return location.schema._validateValue(value, target, location, options);
992
+ }
993
+ if (this.required === true && value === undefined) {
994
+ throw new ValidationError('Missing required value', {location});
995
+ }
996
+ if (value === null || value === undefined) {
997
+ return value;
998
+ }
999
+ if (value instanceof Error && !this.options.allowErrors) {
1000
+ throw new ValidationError(value.message, {cause: value});
1001
+ }
1002
+
1003
+ const validator = this.getValueProcessor('validators');
1004
+
1005
+ if (!validator) {
1006
+ return value;
1007
+ }
1008
+ const revalidate = options?.revalidate ?? location.schema.options?.revalidate ?? true;
1009
+
1010
+ let result;
1011
+ try {
1012
+ result = validator.execute(value, target, location, options);
1013
+ }
1014
+ catch (error) {
1015
+ if (error instanceof ValidationError) {
1016
+ throw error;
1017
+ }
1018
+ // for debugging: result = validator.execute(value, target, location, options);
1019
+ throw new ValidationError('Validation failed', {value, location, cause: error});
1020
+ }
1021
+ if (result instanceof Promise) {
1022
+ return result.then(
1023
+ resolved => {
1024
+ if (resolved !== value && resolved !== undefined && revalidate) {
1025
+ return this._validateValue(resolved, target, location, {...options, revalidate: false})
1026
+ }
1027
+ return (resolved === undefined)? value : this._checkValue(resolved, ValidationError);
1028
+ }, // validation cannot clear data
1029
+ rejected => {
1030
+ if (rejected instanceof ValidationError) {
1031
+ throw rejected;
1032
+ }
1033
+ throw new ValidationError('Validation failed', {value, location, cause: rejected});
1034
+ }
1035
+ );
1036
+ }
1037
+ if (result !== value && result !== undefined && revalidate) {
1038
+ return this._validateValue(result, target, location, {...options, revalidate: false})
1039
+ }
1040
+ return (result === undefined)? value : this._checkValue(result, ValidationError);
1041
+
1042
+ }
1043
+ /**
1044
+ * Validate the provided input value.
1045
+ *
1046
+ * Runs all validator value processors in a pipeline until one throws an error.
1047
+ * Validators can return a different value from the input (presumably "more valid",
1048
+ * e.g. strings trimmed, case made consistent, etc.) but must throw if the input is invalid.
1049
+ *
1050
+ * Children are not traversed in this call.
1051
+ *
1052
+ * @param {any} value - value to validate
1053
+ * @param {any} [target] - complete output target
1054
+ * @param {SchemaLocation} [location] - traversal location of this schema
1055
+ * @param {object} [options] - options to tweak validation behavior
1056
+ * @returns {Promise<any>}
1057
+ * @internal
1058
+ */
1059
+ async validateValue(value, target, location, options) {
1060
+ return this._validateValue(value, target, location, options);
1061
+ }
1062
+
1063
+
1064
+ /**
1065
+ * Serialize the provided input value.
1066
+ *
1067
+ * (This is an executor function that may return synchronous or asynchronous results.)
1068
+ *
1069
+ * @param {any} value - value to serialize
1070
+ * @param {any} [target] - entire output being serialized
1071
+ * @param {SchemaLocation} [location] - traversal location to current schema
1072
+ * @param {object} [options] - options to tweak serialization behavior
1073
+ * @returns {any|Promise<any>}
1074
+ * @internal
1075
+ */
1076
+ _serializeValue(value, target, location = new SchemaLocation(this), options = {}) {
1077
+ if (location.schema !== this) {
1078
+ return location.schema._serializeValue(value, target, location, options);
1079
+ }
1080
+
1081
+ if (value === undefined || value === null || this.isImplicit || isTruthy(this.metadata.omitFromSerialize)) {
1082
+ return null;
1083
+ }
1084
+ const serializer = this.getValueProcessor('serializers') ?? this.getValueProcessor('normalizers');
1085
+
1086
+ if (!serializer) {
1087
+ return value;
1088
+ /* todo - remove, or test thoroughly before reactivating, potential impact on children feels sus...
1089
+ if (this.isOpaque) {
1090
+ try {
1091
+ // perhaps they implemented JSON.stringify?
1092
+ return parse(stringify(value))
1093
+ }
1094
+ catch (error) {
1095
+ return value;
1096
+ }
1097
+ }
1098
+ else if (this.isContainer) {
1099
+ if (this.isArray && Array.isArray(value)) {
1100
+ return [...value];
1101
+ }
1102
+ else if (isPlainObject(value)) {
1103
+ return {...value};
1104
+ }
1105
+ }
1106
+ return value;
1107
+
1108
+ */
1109
+ }
1110
+
1111
+ let result;
1112
+ try {
1113
+ result = serializer.execute(value, target, location, options);
1114
+ }
1115
+ catch (error) {
1116
+ if (options?.strict) {
1117
+ throw new SerializeError('Unable to serialize', {value, location, cause: error});
1118
+ }
1119
+ return undefined;
1120
+ }
1121
+ if (result instanceof Promise) {
1122
+ return result.then(
1123
+ resolved => options?.strict? this._checkValue(resolved, SerializeError) : resolved,
1124
+ rejected => {
1125
+ if (options?.strict) {
1126
+ throw new SerializeError('Unable to serialize', {value, location, cause: rejected});
1127
+ }
1128
+ return undefined;
1129
+ }
1130
+ );
1131
+ }
1132
+ return options?.strict? this._checkValue(result, SerializeError) : result;
1133
+ }
1134
+ /**
1135
+ * Serialize the provided input value.
1136
+ *
1137
+ * @param {any} value - value to serialize
1138
+ * @param {any} [target] - entire output being serialized
1139
+ * @param {SchemaLocation} [location] - traversal location to current schema
1140
+ * @param {object} [options] - options to tweak serialization behavior
1141
+ * @returns {Promise<any>}
1142
+ * @internal
1143
+ */
1144
+ async serializeValue(value, target, location = new SchemaLocation(this), options) {
1145
+ return this._serializeValue(value, target, location, options);
1146
+ }
1147
+
1148
+ /**
1149
+ * Throw an exception if this schema seems able to handle a given input value.
1150
+ *
1151
+ * @param {any} value
1152
+ */
1153
+ ensureAccepts(value) {
1154
+ if (value === undefined) {
1155
+ if (this.options.allowUndefined) {
1156
+ return;
1157
+ }
1158
+ throw new ValidationError('Schema does not accept an undefined input');
1159
+ }
1160
+ if (!Array.isArray(this.values) || this.values.length === 0) {
1161
+ return;
1162
+ }
1163
+ if (this.options.allowUnknownValues) {
1164
+ return;
1165
+ }
1166
+ const found = this.values.some(v => deepEquals(v, value));
1167
+
1168
+ if (!found) {
1169
+ // todo - consider using valueDescription metadata
1170
+ const valueDescription = this.metadata.valueDescription ?? `{${this.values.map(formatValue).join('|')}}`;
1171
+ const details = (valueDescription.length > 80)? `not found in schema "values" option` : `expected one of ${valueDescription}`;
1172
+
1173
+ throw new ValidationError(`Invalid value ${formatValue(value)}, ${details}`);
1174
+ }
1175
+ return;
1176
+ }
1177
+
1178
+ /**
1179
+ * @param {any} value
1180
+ * @returns {boolean}
1181
+ */
1182
+ checkAccepts(value) {
1183
+ try {
1184
+ this.ensureAccepts(value);
1185
+ return true;
1186
+ }
1187
+ catch (e) {
1188
+ return false;
1189
+ }
1190
+ }
1191
+
1192
+ /**
1193
+ * Return a validated output if and only if the input fully matches the schema definition.
1194
+ *
1195
+ * Note: depending on the processors used for validation, the input value may be mutated during validation!
1196
+ * (todo - create an option that prevents this from happening?)
1197
+ *
1198
+ * @param {any} value - input value to validate
1199
+ * @param {ValidateOptions} [options] - any tweaks to the validator behavior
1200
+ * @returns {any|Promise<any>} - validated value
1201
+ * @internal
1202
+ */
1203
+ _validate(value, options = {}) {
1204
+ const location = options.location ?? new SchemaLocation(this);
1205
+ const context = (options.context instanceof TraversalContext) ? options.context : new TraversalContext(location, options);
1206
+
1207
+ const executors = [
1208
+ () => {
1209
+ context.setAssignedInput(value, '');
1210
+ return context.traverse(PRELOAD_EXECUTOR);
1211
+ },
1212
+ () => {
1213
+ for (const state of context.stateMap.values()) {
1214
+ state.completed = false; // yuck.
1215
+ }
1216
+ return context.traverse(VALIDATE_EXECUTOR);
1217
+ },
1218
+ () => context.getValue()
1219
+ ];
1220
+
1221
+ return new PipelineExecutor(executors).execute(context);
1222
+ }
1223
+ /**
1224
+ * Return a validated output if and only if the input fully matches the schema definition.
1225
+ *
1226
+ * Note: depending on the processors used for validation, the input value may be mutated during validation!
1227
+ * (todo - create an option that prevents this from happening?)
1228
+ *
1229
+ * @param {any} value - input value to validate
1230
+ * @param {ValidateOptions} [options] - any tweaks to the validator behavior
1231
+ * @returns {Promise<any>} - validated value
1232
+ */
1233
+ async validate(value, options) {
1234
+ return this._validate(value, options);
1235
+ }
1236
+
1237
+ /**
1238
+ * Process an input value to an output value based on this schema.
1239
+ *
1240
+ * Errors are thrown if:
1241
+ * - the input value doesn't match the schema
1242
+ * - a value processor throws an error
1243
+ * - a value cannot be processed (value processors return undefined) after repeated attempts
1244
+ * - a union cannot be resolved
1245
+ *
1246
+ * If an output target is provided, it is assumed to already be valid.
1247
+ * A few traversal options are supported; pass in a TraversalContext instance for full customization.
1248
+ *
1249
+ * (This is an executor function that may return synchronous or asynchronous results.)
1250
+ *
1251
+ * @param {any} input - the value to process
1252
+ * @param {any} [target] - preexisting output value to build upon, if any
1253
+ * @param {ProcessOptions} [options] - options to customize the traversal
1254
+ * @returns {any|Promise<any>} - returns the output value
1255
+ * @internal
1256
+ */
1257
+ _process(input, target, options = {}) {
1258
+ const location = options?.location ?? new SchemaLocation(this);
1259
+ const context = (options.context instanceof TraversalContext) ? options.context : new TraversalContext(location, {strict: options?.strict, deep: options?.deep, debug: options?.debug});
1260
+
1261
+ const executors = [];
1262
+
1263
+ if (target !== undefined) {
1264
+ executors.push(() => {
1265
+ context.setAssignedInput(target, '');
1266
+ return context.traverse(PRELOAD_EXECUTOR)
1267
+ })
1268
+ }
1269
+ executors.push(() => {
1270
+ context.setAssignedInput(input, options?.inputPath);
1271
+ if (options?.assignments) {
1272
+ for (const [assignmentPath, assignmentInput] of options.assignments) {
1273
+ context.setAssignedInput(assignmentInput, assignmentPath);
1274
+ }
1275
+ }
1276
+ return context.traverse(PROCESS_EXECUTOR)
1277
+ });
1278
+ executors.push(() => {
1279
+ return context.getValue()
1280
+ });
1281
+
1282
+ return new PipelineExecutor(executors).execute(context);
1283
+ }
1284
+
1285
+ /**
1286
+ * Process an input value to an output value based on this schema.
1287
+ *
1288
+ * Errors are thrown if:
1289
+ * - the input value doesn't match the schema
1290
+ * - a value processor throws an error
1291
+ * - a value cannot be processed (value processors return undefined) after repeated attempts
1292
+ * - a union cannot be resolved
1293
+ *
1294
+ * If an output target is provided, it is assumed to already be valid.
1295
+ * A few traversal options are supported; pass in a TraversalContext instance for full customization.
1296
+ *
1297
+ * (This is an async wrapper around the internal `_process` executor function.)
1298
+ *
1299
+ * Note: this method makes `CompiledSchema` look somewhat like a `FunctionValueProcessor`, but it has a
1300
+ * slightly different signature.
1301
+ *
1302
+ * @param {any} input - the value to process
1303
+ * @param {any} [target] - preexisting output value to build upon, if any
1304
+ * @param {ProcessOptions} [options] - options to customize the traversal
1305
+ * @returns {Promise<any>} - returns the output value
1306
+ */
1307
+ async process(input, target, options = {}) {
1308
+ return this._process(input, target, options);
1309
+ }
1310
+
1311
+ /**
1312
+ * Process input path/value assignments into an output value based on the schema.
1313
+ *
1314
+ * Paths are dotted references into the schema hierarchy. Assignment values are normalized, transformed,
1315
+ * validated, and then used to build the output value.
1316
+ *
1317
+ * If a value for the output target is provided, it is assumed to already be valid.
1318
+ *
1319
+ * Errors are thrown if:
1320
+ * - unexpected paths are provided that don't match the schema
1321
+ * - a value is incompatible with the schema referenced by the path
1322
+ * - a value processor throws an error
1323
+ * - a value cannot be processed (value processors return undefined) after repeated attempts
1324
+ * - a union cannot be resolved
1325
+ *
1326
+ * (This an async convenience wrapper around the internal `_process` executor function.)
1327
+ *
1328
+ * @param {Map<string,any>} assignments - path/value associations
1329
+ * @param {any} [target] - preexisting output value to build upon, if any
1330
+ * @param {ProcessAssignmentsOptions} [options] - options to customize the traversal
1331
+ * @returns {Promise<any>} - returns the output value
1332
+ */
1333
+ async processAssignments(assignments, target, options = {}) {
1334
+ return this._process(undefined, target, {...options, assignments})
1335
+ }
1336
+
1337
+
1338
+ /**
1339
+ * Serialize the config data as if you were going to use the result for a config file.
1340
+ *
1341
+ * Runs all serialization processors in a pipeline. By default, if any processor returns undefined or throws an error,
1342
+ * serialization of that value returns undefined and will be omitted from the output. If you set the "strict"
1343
+ * option, errors are re-thrown.
1344
+ *
1345
+ * Serializers attempt to convert resolved values back to an input-friendly value, first via the "serialize"
1346
+ * schema option, or alternatively by trusting that each value is either already compatible, or implements toJSON().
1347
+ *
1348
+ * @param {any} value
1349
+ * @param {SerializeOptions} [options]
1350
+ * @returns {Promise<NonNullable<any>>}
1351
+ */
1352
+ async serialize(value, options = {}) {
1353
+ const location = options?.location ?? new SchemaLocation(this);
1354
+ const context = (options.context instanceof TraversalContext) ? options.context : new TraversalContext(location, {strict: options?.strict, deep: options?.deep, debug: options?.debug});
1355
+
1356
+ // should be able to single-shot serialization
1357
+
1358
+ const executors = [
1359
+ () => {
1360
+ context.setAssignedInput(value, '');
1361
+ return context.traverse(SERIALIZE_EXECUTOR)
1362
+ },
1363
+ () => deepPrune(context.getValue())
1364
+ ]
1365
+ return new PipelineExecutor(executors).execute(context);
1366
+ }
1367
+
1368
+ /**
1369
+ * Given a reference to a union schema member, return the matching key, or undefined if it cannot be found.
1370
+ *
1371
+ * @param {CompiledSchema} unionSchema
1372
+ * @returns {string|undefined}
1373
+ */
1374
+ findUnionKey(unionSchema) {
1375
+ if (!this.isUnion) {
1376
+ throw new SchemaError('Cannot find union key because schema is not a union');
1377
+ }
1378
+ for (const [k, s] of this.unionSchemaEntries) {
1379
+ if (s === unionSchema) {
1380
+ return k;
1381
+ }
1382
+ }
1383
+ return undefined;
1384
+ }
1385
+
1386
+ /**
1387
+ * Given a union key or schema, retrieve the matching union schema, or undefined if it cannot be found or is invalid
1388
+ *
1389
+ * @param {string|CompiledSchema} lookup
1390
+ * @returns {CompiledSchema|undefined}
1391
+ */
1392
+ getUnionSchema(lookup) {
1393
+ if (!lookup) {
1394
+ return undefined
1395
+ }
1396
+ if (typeof lookup === 'string') {
1397
+ if (!this.isUnion) {
1398
+ throw new SchemaError(`Cannot get union schema with key "${lookup}" because schema is not a union`);
1399
+ }
1400
+ if (this.#unionSchemasMap.has(lookup)) {
1401
+ return this.#unionSchemasMap.get(lookup);
1402
+ }
1403
+ throw new SchemaError(`Unknown union schema key "${lookup}"`)
1404
+ }
1405
+ if (lookup instanceof CompiledSchema) {
1406
+ if (!this.isUnion) {
1407
+ throw new SchemaError('Schema is not a union');
1408
+ }
1409
+ if (this.findUnionKey(lookup)) {
1410
+ return lookup;
1411
+ }
1412
+ throw new SchemaError('Schema is not a union member');
1413
+ }
1414
+ throw new SchemaError('Unable to get union schema with provided data');
1415
+ }
1416
+
1417
+ /**
1418
+ * @param {string} key
1419
+ * @param {CompiledSchema} unionSchema
1420
+ * @returns {CompiledSchema}
1421
+ * @internal
1422
+ */
1423
+ _setUnionSchema(key, unionSchema) {
1424
+ if (!key) {
1425
+ throw new SchemaError('Unable to set a union schema without a valid key');
1426
+ }
1427
+ if (!(unionSchema instanceof CompiledSchema)) {
1428
+ throw new SchemaError('Union schema must be a CompiledSchema instance');
1429
+ }
1430
+ if (this.#frozen) {
1431
+ throw new SchemaError(`Cannot add union schema "${key}" to a frozen CompiledSchema`);
1432
+ }
1433
+ // clear cache, if set
1434
+ this.#unionSchemas = undefined;
1435
+
1436
+ this.#unionSchemasMap.set(key, unionSchema);
1437
+
1438
+ return unionSchema;
1439
+ }
1440
+
1441
+
1442
+ /**
1443
+ * Find the schema at a given path, falling back to the wildcard schema if one exists.
1444
+ *
1445
+ * The root schema may be found at '', the empty string. Array members are referenced with dotted integer indexes.
1446
+ * Note that find() does not check union members; use SchemaLocation for finding schemas resolved during traversal.
1447
+ *
1448
+ * @param {string} path
1449
+ * @returns {undefined|CompiledSchema}
1450
+ */
1451
+ find(path) {
1452
+ if (!path || path === '' || path === '.') {
1453
+ return this;
1454
+ }
1455
+ const pathComponents = path.split('.');
1456
+
1457
+ /** @type {CompiledSchema|undefined} */
1458
+ let s = this;
1459
+
1460
+ for (const pathComponent of pathComponents) {
1461
+ s = s?.getPropertySchema(pathComponent);
1462
+
1463
+ if (!s) {
1464
+ return undefined;
1465
+ }
1466
+ }
1467
+ return s;
1468
+ }
1469
+
1470
+ /**
1471
+ * toAssignments - attempt to convert input data to a map of assignments.
1472
+ *
1473
+ * @param {any} object - input
1474
+ * @param {string} prefix - prefix to add to any path generated
1475
+ * @returns {Map<string, any>} - output map of path-to-value associations
1476
+ */
1477
+ toAssignments(object, prefix = '') {
1478
+ if (typeof object !== 'object') {
1479
+ return new Map([['', object]])
1480
+ }
1481
+
1482
+ const assignments = new Map();
1483
+
1484
+ /**
1485
+ *
1486
+ * @param {CompiledSchema|undefined} schema
1487
+ * @param {any} current
1488
+ * @param {string} path
1489
+ */
1490
+ function walk(schema, current, path) {
1491
+ const isContainer = Array.isArray(current) || isPlainObject(current);
1492
+
1493
+ const allowIncremental = schema?.allowIncremental ?? true;
1494
+ //const hasChildren = Boolean(schema?.hasChildren || schema?.isUnion && Object.values(schema.unionSchemas).find(s => s.hasChildren))
1495
+
1496
+ if (isContainer && schema?.hasChildren) {//} && allowIncremental) {
1497
+ const entries = Array.isArray(current)? current.entries() : Object.entries(current);
1498
+
1499
+ for (const [key, value] of entries) {
1500
+ const propertySchema = schema?.getPropertySchema(`${key}`);
1501
+ walk(propertySchema, value, path ? `${path}.${key}` : `${key}`)
1502
+ }
1503
+ }
1504
+ else {
1505
+ assignments.set(path, current);
1506
+ }
1507
+ }
1508
+ walk(this, object, prefix);
1509
+ return assignments;
1510
+ }
1511
+
1512
+ /**
1513
+ * Invoke a provided visitor function on every schema node; if visitor returns false (explicitly), abort early.
1514
+ *
1515
+ * @param {(schema:CompiledSchema, path:string) => any} visitor - visitor function
1516
+ * @param {{onlySerializable?:boolean}} [options]
1517
+ * @returns {boolean} - returns true if visitors all returned true, false if any exited early
1518
+ */
1519
+ visitSchema(visitor, options) {
1520
+ const walked = new Set();
1521
+ const onlySerializable = options?.onlySerializable ?? false;
1522
+ /**
1523
+ * @param {CompiledSchema} schema
1524
+ * @param {string} path
1525
+ * @returns {boolean|undefined}
1526
+ */
1527
+ function walk(schema, path) {
1528
+ if (walked.has(schema)) {
1529
+ return;
1530
+ }
1531
+ walked.add(schema);
1532
+ if (schema.metadata.omitFromSerialize && onlySerializable) {
1533
+ return;
1534
+ }
1535
+ if (schema.hasChildren) {
1536
+ for (const [propName, propSchema] of schema.propertyEntries) {
1537
+ const childPath = path ? `${path}.${propName}` : `${propName}`;
1538
+ if (walk(propSchema, childPath) === false) {
1539
+ return false;
1540
+ }
1541
+ }
1542
+ }
1543
+ if (schema.isUnion) {
1544
+ for (const [unionSchemaKey, unionSchema] of schema.unionSchemaEntries) {
1545
+ if (walk(unionSchema, path) === false) {
1546
+ return;
1547
+ }
1548
+ }
1549
+ }
1550
+ return visitor(schema, path);
1551
+ }
1552
+ return walk(this, '') ?? true;
1553
+ }
1554
+
1555
+
1556
+
1557
+ /**
1558
+ * Compute all possible schema paths (including union schema properties)
1559
+ *
1560
+ * @returns {Set<string>}
1561
+ */
1562
+ getPropertyPaths() {
1563
+ const propertyPaths = new Set();
1564
+
1565
+ this.visitSchema((_schema, path) => {
1566
+ if (path) {
1567
+ propertyPaths.add(path);
1568
+ }
1569
+ })
1570
+ return propertyPaths;
1571
+ }
1572
+
1573
+ /**
1574
+ * Return a named property schema (possibly via wildcard)
1575
+ *
1576
+ * @param {string} propertyName
1577
+ * @returns {CompiledSchema|undefined}
1578
+ */
1579
+ getPropertySchema(propertyName) {
1580
+ if (!propertyName) {
1581
+ throw new SchemaError('Unable to retrieve an unnamed property');
1582
+ }
1583
+ return this.#propertiesMap.get(propertyName) ?? this.#propertiesMap.get('*');
1584
+ }
1585
+
1586
+ /**
1587
+ * Associate a schema with a property name. Only for use during compilation.
1588
+ *
1589
+ * @param {string} propertyName
1590
+ * @param {CompiledSchema} propertySchema
1591
+ * @returns {CompiledSchema}
1592
+ *
1593
+ * @internal
1594
+ */
1595
+ _setPropertySchema(propertyName, propertySchema) {
1596
+ if (!propertyName) {
1597
+ throw new SchemaError('Unable to set an unnamed property');
1598
+ }
1599
+ if (!(propertySchema instanceof CompiledSchema)) {
1600
+ throw new SchemaError('Property schema must be a CompiledSchema instance');
1601
+ }
1602
+ if (this.#frozen) {
1603
+ throw new SchemaError(`Cannot add property ${propertyName} to a frozen CompiledSchema`);
1604
+ }
1605
+ // clear cache, if set
1606
+ this.#properties = undefined;
1607
+
1608
+ this.#propertiesMap.set(propertyName, propertySchema);
1609
+ this.#options.container ??= true;
1610
+
1611
+ return propertySchema;
1612
+ }
1613
+
1614
+ /**
1615
+ * Return all child schemas that have a particular option tag
1616
+ *
1617
+ * @param {string} tag
1618
+ * @returns {CompiledSchema[]}
1619
+ * @deprecated
1620
+ */
1621
+ getTagged(tag) {
1622
+ const schemas = [];
1623
+ for (const propSchema of this.#propertiesMap.values()) {
1624
+ if (propSchema.options[tag]) {
1625
+ schemas.push(propSchema);
1626
+ }
1627
+ }
1628
+ return schemas;
1629
+ }
1630
+
1631
+ /**
1632
+ * Get the first child schema that has a particular option tag
1633
+ *
1634
+ * @param {string} tag
1635
+ * @returns {CompiledSchema|undefined}
1636
+ * @deprecated
1637
+ */
1638
+ getFirstTagged(tag) {
1639
+ for (const propSchema of this.#propertiesMap.values()) {
1640
+ if (propSchema.options[tag]) {
1641
+ return propSchema;
1642
+ }
1643
+ }
1644
+ return undefined;
1645
+ }
1646
+
1647
+ /**
1648
+ * Return true if the path is legal within the schema (including all union schemas)
1649
+ *
1650
+ * @param {string} path
1651
+ * @returns {boolean}
1652
+ */
1653
+ isValidPath(path) {
1654
+ if (path === '') {
1655
+ return true; // means the current schema!
1656
+ }
1657
+ const parts = path.split('.');
1658
+
1659
+ /**
1660
+ * @param {CompiledSchema} schema
1661
+ * @param {number} index
1662
+ * @returns {boolean}
1663
+ */
1664
+ function check(schema, index = 0) {
1665
+ if (index >= parts.length) {
1666
+ return schema !== undefined;
1667
+ }
1668
+ const propertyName = parts[index];
1669
+
1670
+ const propertySchema = schema.getPropertySchema(propertyName);
1671
+ if (propertySchema) {
1672
+ return check(propertySchema, index + 1);
1673
+ }
1674
+ else if (schema.isUnion) {
1675
+ for (const unionSchema of schema.#unionSchemasMap.values()) {
1676
+ if (check(unionSchema, index)) {
1677
+ return true;
1678
+ }
1679
+ }
1680
+ }
1681
+ return false;
1682
+ }
1683
+ return check(this);
1684
+ }
1685
+
1686
+ /**
1687
+ * Write-protect this schema at the end of compilation.
1688
+ * @param {Set<CompiledSchema>} [seen]
1689
+ * @internal
1690
+ */
1691
+ _freeze(seen = new Set()) {
1692
+ if (this.#frozen || seen.has(this)) {
1693
+ return;
1694
+ }
1695
+ this.#frozen = true;
1696
+ seen.add(this);
1697
+ for (const childSchema of this.#propertiesMap.values()) {
1698
+ childSchema._freeze(seen);
1699
+ }
1700
+ Object.freeze(this.#propertiesMap);
1701
+ for (const unionSchema of this.#unionSchemasMap.values()) {
1702
+ unionSchema._freeze(seen);
1703
+ }
1704
+ Object.freeze(this.#unionSchemasMap);
1705
+ Object.freeze(this.#options);
1706
+ Object.freeze(this.#metadata);
1707
+ Object.freeze(this);
1708
+ }
1709
+ }