1+ import { ModelType } from "./Types" ;
2+ import { find } from "lodash" ;
3+ import uniqid from "uniqid" ;
14const uniqid = require ( "uniqid" ) ;
2-
5+ /*
6+ * The Formula class
7+ */
38class Formula {
9+ id = uniqid ( ) ;
10+ // Label
11+ label ;
412 // Original formula string
513 formulaString ;
614 // Holds formula template (tags replaced by unique identifiers)
715 formulaTemplate ;
816 // Array holding all tags
917 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 > ;
1024
1125 // 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+ ) {
1333 this . formulaString = formula ;
1434 this . formulaTemplate = formula ;
35+ this . models = models ;
36+ this . label = label ;
1537
1638 // Pre-parse tags
1739 const tagPattern =
@@ -27,9 +49,100 @@ class Formula {
2749 ) ;
2850 } ) ;
2951
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+ ) ;
3260 }
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+ } ) ;
33146}
34147
35148export default Formula ;
0 commit comments