Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: refactor type definitions to export SequelizeBone, complete spell type definitions and fix index hints logic #337

Merged
merged 3 commits into from
Aug 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
828 changes: 20 additions & 808 deletions index.d.ts

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const Bone = require('./src/bone');
const Collection = require('./src/collection');
const { invokable: DataTypes, LENGTH_VARIANTS } = require('./src/data_types');
const migrations = require('./src/migrations');
const sequelize = require('./src/adapters/sequelize');
const { sequelize, SequelizeBone} = require('./src/adapters/sequelize');
const { heresql } = require('./src/utils/string');
const Hint = require('./src/hint');
const Realm = require('./src/realm');
Expand Down Expand Up @@ -52,6 +52,7 @@ Object.assign(Realm, {
connect,
disconnect,
Bone,
SequelizeBone,
Collection,
DataTypes,
Logger,
Expand Down
190 changes: 190 additions & 0 deletions src/adapters/sequelize.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import {
Attributes, Literal, OperatorCondition,
BoneOptions, ResultSet, Raw,
SetOptions, BeforeHooksType, AfterHooksType,
QueryOptions, OrderOptions, QueryResult
} from '../types/common';
import { AbstractBone } from '../types/abstract_bone';
import { Spell } from '../spell';

type WhereConditions<T extends typeof SequelizeBone> = {
[Property in keyof Extract<InstanceType<T>, Literal>]?: Literal | Literal[] | OperatorCondition;
} | {
[key in '$and' | '$or']?: WhereConditions<T>[];
}

interface SequelizeDestroyOptions extends QueryOptions {
force?: boolean;
}

interface BaseSequelizeConditions<T extends typeof SequelizeBone> extends QueryOptions {
where?: WhereConditions<T>;
order?: OrderOptions<T>;
limit?: number;
attributes?: string | Raw | Array<[keyof Extract<InstanceType<T>, Literal>] | string | Raw> | [keyof Extract<InstanceType<T>, Literal>];
offset?: number;
}

interface SequelizeConditions<T extends typeof SequelizeBone> extends BaseSequelizeConditions<T> {
group?: string | string[] | Raw;
having?: WhereConditions<T> | string | { [key:string]: Literal | Literal[] } | Raw;
include?: string | Raw;
}

interface FindOrCreateOptions<T extends typeof SequelizeBone> extends BaseSequelizeConditions<T> {
defaults?: {
[Property in keyof Extract<InstanceType<T>, Literal>]?: Literal
}
}

interface FindOrBuildOptions<T extends typeof SequelizeBone> extends FindOrCreateOptions<T> {
raw?: boolean;
isNewRecord?: boolean;
validate?: boolean;
}

interface DestroyOptions<T extends typeof SequelizeBone> extends SequelizeConditions<T> {
force?: boolean;
}

type ScopeOptions = {
override?: boolean
}

type Values<T extends typeof SequelizeBone> = {
[Property in keyof Extract<InstanceType<T>, Literal>]?: Literal;
}

type aggregators = 'count' | 'COUNT' | 'average' | 'AVERAGE' | 'minimum' | 'MINIMUM' | 'maximum' | 'MAXIMUM' | 'sum' | 'SUM';

export class Collection<T extends SequelizeBone> extends Array<T> {
save(): Promise<void>;
toJSON(): Object[];
toObject(): Object[];
}

export class SequelizeBone extends AbstractBone {

static get sequelize(): boolean;

static get Instance(): SequelizeBone;

static get rawAttributes(): Attributes;

static getTableName(): boolean;

static removeAttribute(name: string): void;

/**
*
* @static
* @param {string} name before/after create|destroy|upsert|remove|update
* @param {string | Function} fnNameOrFun function name or function
* @param {Function} func hook function
*/
static addHook(
name: BeforeHooksType | AfterHooksType | 'beforeDestroy' | 'afterDestroy' | 'beforeBulkDestroy' | 'afterBulkDestroy' | 'beforeBulkUpdate' | 'afterBulkUpdate',
fnNameOrFun: string | Function,
func?: Function,
): void;

/**
* add scope see https://sequelize.org/master/class/lib/model.js~Model.html#static-method-addScope
* @deprecated scope is not recommended to use
* @param name
* @param scope
* @param opts
*/
static addScope<T extends typeof SequelizeBone>(this: T, name: string, scope: ((...args: any[]) => SequelizeConditions<T>) | SequelizeConditions<T>, opts?: ScopeOptions): void;

/**
* @deprecated scope is not recommended to use
* @param name
* @param args
*/
static scope<T extends typeof SequelizeBone>(this: T, name?: (string | ((...args: any[]) => SequelizeConditions<T>) | SequelizeConditions<T> | Array<SequelizeConditions<T>>), ...args: any[]): T;

static unscoped(): Spell<typeof SequelizeBone>;

/**
* @deprecated scope is not recommended to use
* @param name
* @param args
*/
static setScope<T extends typeof SequelizeBone>(this: T, name: (string | ((...args: any[]) => SequelizeConditions<T>) | SequelizeConditions<T> | Array<SequelizeConditions<T>>), ...args: any[]): void;

static aggregate<T extends typeof SequelizeBone>(this: T, name: string, func: aggregators, options?: SequelizeConditions<T>): Spell<T, number>;

static build<T extends typeof SequelizeBone>(this: T, values: Values<T>, options?: BoneOptions): InstanceType<T>;

/**
* see https://github.com/sequelize/sequelize/blob/a729c4df41fa3a58fbecaf879265d2fb73d80e5f/src/model.js#L2299
* @param valueSets
* @param options
*/
static bulkBuild<T extends typeof SequelizeBone>(this:T, valueSets: Array<Values<T>>, options?: BoneOptions): Array<InstanceType<T>>;

static count<T extends typeof SequelizeBone>(this: T, name?: string): Spell<T, ResultSet | number>;
static count<T extends typeof SequelizeBone>(this: T, conditions?: SequelizeConditions<T>): Spell<T, ResultSet | number>;

static decrement<T extends typeof SequelizeBone>(
this: T,
fields: string | Array<string> | { [Property in keyof Extract<InstanceType<T>, Literal>]?: number },
options?: SequelizeConditions<T>
): Spell<T, QueryResult>;

static increment<T extends typeof SequelizeBone>(
this: T,
fields: string | Array<string> | { [Property in keyof Extract<InstanceType<T>, Literal>]?: number },
options?: SequelizeConditions<T>
): Spell<T, QueryResult>;

static max<T extends typeof SequelizeBone>(this: T, filed: string, options?: SequelizeConditions<T>): Promise<Literal>;
static min<T extends typeof SequelizeBone>(this: T, filed: string, options?: SequelizeConditions<T>): Promise<Literal>;
static sum<T extends typeof SequelizeBone>(this: T, filed: string, options?: SequelizeConditions<T>): Promise<Literal>;

static destroy<T extends typeof SequelizeBone>(this: T, options?: DestroyOptions<T>): Promise<Array<number> | number>;
static bulkDestroy<T extends typeof SequelizeBone>(this: T, options?: DestroyOptions<T>): Spell<T, number>;

static findAll<T extends typeof SequelizeBone>(this: T, options?: SequelizeConditions<T>): Spell<T, Collection<InstanceType<T>>>;
static find<T extends typeof SequelizeBone>(this: T, options?: SequelizeConditions<T>): Spell<T, InstanceType<T> | null>;
static findAndCountAll<T extends typeof SequelizeBone>(this: T, options?: SequelizeConditions<T>): Promise<{
rows: Array<typeof SequelizeBone>,
count: number,
}>

static findByPk<T extends typeof SequelizeBone>(this:T, pk: number | bigint | string, options?: Pick<SequelizeConditions<T>, 'paranoid' | 'connection' | 'transaction' |'hint' | 'hints'>): Spell<T, InstanceType<T>>;

static findOne<T extends typeof SequelizeBone>(this: T, whereConditions: string, ...values: Literal[]): Spell<T, InstanceType<T>>;
static findOne<T extends typeof SequelizeBone>(this: T, primaryKey: number | number[] | bigint): Spell<T, InstanceType<T>>;
static findOne<T extends typeof SequelizeBone>(this: T, options?: SequelizeConditions<T>): Spell<T, InstanceType<T>>;

static findCreateFind<T extends typeof SequelizeBone>(this: T, options: FindOrCreateOptions<T>): Promise<InstanceType<T>>;
static findOrBuild<T extends typeof SequelizeBone>(this: T, options: FindOrBuildOptions<T>): Promise<[InstanceType<T>, boolean]>;
static findOrCreate<T extends typeof SequelizeBone>(this: T, options: FindOrBuildOptions<T>): Promise<[InstanceType<T>, boolean]>;

static restore<T extends typeof SequelizeBone>(this: T, options: BaseSequelizeConditions<T>): Spell<T, number>;

static update<T extends typeof SequelizeBone>(this: T, values: SetOptions<T>, options: BaseSequelizeConditions<T>): Promise<number>;
static bulkUpdate<T extends typeof SequelizeBone>(this: T, values: SetOptions<T>, options: BaseSequelizeConditions<T>): Spell<T, number>;

/**
* An alias of instance constructor. Some legacy code access model name from instance with `this.Model.name`.
*/
get Model(): typeof SequelizeBone;
get dataValues(): { [key: string]: Literal };

where(): { [key: string]: number | bigint | string };
set(key: string, value: Literal | Literal[]): void;
get(key?: string): Literal | { [key: string]: Literal };
setDataValue(key: string, value: Literal | Literal[]): void;
getDataValue(key?: string): Literal | { [key: string]: Literal };
previous(key?: string): Literal | Literal[] | { [key: string]: Literal | Literal[] };
isSoftDeleted(): boolean;

increment(field: string | string[] | { [Property in keyof Extract<this, Literal>]?: number }, options?: QueryOptions): Spell<typeof SequelizeBone, QueryResult>;
decrement(field: string | string[] | { [Property in keyof Extract<this, Literal>]?: number }, options?: QueryOptions): Spell<typeof SequelizeBone, QueryResult>;
destroy(options?: SequelizeDestroyOptions): Promise<this| number>;
}

export const sequelize: (Bone: AbstractBone) => typeof SequelizeBone;
40 changes: 23 additions & 17 deletions src/adapters/sequelize.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function translateOptions(spell, options) {
if (having) spell.$having(having);

if (order) {
if (typeof order === 'string' || order instanceof Raw) {
if (typeof order === 'string' || order instanceof Raw || isPlainObject(order)) {
spell.$order(order);
} else if (Array.isArray(order) && order.length) {
if (order.some(item => Array.isArray(item))) {
Expand All @@ -45,15 +45,7 @@ function translateOptions(spell, options) {
}

const setScopeToSpell = (scope) => (spell) => {
if (scope.where) {
spell.$where(scope.where);
}
if (scope.order) {
spell.$order(scope.order);
}
if (scope.limit) {
spell.$limit(scope.limit);
}
translateOptions(spell, scope);
};

/**
Expand All @@ -78,7 +70,7 @@ function mergeScope(scopes) {
}
}
return merged;
};
}

/**
* parse scope
Expand Down Expand Up @@ -114,7 +106,7 @@ function filterOptions(options = {}) {

// https://sequelize.org/master/class/lib/model.js~Model.html
// https://sequelize.org/master/manual/model-querying-finders.html
module.exports = Bone => {
exports.sequelize = Bone => {
return class Spine extends Bone {

/*
Expand All @@ -139,7 +131,7 @@ module.exports = Bone => {

/**
* add scope see https://sequelize.org/master/class/lib/model.js~Model.html#static-method-addScope
*
* @deprecated scope is not recommended to use
* @static
* @param {string} name
* @param {Object|Function} scope
Expand All @@ -156,20 +148,31 @@ module.exports = Bone => {
}
}

/**
* @deprecated scope is not recommended to use
* @param {string} name
* @param {...any} args
* @returns
*/
static scope(name, ...args) {
const parentName = this.name;
class ScopeClass extends this {
static name = parentName;
};
}
ScopeClass.setScope(name, ...args);
return ScopeClass;
}

static get unscoped() {
return this.scope();
}

static unscoped() {
return this.scope();
}

/**
* @deprecated scope is not recommended to use
* @static
* @param {function|object|array} name
*/
Expand Down Expand Up @@ -274,12 +277,13 @@ module.exports = Bone => {
// EXISTS
// static bulkCreate() {}

static async count(options = {}) {
static count(options = {}) {
if (typeof options === 'string') return spell.$count(options);
const { where, col, group, paranoid } = options;
let spell = super.find(where, filterOptions(options));
if (Array.isArray(group)) spell.$group(...group);
if (paranoid === false) spell = spell.unparanoid;
return await spell.$count(col);
return spell.$count(col);
}

// EXISTS
Expand Down Expand Up @@ -562,7 +566,7 @@ module.exports = Bone => {
// EXISTS
// get isNewRecord() {}

async decrement(fields, options = {}) {
decrement(fields, options = {}) {
const Model = this.constructor;
const { primaryKey } = Model;
if (this[primaryKey] == null) {
Expand Down Expand Up @@ -735,3 +739,5 @@ module.exports = Bone => {
}
};
};

exports.SequelizeBone = this.sequelize(require('../bone'));
53 changes: 53 additions & 0 deletions src/bone.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Spell } from './spell';
import { AbstractBone } from './types/abstract_bone';
import { Collection, Literal, QueryOptions, ResultSet, WhereConditions } from './types/common';

export default class Bone extends AbstractBone {

/**
* SELECT rows
* @example
* Bone.find('foo = ?', 1)
* Bone.find({ foo: { $eq: 1 } })
*/
static find<T extends typeof Bone>(this: T, whereConditions: WhereConditions<T>): Spell<T, Collection<InstanceType<T>>>;
static find<T extends typeof Bone>(this: T, whereConditions: string, ...values: Literal[]): Spell<T, Collection<InstanceType<T>>>;
static find<T extends typeof Bone>(this: T, primaryKey: number | number[] | bigint): Spell<T, Collection<InstanceType<T>>>;
static find<T extends typeof Bone>(this: T, ): Spell<T, Collection<InstanceType<T>>>;

/**
* SELECT rows LIMIT 1. Besides limiting the results to one rows, the type of the return value is different from {@link Bone.find} too. If no results were found, {@link Bone.findOne} returns null. If results were found, it returns the found record instead of wrapping them as a collection.
* @example
* Bone.findOne('foo = ?', 1)
* Bone.findOne({ foo: { $eq: 1 } })
*/
static findOne<T extends typeof Bone>(this: T, whereConditions: WhereConditions<T>): Spell<T, InstanceType<T> | null>;
static findOne<T extends typeof Bone>(this: T, whereConditions: string, ...values: Literal[]): Spell<T, InstanceType<T> | null>;
static findOne<T extends typeof Bone>(this: T, primaryKey: number | number[] | bigint): Spell<T, InstanceType<T> | null>;
static findOne<T extends typeof Bone>(this: T, ): Spell<T, InstanceType<T> | null>;

static sum<T extends typeof Bone>(this: T, name?: string): Spell<T, ResultSet | number>;

/**
* restore rows
* @example
* Bone.restore({ title: 'aaa' })
* Bone.restore({ title: 'aaa' }, { hooks: false })
* @param conditions query conditions
* @param opts query options
*/
static restore<T extends typeof Bone>(this: T, conditions: Object, opts?: QueryOptions): Spell<T, number>;

/**
* UPDATE rows.
*/
static update<T extends typeof Bone>(this: T, whereConditions: WhereConditions<T>, values?: Object, opts?: QueryOptions): Spell<T, number>;

/**
* Discard all the applied scopes.
* @example
* Bone.all.unscoped // includes soft deleted rows
*/
static unscoped: Spell<typeof Bone>;

}
4 changes: 2 additions & 2 deletions src/bone.js
Original file line number Diff line number Diff line change
Expand Up @@ -1291,8 +1291,8 @@ class Bone {
}

for (const name in attributes) {
const { columnName } = attributes[name];
if (!(columnName in row)) {
const attribute = attributes[name];
if (!(attribute.columnName in row) && !attribute.virtual) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

virtual column should not be added to unset in case of custom getter cannot be serialize

class User {
 @Column()
 id: bigint;
 
 // custom will be ignored when toObject()/toJSON() execute, so make it a VIRTUAL column
 get custom(): string {
    return 'v';
 }
}

instance._getRawUnset().add(name);
}
}
Expand Down
Loading