6 minutes
Slim-exp
TLDR
Slim-exp is simple typescript expression parser. GithUb Repo : slim-exp
Good. You’re still here. May be you are not a Javascript/Web developer. They are usually not familiar to expressions in this world. Especially those not from a .NET (😏) Background.
Some usually ask What is the use of writing a function that is never called ? Why not just use an object ?
. Well… It’s not wrong so, if your objectif is just to write code.
Let’s first define what is an expression in .NET world. Below is an extract from MS Doc.
The simplest C# expressions are literals (for example, integer and real numbers) and names of variables. You can combine them into complex expressions by using operators. Operator precedence and associativity determine the order in which the operations in an expression are performed. You can use parentheses to change the order of evaluation imposed by operator precedence and associativity. […] > […] Typically, an expression produces a result and can be included in another expression. A void method call is an example of an expression that doesn’t produce a result. It can be used only as a statement […]
Not very clear right ? Long story short, an expression in C# is a combination of operands (variables, literals, method calls) and operators that can be evaluated to a single value. To be precise, an expression must have at least one operand but may not have any operator
As a core developer I know the importance of facilitating my teams’ work when building softwares. And expression use the right way at the right time facilitates life. The objectif of this library is not just to write some cool stuff but to help developers build an infrastructure, based on expressions, which will ease life.
Why did I built this ?
I’m a .NET developer before being anything else. I love .NET (please haters, slide to the left…😆). The build awesome stuff. One of the most powerful of all is the LINQ (Language Integrated Query) API. Sorry it’s not Entity Framework (IMHO), simply because EF draws most of it’s power from the LINQ API.
So during my moments of distraction when I am not an MS nerd, I am doing Typescript/Javascript (I felt in love some months ago with NESTJS).
I worked on a personal project for a while and i was using NestJS and typeorm. I wanted to do cool stuffs just like I’ve been doing with EFCore and the LINQ API. TypeOrm was offering a pretty good fluent API, which could do almost all the necessary work, but it was not like EFCore (😅). It forces me to handle too much hard coded strings, the api language was not the same (.where(..)
, .first(...)
), the conditions for which you can use the fluent api were not adequate for me.
I found libraries such as TypeOrmLinq, alternatives like MicroOrm… But none was offering the seemless expression handling i was searching for. If you want to know more about what i was searching for, and how i made a slim-ef
(Slim Entity Framework) the next article will have much more details .
Through the struggle of reproducing the minimum of functionnalities offered by EFCore, I had the Idea of implementing the System.Linq.Expressions.Expression
class in typescript. Obviously is still minimal, but it does a pretty good job.
What does it actually do ?
All slim-exp does is converting this n => n.name
into this { leftHandSide: {propertyName: 'name', suffixOperator: '', propertyTree: ['name'] } }
And when you are angry enough, you can convert this :
(n, $) =>
(n.name.includes('hello') && n.isFool) ||
(n.matricule !== 'mat22' &&
n.name.startsWith($.name) &&
n.complexValues.filter((c) =>
c.complexity.made.simple.map(
(s) => s.and.straightTo.the.point !== $.value
)
));
Into this :
_expDesc: {
leftHandSide: {
propertyName: 'name.includes',
suffixOperator: '',
isMethod: true,
content: {
type: 'string',
primitiveValue: 'hello',
methodName: 'includes'
},
propertyTree: ['name', 'includes']
},
next: {
bindedBy: '&&',
followedBy: {
leftHandSide: {
propertyName: 'isFool',
suffixOperator: '',
propertyTree: ['isFool']
},
next: {...} // A LOT of code was ommitted for brevity
}
}
}
More explanation on the result provided by slim-exp can be found on the github repo.
Answer to the question Why not just use an object ?
: We both agree that is easier to write that arrow function than the object ?
Really what’s the use of this ?
The only use of slim-exp is to convert an arrow function expression into a chained list of expression description that will be used to process informations later.
This may look similar to a custom code analyser or to what AST (Abstract Syntax Tree) offers us but it is not.
Be it a code analyzer or AST, they do not give you the value of variable properties up the execution tree at runtime. Due to javascript nature, it is not possible (IMO, but may be you got the solution, just let me know) to do "real"
reflection at runtime.
What do i mean by this ? Suppose you have the following
type Expression<S> = (obj:S) => any;
const matricule = 'MAT0001';
const list = new List<Employees>();
list.getValues(elt => elt.matricule !== matricule);
...
...
class List<T>{
getValues<T>(fn: Expression<T>){
// Now we have to parse 'fn' here...
}
}
Actually the problem is that, the only way to decompose this arrow function is to convert it to string and analyse the function since reflection does not exist in Javascript. As a function object, we can get his name, his args but we cannot get the variable “matricule” on which it depends. Even if we execute the function, “matricule” will undefined.
Now, think about this. If the arrow function represents a query and we want to get data from a data store using that query, we would want something like ... where matricule = 'MAT0001'
as the where clause generated. The only way to solve this issue is by passing the variable independently and storing it for future use (The context ($))
type Expression<S> = (obj:S) => any;
const matricule = 'MAT0001';
const list = new List<Employees>();
list.getValues((e, $) => e.matricule !== $.matricule, {matricule});
...
...
class List<T>{
getValues<T, C>(fn: Expression<T>, context: C){
// Now we have to parse 'fn' here...
}
}
Doing so we are able to manage the dependent data of our Expression and thanks to typescript we have intellisense for our context object.
This is an example as it is emplemented in slim-ef. It servers as expression parser
, i.e the arrow function is extracted as expression and then used to mount a query.
How to use
Read the Readme 😀 available at slim-exp
What’s next ?
I would be glad if you contribute to slim-exp. I think there is still a lot more to be done. Feel free to download the repo and have fun! 😄
To get slim-exp in your project right now, just do npm i slim-exp