Package Exports
- lambdaorm
- lambdaorm/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (lambdaorm) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
λORM
λORM is an ORM for Node.js which bases its queries on a business model, abstracting from the physical model. By means of rules the corresponding Data Source is determined and by definition of mappings how the business model is mapped with the physical one.
What differentiates λORM from other ORMs:
Obtain or modify records from different Databases in the same query.
These Databases may be from different engines (Example: MySQL, PostgreSQL, MongoDB, etc.)Abstraction of the physical model, being the same to work with a single database than with multiple ones.
Define different stages for a business model.
You can define a stage where you work with a MySQL instance and another stage where you work with Oracle and MongoDB.Friendly query syntax, being able to write in the programming language itself as in a string.
Expressions are parsed in the same way as with an expression language.
Features
- Supports MySQL, MariaDB, PostgresSQL, Oracle, SqlServer, SqlJs and MongoDB.
- TypeScript and JavaScript support
- Schema Configuration
- Decoupling the business model from physical model
- Configuration in json or yml formats
- Definition of mappings to map the business model with the physical model
- Extends entities
- Environment variables
- define indices, unique keys and constraints
- Query Language
- Simple query language based on javascript lambda expressions.
- Can write the expression as javascript code or as a string
- Crud clauses
- Implicit joins and group by
- Eager loading using the Include() method.
- Query expression metadata
- Repositories and custom repositories
- Using multiple database instances
- Transactions and distributed transactions
- BulkInsert
- Connection pooling
- Listeners and subscribers
- High performance
- CLI
- Api Rest
Schema
The schema includes all the configuration that the ORM needs.
The schema separates the definition of the business model (Domain) from the persistence of the data (Infrastructure).
In the domain, the entities and enumerators that represent the business model are completely clean, without any attributes that couple them to persistence.
All queries are made according to the business model, so all queries are decoupled from the physical model of the data.
In the infrastructure, all the necessary configuration is defined to be able to persist and obtain the data from the different sources.
The schema configuration can be done in a yaml, json file or passed as a parameter when initializing the ORM.
All the expressions that are used for the definition of conditions and for the execution of actions are based on the expression engine js-expressions
Queries
The query language is based on javascript lambda expression. These expressions can be written as javascript code by browsing the business model entities.
Expressions can also be sent as a string
λOrm translates the expression into the language corresponding to each database engine.
Query Language Example
For a schema where we have mapped the entities to different data sources. For example:
- Orders and OrderDetails are located in a MongoDb collection
- Customers in a MySQL table
- Products in a Postgres table.
Orders.filter(p => p.customerId == customerId)
.include(p => [p.details.include(p=> p.product.map(p=>p.name)),
p.customer.map(p => p.name)])
.order(p=> p.orderDate)
.page(1,2)We can get the execution plan in the code with:
import { orm } from 'lambdaorm'
(async () => {
await orm.init()
const query =
`Orders.filter(p => p.customerId == customerId)
.include(p => [p.details.include(p=> p.product.map(p=>p.name)),
p.customer.map(p => p.name)
])
.order(p=> p.orderDate)
.page(1,1)
`
const plan = orm.plan(query,{ stage:"default"})
console.log(JSON.stringify(plan,null,2))
await orm.end()
})()
Or using CLI:
lambdaorm plan -q 'Orders.filter(p => p.customerId == customerId).include(p => [p.details.include(p=> p.product.map(p=>p.name)),p.customer.map(p => p.name)]).order(p=> p.orderDate).page(1,2)'Result:
{
"entity": "Orders",
"dialect": "MongoDB",
"source": "Ordering",
"sentence": "[{ \"$match\" : { \"CustomerID\":{{customerId}} } }, { \"$project\" :{ \"_id\": 0 , \"id\":\"$_id\", \"customerId\":\"$CustomerID\", \"orderDate\":\"$OrderDate\", \"__id\":\"$_id\", \"__customerId\":\"$CustomerID\" ,\"details\": { \"$map\":{ \"input\": \"$\\\"Order Details\\\"\", \"in\": { \"orderId\":\"$$this.OrderID\", \"productId\":\"$$this.ProductID\", \"unitPrice\":\"$$this.UnitPrice\", \"quantity\":\"$$this.Quantity\", \"__productId\":\"$$this.ProductID\", \"LambdaOrmParentId\":\"$$this.OrderID\" } }} }} , { \"$sort\" :{ \"OrderDate\":1 } } , { \"$skip\" : 0 }, { \"$limit\" : 1 } , { \"$project\": { \"_id\": 0 } }]",
"children": [
{
"entity": "Orders.details",
"dialect": "MongoDB",
"source": "Ordering",
"children": [
{
"entity": "Products",
"dialect": "MySQL",
"source": "Catalog",
"sentence": "SELECT p.ProductName AS name, p.ProductID AS LambdaOrmParentId FROM Products p WHERE p.ProductID IN (?) "
}
]
},
{
"entity": "Customers",
"dialect": "PostgreSQL",
"source": "Crm",
"sentence": "SELECT c.CompanyName AS \"name\", c.CustomerID AS \"LambdaOrmParentId\" FROM Customers c WHERE c.CustomerID IN ($1) "
}
]
}Result:
[
{
"id": 3,
"customerId": "HANAR",
"orderDate": "1996-07-08T00:00:00.000+02:00",
"details": [
{
"orderId": 3,
"productId": 41,
"unitPrice": 7.7,
"quantity": 10,
"product": {
"name": "Jack's New England Clam Chowder"
}
},
{
"orderId": 3,
"productId": 51,
"unitPrice": 42.4,
"quantity": 35,
"product": {
"name": "Manjimup Dried Apples"
}
},
{
"orderId": 3,
"productId": 65,
"unitPrice": 16.8,
"quantity": 15,
"product": {
"name": "Louisiana Fiery Hot Pepper Sauce"
}
}
],
"customer": {
"name": "Hanari Carnes"
}
}
]Advantage:
- Use of the same programming language.
- No need to learn a new language.
- Expressions easy to write and understand.
- Use of the intellisense offered by the IDE to write the expressions.
- Avoid syntax errors.
Usage
To work with the orm we can do it using the singleton object called "orm" or using repositories.
Object orm
This orm object acts as a facade and from this we access all the methods.
When the orm.init() method is called, the orm initialization will be executed from the configuration.
execute method:
This method receives the expression as a javascript lambda function or a string.
Use lambda expression:
The advantage of writing the expression as a javascript lambda function is that this way we will have the help of intellisense and we will make sure that the expression has no syntax errors.
import { orm } from 'lambdaorm'
(async () => {
await orm.init()
const query = (region:string) =>
Countries.filter(p=> p.region == region)
.map(p=> [p.name,p.subregion,p.latitude,p.longitude])
.include(p => p.states.filter(p=> substr(p.name,1,1)=="F")
.map(p=> [p.name,p.latitude,p.longitude])
)
.page(1,3)
const result = await orm.execute(query, { region: 'Asia' })
console.log(JSON.stringify(result, null, 2))
await orm.end()
})()Result:
[
{
"name": "Afghanistan",
"subregion": "Southern Asia",
"latitude": "33.00000000",
"longitude": "65.00000000",
"states": [
{
"name": "Farah",
"latitude": "32.49532800",
"longitude": "62.26266270"
},
{
"name": "Faryab",
"latitude": "36.07956130",
"longitude": "64.90595500"
}
]
},
{
"name": "United Arab Emirates",
"subregion": "Western Asia",
"latitude": "24.00000000",
"longitude": "54.00000000",
"states": [
{
"name": "Fujairah",
"latitude": "25.12880990",
"longitude": "56.32648490"
}
]
},
{
"name": "Armenia",
"subregion": "Western Asia",
"latitude": "40.00000000",
"longitude": "45.00000000",
"states": []
}
]Related projects
Documentation
Full documentation is available in the Wiki.
Labs
You can access various labs at lambdaorm labs