safe-typeorm

Safe Relationship Decorators for the TypeORM

Usage no npm install needed!

<script type="module">
  import safeTypeorm from 'https://cdn.skypack.dev/safe-typeorm';
</script>

README

Safe-TypeORM

logo

GitHub license npm version Downloads Build Status FOSSA Status Chat on Gitter

npm install --save safe-typeorm

The safe-typeorm is a helper library for typeorm, enhancing safety in the compilation level.

  • When writing SQL query,
    • Errors would be detected in the compilation level
    • Auto Completion would be provided
    • Type Hint would be supported
  • You can implement App-join very conveniently
  • When SELECTing for JSON conversion
    • App-Join with the related entities would be automatically done
    • Exact JSON type would be automatically deduced
    • The performance would be automatically tuned
  • When INSERTing records
    • Sequence of tables would be automatically sorted by analyzing dependencies
    • The performance would be automatically tuned

Demonstration

ORM Model Classes

Entity Relationship Diagram

For demonstration, I've taken ORM model classes from the Test Automation Program of this safe-typeorm. The ORM model classes in the Test Automation Program represents a BBS (built-in bullet system) and its ERD (Entity Relationship Diagram) is like upper image.

Also, below is the list of ORM model classes used in the Test Automation Program. If you want to see the detailed code of the ORM model classes, click the below link. Traveling the ORM model classes, you would understand how to define the ORM model classes and their relationships through this safe-typeorm.

Safe Query Builder

Safe Query Builder

In safe-typeorm, you can write SQL query much safely and conveniently.

If you take a mistake when writing an SQL query, the error would be occured in the compilation level. Therefore, you don't need to suffer by runtime error by mistaken SQL query. Also, if you're writing wrong SQL query, the IDE (like VSCode) will warn you with the red underlined emphasizing, to tell you there can be an SQL error.

Also, safe-typeorm supports type hinting with auto-completion when you're writing the SQL query. Therefore, you can write SQL query much faster than before. Of course, the fast-written SQL query would be ensured its safety by the compiler and IDE.

export async function test_safe_query_builder(): Promise<void>
{
    const group: BbsGroup = await BbsGroup.findOneOrFail();
    const category: BbsCategory = await BbsCategory.findOneOrFail();

    const stmt: orm.SelectQueryBuilder<BbsQuestionArticle> = safe
        .createJoinQueryBuilder(BbsQuestionArticle, question =>
        {
            question.innerJoin("base", article =>
            {
                article.innerJoin("group");
                article.innerJoin("category");
                article.innerJoin("__mv_last").innerJoin("content");
            });
            question.leftJoin("answer")
                .leftJoin("base", "AA")
                .leftJoin("__mv_last", "AL")
                .leftJoin("content", "AC");
        })
        .andWhere(...BbsArticle.getWhereArguments("group", group))
        .andWhere(...BbsCategory.getWhereArguments("code", "!=", category.code))
        .select([
            BbsArticle.getColumn("id"),
            BbsGroup.getColumn("name", "group"),
            BbsCategory.getColumn("name", "category"),
            BbsArticle.getColumn("writer"),
            BbsArticleContent.getColumn("title"),
            BbsArticle.getColumn("created_at"),
            BbsArticleContent.getColumn("created_at", "updated_at"),

            BbsArticle.getColumn("AA.writer", "answer_writer"),
            BbsArticleContent.getColumn("AA.title", "answer_title"),
            BbsArticle.getColumn("AA.created_at", "answer_created_at"),
        ]);
    stmt;
}

App Join Builder

Safe Query Builder

With the AppJoinBuilder class, you can implement application level joining very easily.

Also, grammer of the AppJoinBuilder is exactly same with the JoinQueryBuilder. Therefore, you can swap JoinQueryBuilder and AppJoinBuilder very simply without any cost. Thus, you can just select one of them suitable for your case.

export async function test_app_join_builder(): Promise<void>
{
    const builder: safe.AppJoinBuilder<BbsReviewArticle> = safe
        .createAppJoinBuilder(BbsReviewArticle, review =>
        {
            review.join("base", article =>
            {
                article.join("group");
                article.join("category");
                article.join("contents", content =>
                {
                    content.join("reviewContent");
                    content.join("files");
                });
                article.join("comments").join("files");
            });
        });
}

Furthermore, you've determined to using only the AppJoinBuilder, you can configure it much safely. With the AppJoinBuilder.initialize() method, you've configure all of the relationship accessors, and it prevents any type of ommission by your mistake.

export async function test_app_join_builder_initialize(): Promise<void>
{
    const builder = safe.AppJoinBuilder.initialize(BbsGroup, {
        articles: safe.AppJoinBuilder.initialize(BbsArticle, {
            group: undefined,
            review: safe.AppJoinBuilder.initialize(BbsReviewArticle, {
                base: undefined,
            }),
            category: safe.AppJoinBuilder.initialize(BbsCategory, {
                articles: undefined,
                children: undefined,
                parent: "recursive"
            }),
            contents: safe.AppJoinBuilder.initialize(BbsArticleContent, {
                article: undefined,
                files: "join"
            }),
            comments: safe.AppJoinBuilder.initialize(BbsComment, {
                article: undefined,
                files: "join"
            }),
            tags: "join",
            __mv_last: undefined,
            question: undefined,
            answer: undefined,
        })
    });
}

JSON Select Builder

Class Diagram

In safe-typeorm, when you want to load DB records and convert them to a JSON data, you don't need to write any SELECT or JOIN query. You also do not need to consider any performance tuning. Just write down the ORM -> JSON conversion plan, then safe-typeorm will do everything.

The JsonSelectBuilder is the class doing everything. It will analyze your JSON conversion plan, and compose the JSON conversion method automatically with the exact JSON type what you want. Furthermore, the JsonSelectBuilder finds the best (applicataion level) joining plan by itself, when being constructed.

Below code is an example converting ORM model class instances to JSON data with the JsonSelectBuilder. As you can see, there's no special script in the below code, but only the conversion plan is. As I've mentioned, JsonSelectBuilder will construct the exact JSON type by analyzing your conversion plan. Also, the performance tuning would be done automatically.

Therefore, just enjoy the JsonSelectBuilder without any worry.

export async function test_json_select_builder(models: BbsGroup[]): Promise<void>
{
    const builder = BbsGroup.createJsonSelectBuilder
    ({
        articles: BbsArticle.createJsonSelectBuilder
        ({
            group: safe.DEFAULT, // ID ONLY
            category: BbsCategory.createJsonSelectBuilder
            ({ 
                parent: "recursive" as const, // RECURSIVE JOIN
            }),
            tags: BbsArticleTag.createJsonSelectBuilder
            (
                {}, 
                tag => tag.value // OUTPUT CONVERSION BY MAPPING
            ),
            contents: BbsArticleContent.createJsonSelectBuilder
            ({
                files: "join" as const
            }),
        })
    });

    // GET JSON DATA FROM THE BUILDER
    const raw = await builder.getMany(models);

    // THE RETURN TYPE IS ALWAYS EXACT
    // THEREFORE, TYPEOF "RAW" AND "I-BBS-GROUP" ARE EXACTLY SAME
    const regular: IBbsGroup[] = raw;
    const inverse: typeof raw = regular;
}

Insert Collection

When you want to execute INSERT query for lots of records of plural tables, you've to consider dependency relationships. Also, you may construct extended SQL query manually by yourself, if you're interested in the performance tuning.

However, with the InsertCollection class provided by this safe-typeorm, you don't need to consider any dependcy relationship. You also do not need to consider any performance tuning. The InsertCollection will analyze the dependency relationships and orders the insertion sequence automatically. Also, the InsertCollection utilizes the extended insertion query for the optimizing performance.

import safe from "safe-typeorm";
import std from "tstl";

export async function archive
    (
        comments: BbsComment[],
        questions: BbsQuestionArticle[],
        reviews: BbsArticleReview[],
        groups: BbsGroup[],
        files: AttachmentFile[],
        answers: BbsAnswerArticle[],
        categories: BbsCategory[],
        comments: BbsComment[],
        articles: BbsArticle[],
        contents: BbsArticleContent[],
        tags: BbsArticleTag[],
    ): Promise<void>
{
    // PREPARE A NEW COLLECTION
    const collection: safe.InsertCollection = new safe.InsertCollection();
    
    // PUSH TABLE RECORDS TO THE COLLECTION WITH RANDOM SHULFFLING
    const massive = [
        comments,
        questions,
        reviews,
        groups,
        files,
        answers,
        comments,
        articles,
        contents,
        tags
    ];
    std.ranges.shuffle(massive);
    for (const records of massive)
        collection.push(records);

    // PUSH INDIVIDUAL RECORDS
    for (const category of categories)
        collection.push(category);
    
    // EXECUTE THE INSERT QUERY
    await collection.execute();
}

Appendix

TypeORM

typeorm#8184

I've awaited next version of the typeorm for many years, and I can't wait no more.

So I've decided to implement the next version by myself. I'd wanted to contribute to this typeorm after the next version implementation has been completed, but it was not possible by critical change on the relationship definition like Has.OneToMany or Belongs.ManyToOne. Therefore, I've published the next version as a helper library of thetypeorm.

I dedicate this safe-typeorm to the typeorm. If developers of the typeorm accept the critical change on the relationship definition, it would be the next version of the typeorm. Otherwise they reject, this safe-typeorm would be left as a helper library like now.

Nestia

https://github.com/samchon/nestia

nestia is another library what I've developed, automatic SDK generator for the NestJS backend server. With those safe-typeorm and nestia, you can reduce lots of costs and time for developing the backend server.

When you're developing a backend server using the NestJS, you don't need any extra dedication, for delivering the Rest API to the client developers, like writing the swagger comments. You just run the nestia up, then nestia would generate the SDK automatically, by analyzing your controller classes in the compliation and runtime level.

With the automatically generated SDK through the nestia, client developer also does not need any extra work, like reading swagger and writing the duplicated interaction code. Client developer only needs to import the SDK and calls matched function with the await symbol.

import api from "@samchon/bbs-api";
import { IBbsArticle } from "@samchon/bbs-api/lib/structures/bbs/IBbsArticle";
import { IPage } from "@samchon/bbs-api/lib/structures/common/IPage";

export async function test_article_read(connection: api.IConnection): Promise<void>
{
    // LIST UP ARTICLE SUMMARIES
    const index: IPage<IBbsArticle.ISummary> = await api.functional.bbs.articles.index
    (
        connection,
        "free",
        { limit: 100, page: 1 }
    );

    // READ AN ARTICLE DETAILY
    const article: IBbsArticle = await api.functional.bbs.articles.at
    (
        connection,
        "free",
        index.data[0].id
    );
    console.log(article.title, aritlce.body, article.files);
}

Archidraw

https://www.archisketch.com/

I have special thanks to the Archidraw, where I'm working for.

The Archidraw is a great IT company developing 3D interior editor and lots of solutions based on the 3D assets. Also, the Archidraw is the first company who had adopted safe-typeorm on their commercial backend project, even safe-typeorm was in the alpha level.