@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,44 @@
|
|
|
1
|
+
import type { BoundaryRules } from './Boundary.js';
|
|
2
|
+
|
|
3
|
+
export type Decoder = (value: unknown) => { value: unknown } | { error: string };
|
|
4
|
+
export type Encoder = (value: unknown) => unknown;
|
|
5
|
+
|
|
6
|
+
export class Boundaries {
|
|
7
|
+
private static readonly decoders = new Map<string, Decoder>();
|
|
8
|
+
private static readonly encoders = new Map<string, Encoder>();
|
|
9
|
+
private static readonly aliases = new Map<string, BoundaryRules>();
|
|
10
|
+
|
|
11
|
+
static registerDecoder(name: string, fn: Decoder): void { this.decoders.set(name, fn); }
|
|
12
|
+
static registerEncoder(name: string, fn: Encoder): void { this.encoders.set(name, fn); }
|
|
13
|
+
|
|
14
|
+
static registerAlias(name: string, boundary: BoundaryRules): void { this.aliases.set(name, boundary); }
|
|
15
|
+
|
|
16
|
+
static alias(name: string): BoundaryRules | undefined { return this.aliases.get(name); }
|
|
17
|
+
|
|
18
|
+
static decoder(name: string): Decoder { return this.named(this.decoders, name, 'decoder'); }
|
|
19
|
+
static encoder(name: string): Encoder { return this.named(this.encoders, name, 'encoder'); }
|
|
20
|
+
|
|
21
|
+
private static named<T>(registry: Map<string, T>, name: string, kind: string): T {
|
|
22
|
+
const fn = registry.get(name);
|
|
23
|
+
if (!fn) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Unknown boundary ${kind}: '${name}'. Register it with ` +
|
|
26
|
+
`Boundaries.register${kind[0].toUpperCase()}${kind.slice(1)}('${name}', …).`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return fn;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
Boundaries.registerDecoder('isoDate', (value) => {
|
|
34
|
+
if (value instanceof Date) return { value };
|
|
35
|
+
if (typeof value === 'string') {
|
|
36
|
+
const d = new Date(value);
|
|
37
|
+
return Number.isNaN(d.getTime()) ? { error: 'Invalid date' } : { value: d };
|
|
38
|
+
}
|
|
39
|
+
return { error: 'Expected a date' };
|
|
40
|
+
});
|
|
41
|
+
Boundaries.registerEncoder('isoDate', (value) =>
|
|
42
|
+
value instanceof Date ? value.toISOString() : value,
|
|
43
|
+
);
|
|
44
|
+
Boundaries.registerAlias('isoDate', { in: { decode: 'isoDate' }, out: { encode: 'isoDate' } });
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Decoder, Encoder } from './Boundaries.js';
|
|
2
|
+
import { Boundaries } from './Boundaries.js';
|
|
3
|
+
import type { Field } from '../../fields/Field.js';
|
|
4
|
+
import { Anatomy, type Shape } from '../shape/Shape.js';
|
|
5
|
+
|
|
6
|
+
export interface BoundaryRules {
|
|
7
|
+
in?: 'closed' | { decode: string };
|
|
8
|
+
out?: 'closed' | { encode: string };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type BoundaryRef = 'isoDate' | (string & {}) | BoundaryRules;
|
|
12
|
+
|
|
13
|
+
const identityDecoder: Decoder = (value) => ({ value });
|
|
14
|
+
const identityEncoder: Encoder = (value) => value;
|
|
15
|
+
|
|
16
|
+
export class Boundary implements BoundaryRules {
|
|
17
|
+
readonly in?: BoundaryRules['in'];
|
|
18
|
+
readonly out?: BoundaryRules['out'];
|
|
19
|
+
readonly decode: Decoder;
|
|
20
|
+
readonly encode: Encoder;
|
|
21
|
+
|
|
22
|
+
private constructor(rules: BoundaryRules = {}, codecs?: { decode: Decoder; encode: Encoder }) {
|
|
23
|
+
this.in = rules.in;
|
|
24
|
+
this.out = rules.out;
|
|
25
|
+
this.decode = codecs?.decode ?? identityDecoder;
|
|
26
|
+
this.encode = codecs?.encode ?? identityEncoder;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static declared(field: Field): Boundary {
|
|
30
|
+
const ref = field.boundary;
|
|
31
|
+
if (ref === undefined) return new Boundary();
|
|
32
|
+
if (typeof ref !== 'string') return new Boundary(ref);
|
|
33
|
+
|
|
34
|
+
const alias = Boundaries.alias(ref);
|
|
35
|
+
if (!alias) throw new Error(`Unknown boundary alias: '${ref}'`);
|
|
36
|
+
return new Boundary(alias);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static of(field: Field): Boundary {
|
|
40
|
+
const declared = Boundary.declared(field);
|
|
41
|
+
const derived = Boundary.forShape(field.shape);
|
|
42
|
+
const rules: BoundaryRules = { in: declared.in ?? derived.in, out: declared.out ?? derived.out };
|
|
43
|
+
return new Boundary(rules, {
|
|
44
|
+
decode: typeof rules.in === 'object' ? Boundaries.decoder(rules.in.decode) : identityDecoder,
|
|
45
|
+
encode: typeof rules.out === 'object' ? Boundaries.encoder(rules.out.encode) : identityEncoder,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static forShape(shape: Shape | undefined): Boundary {
|
|
50
|
+
const base = Anatomy.of(shape).base;
|
|
51
|
+
if (base?.type === 'string' && base.format === 'date-time') {
|
|
52
|
+
return new Boundary(Boundaries.alias('isoDate')!);
|
|
53
|
+
}
|
|
54
|
+
return new Boundary();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
with(overrides: BoundaryRules): Boundary {
|
|
58
|
+
return new Boundary({ in: overrides.in ?? this.in, out: overrides.out ?? this.out });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get readOnly(): boolean {
|
|
62
|
+
return this.in === 'closed';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get writeOnly(): boolean {
|
|
66
|
+
return this.out === 'closed';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Axis } from '../Axis.js';
|
|
2
|
+
import { admitWire } from '../../card/admission.js';
|
|
3
|
+
import { isObject } from '../../judge/ValueForm.js';
|
|
4
|
+
import type { BoundaryRef } from './Boundary.js';
|
|
5
|
+
|
|
6
|
+
export const boundaryAxis: Axis<BoundaryRef, BoundaryRef> = {
|
|
7
|
+
slot: 'boundary',
|
|
8
|
+
|
|
9
|
+
judge(value, errors) {
|
|
10
|
+
if (typeof value === 'string') return;
|
|
11
|
+
if (!isObject(value)) {
|
|
12
|
+
errors.push({
|
|
13
|
+
path: 'boundary',
|
|
14
|
+
message: `Expected an alias name or { in, out } — got ${JSON.stringify(value)}`,
|
|
15
|
+
});
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
for (const [side, verb] of [['in', 'decode'], ['out', 'encode']] as const) {
|
|
19
|
+
const rule = value[side];
|
|
20
|
+
if (rule === undefined || rule === 'closed') continue;
|
|
21
|
+
if (!isObject(rule) || typeof rule[verb] !== 'string') {
|
|
22
|
+
errors.push({ path: `boundary.${side}`, message: `Expected 'closed' or { ${verb}: <name> }` });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
describe: (value) => value,
|
|
28
|
+
reconstruct: (wire) => {
|
|
29
|
+
admitWire(boundaryAxis.judge, wire, 'boundary');
|
|
30
|
+
return wire;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What "now" means, for the two functions that stamp it.
|
|
3
|
+
*
|
|
4
|
+
* The ONE place either of them reads the time, which is what makes it substitutable at
|
|
5
|
+
* all: `created()`, `updated()` and `create: 'now'` are realized through this and nowhere
|
|
6
|
+
* else, so a test that needs a stable instant sets it here instead of intercepting `Date`
|
|
7
|
+
* globally — where Rails had to build `travel_to` over the language.
|
|
8
|
+
*
|
|
9
|
+
* A value rather than a mock: nothing here is a channel, and a frozen clock is a fact
|
|
10
|
+
* about the run, not an interception of a call.
|
|
11
|
+
*/
|
|
12
|
+
export class Clock {
|
|
13
|
+
private static reading: () => number = Date.now;
|
|
14
|
+
|
|
15
|
+
static now(): number {
|
|
16
|
+
return this.reading();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Freeze the instant this clock reads. Returns the gesture that restores it. */
|
|
20
|
+
static freeze(at: number | Date): () => void {
|
|
21
|
+
const previous = this.reading;
|
|
22
|
+
const instant = at instanceof Date ? at.getTime() : at;
|
|
23
|
+
this.reading = () => instant;
|
|
24
|
+
return () => { this.reading = previous; };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createId } from '@paralleldrive/cuid2';
|
|
2
|
+
|
|
3
|
+
export type GeneratorRef = 'cuid2' | 'uuid' | 'nanoid' | (string & {});
|
|
4
|
+
|
|
5
|
+
const NANOID_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
|
6
|
+
|
|
7
|
+
function nanoid(): string {
|
|
8
|
+
const bytes = new Uint8Array(21);
|
|
9
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
10
|
+
return Array.from(bytes, (byte) => NANOID_ALPHABET[byte & 63]).join('');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Who answers a `generate` by name — one registry per process, like `Formats` and
|
|
15
|
+
* `Boundaries`.
|
|
16
|
+
*
|
|
17
|
+
* The three builtins are REGISTERED rather than switched on, which is what brings the
|
|
18
|
+
* refusal back inside: a registry whose unknown name is refused by a `default:` branch in
|
|
19
|
+
* its caller does not know what it holds, so it cannot say so.
|
|
20
|
+
*/
|
|
21
|
+
export class Generators {
|
|
22
|
+
private static readonly registry = new Map<string, () => string>([
|
|
23
|
+
['cuid2', createId],
|
|
24
|
+
['uuid', () => globalThis.crypto.randomUUID()],
|
|
25
|
+
['nanoid', nanoid],
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
static register(name: string, fn: () => string): void {
|
|
29
|
+
this.registry.set(name, fn);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The generator this name stands for, refused by name when nothing does. */
|
|
33
|
+
static resolve(ref: GeneratorRef): () => string {
|
|
34
|
+
const found = this.registry.get(ref);
|
|
35
|
+
if (found) return found;
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Unknown generator '${ref}' — register it with Generators.register('${ref}', fn). ` +
|
|
38
|
+
`This process answers ${[...this.registry.keys()].join(', ')}.`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Whether a name is answered, for a caller that must not throw to find out. */
|
|
43
|
+
static answers(ref: GeneratorRef): boolean {
|
|
44
|
+
return this.registry.has(ref);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { GeneratorRef } from './Generators.js';
|
|
2
|
+
|
|
3
|
+
export const CREATE_TOKENS = ['now', 'optional'] as const;
|
|
4
|
+
export const UPDATE_TOKENS = ['now', 'forbidden'] as const;
|
|
5
|
+
|
|
6
|
+
export interface LifecycleRules {
|
|
7
|
+
create?: { value: unknown } | { generate: GeneratorRef } | (typeof CREATE_TOKENS)[number];
|
|
8
|
+
update?: (typeof UPDATE_TOKENS)[number];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class Lifecycle implements LifecycleRules {
|
|
12
|
+
readonly create?: LifecycleRules['create'];
|
|
13
|
+
readonly update?: LifecycleRules['update'];
|
|
14
|
+
|
|
15
|
+
private constructor(rules: LifecycleRules = {}) {
|
|
16
|
+
this.create = rules.create;
|
|
17
|
+
this.update = rules.update;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static of(field: { lifecycle?: LifecycleRules }): Lifecycle {
|
|
21
|
+
return new Lifecycle(field.lifecycle);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get requiredAtCreate(): boolean {
|
|
25
|
+
return this.create === undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get stampedOnce(): boolean {
|
|
29
|
+
return this.create === 'now' && this.update !== 'now';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get immutable(): boolean {
|
|
33
|
+
return this.update === 'forbidden';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get stampedOnUpdate(): boolean {
|
|
37
|
+
return this.update === 'now';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get literal(): { value: unknown } | undefined {
|
|
41
|
+
const rule = this.create;
|
|
42
|
+
return typeof rule === 'object' && rule !== null && 'value' in rule
|
|
43
|
+
? { value: (rule as { value: unknown }).value }
|
|
44
|
+
: undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get generator(): GeneratorRef | undefined {
|
|
48
|
+
const rule = this.create;
|
|
49
|
+
return typeof rule === 'object' && rule !== null && 'generate' in rule
|
|
50
|
+
? (rule as { generate: GeneratorRef }).generate
|
|
51
|
+
: undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Axis } from '../Axis.js';
|
|
2
|
+
import type { ValidationError } from '../../judge/result.js';
|
|
3
|
+
import { admitWire } from '../../card/admission.js';
|
|
4
|
+
import { isObject, oneOfTokens } from '../../judge/ValueForm.js';
|
|
5
|
+
import { CREATE_TOKENS, UPDATE_TOKENS, type LifecycleRules } from './Lifecycle.js';
|
|
6
|
+
|
|
7
|
+
export const lifecycleAxis: Axis<LifecycleRules, LifecycleRules> = {
|
|
8
|
+
slot: 'lifecycle',
|
|
9
|
+
|
|
10
|
+
judge(value, errors) {
|
|
11
|
+
if (!isObject(value)) {
|
|
12
|
+
errors.push({ path: 'lifecycle', message: `Expected an object — got ${JSON.stringify(value)}` });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (value.create !== undefined) judgeCreate(value.create, errors);
|
|
16
|
+
if (value.update !== undefined && !oneOfTokens(value.update, UPDATE_TOKENS)) {
|
|
17
|
+
errors.push({
|
|
18
|
+
path: 'lifecycle.update',
|
|
19
|
+
message: `Expected 'now' or 'forbidden' — got ${JSON.stringify(value.update)}`,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
describe: (value) => value,
|
|
25
|
+
reconstruct: (wire) => {
|
|
26
|
+
admitWire(lifecycleAxis.judge, wire, 'lifecycle');
|
|
27
|
+
return wire;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function judgeCreate(rule: unknown, errors: ValidationError[]): void {
|
|
32
|
+
if (oneOfTokens(rule, CREATE_TOKENS)) return;
|
|
33
|
+
if (isObject(rule)) {
|
|
34
|
+
if ('value' in rule) return;
|
|
35
|
+
if ('generate' in rule) {
|
|
36
|
+
if (typeof rule.generate !== 'string') {
|
|
37
|
+
errors.push({
|
|
38
|
+
path: 'lifecycle.create.generate',
|
|
39
|
+
message: `Expected a generator name — got ${JSON.stringify(rule.generate)}`,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
errors.push({
|
|
46
|
+
path: 'lifecycle.create',
|
|
47
|
+
message: `Expected 'now', 'optional', { value } or { generate } — got ${JSON.stringify(rule)}`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Lifecycle } from './Lifecycle.js';
|
|
2
|
+
import { Generators } from './Generators.js';
|
|
3
|
+
import { Clock } from './Clock.js';
|
|
4
|
+
import { type Field, type Fields } from '../../fields/Field.js';
|
|
5
|
+
|
|
6
|
+
type Row = Record<string, unknown>;
|
|
7
|
+
|
|
8
|
+
export function applyCreate(fields: Fields, input: Row): Row {
|
|
9
|
+
const out: Row = { ...input };
|
|
10
|
+
const instant = Clock.now();
|
|
11
|
+
|
|
12
|
+
for (const [name, field] of Object.entries(fields) as [string, Field][]) {
|
|
13
|
+
if (name in out) continue;
|
|
14
|
+
const rule = Lifecycle.of(field);
|
|
15
|
+
|
|
16
|
+
if (rule.create === 'now') out[name] = new Date(instant);
|
|
17
|
+
else if (rule.literal) out[name] = freshValue(rule.literal.value);
|
|
18
|
+
else if (rule.generator) out[name] = Generators.resolve(rule.generator)();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function freshValue(value: unknown): unknown {
|
|
25
|
+
if (value === null || typeof value !== 'object') return value;
|
|
26
|
+
return structuredClone(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function applyUpdate(fields: Fields, patch: Row): Row {
|
|
30
|
+
const out: Row = { ...patch };
|
|
31
|
+
const instant = Clock.now();
|
|
32
|
+
|
|
33
|
+
for (const [name, field] of Object.entries(fields) as [string, Field][]) {
|
|
34
|
+
if (Lifecycle.of(field).stampedOnUpdate && !(name in out)) out[name] = new Date(instant);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type EntityConstructor = abstract new (...args: any[]) => any;
|
|
2
|
+
|
|
3
|
+
export const RELATION_KINDS = ['one', 'many'] as const;
|
|
4
|
+
export const ON_DELETE = ['cascade', 'restrict', 'set null'] as const;
|
|
5
|
+
|
|
6
|
+
export interface Relation {
|
|
7
|
+
to: () => EntityConstructor;
|
|
8
|
+
kind: (typeof RELATION_KINDS)[number];
|
|
9
|
+
onDelete?: (typeof ON_DELETE)[number];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* How a relation is built — the one place a target becomes a thunk.
|
|
14
|
+
*
|
|
15
|
+
* A target arrives as the class or as a function returning it, because a cycle between two
|
|
16
|
+
* entities can only be written the second way. Recognised by FORM, `getFields` on the
|
|
17
|
+
* value: a class answers it, a thunk does not.
|
|
18
|
+
*/
|
|
19
|
+
export const Relation = {
|
|
20
|
+
one(target: EntityConstructor | (() => EntityConstructor), cascade?: boolean): Relation {
|
|
21
|
+
return { to: thunk(target), kind: 'one', onDelete: cascade ? 'cascade' : undefined };
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
many(target: EntityConstructor | (() => EntityConstructor)): Relation {
|
|
25
|
+
return { to: thunk(target), kind: 'many' };
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function thunk<E extends EntityConstructor>(target: E | (() => E)): () => E {
|
|
30
|
+
return 'getFields' in target ? () => target as E : (target as () => E);
|
|
31
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { FieldGroup } from "../../constraint/FieldGroup.js";
|
|
2
|
+
import { Unique } from "../../constraint/Unique.js";
|
|
3
|
+
import type { EntityConstructor, Relation } from "./Relation.js";
|
|
4
|
+
|
|
5
|
+
export interface RoleRules {
|
|
6
|
+
primary?: boolean;
|
|
7
|
+
index?: boolean;
|
|
8
|
+
relation?: Relation;
|
|
9
|
+
rules?: readonly FieldGroup[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class Role implements RoleRules {
|
|
13
|
+
readonly primary?: boolean;
|
|
14
|
+
readonly index?: boolean;
|
|
15
|
+
readonly relation?: Relation;
|
|
16
|
+
readonly rules?: readonly FieldGroup[];
|
|
17
|
+
|
|
18
|
+
private constructor(declared: RoleRules = {}) {
|
|
19
|
+
this.primary = declared.primary;
|
|
20
|
+
this.index = declared.index;
|
|
21
|
+
this.relation = declared.relation;
|
|
22
|
+
this.rules = declared.rules;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static of(field: { role?: RoleRules }): Role {
|
|
26
|
+
return new Role(field.role);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A group written with no members means "this field": only the key it is declared under resolves it. */
|
|
30
|
+
static resolvedOn(role: RoleRules | undefined, key?: string): RoleRules | undefined {
|
|
31
|
+
let rules = FieldGroup.normalize(role?.rules, (members) => new Unique(members));
|
|
32
|
+
if (rules && key !== undefined && rules.some((group) => group.isSelf))
|
|
33
|
+
rules = rules.map((group) => group.resolvedOn(key));
|
|
34
|
+
return rules === role?.rules ? role : { ...role, rules };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get isPrimary(): boolean {
|
|
38
|
+
return this.primary === true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get isIndexed(): boolean {
|
|
42
|
+
return this.index === true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get isCollection(): boolean {
|
|
46
|
+
return this.relation?.kind === "many";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get isReference(): boolean {
|
|
50
|
+
return this.relation?.kind === "one";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get target(): EntityConstructor | undefined {
|
|
54
|
+
return this.relation?.to();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { Axis, Resolver } from '../Axis.js';
|
|
2
|
+
import { refuse } from '../../card/admission.js';
|
|
3
|
+
import { isObject, oneOfTokens } from '../../judge/ValueForm.js';
|
|
4
|
+
import type { ValidationError } from '../../judge/result.js';
|
|
5
|
+
import { ON_DELETE, RELATION_KINDS, type EntityConstructor, type Relation } from './Relation.js';
|
|
6
|
+
import { type RoleRules } from './Role.js';
|
|
7
|
+
import { FieldGroup } from '../../constraint/FieldGroup.js';
|
|
8
|
+
import { Unique } from '../../constraint/Unique.js';
|
|
9
|
+
import type { RoleDescriptor } from '../../card/Descriptor.js';
|
|
10
|
+
import { lowerFirst } from '../../utils.js';
|
|
11
|
+
|
|
12
|
+
export const roleAxis: Axis<RoleRules, RoleDescriptor> = {
|
|
13
|
+
slot: 'role',
|
|
14
|
+
|
|
15
|
+
judge(value, errors) {
|
|
16
|
+
if (!isObject(value)) {
|
|
17
|
+
errors.push({ path: 'role', message: `Expected an object — got ${JSON.stringify(value)}` });
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
for (const flag of ['primary', 'index'] as const) {
|
|
21
|
+
if (value[flag] !== undefined && typeof value[flag] !== 'boolean') {
|
|
22
|
+
errors.push({
|
|
23
|
+
path: `role.${flag}`,
|
|
24
|
+
message: `Expected a boolean — got ${JSON.stringify(value[flag])}`,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (value.rules !== undefined) {
|
|
29
|
+
const legal =
|
|
30
|
+
Array.isArray(value.rules) &&
|
|
31
|
+
value.rules.every(
|
|
32
|
+
(rule) =>
|
|
33
|
+
rule instanceof FieldGroup ||
|
|
34
|
+
(Array.isArray(rule) && rule.every((member) => typeof member === 'string')),
|
|
35
|
+
);
|
|
36
|
+
if (!legal) {
|
|
37
|
+
errors.push({
|
|
38
|
+
path: 'role.rules',
|
|
39
|
+
message: 'Expected field groups, or the member lists they denote — string[][]',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (value.relation !== undefined) judgeRelation(value.relation, errors);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
describe(role, key) {
|
|
47
|
+
const out: Mutable<RoleDescriptor> = {};
|
|
48
|
+
if (role.primary) out.primary = true;
|
|
49
|
+
const unique = (role.rules ?? []).filter((rule) => rule instanceof Unique);
|
|
50
|
+
if (unique.length) out.unique = unique.map((rule) => [...rule.resolvedOn(key).members]);
|
|
51
|
+
if (role.index) out.index = true;
|
|
52
|
+
if (role.relation) {
|
|
53
|
+
const target = role.relation.to() as { name?: string };
|
|
54
|
+
out.relation = {
|
|
55
|
+
to: lowerFirst(target.name ?? ''),
|
|
56
|
+
kind: role.relation.kind,
|
|
57
|
+
...(role.relation.onDelete ? { onDelete: role.relation.onDelete } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return Object.keys(out).length ? out : undefined;
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
reconstruct(wire, resolve?: Resolver) {
|
|
64
|
+
const out: RoleRules = {};
|
|
65
|
+
if (wire.primary) out.primary = true;
|
|
66
|
+
if (wire.unique?.length) out.rules = wire.unique.map((group) => new Unique(group));
|
|
67
|
+
if (wire.index) out.index = true;
|
|
68
|
+
if (wire.relation) {
|
|
69
|
+
// `judge` cannot serve here: it demands a thunk where a card carries a name.
|
|
70
|
+
if (!oneOfTokens(wire.relation.kind, RELATION_KINDS)) {
|
|
71
|
+
refuse(
|
|
72
|
+
`role.relation.kind is ${JSON.stringify(wire.relation.kind)}`,
|
|
73
|
+
`Expected one of ${RELATION_KINDS.join(', ')}.`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
if (wire.relation.onDelete !== undefined && !oneOfTokens(wire.relation.onDelete, ON_DELETE)) {
|
|
77
|
+
refuse(
|
|
78
|
+
`role.relation.onDelete is ${JSON.stringify(wire.relation.onDelete)}`,
|
|
79
|
+
`Expected one of ${ON_DELETE.join(', ')}.`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const name = wire.relation.to;
|
|
83
|
+
out.relation = {
|
|
84
|
+
to: () => (resolve?.(name) ?? ({ name } as unknown)) as EntityConstructor,
|
|
85
|
+
kind: wire.relation.kind as Relation['kind'],
|
|
86
|
+
...(wire.relation.onDelete ? { onDelete: wire.relation.onDelete as Relation['onDelete'] } : {}),
|
|
87
|
+
} as RoleRules['relation'];
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function judgeRelation(relation: unknown, errors: ValidationError[]): void {
|
|
94
|
+
if (!isObject(relation)) {
|
|
95
|
+
errors.push({ path: 'role.relation', message: `Expected an object — got ${JSON.stringify(relation)}` });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!oneOfTokens(relation.kind, RELATION_KINDS)) {
|
|
99
|
+
errors.push({
|
|
100
|
+
path: 'role.relation.kind',
|
|
101
|
+
message: `Expected 'one' or 'many' — got ${JSON.stringify(relation.kind)}`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (typeof relation.to !== 'function') {
|
|
105
|
+
errors.push({ path: 'role.relation.to', message: 'Expected a thunk returning the target entity' });
|
|
106
|
+
}
|
|
107
|
+
if (relation.onDelete !== undefined && !oneOfTokens(relation.onDelete, ON_DELETE)) {
|
|
108
|
+
errors.push({
|
|
109
|
+
path: 'role.relation.onDelete',
|
|
110
|
+
message: `Expected 'cascade', 'restrict' or 'set null' — got ${JSON.stringify(relation.onDelete)}`,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type StringFormat =
|
|
2
|
+
| 'date-time'
|
|
3
|
+
| 'date'
|
|
4
|
+
| 'time'
|
|
5
|
+
| 'email'
|
|
6
|
+
| 'uuid'
|
|
7
|
+
| 'uri'
|
|
8
|
+
| (string & {});
|
|
9
|
+
|
|
10
|
+
export type FormatPredicate = (value: string) => boolean;
|
|
11
|
+
|
|
12
|
+
export class Formats {
|
|
13
|
+
private static readonly registry = new Map<string, FormatPredicate>();
|
|
14
|
+
|
|
15
|
+
static register(name: string, predicate: FormatPredicate): void {
|
|
16
|
+
this.registry.set(name, predicate);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static resolve(name: string): FormatPredicate | undefined {
|
|
20
|
+
return this.registry.get(name);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { JSONSchema7 } from 'json-schema';
|
|
2
|
+
import type { StringFormat } from './Formats.js';
|
|
3
|
+
|
|
4
|
+
type Nullably<T extends string> = T | readonly [T, 'null'];
|
|
5
|
+
|
|
6
|
+
interface StringConstraints { minLength?: number; maxLength?: number; pattern?: string; enum?: readonly (string | null)[]; format?: StringFormat }
|
|
7
|
+
interface NumericConstraints { minimum?: number; maximum?: number }
|
|
8
|
+
interface ArrayConstraints { items?: Shape; minItems?: number; maxItems?: number }
|
|
9
|
+
interface ObjectConstraints { properties?: Record<string, unknown>; required?: readonly string[] }
|
|
10
|
+
|
|
11
|
+
export type Shape =
|
|
12
|
+
| ({ type: Nullably<'string'> } & StringConstraints)
|
|
13
|
+
| ({ type: Nullably<'number'> | Nullably<'integer'> } & NumericConstraints)
|
|
14
|
+
| { type: Nullably<'boolean'> }
|
|
15
|
+
| ({ type: Nullably<'array'> } & ArrayConstraints)
|
|
16
|
+
| ({ type: Nullably<'object'> } & ObjectConstraints);
|
|
17
|
+
|
|
18
|
+
const SHAPE_TYPES = ['string', 'number', 'integer', 'boolean', 'array', 'object'] as const;
|
|
19
|
+
|
|
20
|
+
function isShapeImpl(value: unknown): value is Shape {
|
|
21
|
+
if (typeof value !== 'object' || value === null) return false;
|
|
22
|
+
const declared = (value as Shape).type;
|
|
23
|
+
const names = Array.isArray(declared) ? declared : [declared];
|
|
24
|
+
return (
|
|
25
|
+
names.some((name) => (SHAPE_TYPES as readonly unknown[]).includes(name)) &&
|
|
26
|
+
names.every((name) => name === 'null' || (SHAPE_TYPES as readonly unknown[]).includes(name))
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type BaseShape =
|
|
31
|
+
| ({ type: 'string' } & StringConstraints)
|
|
32
|
+
| ({ type: 'number' | 'integer' } & NumericConstraints)
|
|
33
|
+
| { type: 'boolean' }
|
|
34
|
+
| ({ type: 'array' } & ArrayConstraints)
|
|
35
|
+
| ({ type: 'object' } & ObjectConstraints);
|
|
36
|
+
|
|
37
|
+
function nullableShapeImpl(shape: Shape): Shape {
|
|
38
|
+
if (Array.isArray(shape.type)) return shape;
|
|
39
|
+
const out = { ...shape, type: [shape.type, 'null'] } as unknown as Shape;
|
|
40
|
+
if ('enum' in out && out.enum && !out.enum.includes(null)) {
|
|
41
|
+
(out as { enum: readonly (string | null)[] }).enum = [...out.enum, null];
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface ShapeAnatomy {
|
|
47
|
+
base?: BaseShape;
|
|
48
|
+
nullable: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class Anatomy {
|
|
52
|
+
static is(value: unknown): value is Shape {
|
|
53
|
+
return isShapeImpl(value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static nullable(shape: Shape): Shape {
|
|
57
|
+
return nullableShapeImpl(shape);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private static readonly cache = new WeakMap<object, ShapeAnatomy>();
|
|
61
|
+
private static readonly none: ShapeAnatomy = { base: undefined, nullable: false };
|
|
62
|
+
|
|
63
|
+
static of(shape?: Shape): ShapeAnatomy {
|
|
64
|
+
if (!shape) return this.none;
|
|
65
|
+
let a = this.cache.get(shape);
|
|
66
|
+
if (!a) {
|
|
67
|
+
if (Array.isArray(shape.type)) {
|
|
68
|
+
const baseType = shape.type.find((t) => t !== 'null');
|
|
69
|
+
const base = { ...shape, type: baseType } as BaseShape;
|
|
70
|
+
if ('enum' in base && base.enum) {
|
|
71
|
+
(base as { enum: readonly (string | null)[] }).enum = base.enum.filter((v) => v !== null);
|
|
72
|
+
}
|
|
73
|
+
a = { base, nullable: true };
|
|
74
|
+
} else {
|
|
75
|
+
a = { base: shape as BaseShape, nullable: false };
|
|
76
|
+
}
|
|
77
|
+
this.cache.set(shape, a);
|
|
78
|
+
}
|
|
79
|
+
return a;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static isNullable(shape?: Shape): boolean {
|
|
83
|
+
return this.of(shape).nullable;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type Assert<T extends true> = T;
|
|
88
|
+
type ShapeKeys<T> = T extends unknown ? keyof T : never;
|
|
89
|
+
type _ShapeConformsToJsonSchema = Assert<
|
|
90
|
+
[Exclude<ShapeKeys<Shape>, keyof JSONSchema7>] extends [never] ? true : false
|
|
91
|
+
>;
|