Coder Social home page Coder Social logo

safe-typeorm's Introduction

Safe-TypeORM

logo

GitHub license npm version Downloads Build Status Guide Documents

Make anyorm to be real typeorm.

safe-typeorm is a helper library of typeorm, enhancing type safety like below:

  • 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

JoinQueryBuilder

Setup

npm install --save [email protected]
npm install --save safe-typeorm

Just install through npm install command.

Note that, safe-typeorm supports only typeorm v0.2 yet.

Features

About supported features, see Guide Documents

Appendix

Typia

GitHub license npm version Downloads Build Status Guide Documents

// RUNTIME VALIDATORS
export function is<T>(input: unknown | T): input is T; // returns boolean
export function assert<T>(input: unknown | T): T; // throws TypeGuardError
export function validate<T>(input: unknown | T): IValidation<T>; // detailed

// STRICT VALIDATORS
export function equals<T>(input: unknown | T): input is T;
export function assertEquals<T>(input: unknown | T): T;
export function validateEquals<T>(input: unknown | T): IValidation<T>;

// JSON
export function application<T>(): IJsonApplication; // JSON schema
export function assertParse<T>(input: string): T; // type safe parser
export function assertStringify<T>(input: T): string; // safe and faster
    // +) isParse, validateParse 
    // +) stringify, isStringify, validateStringify

Typia is a transformer library of TypeScript, supporting below features:

  • Super-fast Runtime Validators
  • Safe JSON parse and fast stringify functions
  • JSON schema generator

All functions in typia require only one line. You don't need any extra dedication like JSON schema definitions or decorator function calls. Just call typia function with only one line like typia.assert<T>(input).

Also, as typia performs AOT (Ahead of Time) compilation skill, its performance is much faster than other competitive libaries. For an example, when comparing validate function is() with other competitive libraries, typia is maximum 15,000x times faster than class-validator.

Nestia

GitHub license npm version Downloads Build Status Guide Documents

Nestia is a set of helper libraries for NestJS, supporting below features:

  • @nestia/core: 15,000x times faster validation decorators
  • @nestia/sdk: evolved SDK and Swagger generators
    • SDK (Software Development Kit)
      • interaction library for client developers
      • almost same with tRPC
  • nestia: just CLI (command line interface) tool

nestia-sdk-demo

Reactia

Not published yet, but soon

GitHub license Build Status Guide Documents

Reactia is an automatic React components generator, just by analyzing TypeScript type.

  • @reactia/core: Core Library analyzing TypeScript type
  • @reactia/mui: Material UI Theme for core and nest
  • @reactia/nest: Automatic Frontend Application Builder for NestJS

Sample

When you want to automate an individual component, just use @reactia/core.

import ReactDOM from "react-dom";

import typia from "typia";
import { ReactiaComponent } from "@reactia/core";
import { MuiInputTheme } from "@reactia/mui";

const RequestInput = ReactiaComponent<IRequestDto>(MuiInputTheme());
const input: IRequestDto = { ... };

ReactDOM.render(
    <RequestInput input={input} />,
    document.body
);

Otherwise, you can fully automate frontend application development through @reactia/nest.

import React from "react";
import ReactDOM from "react-dom";

import { ISwagger } "@nestia/swagger";
import { MuiApplicationTheme } from "@reactia/mui";
import { ReactiaApplication } from "@reactia/nest";

// swagger.json must be generated by @nestia/sdk
const swagger: ISwagger = await import("./swagger.json");
const App: React.FC = ReactiaApplication(MuiApplicationTheme())(swagger);

ReactDOM.render(
    <App />,
    document.body
);

safe-typeorm's People

Contributors

samchon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

safe-typeorm's Issues

`AppJoinBuilder` and `JsonSelectBuilder` to support the recursive join

export namespace JsonSelectBuilder
{
    export type Input<Mine extends object> = OmitNever<
    {
        [P in keyof Mine]: Mine[P] extends Relationship<infer Target>
            ? Mine[P] extends Belongs.ManyToOne<Target, any, infer TargetOptions>
                ? TargetOptions extends { nullable: true }
                    ? Same<Mine, Target> extends true
                        ? "recursive" | DEFAULT | undefined
                        : JsonSelectBuilder<Target, any, any> | DEFAULT | undefined
                    : Same<Mine, Target> extends true 
                        ? "recursive" | DEFAULT | undefined
                        : JsonSelectBuilder<Target, any, any> | DEFAULT | undefined
            : Same<Mine, Target> extends true 
                ? "recursive" | undefined
                : JsonSelectBuilder<Target, any, any> | undefined
            : never
    }>;

    export type Output<Mine extends object, InputT extends object> = Primitive<Mine> & OmitNever<
    {
        [P in keyof (Mine|InputT)]
            : InputT[P] extends JsonSelectBuilder<infer Target, any, infer Destination>
                ? Mine[P] extends Belongs.ManyToOne<Target, any, infer Options>
                    ? Options extends { nullable: true }
                        ? Destination | null
                        : Destination
                : Mine[P] extends Has.OneToOne<Target, infer Ensure>
                    ? Ensure extends true
                        ? Destination
                        : Destination | null
                : Destination[]
            : InputT[P] extends "recursive"
                ? Mine[P] extends Belongs.ManyToOne<Mine, any, infer Options>
                    ? Options extends { nullable: true }
                        ? Output.RecursiveReference<Mine, P> | null
                        : never // cannot be happend
                : Mine[P] extends Has.OneToOne<Mine, infer Ensure>
                    ? Ensure extends true
                        ? never // cannot be happend
                        : Output.RecursiveReference<Mine, P> | null
                : Mine[P] extends Has.OneToMany<Mine> 
                    ? Output.RecursiveArray<Mine, P>
                : Mine[P] extends Has.ManyToMany<Mine, any> 
                    ? Output.RecursiveArray<Mine, P>
                : never
            : InputT[P] extends DEFAULT 
                ? Mine[P] extends Belongs.ManyToOne<any, infer PrimaryKey, infer Options>
                    ? Options extends { nullable: true }
                        ? PrimaryGeneratedColumn.ValueType<PrimaryKey> | null
                        : PrimaryGeneratedColumn.ValueType<PrimaryKey>
                    : never
            : never;
    }>;
    export namespace Output
    {
        export type RecursiveReference<
                Mine extends object,
                Key extends keyof Mine>
            = Primitive<Mine> &
        {
            [P in Key]: RecursiveReference<Mine, Key> | null;
        }

        export type RecursiveArray<
                Mine extends object, 
                Key extends keyof Mine>
            = Primitive<Mine> &
        {
            [P in Key]: RecursiveArray<Mine, Key>[];
        }
    }
    
    export type OutputMapper<Mine extends object, InputT extends Input<Mine>, Destination> 
        = (output: Output<Mine, InputT>, index: number, array: Output<Mine, InputT>[]) => Destination;
}

`JsonSelectBuilder.Input` to be much conciser

I've designed the JsonSelectBuilder.Input to list up all of the columns and implemented it successfully.

However, when I use the JsonSelectBuilder.Input by myself, listing up all of the columns was really annyoing. Therefore, I will configure the JsonSelectBuilder.Input to specify only relationship and change the JsonSelectBuilder.Output to combine with the Primitive type like below:

export namespace JsonSelectBuilder
{
    export type Input<Mine extends object> = OmitNever<
    {
        [P in keyof Mine]: Mine[P] extends Relationship<infer Target>
            ? Mine[P] extends Belongs.ManyToOne<Target, any, infer TargetOptions>
                ? TargetOptions extends { nullable: true }
                    ? JsonSelectBuilder<Target, any, any> | DEFAULT | undefined
                    : JsonSelectBuilder<Target, any, any> | DEFAULT | undefined
            : JsonSelectBuilder<Target, any, any> | undefined
            : never
    }>;

    export type Output<Mine extends object, InputT extends object> = Primitive<Mine> & OmitNever<
    {
        [P in keyof (Mine|InputT)]
            : InputT[P] extends JsonSelectBuilder<infer Target, any, infer Destination>
                ? Mine[P] extends Belongs.ManyToOne<Target, any, infer Options>
                    ? Options extends { nullable: true }
                        ? Destination | null
                        : Destination
                : Mine[P] extends Has.OneToOne<Target, infer Ensure>
                    ? Ensure extends true
                        ? Destination
                        : Destination | null
                : Destination[]
            : InputT[P] extends DEFAULT 
                ? Mine[P] extends Belongs.ManyToOne<any, infer PrimaryKey, infer Options>
                    ? Options extends { nullable: true }
                        ? PrimaryGeneratedColumn.ValueType<PrimaryKey> | null
                        : PrimaryGeneratedColumn.ValueType<PrimaryKey>
                    : never
            : never;
    }>;
    
    export type OutputMapper<Mine extends object, InputT extends Input<Mine>, Destination> 
        = (output: Output<Mine, InputT>, index: number, array: Output<Mine, InputT>[]) => Destination;
}

`findRepository()` and `Model.useAdequateConnections()` for multiple DB connections

TypeORM supports multiple DB connections, but it doesn't support the automatic individual table matching with its source DB connection. Therefore, it can't perform such statement in the global level. Below BbsGroup doesn't have its connection information, therefore such getRepository() and findOneOrFail() methods would target the default connection.

BbsGroup.getRepository();
BbsGroup.findOneOrFail("some-id");

To solve such critical bugs of the TypeORM, I've decided to support those two methods, which can assign matched connection information to the individual ORM entity classes. The findRepository() is a global function who can find the exact repository information based on the matched connection. The Model.useAdequateConnections() is another global function who assigns matched connection information to the individual ORM entity classes.

// ALWAYS POSSIBLE
safe.findRepository(BbsGroup);

// FIND-ONE-OR-FAIL WOULD BE POSSIBLE AFTER THE USE-CONNECTIONS()
safe.Model.useAdequateConnections();
BbsGroup.findOneOrFail("some-id");

Refactor `Has` with critical section

export namespace Has
{
    /* -----------------------------------------------------------
        ONE-TO-MANY
    ----------------------------------------------------------- */
    export type OneToMany<Target extends object> = OneToMany.Accessor<Target>;
    export function OneToMany<Mine extends object>
        (
            targetGen: Creator.Generator<Target>,
            inverse: (input: Target) =>  Belongs.ManyToOne<Mine, any>
        ): PropertyDecorator

    export namespace OneToMany
    {
        export class Accessor<Target extends object>
        {
            private mutex_: SharedMutex;

            public get(): Promise<Target[]>;
            public set(value: Target[]): Promise<void>;
        }
    }

    /* -----------------------------------------------------------
        ONE-TO-ONE
    ----------------------------------------------------------- */
    export type OneToOne<Target extends object, Ensure extends boolean = false>
        = OneToOne.Accessor<Target, Ensure extends true ? Target : Target | null>;

    export function OneToOne<Mine extends object, Target extends object>
        (
            targetGen: Creator.Generator<Target>,
            inverse: (input: Target) =>  Belongs.OneToOne<Mine, any>
        ): PropertyDecorator

    export namespace OneToOne
    {
        export class Accessor<Target extends object, Output extends Target | null>
        {
            private mutex_: SharedMutex;

            public get(): Promise<Output>;
            public set(value: Output): Promise<void>;
        }
    }

    /* -----------------------------------------------------------
        MANY-TO-MANY
    ----------------------------------------------------------- */
    export type ManyToMany<Target extends object, Router extends object> = ManyToMany.Accessor<Target, Router>;
    export function ManyToMany<Mine extends object, Target extends object, Router extends object>
        (
            targetGen: Creator.Generator<Target>,
            routerGen: Creator.Generator<Router>,
            targetInverse: (router: Router) => Belongs.ManyToOne<Target, any>,
            myInverse: (router: Router) => Belongs.ManyToOne<Mine, any>,
            comp?: (x: ManyToMany.ITuple<Target, Router>, y: ManyToMany.ITuple<Target, Router>) => boolean
        ): PropertyDecorator;

    export namespace ManyToMany
    {
        export class Accessor<Target extends object, Router extends object>
        {
            private getter_: Singleton<Target[]>;

            public get(): Promise<Target[]>;
            public set(value: Target[]): Promise<void>;
        }
    }
}

Do not configure `uuid` length of `Belongs`

Some DBMS like PostgreSQL has special uuid type and it does not allow to configuring the manual length.

Therefore, safe-typeorm should stop the configuring the default length of the Belongs, either.

Enhance `AppJoinBuilder` like `Map<Key, T>`

Enhance #15

export class AppJoinBuilider<Mine extends object>
    implements Map<AppJoinBuilder.Key<Mine>, AppJoinBuilder.Value<Mine>>
{
}
export namespace AppJoinBuilder
{
    export type Closure<T extends object> = (builder: AppJoinBuilder<T>) => void;
    export type Key<T extends object> = SpecialFields<T, Relationship<any>>;
    export type Value<T extends object> = AppJoinBuilder<Relationship.TargetType<T, any>>;
    export type Entry<T extends object> = [Key<T>, Value<T>];
}

Wrong return type on `AppJoinBuildere.get()`

export class AppJoinBuilder<Mine extends object>
{
    // CURRENT, THE WRONG RETURN TYPE
    public get(key: SpecialFields<Mine, Relationship<any>>): AppJoinBuilder<Mine>;

    // MUST CHANGED LIKE BELOW
    public get<Field extends AppJoinBuilder.Key<Mine>>
        (
            field: Field
        ): AppJoinBuilder.Value<Relationship.TargetType<Mine, Field>> | undefined
}

Refactor `Belongs` with critical section

export namespace Belongs
{
    /* -----------------------------------------------------------
        MANY-TO-ONE
    ----------------------------------------------------------- */
    export type ManyToOne<
            Target extends object, 
            Type extends PrimaryGeneratedColumn, 
            Options extends Partial<ManyToOne.IOptions<Type>> = {}> 
        = ManyToOne.Accessor<Target, Type, Options>;

    export function ManyToOne<
            Mine extends object,
            Target extends object, 
            Type extends PrimaryGeneratedColumn,
            Options extends Partial<ManyToOne.IOptions<Type>>>
        (
            targetGen: Creator.Generator<Target>, 
            inverse: (target: Target) => Has.OneToMany<Mine>,
            type: Type,
            myField: string, 
            options?: Options
        ): PropertyDecorator;

    export namespace ManyToOne
    {
        export class Accessor<
                Target extends object, 
                Type extends PrimaryGeneratedColumn, 
                Options extends Partial<ManyToOne.IOptions<Type>>>
        {
            private mutex_: SharedMutex;

            public async get(): Promise<CapsuleNullable<Target, Options>>;
            public async set(obj: CapsuleNullable<Target, Options>): Promise<void>;
        }
    }

    /* -----------------------------------------------------------
        ONE-TO-ONE
    ----------------------------------------------------------- */
    export type OneToOne<
            Target extends object, 
            Type extends PrimaryGeneratedColumn, 
            Options extends Partial<OneToOne.IOptions<Type>> = {}> 
        = OneToOne.Accessor<Target, Type, Options>;

    export function OneToOne<
            Mine extends object, 
            Target extends object, 
            Type extends PrimaryGeneratedColumn,
            Options extends Partial<OneToOne.IOptions<Type>>>
        (
            targetGen: Creator.Generator<Target>, 
            inverse: (input: Target) => Has.OneToOne<Mine>,
            type: Type,
            myField: string, 
            options?: Options
        ): PropertyDecorator;

    export namespace OneToOne
    {
        export import Accessor = ManyToOne.Accessor;
    }
}

Indexing on the `@Belongs` is not working

TypeORM allows unique property on the @Column, but index property is not.

  • @orm.Column("varchar", { unique: true }) - working
  • @orm.Column("varchar", { index: true }) - not working

Therefore, I must change the @Belongs to use @Index instead.

Design a new feature, `JsonSelectBuilder`

Design a new class JsonSelectBuilder who can convert ORM Model class to a specified JSON data. When the JsonSelectBuilder converting ORM Model to the JSON data, it automatically performs the app joining (by the AppJoinBuilder - #15). The automatic app joining would find the best app joi plan by itself.

Support `@Embedded` with `initialize` and `toPrimitive`

Example Code

export class Geometry3D
{
    @orm.Column("double")
    public readonly x: number;

    @orm.Column("double")
    public readonly y: number;

    @orm.Column("double")
    public readonly z: number;
}

@orm.Entity()
export class Cube extends safe.Model
{
    @orm.PrimaryGeneratedColumn("uuid")
    public readonly id!: string;

    @safe.Embedded(() => Geometry3D)
    public readonly position!: safe.Embedded<Geometry3D>;

    @safe.Embedded(() => Geometry3D)
    public readonly scale!: safe.Embedded<Geometry3D>;

    @safe.Embedded(() => Geometry3D)
    public readonly rotation!: safe.Embedded<Geometry3D>;
}

Initialization

const cube: Cube = Cube.initialize({
    id: safe.DEFAULT,
    position: { x: 3, y: 2, z: 5 },
    scale: { x: 10, y: 15, z: 7 },
    rotation: { x: 45, y: 30, z: 90 }
});

JSON Convertion

type safe.typings.Primitive<Cube> := 
{
    id: string;
    position: IGeometry3D;
    scale: IGeometry3D;
    rotation: IGeometry3D;    
}

interface IGeometry3D
{
    x: number;
    y: number;
    z: number;
}

Database

CREATE TABLE cubes
(
    id CHAR(36) NOT NULL PRIMARY KEY,
    position_x DOUBLE NOT NULL,
    position_y DOUBLE NOT NULL,
    position_z DOUBLE NOT NULL,
    scale_x DOUBLE NOT NULL,
    scale_y DOUBLE NOT NULL,
    scale_z DOUBLE NOT NULL,
    rotation_x DOUBLE NOT NULL,
    rotation_y DOUBLE NOT NULL,
    rotation_z DOUBLE NOT NULL
)

Bug when using the "uuid" typed primary key

typings.Relationship is using not typings.PrimaryGeneratedColumn but typeorm.PrimaryGeneratedColumnType and it causes compilation error when composing join statement through the "uuid" typed primary key.

import { PrimaryGeneratedColumnType } from "typeorm/driver/types/ColumnTypes";
import { Belongs } from "../decorators/Belongs";
import { Has } from "../decorators/Has";
import { SpecialFields } from "./SpecialFields";
export type Relationship<T extends object>
= Belongs.ManyToOne<T, PrimaryGeneratedColumnType, any>
| Belongs.OneToOne<T, PrimaryGeneratedColumnType, any>
| Has.OneToOne<T>
| Has.OneToMany<T>;
export namespace Relationship
{
export type TargetType<Mine extends object, Field extends SpecialFields<Mine, Relationship<any>>>
= Mine[Field] extends Belongs.ManyToOne<infer Target, any> ? Target
: Mine[Field] extends Belongs.OneToOne<infer Target, any> ? Target
: Mine[Field] extends Has.OneToMany<infer Target> ? Target
: Mine[Field] extends Has.OneToOne<infer Target> ? Target
: never;
}

Add a new method `JsonSelectBuilder.join()`

export class JsonSelectBuilder<
        Mine extends object, 
        InputT extends JsonSelectBuilder.Input<Mine>,
        Destination = JsonSelectBuilder.Output<Mine, InputT>>
{
    private readonly joiner_: AppJoinBuilder<Mine>;

    public constructor
        (
            creator: Creator<Mine>, 
            input: InputT, 
            closure?: (output: JsonSelectBuilder.Output<Mine, InputT>) => Destination
        );

    public join(data: Mine | Mine[]): Promise<void>
    {
        await this.joiner_.execute(data);
    }
}

Migrate `Model.toPrimitive()` to be a global function

export function toPrimitive<T extends object>(obj: T): Primitive<T>;
export function toPrimitive<T extends object, OmitField extends keyof Primitive<T>>
    (obj: T, ...omitFields: OmitField[]): OmitNever<Omit<Primitive<T>, OmitField>>;

Implement much safer factory method `AppJoinBuilder.initialize()`

Helps #15 in the compilation level.

export class AppJoinBuilder<Mine extends object>
{
    public static initialize<Mine extends object>
        (
            creator: Creator<Mine>,
            input: AppJoinBuilder.Initialized<Mine>
        ): AppJoinBuilder<Mine>
}
export namespace AppJoinBuilder
{
    export type Initialized<Mine extends object> = OmitNever<
    {
        [P in keyof Mine]: Mine[P] extends Relationship<infer Target>
            ? (AppJoinBuilder<Target> | null)
            : never
    }>;
}

Relationship helper classes to have less construction parameters

export class Accessor<Target extends object, Router extends object>
{
private readonly stmt_: orm.SelectQueryBuilder<Router>;
private readonly getter_: MutableSingleton<Target[]>;
private constructor
(
mine: any,
targetFactory: Creator<Target>,
routerFactory: Creator<Router>,
targetField: string,
targetInverseField: string,
myInverseField: string,
primaryKeyTuple: [string, string],
comp?: Comparator<ITuple<Target, Router>>
)

Relationship helper classes have too much construction parameters. Reduce the parameters thorugh the IMetadata definition.

Implement `bindAppJoin()` who supports the lazy app join

const group: BbsGroup = await BbsGroup.findOneOrFail();
safe.bindAppJoin(group);

const articleList: BbsArticle[] = await bbs.articles.get();
const top: BbsArticle = articleList[0];
await top.tags.get();
await top.comments.get();
await (await top.contents.get())[0].files.get();

// ANY SELECT QUERY WOULD BE OCCURED
// BECAUSE ALL OF THEM ALREADY BEEN LOADED BY
// LAZY APP JOIN
for (const article of articleList)
{
    await article.tags.get();
    await article.comments.get();
    for (const content of article.contents.get())
        await content.files.get();
}

Model.getWhereArguments() to be adaptable the null value

export abstract class Model
{
    public static getWhereArguments<T extends Model, 
            Literal extends SpecialFields<T, Field>,
            OperatorType extends Operator>
        (
            this: Model.Creator<T>,
            fieldLike: `${Literal}` | `${string}.${Literal}`,
            operator: OperatorType,
            param: OperatorType extends "="|"!="|"<>" 
                ? Field.MemberType<T, Literal> | null
                : Field.MemberType<T, Literal>
        ): [string, { [key: string]: Field.ValueType<T[Literal]> }];
}

Critical change from the [email protected]

https://github.com/samchon/safe-typeorm/actions/runs/1502087692

After updating TypeScript version from @4.4 to @4.5, compilation error has been occured.

However, I can't find any related information from the TypeScript release note. I assume that meta programming type checker of the TypeScript has been enhanced by another issue (Tail-Recursion Elimination on Conditional Types) and such enhancement caused upper errors - microsoft/TypeScript#45711

Thus, I should enhance all of the generic object parameters who're referenced by the SpecialFields like below:

//----
// BEFORE
//----
export function getWhereArguments<
        T extends object, 
        Literal extends SpecialFields<T, Field>>
    (
        creator: Creator<T>,
        fieldLike: `${Literal}` | `${string}.${Literal}`,
        param: Field.MemberType<T, Literal> | null
    ): [string, Record<string, Field.ValueType<T[Literal]>>];

//----
// AFTER
//----
export function getWhereArguments<
        T extends { [P in Literal]: Field; }, 
        Literal extends SpecialFields<T, Field>>
    (
        creator: Creator<T>,
        fieldLike: `${Literal}` | `${string}.${Literal}`,
        param: Field.MemberType<T, Literal> | null
    ): [string, Record<string, Field.ValueType<T[Literal]>>];

Relationship decorators for the external DB entities.

Support new relationship decorators for the external DB entities:

  • @Belongs.External.ManyToOne
  • @Belongs.External.OneToOne
  • @Has.External.OneToOne
  • @Has.External.OneToMany

With those external relationship decorators, it's not possible to perform the DB join, but App join is possible.

Below is an example ORM classes using those external relationship decorators.

// ENTITY IN A MYSQL DB
export class BbsArticle
{
    @safe.Has.OneToMany
    (
        () => BbsComment,
        comment => comment.article,
        (x, y) => x.created_at.getTime() - y.created_at.getTime(),
    )
    public readonly comments!: safe.Has.OneToMany<BbsComment>;

    @safe.Has.External.OneToMany
    (
        () => BlogUserScrap,
        scrap => scrap.article,
    )
    public readonly blogScraps!: safe.Has.External.OneToMany<BlogUserScrap>;
}

// ANOTHER ENTITY IN THE OTHER ORACLE DB
@orm.Unique(["blog_user_id", "bbs_article_id"])
@orm.Entity()
export class BlogUserScrap
{
    @safe.Belongs.ManyToOne(() => User,
        user => user.scraps,
        "uuid",
        "blog_user_id",
        // INDEXED
    )
    public readonly user!: safe.Belongs.ManyToOne<User, "uuid">;

    @safe.Belongs.External.ManyToOne(() => BbsArticle,
       article => article.scraps,
       "uuid",
       "bbs_article_id",
       { index: true }
    )
    public readonly article!: safe.Belongs.External.ManyToOne<BbsArticle, "uuid">;
}

Implement `createAppJoinBuilder()` with `AppJoinBuilder`

The new function appJoin() would help developers to implement the application join much easier.

The function appJoin() and its helper class AppJoinBuilder would form like below:

export function createAppJoinBuilder<T extends object>
    (
        creator: Creator<T>, 
        closure: (builder: AppJoinBuilder<T>) => void
     ): AppJoinBuilder<T>;

export class AppJoinBuilder<Mine extends object>
{
    public join<Field extends SpecialFields<Mine, Relationship<object>>>
        (
            field: Field,
            closure?: (builder: AppJoinBuilder<Relationship.TargetType<Mine, Field>>) => void
        ): AppJoinBuilder<Relationship.TargetType<Mine, Field>>;

    public execute(data: T[]): Promise<void>;
}

Because the helper class AppJoinBuilder reads relationship metadat from the target ORM class, you can utilize the auto-completion when joining with related tables. If you type any invalid word, it would be detected in the compilation level. Therefore, the code using the appJoin() would be like the createJoinQueryBuilder().

export async function join_sales(saleList: ShoppingSale[]): Promise<void>
{
    const builder: AppJoinBuilder<ShoppingSale> = createAppJoinBuilder(ShoppingSale, sale =>
    {
        sale.join("section");
        sale.join("seller", seller =>
        {
            seller.join("base").join("citizen");
            seller.join("company").join("branches");
        });
        sale.join("units", unit =>
        {
            unit.join("options", option => 
            {
                option.join("images");
                option.join("candidates").join("images");
            });
            unit.join("stocks");
        });
        sale.join("images");
    });
    await builder.execute(saleList);
}

Code using the createJoinQueryBuilder()

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.