@fougere/schema 0.5.0-alpha.1 → 0.7.0-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 (403) hide show
  1. package/README.md +5 -4
  2. package/dist/{schema/Schema.d.ts → Schema.d.ts} +18 -28
  3. package/dist/Schema.d.ts.map +1 -0
  4. package/dist/Schema.js +136 -0
  5. package/dist/Schema.js.map +1 -0
  6. package/dist/SchemaDefinition.d.ts +73 -0
  7. package/dist/SchemaDefinition.d.ts.map +1 -0
  8. package/dist/SchemaDefinition.js +184 -0
  9. package/dist/SchemaDefinition.js.map +1 -0
  10. package/dist/SchemaDerivation.d.ts +16 -0
  11. package/dist/SchemaDerivation.d.ts.map +1 -0
  12. package/dist/SchemaDerivation.js +31 -0
  13. package/dist/SchemaDerivation.js.map +1 -0
  14. package/dist/{schema/SchemaView.d.ts → SchemaView.d.ts} +9 -9
  15. package/dist/SchemaView.d.ts.map +1 -0
  16. package/dist/{schema/SchemaView.js.map → SchemaView.js.map} +1 -1
  17. package/dist/axis/Axis.d.ts +2 -2
  18. package/dist/axis/Axis.d.ts.map +1 -1
  19. package/dist/axis/boundary/Boundaries.d.ts +13 -12
  20. package/dist/axis/boundary/Boundaries.d.ts.map +1 -1
  21. package/dist/axis/boundary/Boundaries.js +18 -24
  22. package/dist/axis/boundary/Boundaries.js.map +1 -1
  23. package/dist/axis/boundary/Boundary.d.ts +3 -1
  24. package/dist/axis/boundary/Boundary.d.ts.map +1 -1
  25. package/dist/axis/boundary/Boundary.js +16 -7
  26. package/dist/axis/boundary/Boundary.js.map +1 -1
  27. package/dist/axis/boundary/BoundaryAxis.js +4 -4
  28. package/dist/axis/boundary/BoundaryAxis.js.map +1 -1
  29. package/dist/axis/lifecycle/Clock.d.ts +1 -1
  30. package/dist/axis/lifecycle/Clock.d.ts.map +1 -1
  31. package/dist/axis/lifecycle/Clock.js +1 -1
  32. package/dist/axis/lifecycle/Clock.js.map +1 -1
  33. package/dist/axis/lifecycle/Generators.d.ts +6 -14
  34. package/dist/axis/lifecycle/Generators.d.ts.map +1 -1
  35. package/dist/axis/lifecycle/Generators.js +10 -28
  36. package/dist/axis/lifecycle/Generators.js.map +1 -1
  37. package/dist/axis/lifecycle/LifecycleAxis.js +11 -6
  38. package/dist/axis/lifecycle/LifecycleAxis.js.map +1 -1
  39. package/dist/axis/lifecycle/apply.d.ts +14 -5
  40. package/dist/axis/lifecycle/apply.d.ts.map +1 -1
  41. package/dist/axis/lifecycle/apply.js +26 -10
  42. package/dist/axis/lifecycle/apply.js.map +1 -1
  43. package/dist/axis/role/Relation.d.ts +3 -2
  44. package/dist/axis/role/Relation.d.ts.map +1 -1
  45. package/dist/axis/role/Relation.js +6 -5
  46. package/dist/axis/role/Relation.js.map +1 -1
  47. package/dist/axis/role/Role.d.ts +6 -6
  48. package/dist/axis/role/Role.d.ts.map +1 -1
  49. package/dist/axis/role/Role.js +13 -17
  50. package/dist/axis/role/Role.js.map +1 -1
  51. package/dist/axis/role/RoleAxis.d.ts +1 -1
  52. package/dist/axis/role/RoleAxis.d.ts.map +1 -1
  53. package/dist/axis/role/RoleAxis.js +28 -37
  54. package/dist/axis/role/RoleAxis.js.map +1 -1
  55. package/dist/axis/shape/Formats.d.ts +8 -5
  56. package/dist/axis/shape/Formats.d.ts.map +1 -1
  57. package/dist/axis/shape/Formats.js +8 -9
  58. package/dist/axis/shape/Formats.js.map +1 -1
  59. package/dist/axis/shape/Shape.d.ts +5 -3
  60. package/dist/axis/shape/Shape.d.ts.map +1 -1
  61. package/dist/axis/shape/Shape.js +13 -13
  62. package/dist/axis/shape/Shape.js.map +1 -1
  63. package/dist/entity/EntityAdapterSet.d.ts +22 -0
  64. package/dist/entity/EntityAdapterSet.d.ts.map +1 -0
  65. package/dist/entity/EntityAdapterSet.js +63 -0
  66. package/dist/entity/EntityAdapterSet.js.map +1 -0
  67. package/dist/entity/EntityAdapters.d.ts +5 -0
  68. package/dist/entity/EntityAdapters.d.ts.map +1 -0
  69. package/dist/entity/EntityAdapters.js +2 -0
  70. package/dist/entity/EntityAdapters.js.map +1 -0
  71. package/dist/{EntityDeclarations.d.ts → entity/EntityDeclarations.d.ts} +2 -7
  72. package/dist/entity/EntityDeclarations.d.ts.map +1 -0
  73. package/dist/entity/EntityDeclarations.js.map +1 -0
  74. package/dist/entity.d.ts +8 -3
  75. package/dist/entity.d.ts.map +1 -1
  76. package/dist/entity.js +6 -1
  77. package/dist/entity.js.map +1 -1
  78. package/dist/{fields → field}/Field.d.ts +8 -9
  79. package/dist/field/Field.d.ts.map +1 -0
  80. package/dist/field/Field.js +35 -0
  81. package/dist/field/Field.js.map +1 -0
  82. package/dist/field/FieldSet.d.ts +22 -0
  83. package/dist/field/FieldSet.d.ts.map +1 -0
  84. package/dist/field/FieldSet.js +58 -0
  85. package/dist/field/FieldSet.js.map +1 -0
  86. package/dist/index.d.ts +24 -28
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +17 -21
  89. package/dist/index.js.map +1 -1
  90. package/dist/lib/Registry.d.ts +12 -0
  91. package/dist/lib/Registry.d.ts.map +1 -0
  92. package/dist/lib/Registry.js +29 -0
  93. package/dist/lib/Registry.js.map +1 -0
  94. package/dist/{utils.d.ts → lib/utils.d.ts} +1 -0
  95. package/dist/lib/utils.d.ts.map +1 -0
  96. package/dist/{utils.js → lib/utils.js} +1 -0
  97. package/dist/lib/utils.js.map +1 -0
  98. package/dist/projection/Cases.d.ts +39 -0
  99. package/dist/projection/Cases.d.ts.map +1 -0
  100. package/dist/projection/Cases.js +146 -0
  101. package/dist/projection/Cases.js.map +1 -0
  102. package/dist/projection/Visibility.d.ts +2 -1
  103. package/dist/projection/Visibility.d.ts.map +1 -1
  104. package/dist/projection/Visibility.js +1 -0
  105. package/dist/projection/Visibility.js.map +1 -1
  106. package/dist/{card → projection/card}/Bundle.d.ts +1 -1
  107. package/dist/projection/card/Bundle.d.ts.map +1 -0
  108. package/dist/{card → projection/card}/Bundle.js +7 -7
  109. package/dist/projection/card/Bundle.js.map +1 -0
  110. package/dist/projection/card/Card.d.ts +21 -0
  111. package/dist/projection/card/Card.d.ts.map +1 -0
  112. package/dist/{card → projection/card}/Card.js +119 -58
  113. package/dist/projection/card/Card.js.map +1 -0
  114. package/dist/{card → projection/card}/Descriptor.d.ts +7 -7
  115. package/dist/projection/card/Descriptor.d.ts.map +1 -0
  116. package/dist/projection/card/Descriptor.js.map +1 -0
  117. package/dist/projection/card/admission.d.ts +20 -0
  118. package/dist/projection/card/admission.d.ts.map +1 -0
  119. package/dist/projection/card/admission.js +27 -0
  120. package/dist/projection/card/admission.js.map +1 -0
  121. package/dist/{card → projection/card}/diff.d.ts +3 -3
  122. package/dist/projection/card/diff.d.ts.map +1 -0
  123. package/dist/{card → projection/card}/diff.js.map +1 -1
  124. package/dist/{judge/result.d.ts → validation.d.ts} +1 -1
  125. package/dist/validation.d.ts.map +1 -0
  126. package/dist/validation.js +2 -0
  127. package/dist/validation.js.map +1 -0
  128. package/dist/validator/AdapterFieldValidator.d.ts +14 -0
  129. package/dist/validator/AdapterFieldValidator.d.ts.map +1 -0
  130. package/dist/validator/AdapterFieldValidator.js +36 -0
  131. package/dist/validator/AdapterFieldValidator.js.map +1 -0
  132. package/dist/validator/FieldDeclarationValidator.d.ts +9 -0
  133. package/dist/validator/FieldDeclarationValidator.d.ts.map +1 -0
  134. package/dist/{judge/FieldJudge.js → validator/FieldDeclarationValidator.js} +13 -13
  135. package/dist/validator/FieldDeclarationValidator.js.map +1 -0
  136. package/dist/validator/FieldValueValidator.d.ts +13 -0
  137. package/dist/validator/FieldValueValidator.d.ts.map +1 -0
  138. package/dist/{judge/ValueJudge.js → validator/FieldValueValidator.js} +14 -11
  139. package/dist/validator/FieldValueValidator.js.map +1 -0
  140. package/dist/{judge/RowRefusal.d.ts → validator/InputRefusal.d.ts} +3 -4
  141. package/dist/validator/InputRefusal.d.ts.map +1 -0
  142. package/dist/{judge/RowRefusal.js → validator/InputRefusal.js} +3 -3
  143. package/dist/validator/InputRefusal.js.map +1 -0
  144. package/dist/validator/InputValidator.d.ts +15 -0
  145. package/dist/validator/InputValidator.d.ts.map +1 -0
  146. package/dist/{judge/RowJudge.js → validator/InputValidator.js} +21 -17
  147. package/dist/validator/InputValidator.js.map +1 -0
  148. package/dist/vocabulary/bool.d.ts +6 -1
  149. package/dist/vocabulary/bool.d.ts.map +1 -1
  150. package/dist/vocabulary/bool.js +6 -1
  151. package/dist/vocabulary/bool.js.map +1 -1
  152. package/dist/vocabulary/created.d.ts +6 -1
  153. package/dist/vocabulary/created.d.ts.map +1 -1
  154. package/dist/vocabulary/created.js +6 -1
  155. package/dist/vocabulary/created.js.map +1 -1
  156. package/dist/vocabulary/date.d.ts +6 -1
  157. package/dist/vocabulary/date.d.ts.map +1 -1
  158. package/dist/vocabulary/date.js +6 -1
  159. package/dist/vocabulary/date.js.map +1 -1
  160. package/dist/vocabulary/email.d.ts +6 -1
  161. package/dist/vocabulary/email.d.ts.map +1 -1
  162. package/dist/vocabulary/email.js +5 -0
  163. package/dist/vocabulary/email.js.map +1 -1
  164. package/dist/vocabulary/immutable.d.ts +6 -1
  165. package/dist/vocabulary/immutable.d.ts.map +1 -1
  166. package/dist/vocabulary/immutable.js +5 -0
  167. package/dist/vocabulary/immutable.js.map +1 -1
  168. package/dist/vocabulary/indexed.d.ts +6 -1
  169. package/dist/vocabulary/indexed.d.ts.map +1 -1
  170. package/dist/vocabulary/indexed.js +8 -1
  171. package/dist/vocabulary/indexed.js.map +1 -1
  172. package/dist/vocabulary/json.d.ts +2 -2
  173. package/dist/vocabulary/json.d.ts.map +1 -1
  174. package/dist/vocabulary/json.js +9 -4
  175. package/dist/vocabulary/json.js.map +1 -1
  176. package/dist/vocabulary/list.d.ts +6 -1
  177. package/dist/vocabulary/list.d.ts.map +1 -1
  178. package/dist/vocabulary/list.js +6 -1
  179. package/dist/vocabulary/list.js.map +1 -1
  180. package/dist/vocabulary/many.d.ts +6 -1
  181. package/dist/vocabulary/many.d.ts.map +1 -1
  182. package/dist/vocabulary/many.js +6 -1
  183. package/dist/vocabulary/many.js.map +1 -1
  184. package/dist/vocabulary/nullable.d.ts +6 -1
  185. package/dist/vocabulary/nullable.d.ts.map +1 -1
  186. package/dist/vocabulary/nullable.js +7 -2
  187. package/dist/vocabulary/nullable.js.map +1 -1
  188. package/dist/vocabulary/number.d.ts +6 -1
  189. package/dist/vocabulary/number.d.ts.map +1 -1
  190. package/dist/vocabulary/number.js +11 -2
  191. package/dist/vocabulary/number.js.map +1 -1
  192. package/dist/vocabulary/oneOf.d.ts +1 -1
  193. package/dist/vocabulary/oneOf.d.ts.map +1 -1
  194. package/dist/vocabulary/oneOf.js +6 -1
  195. package/dist/vocabulary/oneOf.js.map +1 -1
  196. package/dist/vocabulary/optional.d.ts +6 -1
  197. package/dist/vocabulary/optional.d.ts.map +1 -1
  198. package/dist/vocabulary/optional.js +10 -3
  199. package/dist/vocabulary/optional.js.map +1 -1
  200. package/dist/vocabulary/primary.d.ts +1 -1
  201. package/dist/vocabulary/primary.d.ts.map +1 -1
  202. package/dist/vocabulary/primary.js +10 -5
  203. package/dist/vocabulary/primary.js.map +1 -1
  204. package/dist/vocabulary/readOnly.d.ts +6 -1
  205. package/dist/vocabulary/readOnly.d.ts.map +1 -1
  206. package/dist/vocabulary/readOnly.js +5 -0
  207. package/dist/vocabulary/readOnly.js.map +1 -1
  208. package/dist/vocabulary/ref.d.ts +6 -1
  209. package/dist/vocabulary/ref.d.ts.map +1 -1
  210. package/dist/vocabulary/ref.js +6 -1
  211. package/dist/vocabulary/ref.js.map +1 -1
  212. package/dist/vocabulary/text.d.ts +6 -1
  213. package/dist/vocabulary/text.d.ts.map +1 -1
  214. package/dist/vocabulary/text.js +13 -2
  215. package/dist/vocabulary/text.js.map +1 -1
  216. package/dist/vocabulary/unique.d.ts +6 -1
  217. package/dist/vocabulary/unique.d.ts.map +1 -1
  218. package/dist/vocabulary/unique.js +8 -4
  219. package/dist/vocabulary/unique.js.map +1 -1
  220. package/dist/vocabulary/updated.d.ts +6 -1
  221. package/dist/vocabulary/updated.d.ts.map +1 -1
  222. package/dist/vocabulary/updated.js +5 -0
  223. package/dist/vocabulary/updated.js.map +1 -1
  224. package/dist/vocabulary/url.d.ts +6 -1
  225. package/dist/vocabulary/url.d.ts.map +1 -1
  226. package/dist/vocabulary/url.js +5 -0
  227. package/dist/vocabulary/url.js.map +1 -1
  228. package/dist/vocabulary/vocabulary.d.ts +6 -1
  229. package/dist/vocabulary/vocabulary.d.ts.map +1 -1
  230. package/dist/vocabulary/vocabulary.js +21 -11
  231. package/dist/vocabulary/vocabulary.js.map +1 -1
  232. package/dist/vocabulary/writeOnly.d.ts +6 -1
  233. package/dist/vocabulary/writeOnly.d.ts.map +1 -1
  234. package/dist/vocabulary/writeOnly.js +5 -0
  235. package/dist/vocabulary/writeOnly.js.map +1 -1
  236. package/package.json +1 -1
  237. package/src/Schema.ts +208 -0
  238. package/src/SchemaDefinition.ts +257 -0
  239. package/src/SchemaDerivation.ts +44 -0
  240. package/src/{schema/SchemaView.ts → SchemaView.ts} +9 -9
  241. package/src/axis/Axis.ts +2 -2
  242. package/src/axis/boundary/Boundaries.ts +25 -32
  243. package/src/axis/boundary/Boundary.ts +23 -10
  244. package/src/axis/boundary/BoundaryAxis.ts +4 -4
  245. package/src/axis/lifecycle/Clock.ts +1 -1
  246. package/src/axis/lifecycle/Generators.ts +12 -29
  247. package/src/axis/lifecycle/LifecycleAxis.ts +12 -7
  248. package/src/axis/lifecycle/apply.ts +30 -15
  249. package/src/axis/role/Relation.ts +6 -5
  250. package/src/axis/role/Role.ts +16 -20
  251. package/src/axis/role/RoleAxis.ts +28 -41
  252. package/src/axis/shape/Formats.ts +9 -11
  253. package/src/axis/shape/Shape.ts +18 -18
  254. package/src/entity/EntityAdapterSet.ts +81 -0
  255. package/src/entity/EntityAdapters.ts +8 -0
  256. package/src/entity/EntityDeclarations.ts +16 -0
  257. package/src/entity.ts +8 -3
  258. package/src/field/Field.ts +61 -0
  259. package/src/field/FieldSet.ts +75 -0
  260. package/src/index.ts +50 -28
  261. package/src/lib/Registry.ts +37 -0
  262. package/src/{utils.ts → lib/utils.ts} +3 -0
  263. package/src/projection/Cases.ts +176 -0
  264. package/src/projection/Visibility.ts +6 -2
  265. package/src/{card → projection/card}/Bundle.ts +9 -9
  266. package/src/projection/card/Card.ts +342 -0
  267. package/src/{card → projection/card}/Descriptor.ts +7 -7
  268. package/src/projection/card/admission.ts +40 -0
  269. package/src/{card → projection/card}/diff.ts +3 -3
  270. package/src/validator/AdapterFieldValidator.ts +40 -0
  271. package/src/{judge/FieldJudge.ts → validator/FieldDeclarationValidator.ts} +15 -15
  272. package/src/{judge/ValueJudge.ts → validator/FieldValueValidator.ts} +18 -13
  273. package/src/{judge/RowRefusal.ts → validator/InputRefusal.ts} +2 -4
  274. package/src/{judge/RowJudge.ts → validator/InputValidator.ts} +27 -20
  275. package/src/vocabulary/bool.ts +8 -2
  276. package/src/vocabulary/created.ts +7 -2
  277. package/src/vocabulary/date.ts +6 -1
  278. package/src/vocabulary/email.ts +6 -1
  279. package/src/vocabulary/immutable.ts +12 -4
  280. package/src/vocabulary/indexed.ts +9 -2
  281. package/src/vocabulary/json.ts +13 -6
  282. package/src/vocabulary/list.ts +10 -2
  283. package/src/vocabulary/many.ts +9 -2
  284. package/src/vocabulary/nullable.ts +13 -5
  285. package/src/vocabulary/number.ts +13 -3
  286. package/src/vocabulary/oneOf.ts +11 -3
  287. package/src/vocabulary/optional.ts +16 -6
  288. package/src/vocabulary/primary.ts +10 -5
  289. package/src/vocabulary/readOnly.ts +12 -4
  290. package/src/vocabulary/ref.ts +10 -2
  291. package/src/vocabulary/text.ts +15 -3
  292. package/src/vocabulary/unique.ts +10 -9
  293. package/src/vocabulary/updated.ts +6 -1
  294. package/src/vocabulary/url.ts +6 -1
  295. package/src/vocabulary/vocabulary.ts +21 -12
  296. package/src/vocabulary/writeOnly.ts +12 -4
  297. package/dist/EntityAdapters.d.ts +0 -28
  298. package/dist/EntityAdapters.d.ts.map +0 -1
  299. package/dist/EntityAdapters.js +0 -75
  300. package/dist/EntityAdapters.js.map +0 -1
  301. package/dist/EntityDeclarations.d.ts.map +0 -1
  302. package/dist/EntityDeclarations.js.map +0 -1
  303. package/dist/card/Bundle.d.ts.map +0 -1
  304. package/dist/card/Bundle.js.map +0 -1
  305. package/dist/card/Card.d.ts +0 -27
  306. package/dist/card/Card.d.ts.map +0 -1
  307. package/dist/card/Card.js.map +0 -1
  308. package/dist/card/Descriptor.d.ts.map +0 -1
  309. package/dist/card/Descriptor.js.map +0 -1
  310. package/dist/card/EntityTypeSource.d.ts +0 -12
  311. package/dist/card/EntityTypeSource.d.ts.map +0 -1
  312. package/dist/card/EntityTypeSource.js +0 -78
  313. package/dist/card/EntityTypeSource.js.map +0 -1
  314. package/dist/card/FacadeTypeSource.d.ts +0 -19
  315. package/dist/card/FacadeTypeSource.d.ts.map +0 -1
  316. package/dist/card/FacadeTypeSource.js +0 -40
  317. package/dist/card/FacadeTypeSource.js.map +0 -1
  318. package/dist/card/admission.d.ts +0 -9
  319. package/dist/card/admission.d.ts.map +0 -1
  320. package/dist/card/admission.js +0 -16
  321. package/dist/card/admission.js.map +0 -1
  322. package/dist/card/diff.d.ts.map +0 -1
  323. package/dist/constraint/FieldGroup.d.ts +0 -21
  324. package/dist/constraint/FieldGroup.d.ts.map +0 -1
  325. package/dist/constraint/FieldGroup.js +0 -65
  326. package/dist/constraint/FieldGroup.js.map +0 -1
  327. package/dist/constraint/Unique.d.ts +0 -4
  328. package/dist/constraint/Unique.d.ts.map +0 -1
  329. package/dist/constraint/Unique.js +0 -4
  330. package/dist/constraint/Unique.js.map +0 -1
  331. package/dist/fields/Field.d.ts.map +0 -1
  332. package/dist/fields/Field.js +0 -52
  333. package/dist/fields/Field.js.map +0 -1
  334. package/dist/fields/FieldSet.d.ts +0 -28
  335. package/dist/fields/FieldSet.d.ts.map +0 -1
  336. package/dist/fields/FieldSet.js +0 -55
  337. package/dist/fields/FieldSet.js.map +0 -1
  338. package/dist/judge/Cases.d.ts +0 -50
  339. package/dist/judge/Cases.d.ts.map +0 -1
  340. package/dist/judge/Cases.js +0 -108
  341. package/dist/judge/Cases.js.map +0 -1
  342. package/dist/judge/FieldJudge.d.ts +0 -9
  343. package/dist/judge/FieldJudge.d.ts.map +0 -1
  344. package/dist/judge/FieldJudge.js.map +0 -1
  345. package/dist/judge/RowJudge.d.ts +0 -12
  346. package/dist/judge/RowJudge.d.ts.map +0 -1
  347. package/dist/judge/RowJudge.js.map +0 -1
  348. package/dist/judge/RowRefusal.d.ts.map +0 -1
  349. package/dist/judge/RowRefusal.js.map +0 -1
  350. package/dist/judge/ValueForm.d.ts +0 -3
  351. package/dist/judge/ValueForm.d.ts.map +0 -1
  352. package/dist/judge/ValueForm.js +0 -3
  353. package/dist/judge/ValueForm.js.map +0 -1
  354. package/dist/judge/ValueJudge.d.ts +0 -12
  355. package/dist/judge/ValueJudge.d.ts.map +0 -1
  356. package/dist/judge/ValueJudge.js.map +0 -1
  357. package/dist/judge/options.d.ts +0 -4
  358. package/dist/judge/options.d.ts.map +0 -1
  359. package/dist/judge/options.js +0 -2
  360. package/dist/judge/options.js.map +0 -1
  361. package/dist/judge/result.d.ts.map +0 -1
  362. package/dist/judge/result.js +0 -2
  363. package/dist/judge/result.js.map +0 -1
  364. package/dist/projection/SchemaOrCard.d.ts +0 -7
  365. package/dist/projection/SchemaOrCard.d.ts.map +0 -1
  366. package/dist/projection/SchemaOrCard.js +0 -11
  367. package/dist/projection/SchemaOrCard.js.map +0 -1
  368. package/dist/schema/Schema.d.ts.map +0 -1
  369. package/dist/schema/Schema.js +0 -150
  370. package/dist/schema/Schema.js.map +0 -1
  371. package/dist/schema/SchemaDefinition.d.ts +0 -60
  372. package/dist/schema/SchemaDefinition.d.ts.map +0 -1
  373. package/dist/schema/SchemaDefinition.js +0 -85
  374. package/dist/schema/SchemaDefinition.js.map +0 -1
  375. package/dist/schema/SchemaDerivation.d.ts +0 -34
  376. package/dist/schema/SchemaDerivation.d.ts.map +0 -1
  377. package/dist/schema/SchemaDerivation.js +0 -46
  378. package/dist/schema/SchemaDerivation.js.map +0 -1
  379. package/dist/schema/SchemaView.d.ts.map +0 -1
  380. package/dist/utils.d.ts.map +0 -1
  381. package/dist/utils.js.map +0 -1
  382. package/src/EntityAdapters.ts +0 -100
  383. package/src/EntityDeclarations.ts +0 -19
  384. package/src/card/Card.ts +0 -256
  385. package/src/card/EntityTypeSource.ts +0 -90
  386. package/src/card/FacadeTypeSource.ts +0 -55
  387. package/src/card/admission.ts +0 -25
  388. package/src/constraint/FieldGroup.ts +0 -91
  389. package/src/constraint/Unique.ts +0 -3
  390. package/src/fields/Field.ts +0 -80
  391. package/src/fields/FieldSet.ts +0 -62
  392. package/src/judge/Cases.ts +0 -139
  393. package/src/judge/ValueForm.ts +0 -7
  394. package/src/judge/options.ts +0 -3
  395. package/src/projection/SchemaOrCard.ts +0 -18
  396. package/src/schema/Schema.ts +0 -197
  397. package/src/schema/SchemaDefinition.ts +0 -120
  398. package/src/schema/SchemaDerivation.ts +0 -57
  399. /package/dist/{schema/SchemaView.js → SchemaView.js} +0 -0
  400. /package/dist/{EntityDeclarations.js → entity/EntityDeclarations.js} +0 -0
  401. /package/dist/{card → projection/card}/Descriptor.js +0 -0
  402. /package/dist/{card → projection/card}/diff.js +0 -0
  403. /package/src/{judge/result.ts → validation.ts} +0 -0
@@ -6,7 +6,7 @@ type Nullably<T extends string> = T | readonly [T, 'null'];
6
6
  interface StringConstraints { minLength?: number; maxLength?: number; pattern?: string; enum?: readonly (string | null)[]; format?: StringFormat }
7
7
  interface NumericConstraints { minimum?: number; maximum?: number }
8
8
  interface ArrayConstraints { items?: Shape; minItems?: number; maxItems?: number }
9
- interface ObjectConstraints { properties?: Record<string, unknown>; required?: readonly string[] }
9
+ interface ObjectConstraints { properties?: Record<string, unknown>; required?: readonly string[]; additionalProperties?: boolean | Shape; propertyNames?: Shape }
10
10
 
11
11
  export type Shape =
12
12
  | ({ type: Nullably<'string'> } & StringConstraints)
@@ -19,8 +19,8 @@ const SHAPE_TYPES = ['string', 'number', 'integer', 'boolean', 'array', 'object'
19
19
 
20
20
  function isShapeImpl(value: unknown): value is Shape {
21
21
  if (typeof value !== 'object' || value === null) return false;
22
- const declared = (value as Shape).type;
23
- const names = Array.isArray(declared) ? declared : [declared];
22
+ const type = (value as Shape).type;
23
+ const names = Array.isArray(type) ? type : [type];
24
24
  return (
25
25
  names.some((name) => (SHAPE_TYPES as readonly unknown[]).includes(name)) &&
26
26
  names.every((name) => name === 'null' || (SHAPE_TYPES as readonly unknown[]).includes(name))
@@ -36,19 +36,19 @@ type BaseShape =
36
36
 
37
37
  function nullableShapeImpl(shape: Shape): Shape {
38
38
  if (Array.isArray(shape.type)) return shape;
39
- const out = { ...shape, type: [shape.type, 'null'] } as unknown as Shape;
40
- if ('enum' in out && out.enum && !out.enum.includes(null)) {
41
- (out as { enum: readonly (string | null)[] }).enum = [...out.enum, null];
39
+ const nullable = { ...shape, type: [shape.type, 'null'] } as unknown as Shape;
40
+ if ('enum' in nullable && nullable.enum && !nullable.enum.includes(null)) {
41
+ (nullable as { enum: readonly (string | null)[] }).enum = [...nullable.enum, null];
42
42
  }
43
- return out;
43
+ return nullable;
44
44
  }
45
45
 
46
- interface ShapeAnatomy {
46
+ interface ShapeParts {
47
47
  base?: BaseShape;
48
48
  nullable: boolean;
49
49
  }
50
50
 
51
- export class Anatomy {
51
+ export class Shapes {
52
52
  static is(value: unknown): value is Shape {
53
53
  return isShapeImpl(value);
54
54
  }
@@ -57,26 +57,26 @@ export class Anatomy {
57
57
  return nullableShapeImpl(shape);
58
58
  }
59
59
 
60
- private static readonly cache = new WeakMap<object, ShapeAnatomy>();
61
- private static readonly none: ShapeAnatomy = { base: undefined, nullable: false };
60
+ private static readonly cache = new WeakMap<object, ShapeParts>();
61
+ private static readonly none: ShapeParts = { base: undefined, nullable: false };
62
62
 
63
- static of(shape?: Shape): ShapeAnatomy {
63
+ static of(shape?: Shape): ShapeParts {
64
64
  if (!shape) return this.none;
65
- let a = this.cache.get(shape);
66
- if (!a) {
65
+ let parts = this.cache.get(shape);
66
+ if (!parts) {
67
67
  if (Array.isArray(shape.type)) {
68
68
  const baseType = shape.type.find((t) => t !== 'null');
69
69
  const base = { ...shape, type: baseType } as BaseShape;
70
70
  if ('enum' in base && base.enum) {
71
71
  (base as { enum: readonly (string | null)[] }).enum = base.enum.filter((v) => v !== null);
72
72
  }
73
- a = { base, nullable: true };
73
+ parts = { base, nullable: true };
74
74
  } else {
75
- a = { base: shape as BaseShape, nullable: false };
75
+ parts = { base: shape as BaseShape, nullable: false };
76
76
  }
77
- this.cache.set(shape, a);
77
+ this.cache.set(shape, parts);
78
78
  }
79
- return a;
79
+ return parts;
80
80
  }
81
81
 
82
82
  static isNullable(shape?: Shape): boolean {
@@ -0,0 +1,81 @@
1
+ import type { Fields } from '../field/Field.js';
2
+ import { isObject } from '../lib/utils.js';
3
+ import type { EntityAdapters } from './EntityAdapters.js';
4
+
5
+ type AdapterConfiguration = Record<string, unknown>;
6
+ type AdapterConfigurations = Record<string, AdapterConfiguration>;
7
+
8
+ export class EntityAdapterSet {
9
+ /**
10
+ * `{ sql: { body: { columnType: { pg: 'tsvector' } } } }`
11
+ */
12
+ private constructor(readonly adapters: EntityAdapters<Fields>) {}
13
+
14
+ /**
15
+ * Stops a malformed declaration from propagating, by checking its two levels.
16
+ * `of({ sql: { body: 3 } })` → passes; `of({ sql: 'oops' })` → throws, naming `adapters.sql`
17
+ */
18
+ static of(adapters?: EntityAdapters<Fields>): EntityAdapterSet {
19
+ if (!adapters) return new EntityAdapterSet({});
20
+
21
+ if (!isObject(adapters)) {
22
+ throw new Error(
23
+ `adapters: expected an object keyed by adapter name, got ${typeof adapters}.`,
24
+ );
25
+ }
26
+
27
+ for (const [adapter, fields] of Object.entries(adapters)) {
28
+ if (!isObject(fields)) {
29
+ throw new Error(
30
+ `adapters.${adapter}: expected an object keyed by field name, got ${typeof fields}. ` +
31
+ `What an adapter is handed is addressed by the field it applies to.`,
32
+ );
33
+ }
34
+ }
35
+
36
+ return new EntityAdapterSet(adapters);
37
+ }
38
+
39
+ /** Folds several into one, a later field entry replacing the earlier. Takes sets only, so */
40
+ static merged(adapterSets: readonly EntityAdapterSet[]): EntityAdapterSet {
41
+ const configurations: AdapterConfigurations = {};
42
+
43
+ for (const adapterSet of adapterSets)
44
+ for (const [adapter, configuration] of adapterSet.entries)
45
+ configurations[adapter] = { ...configurations[adapter], ...configuration };
46
+
47
+ return new EntityAdapterSet(configurations);
48
+ }
49
+
50
+ private get entries(): [string, AdapterConfiguration][] {
51
+ return Object.entries(this.adapters);
52
+ }
53
+
54
+ /** Collects every field addressed, so a declaration aimed at a missing field is caught. */
55
+ get fieldNames(): string[] {
56
+ return [
57
+ ...new Set(this.entries.flatMap(([, configuration]) => Object.keys(configuration))),
58
+ ];
59
+ }
60
+
61
+ /** Follows a derivation — `pick`, `omit` and `rename` are one gesture: what remains, and */
62
+ rename(transform: (key: string) => string | undefined): EntityAdapterSet {
63
+ const renamed: AdapterConfigurations = {};
64
+
65
+ for (const [adapter, configuration] of this.entries) {
66
+ const mapped: AdapterConfiguration = {};
67
+
68
+ for (const [key, entry] of Object.entries(configuration)) {
69
+ const newName = transform(key);
70
+
71
+ if (newName !== undefined) mapped[newName] = entry;
72
+ }
73
+
74
+ // Drop an adapter left with no field. `Post.pick('id')` would otherwise answer
75
+ // `{ sql: {} }` — a line the entity never wrote.
76
+ if (Object.keys(mapped).length) renamed[adapter] = mapped;
77
+ }
78
+
79
+ return new EntityAdapterSet(renamed);
80
+ }
81
+ }
@@ -0,0 +1,8 @@
1
+ import type { FieldName, Fields } from '../field/Field.js';
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-object-type
4
+ export interface FougereEntityAdapters<K extends string> {}
5
+
6
+ export type EntityAdapters<TFields extends Fields> = Readonly<
7
+ Partial<FougereEntityAdapters<FieldName<TFields>>>
8
+ >;
@@ -0,0 +1,16 @@
1
+ import type { FieldName, Fields } from '../field/Field.js';
2
+ import type { EntityAdapters } from './EntityAdapters.js';
3
+
4
+ // Ex: [['title', 'name'], ['id']]
5
+ export type CompositeUnique<TFields extends Fields> =
6
+ readonly (readonly FieldName<TFields>[])[];
7
+
8
+ export type PreviousNames<TFields extends Fields> = Readonly<
9
+ Partial<Record<FieldName<TFields>, string>>
10
+ >;
11
+
12
+ export interface EntityDeclarations<TFields extends Fields> {
13
+ adapters?: EntityAdapters<TFields>;
14
+ previous?: PreviousNames<TFields>;
15
+ unique?: CompositeUnique<TFields>;
16
+ }
package/src/entity.ts CHANGED
@@ -1,7 +1,12 @@
1
- import { type Fields } from './fields/Field.js';
2
- import { type EntityDeclarations } from './EntityDeclarations.js';
3
- import { Schema, type SchemaConstructor } from './schema/Schema.js';
1
+ import { type Fields } from './field/Field.js';
2
+ import { type EntityDeclarations } from './entity/EntityDeclarations.js';
3
+ import { Schema, type SchemaConstructor } from './Schema.js';
4
4
 
5
+ /**
6
+ * So the whole framework starts from one call: fields, and what they say about themselves.
7
+ * FR : pour que tout parte d'un appel : les champs, et ce qu'ils disent d'eux-mêmes.
8
+ * `class Post extends entity({ id: primary(), title: text() }, { unique: [['title']] }) {}`
9
+ */
5
10
  export function entity<TFields extends Fields>(
6
11
  fields: TFields,
7
12
  declarations: EntityDeclarations<TFields> = {},
@@ -0,0 +1,61 @@
1
+ import type { Shape } from '../axis/shape/Shape.js';
2
+ import type { RoleRules } from '../axis/role/Role.js';
3
+ import type { LifecycleRules } from '../axis/lifecycle/Lifecycle.js';
4
+ import type { BoundaryRef } from '../axis/boundary/Boundary.js';
5
+ import type { Meta } from '../axis/Meta.js';
6
+ import type { Axis } from '../axis/Axis.js';
7
+ import { FieldDeclarationValidator } from '../validator/FieldDeclarationValidator.js';
8
+ import { FieldValueValidator } from '../validator/FieldValueValidator.js';
9
+
10
+ export type Fields = Record<string, Field>;
11
+
12
+ /** A key of a field set — the name a declaration addresses a field by. */
13
+ export type FieldName<TFields extends Fields> = Extract<keyof TFields, string>;
14
+
15
+ /** The carrier, the three extension axes, the annotation — the five the validator reads. */
16
+ export type FieldDeclaration = Pick<Field, 'shape' | Axis['slot'] | 'meta'>;
17
+
18
+ export class Field<T = unknown> {
19
+ readonly shape: Shape;
20
+ readonly role?: RoleRules;
21
+ readonly lifecycle?: LifecycleRules;
22
+ readonly boundary?: BoundaryRef;
23
+ readonly meta?: Meta;
24
+
25
+ declare readonly _type?: T;
26
+
27
+ constructor(init: FieldDeclaration, key?: string) {
28
+ const verdict = FieldDeclarationValidator.of(init).verdict;
29
+
30
+ if (!verdict.success) {
31
+ throw new Error(
32
+ `${key ? `Field '${key}': ` : ''}` +
33
+ verdict.errors.map((e) => `${e.path}: ${e.message}`).join('; '),
34
+ );
35
+ }
36
+
37
+ this.shape = init.shape;
38
+ this.role = init.role;
39
+ this.lifecycle = init.lifecycle;
40
+ this.boundary = init.boundary;
41
+ this.meta = init.meta;
42
+
43
+ const create = this.lifecycle?.create;
44
+ if (typeof create === 'object' && create !== null && 'value' in create) {
45
+ const checked = FieldValueValidator.of(this).validate(create.value);
46
+ if ('error' in checked)
47
+ throw new Error(
48
+ `${key ? `Field '${key}': ` : ''}the declared default ${JSON.stringify(create.value)} ` +
49
+ `is not a legal value for it — ${checked.error}.`,
50
+ );
51
+ }
52
+ }
53
+
54
+ static is(value: unknown): value is Field {
55
+ return FieldDeclarationValidator.of(value).verdict.success;
56
+ }
57
+
58
+ with<U = T>(overrides: Partial<FieldDeclaration>): Field<U> {
59
+ return new Field<U>({ ...this, ...overrides });
60
+ }
61
+ }
@@ -0,0 +1,75 @@
1
+ import { Role } from '../axis/role/Role.js';
2
+ import type { CompositeUnique } from '../entity/EntityDeclarations.js';
3
+ import { Field, type FieldName, type Fields } from './Field.js';
4
+
5
+ export class FieldSet<TFields extends Fields = Fields> {
6
+ private constructor(private readonly fields: TFields) {}
7
+
8
+ static of<TFields extends Fields>(fields: TFields): FieldSet<TFields> {
9
+ return new FieldSet(fields);
10
+ }
11
+
12
+ /**
13
+ * So a declared unique lands where it belongs: on the field, or on the schema.
14
+ * FR : pour qu'une unicité déclarée atterrisse sur le champ ou sur le schéma.
15
+ * `declaring(fields, [['email'], ['listId', 'docId']])`
16
+ * → `email` carries `role.unique`, the pair becomes a schema constraint
17
+ */
18
+ static declaring<TFields extends Fields>(
19
+ declared: TFields,
20
+ unique?: CompositeUnique<TFields>,
21
+ ): { fields: TFields; unique: CompositeUnique<TFields> | undefined } {
22
+ const fields: Fields = {};
23
+ for (const [key, field] of Object.entries(declared))
24
+ fields[key] = new Field(field, key);
25
+
26
+ const composite: string[][] = [];
27
+ for (const group of unique ?? []) {
28
+ const missing = group.filter((key) => !(key in fields));
29
+ if (missing.length)
30
+ throw new Error(
31
+ `unique: [${group.join(', ')}] names ` +
32
+ `${missing.map((key) => `'${key}'`).join(', ')}, which the entity does not declare.`,
33
+ );
34
+
35
+ if (group.length === 1) {
36
+ const key = group[0]!;
37
+ fields[key] = fields[key]!.with({ role: { ...fields[key]!.role, unique: true } });
38
+ continue;
39
+ }
40
+ composite.push([...group]);
41
+ }
42
+
43
+ return {
44
+ fields: fields as TFields,
45
+ unique: deduplicated(composite) as CompositeUnique<TFields> | undefined,
46
+ };
47
+ }
48
+
49
+ /** A second `primary` is refused, naming both, rather than the first winning silently. */
50
+ get primary(): FieldName<TFields> | undefined {
51
+ const primaries = Object.entries(this.fields)
52
+ .filter(([, field]) => Role.of(field).isPrimary)
53
+ .map(([name]) => name);
54
+
55
+ if (primaries.length > 1) {
56
+ throw new Error(
57
+ `FieldSet.primary: ${primaries.map((name) => JSON.stringify(name)).join(', ')} all declare ` +
58
+ '`primary`; a field set can have only one primary field.',
59
+ );
60
+ }
61
+
62
+ return primaries[0] as FieldName<TFields> | undefined;
63
+ }
64
+
65
+ }
66
+
67
+ /** The order is part of a group: `['a','b']` and `['b','a']` are two constraints. */
68
+ export function deduplicated(
69
+ groups: readonly (readonly string[])[],
70
+ ): readonly (readonly string[])[] | undefined {
71
+ const seen = new Map<string, readonly string[]>();
72
+ for (const group of groups) seen.set(JSON.stringify(group), group);
73
+
74
+ return seen.size ? [...seen.values()] : undefined;
75
+ }
package/src/index.ts CHANGED
@@ -1,31 +1,39 @@
1
- export { Field, type Fields } from './fields/Field.js';
2
- export { FieldSet } from './fields/FieldSet.js';
3
- export { type Shape, Anatomy } from './axis/shape/Shape.js';
4
- export { type StringFormat, type FormatPredicate, Formats } from './axis/shape/Formats.js';
1
+ export { Field, type Fields } from './field/Field.js';
2
+ export { FieldSet } from './field/FieldSet.js';
3
+ export { type Shape, Shapes } from './axis/shape/Shape.js';
4
+ export {
5
+ type StringFormat,
6
+ type FormatPredicate,
7
+ Formats,
8
+ } from './axis/shape/Formats.js';
5
9
  export { Role, type RoleRules } from './axis/role/Role.js';
6
10
  export { type Relation, type EntityConstructor } from './axis/role/Relation.js';
7
11
  export { Lifecycle, type LifecycleRules } from './axis/lifecycle/Lifecycle.js';
8
12
  export { type GeneratorRef, Generators } from './axis/lifecycle/Generators.js';
9
13
  export { Clock } from './axis/lifecycle/Clock.js';
10
- export { Boundary, type BoundaryRules, type BoundaryRef } from './axis/boundary/Boundary.js';
14
+ export {
15
+ Boundary,
16
+ type BoundaryRules,
17
+ type BoundaryRef,
18
+ } from './axis/boundary/Boundary.js';
11
19
  export { type Decoder, type Encoder, Boundaries } from './axis/boundary/Boundaries.js';
12
20
  export { type Meta } from './axis/Meta.js';
13
- export { FieldGroup } from './constraint/FieldGroup.js';
14
- export { Unique } from './constraint/Unique.js';
15
21
 
16
- export { Schema, ANONYMOUS_SCHEMA_NAME, type SchemaConstructor } from './schema/Schema.js';
17
- export { SchemaDerivation } from './schema/SchemaDerivation.js';
18
- export { isObject } from './judge/ValueForm.js';
19
- export { Cases, type Case } from './judge/Cases.js';
20
- export { RowRefusal } from './judge/RowRefusal.js';
21
- export { type SchemaView, type Row, type PartialRow } from './schema/SchemaView.js';
22
- export { type FougereEntityAdapters, type EntityAdapters } from './EntityAdapters.js';
22
+ export { Schema, ANONYMOUS_SCHEMA_NAME, type SchemaConstructor } from './Schema.js';
23
+ export { SchemaDerivation } from './SchemaDerivation.js';
24
+ export { Cases, type ValidationCase } from './projection/Cases.js';
25
+ export { InputRefusal } from './validator/InputRefusal.js';
26
+ export { type SchemaView, type Values, type PartialValues } from './SchemaView.js';
27
+ export {
28
+ type FougereEntityAdapters,
29
+ type EntityAdapters,
30
+ } from './entity/EntityAdapters.js';
23
31
  export {
24
32
  type EntityDeclarations,
25
33
  type CompositeUnique,
26
34
  type PreviousNames,
27
- } from './EntityDeclarations.js';
28
- export { lowerFirst, upperFirst } from './utils.js';
35
+ } from './entity/EntityDeclarations.js';
36
+ export { lowerFirst, upperFirst, isObject } from './lib/utils.js';
29
37
 
30
38
  export { primary } from './vocabulary/primary.js';
31
39
  export { text } from './vocabulary/text.js';
@@ -51,19 +59,33 @@ export { json } from './vocabulary/json.js';
51
59
 
52
60
  export { applyCreate, applyUpdate } from './axis/lifecycle/apply.js';
53
61
  export { Visibility } from './projection/Visibility.js';
54
- export { Card } from './card/Card.js';
55
- export { Bundle } from './card/Bundle.js';
56
- export type { Change, Diff, DiffOptions, SetDiff, SetDiffOptions, RenameCandidate, TypeSet } from './card/diff.js';
57
- export { EntityTypeSource, type EntityTypeSourceOptions } from './card/EntityTypeSource.js';
58
- export { FacadeTypeSource, type FacadeTypeSourceOptions, type OpDescriptor } from './card/FacadeTypeSource.js';
59
- export { type SchemaBundle, type SchemaDescriptor, type FieldDescriptor, type FieldExtension, type RoleDescriptor, type RelationDescriptor } from './card/Descriptor.js';
60
- export { type SchemaOrCard, schemaOf, fieldsOf } from './projection/SchemaOrCard.js';
62
+ export { Card } from './projection/card/Card.js';
63
+ export { Bundle } from './projection/card/Bundle.js';
64
+ export type {
65
+ Change,
66
+ Diff,
67
+ DiffOptions,
68
+ SetDiff,
69
+ SetDiffOptions,
70
+ RenameCandidate,
71
+ TypeSet,
72
+ } from './projection/card/diff.js';
73
+ export {
74
+ type SchemaBundle,
75
+ type SchemaDescriptor,
76
+ type FieldDescriptor,
77
+ type FieldExtension,
78
+ type RoleDescriptor,
79
+ type RelationDescriptor,
80
+ } from './projection/card/Descriptor.js';
61
81
  export type { StandardSchemaV1 } from './projection/standard.js';
62
82
 
63
83
  export { entity } from './entity.js';
64
84
 
65
- export { FieldJudge } from './judge/FieldJudge.js';
66
- export { ValueJudge } from './judge/ValueJudge.js';
67
- export { RowJudge } from './judge/RowJudge.js';
68
- export { type ValidateOptions } from './judge/options.js';
69
- export { type ValidationError, type ValidationResult, type Checked } from './judge/result.js';
85
+ export { FieldDeclarationValidator } from './validator/FieldDeclarationValidator.js';
86
+ export { FieldValueValidator } from './validator/FieldValueValidator.js';
87
+ export { InputValidator } from './validator/InputValidator.js';
88
+ export { AdapterFieldValidator } from './validator/AdapterFieldValidator.js';
89
+ export { Registry } from './lib/Registry.js';
90
+ export { type ValidateOptions } from './validator/InputValidator.js';
91
+ export { type ValidationError, type ValidationResult, type Checked } from './validation.js';
@@ -0,0 +1,37 @@
1
+ export class Registry<T> {
2
+ private readonly entries: Map<string, T>;
3
+
4
+ /** `Unknown boundary decoder 'celsius' — call Boundaries.decoders.register(name, fn).` */
5
+ constructor(
6
+ private readonly label: string,
7
+ private readonly hint?: string,
8
+ builtins?: Iterable<readonly [string, T]>,
9
+ ) {
10
+ this.entries = new Map(builtins);
11
+ }
12
+
13
+ register(name: string, value: T): T {
14
+ this.entries.set(name, value);
15
+
16
+ return value;
17
+ }
18
+
19
+ find(name: string): T | undefined {
20
+ return this.entries.get(name);
21
+ }
22
+
23
+ resolve(name: string, path?: string): T {
24
+ const found = this.find(name);
25
+
26
+ if (found !== undefined) return found;
27
+
28
+ throw new Error(
29
+ `${path ? `${path}: ` : ''}Unknown ${this.label} '${name}'${this.hint ? ` — ${this.hint}` : ''}. ` +
30
+ `This process answers ${this.names.join(', ') || 'nothing yet'}.`,
31
+ );
32
+ }
33
+
34
+ private get names(): string[] {
35
+ return [...this.entries.keys()];
36
+ }
37
+ }
@@ -11,3 +11,6 @@ export function clean<T extends Record<string, unknown>>(obj: T): T {
11
11
 
12
12
  return obj;
13
13
  }
14
+
15
+ export const isObject = (value: unknown): value is Record<string, unknown> =>
16
+ typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -0,0 +1,176 @@
1
+ import { Shapes } from '../axis/shape/Shape.js';
2
+ import { Boundary } from '../axis/boundary/Boundary.js';
3
+ import { Lifecycle } from '../axis/lifecycle/Lifecycle.js';
4
+ import { Role } from '../axis/role/Role.js';
5
+ import type { Field } from '../field/Field.js';
6
+ import type { SchemaView } from '../SchemaView.js';
7
+ import { InputValidator } from '../validator/InputValidator.js';
8
+ import { InputRefusal } from '../validator/InputRefusal.js';
9
+
10
+ /**
11
+ * One input, and what the validator must answer.
12
+ *
13
+ * `expect` carries the PATH of the field at fault and never the message. We know which
14
+ * field must be refused because we are the ones who broke it; naming the message would
15
+ * make this list a second validator, and the two would then have to be kept in step by hand —
16
+ * which is the duplication the whole thing exists to remove.
17
+ */
18
+ export interface ValidationCase {
19
+ /** What this input does, in one clause — carried into the assertion so a failure reads. */
20
+ why: string;
21
+ input: unknown;
22
+ /** Judged as an update rather than a creation. */
23
+ patch: boolean;
24
+ expect: 'accept' | { reject: string };
25
+ }
26
+
27
+ /**
28
+ * So every field has at least one case, whatever else it declares.
29
+ * FR : pour que chaque champ ait un cas, quoi qu'il déclare par ailleurs.
30
+ * `text()` → `42`; `number()` → `'not-a-value-of-this-shape'`
31
+ */
32
+ function wrongTypeFor(field: Field): unknown {
33
+ const declared = (field.shape as { type?: string | readonly string[] }).type;
34
+ const names = Array.isArray(declared) ? declared : [declared];
35
+ return names.includes('string') ? 42 : 'not-a-value-of-this-shape';
36
+ }
37
+
38
+ /**
39
+ * So a bound stated in a shape produces the case that breaks it, and none is invented.
40
+ * FR : pour qu'une borne énoncée produise le cas qui la casse, et aucun autre.
41
+ * `text({ maxLength: 3 })` → one case with `'xxxx'`; `text()` → no case
42
+ */
43
+ function outOfBoundsFor(field: Field): { why: string; value: unknown }[] {
44
+ // Through `Shapes`, not a cast: the cast admitted any keyword, and it declared
45
+ // `maxItems` — which no branch below ever produced. In the one function whose job is
46
+ // to be exhaustive, a bound the type does not carry is a case nobody notices missing.
47
+ const shape = Shapes.of(field.shape).base;
48
+ const cases: { why: string; value: unknown }[] = [];
49
+ if (shape?.type === 'string') {
50
+ if (typeof shape.minLength === 'number' && shape.minLength > 0)
51
+ cases.push({ why: 'shorter than min', value: 'x'.repeat(shape.minLength - 1) });
52
+ if (typeof shape.maxLength === 'number')
53
+ cases.push({ why: 'longer than max', value: 'x'.repeat(shape.maxLength + 1) });
54
+ if (shape.enum) cases.push({ why: 'outside the stated set', value: '__not-in-enum__' });
55
+ }
56
+ if (shape?.type === 'number' || shape?.type === 'integer') {
57
+ if (typeof shape.minimum === 'number')
58
+ cases.push({ why: 'below minimum', value: shape.minimum - 1 });
59
+ if (typeof shape.maximum === 'number')
60
+ cases.push({ why: 'above maximum', value: shape.maximum + 1 });
61
+ }
62
+ if (shape?.type === 'array') {
63
+ if (typeof shape.minItems === 'number' && shape.minItems > 0)
64
+ cases.push({ why: 'fewer items than min', value: [] });
65
+ if (typeof shape.maxItems === 'number')
66
+ cases.push({
67
+ why: 'more items than max',
68
+ value: Array.from({ length: shape.maxItems + 1 }, () => null),
69
+ });
70
+ }
71
+ return cases;
72
+ }
73
+
74
+ /**
75
+ * The decision table, enumerated on the entity's own fields.
76
+ *
77
+ * Use the complete entity so a derived view cannot hide a divergent field. The caller
78
+ * supplies a valid row; generating data belongs to the testing package.
79
+ */
80
+ export class Cases {
81
+ private constructor(readonly all: readonly ValidationCase[]) {}
82
+
83
+ static of(entity: SchemaView, valid: Record<string, unknown>): Cases {
84
+ return new Cases(enumerate(entity, valid));
85
+ }
86
+
87
+ static holds(
88
+ expected: ValidationCase['expect'],
89
+ result: { success: boolean; errors?: { path: string }[] },
90
+ ): boolean {
91
+ if (expected === 'accept') return result.success;
92
+ if (result.success) return false;
93
+ return (result.errors ?? []).some((error) => error.path === expected.reject);
94
+ }
95
+
96
+ static get refusals(): string[] {
97
+ return Object.values(InputRefusal);
98
+ }
99
+
100
+ [Symbol.iterator](): Iterator<ValidationCase> {
101
+ return this.all[Symbol.iterator]();
102
+ }
103
+ }
104
+
105
+ /**
106
+ * So the four axes decide which cases exist, and no list of them is maintained by hand.
107
+ * FR : pour que les axes décident des cas, sans liste tenue à la main.
108
+ */
109
+ function enumerate(entity: SchemaView, valid: Record<string, unknown>): ValidationCase[] {
110
+ const fields = entity.getFields();
111
+ const validator = InputValidator.of(fields);
112
+ const cases: ValidationCase[] = [];
113
+ const withField = (name: string, value: unknown) => ({ ...valid, [name]: value });
114
+
115
+ cases.push({ why: 'a valid input', input: valid, patch: false, expect: 'accept' });
116
+ cases.push({
117
+ why: 'a key outside the contract',
118
+ input: { ...valid, __unknown__: 'x' },
119
+ patch: false,
120
+ expect: { reject: '__unknown__' },
121
+ });
122
+ cases.push({
123
+ why: 'not an object at all',
124
+ input: 'a string',
125
+ patch: false,
126
+ expect: { reject: '.' },
127
+ });
128
+
129
+ for (const [name, field] of Object.entries(fields) as [string, Field][]) {
130
+ // A reference names a row that must exist; the caller supplied its id and we do not
131
+ // get to invent a second one, so the only case we can state about it is the bound one.
132
+ const isRef = Role.of(field).isReference;
133
+
134
+ if (validator.onAbsent(field) === null && name in valid) {
135
+ const input = { ...valid };
136
+ delete input[name];
137
+ cases.push({ why: `${name} absent`, input, patch: false, expect: { reject: name } });
138
+ }
139
+
140
+ if (Boundary.of(field).readOnly) {
141
+ cases.push({
142
+ why: `${name} supplied although read-only`,
143
+ input: withField(name, wrongTypeFor(field)),
144
+ patch: false,
145
+ expect: { reject: name },
146
+ });
147
+ }
148
+
149
+ if (Lifecycle.of(field).immutable && !Role.of(field).isPrimary) {
150
+ cases.push({
151
+ why: `${name} supplied on an update`,
152
+ input: { [name]: valid[name] ?? wrongTypeFor(field) },
153
+ patch: true,
154
+ expect: { reject: name },
155
+ });
156
+ }
157
+
158
+ if (name in valid && !isRef) {
159
+ cases.push({
160
+ why: `${name} of the wrong type`,
161
+ input: withField(name, wrongTypeFor(field)),
162
+ patch: false,
163
+ expect: { reject: name },
164
+ });
165
+ for (const { why, value } of outOfBoundsFor(field))
166
+ cases.push({
167
+ why: `${name} ${why}`,
168
+ input: withField(name, value),
169
+ patch: false,
170
+ expect: { reject: name },
171
+ });
172
+ }
173
+ }
174
+
175
+ return cases;
176
+ }