• Stars
    star
    149
  • Rank 247,846 (Top 5 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 3 years ago
  • Updated 11 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

This library provides utitlites to create & drop the database, seed the database and apply URL query parameter(s).

Typeorm Extension πŸš€

npm version codecov Master Workflow Known Vulnerabilities Conventional Commits

This is a library to

  • create, drop & seed the (default-) database πŸ”₯
  • manage one or many data-source instances πŸ‘»
  • parse & apply query parameters (extended JSON:API specification & fully typed) to:
    • filter (related) resources according to one or more criteria,
    • reduce (related) resource fields,
    • include related resources,
    • sort resources according to one or more criteria,
    • limit the number of resources returned in a response by page limit & offset

Table of Contents

Installation

npm install typeorm-extension --save

Documentation

To read the docs, visit https://typeorm-extension.tada5hi.net

Usage

CLI

If you use esm, the executable must be changed from typeorm-extension to typeorm-extension-esm. The following commands are available in the terminal:

  • typeorm-extension db:create to create the database
  • typeorm-extension db:drop to drop the database
  • typeorm-extension seed seed the database

If the application has not yet been built or is to be tested with ts-node, the commands can be adapted as follows:

"scripts": {
    "db:create": "ts-node ./node_modules/typeorm-extension/bin/cli.cjs db:create",
    "db:drop": "ts-node ./node_modules/typeorm-extension/bin/cli.cjs db:drop",
    "seed": "ts-node ./node_modules/typeorm-extension/bin/cli.cjs seed"
}

To test the application in the context of an esm project, the following adjustments must be made:

  • executable ts-node to ts-node-esm
  • library path cli.cjs to cli.mjs

Read the Seeding Configuration section to find out how to specify the path, for the seeder- & factory-location.

Options

Option Commands Default Deprecated Description
--root or -r db:create, db:drop & seed process.cwd() no Path to the data-source / config file.
--dataSource or -d db:create, db:drop & seed data-source no Name of the data-source file.
--synchronize or -s db:create & db:drop yes no Synchronize the database schema after database creation. Options: yes or no.
--initialDatabase db:create undefined no Specify the initial database to connect to. This option is only relevant for the postgres driver, which must always to connect to a database. If no database is provided, the database name will be equal to the connection user name.
--seed seed undefined no Specify a specific seed class to run.

Database

An alternative to the CLI variant, is to create the database in the code base during the runtime of the application. Therefore, provide the DataSourceOptions for the DataSource manually, or let it be created automatically:

Create

Example #1

import { DataSource, DataSourceOptions } from 'typeorm';
import { createDatabase } from 'typeorm-extension';

(async () => {
    const options: DataSourceOptions = {
        type: 'better-sqlite',
        database: 'db.sqlite'
    };

    // Create the database with specification of the DataSource options
    await createDatabase({
        options
    });

    const dataSource = new DataSource(options);
    await dataSource.initialize();
    // do something with the DataSource
})();

Example #2

import {
    buildDataSourceOptions,
    createDatabase
} from 'typeorm-extension';

(async () => {
    const options = await buildDataSourceOptions();

    // modify options

    // Create the database with specification of the DataSource options
    await createDatabase({
        options
    });

    const dataSource = new DataSource(options);
    await dataSource.initialize();
    // do something with the DataSource
})();

Example #3

It is also possible to let the library automatically search for the data-source under the hood. Therefore, it will search by default for a data-source.{ts,js} file in the following directories:

  • {src,dist}/db/
  • {src,dist}/database
  • {src,dist}
import { createDatabase } from 'typeorm-extension';

(async () => {
    // Create the database without specifying it manually
    await createDatabase();
})();

To get a better overview and understanding of the createDatabase function, check out the documentation.

Drop

Example #1

import {
    DataSource,
    DataSourceOptions
} from 'typeorm';
import { dropDatabase } from 'typeorm-extension';

(async () => {
    const options: DataSourceOptions = {
        type: 'better-sqlite',
        database: 'db.sqlite'
    };

    // Drop the database with specification of the DataSource options
    await dropDatabase({
        options
    });
})();

Example #2

import {
    buildDataSourceOptions,
    dropDatabase
} from 'typeorm-extension';

(async () => {
    const options = await buildDataSourceOptions();

    // modify options

    // Drop the database with specification of the DataSource options
    await dropDatabase({
        options
    });
})();

Example #3

It is also possible to let the library automatically search for the data-source under the hood. Therefore, it will search by default for a data-source.{ts,js} file in the following directories:

  • {src,dist}/db/
  • {src,dist}/database
  • {src,dist}
import { dropDatabase } from 'typeorm-extension';

(async () => {
    // Drop the database without specifying it manually
    await dropDatabase();
})();

To get a better overview and understanding of the dropDatabase function, check out the documentation.

Instances

Single

The default DataSource instance can be acquired, by not providing any alias at all or using the key default. If no DataSource instance or DataSourceOptions object is deposited initially the method will attempt to locate and load the DataSource file and initialize itself from there.

import { useDataSource } from 'typeorm-extension';

(async () => {
    const dataSource : DataSource = await useDataSource();
})();

Reference(s):

Multiple

It is also possible to manage multiple DataSource instances. Therefore, each additional DataSource must be registered under a different alias. This can be done by either setting the DataSource instance or the DataSourceOptions object for the given alias.

import { DataSource, DataSourceOptions } from 'typeorm';
import { setDataSource, useDataSource } from 'typeorm-extension';

(async () => {
    const secondDataSourceOptions : DataSourceOptions = {
        // ...
    };

    const dataSource = new DataSource(secondDataSourceOptions);
    setDataSource(dataSource, 'second');

    const instance : DataSource = await useDataSource('second');
})();

Reference(s):

Seeding

Seeding the database is fairly easy and can be achieved by following the steps below:

  • Configuration: Specify the seed and factory location by path or object.
  • Entity: Define one or more entities.
  • Factory (optional): Define a factory for each entity for which data should be automatically generated.
  • Seed: Define one or more seed classes to populate the database with an initial data set or generated data by a factory.
  • Execute: Run the seeder(s) with the CLI or in the code base.

Configuration

Seeder paths are configured as glob patterns, making it easy to match all the factory/seeder files in your project without configuration effort:

  • use * to match anything expect slashes and hidden files
  • use ** to match zero or more directories
  • use comma separate values between {} to match against a list of options

Check out the glob documentation for other supported pattern features. It is important to use the posix/unix path separator (/) because the Windows path separator (\) is used to match paths with literal global pattern characters.

The seeder- & factory-location, can be specified via:

  • environment variable(s)
  • extended data-source.ts file
  • runSeeder(s) method options parameter, in case of a direct code base usage

The following values are assumed by default:

  • factory path: src/database/factories/**/*{.ts,.js}
  • seed path: src/database/seeds/**/*{.ts,.js}

Note: When seeder paths are configured as glob patterns, the paths are resolved and sorted in alphabetical order using filenames. This helps to ensure that the seeders are executed in the correct order.

data-source.ts

import { DataSource, DataSourceOptions } from 'typeorm';
import { SeederOptions } from 'typeorm-extension';

const options: DataSourceOptions & SeederOptions = {
    type: 'better-sqlite',
    database: 'db.sqlite',

    seeds: ['src/database/seeds/**/*{.ts,.js}'],
    factories: ['src/database/factories/**/*{.ts,.js}']
};

export const dataSource = new DataSource(options);

runSeeder(s)

import { DataSource, DataSourceOptions } from 'typeorm';
import { runSeeders, SeederOptions } from 'typeorm-extension';

(async () => {
    const options: DataSourceOptions = {
        type: 'better-sqlite',
        database: 'db.sqlite',
    };

    const dataSource = new DataSource(options);
    await dataSource.initialize();

    runSeeders(dataSource, {
        seeds: ['src/database/seeds/**/*{.ts,.js}'],
        factories: ['src/database/factories/**/*{.ts,.js}']
    });
})();

Entity

To get started, define one or more entities.

user.ts

import {
    Entity,
    PrimaryGeneratedColumn,
    Column
} from 'typeorm';

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    firstName: string

    @Column()
    lastName: string

    @Column()
    email: string
}

Factory

To create entities with random data, create a factory for each desired entity. The definition of a factory is optional.

The factory callback provides an instance of the faker library as function argument, to populate the entity with random data.

user.factory.ts

import { setSeederFactory } from 'typeorm-extension';
import { User } from './user';

export default setSeederFactory(User, (faker) => {
    const user = new User();
    user.firstName = faker.name.firstName('male');
    user.lastName = faker.name.lastName('male');
    user.email = faker.internet.email(user.firstName, user.lastName);

    return user;
})

Seed

And last but not least, create a seeder. The seeder can be called by the cli command seed or in the codebase by using the function runSeeder. A seeder class only requires one method, called run and provides the arguments dataSource & factoryManager.

user.seeder.ts

A seeder class must implement the Seeder interface, and could look like this:

import { Seeder, SeederFactoryManager } from 'typeorm-extension';
import { DataSource } from 'typeorm';
import { User } from './user';

export default class UserSeeder implements Seeder {
    public async run(
        dataSource: DataSource,
        factoryManager: SeederFactoryManager
    ): Promise<any> {
        const repository =  dataSource.getRepository(User);
        await repository.insert([
            {
                firstName: 'Caleb',
                lastName: 'Barrows',
                email: '[email protected]'
            }
        ]);

        // ---------------------------------------------------

        const userFactory = await factoryManager.get(User);
        // save 1 factory generated entity, to the database
        await userFactory.save();

        // save 5 factory generated entities, to the database
        await userFactory.saveMany(5);
    }
}

Execute

Populate the database from the code base:

import { DataSource, DataSourceOptions } from 'typeorm';
import { runSeeders, SeederOptions } from 'typeorm-extension';
import { User } from 'user';

(async () => {
    const options: DataSourceOptions & SeederOptions = {
        type: 'better-sqlite',
        database: 'db.sqlite',
        entities: [User],

        seeds: ['./*.seeder.ts'],
        factories: ['./*.factory.ts']
    };

    const dataSource = new DataSource(options);
    await dataSource.initialize();

    await runSeeders(dataSource);
})();

Populate the database by explicit definitions from the codebase.

import { DataSource, DataSourceOptions } from 'typeorm';
import { runSeeders, SeederOptions } from 'typeorm-extension';
import { User } from 'user';
import UserSeeder from 'user.seeder';
import UserFactory from 'user.factory';

(async () => {
    const options: DataSourceOptions & SeederOptions = {
        type: 'better-sqlite',
        database: 'db.sqlite',
        entities: [User],

        seeds: [UserSeeder],
        factories: [UserFactory]
    };

    const dataSource = new DataSource(options);
    await dataSource.initialize();

    await runSeeders(dataSource);
})();

Query

The query submodule enables query parameter (fields, filter, ...) values to be build, parsed & validated. Therefore, the rapiq library is used under the hood.

The query parameter options (allowed, default, ...) are fully typed πŸ”₯ and depend on the (nested-) properties of the target entity passed to the typeorm query builder.

For explanation proposes, two simple entities with a relation between them are declared to demonstrate the usage of the query utils:

import {
    Entity,
    PrimaryGeneratedColumn,
    Column,
    OneToOne,
    JoinColumn
} from 'typeorm';

@Entity()
export class User {
    @PrimaryGeneratedColumn({unsigned: true})
    id: number;

    @Column({type: 'varchar', length: 30})
    @Index({unique: true})
    name: string;

    @Column({type: 'varchar', length: 255, default: null, nullable: true})
    email: string;

    @OneToOne(() => Profile)
    profile: Profile;
}

@Entity()
export class Profile {
    @PrimaryGeneratedColumn({unsigned: true})
    id: number;

    @Column({type: 'varchar', length: 255, default: null, nullable: true})
    avatar: string;

    @Column({type: 'varchar', length: 255, default: null, nullable: true})
    cover: string;

    @OneToOne(() => User)
    @JoinColumn()
    user: User;
}

In this example routup and the plugin @routup/query is used to handle HTTP requests, but there is also a guide available for express.

import type { Request, Response } from 'routup';
import { Router, send } from 'routup';
import { createHandler, useQuery } from '@routup/query';

import {
    applyQuery,
    useDataSource
} from 'typeorm-extension';

const router = new Router();
router.use(createHandler());

/**
 * Get many users.
 *
 * Request example
 * - url: /users?page[limit]=10&page[offset]=0&include=profile&filter[id]=1&fields[user]=id,name
 *
 * Return Example:
 * {
 *     data: [
 *         {id: 1, name: 'tada5hi', profile: {avatar: 'avatar.jpg', cover: 'cover.jpg'}}
 *      ],
 *     meta: {
 *        total: 1,
 *        limit: 20,
 *        offset: 0
 *    }
 * }
 * @param req
 * @param res
 */
router.get('users', async (req: Request, res: Response) => {
    const dataSource = await useDataSource();
    const repository = dataSource.getRepository(User);
    const query = repository.createQueryBuilder('user');

    // -----------------------------------------------------

    const { pagination } = applyQuery(query, useQuery(req), {
        defaultAlias: 'user',
        fields: {
            // porfile fields can only be included,
            // if the relation 'profile' is included.
            allowed: ['id', 'name', 'profile.id', 'profile.avatar'],
        },
        filters: {
            // porfile.id can only be used as a filter,
            // if the relation 'profile' is included.
            allowed: ['id', 'name', 'profile.id'],
        },
        pagination: {
            // only allow to select 20 items at maximum.
            maxLimit: 20
        },
        relations: {
            allowed: ['profile']
        },
        sort: {
            // profile.id can only be used as sorting key,
            // if the relation 'profile' is included.
            allowed: ['id', 'name', 'profile.id']
        },
    });

    // -----------------------------------------------------

    const [entities, total] = await query.getManyAndCount();

    send(res, {
        data: entities,
        meta: {
            total,
            ...pagination
        }
    });
});

router.listen(80);

Contributing

Before starting to work on a pull request, it is important to review the guidelines for contributing and the code of conduct. These guidelines will help to ensure that contributions are made effectively and are accepted.

License

Made with πŸ’š

Published under MIT License.

More Repositories

1

smob

A zero dependency library to safe merge objects and arrays with customizable behavior.
TypeScript
52
star
2

rapiq

A tiny library which provides utility types/functions for request and response query handling.
TypeScript
13
star
3

vitron

This is a library to build (win, linux, mac) desktop apps for modern web projects with vite and electron.
TypeScript
11
star
4

bulletin-board-code

This library provides utitlites to parse BBCodes to HTML and HTML to BBCodes.
TypeScript
7
star
5

ebec

A collection of extensible ES6 error classes for different contexts.
TypeScript
5
star
6

authup

Authup is an authentication & authorization system.
TypeScript
5
star
7

routup

Routup is a lightweight & extendable http interface based routing framework.
TypeScript
4
star
8

typescript-swagger

This is a library/tool to generate swagger files from a decorator library or your own definitions.
TypeScript
2
star
9

locter

Locter is a library to locate and load a file regarding specific criteria.
TypeScript
2
star
10

ilingo

Ilingo is a lightweight library for translation and internationalization.
TypeScript
2
star
11

typescript-error

This monorepo contains error interfaces/classes in general and explicit implementations for http & websocket errors.
TypeScript
1
star
12

workspaces-publish

This library facilitates the publication of packages encompassing multiple workspaces as defined in a package.json file.
TypeScript
1
star
13

amqp-extension

This is a library on top of the famous amqplib library and defines a message format for queue messages through a message broker across multiple standalone services. All utility functions support the usage of multiple registered connections.
TypeScript
1
star
14

trapi

TRAPI is a collection of packages to create/generate metadata for REST-APis and generate swagger documentations.
TypeScript
1
star
15

docker-scan

This is a library to scan for docker images in a defined directory and associate them to (nested) groups πŸ”₯.
TypeScript
1
star
16

vue-layout

This repository contains different packages, which provides different vue components to simplify layout creation.
TypeScript
1
star
17

action-docker-image-publish

GitHub action, to build, tag and push docker images.
TypeScript
1
star
18

tada5hi

1
star
19

typescript-package-template

JavaScript
1
star
20

mime-explorer

This is a library for mime types. It provides an ESM and CJS build.
TypeScript
1
star
21

browser-storage-adapter

This is a library to save key-value pairs simultaneously to one or multiple storages (e.g. SessionStorage, LocalStorage, Cookie, ...).
TypeScript
1
star
22

redis-extension

This is an redis extension to manage singeltone client & cluster instances, cache & track entity identifiers in groups.
TypeScript
1
star
23

javascript

This a collection of JavaScript and TypeScript linting- & prettier-rules.
JavaScript
1
star
24

hapic

A tiny & simple fetch based http client with a collection of different presets.
TypeScript
1
star
25

continu

Continu is a powerful tool for managing key-value pairs in an application. Its simple and lightweight design makes it easy to use. Besides, defining default values it is also possible to execute transformations & validations before setting any value.
TypeScript
1
star