@fougere/schema 0.12.0-alpha.0 → 0.12.2-alpha.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 (348) hide show
  1. package/dist/FougereFieldAxes.d.ts +3 -0
  2. package/dist/FougereFieldAxes.d.ts.map +1 -0
  3. package/dist/FougereFieldAxes.js +2 -0
  4. package/dist/FougereFieldAxes.js.map +1 -0
  5. package/dist/Schema.d.ts +4 -3
  6. package/dist/Schema.d.ts.map +1 -1
  7. package/dist/Schema.js +3 -0
  8. package/dist/Schema.js.map +1 -1
  9. package/dist/SchemaConstraints.d.ts +13 -10
  10. package/dist/SchemaConstraints.d.ts.map +1 -1
  11. package/dist/SchemaConstraints.js +32 -20
  12. package/dist/SchemaConstraints.js.map +1 -1
  13. package/dist/SchemaDefinition.d.ts.map +1 -1
  14. package/dist/SchemaDefinition.js +6 -5
  15. package/dist/SchemaDefinition.js.map +1 -1
  16. package/dist/SchemaError.d.ts +4 -1
  17. package/dist/SchemaError.d.ts.map +1 -1
  18. package/dist/SchemaError.js +6 -3
  19. package/dist/SchemaError.js.map +1 -1
  20. package/dist/SchemaView.d.ts +3 -2
  21. package/dist/SchemaView.d.ts.map +1 -1
  22. package/dist/axis/Axes.d.ts +16 -0
  23. package/dist/axis/Axes.d.ts.map +1 -0
  24. package/dist/axis/Axes.js +27 -0
  25. package/dist/axis/Axes.js.map +1 -0
  26. package/dist/axis/Axis.d.ts +15 -9
  27. package/dist/axis/Axis.d.ts.map +1 -1
  28. package/dist/axis/Axis.js +1 -10
  29. package/dist/axis/Axis.js.map +1 -1
  30. package/dist/axis/boundary/Boundary.d.ts +38 -5
  31. package/dist/axis/boundary/Boundary.d.ts.map +1 -1
  32. package/dist/axis/boundary/Boundary.js +18 -11
  33. package/dist/axis/boundary/Boundary.js.map +1 -1
  34. package/dist/axis/boundary/Decoder.d.ts +8 -2
  35. package/dist/axis/boundary/Decoder.d.ts.map +1 -1
  36. package/dist/axis/boundary/Decoder.js.map +1 -1
  37. package/dist/axis/lifecycle/Lifecycle.d.ts +29 -8
  38. package/dist/axis/lifecycle/Lifecycle.d.ts.map +1 -1
  39. package/dist/axis/lifecycle/Lifecycle.js +26 -11
  40. package/dist/axis/lifecycle/Lifecycle.js.map +1 -1
  41. package/dist/axis/lifecycle/apply.d.ts.map +1 -1
  42. package/dist/axis/lifecycle/apply.js +3 -4
  43. package/dist/axis/lifecycle/apply.js.map +1 -1
  44. package/dist/axis/role/Role.d.ts +31 -12
  45. package/dist/axis/role/Role.d.ts.map +1 -1
  46. package/dist/axis/role/Role.js +56 -22
  47. package/dist/axis/role/Role.js.map +1 -1
  48. package/dist/axis/shape/Shape.d.ts +32 -9
  49. package/dist/axis/shape/Shape.d.ts.map +1 -1
  50. package/dist/axis/shape/Shape.js +43 -12
  51. package/dist/axis/shape/Shape.js.map +1 -1
  52. package/dist/axis/shape/ShapeFormat.d.ts +10 -0
  53. package/dist/axis/shape/ShapeFormat.d.ts.map +1 -0
  54. package/dist/axis/shape/ShapeFormat.js +32 -0
  55. package/dist/axis/shape/ShapeFormat.js.map +1 -0
  56. package/dist/axis/shape/{Formats.d.ts → StringFormats.d.ts} +3 -3
  57. package/dist/axis/shape/StringFormats.d.ts.map +1 -0
  58. package/dist/axis/shape/{Formats.js → StringFormats.js} +3 -3
  59. package/dist/axis/shape/StringFormats.js.map +1 -0
  60. package/dist/entity/EntityDeclarations.d.ts +3 -2
  61. package/dist/entity/EntityDeclarations.d.ts.map +1 -1
  62. package/dist/entity/FieldGroups.d.ts +9 -0
  63. package/dist/entity/FieldGroups.d.ts.map +1 -0
  64. package/dist/entity/FieldGroups.js +2 -0
  65. package/dist/entity/FieldGroups.js.map +1 -0
  66. package/dist/field/Field.d.ts +37 -12
  67. package/dist/field/Field.d.ts.map +1 -1
  68. package/dist/field/Field.js +55 -8
  69. package/dist/field/Field.js.map +1 -1
  70. package/dist/field/FieldFormat.d.ts +9 -0
  71. package/dist/field/FieldFormat.d.ts.map +1 -0
  72. package/dist/field/FieldFormat.js +12 -0
  73. package/dist/field/FieldFormat.js.map +1 -0
  74. package/dist/field/FieldSet.d.ts +12 -3
  75. package/dist/field/FieldSet.d.ts.map +1 -1
  76. package/dist/field/FieldSet.js +22 -15
  77. package/dist/field/FieldSet.js.map +1 -1
  78. package/dist/index.d.ts +7 -3
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +4 -3
  81. package/dist/index.js.map +1 -1
  82. package/dist/lib/Format.d.ts +75 -0
  83. package/dist/lib/Format.d.ts.map +1 -0
  84. package/dist/lib/Format.js +123 -0
  85. package/dist/lib/Format.js.map +1 -0
  86. package/dist/lib/JsonSchema.d.ts +2 -0
  87. package/dist/lib/JsonSchema.d.ts.map +1 -0
  88. package/dist/lib/JsonSchema.js +2 -0
  89. package/dist/lib/JsonSchema.js.map +1 -0
  90. package/dist/lib/Registry.d.ts +4 -2
  91. package/dist/lib/Registry.d.ts.map +1 -1
  92. package/dist/lib/Registry.js +11 -5
  93. package/dist/lib/Registry.js.map +1 -1
  94. package/dist/lib/UnionToIntersection.d.ts +7 -0
  95. package/dist/lib/UnionToIntersection.d.ts.map +1 -0
  96. package/dist/lib/UnionToIntersection.js +2 -0
  97. package/dist/lib/UnionToIntersection.js.map +1 -0
  98. package/dist/lib/ValidationResult.d.ts +2 -0
  99. package/dist/lib/ValidationResult.d.ts.map +1 -1
  100. package/dist/lib/ValidationResult.js +6 -0
  101. package/dist/lib/ValidationResult.js.map +1 -1
  102. package/dist/lib/utils.d.ts +1 -1
  103. package/dist/lib/utils.d.ts.map +1 -1
  104. package/dist/lib/utils.js +1 -1
  105. package/dist/lib/utils.js.map +1 -1
  106. package/dist/projection/Cases.d.ts +1 -1
  107. package/dist/projection/Cases.d.ts.map +1 -1
  108. package/dist/projection/Cases.js +20 -8
  109. package/dist/projection/Cases.js.map +1 -1
  110. package/dist/projection/Visibility.d.ts +11 -0
  111. package/dist/projection/Visibility.d.ts.map +1 -1
  112. package/dist/projection/Visibility.js +28 -6
  113. package/dist/projection/Visibility.js.map +1 -1
  114. package/dist/projection/card/Bundle.d.ts.map +1 -1
  115. package/dist/projection/card/Bundle.js.map +1 -1
  116. package/dist/projection/card/Card.d.ts.map +1 -1
  117. package/dist/projection/card/Card.js +24 -30
  118. package/dist/projection/card/Card.js.map +1 -1
  119. package/dist/projection/card/CardForm.d.ts +7 -0
  120. package/dist/projection/card/CardForm.d.ts.map +1 -0
  121. package/dist/projection/card/CardForm.js +2 -0
  122. package/dist/projection/card/CardForm.js.map +1 -0
  123. package/dist/projection/card/CardForms.d.ts +10 -0
  124. package/dist/projection/card/CardForms.d.ts.map +1 -0
  125. package/dist/projection/card/CardForms.js +12 -0
  126. package/dist/projection/card/CardForms.js.map +1 -0
  127. package/dist/projection/card/Change.d.ts +2 -2
  128. package/dist/projection/card/Change.d.ts.map +1 -1
  129. package/dist/projection/card/Diff.js +2 -2
  130. package/dist/projection/card/Diff.js.map +1 -1
  131. package/dist/projection/card/FieldExtension.d.ts +4 -2
  132. package/dist/projection/card/FieldExtension.d.ts.map +1 -1
  133. package/dist/projection/card/RoleDescriptor.d.ts +15 -3
  134. package/dist/projection/card/RoleDescriptor.d.ts.map +1 -1
  135. package/dist/projection/card/RoleDescriptor.js +40 -1
  136. package/dist/projection/card/RoleDescriptor.js.map +1 -1
  137. package/dist/projection/card/ShapeKeywords.d.ts +1 -1
  138. package/dist/projection/card/ShapeKeywords.d.ts.map +1 -1
  139. package/dist/projection/card/admission.d.ts +0 -14
  140. package/dist/projection/card/admission.d.ts.map +1 -1
  141. package/dist/projection/card/admission.js +0 -20
  142. package/dist/projection/card/admission.js.map +1 -1
  143. package/dist/validator/FieldDeclarationValidator.d.ts.map +1 -1
  144. package/dist/validator/FieldDeclarationValidator.js +10 -25
  145. package/dist/validator/FieldDeclarationValidator.js.map +1 -1
  146. package/dist/validator/FieldValueValidator.d.ts.map +1 -1
  147. package/dist/validator/FieldValueValidator.js +9 -29
  148. package/dist/validator/FieldValueValidator.js.map +1 -1
  149. package/dist/validator/InputValidator.d.ts +7 -1
  150. package/dist/validator/InputValidator.d.ts.map +1 -1
  151. package/dist/validator/InputValidator.js +15 -14
  152. package/dist/validator/InputValidator.js.map +1 -1
  153. package/dist/validator/JsonSchemaValidator.d.ts +23 -0
  154. package/dist/validator/JsonSchemaValidator.d.ts.map +1 -0
  155. package/dist/validator/JsonSchemaValidator.js +113 -0
  156. package/dist/validator/JsonSchemaValidator.js.map +1 -0
  157. package/dist/vocabulary/preset/created.d.ts +2 -2
  158. package/dist/vocabulary/preset/created.d.ts.map +1 -1
  159. package/dist/vocabulary/preset/created.js +2 -2
  160. package/dist/vocabulary/preset/created.js.map +1 -1
  161. package/dist/vocabulary/preset/updated.d.ts +2 -2
  162. package/dist/vocabulary/preset/updated.d.ts.map +1 -1
  163. package/dist/vocabulary/preset/updated.js +2 -2
  164. package/dist/vocabulary/preset/updated.js.map +1 -1
  165. package/dist/vocabulary/primitive/bool.d.ts +2 -5
  166. package/dist/vocabulary/primitive/bool.d.ts.map +1 -1
  167. package/dist/vocabulary/primitive/bool.js +1 -5
  168. package/dist/vocabulary/primitive/bool.js.map +1 -1
  169. package/dist/vocabulary/primitive/date.d.ts +2 -3
  170. package/dist/vocabulary/primitive/date.d.ts.map +1 -1
  171. package/dist/vocabulary/primitive/date.js +2 -3
  172. package/dist/vocabulary/primitive/date.js.map +1 -1
  173. package/dist/vocabulary/primitive/json.d.ts +3 -3
  174. package/dist/vocabulary/primitive/json.d.ts.map +1 -1
  175. package/dist/vocabulary/primitive/json.js +11 -11
  176. package/dist/vocabulary/primitive/json.js.map +1 -1
  177. package/dist/vocabulary/primitive/list.d.ts +6 -5
  178. package/dist/vocabulary/primitive/list.d.ts.map +1 -1
  179. package/dist/vocabulary/primitive/list.js +13 -7
  180. package/dist/vocabulary/primitive/list.js.map +1 -1
  181. package/dist/vocabulary/primitive/many.d.ts +2 -4
  182. package/dist/vocabulary/primitive/many.d.ts.map +1 -1
  183. package/dist/vocabulary/primitive/many.js +2 -4
  184. package/dist/vocabulary/primitive/many.js.map +1 -1
  185. package/dist/vocabulary/primitive/number.d.ts +2 -4
  186. package/dist/vocabulary/primitive/number.d.ts.map +1 -1
  187. package/dist/vocabulary/primitive/number.js +1 -3
  188. package/dist/vocabulary/primitive/number.js.map +1 -1
  189. package/dist/vocabulary/primitive/oneOf.d.ts +4 -7
  190. package/dist/vocabulary/primitive/oneOf.d.ts.map +1 -1
  191. package/dist/vocabulary/primitive/oneOf.js +19 -11
  192. package/dist/vocabulary/primitive/oneOf.js.map +1 -1
  193. package/dist/vocabulary/primitive/primary.d.ts +2 -2
  194. package/dist/vocabulary/primitive/primary.d.ts.map +1 -1
  195. package/dist/vocabulary/primitive/primary.js +2 -3
  196. package/dist/vocabulary/primitive/primary.js.map +1 -1
  197. package/dist/vocabulary/primitive/ref.d.ts +2 -6
  198. package/dist/vocabulary/primitive/ref.d.ts.map +1 -1
  199. package/dist/vocabulary/primitive/ref.js +1 -1
  200. package/dist/vocabulary/primitive/ref.js.map +1 -1
  201. package/dist/vocabulary/primitive/text.d.ts +2 -4
  202. package/dist/vocabulary/primitive/text.d.ts.map +1 -1
  203. package/dist/vocabulary/primitive/text.js +1 -3
  204. package/dist/vocabulary/primitive/text.js.map +1 -1
  205. package/dist/vocabulary/rule/nullable.d.ts +0 -1
  206. package/dist/vocabulary/rule/nullable.d.ts.map +1 -1
  207. package/dist/vocabulary/rule/nullable.js +0 -1
  208. package/dist/vocabulary/rule/nullable.js.map +1 -1
  209. package/dist/vocabulary/rule/optional.d.ts +0 -1
  210. package/dist/vocabulary/rule/optional.d.ts.map +1 -1
  211. package/dist/vocabulary/rule/optional.js +0 -1
  212. package/dist/vocabulary/rule/optional.js.map +1 -1
  213. package/dist/vocabulary/rule/readOnly.d.ts +0 -2
  214. package/dist/vocabulary/rule/readOnly.d.ts.map +1 -1
  215. package/dist/vocabulary/rule/readOnly.js +0 -2
  216. package/dist/vocabulary/rule/readOnly.js.map +1 -1
  217. package/dist/vocabulary/rule/writeOnly.d.ts +0 -2
  218. package/dist/vocabulary/rule/writeOnly.d.ts.map +1 -1
  219. package/dist/vocabulary/rule/writeOnly.js +0 -2
  220. package/dist/vocabulary/rule/writeOnly.js.map +1 -1
  221. package/dist/vocabulary/vocabulary.d.ts +2 -3
  222. package/dist/vocabulary/vocabulary.d.ts.map +1 -1
  223. package/dist/vocabulary/vocabulary.js +18 -20
  224. package/dist/vocabulary/vocabulary.js.map +1 -1
  225. package/package.json +1 -1
  226. package/src/FougereFieldAxes.ts +2 -0
  227. package/src/Schema.ts +11 -8
  228. package/src/SchemaConstraints.ts +40 -22
  229. package/src/SchemaDefinition.ts +7 -5
  230. package/src/SchemaError.ts +9 -3
  231. package/src/SchemaView.ts +3 -2
  232. package/src/axis/Axes.ts +34 -0
  233. package/src/axis/Axis.ts +15 -19
  234. package/src/axis/boundary/Boundary.ts +39 -13
  235. package/src/axis/boundary/Decoder.ts +3 -1
  236. package/src/axis/lifecycle/Lifecycle.ts +42 -14
  237. package/src/axis/lifecycle/apply.ts +12 -6
  238. package/src/axis/role/Role.ts +73 -25
  239. package/src/axis/shape/Shape.ts +89 -23
  240. package/src/axis/shape/ShapeFormat.ts +37 -0
  241. package/src/axis/shape/{Formats.ts → StringFormats.ts} +2 -2
  242. package/src/entity/EntityDeclarations.ts +3 -2
  243. package/src/entity/FieldGroups.ts +9 -0
  244. package/src/field/Field.ts +89 -16
  245. package/src/field/FieldFormat.ts +18 -0
  246. package/src/field/FieldSet.ts +27 -19
  247. package/src/index.ts +7 -3
  248. package/src/lib/Format.ts +172 -0
  249. package/src/lib/JsonSchema.ts +1 -0
  250. package/src/lib/Registry.ts +14 -6
  251. package/src/lib/UnionToIntersection.ts +10 -0
  252. package/src/lib/ValidationResult.ts +8 -0
  253. package/src/lib/utils.ts +5 -1
  254. package/src/projection/Cases.ts +25 -8
  255. package/src/projection/Visibility.ts +31 -6
  256. package/src/projection/card/Bundle.ts +2 -0
  257. package/src/projection/card/Card.ts +29 -27
  258. package/src/projection/card/CardForm.ts +8 -0
  259. package/src/projection/card/CardForms.ts +13 -0
  260. package/src/projection/card/Change.ts +2 -2
  261. package/src/projection/card/Diff.ts +6 -2
  262. package/src/projection/card/FieldExtension.ts +5 -2
  263. package/src/projection/card/RoleDescriptor.ts +54 -3
  264. package/src/projection/card/ShapeKeywords.ts +1 -1
  265. package/src/projection/card/admission.ts +0 -33
  266. package/src/validator/FieldDeclarationValidator.ts +10 -25
  267. package/src/validator/FieldValueValidator.ts +13 -36
  268. package/src/validator/InputValidator.ts +17 -12
  269. package/src/validator/JsonSchemaValidator.ts +142 -0
  270. package/src/vocabulary/preset/created.ts +3 -3
  271. package/src/vocabulary/preset/updated.ts +4 -3
  272. package/src/vocabulary/primitive/bool.ts +3 -8
  273. package/src/vocabulary/primitive/date.ts +5 -4
  274. package/src/vocabulary/primitive/json.ts +27 -12
  275. package/src/vocabulary/primitive/list.ts +16 -9
  276. package/src/vocabulary/primitive/many.ts +3 -4
  277. package/src/vocabulary/primitive/number.ts +3 -8
  278. package/src/vocabulary/primitive/oneOf.ts +40 -24
  279. package/src/vocabulary/primitive/primary.ts +9 -5
  280. package/src/vocabulary/primitive/ref.ts +3 -7
  281. package/src/vocabulary/primitive/text.ts +3 -8
  282. package/src/vocabulary/rule/nullable.ts +0 -1
  283. package/src/vocabulary/rule/optional.ts +0 -1
  284. package/src/vocabulary/rule/readOnly.ts +0 -2
  285. package/src/vocabulary/rule/writeOnly.ts +0 -2
  286. package/src/vocabulary/vocabulary.ts +19 -21
  287. package/dist/axis/boundary/BoundaryAxis.d.ts +0 -4
  288. package/dist/axis/boundary/BoundaryAxis.d.ts.map +0 -1
  289. package/dist/axis/boundary/BoundaryAxis.js +0 -30
  290. package/dist/axis/boundary/BoundaryAxis.js.map +0 -1
  291. package/dist/axis/boundary/BoundaryRef.d.ts +0 -3
  292. package/dist/axis/boundary/BoundaryRef.d.ts.map +0 -1
  293. package/dist/axis/boundary/BoundaryRef.js +0 -2
  294. package/dist/axis/boundary/BoundaryRef.js.map +0 -1
  295. package/dist/axis/boundary/BoundaryRules.d.ts +0 -9
  296. package/dist/axis/boundary/BoundaryRules.d.ts.map +0 -1
  297. package/dist/axis/boundary/BoundaryRules.js +0 -2
  298. package/dist/axis/boundary/BoundaryRules.js.map +0 -1
  299. package/dist/axis/lifecycle/Clock.d.ts +0 -10
  300. package/dist/axis/lifecycle/Clock.d.ts.map +0 -1
  301. package/dist/axis/lifecycle/Clock.js +0 -19
  302. package/dist/axis/lifecycle/Clock.js.map +0 -1
  303. package/dist/axis/lifecycle/LifecycleAxis.d.ts +0 -4
  304. package/dist/axis/lifecycle/LifecycleAxis.d.ts.map +0 -1
  305. package/dist/axis/lifecycle/LifecycleAxis.js +0 -52
  306. package/dist/axis/lifecycle/LifecycleAxis.js.map +0 -1
  307. package/dist/axis/lifecycle/LifecycleRules.d.ts +0 -12
  308. package/dist/axis/lifecycle/LifecycleRules.d.ts.map +0 -1
  309. package/dist/axis/lifecycle/LifecycleRules.js +0 -3
  310. package/dist/axis/lifecycle/LifecycleRules.js.map +0 -1
  311. package/dist/axis/role/RoleAxis.d.ts +0 -5
  312. package/dist/axis/role/RoleAxis.d.ts.map +0 -1
  313. package/dist/axis/role/RoleAxis.js +0 -93
  314. package/dist/axis/role/RoleAxis.js.map +0 -1
  315. package/dist/axis/role/RoleRules.d.ts +0 -8
  316. package/dist/axis/role/RoleRules.d.ts.map +0 -1
  317. package/dist/axis/role/RoleRules.js +0 -2
  318. package/dist/axis/role/RoleRules.js.map +0 -1
  319. package/dist/axis/shape/Formats.d.ts.map +0 -1
  320. package/dist/axis/shape/Formats.js.map +0 -1
  321. package/dist/entity/CompositeUnique.d.ts +0 -4
  322. package/dist/entity/CompositeUnique.d.ts.map +0 -1
  323. package/dist/entity/CompositeUnique.js +0 -2
  324. package/dist/entity/CompositeUnique.js.map +0 -1
  325. package/dist/field/Meta.d.ts +0 -4
  326. package/dist/field/Meta.d.ts.map +0 -1
  327. package/dist/field/Meta.js +0 -2
  328. package/dist/field/Meta.js.map +0 -1
  329. package/dist/projection/card/RelationDescriptor.d.ts +0 -5
  330. package/dist/projection/card/RelationDescriptor.d.ts.map +0 -1
  331. package/dist/projection/card/RelationDescriptor.js +0 -2
  332. package/dist/projection/card/RelationDescriptor.js.map +0 -1
  333. package/dist/validator/AdapterFieldValidator.d.ts +0 -14
  334. package/dist/validator/AdapterFieldValidator.d.ts.map +0 -1
  335. package/dist/validator/AdapterFieldValidator.js +0 -37
  336. package/dist/validator/AdapterFieldValidator.js.map +0 -1
  337. package/src/axis/boundary/BoundaryAxis.ts +0 -32
  338. package/src/axis/boundary/BoundaryRef.ts +0 -3
  339. package/src/axis/boundary/BoundaryRules.ts +0 -4
  340. package/src/axis/lifecycle/Clock.ts +0 -22
  341. package/src/axis/lifecycle/LifecycleAxis.ts +0 -55
  342. package/src/axis/lifecycle/LifecycleRules.ts +0 -9
  343. package/src/axis/role/RoleAxis.ts +0 -104
  344. package/src/axis/role/RoleRules.ts +0 -8
  345. package/src/entity/CompositeUnique.ts +0 -6
  346. package/src/field/Meta.ts +0 -3
  347. package/src/projection/card/RelationDescriptor.ts +0 -3
  348. package/src/validator/AdapterFieldValidator.ts +0 -41
@@ -1,9 +1,10 @@
1
- import { EXTENSION_AXES } from '../axis/Axis.js';
2
- import { Shapes } from '../axis/shape/Shape.js';
1
+ import { Axes } from '../axis/Axes.js';
3
2
  import type { Field } from '../field/Field.js';
4
- import { isObject, shown } from '../lib/utils.js';
3
+ import { isObject } from '../lib/utils.js';
4
+ import { SchemaError } from '../SchemaError.js';
5
5
  import type { ValidationError } from '../lib/ValidationError.js';
6
6
  import type { ValidationResult } from '../lib/ValidationResult.js';
7
+ import { JsonSchemaValidator } from './JsonSchemaValidator.js';
7
8
 
8
9
  export class FieldDeclarationValidator {
9
10
  private constructor(private readonly declaration: unknown) {}
@@ -21,7 +22,7 @@ export class FieldDeclarationValidator {
21
22
  errors: [
22
23
  {
23
24
  path: [],
24
- message: `Expected an object — got ${shown(declaration)}`,
25
+ message: `Expected an object — got ${SchemaError.inspect(declaration)}`,
25
26
  },
26
27
  ],
27
28
  };
@@ -29,30 +30,14 @@ export class FieldDeclarationValidator {
29
30
 
30
31
  const errors: ValidationError[] = [];
31
32
 
32
- if (!Shapes.is(declaration.shape)) {
33
- errors.push({
34
- path: ['shape'],
35
- message: `Every field states a shape — got ${shown(declaration.shape)}`,
36
- });
33
+ if (declaration.shape === undefined) {
34
+ errors.push({ path: ['shape'], message: 'Every field states a shape' });
37
35
  }
38
36
 
39
- for (const axis of EXTENSION_AXES) {
40
- const declared = declaration[axis.slot];
41
- if (declared !== undefined) axis.validator(declared, errors);
42
- }
37
+ errors.push(...JsonSchemaValidator.of(Axes.fieldFormat).refusalsOf(declaration, []));
43
38
 
44
- if (declaration.meta !== undefined) {
45
- if (!isObject(declaration.meta)) {
46
- errors.push({
47
- path: ['meta'],
48
- message: `Expected an object — got ${shown(declaration.meta)}`,
49
- });
50
- } else if (
51
- declaration.meta.description !== undefined &&
52
- typeof declaration.meta.description !== 'string'
53
- ) {
54
- errors.push({ path: ['meta', 'description'], message: 'Expected a string' });
55
- }
39
+ for (const [name, axis] of Axes.entries) {
40
+ errors.push(...(axis.refusals?.(declaration[name], declaration) ?? []));
56
41
  }
57
42
 
58
43
  return errors.length
@@ -1,16 +1,16 @@
1
- import { Validator, format as engineFormats } from '@cfworker/json-schema';
1
+ import { format as engineFormats } from '@cfworker/json-schema';
2
2
  import { Boundary } from '../axis/boundary/Boundary.js';
3
3
  import { type FormatPredicate } from '../axis/shape/FormatPredicate.js';
4
- import { Formats } from '../axis/shape/Formats.js';
4
+ import { StringFormats } from '../axis/shape/StringFormats.js';
5
5
  import { type Shape } from '../axis/shape/Shape.js';
6
6
  import { Shapes } from '../axis/shape/Shape.js';
7
7
  import type { Field } from '../field/Field.js';
8
8
  import type { Verdict } from '../lib/Verdict.js';
9
- import type { OutputUnit } from '@cfworker/json-schema';
10
9
  import { SchemaError } from '../SchemaError.js';
10
+ import type { JsonSchema } from '../lib/JsonSchema.js';
11
+ import { JsonSchemaValidator } from './JsonSchemaValidator.js';
11
12
 
12
13
  interface ShapePlan {
13
- validator: Validator;
14
14
  custom?: FormatPredicate;
15
15
  formatName?: string;
16
16
  }
@@ -30,7 +30,6 @@ export class FieldValueValidator {
30
30
  const type = Shapes.typeOf(shape);
31
31
  const base = Shapes.of(shape).base;
32
32
  if (value !== null) {
33
- if (base?.type === 'object' && !base.properties) return { value };
34
33
  if (type === 'date' && value instanceof Date) {
35
34
  return Number.isNaN(value.getTime()) ? { message: 'Invalid date' } : { value };
36
35
  }
@@ -39,11 +38,14 @@ export class FieldValueValidator {
39
38
  }
40
39
  }
41
40
  const plan = FieldValueValidator.planFor(shape);
42
- const result = plan.validator.validate(value);
43
- if (!result.valid) return refusalOf(result.errors);
41
+ const refusal = JsonSchemaValidator.of(shape as JsonSchema).refusalOf(value, []);
42
+ if (refusal) {
43
+ return refusal.path.length > 0 ? { message: refusal.message, path: refusal.path } : { message: refusal.message };
44
+ }
44
45
  if (plan.custom && typeof value === 'string' && !plan.custom(value)) {
45
46
  return { message: `String does not match format "${plan.formatName}".` };
46
47
  }
48
+
47
49
  return { value };
48
50
  }
49
51
 
@@ -65,49 +67,24 @@ export class FieldValueValidator {
65
67
  const base = Shapes.of(shape).base;
66
68
  const formatName = base?.type === 'string' ? base.format : undefined;
67
69
  plan = {
68
- validator: new Validator(shape as object, '2020-12', true),
69
70
  custom: formatName === undefined ? undefined : this.customFormatOf(formatName),
70
71
  formatName,
71
72
  };
72
73
  this.plans.set(shape, plan);
73
74
  }
75
+
74
76
  return plan;
75
77
  }
76
78
 
77
79
  private static customFormatOf(name: string): FormatPredicate | undefined {
78
- const custom = Formats.find(name);
80
+ const custom = StringFormats.find(name);
79
81
  if (!custom && !(name in engineFormats)) {
80
82
  throw new SchemaError(
81
- `Unknown format: '${name}'. Register it with Formats.register('${name}', …) — ` +
83
+ `Unknown format: '${name}'. Register it with StringFormats.register('${name}', …) — ` +
82
84
  `the engine validates ${Object.keys(engineFormats).length} formats natively and this is not one of them.`,
83
85
  );
84
86
  }
87
+
85
88
  return custom;
86
89
  }
87
90
  }
88
-
89
- /**
90
- * The engine states its refusals outermost first, so `errors[0]` on a nested shape is the
91
- * parent's `Property "street" does not match schema.` — true, and never the reason. The
92
- * DEEPEST one is the reason, and it is the one that says where.
93
- */
94
- function refusalOf(errors: readonly OutputUnit[]): Verdict {
95
- const deepest = errors.reduce<OutputUnit | undefined>(
96
- (held, one) => (held && depthOf(held) >= depthOf(one) ? held : one),
97
- undefined,
98
- );
99
- if (!deepest) return { message: 'Invalid value' };
100
- const path = locationOf(deepest.instanceLocation);
101
-
102
- return path.length > 0 ? { message: deepest.error, path } : { message: deepest.error };
103
- }
104
-
105
- const depthOf = (unit: OutputUnit): number => locationOf(unit.instanceLocation).length;
106
-
107
- /** `#/city/zip` — a JSON Pointer fragment, and `~1`/`~0` are how it spells `/` and `~`. */
108
- const locationOf = (instanceLocation: string): string[] =>
109
- instanceLocation
110
- .replace(/^#/, '')
111
- .split('/')
112
- .filter(Boolean)
113
- .map((segment) => segment.replace(/~1/g, '/').replace(/~0/g, '~'));
@@ -1,3 +1,4 @@
1
+ import { Axes } from '../axis/Axes.js';
1
2
  import { Boundary } from '../axis/boundary/Boundary.js';
2
3
  import { Lifecycle } from '../axis/lifecycle/Lifecycle.js';
3
4
  import { Role } from '../axis/role/Role.js';
@@ -20,11 +21,14 @@ export class InputValidator {
20
21
  return new InputValidator(fields, options);
21
22
  }
22
23
 
23
- onAbsent(field: Field): 'skip' | 'empty-list' | null {
24
- if (Boundary.of(field).readOnly) return 'skip';
25
- if (!Lifecycle.of(field).requiredAtCreate) return 'skip';
26
- if (Role.of(field).isCollection) return 'empty-list';
27
- return null;
24
+ /**
25
+ * The three REGISTERED axes decide, and the shape is not one of them — it could not answer
26
+ * anyway: `nullable(text())` and `optional(text())` both shape `['string', 'null']`, and only
27
+ * the second dispenses. JSON Schema draws the same line, where `required` is a list on the
28
+ * OBJECT and never a keyword on the property.
29
+ */
30
+ requires(field: Field): boolean {
31
+ return !Axes.all.some((axis) => axis.admitsAbsence?.(field));
28
32
  }
29
33
 
30
34
  /** Hands on the value it PARSED, so a handler never re-checks a row. */
@@ -43,7 +47,8 @@ export class InputValidator {
43
47
  for (const [key, field] of Object.entries(this.fields)) {
44
48
  const verdict = this.admit(field, data[key]);
45
49
  if (verdict === undefined) continue;
46
- if ('message' in verdict) errors.push({ path: [key, ...(verdict.path ?? [])], message: verdict.message });
50
+ if ('message' in verdict)
51
+ errors.push({ path: [key, ...(verdict.path ?? [])], message: verdict.message });
47
52
  else row[key] = verdict.value;
48
53
  }
49
54
 
@@ -61,8 +66,11 @@ export class InputValidator {
61
66
  private admit(field: Field, value: unknown): Verdict | undefined {
62
67
  if (value === undefined) return this.whenAbsent(field);
63
68
 
64
- if (Boundary.of(field).readOnly) return { message: InputRefusal.readOnly };
65
- if (this.options.patch && Lifecycle.of(field).immutable) return { message: InputRefusal.immutable };
69
+ if (Boundary.of(field).readOnly()) return { message: InputRefusal.readOnly };
70
+ // The other side of the relation carries the key: there is nothing to write here.
71
+ if (Role.of(field).isCollection()) return { message: InputRefusal.readOnly };
72
+ if (this.options.patch && Lifecycle.of(field).immutable())
73
+ return { message: InputRefusal.immutable };
66
74
 
67
75
  return FieldValueValidator.of(field).parse(value);
68
76
  }
@@ -70,9 +78,6 @@ export class InputValidator {
70
78
  private whenAbsent(field: Field): Verdict | undefined {
71
79
  if (this.options.patch) return undefined;
72
80
 
73
- const absence = this.onAbsent(field);
74
- if (absence === null) return { message: InputRefusal.required };
75
-
76
- return absence === 'empty-list' ? { value: [] } : undefined;
81
+ return this.requires(field) ? { message: InputRefusal.required } : undefined;
77
82
  }
78
83
  }
@@ -0,0 +1,142 @@
1
+ import { Validator, type OutputUnit } from '@cfworker/json-schema';
2
+ import type { JsonSchema } from '../lib/JsonSchema.js';
3
+ import { isObject } from '../lib/utils.js';
4
+ import type { ValidationError } from '../lib/ValidationError.js';
5
+ import { locationOf } from '../lib/ValidationResult.js';
6
+
7
+ /**
8
+ * The one door onto the engine: a field's value against its shape, a declaration against the
9
+ * format of its key. The format is DATA, not a TypeScript interface: an interface is erased
10
+ * before a JS caller, a config or a card from another language could be measured against it.
11
+ */
12
+ export class JsonSchemaValidator {
13
+ private static readonly held = new WeakMap<JsonSchema, JsonSchemaValidator>();
14
+
15
+ private first?: Validator;
16
+ private every?: Validator;
17
+
18
+ private constructor(private readonly schema: JsonSchema) {}
19
+
20
+ static of(schema: JsonSchema): JsonSchemaValidator {
21
+ let validator = this.held.get(schema);
22
+
23
+ if (!validator) {
24
+ validator = new JsonSchemaValidator(schema);
25
+ this.held.set(schema, validator);
26
+ }
27
+
28
+ return validator;
29
+ }
30
+
31
+ /** `refusalOf({ update: 'maybe' }, ['lifecycle'])` → `lifecycle.update: Instance does not match any of ["now","forbidden"].` */
32
+ refusalOf(value: unknown, path: readonly string[]): ValidationError | undefined {
33
+ this.first ??= new Validator(this.schema, '2020-12', true);
34
+
35
+ const refused = refusedBy(this.first, value);
36
+
37
+ if (!Array.isArray(refused)) return under(path, refused);
38
+ if (!refused.length) return undefined;
39
+
40
+ const reasons = named(refused);
41
+
42
+ return at(path, reasons.length ? deepestOf(reasons) : refused[0]);
43
+ }
44
+
45
+ /**
46
+ * One refusal per key, so a declaration hears every fault at once. It reads through an engine
47
+ * of its own: the other stops at the first, which is what a single value wants.
48
+ */
49
+ refusalsOf(value: unknown, path: readonly string[]): ValidationError[] {
50
+ this.every ??= new Validator(this.schema, '2020-12', false);
51
+
52
+ const refused = refusedBy(this.every, value);
53
+
54
+ if (!Array.isArray(refused)) return [under(path, refused)];
55
+ if (!refused.length) return [];
56
+
57
+ const faults = [...byKey(named(refused)).values()];
58
+
59
+ return faults.length ? faults.map((units) => at(path, deepestOf(units))) : [at(path, refused[0])];
60
+ }
61
+ }
62
+
63
+ /** What it refused on, or the member JSON cannot hold — the engine throws on one rather than refusing. */
64
+ function refusedBy(engine: Validator, value: unknown): OutputUnit[] | ValidationError {
65
+ const json = stated(value);
66
+
67
+ try {
68
+ const verdict = engine.validate(json);
69
+
70
+ return verdict.valid ? [] : verdict.errors;
71
+ } catch {
72
+ const where = unreadableIn(json) ?? { path: [], value: json };
73
+
74
+ return { path: where.path, message: `Expected a JSON value — got ${typeof where.value}` };
75
+ }
76
+ }
77
+
78
+ /** `False boolean schema` names nothing: the unit above it carries the offending key. */
79
+ const named = (units: readonly OutputUnit[]): OutputUnit[] =>
80
+ units.filter((unit) => unit.keyword !== 'false');
81
+
82
+ /** The engine states its refusals outermost first: the deepest says where, the last one is why. */
83
+ function deepestOf(units: readonly OutputUnit[]): OutputUnit {
84
+ return units.reduce((held, unit) => (depthOf(held) > depthOf(unit) ? held : unit));
85
+ }
86
+
87
+ /**
88
+ * The units of one fault, addressed by the key they happened under. A unit on the value ITSELF
89
+ * points at a key when another one is deeper — `Property "lifecycle" does not match schema.` —
90
+ * so it is kept only when it is all there is.
91
+ */
92
+ function byKey(units: readonly OutputUnit[]): Map<string, OutputUnit[]> {
93
+ const held = new Map<string, OutputUnit[]>();
94
+
95
+ for (const unit of units) {
96
+ const key = locationOf(unit.instanceLocation)[0] ?? '';
97
+ held.set(key, [...(held.get(key) ?? []), unit]);
98
+ }
99
+
100
+ if (held.size > 1) held.delete('');
101
+
102
+ return held;
103
+ }
104
+
105
+ const at = (path: readonly string[], unit: OutputUnit): ValidationError => ({
106
+ path: [...path, ...locationOf(unit.instanceLocation)],
107
+ message: unit.error,
108
+ });
109
+
110
+ const under = (path: readonly string[], refusal: ValidationError): ValidationError => ({
111
+ path: [...path, ...refusal.path],
112
+ message: refusal.message,
113
+ });
114
+
115
+ const depthOf = (unit: OutputUnit): number => locationOf(unit.instanceLocation).length;
116
+
117
+ /** A key set to `undefined` states nothing, as in JSON — and the engine refuses to read one. */
118
+ function stated(value: unknown): unknown {
119
+ if (Array.isArray(value)) return value.map(stated);
120
+
121
+ if (!isObject(value)) return value;
122
+
123
+ const present = Object.entries(value).filter(([, member]) => member !== undefined);
124
+
125
+ return Object.fromEntries(present.map(([key, member]) => [key, stated(member)]));
126
+ }
127
+
128
+ /** Where the engine stopped: a function, a symbol or a bigint is not a value JSON holds. */
129
+ function unreadableIn(
130
+ value: unknown,
131
+ path: readonly string[] = [],
132
+ ): { path: readonly string[]; value: unknown } | undefined {
133
+ if (['function', 'symbol', 'bigint'].includes(typeof value)) return { path, value };
134
+
135
+ if (Array.isArray(value)) {
136
+ return value.flatMap((member, index) => unreadableIn(member, [...path, String(index)]) ?? [])[0];
137
+ }
138
+
139
+ if (!isObject(value)) return undefined;
140
+
141
+ return Object.entries(value).flatMap(([key, member]) => unreadableIn(member, [...path, key]) ?? [])[0];
142
+ }
@@ -1,8 +1,8 @@
1
- import { Field } from '../../field/Field.js';
1
+ import { Field, type Described } from '../../field/Field.js';
2
2
  import { date } from '../primitive/date.js';
3
3
 
4
- export function created(): Field<Date> {
5
- return date().with({
4
+ export function created(opts?: Described): Field<Date> {
5
+ return date(opts).with({
6
6
  lifecycle: { create: 'now', update: 'forbidden' },
7
7
  });
8
8
  }
@@ -1,7 +1,8 @@
1
+ import { Field, type Described } from '../../field/Field.js';
1
2
  import { created } from './created.js';
2
- import { Field } from '../../field/Field.js';
3
3
 
4
- export function updated(): Field<Date> {
5
- const base = created();
4
+ export function updated(opts?: Described): Field<Date> {
5
+ const base = created(opts);
6
+
6
7
  return base.with({ lifecycle: { ...base.lifecycle, update: 'now' } });
7
8
  }
@@ -1,10 +1,5 @@
1
- import { Field } from '../../field/Field.js';
1
+ import { Field, type Shared } from '../../field/Field.js';
2
2
 
3
- export function bool(opts?: { default?: boolean; description?: string }): Field<boolean> {
4
- return new Field<boolean>({
5
- shape: { type: 'boolean' },
6
- lifecycle:
7
- opts?.default !== undefined ? { create: { value: opts.default } } : undefined,
8
- meta: opts?.description !== undefined ? { description: opts.description } : undefined,
9
- });
3
+ export function bool(opts?: Shared<boolean>): Field<boolean> {
4
+ return new Field<boolean>({ shape: { type: 'boolean' } }).setShared(opts);
10
5
  }
@@ -1,10 +1,11 @@
1
- import { Field } from '../../field/Field.js';
1
+ import { Field, type Shared } from '../../field/Field.js';
2
2
 
3
3
  /**
4
4
  * A `Date` in a handler, an ISO string on the wire — the `isoDate` boundary converts.
5
- * FR : un `Date` dans un handler, une chaîne ISO sur le fil — `isoDate` convertit.
6
5
  * `date()` → `{ type: 'string', format: 'date-time' }`
7
6
  */
8
- export function date(): Field<Date> {
9
- return new Field<Date>({ shape: { type: 'string', format: 'date-time' } });
7
+ export function date(opts?: Shared<Date>): Field<Date> {
8
+ return new Field<Date>({ shape: { type: 'string', format: 'date-time' } }).setShared(
9
+ opts,
10
+ );
10
11
  }
@@ -1,29 +1,44 @@
1
- import { Field } from '../../field/Field.js';
1
+ import { Field, type Shared } from '../../field/Field.js';
2
2
  import type { SchemaView } from '../../SchemaView.js';
3
3
  import { InputValidator } from '../../validator/InputValidator.js';
4
+ import { SchemaError } from '../../SchemaError.js';
4
5
 
5
- export function json<T = unknown>(): Field<T>;
6
+ type Entity = SchemaView & (new (...args: never[]) => unknown);
7
+
8
+ export function json<T = unknown>(opts?: Shared<T>): Field<T>;
6
9
  export function json<E extends SchemaView & (new (...args: any[]) => any)>(
7
10
  of: E,
11
+ opts?: Shared<InstanceType<E>>,
8
12
  ): Field<InstanceType<E>>;
13
+
9
14
  /**
10
- * `json(Address)` where the object has a shape; `json()` alone admits any shape forever.
11
- * FR : `json(Address)` quand l'objet a une forme ; `json()` seul admet tout, à jamais.
12
15
  * `json(Address)` → the entity's properties, and its required keys
13
16
  */
14
- export function json(of?: SchemaView): Field<unknown> {
15
- if (!of) return new Field({ shape: { type: 'object' } });
16
- const fields = of.getFields();
17
+ export function json(
18
+ of?: Entity | Shared<unknown>,
19
+ opts?: Shared<unknown>,
20
+ ): Field<unknown> {
21
+ const [schema, shared] = typeof of === 'function' ? [of, opts] : [undefined, of];
22
+
23
+ if (schema && typeof schema.getFields !== 'function')
24
+ throw new SchemaError(
25
+ 'json() takes an entity, such as json(Address) — got a function that is not one',
26
+ );
27
+
28
+ if (!schema) return new Field({ shape: { type: 'object' } }).setShared(shared);
29
+
30
+ const fields = schema.getFields();
17
31
  const validator = InputValidator.of(fields);
18
32
  const properties: Record<string, unknown> = {};
19
33
  const required: string[] = [];
34
+
20
35
  for (const [key, field] of Object.entries(fields)) {
21
- properties[key] = field.meta?.description
22
- ? { ...field.shape, description: field.meta.description }
23
- : field.shape;
24
- if (validator.onAbsent(field) === null) required.push(key);
36
+ properties[key] = field.shape;
37
+
38
+ if (validator.requires(field)) required.push(key);
25
39
  }
40
+
26
41
  return new Field({
27
42
  shape: { type: 'object', properties, ...(required.length ? { required } : {}) },
28
- });
43
+ }).setShared(shared);
29
44
  }
@@ -1,22 +1,29 @@
1
- import { Field } from '../../field/Field.js';
1
+ import { Field, type Shared } from '../../field/Field.js';
2
2
  import { SchemaError } from '../../SchemaError.js';
3
3
 
4
- export interface ListOptions {
4
+ export interface ListOptions<T = unknown> extends Shared<T[]> {
5
5
  min?: number;
6
6
  max?: number;
7
7
  }
8
8
 
9
9
  /**
10
- * A list of VALUES. The other side of a relation is `many()`, and passing one here throws.
11
- * FR : une liste de VALEURS. L'autre côté d'une relation est `many()`, et en passer une ici lève.
10
+ * A list of values. It keeps the shape of the item and nothing else: `nullable()` and a
11
+ * `description` write in the shape, so they work. A rule writes beside it, and an item of a
12
+ * list has no key, no column and no name to keep it on.
12
13
  * `list(text(), { max: 5 })` → `{ type: 'array', items: { type: 'string' }, maxItems: 5 }`
13
14
  */
14
- export function list<T>(item: Field<T>, opts?: ListOptions): Field<T[]> {
15
- if (item.role?.relation)
15
+ export function list<T>(field: Field<T>, opts?: ListOptions<T>): Field<T[]> {
16
+ if (field.hasAxes())
16
17
  throw new SchemaError(
17
- 'list() takes a value field (text(), number()…) — a relation has no value shape',
18
+ 'list() takes a value, not a rule. Put the rule on the list: unique(list(text())).',
18
19
  );
20
+
19
21
  return new Field<T[]>({
20
- shape: { type: 'array', items: item.shape, minItems: opts?.min, maxItems: opts?.max },
21
- });
22
+ shape: {
23
+ type: 'array',
24
+ items: field.shape,
25
+ minItems: opts?.min,
26
+ maxItems: opts?.max,
27
+ },
28
+ }).setShared(opts);
22
29
  }
@@ -1,17 +1,16 @@
1
1
  import { type EntityConstructor } from '../../axis/role/EntityConstructor.js';
2
2
  import { Relation } from '../../axis/role/Relation.js';
3
- import { Field } from '../../field/Field.js';
3
+ import { Field, type Described } from '../../field/Field.js';
4
4
 
5
5
  /**
6
- * The far side of a `ref()`: no column of its own, and absent from what a client sends.
7
- * FR : l'autre côté d'un `ref()` : aucune colonne à lui, et absent de ce qu'un client envoie.
8
6
  * `many(() => Post)` → `{ type: 'array' }` with `role.relation.kind` `'many'`
9
7
  */
10
8
  export function many<E extends EntityConstructor>(
11
9
  target: E | (() => E),
10
+ opts?: Described,
12
11
  ): Field<InstanceType<E>[]> {
13
12
  return new Field<InstanceType<E>[]>({
14
13
  shape: { type: 'array' },
15
14
  role: { relation: Relation.many(target) },
16
- });
15
+ }).setShared(opts);
17
16
  }
@@ -1,11 +1,9 @@
1
- import { Field } from '../../field/Field.js';
1
+ import { Field, type Shared } from '../../field/Field.js';
2
2
 
3
- export interface NumberOptions {
3
+ export interface NumberOptions extends Shared<number> {
4
4
  min?: number;
5
5
  max?: number;
6
6
  integer?: boolean;
7
- default?: number;
8
- description?: string;
9
7
  }
10
8
 
11
9
  export function number(opts?: NumberOptions): Field<number> {
@@ -15,8 +13,5 @@ export function number(opts?: NumberOptions): Field<number> {
15
13
  minimum: opts?.min,
16
14
  maximum: opts?.max,
17
15
  },
18
- lifecycle:
19
- opts?.default !== undefined ? { create: { value: opts.default } } : undefined,
20
- meta: opts?.description !== undefined ? { description: opts.description } : undefined,
21
- });
16
+ }).setShared(opts);
22
17
  }
@@ -1,32 +1,48 @@
1
- import { Field } from '../../field/Field.js';
1
+ import { Field, type Shared } from '../../field/Field.js';
2
+ import type { Shape } from '../../axis/shape/Shape.js';
3
+ import { isArrayOf, isObject } from '../../lib/utils.js';
4
+ import { SchemaError } from '../../SchemaError.js';
2
5
 
3
- interface OneOfOptions<V extends string = string> {
4
- description?: string;
5
- default?: V;
6
- }
6
+ type Values = readonly [string, ...string[]] | readonly [number, ...number[]];
7
7
 
8
- export function oneOf<const T extends readonly string[]>(
9
- ...values: [...T]
10
- ): Field<T[number]>;
11
- export function oneOf<const T extends readonly string[]>(
12
- ...args: [...T, OneOfOptions<T[number]>]
8
+ export function oneOf<const T extends Values>(...values: [...T]): Field<T[number]>;
9
+
10
+ export function oneOf<const T extends Values>(
11
+ ...args: [...T, Shared<T[number]>]
13
12
  ): Field<T[number]>;
13
+
14
14
  /**
15
- * The set travels: GraphQL enum, form `select` and DDL `CHECK`, from this one declaration.
16
- * FR : l'ensemble voyage : enum GraphQL, `select` du formulaire, `CHECK` du DDL.
17
15
  * `oneOf('draft', 'published', { default: 'draft' })` → `{ type: 'string', enum: [...] }`
18
16
  */
19
- export function oneOf<const T extends readonly string[]>(
20
- ...args: [...T] | [...T, OneOfOptions<T[number]>]
17
+ export function oneOf<const T extends Values>(
18
+ ...args: [...T] | [...T, Shared<T[number]>]
21
19
  ): Field<T[number]> {
22
- const last = args[args.length - 1];
23
- const hasOpts = typeof last === 'object' && last !== null && !Array.isArray(last);
24
- const values = (hasOpts ? args.slice(0, -1) : args) as unknown as readonly string[];
25
- const opts = hasOpts ? (last as OneOfOptions) : {};
26
- return new Field<T[number]>({
27
- shape: { type: 'string', enum: values },
28
- lifecycle:
29
- opts.default !== undefined ? { create: { value: opts.default } } : undefined,
30
- meta: opts.description !== undefined ? { description: opts.description } : undefined,
31
- });
20
+ const given: readonly unknown[] = args;
21
+ const last = given.at(-1);
22
+
23
+ const opts = isObject(last) ? last : undefined;
24
+ const values = opts ? given.slice(0, -1) : given;
25
+
26
+ let shape: Shape | undefined = undefined;
27
+
28
+ if (values.length === 0)
29
+ throw new SchemaError(
30
+ 'oneOf() takes strings or numbers, then an optional object of options', { received: given },
31
+ );
32
+
33
+ if (isArrayOf(values, (value) => typeof value === 'string'))
34
+ shape = { type: 'string', enum: values };
35
+
36
+ if (isArrayOf(values, (value) => typeof value === 'number'))
37
+ shape = {
38
+ type: values.every(Number.isInteger) ? 'integer' : 'number',
39
+ enum: values,
40
+ };
41
+
42
+ if (shape === undefined)
43
+ throw new SchemaError(
44
+ 'oneOf() takes strings or numbers, then an optional object of options', { received: given },
45
+ );
46
+
47
+ return new Field<T[number]>({ shape }).setShared(opts);
32
48
  }