UniMapperJS is universal Node.js (native ES6/ES7) LINQ-like object mapper (ORM/ODM) which can map whatever you create adapter for.
!! Under DEVELOPMENT !!
See wiki
- Adapters - if your database isn't supported, you can write own adapter,
- migrations - migration scripts - you can check migration script before applying,
- seeding - seed default data into storage,
- more connections across adapters (eg. take data from MongoDB or two MongoDBs and insert it into MySQL),
- unit of work - tracking changes in transactions,
- updating just changed properties - saving resources,
- quering like C# LINQ to Entities (with JS function names)
- implement Entity.addUnique() which allow UNIQUE key over more fields
- implement Entity.addPrimary() which allow PRIMARY key over more fields
- implement bulk operations
- implement JOINs
- implement nested operations (eg. saving Enterprise should save all Employees in Enterprise.Employees collection too. Same rollbacking etc.)
- add beforeSave() method into entity class - allow some operation before saving
- add baseQuery property into entity class - it'll be used in ALL select queries (can be used for soft-delete)
- implement client-side execution (query from client will send AJAJ request to server which will handle it as normal server query; something like automatic GraphQL)
- add whereOr() and whereOrIf() into Query
You can clone and play with the example in this repo.
- entities
- migrations
- domain.js
- create-migration.js
- run-migration.js
domain.js
const $um = require("unimapperjs");
const MySqlAdapter = require("unimapperjs/adapters/MySqlAdapter");
// Domain creation - connect to database via MySQL adapter
const domain = $um.createDomain(MySqlAdapter, { // connection string or object with options - specific to adapter
host: '127.0.0.1',
user: 'test',
password: 'test',
database: "test"
});
exports.domain = domain;
Then create entities in given domain. In TypeScript you can declare entity like this one.
etities/Student.ts
import {type} from "unimapperjs";
import {Entity} from "unimapperjs/src/Entity";
import {domain} from "../domain";
import {Teacher} from "./Teacher";
@domain.entity()
export class Student extends Entity<Student>
{
/**
* Student Id
*/
id: number;
/**
* Student name
*/
name: string;
/**
* Student's teacher id
*/
teacherId: number;
/**
* Navigation property to Teacher
*/
teacher: Promise<Teacher>;
static map(map: Student) {
const {Teacher} = require("./Teacher");
map.id = <any>type.uuid;
map.name = <any>type.string.length(100);
map.teacherId = <any>type.number;
map.teacher = <any>type.foreign(Teacher.name)
.withForeign<Student>(s => s.teacherId);
}
}
entities/Teacher.ts
import {type} from "unimapperjs";
import {Entity} from "unimapperjs/src/Entity";
import {domain} from "../domain";
import {Student} from "./Student";
/**
* Teacher entity
*/
@domain.entity()
export class Teacher extends Entity<Teacher>
{
/**
* Teacher ID
*/
id: number;
/**
* First name
*/
firstName: string;
/**
* Last name
*/
lastName: string;
/**
* Navigations property to assigned students
*/
students: Promise<Array<Student>>;
/**
* Mapping
*/
static map(map: Teacher) {
const {Student} = require("./Student");
map.id = <any>type.number.primary().autoIncrement();
map.firstName = <any>type.string.length(50);
map.lastName = <any>type.string.length(50);
map.students = <any>type.foreign(Student.name)
.hasMany<Student>(s => s.teacherId);
}
}
Now you can run migraion.
create-migration.js
const $umjs = require("unimapperjs");
const $path = require("path");
const {domain} = require("./domain");
// Discove all entities from given path
$umjs.initEntitiesFrom($path.resolve(__dirname, "entities"));
// Run it in next event loop iteration
setImmediate(async () => {
await domain.createMigration($path.resolve(__dirname, "migrations"));
await domain.dispose();
});
New migration script is gonna be generated in folder ./migrations
like this one.
/**
* Migration script
*/
module.exports = {
up: async function up(adapter) {
await adapter.createEntity("Student", {
"id": {
"type": "String"
, "length": 37
, "primary": true
}
, "name": {
"type": "String"
, "length": 100
}
, "teacherId": {
"type": "Number"
, "length": 11
}
});
await adapter.createEntity("Teacher", {
"id": {
"type": "Number"
, "length": 11
, "primary": true
, "autoIncrement": true
}
, "firstName": {
"type": "String"
, "length": 50
}
, "lastName": {
"type": "String"
, "length": 50
}
});
await adapter.addForeignKey("Student", "teacherId", "Teacher", "fk_Student_teacherId_Teacher_id");
}
};
You can run that migration script with
run-migration.js
const $path = require("path");
const {domain} = require("./domain");
setImmediate(async () => {
await domain.runMigration($path.resolve(__dirname, "migrations"));
await domain.dispose();
});
let teacher = new Teacher({
firstName: "John",
lastName: "Wick"
});
await Teacher.insert(teacher);
let teacher2 = new Teacher();
teacher2.firstName = "John";
teacher2.lastName = "Smith";
await Teacher.insert(teacher2);
let student = new Student({
name: "Bart Simpson",
teacherId: teacher.id
});
await Student.insert(student);
student.teacher = teacher2;
await student.save();
// Teachers their first name contains "u"
let teachers = await Teacher.getAll()
.filter(t => t.firstName.includes("u"))
.sort(t => t.firstName)
.exec();
// Students their name starts with 'P' or ends with 's'
let startsWith = "P";
let students = await Student.getAll()
.filter(s => s.name.startsWith($) || s.name.endsWith("s"), startsWith)
.sortDesc(s => s.name)
.slice(3, 8) // limit 5, skip 3
.exec();
let onlyActive = true;
let activeSubjectsCount = await Subject.getAll()
.filterIf(s => s.active === true, onlyActive) // if (onlyActive) { .filter(s => s.active === true) }
.count()
.exec();
let subjectNames = await Subject.getAll()
.sort(s => s.name)
.map(s => s.name) // select only names ; in SQL SELECT name FROM Subject
.exec();
let subjectMap = await Subject.getAll()
.sort(s => s.name)
.map(s => ({
id: s.id,
name: s.name
}))
.exec();