1
+ import { ModelType } from "./Types" ;
2
+ import { find } from "lodash" ;
3
+ import uniqid from "uniqid" ;
1
4
const uniqid = require ( "uniqid" ) ;
2
-
5
+ /*
6
+ * The Formula class
7
+ */
3
8
class Formula {
9
+ id = uniqid ( ) ;
10
+ // Label
11
+ label ;
4
12
// Original formula string
5
13
formulaString ;
6
14
// Holds formula template (tags replaced by unique identifiers)
7
15
formulaTemplate ;
8
16
// Array holding all tags
9
17
tags : { tag : string ; identifier : string } [ ] = [ ] ;
18
+ // Array holding all dependencies
19
+ dependencies : { field : string ; model : string ; localDependency ?: true } [ ] = [ ] ;
20
+ // Hold all models
21
+ models : ModelType [ ] ;
22
+ // Promise to check if constructor is done working asynchronously
23
+ onParsed : Promise < void > ;
10
24
11
25
// Constructor
12
- constructor ( formula , mode : "{{" | "[[" = "{{" ) {
26
+ constructor (
27
+ formula ,
28
+ startingModelKey : string ,
29
+ label ?: string ,
30
+ mode : "{{" | "[[" = "{{" , // This is for nested formulas, such as templates
31
+ models ?: ModelType [ ] // Some data may be statically delivered in JSON format. Then we don't need this. If we have dynamic (field__r) data we need to query the database and parse the correct dependencies.
32
+ ) {
13
33
this . formulaString = formula ;
14
34
this . formulaTemplate = formula ;
35
+ this . models = models ;
36
+ this . label = label ;
15
37
16
38
// Pre-parse tags
17
39
const tagPattern =
@@ -27,9 +49,100 @@ class Formula {
27
49
) ;
28
50
} ) ;
29
51
30
- // Loop through all the tags
31
- console . log ( this . formulaString , this . formulaTemplate , this . tags ) ;
52
+ // Parse dependencies
53
+ this . onParsed = new Promise ( ( resolve , reject ) =>
54
+ this . parseDependencies ( startingModelKey ) . then (
55
+ ( ) => resolve ( ) ,
56
+ ( reason ) =>
57
+ reject ( `(${ label } ) couldn't process dependencies: ${ reason } ` )
58
+ )
59
+ ) ;
32
60
}
61
+
62
+ // Parse dependencies for all tags (asynchronously used in )
63
+ parseDependencies = ( startModelKey : string ) =>
64
+ new Promise < void > ( async ( resolve , reject ) => {
65
+ //@ts -ignore
66
+ await this . tags . reduce ( async ( prevTag , tag ) => {
67
+ await prevTag ;
68
+
69
+ const tagParts = tag . tag . split ( / [ - + * \/ ] (? ! [ ^ \( ] * \) ) / gm) ;
70
+ //@ts -ignore
71
+ await tagParts . reduce ( async ( prevTagPart , tagPart ) => {
72
+ // The regexp splits on -, but not within parenthesis
73
+ const part = tagPart . trim ( ) ;
74
+
75
+ // Check the context of the tag part and perform the appropriate action
76
+ if ( part . match ( / \w * \( .+ \) / ) ) {
77
+ // This part has a function call. We need to preprocess these functions to figure out what the dependencies are.
78
+ const func = new RegExp ( / (?< fName > \w * ) \( (?< fArgs > .* ) \) / gm) . exec (
79
+ part
80
+ ) ;
81
+ console . log ( "Preprocessing" , func . groups . fName , func . groups . fArgs ) ;
82
+ } else if ( part . match ( / \. / ) ) {
83
+ if ( part . match ( "__r" ) ) {
84
+ // This is an object based relationship. Resolve the dependencies
85
+ if ( this . models ) {
86
+ // We're going to split by . and resolve them all to set a dependency.
87
+ const tagParts = part . split ( "." ) ;
88
+ let currentModelKey = startModelKey ;
89
+ //@ts -ignore
90
+ await tagParts . reduce ( async ( prevPart , currPart ) => {
91
+ await prevPart ;
92
+
93
+ if ( currPart . match ( "__r" ) ) {
94
+ const fieldName = currPart . replace ( "__r" , "" ) ;
95
+
96
+ // This is a part of the relationship. It needs to be registered as dependency, in case it's value changes.
97
+ this . dependencies . push ( {
98
+ model : currentModelKey ,
99
+ field : fieldName ,
100
+ ...( currentModelKey === startModelKey
101
+ ? { localDependency : true }
102
+ : { } ) ,
103
+ } ) ;
104
+ // It also needs to be parsed to figure out what model the next
105
+ const currentModel = find (
106
+ this . models ,
107
+ ( o ) => o . key === currentModelKey
108
+ ) ;
109
+ const field = currentModel . fields [ fieldName ] ;
110
+ currentModelKey = field . relationshipTo ;
111
+ } else {
112
+ this . dependencies . push ( {
113
+ model : currentModelKey ,
114
+ field : currPart ,
115
+ } ) ;
116
+ }
117
+
118
+ return currPart ;
119
+ } , tagParts [ 0 ] ) ;
120
+ resolve ( ) ;
121
+ } else {
122
+ reject ( "no-models-provided" ) ;
123
+ }
124
+ } else {
125
+ // This is a regular dependency (a.b.c), so we can just add it as a field
126
+ this . dependencies . push ( {
127
+ field : part ,
128
+ model : startModelKey ,
129
+ localDependency : true ,
130
+ } ) ;
131
+ }
132
+ } else {
133
+ this . dependencies . push ( {
134
+ field : part ,
135
+ model : startModelKey ,
136
+ localDependency : true ,
137
+ } ) ;
138
+ }
139
+ } , tagParts [ 0 ] ) ;
140
+
141
+ return tag ;
142
+ } , this . tags [ 0 ] ) ;
143
+
144
+ resolve ( ) ;
145
+ } ) ;
33
146
}
34
147
35
148
export default Formula ;
0 commit comments