Skip to content

Commit

Permalink
fix: type definitions for columns constraint (#355)
Browse files Browse the repository at this point in the history
* fix: type definitions for columns constraint

* fix: change raw to ts and complete unit test

* chore: columnAttributes definitions

* chore: { toDate: () => Date } (#356)

Co-authored-by: JimmyDaddy <[email protected]>
Co-authored-by: Chen Yangjian <[email protected]>
  • Loading branch information
3 people authored Oct 9, 2022
1 parent 8c1520a commit e8e2bb3
Show file tree
Hide file tree
Showing 25 changed files with 383 additions and 102 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ test/types/*.js
test/types/*.map
src/decorators.js*
src/data_types.js*

src/raw.js*
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const { heresql } = require('./src/utils/string');
const Hint = require('./src/hint');
const Realm = require('./src/realm');
const Decorators = require('./src/decorators');
const Raw = require('./src/raw');
const Raw = require('./src/raw').default;
const { MysqlDriver, PostgresDriver, SqliteDriver, AbstractDriver } = require('./src/drivers');
const { isBone } = require('./src/utils');

Expand Down
55 changes: 38 additions & 17 deletions src/adapters/sequelize.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
Attributes, Literal, OperatorCondition,
BoneOptions, ResultSet, Raw,
SetOptions, BeforeHooksType, AfterHooksType,
QueryOptions, OrderOptions, QueryResult, Values as CommonValues,
QueryOptions, OrderOptions, QueryResult, Values as CommonValues, BoneColumns, InstanceColumns,
} from '../types/common';
import { AbstractBone } from '../types/abstract_bone';
import { Spell } from '../spell';
Expand All @@ -21,21 +21,21 @@ interface BaseSequelizeConditions<T extends typeof SequelizeBone> extends QueryO
where?: WhereConditions<T>;
order?: OrderOptions<T>;
limit?: number;
attributes?: string | Raw | Array<[keyof Extract<CommonValues<InstanceType<T>>, Literal>] | string | Raw> | [keyof Extract<CommonValues<InstanceType<T>>, Literal>];
attributes?: BoneColumns<T> | Array<BoneColumns<T> | string | Raw> | string | Raw;
offset?: number;
}

type SequelizeUpdateOptions<T extends typeof SequelizeBone> = BaseSequelizeConditions<T> & {
fields?: Array<[keyof Extract<CommonValues<InstanceType<T>>, Literal>] | string | Raw> | [keyof Extract<CommonValues<InstanceType<T>>, Literal>];
fields?: BoneColumns<T> | Array<BoneColumns<T> | string | Raw> | string;
}

interface SequelizeInstanceUpdateOptions<T extends SequelizeBone> extends QueryOptions {
attributes?: string | Raw | Array<[keyof Extract<CommonValues<T>, Literal>] | string | Raw> | [keyof Extract<CommonValues<T>, Literal>];
attributes?: [keyof Extract<CommonValues<T>, Literal>] | string | Raw | Array<[keyof Extract<CommonValues<T>, Literal>] | string | Raw>;
fields?: Array<[keyof Extract<CommonValues<T>, Literal>] | string | Raw> | [keyof Extract<CommonValues<T>, Literal>];
}

interface SequelizeConditions<T extends typeof SequelizeBone> extends BaseSequelizeConditions<T> {
group?: string | string[] | Raw;
group?: BoneColumns<T> | BoneColumns<T>[] | Raw | string;
having?: WhereConditions<T> | string | { [key:string]: Literal | Literal[] } | Raw;
include?: string | Raw;
}
Expand Down Expand Up @@ -82,7 +82,7 @@ export class SequelizeBone extends AbstractBone {

static getTableName(): boolean;

static removeAttribute(name: string): void;
static removeAttribute<T extends typeof SequelizeBone>(this: T, name?: BoneColumns<T>): void;

/**
*
Expand Down Expand Up @@ -122,7 +122,8 @@ export class SequelizeBone extends AbstractBone {
*/
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 aggregate<T extends typeof SequelizeBone>(this: T, name: BoneColumns<T>, func: aggregators, options?: SequelizeConditions<T>): Spell<T, number>;
static aggregate<T extends typeof SequelizeBone>(this: T, name: Raw | '*', func: aggregators, options?: SequelizeConditions<T>): Spell<T, number>;

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

Expand All @@ -133,24 +134,30 @@ export class SequelizeBone extends AbstractBone {
*/
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<T> | number>;
static count<T extends typeof SequelizeBone>(this: T, name?: BoneColumns<T>): Spell<T, ResultSet<T> | number>;
static count<T extends typeof SequelizeBone>(this: T, name?: Raw | '*'): Spell<T, ResultSet<T> | number>;
static count<T extends typeof SequelizeBone>(this: T, conditions?: SequelizeConditions<T>): Spell<T, ResultSet<T> | number>;

static decrement<T extends typeof SequelizeBone>(
this: T,
fields: string | Array<string> | { [Property in keyof Extract<InstanceType<T>, Literal>]?: number },
fields: { [Property in keyof Extract<InstanceType<T>, Literal>]?: number } | string | Array<BoneColumns<T> | string> ,
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 },
fields: { [Property in keyof Extract<InstanceType<T>, Literal>]?: number } | string | Array<BoneColumns<T> | string> ,
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 max<T extends typeof SequelizeBone>(this: T, field: BoneColumns<T>, options?: SequelizeConditions<T>): Promise<Literal>;
static max<T extends typeof SequelizeBone>(this: T, field: Raw, options?: SequelizeConditions<T>): Promise<Literal>;

static min<T extends typeof SequelizeBone>(this: T, field: BoneColumns<T>, options?: SequelizeConditions<T>): Promise<Literal>;
static min<T extends typeof SequelizeBone>(this: T, field: Raw, options?: SequelizeConditions<T>): Promise<Literal>;

static sum<T extends typeof SequelizeBone>(this: T, field: BoneColumns<T>, options?: SequelizeConditions<T>): Promise<Literal>;
static sum<T extends typeof SequelizeBone>(this: T, field: Raw, 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>;
Expand Down Expand Up @@ -184,18 +191,32 @@ export class SequelizeBone extends AbstractBone {
get dataValues(): { [key: string]: Literal };

where(): { [key: string]: number | bigint | string };
set<T, Key extends keyof CommonValues<T>>(this: T, key: Key, value: T[Key]): void;
set<T, Key extends keyof T>(this: T, key: Key, value: T[Key]): void;

get<T, Key extends keyof CommonValues<T>>(this: T, key?: Key): T[Key];
get<T, Key extends keyof T>(this: T, key?: Key): T[Key];

setDataValue<T, Key extends keyof CommonValues<T>>(this: T, key: Key, value: T[Key]): void;
setDataValue<T, Key extends keyof T>(this: T, key: Key, value: T[Key]): void;

getDataValue<T>(this: T): T;
getDataValue<T, Key extends keyof CommonValues<T>>(this: T, key: Key): T[Key];
getDataValue<T, Key extends keyof T>(this: T, key: Key): T[Key];
previous(key?: string): Literal | Literal[] | { [key: string]: Literal | Literal[] };

previous<T, Key extends keyof CommonValues<T>>(this: T, key?: Key): Literal | Literal[] | { [Property in keyof Extract<this, Literal>]?: Literal | Literal[] };
previous<T, Key extends keyof T>(this: T, key?: Key): Literal | Literal[] | { [Property in keyof Extract<this, Literal>]?: 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>;
increment(field: InstanceColumns<this> | Array<InstanceColumns<this>> | { [Property in keyof Extract<this, Literal>]?: number }, options?: QueryOptions): Spell<typeof SequelizeBone, QueryResult>;
increment(field: string | Raw | Array<string | Raw>, options?: QueryOptions): Spell<typeof SequelizeBone, QueryResult>;
decrement(field: InstanceColumns<this> | Array<InstanceColumns<this>> | { [Property in keyof Extract<this, Literal>]?: number }, options?: QueryOptions): Spell<typeof SequelizeBone, QueryResult>;
decrement(field: string | Raw | Array<string | Raw> , options?: QueryOptions): Spell<typeof SequelizeBone, QueryResult>;
destroy(options?: SequelizeDestroyOptions): Promise<this| number>;
update<T = this>(this: T, changes?: { [key: string]: Literal } | { [Property in keyof Extract<this, Literal>]?: Literal }, opts?: SequelizeInstanceUpdateOptions<this>): Promise<number>;
update<T = this>(this: T, changes?: { [Property in keyof Extract<this, Literal>]?: Literal }, opts?: SequelizeInstanceUpdateOptions<this>): Promise<number>;
update<T = this>(this: T, changes?: { [key: string]: Literal }, opts?: SequelizeInstanceUpdateOptions<this>): Promise<number>;

}

export const sequelize: (Bone: AbstractBone) => typeof SequelizeBone;
2 changes: 1 addition & 1 deletion src/adapters/sequelize.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const { setupSingleHook } = require('../setup_hooks');
const { compose, isPlainObject } = require('../utils');
const Raw = require('../raw');
const Raw = require('../raw').default;

function translateOptions(spell, options) {
const { attributes, where, group, order, offset, limit, include, having } = options;
Expand Down
9 changes: 5 additions & 4 deletions src/bone.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Spell } from './spell';
import { AbstractBone } from './types/abstract_bone';
import { Collection, Literal, QueryOptions, ResultSet, WhereConditions } from './types/common';
import { BoneColumns, Collection, Literal, QueryOptions, Raw, ResultSet, Values, WhereConditions } from './types/common';

export default class Bone extends AbstractBone {

Expand All @@ -26,7 +26,8 @@ export default class Bone extends AbstractBone {
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<T> | number>;
static sum<T extends typeof Bone>(this: T, name?: BoneColumns<T>): Spell<T, ResultSet<T> | number>;
static sum<T extends typeof Bone>(this: T, name?: Raw): Spell<T, ResultSet<T> | number>;

/**
* restore rows
Expand All @@ -36,12 +37,12 @@ export default class Bone extends AbstractBone {
* @param conditions query conditions
* @param opts query options
*/
static restore<T extends typeof Bone>(this: T, conditions: Object, opts?: QueryOptions): Spell<T, number>;
static restore<T extends typeof Bone>(this: T, conditions: WhereConditions<T>, 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>;
static update<T extends typeof Bone>(this: T, whereConditions: WhereConditions<T>, values?: Values<InstanceType<T>> & Partial<Record<BoneColumns<T>, Literal>>, opts?: QueryOptions): Spell<T, number>;

/**
* Discard all the applied scopes.
Expand Down
2 changes: 1 addition & 1 deletion src/bone.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ require('reflect-metadata');
const { default: DataTypes } = require('./data_types');
const Collection = require('./collection');
const Spell = require('./spell');
const Raw = require('./raw');
const Raw = require('./raw').default;
const { capitalize, camelCase, snakeCase } = require('./utils/string');
const { hookNames, setupSingleHook } = require('./setup_hooks');
const {
Expand Down
23 changes: 11 additions & 12 deletions src/data_types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
'use strict';

const util = require('util');
import Raw from './raw';
import util from 'util';
const invokableFunc = require('./utils/invokable');
const Raw = require('./raw');

export enum LENGTH_VARIANTS {
tiny = 'tiny',
Expand Down Expand Up @@ -67,7 +65,7 @@ class STRING extends DataType {
return chunks.join(' ');
}

uncast(value: string | typeof Raw | null): string {
uncast(value: string | Raw | null): string | Raw {
if (value == null || value instanceof Raw) return value;
return '' + value;
}
Expand Down Expand Up @@ -306,13 +304,14 @@ class DATE extends DataType {
return this._round(value);
}

uncast(value: null | typeof Raw | string | Date, _strict?: boolean): string | Date {
uncast(value: null | Raw | string | Date | { toDate: () => Date }, _strict?: boolean): string | Date | Raw {
const originValue = value;

if (value == null || value instanceof Raw) return value;
if (typeof value.toDate === 'function') {
value = value.toDate();
}
// type narrowing doesn't handle `return value` correctly
if (value == null) return value as null | undefined;
if (value instanceof Raw) return value;
// Date | Moment
if (typeof value === 'object' && 'toDate' in value) value = value.toDate();

// @deprecated
// vaguely standard date formats such as 2021-10-15 15:50:02,548
Expand All @@ -324,8 +323,8 @@ class DATE extends DataType {

// 1634611135776
// '2021-10-15T08:38:43.877Z'
if (!(value instanceof Date)) value = new Date(value);
if (isNaN(value)) throw new Error(util.format('invalid date: %s', originValue));
if (!(value instanceof Date)) value = new Date((value as string));
if (isNaN((value as any))) throw new Error(util.format('invalid date: %s', originValue));

return this._round(value);
}
Expand Down
2 changes: 1 addition & 1 deletion src/drivers/abstract/spellbook.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const SqlString = require('sqlstring');

const { copyExpr, findExpr, walkExpr } = require('../../expr');
const { formatExpr, formatConditions, collectLiteral, isAggregatorExpr } = require('../../expr_formatter');
const Raw = require('../../raw');
const Raw = require('../../raw').default;

/**
* Create a subquery to make sure OFFSET and LIMIT on left table takes effect.
Expand Down
2 changes: 1 addition & 1 deletion src/drivers/postgres/data_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const { default: DataTypes } = require('../../data_types');
const util = require('util');
const Raw = require('../../raw');
const Raw = require('../../raw').default;


class Postgres_DATE extends DataTypes.DATE {
Expand Down
3 changes: 3 additions & 0 deletions src/expr.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

const Raw = require('./raw').default;

/**
* This module contains a simple SQL expression parser which parses `select_expr` and `expr` in `WHERE`/`HAVING`/`ON` conditions. Most of {@link Spell}'s functionalities are made possible because of this parser. Currently, it cannot parse a full SQL.
* @module
Expand Down Expand Up @@ -133,6 +135,7 @@ function parseValue(value) {
* @returns {Object[]}
*/
function parseExprList(str, ...values) {
if (str instanceof Raw) return [ str ];
let i = 0;
let chr = str[i];
let valueIndex = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/query_object.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const util = require('util');
const { isPlainObject } = require('./utils');
const { parseExpr } = require('./expr');
const Raw = require('./raw');
const Raw = require('./raw').default;
// deferred to break cyclic dependencies
let Spell;

Expand Down
9 changes: 0 additions & 9 deletions src/raw.js

This file was deleted.

21 changes: 21 additions & 0 deletions src/raw.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export default class Raw {
value: string;

// consumed in expr_formatter.js
type: string = 'raw';

constructor(value: string) {
if (typeof value !== 'string') {
throw new Error('invalid type of raw value');
}
this.value = value;
}

toString() {
return this.value;
}

static build(value: string) {
return new Raw(value);
}
};
2 changes: 1 addition & 1 deletion src/realm.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const { findDriver, AbstractDriver } = require('./drivers');
const { camelCase } = require('./utils/string');
const { isBone } = require('./utils');
const sequelize = require('./adapters/sequelize');
const Raw = require('./raw');
const Raw = require('./raw').default;
const { LEGACY_TIMESTAMP_MAP } = require('./constants');

const SequelizeBone = sequelize(Bone);
Expand Down
25 changes: 19 additions & 6 deletions src/spell.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
Literal, command, Raw, Connection,
ResultSet, QueryResult,
QueryOptions, SetOptions, WithOptions,
Collection, WhereConditions, OrderOptions,
Collection, WhereConditions, OrderOptions, BoneColumns,
} from './types/common';
import { AbstractBone } from './types/abstract_bone';
import { Hint, IndexHint, CommonHintsArgs, HintInterface } from './hint';
Expand Down Expand Up @@ -134,15 +134,28 @@ export class Spell<T extends typeof AbstractBone, U = InstanceType<T> | Collecti
$limit(rowCount: number): Spell<T, U>;
limit(rowCount: number): Spell<T, U>;

count(name?: string): Spell<T, Extract<U, ResultSet<T> | number>>;
average(name?: string): Spell<T, Extract<U, ResultSet<T> | number>>;
minimum(name?: string): Spell<T, Extract<U, ResultSet<T> | number>>;
maximum(name?: string): Spell<T, Extract<U, ResultSet<T> | number>>;
sum(name?: string): Spell<T, Extract<U, ResultSet<T> | number>>;
// aggregator(name: string) for Model.first/all/last.aggregator(name) because of ts(2526)
count(name?: BoneColumns<T>): Spell<T, Extract<U, ResultSet<T> | number>>;
count(name?: Raw | string): Spell<T, Extract<U, ResultSet<T> | number>>;

average(name?: BoneColumns<T>): Spell<T, Extract<U, ResultSet<T> | number>>;
average(name?: Raw | string): Spell<T, Extract<U, ResultSet<T> | number>>;

minimum(name?: BoneColumns<T>): Spell<T, Extract<U, ResultSet<T> | number>>;
minimum(name?: Raw | string): Spell<T, Extract<U, ResultSet<T> | number>>;

maximum(name?: BoneColumns<T>): Spell<T, Extract<U, ResultSet<T> | number>>;
maximum(name?: Raw | string): Spell<T, Extract<U, ResultSet<T> | number>>;

sum(name?: BoneColumns<T>): Spell<T, Extract<U, ResultSet<T> | number>>;
sum(name?: Raw | string): Spell<T, Extract<U, ResultSet<T> | number>>;

batch(size?: number): AsyncIterable<T>;

increment(name: BoneColumns<T>, by?: number, options?: QueryOptions): Spell<T, QueryResult>;
increment(name: string, by?: number, options?: QueryOptions): Spell<T, QueryResult>;

decrement(name: BoneColumns<T>, by?: number, options?: QueryOptions): Spell<T, QueryResult>;
decrement(name: string, by?: number, options?: QueryOptions): Spell<T, QueryResult>;

toSqlString(): string;
Expand Down
8 changes: 6 additions & 2 deletions src/spell.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const { parseExprList, parseExpr, walkExpr } = require('./expr');
const { isPlainObject } = require('./utils');
const { IndexHint, INDEX_HINT_TYPE, Hint } = require('./hint');
const { parseObject } = require('./query_object');
const Raw = require('./raw');
const Raw = require('./raw').default;
const { AGGREGATOR_MAP } = require('./constants');

/**
Expand Down Expand Up @@ -961,7 +961,11 @@ for (const aggregator in AGGREGATOR_MAP) {
configurable: true,
writable: true,
value: function Spell_aggregator(name = '*') {
if (name != '*' && parseExpr(name).type != 'id') {
if (name instanceof Raw) {
this.$select(Raw.build(`${func.toUpperCase()}(${name}) AS ${aggregator}`));
return this
}
if (name !== '*' && parseExpr(name).type !== 'id') {
throw new Error(`unexpected operand ${name} for ${func.toUpperCase()}()`);
}
this.$select(`${func}(${name}) as ${aggregator}`);
Expand Down
Loading

0 comments on commit e8e2bb3

Please sign in to comment.