Skip to content
This repository has been archived by the owner on May 18, 2024. It is now read-only.

Insertions

Jeongho Nam edited this page Sep 15, 2023 · 4 revisions

initialize

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,
    });
}

InsertCollection

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();
}

EntityUtil

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;
}