@fougere/schema 0.12.0-alpha.0 → 0.12.2-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FougereFieldAxes.d.ts +3 -0
- package/dist/FougereFieldAxes.d.ts.map +1 -0
- package/dist/FougereFieldAxes.js +2 -0
- package/dist/FougereFieldAxes.js.map +1 -0
- package/dist/Schema.d.ts +4 -3
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +3 -0
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaConstraints.d.ts +13 -10
- package/dist/SchemaConstraints.d.ts.map +1 -1
- package/dist/SchemaConstraints.js +32 -20
- package/dist/SchemaConstraints.js.map +1 -1
- package/dist/SchemaDefinition.d.ts.map +1 -1
- package/dist/SchemaDefinition.js +6 -5
- package/dist/SchemaDefinition.js.map +1 -1
- package/dist/SchemaError.d.ts +4 -1
- package/dist/SchemaError.d.ts.map +1 -1
- package/dist/SchemaError.js +6 -3
- package/dist/SchemaError.js.map +1 -1
- package/dist/SchemaView.d.ts +3 -2
- package/dist/SchemaView.d.ts.map +1 -1
- package/dist/axis/Axes.d.ts +16 -0
- package/dist/axis/Axes.d.ts.map +1 -0
- package/dist/axis/Axes.js +27 -0
- package/dist/axis/Axes.js.map +1 -0
- package/dist/axis/Axis.d.ts +15 -9
- package/dist/axis/Axis.d.ts.map +1 -1
- package/dist/axis/Axis.js +1 -10
- package/dist/axis/Axis.js.map +1 -1
- package/dist/axis/boundary/Boundary.d.ts +38 -5
- package/dist/axis/boundary/Boundary.d.ts.map +1 -1
- package/dist/axis/boundary/Boundary.js +18 -11
- package/dist/axis/boundary/Boundary.js.map +1 -1
- package/dist/axis/boundary/Decoder.d.ts +8 -2
- package/dist/axis/boundary/Decoder.d.ts.map +1 -1
- package/dist/axis/boundary/Decoder.js.map +1 -1
- package/dist/axis/lifecycle/Lifecycle.d.ts +29 -8
- package/dist/axis/lifecycle/Lifecycle.d.ts.map +1 -1
- package/dist/axis/lifecycle/Lifecycle.js +26 -11
- package/dist/axis/lifecycle/Lifecycle.js.map +1 -1
- package/dist/axis/lifecycle/apply.d.ts.map +1 -1
- package/dist/axis/lifecycle/apply.js +3 -4
- package/dist/axis/lifecycle/apply.js.map +1 -1
- package/dist/axis/role/Role.d.ts +31 -12
- package/dist/axis/role/Role.d.ts.map +1 -1
- package/dist/axis/role/Role.js +56 -22
- package/dist/axis/role/Role.js.map +1 -1
- package/dist/axis/shape/Shape.d.ts +32 -9
- package/dist/axis/shape/Shape.d.ts.map +1 -1
- package/dist/axis/shape/Shape.js +43 -12
- package/dist/axis/shape/Shape.js.map +1 -1
- package/dist/axis/shape/ShapeFormat.d.ts +10 -0
- package/dist/axis/shape/ShapeFormat.d.ts.map +1 -0
- package/dist/axis/shape/ShapeFormat.js +32 -0
- package/dist/axis/shape/ShapeFormat.js.map +1 -0
- package/dist/axis/shape/{Formats.d.ts → StringFormats.d.ts} +3 -3
- package/dist/axis/shape/StringFormats.d.ts.map +1 -0
- package/dist/axis/shape/{Formats.js → StringFormats.js} +3 -3
- package/dist/axis/shape/StringFormats.js.map +1 -0
- package/dist/entity/EntityDeclarations.d.ts +3 -2
- package/dist/entity/EntityDeclarations.d.ts.map +1 -1
- package/dist/entity/FieldGroups.d.ts +9 -0
- package/dist/entity/FieldGroups.d.ts.map +1 -0
- package/dist/entity/FieldGroups.js +2 -0
- package/dist/entity/FieldGroups.js.map +1 -0
- package/dist/field/Field.d.ts +37 -12
- package/dist/field/Field.d.ts.map +1 -1
- package/dist/field/Field.js +55 -8
- package/dist/field/Field.js.map +1 -1
- package/dist/field/FieldFormat.d.ts +9 -0
- package/dist/field/FieldFormat.d.ts.map +1 -0
- package/dist/field/FieldFormat.js +12 -0
- package/dist/field/FieldFormat.js.map +1 -0
- package/dist/field/FieldSet.d.ts +12 -3
- package/dist/field/FieldSet.d.ts.map +1 -1
- package/dist/field/FieldSet.js +22 -15
- package/dist/field/FieldSet.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/Format.d.ts +75 -0
- package/dist/lib/Format.d.ts.map +1 -0
- package/dist/lib/Format.js +123 -0
- package/dist/lib/Format.js.map +1 -0
- package/dist/lib/JsonSchema.d.ts +2 -0
- package/dist/lib/JsonSchema.d.ts.map +1 -0
- package/dist/lib/JsonSchema.js +2 -0
- package/dist/lib/JsonSchema.js.map +1 -0
- package/dist/lib/Registry.d.ts +4 -2
- package/dist/lib/Registry.d.ts.map +1 -1
- package/dist/lib/Registry.js +11 -5
- package/dist/lib/Registry.js.map +1 -1
- package/dist/lib/UnionToIntersection.d.ts +7 -0
- package/dist/lib/UnionToIntersection.d.ts.map +1 -0
- package/dist/lib/UnionToIntersection.js +2 -0
- package/dist/lib/UnionToIntersection.js.map +1 -0
- package/dist/lib/ValidationResult.d.ts +2 -0
- package/dist/lib/ValidationResult.d.ts.map +1 -1
- package/dist/lib/ValidationResult.js +6 -0
- package/dist/lib/ValidationResult.js.map +1 -1
- package/dist/lib/utils.d.ts +1 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/lib/utils.js.map +1 -1
- package/dist/projection/Cases.d.ts +1 -1
- package/dist/projection/Cases.d.ts.map +1 -1
- package/dist/projection/Cases.js +20 -8
- package/dist/projection/Cases.js.map +1 -1
- package/dist/projection/Visibility.d.ts +11 -0
- package/dist/projection/Visibility.d.ts.map +1 -1
- package/dist/projection/Visibility.js +28 -6
- package/dist/projection/Visibility.js.map +1 -1
- package/dist/projection/card/Bundle.d.ts.map +1 -1
- package/dist/projection/card/Bundle.js.map +1 -1
- package/dist/projection/card/Card.d.ts.map +1 -1
- package/dist/projection/card/Card.js +24 -30
- package/dist/projection/card/Card.js.map +1 -1
- package/dist/projection/card/CardForm.d.ts +7 -0
- package/dist/projection/card/CardForm.d.ts.map +1 -0
- package/dist/projection/card/CardForm.js +2 -0
- package/dist/projection/card/CardForm.js.map +1 -0
- package/dist/projection/card/CardForms.d.ts +10 -0
- package/dist/projection/card/CardForms.d.ts.map +1 -0
- package/dist/projection/card/CardForms.js +12 -0
- package/dist/projection/card/CardForms.js.map +1 -0
- package/dist/projection/card/Change.d.ts +2 -2
- package/dist/projection/card/Change.d.ts.map +1 -1
- package/dist/projection/card/Diff.js +2 -2
- package/dist/projection/card/Diff.js.map +1 -1
- package/dist/projection/card/FieldExtension.d.ts +4 -2
- package/dist/projection/card/FieldExtension.d.ts.map +1 -1
- package/dist/projection/card/RoleDescriptor.d.ts +15 -3
- package/dist/projection/card/RoleDescriptor.d.ts.map +1 -1
- package/dist/projection/card/RoleDescriptor.js +40 -1
- package/dist/projection/card/RoleDescriptor.js.map +1 -1
- package/dist/projection/card/ShapeKeywords.d.ts +1 -1
- package/dist/projection/card/ShapeKeywords.d.ts.map +1 -1
- package/dist/projection/card/admission.d.ts +0 -14
- package/dist/projection/card/admission.d.ts.map +1 -1
- package/dist/projection/card/admission.js +0 -20
- package/dist/projection/card/admission.js.map +1 -1
- package/dist/validator/FieldDeclarationValidator.d.ts.map +1 -1
- package/dist/validator/FieldDeclarationValidator.js +10 -25
- package/dist/validator/FieldDeclarationValidator.js.map +1 -1
- package/dist/validator/FieldValueValidator.d.ts.map +1 -1
- package/dist/validator/FieldValueValidator.js +9 -29
- package/dist/validator/FieldValueValidator.js.map +1 -1
- package/dist/validator/InputValidator.d.ts +7 -1
- package/dist/validator/InputValidator.d.ts.map +1 -1
- package/dist/validator/InputValidator.js +15 -14
- package/dist/validator/InputValidator.js.map +1 -1
- package/dist/validator/JsonSchemaValidator.d.ts +23 -0
- package/dist/validator/JsonSchemaValidator.d.ts.map +1 -0
- package/dist/validator/JsonSchemaValidator.js +113 -0
- package/dist/validator/JsonSchemaValidator.js.map +1 -0
- package/dist/vocabulary/preset/created.d.ts +2 -2
- package/dist/vocabulary/preset/created.d.ts.map +1 -1
- package/dist/vocabulary/preset/created.js +2 -2
- package/dist/vocabulary/preset/created.js.map +1 -1
- package/dist/vocabulary/preset/updated.d.ts +2 -2
- package/dist/vocabulary/preset/updated.d.ts.map +1 -1
- package/dist/vocabulary/preset/updated.js +2 -2
- package/dist/vocabulary/preset/updated.js.map +1 -1
- package/dist/vocabulary/primitive/bool.d.ts +2 -5
- package/dist/vocabulary/primitive/bool.d.ts.map +1 -1
- package/dist/vocabulary/primitive/bool.js +1 -5
- package/dist/vocabulary/primitive/bool.js.map +1 -1
- package/dist/vocabulary/primitive/date.d.ts +2 -3
- package/dist/vocabulary/primitive/date.d.ts.map +1 -1
- package/dist/vocabulary/primitive/date.js +2 -3
- package/dist/vocabulary/primitive/date.js.map +1 -1
- package/dist/vocabulary/primitive/json.d.ts +3 -3
- package/dist/vocabulary/primitive/json.d.ts.map +1 -1
- package/dist/vocabulary/primitive/json.js +11 -11
- package/dist/vocabulary/primitive/json.js.map +1 -1
- package/dist/vocabulary/primitive/list.d.ts +6 -5
- package/dist/vocabulary/primitive/list.d.ts.map +1 -1
- package/dist/vocabulary/primitive/list.js +13 -7
- package/dist/vocabulary/primitive/list.js.map +1 -1
- package/dist/vocabulary/primitive/many.d.ts +2 -4
- package/dist/vocabulary/primitive/many.d.ts.map +1 -1
- package/dist/vocabulary/primitive/many.js +2 -4
- package/dist/vocabulary/primitive/many.js.map +1 -1
- package/dist/vocabulary/primitive/number.d.ts +2 -4
- package/dist/vocabulary/primitive/number.d.ts.map +1 -1
- package/dist/vocabulary/primitive/number.js +1 -3
- package/dist/vocabulary/primitive/number.js.map +1 -1
- package/dist/vocabulary/primitive/oneOf.d.ts +4 -7
- package/dist/vocabulary/primitive/oneOf.d.ts.map +1 -1
- package/dist/vocabulary/primitive/oneOf.js +19 -11
- package/dist/vocabulary/primitive/oneOf.js.map +1 -1
- package/dist/vocabulary/primitive/primary.d.ts +2 -2
- package/dist/vocabulary/primitive/primary.d.ts.map +1 -1
- package/dist/vocabulary/primitive/primary.js +2 -3
- package/dist/vocabulary/primitive/primary.js.map +1 -1
- package/dist/vocabulary/primitive/ref.d.ts +2 -6
- package/dist/vocabulary/primitive/ref.d.ts.map +1 -1
- package/dist/vocabulary/primitive/ref.js +1 -1
- package/dist/vocabulary/primitive/ref.js.map +1 -1
- package/dist/vocabulary/primitive/text.d.ts +2 -4
- package/dist/vocabulary/primitive/text.d.ts.map +1 -1
- package/dist/vocabulary/primitive/text.js +1 -3
- package/dist/vocabulary/primitive/text.js.map +1 -1
- package/dist/vocabulary/rule/nullable.d.ts +0 -1
- package/dist/vocabulary/rule/nullable.d.ts.map +1 -1
- package/dist/vocabulary/rule/nullable.js +0 -1
- package/dist/vocabulary/rule/nullable.js.map +1 -1
- package/dist/vocabulary/rule/optional.d.ts +0 -1
- package/dist/vocabulary/rule/optional.d.ts.map +1 -1
- package/dist/vocabulary/rule/optional.js +0 -1
- package/dist/vocabulary/rule/optional.js.map +1 -1
- package/dist/vocabulary/rule/readOnly.d.ts +0 -2
- package/dist/vocabulary/rule/readOnly.d.ts.map +1 -1
- package/dist/vocabulary/rule/readOnly.js +0 -2
- package/dist/vocabulary/rule/readOnly.js.map +1 -1
- package/dist/vocabulary/rule/writeOnly.d.ts +0 -2
- package/dist/vocabulary/rule/writeOnly.d.ts.map +1 -1
- package/dist/vocabulary/rule/writeOnly.js +0 -2
- package/dist/vocabulary/rule/writeOnly.js.map +1 -1
- package/dist/vocabulary/vocabulary.d.ts +2 -3
- package/dist/vocabulary/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary/vocabulary.js +18 -20
- package/dist/vocabulary/vocabulary.js.map +1 -1
- package/package.json +1 -1
- package/src/FougereFieldAxes.ts +2 -0
- package/src/Schema.ts +11 -8
- package/src/SchemaConstraints.ts +40 -22
- package/src/SchemaDefinition.ts +7 -5
- package/src/SchemaError.ts +9 -3
- package/src/SchemaView.ts +3 -2
- package/src/axis/Axes.ts +34 -0
- package/src/axis/Axis.ts +15 -19
- package/src/axis/boundary/Boundary.ts +39 -13
- package/src/axis/boundary/Decoder.ts +3 -1
- package/src/axis/lifecycle/Lifecycle.ts +42 -14
- package/src/axis/lifecycle/apply.ts +12 -6
- package/src/axis/role/Role.ts +73 -25
- package/src/axis/shape/Shape.ts +89 -23
- package/src/axis/shape/ShapeFormat.ts +37 -0
- package/src/axis/shape/{Formats.ts → StringFormats.ts} +2 -2
- package/src/entity/EntityDeclarations.ts +3 -2
- package/src/entity/FieldGroups.ts +9 -0
- package/src/field/Field.ts +89 -16
- package/src/field/FieldFormat.ts +18 -0
- package/src/field/FieldSet.ts +27 -19
- package/src/index.ts +7 -3
- package/src/lib/Format.ts +172 -0
- package/src/lib/JsonSchema.ts +1 -0
- package/src/lib/Registry.ts +14 -6
- package/src/lib/UnionToIntersection.ts +10 -0
- package/src/lib/ValidationResult.ts +8 -0
- package/src/lib/utils.ts +5 -1
- package/src/projection/Cases.ts +25 -8
- package/src/projection/Visibility.ts +31 -6
- package/src/projection/card/Bundle.ts +2 -0
- package/src/projection/card/Card.ts +29 -27
- package/src/projection/card/CardForm.ts +8 -0
- package/src/projection/card/CardForms.ts +13 -0
- package/src/projection/card/Change.ts +2 -2
- package/src/projection/card/Diff.ts +6 -2
- package/src/projection/card/FieldExtension.ts +5 -2
- package/src/projection/card/RoleDescriptor.ts +54 -3
- package/src/projection/card/ShapeKeywords.ts +1 -1
- package/src/projection/card/admission.ts +0 -33
- package/src/validator/FieldDeclarationValidator.ts +10 -25
- package/src/validator/FieldValueValidator.ts +13 -36
- package/src/validator/InputValidator.ts +17 -12
- package/src/validator/JsonSchemaValidator.ts +142 -0
- package/src/vocabulary/preset/created.ts +3 -3
- package/src/vocabulary/preset/updated.ts +4 -3
- package/src/vocabulary/primitive/bool.ts +3 -8
- package/src/vocabulary/primitive/date.ts +5 -4
- package/src/vocabulary/primitive/json.ts +27 -12
- package/src/vocabulary/primitive/list.ts +16 -9
- package/src/vocabulary/primitive/many.ts +3 -4
- package/src/vocabulary/primitive/number.ts +3 -8
- package/src/vocabulary/primitive/oneOf.ts +40 -24
- package/src/vocabulary/primitive/primary.ts +9 -5
- package/src/vocabulary/primitive/ref.ts +3 -7
- package/src/vocabulary/primitive/text.ts +3 -8
- package/src/vocabulary/rule/nullable.ts +0 -1
- package/src/vocabulary/rule/optional.ts +0 -1
- package/src/vocabulary/rule/readOnly.ts +0 -2
- package/src/vocabulary/rule/writeOnly.ts +0 -2
- package/src/vocabulary/vocabulary.ts +19 -21
- package/dist/axis/boundary/BoundaryAxis.d.ts +0 -4
- package/dist/axis/boundary/BoundaryAxis.d.ts.map +0 -1
- package/dist/axis/boundary/BoundaryAxis.js +0 -30
- package/dist/axis/boundary/BoundaryAxis.js.map +0 -1
- package/dist/axis/boundary/BoundaryRef.d.ts +0 -3
- package/dist/axis/boundary/BoundaryRef.d.ts.map +0 -1
- package/dist/axis/boundary/BoundaryRef.js +0 -2
- package/dist/axis/boundary/BoundaryRef.js.map +0 -1
- package/dist/axis/boundary/BoundaryRules.d.ts +0 -9
- package/dist/axis/boundary/BoundaryRules.d.ts.map +0 -1
- package/dist/axis/boundary/BoundaryRules.js +0 -2
- package/dist/axis/boundary/BoundaryRules.js.map +0 -1
- package/dist/axis/lifecycle/Clock.d.ts +0 -10
- package/dist/axis/lifecycle/Clock.d.ts.map +0 -1
- package/dist/axis/lifecycle/Clock.js +0 -19
- package/dist/axis/lifecycle/Clock.js.map +0 -1
- package/dist/axis/lifecycle/LifecycleAxis.d.ts +0 -4
- package/dist/axis/lifecycle/LifecycleAxis.d.ts.map +0 -1
- package/dist/axis/lifecycle/LifecycleAxis.js +0 -52
- package/dist/axis/lifecycle/LifecycleAxis.js.map +0 -1
- package/dist/axis/lifecycle/LifecycleRules.d.ts +0 -12
- package/dist/axis/lifecycle/LifecycleRules.d.ts.map +0 -1
- package/dist/axis/lifecycle/LifecycleRules.js +0 -3
- package/dist/axis/lifecycle/LifecycleRules.js.map +0 -1
- package/dist/axis/role/RoleAxis.d.ts +0 -5
- package/dist/axis/role/RoleAxis.d.ts.map +0 -1
- package/dist/axis/role/RoleAxis.js +0 -93
- package/dist/axis/role/RoleAxis.js.map +0 -1
- package/dist/axis/role/RoleRules.d.ts +0 -8
- package/dist/axis/role/RoleRules.d.ts.map +0 -1
- package/dist/axis/role/RoleRules.js +0 -2
- package/dist/axis/role/RoleRules.js.map +0 -1
- package/dist/axis/shape/Formats.d.ts.map +0 -1
- package/dist/axis/shape/Formats.js.map +0 -1
- package/dist/entity/CompositeUnique.d.ts +0 -4
- package/dist/entity/CompositeUnique.d.ts.map +0 -1
- package/dist/entity/CompositeUnique.js +0 -2
- package/dist/entity/CompositeUnique.js.map +0 -1
- package/dist/field/Meta.d.ts +0 -4
- package/dist/field/Meta.d.ts.map +0 -1
- package/dist/field/Meta.js +0 -2
- package/dist/field/Meta.js.map +0 -1
- package/dist/projection/card/RelationDescriptor.d.ts +0 -5
- package/dist/projection/card/RelationDescriptor.d.ts.map +0 -1
- package/dist/projection/card/RelationDescriptor.js +0 -2
- package/dist/projection/card/RelationDescriptor.js.map +0 -1
- package/dist/validator/AdapterFieldValidator.d.ts +0 -14
- package/dist/validator/AdapterFieldValidator.d.ts.map +0 -1
- package/dist/validator/AdapterFieldValidator.js +0 -37
- package/dist/validator/AdapterFieldValidator.js.map +0 -1
- package/src/axis/boundary/BoundaryAxis.ts +0 -32
- package/src/axis/boundary/BoundaryRef.ts +0 -3
- package/src/axis/boundary/BoundaryRules.ts +0 -4
- package/src/axis/lifecycle/Clock.ts +0 -22
- package/src/axis/lifecycle/LifecycleAxis.ts +0 -55
- package/src/axis/lifecycle/LifecycleRules.ts +0 -9
- package/src/axis/role/RoleAxis.ts +0 -104
- package/src/axis/role/RoleRules.ts +0 -8
- package/src/entity/CompositeUnique.ts +0 -6
- package/src/field/Meta.ts +0 -3
- package/src/projection/card/RelationDescriptor.ts +0 -3
- package/src/validator/AdapterFieldValidator.ts +0 -41
package/src/lib/Registry.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { SchemaError } from '../SchemaError.js';
|
|
2
2
|
|
|
3
3
|
export class Registry<T> {
|
|
4
|
-
private readonly
|
|
4
|
+
private readonly held: Map<string, T>;
|
|
5
5
|
|
|
6
6
|
constructor(
|
|
7
7
|
private readonly label: string,
|
|
8
8
|
private readonly hint?: string,
|
|
9
9
|
entries?: Iterable<readonly [string, T]>,
|
|
10
10
|
) {
|
|
11
|
-
this.
|
|
11
|
+
this.held = new Map(entries);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
register(name: string, value: T): T {
|
|
15
|
-
this.
|
|
15
|
+
this.held.set(name, value);
|
|
16
16
|
|
|
17
17
|
return value;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
find(name: string): T | undefined {
|
|
21
|
-
return this.
|
|
21
|
+
return this.held.get(name);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
resolve(name: string, path?: string): T {
|
|
@@ -32,7 +32,15 @@ export class Registry<T> {
|
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
return [...this.
|
|
35
|
+
get names(): string[] {
|
|
36
|
+
return [...this.held.keys()];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get entries(): [string, T][] {
|
|
40
|
+
return [...this.held];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get all(): T[] {
|
|
44
|
+
return [...this.held.values()];
|
|
37
45
|
}
|
|
38
46
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a union becomes when every member must hold at once — `A | B` → `A & B`, through the
|
|
3
|
+
* contravariance of a parameter position, which is the only place TypeScript folds a union.
|
|
4
|
+
* `Merged<[Post, Draft]>` and `ShapeKeywords` both need it, and each had written it out.
|
|
5
|
+
*/
|
|
6
|
+
export type UnionToIntersection<U> = (U extends unknown ? (member: U) => void : never) extends (
|
|
7
|
+
member: infer Held,
|
|
8
|
+
) => void
|
|
9
|
+
? Held
|
|
10
|
+
: never;
|
|
@@ -9,3 +9,11 @@ export type ValidationResult<T> =
|
|
|
9
9
|
* reason the value stays segmented: this one cannot be read back.
|
|
10
10
|
*/
|
|
11
11
|
export const dotted = (path: readonly string[]): string => path.join('.');
|
|
12
|
+
|
|
13
|
+
/** `#/city/zip` — a JSON Pointer fragment, and `~1`/`~0` are how it spells `/` and `~`. */
|
|
14
|
+
export const locationOf = (instanceLocation: string): string[] =>
|
|
15
|
+
instanceLocation
|
|
16
|
+
.replace(/^#/, '')
|
|
17
|
+
.split('/')
|
|
18
|
+
.filter(Boolean)
|
|
19
|
+
.map((segment) => segment.replace(/~1/g, '/').replace(/~0/g, '~'));
|
package/src/lib/utils.ts
CHANGED
|
@@ -15,4 +15,8 @@ export function clean<T extends Record<string, unknown>>(obj: T): T {
|
|
|
15
15
|
export const isObject = (value: unknown): value is Record<string, unknown> =>
|
|
16
16
|
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
17
17
|
|
|
18
|
-
export const
|
|
18
|
+
export const isArrayOf = <T>(
|
|
19
|
+
values: readonly unknown[],
|
|
20
|
+
is: (value: unknown) => value is T,
|
|
21
|
+
): values is readonly T[] => values.every(is);
|
|
22
|
+
|
package/src/projection/Cases.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type { ValidationCase } from './ValidationCase.js';
|
|
|
16
16
|
function wrongTypeFor(field: Field): unknown {
|
|
17
17
|
const declared = (field.shape as { type?: string | readonly string[] }).type;
|
|
18
18
|
const names = Array.isArray(declared) ? declared : [declared];
|
|
19
|
+
|
|
19
20
|
return names.includes('string') ? 42 : 'not-a-value-of-this-shape';
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -42,6 +43,11 @@ function outOfBoundsFor(field: Field): { why: string; value: unknown }[] {
|
|
|
42
43
|
cases.push({ why: 'below minimum', value: shape.minimum - 1 });
|
|
43
44
|
if (typeof shape.maximum === 'number')
|
|
44
45
|
cases.push({ why: 'above maximum', value: shape.maximum + 1 });
|
|
46
|
+
if (shape.enum) {
|
|
47
|
+
const members = shape.enum.filter((value) => typeof value === 'number');
|
|
48
|
+
|
|
49
|
+
cases.push({ why: 'outside the stated set', value: Math.max(...members) + 1 });
|
|
50
|
+
}
|
|
45
51
|
}
|
|
46
52
|
if (shape?.type === 'array') {
|
|
47
53
|
if (typeof shape.minItems === 'number' && shape.minItems > 0)
|
|
@@ -52,6 +58,7 @@ function outOfBoundsFor(field: Field): { why: string; value: unknown }[] {
|
|
|
52
58
|
value: Array.from({ length: shape.maxItems + 1 }, () => null),
|
|
53
59
|
});
|
|
54
60
|
}
|
|
61
|
+
|
|
55
62
|
return cases;
|
|
56
63
|
}
|
|
57
64
|
|
|
@@ -64,8 +71,8 @@ function outOfBoundsFor(field: Field): { why: string; value: unknown }[] {
|
|
|
64
71
|
export class Cases {
|
|
65
72
|
private constructor(readonly all: readonly ValidationCase[]) {}
|
|
66
73
|
|
|
67
|
-
static of(
|
|
68
|
-
return new Cases(enumerate(
|
|
74
|
+
static of(schema: SchemaView, valid: Record<string, unknown>): Cases {
|
|
75
|
+
return new Cases(enumerate(schema, valid));
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
static holds(
|
|
@@ -74,6 +81,7 @@ export class Cases {
|
|
|
74
81
|
): boolean {
|
|
75
82
|
if (expected === 'accept') return result.success;
|
|
76
83
|
if (result.success) return false;
|
|
84
|
+
|
|
77
85
|
return (result.errors ?? []).some((error) => rejected(error.path) === expected.reject);
|
|
78
86
|
}
|
|
79
87
|
|
|
@@ -92,8 +100,8 @@ export class Cases {
|
|
|
92
100
|
* FR : lit les quatre axes plutôt qu'une liste tenue à la main : un cas existe parce qu'un
|
|
93
101
|
* champ déclare la règle qu'il casse.
|
|
94
102
|
*/
|
|
95
|
-
function enumerate(
|
|
96
|
-
const fields =
|
|
103
|
+
function enumerate(schema: SchemaView, valid: Record<string, unknown>): ValidationCase[] {
|
|
104
|
+
const fields = schema.getFields();
|
|
97
105
|
const validator = InputValidator.of(fields);
|
|
98
106
|
|
|
99
107
|
return [
|
|
@@ -124,13 +132,13 @@ function aboutField(
|
|
|
124
132
|
const withField = (value: unknown) => ({ ...valid, [name]: value });
|
|
125
133
|
const cases: ValidationCase[] = [];
|
|
126
134
|
|
|
127
|
-
if (validator.
|
|
135
|
+
if (validator.requires(field) && name in valid) {
|
|
128
136
|
const input = { ...valid };
|
|
129
137
|
delete input[name];
|
|
130
138
|
cases.push({ why: `${name} absent`, input, patch: false, expect: { reject: name } });
|
|
131
139
|
}
|
|
132
140
|
|
|
133
|
-
if (Boundary.of(field).readOnly) {
|
|
141
|
+
if (Boundary.of(field).readOnly()) {
|
|
134
142
|
cases.push({
|
|
135
143
|
why: `${name} supplied although read-only`,
|
|
136
144
|
input: withField(wrongTypeFor(field)),
|
|
@@ -139,7 +147,16 @@ function aboutField(
|
|
|
139
147
|
});
|
|
140
148
|
}
|
|
141
149
|
|
|
142
|
-
if (
|
|
150
|
+
if (Role.of(field).isCollection()) {
|
|
151
|
+
cases.push({
|
|
152
|
+
why: `${name} supplied although the other side carries it`,
|
|
153
|
+
input: withField([]),
|
|
154
|
+
patch: false,
|
|
155
|
+
expect: { reject: name },
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (Lifecycle.of(field).immutable() && !Role.of(field).isPrimary()) {
|
|
143
160
|
cases.push({
|
|
144
161
|
why: `${name} supplied on an update`,
|
|
145
162
|
input: { [name]: valid[name] ?? wrongTypeFor(field) },
|
|
@@ -150,7 +167,7 @@ function aboutField(
|
|
|
150
167
|
|
|
151
168
|
// A reference names a row that must exist; the caller supplied its id and we do not
|
|
152
169
|
// get to invent a second one, so the only case we can state about it is the bound one.
|
|
153
|
-
if (name in valid && !Role.of(field).isReference) {
|
|
170
|
+
if (name in valid && !Role.of(field).isReference()) {
|
|
154
171
|
cases.push({
|
|
155
172
|
why: `${name} of the wrong type`,
|
|
156
173
|
input: withField(wrongTypeFor(field)),
|
|
@@ -15,30 +15,54 @@ export class Visibility {
|
|
|
15
15
|
const result: Fields = {};
|
|
16
16
|
for (const [name, field] of Object.entries(this.fields)) {
|
|
17
17
|
const role = Role.of(field);
|
|
18
|
-
if (role.isPrimary) continue;
|
|
19
|
-
if (Lifecycle.of(field).stampedAtCreate) continue;
|
|
20
|
-
if (role.isCollection) continue;
|
|
21
|
-
if (Boundary.of(field).readOnly) continue;
|
|
18
|
+
if (role.isPrimary()) continue;
|
|
19
|
+
if (Lifecycle.of(field).stampedAtCreate()) continue;
|
|
20
|
+
if (role.isCollection()) continue;
|
|
21
|
+
if (Boundary.of(field).readOnly()) continue;
|
|
22
22
|
result[name] = field;
|
|
23
23
|
}
|
|
24
|
+
|
|
24
25
|
return result;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
get output(): Fields {
|
|
28
29
|
const result: Fields = {};
|
|
29
30
|
for (const [name, field] of Object.entries(this.fields)) {
|
|
30
|
-
if (Boundary.of(field).writeOnly) continue;
|
|
31
|
+
if (Boundary.of(field).writeOnly()) continue;
|
|
31
32
|
result[name] = field;
|
|
32
33
|
}
|
|
34
|
+
|
|
33
35
|
return result;
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
/**
|
|
39
|
+
* The dual of `encode`, read where a caller RECEIVES a row.
|
|
40
|
+
*
|
|
41
|
+
* `date-time` means a `Date` on both sides, and only the outgoing half was ever applied: a
|
|
42
|
+
* handler answered `new Date(0)`, the facade encoded it, and the caller was handed
|
|
43
|
+
* `"1970-01-01T00:00:00.000Z"` while its type said `Date` — locally as well as across a wire.
|
|
44
|
+
*
|
|
45
|
+
* It converts and never judges: what arrives was validated where it was produced, so a value
|
|
46
|
+
* the codec refuses is kept as it came rather than replaced or thrown over.
|
|
47
|
+
*/
|
|
48
|
+
decode(record: Record<string, unknown>): Record<string, unknown> {
|
|
49
|
+
const out: Record<string, unknown> = { ...record };
|
|
50
|
+
for (const [key, field] of Object.entries(this.fields)) {
|
|
51
|
+
const value = record[key];
|
|
52
|
+
if (!(key in record) || value === null || value === undefined) continue;
|
|
53
|
+
const verdict = Boundary.of(field).decode(value);
|
|
54
|
+
if ('value' in verdict) out[key] = verdict.value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
36
60
|
encode(record: Record<string, unknown>): Record<string, unknown> {
|
|
37
61
|
const out: Record<string, unknown> = { ...record };
|
|
38
62
|
for (const [key, field] of Object.entries(this.fields)) {
|
|
39
63
|
if (!(key in record)) continue;
|
|
40
64
|
const boundary = Boundary.of(field);
|
|
41
|
-
if (boundary.writeOnly) {
|
|
65
|
+
if (boundary.writeOnly()) {
|
|
42
66
|
delete out[key];
|
|
43
67
|
continue;
|
|
44
68
|
}
|
|
@@ -46,6 +70,7 @@ export class Visibility {
|
|
|
46
70
|
if (value === null || value === undefined) continue;
|
|
47
71
|
out[key] = boundary.encode(value);
|
|
48
72
|
}
|
|
73
|
+
|
|
49
74
|
return out;
|
|
50
75
|
}
|
|
51
76
|
}
|
|
@@ -56,6 +56,7 @@ export class Bundle {
|
|
|
56
56
|
byName[name.toLowerCase()] = schema as unknown as EntityConstructor;
|
|
57
57
|
schemas[name] = schema;
|
|
58
58
|
}
|
|
59
|
+
|
|
59
60
|
return schemas;
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -72,6 +73,7 @@ export class Bundle {
|
|
|
72
73
|
);
|
|
73
74
|
if (diff.changes.length > 0 || diff.ambiguous.length > 0) entities[name] = diff;
|
|
74
75
|
}
|
|
76
|
+
|
|
75
77
|
return {
|
|
76
78
|
entitiesAdded: Object.keys(after).filter((name) => !(name in before)),
|
|
77
79
|
entitiesRemoved: Object.keys(before).filter((name) => !(name in after)),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Axes } from '../../axis/Axes.js';
|
|
2
|
+
import { CardForms } from './CardForms.js';
|
|
2
3
|
import { type Resolver } from '../../axis/Resolver.js';
|
|
3
4
|
import { clean, isObject } from '../../lib/utils.js';
|
|
4
5
|
import { Field } from '../../field/Field.js';
|
|
@@ -38,10 +39,11 @@ export class Card<T = Values<Fields>> {
|
|
|
38
39
|
const required: string[] = [];
|
|
39
40
|
for (const [key, field] of Object.entries(fields)) {
|
|
40
41
|
properties[key] = describeField(field, key);
|
|
41
|
-
if (validator.
|
|
42
|
+
if (validator.requires(field)) required.push(key);
|
|
42
43
|
}
|
|
43
|
-
for (const
|
|
44
|
-
for (const
|
|
44
|
+
for (const kind of ['unique', 'index'] as const)
|
|
45
|
+
for (const group of (kind === 'unique' ? schema.getUnique() : schema.getIndex()) ?? [])
|
|
46
|
+
for (const member of group) carryGroup(properties[member], kind, group);
|
|
45
47
|
|
|
46
48
|
const descriptor: SchemaDescriptor = {
|
|
47
49
|
type: 'object',
|
|
@@ -55,6 +57,7 @@ export class Card<T = Values<Fields>> {
|
|
|
55
57
|
|
|
56
58
|
const origin = originOf(schema);
|
|
57
59
|
if (origin) descriptor['x-fougere-derived'] = origin;
|
|
60
|
+
|
|
58
61
|
return new Card<Values<TFields>>(descriptor);
|
|
59
62
|
}
|
|
60
63
|
|
|
@@ -94,7 +97,7 @@ export class Card<T = Values<Fields>> {
|
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
const fields: Fields = {};
|
|
97
|
-
const groups: string[][] = [];
|
|
100
|
+
const groups: { unique: string[][]; index: string[][] } = { unique: [], index: [] };
|
|
98
101
|
for (const [key, property] of Object.entries(descriptor.properties)) {
|
|
99
102
|
if (!isObject(property)) {
|
|
100
103
|
refuse(
|
|
@@ -103,13 +106,15 @@ export class Card<T = Values<Fields>> {
|
|
|
103
106
|
);
|
|
104
107
|
}
|
|
105
108
|
fields[key] = reconstructField(property, key, resolve);
|
|
106
|
-
for (const
|
|
107
|
-
|
|
109
|
+
for (const kind of ['unique', 'index'] as const)
|
|
110
|
+
for (const group of property['x-fougere']?.role?.[kind] ?? [])
|
|
111
|
+
if (group.length > 1) groups[kind].push([...group]);
|
|
108
112
|
}
|
|
109
113
|
const schema = Schema.of({ fields, constraints: SchemaConstraints.of(groups) });
|
|
110
114
|
const title = name ?? descriptor.title;
|
|
111
115
|
if (title)
|
|
112
116
|
Object.defineProperty(schema, 'name', { value: title, configurable: true });
|
|
117
|
+
|
|
113
118
|
return schema as unknown as SchemaConstructor<FieldsOf<T>>;
|
|
114
119
|
}
|
|
115
120
|
|
|
@@ -126,13 +131,13 @@ export class Card<T = Values<Fields>> {
|
|
|
126
131
|
*/
|
|
127
132
|
function describeExtension(field: Field, key: string): FieldExtension | undefined {
|
|
128
133
|
const extension: Record<string, unknown> = {};
|
|
129
|
-
for (const
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (wire !== undefined) extension[axis.slot] = wire;
|
|
134
|
+
for (const [name, declared] of Object.entries(field.axes)) {
|
|
135
|
+
const form = CardForms.find(name);
|
|
136
|
+
const wire = form ? form.describe(declared, key) : declared;
|
|
137
|
+
if (wire !== undefined) extension[name] = wire;
|
|
134
138
|
}
|
|
135
139
|
clean(extension);
|
|
140
|
+
|
|
136
141
|
return Object.keys(extension).length ? (extension as FieldExtension) : undefined;
|
|
137
142
|
}
|
|
138
143
|
|
|
@@ -140,9 +145,9 @@ function describeField(field: Field, key: string): FieldDescriptor {
|
|
|
140
145
|
// The shape IS JSON Schema, so it lands whole rather than key by key — which is what let
|
|
141
146
|
// it be copied under a computed key into a type that names its own.
|
|
142
147
|
const descriptor: FieldDescriptor = clean({ ...field.shape }) as FieldDescriptor;
|
|
143
|
-
if (field.meta?.description) descriptor.description = field.meta.description;
|
|
144
148
|
const extension = describeExtension(field, key);
|
|
145
149
|
if (extension) descriptor['x-fougere'] = extension;
|
|
150
|
+
|
|
146
151
|
return descriptor;
|
|
147
152
|
}
|
|
148
153
|
|
|
@@ -157,6 +162,7 @@ function originOf(schema: SchemaView): DerivedFrom | undefined {
|
|
|
157
162
|
const nameOf = Object.fromEntries(
|
|
158
163
|
Object.entries(derivation.nameOf).map(([key, value]) => [key, value ?? null]),
|
|
159
164
|
);
|
|
165
|
+
|
|
160
166
|
return { from: derivation.sourceName, nameOf };
|
|
161
167
|
}
|
|
162
168
|
|
|
@@ -175,7 +181,7 @@ function reconstructShape(property: FieldDescriptor): Field['shape'] | undefined
|
|
|
175
181
|
|
|
176
182
|
// `describeField` writes the shape whole, so it is read whole: a list of keywords here
|
|
177
183
|
// would be a second inventory, and the one that forgets a keyword loses it in silence.
|
|
178
|
-
const { 'x-fougere': _extension,
|
|
184
|
+
const { 'x-fougere': _extension, ...shape } = property;
|
|
179
185
|
if (shape.items) shape.items = reconstructShape(shape.items) as FieldDescriptor;
|
|
180
186
|
|
|
181
187
|
return clean(shape) as Field['shape'];
|
|
@@ -201,18 +207,13 @@ function reconstructField(
|
|
|
201
207
|
admitPatterns(shape, `Field '${key}'`);
|
|
202
208
|
const extension = property['x-fougere'];
|
|
203
209
|
const axes: Record<string, unknown> = {};
|
|
204
|
-
for (const
|
|
205
|
-
const wire = (extension as Record<string, unknown> | undefined)?.[
|
|
206
|
-
|
|
210
|
+
for (const name of Axes.names) {
|
|
211
|
+
const wire = (extension as Record<string, unknown> | undefined)?.[name];
|
|
212
|
+
const form = CardForms.find(name);
|
|
213
|
+
if (wire !== undefined) axes[name] = form ? form.reconstruct(wire, resolve) : wire;
|
|
207
214
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
...axes,
|
|
211
|
-
meta:
|
|
212
|
-
property.description !== undefined
|
|
213
|
-
? { description: property.description }
|
|
214
|
-
: undefined,
|
|
215
|
-
} as never);
|
|
215
|
+
|
|
216
|
+
return new Field({ shape, ...axes } as never, key);
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
/**
|
|
@@ -222,10 +223,11 @@ function reconstructField(
|
|
|
222
223
|
*/
|
|
223
224
|
function carryGroup(
|
|
224
225
|
property: FieldDescriptor | undefined,
|
|
226
|
+
kind: 'unique' | 'index',
|
|
225
227
|
group: readonly string[],
|
|
226
228
|
): void {
|
|
227
229
|
if (!property) return;
|
|
228
|
-
const extension = (property['x-fougere'] ??= {}) as { role?:
|
|
230
|
+
const extension = (property['x-fougere'] ??= {}) as { role?: Record<string, string[][]> };
|
|
229
231
|
const role = (extension.role ??= {});
|
|
230
|
-
(role
|
|
232
|
+
(role[kind] ??= []).push([...group]);
|
|
231
233
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Resolver } from '../../axis/Resolver.js';
|
|
2
|
+
|
|
3
|
+
/** How a card writes what an axis declared, and reads it back — stated by the card, not the axis. */
|
|
4
|
+
export interface CardForm<Declared = unknown, Wire = unknown> {
|
|
5
|
+
describe(value: Declared, key: string): Wire | undefined;
|
|
6
|
+
|
|
7
|
+
reconstruct(wire: Wire, resolve?: Resolver): Declared;
|
|
8
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Registry } from '../../lib/Registry.js';
|
|
2
|
+
import type { CardForm } from './CardForm.js';
|
|
3
|
+
import { roleOnCard } from './RoleDescriptor.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The axes a card writes differently from their declaration. An axis absent here travels as
|
|
7
|
+
* itself, which is what `lifecycle`, `boundary` and anything registered from outside do.
|
|
8
|
+
* FR : les axes qu'une carte écrit autrement que déclarés ; les autres voyagent tels quels.
|
|
9
|
+
* `CardForms.register('tenancy', tenancyOnCard)`
|
|
10
|
+
*/
|
|
11
|
+
export const CardForms = new Registry<CardForm>('card form', 'call CardForms.register(name, form)', [
|
|
12
|
+
['role', roleOnCard as CardForm],
|
|
13
|
+
]);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { BoundaryRef } from '../../axis/boundary/
|
|
2
|
-
import type { LifecycleRules } from '../../axis/lifecycle/
|
|
1
|
+
import type { BoundaryRef } from '../../axis/boundary/Boundary.js';
|
|
2
|
+
import type { LifecycleRules } from '../../axis/lifecycle/Lifecycle.js';
|
|
3
3
|
import type { FieldDescriptor } from './FieldDescriptor.js';
|
|
4
4
|
import type { RoleDescriptor } from './RoleDescriptor.js';
|
|
5
5
|
import type { TypeSet } from './TypeSet.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dequal } from 'dequal';
|
|
2
|
-
import {
|
|
2
|
+
import { Axes } from '../../axis/Axes.js';
|
|
3
3
|
import type { FieldDescriptor } from './FieldDescriptor.js';
|
|
4
4
|
import type { FieldExtension } from './FieldExtension.js';
|
|
5
5
|
|
|
@@ -93,7 +93,7 @@ function restated(
|
|
|
93
93
|
before: FieldExtension | undefined,
|
|
94
94
|
after: FieldExtension | undefined,
|
|
95
95
|
): Change[] {
|
|
96
|
-
return
|
|
96
|
+
return Axes.names.filter((axis) => !dequal(before?.[axis], after?.[axis])).map(
|
|
97
97
|
(axis) =>
|
|
98
98
|
({
|
|
99
99
|
kind: 'restated',
|
|
@@ -107,6 +107,7 @@ function restated(
|
|
|
107
107
|
|
|
108
108
|
function shapeOf(descriptor: FieldDescriptor): Record<string, unknown> {
|
|
109
109
|
const { 'x-fougere': _extension, description: _description, ...shape } = descriptor;
|
|
110
|
+
|
|
110
111
|
return shape as Record<string, unknown>;
|
|
111
112
|
}
|
|
112
113
|
|
|
@@ -118,6 +119,7 @@ function shapeOf(descriptor: FieldDescriptor): Record<string, unknown> {
|
|
|
118
119
|
function typesOf(descriptor: FieldDescriptor): TypeSet {
|
|
119
120
|
const type = descriptor.type;
|
|
120
121
|
if (type === undefined) return [];
|
|
122
|
+
|
|
121
123
|
return (Array.isArray(type) ? [...type] : [type]).sort();
|
|
122
124
|
}
|
|
123
125
|
|
|
@@ -128,6 +130,7 @@ function typesOf(descriptor: FieldDescriptor): TypeSet {
|
|
|
128
130
|
*/
|
|
129
131
|
function boundsOf(descriptor: FieldDescriptor): Record<string, unknown> {
|
|
130
132
|
const { type: _type, ...rest } = shapeOf(descriptor);
|
|
133
|
+
|
|
131
134
|
return rest;
|
|
132
135
|
}
|
|
133
136
|
|
|
@@ -153,5 +156,6 @@ function candidates(
|
|
|
153
156
|
const now = Object.keys(after);
|
|
154
157
|
const apart = ({ removed: gone, added: appeared }: RenameCandidate): number =>
|
|
155
158
|
Math.abs(now.indexOf(appeared) - was.indexOf(gone));
|
|
159
|
+
|
|
156
160
|
return found.sort((a, b) => apart(a) - apart(b));
|
|
157
161
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import type { BoundaryRef } from '../../axis/boundary/
|
|
2
|
-
import type { LifecycleRules } from '../../axis/lifecycle/
|
|
1
|
+
import type { BoundaryRef } from '../../axis/boundary/Boundary.js';
|
|
2
|
+
import type { LifecycleRules } from '../../axis/lifecycle/Lifecycle.js';
|
|
3
3
|
import type { RoleDescriptor } from './RoleDescriptor.js';
|
|
4
4
|
|
|
5
5
|
export interface FieldExtension {
|
|
6
6
|
role?: RoleDescriptor;
|
|
7
7
|
lifecycle?: LifecycleRules;
|
|
8
8
|
boundary?: BoundaryRef;
|
|
9
|
+
|
|
10
|
+
/** An axis registered from outside writes here, under its own name. */
|
|
11
|
+
[axis: string]: unknown;
|
|
9
12
|
}
|
|
@@ -1,7 +1,58 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { EntityConstructor } from '../../axis/role/EntityConstructor.js';
|
|
2
|
+
import type { Relation } from '../../axis/role/Relation.js';
|
|
3
|
+
import type { RoleRules } from '../../axis/role/Role.js';
|
|
4
|
+
import type { Resolver } from '../../axis/Resolver.js';
|
|
5
|
+
import { lowerFirst } from '../../lib/utils.js';
|
|
6
|
+
import type { CardForm } from './CardForm.js';
|
|
3
7
|
|
|
4
|
-
|
|
8
|
+
/**
|
|
9
|
+
* What a role becomes on a card: a target is a NAME there, and `unique` and `index` are both
|
|
10
|
+
* GROUPS — the field says `true`, the card says which names go together, since a group of
|
|
11
|
+
* several belongs to no single field.
|
|
12
|
+
*/
|
|
13
|
+
export type RoleDescriptor = Pick<RoleRules, 'primary'> & {
|
|
5
14
|
unique?: string[][];
|
|
15
|
+
index?: string[][];
|
|
6
16
|
relation?: RelationDescriptor;
|
|
7
17
|
};
|
|
18
|
+
|
|
19
|
+
export type RelationDescriptor = Pick<Relation, 'kind' | 'onDelete'> & { to: string };
|
|
20
|
+
|
|
21
|
+
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
|
|
22
|
+
|
|
23
|
+
/** A card carries the target's NAME: a class cannot cross a process boundary. */
|
|
24
|
+
export const roleOnCard: CardForm<RoleRules, RoleDescriptor> = {
|
|
25
|
+
describe(role, key) {
|
|
26
|
+
const descriptor: Mutable<RoleDescriptor> = {};
|
|
27
|
+
if (role.primary) descriptor.primary = true;
|
|
28
|
+
if (role.unique) descriptor.unique = [[key]];
|
|
29
|
+
if (role.index) descriptor.index = [[key]];
|
|
30
|
+
if (role.relation) {
|
|
31
|
+
const target = role.relation.to() as { name?: string };
|
|
32
|
+
descriptor.relation = {
|
|
33
|
+
to: lowerFirst(target.name ?? ''),
|
|
34
|
+
kind: role.relation.kind,
|
|
35
|
+
...(role.relation.onDelete ? { onDelete: role.relation.onDelete } : {}),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return Object.keys(descriptor).length ? descriptor : undefined;
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
reconstruct(wire, resolve?: Resolver) {
|
|
43
|
+
const rules: Mutable<RoleRules> = {};
|
|
44
|
+
if (wire.primary) rules.primary = true;
|
|
45
|
+
if (wire.unique?.some((group) => group.length === 1)) rules.unique = true;
|
|
46
|
+
if (wire.index?.some((group) => group.length === 1)) rules.index = true;
|
|
47
|
+
if (wire.relation) {
|
|
48
|
+
const name = wire.relation.to;
|
|
49
|
+
rules.relation = {
|
|
50
|
+
to: () => (resolve?.(name) ?? ({ name } as unknown)) as EntityConstructor,
|
|
51
|
+
kind: wire.relation.kind,
|
|
52
|
+
...(wire.relation.onDelete ? { onDelete: wire.relation.onDelete } : {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return rules;
|
|
57
|
+
},
|
|
58
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Shape } from '../../axis/shape/Shape.js';
|
|
2
|
+
import type { UnionToIntersection } from '../../lib/UnionToIntersection.js';
|
|
2
3
|
|
|
3
4
|
type KeywordsOf<S> = S extends unknown ? Partial<Omit<S, 'type' | 'items' | 'properties'>> : never;
|
|
4
|
-
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
5
5
|
|
|
6
6
|
/** Every keyword any shape may carry, flattened — what a descriptor records of one. */
|
|
7
7
|
export type ShapeKeywords = UnionToIntersection<KeywordsOf<Shape>>;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import type { ValidationError } from '../../lib/ValidationError.js';
|
|
2
1
|
import { Shapes } from '../../axis/shape/Shape.js';
|
|
3
|
-
import { dotted } from '../../lib/ValidationResult.js';
|
|
4
2
|
import { SchemaError } from '../../SchemaError.js';
|
|
5
3
|
|
|
6
4
|
/**
|
|
@@ -12,37 +10,6 @@ export function refuse(what: string, fix: string): never {
|
|
|
12
10
|
throw new SchemaError(`This card cannot be read: ${what}.\n ${fix}`);
|
|
13
11
|
}
|
|
14
12
|
|
|
15
|
-
/**
|
|
16
|
-
* Runs a card's axis through the validator a hand-written field goes through, so there is
|
|
17
|
-
* one judge and not two.
|
|
18
|
-
* FR : passe l'axe d'une carte par le juge d'un champ écrit à la main : un juge, pas deux.
|
|
19
|
-
* `{ update: 'maybe' }` → `lifecycle is malformed — lifecycle.update: Expected 'now' or 'forbidden'`
|
|
20
|
-
*/
|
|
21
|
-
export function admitWire(
|
|
22
|
-
validator: (value: unknown, errors: ValidationError[]) => void,
|
|
23
|
-
value: unknown,
|
|
24
|
-
slot: string,
|
|
25
|
-
): void {
|
|
26
|
-
const errors: ValidationError[] = [];
|
|
27
|
-
validator(value, errors);
|
|
28
|
-
if (errors.length) {
|
|
29
|
-
refuse(
|
|
30
|
-
`${slot} is malformed — ${errors.map((e) => `${dotted(e.path)}: ${e.message}`).join('; ')}`,
|
|
31
|
-
'A card states an axis the way a declaration does.',
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Checks a token against the list that declares it, never against a copy written here.
|
|
38
|
-
* FR : vérifie un mot contre la liste qui le déclare, jamais contre une copie locale.
|
|
39
|
-
* `oneOfTokens('many', RELATION_KINDS)` → `true`
|
|
40
|
-
*/
|
|
41
|
-
export const oneOfTokens = <T extends readonly string[]>(
|
|
42
|
-
value: unknown,
|
|
43
|
-
tokens: T,
|
|
44
|
-
): value is T[number] => typeof value === 'string' && (tokens as readonly string[]).includes(value);
|
|
45
|
-
|
|
46
13
|
/**
|
|
47
14
|
* Compiles every `pattern` at the facade, so a bad one is refused here and not at the first row.
|
|
48
15
|
* FR : compile chaque `pattern` à la porte : un mauvais est refusé ici, pas au premier row.
|