@fougere/schema 0.10.0-alpha.0 → 0.12.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 (167) hide show
  1. package/README.md +4 -2
  2. package/dist/Schema.d.ts +1 -1
  3. package/dist/Schema.d.ts.map +1 -1
  4. package/dist/Schema.js +4 -3
  5. package/dist/Schema.js.map +1 -1
  6. package/dist/SchemaConstructor.d.ts +3 -4
  7. package/dist/SchemaConstructor.d.ts.map +1 -1
  8. package/dist/SchemaDefinition.d.ts.map +1 -1
  9. package/dist/SchemaDefinition.js +2 -1
  10. package/dist/SchemaDefinition.js.map +1 -1
  11. package/dist/SchemaError.d.ts +4 -0
  12. package/dist/SchemaError.d.ts.map +1 -0
  13. package/dist/SchemaError.js +7 -0
  14. package/dist/SchemaError.js.map +1 -0
  15. package/dist/SchemaView.d.ts +1 -1
  16. package/dist/SchemaView.d.ts.map +1 -1
  17. package/dist/axis/boundary/Boundary.d.ts.map +1 -1
  18. package/dist/axis/boundary/Boundary.js +3 -2
  19. package/dist/axis/boundary/Boundary.js.map +1 -1
  20. package/dist/axis/boundary/Decoder.d.ts +2 -5
  21. package/dist/axis/boundary/Decoder.d.ts.map +1 -1
  22. package/dist/axis/boundary/Decoder.js +2 -2
  23. package/dist/axis/boundary/Decoder.js.map +1 -1
  24. package/dist/axis/lifecycle/Clock.d.ts +4 -12
  25. package/dist/axis/lifecycle/Clock.d.ts.map +1 -1
  26. package/dist/axis/lifecycle/Clock.js +7 -13
  27. package/dist/axis/lifecycle/Clock.js.map +1 -1
  28. package/dist/axis/lifecycle/Generators.d.ts +5 -5
  29. package/dist/axis/lifecycle/Generators.d.ts.map +1 -1
  30. package/dist/axis/lifecycle/Generators.js +5 -15
  31. package/dist/axis/lifecycle/Generators.js.map +1 -1
  32. package/dist/axis/lifecycle/Lifecycle.d.ts +3 -3
  33. package/dist/axis/lifecycle/Lifecycle.d.ts.map +1 -1
  34. package/dist/axis/lifecycle/Lifecycle.js +11 -7
  35. package/dist/axis/lifecycle/Lifecycle.js.map +1 -1
  36. package/dist/axis/lifecycle/apply.d.ts.map +1 -1
  37. package/dist/axis/lifecycle/apply.js +3 -19
  38. package/dist/axis/lifecycle/apply.js.map +1 -1
  39. package/dist/axis/role/Relation.d.ts +4 -2
  40. package/dist/axis/role/Relation.d.ts.map +1 -1
  41. package/dist/axis/role/Relation.js +2 -2
  42. package/dist/axis/role/Relation.js.map +1 -1
  43. package/dist/axis/shape/Shape.d.ts +2 -2
  44. package/dist/axis/shape/Shape.d.ts.map +1 -1
  45. package/dist/axis/shape/Shape.js +2 -2
  46. package/dist/axis/shape/Shape.js.map +1 -1
  47. package/dist/entity/EntityAdapterSet.d.ts.map +1 -1
  48. package/dist/entity/EntityAdapterSet.js +3 -2
  49. package/dist/entity/EntityAdapterSet.js.map +1 -1
  50. package/dist/entity.d.ts +0 -1
  51. package/dist/entity.d.ts.map +1 -1
  52. package/dist/entity.js +0 -1
  53. package/dist/entity.js.map +1 -1
  54. package/dist/field/Field.d.ts.map +1 -1
  55. package/dist/field/Field.js +5 -4
  56. package/dist/field/Field.js.map +1 -1
  57. package/dist/field/FieldSet.d.ts.map +1 -1
  58. package/dist/field/FieldSet.js +5 -3
  59. package/dist/field/FieldSet.js.map +1 -1
  60. package/dist/field/Values.d.ts +6 -0
  61. package/dist/field/Values.d.ts.map +1 -0
  62. package/dist/field/Values.js.map +1 -0
  63. package/dist/index.d.ts +3 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +2 -0
  66. package/dist/index.js.map +1 -1
  67. package/dist/lib/Registry.d.ts +1 -2
  68. package/dist/lib/Registry.d.ts.map +1 -1
  69. package/dist/lib/Registry.js +4 -4
  70. package/dist/lib/Registry.js.map +1 -1
  71. package/dist/lib/Verdict.d.ts +12 -0
  72. package/dist/lib/Verdict.d.ts.map +1 -0
  73. package/dist/lib/Verdict.js +2 -0
  74. package/dist/lib/Verdict.js.map +1 -0
  75. package/dist/projection/Cases.js +52 -54
  76. package/dist/projection/Cases.js.map +1 -1
  77. package/dist/projection/card/Bundle.d.ts.map +1 -1
  78. package/dist/projection/card/Bundle.js +2 -1
  79. package/dist/projection/card/Bundle.js.map +1 -1
  80. package/dist/projection/card/Card.d.ts +1 -1
  81. package/dist/projection/card/Card.d.ts.map +1 -1
  82. package/dist/projection/card/Card.js +7 -2
  83. package/dist/projection/card/Card.js.map +1 -1
  84. package/dist/projection/card/Diff.d.ts.map +1 -1
  85. package/dist/projection/card/Diff.js +34 -37
  86. package/dist/projection/card/Diff.js.map +1 -1
  87. package/dist/projection/card/admission.d.ts.map +1 -1
  88. package/dist/projection/card/admission.js +2 -1
  89. package/dist/projection/card/admission.js.map +1 -1
  90. package/dist/validator/AdapterFieldValidator.d.ts.map +1 -1
  91. package/dist/validator/AdapterFieldValidator.js +3 -2
  92. package/dist/validator/AdapterFieldValidator.js.map +1 -1
  93. package/dist/validator/FieldValueValidator.d.ts +3 -3
  94. package/dist/validator/FieldValueValidator.d.ts.map +1 -1
  95. package/dist/validator/FieldValueValidator.js +12 -11
  96. package/dist/validator/FieldValueValidator.js.map +1 -1
  97. package/dist/validator/InputValidator.d.ts +3 -0
  98. package/dist/validator/InputValidator.d.ts.map +1 -1
  99. package/dist/validator/InputValidator.js +28 -31
  100. package/dist/validator/InputValidator.js.map +1 -1
  101. package/dist/vocabulary/primitive/list.d.ts.map +1 -1
  102. package/dist/vocabulary/primitive/list.js +2 -1
  103. package/dist/vocabulary/primitive/list.js.map +1 -1
  104. package/dist/vocabulary/primitive/ref.d.ts +6 -1
  105. package/dist/vocabulary/primitive/ref.d.ts.map +1 -1
  106. package/dist/vocabulary/primitive/ref.js +1 -1
  107. package/dist/vocabulary/primitive/ref.js.map +1 -1
  108. package/dist/vocabulary/vocabulary.d.ts.map +1 -1
  109. package/dist/vocabulary/vocabulary.js +2 -1
  110. package/dist/vocabulary/vocabulary.js.map +1 -1
  111. package/package.json +1 -1
  112. package/src/Schema.ts +6 -4
  113. package/src/SchemaConstructor.ts +4 -4
  114. package/src/SchemaDefinition.ts +2 -1
  115. package/src/SchemaError.ts +7 -0
  116. package/src/SchemaView.ts +1 -1
  117. package/src/axis/boundary/Boundary.ts +3 -2
  118. package/src/axis/boundary/Decoder.ts +4 -3
  119. package/src/axis/lifecycle/Clock.ts +9 -13
  120. package/src/axis/lifecycle/Generators.ts +6 -18
  121. package/src/axis/lifecycle/Lifecycle.ts +14 -9
  122. package/src/axis/lifecycle/apply.ts +2 -16
  123. package/src/axis/role/Relation.ts +6 -3
  124. package/src/axis/shape/Shape.ts +3 -3
  125. package/src/entity/EntityAdapterSet.ts +3 -2
  126. package/src/entity.ts +0 -1
  127. package/src/field/Field.ts +5 -4
  128. package/src/field/FieldSet.ts +5 -3
  129. package/src/{Values.ts → field/Values.ts} +2 -2
  130. package/src/index.ts +3 -0
  131. package/src/lib/Registry.ts +5 -4
  132. package/src/lib/Verdict.ts +10 -0
  133. package/src/projection/Cases.ts +61 -57
  134. package/src/projection/card/Bundle.ts +2 -1
  135. package/src/projection/card/Card.ts +12 -4
  136. package/src/projection/card/Diff.ts +42 -42
  137. package/src/projection/card/admission.ts +2 -1
  138. package/src/validator/AdapterFieldValidator.ts +3 -2
  139. package/src/validator/FieldValueValidator.ts +15 -14
  140. package/src/validator/InputValidator.ts +30 -32
  141. package/src/vocabulary/primitive/list.ts +2 -1
  142. package/src/vocabulary/primitive/ref.ts +7 -3
  143. package/src/vocabulary/vocabulary.ts +2 -1
  144. package/dist/PartialValues.d.ts +0 -4
  145. package/dist/PartialValues.d.ts.map +0 -1
  146. package/dist/PartialValues.js +0 -2
  147. package/dist/PartialValues.js.map +0 -1
  148. package/dist/Values.d.ts +0 -6
  149. package/dist/Values.d.ts.map +0 -1
  150. package/dist/Values.js.map +0 -1
  151. package/dist/axis/boundary/Boundaries.d.ts +0 -17
  152. package/dist/axis/boundary/Boundaries.d.ts.map +0 -1
  153. package/dist/axis/boundary/Boundaries.js +0 -25
  154. package/dist/axis/boundary/Boundaries.js.map +0 -1
  155. package/dist/lib/Checked.d.ts +0 -11
  156. package/dist/lib/Checked.d.ts.map +0 -1
  157. package/dist/lib/Checked.js +0 -2
  158. package/dist/lib/Checked.js.map +0 -1
  159. package/dist/projection/standard.d.ts +0 -14
  160. package/dist/projection/standard.d.ts.map +0 -1
  161. package/dist/projection/standard.js +0 -2
  162. package/dist/projection/standard.js.map +0 -1
  163. package/src/PartialValues.ts +0 -4
  164. package/src/axis/boundary/Boundaries.ts +0 -36
  165. package/src/lib/Checked.ts +0 -5
  166. package/src/projection/standard.ts +0 -13
  167. /package/dist/{Values.js → field/Values.js} +0 -0
@@ -3,7 +3,7 @@ import { Field } from '../../field/Field.js';
3
3
  export function ref(target, opts) {
4
4
  return new Field({
5
5
  shape: { type: 'string' },
6
- role: { relation: Relation.one(target, opts?.cascade) },
6
+ role: { relation: Relation.one(target, opts?.onDelete) },
7
7
  });
8
8
  }
9
9
  //# sourceMappingURL=ref.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ref.js","sourceRoot":"","sources":["../../../src/vocabulary/primitive/ref.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AACvD,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAM7C,MAAM,UAAU,GAAG,CACjB,MAAqB,EACrB,IAAiB;IAEjB,OAAO,IAAI,KAAK,CAAS;QACvB,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACzB,IAAI,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE;KACxD,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"ref.js","sourceRoot":"","sources":["../../../src/vocabulary/primitive/ref.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAiB,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAU7C,MAAM,UAAU,GAAG,CACjB,MAAqB,EACrB,IAAiB;IAEjB,OAAO,IAAI,KAAK,CAAS;QACvB,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACzB,IAAI,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE;KACzD,CAAC,CAAC;AACL,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"vocabulary.d.ts","sourceRoot":"","sources":["../../src/vocabulary/vocabulary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAM1C;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,KAAK,CAAC,GACvC,SAAS,CAEX;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,CAAC"}
1
+ {"version":3,"file":"vocabulary.d.ts","sourceRoot":"","sources":["../../src/vocabulary/vocabulary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAO1C;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,KAAK,CAAC,GACvC,SAAS,CAEX;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { EXTENSION_SLOTS } from '../axis/Axis.js';
2
2
  import { dequal } from 'dequal';
3
+ import { SchemaError } from '../SchemaError.js';
3
4
  const MEMBER_SLOTS = [...EXTENSION_SLOTS, 'meta'];
4
5
  /**
5
6
  * Builds a `rule/` word, which states members on the field it receives.
@@ -31,7 +32,7 @@ function merge(name, field, given) {
31
32
  const previous = already?.[member];
32
33
  if (previous === undefined || dequal(previous, value))
33
34
  continue;
34
- throw new Error(`vocabulary: \`${name}\` states ${slot}.${member} = ${JSON.stringify(value)}, but the ` +
35
+ throw new SchemaError(`vocabulary: \`${name}\` states ${slot}.${member} = ${JSON.stringify(value)}, but the ` +
35
36
  `field already states ${JSON.stringify(previous)}. Apply one or the other, not both.`);
36
37
  }
37
38
  merged[slot] = { ...already, ...members };
@@ -1 +1 @@
1
- {"version":3,"file":"vocabulary.js","sourceRoot":"","sources":["../../src/vocabulary/vocabulary.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,YAAY,GAAG,CAAC,GAAG,eAAe,EAAE,MAAM,CAAU,CAAC;AAE3D;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,IAAY,EACZ,MAAwC;IAExC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC;AAID;;;;GAIG;AACH,SAAS,KAAK,CAAC,IAAY,EAAE,KAAY,EAAE,KAAqB;IAC9D,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,IAAI,OAAO,IAAI,KAAK;QAAE,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAEjD,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,OAAO,KAAK,SAAS;YAAE,SAAS;QACpC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAwC,CAAC;QACnE,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YACnF,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;YACvB,SAAS;QACX,CAAC;QACD,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACtD,MAAM,QAAQ,GAAG,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;YACnC,IAAI,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC;gBAAE,SAAS;YAChE,MAAM,IAAI,KAAK,CACb,iBAAiB,IAAI,aAAa,IAAI,IAAI,MAAM,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,YAAY;gBACrF,wBAAwB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,qCAAqC,CACxF,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC;IAC5C,CAAC;IACD,OAAO,MAAwB,CAAC;AAClC,CAAC"}
1
+ {"version":3,"file":"vocabulary.js","sourceRoot":"","sources":["../../src/vocabulary/vocabulary.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,YAAY,GAAG,CAAC,GAAG,eAAe,EAAE,MAAM,CAAU,CAAC;AAE3D;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,IAAY,EACZ,MAAwC;IAExC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC;AAID;;;;GAIG;AACH,SAAS,KAAK,CAAC,IAAY,EAAE,KAAY,EAAE,KAAqB;IAC9D,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,IAAI,OAAO,IAAI,KAAK;QAAE,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAEjD,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,OAAO,KAAK,SAAS;YAAE,SAAS;QACpC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAwC,CAAC;QACnE,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YACnF,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;YACvB,SAAS;QACX,CAAC;QACD,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACtD,MAAM,QAAQ,GAAG,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;YACnC,IAAI,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC;gBAAE,SAAS;YAChE,MAAM,IAAI,WAAW,CACnB,iBAAiB,IAAI,aAAa,IAAI,IAAI,MAAM,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,YAAY;gBACrF,wBAAwB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,qCAAqC,CACxF,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC;IAC5C,CAAC;IACD,OAAO,MAAwB,CAAC;AAClC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fougere/schema",
3
- "version": "0.10.0-alpha.0",
3
+ "version": "0.12.0-alpha.0",
4
4
  "description": "The entity and its 4 axes — shape, role, lifecycle, boundary. Subject-owned judgments, edge-safe.",
5
5
  "keywords": [
6
6
  "fougere",
package/src/Schema.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
2
+
1
3
  import { Boundary } from './axis/boundary/Boundary.js';
2
4
  import { type Fields } from './field/Fields.js';
3
5
  import { type CompositeUnique } from './entity/CompositeUnique.js';
@@ -9,11 +11,11 @@ import { SchemaDerivation } from './SchemaDerivation.js';
9
11
  import { type SchemaConstraints } from './SchemaConstraints.js';
10
12
  import { SchemaDefinition } from './SchemaDefinition.js';
11
13
  import { type ValidateOptions } from './validator/ValidateOptions.js';
12
- import type { StandardSchemaV1 } from './projection/standard.js';
13
14
 
14
15
  import type { SchemaView } from './SchemaView.js';
15
16
 
16
17
  import type { SchemaConstructor } from './SchemaConstructor.js';
18
+ import { SchemaError } from './SchemaError.js';
17
19
 
18
20
  export const ANONYMOUS_SCHEMA_NAME = 'Schema';
19
21
 
@@ -74,7 +76,7 @@ export class Schema {
74
76
  continue;
75
77
  }
76
78
  const decoded = Boundary.of(field).decode(value);
77
- row[key] = 'error' in decoded ? value : decoded.value;
79
+ row[key] = 'message' in decoded ? value : decoded.value;
78
80
  }
79
81
  return row;
80
82
  }
@@ -128,10 +130,10 @@ export class Schema {
128
130
 
129
131
  static named(name: string) {
130
132
  if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) {
131
- throw new Error(`named(): \`${name}\` is not a valid class name.`);
133
+ throw new SchemaError(`named(): \`${name}\` is not a valid class name.`);
132
134
  }
133
135
  if (this.name !== ANONYMOUS_SCHEMA_NAME) {
134
- throw new Error(
136
+ throw new SchemaError(
135
137
  `named(): \`${this.name}\` is already named by its class declaration.`,
136
138
  );
137
139
  }
@@ -1,15 +1,15 @@
1
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
2
+
1
3
  import { type Fields } from './field/Fields.js';
2
4
 
3
5
  import { type EntityDeclarations } from './entity/EntityDeclarations.js';
4
6
  import { type PreviousNames } from './entity/PreviousNames.js';
5
7
  import { SchemaDerivation } from './SchemaDerivation.js';
6
- import type { StandardSchemaV1 } from './projection/standard.js';
7
- import type { PartialValues } from './PartialValues.js';
8
8
  import type { SchemaView } from './SchemaView.js';
9
- import type { Values } from './Values.js';
9
+ import type { Values } from './field/Values.js';
10
10
 
11
11
  export interface SchemaConstructor<TFields extends Fields> extends SchemaView<TFields> {
12
- new (data: PartialValues<TFields>): Values<TFields>;
12
+ new (data: Partial<Values<TFields>>): Values<TFields>;
13
13
  readonly '~standard': StandardSchemaV1.Props<Record<string, unknown>, Values<TFields>>;
14
14
  readonly derivation?: SchemaDerivation;
15
15
  readonly previous?: PreviousNames<TFields>;
@@ -9,6 +9,7 @@ import { SchemaDerivation } from './SchemaDerivation.js';
9
9
  import { type ValidateOptions } from './validator/ValidateOptions.js';
10
10
  import type { SchemaView } from './SchemaView.js';
11
11
  import { SchemaConstraints } from './SchemaConstraints.js';
12
+ import { SchemaError } from './SchemaError.js';
12
13
 
13
14
  /**
14
15
  * Everything a definition is made of, every member required.
@@ -225,7 +226,7 @@ export class SchemaDefinition {
225
226
  const strangers = keys.filter((key) => !Object.hasOwn(this.fields, key));
226
227
  if (strangers.length === 0) return;
227
228
 
228
- throw new Error(
229
+ throw new SchemaError(
229
230
  `${operation}(): unknown field ${strangers.map((s) => `\`${s}\``).join(', ')}. ` +
230
231
  `This schema carries ${Object.keys(this.fields).join(', ')}.`,
231
232
  );
@@ -0,0 +1,7 @@
1
+ export class SchemaError extends Error {
2
+ constructor(message: string) {
3
+ super(message);
4
+
5
+ this.name = new.target.name;
6
+ }
7
+ }
package/src/SchemaView.ts CHANGED
@@ -5,7 +5,7 @@ import type { EntityAdapters } from './entity/EntityAdapters.js';
5
5
  import type { ValidationResult } from './lib/ValidationResult.js';
6
6
  import type { ValidateOptions } from './validator/ValidateOptions.js';
7
7
  import type { SchemaDerivation } from './SchemaDerivation.js';
8
- import type { Values } from './Values.js';
8
+ import type { Values } from './field/Values.js';
9
9
 
10
10
  export interface SchemaView<TFields extends Fields = Fields> {
11
11
  readonly name: string;
@@ -5,6 +5,7 @@ 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
7
  import type { BoundaryRules } from './BoundaryRules.js';
8
+ import { SchemaError } from '../../SchemaError.js';
8
9
 
9
10
  const identityDecoder: Decoder = (value) => ({ value });
10
11
 
@@ -33,7 +34,7 @@ export class Boundary {
33
34
  if (typeof ref !== 'string') return new Boundary(ref);
34
35
 
35
36
  const alias = Boundaries.aliases.find(ref);
36
- if (!alias) throw new Error(`Unknown boundary alias: '${ref}'`);
37
+ if (!alias) throw new SchemaError(`Unknown boundary alias: '${ref}'`);
37
38
  return new Boundary(alias);
38
39
  }
39
40
 
@@ -58,7 +59,7 @@ export class Boundary {
58
59
 
59
60
  /** `date-time` means a `Date` on both sides, without a word in the entity. */
60
61
  static forShape(shape: Shape | undefined): Boundary {
61
- if (Shapes.typeOf(shape) === 'date') return new Boundary(Boundaries.aliases.find('isoDate')!);
62
+ if (Shapes.typeOf(shape) === 'date') return new Boundary(Boundaries.aliases.resolve('isoDate'));
62
63
  return new Boundary();
63
64
  }
64
65
 
@@ -1,13 +1,14 @@
1
1
  import type { BoundaryRules } from './BoundaryRules.js';
2
2
  import { Registry } from '../../lib/Registry.js';
3
3
  import type { Encoder } from './Encoder.js';
4
+ import type { Verdict } from '../../lib/Verdict.js';
4
5
 
5
6
  /**
6
7
  * Wire to domain, and it must ANSWER a value it already produced: two facades decode — the
7
8
  * client one on what arrives, `StorageGuard` on what a handler writes — so a decoder that
8
9
  * halves cents halves them twice and stores a hundredth.
9
10
  */
10
- export type Decoder = (value: unknown) => { value: unknown } | { error: string };
11
+ export type Decoder = (value: unknown) => Verdict;
11
12
 
12
13
  /**
13
14
  * `decoders` for a value coming in, `encoders` for one going out, `aliases` for the word
@@ -32,9 +33,9 @@ Boundaries.decoders.register('isoDate', (value) => {
32
33
  if (value instanceof Date) return { value };
33
34
  if (typeof value === 'string') {
34
35
  const date = new Date(value);
35
- return Number.isNaN(date.getTime()) ? { error: 'Invalid date' } : { value: date };
36
+ return Number.isNaN(date.getTime()) ? { message: 'Invalid date' } : { value: date };
36
37
  }
37
- return { error: 'Expected a date' };
38
+ return { message: 'Expected a date' };
38
39
  });
39
40
  Boundaries.encoders.register('isoDate', (value) =>
40
41
  value instanceof Date ? value.toISOString() : value,
@@ -1,14 +1,3 @@
1
- /**
2
- * What "now" means, for the two functions that stamp it.
3
- *
4
- * The ONE place either of them reads the time, which is what makes it substitutable at
5
- * all: `created()`, `updated()` and `create: 'now'` are realized through this and nowhere
6
- * else, so a test that needs a stable instant sets it here instead of intercepting `Date`
7
- * globally — where Rails had to build `travel_to` over the language.
8
- *
9
- * A value rather than a mock: nothing here is a channel, and a frozen clock is a fact
10
- * about the run, not an interception of a call.
11
- */
12
1
  export class Clock {
13
2
  private static reading: () => number = Date.now;
14
3
 
@@ -16,11 +5,18 @@ export class Clock {
16
5
  return this.reading();
17
6
  }
18
7
 
19
- /** One substitution moves time for every stamp in the process at once. */
8
+ /**
9
+ *
10
+ * @returns function to restore the clock
11
+ */
20
12
  static freeze(at: number | Date): () => void {
21
13
  const previous = this.reading;
22
14
  const instant = at instanceof Date ? at.getTime() : at;
15
+
23
16
  this.reading = () => instant;
24
- return () => { this.reading = previous; };
17
+
18
+ return () => {
19
+ this.reading = previous;
20
+ };
25
21
  }
26
22
  }
@@ -2,28 +2,16 @@ import { createId } from '@paralleldrive/cuid2';
2
2
 
3
3
  import { Registry } from '../../lib/Registry.js';
4
4
 
5
- export type GeneratorRef = 'cuid2' | 'uuid' | 'nanoid' | (string & {});
6
-
7
- const NANOID_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
8
-
9
- function nanoid(): string {
10
- const bytes = new Uint8Array(21);
11
- globalThis.crypto.getRandomValues(bytes);
12
- return Array.from(bytes, (byte) => NANOID_ALPHABET[byte & 63]).join('');
13
- }
5
+ export type GeneratorRef = 'cuid2' | (string & {});
14
6
 
15
7
  /**
16
- * Who answers a `generate:` name. `cuid2`, `uuid` and `nanoid` are registered like any
17
- * other, so `Generators.resolve('ulid')` is refused here and lists the three.
18
- * FR : pour qu'un `generate:` soit répondu par un registre, builtins compris.
19
- * `Generators.register('ulid', ulid)` → `create: { generate: 'ulid' }` now resolves
8
+ * Who answers a `generate:` name. `cuid2` is registered like any other, so
9
+ * `Generators.resolve('uuid')` is refused here and names what the process holds.
10
+ * `Generators.register('uuid', () => globalThis.crypto.randomUUID())` →
11
+ * `create: { generate: 'uuid' }` now resolves
20
12
  */
21
13
  export const Generators = new Registry<() => string>(
22
14
  'generator',
23
15
  'call Generators.register(name, fn)',
24
- [
25
- ['cuid2', createId],
26
- ['uuid', () => globalThis.crypto.randomUUID()],
27
- ['nanoid', nanoid],
28
- ],
16
+ [['cuid2', createId]],
29
17
  );
@@ -1,4 +1,4 @@
1
- import type { GeneratorRef } from './Generators.js';
1
+ import { Generators } from './Generators.js';
2
2
  import type { LifecycleRules } from './LifecycleRules.js';
3
3
 
4
4
  export class Lifecycle {
@@ -14,11 +14,22 @@ export class Lifecycle {
14
14
  return new Lifecycle(field.lifecycle);
15
15
  }
16
16
 
17
+ bornWith(instant: number): { value: unknown } | undefined {
18
+ const rule = this.create;
19
+
20
+ if (rule === 'now') return { value: new Date(instant) };
21
+
22
+ if (typeof rule !== 'object') return undefined;
23
+
24
+ if ('value' in rule) return { value: structuredClone(rule.value) };
25
+
26
+ return { value: Generators.resolve(rule.generate)() };
27
+ }
28
+
17
29
  get requiredAtCreate(): boolean {
18
30
  return this.create === undefined;
19
31
  }
20
32
 
21
- /** The server fills it at create, so a client form never carries it. */
22
33
  get stampedAtCreate(): boolean {
23
34
  return this.create === 'now';
24
35
  }
@@ -37,15 +48,9 @@ export class Lifecycle {
37
48
 
38
49
  get literal(): { value: unknown } | undefined {
39
50
  const rule = this.create;
51
+
40
52
  return typeof rule === 'object' && rule !== null && 'value' in rule
41
53
  ? { value: (rule as { value: unknown }).value }
42
54
  : undefined;
43
55
  }
44
-
45
- get generator(): GeneratorRef | undefined {
46
- const rule = this.create;
47
- return typeof rule === 'object' && rule !== null && 'generate' in rule
48
- ? (rule as { generate: GeneratorRef }).generate
49
- : undefined;
50
- }
51
56
  }
@@ -1,5 +1,4 @@
1
1
  import { Lifecycle } from './Lifecycle.js';
2
- import { Generators } from './Generators.js';
3
2
  import { Clock } from './Clock.js';
4
3
  import { type Field } from '../../field/Field.js';
5
4
  import { type Fields } from '../../field/Fields.js';
@@ -15,27 +14,14 @@ export function applyCreate(fields: Fields, input: Record<string, unknown>): Rec
15
14
 
16
15
  for (const [name, field] of Object.entries(fields) as [string, Field][]) {
17
16
  if (name in values) continue;
18
- const rule = Lifecycle.of(field);
17
+ const born = Lifecycle.of(field).bornWith(instant);
19
18
 
20
- if (rule.stampedAtCreate) values[name] = new Date(instant);
21
- else if (rule.literal) values[name] = freshValue(rule.literal.value);
22
- else if (rule.generator) values[name] = Generators.resolve(rule.generator)();
19
+ if (born) values[name] = born.value;
23
20
  }
24
21
 
25
22
  return values;
26
23
  }
27
24
 
28
- /**
29
- * Clones a declared default, so two rows born of `create: { value: [] }` hold two arrays.
30
- * FR : clone un défaut déclaré, pour que deux lignes nées de `create: { value: [] }`
31
- * tiennent deux tableaux.
32
- * `create: { value: [] }` → each instance gets its own array
33
- */
34
- function freshValue(value: unknown): unknown {
35
- if (value === null || typeof value !== 'object') return value;
36
- return structuredClone(value);
37
- }
38
-
39
25
  /**
40
26
  * The dual of `applyCreate` on a patch: only `update: 'now'` fields are touched.
41
27
  * FR : le dual d'`applyCreate` sur une modification : seuls les champs `update: 'now'`
@@ -4,10 +4,13 @@ export const RELATION_KINDS = ['one', 'many'] as const;
4
4
 
5
5
  export const ON_DELETE = ['cascade', 'restrict', 'set null'] as const;
6
6
 
7
+ /** What becomes of the rows that name a deleted one. */
8
+ export type OnDelete = (typeof ON_DELETE)[number];
9
+
7
10
  export interface Relation {
8
11
  to: () => EntityConstructor;
9
12
  kind: (typeof RELATION_KINDS)[number];
10
- onDelete?: (typeof ON_DELETE)[number];
13
+ onDelete?: OnDelete;
11
14
  }
12
15
 
13
16
  /**
@@ -19,8 +22,8 @@ export interface Relation {
19
22
  */
20
23
  export const Relation = {
21
24
  /** `() => Post` so two entities can point at each other. */
22
- one(target: EntityConstructor | (() => EntityConstructor), cascade?: boolean): Relation {
23
- return { to: normalizeTarget(target), kind: 'one', onDelete: cascade ? 'cascade' : undefined };
25
+ one(target: EntityConstructor | (() => EntityConstructor), onDelete?: OnDelete): Relation {
26
+ return { to: normalizeTarget(target), kind: 'one', ...(onDelete ? { onDelete } : {}) };
24
27
  },
25
28
 
26
29
  many(target: EntityConstructor | (() => EntityConstructor)): Relation {
@@ -48,6 +48,9 @@ type _ShapeTypesAreTheStandardsLessNull = Assert<
48
48
  >;
49
49
 
50
50
  export class Shapes {
51
+ private static readonly cache = new WeakMap<object, ShapeParts>();
52
+ private static readonly none: ShapeParts = { base: undefined, nullable: false };
53
+
51
54
  static is(value: unknown): value is Shape {
52
55
  if (typeof value !== 'object' || value === null) return false;
53
56
  const type = (value as Shape).type;
@@ -96,9 +99,6 @@ export class Shapes {
96
99
  return nullable;
97
100
  }
98
101
 
99
- private static readonly cache = new WeakMap<object, ShapeParts>();
100
- private static readonly none: ShapeParts = { base: undefined, nullable: false };
101
-
102
102
  static of(shape?: Shape): ShapeParts {
103
103
  if (!shape) return this.none;
104
104
  let parts = this.cache.get(shape);
@@ -1,6 +1,7 @@
1
1
  import type { Fields } from '../field/Fields.js';
2
2
  import { isObject } from '../lib/utils.js';
3
3
  import type { EntityAdapters } from './EntityAdapters.js';
4
+ import { SchemaError } from '../SchemaError.js';
4
5
 
5
6
  type AdapterConfiguration = Record<string, unknown>;
6
7
  type AdapterConfigurations = Record<string, AdapterConfiguration>;
@@ -19,14 +20,14 @@ export class EntityAdapterSet {
19
20
  if (!adapters) return new EntityAdapterSet({});
20
21
 
21
22
  if (!isObject(adapters)) {
22
- throw new Error(
23
+ throw new SchemaError(
23
24
  `adapters: expected an object keyed by adapter name, got ${typeof adapters}.`,
24
25
  );
25
26
  }
26
27
 
27
28
  for (const [adapter, fields] of Object.entries(adapters)) {
28
29
  if (!isObject(fields)) {
29
- throw new Error(
30
+ throw new SchemaError(
30
31
  `adapters.${adapter}: expected an object keyed by field name, got ${typeof fields}. ` +
31
32
  `What an adapter is handed is addressed by the field it applies to.`,
32
33
  );
package/src/entity.ts CHANGED
@@ -5,7 +5,6 @@ import { type SchemaConstructor } from './SchemaConstructor.js';
5
5
 
6
6
  /**
7
7
  * The one call everything derives from: the fields, and what the entity states about them.
8
- * FR : l'appel dont tout dérive : les champs, et ce que l'entité en dit.
9
8
  * `class Post extends entity({ id: primary(), title: text() }, { unique: [['title']] }) {}`
10
9
  *
11
10
  * Documented: [entities](https://fougere.dev/docs/schema/entities).
@@ -7,6 +7,7 @@ import type { Axis } from '../axis/Axis.js';
7
7
  import { FieldDeclarationValidator } from '../validator/FieldDeclarationValidator.js';
8
8
  import { FieldValueValidator } from '../validator/FieldValueValidator.js';
9
9
  import { dotted } from '../lib/ValidationResult.js';
10
+ import { SchemaError } from '../SchemaError.js';
10
11
 
11
12
  type FieldDeclaration = Pick<Field, 'shape' | Axis['slot'] | 'meta'>;
12
13
 
@@ -23,7 +24,7 @@ export class Field<T = unknown> {
23
24
  const verdict = FieldDeclarationValidator.of(init).verdict;
24
25
 
25
26
  if (!verdict.success) {
26
- throw new Error(
27
+ throw new SchemaError(
27
28
  `${key ? `Field '${key}': ` : ''}` +
28
29
  verdict.errors.map((e) => `${dotted(e.path)}: ${e.message}`).join('; '),
29
30
  );
@@ -38,10 +39,10 @@ export class Field<T = unknown> {
38
39
  const create = this.lifecycle?.create;
39
40
  if (typeof create === 'object' && create !== null && 'value' in create) {
40
41
  const checked = FieldValueValidator.of(this).validate(create.value);
41
- if ('error' in checked)
42
- throw new Error(
42
+ if ('message' in checked)
43
+ throw new SchemaError(
43
44
  `${key ? `Field '${key}': ` : ''}the declared default ${JSON.stringify(create.value)} ` +
44
- `is not a legal value for it — ${checked.error}.`,
45
+ `is not a legal value for it — ${checked.message}.`,
45
46
  );
46
47
  }
47
48
  }
@@ -3,6 +3,7 @@ import type { CompositeUnique } from '../entity/CompositeUnique.js';
3
3
  import { Field } from './Field.js';
4
4
  import { type FieldName } from './FieldName.js';
5
5
  import { type Fields } from './Fields.js';
6
+ import { SchemaError } from '../SchemaError.js';
6
7
 
7
8
  export class FieldSet<TFields extends Fields = Fields> {
8
9
  private constructor(private readonly fields: TFields) {}
@@ -28,14 +29,15 @@ export class FieldSet<TFields extends Fields = Fields> {
28
29
  for (const group of unique ?? []) {
29
30
  const missing = group.filter((key) => !Object.hasOwn(fields, key));
30
31
  if (missing.length)
31
- throw new Error(
32
+ throw new SchemaError(
32
33
  `unique: [${group.join(', ')}] names ` +
33
34
  `${missing.map((key) => `'${key}'`).join(', ')}, which the entity does not declare.`,
34
35
  );
35
36
 
36
37
  if (group.length === 1) {
37
38
  const key = group[0]!;
38
- fields[key] = fields[key]!.with({ role: { ...fields[key]!.role, unique: true } });
39
+ const alone = fields[key]!;
40
+ fields[key] = alone.with({ role: { ...alone.role, unique: true } });
39
41
  continue;
40
42
  }
41
43
  composite.push([...group]);
@@ -51,7 +53,7 @@ export class FieldSet<TFields extends Fields = Fields> {
51
53
  .map(([name]) => name);
52
54
 
53
55
  if (primaries.length > 1) {
54
- throw new Error(
56
+ throw new SchemaError(
55
57
  `FieldSet.primary: ${primaries.map((name) => JSON.stringify(name)).join(', ')} all declare ` +
56
58
  '`primary`; a field set can have only one primary field.',
57
59
  );
@@ -1,5 +1,5 @@
1
- import type { Field } from './field/Field.js';
2
- import type { Fields } from './field/Fields.js';
1
+ import type { Field } from './Field.js';
2
+ import type { Fields } from './Fields.js';
3
3
 
4
4
  export type Values<TFields extends Fields> = {
5
5
  [K in keyof TFields]: TFields[K] extends Field<infer T> ? T : never;
package/src/index.ts CHANGED
@@ -12,12 +12,15 @@ export { type Shape } from './axis/shape/Shape.js';
12
12
  export { type ShapeType } from './axis/shape/ShapeType.js';
13
13
  export { Shapes } from './axis/shape/Shape.js';
14
14
  export { Role } from './axis/role/Role.js';
15
+ export { ON_DELETE, type OnDelete } from './axis/role/Relation.js';
15
16
  export { type EntityConstructor } from './axis/role/EntityConstructor.js';
16
17
  export { Lifecycle } from './axis/lifecycle/Lifecycle.js';
17
18
  export { Boundary } from './axis/boundary/Boundary.js';
18
19
  export { applyCreate, applyUpdate } from './axis/lifecycle/apply.js';
19
20
  export { InputRefusal } from './validator/InputRefusal.js';
21
+ export { SchemaError } from './SchemaError.js';
20
22
  export { type ValidationError } from './lib/ValidationError.js';
23
+ export { type Verdict } from './lib/Verdict.js';
21
24
  export { dotted, type ValidationResult } from './lib/ValidationResult.js';
22
25
 
23
26
  export { Card } from './projection/card/Card.js';
@@ -1,13 +1,14 @@
1
+ import { SchemaError } from '../SchemaError.js';
2
+
1
3
  export class Registry<T> {
2
4
  private readonly entries: Map<string, T>;
3
5
 
4
- /** `Unknown boundary decoder 'celsius' — call Boundaries.decoders.register(name, fn).` */
5
6
  constructor(
6
7
  private readonly label: string,
7
8
  private readonly hint?: string,
8
- builtins?: Iterable<readonly [string, T]>,
9
+ entries?: Iterable<readonly [string, T]>,
9
10
  ) {
10
- this.entries = new Map(builtins);
11
+ this.entries = new Map(entries);
11
12
  }
12
13
 
13
14
  register(name: string, value: T): T {
@@ -25,7 +26,7 @@ export class Registry<T> {
25
26
 
26
27
  if (found !== undefined) return found;
27
28
 
28
- throw new Error(
29
+ throw new SchemaError(
29
30
  `${path ? `${path}: ` : ''}Unknown ${this.label} '${name}'${this.hint ? ` — ${this.hint}` : ''}. ` +
30
31
  `This process answers ${this.names.join(', ') || 'nothing yet'}.`,
31
32
  );
@@ -0,0 +1,10 @@
1
+ import type { ValidationError } from './ValidationError.js';
2
+
3
+ /**
4
+ * A field's verdict — the value it admitted, or a refusal whose `path` says where INSIDE the
5
+ * value it happened. That path is optional here and required on a `ValidationError`: a field
6
+ * does not know its own name, so the key is prefixed by whoever iterates the fields.
7
+ */
8
+ export type Verdict =
9
+ | { value: unknown }
10
+ | (Omit<ValidationError, 'path'> & { path?: ValidationError['path'] });