{"id":15495098,"url":"https://github.com/samchon/safeorm","last_synced_at":"2025-04-22T20:27:12.288Z","repository":{"id":57144432,"uuid":"453544618","full_name":"samchon/safeorm","owner":"samchon","description":"Ultimate Safe ORM for the TypeScript","archived":false,"fork":false,"pushed_at":"2022-02-06T15:23:29.000Z","size":57,"stargazers_count":4,"open_issues_count":2,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-20T03:32:22.579Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/samchon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-01-29T23:36:12.000Z","updated_at":"2023-12-20T01:33:58.000Z","dependencies_parsed_at":"2022-09-06T08:53:52.001Z","dependency_job_id":null,"html_url":"https://github.com/samchon/safeorm","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samchon%2Fsafeorm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samchon%2Fsafeorm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samchon%2Fsafeorm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samchon%2Fsafeorm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/samchon","download_url":"https://codeload.github.com/samchon/safeorm/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250317827,"owners_count":21410824,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-02T08:16:05.926Z","updated_at":"2025-04-22T20:27:12.268Z","avatar_url":"https://github.com/samchon.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# **SafeORM**\n## Outline\n\u003e Development of the `safeorm` has not been completed yet.\n\u003e\n\u003e Until the completion, only interfaces and test automation programs are being provided. When the development has been completed, its 1st version `0.1.0` would be published. Before the completion, version of the `safeorm` would keep the `0.0.x`.\n\n[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/samchon/safeorm/blob/master/LICENSE)\n[![npm version](https://badge.fury.io/js/safeorm.svg)](https://www.npmjs.com/package/safeorm)\n[![Downloads](https://img.shields.io/npm/dm/safeorm.svg)](https://www.npmjs.com/package/safeorm)\n[![Build Status](https://github.com/samchon/safeorm/workflows/build/badge.svg)](https://github.com/samchon/safeorm/actions?query=workflow%3Abuild)\n\n```bash\nnpm install --save safeorm\n```\n\nThe ultimate **Safe ORM** library for the TypeScript.\n\n  - Repository: https://github.com/samchon/safeorm\n  - ~~Guide Documents: https://github.com/samchon/safeorm/wiki~~\n  - ~~API Documents: https://samchon.github.io/safeorm/api~~\n  - Template Project: https://github.com/samchon/backend\n\n\n\n\n## Demonstrations\nWith the **SafeORM**, you can define database and programmable type at the same time through the \u003cfont color=\"green\"\u003eTMP\u003c/font\u003e (\u003cfont color=\"green\"\u003eType Meta Programming\u003c/font\u003e). Also, such type would be utilized in the whole **SafeORM** components level. Therefore, you can be helped by auto-completion with type hint when writing SQL query or planning App join.\n\nFurthermore, you don't need to worry about any type of mistake when writing SQL query or planning App join. Your mistake would be caught in the compilation level. Therefore, if you type a wrong word on the SQL query, it would be enhanced by IDE through the \u003cfont color=\"red\"\u003e\u003cu\u003ered underline\u003c/u\u003e\u003c/font\u003e.\n\nUnlike other ORM libraries who've to define DB and programmable type duplicatedly and cause \u003cfont color=\"red\"\u003ecritical runtime error\u003c/font\u003e by not supporting the \u003cfont color=\"green\"\u003eTMP\u003c/font\u003e, **SafeORM** supports those features through below components and they make the DB development to be much safer. Read below components and feel how \u003cfont color=\"green\"\u003esafe\u003c/font\u003e it is.\n\n  - [Entity](#entity): Define DB and TypeScript type at the time by \u003cfont color=\"green\"\u003eTMP\u003c/font\u003e\n  - [QueryBuilder](#querybuilder): Auto completion with type hint\n  - [bindAppJoin](#bindappjoin): Lazy app join binder, extremely \u003cfont color=\"purple\"\u003eeasy\u003c/font\u003e but powerful \u003cfont color=\"purple\"\u003eperformance\u003c/font\u003e\n  - [AppJoinBuilder](#appjoinbuilder): Eager app join builder, same grammer with the [QueryBuilder](#querybuilder)\n  - [JsonBuilder](#jsonselectbuilder): \u003cfont color=\"orange\"\u003eJSON\u003c/font\u003e converter without any SQL query\n  - [InsertCollection](#insertcollection): Massive insertion without considering any dependency relationship\n\n### Entity\n\u003e Template Meta Programming\n\nAs you can see from the below example code, **SafeORM** supports the TMP (Type Meta Programming) ORM who can define database type and programmable type at the same time. When you define a column with database type, **SafeORM** would convert it to the programmable type automatically. \n\nSuch automatic conversion even works for the relationship type. If you define a dependency relationship of the database, **SafeORM** converts it to the programmable relationship type, too. As you can see from the below, relationship functions like `Belongs.ManyToOne` and `Has.ManyToMany` define the database relationships and programmable accessors, at the same time.\n\nAlso, such `Entity` types can be utilized in whole components of the **SafeORM**. For an example, [QueryBuilder](#querybuilder) realizes the auto completion with type hint by analyzing the target `Entity` types.\n\n```typescript\nimport safe from \"safeorm\";\nimport { BbsArticleTag } from \"./BbsArticleTag\";\nimport { BbsComment } from \"./BbsComment\";\nimport { BbsGroup } from \"./BbsGroup\";\n\nexport class BbsArticle\n{\n    //----\n    // COLUMNS\n    //----\n    // BE `string`\n    public readonly id = safe.PrimaryGeneratedColumn(\"uuid\");\n\n    // BE `Belongs.ManyToOne\u003cBbsGroup, { nullable: true }\u003e`\n    //\n    // `group.id` -\u003e `string|null`\n    // `group.get()` -\u003e `Promise\u003cBbsGroup|null\u003e`\n    public readonly group = safe.Belongs.ManyToOne\n    (\n        () =\u003e BbsGroup,\n        { nullable: true, index: true }\n    );\n\n    // BE `string`\n    public readonly title = safe.Column(\"varchar\"); \n\n    // BE `string | null`\n    public readonly sub_title = safe.Column(\"varchar\", { nullable: true });\n    public readonly title = safe.Column(\"varchar\"); // `string`\n    public readonly body = safe.Column(\"text\"); // `string`\n    public readonly created_at = safe.CreateDateColumn(\"datetime\"); // `Date`\n    public readonly updated_at = safe.UpdateDateColumn(\"datetime\"); // `Date`\n    public readonly deleted_at = safe.DeleteDateColumn(\"datetime\"); // `Date | null`\n\n    // BE `number`\n    public hit = safe.Column(\"int\");\n\n    //----\n    // HAS\n    //----\n    // BE `Has.OneToMany\u003cBbsArticleTag\u003e`\n    //\n    // `tags.get() -\u003e Promise\u003cBbsArticleTag\u003e`\n    public readonly tags = safe.Has.OneToMany\n    (\n        () =\u003e BbsArticleTag, \n        tag =\u003e tag.article\n    );\n\n    // BE `Has.OneToMany\u003cBbsComment\u003e`\n    public readonly comments = safe.Has.OneToMany\n    (\n        () =\u003e BbsComment, \n        comment =\u003e comment.article\n    );\n\n    // BE `Has.ManyToMany`\n    //\n    // `files.get() -\u003e Promise\u003cAttachmentFile\u003e`\n    public readonly files = safe.Has.ManyToMany\n    (\n        () =\u003e AttachmentFile,\n        () =\u003e BbsArticleFile,\n        router =\u003e router.file,\n        router =\u003e router.article,\n        (x, y) =\u003e x.router.sequence - y.router.sequence // SORT FUNCTION\n    );\n}\nsafe.Index(BbsArticle, [\"created_at\", \"deleted_at\"]); // COMPOSITE INDEX\nsafe.Index(BbsArticle, \"title\"); // SINGULAR INDEX\nsafe.Table(BbsArticle); // BE TABLE\n```\n\n### QueryBuilder\n\u003e Extremely safe with the TMP.\n\nWhen you've defined some [Entities](#entity), you can compose SQL query very easily and safely by this `QueryBuilder`. The `QueryBuilder` analyzes those [Entities](#entity) and supports the auto completion with type hint. \n\nAlso, some mistakes like mis-written column name would be automatically detected in the compilation level. Therefore, you don't need to worry about any type of mistake when wrting the SQL query. All of the mistakes would be enhanced by IDE by the \u003cfont color=\"red\"\u003e\u003cu\u003ered underline\u003c/u\u003e\u003c/font\u003e. \n\nLook at the below gif image and feel how strong it is. Other ORM libraries like *TypeORM* never can provide such beautiful \u003cfont color=\"green\"\u003eTMP\u003c/font\u003e (\u003cfont color=\"green\"\u003eType Meta Programming\u003c/font\u003e). They may cause \u003cfont color=\"red\"\u003ethe critical runtime error\u003c/font\u003e for the mis-writiten SQL query.\n\n![Safe Query Builder](https://raw.githubusercontent.com/samchon/safe-typeorm/master/assets/demonstrations/safe-query-builder.gif)\n\n```typescript\nexport async function test_safe_query_builder(): Promise\u003cvoid\u003e\n{\n    const group: BbsGroup = await BbsGroup.findOneOrFail();\n    const category: BbsCategory = await BbsCategory.findOneOrFail();\n\n    const stmt: safe.SelectQueryBuilder\u003cBbsQuestionArticle\u003e = safe\n        .createJoinQueryBuilder(BbsQuestionArticle, question =\u003e\n        {\n            question.innerJoin(\"base\", article =\u003e\n            {\n                article.innerJoin(\"group\");\n                article.innerJoin(\"category\");\n                article.innerJoin(\"__mv_last\").innerJoin(\"content\");\n            });\n            question.leftJoin(\"answer\")\n                .leftJoin(\"base\", \"AA\")\n                .leftJoin(\"__mv_last\", \"AL\")\n                .leftJoin(\"content\", \"AC\");\n        })\n        .andWhere(...BbsArticle.getWhereArguments(\"group\", group))\n        .andWhere(...BbsCategory.getWhereArguments(\"code\", \"!=\", category.code))\n        .select([\n            BbsArticle.getColumn(\"id\"),\n            BbsGroup.getColumn(\"name\", \"group\"),\n            BbsCategory.getColumn(\"name\", \"category\"),\n            BbsArticle.getColumn(\"writer\"),\n            BbsArticleContent.getColumn(\"title\"),\n            BbsArticle.getColumn(\"created_at\"),\n            BbsArticleContent.getColumn(\"created_at\", \"updated_at\"),\n\n            BbsArticle.getColumn(\"AA.writer\", \"answer_writer\"),\n            BbsArticleContent.getColumn(\"AA.title\", \"answer_title\"),\n            BbsArticle.getColumn(\"AA.created_at\", \"answer_created_at\"),\n        ]);\n}\n```\n\n### bindAppJoin\n\u003e Extremely easy but powerful performance.\n\nThe `bindAppJoin` is a function who binds an ORM object (or objects) to perform the lazy application level join. With the lazy app join binding, relationship accessors would never call the repeated `SELECT` query for the same type. The `SELECT` query would be occured only when same type of the relationship accessor has been called at fisrt.\n\nIf that description is hard to understand, read the below code, then you may understand. As you can see from the below, the example code called relationshiop accessor for only one instance `topArticle: BbsArticle`. However, another `BbsArticle` instances also performs the application level join at the same time and never calls the `SELECT` query again. It's the lazy app join.\n\nAlso, as you can see from the below description and benchmark result of the App join, the App join is much fater and consumes much fewer resources than the DB join query. Even implementation code of the App join is much easier than the DB join in this **SafeORM** library. Therefore, I say with condidence, \"There's no reason to denying this `bindAppJoin` function\".\n\n\u003e The application level join means that joining related records are done not by the DB join query, but through the manual application code using the *HashMap* with their primary and foreign key values mapping.\n\u003e\n\u003e Comparing those DB join query and application level joining, the application level joining consumes much fewer resources and its elapsed time is also much shorter than the DB join query. Those differences grow up whenever the join relationship be more compliate.\n\u003e\n\u003e  - [stackoverflow/join-queries-vs-multiple-queries](https://stackoverflow.com/questions/1067016/join-queries-vs-multiple-queries)\n\u003e\n\u003e Type         | DB Join   | App Join  |\n\u003e :------------|----------:|----------:|\n\u003e Records      | 2,258,000 | 165       |\n\u003e Elapsed Time | 8.07508   | 0.00262   |\n\n```typescript\nexport async function test_lazy_app_join(): Promise\u003cvoid\u003e\n{\n    const group: BbsGroup = await BbsGroup.findOneOrFail();\n    safe.bindAppJoin(group);\n\n    const articleList: BbsArticle[] = await group.articles.get();\n    const topArticle: BbsArticle = articleList[0];\n\n    // APP-JOIN WOULD BE DONE\n    await topArticle.comments.get();\n    await topArticle.files.get();\n    await topArticle.tags.get();\n\n    // ANY SELECT QUERY WOULD BE OCCURED\n    await must_not_query_anything(\"bindAppJoin\", async () =\u003e\n    {\n        for (const article of articleList)\n        {\n            await article.comments.get();\n            await article.files.get();\n            await article.tags.get();\n        }\n    });\n}\n```\n\n### AppJoinBuilder\nWith the `AppJoinBuilder` class, you can implement eager application level join. \n\nAlso, grammer of the `AppJoinBuilder` is exactly same with the `QueryBuilder`. Therefore, you can swap `QueryBuilder` and `AppJoinBuilder` very simply without any cost. Thus, you can just select one of them suitable for your case.\n\n![AppJoinBuilder](https://raw.githubusercontent.com/samchon/safe-typeorm/master/assets/demonstrations/app-join-builder.gif)\n\n```typescript\nexport async function test_app_join_builder(): Promise\u003cvoid\u003e\n{\n    const builder: safe.AppJoinBuilder\u003cBbsReviewArticle\u003e = safe\n        .createAppJoinBuilder(BbsReviewArticle, review =\u003e\n        {\n            review.join(\"base\", article =\u003e\n            {\n                article.join(\"group\");\n                article.join(\"category\");\n                article.join(\"contents\", content =\u003e\n                {\n                    content.join(\"reviewContent\");\n                    content.join(\"files\");\n                });\n                article.join(\"comments\").join(\"files\");\n            });\n        });\n}\n```\n\nFurthermore, 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.\n\n```typescript\nexport async function test_app_join_builder_initialize(): Promise\u003cvoid\u003e\n{\n    const builder = safe.AppJoinBuilder.initialize(BbsGroup, {\n        articles: safe.AppJoinBuilder.initialize(BbsArticle, {\n            group: undefined,\n            review: safe.AppJoinBuilder.initialize(BbsReviewArticle, {\n                base: undefined,\n            }),\n            category: safe.AppJoinBuilder.initialize(BbsCategory, {\n                articles: undefined,\n                children: undefined,\n                parent: \"recursive\"\n            }),\n            contents: safe.AppJoinBuilder.initialize(BbsArticleContent, {\n                article: undefined,\n                files: \"join\"\n            }),\n            comments: safe.AppJoinBuilder.initialize(BbsComment, {\n                article: undefined,\n                files: \"join\"\n            }),\n            tags: \"join\",\n            __mv_last: undefined,\n            question: undefined,\n            answer: undefined,\n        })\n    });\n}\n```\n\n### JsonSelectBuilder\n![Class Diagram](https://raw.githubusercontent.com/samchon/safe-typeorm/master/assets/designs/class-diagram.png)\n\n\u003e JSON converter without any SQL query\n\nIn the **SafeORM**, when you want to load DB records and convert them to a \u003cfont color=\"orange\"\u003eJSON\u003c/font\u003e 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 -\u003e JSON` conversion plan, then **SafeORM** will do everything.\n\nThe `JsonSelectBuilder` is the class doing everything. It will analyze your \u003cfont color=\"orange\"\u003eJSON\u003c/font\u003e conversion plan, and compose the \u003cfont color=\"orange\"\u003eJSON\u003c/font\u003e conversion method automatically with the exact \u003cfont color=\"orange\"\u003eJSON\u003c/font\u003e type what you want. Furthermore, the `JsonSelectBuilder` finds the best (applicataion level) joining plan by itself.\n\nBelow code is an example converting ORM model class instances to \u003cfont color=\"orange\"\u003eJSON\u003c/font\u003e data with the `JsonSelectBuilder`. As you can see, there's no special script in the below code, but only the conversion plan exists. As I've mentioned, `JsonSelectBuilder` will construct the exact \u003cfont color=\"orange\"\u003eJSON\u003c/font\u003e type by analyzing your conversion plan. Also, the performance tuning would be done automatically. \n\nTherefore, just enjoy the `JsonSelectBuilder` without any worry.\n\n```typescript\nexport async function test_json_select_builder(models: BbsGroup[]): Promise\u003cvoid\u003e\n{\n    const builder = BbsGroup.createJsonSelectBuilder\n    ({\n        articles: BbsArticle.createJsonSelectBuilder\n        ({\n            group: safe.DEFAULT, // ID ONLY\n            category: BbsCategory.createJsonSelectBuilder\n            ({ \n                parent: \"recursive\" as const, // RECURSIVE JOIN\n            }),\n            tags: BbsArticleTag.createJsonSelectBuilder\n            (\n                {}, \n                tag =\u003e tag.value // OUTPUT CONVERSION BY MAPPING\n            ),\n            contents: BbsArticleContent.createJsonSelectBuilder\n            ({\n                files: \"join\" as const\n            }),\n        })\n    });\n\n    // GET JSON DATA FROM THE BUILDER\n    const raw = await builder.getMany(models);\n\n    // THE RETURN TYPE IS ALWAYS EXACT\n    // THEREFORE, TYPEOF \"RAW\" AND \"I-BBS-GROUP\" ARE EXACTLY SAME\n    const regular: IBbsGroup[] = raw;\n    const inverse: typeof raw = regular;\n}\n```\n\n### InsertCollection\n\u003e Massive insertion without considering any dependency relationship\n\nWhen 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.\n\nHowever, with the `InsertCollection` class provided by this **SafeORM**, 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.\n\n```typescript\nimport safe from \"safeorm\";\nimport std from \"tstl\";\n\nexport async function archive\n    (\n        comments: BbsComment[],\n        questions: BbsQuestionArticle[],\n        reviews: BbsArticleReview[],\n        groups: BbsGroup[],\n        files: AttachmentFile[],\n        answers: BbsAnswerArticle[],\n        categories: BbsCategory[],\n        comments: BbsComment[],\n        articles: BbsArticle[],\n        contents: BbsArticleContent[],\n        tags: BbsArticleTag[],\n    ): Promise\u003cvoid\u003e\n{\n    // PREPARE A NEW COLLECTION\n    const collection: safe.InsertCollection = new safe.InsertCollection();\n    \n    // PUSH TABLE RECORDS TO THE COLLECTION WITH RANDOM SHULFFLING\n    const massive = [\n        comments,\n        questions,\n        reviews,\n        groups,\n        files,\n        answers,\n        comments,\n        articles,\n        contents,\n        tags\n    ];\n    std.ranges.shuffle(massive);\n    for (const records of massive)\n        collection.push(records);\n\n    // PUSH INDIVIDUAL RECORDS\n    for (const category of categories)\n        collection.push(category);\n    \n    // EXECUTE THE INSERT QUERY\n    await collection.execute();\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamchon%2Fsafeorm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamchon%2Fsafeorm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamchon%2Fsafeorm/lists"}