@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
@@ -113,7 +113,7 @@ export class SchemaDefinition {
113
113
  ...addressed,
114
114
  ...Object.keys(declarations.previous ?? {}),
115
115
  ]);
116
- const declared = FieldSet.declaring(this.fields, declarations.unique);
116
+ const declared = FieldSet.declaring(this.fields, declarations);
117
117
 
118
118
  return this.restated({
119
119
  fields: declared.fields,
@@ -123,9 +123,9 @@ export class SchemaDefinition {
123
123
  ]),
124
124
  previous: declarations.previous ?? this.previous,
125
125
  constraints:
126
- declarations.unique === undefined
126
+ declarations.unique === undefined && declarations.index === undefined
127
127
  ? this.constraints
128
- : SchemaConstraints.of(declared.groups),
128
+ : SchemaConstraints.of(declared),
129
129
  });
130
130
  }
131
131
 
@@ -199,11 +199,13 @@ export class SchemaDefinition {
199
199
  static merged(views: readonly SchemaView[]): SchemaDefinition {
200
200
  const fields: Fields = {};
201
201
  let opts: ValidateOptions = {};
202
- const groups: (readonly string[])[] = [];
202
+ const groups: { unique: (readonly string[])[]; index: (readonly string[])[] } =
203
+ { unique: [], index: [] };
203
204
  for (const view of views) {
204
205
  Object.assign(fields, view.getFields());
205
206
  opts = { ...opts, ...view.getOpts() };
206
- groups.push(...(view.getUnique() ?? []));
207
+ groups.unique.push(...(view.getUnique() ?? []));
208
+ groups.index.push(...(view.getIndex() ?? []));
207
209
  }
208
210
 
209
211
  return SchemaDefinition.derived({
@@ -1,7 +1,13 @@
1
1
  export class SchemaError extends Error {
2
- constructor(message: string) {
3
- super(message);
2
+ constructor(message: string, options?: { received: unknown }) {
3
+ super(
4
+ options ? `${message} — got ${SchemaError.inspect(options.received)}` : message,
5
+ );
4
6
 
5
- this.name = new.target.name;
7
+ this.name = 'SchemaError';
8
+ }
9
+
10
+ static inspect(value: unknown): string {
11
+ return JSON.stringify(value) ?? typeof value;
6
12
  }
7
13
  }
package/src/SchemaView.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Fields } from './field/Fields.js';
2
- import type { CompositeUnique } from './entity/CompositeUnique.js';
2
+ import type { FieldGroups } from './entity/FieldGroups.js';
3
3
  import type { PreviousNames } from './entity/PreviousNames.js';
4
4
  import type { EntityAdapters } from './entity/EntityAdapters.js';
5
5
  import type { ValidationResult } from './lib/ValidationResult.js';
@@ -17,7 +17,8 @@ export interface SchemaView<TFields extends Fields = Fields> {
17
17
  readonly anchored?: boolean;
18
18
  getFields(): TFields;
19
19
  getAdapters(): EntityAdapters<TFields>;
20
- getUnique(): CompositeUnique<TFields> | undefined;
20
+ getUnique(): FieldGroups<TFields> | undefined;
21
+ getIndex(): FieldGroups<TFields> | undefined;
21
22
  getOpts(): ValidateOptions;
22
23
  validate(input: unknown): ValidationResult<Values<TFields>>;
23
24
  }
@@ -0,0 +1,34 @@
1
+ import { Registry } from '../lib/Registry.js';
2
+ import type { JsonSchema } from '../lib/JsonSchema.js';
3
+ import { fieldFormat } from '../field/FieldFormat.js';
4
+ import type { Axis } from './Axis.js';
5
+ import { Boundary } from './boundary/Boundary.js';
6
+ import { Lifecycle } from './lifecycle/Lifecycle.js';
7
+ import { Role } from './role/Role.js';
8
+
9
+ class AxisRegistry extends Registry<Axis> {
10
+ private composed?: JsonSchema;
11
+
12
+ register(name: string, axis: Axis): Axis {
13
+ this.composed = undefined;
14
+
15
+ return super.register(name, axis);
16
+ }
17
+
18
+ /** The document a field declaration is judged against, rebuilt the next time one arrives. */
19
+ get fieldFormat(): JsonSchema {
20
+ this.composed ??= fieldFormat(this.entries);
21
+
22
+ return this.composed;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * The axes this process reads, and the door an axis declared elsewhere comes through.
28
+ * `Axes.register('tenancy', Tenancy)` — from a `vocabulary/` file, read before `entities/`
29
+ */
30
+ export const Axes = new AxisRegistry('axis', 'call Axes.register(name, axis)', [
31
+ ['role', Role],
32
+ ['lifecycle', Lifecycle],
33
+ ['boundary', Boundary],
34
+ ]);
package/src/axis/Axis.ts CHANGED
@@ -1,24 +1,20 @@
1
- import { roleAxis } from './role/RoleAxis.js';
2
- import { lifecycleAxis } from './lifecycle/LifecycleAxis.js';
3
- import { boundaryAxis } from './boundary/BoundaryAxis.js';
1
+ import type { Format } from '../lib/Format.js';
2
+ import type { Field } from '../field/Field.js';
4
3
  import type { ValidationError } from '../lib/ValidationError.js';
5
- import type { Resolver } from './Resolver.js';
6
4
 
7
- export interface Axis<Declared = unknown, Wire = unknown> {
8
- readonly slot: 'role' | 'lifecycle' | 'boundary';
5
+ export interface Axis {
6
+ readonly format: Format;
9
7
 
10
- validator(value: unknown, errors: ValidationError[]): void;
8
+ /**
9
+ * What a format cannot state: `role.relation.to` is a function, and JSON holds none.
10
+ * The whole declaration comes with the name, because a contradiction is a PAIR — a
11
+ * primary key that admits null states one legal `role` beside one legal `shape`.
12
+ */
13
+ refusals?(value: unknown, declaration: Record<string, unknown>): ValidationError[];
11
14
 
12
- describe(value: Declared, key: string): Wire | undefined;
13
-
14
- reconstruct(wire: Wire, resolve?: Resolver): Declared;
15
+ /**
16
+ * Whether this axis alone lets the field be absent from what a caller sends. Asked of every
17
+ * REGISTERED axis, so a fourth one weighs on it without the validator naming it.
18
+ */
19
+ admitsAbsence?(field: Field): boolean;
15
20
  }
16
-
17
- export const EXTENSION_AXES: readonly Axis[] = [
18
- roleAxis,
19
- lifecycleAxis,
20
- boundaryAxis,
21
- ];
22
-
23
- /** The slots they occupy: the keys of `x-fougere` on a field descriptor. */
24
- export const EXTENSION_SLOTS = EXTENSION_AXES.map((axis) => axis.slot);
@@ -4,25 +4,37 @@ import { Boundaries } from './Decoder.js';
4
4
  import type { Field } from '../../field/Field.js';
5
5
  import { type Shape } from '../shape/Shape.js';
6
6
  import { Shapes } from '../shape/Shape.js';
7
- import type { BoundaryRules } from './BoundaryRules.js';
8
7
  import { SchemaError } from '../../SchemaError.js';
8
+ import { Format, type Accepted } from '../../lib/Format.js';
9
+
10
+ const closedOr = <Verb extends 'decode' | 'encode'>(verb: Verb) =>
11
+ Format.either(Format.tokens(['closed']), Format.of().key(verb, Format.text).needs(verb).closed());
12
+
13
+ const RULES = Format.of().key('in', closedOr('decode')).key('out', closedOr('encode')).closed();
14
+
15
+ const BOUNDARY = Format.named(
16
+ 'axis/boundary',
17
+ Format.either(Format.text.as<'isoDate' | (string & {})>(), RULES),
18
+ );
19
+
20
+ export type BoundaryRules = Accepted<typeof RULES>;
21
+
22
+ export type BoundaryRef = Accepted<typeof BOUNDARY>;
9
23
 
10
24
  const identityDecoder: Decoder = (value) => ({ value });
11
25
 
12
26
  const identityEncoder: Encoder = (value) => value;
13
27
 
14
28
  export class Boundary {
15
- private readonly in?: BoundaryRules['in'];
16
- private readonly out?: BoundaryRules['out'];
29
+ static readonly format = BOUNDARY;
30
+
17
31
  readonly decode: Decoder;
18
32
  readonly encode: Encoder;
19
33
 
20
34
  private constructor(
21
- rules: BoundaryRules = {},
35
+ private readonly rules: BoundaryRules = {},
22
36
  codecs?: { decode: Decoder; encode: Encoder },
23
37
  ) {
24
- this.in = rules.in;
25
- this.out = rules.out;
26
38
  this.decode = codecs?.decode ?? identityDecoder;
27
39
  this.encode = codecs?.encode ?? identityEncoder;
28
40
  }
@@ -35,16 +47,23 @@ export class Boundary {
35
47
 
36
48
  const alias = Boundaries.aliases.find(ref);
37
49
  if (!alias) throw new SchemaError(`Unknown boundary alias: '${ref}'`);
50
+
38
51
  return new Boundary(alias);
39
52
  }
40
53
 
54
+ /** The server fills it, so a caller never sends it. */
55
+ static admitsAbsence(field: Field): boolean {
56
+ return this.of(field).readOnly();
57
+ }
58
+
41
59
  static of(field: Field): Boundary {
42
60
  const declared = Boundary.declared(field);
43
61
  const derived = Boundary.forShape(field.shape);
44
62
  const rules: BoundaryRules = {
45
- in: declared.in ?? derived.in,
46
- out: declared.out ?? derived.out,
63
+ in: declared.rules.in ?? derived.rules.in,
64
+ out: declared.rules.out ?? derived.rules.out,
47
65
  };
66
+
48
67
  return new Boundary(rules, {
49
68
  decode:
50
69
  typeof rules.in === 'object'
@@ -60,19 +79,26 @@ export class Boundary {
60
79
  /** `date-time` means a `Date` on both sides, without a word in the entity. */
61
80
  static forShape(shape: Shape | undefined): Boundary {
62
81
  if (Shapes.typeOf(shape) === 'date') return new Boundary(Boundaries.aliases.resolve('isoDate'));
82
+
63
83
  return new Boundary();
64
84
  }
65
85
 
66
86
  /** The dual of `declared`: what `readOnly()` writes back on the field, not a judge. */
67
87
  declaring(overrides: BoundaryRules): BoundaryRules {
68
- return { in: overrides.in ?? this.in, out: overrides.out ?? this.out };
88
+ return { in: overrides.in ?? this.rules.in, out: overrides.out ?? this.rules.out };
69
89
  }
70
90
 
71
- get readOnly(): boolean {
72
- return this.in === 'closed';
91
+ readOnly(): boolean {
92
+ return this.rules.in === 'closed';
73
93
  }
74
94
 
75
- get writeOnly(): boolean {
76
- return this.out === 'closed';
95
+ writeOnly(): boolean {
96
+ return this.rules.out === 'closed';
97
+ }
98
+ }
99
+
100
+ declare module '../../FougereFieldAxes.js' {
101
+ interface FougereFieldAxes {
102
+ readonly boundary?: BoundaryRef;
77
103
  }
78
104
  }
@@ -1,4 +1,4 @@
1
- import type { BoundaryRules } from './BoundaryRules.js';
1
+ import type { BoundaryRules } from './Boundary.js';
2
2
  import { Registry } from '../../lib/Registry.js';
3
3
  import type { Encoder } from './Encoder.js';
4
4
  import type { Verdict } from '../../lib/Verdict.js';
@@ -33,8 +33,10 @@ Boundaries.decoders.register('isoDate', (value) => {
33
33
  if (value instanceof Date) return { value };
34
34
  if (typeof value === 'string') {
35
35
  const date = new Date(value);
36
+
36
37
  return Number.isNaN(date.getTime()) ? { message: 'Invalid date' } : { value: date };
37
38
  }
39
+
38
40
  return { message: 'Expected a date' };
39
41
  });
40
42
  Boundaries.encoders.register('isoDate', (value) =>
@@ -1,19 +1,45 @@
1
+ import { Format, type Accepted } from '../../lib/Format.js';
1
2
  import { Generators } from './Generators.js';
2
- import type { LifecycleRules } from './LifecycleRules.js';
3
+ import type { GeneratorRef } from './Generators.js';
3
4
 
5
+ export type LifecycleRules = Accepted<typeof Lifecycle.format>;
4
6
  export class Lifecycle {
5
- private readonly create?: LifecycleRules['create'];
6
- private readonly update?: LifecycleRules['update'];
7
+ static readonly CREATE = ['now', 'optional'] as const;
8
+ static readonly UPDATE = ['now', 'forbidden'] as const;
7
9
 
8
- private constructor(rules: LifecycleRules = {}) {
9
- this.create = rules.create;
10
- this.update = rules.update;
10
+ static readonly format = Format.of('axis/lifecycle')
11
+ .key(
12
+ 'create',
13
+ Format.either(
14
+ Format.tokens(Lifecycle.CREATE),
15
+ Format.exactlyOne({
16
+ value: Format.anything,
17
+ generate: Format.text.as<GeneratorRef>(),
18
+ }),
19
+ ),
20
+ )
21
+ .key('update', Format.tokens(Lifecycle.UPDATE))
22
+ .closed();
23
+
24
+ private constructor(private readonly rules: LifecycleRules = {}) {}
25
+
26
+ /** `optional()`, a default, a generator: the rule says the value need not arrive. */
27
+ static admitsAbsence(field: { lifecycle?: LifecycleRules }): boolean {
28
+ return !this.of(field).requiredAtCreate();
11
29
  }
12
30
 
13
31
  static of(field: { lifecycle?: LifecycleRules }): Lifecycle {
14
32
  return new Lifecycle(field.lifecycle);
15
33
  }
16
34
 
35
+ private get create() {
36
+ return this.rules.create;
37
+ }
38
+
39
+ private get update() {
40
+ return this.rules.update;
41
+ }
42
+
17
43
  bornWith(instant: number): { value: unknown } | undefined {
18
44
  const rule = this.create;
19
45
 
@@ -26,23 +52,19 @@ export class Lifecycle {
26
52
  return { value: Generators.resolve(rule.generate)() };
27
53
  }
28
54
 
29
- get requiredAtCreate(): boolean {
55
+ requiredAtCreate(): boolean {
30
56
  return this.create === undefined;
31
57
  }
32
58
 
33
- get stampedAtCreate(): boolean {
59
+ stampedAtCreate(): boolean {
34
60
  return this.create === 'now';
35
61
  }
36
62
 
37
- get stampedOnce(): boolean {
38
- return this.stampedAtCreate && !this.stampedOnUpdate;
39
- }
40
-
41
- get immutable(): boolean {
63
+ immutable(): boolean {
42
64
  return this.update === 'forbidden';
43
65
  }
44
66
 
45
- get stampedOnUpdate(): boolean {
67
+ stampedOnUpdate(): boolean {
46
68
  return this.update === 'now';
47
69
  }
48
70
 
@@ -54,3 +76,9 @@ export class Lifecycle {
54
76
  : undefined;
55
77
  }
56
78
  }
79
+
80
+ declare module '../../FougereFieldAxes.js' {
81
+ interface FougereFieldAxes {
82
+ readonly lifecycle?: LifecycleRules;
83
+ }
84
+ }
@@ -1,5 +1,4 @@
1
1
  import { Lifecycle } from './Lifecycle.js';
2
- import { Clock } from './Clock.js';
3
2
  import { type Field } from '../../field/Field.js';
4
3
  import { type Fields } from '../../field/Fields.js';
5
4
 
@@ -8,12 +7,16 @@ import { type Fields } from '../../field/Fields.js';
8
7
  * FR : remplit ce que la déclaration laisse au storage : aucun handler n'estampe lui-même.
9
8
  * `applyCreate({ id: primary(), createdAt: created() }, { title: 'a' })` → `id`, `createdAt`
10
9
  */
11
- export function applyCreate(fields: Fields, input: Record<string, unknown>): Record<string, unknown> {
10
+ export function applyCreate(
11
+ fields: Fields,
12
+ input: Record<string, unknown>,
13
+ ): Record<string, unknown> {
12
14
  const values: Record<string, unknown> = { ...input };
13
- const instant = Clock.now();
15
+ const instant = Date.now();
14
16
 
15
17
  for (const [name, field] of Object.entries(fields) as [string, Field][]) {
16
18
  if (name in values) continue;
19
+
17
20
  const born = Lifecycle.of(field).bornWith(instant);
18
21
 
19
22
  if (born) values[name] = born.value;
@@ -28,12 +31,15 @@ export function applyCreate(fields: Fields, input: Record<string, unknown>): Rec
28
31
  * sont touchés.
29
32
  * `applyUpdate(fields, { title: 'b' })` → `updatedAt` added, nothing else
30
33
  */
31
- export function applyUpdate(fields: Fields, patch: Record<string, unknown>): Record<string, unknown> {
34
+ export function applyUpdate(
35
+ fields: Fields,
36
+ patch: Record<string, unknown>,
37
+ ): Record<string, unknown> {
32
38
  const values: Record<string, unknown> = { ...patch };
33
- const instant = Clock.now();
39
+ const instant = Date.now();
34
40
 
35
41
  for (const [name, field] of Object.entries(fields) as [string, Field][]) {
36
- if (Lifecycle.of(field).stampedOnUpdate && !(name in values))
42
+ if (Lifecycle.of(field).stampedOnUpdate() && !(name in values))
37
43
  values[name] = new Date(instant);
38
44
  }
39
45
 
@@ -1,56 +1,104 @@
1
- import type { EntityConstructor } from './EntityConstructor.js';
2
1
  import type { Relation } from './Relation.js';
3
- import type { RoleRules } from './RoleRules.js';
2
+ import type { EntityConstructor } from './EntityConstructor.js';
3
+ import { isObject } from '../../lib/utils.js';
4
+ import { Shapes, type Shape } from '../shape/Shape.js';
5
+ import type { ValidationError } from '../../lib/ValidationError.js';
6
+ import { Format, type Accepted } from '../../lib/Format.js';
7
+ import { ON_DELETE, RELATION_KINDS } from './Relation.js';
4
8
 
5
9
  export class Role {
6
- private readonly primary?: boolean;
7
- private readonly index?: boolean;
8
- private readonly unique?: boolean;
9
- private readonly relation?: Relation;
10
+ static readonly format = Format.of('axis/role')
11
+ .key('primary', Format.flag)
12
+ .key('index', Format.flag)
13
+ .key('unique', Format.flag)
14
+ .key(
15
+ 'relation',
16
+ Format.of()
17
+ .key('to', Format.anything.as<() => EntityConstructor>())
18
+ .key('kind', Format.tokens(RELATION_KINDS))
19
+ .key('onDelete', Format.tokens(ON_DELETE))
20
+ .needs('to', 'kind')
21
+ .closed(),
22
+ )
23
+ .closed();
24
+
25
+ static refusals(value: unknown, declaration: Record<string, unknown> = {}): ValidationError[] {
26
+ const role = Role.of({ role: isObject(value) ? (value as RoleRules) : undefined });
27
+ const errors: ValidationError[] = [];
28
+ const relation = isObject(value) ? value.relation : undefined;
29
+
30
+ if (isObject(relation) && 'to' in relation && typeof relation.to !== 'function')
31
+ errors.push({
32
+ path: ['role', 'relation', 'to'],
33
+ message: 'Expected a function returning the target entity, such as () => Post',
34
+ });
10
35
 
11
- private constructor(rules: RoleRules = {}) {
12
- this.primary = rules.primary;
13
- this.index = rules.index;
14
- this.unique = rules.unique;
15
- this.relation = rules.relation;
36
+ if (role.isPrimary() && Shapes.isNullable(declaration.shape as Shape))
37
+ errors.push({
38
+ path: ['role', 'primary'],
39
+ message: 'A primary key admits no null — a row is addressed by it',
40
+ });
41
+
42
+ if (role.isCollection() && (role.isUnique() || role.isIndexed()))
43
+ errors.push({
44
+ path: ['role', role.isUnique() ? 'unique' : 'index'],
45
+ message: 'A collection has no column of its own, so there is nothing to index',
46
+ });
47
+
48
+ return errors;
49
+ }
50
+
51
+ private constructor(private readonly rules: RoleRules = {}) {}
52
+
53
+ /** A collection has no column: the far side of the relation carries the key. */
54
+ static admitsAbsence(field: { role?: RoleRules }): boolean {
55
+ return this.of(field).isCollection();
16
56
  }
17
57
 
18
58
  static of(field: { role?: RoleRules }): Role {
19
59
  return new Role(field.role);
20
60
  }
21
61
 
22
- get isPrimary(): boolean {
23
- return this.primary === true;
62
+ isPrimary(): boolean {
63
+ return this.rules.primary === true;
24
64
  }
25
65
 
26
- get isIndexed(): boolean {
27
- return this.index === true;
66
+ isIndexed(): boolean {
67
+ return this.rules.index === true;
28
68
  }
29
69
 
30
70
  /** A field unique on its own. A group spanning several belongs to the schema. */
31
- get isUnique(): boolean {
32
- return this.unique === true;
71
+ isUnique(): boolean {
72
+ return this.rules.unique === true;
33
73
  }
34
74
 
35
- get isCollection(): boolean {
36
- return this.relation?.kind === 'many';
75
+ isCollection(): boolean {
76
+ return this.rules.relation?.kind === 'many';
37
77
  }
38
78
 
39
- get isReference(): boolean {
40
- return this.relation?.kind === 'one';
79
+ isReference(): boolean {
80
+ return this.rules.relation?.kind === 'one';
41
81
  }
42
82
 
43
83
  /** Either kind — what a CLI flag and a GraphQL selection both leave out. */
44
- get isRelation(): boolean {
45
- return this.relation !== undefined;
84
+ isRelation(): boolean {
85
+ return this.rules.relation !== undefined;
46
86
  }
47
87
 
48
88
  /** Calls `() => Post` so no caller has to. */
49
89
  get target(): EntityConstructor | undefined {
50
- return this.relation?.to();
90
+ return this.rules.relation?.to();
51
91
  }
52
92
 
53
93
  get onDelete(): Relation['onDelete'] {
54
- return this.relation?.onDelete;
94
+ return this.rules.relation?.onDelete;
95
+ }
96
+ }
97
+
98
+ export type RoleRules = Accepted<typeof Role.format>;
99
+
100
+ declare module '../../FougereFieldAxes.js' {
101
+ interface FougereFieldAxes {
102
+ readonly role?: RoleRules;
55
103
  }
56
104
  }