Insertions - samchon/safe-typeorm GitHub Wiki
export function initialize<T>(classDef: Creator<T>, input: Props<T>): T;
initialize()
is a function creating a model instance type safely.
You know what? When creating an ORM entity instance, typeorm
does not support any type safe factory function. typeorm
recommends user to create ORM entity instance by using new
statement or getRepository(Entity).create()
method, which can be cause critical runtime error by forgetting to set a required property.
import * as orm from typeorm;
/**
* No error when compliation.
*
* However, becomes runtime error due to `writer` property omission.
*/
async function new_bbs_article(group: BbsGroup): Promise<BbsArticle> {
const article = new BbsArticle();
await article.group.set(group);
await article.category.set(null);
// article.writer = "Samchon";
article.ip = "127.0.0.1";
article.created_at = new Date();
return article;
}
Therfore, safe-typeorm
supports type safe factory function initialize()
. With the initialize()
function, you can create an ORM entity instance type safely. If you omit any required property, compilation error would be occured and you may avoid critical runtime error by fixing it.
import safe from "safe-typeorm";
/**
* Type safe factory function.
*
* Compilation error occurs due to `writer` property omission.
*/
function initialize_bbs_article(group: BbsGroup): BbsArticle {
return safe.initialize(BbsArticle, {
id: safe.DEFAULT,
group: group,
category: null,
// writer: "Samchon",
ip: "127.0.0.1",
created_at: new Date(),
deleted_at: null,
});
}
import * as orm from "typeorm";
export class InsertCollection {
public execute(manager?: orm.EntityManager): Promise<void>;
public push<T extends object>(record: T, ignore?: string | boolean): T;
public push<T extends object>(records: T[], ignore?: string | boolean): T[];
public before(process: InsertCollection.Process): void;
public after(process: InsertCollection.Process): void;
}
export namespace InsertCollection {
export interface Process {
(manager: orm.EntityManager): Promise<any>;
}
}
InsertCollection
is an utility class supporting massive insertions.
Also, when multiple table records are prepared at the same time, InsertCollection
analyzes the dependency relationships of each table and automatically sorts the insertion order, so that there would not be any error due to the foreign key constraint.
However, note that, InsertCollection
does not support auto-increment (sequence) typed primary key. If you put any entity record that using the auto-increment typed primary key, InsertCollection
would throw an error. Recommend to use only UUID (or string) typed primary key.
async function insert(
tags: BbsArticleTag[],
articles: BbsArticle[],
contents: BbsArticleContent[],
groups: BbsGroup[],
contentFiles: BbsArticleContentFile[],
categories: BbsCategory[],
files: AttachmentFile[],
): Promise<void> {
// although you've pushed entity records
// without considering dependency relationships
const collection: safe.InsertCollection = new safe.InsertCollection();
collection.push(tags);
collection.push(articles);
collection.push(contents);
collection.push(groups);
collection.push(contentFiles);
collection.push(categories);
collection.push(files);
// `InsertCollection` would automatically sort insertion order
// just by analyzing dependency relationships by itself
await collection.execute();
}
Also, you can add extra processes to be executed both before and after insertion.
Additionally, if you prepare insertion queries and pre/post processes and execute them all by calling InsertCollection.execute()
method, those prepared queries and processes would be cleared. Therefore, you can reuse the same InsertCollection
instance.
However, note that, it is not possible to run the InsertCollection.execute()
methods multiple times in parallel. There would not be any runtime error, but InsertCollection
would block your simultaneous calls until previous execution be completed through Mutex.
async function insert(article: BbsArticle, ...): Promise<void> {
const collection: safe.InsertCollection = new safe.InsertCollection();
collection.push(article);
// executed before insert query
collection.before(async () => {
await archive_article_insert_log(article);
});
// executed after insert query
collection.after(async () => {
await update_article_count(1);
});
collection.after(async () => {
await send_push_message_to_writer("article", article);
});
// do execute
// 1. before process(es)
// 2. insert query
// 3. after process(es)
await collection.execute();
}
export namespace EntityUtil {
export function unify<Entity extends object>(
original: Entity,
duplicates: Entity[],
): Promise<void>;
export function name<Entity extends object>(
entity: Creator<Entity>,
): string;
export function info<Entity extends object>(
entity: Creator<Entity>,
): ITableInfo;
}
EntityUtil
is a utility class designed to merge duplicate records into one.
When call EntityUtil.unify()
method, duplicates
records would be absorbed into original
. Tha means, all records listed in duplicates
would be erased, and instead, references to entities of all records subordinate to duplicates
records would be replaced with original
one.
During unification, if there're some children entities dependent on Entity
and there foreign columns referencing Entity
are belonged to unique constraint, they would be unified in chain. Also, there're grand children entities dependent on children entities with unique constraint, they also be unified.
For example, if you unify Manufacturer
entity records, Product
records also be unified by unique constraint. ProductImage
also would be unified in chain, because of Product
unification.
import * as orm from "typeorm";
import safe from "safe-typeorm";
async function unify(
original: Manufacturer,
duplicates: Manufacturer[],
): Promise<void> {
await safe.EntityUtil.unify(original, duplicates);
// manufacturers would be unified
// products would be unified
// product images would be unified
}
@orm.Entity()
class Manufacturer {
id: string;
name: string;
}
@orm.Entity()
@orm.Unique(["manufacturer_id", "name"])
class Product {
id: string;
manufacturer: safe.Belongs.ManyToOne<Manufacturer, "uuid">;
name: string;
}
@orm.Entity()
@orm.Unique(["product_id", "name"])
class ProductImage {
id: string;
product: safe.Belongs.ManyToOne<Product, "uuid">;
name: string;
url: string;
}