@fougere/schema 0.11.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 (156) hide show
  1. package/dist/Schema.d.ts +1 -1
  2. package/dist/Schema.d.ts.map +1 -1
  3. package/dist/Schema.js +4 -3
  4. package/dist/Schema.js.map +1 -1
  5. package/dist/SchemaConstructor.d.ts +3 -4
  6. package/dist/SchemaConstructor.d.ts.map +1 -1
  7. package/dist/SchemaDefinition.d.ts.map +1 -1
  8. package/dist/SchemaDefinition.js +2 -1
  9. package/dist/SchemaDefinition.js.map +1 -1
  10. package/dist/SchemaError.d.ts +4 -0
  11. package/dist/SchemaError.d.ts.map +1 -0
  12. package/dist/SchemaError.js +7 -0
  13. package/dist/SchemaError.js.map +1 -0
  14. package/dist/SchemaView.d.ts +1 -1
  15. package/dist/SchemaView.d.ts.map +1 -1
  16. package/dist/axis/boundary/Boundary.d.ts.map +1 -1
  17. package/dist/axis/boundary/Boundary.js +3 -2
  18. package/dist/axis/boundary/Boundary.js.map +1 -1
  19. package/dist/axis/boundary/Decoder.d.ts +2 -5
  20. package/dist/axis/boundary/Decoder.d.ts.map +1 -1
  21. package/dist/axis/boundary/Decoder.js +2 -2
  22. package/dist/axis/boundary/Decoder.js.map +1 -1
  23. package/dist/axis/lifecycle/Clock.d.ts +4 -12
  24. package/dist/axis/lifecycle/Clock.d.ts.map +1 -1
  25. package/dist/axis/lifecycle/Clock.js +7 -13
  26. package/dist/axis/lifecycle/Clock.js.map +1 -1
  27. package/dist/axis/lifecycle/Generators.d.ts +5 -5
  28. package/dist/axis/lifecycle/Generators.d.ts.map +1 -1
  29. package/dist/axis/lifecycle/Generators.js +5 -15
  30. package/dist/axis/lifecycle/Generators.js.map +1 -1
  31. package/dist/axis/lifecycle/Lifecycle.d.ts +3 -3
  32. package/dist/axis/lifecycle/Lifecycle.d.ts.map +1 -1
  33. package/dist/axis/lifecycle/Lifecycle.js +11 -7
  34. package/dist/axis/lifecycle/Lifecycle.js.map +1 -1
  35. package/dist/axis/lifecycle/apply.d.ts.map +1 -1
  36. package/dist/axis/lifecycle/apply.js +3 -19
  37. package/dist/axis/lifecycle/apply.js.map +1 -1
  38. package/dist/axis/shape/Shape.d.ts +2 -2
  39. package/dist/axis/shape/Shape.d.ts.map +1 -1
  40. package/dist/axis/shape/Shape.js +2 -2
  41. package/dist/axis/shape/Shape.js.map +1 -1
  42. package/dist/entity/EntityAdapterSet.d.ts.map +1 -1
  43. package/dist/entity/EntityAdapterSet.js +3 -2
  44. package/dist/entity/EntityAdapterSet.js.map +1 -1
  45. package/dist/entity.d.ts +0 -1
  46. package/dist/entity.d.ts.map +1 -1
  47. package/dist/entity.js +0 -1
  48. package/dist/entity.js.map +1 -1
  49. package/dist/field/Field.d.ts.map +1 -1
  50. package/dist/field/Field.js +5 -4
  51. package/dist/field/Field.js.map +1 -1
  52. package/dist/field/FieldSet.d.ts.map +1 -1
  53. package/dist/field/FieldSet.js +5 -3
  54. package/dist/field/FieldSet.js.map +1 -1
  55. package/dist/field/Values.d.ts +6 -0
  56. package/dist/field/Values.d.ts.map +1 -0
  57. package/dist/field/Values.js.map +1 -0
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +1 -0
  61. package/dist/index.js.map +1 -1
  62. package/dist/lib/Registry.d.ts +1 -2
  63. package/dist/lib/Registry.d.ts.map +1 -1
  64. package/dist/lib/Registry.js +4 -4
  65. package/dist/lib/Registry.js.map +1 -1
  66. package/dist/lib/Verdict.d.ts +12 -0
  67. package/dist/lib/Verdict.d.ts.map +1 -0
  68. package/dist/lib/Verdict.js +2 -0
  69. package/dist/lib/Verdict.js.map +1 -0
  70. package/dist/projection/Cases.js +52 -54
  71. package/dist/projection/Cases.js.map +1 -1
  72. package/dist/projection/card/Bundle.d.ts.map +1 -1
  73. package/dist/projection/card/Bundle.js +2 -1
  74. package/dist/projection/card/Bundle.js.map +1 -1
  75. package/dist/projection/card/Card.d.ts +1 -1
  76. package/dist/projection/card/Card.d.ts.map +1 -1
  77. package/dist/projection/card/Card.js +7 -2
  78. package/dist/projection/card/Card.js.map +1 -1
  79. package/dist/projection/card/Diff.d.ts.map +1 -1
  80. package/dist/projection/card/Diff.js +34 -37
  81. package/dist/projection/card/Diff.js.map +1 -1
  82. package/dist/projection/card/admission.d.ts.map +1 -1
  83. package/dist/projection/card/admission.js +2 -1
  84. package/dist/projection/card/admission.js.map +1 -1
  85. package/dist/validator/AdapterFieldValidator.d.ts.map +1 -1
  86. package/dist/validator/AdapterFieldValidator.js +3 -2
  87. package/dist/validator/AdapterFieldValidator.js.map +1 -1
  88. package/dist/validator/FieldValueValidator.d.ts +3 -3
  89. package/dist/validator/FieldValueValidator.d.ts.map +1 -1
  90. package/dist/validator/FieldValueValidator.js +12 -11
  91. package/dist/validator/FieldValueValidator.js.map +1 -1
  92. package/dist/validator/InputValidator.d.ts +3 -0
  93. package/dist/validator/InputValidator.d.ts.map +1 -1
  94. package/dist/validator/InputValidator.js +28 -31
  95. package/dist/validator/InputValidator.js.map +1 -1
  96. package/dist/vocabulary/primitive/list.d.ts.map +1 -1
  97. package/dist/vocabulary/primitive/list.js +2 -1
  98. package/dist/vocabulary/primitive/list.js.map +1 -1
  99. package/dist/vocabulary/vocabulary.d.ts.map +1 -1
  100. package/dist/vocabulary/vocabulary.js +2 -1
  101. package/dist/vocabulary/vocabulary.js.map +1 -1
  102. package/package.json +1 -1
  103. package/src/Schema.ts +6 -4
  104. package/src/SchemaConstructor.ts +4 -4
  105. package/src/SchemaDefinition.ts +2 -1
  106. package/src/SchemaError.ts +7 -0
  107. package/src/SchemaView.ts +1 -1
  108. package/src/axis/boundary/Boundary.ts +3 -2
  109. package/src/axis/boundary/Decoder.ts +4 -3
  110. package/src/axis/lifecycle/Clock.ts +9 -13
  111. package/src/axis/lifecycle/Generators.ts +6 -18
  112. package/src/axis/lifecycle/Lifecycle.ts +14 -9
  113. package/src/axis/lifecycle/apply.ts +2 -16
  114. package/src/axis/shape/Shape.ts +3 -3
  115. package/src/entity/EntityAdapterSet.ts +3 -2
  116. package/src/entity.ts +0 -1
  117. package/src/field/Field.ts +5 -4
  118. package/src/field/FieldSet.ts +5 -3
  119. package/src/{Values.ts → field/Values.ts} +2 -2
  120. package/src/index.ts +2 -0
  121. package/src/lib/Registry.ts +5 -4
  122. package/src/lib/Verdict.ts +10 -0
  123. package/src/projection/Cases.ts +61 -57
  124. package/src/projection/card/Bundle.ts +2 -1
  125. package/src/projection/card/Card.ts +12 -4
  126. package/src/projection/card/Diff.ts +42 -42
  127. package/src/projection/card/admission.ts +2 -1
  128. package/src/validator/AdapterFieldValidator.ts +3 -2
  129. package/src/validator/FieldValueValidator.ts +15 -14
  130. package/src/validator/InputValidator.ts +30 -32
  131. package/src/vocabulary/primitive/list.ts +2 -1
  132. package/src/vocabulary/vocabulary.ts +2 -1
  133. package/dist/PartialValues.d.ts +0 -4
  134. package/dist/PartialValues.d.ts.map +0 -1
  135. package/dist/PartialValues.js +0 -2
  136. package/dist/PartialValues.js.map +0 -1
  137. package/dist/Values.d.ts +0 -6
  138. package/dist/Values.d.ts.map +0 -1
  139. package/dist/Values.js.map +0 -1
  140. package/dist/axis/boundary/Boundaries.d.ts +0 -17
  141. package/dist/axis/boundary/Boundaries.d.ts.map +0 -1
  142. package/dist/axis/boundary/Boundaries.js +0 -25
  143. package/dist/axis/boundary/Boundaries.js.map +0 -1
  144. package/dist/lib/Checked.d.ts +0 -11
  145. package/dist/lib/Checked.d.ts.map +0 -1
  146. package/dist/lib/Checked.js +0 -2
  147. package/dist/lib/Checked.js.map +0 -1
  148. package/dist/projection/standard.d.ts +0 -14
  149. package/dist/projection/standard.d.ts.map +0 -1
  150. package/dist/projection/standard.js +0 -2
  151. package/dist/projection/standard.js.map +0 -1
  152. package/src/PartialValues.ts +0 -4
  153. package/src/axis/boundary/Boundaries.ts +0 -36
  154. package/src/lib/Checked.ts +0 -5
  155. package/src/projection/standard.ts +0 -13
  156. /package/dist/{Values.js → field/Values.js} +0 -0
@@ -27,65 +27,65 @@ export function compare(
27
27
  is: SchemaDescriptor,
28
28
  options: DiffOptions = {},
29
29
  ): Diff {
30
- const changes: Change[] = [];
31
30
  const renamed = options.renamed ?? {};
32
31
  const before = was.properties ?? {};
33
32
  const after = is.properties ?? {};
34
- const requiredBefore = new Set(was.required ?? []);
35
- const requiredAfter = new Set(is.required ?? []);
33
+ const wasRequired = new Set(was.required ?? []);
34
+ const isRequired = new Set(is.required ?? []);
36
35
 
37
- // Apply a declared rename first so subsequent differences use the new field name.
38
- const nameAfter = (field: string): string => renamed[field] ?? field;
36
+ const kept: Change[] = [];
39
37
  const removed: string[] = [];
38
+ // A declared rename first, so every difference below is read under the new name.
40
39
  for (const [field, descriptor] of Object.entries(before)) {
41
- const now = nameAfter(field);
40
+ const now = renamed[field] ?? field;
42
41
  const target = after[now];
43
42
  if (target === undefined) {
44
43
  removed.push(field);
45
44
  continue;
46
45
  }
46
+ kept.push(...movedIn(field, now, descriptor, target, wasRequired.has(field), isRequired.has(now)));
47
+ }
47
48
 
48
- if (now !== field)
49
- changes.push({ kind: 'renamed', from: field, to: now, field: target });
49
+ const claimed = new Set(Object.values(renamed));
50
+ const added = Object.keys(after).filter((field) => !(field in before) && !claimed.has(field));
50
51
 
51
- const wasType = typesOf(descriptor);
52
- const isType = typesOf(target);
53
- if (!dequal(wasType, isType))
54
- changes.push({ kind: 'retyped', field: now, from: wasType, to: isType });
55
- else if (!dequal(boundsOf(descriptor), boundsOf(target))) {
56
- changes.push({ kind: 'reshaped', field: now, from: descriptor, to: target });
57
- }
52
+ return {
53
+ changes: [
54
+ ...kept,
55
+ ...removed.map((field): Change => ({
56
+ kind: 'removed', field, from: before[field]!, required: wasRequired.has(field),
57
+ })),
58
+ ...added.map((field): Change => ({
59
+ kind: 'added', field, to: after[field]!, required: isRequired.has(field),
60
+ })),
61
+ ],
62
+ ambiguous: candidates(removed, added, before, after),
63
+ };
64
+ }
58
65
 
59
- const wasRequired = requiredBefore.has(field);
60
- const isRequired = requiredAfter.has(now);
61
- if (wasRequired !== isRequired) {
62
- changes.push({ kind: 'required', field: now, from: wasRequired, to: isRequired });
63
- }
64
- changes.push(...restated(now, descriptor['x-fougere'], target['x-fougere']));
65
- }
66
+ /** What one field that survived the two descriptions says about itself now. */
67
+ function movedIn(
68
+ field: string,
69
+ now: string,
70
+ before: FieldDescriptor,
71
+ after: FieldDescriptor,
72
+ was: boolean,
73
+ is: boolean,
74
+ ): Change[] {
75
+ const changes: Change[] = [];
76
+ if (now !== field) changes.push({ kind: 'renamed', from: field, to: now, field: after });
66
77
 
67
- const claimed = new Set(Object.values(renamed));
68
- const added = Object.keys(after).filter(
69
- (field) => !(field in before) && !claimed.has(field),
70
- );
71
- for (const field of removed) {
72
- changes.push({
73
- kind: 'removed',
74
- field,
75
- from: before[field],
76
- required: requiredBefore.has(field),
77
- });
78
- }
79
- for (const field of added) {
80
- changes.push({
81
- kind: 'added',
82
- field,
83
- to: after[field],
84
- required: requiredAfter.has(field),
85
- });
78
+ const wasType = typesOf(before);
79
+ const isType = typesOf(after);
80
+ if (!dequal(wasType, isType)) changes.push({ kind: 'retyped', field: now, from: wasType, to: isType });
81
+ else if (!dequal(boundsOf(before), boundsOf(after))) {
82
+ changes.push({ kind: 'reshaped', field: now, from: before, to: after });
86
83
  }
87
84
 
88
- return { changes, ambiguous: candidates(removed, added, before, after) };
85
+ if (was !== is) changes.push({ kind: 'required', field: now, from: was, to: is });
86
+ changes.push(...restated(now, before['x-fougere'], after['x-fougere']));
87
+
88
+ return changes;
89
89
  }
90
90
 
91
91
  function restated(
@@ -1,6 +1,7 @@
1
1
  import type { ValidationError } from '../../lib/ValidationError.js';
2
2
  import { Shapes } from '../../axis/shape/Shape.js';
3
3
  import { dotted } from '../../lib/ValidationResult.js';
4
+ import { SchemaError } from '../../SchemaError.js';
4
5
 
5
6
  /**
6
7
  * Throws with what is wrong AND what to write: a card's author is in another process.
@@ -8,7 +9,7 @@ import { dotted } from '../../lib/ValidationResult.js';
8
9
  * `refuse('role.relation.kind is "maybe"', 'Expected one of one, many.')`
9
10
  */
10
11
  export function refuse(what: string, fix: string): never {
11
- throw new Error(`This card cannot be read: ${what}.\n ${fix}`);
12
+ throw new SchemaError(`This card cannot be read: ${what}.\n ${fix}`);
12
13
  }
13
14
 
14
15
  /**
@@ -1,6 +1,7 @@
1
1
  import { Validator } from '@cfworker/json-schema';
2
2
  import type { Shape } from '../axis/shape/Shape.js';
3
3
  import { isObject } from '../lib/utils.js';
4
+ import { SchemaError } from '../SchemaError.js';
4
5
 
5
6
  /**
6
7
  * The format is DATA, not a TypeScript interface: an interface is erased before a JS
@@ -19,7 +20,7 @@ export class AdapterFieldValidator {
19
20
  if (entries === undefined) return;
20
21
 
21
22
  if (!isObject(entries)) {
22
- throw new Error(
23
+ throw new SchemaError(
23
24
  `${path}: expected an object keyed by field name, got ${typeof entries}.`,
24
25
  );
25
26
  }
@@ -34,7 +35,7 @@ export class AdapterFieldValidator {
34
35
  const failure = units[units.length - 1] ?? verdict.errors[0];
35
36
  const nested = (failure?.instanceLocation ?? '#').slice(1).replaceAll('/', '.');
36
37
 
37
- throw new Error(`${path}.${field}${nested}: ${failure?.error ?? 'does not match the format'}`);
38
+ throw new SchemaError(`${path}.${field}${nested}: ${failure?.error ?? 'does not match the format'}`);
38
39
  }
39
40
  }
40
41
  }
@@ -5,8 +5,9 @@ import { Formats } from '../axis/shape/Formats.js';
5
5
  import { type Shape } from '../axis/shape/Shape.js';
6
6
  import { Shapes } from '../axis/shape/Shape.js';
7
7
  import type { Field } from '../field/Field.js';
8
- import type { Checked } from '../lib/Checked.js';
8
+ import type { Verdict } from '../lib/Verdict.js';
9
9
  import type { OutputUnit } from '@cfworker/json-schema';
10
+ import { SchemaError } from '../SchemaError.js';
10
11
 
11
12
  interface ShapePlan {
12
13
  validator: Validator;
@@ -24,24 +25,24 @@ export class FieldValueValidator {
24
25
  return new FieldValueValidator(field);
25
26
  }
26
27
 
27
- validate(value: unknown): Checked {
28
+ validate(value: unknown): Verdict {
28
29
  const shape = this.field.shape;
29
30
  const type = Shapes.typeOf(shape);
30
31
  const base = Shapes.of(shape).base;
31
32
  if (value !== null) {
32
33
  if (base?.type === 'object' && !base.properties) return { value };
33
34
  if (type === 'date' && value instanceof Date) {
34
- return Number.isNaN(value.getTime()) ? { error: 'Invalid date' } : { value };
35
+ return Number.isNaN(value.getTime()) ? { message: 'Invalid date' } : { value };
35
36
  }
36
37
  if ((type === 'number' || type === 'integer') && typeof value === 'number' && Number.isNaN(value)) {
37
- return { error: 'Expected a number' };
38
+ return { message: 'Expected a number' };
38
39
  }
39
40
  }
40
41
  const plan = FieldValueValidator.planFor(shape);
41
42
  const result = plan.validator.validate(value);
42
43
  if (!result.valid) return refusalOf(result.errors);
43
44
  if (plan.custom && typeof value === 'string' && !plan.custom(value)) {
44
- return { error: `String does not match format "${plan.formatName}".` };
45
+ return { message: `String does not match format "${plan.formatName}".` };
45
46
  }
46
47
  return { value };
47
48
  }
@@ -50,12 +51,12 @@ export class FieldValueValidator {
50
51
  * The value admitted, then handed back in the form the domain writes — an ISO string
51
52
  * arrives as a `Date`. `null` passes untouched, and a refusal stops before the codec.
52
53
  */
53
- parse(value: unknown): Checked {
54
- const checked = this.validate(value);
55
- if ('error' in checked) return checked;
56
- if (checked.value === null) return { value: null };
54
+ parse(value: unknown): Verdict {
55
+ const verdict = this.validate(value);
56
+ if ('message' in verdict) return verdict;
57
+ if (verdict.value === null) return { value: null };
57
58
 
58
- return Boundary.of(this.field).decode(checked.value);
59
+ return Boundary.of(this.field).decode(verdict.value);
59
60
  }
60
61
 
61
62
  private static planFor(shape: Shape): ShapePlan {
@@ -76,7 +77,7 @@ export class FieldValueValidator {
76
77
  private static customFormatOf(name: string): FormatPredicate | undefined {
77
78
  const custom = Formats.find(name);
78
79
  if (!custom && !(name in engineFormats)) {
79
- throw new Error(
80
+ throw new SchemaError(
80
81
  `Unknown format: '${name}'. Register it with Formats.register('${name}', …) — ` +
81
82
  `the engine validates ${Object.keys(engineFormats).length} formats natively and this is not one of them.`,
82
83
  );
@@ -90,15 +91,15 @@ export class FieldValueValidator {
90
91
  * parent's `Property "street" does not match schema.` — true, and never the reason. The
91
92
  * DEEPEST one is the reason, and it is the one that says where.
92
93
  */
93
- function refusalOf(errors: readonly OutputUnit[]): Checked {
94
+ function refusalOf(errors: readonly OutputUnit[]): Verdict {
94
95
  const deepest = errors.reduce<OutputUnit | undefined>(
95
96
  (held, one) => (held && depthOf(held) >= depthOf(one) ? held : one),
96
97
  undefined,
97
98
  );
98
- if (!deepest) return { error: 'Invalid value' };
99
+ if (!deepest) return { message: 'Invalid value' };
99
100
  const path = locationOf(deepest.instanceLocation);
100
101
 
101
- return path.length > 0 ? { error: deepest.error, path } : { error: deepest.error };
102
+ return path.length > 0 ? { message: deepest.error, path } : { message: deepest.error };
102
103
  }
103
104
 
104
105
  const depthOf = (unit: OutputUnit): number => locationOf(unit.instanceLocation).length;
@@ -3,6 +3,7 @@ import { Lifecycle } from '../axis/lifecycle/Lifecycle.js';
3
3
  import { Role } from '../axis/role/Role.js';
4
4
  import type { Field } from '../field/Field.js';
5
5
  import type { Fields } from '../field/Fields.js';
6
+ import type { Verdict } from '../lib/Verdict.js';
6
7
  import type { ValidationError } from '../lib/ValidationError.js';
7
8
  import type { ValidationResult } from '../lib/ValidationResult.js';
8
9
  import { FieldValueValidator } from './FieldValueValidator.js';
@@ -36,45 +37,42 @@ export class InputValidator {
36
37
  }
37
38
 
38
39
  const data = input as Record<string, unknown>;
39
- const errors: ValidationError[] = [];
40
+ const errors: ValidationError[] = this.unknownIn(data);
40
41
  const row: Record<string, unknown> = {};
41
42
 
42
- for (const key of Object.keys(data)) {
43
- if (!Object.hasOwn(this.fields, key)) {
44
- errors.push({ path: [key], message: InputRefusal.unknownField });
45
- }
43
+ for (const [key, field] of Object.entries(this.fields)) {
44
+ const verdict = this.admit(field, data[key]);
45
+ if (verdict === undefined) continue;
46
+ if ('message' in verdict) errors.push({ path: [key, ...(verdict.path ?? [])], message: verdict.message });
47
+ else row[key] = verdict.value;
46
48
  }
47
49
 
48
- for (const [key, field] of Object.entries(this.fields)) {
49
- const path = [key];
50
- const value = data[key];
50
+ if (errors.length > 0) return { success: false, errors };
51
51
 
52
- if (value === undefined) {
53
- if (this.options.patch) continue;
54
- const absence = this.onAbsent(field);
55
- if (absence === null) {
56
- errors.push({ path, message: InputRefusal.required });
57
- continue;
58
- }
59
- if (absence === 'empty-list') row[key] = [];
60
- continue;
61
- }
52
+ return { success: true, data: row };
53
+ }
62
54
 
63
- if (Boundary.of(field).readOnly) {
64
- errors.push({ path, message: InputRefusal.readOnly });
65
- continue;
66
- }
67
- if (this.options.patch && Lifecycle.of(field).immutable) {
68
- errors.push({ path, message: InputRefusal.immutable });
69
- continue;
70
- }
55
+ private unknownIn(data: Record<string, unknown>): ValidationError[] {
56
+ return Object.keys(data)
57
+ .filter((key) => !Object.hasOwn(this.fields, key))
58
+ .map((key) => ({ path: [key], message: InputRefusal.unknownField }));
59
+ }
71
60
 
72
- const parsed = FieldValueValidator.of(field).parse(value);
73
- if ('error' in parsed) errors.push({ path: [...path, ...parsed.path ?? []], message: parsed.error });
74
- else row[key] = parsed.value;
75
- }
61
+ private admit(field: Field, value: unknown): Verdict | undefined {
62
+ if (value === undefined) return this.whenAbsent(field);
76
63
 
77
- if (errors.length > 0) return { success: false, errors };
78
- return { success: true, data: row };
64
+ if (Boundary.of(field).readOnly) return { message: InputRefusal.readOnly };
65
+ if (this.options.patch && Lifecycle.of(field).immutable) return { message: InputRefusal.immutable };
66
+
67
+ return FieldValueValidator.of(field).parse(value);
68
+ }
69
+
70
+ private whenAbsent(field: Field): Verdict | undefined {
71
+ if (this.options.patch) return undefined;
72
+
73
+ const absence = this.onAbsent(field);
74
+ if (absence === null) return { message: InputRefusal.required };
75
+
76
+ return absence === 'empty-list' ? { value: [] } : undefined;
79
77
  }
80
78
  }
@@ -1,4 +1,5 @@
1
1
  import { Field } from '../../field/Field.js';
2
+ import { SchemaError } from '../../SchemaError.js';
2
3
 
3
4
  export interface ListOptions {
4
5
  min?: number;
@@ -12,7 +13,7 @@ export interface ListOptions {
12
13
  */
13
14
  export function list<T>(item: Field<T>, opts?: ListOptions): Field<T[]> {
14
15
  if (item.role?.relation)
15
- throw new Error(
16
+ throw new SchemaError(
16
17
  'list() takes a value field (text(), number()…) — a relation has no value shape',
17
18
  );
18
19
  return new Field<T[]>({
@@ -1,6 +1,7 @@
1
1
  import { Field } from '../field/Field.js';
2
2
  import { EXTENSION_SLOTS } from '../axis/Axis.js';
3
3
  import { dequal } from 'dequal';
4
+ import { SchemaError } from '../SchemaError.js';
4
5
 
5
6
  const MEMBER_SLOTS = [...EXTENSION_SLOTS, 'meta'] as const;
6
7
 
@@ -38,7 +39,7 @@ function merge(name: string, field: Field, given: Partial<Field>): Partial<Field
38
39
  for (const [member, value] of Object.entries(members)) {
39
40
  const previous = already?.[member];
40
41
  if (previous === undefined || dequal(previous, value)) continue;
41
- throw new Error(
42
+ throw new SchemaError(
42
43
  `vocabulary: \`${name}\` states ${slot}.${member} = ${JSON.stringify(value)}, but the ` +
43
44
  `field already states ${JSON.stringify(previous)}. Apply one or the other, not both.`,
44
45
  );
@@ -1,4 +0,0 @@
1
- import type { Fields } from './field/Fields.js';
2
- import type { Values } from './Values.js';
3
- export type PartialValues<TFields extends Fields> = Partial<Values<TFields>>;
4
- //# sourceMappingURL=PartialValues.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PartialValues.d.ts","sourceRoot":"","sources":["../src/PartialValues.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,MAAM,aAAa,CAAC,OAAO,SAAS,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=PartialValues.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PartialValues.js","sourceRoot":"","sources":["../src/PartialValues.ts"],"names":[],"mappings":""}
package/dist/Values.d.ts DELETED
@@ -1,6 +0,0 @@
1
- import type { Field } from './field/Field.js';
2
- import type { Fields } from './field/Fields.js';
3
- export type Values<TFields extends Fields> = {
4
- [K in keyof TFields]: TFields[K] extends Field<infer T> ? T : never;
5
- };
6
- //# sourceMappingURL=Values.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Values.d.ts","sourceRoot":"","sources":["../src/Values.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,MAAM,MAAM,CAAC,OAAO,SAAS,MAAM,IAAI;KAC1C,CAAC,IAAI,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK;CACpE,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"Values.js","sourceRoot":"","sources":["../src/Values.ts"],"names":[],"mappings":""}
@@ -1,17 +0,0 @@
1
- import type { Decoder } from './Decoder.js';
2
- import type { Encoder } from './Encoder.js';
3
- import type { BoundaryRules } from './BoundaryRules.js';
4
- import { Registry } from '../../lib/Registry.js';
5
- /**
6
- * `decoders` for a value coming in, `encoders` for one going out, `aliases` for the word
7
- * that names a pair. Three registries, so a codec is never half declared.
8
- * FR : `decoders` à l'entrée, `encoders` à la sortie, `aliases` pour le mot qui nomme la paire.
9
- * `Boundaries.aliases.register('isoDate', { in: { decode: 'isoDate' }, out: { encode: 'isoDate' } })`
10
- * → a field saying `boundary: 'isoDate'` decodes to a `Date` and leaves as a string
11
- */
12
- export declare const Boundaries: {
13
- decoders: Registry<Decoder>;
14
- encoders: Registry<Encoder>;
15
- aliases: Registry<BoundaryRules>;
16
- };
17
- //# sourceMappingURL=Boundaries.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Boundaries.d.ts","sourceRoot":"","sources":["../../../src/axis/boundary/Boundaries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,UAAU;IACrB,QAAQ;IAIR,QAAQ;IAIR,OAAO;CACR,CAAC"}
@@ -1,25 +0,0 @@
1
- import { Registry } from '../../lib/Registry.js';
2
- /**
3
- * `decoders` for a value coming in, `encoders` for one going out, `aliases` for the word
4
- * that names a pair. Three registries, so a codec is never half declared.
5
- * FR : `decoders` à l'entrée, `encoders` à la sortie, `aliases` pour le mot qui nomme la paire.
6
- * `Boundaries.aliases.register('isoDate', { in: { decode: 'isoDate' }, out: { encode: 'isoDate' } })`
7
- * → a field saying `boundary: 'isoDate'` decodes to a `Date` and leaves as a string
8
- */
9
- export const Boundaries = {
10
- decoders: new Registry('boundary decoder', 'call Boundaries.decoders.register(name, fn)'),
11
- encoders: new Registry('boundary encoder', 'call Boundaries.encoders.register(name, fn)'),
12
- aliases: new Registry('boundary alias'),
13
- };
14
- Boundaries.decoders.register('isoDate', (value) => {
15
- if (value instanceof Date)
16
- return { value };
17
- if (typeof value === 'string') {
18
- const date = new Date(value);
19
- return Number.isNaN(date.getTime()) ? { error: 'Invalid date' } : { value: date };
20
- }
21
- return { error: 'Expected a date' };
22
- });
23
- Boundaries.encoders.register('isoDate', (value) => value instanceof Date ? value.toISOString() : value);
24
- Boundaries.aliases.register('isoDate', { in: { decode: 'isoDate' }, out: { encode: 'isoDate' } });
25
- //# sourceMappingURL=Boundaries.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Boundaries.js","sourceRoot":"","sources":["../../../src/axis/boundary/Boundaries.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,QAAQ,EAAE,IAAI,QAAQ,CACpB,kBAAkB,EAClB,6CAA6C,CAC9C;IACD,QAAQ,EAAE,IAAI,QAAQ,CACpB,kBAAkB,EAClB,6CAA6C,CAC9C;IACD,OAAO,EAAE,IAAI,QAAQ,CAAgB,gBAAgB,CAAC;CACvD,CAAC;AAEF,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;IAChD,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7B,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACpF,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;AACtC,CAAC,CAAC,CAAC;AACH,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAChD,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,KAAK,CACpD,CAAC;AACF,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC"}
@@ -1,11 +0,0 @@
1
- /**
2
- * A field's verdict. `path` is where INSIDE the value the refusal happened — empty when the
3
- * field itself is refused, `['street']` when the shape below it is.
4
- */
5
- export type Checked = {
6
- value: unknown;
7
- } | {
8
- error: string;
9
- path?: readonly string[];
10
- };
11
- //# sourceMappingURL=Checked.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Checked.d.ts","sourceRoot":"","sources":["../../src/lib/Checked.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,OAAO,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=Checked.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Checked.js","sourceRoot":"","sources":["../../src/lib/Checked.ts"],"names":[],"mappings":""}
@@ -1,14 +0,0 @@
1
- /**
2
- * The Standard Schema interface, from the package that owns it.
3
- *
4
- * This file used to CARRY the spec — 47 lines of hand-copied interface — and the copy had
5
- * silently fallen a version behind: v1.1 gave `Props` a base it extends (`StandardTypedV1`)
6
- * and gave `validate` a second `options` parameter. Nothing broke, because a realization may
7
- * ignore an optional argument, which is exactly why nobody noticed. A standard is owned by
8
- * whoever publishes it; the package is types only (`index.js` is zero bytes), so importing
9
- * it costs a browser nothing.
10
- *
11
- * Documented: [Standard Schema](https://fougere.dev/docs/schema/standard-schema).
12
- */
13
- export type { StandardSchemaV1 } from '@standard-schema/spec';
14
- //# sourceMappingURL=standard.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"standard.d.ts","sourceRoot":"","sources":["../../src/projection/standard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=standard.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"standard.js","sourceRoot":"","sources":["../../src/projection/standard.ts"],"names":[],"mappings":""}
@@ -1,4 +0,0 @@
1
- import type { Fields } from './field/Fields.js';
2
- import type { Values } from './Values.js';
3
-
4
- export type PartialValues<TFields extends Fields> = Partial<Values<TFields>>;
@@ -1,36 +0,0 @@
1
- import type { Decoder } from './Decoder.js';
2
- import type { Encoder } from './Encoder.js';
3
- import type { BoundaryRules } from './BoundaryRules.js';
4
- import { Registry } from '../../lib/Registry.js';
5
-
6
- /**
7
- * `decoders` for a value coming in, `encoders` for one going out, `aliases` for the word
8
- * that names a pair. Three registries, so a codec is never half declared.
9
- * FR : `decoders` à l'entrée, `encoders` à la sortie, `aliases` pour le mot qui nomme la paire.
10
- * `Boundaries.aliases.register('isoDate', { in: { decode: 'isoDate' }, out: { encode: 'isoDate' } })`
11
- * → a field saying `boundary: 'isoDate'` decodes to a `Date` and leaves as a string
12
- */
13
- export const Boundaries = {
14
- decoders: new Registry<Decoder>(
15
- 'boundary decoder',
16
- 'call Boundaries.decoders.register(name, fn)',
17
- ),
18
- encoders: new Registry<Encoder>(
19
- 'boundary encoder',
20
- 'call Boundaries.encoders.register(name, fn)',
21
- ),
22
- aliases: new Registry<BoundaryRules>('boundary alias'),
23
- };
24
-
25
- Boundaries.decoders.register('isoDate', (value) => {
26
- if (value instanceof Date) return { value };
27
- if (typeof value === 'string') {
28
- const date = new Date(value);
29
- return Number.isNaN(date.getTime()) ? { error: 'Invalid date' } : { value: date };
30
- }
31
- return { error: 'Expected a date' };
32
- });
33
- Boundaries.encoders.register('isoDate', (value) =>
34
- value instanceof Date ? value.toISOString() : value,
35
- );
36
- Boundaries.aliases.register('isoDate', { in: { decode: 'isoDate' }, out: { encode: 'isoDate' } });
@@ -1,5 +0,0 @@
1
- /**
2
- * A field's verdict. `path` is where INSIDE the value the refusal happened — empty when the
3
- * field itself is refused, `['street']` when the shape below it is.
4
- */
5
- export type Checked = { value: unknown } | { error: string; path?: readonly string[] };
@@ -1,13 +0,0 @@
1
- /**
2
- * The Standard Schema interface, from the package that owns it.
3
- *
4
- * This file used to CARRY the spec — 47 lines of hand-copied interface — and the copy had
5
- * silently fallen a version behind: v1.1 gave `Props` a base it extends (`StandardTypedV1`)
6
- * and gave `validate` a second `options` parameter. Nothing broke, because a realization may
7
- * ignore an optional argument, which is exactly why nobody noticed. A standard is owned by
8
- * whoever publishes it; the package is types only (`index.js` is zero bytes), so importing
9
- * it costs a browser nothing.
10
- *
11
- * Documented: [Standard Schema](https://fougere.dev/docs/schema/standard-schema).
12
- */
13
- export type { StandardSchemaV1 } from '@standard-schema/spec';
File without changes