@tsrt/sequelize

Sequelize ORM BaseRepository and connection factory

Usage no npm install needed!

<script type="module">
  import tsrtSequelize from 'https://cdn.skypack.dev/@tsrt/sequelize';
</script>

README

Typescript Reusable Tools: Sequelize

npm version GitHub license Size Downloads

Basic Sequlize Connection Factory and BaseRepository build on top of sequelize (v5*).

Could be used (and i'm personally recommend) with sequelize-typescript.

Important

Until version 1.0.0 Api should be considered as unstable and may be changed.

So prefer using exact version instead of version with ~ or ^.

Usage

More examples (w/ models and all methods) could be found in tests.

Databse connection factory

For example using sequelize-typescript and PostgreSql:

import { SequelizeOptions } from 'sequelize-typescript'
import { Database } from '@tsrt/sequelize';

import * as Models from 'path/to/models';

async function bootstrap(): Promise<void> {
  const sequelizeOptions: SequelizeOptions = {
    logging: false,
    dialect: 'postgres',
    database: 'database',
    username: 'username',
    password: 'password',
    host: 'localhost',
    port: 5432,
    models: Database.getModelsList(Models),
  };
  const database1 = await Database.createConnection(Sequelize, sequelizeOptions);
  const database2 = await Database.createConnection(Sequelize, { ... });

  // Close connection:
  // await Database.closeConnection(database1);

  // Use current connection (for example, models):
  // console.log(database1.connection.models);

  // ... start application
}

Repository usage

Example 1
import { BaseRepository } from '@tsrt/sequelize';

import { SomeModel, ISomeModelEntity } from 'path/to/models';

export const SomeModelRepository = new BaseRepository<ISomeModelEntity>(SomeModel);


// ... later in code

class SomeService {
  public async someMethod(id: number): Promise<ISomeModelEntity> {
    // ... some logic
    const entity = await SomeModelRepository.read({
      skip: 10,
      limit: 100,
      select: ['id', 'title'],
      sort: 'title:asc',
      include: ['nested', 'nested.deeply', { model: OtherModel, as: 'otherModel' }],
      where: {
        $or: {
          id: { $gt: 10 },
          title: { $iLike: '%hello%' },
          'nested.id': 10,
        },
      },
    }, id);
    // ... some logic
  }
}

Example 2

Imagine usage in express:

router.get('/entities', async (req, res) => {
  const { skip, limit, sort, select, include, filter } = req.query;
  try {
    const result = await SomeModelRepository.read({ skip, limit, sort, select, include, filter });
    res.status(200).send(result); // { total: number, nextSkip: number, value: [] };
  } catch (err) {
    res.status(err.status).send(err);
  }
});
Enhanced usage of some query params
SomeRepository.read({
  limit: 1, // -> basic limit
  limit: 'none', // -> will remove limit at all, as by default it is used limit = 10. Default limit could be changed when instantiating BaseRepository by providing options.

  sort: 'id:asc,title:desc', // -> [['id', 'ASC'], ['title', 'DESC']]
  sort: ['id:asc', 'title:desc'], // -> same as example above
  sort: ['nested.id:asc'], // -> [['nested', 'id', 'ASC']] - so it is possible to sort by nested includes.

  /**
   *  In `filter` and `where` options we can provide any of Sequelize compatible query operators
   *  prefixed with `

	
		
		
		
		
		
		
		
	npm:@tsrt/sequelize | Skypack
	
		
		
		
		.
   *  So we can compose complex queries here.
   *  @example: { id: { $eq: 1 } }
   *
   *  @see https://sequelize.org/master/manual/model-querying-basics.html#operators
   */
  filter: {
    $and: {
      id: { $eq: 1 },
      $or: {
        title: { $iLike: '%hello%' },
        age: { $gt: 18 },
        'nested.id': { $gt: { 18 } }, // Yes, we can filter by nested columns
      }
    }
  },
  // or where { ... },

  include: 'nested1, nested2', // -> [{ duplicating: false, association: 'nested1' }, { duplicating: false, association: 'nested2' }]
  include: ['nested1', 'nested2'], // -> same as example above
  include: ['nested1.deepNested1', 'nested2'], // -> [{ duplicating: false, association: 'nested1', include: [{ duplicating: false, association: 'deepNested1' }] }, { duplicating: false, association: 'nested2' }]
  include: [{ association: 'nested1', duplicating: true, ... }] // -> here we can use all Sequelize appropriate options.
})
Left join + limit and count problem solved

This problem is described here.

Problem - we cannot achieve expected result using include and limit for FindAndCountAll. In different cases we can play w/ duplicating: false, subQuery: false sequelize params, but:

  • here we need universal solution.
  • still there is a problem w/ incorrect count (total amount of carthesian product).

This problem is solved under the hood of BaseRepository.

API reference

All methods will create its own transaction, execute all queries inside it and commits/rollback at the end.

If method provided w/ transaction manually - it will execute all queries inside it and wiil NOT commit/rollback at the end, it should be done manually by developer when needed.

In such a way it is possible to execute a bunch of different queries inside 1 transaction.

interface IBaseRepository<
  /** DatabaseEntity interface. Common return type and body type for update/create methods. */
  I extends GenericObject & O,
  
  /**
   *  Entity specific interface without database specific fields like pk, deletedAt.
   *  Common body type for update/create methods.
   *  Here could be defined virtual properties such as associations primaryKeys array.
   *  Could be used to improve TS typechecking and intellisense.
   */
  R = Partial<I>,

  /** Interface for Entity with ordering properties: `primaryKey` and `orderKey`. */
  O extends GenericObject = IOrderingItemDefault,

  /** Repository Model. */
  M extends Model = Model
> {
  /** A reference to Model, provided for BaseRepository constructor. Could be used as native sequelize Active Record pattern. */
  model: Model;

  /**
   *  Creates a transaction or executes a transaction callback.
   *
   *  @param [options] - Transactions options.
   *  @param [cb] - Transaction callback to be executed for managed transactions.
   *
   *  @see https://sequelize.org/master/manual/transactions
   */
  createTransaction<T = I>(optionsOrCb?: TransactionOptions | TransactionCallBack<T>, cb?: TransactionCallBack<T>): Promise<T | Transaction>;

  /**
   *  Creates entity w/ additional ICreateOptions options.
   * 
   *  @param body - Entity data.
   *  @param [customOptions] - Custom options for entity creation.
   *  @param [through] - Data to add into association for Many to Many relations.
   *
   *  Under the hood can create relations if they are define in model definition.
   *  For example if model has a BelongsToMany association w/ model, and association alias is `types`.
   *  Is is possible to provide next body (types array is list of ids):
   *
   *  @example:
   *  create({ name: 'test', types: [1, 2] }, { ... }, { defaultType: 'test' }).
   *  So it will create entity w/ name 'test', created references w/ `types` and inserts { defaultType: 'test' } into those references.
   */
  create(body: Partial<I>, createOptions?: ICreateOptions, through?: GenericObject): Promise<I>;

  /**
   *  Creates multiple entities from provided list (inside transaction).
   *
   *  @param body - List of entities to be created.
   *  @param [createOptions] - Custom options for record creation. Include QueryOptions and CreateOptions.
   *  @param [through] - Data to add into association for Many to Many relations.
   */
  bulkCreate(body: Array<Partial<I>>, createOptions?: ICreateOptions, through?: GenericObject): Promise<I[]>;

  /**
   *  Alias for common read operations. Works for both readOne and readMany.
   *
   *  @param [readOptionsOrPk] - Optional read options or primaryKey.
   *  @param [readOptions] - Reqd options if primaryKey provided as first arument.
   *
   *  If entity has `orderKey` column, will ensure for each request that all entities for similar conditions
   *  have valid `orderKey` (no NULL(s) and duplicates).
   */
  read(readOptionsOrPk?: number | string | IReadOptions, readOptions?: IReadOptions): Promise<I | IPagedData<I>>

  /**
   *  Reads one record (by pk or options).
   *
   *  @param readOptionsOrPk - Read options or primaryKey.
   *  @param [readOptions] - Reqd options if primaryKey provided as first arument.
   *
   *  If entity has `orderKey` column, will ensure for each request that all entities for similar conditions
   *  have valid `orderKey` (no NULL(s) and duplicates).
   */
  readOne(readOptionsOrPk: number | string | IReadOptions, pk?: number | string): Promise<I>

  /**
   *  Reads multiple entities and returns paged response.
   *
   *  @param [readOptions] - Optional read options.
   * 
   *  If entity has `orderKey` column, will ensure for each request that all entities for similar conditions
   *  have valid `orderKey` (no NULL(s) and duplicates).
   */
  readMany(readOptions: IReadOptions = { }): Promise<IPagedData<I>>;

  /**
   *  Updates entity.
   *
   *  @param body - Data to be updated.
   *  @param pk - Entity primaryKey or updateOptions.
   *  @param [updateOptions] - Custom options for updating operation or through.
   *  @param [through] - Data to add into association for Many to Many relations.
   *
   *  Creates associations same as `create` method.
   */
  update(body: Partial<I>, pk: number | string | IUpdateOptions, updateOptions?: IUpdateOptions, through?: GenericObject): Promise<I>;

  /**
   *  Updates multiple entities from provided list.
   *
   *  @param body - List of entities to be updated.
   *  @param [updateOptions] - Custom options for updating operation.
   *  @param [through] - Data to add into association for Many to Many relations.
   */
  bulkUpdate(body: Array<Partial<I>>, updateOptions?: IUpdateOptions, through?: GenericObject): Promise<I[]>;

  /**
   *  Update entities order (reordering).
   *
   *  @param body - Items with new orders or array of changes
   *  @param [options] - Additional Query options
   *  @param [checkPermissions=true] - Whether to check permissions or not
   */
  updateItemsOrder<C extends Required<O>>(body: C[], options: IReadOptions = { }): Promise<I[]>;

  /**
   *  Deletes entity by pk.
   *  If `paranoid` mode is enabled - soft deletes. Alternatively deletes entity totally.
   *
   *  @param deleteOptionsOrPk - Entity primaryKey or deleteOptions.
   *  @param [deleteOptions] - Custom options for entity deletion if first argument is primaryKey.
   */
  delete(deleteOptionsOrPk: string | number | IDeleteOptions, deleteOptions?: IDeleteOptions): Promise<string>;

  /**
   *  Soft deletes entity by pk (only if `paranoid` mode enabled). Alternatively deletes entity totally.
   *  Alias for delete(pk, { force: false }) or just delete(pk);
   *
   *  @param deleteOptionsOrPk - Entity primaryKey or deleteOptions.
   *  @param [deleteOptions] - Custom options for entity deletion if first argument is primaryKey.
   */
  softDelete(deleteOptionsOrPk: string | number | IDeleteOptions, deleteOptions?: IDeleteOptions): Promise<string>;

  /**
   *  Totally deletes entity by pk.
   *  Alias for delete(pk, { force: true });
   *
   *  @param deleteOptionsOrPk - Entity primaryKey or deleteOptions.
   *  @param [deleteOptions] - Custom options for entity deletion if first argument is primaryKey.
   */
  forceDelete(deleteOptionsOrPk: string | number | IDeleteOptions, deleteOptions?: IDeleteOptions): Promise<string>;

  /**
   *  Restores soft deleted entity(-ies).
   *
   *  @param restoreOptionsOrPk - Entity primaryKey or restoreOptions.
   *  @param [restoreOptions] - Custom options for restore operation if first argument is primaryKey.
   *
   *  @returns restored entity / list of restored entities.
   */
  restore(restoreOptionsOrPk: string | number | IRestoreOptions, restoreOptions?: IRestoreOptions): Promise<I | I[]>;
}

Hooks

Using hooks, it is possible to extend BaseRepository and provide some extra logic/validations/context. For example here we can provide some tenant specific context in a multi-tenant system.

Example:
export class CustomRepository<I extends GenericObject, R = Partial<I>, IOrderingItem, M extends Model = Model> extends BaseRepository<I, R, IOrderingItem, M> {
  protected async onAfterQueryBuilt(query?: FindAndCountOptions): Promise<FindAndCountOptions> {
    const context = await this.provideContext();
    // ... altering original `query`;
    return queryWithContext;
  }

  protected async onBeforeCreate(body: R, createOptions?: ICreateOptions, through?: GenericObject): Promise<void> {
    await this.insertContext(body, createOptions);
  }

  protected async onBeforeBulkCreate(body: R[], createOptions: ICreateOptions): Promise<void> {
    await this.insertContext(body, createOptions);
  }

  protected async onBeforeUpdate(
    body: Partial<R>, pk?: number | string, updateOptions?: IUpdateOptions, through?: GenericObject,
  ): Promise<void> {
    await this.insertContext(body, updateoptions);
  }

  protected async onBeforeInsertAssociations(
    _entity: M, _body: Partial<R>, _inserToptions?: IBaseRepositoryExtendedOptions, through?: GenericObject,
  ): Promise<void> {
    await this.insertContext(through, through);
  }

  protected async onBeforeDelete(deleteOptions: IDeleteOptions, pk?: number | string): Promise<void> {
    const context = await this.provideContext();
    if (someCondition) throw new Error('Cannot delete');
    // ...other logic ...
  }

  protected async insertContext(target: GenericObject | GenericObject[], options?: GenericObject): Promise<void> {
    const context = await this.provideContext();
    if (!context) return;

    if (options) options.context = context;
    if (!Array.isArray(target)) Object.keys(context).forEach((key) => { target[key] = (context as GenericObject)[key]; });
    else target.forEach((item) => Object.keys(context).forEach((key) => { item[key] = (context as GenericObject)[key]; }));
  }

  protected async provideContext(): Promise<IContext> {
    // ...retrieve some context, for example using `express-http-context` package. Or `async_hooks`.
  }
}

// ... and then

export const SomeModelRepository = new CustomRepository<ISomeModelInterface>(SomeModel);

// ... or with all types for better typization

export const SomeModelRepository = new CustomRepository<ISomeModelInterface, ICreateInterface, IOrdering, SomeModel>(SomeModel);

// Note, that it is alos possible to set default Ordering type by declaring IBaseOrderingItem for @tsrt/sequelize module.
// Example: declare module '@tsrt/sequelize' { export interface IBaseOrderingItem { pk: number; order?: number; } }

Hooks
interface IHooks {
  /**
 *  Hook called after query was built.
 *
 *  @param [parsedQuery] - Previously parsed query params into Sequelize appropriate find and count options.
 *
 *  @note Unlike other hooks should return updated query.
 */
onAfterQueryBuilt(parsedQuery?: FindAndCountOptions): Promise<FindAndCountOptions>;

/**
 *  Hook which invokes directly before create operation.
 *
 *  @param _body - Body for entity creation.
 *  @param [_createOptions] - Custom options for record creation.
 *  @param [_through] - Data to add into association for Many to Many relations.
 */
onBeforeCreate(_body: GenericObject, _createOptions?: ICreateOptions, _through?: GenericObject): Promise<void>;

/**
 *  Hook which invokes directly before bulk create operation.
 *
 *  @param _body - Body for record creation.
 *  @param [_createOptions] - Custom options for record creation.
 *  @param [_through] - Data to add into association for Many to Many relations.
 */
onBeforeBulkCreate(_body: GenericObject[], _createOptions?: ICreateOptions, _through?: GenericObject): Promise<void>;

/**
 *  Hook which invokes directly before read operations.
 *
 *  @param [_options] - Read options.
 *  @param [_pk] - PrimaryKey.
 */
onBeforeRead(_options?: IReadOptions, _pk?: string | number | boolean): Promise<void>;

/**
 *  Hook which invokes directly before update operation.
 *
 *  @param _body - Body for record creation.
 *  @param _pk - Entity primaryKey or query.
 *  @param [_updateOptions] - Custom options for record update.
 *  @param [_through] - Data to add into association for Many to Many relations.
 *
 *  @note `_pk` arg could be null.
 */
onBeforeUpdate(
  _body: GenericObject, _pk: number | string, _updateOptions?: IUpdateOptions, _through?: GenericObject,
): Promise<void>;

/**
 *  Hook which is called before updating items order.
 *
 *  @param _body - Orders changes.
 *  @param _options - Optional readOptions for getiing range of all items which need to be reordered.
 */
onBeforeUpdateItemsOrder<C extends Required<O>>(_body: C[], _readOptions?: IReadOptions): Promise<void>;

/**
 *  Hook which invokes directly before delete operation.
 *
 *  @param [_deleteOptions] - Custom options for record (s) destroy.
 *  @param [_pk] - primaryKey.
 */
onBeforeDelete(_deleteOptions: IDeleteOptions, _pk?: number | string): Promise<void>;

/**
 *  Hook which fires right before adding associated data.
 *
 *  @param _entity - Previously created / updated entity.
 *  @param _body - Data / body to find associated data in.
 *  @param [_insertOptions] - Optional params.
 *  @param [_through] - Data which should be added into associated entities (for Many to Many relations).
 */
onBeforeInsertAssociations(_entity: M, _body: Partial<R>, _insertOptions?: IBaseRepositoryExtendedOptions, _through?: GenericObject): Promise<void>;

/**
 *  Hook which is called before restoring.
 *
 *  @param _restoreOptions - Restore options.
 */
onBeforeRestore(_restoreOptions: IRestoreOptions, _pk?: number | string): Promise<void>;
}

Options

Default BaseRepository options
export const defaultBaseRepositoryConfig: IBaseRepositoryConfig = {
  defaults: {
    restrictedProperties: ['createdAt', 'updatedAt', 'deletedAt'],
    limit: 10,
    logError: process.env.NODE_ENV !== 'production',
  },
  orderingServiceOptions: {
    orderKey: 'order',
    clampRange: true,
    insertAfterOnly: true,
  },
};
All available options
import { CreateOptions, UpdateOptions, DestroyOptions, RestoreOptions, WhereAttributeHash, IncludeOptions } from 'sequelize';

type GenericObject<T = any> = Record<string, T>;

export interface IPagedData<T> {
  total?: number;
  nextSkip?: number;
  value: T[];
}

/** Database factory config */
export interface IDatabaseConfig {
  /** Whether to sync (Sequelize sync()) after connection established. */
  sync?: boolean;

  /** Whether to log into console connection info after connection established. */
  logConnectionInfo?: boolean;

  /**
   *  Callback, which would be called after connection establised.
   *  Here it is possible, for example, associate Models, if using pure Sequelize (not `sequelize-typescript`).
   *
   *  @param sequelize - Sequelize connection.
   */
  cbAfterConnected?: (sequelize: Sequelize) => Promise<void>;
}

/** Default repository options. */
export interface IBaseRepositoryDefaults {
  /**
   *  Properties, which would ne stripped while update/create operations.
   *  Default: ['createdAt', 'updatedAt', 'deletedAt'].
   */
  restrictedProperties: string[],

  /** Defalt limit param for read operations. Default: 10. */
  limit: number;

  /** Defalt order param for read operations. Default: [primaryKey, 'asc']. */
  order: string[];

  /** Whether to log BaseRepository errors. */
  logError?: boolean;
}

export interface IBaseRepositoryConfig {
  /** Default repository options. */
  defaults: Partial<IBaseRepositoryDefaults>;

  /** Config for OrderingService. @see @tsrt/ordering package for details */
  orderingServiceConfig: IOrderingServiceConfig;
}

export interface IBaseRepositorySilentQuery {
  /**
   *  Whether to throw an Error if query fails.
   *  For example while reading/creating/updating data.
   *
   *  @note that error will be throw even if `silent: true` and it is some validation error, for example incorrect body for update method.
   *  @default false;
   */
  silent?: boolean;
}

export interface IBaseRepositoryOptions extends IBaseRepositorySilentQuery {
  /** Limit for Sql query. Applies for limitation of main entity records. @default: 10. */
  limit?: number | 'none';

  /** Offset for Sql query. @default: 0. */
  skip?: number;

  /**
   *  Applies only in case of reading by pk (`readOne or read` methods) and gives an alias for reading by value if some field.
   *  @default: model primaryKey.
   */
  getBy?: string;

  /** Select attributes from main entity to query for. @example: 'pk, title' or ['pk', 'title'] */
  select?: string | string[];

  /**
   * Sorting conditions. Key:value paires.
   * @example: 'pk:asc,title:desc' or ['pk:asc', 'title:desc'].
   * @default: 'pk:asc'.
   */
  sort?: string | string[];

  /**
   *  Associations for eager loading.
   *
   *  Could a string of aliases, define in model definition (for example if come as queryString from client).
   *  @example: 'nested1, nested2'.
   *
   *  Array of aliases, define in model definition.
   *  @example: ['nested1', 'nested2'].
   *
   *  Also could be nested in both above cases.
   *  @example: ['nested1.deepNested1'].
   *
   *  Or list of full IncludeOptions from Sequelize.
   *  @example: [{ association: 'test' }, { model: SomeModel, as: 'test2', required: false }].
   */
  include?: string | Array<string | IncludeOptions>;

  /**
   *  Filtering (aka `where`) conditions. This one could be shared to get filters from client side.
   *  Will be merged and replaced by conditions from `where` property.
   *
   *  @note - Supports for nested conditions by dot notation.
   *  @note - Supports all Sequelize operators. Operators should be prefixed with `

	
		
		
		
		
		
		
		
	npm:@tsrt/sequelize | Skypack
	
		
		
		
		.
   *  @see https://sequelize.org/master/manual/model-querying-basics.html#operators
   *
   *  @example:
   *  filter: {
   *    $or: {
   *      id: 1,
   *      title: { $iLike: '%hello%' }
   *      'nested.id': 10,
   *    }
   *  }
   */
  filter?: WhereAttributeHash;

  /**
   *  `Where` conditions. Has priority over `filter` property.
   *
   *  @note - Supports for nested conditions by dot notation.
   *  @note - Supports all Sequelize operators. Operators should be prefixed with `

	
		
		
		
		
		
		
		
	npm:@tsrt/sequelize | Skypack
	
		
		
		
		.
   *  @see https://sequelize.org/master/manual/model-querying-basics.html#operators
   *
   *  @example:
   *  where: {
   *    $or: {
   *      id: 1,
   *      title: { $iLike: '%hello%' }
   *      'nested.id': 10,
   *    }
   *  }
   */
  where?: WhereAttributeHash;
}

type IBaseRepositoryCroppedOptions = Omit<IBaseRepositoryOptions, 'skip' | 'getBy' | 'select' | 'sort' | 'limit' | 'include'>;

/**
 *  Interface for CRUD controller method options, which influence on adding association data and
 *  response object (whether to create association between tables, or return JOINed result).
 */
export interface IBaseRepositoryExtendedOptions
  extends Transactionable, Omit<IBaseRepositoryOptions, 'skip' | 'getBy' | 'select' | 'sort'> {
  /** Whether it is necessary to associate (create reference) if reference primaryKeys list provided. Default: true */
  associate?: boolean;

  /**
   *  Whether it should replace associatins w/ new reference list (delete and add).
   *
   *  Example for replaceAssociations: true(default):
   *  Model.update({ id: 1, files: [1, 2, 3] }) -> after this query Model will
   *  be associated only w/ files w/ ids [1, 2, 3] even if previously it was associted w/ some others.
   *
   *  Example for replaceAssociations: false:
   *  Model.update({ id: 1, files: [1, 2, 3] }) -> after this query Model will be associted w/ those files
   *  it was associated before + new unique ids (if they were unique in provided list).
   *  So if previously Model was associated with [1, 2, 4] Files, after query it will be: [1, 2, 3, 4].
   */
  replaceAssociations?: boolean;

  /** Whether to return values with associations, after adding them. If false -> returns value without associations. */
  returnAssociations?: boolean;
}

/** Interface for possible options of create method */
export interface ICreateOptions extends Omit<IBaseRepositoryExtendedOptions, 'where' | 'filter'>, Omit<Partial<CreateOptions>, 'include'> {}

/** Interface for possible options of bulk create method */
export type IBulkCreateOptions = ICreateOptions;

/** Interface for possible options of read method */
export interface IReadOptions extends IBaseRepositoryOptions, Omit<Partial<FindAndCountOptions>, 'where' | 'include' | 'limit'> {}

/** Interface for possible options of update method */
export interface IUpdateOptions extends IBaseRepositoryExtendedOptions, Omit<Partial<UpdateOptions>, 'where'> {}

/** Interface for possible options of bulk update method */
export type IBulkUpdateOptions = Omit<IUpdateOptions, 'limit' | 'where' | 'filter'>;

/** Interface for possible options of delete method */
export interface IDeleteOptions extends Pick<IBaseRepositoryOptions, 'where' | 'filter' | 'silent'>, Omit<Partial<DestroyOptions>, 'where'> {}

/** Interface for possible options of restore method */
export interface IRestoreOptions extends Pick<IBaseRepositoryOptions, 'where' | 'filter' | 'silent' | 'select' | 'include'>, Omit<Partial<RestoreOptions>, 'where'> {}

/** Type for transaction callback function */
export type TransactionCallBack<T> = (t: Transaction) => PromiseLike<T>;

/**
 *  Empty interface for TS augumentation in importing module.
 *
 *  Example: declare module '@tsrt/sequelize' { export interface IBaseOrderingItem { pk: number; order?: number; } }
 */
export interface IBaseOrderingItem { }

License

This project is licensed under the terms of the MIT license.