@fougere/schema 0.8.2-alpha.0 → 0.8.4-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Schema.d.ts +3 -6
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +9 -17
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaDefinition.d.ts +24 -6
- package/dist/SchemaDefinition.d.ts.map +1 -1
- package/dist/SchemaDefinition.js +76 -44
- package/dist/SchemaDefinition.js.map +1 -1
- package/dist/SchemaView.d.ts +1 -1
- package/dist/SchemaView.d.ts.map +1 -1
- package/dist/axis/Axis.d.ts +1 -1
- package/dist/axis/Axis.d.ts.map +1 -1
- package/dist/axis/boundary/Boundaries.d.ts +5 -0
- package/dist/axis/boundary/Boundaries.d.ts.map +1 -1
- package/dist/axis/boundary/Boundaries.js.map +1 -1
- package/dist/axis/boundary/Boundary.d.ts +5 -4
- package/dist/axis/boundary/Boundary.d.ts.map +1 -1
- package/dist/axis/boundary/Boundary.js +4 -5
- package/dist/axis/boundary/Boundary.js.map +1 -1
- package/dist/axis/boundary/BoundaryAxis.js +2 -2
- package/dist/axis/boundary/BoundaryAxis.js.map +1 -1
- package/dist/axis/lifecycle/Lifecycle.d.ts +5 -3
- package/dist/axis/lifecycle/Lifecycle.d.ts.map +1 -1
- package/dist/axis/lifecycle/Lifecycle.js +5 -1
- package/dist/axis/lifecycle/Lifecycle.js.map +1 -1
- package/dist/axis/lifecycle/LifecycleAxis.js +6 -6
- package/dist/axis/lifecycle/LifecycleAxis.js.map +1 -1
- package/dist/axis/lifecycle/apply.d.ts +6 -6
- package/dist/axis/lifecycle/apply.d.ts.map +1 -1
- package/dist/axis/lifecycle/apply.js +10 -9
- package/dist/axis/lifecycle/apply.js.map +1 -1
- package/dist/axis/role/Role.d.ts +8 -5
- package/dist/axis/role/Role.d.ts.map +1 -1
- package/dist/axis/role/Role.js +7 -0
- package/dist/axis/role/Role.js.map +1 -1
- package/dist/axis/role/RoleAxis.js +9 -9
- package/dist/axis/role/RoleAxis.js.map +1 -1
- package/dist/axis/shape/Shape.d.ts +14 -0
- package/dist/axis/shape/Shape.d.ts.map +1 -1
- package/dist/axis/shape/Shape.js +43 -34
- package/dist/axis/shape/Shape.js.map +1 -1
- package/dist/entity/EntityAdapterSet.d.ts +2 -2
- package/dist/entity/EntityAdapterSet.d.ts.map +1 -1
- package/dist/entity/EntityAdapterSet.js +2 -2
- package/dist/entity/EntityAdapterSet.js.map +1 -1
- package/dist/entity.d.ts +2 -2
- package/dist/entity.js +2 -2
- package/dist/field/Field.d.ts.map +1 -1
- package/dist/field/Field.js +2 -1
- package/dist/field/Field.js.map +1 -1
- package/dist/field/FieldSet.d.ts +4 -7
- package/dist/field/FieldSet.d.ts.map +1 -1
- package/dist/field/FieldSet.js +4 -15
- package/dist/field/FieldSet.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/validation.d.ts +32 -0
- package/dist/lib/validation.d.ts.map +1 -0
- package/dist/lib/validation.js +6 -0
- package/dist/lib/validation.js.map +1 -0
- package/dist/projection/Cases.d.ts +1 -1
- package/dist/projection/Cases.d.ts.map +1 -1
- package/dist/projection/Cases.js +11 -7
- package/dist/projection/Cases.js.map +1 -1
- package/dist/projection/Visibility.js +1 -1
- package/dist/projection/Visibility.js.map +1 -1
- package/dist/projection/card/Bundle.d.ts.map +1 -1
- package/dist/projection/card/Bundle.js +1 -4
- package/dist/projection/card/Bundle.js.map +1 -1
- package/dist/projection/card/Card.d.ts +7 -1
- package/dist/projection/card/Card.d.ts.map +1 -1
- package/dist/projection/card/Card.js +25 -133
- package/dist/projection/card/Card.js.map +1 -1
- package/dist/projection/card/Descriptor.d.ts +2 -2
- package/dist/projection/card/Descriptor.d.ts.map +1 -1
- package/dist/projection/card/admission.d.ts +12 -11
- package/dist/projection/card/admission.d.ts.map +1 -1
- package/dist/projection/card/admission.js +13 -11
- package/dist/projection/card/admission.js.map +1 -1
- package/dist/projection/card/diff.d.ts +7 -1
- package/dist/projection/card/diff.d.ts.map +1 -1
- package/dist/projection/card/diff.js +111 -1
- package/dist/projection/card/diff.js.map +1 -1
- package/dist/validator/FieldDeclarationValidator.d.ts +1 -1
- package/dist/validator/FieldDeclarationValidator.d.ts.map +1 -1
- package/dist/validator/FieldDeclarationValidator.js +4 -4
- package/dist/validator/FieldDeclarationValidator.js.map +1 -1
- package/dist/validator/FieldValueValidator.d.ts +6 -1
- package/dist/validator/FieldValueValidator.d.ts.map +1 -1
- package/dist/validator/FieldValueValidator.js +36 -7
- package/dist/validator/FieldValueValidator.js.map +1 -1
- package/dist/validator/InputValidator.d.ts +1 -1
- package/dist/validator/InputValidator.d.ts.map +1 -1
- package/dist/validator/InputValidator.js +8 -18
- package/dist/validator/InputValidator.js.map +1 -1
- package/dist/vocabulary/preset/created.d.ts +0 -5
- package/dist/vocabulary/preset/created.d.ts.map +1 -1
- package/dist/vocabulary/preset/created.js +0 -5
- package/dist/vocabulary/preset/created.js.map +1 -1
- package/dist/vocabulary/preset/email.d.ts +0 -5
- package/dist/vocabulary/preset/email.d.ts.map +1 -1
- package/dist/vocabulary/preset/email.js +0 -5
- package/dist/vocabulary/preset/email.js.map +1 -1
- package/dist/vocabulary/preset/updated.d.ts +0 -5
- package/dist/vocabulary/preset/updated.d.ts.map +1 -1
- package/dist/vocabulary/preset/updated.js +0 -5
- package/dist/vocabulary/preset/updated.js.map +1 -1
- package/dist/vocabulary/preset/url.d.ts +0 -5
- package/dist/vocabulary/preset/url.d.ts.map +1 -1
- package/dist/vocabulary/preset/url.js +0 -5
- package/dist/vocabulary/preset/url.js.map +1 -1
- package/dist/vocabulary/primitive/bool.d.ts +0 -5
- package/dist/vocabulary/primitive/bool.d.ts.map +1 -1
- package/dist/vocabulary/primitive/bool.js +0 -5
- package/dist/vocabulary/primitive/bool.js.map +1 -1
- package/dist/vocabulary/primitive/date.d.ts +3 -3
- package/dist/vocabulary/primitive/date.js +3 -3
- package/dist/vocabulary/primitive/json.js +3 -3
- package/dist/vocabulary/primitive/list.d.ts +3 -3
- package/dist/vocabulary/primitive/list.js +3 -3
- package/dist/vocabulary/primitive/many.d.ts +3 -3
- package/dist/vocabulary/primitive/many.js +3 -3
- package/dist/vocabulary/primitive/number.d.ts +0 -5
- package/dist/vocabulary/primitive/number.d.ts.map +1 -1
- package/dist/vocabulary/primitive/number.js +0 -5
- package/dist/vocabulary/primitive/number.js.map +1 -1
- package/dist/vocabulary/primitive/oneOf.js +3 -3
- package/dist/vocabulary/primitive/primary.js +3 -3
- package/dist/vocabulary/primitive/ref.d.ts +0 -5
- package/dist/vocabulary/primitive/ref.d.ts.map +1 -1
- package/dist/vocabulary/primitive/ref.js +0 -5
- package/dist/vocabulary/primitive/ref.js.map +1 -1
- package/dist/vocabulary/primitive/text.d.ts +0 -5
- package/dist/vocabulary/primitive/text.d.ts.map +1 -1
- package/dist/vocabulary/primitive/text.js +0 -5
- package/dist/vocabulary/primitive/text.js.map +1 -1
- package/dist/vocabulary/rule/immutable.d.ts +0 -5
- package/dist/vocabulary/rule/immutable.d.ts.map +1 -1
- package/dist/vocabulary/rule/immutable.js +0 -5
- package/dist/vocabulary/rule/immutable.js.map +1 -1
- package/dist/vocabulary/rule/indexed.d.ts +0 -5
- package/dist/vocabulary/rule/indexed.d.ts.map +1 -1
- package/dist/vocabulary/rule/indexed.js +0 -5
- package/dist/vocabulary/rule/indexed.js.map +1 -1
- package/dist/vocabulary/rule/nullable.d.ts +3 -3
- package/dist/vocabulary/rule/nullable.js +3 -3
- package/dist/vocabulary/rule/optional.d.ts +3 -3
- package/dist/vocabulary/rule/optional.js +3 -3
- package/dist/vocabulary/rule/readOnly.d.ts +3 -3
- package/dist/vocabulary/rule/readOnly.js +4 -4
- package/dist/vocabulary/rule/readOnly.js.map +1 -1
- package/dist/vocabulary/rule/unique.d.ts +0 -5
- package/dist/vocabulary/rule/unique.d.ts.map +1 -1
- package/dist/vocabulary/rule/unique.js +0 -5
- package/dist/vocabulary/rule/unique.js.map +1 -1
- package/dist/vocabulary/rule/writeOnly.d.ts +2 -2
- package/dist/vocabulary/rule/writeOnly.js +3 -3
- package/dist/vocabulary/rule/writeOnly.js.map +1 -1
- package/dist/vocabulary/vocabulary.d.ts +2 -2
- package/dist/vocabulary/vocabulary.js +5 -5
- package/package.json +1 -1
- package/src/Schema.ts +14 -20
- package/src/SchemaDefinition.ts +74 -48
- package/src/SchemaView.ts +1 -1
- package/src/axis/Axis.ts +1 -1
- package/src/axis/boundary/Boundaries.ts +5 -0
- package/src/axis/boundary/Boundary.ts +7 -9
- package/src/axis/boundary/BoundaryAxis.ts +2 -2
- package/src/axis/lifecycle/Lifecycle.ts +9 -4
- package/src/axis/lifecycle/LifecycleAxis.ts +7 -7
- package/src/axis/lifecycle/apply.ts +10 -9
- package/src/axis/role/Role.ts +14 -5
- package/src/axis/role/RoleAxis.ts +10 -10
- package/src/axis/shape/Shape.ts +61 -37
- package/src/entity/EntityAdapterSet.ts +2 -2
- package/src/entity.ts +2 -2
- package/src/field/Field.ts +2 -1
- package/src/field/FieldSet.ts +6 -20
- package/src/index.ts +2 -2
- package/src/lib/validation.ts +25 -0
- package/src/projection/Cases.ts +13 -8
- package/src/projection/Visibility.ts +1 -1
- package/src/projection/card/Bundle.ts +1 -4
- package/src/projection/card/Card.ts +26 -159
- package/src/projection/card/Descriptor.ts +2 -10
- package/src/projection/card/admission.ts +14 -12
- package/src/projection/card/diff.ts +142 -1
- package/src/validator/FieldDeclarationValidator.ts +5 -5
- package/src/validator/FieldValueValidator.ts +45 -12
- package/src/validator/InputValidator.ts +8 -18
- package/src/vocabulary/preset/created.ts +0 -5
- package/src/vocabulary/preset/email.ts +0 -5
- package/src/vocabulary/preset/updated.ts +0 -5
- package/src/vocabulary/preset/url.ts +0 -5
- package/src/vocabulary/primitive/bool.ts +0 -5
- package/src/vocabulary/primitive/date.ts +3 -3
- package/src/vocabulary/primitive/json.ts +3 -3
- package/src/vocabulary/primitive/list.ts +3 -3
- package/src/vocabulary/primitive/many.ts +3 -3
- package/src/vocabulary/primitive/number.ts +0 -5
- package/src/vocabulary/primitive/oneOf.ts +3 -3
- package/src/vocabulary/primitive/primary.ts +3 -3
- package/src/vocabulary/primitive/ref.ts +0 -5
- package/src/vocabulary/primitive/text.ts +0 -5
- package/src/vocabulary/rule/immutable.ts +0 -5
- package/src/vocabulary/rule/indexed.ts +0 -5
- package/src/vocabulary/rule/nullable.ts +3 -3
- package/src/vocabulary/rule/optional.ts +3 -3
- package/src/vocabulary/rule/readOnly.ts +4 -4
- package/src/vocabulary/rule/unique.ts +0 -5
- package/src/vocabulary/rule/writeOnly.ts +3 -3
- package/src/vocabulary/vocabulary.ts +5 -5
- package/dist/validation.d.ts +0 -17
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -2
- package/dist/validation.js.map +0 -1
- package/src/validation.ts +0 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unique.js","sourceRoot":"","sources":["../../../src/vocabulary/rule/unique.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C
|
|
1
|
+
{"version":3,"file":"unique.js","sourceRoot":"","sources":["../../../src/vocabulary/rule/unique.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,MAAM,CAAC,MAAM,MAAM,GAAqC,UAAU,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;IAClF,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;CACvB,CAAC,CAAC,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Field } from '../../field/Field.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* FR :
|
|
3
|
+
* Closes the way out, the dual of `readOnly`: the value is written and never sent back.
|
|
4
|
+
* FR : ferme la sortie, dual de `readOnly` : la valeur s'écrit et ne repart jamais.
|
|
5
5
|
* `writeOnly(text())` → `boundary.out` is `'closed'`
|
|
6
6
|
*/
|
|
7
7
|
export declare const writeOnly: <T>(field: Field<T>) => Field<T>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { vocabulary } from '../vocabulary.js';
|
|
2
2
|
import { Boundary } from '../../axis/boundary/Boundary.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* FR :
|
|
4
|
+
* Closes the way out, the dual of `readOnly`: the value is written and never sent back.
|
|
5
|
+
* FR : ferme la sortie, dual de `readOnly` : la valeur s'écrit et ne repart jamais.
|
|
6
6
|
* `writeOnly(text())` → `boundary.out` is `'closed'`
|
|
7
7
|
*/
|
|
8
8
|
export const writeOnly = vocabulary('writeOnly', (field) => ({
|
|
9
|
-
boundary: Boundary.declared(field).
|
|
9
|
+
boundary: Boundary.declared(field).declaring({ out: 'closed' }),
|
|
10
10
|
}));
|
|
11
11
|
//# sourceMappingURL=writeOnly.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writeOnly.js","sourceRoot":"","sources":["../../../src/vocabulary/rule/writeOnly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAG3D;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAqC,UAAU,CACnE,WAAW,EACX,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACV,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"writeOnly.js","sourceRoot":"","sources":["../../../src/vocabulary/rule/writeOnly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAG3D;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAqC,UAAU,CACnE,WAAW,EACX,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACV,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;CAChE,CAAC,CACH,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Field } from '../field/Field.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* FR :
|
|
3
|
+
* Builds a `rule/` word, which states members on the field it receives.
|
|
4
|
+
* FR : fabrique un mot de `rule/`, qui énonce des membres sur le champ reçu.
|
|
5
5
|
* `vocabulary('indexed', () => ({ role: { index: true } }))`
|
|
6
6
|
*/
|
|
7
7
|
export declare function vocabulary(name: string, states: (field: Field) => Partial<Field>): FieldWord;
|
|
@@ -2,17 +2,17 @@ import { EXTENSION_SLOTS } from '../axis/Axis.js';
|
|
|
2
2
|
import { dequal } from 'dequal';
|
|
3
3
|
const MEMBER_SLOTS = [...EXTENSION_SLOTS, 'meta'];
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
* FR :
|
|
5
|
+
* Builds a `rule/` word, which states members on the field it receives.
|
|
6
|
+
* FR : fabrique un mot de `rule/`, qui énonce des membres sur le champ reçu.
|
|
7
7
|
* `vocabulary('indexed', () => ({ role: { index: true } }))`
|
|
8
8
|
*/
|
|
9
9
|
export function vocabulary(name, states) {
|
|
10
10
|
return (field) => field.with(merge(name, field, states(field)));
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
* FR :
|
|
15
|
-
* `readOnly(writeOnly(text()))` → both
|
|
13
|
+
* Refuses two words stating one member differently, rather than letting the outer win.
|
|
14
|
+
* FR : refuse deux mots qui énoncent un même membre différemment.
|
|
15
|
+
* `readOnly(writeOnly(text()))` → both apply; `immutable(created())` → throws
|
|
16
16
|
*/
|
|
17
17
|
function merge(name, field, given) {
|
|
18
18
|
const merged = {};
|
package/package.json
CHANGED
package/src/Schema.ts
CHANGED
|
@@ -42,16 +42,6 @@ export class Schema {
|
|
|
42
42
|
/** The one place a schema holds what it is. The readings below are its projections. */
|
|
43
43
|
static definition: SchemaDefinition = SchemaDefinition.of({ fields: {} });
|
|
44
44
|
|
|
45
|
-
static get fields(): Fields {
|
|
46
|
-
return this.definition.fields;
|
|
47
|
-
}
|
|
48
|
-
/** An entry survives every derivation that kept its field: `Post.pick('body').adapters`. */
|
|
49
|
-
static get adapters(): EntityAdapters<Fields> {
|
|
50
|
-
return this.definition.adapterSet.adapters;
|
|
51
|
-
}
|
|
52
|
-
static get opts(): ValidateOptions {
|
|
53
|
-
return this.definition.opts;
|
|
54
|
-
}
|
|
55
45
|
static get derivation(): SchemaDerivation | undefined {
|
|
56
46
|
return this.definition.derivation;
|
|
57
47
|
}
|
|
@@ -75,26 +65,29 @@ export class Schema {
|
|
|
75
65
|
}
|
|
76
66
|
}
|
|
77
67
|
|
|
78
|
-
static getFields() {
|
|
79
|
-
return this.fields;
|
|
68
|
+
static getFields(): Fields {
|
|
69
|
+
return this.definition.fields;
|
|
80
70
|
}
|
|
81
|
-
|
|
82
|
-
|
|
71
|
+
/** An entry survives every derivation that kept its field: `Post.pick('body').getAdapters()`. */
|
|
72
|
+
static getAdapters(): EntityAdapters<Fields> {
|
|
73
|
+
return this.definition.adapterSet.adapters;
|
|
83
74
|
}
|
|
84
75
|
static getUnique(): CompositeUnique<Fields> | undefined {
|
|
85
76
|
return this.definition.constraints.unique;
|
|
86
77
|
}
|
|
87
|
-
static getOpts() {
|
|
88
|
-
return this.opts;
|
|
78
|
+
static getOpts(): ValidateOptions {
|
|
79
|
+
return this.definition.opts;
|
|
89
80
|
}
|
|
90
81
|
|
|
91
82
|
static validate(input: unknown) {
|
|
92
|
-
|
|
83
|
+
const { fields, opts } = this.definition;
|
|
84
|
+
|
|
85
|
+
return InputValidator.of(fields, opts).validate(input);
|
|
93
86
|
}
|
|
94
87
|
|
|
95
88
|
static from(data: Record<string, unknown>) {
|
|
96
89
|
const row: Record<string, unknown> = {};
|
|
97
|
-
for (const [key, field] of Object.entries(this.fields)) {
|
|
90
|
+
for (const [key, field] of Object.entries(this.definition.fields)) {
|
|
98
91
|
if (!Object.hasOwn(data, key)) continue;
|
|
99
92
|
const value = data[key];
|
|
100
93
|
if (value === null || value === undefined) {
|
|
@@ -108,7 +101,8 @@ export class Schema {
|
|
|
108
101
|
}
|
|
109
102
|
|
|
110
103
|
static get ['~standard'](): StandardSchemaV1.Props<Record<string, unknown>> {
|
|
111
|
-
const { fields, opts } = this;
|
|
104
|
+
const { fields, opts } = this.definition;
|
|
105
|
+
|
|
112
106
|
return {
|
|
113
107
|
version: 1,
|
|
114
108
|
vendor: 'fougere',
|
|
@@ -118,7 +112,7 @@ export class Schema {
|
|
|
118
112
|
return {
|
|
119
113
|
issues: verdict.errors.map((e) => ({
|
|
120
114
|
message: e.message,
|
|
121
|
-
path: e.path
|
|
115
|
+
path: e.path.length > 0 ? e.path.map((key) => ({ key })) : undefined,
|
|
122
116
|
})),
|
|
123
117
|
};
|
|
124
118
|
},
|
package/src/SchemaDefinition.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Fields } from './field/Field.js';
|
|
2
|
-
import { FieldSet
|
|
1
|
+
import { type FieldName, type Fields } from './field/Field.js';
|
|
2
|
+
import { FieldSet } from './field/FieldSet.js';
|
|
3
3
|
import {
|
|
4
4
|
type CompositeUnique,
|
|
5
5
|
type EntityDeclarations,
|
|
@@ -18,8 +18,40 @@ import type { SchemaView } from './SchemaView.js';
|
|
|
18
18
|
* and `opts` because the next rule spanning two fields — a check, an exclusion — is a
|
|
19
19
|
* member here, not an eighth positional argument in a list that has already lost one.
|
|
20
20
|
*/
|
|
21
|
-
export
|
|
21
|
+
export class SchemaConstraints<TFields extends Fields = Fields> {
|
|
22
22
|
readonly unique?: CompositeUnique<TFields>;
|
|
23
|
+
|
|
24
|
+
private constructor(unique?: CompositeUnique<TFields>) {
|
|
25
|
+
if (unique) this.unique = unique;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static readonly none = new SchemaConstraints();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The order is part of a group, so `['a','b']` and `['b','a']` are two constraints.
|
|
32
|
+
* FR : l'ordre fait partie du groupe : `['a','b']` et `['b','a']` sont deux contraintes.
|
|
33
|
+
* `SchemaConstraints.of([['a','b'], ['a','b']])` → `unique` holds one group
|
|
34
|
+
*/
|
|
35
|
+
static of<TFields extends Fields>(
|
|
36
|
+
groups: readonly (readonly FieldName<TFields>[])[],
|
|
37
|
+
): SchemaConstraints<TFields> {
|
|
38
|
+
const seen = new Map<string, readonly FieldName<TFields>[]>();
|
|
39
|
+
for (const group of groups) seen.set(JSON.stringify(group), group);
|
|
40
|
+
|
|
41
|
+
return new SchemaConstraints(seen.size ? [...seen.values()] : undefined);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** One member gone and the group is gone: it constrained a pair that no longer exists. */
|
|
45
|
+
renamed(transform: (key: string) => string | undefined): SchemaConstraints {
|
|
46
|
+
if (!this.unique) return SchemaConstraints.none;
|
|
47
|
+
const kept: string[][] = [];
|
|
48
|
+
for (const group of this.unique) {
|
|
49
|
+
const renamed = group.map(transform);
|
|
50
|
+
if (renamed.every((key): key is string => key !== undefined)) kept.push(renamed);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return SchemaConstraints.of(kept);
|
|
54
|
+
}
|
|
23
55
|
}
|
|
24
56
|
|
|
25
57
|
/**
|
|
@@ -72,6 +104,31 @@ export class SchemaDefinition {
|
|
|
72
104
|
this.constraints = state.constraints;
|
|
73
105
|
}
|
|
74
106
|
|
|
107
|
+
/**
|
|
108
|
+
* A schema cut from another one: it carries no previous names and holds no rows of its
|
|
109
|
+
* own, whatever else it keeps. `pick`, `partial`, `extend` and `compose` all land here.
|
|
110
|
+
*/
|
|
111
|
+
private static derived(state: Omit<SchemaState, 'previous' | 'anchored'>): SchemaDefinition {
|
|
112
|
+
return new SchemaDefinition({ ...state, previous: undefined, anchored: false });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The same schema, one member restated — `declares` and `anchor`, which cut nothing.
|
|
117
|
+
* What they do not name is kept, and that is the whole difference with `derived`.
|
|
118
|
+
*/
|
|
119
|
+
private restated(state: Partial<SchemaState>): SchemaDefinition {
|
|
120
|
+
return new SchemaDefinition({
|
|
121
|
+
fields: this.fields,
|
|
122
|
+
adapterSet: this.adapterSet,
|
|
123
|
+
opts: this.opts,
|
|
124
|
+
previous: this.previous,
|
|
125
|
+
derivation: this.derivation,
|
|
126
|
+
anchored: this.anchored,
|
|
127
|
+
constraints: this.constraints,
|
|
128
|
+
...state,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
75
132
|
/** Built complete or not at all — no static assigned on the side. */
|
|
76
133
|
static of(declaration: {
|
|
77
134
|
fields: Fields;
|
|
@@ -89,7 +146,7 @@ export class SchemaDefinition {
|
|
|
89
146
|
previous: declaration.previous,
|
|
90
147
|
derivation: declaration.derivation,
|
|
91
148
|
anchored: declaration.anchored ?? false,
|
|
92
|
-
constraints: declaration.constraints ??
|
|
149
|
+
constraints: declaration.constraints ?? SchemaConstraints.none,
|
|
93
150
|
});
|
|
94
151
|
}
|
|
95
152
|
|
|
@@ -101,31 +158,22 @@ export class SchemaDefinition {
|
|
|
101
158
|
]);
|
|
102
159
|
const declared = FieldSet.declaring(this.fields, declarations.unique);
|
|
103
160
|
|
|
104
|
-
return
|
|
161
|
+
return this.restated({
|
|
105
162
|
fields: declared.fields,
|
|
106
163
|
adapterSet: EntityAdapterSet.merged([
|
|
107
164
|
this.adapterSet,
|
|
108
165
|
EntityAdapterSet.of(declarations.adapters),
|
|
109
166
|
]),
|
|
110
|
-
opts: this.opts,
|
|
111
167
|
previous: declarations.previous ?? this.previous,
|
|
112
|
-
derivation: this.derivation,
|
|
113
|
-
anchored: this.anchored,
|
|
114
168
|
constraints:
|
|
115
|
-
declarations.unique === undefined
|
|
169
|
+
declarations.unique === undefined
|
|
170
|
+
? this.constraints
|
|
171
|
+
: SchemaConstraints.of(declared.groups),
|
|
116
172
|
});
|
|
117
173
|
}
|
|
118
174
|
|
|
119
175
|
anchoring(): SchemaDefinition {
|
|
120
|
-
return
|
|
121
|
-
fields: this.fields,
|
|
122
|
-
adapterSet: this.adapterSet,
|
|
123
|
-
opts: this.opts,
|
|
124
|
-
previous: this.previous,
|
|
125
|
-
derivation: this.derivation,
|
|
126
|
-
anchored: true,
|
|
127
|
-
constraints: this.constraints,
|
|
128
|
-
});
|
|
176
|
+
return this.restated({ anchored: true });
|
|
129
177
|
}
|
|
130
178
|
|
|
131
179
|
pick(keys: readonly string[], root: SchemaView): SchemaDefinition {
|
|
@@ -160,52 +208,32 @@ export class SchemaDefinition {
|
|
|
160
208
|
}
|
|
161
209
|
const nameOf = (key: string) => kept.get(key);
|
|
162
210
|
|
|
163
|
-
return
|
|
211
|
+
return SchemaDefinition.derived({
|
|
164
212
|
fields,
|
|
165
213
|
adapterSet: this.adapterSet.rename(nameOf),
|
|
166
214
|
opts: this.opts,
|
|
167
|
-
previous: undefined,
|
|
168
215
|
derivation: this.origin(root).rename(nameOf),
|
|
169
|
-
|
|
170
|
-
constraints: this.constraintsRenamed(nameOf),
|
|
216
|
+
constraints: this.constraints.renamed(nameOf),
|
|
171
217
|
});
|
|
172
218
|
}
|
|
173
219
|
|
|
174
|
-
/** One member gone and the group is gone: it constrained a pair that no longer exists. */
|
|
175
|
-
private constraintsRenamed(
|
|
176
|
-
transform: (key: string) => string | undefined,
|
|
177
|
-
): SchemaConstraints {
|
|
178
|
-
if (!this.constraints.unique) return this.constraints;
|
|
179
|
-
const kept: string[][] = [];
|
|
180
|
-
for (const group of this.constraints.unique) {
|
|
181
|
-
const renamed = group.map(transform);
|
|
182
|
-
if (renamed.every((key): key is string => key !== undefined)) kept.push(renamed);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return { unique: deduplicated(kept) as CompositeUnique<Fields> | undefined };
|
|
186
|
-
}
|
|
187
|
-
|
|
188
220
|
patched(root: SchemaView): SchemaDefinition {
|
|
189
|
-
return
|
|
221
|
+
return SchemaDefinition.derived({
|
|
190
222
|
fields: { ...this.fields },
|
|
191
223
|
adapterSet: this.adapterSet,
|
|
192
224
|
opts: { ...this.opts, patch: true },
|
|
193
|
-
previous: undefined,
|
|
194
225
|
derivation: this.origin(root),
|
|
195
|
-
anchored: false,
|
|
196
226
|
constraints: this.constraints,
|
|
197
227
|
});
|
|
198
228
|
}
|
|
199
229
|
|
|
200
230
|
/** An added field does not pretend to come from the root, which never declared it. */
|
|
201
231
|
extended(extra: Fields, root: SchemaView): SchemaDefinition {
|
|
202
|
-
return
|
|
232
|
+
return SchemaDefinition.derived({
|
|
203
233
|
fields: { ...this.fields, ...extra },
|
|
204
234
|
adapterSet: this.adapterSet,
|
|
205
235
|
opts: this.opts,
|
|
206
|
-
previous: undefined,
|
|
207
236
|
derivation: this.origin(root),
|
|
208
|
-
anchored: false,
|
|
209
237
|
constraints: this.constraints,
|
|
210
238
|
});
|
|
211
239
|
}
|
|
@@ -221,22 +249,20 @@ export class SchemaDefinition {
|
|
|
221
249
|
groups.push(...(view.getUnique() ?? []));
|
|
222
250
|
}
|
|
223
251
|
|
|
224
|
-
return
|
|
252
|
+
return SchemaDefinition.derived({
|
|
225
253
|
fields,
|
|
226
254
|
adapterSet: EntityAdapterSet.merged(
|
|
227
255
|
views.map((view) => EntityAdapterSet.of(view.getAdapters())),
|
|
228
256
|
),
|
|
229
257
|
opts,
|
|
230
|
-
previous: undefined,
|
|
231
258
|
derivation: undefined,
|
|
232
|
-
|
|
233
|
-
constraints: { unique: deduplicated(groups) as CompositeUnique<Fields> | undefined },
|
|
259
|
+
constraints: SchemaConstraints.of(groups),
|
|
234
260
|
});
|
|
235
261
|
}
|
|
236
262
|
|
|
237
263
|
/**
|
|
238
|
-
*
|
|
239
|
-
* FR :
|
|
264
|
+
* Refuses a gesture naming a field the schema does not carry, and lists what it does.
|
|
265
|
+
* FR : refuse un geste nommant un champ absent, et énumère ceux qui existent.
|
|
240
266
|
* `pick('titel')` → `pick(): unknown field \`titel\`. This schema carries id, title, body.`
|
|
241
267
|
*/
|
|
242
268
|
private assertKnown(operation: string, keys: readonly string[]): void {
|
package/src/SchemaView.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Field, Fields } from './field/Field.js';
|
|
2
2
|
import type { CompositeUnique, PreviousNames } from './entity/EntityDeclarations.js';
|
|
3
3
|
import type { EntityAdapters } from './entity/EntityAdapters.js';
|
|
4
|
-
import type { ValidationResult } from './validation.js';
|
|
4
|
+
import type { ValidationResult } from './lib/validation.js';
|
|
5
5
|
import type { ValidateOptions } from './validator/InputValidator.js';
|
|
6
6
|
import type { SchemaDerivation } from './SchemaDerivation.js';
|
|
7
7
|
|
package/src/axis/Axis.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { roleAxis } from './role/RoleAxis.js';
|
|
2
2
|
import { lifecycleAxis } from './lifecycle/LifecycleAxis.js';
|
|
3
3
|
import { boundaryAxis } from './boundary/BoundaryAxis.js';
|
|
4
|
-
import type { ValidationError } from '../validation.js';
|
|
4
|
+
import type { ValidationError } from '../lib/validation.js';
|
|
5
5
|
|
|
6
6
|
export interface Axis<Declared = unknown, Wire = unknown> {
|
|
7
7
|
readonly slot: 'role' | 'lifecycle' | 'boundary';
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { BoundaryRules } from './Boundary.js';
|
|
2
2
|
import { Registry } from '../../lib/Registry.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Wire to domain, and it must ANSWER a value it already produced: two doors decode — the
|
|
6
|
+
* client one on what arrives, `StorageGuard` on what a handler writes — so a decoder that
|
|
7
|
+
* halves cents halves them twice and stores a hundredth.
|
|
8
|
+
*/
|
|
4
9
|
export type Decoder = (value: unknown) => { value: unknown } | { error: string };
|
|
5
10
|
export type Encoder = (value: unknown) => unknown;
|
|
6
11
|
|
|
@@ -13,9 +13,9 @@ export type BoundaryRef = 'isoDate' | (string & {}) | BoundaryRules;
|
|
|
13
13
|
const identityDecoder: Decoder = (value) => ({ value });
|
|
14
14
|
const identityEncoder: Encoder = (value) => value;
|
|
15
15
|
|
|
16
|
-
export class Boundary
|
|
17
|
-
readonly in?: BoundaryRules['in'];
|
|
18
|
-
readonly out?: BoundaryRules['out'];
|
|
16
|
+
export class Boundary {
|
|
17
|
+
private readonly in?: BoundaryRules['in'];
|
|
18
|
+
private readonly out?: BoundaryRules['out'];
|
|
19
19
|
readonly decode: Decoder;
|
|
20
20
|
readonly encode: Encoder;
|
|
21
21
|
|
|
@@ -61,15 +61,13 @@ export class Boundary implements BoundaryRules {
|
|
|
61
61
|
|
|
62
62
|
/** `date-time` means a `Date` on both sides, without a word in the entity. */
|
|
63
63
|
static forShape(shape: Shape | undefined): Boundary {
|
|
64
|
-
|
|
65
|
-
if (base?.type === 'string' && base.format === 'date-time') {
|
|
66
|
-
return new Boundary(Boundaries.aliases.find('isoDate')!);
|
|
67
|
-
}
|
|
64
|
+
if (Shapes.typeOf(shape) === 'date') return new Boundary(Boundaries.aliases.find('isoDate')!);
|
|
68
65
|
return new Boundary();
|
|
69
66
|
}
|
|
70
67
|
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
/** The dual of `declared`: what `readOnly()` writes back on the field, not a judge. */
|
|
69
|
+
declaring(overrides: BoundaryRules): BoundaryRules {
|
|
70
|
+
return { in: overrides.in ?? this.in, out: overrides.out ?? this.out };
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
get readOnly(): boolean {
|
|
@@ -10,7 +10,7 @@ export const boundaryAxis: Axis<BoundaryRef, BoundaryRef> = {
|
|
|
10
10
|
if (typeof value === 'string') return;
|
|
11
11
|
if (!isObject(value)) {
|
|
12
12
|
errors.push({
|
|
13
|
-
path: 'boundary',
|
|
13
|
+
path: ['boundary'],
|
|
14
14
|
message: `Expected an alias name or { in, out } — got ${shown(value)}`,
|
|
15
15
|
});
|
|
16
16
|
return;
|
|
@@ -19,7 +19,7 @@ export const boundaryAxis: Axis<BoundaryRef, BoundaryRef> = {
|
|
|
19
19
|
const rule = value[side];
|
|
20
20
|
if (rule === undefined || rule === 'closed') continue;
|
|
21
21
|
if (!isObject(rule) || typeof rule[verb] !== 'string') {
|
|
22
|
-
errors.push({ path:
|
|
22
|
+
errors.push({ path: ['boundary', side], message: `Expected 'closed' or { ${verb}: <name> }` });
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
},
|
|
@@ -8,9 +8,9 @@ export interface LifecycleRules {
|
|
|
8
8
|
update?: (typeof UPDATE_TOKENS)[number];
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export class Lifecycle
|
|
12
|
-
readonly create?: LifecycleRules['create'];
|
|
13
|
-
readonly update?: LifecycleRules['update'];
|
|
11
|
+
export class Lifecycle {
|
|
12
|
+
private readonly create?: LifecycleRules['create'];
|
|
13
|
+
private readonly update?: LifecycleRules['update'];
|
|
14
14
|
|
|
15
15
|
private constructor(rules: LifecycleRules = {}) {
|
|
16
16
|
this.create = rules.create;
|
|
@@ -25,8 +25,13 @@ export class Lifecycle implements LifecycleRules {
|
|
|
25
25
|
return this.create === undefined;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/** The server fills it at create, so a client form never carries it. */
|
|
29
|
+
get stampedAtCreate(): boolean {
|
|
30
|
+
return this.create === 'now';
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
get stampedOnce(): boolean {
|
|
29
|
-
return this.
|
|
34
|
+
return this.stampedAtCreate && !this.stampedOnUpdate;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
get immutable(): boolean {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Axis } from '../Axis.js';
|
|
2
|
-
import type { ValidationError } from '../../validation.js';
|
|
2
|
+
import type { ValidationError } from '../../lib/validation.js';
|
|
3
3
|
import { admitWire, oneOfTokens } from '../../projection/card/admission.js';
|
|
4
4
|
import { isObject, shown } from '../../lib/utils.js';
|
|
5
5
|
import { CREATE_TOKENS, UPDATE_TOKENS, type LifecycleRules } from './Lifecycle.js';
|
|
@@ -9,13 +9,13 @@ export const lifecycleAxis: Axis<LifecycleRules, LifecycleRules> = {
|
|
|
9
9
|
|
|
10
10
|
validator(value, errors) {
|
|
11
11
|
if (!isObject(value)) {
|
|
12
|
-
errors.push({ path: 'lifecycle', message: `Expected an object — got ${shown(value)}` });
|
|
12
|
+
errors.push({ path: ['lifecycle'], message: `Expected an object — got ${shown(value)}` });
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
15
|
if (value.create !== undefined) validateCreate(value.create, errors);
|
|
16
16
|
if (value.update !== undefined && !oneOfTokens(value.update, UPDATE_TOKENS)) {
|
|
17
17
|
errors.push({
|
|
18
|
-
path: 'lifecycle
|
|
18
|
+
path: ['lifecycle', 'update'],
|
|
19
19
|
message: `Expected 'now' or 'forbidden' — got ${shown(value.update)}`,
|
|
20
20
|
});
|
|
21
21
|
}
|
|
@@ -29,8 +29,8 @@ export const lifecycleAxis: Axis<LifecycleRules, LifecycleRules> = {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
* FR :
|
|
32
|
+
* Judges the create rule, whose four legal forms are stated here and nowhere else.
|
|
33
|
+
* FR : juge la règle de création, dont les quatre formes légales sont énoncées ici seulement.
|
|
34
34
|
* `create: 3` → `Expected 'now', 'optional', { value } or { generate } — got 3`
|
|
35
35
|
*/
|
|
36
36
|
function validateCreate(rule: unknown, errors: ValidationError[]): void {
|
|
@@ -40,7 +40,7 @@ function validateCreate(rule: unknown, errors: ValidationError[]): void {
|
|
|
40
40
|
if ('generate' in rule) {
|
|
41
41
|
if (typeof rule.generate !== 'string') {
|
|
42
42
|
errors.push({
|
|
43
|
-
path: 'lifecycle
|
|
43
|
+
path: ['lifecycle', 'create', 'generate'],
|
|
44
44
|
message: `Expected a generator name — got ${shown(rule.generate)}`,
|
|
45
45
|
});
|
|
46
46
|
}
|
|
@@ -48,7 +48,7 @@ function validateCreate(rule: unknown, errors: ValidationError[]): void {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
errors.push({
|
|
51
|
-
path: 'lifecycle
|
|
51
|
+
path: ['lifecycle', 'create'],
|
|
52
52
|
message: `Expected 'now', 'optional', { value } or { generate } — got ${shown(rule)}`,
|
|
53
53
|
});
|
|
54
54
|
}
|
|
@@ -4,10 +4,9 @@ import { Clock } from './Clock.js';
|
|
|
4
4
|
import { type Field, type Fields } from '../../field/Field.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* FR :
|
|
9
|
-
* `applyCreate({ id: primary(), createdAt: created() }, { title: 'a' })`
|
|
10
|
-
* → `id` generated, `createdAt` stamped, `title` untouched
|
|
7
|
+
* Fills what the declaration leaves to the storage, so no handler stamps a date itself.
|
|
8
|
+
* FR : remplit ce que la déclaration laisse au storage : aucun handler n'estampe lui-même.
|
|
9
|
+
* `applyCreate({ id: primary(), createdAt: created() }, { title: 'a' })` → `id`, `createdAt`
|
|
11
10
|
*/
|
|
12
11
|
export function applyCreate(fields: Fields, input: Record<string, unknown>): Record<string, unknown> {
|
|
13
12
|
const values: Record<string, unknown> = { ...input };
|
|
@@ -17,7 +16,7 @@ export function applyCreate(fields: Fields, input: Record<string, unknown>): Rec
|
|
|
17
16
|
if (name in values) continue;
|
|
18
17
|
const rule = Lifecycle.of(field);
|
|
19
18
|
|
|
20
|
-
if (rule.
|
|
19
|
+
if (rule.stampedAtCreate) values[name] = new Date(instant);
|
|
21
20
|
else if (rule.literal) values[name] = freshValue(rule.literal.value);
|
|
22
21
|
else if (rule.generator) values[name] = Generators.resolve(rule.generator)();
|
|
23
22
|
}
|
|
@@ -26,8 +25,9 @@ export function applyCreate(fields: Fields, input: Record<string, unknown>): Rec
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
|
-
*
|
|
30
|
-
* FR : pour que deux lignes nées
|
|
28
|
+
* Clones a declared default, so two rows born of `create: { value: [] }` hold two arrays.
|
|
29
|
+
* FR : clone un défaut déclaré, pour que deux lignes nées de `create: { value: [] }`
|
|
30
|
+
* tiennent deux tableaux.
|
|
31
31
|
* `create: { value: [] }` → each instance gets its own array
|
|
32
32
|
*/
|
|
33
33
|
function freshValue(value: unknown): unknown {
|
|
@@ -36,8 +36,9 @@ function freshValue(value: unknown): unknown {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
40
|
-
* FR :
|
|
39
|
+
* The dual of `applyCreate` on a patch: only `update: 'now'` fields are touched.
|
|
40
|
+
* FR : le dual d'`applyCreate` sur une modification : seuls les champs `update: 'now'`
|
|
41
|
+
* sont touchés.
|
|
41
42
|
* `applyUpdate(fields, { title: 'b' })` → `updatedAt` added, nothing else
|
|
42
43
|
*/
|
|
43
44
|
export function applyUpdate(fields: Fields, patch: Record<string, unknown>): Record<string, unknown> {
|
package/src/axis/role/Role.ts
CHANGED
|
@@ -7,11 +7,11 @@ export interface RoleRules {
|
|
|
7
7
|
relation?: Relation;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export class Role
|
|
11
|
-
readonly primary?: boolean;
|
|
12
|
-
readonly index?: boolean;
|
|
13
|
-
readonly unique?: boolean;
|
|
14
|
-
readonly relation?: Relation;
|
|
10
|
+
export class Role {
|
|
11
|
+
private readonly primary?: boolean;
|
|
12
|
+
private readonly index?: boolean;
|
|
13
|
+
private readonly unique?: boolean;
|
|
14
|
+
private readonly relation?: Relation;
|
|
15
15
|
|
|
16
16
|
private constructor(rules: RoleRules = {}) {
|
|
17
17
|
this.primary = rules.primary;
|
|
@@ -45,8 +45,17 @@ export class Role implements RoleRules {
|
|
|
45
45
|
return this.relation?.kind === 'one';
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/** Either kind — what a CLI flag and a GraphQL selection both leave out. */
|
|
49
|
+
get isRelation(): boolean {
|
|
50
|
+
return this.relation !== undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
48
53
|
/** Calls `() => Post` so no caller has to. */
|
|
49
54
|
get target(): EntityConstructor | undefined {
|
|
50
55
|
return this.relation?.to();
|
|
51
56
|
}
|
|
57
|
+
|
|
58
|
+
get onDelete(): Relation['onDelete'] {
|
|
59
|
+
return this.relation?.onDelete;
|
|
60
|
+
}
|
|
52
61
|
}
|