@fougere/schema 0.8.2-alpha.0 → 0.8.4-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 (219) hide show
  1. package/dist/Schema.d.ts +3 -6
  2. package/dist/Schema.d.ts.map +1 -1
  3. package/dist/Schema.js +9 -17
  4. package/dist/Schema.js.map +1 -1
  5. package/dist/SchemaDefinition.d.ts +24 -6
  6. package/dist/SchemaDefinition.d.ts.map +1 -1
  7. package/dist/SchemaDefinition.js +76 -44
  8. package/dist/SchemaDefinition.js.map +1 -1
  9. package/dist/SchemaView.d.ts +1 -1
  10. package/dist/SchemaView.d.ts.map +1 -1
  11. package/dist/axis/Axis.d.ts +1 -1
  12. package/dist/axis/Axis.d.ts.map +1 -1
  13. package/dist/axis/boundary/Boundaries.d.ts +5 -0
  14. package/dist/axis/boundary/Boundaries.d.ts.map +1 -1
  15. package/dist/axis/boundary/Boundaries.js.map +1 -1
  16. package/dist/axis/boundary/Boundary.d.ts +5 -4
  17. package/dist/axis/boundary/Boundary.d.ts.map +1 -1
  18. package/dist/axis/boundary/Boundary.js +4 -5
  19. package/dist/axis/boundary/Boundary.js.map +1 -1
  20. package/dist/axis/boundary/BoundaryAxis.js +2 -2
  21. package/dist/axis/boundary/BoundaryAxis.js.map +1 -1
  22. package/dist/axis/lifecycle/Lifecycle.d.ts +5 -3
  23. package/dist/axis/lifecycle/Lifecycle.d.ts.map +1 -1
  24. package/dist/axis/lifecycle/Lifecycle.js +5 -1
  25. package/dist/axis/lifecycle/Lifecycle.js.map +1 -1
  26. package/dist/axis/lifecycle/LifecycleAxis.js +6 -6
  27. package/dist/axis/lifecycle/LifecycleAxis.js.map +1 -1
  28. package/dist/axis/lifecycle/apply.d.ts +6 -6
  29. package/dist/axis/lifecycle/apply.d.ts.map +1 -1
  30. package/dist/axis/lifecycle/apply.js +10 -9
  31. package/dist/axis/lifecycle/apply.js.map +1 -1
  32. package/dist/axis/role/Role.d.ts +8 -5
  33. package/dist/axis/role/Role.d.ts.map +1 -1
  34. package/dist/axis/role/Role.js +7 -0
  35. package/dist/axis/role/Role.js.map +1 -1
  36. package/dist/axis/role/RoleAxis.js +9 -9
  37. package/dist/axis/role/RoleAxis.js.map +1 -1
  38. package/dist/axis/shape/Shape.d.ts +14 -0
  39. package/dist/axis/shape/Shape.d.ts.map +1 -1
  40. package/dist/axis/shape/Shape.js +43 -34
  41. package/dist/axis/shape/Shape.js.map +1 -1
  42. package/dist/entity/EntityAdapterSet.d.ts +2 -2
  43. package/dist/entity/EntityAdapterSet.d.ts.map +1 -1
  44. package/dist/entity/EntityAdapterSet.js +2 -2
  45. package/dist/entity/EntityAdapterSet.js.map +1 -1
  46. package/dist/entity.d.ts +2 -2
  47. package/dist/entity.js +2 -2
  48. package/dist/field/Field.d.ts.map +1 -1
  49. package/dist/field/Field.js +2 -1
  50. package/dist/field/Field.js.map +1 -1
  51. package/dist/field/FieldSet.d.ts +4 -7
  52. package/dist/field/FieldSet.d.ts.map +1 -1
  53. package/dist/field/FieldSet.js +4 -15
  54. package/dist/field/FieldSet.js.map +1 -1
  55. package/dist/index.d.ts +2 -2
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +1 -0
  58. package/dist/index.js.map +1 -1
  59. package/dist/lib/validation.d.ts +32 -0
  60. package/dist/lib/validation.d.ts.map +1 -0
  61. package/dist/lib/validation.js +6 -0
  62. package/dist/lib/validation.js.map +1 -0
  63. package/dist/projection/Cases.d.ts +1 -1
  64. package/dist/projection/Cases.d.ts.map +1 -1
  65. package/dist/projection/Cases.js +11 -7
  66. package/dist/projection/Cases.js.map +1 -1
  67. package/dist/projection/Visibility.js +1 -1
  68. package/dist/projection/Visibility.js.map +1 -1
  69. package/dist/projection/card/Bundle.d.ts.map +1 -1
  70. package/dist/projection/card/Bundle.js +1 -4
  71. package/dist/projection/card/Bundle.js.map +1 -1
  72. package/dist/projection/card/Card.d.ts +7 -1
  73. package/dist/projection/card/Card.d.ts.map +1 -1
  74. package/dist/projection/card/Card.js +25 -133
  75. package/dist/projection/card/Card.js.map +1 -1
  76. package/dist/projection/card/Descriptor.d.ts +2 -2
  77. package/dist/projection/card/Descriptor.d.ts.map +1 -1
  78. package/dist/projection/card/admission.d.ts +12 -11
  79. package/dist/projection/card/admission.d.ts.map +1 -1
  80. package/dist/projection/card/admission.js +13 -11
  81. package/dist/projection/card/admission.js.map +1 -1
  82. package/dist/projection/card/diff.d.ts +7 -1
  83. package/dist/projection/card/diff.d.ts.map +1 -1
  84. package/dist/projection/card/diff.js +111 -1
  85. package/dist/projection/card/diff.js.map +1 -1
  86. package/dist/validator/FieldDeclarationValidator.d.ts +1 -1
  87. package/dist/validator/FieldDeclarationValidator.d.ts.map +1 -1
  88. package/dist/validator/FieldDeclarationValidator.js +4 -4
  89. package/dist/validator/FieldDeclarationValidator.js.map +1 -1
  90. package/dist/validator/FieldValueValidator.d.ts +6 -1
  91. package/dist/validator/FieldValueValidator.d.ts.map +1 -1
  92. package/dist/validator/FieldValueValidator.js +36 -7
  93. package/dist/validator/FieldValueValidator.js.map +1 -1
  94. package/dist/validator/InputValidator.d.ts +1 -1
  95. package/dist/validator/InputValidator.d.ts.map +1 -1
  96. package/dist/validator/InputValidator.js +8 -18
  97. package/dist/validator/InputValidator.js.map +1 -1
  98. package/dist/vocabulary/preset/created.d.ts +0 -5
  99. package/dist/vocabulary/preset/created.d.ts.map +1 -1
  100. package/dist/vocabulary/preset/created.js +0 -5
  101. package/dist/vocabulary/preset/created.js.map +1 -1
  102. package/dist/vocabulary/preset/email.d.ts +0 -5
  103. package/dist/vocabulary/preset/email.d.ts.map +1 -1
  104. package/dist/vocabulary/preset/email.js +0 -5
  105. package/dist/vocabulary/preset/email.js.map +1 -1
  106. package/dist/vocabulary/preset/updated.d.ts +0 -5
  107. package/dist/vocabulary/preset/updated.d.ts.map +1 -1
  108. package/dist/vocabulary/preset/updated.js +0 -5
  109. package/dist/vocabulary/preset/updated.js.map +1 -1
  110. package/dist/vocabulary/preset/url.d.ts +0 -5
  111. package/dist/vocabulary/preset/url.d.ts.map +1 -1
  112. package/dist/vocabulary/preset/url.js +0 -5
  113. package/dist/vocabulary/preset/url.js.map +1 -1
  114. package/dist/vocabulary/primitive/bool.d.ts +0 -5
  115. package/dist/vocabulary/primitive/bool.d.ts.map +1 -1
  116. package/dist/vocabulary/primitive/bool.js +0 -5
  117. package/dist/vocabulary/primitive/bool.js.map +1 -1
  118. package/dist/vocabulary/primitive/date.d.ts +3 -3
  119. package/dist/vocabulary/primitive/date.js +3 -3
  120. package/dist/vocabulary/primitive/json.js +3 -3
  121. package/dist/vocabulary/primitive/list.d.ts +3 -3
  122. package/dist/vocabulary/primitive/list.js +3 -3
  123. package/dist/vocabulary/primitive/many.d.ts +3 -3
  124. package/dist/vocabulary/primitive/many.js +3 -3
  125. package/dist/vocabulary/primitive/number.d.ts +0 -5
  126. package/dist/vocabulary/primitive/number.d.ts.map +1 -1
  127. package/dist/vocabulary/primitive/number.js +0 -5
  128. package/dist/vocabulary/primitive/number.js.map +1 -1
  129. package/dist/vocabulary/primitive/oneOf.js +3 -3
  130. package/dist/vocabulary/primitive/primary.js +3 -3
  131. package/dist/vocabulary/primitive/ref.d.ts +0 -5
  132. package/dist/vocabulary/primitive/ref.d.ts.map +1 -1
  133. package/dist/vocabulary/primitive/ref.js +0 -5
  134. package/dist/vocabulary/primitive/ref.js.map +1 -1
  135. package/dist/vocabulary/primitive/text.d.ts +0 -5
  136. package/dist/vocabulary/primitive/text.d.ts.map +1 -1
  137. package/dist/vocabulary/primitive/text.js +0 -5
  138. package/dist/vocabulary/primitive/text.js.map +1 -1
  139. package/dist/vocabulary/rule/immutable.d.ts +0 -5
  140. package/dist/vocabulary/rule/immutable.d.ts.map +1 -1
  141. package/dist/vocabulary/rule/immutable.js +0 -5
  142. package/dist/vocabulary/rule/immutable.js.map +1 -1
  143. package/dist/vocabulary/rule/indexed.d.ts +0 -5
  144. package/dist/vocabulary/rule/indexed.d.ts.map +1 -1
  145. package/dist/vocabulary/rule/indexed.js +0 -5
  146. package/dist/vocabulary/rule/indexed.js.map +1 -1
  147. package/dist/vocabulary/rule/nullable.d.ts +3 -3
  148. package/dist/vocabulary/rule/nullable.js +3 -3
  149. package/dist/vocabulary/rule/optional.d.ts +3 -3
  150. package/dist/vocabulary/rule/optional.js +3 -3
  151. package/dist/vocabulary/rule/readOnly.d.ts +3 -3
  152. package/dist/vocabulary/rule/readOnly.js +4 -4
  153. package/dist/vocabulary/rule/readOnly.js.map +1 -1
  154. package/dist/vocabulary/rule/unique.d.ts +0 -5
  155. package/dist/vocabulary/rule/unique.d.ts.map +1 -1
  156. package/dist/vocabulary/rule/unique.js +0 -5
  157. package/dist/vocabulary/rule/unique.js.map +1 -1
  158. package/dist/vocabulary/rule/writeOnly.d.ts +2 -2
  159. package/dist/vocabulary/rule/writeOnly.js +3 -3
  160. package/dist/vocabulary/rule/writeOnly.js.map +1 -1
  161. package/dist/vocabulary/vocabulary.d.ts +2 -2
  162. package/dist/vocabulary/vocabulary.js +5 -5
  163. package/package.json +1 -1
  164. package/src/Schema.ts +14 -20
  165. package/src/SchemaDefinition.ts +74 -48
  166. package/src/SchemaView.ts +1 -1
  167. package/src/axis/Axis.ts +1 -1
  168. package/src/axis/boundary/Boundaries.ts +5 -0
  169. package/src/axis/boundary/Boundary.ts +7 -9
  170. package/src/axis/boundary/BoundaryAxis.ts +2 -2
  171. package/src/axis/lifecycle/Lifecycle.ts +9 -4
  172. package/src/axis/lifecycle/LifecycleAxis.ts +7 -7
  173. package/src/axis/lifecycle/apply.ts +10 -9
  174. package/src/axis/role/Role.ts +14 -5
  175. package/src/axis/role/RoleAxis.ts +10 -10
  176. package/src/axis/shape/Shape.ts +61 -37
  177. package/src/entity/EntityAdapterSet.ts +2 -2
  178. package/src/entity.ts +2 -2
  179. package/src/field/Field.ts +2 -1
  180. package/src/field/FieldSet.ts +6 -20
  181. package/src/index.ts +2 -2
  182. package/src/lib/validation.ts +25 -0
  183. package/src/projection/Cases.ts +13 -8
  184. package/src/projection/Visibility.ts +1 -1
  185. package/src/projection/card/Bundle.ts +1 -4
  186. package/src/projection/card/Card.ts +26 -159
  187. package/src/projection/card/Descriptor.ts +2 -10
  188. package/src/projection/card/admission.ts +14 -12
  189. package/src/projection/card/diff.ts +142 -1
  190. package/src/validator/FieldDeclarationValidator.ts +5 -5
  191. package/src/validator/FieldValueValidator.ts +45 -12
  192. package/src/validator/InputValidator.ts +8 -18
  193. package/src/vocabulary/preset/created.ts +0 -5
  194. package/src/vocabulary/preset/email.ts +0 -5
  195. package/src/vocabulary/preset/updated.ts +0 -5
  196. package/src/vocabulary/preset/url.ts +0 -5
  197. package/src/vocabulary/primitive/bool.ts +0 -5
  198. package/src/vocabulary/primitive/date.ts +3 -3
  199. package/src/vocabulary/primitive/json.ts +3 -3
  200. package/src/vocabulary/primitive/list.ts +3 -3
  201. package/src/vocabulary/primitive/many.ts +3 -3
  202. package/src/vocabulary/primitive/number.ts +0 -5
  203. package/src/vocabulary/primitive/oneOf.ts +3 -3
  204. package/src/vocabulary/primitive/primary.ts +3 -3
  205. package/src/vocabulary/primitive/ref.ts +0 -5
  206. package/src/vocabulary/primitive/text.ts +0 -5
  207. package/src/vocabulary/rule/immutable.ts +0 -5
  208. package/src/vocabulary/rule/indexed.ts +0 -5
  209. package/src/vocabulary/rule/nullable.ts +3 -3
  210. package/src/vocabulary/rule/optional.ts +3 -3
  211. package/src/vocabulary/rule/readOnly.ts +4 -4
  212. package/src/vocabulary/rule/unique.ts +0 -5
  213. package/src/vocabulary/rule/writeOnly.ts +3 -3
  214. package/src/vocabulary/vocabulary.ts +5 -5
  215. package/dist/validation.d.ts +0 -17
  216. package/dist/validation.d.ts.map +0 -1
  217. package/dist/validation.js +0 -2
  218. package/dist/validation.js.map +0 -1
  219. package/src/validation.ts +0 -10
@@ -2,7 +2,7 @@ import { Boundary } from '../axis/boundary/Boundary.js';
2
2
  import { Lifecycle } from '../axis/lifecycle/Lifecycle.js';
3
3
  import { Role } from '../axis/role/Role.js';
4
4
  import type { Field, Fields } from '../field/Field.js';
5
- import type { ValidationError, ValidationResult } from '../validation.js';
5
+ import type { ValidationError, ValidationResult } from '../lib/validation.js';
6
6
  import { FieldValueValidator } from './FieldValueValidator.js';
7
7
  import { InputRefusal } from './InputRefusal.js';
8
8
 
@@ -32,7 +32,7 @@ export class InputValidator {
32
32
  if (typeof input !== 'object' || input === null) {
33
33
  return {
34
34
  success: false,
35
- errors: [{ path: '.', message: InputRefusal.notAnObject }],
35
+ errors: [{ path: [], message: InputRefusal.notAnObject }],
36
36
  };
37
37
  }
38
38
 
@@ -42,12 +42,12 @@ export class InputValidator {
42
42
 
43
43
  for (const key of Object.keys(data)) {
44
44
  if (!Object.hasOwn(this.fields, key)) {
45
- errors.push({ path: key, message: InputRefusal.unknownField });
45
+ errors.push({ path: [key], message: InputRefusal.unknownField });
46
46
  }
47
47
  }
48
48
 
49
49
  for (const [key, field] of Object.entries(this.fields)) {
50
- const path = key;
50
+ const path = [key];
51
51
  const value = data[key];
52
52
 
53
53
  if (value === undefined) {
@@ -61,8 +61,7 @@ export class InputValidator {
61
61
  continue;
62
62
  }
63
63
 
64
- const boundary = Boundary.of(field);
65
- if (boundary.readOnly) {
64
+ if (Boundary.of(field).readOnly) {
66
65
  errors.push({ path, message: InputRefusal.readOnly });
67
66
  continue;
68
67
  }
@@ -71,18 +70,9 @@ export class InputValidator {
71
70
  continue;
72
71
  }
73
72
 
74
- const checked = FieldValueValidator.of(field).validate(value);
75
- if ('error' in checked) {
76
- errors.push({ path, message: checked.error });
77
- continue;
78
- }
79
- if (checked.value === null) {
80
- row[key] = null;
81
- continue;
82
- }
83
- const decoded = boundary.decode(checked.value);
84
- if ('error' in decoded) errors.push({ path, message: decoded.error });
85
- else row[key] = decoded.value;
73
+ const parsed = FieldValueValidator.of(field).parse(value);
74
+ if ('error' in parsed) errors.push({ path: [...path, ...parsed.path ?? []], message: parsed.error });
75
+ else row[key] = parsed.value;
86
76
  }
87
77
 
88
78
  if (errors.length > 0) return { success: false, errors };
@@ -1,11 +1,6 @@
1
1
  import { Field } from '../../field/Field.js';
2
2
  import { date } from '../primitive/date.js';
3
3
 
4
- /**
5
- * So a creation stamp is written by the storage and refused from a client.
6
- * FR : pour qu'un horodatage de création soit écrit par le storage et refusé au client.
7
- * `created()` → `create: 'now'`, `update: 'forbidden'`
8
- */
9
4
  export function created(): Field<Date> {
10
5
  return date().with({
11
6
  lifecycle: { create: 'now', update: 'forbidden' },
@@ -1,11 +1,6 @@
1
1
  import { text, type TextOptions } from '../primitive/text.js';
2
2
  import type { Field } from '../../field/Field.js';
3
3
 
4
- /**
5
- * So the format is stated once, where everyone would otherwise write the same regex.
6
- * FR : pour que le format soit énoncé une fois, là où chacun réécrirait la même regex.
7
- * `email({ max: 254 })`
8
- */
9
4
  export function email(opts?: Omit<TextOptions, 'format'>): Field<string> {
10
5
  return text({ ...opts, format: 'email' });
11
6
  }
@@ -1,11 +1,6 @@
1
1
  import { created } from './created.js';
2
2
  import { Field } from '../../field/Field.js';
3
3
 
4
- /**
5
- * So the dual of `created()` is re-stamped at every write, by the same hand.
6
- * FR : pour que le dual de `created()` soit ré-estampé à chaque écriture, par la même main.
7
- * `updated()` → `create: 'now'`, `update: 'now'`
8
- */
9
4
  export function updated(): Field<Date> {
10
5
  const base = created();
11
6
  return base.with({ lifecycle: { ...base.lifecycle, update: 'now' } });
@@ -1,11 +1,6 @@
1
1
  import { text, type TextOptions } from '../primitive/text.js';
2
2
  import type { Field } from '../../field/Field.js';
3
3
 
4
- /**
5
- * So a URL is a text with a stated format, not a type of its own.
6
- * FR : pour qu'une URL soit un texte au format énoncé, pas un type à part.
7
- * `url()` → `{ type: 'string', format: 'uri' }`
8
- */
9
4
  export function url(opts?: Omit<TextOptions, 'format'>): Field<string> {
10
5
  return text({ ...opts, format: 'uri' });
11
6
  }
@@ -1,10 +1,5 @@
1
1
  import { Field } from '../../field/Field.js';
2
2
 
3
- /**
4
- * So a default is a lifecycle rule, not a value the handler remembers to set.
5
- * FR : pour qu'un défaut soit une règle de cycle de vie, pas un oubli possible du handler.
6
- * `bool({ default: false })`
7
- */
8
3
  export function bool(opts?: { default?: boolean; description?: string }): Field<boolean> {
9
4
  return new Field<boolean>({
10
5
  shape: { type: 'boolean' },
@@ -1,9 +1,9 @@
1
1
  import { Field } from '../../field/Field.js';
2
2
 
3
3
  /**
4
- * So a date is a string on the wire and a `Date` in a handler, with no word said.
5
- * FR : pour qu'une date soit une chaîne sur le fil et une `Date` dans un handler.
6
- * `date()` → `{ type: 'string', format: 'date-time' }`, decoded by the `isoDate` boundary
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
+ * `date()` → `{ type: 'string', format: 'date-time' }`
7
7
  */
8
8
  export function date(): Field<Date> {
9
9
  return new Field<Date>({ shape: { type: 'string', format: 'date-time' } });
@@ -7,9 +7,9 @@ export function json<E extends SchemaView & (new (...args: any[]) => any)>(
7
7
  of: E,
8
8
  ): Field<InstanceType<E>>;
9
9
  /**
10
- * So a stored object can carry a schema, instead of admitting any shape forever.
11
- * FR : pour qu'un objet stocké porte un schéma, au lieu d'admettre toute forme à jamais.
12
- * `json(Address)` → the object's properties and its required keys
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
+ * `json(Address)` → the entity's properties, and its required keys
13
13
  */
14
14
  export function json(of?: SchemaView): Field<unknown> {
15
15
  if (!of) return new Field({ shape: { type: 'object' } });
@@ -6,9 +6,9 @@ export interface ListOptions {
6
6
  }
7
7
 
8
8
  /**
9
- * So a list of values is told apart from a relation, which `many()` declares.
10
- * FR : pour qu'une liste de valeurs se distingue d'une relation, que `many()` déclare.
11
- * `list(text(), { max: 5 })`; `list(ref(User))`throws
9
+ * A list of VALUES. The other side of a relation is `many()`, and passing one here throws.
10
+ * FR : une liste de VALEURS. L'autre côté d'une relation est `many()`, et en passer une ici lève.
11
+ * `list(text(), { max: 5 })` → `{ type: 'array', items: { type: 'string' }, maxItems: 5 }`
12
12
  */
13
13
  export function list<T>(item: Field<T>, opts?: ListOptions): Field<T[]> {
14
14
  if (item.role?.relation)
@@ -2,9 +2,9 @@ import { Relation, type EntityConstructor } from '../../axis/role/Relation.js';
2
2
  import { Field } from '../../field/Field.js';
3
3
 
4
4
  /**
5
- * So the other side of a reference is declared, and holds no column.
6
- * FR : pour que l'autre côté d'une référence soit déclaré, sans porter de colonne.
7
- * `many(() => Post)` → a collection, absent from the row a client sends
5
+ * The far side of a `ref()`: no column of its own, and absent from what a client sends.
6
+ * FR : l'autre côté d'un `ref()` : aucune colonne à lui, et absent de ce qu'un client envoie.
7
+ * `many(() => Post)` → `{ type: 'array' }` with `role.relation.kind` `'many'`
8
8
  */
9
9
  export function many<E extends EntityConstructor>(
10
10
  target: E | (() => E),
@@ -8,11 +8,6 @@ export interface NumberOptions {
8
8
  description?: string;
9
9
  }
10
10
 
11
- /**
12
- * So `integer` is a shape, not a second word to remember.
13
- * FR : pour qu'`integer` soit une forme, pas un second mot à retenir.
14
- * `number({ integer: true, min: 0 })` → `{ type: 'integer', minimum: 0 }`
15
- */
16
11
  export function number(opts?: NumberOptions): Field<number> {
17
12
  return new Field<number>({
18
13
  shape: {
@@ -12,9 +12,9 @@ export function oneOf<const T extends readonly string[]>(
12
12
  ...args: [...T, OneOfOptions<T[number]>]
13
13
  ): Field<T[number]>;
14
14
  /**
15
- * So the set is in the type and in the CHECK constraint, from one declaration.
16
- * FR : pour que l'ensemble soit dans le type et dans la contrainte CHECK, d'une déclaration.
17
- * `oneOf('draft', 'published', { default: 'draft' })`
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
+ * `oneOf('draft', 'published', { default: 'draft' })` → `{ type: 'string', enum: [...] }`
18
18
  */
19
19
  export function oneOf<const T extends readonly string[]>(
20
20
  ...args: [...T] | [...T, OneOfOptions<T[number]>]
@@ -8,9 +8,9 @@ interface PrimaryOptions {
8
8
  export function primary(opts?: PrimaryOptions): Field<string>;
9
9
  export function primary<T>(field: Field<T>): Field<T>;
10
10
  /**
11
- * So an id is generated and write-once by declaring nothing else.
12
- * FR : pour qu'un identifiant soit généré et écrit une fois sans rien déclarer d'autre.
13
- * `primary()` → cuid2, `update: 'forbidden'`; `primary(number())` → the same on a number
11
+ * `primary()` for a generated id; `primary(number())` to make a declared field the key.
12
+ * FR : `primary()` pour un identifiant généré ; `primary(number())` pour promouvoir un champ.
13
+ * `primary()` → `create: { generate: 'cuid2' }`, `update: 'forbidden'`
14
14
  */
15
15
  export function primary(fieldOrOptions?: Field | PrimaryOptions): Field {
16
16
  if (Field.is(fieldOrOptions)) {
@@ -5,11 +5,6 @@ export interface RefOptions {
5
5
  cascade?: boolean;
6
6
  }
7
7
 
8
- /**
9
- * So a reference names the target entity, and the column type follows from it.
10
- * FR : pour qu'une référence nomme l'entité cible, la colonne en découlant.
11
- * `ref(() => User, { cascade: true })`
12
- */
13
8
  export function ref<E extends EntityConstructor>(
14
9
  target: E | (() => E),
15
10
  opts?: RefOptions,
@@ -10,11 +10,6 @@ export interface TextOptions {
10
10
  description?: string;
11
11
  }
12
12
 
13
- /**
14
- * So a string field states its bounds where the DDL and the validator both read them.
15
- * FR : pour qu'un champ texte énonce ses bornes là où le DDL et le juge les lisent.
16
- * `text({ max: 200, format: 'email' })`
17
- */
18
13
  export function text(opts?: TextOptions): Field<string> {
19
14
  return new Field<string>({
20
15
  shape: {
@@ -1,11 +1,6 @@
1
1
  import { vocabulary } from '../vocabulary.js';
2
2
  import { Field } from '../../field/Field.js';
3
3
 
4
- /**
5
- * So a write-once field is refused on a patch by the validator, not by the table.
6
- * FR : pour qu'un champ écrit une fois soit refusé par le juge sur une modification.
7
- * `immutable(text())` → `update: 'forbidden'`
8
- */
9
4
  export const immutable: <T>(field: Field<T>) => Field<T> = vocabulary(
10
5
  'immutable',
11
6
  () => ({
@@ -1,11 +1,6 @@
1
1
  import { vocabulary } from '../vocabulary.js';
2
2
  import { Field } from '../../field/Field.js';
3
3
 
4
- /**
5
- * So the entity states the index, and the DDL is the one that emits it.
6
- * FR : pour que l'entité énonce l'index, le DDL étant celui qui l'émet.
7
- * `indexed(text())` → `role.index` is `true`
8
- */
9
4
  export const indexed: <T>(field: Field<T>) => Field<T> = vocabulary('indexed', () => ({
10
5
  role: { index: true },
11
6
  }));
@@ -3,9 +3,9 @@ import { Shapes } from '../../axis/shape/Shape.js';
3
3
  import { type Field } from '../../field/Field.js';
4
4
 
5
5
  /**
6
- * So `null` becomes a legal value, without touching whether the field is required.
7
- * FR : pour que `null` devienne légal, sans toucher au caractère obligatoire du champ.
8
- * `nullable(text())` → `{ type: ['string', 'null'] }`, still required at create
6
+ * Adds `null` and leaves the field REQUIRED `optional()` is the one permitting absence.
7
+ * FR : ajoute `null` en laissant le champ OBLIGATOIRE `optional()` permet l'absence.
8
+ * `nullable(text())` → `{ type: ['string', 'null'] }`, and `validate({})` still refuses
9
9
  */
10
10
  export const nullable: <T>(field: Field<T>) => Field<T | null> = vocabulary(
11
11
  'nullable',
@@ -3,9 +3,9 @@ import { Shapes } from '../../axis/shape/Shape.js';
3
3
  import { type Field } from '../../field/Field.js';
4
4
 
5
5
  /**
6
- * So the two questions are settled at once: `null` is legal, and absence is too.
7
- * FR : pour que les deux questions soient réglées d'un coup : `null` légal, absence permise.
8
- * `optional(text())` → nullable shape and `create: 'optional'`
6
+ * Adds `null` AND permits absence. A field that already states a `create` rule keeps it.
7
+ * FR : ajoute `null` ET permet l'absence. Un champ qui énonce déjà un `create` le garde.
8
+ * `optional(text())` → `{ type: ['string', 'null'] }` and `create: 'optional'`
9
9
  */
10
10
  export const optional: <T>(field: Field<T>) => Field<T | null> = vocabulary(
11
11
  'optional',
@@ -3,13 +3,13 @@ import { Boundary } from '../../axis/boundary/Boundary.js';
3
3
  import { Field } from '../../field/Field.js';
4
4
 
5
5
  /**
6
- * So a field a client may read but never write says so once, on the field.
7
- * FR : pour qu'un champ lisible mais non écrivable le dise une fois, sur le champ.
8
- * `readOnly(text())` → `boundary.in` is `'closed'`, absent from every form
6
+ * Closes the way in, so the field leaves `Visibility.input` and every form derived from it.
7
+ * FR : ferme l'entrée : le champ quitte `Visibility.input` et tout formulaire qui en dérive.
8
+ * `readOnly(text())` → `boundary.in` is `'closed'`
9
9
  */
10
10
  export const readOnly: <T>(field: Field<T>) => Field<T> = vocabulary(
11
11
  'readOnly',
12
12
  (field) => ({
13
- boundary: Boundary.declared(field).with({ in: 'closed' }),
13
+ boundary: Boundary.declared(field).declaring({ in: 'closed' }),
14
14
  }),
15
15
  );
@@ -1,11 +1,6 @@
1
1
  import { vocabulary } from '../vocabulary.js';
2
2
  import { Field } from '../../field/Field.js';
3
3
 
4
- /**
5
- * So a field is unique on its own, whatever key it ends up under.
6
- * FR : pour qu'un champ soit unique à lui seul, quelle que soit sa clé.
7
- * `unique(email)` → `role.unique` is true
8
- */
9
4
  export const unique: <T>(field: Field<T>) => Field<T> = vocabulary('unique', () => ({
10
5
  role: { unique: true },
11
6
  }));
@@ -3,13 +3,13 @@ import { Boundary } from '../../axis/boundary/Boundary.js';
3
3
  import { Field } from '../../field/Field.js';
4
4
 
5
5
  /**
6
- * So a secret leaves no response, the dual of `readOnly` on the same axis.
7
- * FR : pour qu'un secret ne reparte dans aucune réponse, dual de `readOnly`.
6
+ * Closes the way out, the dual of `readOnly`: the value is written and never sent back.
7
+ * FR : ferme la sortie, dual de `readOnly` : la valeur s'écrit et ne repart jamais.
8
8
  * `writeOnly(text())` → `boundary.out` is `'closed'`
9
9
  */
10
10
  export const writeOnly: <T>(field: Field<T>) => Field<T> = vocabulary(
11
11
  'writeOnly',
12
12
  (field) => ({
13
- boundary: Boundary.declared(field).with({ out: 'closed' }),
13
+ boundary: Boundary.declared(field).declaring({ out: 'closed' }),
14
14
  }),
15
15
  );
@@ -5,8 +5,8 @@ import { dequal } from 'dequal';
5
5
  const MEMBER_SLOTS = [...EXTENSION_SLOTS, 'meta'] as const;
6
6
 
7
7
  /**
8
- * So a word is one statement about a field, not a rebuilt field.
9
- * FR : pour qu'un mot soit une affirmation sur un champ, pas un champ reconstruit.
8
+ * Builds a `rule/` word, which states members on the field it receives.
9
+ * FR : fabrique un mot de `rule/`, qui énonce des membres sur le champ reçu.
10
10
  * `vocabulary('indexed', () => ({ role: { index: true } }))`
11
11
  */
12
12
  export function vocabulary(
@@ -19,9 +19,9 @@ export function vocabulary(
19
19
  export type FieldWord = (field: Field<any>) => Field<any>;
20
20
 
21
21
  /**
22
- * So two words that state the same member differently refuse instead of one winning.
23
- * FR : pour que deux mots contredisant un même membre refusent, plutôt qu'un l'emporte.
24
- * `readOnly(writeOnly(text()))` → both close a different side, so both apply
22
+ * Refuses two words stating one member differently, rather than letting the outer win.
23
+ * FR : refuse deux mots qui énoncent un même membre différemment.
24
+ * `readOnly(writeOnly(text()))` → both apply; `immutable(created())` throws
25
25
  */
26
26
  function merge(name: string, field: Field, given: Partial<Field>): Partial<Field> {
27
27
  const merged: Record<string, unknown> = {};
@@ -1,17 +0,0 @@
1
- export interface ValidationError {
2
- path: string;
3
- message: string;
4
- }
5
- export type ValidationResult<T> = {
6
- success: true;
7
- data: T;
8
- } | {
9
- success: false;
10
- errors: ValidationError[];
11
- };
12
- export type Checked = {
13
- value: unknown;
14
- } | {
15
- error: string;
16
- };
17
- //# sourceMappingURL=validation.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAC1B;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GAC1B;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,OAAO,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=validation.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":""}
package/src/validation.ts DELETED
@@ -1,10 +0,0 @@
1
- export interface ValidationError {
2
- path: string;
3
- message: string;
4
- }
5
-
6
- export type ValidationResult<T> =
7
- | { success: true; data: T }
8
- | { success: false; errors: ValidationError[] };
9
-
10
- export type Checked = { value: unknown } | { error: string };