@@ -7,137 +7,61 @@ import {
7
7
Predicate ,
8
8
} from './logic_node' ;
9
9
10
- /**
11
- * Container for all the different types of logic that can be applied to a field
12
- * (disabled, hidden, errors, etc.)
13
- */
14
- export class Logic {
15
- readonly hidden : BooleanOrLogic ;
16
- readonly disabled : BooleanOrLogic ;
17
- readonly errors : ArrayMergeLogic < FormError > ;
18
- private readonly metadata = new Map < MetadataKey < unknown > , AbstractLogic < unknown > > ( ) ;
19
-
20
- constructor ( private predicates : ReadonlyArray < Predicate > ) {
21
- this . hidden = new BooleanOrLogic ( predicates ) ;
22
- this . disabled = new BooleanOrLogic ( predicates ) ;
23
- this . errors = new ArrayMergeLogic < FormError > ( predicates ) ;
24
- }
25
-
26
- getMetadata < T > ( key : MetadataKey < T > ) : AbstractLogic < T > {
27
- if ( ! this . metadata . has ( key as MetadataKey < unknown > ) ) {
28
- this . metadata . set ( key as MetadataKey < unknown > , new MetadataMergeLogic ( this . predicates , key ) ) ;
29
- }
30
- return this . metadata . get ( key as MetadataKey < unknown > ) ! as AbstractLogic < T > ;
31
- }
32
-
33
- readMetadata < T > ( key : MetadataKey < T > , arg : FieldContext < unknown > ) : T {
34
- if ( this . metadata . has ( key as MetadataKey < unknown > ) ) {
35
- return this . metadata . get ( key as MetadataKey < unknown > ) ! . compute ( arg ) as T ;
36
- } else {
37
- return key . defaultValue ;
38
- }
39
- }
40
-
41
- getMetadataKeys ( ) {
42
- return this . metadata . keys ( ) ;
43
- }
44
- }
45
-
46
- /**
47
- * A tree structure of `Logic` corresponding to a tree of fields.
48
- */
49
- export class LogicNode {
50
- readonly logic : Logic ;
51
-
52
- constructor ( private builder : LogicNodeBuilder | undefined ) {
53
- this . logic = builder ? createLogic ( builder ) : new Logic ( [ ] ) ;
54
- }
55
-
56
- getChild ( key : PropertyKey ) : LogicNode {
57
- // The logic for a particular child may be spread across multiple builders. We lazily combine
58
- // this logic at the time the child logic node is requested to be created.
59
- const childBuilders = this . builder ? getAllChildren ( this . builder , key ) : [ ] ;
60
- if ( childBuilders . length <= 1 ) {
61
- return new LogicNode ( childBuilders [ 0 ] ) ;
62
- } else {
63
- // If there are multiple child builders, combine them all into one we ca pass to the new logic
64
- // node.
65
- const combined = LogicNodeBuilder . newRoot ( ) ;
66
- for ( const child of childBuilders ) {
67
- combined . mergeIn ( child ) ;
68
- }
69
- return new LogicNode ( combined ) ;
70
- }
71
- }
72
- }
73
-
74
- /**
75
- * A builder for `LogicNode`, which itself is a tree.
76
- */
77
- export abstract class LogicNodeBuilder {
78
- protected constructor ( readonly predicates : ReadonlyArray < Predicate > ) { }
10
+ abstract class AbstractLogicNodeBuilder {
11
+ constructor ( readonly predicates : ReadonlyArray < Predicate > ) { }
79
12
80
13
abstract addHiddenRule ( logic : LogicFn < unknown , boolean > ) : void ;
81
14
abstract addDisabledRule ( logic : LogicFn < unknown , boolean > ) : void ;
82
15
abstract addErrorRule ( logic : LogicFn < unknown , ValidationResult > ) : void ;
83
16
abstract addMetadataRule < T > ( key : MetadataKey < T > , logic : LogicFn < unknown , T > ) : void ;
84
- abstract getChild ( key : PropertyKey ) : MergableLogicNodeBuilder ;
85
- abstract predicate ( predicates : ReadonlyArray < Predicate > ) : LogicNodeBuilder ;
17
+ abstract getChild ( key : PropertyKey ) : LogicNodeBuilder ;
18
+ abstract predicate ( predicates : ReadonlyArray < Predicate > ) : AbstractLogicNodeBuilder ;
86
19
87
20
build ( ) : LogicNode {
88
21
return new LogicNode ( this ) ;
89
22
}
90
-
91
- static newRoot ( ) : MergableLogicNodeBuilder {
92
- return new CompositeLogicNodeBuilder ( [ ] ) ;
93
- }
94
23
}
95
24
96
25
/**
97
- * A subclass of `LogicNodeBuilder` that supports merging with other logic trees .
26
+ * A builder for `LogicNode`. Used to add logic to the final `LogicNode` tree .
98
27
*/
99
- export interface MergableLogicNodeBuilder extends LogicNodeBuilder {
100
- mergeIn ( other : MergableLogicNodeBuilder , predicate ?: Predicate ) : void ;
101
- predicate ( predicates : ReadonlyArray < Predicate > ) : MergableLogicNodeBuilder ;
102
- }
28
+ export class LogicNodeBuilder extends AbstractLogicNodeBuilder {
29
+ private current : NonMergableLogicNodeBuilder | undefined ;
30
+ readonly all : AbstractLogicNodeBuilder [ ] = [ ] ;
103
31
104
- /**
105
- * A `MergableLogicNodeBuilder` that delegates to its "current" builder and creates a new current
106
- * builder after another builder tree is merged in.
107
- */
108
- class CompositeLogicNodeBuilder extends LogicNodeBuilder implements MergableLogicNodeBuilder {
109
- private current : SimpleLogicNodeBuilder | undefined ;
110
- readonly all : LogicNodeBuilder [ ] = [ ] ;
32
+ constructor ( predicates : ReadonlyArray < Predicate > ) {
33
+ super ( predicates ) ;
34
+ }
111
35
112
- override addHiddenRule ( logic : LogicFn < unknown , boolean > ) : void {
36
+ addHiddenRule ( logic : LogicFn < unknown , boolean > ) : void {
113
37
this . getCurrent ( ) . addHiddenRule ( logic ) ;
114
38
}
115
39
116
- override addDisabledRule ( logic : LogicFn < unknown , boolean > ) : void {
40
+ addDisabledRule ( logic : LogicFn < unknown , boolean > ) : void {
117
41
this . getCurrent ( ) . addDisabledRule ( logic ) ;
118
42
}
119
43
120
- override addErrorRule ( logic : LogicFn < unknown , FormError > ) : void {
44
+ addErrorRule ( logic : LogicFn < unknown , ValidationResult > ) : void {
121
45
this . getCurrent ( ) . addErrorRule ( logic ) ;
122
46
}
123
47
124
- override addMetadataRule < T > ( key : MetadataKey < T > , logic : LogicFn < unknown , T > ) : void {
48
+ addMetadataRule < T > ( key : MetadataKey < T > , logic : LogicFn < unknown , T > ) : void {
125
49
this . getCurrent ( ) . addMetadataRule ( key , logic ) ;
126
50
}
127
51
128
- override getChild ( key : PropertyKey ) : MergableLogicNodeBuilder {
52
+ getChild ( key : PropertyKey ) : LogicNodeBuilder {
129
53
return this . getCurrent ( ) . getChild ( key ) ;
130
54
}
131
55
132
- override predicate ( predicates : ReadonlyArray < Predicate > ) {
56
+ predicate ( predicates : ReadonlyArray < Predicate > ) {
133
57
const newPredicates = [ ...this . predicates , ...predicates ] ;
134
- const clone = new CompositeLogicNodeBuilder ( newPredicates ) ;
58
+ const clone = new LogicNodeBuilder ( newPredicates ) ;
135
59
clone . all . push ( ...this . all . map ( ( b ) => b . predicate ( newPredicates ) ) ) ;
136
60
clone . current = this . current ?. predicate ( newPredicates ) ;
137
61
return clone ;
138
62
}
139
63
140
- mergeIn ( other : MergableLogicNodeBuilder , predicate ?: Predicate ) : void {
64
+ mergeIn ( other : LogicNodeBuilder , predicate ?: Predicate ) : void {
141
65
// Add the other builder to our collection, we'll defer the actual merging of the logic until
142
66
// the logic node is requested to be created. In order to preserve the original ordering of the
143
67
// rules, we close off the current builder to any further edits. If additional logic is added,
@@ -154,28 +78,29 @@ class CompositeLogicNodeBuilder extends LogicNodeBuilder implements MergableLogi
154
78
this . current = undefined ;
155
79
}
156
80
157
- private getCurrent ( ) : SimpleLogicNodeBuilder {
81
+ private getCurrent ( ) : NonMergableLogicNodeBuilder {
158
82
// All rules added to this builder get added on to the current builder. If there is no current
159
83
// builder, a new one is created. In order to preserve the original ordering of the rules, we
160
84
// clear the current builder whenever a separate builder tree is merged in.
161
85
if ( this . current === undefined ) {
162
- this . current = new SimpleLogicNodeBuilder ( this . predicates ) ;
86
+ this . current = new NonMergableLogicNodeBuilder ( this . predicates ) ;
163
87
this . all . push ( this . current ) ;
164
88
}
165
89
return this . current ;
166
90
}
91
+
92
+ static newRoot ( ) : LogicNodeBuilder {
93
+ return new LogicNodeBuilder ( [ ] ) ;
94
+ }
167
95
}
168
96
169
97
/**
170
- * A `LogicNodeBuilder` that directly adds logic to its backing `Logic` instance.
171
- *
172
- * The user should not be given a reference to this class, it is used internally to keep track of
173
- * concrete tree chunks within the `CompositeLogicNodeBuilder`. When handing a `LogicNodeBuilder`
174
- * to the user it should always be a `CompositeLogicNodeBuilder`.
98
+ * A type of `AbstractLogicNodeBuilder` used internally by the `LogicNodeBuilder` to record "pure"
99
+ * chunks of logic that do not require merging in other builders.
175
100
*/
176
- class SimpleLogicNodeBuilder extends LogicNodeBuilder {
101
+ class NonMergableLogicNodeBuilder extends AbstractLogicNodeBuilder {
177
102
logic = new Logic ( [ ] ) ;
178
- readonly children = new Map < PropertyKey , MergableLogicNodeBuilder > ( ) ;
103
+ readonly children = new Map < PropertyKey , LogicNodeBuilder > ( ) ;
179
104
180
105
override addHiddenRule ( logic : LogicFn < unknown , boolean > ) : void {
181
106
this . logic . hidden . push ( logic ) ;
@@ -195,31 +120,94 @@ class SimpleLogicNodeBuilder extends LogicNodeBuilder {
195
120
196
121
override predicate ( predicates : ReadonlyArray < Predicate > ) {
197
122
const newPredicates = [ ...this . predicates , ...predicates ] ;
198
- const clone = new SimpleLogicNodeBuilder ( newPredicates ) ;
123
+ const clone = new NonMergableLogicNodeBuilder ( newPredicates ) ;
199
124
for ( const [ prop , child ] of this . children ) {
200
125
clone . children . set ( prop , child . predicate ( newPredicates ) ) ;
201
126
}
202
127
clone . logic = this . logic ;
203
128
return clone ;
204
129
}
205
130
206
- override getChild ( key : PropertyKey ) : MergableLogicNodeBuilder {
207
- // We always create a `CompositeLogicNodeBuilder` for children since someone may call `apply` on
208
- // the child.
131
+ override getChild ( key : PropertyKey ) : LogicNodeBuilder {
209
132
if ( ! this . children . has ( key ) ) {
210
- this . children . set ( key , new CompositeLogicNodeBuilder ( this . predicates ) ) ;
133
+ this . children . set ( key , new LogicNodeBuilder ( this . predicates ) ) ;
211
134
}
212
135
return this . children . get ( key ) ! ;
213
136
}
214
137
}
215
138
139
+ /**
140
+ * Container for all the different types of logic that can be applied to a field
141
+ * (disabled, hidden, errors, etc.)
142
+ */
143
+ export class Logic {
144
+ readonly hidden : BooleanOrLogic ;
145
+ readonly disabled : BooleanOrLogic ;
146
+ readonly errors : ArrayMergeLogic < FormError > ;
147
+ private readonly metadata = new Map < MetadataKey < unknown > , AbstractLogic < unknown > > ( ) ;
148
+
149
+ constructor ( private predicates : ReadonlyArray < Predicate > ) {
150
+ this . hidden = new BooleanOrLogic ( predicates ) ;
151
+ this . disabled = new BooleanOrLogic ( predicates ) ;
152
+ this . errors = new ArrayMergeLogic < FormError > ( predicates ) ;
153
+ }
10000
span>
154
+
155
+ getMetadata < T > ( key : MetadataKey < T > ) : AbstractLogic < T > {
156
+ if ( ! this . metadata . has ( key as MetadataKey < unknown > ) ) {
157
+ this . metadata . set ( key as MetadataKey < unknown > , new MetadataMergeLogic ( this . predicates , key ) ) ;
158
+ }
159
+ return this . metadata . get ( key as MetadataKey < unknown > ) ! as AbstractLogic < T > ;
160
+ }
161
+
162
+ readMetadata < T > ( key : MetadataKey < T > , arg : FieldContext < unknown > ) : T {
163
+ if ( this . metadata . has ( key as MetadataKey < unknown > ) ) {
164
+ return this . metadata . get ( key as MetadataKey < unknown > ) ! . compute ( arg ) as T ;
165
+ } else {
166
+ return key . defaultValue ;
167
+ }
168
+ }
169
+
170
+ getMetadataKeys ( ) {
171
+ return this . metadata . keys ( ) ;
172
+ }
173
+ }
174
+
175
+ /**
176
+ * A tree structure of `Logic` corresponding to a tree of fields.
177
+ */
178
+ export class LogicNode {
179
+ readonly logic : Logic ;
180
+
181
+ constructor ( private builder : AbstractLogicNodeBuilder | undefined ) {
182
+ this . logic = builder ? createLogic ( builder ) : new Logic ( [ ] ) ;
183
+ }
184
+
185
+ // TODO: cache here, or just rely on the user of this API to do caching?
186
+ getChild ( key : PropertyKey ) : LogicNode {
187
+ // The logic for a particular child may be spread across multiple builders. We lazily combine
188
+ // this logic at the time the child logic node is requested to be created.
189
+ const childBuilders = this . builder ? getAllChildren ( this . builder , key ) : [ ] ;
190
+ if ( childBuilders . length <= 1 ) {
191
+ return new LogicNode ( childBuilders [ 0 ] ) ;
192
+ } else {
193
+ // If there are multiple child builders, combine them all into one we ca pass to the new logic
194
+ // node.
195
+ const combined = LogicNodeBuilder . newRoot ( ) ;
196
+ for ( const child of childBuilders ) {
197
+ combined . mergeIn ( child ) ;
198
+ }
199
+ return new LogicNode ( combined ) ;
200
+ }
201
+ }
202
+ }
203
+
216
204
/**
217
205
* Gets all of the builders that contribute logic to the given child of the parent builder.
218
206
*/
219
- function getAllChildren ( builder : LogicNodeBuilder , key : PropertyKey ) : MergableLogicNodeBuilder [ ] {
220
- if ( builder instanceof CompositeLogicNodeBuilder ) {
207
+ function getAllChildren ( builder : AbstractLogicNodeBuilder , key : PropertyKey ) : LogicNodeBuilder [ ] {
208
+ if ( builder instanceof LogicNodeBuilder ) {
221
209
return builder . all . flatMap ( ( node ) => getAllChildren ( node , key ) ) ;
222
- } else if ( builder instanceof SimpleLogicNodeBuilder ) {
210
+ } else if ( builder instanceof NonMergableLogicNodeBuilder ) {
223
211
if ( builder . children . has ( key ) ) {
224
212
return [ builder . children . get ( key ) ! ] ;
225
213
}
@@ -232,9 +220,10 @@ function getAllChildren(builder: LogicNodeBuilder, key: PropertyKey): MergableLo
232
220
/**
233
221
* Creates the full `Logic` for a given builder.
234
222
*/
235
- function createLogic ( builder : LogicNodeBuilder ) : Logic {
236
- const logic = new Logic ( builder . predicates ) ;
237
- if ( builder instanceof CompositeLogicNodeBuilder ) {
223
+ function createLogic ( builder : AbstractLogicNodeBuilder ) : Logic {
224
+ if ( builder instanceof LogicNodeBuilder ) {
225
+ // Build the logic for all sub-builders, and merge them into one.
226
+ const logic = new Logic ( [ ] ) ;
238
227
const builtNodes = builder . all . map ( ( b ) => b . build ( ) ) ;
239
228
for ( const node of builtNodes ) {
240
229
logic . disabled . mergeIn ( node . logic . disabled ) ;
@@ -244,15 +233,17 @@ function createLogic(builder: LogicNodeBuilder): Logic {
244
233
logic . getMetadata ( key ) . mergeIn ( node . logic . getMetadata ( key ) ) ;
245
234
}
246
235
}
247
- } else if ( builder instanceof SimpleLogicNodeBuilder ) {
236
+ return logic ;
237
+ } else if ( builder instanceof NonMergableLogicNodeBuilder ) {
238
+ const logic = new Logic ( builder . predicates ) ;
248
239
logic . disabled . mergeIn ( builder . logic . disabled ) ;
249
240
logic . hidden . mergeIn ( builder . logic . hidden ) ;
250
241
logic . errors . mergeIn ( builder . logic . errors ) ;
251
242
for ( const key of builder . logic . getMetadataKeys ( ) ) {
252
243
logic . getMetadata ( key ) . mergeIn ( builder . logic . getMetadata ( key ) ) ;
253
244
}
245
+ return logic ;
254
246
} else {
255
247
throw new Error ( 'Unknown LogicNodeBuilder type' ) ;
256
248
}
257
- return logic ;
258
249
}
0 commit comments