@fougere/schema 0.2.0-alpha.2 → 0.4.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.
- package/README.md +1 -1
- package/dist/EntityAdapters.d.ts +26 -0
- package/dist/EntityAdapters.d.ts.map +1 -0
- package/dist/EntityAdapters.js +71 -0
- package/dist/EntityAdapters.js.map +1 -0
- package/dist/EntityDeclarations.d.ts +15 -0
- package/dist/EntityDeclarations.d.ts.map +1 -0
- package/dist/EntityDeclarations.js +2 -0
- package/dist/EntityDeclarations.js.map +1 -0
- package/dist/axis/Axis.d.ts +12 -0
- package/dist/axis/Axis.d.ts.map +1 -0
- package/dist/axis/Axis.js +11 -0
- package/dist/axis/Axis.js.map +1 -0
- package/dist/axis/Meta.d.ts +4 -0
- package/dist/axis/Meta.d.ts.map +1 -0
- package/dist/axis/Meta.js +2 -0
- package/dist/axis/Meta.js.map +1 -0
- package/dist/axis/boundary/Boundaries.d.ts +20 -0
- package/dist/axis/boundary/Boundaries.d.ts.map +1 -0
- package/dist/axis/boundary/Boundaries.js +31 -0
- package/dist/axis/boundary/Boundaries.js.map +1 -0
- package/dist/axis/boundary/Boundary.d.ts +26 -0
- package/dist/axis/boundary/Boundary.d.ts.map +1 -0
- package/dist/axis/boundary/Boundary.js +53 -0
- package/dist/axis/boundary/Boundary.js.map +1 -0
- package/dist/axis/boundary/BoundaryAxis.d.ts +4 -0
- package/dist/axis/boundary/BoundaryAxis.d.ts.map +1 -0
- package/dist/axis/boundary/BoundaryAxis.js +30 -0
- package/dist/axis/boundary/BoundaryAxis.js.map +1 -0
- package/dist/axis/lifecycle/Clock.d.ts +18 -0
- package/dist/axis/lifecycle/Clock.d.ts.map +1 -0
- package/dist/axis/lifecycle/Clock.js +25 -0
- package/dist/axis/lifecycle/Clock.js.map +1 -0
- package/dist/axis/lifecycle/Generators.d.ts +18 -0
- package/dist/axis/lifecycle/Generators.d.ts.map +1 -0
- package/dist/axis/lifecycle/Generators.js +38 -0
- package/dist/axis/lifecycle/Generators.js.map +1 -0
- package/dist/axis/lifecycle/Lifecycle.d.ts +28 -0
- package/dist/axis/lifecycle/Lifecycle.d.ts.map +1 -0
- package/dist/axis/lifecycle/Lifecycle.js +38 -0
- package/dist/axis/lifecycle/Lifecycle.js.map +1 -0
- package/dist/axis/lifecycle/LifecycleAxis.d.ts +4 -0
- package/dist/axis/lifecycle/LifecycleAxis.d.ts.map +1 -0
- package/dist/axis/lifecycle/LifecycleAxis.js +47 -0
- package/dist/axis/lifecycle/LifecycleAxis.js.map +1 -0
- package/dist/axis/lifecycle/apply.d.ts +6 -0
- package/dist/axis/lifecycle/apply.d.ts.map +1 -0
- package/dist/axis/lifecycle/apply.js +34 -0
- package/dist/axis/lifecycle/apply.js.map +1 -0
- package/dist/axis/role/Relation.d.ts +20 -0
- package/dist/axis/role/Relation.d.ts.map +1 -0
- package/dist/axis/role/Relation.js +21 -0
- package/dist/axis/role/Relation.js.map +1 -0
- package/dist/axis/role/Role.d.ts +26 -0
- package/dist/axis/role/Role.d.ts.map +1 -0
- package/dist/axis/role/Role.js +40 -0
- package/dist/axis/role/Role.js.map +1 -0
- package/dist/axis/role/RoleAxis.d.ts +5 -0
- package/dist/axis/role/RoleAxis.d.ts.map +1 -0
- package/dist/axis/role/RoleAxis.js +102 -0
- package/dist/axis/role/RoleAxis.js.map +1 -0
- package/dist/axis/shape/Formats.d.ts +8 -0
- package/dist/axis/shape/Formats.d.ts.map +1 -0
- package/dist/axis/shape/Formats.js +10 -0
- package/dist/axis/shape/Formats.js.map +1 -0
- package/dist/axis/shape/Shape.d.ts +58 -0
- package/dist/axis/shape/Shape.d.ts.map +1 -0
- package/dist/{field/shape.js → axis/shape/Shape.js} +10 -25
- package/dist/axis/shape/Shape.js.map +1 -0
- package/dist/card/Bundle.d.ts +15 -0
- package/dist/card/Bundle.d.ts.map +1 -0
- package/dist/card/Bundle.js +67 -0
- package/dist/card/Bundle.js.map +1 -0
- package/dist/card/Card.d.ts +27 -0
- package/dist/card/Card.d.ts.map +1 -0
- package/dist/card/Card.js +230 -0
- package/dist/card/Card.js.map +1 -0
- package/dist/card/Descriptor.d.ts +55 -0
- package/dist/card/Descriptor.d.ts.map +1 -0
- package/dist/card/Descriptor.js +2 -0
- package/dist/card/Descriptor.js.map +1 -0
- package/dist/card/EntityTypeSource.d.ts +12 -0
- package/dist/card/EntityTypeSource.d.ts.map +1 -0
- package/dist/card/EntityTypeSource.js +78 -0
- package/dist/card/EntityTypeSource.js.map +1 -0
- package/dist/card/FacadeTypeSource.d.ts +19 -0
- package/dist/card/FacadeTypeSource.d.ts.map +1 -0
- package/dist/card/FacadeTypeSource.js +40 -0
- package/dist/card/FacadeTypeSource.js.map +1 -0
- package/dist/card/admission.d.ts +9 -0
- package/dist/card/admission.d.ts.map +1 -0
- package/dist/card/admission.js +16 -0
- package/dist/card/admission.js.map +1 -0
- package/dist/card/diff.d.ts +96 -0
- package/dist/card/diff.d.ts.map +1 -0
- package/dist/card/diff.js +2 -0
- package/dist/card/diff.js.map +1 -0
- package/dist/constraint/FieldGroup.d.ts +21 -0
- package/dist/constraint/FieldGroup.d.ts.map +1 -0
- package/dist/constraint/FieldGroup.js +65 -0
- package/dist/constraint/FieldGroup.js.map +1 -0
- package/dist/constraint/Unique.d.ts +4 -0
- package/dist/constraint/Unique.d.ts.map +1 -0
- package/dist/constraint/Unique.js +4 -0
- package/dist/constraint/Unique.js.map +1 -0
- package/dist/entity.d.ts +4 -19
- package/dist/entity.d.ts.map +1 -1
- package/dist/entity.js +8 -39
- package/dist/entity.js.map +1 -1
- package/dist/fields/Field.d.ts +24 -0
- package/dist/fields/Field.d.ts.map +1 -0
- package/dist/fields/Field.js +52 -0
- package/dist/fields/Field.js.map +1 -0
- package/dist/fields/FieldSet.d.ts +28 -0
- package/dist/fields/FieldSet.d.ts.map +1 -0
- package/dist/fields/FieldSet.js +55 -0
- package/dist/fields/FieldSet.js.map +1 -0
- package/dist/index.d.ts +59 -37
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +50 -48
- package/dist/index.js.map +1 -1
- package/dist/judge/Cases.d.ts +50 -0
- package/dist/judge/Cases.d.ts.map +1 -0
- package/dist/judge/Cases.js +108 -0
- package/dist/judge/Cases.js.map +1 -0
- package/dist/judge/FieldJudge.d.ts +9 -0
- package/dist/judge/FieldJudge.d.ts.map +1 -0
- package/dist/judge/FieldJudge.js +54 -0
- package/dist/judge/FieldJudge.js.map +1 -0
- package/dist/judge/RowJudge.d.ts +12 -0
- package/dist/judge/RowJudge.d.ts.map +1 -0
- package/dist/judge/RowJudge.js +81 -0
- package/dist/judge/RowJudge.js.map +1 -0
- package/dist/judge/RowRefusal.d.ts +17 -0
- package/dist/judge/RowRefusal.d.ts.map +1 -0
- package/dist/judge/RowRefusal.js +16 -0
- package/dist/judge/RowRefusal.js.map +1 -0
- package/dist/judge/ValueForm.d.ts +3 -0
- package/dist/judge/ValueForm.d.ts.map +1 -0
- package/dist/judge/ValueForm.js +3 -0
- package/dist/judge/ValueForm.js.map +1 -0
- package/dist/judge/ValueJudge.d.ts +12 -0
- package/dist/judge/ValueJudge.d.ts.map +1 -0
- package/dist/judge/ValueJudge.js +60 -0
- package/dist/judge/ValueJudge.js.map +1 -0
- package/dist/judge/options.d.ts +4 -0
- package/dist/judge/options.d.ts.map +1 -0
- package/dist/judge/options.js +2 -0
- package/dist/judge/options.js.map +1 -0
- package/dist/judge/result.d.ts +17 -0
- package/dist/judge/result.d.ts.map +1 -0
- package/dist/judge/result.js +2 -0
- package/dist/judge/result.js.map +1 -0
- package/dist/projection/SchemaOrCard.d.ts +7 -0
- package/dist/projection/SchemaOrCard.d.ts.map +1 -0
- package/dist/projection/SchemaOrCard.js +11 -0
- package/dist/projection/SchemaOrCard.js.map +1 -0
- package/dist/projection/Visibility.d.ts +10 -0
- package/dist/projection/Visibility.d.ts.map +1 -0
- package/dist/projection/Visibility.js +55 -0
- package/dist/projection/Visibility.js.map +1 -0
- package/dist/{projections → projection}/standard.d.ts +2 -10
- package/dist/projection/standard.d.ts.map +1 -0
- package/dist/projection/standard.js +2 -0
- package/dist/projection/standard.js.map +1 -0
- package/dist/schema/Schema.d.ts +63 -0
- package/dist/schema/Schema.d.ts.map +1 -0
- package/dist/schema/Schema.js +129 -0
- package/dist/schema/Schema.js.map +1 -0
- package/dist/schema/SchemaDefinition.d.ts +46 -0
- package/dist/schema/SchemaDefinition.d.ts.map +1 -0
- package/dist/schema/SchemaDefinition.js +64 -0
- package/dist/schema/SchemaDefinition.js.map +1 -0
- package/dist/schema/SchemaDerivation.d.ts +28 -0
- package/dist/schema/SchemaDerivation.d.ts.map +1 -0
- package/dist/schema/SchemaDerivation.js +37 -0
- package/dist/schema/SchemaDerivation.js.map +1 -0
- package/dist/schema/SchemaView.d.ts +23 -0
- package/dist/schema/SchemaView.d.ts.map +1 -0
- package/dist/schema/SchemaView.js +2 -0
- package/dist/schema/SchemaView.js.map +1 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +13 -0
- package/dist/utils.js.map +1 -0
- package/dist/vocabulary/bool.d.ts +1 -1
- package/dist/vocabulary/bool.d.ts.map +1 -1
- package/dist/vocabulary/bool.js +1 -1
- package/dist/vocabulary/bool.js.map +1 -1
- package/dist/vocabulary/created.d.ts +1 -5
- package/dist/vocabulary/created.d.ts.map +1 -1
- package/dist/vocabulary/created.js +1 -5
- package/dist/vocabulary/created.js.map +1 -1
- package/dist/vocabulary/date.d.ts +1 -1
- package/dist/vocabulary/date.d.ts.map +1 -1
- package/dist/vocabulary/date.js +1 -1
- package/dist/vocabulary/date.js.map +1 -1
- package/dist/vocabulary/email.d.ts +1 -2
- package/dist/vocabulary/email.d.ts.map +1 -1
- package/dist/vocabulary/email.js +0 -1
- package/dist/vocabulary/email.js.map +1 -1
- package/dist/vocabulary/immutable.d.ts +2 -6
- package/dist/vocabulary/immutable.d.ts.map +1 -1
- package/dist/vocabulary/immutable.js +4 -7
- package/dist/vocabulary/immutable.js.map +1 -1
- package/dist/vocabulary/indexed.d.ts +2 -7
- package/dist/vocabulary/indexed.d.ts.map +1 -1
- package/dist/vocabulary/indexed.js +2 -8
- package/dist/vocabulary/indexed.js.map +1 -1
- package/dist/vocabulary/json.d.ts +2 -10
- package/dist/vocabulary/json.d.ts.map +1 -1
- package/dist/vocabulary/json.js +12 -7
- package/dist/vocabulary/json.js.map +1 -1
- package/dist/vocabulary/list.d.ts +1 -12
- package/dist/vocabulary/list.d.ts.map +1 -1
- package/dist/vocabulary/list.js +1 -14
- package/dist/vocabulary/list.js.map +1 -1
- package/dist/vocabulary/many.d.ts +2 -5
- package/dist/vocabulary/many.d.ts.map +1 -1
- package/dist/vocabulary/many.js +3 -6
- package/dist/vocabulary/many.js.map +1 -1
- package/dist/vocabulary/nullable.d.ts +2 -6
- package/dist/vocabulary/nullable.d.ts.map +1 -1
- package/dist/vocabulary/nullable.js +5 -8
- package/dist/vocabulary/nullable.js.map +1 -1
- package/dist/vocabulary/number.d.ts +1 -1
- package/dist/vocabulary/number.d.ts.map +1 -1
- package/dist/vocabulary/number.js +1 -1
- package/dist/vocabulary/number.js.map +1 -1
- package/dist/vocabulary/oneOf.d.ts +1 -8
- package/dist/vocabulary/oneOf.d.ts.map +1 -1
- package/dist/vocabulary/oneOf.js +1 -1
- package/dist/vocabulary/oneOf.js.map +1 -1
- package/dist/vocabulary/optional.d.ts +2 -9
- package/dist/vocabulary/optional.d.ts.map +1 -1
- package/dist/vocabulary/optional.js +6 -14
- package/dist/vocabulary/optional.js.map +1 -1
- package/dist/vocabulary/primary.d.ts +2 -15
- package/dist/vocabulary/primary.d.ts.map +1 -1
- package/dist/vocabulary/primary.js +4 -11
- package/dist/vocabulary/primary.js.map +1 -1
- package/dist/vocabulary/readOnly.d.ts +2 -6
- package/dist/vocabulary/readOnly.d.ts.map +1 -1
- package/dist/vocabulary/readOnly.js +5 -8
- package/dist/vocabulary/readOnly.js.map +1 -1
- package/dist/vocabulary/ref.d.ts +2 -7
- package/dist/vocabulary/ref.d.ts.map +1 -1
- package/dist/vocabulary/ref.js +3 -14
- package/dist/vocabulary/ref.js.map +1 -1
- package/dist/vocabulary/text.d.ts +2 -2
- package/dist/vocabulary/text.d.ts.map +1 -1
- package/dist/vocabulary/text.js +1 -1
- package/dist/vocabulary/text.js.map +1 -1
- package/dist/vocabulary/unique.d.ts +2 -12
- package/dist/vocabulary/unique.d.ts.map +1 -1
- package/dist/vocabulary/unique.js +5 -13
- package/dist/vocabulary/unique.js.map +1 -1
- package/dist/vocabulary/updated.d.ts +1 -6
- package/dist/vocabulary/updated.d.ts.map +1 -1
- package/dist/vocabulary/updated.js +0 -5
- package/dist/vocabulary/updated.js.map +1 -1
- package/dist/vocabulary/url.d.ts +1 -2
- package/dist/vocabulary/url.d.ts.map +1 -1
- package/dist/vocabulary/url.js +0 -1
- package/dist/vocabulary/url.js.map +1 -1
- package/dist/vocabulary/vocabulary.d.ts +4 -0
- package/dist/vocabulary/vocabulary.d.ts.map +1 -0
- package/dist/vocabulary/vocabulary.js +31 -0
- package/dist/vocabulary/vocabulary.js.map +1 -0
- package/dist/vocabulary/writeOnly.d.ts +2 -6
- package/dist/vocabulary/writeOnly.d.ts.map +1 -1
- package/dist/vocabulary/writeOnly.js +5 -8
- package/dist/vocabulary/writeOnly.js.map +1 -1
- package/package.json +8 -4
- package/src/EntityAdapters.ts +95 -0
- package/src/EntityDeclarations.ts +19 -0
- package/src/axis/Axis.ts +25 -0
- package/src/axis/Meta.ts +3 -0
- package/src/axis/boundary/Boundaries.ts +44 -0
- package/src/axis/boundary/Boundary.ts +69 -0
- package/src/axis/boundary/BoundaryAxis.ts +32 -0
- package/src/axis/lifecycle/Clock.ts +26 -0
- package/src/axis/lifecycle/Generators.ts +46 -0
- package/src/axis/lifecycle/Lifecycle.ts +53 -0
- package/src/axis/lifecycle/LifecycleAxis.ts +49 -0
- package/src/axis/lifecycle/apply.ts +38 -0
- package/src/axis/role/Relation.ts +31 -0
- package/src/axis/role/Role.ts +56 -0
- package/src/axis/role/RoleAxis.ts +115 -0
- package/src/axis/shape/Formats.ts +22 -0
- package/src/axis/shape/Shape.ts +91 -0
- package/src/card/Bundle.ts +80 -0
- package/src/card/Card.ts +256 -0
- package/src/card/Descriptor.ts +68 -0
- package/src/card/EntityTypeSource.ts +90 -0
- package/src/card/FacadeTypeSource.ts +55 -0
- package/src/card/admission.ts +25 -0
- package/src/card/diff.ts +56 -0
- package/src/constraint/FieldGroup.ts +91 -0
- package/src/constraint/Unique.ts +3 -0
- package/src/entity.ts +15 -0
- package/src/fields/Field.ts +80 -0
- package/src/fields/FieldSet.ts +62 -0
- package/src/index.ts +64 -0
- package/src/judge/Cases.ts +139 -0
- package/src/judge/FieldJudge.ts +61 -0
- package/src/judge/RowJudge.ts +84 -0
- package/src/judge/RowRefusal.ts +17 -0
- package/src/judge/ValueForm.ts +7 -0
- package/src/judge/ValueJudge.ts +72 -0
- package/src/judge/options.ts +3 -0
- package/src/judge/result.ts +10 -0
- package/src/projection/SchemaOrCard.ts +18 -0
- package/src/projection/Visibility.ts +47 -0
- package/src/projection/standard.ts +47 -0
- package/src/schema/Schema.ts +167 -0
- package/src/schema/SchemaDefinition.ts +87 -0
- package/src/schema/SchemaDerivation.ts +46 -0
- package/src/schema/SchemaView.ts +25 -0
- package/src/utils.ts +13 -0
- package/src/vocabulary/bool.ts +9 -0
- package/src/vocabulary/created.ts +8 -0
- package/src/vocabulary/date.ts +5 -0
- package/src/vocabulary/email.ts +6 -0
- package/src/vocabulary/immutable.ts +6 -0
- package/src/vocabulary/indexed.ts +4 -0
- package/src/vocabulary/json.ts +22 -0
- package/src/vocabulary/list.ts +13 -0
- package/src/vocabulary/many.ts +9 -0
- package/src/vocabulary/nullable.ts +7 -0
- package/src/vocabulary/number.ts +17 -0
- package/src/vocabulary/oneOf.ts +24 -0
- package/src/vocabulary/optional.ts +8 -0
- package/src/vocabulary/primary.ts +33 -0
- package/src/vocabulary/readOnly.ts +7 -0
- package/src/vocabulary/ref.ts +13 -0
- package/src/vocabulary/text.ts +19 -0
- package/src/vocabulary/unique.ts +10 -0
- package/src/vocabulary/updated.ts +7 -0
- package/src/vocabulary/url.ts +6 -0
- package/src/vocabulary/vocabulary.ts +40 -0
- package/src/vocabulary/writeOnly.ts +7 -0
- package/dist/field/boundary.d.ts +0 -69
- package/dist/field/boundary.d.ts.map +0 -1
- package/dist/field/boundary.js +0 -82
- package/dist/field/boundary.js.map +0 -1
- package/dist/field/field.d.ts +0 -21
- package/dist/field/field.d.ts.map +0 -1
- package/dist/field/field.js +0 -24
- package/dist/field/field.js.map +0 -1
- package/dist/field/format.d.ts +0 -29
- package/dist/field/format.d.ts.map +0 -1
- package/dist/field/format.js +0 -24
- package/dist/field/format.js.map +0 -1
- package/dist/field/index.d.ts +0 -8
- package/dist/field/index.d.ts.map +0 -1
- package/dist/field/index.js +0 -10
- package/dist/field/index.js.map +0 -1
- package/dist/field/lifecycle.d.ts +0 -31
- package/dist/field/lifecycle.d.ts.map +0 -1
- package/dist/field/lifecycle.js +0 -20
- package/dist/field/lifecycle.js.map +0 -1
- package/dist/field/meta.d.ts +0 -5
- package/dist/field/meta.d.ts.map +0 -1
- package/dist/field/meta.js +0 -5
- package/dist/field/meta.js.map +0 -1
- package/dist/field/role.d.ts +0 -47
- package/dist/field/role.d.ts.map +0 -1
- package/dist/field/role.js +0 -35
- package/dist/field/role.js.map +0 -1
- package/dist/field/shape.d.ts +0 -89
- package/dist/field/shape.d.ts.map +0 -1
- package/dist/field/shape.js.map +0 -1
- package/dist/hints.d.ts +0 -28
- package/dist/hints.d.ts.map +0 -1
- package/dist/hints.js +0 -23
- package/dist/hints.js.map +0 -1
- package/dist/name.d.ts +0 -8
- package/dist/name.d.ts.map +0 -1
- package/dist/name.js +0 -10
- package/dist/name.js.map +0 -1
- package/dist/projections/card.d.ts +0 -98
- package/dist/projections/card.d.ts.map +0 -1
- package/dist/projections/card.js +0 -8
- package/dist/projections/card.js.map +0 -1
- package/dist/projections/describe.d.ts +0 -25
- package/dist/projections/describe.d.ts.map +0 -1
- package/dist/projections/describe.js +0 -131
- package/dist/projections/describe.js.map +0 -1
- package/dist/projections/encode.d.ts +0 -11
- package/dist/projections/encode.d.ts.map +0 -1
- package/dist/projections/encode.js +0 -26
- package/dist/projections/encode.js.map +0 -1
- package/dist/projections/io.d.ts +0 -24
- package/dist/projections/io.d.ts.map +0 -1
- package/dist/projections/io.js +0 -46
- package/dist/projections/io.js.map +0 -1
- package/dist/projections/lifecycle.d.ts +0 -29
- package/dist/projections/lifecycle.d.ts.map +0 -1
- package/dist/projections/lifecycle.js +0 -93
- package/dist/projections/lifecycle.js.map +0 -1
- package/dist/projections/reconstruct.d.ts +0 -31
- package/dist/projections/reconstruct.d.ts.map +0 -1
- package/dist/projections/reconstruct.js +0 -146
- package/dist/projections/reconstruct.js.map +0 -1
- package/dist/projections/source.d.ts +0 -18
- package/dist/projections/source.d.ts.map +0 -1
- package/dist/projections/source.js +0 -18
- package/dist/projections/source.js.map +0 -1
- package/dist/projections/standard.d.ts.map +0 -1
- package/dist/projections/standard.js +0 -10
- package/dist/projections/standard.js.map +0 -1
- package/dist/projections/typescript.d.ts +0 -37
- package/dist/projections/typescript.d.ts.map +0 -1
- package/dist/projections/typescript.js +0 -157
- package/dist/projections/typescript.js.map +0 -1
- package/dist/schema/index.d.ts +0 -3
- package/dist/schema/index.d.ts.map +0 -1
- package/dist/schema/index.js +0 -2
- package/dist/schema/index.js.map +0 -1
- package/dist/schema/schema.d.ts +0 -74
- package/dist/schema/schema.d.ts.map +0 -1
- package/dist/schema/schema.js +0 -162
- package/dist/schema/schema.js.map +0 -1
- package/dist/schema/view.d.ts +0 -26
- package/dist/schema/view.d.ts.map +0 -1
- package/dist/schema/view.js +0 -2
- package/dist/schema/view.js.map +0 -1
- package/dist/unique.d.ts +0 -40
- package/dist/unique.d.ts.map +0 -1
- package/dist/unique.js +0 -57
- package/dist/unique.js.map +0 -1
- package/dist/validation/index.d.ts +0 -4
- package/dist/validation/index.d.ts.map +0 -1
- package/dist/validation/index.js +0 -2
- package/dist/validation/index.js.map +0 -1
- package/dist/validation/judge.d.ts +0 -55
- package/dist/validation/judge.d.ts.map +0 -1
- package/dist/validation/judge.js +0 -351
- package/dist/validation/judge.js.map +0 -1
- package/dist/validation/options.d.ts +0 -6
- package/dist/validation/options.d.ts.map +0 -1
- package/dist/validation/options.js +0 -3
- package/dist/validation/options.js.map +0 -1
- package/dist/validation/result.d.ts +0 -20
- package/dist/validation/result.d.ts.map +0 -1
- package/dist/validation/result.js +0 -3
- package/dist/validation/result.js.map +0 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Field, Fields } from "../fields/Field.js";
|
|
2
|
+
|
|
3
|
+
export abstract class FieldGroup {
|
|
4
|
+
constructor(readonly members: readonly string[]) {}
|
|
5
|
+
|
|
6
|
+
protected withMembers(members: readonly string[]): this {
|
|
7
|
+
return new (this.constructor as new (members: readonly string[]) => this)(
|
|
8
|
+
members,
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get isSelf(): boolean {
|
|
13
|
+
return this.members.length === 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
resolvedOn(ownKey: string): this {
|
|
17
|
+
return this.isSelf ? this.withMembers([ownKey]) : this;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
covers(key: string): boolean {
|
|
21
|
+
return this.members.includes(key);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
equals(other: FieldGroup): boolean {
|
|
25
|
+
return (
|
|
26
|
+
this.constructor === other.constructor &&
|
|
27
|
+
this.members.length === other.members.length &&
|
|
28
|
+
this.members.every((member, i) => member === other.members[i])
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
onto(fields: Fields): Fields {
|
|
33
|
+
const missing = this.members.filter((key) => !(key in fields));
|
|
34
|
+
if (missing.length)
|
|
35
|
+
throw new Error(
|
|
36
|
+
`${this.constructor.name.toLowerCase()}: [${this.members.join(", ")}] names ` +
|
|
37
|
+
`${missing.map((m) => `'${m}'`).join(", ")}, which the entity does not declare.`,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const out: Fields = { ...fields };
|
|
41
|
+
for (const key of this.members) {
|
|
42
|
+
const field = out[key]!;
|
|
43
|
+
out[key] = field.with({
|
|
44
|
+
role: { ...field.role, rules: [...(field.role?.rules ?? []), this] },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
rename(map: (key: string) => string | undefined): this | null {
|
|
51
|
+
if (this.isSelf) return this;
|
|
52
|
+
const carried = this.members.map(map);
|
|
53
|
+
return carried.every((key): key is string => key !== undefined)
|
|
54
|
+
? this.withMembers(carried)
|
|
55
|
+
: null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
toJSON(): string[] {
|
|
59
|
+
return [...this.members];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static normalize<R extends FieldGroup>(
|
|
63
|
+
groups: readonly (R | readonly string[])[] | undefined,
|
|
64
|
+
make: (members: readonly string[]) => R,
|
|
65
|
+
): readonly R[] | undefined {
|
|
66
|
+
if (!groups) return undefined;
|
|
67
|
+
return groups.map((group) =>
|
|
68
|
+
group instanceof FieldGroup ? (group as R) : make(group),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static on<R extends FieldGroup>(
|
|
73
|
+
field: Field,
|
|
74
|
+
kind: Function & { prototype: R },
|
|
75
|
+
): R[] {
|
|
76
|
+
return (field.role?.rules ?? []).filter(
|
|
77
|
+
(rule): rule is R => rule instanceof kind,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static groupsOf<R extends FieldGroup>(
|
|
82
|
+
fields: Fields,
|
|
83
|
+
kind: Function & { prototype: R },
|
|
84
|
+
): R[] {
|
|
85
|
+
const seen = new Map<string, R>();
|
|
86
|
+
for (const field of Object.values(fields))
|
|
87
|
+
for (const rule of FieldGroup.on(field, kind))
|
|
88
|
+
if (rule.members.length > 1) seen.set(rule.members.join(" "), rule);
|
|
89
|
+
return [...seen.values()];
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/entity.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Fields } from './fields/Field.js';
|
|
2
|
+
import { FieldSet } from './fields/FieldSet.js';
|
|
3
|
+
import { type EntityDeclarations } from './EntityDeclarations.js';
|
|
4
|
+
import { Schema, type SchemaConstructor } from './schema/Schema.js';
|
|
5
|
+
|
|
6
|
+
export function entity<TFields extends Fields>(
|
|
7
|
+
originalFields: TFields,
|
|
8
|
+
{ unique, adapters, previous }: EntityDeclarations<TFields> = {},
|
|
9
|
+
): SchemaConstructor<TFields> {
|
|
10
|
+
return Schema.of({
|
|
11
|
+
fields: FieldSet.declared(originalFields, unique),
|
|
12
|
+
adapters,
|
|
13
|
+
previous,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Shape } from "../axis/shape/Shape.js";
|
|
2
|
+
import { Role, 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
|
+
import type { Meta } from "../axis/Meta.js";
|
|
6
|
+
import type { Axis } from "../axis/Axis.js";
|
|
7
|
+
import { FieldJudge } from "../judge/FieldJudge.js";
|
|
8
|
+
import { ValueJudge } from "../judge/ValueJudge.js";
|
|
9
|
+
|
|
10
|
+
export type Fields = Record<string, Field>;
|
|
11
|
+
|
|
12
|
+
/** A key of a field set — the name a declaration addresses a field by. */
|
|
13
|
+
export type FieldName<TFields extends Fields> = Extract<keyof TFields, string>;
|
|
14
|
+
|
|
15
|
+
/** The carrier, the three extension axes, the annotation — the five the judge reads. */
|
|
16
|
+
export type FieldDeclaration = Pick<Field, "shape" | Axis["slot"] | "meta">;
|
|
17
|
+
|
|
18
|
+
export class Field<T = unknown> {
|
|
19
|
+
readonly shape: Shape;
|
|
20
|
+
readonly role?: RoleRules;
|
|
21
|
+
readonly lifecycle?: LifecycleRules;
|
|
22
|
+
readonly boundary?: BoundaryRef;
|
|
23
|
+
readonly meta?: Meta;
|
|
24
|
+
|
|
25
|
+
declare readonly _type?: T;
|
|
26
|
+
|
|
27
|
+
constructor(init: FieldDeclaration, key?: string) {
|
|
28
|
+
const verdict = FieldJudge.of(init).verdict;
|
|
29
|
+
|
|
30
|
+
if (!verdict.success) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`${key ? `Field '${key}': ` : ""}` +
|
|
33
|
+
verdict.errors.map((e) => `${e.path}: ${e.message}`).join("; "),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.shape = init.shape;
|
|
38
|
+
this.role = Role.resolvedOn(init.role, key);
|
|
39
|
+
this.lifecycle = init.lifecycle;
|
|
40
|
+
this.boundary = init.boundary;
|
|
41
|
+
this.meta = init.meta;
|
|
42
|
+
|
|
43
|
+
const create = this.lifecycle?.create;
|
|
44
|
+
if (typeof create === "object" && create !== null && "value" in create) {
|
|
45
|
+
const checked = ValueJudge.of(this).check(create.value);
|
|
46
|
+
if ("error" in checked)
|
|
47
|
+
throw new Error(
|
|
48
|
+
`${key ? `Field '${key}': ` : ""}the declared default ${JSON.stringify(create.value)} ` +
|
|
49
|
+
`is not a legal value for it — ${checked.error}.`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static is(value: unknown): value is Field {
|
|
55
|
+
return FieldJudge.of(value).verdict.success;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
with<U = T>(overrides: Partial<FieldDeclaration>): Field<U> {
|
|
59
|
+
return new Field<U>({ ...this, ...overrides });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
rename(map: (key: string) => string | undefined): Field<T> {
|
|
63
|
+
const rules = this.role?.rules;
|
|
64
|
+
if (!rules?.length) return this;
|
|
65
|
+
|
|
66
|
+
const carried = rules
|
|
67
|
+
.map((group) => group.rename(map))
|
|
68
|
+
.filter((group) => group !== null);
|
|
69
|
+
const unchanged =
|
|
70
|
+
carried.length === rules.length &&
|
|
71
|
+
carried.every((group, i) => group.equals(rules[i]!));
|
|
72
|
+
if (unchanged) return this;
|
|
73
|
+
|
|
74
|
+
const { rules: _dropped, ...rest } = this.role!;
|
|
75
|
+
return this.with({
|
|
76
|
+
role: carried.length ? { ...rest, rules: carried } : rest,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Role } from '../axis/role/Role.js';
|
|
2
|
+
import { FieldGroup } from '../constraint/FieldGroup.js';
|
|
3
|
+
import { Unique } from '../constraint/Unique.js';
|
|
4
|
+
import type { CompositeUnique } from '../EntityDeclarations.js';
|
|
5
|
+
import { Field, type FieldName, type Fields } from './Field.js';
|
|
6
|
+
|
|
7
|
+
export class FieldSet<TFields extends Fields = Fields> {
|
|
8
|
+
private constructor(private readonly fields: TFields) {}
|
|
9
|
+
|
|
10
|
+
static of<TFields extends Fields>(fields: TFields): FieldSet<TFields> {
|
|
11
|
+
return new FieldSet(fields);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Every entry through the field door, then the groups declared over several of them.
|
|
16
|
+
* A group written here names its members; one written on a field named itself, and the
|
|
17
|
+
* key it sat under resolved it.
|
|
18
|
+
*/
|
|
19
|
+
static declared<TFields extends Fields>(
|
|
20
|
+
declared: TFields,
|
|
21
|
+
unique?: CompositeUnique<TFields>,
|
|
22
|
+
): TFields {
|
|
23
|
+
let fields: Fields = {};
|
|
24
|
+
for (const [key, field] of Object.entries(declared)) fields[key] = new Field(field, key);
|
|
25
|
+
for (const group of unique ?? []) fields = new Unique(group).onto(fields);
|
|
26
|
+
return fields as TFields;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The name of the field that carries `primary`, or `undefined` when none does.
|
|
31
|
+
*
|
|
32
|
+
* `Role.of(field).isPrimary` answers about ONE field; this answers about a shape, which
|
|
33
|
+
* is the question every reader actually had. It was the same three lines spelled five
|
|
34
|
+
* times — a mirror's key, a form's row identity, GraphQL's node id, the DDL's primary
|
|
35
|
+
* key and the ORM's `findById` column — so a shape with two primaries meant whichever
|
|
36
|
+
* one that loop happened to see first, five times over.
|
|
37
|
+
*
|
|
38
|
+
* The absence is answered and not defaulted: `'id'` is a fine fallback for a form and a
|
|
39
|
+
* lie for a DDL, so the caller decides.
|
|
40
|
+
*/
|
|
41
|
+
get primary(): FieldName<TFields> | undefined {
|
|
42
|
+
const primaries = Object.entries(this.fields)
|
|
43
|
+
.filter(([, field]) => Role.of(field).isPrimary)
|
|
44
|
+
.map(([name]) => name);
|
|
45
|
+
|
|
46
|
+
if (primaries.length > 1) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`FieldSet.primary: ${primaries.map((name) => JSON.stringify(name)).join(', ')} all declare ` +
|
|
49
|
+
'`primary`; a field set can have only one primary field.',
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return primaries[0] as FieldName<TFields> | undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get uniqueGroups(): CompositeUnique<TFields> | undefined {
|
|
57
|
+
const groups = FieldGroup.groupsOf(this.fields, Unique);
|
|
58
|
+
return groups.length
|
|
59
|
+
? groups.map((group) => [...group.members]) as unknown as CompositeUnique<TFields>
|
|
60
|
+
: undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export { Field, type Fields } from './fields/Field.js';
|
|
2
|
+
export { FieldSet } from './fields/FieldSet.js';
|
|
3
|
+
export { type Shape, Anatomy } from './axis/shape/Shape.js';
|
|
4
|
+
export { type StringFormat, type FormatPredicate, Formats } from './axis/shape/Formats.js';
|
|
5
|
+
export { Role, type RoleRules } from './axis/role/Role.js';
|
|
6
|
+
export { type Relation, type EntityConstructor } from './axis/role/Relation.js';
|
|
7
|
+
export { Lifecycle, type LifecycleRules } from './axis/lifecycle/Lifecycle.js';
|
|
8
|
+
export { type GeneratorRef, Generators } from './axis/lifecycle/Generators.js';
|
|
9
|
+
export { Clock } from './axis/lifecycle/Clock.js';
|
|
10
|
+
export { Boundary, type BoundaryRules, type BoundaryRef } from './axis/boundary/Boundary.js';
|
|
11
|
+
export { type Decoder, type Encoder, Boundaries } from './axis/boundary/Boundaries.js';
|
|
12
|
+
export { type Meta } from './axis/Meta.js';
|
|
13
|
+
export { FieldGroup } from './constraint/FieldGroup.js';
|
|
14
|
+
export { Unique } from './constraint/Unique.js';
|
|
15
|
+
|
|
16
|
+
export { Schema, ANONYMOUS_SCHEMA_NAME, type SchemaConstructor } from './schema/Schema.js';
|
|
17
|
+
export { SchemaDerivation } from './schema/SchemaDerivation.js';
|
|
18
|
+
export { isObject } from './judge/ValueForm.js';
|
|
19
|
+
export { Cases, type Case } from './judge/Cases.js';
|
|
20
|
+
export { RowRefusal } from './judge/RowRefusal.js';
|
|
21
|
+
export { type SchemaView, type Row, type PartialRow } from './schema/SchemaView.js';
|
|
22
|
+
export { type FougereEntityAdapters, type EntityAdapters } from './EntityAdapters.js';
|
|
23
|
+
export { lowerFirst, upperFirst } from './utils.js';
|
|
24
|
+
|
|
25
|
+
export { primary } from './vocabulary/primary.js';
|
|
26
|
+
export { text } from './vocabulary/text.js';
|
|
27
|
+
export { email } from './vocabulary/email.js';
|
|
28
|
+
export { url } from './vocabulary/url.js';
|
|
29
|
+
export { list } from './vocabulary/list.js';
|
|
30
|
+
export { number } from './vocabulary/number.js';
|
|
31
|
+
export { bool } from './vocabulary/bool.js';
|
|
32
|
+
export { date } from './vocabulary/date.js';
|
|
33
|
+
export { created } from './vocabulary/created.js';
|
|
34
|
+
export { updated } from './vocabulary/updated.js';
|
|
35
|
+
export { oneOf } from './vocabulary/oneOf.js';
|
|
36
|
+
export { ref } from './vocabulary/ref.js';
|
|
37
|
+
export { many } from './vocabulary/many.js';
|
|
38
|
+
export { optional } from './vocabulary/optional.js';
|
|
39
|
+
export { nullable } from './vocabulary/nullable.js';
|
|
40
|
+
export { immutable } from './vocabulary/immutable.js';
|
|
41
|
+
export { unique } from './vocabulary/unique.js';
|
|
42
|
+
export { indexed } from './vocabulary/indexed.js';
|
|
43
|
+
export { readOnly } from './vocabulary/readOnly.js';
|
|
44
|
+
export { writeOnly } from './vocabulary/writeOnly.js';
|
|
45
|
+
export { json } from './vocabulary/json.js';
|
|
46
|
+
|
|
47
|
+
export { applyCreate, applyUpdate } from './axis/lifecycle/apply.js';
|
|
48
|
+
export { Visibility } from './projection/Visibility.js';
|
|
49
|
+
export { Card } from './card/Card.js';
|
|
50
|
+
export { Bundle } from './card/Bundle.js';
|
|
51
|
+
export type { Change, Diff, DiffOptions, SetDiff, SetDiffOptions, RenameCandidate, TypeSet } from './card/diff.js';
|
|
52
|
+
export { EntityTypeSource, type EntityTypeSourceOptions } from './card/EntityTypeSource.js';
|
|
53
|
+
export { FacadeTypeSource, type FacadeTypeSourceOptions, type OpDescriptor } from './card/FacadeTypeSource.js';
|
|
54
|
+
export { type SchemaBundle, type SchemaDescriptor, type FieldDescriptor, type FieldExtension, type RoleDescriptor, type RelationDescriptor } from './card/Descriptor.js';
|
|
55
|
+
export { type SchemaOrCard, schemaOf, fieldsOf } from './projection/SchemaOrCard.js';
|
|
56
|
+
export type { StandardSchemaV1 } from './projection/standard.js';
|
|
57
|
+
|
|
58
|
+
export { entity } from './entity.js';
|
|
59
|
+
|
|
60
|
+
export { FieldJudge } from './judge/FieldJudge.js';
|
|
61
|
+
export { ValueJudge } from './judge/ValueJudge.js';
|
|
62
|
+
export { RowJudge } from './judge/RowJudge.js';
|
|
63
|
+
export { type ValidateOptions } from './judge/options.js';
|
|
64
|
+
export { type ValidationError, type ValidationResult, type Checked } from './judge/result.js';
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Anatomy } from '../axis/shape/Shape.js';
|
|
2
|
+
import { Boundary } from '../axis/boundary/Boundary.js';
|
|
3
|
+
import { Lifecycle } from '../axis/lifecycle/Lifecycle.js';
|
|
4
|
+
import { Role } from '../axis/role/Role.js';
|
|
5
|
+
import type { Field } from '../fields/Field.js';
|
|
6
|
+
import type { SchemaView } from '../schema/SchemaView.js';
|
|
7
|
+
import { RowJudge } from './RowJudge.js';
|
|
8
|
+
import { RowRefusal } from './RowRefusal.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* One input, and what the judge must answer.
|
|
12
|
+
*
|
|
13
|
+
* `expect` carries the PATH of the field at fault and never the message. We know which
|
|
14
|
+
* field must be refused because we are the ones who broke it; naming the message would
|
|
15
|
+
* make this list a second judge, and the two would then have to be kept in step by hand —
|
|
16
|
+
* which is the duplication the whole thing exists to remove.
|
|
17
|
+
*/
|
|
18
|
+
export interface Case {
|
|
19
|
+
/** What this input does, in one clause — carried into the assertion so a failure reads. */
|
|
20
|
+
why: string;
|
|
21
|
+
body: unknown;
|
|
22
|
+
/**
|
|
23
|
+
* Judged as an update rather than a creation. The two modes refuse different things:
|
|
24
|
+
* an absence is `Required` on create and legal on patch, an immutable field is legal
|
|
25
|
+
* on create and refused on patch.
|
|
26
|
+
*/
|
|
27
|
+
patch: boolean;
|
|
28
|
+
expect: 'accept' | { reject: string };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A value of the wrong type for the declared shape — the one violation every field has. */
|
|
32
|
+
function wrongTypeFor(field: Field): unknown {
|
|
33
|
+
const declared = (field.shape as { type?: string | readonly string[] }).type;
|
|
34
|
+
const names = Array.isArray(declared) ? declared : [declared];
|
|
35
|
+
return names.includes('string') ? 42 : 'not-a-value-of-this-shape';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Values that break a stated bound. Empty when the field states none. */
|
|
39
|
+
function outOfBoundsFor(field: Field): { why: string; value: unknown }[] {
|
|
40
|
+
// Through `Anatomy`, not a cast: the cast admitted any keyword, and it declared
|
|
41
|
+
// `maxItems` — which no branch below ever produced. In the one function whose job is
|
|
42
|
+
// to be exhaustive, a bound the type does not carry is a case nobody notices missing.
|
|
43
|
+
const shape = Anatomy.of(field.shape).base;
|
|
44
|
+
const out: { why: string; value: unknown }[] = [];
|
|
45
|
+
if (shape?.type === 'string') {
|
|
46
|
+
if (typeof shape.minLength === 'number' && shape.minLength > 0)
|
|
47
|
+
out.push({ why: 'shorter than min', value: 'x'.repeat(shape.minLength - 1) });
|
|
48
|
+
if (typeof shape.maxLength === 'number')
|
|
49
|
+
out.push({ why: 'longer than max', value: 'x'.repeat(shape.maxLength + 1) });
|
|
50
|
+
if (shape.enum) out.push({ why: 'outside the stated set', value: '__not-in-enum__' });
|
|
51
|
+
}
|
|
52
|
+
if (shape?.type === 'number' || shape?.type === 'integer') {
|
|
53
|
+
if (typeof shape.minimum === 'number') out.push({ why: 'below minimum', value: shape.minimum - 1 });
|
|
54
|
+
if (typeof shape.maximum === 'number') out.push({ why: 'above maximum', value: shape.maximum + 1 });
|
|
55
|
+
}
|
|
56
|
+
if (shape?.type === 'array') {
|
|
57
|
+
if (typeof shape.minItems === 'number' && shape.minItems > 0)
|
|
58
|
+
out.push({ why: 'fewer items than min', value: [] });
|
|
59
|
+
if (typeof shape.maxItems === 'number')
|
|
60
|
+
out.push({ why: 'more items than max', value: Array.from({ length: shape.maxItems + 1 }, () => null) });
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The decision table, enumerated on the entity's own fields.
|
|
67
|
+
*
|
|
68
|
+
* Use the complete entity so a derived view cannot hide a divergent field. The caller
|
|
69
|
+
* supplies a valid row; generating data belongs to the testing package.
|
|
70
|
+
*/
|
|
71
|
+
export class Cases {
|
|
72
|
+
private constructor(
|
|
73
|
+
readonly all: readonly Case[],
|
|
74
|
+
) {}
|
|
75
|
+
|
|
76
|
+
static of(entity: SchemaView, valid: Record<string, unknown>): Cases {
|
|
77
|
+
return new Cases(enumerate(entity, valid));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Whether a verdict is the one a case expected.
|
|
82
|
+
*
|
|
83
|
+
* Takes the RESULT rather than the input so the same reader serves the local judge, the
|
|
84
|
+
* façade and a door — the three that have to agree.
|
|
85
|
+
*/
|
|
86
|
+
static holds(expected: Case['expect'], result: { success: boolean; errors?: { path: string }[] }): boolean {
|
|
87
|
+
if (expected === 'accept') return result.success;
|
|
88
|
+
if (result.success) return false;
|
|
89
|
+
return (result.errors ?? []).some((error) => error.path === expected.reject);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Every way a row is refused for its structure — the closed set this table must cover. */
|
|
93
|
+
static get refusals(): string[] {
|
|
94
|
+
return Object.values(RowRefusal);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
[Symbol.iterator](): Iterator<Case> {
|
|
98
|
+
return this.all[Symbol.iterator]();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function enumerate(entity: SchemaView, valid: Record<string, unknown>): Case[] {
|
|
103
|
+
const fields = entity.getFields();
|
|
104
|
+
const judge = RowJudge.of(fields);
|
|
105
|
+
const cases: Case[] = [];
|
|
106
|
+
const withField = (name: string, value: unknown) => ({ ...valid, [name]: value });
|
|
107
|
+
|
|
108
|
+
cases.push({ why: 'a valid body', body: valid, patch: false, expect: 'accept' });
|
|
109
|
+
cases.push({ why: 'a key outside the contract', body: { ...valid, __unknown__: 'x' }, patch: false, expect: { reject: '__unknown__' } });
|
|
110
|
+
cases.push({ why: 'not an object at all', body: 'a string', patch: false, expect: { reject: '.' } });
|
|
111
|
+
|
|
112
|
+
for (const [name, field] of Object.entries(fields) as [string, Field][]) {
|
|
113
|
+
// A reference names a row that must exist; the caller supplied its id and we do not
|
|
114
|
+
// get to invent a second one, so the only case we can state about it is the bound one.
|
|
115
|
+
const isRef = Role.of(field).isReference;
|
|
116
|
+
|
|
117
|
+
if (judge.onAbsent(field) === null && name in valid) {
|
|
118
|
+
const body = { ...valid };
|
|
119
|
+
delete body[name];
|
|
120
|
+
cases.push({ why: `${name} absent`, body, patch: false, expect: { reject: name } });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (Boundary.of(field).readOnly) {
|
|
124
|
+
cases.push({ why: `${name} supplied although read-only`, body: withField(name, wrongTypeFor(field)), patch: false, expect: { reject: name } });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (Lifecycle.of(field).immutable && !Role.of(field).isPrimary) {
|
|
128
|
+
cases.push({ why: `${name} supplied on an update`, body: { [name]: valid[name] ?? wrongTypeFor(field) }, patch: true, expect: { reject: name } });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (name in valid && !isRef) {
|
|
132
|
+
cases.push({ why: `${name} of the wrong type`, body: withField(name, wrongTypeFor(field)), patch: false, expect: { reject: name } });
|
|
133
|
+
for (const { why, value } of outOfBoundsFor(field))
|
|
134
|
+
cases.push({ why: `${name} ${why}`, body: withField(name, value), patch: false, expect: { reject: name } });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return cases;
|
|
139
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { EXTENSION_AXES } from "../axis/Axis.js";
|
|
2
|
+
import { Anatomy } from "../axis/shape/Shape.js";
|
|
3
|
+
import type { Field } from "../fields/Field.js";
|
|
4
|
+
import { isObject } from "./ValueForm.js";
|
|
5
|
+
import type { ValidationError, ValidationResult } from "./result.js";
|
|
6
|
+
|
|
7
|
+
export class FieldJudge {
|
|
8
|
+
private constructor(private readonly declaration: unknown) {}
|
|
9
|
+
|
|
10
|
+
static of(declaration: unknown): FieldJudge {
|
|
11
|
+
return new FieldJudge(declaration);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get verdict(): ValidationResult<Field> {
|
|
15
|
+
const declaration = this.declaration;
|
|
16
|
+
|
|
17
|
+
if (!isObject(declaration)) {
|
|
18
|
+
return {
|
|
19
|
+
success: false,
|
|
20
|
+
errors: [
|
|
21
|
+
{
|
|
22
|
+
path: ".",
|
|
23
|
+
message: `Expected an object — got ${JSON.stringify(declaration)}`,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const errors: ValidationError[] = [];
|
|
30
|
+
|
|
31
|
+
if (!Anatomy.is(declaration.shape)) {
|
|
32
|
+
errors.push({
|
|
33
|
+
path: "shape",
|
|
34
|
+
message: `Every field states a shape — got ${JSON.stringify(declaration.shape)}`,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const axis of EXTENSION_AXES) {
|
|
39
|
+
const declared = declaration[axis.slot];
|
|
40
|
+
if (declared !== undefined) axis.judge(declared, errors);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (declaration.meta !== undefined) {
|
|
44
|
+
if (!isObject(declaration.meta)) {
|
|
45
|
+
errors.push({
|
|
46
|
+
path: "meta",
|
|
47
|
+
message: `Expected an object — got ${JSON.stringify(declaration.meta)}`,
|
|
48
|
+
});
|
|
49
|
+
} else if (
|
|
50
|
+
declaration.meta.description !== undefined &&
|
|
51
|
+
typeof declaration.meta.description !== "string"
|
|
52
|
+
) {
|
|
53
|
+
errors.push({ path: "meta.description", message: "Expected a string" });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return errors.length
|
|
58
|
+
? { success: false, errors }
|
|
59
|
+
: { success: true, data: declaration as unknown as Field };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Boundary } from '../axis/boundary/Boundary.js';
|
|
2
|
+
import { Lifecycle } from '../axis/lifecycle/Lifecycle.js';
|
|
3
|
+
import { Role } from '../axis/role/Role.js';
|
|
4
|
+
import type { Field, Fields } from '../fields/Field.js';
|
|
5
|
+
import type { ValidateOptions } from './options.js';
|
|
6
|
+
import type { ValidationError, ValidationResult } from './result.js';
|
|
7
|
+
import { ValueJudge } from './ValueJudge.js';
|
|
8
|
+
import { RowRefusal } from './RowRefusal.js';
|
|
9
|
+
|
|
10
|
+
export class RowJudge {
|
|
11
|
+
private constructor(
|
|
12
|
+
private readonly fields: Fields,
|
|
13
|
+
private readonly options: ValidateOptions,
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
static of(fields: Fields, options: ValidateOptions = {}): RowJudge {
|
|
17
|
+
return new RowJudge(fields, options);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
onAbsent(field: Field): 'skip' | 'empty-list' | null {
|
|
21
|
+
if (Boundary.of(field).readOnly) return 'skip';
|
|
22
|
+
if (!Lifecycle.of(field).requiredAtCreate) return 'skip';
|
|
23
|
+
if (Role.of(field).isCollection) return 'empty-list';
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
check(input: unknown): ValidationResult<Record<string, unknown>> {
|
|
28
|
+
if (typeof input !== 'object' || input === null) {
|
|
29
|
+
return { success: false, errors: [{ path: '.', message: RowRefusal.notAnObject }] };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const data = input as Record<string, unknown>;
|
|
33
|
+
const errors: ValidationError[] = [];
|
|
34
|
+
const out: Record<string, unknown> = {};
|
|
35
|
+
|
|
36
|
+
for (const key of Object.keys(data)) {
|
|
37
|
+
if (!(key in this.fields)) {
|
|
38
|
+
errors.push({ path: key, message: RowRefusal.unknownField });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const [key, field] of Object.entries(this.fields)) {
|
|
43
|
+
const path = key;
|
|
44
|
+
const value = data[key];
|
|
45
|
+
|
|
46
|
+
if (value === undefined) {
|
|
47
|
+
if (this.options.patch) continue;
|
|
48
|
+
const absence = this.onAbsent(field);
|
|
49
|
+
if (absence === null) {
|
|
50
|
+
errors.push({ path, message: RowRefusal.required });
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (absence === 'empty-list') out[key] = [];
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const boundary = Boundary.of(field);
|
|
58
|
+
if (boundary.readOnly) {
|
|
59
|
+
errors.push({ path, message: RowRefusal.readOnly });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (this.options.patch && Lifecycle.of(field).immutable) {
|
|
63
|
+
errors.push({ path, message: RowRefusal.immutable });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const checked = ValueJudge.of(field).check(value);
|
|
68
|
+
if ('error' in checked) {
|
|
69
|
+
errors.push({ path, message: checked.error });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (checked.value === null) {
|
|
73
|
+
out[key] = null;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const decoded = boundary.decode(checked.value);
|
|
77
|
+
if ('error' in decoded) errors.push({ path, message: decoded.error });
|
|
78
|
+
else out[key] = decoded.value;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (errors.length > 0) return { success: false, errors };
|
|
82
|
+
return { success: true, data: out };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every way a row is refused for its STRUCTURE — the closed set, stated once.
|
|
3
|
+
*
|
|
4
|
+
* A value refused by its own shape carries the engine's message and a decoder refused by
|
|
5
|
+
* user code carries that code's; neither is structural and neither belongs here. These
|
|
6
|
+
* five are `RowJudge`'s own, and a table of cases enumerates them instead of reading the
|
|
7
|
+
* text of a function — which a minifier rewrites without a word.
|
|
8
|
+
*/
|
|
9
|
+
export const RowRefusal = {
|
|
10
|
+
notAnObject: 'Expected an object',
|
|
11
|
+
unknownField: 'Unknown field',
|
|
12
|
+
required: 'Required',
|
|
13
|
+
readOnly: 'Read-only',
|
|
14
|
+
immutable: 'Immutable',
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
export type RowRefusal = (typeof RowRefusal)[keyof typeof RowRefusal];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const isObject = (value: unknown): value is Record<string, unknown> =>
|
|
2
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
|
+
|
|
4
|
+
export const oneOfTokens = <T extends readonly string[]>(
|
|
5
|
+
value: unknown,
|
|
6
|
+
tokens: T,
|
|
7
|
+
): value is T[number] => typeof value === 'string' && (tokens as readonly string[]).includes(value);
|