@fougere/schema 0.11.0-alpha.0 → 0.12.1-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/Schema.d.ts +1 -1
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +4 -3
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaConstructor.d.ts +3 -4
- package/dist/SchemaConstructor.d.ts.map +1 -1
- package/dist/SchemaDefinition.d.ts.map +1 -1
- package/dist/SchemaDefinition.js +2 -1
- package/dist/SchemaDefinition.js.map +1 -1
- package/dist/SchemaError.d.ts +4 -0
- package/dist/SchemaError.d.ts.map +1 -0
- package/dist/SchemaError.js +7 -0
- package/dist/SchemaError.js.map +1 -0
- package/dist/SchemaView.d.ts +1 -1
- 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 +5 -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 +29 -3
- package/dist/axis/boundary/Boundary.d.ts.map +1 -1
- package/dist/axis/boundary/Boundary.js +15 -11
- package/dist/axis/boundary/Boundary.js.map +1 -1
- package/dist/axis/boundary/Decoder.d.ts +10 -7
- package/dist/axis/boundary/Decoder.d.ts.map +1 -1
- package/dist/axis/boundary/Decoder.js +2 -2
- package/dist/axis/boundary/Decoder.js.map +1 -1
- package/dist/axis/lifecycle/Generators.d.ts +5 -5
- package/dist/axis/lifecycle/Generators.d.ts.map +1 -1
- package/dist/axis/lifecycle/Generators.js +5 -15
- package/dist/axis/lifecycle/Generators.js.map +1 -1
- package/dist/axis/lifecycle/Lifecycle.d.ts +18 -6
- package/dist/axis/lifecycle/Lifecycle.d.ts.map +1 -1
- package/dist/axis/lifecycle/Lifecycle.js +29 -14
- 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 +5 -22
- package/dist/axis/lifecycle/apply.js.map +1 -1
- package/dist/axis/role/Role.d.ts +18 -6
- package/dist/axis/role/Role.d.ts.map +1 -1
- package/dist/axis/role/Role.js +30 -16
- package/dist/axis/role/Role.js.map +1 -1
- package/dist/axis/shape/Shape.d.ts +2 -2
- package/dist/axis/shape/Shape.d.ts.map +1 -1
- package/dist/axis/shape/Shape.js +2 -2
- package/dist/axis/shape/Shape.js.map +1 -1
- 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/EntityAdapterSet.d.ts.map +1 -1
- package/dist/entity/EntityAdapterSet.js +3 -2
- package/dist/entity/EntityAdapterSet.js.map +1 -1
- package/dist/entity.d.ts +0 -1
- package/dist/entity.d.ts.map +1 -1
- package/dist/entity.js +0 -1
- package/dist/entity.js.map +1 -1
- package/dist/field/Field.d.ts +11 -5
- package/dist/field/Field.d.ts.map +1 -1
- package/dist/field/Field.js +10 -7
- 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.map +1 -1
- package/dist/field/FieldSet.js +5 -3
- package/dist/field/FieldSet.js.map +1 -1
- package/dist/field/FougereFieldAxes.d.ts +3 -0
- package/dist/field/FougereFieldAxes.d.ts.map +1 -0
- package/dist/field/FougereFieldAxes.js +2 -0
- package/dist/field/FougereFieldAxes.js.map +1 -0
- package/dist/field/Meta.d.ts +5 -3
- package/dist/field/Meta.d.ts.map +1 -1
- package/dist/field/Meta.js +2 -1
- package/dist/field/Meta.js.map +1 -1
- package/dist/field/Values.d.ts +6 -0
- package/dist/field/Values.d.ts.map +1 -0
- package/dist/field/Values.js.map +1 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/Format.d.ts +56 -0
- package/dist/lib/Format.d.ts.map +1 -0
- package/dist/lib/Format.js +95 -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 +5 -4
- package/dist/lib/Registry.d.ts.map +1 -1
- package/dist/lib/Registry.js +14 -8
- package/dist/lib/Registry.js.map +1 -1
- 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/Verdict.d.ts +12 -0
- package/dist/lib/Verdict.d.ts.map +1 -0
- package/dist/lib/Verdict.js +2 -0
- package/dist/lib/Verdict.js.map +1 -0
- package/dist/projection/Cases.js +52 -54
- 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 +22 -0
- package/dist/projection/Visibility.js.map +1 -1
- package/dist/projection/card/Bundle.d.ts.map +1 -1
- package/dist/projection/card/Bundle.js +2 -1
- package/dist/projection/card/Bundle.js.map +1 -1
- package/dist/projection/card/Card.d.ts +1 -1
- package/dist/projection/card/Card.d.ts.map +1 -1
- package/dist/projection/card/Card.js +19 -11
- 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.d.ts.map +1 -1
- package/dist/projection/card/Diff.js +36 -39
- 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 +9 -2
- 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/admission.d.ts +0 -14
- package/dist/projection/card/admission.d.ts.map +1 -1
- package/dist/projection/card/admission.js +2 -21
- package/dist/projection/card/admission.js.map +1 -1
- package/dist/validator/FieldDeclarationValidator.d.ts.map +1 -1
- package/dist/validator/FieldDeclarationValidator.js +5 -17
- package/dist/validator/FieldDeclarationValidator.js.map +1 -1
- package/dist/validator/FieldValueValidator.d.ts +3 -3
- package/dist/validator/FieldValueValidator.d.ts.map +1 -1
- package/dist/validator/FieldValueValidator.js +18 -35
- package/dist/validator/FieldValueValidator.js.map +1 -1
- package/dist/validator/InputValidator.d.ts +3 -0
- package/dist/validator/InputValidator.d.ts.map +1 -1
- package/dist/validator/InputValidator.js +28 -31
- 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/primitive/list.d.ts.map +1 -1
- package/dist/vocabulary/primitive/list.js +2 -1
- package/dist/vocabulary/primitive/list.js.map +1 -1
- package/dist/vocabulary/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary/vocabulary.js +9 -6
- package/dist/vocabulary/vocabulary.js.map +1 -1
- package/package.json +1 -1
- package/src/Schema.ts +6 -4
- package/src/SchemaConstructor.ts +4 -4
- package/src/SchemaDefinition.ts +2 -1
- package/src/SchemaError.ts +7 -0
- package/src/SchemaView.ts +1 -1
- package/src/axis/Axes.ts +34 -0
- package/src/axis/Axis.ts +5 -20
- package/src/axis/boundary/Boundary.ts +26 -13
- package/src/axis/boundary/Decoder.ts +5 -4
- package/src/axis/lifecycle/Generators.ts +6 -18
- package/src/axis/lifecycle/Lifecycle.ts +41 -19
- package/src/axis/lifecycle/apply.ts +13 -21
- package/src/axis/role/Role.ts +35 -19
- package/src/axis/shape/Shape.ts +3 -3
- package/src/axis/shape/{Formats.ts → StringFormats.ts} +2 -2
- package/src/entity/EntityAdapterSet.ts +3 -2
- package/src/entity.ts +0 -1
- package/src/field/Field.ts +23 -12
- package/src/field/FieldFormat.ts +18 -0
- package/src/field/FieldSet.ts +5 -3
- package/src/field/FougereFieldAxes.ts +2 -0
- package/src/field/Meta.ts +5 -3
- package/src/{Values.ts → field/Values.ts} +2 -2
- package/src/index.ts +8 -3
- package/src/lib/Format.ts +139 -0
- package/src/lib/JsonSchema.ts +1 -0
- package/src/lib/Registry.ts +18 -9
- package/src/lib/ValidationResult.ts +8 -0
- package/src/lib/Verdict.ts +10 -0
- package/src/projection/Cases.ts +61 -57
- package/src/projection/Visibility.ts +22 -0
- package/src/projection/card/Bundle.ts +2 -1
- package/src/projection/card/Card.ts +24 -13
- 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 +44 -44
- package/src/projection/card/FieldExtension.ts +5 -2
- package/src/projection/card/RoleDescriptor.ts +48 -2
- package/src/projection/card/admission.ts +2 -34
- package/src/validator/FieldDeclarationValidator.ts +5 -17
- package/src/validator/FieldValueValidator.ts +22 -46
- package/src/validator/InputValidator.ts +30 -32
- package/src/validator/JsonSchemaValidator.ts +142 -0
- package/src/vocabulary/primitive/list.ts +2 -1
- package/src/vocabulary/vocabulary.ts +9 -6
- package/dist/PartialValues.d.ts +0 -4
- package/dist/PartialValues.d.ts.map +0 -1
- package/dist/PartialValues.js +0 -2
- package/dist/PartialValues.js.map +0 -1
- package/dist/Values.d.ts +0 -6
- package/dist/Values.d.ts.map +0 -1
- package/dist/Values.js.map +0 -1
- package/dist/axis/boundary/Boundaries.d.ts +0 -17
- package/dist/axis/boundary/Boundaries.d.ts.map +0 -1
- package/dist/axis/boundary/Boundaries.js +0 -25
- package/dist/axis/boundary/Boundaries.js.map +0 -1
- 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 -18
- package/dist/axis/lifecycle/Clock.d.ts.map +0 -1
- package/dist/axis/lifecycle/Clock.js +0 -25
- 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/lib/Checked.d.ts +0 -11
- package/dist/lib/Checked.d.ts.map +0 -1
- package/dist/lib/Checked.js +0 -2
- package/dist/lib/Checked.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/projection/standard.d.ts +0 -14
- package/dist/projection/standard.d.ts.map +0 -1
- package/dist/projection/standard.js +0 -2
- package/dist/projection/standard.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 -36
- package/dist/validator/AdapterFieldValidator.js.map +0 -1
- package/src/PartialValues.ts +0 -4
- package/src/axis/boundary/Boundaries.ts +0 -36
- 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 -26
- 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/lib/Checked.ts +0 -5
- package/src/projection/card/RelationDescriptor.ts +0 -3
- package/src/projection/standard.ts +0 -13
- package/src/validator/AdapterFieldValidator.ts +0 -40
- /package/dist/{Values.js → field/Values.js} +0 -0
package/src/axis/role/Role.ts
CHANGED
|
@@ -1,56 +1,72 @@
|
|
|
1
|
-
import type { EntityConstructor } from './EntityConstructor.js';
|
|
2
1
|
import type { Relation } from './Relation.js';
|
|
3
|
-
import type {
|
|
2
|
+
import type { EntityConstructor } from './EntityConstructor.js';
|
|
3
|
+
import { isObject } from '../../lib/utils.js';
|
|
4
|
+
import { Format, type Accepted } from '../../lib/Format.js';
|
|
5
|
+
import { ON_DELETE, RELATION_KINDS } from './Relation.js';
|
|
4
6
|
|
|
5
7
|
export class Role {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
static readonly format = Format.of('axis/role')
|
|
9
|
+
.key('primary', Format.flag)
|
|
10
|
+
.key('index', Format.flag)
|
|
11
|
+
.key('unique', Format.flag)
|
|
12
|
+
.key(
|
|
13
|
+
'relation',
|
|
14
|
+
Format.of()
|
|
15
|
+
.key('to', Format.anything.as<() => EntityConstructor>())
|
|
16
|
+
.key('kind', Format.tokens(RELATION_KINDS))
|
|
17
|
+
.key('onDelete', Format.tokens(ON_DELETE))
|
|
18
|
+
.needs('to', 'kind')
|
|
19
|
+
.closed(),
|
|
20
|
+
)
|
|
21
|
+
.closed();
|
|
22
|
+
|
|
23
|
+
static refusals(value: unknown) {
|
|
24
|
+
const relation = isObject(value) ? value.relation : undefined;
|
|
25
|
+
if (!isObject(relation) || !('to' in relation) || typeof relation.to === 'function') return [];
|
|
10
26
|
|
|
11
|
-
|
|
12
|
-
this.primary = rules.primary;
|
|
13
|
-
this.index = rules.index;
|
|
14
|
-
this.unique = rules.unique;
|
|
15
|
-
this.relation = rules.relation;
|
|
27
|
+
return [{ path: ['role', 'relation', 'to'], message: 'Expected a function returning the target entity, such as () => Post' }];
|
|
16
28
|
}
|
|
17
29
|
|
|
30
|
+
private constructor(private readonly rules: RoleRules = {}) {}
|
|
31
|
+
|
|
18
32
|
static of(field: { role?: RoleRules }): Role {
|
|
19
33
|
return new Role(field.role);
|
|
20
34
|
}
|
|
21
35
|
|
|
22
36
|
get isPrimary(): boolean {
|
|
23
|
-
return this.primary === true;
|
|
37
|
+
return this.rules.primary === true;
|
|
24
38
|
}
|
|
25
39
|
|
|
26
40
|
get isIndexed(): boolean {
|
|
27
|
-
return this.index === true;
|
|
41
|
+
return this.rules.index === true;
|
|
28
42
|
}
|
|
29
43
|
|
|
30
44
|
/** A field unique on its own. A group spanning several belongs to the schema. */
|
|
31
45
|
get isUnique(): boolean {
|
|
32
|
-
return this.unique === true;
|
|
46
|
+
return this.rules.unique === true;
|
|
33
47
|
}
|
|
34
48
|
|
|
35
49
|
get isCollection(): boolean {
|
|
36
|
-
return this.relation?.kind === 'many';
|
|
50
|
+
return this.rules.relation?.kind === 'many';
|
|
37
51
|
}
|
|
38
52
|
|
|
39
53
|
get isReference(): boolean {
|
|
40
|
-
return this.relation?.kind === 'one';
|
|
54
|
+
return this.rules.relation?.kind === 'one';
|
|
41
55
|
}
|
|
42
56
|
|
|
43
57
|
/** Either kind — what a CLI flag and a GraphQL selection both leave out. */
|
|
44
58
|
get isRelation(): boolean {
|
|
45
|
-
return this.relation !== undefined;
|
|
59
|
+
return this.rules.relation !== undefined;
|
|
46
60
|
}
|
|
47
61
|
|
|
48
62
|
/** Calls `() => Post` so no caller has to. */
|
|
49
63
|
get target(): EntityConstructor | undefined {
|
|
50
|
-
return this.relation?.to();
|
|
64
|
+
return this.rules.relation?.to();
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
get onDelete(): Relation['onDelete'] {
|
|
54
|
-
return this.relation?.onDelete;
|
|
68
|
+
return this.rules.relation?.onDelete;
|
|
55
69
|
}
|
|
56
70
|
}
|
|
71
|
+
|
|
72
|
+
export type RoleRules = Accepted<typeof Role.format>;
|
package/src/axis/shape/Shape.ts
CHANGED
|
@@ -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);
|
|
@@ -5,6 +5,6 @@ import type { FormatPredicate } from './FormatPredicate.js';
|
|
|
5
5
|
* String formats the JSON Schema engine does not know. `find` answers `undefined` for
|
|
6
6
|
* `email` or `uuid`, which it validates on its own.
|
|
7
7
|
* FR : les formats de chaîne que le moteur ignore ; `find` rend `undefined` pour les siens.
|
|
8
|
-
* `
|
|
8
|
+
* `StringFormats.register('siret', (v) => /^\d{14}$/.test(v))`
|
|
9
9
|
*/
|
|
10
|
-
export const
|
|
10
|
+
export const StringFormats = new Registry<FormatPredicate>('format');
|
|
@@ -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
|
|
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
|
|
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).
|
package/src/field/Field.ts
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import type { Shape } from '../axis/shape/Shape.js';
|
|
2
|
-
import type { RoleRules } from '../axis/role/
|
|
3
|
-
import type { LifecycleRules } from '../axis/lifecycle/
|
|
4
|
-
import type { BoundaryRef } from '../axis/boundary/
|
|
2
|
+
import type { RoleRules } from '../axis/role/Role.js';
|
|
3
|
+
import type { LifecycleRules } from '../axis/lifecycle/Lifecycle.js';
|
|
4
|
+
import type { BoundaryRef } from '../axis/boundary/Boundary.js';
|
|
5
5
|
import type { Meta } from './Meta.js';
|
|
6
|
-
import
|
|
6
|
+
import { Axes } from '../axis/Axes.js';
|
|
7
|
+
import type { FougereFieldAxes } from './FougereFieldAxes.js';
|
|
7
8
|
import { FieldDeclarationValidator } from '../validator/FieldDeclarationValidator.js';
|
|
8
9
|
import { FieldValueValidator } from '../validator/FieldValueValidator.js';
|
|
9
10
|
import { dotted } from '../lib/ValidationResult.js';
|
|
11
|
+
import { SchemaError } from '../SchemaError.js';
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
interface FieldDeclaration extends FougereFieldAxes {
|
|
14
|
+
shape: Shape;
|
|
15
|
+
role?: RoleRules;
|
|
16
|
+
lifecycle?: LifecycleRules;
|
|
17
|
+
boundary?: BoundaryRef;
|
|
18
|
+
meta?: Meta;
|
|
19
|
+
}
|
|
12
20
|
|
|
13
21
|
export class Field<T = unknown> {
|
|
14
22
|
readonly shape: Shape;
|
|
@@ -23,25 +31,28 @@ export class Field<T = unknown> {
|
|
|
23
31
|
const verdict = FieldDeclarationValidator.of(init).verdict;
|
|
24
32
|
|
|
25
33
|
if (!verdict.success) {
|
|
26
|
-
throw new
|
|
34
|
+
throw new SchemaError(
|
|
27
35
|
`${key ? `Field '${key}': ` : ''}` +
|
|
28
36
|
verdict.errors.map((e) => `${dotted(e.path)}: ${e.message}`).join('; '),
|
|
29
37
|
);
|
|
30
38
|
}
|
|
31
39
|
|
|
32
40
|
this.shape = init.shape;
|
|
33
|
-
this.role = init.role;
|
|
34
|
-
this.lifecycle = init.lifecycle;
|
|
35
|
-
this.boundary = init.boundary;
|
|
36
41
|
this.meta = init.meta;
|
|
37
42
|
|
|
43
|
+
const stated = init as unknown as Record<string, unknown>;
|
|
44
|
+
|
|
45
|
+
for (const slot of Axes.names) {
|
|
46
|
+
(this as unknown as Record<string, unknown>)[slot] = stated[slot];
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
const create = this.lifecycle?.create;
|
|
39
50
|
if (typeof create === 'object' && create !== null && 'value' in create) {
|
|
40
51
|
const checked = FieldValueValidator.of(this).validate(create.value);
|
|
41
|
-
if ('
|
|
42
|
-
throw new
|
|
52
|
+
if ('message' in checked)
|
|
53
|
+
throw new SchemaError(
|
|
43
54
|
`${key ? `Field '${key}': ` : ''}the declared default ${JSON.stringify(create.value)} ` +
|
|
44
|
-
`is not a legal value for it — ${checked.
|
|
55
|
+
`is not a legal value for it — ${checked.message}.`,
|
|
45
56
|
);
|
|
46
57
|
}
|
|
47
58
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Axis } from '../axis/Axis.js';
|
|
2
|
+
import { Format } from '../lib/Format.js';
|
|
3
|
+
import type { JsonSchema } from '../lib/JsonSchema.js';
|
|
4
|
+
import { META_FORMAT } from './Meta.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* What a field may state, composed from the keys that state a format: each is carried whole and
|
|
8
|
+
* cited by its `$id`, so the document travels alone and a key keeps its own identity. `shape` is
|
|
9
|
+
* JSON Schema itself, and `Shapes.is` is what reads it.
|
|
10
|
+
*/
|
|
11
|
+
export function fieldFormat(axes: readonly [string, Axis][]): JsonSchema {
|
|
12
|
+
const document = axes.reduce(
|
|
13
|
+
(held, [slot, axis]) => held.cites(slot, axis.format),
|
|
14
|
+
Format.of('field').key('shape', Format.anything),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
return document.cites('meta', META_FORMAT).closed().schema as JsonSchema;
|
|
18
|
+
}
|
package/src/field/FieldSet.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
);
|
package/src/field/Meta.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Field } from './
|
|
2
|
-
import type { Fields } from './
|
|
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
|
@@ -18,7 +18,9 @@ export { Lifecycle } from './axis/lifecycle/Lifecycle.js';
|
|
|
18
18
|
export { Boundary } from './axis/boundary/Boundary.js';
|
|
19
19
|
export { applyCreate, applyUpdate } from './axis/lifecycle/apply.js';
|
|
20
20
|
export { InputRefusal } from './validator/InputRefusal.js';
|
|
21
|
+
export { SchemaError } from './SchemaError.js';
|
|
21
22
|
export { type ValidationError } from './lib/ValidationError.js';
|
|
23
|
+
export { type Verdict } from './lib/Verdict.js';
|
|
22
24
|
export { dotted, type ValidationResult } from './lib/ValidationResult.js';
|
|
23
25
|
|
|
24
26
|
export { Card } from './projection/card/Card.js';
|
|
@@ -35,11 +37,14 @@ export type { SetDiff } from './projection/card/SetDiff.js';
|
|
|
35
37
|
export type { SetDiffOptions } from './projection/card/SetDiffOptions.js';
|
|
36
38
|
|
|
37
39
|
export * from './lib/index.js';
|
|
38
|
-
export {
|
|
40
|
+
export { StringFormats } from './axis/shape/StringFormats.js';
|
|
39
41
|
export { Generators } from './axis/lifecycle/Generators.js';
|
|
40
42
|
export { Boundaries } from './axis/boundary/Decoder.js';
|
|
41
|
-
export { Clock } from './axis/lifecycle/Clock.js';
|
|
42
43
|
export { type FougereEntityAdapters } from './entity/FougereEntityAdapters.js';
|
|
43
44
|
export { InputValidator } from './validator/InputValidator.js';
|
|
44
45
|
export { FieldValueValidator } from './validator/FieldValueValidator.js';
|
|
45
|
-
export {
|
|
46
|
+
export { JsonSchemaValidator } from './validator/JsonSchemaValidator.js';
|
|
47
|
+
export type { JsonSchema } from './lib/JsonSchema.js';
|
|
48
|
+
export { Axes } from './axis/Axes.js';
|
|
49
|
+
export { Format } from './lib/Format.js';
|
|
50
|
+
export type { Axis } from './axis/Axis.js';
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { JsonSchema } from './JsonSchema.js';
|
|
2
|
+
import { SchemaError } from '../SchemaError.js';
|
|
3
|
+
|
|
4
|
+
type Stated = JsonSchema | boolean;
|
|
5
|
+
|
|
6
|
+
const HERE = 'https://fougere.dev/schema/';
|
|
7
|
+
|
|
8
|
+
const absolute = (id?: string): string | undefined =>
|
|
9
|
+
id === undefined || id.includes('://') ? id : `${HERE}${id}`;
|
|
10
|
+
|
|
11
|
+
/** The values a format lets in, as a type — `Accepted<typeof Lifecycle.format>`. */
|
|
12
|
+
export type Accepted<F> = F extends Format<infer T> ? T : never;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* How a format is written here: its keys, never its JSON Schema. `$id`, `$ref`, `$defs` and the
|
|
16
|
+
* closing are produced, and what it accepts is carried as a TYPE, so a declaration is stated once.
|
|
17
|
+
* FR : on écrit les clés d'un format ; le document et le type sont produits.
|
|
18
|
+
* `Format.of('axis/tenancy').key('scope', Format.tokens(SCOPES)).closed()`
|
|
19
|
+
*/
|
|
20
|
+
export class Format<T = unknown> {
|
|
21
|
+
static readonly anything = new Format<unknown>(true);
|
|
22
|
+
static readonly text = new Format<string>({ type: 'string' });
|
|
23
|
+
static readonly flag = new Format<boolean>({ type: 'boolean' });
|
|
24
|
+
|
|
25
|
+
declare readonly _admits?: T;
|
|
26
|
+
|
|
27
|
+
protected constructor(
|
|
28
|
+
private readonly stated: Stated,
|
|
29
|
+
readonly id?: string,
|
|
30
|
+
) {}
|
|
31
|
+
|
|
32
|
+
/** `Format.tokens(['now', 'optional'])` → `Instance does not match any of ["now","optional"].` */
|
|
33
|
+
static tokens<const Words extends readonly string[]>(words: Words): Format<Words[number]> {
|
|
34
|
+
return new Format({ enum: [...words] });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A word or a shape, told apart by TYPE, so a refusal names the half the value belongs to. */
|
|
38
|
+
static either<Word, Shape>(word: Format<Word>, shape: Format<Shape>): Format<Word | Shape> {
|
|
39
|
+
return new Format({
|
|
40
|
+
if: { type: 'string' },
|
|
41
|
+
then: word.schema as JsonSchema,
|
|
42
|
+
else: shape.schema as JsonSchema,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** One of these keys and no other — `{ value }` or `{ generate }`, never both and never neither. */
|
|
47
|
+
static exactlyOne<Keys extends Record<string, Format<unknown>>>(
|
|
48
|
+
keys: Keys,
|
|
49
|
+
): Format<{ [K in keyof Keys]: { [P in K]: Accepted<Keys[K]> } }[keyof Keys]> {
|
|
50
|
+
return new Format({
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: Object.fromEntries(
|
|
53
|
+
Object.entries(keys).map(([key, format]) => [key, format.schema]),
|
|
54
|
+
),
|
|
55
|
+
propertyNames: { enum: Object.keys(keys) },
|
|
56
|
+
minProperties: 1,
|
|
57
|
+
maxProperties: 1,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** A name resolves under this project's own space; another one states its whole `$id`. */
|
|
62
|
+
static of(id?: string): ObjectFormat<object> {
|
|
63
|
+
return new ObjectFormat(absolute(id));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** What `of` does for an object, for a format that is not one — `either`, a list of words. */
|
|
67
|
+
static named<Value>(id: string, format: Format<Value>): Format<Value> {
|
|
68
|
+
const named = absolute(id) as string;
|
|
69
|
+
|
|
70
|
+
return new Format({ $id: named, ...(format.schema as JsonSchema) }, named);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The type JSON Schema cannot carry: a generator's name, a thunk to an entity. */
|
|
74
|
+
as<Value>(): Format<Value> {
|
|
75
|
+
return this as unknown as Format<Value>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
get schema(): Stated {
|
|
79
|
+
return this.stated;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export class ObjectFormat<T extends object> extends Format<T> {
|
|
84
|
+
private readonly keys = new Map<string, Stated>();
|
|
85
|
+
private readonly cited = new Map<string, JsonSchema>();
|
|
86
|
+
private readonly needed: string[] = [];
|
|
87
|
+
|
|
88
|
+
constructor(id?: string) {
|
|
89
|
+
super(true, id);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
key<Name extends string, Value>(
|
|
93
|
+
name: Name,
|
|
94
|
+
format: Format<Value>,
|
|
95
|
+
): ObjectFormat<T & { [K in Name]?: Value }> {
|
|
96
|
+
this.keys.set(name, format.schema);
|
|
97
|
+
|
|
98
|
+
return this as ObjectFormat<T & { [K in Name]?: Value }>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** A key carried whole and pointed at by its `$id`, so it keeps an identity of its own. */
|
|
102
|
+
cites<Name extends string, Value>(
|
|
103
|
+
name: Name,
|
|
104
|
+
format: Format<Value>,
|
|
105
|
+
): ObjectFormat<T & { [K in Name]?: Value }> {
|
|
106
|
+
if (!format.id) {
|
|
107
|
+
throw new SchemaError(
|
|
108
|
+
`A format cited as '${name}' states no \`$id\`, so nothing can point at it.`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
this.keys.set(name, { $ref: format.id });
|
|
112
|
+
this.cited.set(name, format.schema as JsonSchema);
|
|
113
|
+
|
|
114
|
+
return this as ObjectFormat<T & { [K in Name]?: Value }>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
needs<Name extends keyof T & string>(
|
|
118
|
+
...names: Name[]
|
|
119
|
+
): ObjectFormat<Omit<T, Name> & Required<Pick<T, Name>>> {
|
|
120
|
+
this.needed.push(...names);
|
|
121
|
+
|
|
122
|
+
return this as unknown as ObjectFormat<Omit<T, Name> & Required<Pick<T, Name>>>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** These keys and no other: a name nothing states is refused UNDER that name. */
|
|
126
|
+
closed(): Format<{ [K in keyof T]: T[K] }> {
|
|
127
|
+
return new Format(
|
|
128
|
+
{
|
|
129
|
+
...(this.id ? { $id: this.id } : {}),
|
|
130
|
+
type: 'object',
|
|
131
|
+
properties: Object.fromEntries(this.keys),
|
|
132
|
+
propertyNames: { enum: [...this.keys.keys()] },
|
|
133
|
+
...(this.needed.length ? { required: [...this.needed] } : {}),
|
|
134
|
+
...(this.cited.size ? { $defs: Object.fromEntries(this.cited) } : {}),
|
|
135
|
+
},
|
|
136
|
+
this.id,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { Schema as JsonSchema } from '@cfworker/json-schema';
|
package/src/lib/Registry.ts
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
+
import { SchemaError } from '../SchemaError.js';
|
|
2
|
+
|
|
1
3
|
export class Registry<T> {
|
|
2
|
-
private readonly
|
|
4
|
+
private readonly held: 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
|
-
|
|
9
|
+
entries?: Iterable<readonly [string, T]>,
|
|
9
10
|
) {
|
|
10
|
-
this.
|
|
11
|
+
this.held = new Map(entries);
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
register(name: string, value: T): T {
|
|
14
|
-
this.
|
|
15
|
+
this.held.set(name, value);
|
|
15
16
|
|
|
16
17
|
return value;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
find(name: string): T | undefined {
|
|
20
|
-
return this.
|
|
21
|
+
return this.held.get(name);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
resolve(name: string, path?: string): T {
|
|
@@ -25,13 +26,21 @@ export class Registry<T> {
|
|
|
25
26
|
|
|
26
27
|
if (found !== undefined) return found;
|
|
27
28
|
|
|
28
|
-
throw new
|
|
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
|
);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
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()];
|
|
36
45
|
}
|
|
37
46
|
}
|
|
@@ -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, '~'));
|
|
@@ -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'] });
|