From 0d34906b771e88b8b783c69d357491fccbc7167f Mon Sep 17 00:00:00 2001 From: Chen Yangjian <252317+cyjake@users.noreply.github.com> Date: Wed, 1 Nov 2023 16:47:17 +0800 Subject: [PATCH] refactor: turn on compilerOptions.strict (#403) --- .eslintignore | 1 + .gitignore | 10 +- package.json | 21 ++- src/adapters/sequelize.js | 9 +- src/browser.ts | 6 +- src/data_types.ts | 37 +++-- src/decorators.ts | 14 +- src/drivers/index.d.ts | 7 +- src/drivers/index.js | 2 + src/drivers/sqljs/index.ts | 3 +- src/drivers/sqljs/sqljs-connection.ts | 9 +- index.d.ts => src/index.d.ts | 18 +- index.js => src/index.js | 29 ++-- src/realm/base.js | 10 +- src/types/abstract_bone.d.ts | 21 ++- src/types/common.d.ts | 7 +- test/integration/custom.test.js | 5 +- test/integration/sqljs.test.js | 8 +- test/models/photo.ts | 12 +- test/start.sh | 18 +- test/types/basics.test.ts | 46 +++--- test/types/collection.test.ts | 12 +- test/types/custom_driver.test.ts | 43 ++--- test/types/decorators.test.ts | 154 +++++++++--------- test/types/querying.test.ts | 29 ++-- test/types/realm.test.ts | 4 +- test/types/sequelize.test.ts | 45 ++--- test/types/spell.test.ts | 58 ++++--- test/unit/adapters/sequelize.test.js | 5 +- test/unit/drivers/abstract/index.test.js | 3 +- test/unit/drivers/abstract/spellbook.test.js | 30 ++-- test/unit/drivers/sqljs/index.test.js | 2 +- test/unit/hint.test.js | 9 +- test/unit/realm.test.js | 10 +- test/unit/utils/index.test.js | 4 +- .../utils/{string.test.js => string.test.ts} | 11 +- tsconfig.browser.json | 9 +- tsconfig.json | 28 +++- 38 files changed, 396 insertions(+), 353 deletions(-) rename index.d.ts => src/index.d.ts (89%) rename index.js => src/index.js (62%) rename test/unit/utils/{string.test.js => string.test.ts} (63%) diff --git a/.eslintignore b/.eslintignore index f605ddb0..2c926a3d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ +dist docs types diff --git a/.gitignore b/.gitignore index 46b8ae32..170f1835 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ coverage docs/vendor docs/.bundle dist +lib logs node_modules tmp @@ -11,12 +12,3 @@ tmp docs/Gemfile.lock package-lock.json .DS_Store -test/types/*.js -test/types/*.map -test/integration/suite/sharding.test.js* -test/models/photo.js* -src/browser.js* -src/decorators.js* -src/data_types.js* -src/drivers/sqljs/*.js* -src/raw.js* diff --git a/package.json b/package.json index 4436b318..56f49798 100644 --- a/package.json +++ b/package.json @@ -2,21 +2,21 @@ "name": "leoric", "version": "2.11.4", "description": "JavaScript Object-relational mapping alchemy", - "main": "index.js", "browser": "dist/browser.js", - "types": "index.d.ts", + "main": "lib/index.js", + "types": "lib/index.d.ts", "files": [ "dist", - "src", - "index.js", - "index.d.ts" + "lib" ], "scripts": { "jsdoc": "rm -rf docs/api && jsdoc -c .jsdoc.json -d docs/api -t node_modules/@cara/minami", "clean": "tsc -b --clean", "lint-staged": "lint-staged", - "prepack": "tsc", - "prepack:browser": "rm -rf dist && tsc -p tsconfig.browser.json", + "copy-dts": "mkdir -p lib && cd src && rsync -R ./**/*.d.ts ./*.d.ts ../lib && cd -", + "copy-dts:browser": "mkdir -p dist && cd src && rsync -R ./**/*.d.ts ./*.d.ts ../dist && cd -", + "prepack": "tsc && npm run copy-dts", + "prepack:browser": "rm -rf dist && tsc -p tsconfig.browser.json && npm run copy-dts:browser", "prepublishOnly": "npm run prepack && npm run prepack:browser", "pretest": "npm run prepack && ./test/prepare.sh", "test": "./test/start.sh", @@ -101,18 +101,20 @@ "@babel/eslint-parser": "^7.14.7", "@cara/minami": "^1.2.3", "@journeyapps/sqlcipher": "^5.2.0", + "@tsconfig/node16": "^16.1.1", "@types/mocha": "^9.0.0", "@types/node": "^16.10.1", + "@types/sinon": "^10.0.20", "@types/sql.js": "^1.4.4", "@typescript-eslint/eslint-plugin": "^5.59.2", "@typescript-eslint/parser": "^5.59.2", - "eslint": "^7.20.0", + "eslint": "^8.52.0", "eslint-plugin-no-only-tests": "^3.0.0", "expect.js": "^0.3.1", "husky": "^8.0.3", "jsdoc": "^3.6.3", "lint-staged": "^13.2.2", - "mocha": "^8.2.1", + "mocha": "^10.2.0", "mysql": "^2.17.1", "mysql2": "^2.3.0", "nyc": "^15.1.0", @@ -120,6 +122,7 @@ "sinon": "^10.0.0", "sql.js": "^1.8.0", "sqlite3": "^5.0.2", + "ts-node": "^10.9.1", "typescript": "^4.6.2" } } diff --git a/src/adapters/sequelize.js b/src/adapters/sequelize.js index 498a49fc..fce70d24 100644 --- a/src/adapters/sequelize.js +++ b/src/adapters/sequelize.js @@ -113,7 +113,7 @@ module.exports = function sequelize(Bone) { * @protect * store all configured scopes */ - static _scopes = {} + static _scopes = {}; // scope static _scope = null; @@ -159,7 +159,6 @@ module.exports = function sequelize(Bone) { const parentTable = this.table; const parent = this; class ScopeClass extends this { - static name = parentName; static get synchronized() { return parent.synchronized; } @@ -167,6 +166,12 @@ module.exports = function sequelize(Bone) { return parentTable; } } + Object.defineProperty(ScopeClass, 'name', { + value: parentName, + writable: false, + enumerable: false, + configurable: true, + }); ScopeClass.setScope(name, ...args); return ScopeClass; diff --git a/src/browser.ts b/src/browser.ts index bbd52877..93a2fd03 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -9,6 +9,8 @@ const Realm = require('./realm/base'); const AbstractDriver = require('./drivers/abstract'); const { isBone } = require('./utils'); +import { ConnectOptions } from './drivers'; + /** * @typedef {Object} RawSql * @property {boolean} __raw @@ -25,7 +27,7 @@ const { isBone } = require('./utils'); * @param {string|Bone[]} opts.models - an array of models * @returns {Pool} the connection pool in case we need to perform raw query */ -export const connect = async function connect(opts) { +export const connect = async function connect(opts: ConnectOptions & { Bone?: typeof Bone }): Promise> { opts = { Bone, ...opts }; if (opts.Bone.driver) throw new Error('connected already'); const realm = new Realm(opts); @@ -33,7 +35,7 @@ export const connect = async function connect(opts) { return realm; }; -export const disconnect = async function disconnect(realm, ...args) { +export const disconnect = async function disconnect(realm: InstanceType, ...args: unknown[]) { if (realm instanceof Realm && realm.connected) { return await realm.disconnect(...args); } diff --git a/src/data_types.ts b/src/data_types.ts index 3d13dd53..016b5afe 100644 --- a/src/data_types.ts +++ b/src/data_types.ts @@ -8,7 +8,7 @@ export enum LENGTH_VARIANTS { empty = '', medium = 'medium', long = 'long', -}; +} export interface AbstractDataType { new (dataLength?: LENGTH_VARIANTS | number): DataType & T; @@ -24,7 +24,7 @@ export interface AbstractDataType { */ export abstract class DataType { - dataType: string = ''; + dataType = ''; dataLength?: string | number; /** @@ -59,7 +59,7 @@ function hasDataLength(dataLength: string | number | undefined) { * @param {number} dataLength */ class STRING extends DataType { - constructor(dataLength: number = 255) { + constructor(dataLength = 255) { super(); this.dataType = 'varchar'; this.dataLength = dataLength; @@ -90,7 +90,7 @@ class STRING extends DataType { } class CHAR extends STRING { - constructor(dataLength: number = 255) { + constructor(dataLength = 255) { super(dataLength); this.dataType = 'char'; } @@ -289,7 +289,7 @@ class DATE extends DataType { precision?: number | null; timezone?: boolean = true; - constructor(precision?: number | null, timezone: boolean = true) { + constructor(precision?: number | null, timezone = true) { super(); this.dataType = 'datetime'; this.precision = precision; @@ -305,7 +305,7 @@ class DATE extends DataType { return dataType; } - _round(value) { + _round(value: Date) { const { precision } = this; if (precision != null && precision < 3 && value instanceof Date) { const divider = 10 ** (3 - precision); @@ -359,7 +359,7 @@ class DATEONLY extends DATE { return this.dataType.toUpperCase(); } - _round(value) { + _round(value: Date) { if (value instanceof Date) { return new Date(value.getFullYear(), value.getMonth(), value.getDate()); } @@ -377,7 +377,7 @@ class BOOLEAN extends DataType { return this.dataType.toUpperCase(); } - cast(value) { + cast(value: boolean) { if (value == null) return value; return Boolean(value); } @@ -412,7 +412,7 @@ class BLOB extends DataType { return [ this.dataLength, this.dataType ].join('').toUpperCase(); } - cast(value) { + cast(value: Buffer | string) { if (value == null) return value; if (Buffer.isBuffer(value)) return value; return Buffer.from(value); @@ -434,7 +434,7 @@ class MYJSON extends DataType { return 'TEXT'; } - cast(value) { + cast(value: null | object | string) { if (!value) return value; // type === JSONB if (typeof value === 'object') return value; @@ -446,7 +446,7 @@ class MYJSON extends DataType { } } - uncast(value) { + uncast(value: null | Raw | object) { if (value == null || value instanceof Raw) return value; return global.JSON.stringify(value); } @@ -471,7 +471,8 @@ class JSONB extends MYJSON { } class VIRTUAL extends DataType { - virtual: boolean = true; + virtual = true; + constructor() { super(); this.dataType = 'virtual'; @@ -532,15 +533,17 @@ class DataTypes { static BOOLEAN: DATA_TYPE = BOOLEAN as any; static findType(columnType: string): DataTypes { + /* eslint-disable @typescript-eslint/no-shadow */ const { CHAR, STRING, TEXT, DATE, DATEONLY, - TINYINT, SMALLINT, MEDIUMINT, INTEGER, + TINYINT, SMALLINT, MEDIUMINT, INTEGER, BIGINT, DECIMAL, BOOLEAN, BINARY, VARBINARY, BLOB, } = this; + /* eslint-enable @typescript-eslint/no-shadow */ const res = columnType?.match(/(\w+)(?:\((\d+)(?:,(\d+))?\))?/); - if(!res) { + if (!res) { throw new Error(`Unknown columnType ${columnType}`); } const [ , dataType, ...matches ] = res; @@ -548,7 +551,7 @@ class DataTypes { for (let i = 0; i < matches.length; i++) { if (matches[i] != null) params[i] = parseInt(matches[i], 10); } - + switch (dataType) { case 'char': return new CHAR(...params); @@ -608,12 +611,12 @@ class DataTypes { static get invokable() { return new Proxy(this, { - get(target, p) { + get(target, p: keyof DataTypes) { const value = target[p]; if (AllDataTypes.hasOwnProperty(p)) return invokableFunc(value); return value; } - }); + }); } /** diff --git a/src/decorators.ts b/src/decorators.ts index de846b08..515e67a4 100644 --- a/src/decorators.ts +++ b/src/decorators.ts @@ -13,7 +13,7 @@ interface ColumnOption extends Omit { } } -function findType(tsType) { +function findType(tsType: typeof BigInt | typeof Number | typeof Date | typeof String | typeof Boolean) { const { BIGINT, INTEGER, DATE, @@ -40,7 +40,7 @@ function findType(tsType) { export function Column(options: ColumnOption | DATA_TYPE | DataType = {}) { return function(target: Bone, propertyKey: string) { // target refers to model prototype, an internal instance of `Bone {}` - if (options['prototype'] instanceof DataType || options instanceof DataType) { + if (('prototype' in options && options['prototype'] instanceof DataType) || options instanceof DataType) { options = { type: options as DATA_TYPE }; } @@ -55,7 +55,7 @@ export function Column(options: ColumnOption | DATA_TYPE | DataType = // target refers to model prototype, an internal instance of `Bone {}` const model = target.constructor as any; if (!model.hasOwnProperty('attributes') || !model.attributes) { - Object.defineProperty(model, 'attributes', { + Object.defineProperty(model, 'attributes', { value: { ...model.attributes }, writable: false, enumerable: false, @@ -77,7 +77,7 @@ export function HasMany(options: AssociateOptions = {}) { ...Reflect.getMetadata(hasMany, model), [propertyKey]: options, }, model); - } + }; } export function HasOne(options: AssociateOptions = {}) { @@ -91,12 +91,12 @@ export function HasOne(options: AssociateOptions = {}) { ...Reflect.getMetadata(hasOne, model), [propertyKey]: options, }, model); - } + }; } /** * design:type will be `Function { [native code] }` in following example - * + * * @example * import type Foo from './foo'; * class Bar extends Bone { @@ -115,5 +115,5 @@ export function BelongsTo(options: AssociateOptions = {}) { ...Reflect.getMetadata(belongsTo, model), [propertyKey]: options, }, model); - } + }; } diff --git a/src/drivers/index.d.ts b/src/drivers/index.d.ts index 502ae9b8..34a50d0e 100644 --- a/src/drivers/index.d.ts +++ b/src/drivers/index.d.ts @@ -63,7 +63,7 @@ export class AbstractDriver { * disconnect manually * @param callback */ - disconnect(callback?: Function): Promise; + disconnect(callback?: () => Promise): Promise; /** * query with spell @@ -186,3 +186,8 @@ export class SqliteDriver extends AbstractDriver { type: 'sqlite'; dialect: 'sqlite'; } + +export class SqljsDriver extends AbstractDriver { + type: 'sqlite'; + dialect: 'sqlite'; +} diff --git a/src/drivers/index.js b/src/drivers/index.js index 2a8dd85e..649b2509 100644 --- a/src/drivers/index.js +++ b/src/drivers/index.js @@ -3,6 +3,7 @@ const MysqlDriver = require('./mysql'); const PostgresDriver = require('./postgres'); const SqliteDriver = require('./sqlite'); +const { default: SqljsDriver } = require('./sqljs'); const AbstractDriver = require('./abstract'); function findDriver(dialect) { @@ -25,5 +26,6 @@ module.exports = { MysqlDriver, PostgresDriver, SqliteDriver, + SqljsDriver, AbstractDriver, }; diff --git a/src/drivers/sqljs/index.ts b/src/drivers/sqljs/index.ts index 212a90de..3bf64718 100644 --- a/src/drivers/sqljs/index.ts +++ b/src/drivers/sqljs/index.ts @@ -22,12 +22,14 @@ export default class SqljsDriver extends SqliteDriver { /** * @override */ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore pool: SqljsConnection; /** * @override */ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore createPool(opts: DriverOptions) { const { database, ...restOpts } = opts; @@ -54,7 +56,6 @@ export default class SqljsDriver extends SqliteDriver { }); } - // @ts-ignore const { logger } = this; const logOpts = { ...spell, query }; const sql = logger.format(query, values, spell); diff --git a/src/drivers/sqljs/sqljs-connection.ts b/src/drivers/sqljs/sqljs-connection.ts index da19e9f0..261f91eb 100644 --- a/src/drivers/sqljs/sqljs-connection.ts +++ b/src/drivers/sqljs/sqljs-connection.ts @@ -2,6 +2,7 @@ import type { Database, QueryExecResult } from 'sql.js'; import type { SqljsConnectionOptions, SqljsConnectionQueryResult, SqljsQueryQuery, SqljsQueryValues } from './interface'; import type { SpellMeta } from '../../spell'; +import type { Literal } from '../../types/common'; /** * 组装和转换结果 @@ -14,7 +15,7 @@ function dataConvert(result: QueryExecResult) { return { fields: columns, rows: values.map((val) => { - return columns.reduce((prev, col, index) => { + return columns.reduce((prev: Record, col, index) => { prev[col] = val[index]; return prev; }, {}); @@ -36,13 +37,13 @@ function normalizeResult(res: QueryExecResult[]): SqljsConnectionQueryResult { // SELECT users.id AS "users:id", ... // => [ { users: { id, ... } } ] -function nest(rows, fields, spell) { +function nest(rows: Record[], fields: string[], spell: SpellMeta) { const { Model } = spell; const { tableAlias } = Model; const results: any[] = []; for (const row of rows) { - const result = {}; + const result: Record> = {}; const qualified = Object.keys(row).some(entry => entry.includes(':')); for (const key in row) { const parts = key.split(':'); @@ -106,7 +107,7 @@ export class SqljsConnection { if (/^(?:pragma|select)/i.test(sql)) { const result = await this._executeSQL(sql, values); - if (nestTables) return nest(result.rows, result.fields, spell); + if (nestTables && spell) return nest(result.rows, result.fields, spell); return result; } diff --git a/index.d.ts b/src/index.d.ts similarity index 89% rename from index.d.ts rename to src/index.d.ts index 9fa86ca5..304e5c60 100644 --- a/index.d.ts +++ b/src/index.d.ts @@ -1,18 +1,18 @@ -import DataTypes, { DataType, AbstractDataType, LENGTH_VARIANTS } from './src/data_types'; +import DataTypes, { DataType, AbstractDataType, LENGTH_VARIANTS } from './data_types'; import { Hint, IndexHint, HintInterface, INDEX_HINT_SCOPE_TYPE, INDEX_HINT_SCOPE, INDEX_HINT_TYPE -} from './src/hint'; +} from './hint'; import { Literal, Validator, Connection, QueryOptions, WhereConditions, Raw, ColumnMeta, AttributeMeta, BeforeHooksType, AfterHooksType, Collection, GeneratorReturnType, Values, BoneCreateValues, BoneInstanceValues, -} from './src/types/common'; -import { SpellMeta, Spell, SpellBookFormatResult } from './src/spell'; -import Bone from './src/bone'; -import { ConnectOptions, AbstractDriver } from './src/drivers'; +} from './types/common'; +import { SpellMeta, Spell, SpellBookFormatResult } from './spell'; +import Bone from './bone'; +import { ConnectOptions, AbstractDriver } from './drivers'; export { LENGTH_VARIANTS as LENGTH_VARIANTS, @@ -22,9 +22,9 @@ export { SpellMeta, Spell, ColumnMeta, AttributeMeta, SpellBookFormatResult, Values, BoneCreateValues, BoneInstanceValues, }; -export * from './src/decorators'; -export * from './src/drivers'; -export * from './src/adapters/sequelize'; +export * from './decorators'; +export * from './drivers'; +export * from './adapters/sequelize'; interface InitOptions { underscored?: boolean; diff --git a/index.js b/src/index.js similarity index 62% rename from index.js rename to src/index.js index 94505cc9..cb13f6f6 100644 --- a/index.js +++ b/src/index.js @@ -1,20 +1,20 @@ 'use strict'; -const Logger = require('./src/drivers/abstract/logger'); -const Spell = require('./src/spell'); -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 { 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').default; -const { MysqlDriver, PostgresDriver, SqliteDriver, AbstractDriver } = require('./src/drivers'); -const { isBone } = require('./src/utils'); +const Logger = require('./drivers/abstract/logger'); +const Spell = require('./spell'); +const Bone = require('./bone'); +const Collection = require('./collection'); +const { invokable: DataTypes, LENGTH_VARIANTS } = require('./data_types'); +const migrations = require('./migrations'); +const sequelize = require('./adapters/sequelize'); +const { heresql } = require('./utils/string'); +const Hint = require('./hint'); +const Realm = require('./realm'); +const Decorators = require('./decorators'); +const Raw = require('./raw').default; +const { MysqlDriver, PostgresDriver, SqliteDriver, SqljsDriver, AbstractDriver } = require('./drivers'); +const { isBone } = require('./utils'); /** * @typedef {Object} RawSql @@ -63,6 +63,7 @@ Object.assign(Realm, { MysqlDriver, PostgresDriver, SqliteDriver, + SqljsDriver, AbstractDriver, Raw, LENGTH_VARIANTS, diff --git a/src/realm/base.js b/src/realm/base.js index c00a5bb1..ce583a09 100644 --- a/src/realm/base.js +++ b/src/realm/base.js @@ -126,9 +126,13 @@ class BaseRealm { } define(name, attributes, opts = {}, descriptors = {}) { - const Model = class extends this.Bone { - static name = name; - }; + const Model = class extends this.Bone {}; + Object.defineProperty(Model, 'name', { + value: name, + writable: false, + enumerable: false, + configurable: true, + }); Model.init(attributes, opts, descriptors); this.Bone.models[name] = Model; return Model; diff --git a/src/types/abstract_bone.d.ts b/src/types/abstract_bone.d.ts index f75ed175..f59da4dc 100644 --- a/src/types/abstract_bone.d.ts +++ b/src/types/abstract_bone.d.ts @@ -1,5 +1,5 @@ -import DataTypes, { AbstractDataType, DataType } from "../data_types"; -import { +import DataTypes, { AbstractDataType, DataType } from '../data_types'; +import { Pool, Literal, WhereConditions, Collection, ResultSet, OrderOptions, QueryOptions, AttributeMeta, AssociateOptions, Values, Connection, BulkCreateOptions, BoneCreateValues, @@ -63,6 +63,11 @@ export class AbstractBone { */ static attributes: { [key: string]: AbstractDataType | AttributeMeta }; + /** + * The attribute definitions of the model, referenced by column name. + */ + static attributeMap: { [key: string]: AbstractDataType | AttributeMeta }; + /** * The actual attribute definitions of the model. */ @@ -111,13 +116,13 @@ export class AbstractBone { static alias(this: T, name: BoneColumns): string; static alias(this: T, data: { [key in BoneColumns]: Literal }): Record; - static unalias(this: T, name: BoneColumns): string; - - // after alias/unalias static alias(this: T, name: string): string; static alias(this: T, data: Record): Record; + + static unalias(this: T, name: BoneColumns): string; static unalias(this: T, name: string): string; + static hasOne(name: string, opts?: AssociateOptions): void; static hasMany(name: string, opts?: AssociateOptions): void; static belongsTo(name: string, opts?: AssociateOptions): void; @@ -247,7 +252,7 @@ export class AbstractBone { */ static transaction Generator>(callback: T): Promise>>; static transaction Promise>(callback: T): Promise>; - + static describe(): Promise<{[key: string]: any[]}>; /** @@ -333,8 +338,8 @@ export class AbstractBone { previousChanges>(this: T, name: Key): boolean; previousChanges(this: T, name: Key): boolean; - previousChanges>(this: T ): Array; - previousChanges(this: T ): Array; + previousChanges>(this: T): Array; + previousChanges(this: T): Array; /** * Persist changes of current record to database. If current record has never been saved before, an INSERT query is performed. If the primary key was set and is not changed since, an UPDATE query is performed. If the primary key is changed, an INSERT ... UPDATE query is performed instead. diff --git a/src/types/common.d.ts b/src/types/common.d.ts index 1adfeaee..13ac6a33 100644 --- a/src/types/common.d.ts +++ b/src/types/common.d.ts @@ -5,6 +5,7 @@ import { AbstractBone } from './abstract_bone'; export type Literal = null | undefined | boolean | number | bigint | string | Date | Record | ArrayBuffer; +// eslint-disable-next-line @typescript-eslint/ban-types type BaseValidateArgs = boolean | RegExp | Function | Array> | string | Array; export type Validator = BaseValidateArgs | { @@ -159,7 +160,7 @@ export class Raw { export type SetOptions = { [Property in BoneColumns]: Literal -} | { +} | { [key: string]: Literal }; @@ -171,7 +172,7 @@ type OrderSortType = 'desc' | 'asc' | Uppercase<'desc' | 'asc'>; type OrderOptions = { [Property in Extract, Literal>]?: OrderSortType -} | [ BoneColumns, OrderSortType ] +} | [ BoneColumns, OrderSortType ] | Array | [ BoneColumns, OrderSortType ] | Raw | string | Array> | string | Raw; @@ -198,6 +199,7 @@ export type PickTypeKeys = ({ [P in export type NullablePartial = { [P in keyof T]?: T[P] | null }; +// eslint-disable-next-line @typescript-eslint/ban-types export type Values = NullablePartial | 'isNewRecord' | 'Model' | 'dataValues'>>; export type BoneColumns = keyof Values>> = Key; @@ -219,4 +221,5 @@ type GeneratorReturnType = T extends Generator = { id: number, name: string } */ +// eslint-disable-next-line @typescript-eslint/ban-types export type BoneInstanceValues = Omit, PickTypeKeys, Function> | 'isNewRecord' | 'Model' | 'dataValues'>; diff --git a/test/integration/custom.test.js b/test/integration/custom.test.js index 65f14e51..3cae1bfd 100644 --- a/test/integration/custom.test.js +++ b/test/integration/custom.test.js @@ -5,13 +5,10 @@ const path = require('path'); const sinon = require('sinon'); const SqlString = require('sqlstring'); -const { connect, raw, Bone, disconnect } = require('../..'); +const { connect, raw, Bone, disconnect, Raw, SqliteDriver } = require('../..'); const { checkDefinitions } = require('./helpers'); const { formatConditions, collectLiteral } = require('../../src/expr_formatter'); const { findExpr } = require('../../src/expr'); -const Raw = require('../../src/raw').default; - -const SqliteDriver = require('../../src/drivers/sqlite'); class MySpellbook extends SqliteDriver.Spellbook { diff --git a/test/integration/sqljs.test.js b/test/integration/sqljs.test.js index 2ddbf025..2bbe2cd1 100644 --- a/test/integration/sqljs.test.js +++ b/test/integration/sqljs.test.js @@ -5,10 +5,10 @@ const path = require('path'); const fs = require('fs').promises; const sinon = require('sinon'); -const { raw, Bone } = require('../..'); -const Realm = require('../../src/realm/base'); +const Realm = require('../..'); const { checkDefinitions } = require('./helpers'); -const { default: SqljsDriver } = require('../../src/drivers/sqljs'); + +const { raw, Bone, SqljsDriver } = Realm; async function migrate(dbDriver) { let content = await fs.readFile(path.resolve(__dirname, '../dumpfile.sql'), 'utf-8'); @@ -29,7 +29,7 @@ describe('integration tests for sqljs', () => { return model?.default || model; }); - const realm = new Realm({ + const realm = await new Realm({ database: 'sqljs', driver: SqljsDriver, models, diff --git a/test/models/photo.ts b/test/models/photo.ts index 63e75614..3f2a358a 100644 --- a/test/models/photo.ts +++ b/test/models/photo.ts @@ -2,23 +2,23 @@ import { BelongsTo, Bone, Column } from '../..'; import User from './user'; export default class Photo extends Bone { - static shardingKey: string = 'userId'; + static shardingKey = 'userId'; @Column() - id: bigint; + id!: bigint; @Column() - userId: bigint; + userId!: bigint; @Column() - url: string; + url!: string; @Column() - filename: string; + filename!: string; @Column({ allowNull: true }) caption?: string; @BelongsTo({ foreignKey: 'userId' }) - user: User; + user?: User; } diff --git a/test/start.sh b/test/start.sh index c5cff924..b2a3610d 100755 --- a/test/start.sh +++ b/test/start.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + args= function run { @@ -8,8 +10,8 @@ function run { args=("${args[@]:1}"); fi echo ""; - printf '"%s" ' "${args[@]}" | xargs echo "> DEBUG=leoric mocha -R dot --exit --timeout 5000 ${file}"; - printf '"%s" ' "${args[@]}" | DEBUG=leoric NODE_OPTIONS=--enable-source-maps xargs mocha -R dot --exit --timeout 5000 ${file} || exit $?; + printf '"%s" ' "${args[@]}" | xargs echo "> DEBUG=leoric mocha --node-option require=ts-node/register,enable-source-maps -R dot --exit --timeout 5000 ${file}"; + printf '"%s" ' "${args[@]}" | DEBUG=leoric xargs mocha --node-option require=ts-node/register,enable-source-maps -R dot --exit --timeout 5000 ${file} || exit $?; } ## @@ -17,7 +19,7 @@ function run { function unit { # recursive glob nor available in bash 3 # - https://unix.stackexchange.com/questions/49913/recursive-glob - run "$(ls test/unit/{,drivers/,drivers/*/,adapters/,utils/}*.test.js)"; + run "$(ls test/unit/{,drivers/,drivers/*/,adapters/,utils/}*.test.{js,ts})"; } ## @@ -29,28 +31,28 @@ function integration { ## # definition type tests function dts { - run "$(ls test/types/*.test.js)"; + run "$(ls test/types/*.test.{js,ts})"; } case $1 in unit) args=("${@:2}") - npx tsc + npm run prepack unit ;; integration) args=("${@:2}") - npx tsc + npm run prepack integration ;; dts) args=("${@:2}") - npx tsc + npm run prepack dts ;; *.js) args=("${@:1}") - npx tsc + npm run prepack run $1 ;; *) diff --git a/test/types/basics.test.ts b/test/types/basics.test.ts index f06ecd8a..0c24d5ce 100644 --- a/test/types/basics.test.ts +++ b/test/types/basics.test.ts @@ -1,5 +1,5 @@ import { strict as assert } from 'assert'; -import sinon from 'sinon'; +import sinon, { SinonFakeTimers } from 'sinon'; import Realm, { Bone, Column, DataTypes, connect, Raw } from '../..'; describe('=> Basics (TypeScript)', function() { @@ -10,43 +10,43 @@ describe('=> Basics (TypeScript)', function() { static table = 'articles'; @Column() - id: bigint; + id!: bigint; @Column({ name: 'gmt_create' }) - createdAt: Date; + createdAt!: Date; - @Column({ name: 'gmt_modified'}) - updatedAt: Date; + @Column({ name: 'gmt_modified' }) + updatedAt!: Date; @Column({ name: 'gmt_deleted' }) - deletedAt: Date; + deletedAt!: Date; @Column() - title: string; + title!: string; @Column(TEXT) - content: string; + content!: string; @Column(TEXT) - extra: string; + extra!: string; @Column() get thumb(): string { return this.attribute('thumb'); - }; + } set thumb(value: string) { this.attribute('thumb', value.replace('http://', 'https://')); } @Column() - authorId: bigint; + authorId!: bigint; @Column() - isPrivate: boolean; + isPrivate!: boolean; @Column(TEXT) - summary: string; + summary!: string; @Column(TEXT) get settings(): Record | null { @@ -65,7 +65,7 @@ describe('=> Basics (TypeScript)', function() { } @Column() - wordCount: number; + wordCount!: number; @Column(DataTypes.VIRTUAL) get isEmptyContent(): boolean { @@ -144,7 +144,7 @@ describe('=> Basics (TypeScript)', function() { assert.deepEqual(article.settings, { bar: 2 }); assert.equal(article.isPrivate, true); assert.equal(article.callback(), 1); - }) + }); it('bone.attribute(name, value)', async function() { const post = new Post({}); @@ -484,40 +484,40 @@ describe('=> Basics (TypeScript)', function() { describe('Num', () => { - let clock; + let clock: SinonFakeTimers; before(() => { const date = new Date(2017, 11, 12); const fakeDate = date.getTime(); - sinon.useFakeTimers(fakeDate); + clock = sinon.useFakeTimers(fakeDate); }); - + after(() => { clock?.restore(); }); it('count', () => { assert.equal(Post.count('authorId').toSqlString(), 'SELECT COUNT("author_id") AS "count" FROM "articles" WHERE "gmt_deleted" IS NULL'); - assert.equal(Post.count(new Raw("DISTINCT(author_id)")).toSqlString(), 'SELECT COUNT(DISTINCT(author_id)) AS count FROM "articles" WHERE "gmt_deleted" IS NULL'); + assert.equal(Post.count(new Raw('DISTINCT(author_id)')).toSqlString(), 'SELECT COUNT(DISTINCT(author_id)) AS count FROM "articles" WHERE "gmt_deleted" IS NULL'); }); it('average', () => { assert.equal(Post.average('wordCount').toSqlString(), 'SELECT AVG("word_count") AS "average" FROM "articles" WHERE "gmt_deleted" IS NULL'); - assert.equal(Post.average(new Raw("DISTINCT(word_count)")).toSqlString(), 'SELECT AVG(DISTINCT(word_count)) AS average FROM "articles" WHERE "gmt_deleted" IS NULL'); + assert.equal(Post.average(new Raw('DISTINCT(word_count)')).toSqlString(), 'SELECT AVG(DISTINCT(word_count)) AS average FROM "articles" WHERE "gmt_deleted" IS NULL'); }); it('minimum', () => { assert.equal(Post.minimum('wordCount').toSqlString(), 'SELECT MIN("word_count") AS "minimum" FROM "articles" WHERE "gmt_deleted" IS NULL'); - assert.equal(Post.minimum(new Raw("DISTINCT(word_count)")).toSqlString(), 'SELECT MIN(DISTINCT(word_count)) AS minimum FROM "articles" WHERE "gmt_deleted" IS NULL'); + assert.equal(Post.minimum(new Raw('DISTINCT(word_count)')).toSqlString(), 'SELECT MIN(DISTINCT(word_count)) AS minimum FROM "articles" WHERE "gmt_deleted" IS NULL'); }); it('maximum', () => { assert.equal(Post.maximum('wordCount').toSqlString(), 'SELECT MAX("word_count") AS "maximum" FROM "articles" WHERE "gmt_deleted" IS NULL'); - assert.equal(Post.maximum(new Raw("DISTINCT(word_count)")).toSqlString(), 'SELECT MAX(DISTINCT(word_count)) AS maximum FROM "articles" WHERE "gmt_deleted" IS NULL'); + assert.equal(Post.maximum(new Raw('DISTINCT(word_count)')).toSqlString(), 'SELECT MAX(DISTINCT(word_count)) AS maximum FROM "articles" WHERE "gmt_deleted" IS NULL'); }); it('sum', () => { assert.equal(Post.sum('wordCount').toSqlString(), 'SELECT SUM("word_count") AS "sum" FROM "articles" WHERE "gmt_deleted" IS NULL'); - assert.equal(Post.sum(new Raw("DISTINCT(word_count)")).toSqlString(), 'SELECT SUM(DISTINCT(word_count)) AS sum FROM "articles" WHERE "gmt_deleted" IS NULL'); + assert.equal(Post.sum(new Raw('DISTINCT(word_count)')).toSqlString(), 'SELECT SUM(DISTINCT(word_count)) AS sum FROM "articles" WHERE "gmt_deleted" IS NULL'); }); it('increment', () => { diff --git a/test/types/collection.test.ts b/test/types/collection.test.ts index 4f10eeb2..65a8b2e1 100644 --- a/test/types/collection.test.ts +++ b/test/types/collection.test.ts @@ -3,12 +3,12 @@ import { Bone, Collection, connect } from '../..'; describe('=> Collection (TypeScript)', function() { class User extends Bone { - id: number; - createdAt: Date; - deletedAt: Date; - email: string; - nickname: string; - status: number; + id!: number; + createdAt!: Date; + deletedAt!: Date; + email!: string; + nickname!: string; + status!: number; } before(async function() { diff --git a/test/types/custom_driver.test.ts b/test/types/custom_driver.test.ts index 6662950c..27d5ec8c 100644 --- a/test/types/custom_driver.test.ts +++ b/test/types/custom_driver.test.ts @@ -7,7 +7,7 @@ const { findExpr } = require('../../src/expr'); interface FormatResult { table?: string; - whereArgs?: Array + whereArgs?: Array whereClause?: string, values?: Array | { [key: string]: Literal @@ -43,6 +43,7 @@ class MySpellbook extends SqliteDriver.Spellbook { const { Model, sets, whereConditions } = spell; const { shardingKey } = Model; const { escapeId } = Model.driver!; + if (Array.isArray(sets)) throw new Error('multiple sets not allowed in UPDATE command'); if (shardingKey) { if (sets!.hasOwnProperty(shardingKey) && sets![shardingKey] == null) { throw new Error(`Sharding key ${Model.table}.${shardingKey} cannot be NULL`); @@ -57,13 +58,13 @@ class MySpellbook extends SqliteDriver.Spellbook { } const table = escapeId(spell.table.value); - const opValues = {}; - Object.keys(spell.sets!).reduce((obj, key) => { - obj[escapeId(Model.unalias(key))] = spell.sets![key]; + const opValues: Record = {}; + Object.keys(sets!).reduce((obj, key) => { + obj[escapeId(Model.unalias(key))] = sets![key]; return obj; }, opValues); - - let whereArgs = []; + + const whereArgs: Literal[] = []; let whereClause = ''; if (whereConditions.length > 0) { for (const condition of whereConditions) collectLiteral(spell, condition, whereArgs); @@ -81,7 +82,7 @@ class MySpellbook extends SqliteDriver.Spellbook { const { Model, whereConditions } = spell; const { escapeId } = Model.driver!; const table = escapeId(spell.table.value); - let whereArgs = []; + const whereArgs: Literal[] = []; let whereClause = ''; if (whereConditions.length > 0) { for (const condition of whereConditions) collectLiteral(spell, condition, whereArgs); @@ -98,12 +99,13 @@ class MySpellbook extends SqliteDriver.Spellbook { const { Model, sets } = spell; const { escapeId } = Model.driver!; const table = escapeId(spell.table.value); - let values = {}; + if (Array.isArray(sets)) throw new Error('multiple sets not supported in INSERT command yet'); const { shardingKey } = Model; if (shardingKey && sets![shardingKey] == null) { throw new Error(`Sharding key ${Model.table}.${shardingKey} cannot be NULL.`); } + const values: Record = {}; for (const name in sets) { const value = sets[name]; values[escapeId(Model.unalias(name))] = value instanceof Raw? SqlString.raw(value.value) : value; @@ -115,11 +117,12 @@ class MySpellbook extends SqliteDriver.Spellbook { }; } -}; +} class CustomDriver extends SqliteDriver { static Spellbook = MySpellbook; + // @ts-ignore async cast(spell) { const { command } = spell; switch (command) { @@ -147,7 +150,7 @@ class CustomDriver extends SqliteDriver { } } - async update({ table, values, whereClause, whereArgs }, options?: SpellMeta) { + async update({ table, values, whereClause, whereArgs }: { table: string, values: Record, whereClause: string, whereArgs: Literal[] }, options?: SpellMeta) { const valueSets: string[] = []; const assignValues: Literal[] = []; Object.keys(values).map((key) => { @@ -158,7 +161,7 @@ class CustomDriver extends SqliteDriver { return await this.query(sql, assignValues.concat(whereArgs), options); } - async delete({ table, whereClause, whereArgs }, options) { + async delete({ table, whereClause, whereArgs }: { table: string, whereClause: string, whereArgs: Literal[] }, options?: SpellMeta) { const sql = `DELETE FROM ${table} ${whereClause}`; return await this.query(sql, whereArgs, options); } @@ -173,7 +176,7 @@ class CustomDriver extends SqliteDriver { const sql = `INSERT INTO ${table} (${valueSets.join(',')}) VALUES (${valueSets.map(_ => '?')})`; return await this.query(sql, assignValues, options); } -}; +} describe('=> Realm (TypeScript)', function () { let realm: Realm; @@ -201,13 +204,13 @@ describe('=> Realm (TypeScript)', function () { const { STRING } = realm.DataTypes; const User = realm.define('TestUser', { name: STRING }, {}, { get name() { - return this.attribute('name').replace(/^([a-z])/, function(m, chr) { + return this.attribute('name').replace(/^([a-z])/, function(m: string, chr: string) { return chr.toUpperCase(); }); }, set name(value) { if (typeof value !== 'string') throw new Error('unexpected name' + value); - this.attribute('name', value); + if (this instanceof realm.Bone) this.attribute('name', value); } }); // User.findOne should exists @@ -267,16 +270,16 @@ describe('=> Realm (TypeScript)', function () { static table = 'test_user'; @Column() - id: bigint; + id!: bigint; @Column() - name: string; + name!: string; @Column(TEXT) - content: string; + content!: string; @Column(INTEGER.UNSIGNED) - rank: number; + rank!: number; } // TODO how to avoid calling load() manually @@ -291,6 +294,6 @@ describe('=> Realm (TypeScript)', function () { assert(user.id); assert.equal(user.name, 'giant Y'); - }) - }) + }); + }); }); diff --git a/test/types/decorators.test.ts b/test/types/decorators.test.ts index 6a0a7e23..71160c31 100644 --- a/test/types/decorators.test.ts +++ b/test/types/decorators.test.ts @@ -19,19 +19,19 @@ describe('=> Decorators (TypeScript)', function() { it('should be able to deduce column type from typescript', async function() { class Note extends Bone { @Column() - id: bigint; + id!: bigint; @Column({ allowNull: false }) - name: string; + name!: string; @Column({ defaultValue: true }) - isPrivate: boolean; + isPrivate!: boolean; @Column() - createdAt: Date; + createdAt!: Date; @Column() - updatedAt: Date; + updatedAt!: Date; } await Note.sync({ force: true }); assert.deepEqual(Object.keys(Note.attributes), [ @@ -47,10 +47,10 @@ describe('=> Decorators (TypeScript)', function() { it('should be able to override column type', async function() { class Note extends Bone { @Column() - id: bigint; + id!: bigint; @Column(TEXT) - content: string; + content!: string; } await Note.sync({ force: true }); assert.deepEqual(Object.keys(Note.attributes), [ 'id', 'content' ]); @@ -62,13 +62,13 @@ describe('=> Decorators (TypeScript)', function() { it('should be able to override column name', async function() { class Note extends Bone { @Column() - id: bigint; + id!: bigint; @Column({ name: 'gmt_create' }) - createdAt: Date; + createdAt!: Date; @Column({ name: 'gmt_modified' }) - updatedAt: Date; + updatedAt!: Date; } await Note.sync({ force: true }); assert.deepEqual(Object.keys(Note.attributes), [ 'id', 'createdAt', 'updatedAt' ]); @@ -81,16 +81,16 @@ describe('=> Decorators (TypeScript)', function() { it('should work with setter', async () => { class Note extends Bone { @Column() - id: bigint; + id!: bigint; @Column({ defaultValue: true }) - isPrivate: boolean; + isPrivate!: boolean; @Column() - createdAt: Date; + createdAt!: Date; @Column() - updatedAt: Date; + updatedAt!: Date; get name(): string { return (this.attribute('name') as string)?.toUpperCase() as string; @@ -119,16 +119,16 @@ describe('=> Decorators (TypeScript)', function() { it('should work with getter', async () => { class Note extends Bone { @Column() - id: bigint; + id!: bigint; @Column({ defaultValue: true }) - isPrivate: boolean; + isPrivate!: boolean; @Column() - createdAt: Date; + createdAt!: Date; @Column() - updatedAt: Date; + updatedAt!: Date; @Column({ allowNull: false, @@ -163,18 +163,18 @@ describe('=> Decorators (TypeScript)', function() { it('should work with validate',async () => { class Note extends Bone { @Column() - id: bigint; + id!: bigint; - @Column({ + @Column({ allowNull: false, validate: { isNotNull(v?: string) { - if(!v) throw new Error('name cannot be null') + if (!v) throw new Error('name cannot be null'); }, - notIn: [ [ 'Yhorm', 'Gwyn' ] ], + notIn: [['Yhorm', 'Gwyn']], } }) - name: string; + name!: string; @Column({ type: DataTypes.INTEGER, @@ -183,12 +183,12 @@ describe('=> Decorators (TypeScript)', function() { validate: { isNumeric: true, isIn: { - args: [ [ '1', '2' ] ], + args: [['1', '2']], msg: 'Error status', }, }, }) - status: number; + status!: number; } await Note.sync({ force: true }); let note = new Note({ name: '' }); @@ -209,22 +209,22 @@ describe('=> Decorators (TypeScript)', function() { it('should work with other options', async () => { class Note extends Bone { @Column() - id: bigint; + id!: bigint; @Column({ type: STRING }) - body: string; + body!: string; @Column({ type: STRING(64) }) - description: string; + description!: string; @Column({ type: INTEGER(2).UNSIGNED, }) - status: number; + status!: number; } await Note.sync({ force: true }); @@ -244,13 +244,13 @@ describe('=> Decorators (TypeScript)', function() { primaryKey: true, autoIncrement: true, }) - noteId: bigint; + noteId!: bigint; @Column({ comment: 'note index', unique: true, }) - noteIndex: number; + noteIndex!: number; } await Note.sync({ force: true }); assert.deepEqual(Object.keys(Note.attributes), [ 'noteId', 'noteIndex' ]); @@ -263,16 +263,16 @@ describe('=> Decorators (TypeScript)', function() { it('should work with invokable data types', async () => { class Note extends Bone { @Column() - id: bigint; + id!: bigint; @Column(STRING) - body: string; + body!: string; @Column(STRING(64)) - description: string; + description!: string; @Column(INTEGER(2).UNSIGNED) - status: number; + status!: number; } await Note.sync({ force: true }); @@ -288,34 +288,34 @@ describe('=> Decorators (TypeScript)', function() { it('should not override attributes of parent class', async function() { class Base extends Bone { @Column() - id: bigint; + id!: bigint; } class Note extends Base { @Column() - body: string; + body!: string; } class Comment extends Note { - static table: string = 'comments'; + static table = 'comments'; @Column() - targetType: string; + targetType!: string; @Column() - targetId: number; + targetId!: number; } class SubContent extends Comment { - static table: string = 'contents'; + static table = 'contents'; @Column() - description: string; + description!: string; @Column({ allowNull: false, }) - status: number; + status!: number; } // normal subclass that not sync will inherent all the features from parent class @@ -388,21 +388,21 @@ describe('=> Decorators (TypeScript)', function() { describe('=> @HasMany()', function() { class Note extends Bone { @Column() - id: bigint; + id!: bigint; @Column() - memberId: bigint; + memberId!: bigint; } class Member extends Bone { @Column() - id: bigint; + id!: bigint; @Column() - email: string; + email!: string; @HasMany() - notes: Note[]; + notes!: Note[]; } before(async function() { @@ -422,7 +422,7 @@ describe('=> Decorators (TypeScript)', function() { it('should be able to declare 1:n association', async function() { const { id: memberId } = await Member.create({ email: 'hi@example.com' }); - await Note.create({ memberId }) + await Note.create({ memberId }); const member = await Member.findOne().with('notes'); assert.equal(member!.notes.length, 1); assert.ok(member!.notes[0] instanceof Note); @@ -433,13 +433,13 @@ describe('=> Decorators (TypeScript)', function() { describe('=> @HasMany({ through })', function() { class Tag extends Bone { @Column() - id: bigint; + id!: bigint; @Column() - type: number; + type!: number; @Column() - name: string; + name!: string; } enum TARGET_TYPE { @@ -448,36 +448,36 @@ describe('=> Decorators (TypeScript)', function() { class TagMap extends Bone { @Column() - id: bigint; + id!: bigint; @Column() - targetId: bigint; + targetId!: bigint; @Column() - targetType: number; + targetType!: number; @Column() - tagId: bigint; + tagId!: bigint; @BelongsTo() - tag: Tag; + tag!: Tag; } class Note extends Bone { @Column() - id: bigint; + id!: bigint; @Column() - content: string; - - @HasMany({ + content!: string; + + @HasMany({ foreignKey: 'targetId', where: { targetType: TARGET_TYPE.note }, }) - tagMaps: TagMap[]; + tagMaps!: TagMap[]; @HasMany({ through: 'tagMaps' }) - tags: Tag[]; + tags!: Tag[]; } before(async function() { @@ -512,28 +512,28 @@ describe('=> Decorators (TypeScript)', function() { describe('HasMany({ select })', function() { class Note extends Bone { @Column() - id: bigint; + id!: bigint; @Column({ type: DataTypes.TEXT }) - content: string; + content!: string; @Column() - memberId: bigint; + memberId!: bigint; } class Member extends Bone { @Column() - id: bigint; + id!: bigint; @Column() - email: string; + email!: string; - @HasMany({ + @HasMany({ select(name) { return name !== 'content'; }, }) - notes: Note[]; + notes?: Note[]; } before(async function() { @@ -555,8 +555,8 @@ describe('=> Decorators (TypeScript)', function() { const member = await Member.create({ email: 'hi@example.com' }); await Note.create({ memberId: member.id, content: 'hello' }); const result = await Member.findOne().with('notes'); - assert.equal(result!.notes.length, 1); - assert.equal(result!.notes[0].content, undefined); + assert.equal(result!.notes?.length, 1); + assert.equal(result!.notes?.[0].content, undefined); const [note] = result!.notes; await note.reload(); assert.equal(note.content, 'hello'); @@ -566,21 +566,21 @@ describe('=> Decorators (TypeScript)', function() { describe('HasOne()', function() { class Member extends Bone { @Column() - id: bigint; + id!: bigint; @Column() - email: string; + email!: string; } class Note extends Bone { @Column() - id: bigint; + id!: bigint; @Column() - content: string; + content!: string; @Column() - authorId: bigint; + authorId!: bigint; @BelongsTo({ foreignKey: 'authorId' }) author?: Member; diff --git a/test/types/querying.test.ts b/test/types/querying.test.ts index f100a71a..e7e4cc8f 100644 --- a/test/types/querying.test.ts +++ b/test/types/querying.test.ts @@ -1,36 +1,36 @@ import { strict as assert } from 'assert'; -import { Bone, DataTypes, Column, HasMany, connect, Raw } from '../..' +import { Bone, DataTypes, Column, HasMany, connect, Raw } from '../..'; describe('=> Querying (TypeScript)', function() { const { BIGINT, INTEGER, STRING } = DataTypes; class Post extends Bone { - static table = 'articles' + static table = 'articles'; @Column(BIGINT) - id: number; + id!: number; @Column(BIGINT) - authorId: number + authorId!: number; @Column() - title: string; + title!: string; } class User extends Bone { @Column(BIGINT) - id: number; + id!: number; @Column(STRING) - email: string; + email!: string; @Column(STRING) - nickname: string; + nickname!: string; @Column({ type: INTEGER, allowNull: false }) - status: number; + status!: number; @Column(INTEGER) - level: number; + level!: number; @HasMany({ foreignKey: 'authorId' }) posts?: Post[]; @@ -76,7 +76,7 @@ describe('=> Querying (TypeScript)', function() { await Post.bulkCreate([ { title: 'Leah' }, { title: 'Stranger', authorId: author.id } - ]) + ]); const user = await User.findOne({}).with({ posts: { select: 'title' } }); assert.equal(user!.id, author.id); assert.ok(Array.isArray(user!.posts)); @@ -94,7 +94,7 @@ describe('=> Querying (TypeScript)', function() { }); it('Bone.group().count()', async function() { - await Post.create({ title: 'Samoa' }) + await Post.create({ title: 'Samoa' }); const results = await Post.group('title').count(); assert.ok(Array.isArray(results)); const [result] = results; @@ -104,7 +104,7 @@ describe('=> Querying (TypeScript)', function() { }); it('Bone.group(raw).count()', async function() { - await Post.create({ title: 'Samoa' }) + await Post.create({ title: 'Samoa' }); const results = await Post.group(new Raw('title')).count(); assert.ok(Array.isArray(results)); const [result] = results; @@ -157,7 +157,8 @@ describe('=> Querying (TypeScript)', function() { it('Bone.select(Raw)', async function() { const posts = await Post.select(new Raw('COUNT(author_id) as count')); - assert.equal(posts?.[0].count, 2); + assert.ok(Array.isArray(posts)); + if ('count' in posts[0]) assert.equal(posts[0].count, 2); }); it('Bone.where(Raw)', async function() { diff --git a/test/types/realm.test.ts b/test/types/realm.test.ts index 27cba18b..4565675c 100644 --- a/test/types/realm.test.ts +++ b/test/types/realm.test.ts @@ -25,13 +25,13 @@ describe('=> Realm (TypeScript)', function () { const { STRING } = realm.DataTypes; const User = realm.define('User', { name: STRING }, {}, { get name() { - return this.attribute('name').replace(/^([a-z])/, function(m, chr) { + return this.attribute('name').replace(/^([a-z])/, function(m: string, chr: string) { return chr.toUpperCase(); }); }, set name(value) { if (typeof value !== 'string') throw new Error('unexpected name' + value); - this.attribute('name', value); + if (this instanceof realm.Bone) this.attribute('name', value); } }); // User.findOne should exists diff --git a/test/types/sequelize.test.ts b/test/types/sequelize.test.ts index 1e32db9a..6cb3f666 100644 --- a/test/types/sequelize.test.ts +++ b/test/types/sequelize.test.ts @@ -5,51 +5,52 @@ import { SequelizeBone, Column, DataTypes, connect, Hint, Raw, Bone } from '../. describe('=> sequelize (TypeScript)', function() { const { TEXT, STRING, VIRTUAL } = DataTypes; + class Post extends SequelizeBone { static table = 'articles'; @Column(DataTypes.BIGINT) - id: number; + id!: number; @Column({ name: 'gmt_create' }) - createdAt: Date; + createdAt!: Date; - @Column({ name: 'gmt_modified'}) - updatedAt: Date; + @Column({ name: 'gmt_modified' }) + updatedAt!: Date; @Column({ name: 'gmt_deleted' }) - deletedAt: Date; + deletedAt!: Date; @Column() - title: string; + title!: string; @Column(TEXT) - content: string; + content!: string; @Column(TEXT) - extra: string; + extra!: string; @Column() - thumb: string; + thumb!: string; @Column() - authorId: bigint; + authorId!: bigint; @Column({ defaultValue: false, }) - isPrivate: boolean; + isPrivate!: boolean; @Column(TEXT) - summary: string; + summary!: string; @Column(TEXT) - settings: string; + settings!: string; @Column({ defaultValue: 0, }) - wordCount: number; + wordCount!: number; @Column(VIRTUAL) get virtualField(): string { @@ -65,27 +66,27 @@ describe('=> sequelize (TypeScript)', function() { @Column({ primaryKey: true, }) - isbn: bigint; + isbn!: bigint; @Column({ name: 'gmt_create' }) - createdAt: Date; + createdAt!: Date; - @Column({ name: 'gmt_modified'}) - updatedAt: Date; + @Column({ name: 'gmt_modified' }) + updatedAt!: Date; @Column({ name: 'gmt_deleted' }) - deletedAt: Date; + deletedAt!: Date; @Column(STRING(1000)) - name: string; + name!: string; @Column() - price: number; + price!: number; } class Like extends SequelizeBone { @Column() - userId: number; + userId!: number; } before(async function() { diff --git a/test/types/spell.test.ts b/test/types/spell.test.ts index 63788509..cf1e987a 100644 --- a/test/types/spell.test.ts +++ b/test/types/spell.test.ts @@ -1,107 +1,105 @@ import { strict as assert } from 'assert'; -import sinon from 'sinon'; +import sinon, { SinonFakeTimers } from 'sinon'; import { - Bone, DataTypes, Column, + Bone, DataTypes, Column, HasOne, connect, INDEX_HINT_SCOPE_TYPE, INDEX_HINT_TYPE, INDEX_HINT_SCOPE, Hint, IndexHint, Raw, heresql, } from '../..'; -import { HasOne } from '../../src/decorators'; describe('=> Spell (TypeScript)', function() { const { STRING, TEXT, TINYINT } = DataTypes; class Attachment extends Bone { @Column() - id: number; + id!: number; @Column() - articleId: number; + articleId!: number; @Column() - url: string; + url!: string; @Column() - width: number; + width!: number; @Column() - height: number; + height!: number; @Column({ name: 'gmt_deleted', }) - deletedAt: Date; - + deletedAt!: Date; } class Post extends Bone { - static table = 'articles' + static table = 'articles'; @Column() - id: bigint; + id!: bigint; @Column() - authorId: bigint + authorId!: bigint; @Column() - title: string; + title!: string; @Column({ name: 'gmt_create', }) - createdAt: Date; + createdAt!: Date; @Column({ name: 'gmt_modified', }) - updatedAt: Date; + updatedAt!: Date; @Column({ name: 'gmt_deleted', }) - deletedAt: Date; + deletedAt!: Date; @Column(TEXT) - content: string; + content!: string; @Column(TEXT) - extra: string; + extra!: string; @Column(STRING(1000)) - thumb: string; + thumb!: string; @Column({ type: TINYINT, defaultValue: 0, }) - isPrivate: number; + isPrivate!: number; @Column(TEXT) - summary: string; + summary!: string; @Column({ defaultValue: 0, }) - word_count: number; + word_count!: number; @Column(TEXT) - settings: string; + settings!: string; @HasOne({ foreignKey: 'articleId', }) - attachment: Attachment; + attachment!: Attachment; } class Comment extends Bone { @Column() - id: number; + id!: number; @Column() - articleId: number; + articleId!: number; @Column() - content: string; + content!: string; } @@ -215,11 +213,11 @@ describe('=> Spell (TypeScript)', function() { }); describe('Num', () => { - let clock; + let clock: SinonFakeTimers; before(() => { const date = new Date(2017, 11, 12); const fakeDate = date.getTime(); - sinon.useFakeTimers(fakeDate); + clock = sinon.useFakeTimers(fakeDate); }); after(() => { diff --git a/test/unit/adapters/sequelize.test.js b/test/unit/adapters/sequelize.test.js index 5f2beef6..481d4f38 100644 --- a/test/unit/adapters/sequelize.test.js +++ b/test/unit/adapters/sequelize.test.js @@ -3,8 +3,7 @@ const assert = require('assert').strict; const crypto = require('crypto'); const sinon = require('sinon'); -const { Bone, connect, sequelize, DataTypes, raw } = require('../../..'); -const { Hint } = require('../../../src/hint'); +const { Bone, connect, sequelize, DataTypes, raw, Hint } = require('../../..'); const userAttributes = { id: DataTypes.BIGINT, @@ -1901,7 +1900,7 @@ describe('Transaction', function() { const Spine = sequelize(Bone); class User extends Spine { - static table = 'users' + static table = 'users'; } before(async function() { diff --git a/test/unit/drivers/abstract/index.test.js b/test/unit/drivers/abstract/index.test.js index b00292aa..6e2de127 100644 --- a/test/unit/drivers/abstract/index.test.js +++ b/test/unit/drivers/abstract/index.test.js @@ -1,8 +1,7 @@ 'use strict'; const assert = require('assert').strict; -const { Logger } = require('../../../..'); -const AbstractDriver = require('../../../../src/drivers/abstract'); +const { Logger, AbstractDriver } = require('../../../..'); describe('=> AbstractDriver#logger', function() { it('should create logger by default', async function() { diff --git a/test/unit/drivers/abstract/spellbook.test.js b/test/unit/drivers/abstract/spellbook.test.js index 13b934bb..1e4e857d 100644 --- a/test/unit/drivers/abstract/spellbook.test.js +++ b/test/unit/drivers/abstract/spellbook.test.js @@ -7,7 +7,7 @@ describe('=> Spellbook', function() { class User extends Bone {} class Attachment extends Bone {} class Post extends Bone { - static table = 'articles' + static table = 'articles'; static initialize() { this.belongsTo('author', { className: 'User' }); } @@ -30,7 +30,8 @@ describe('=> Spellbook', function() { authorId: User.where({ stauts: 1 }), }).with('author'); - assert.equal(query.limit(10).toString(), heresql(function() {/* + assert.equal(query.limit(10).toString(), heresql(function() { + /* SELECT `posts`.*, `author`.* FROM `articles` AS `posts` LEFT JOIN `users` AS `author` @@ -39,10 +40,12 @@ describe('=> Spellbook', function() { AND `posts`.`author_id` IN (SELECT * FROM `users` WHERE `stauts` = 1) AND `posts`.`gmt_deleted` IS NULL LIMIT 10 - */})); + */ + })); assert.doesNotThrow(function() { - assert.equal(query.count().toString(), heresql(function() {/* + assert.equal(query.count().toString(), heresql(function() { + /* SELECT COUNT(*) AS `count` FROM `articles` AS `posts` LEFT JOIN `users` AS `author` @@ -50,23 +53,28 @@ describe('=> Spellbook', function() { WHERE `posts`.`is_private` = true AND `posts`.`author_id` IN (SELECT * FROM `users` WHERE `stauts` = 1) AND `posts`.`gmt_deleted` IS NULL - */})); + */ + })); }); }); it('should format arithmetic operators as is', async function() { const query = Attachment.where('width/height > 16/9'); - assert.equal(query.toString(), heresql(function() {/* - SELECT * FROM `attachments` WHERE `width` / `height` > 16 / 9 AND `gmt_deleted` IS NULL - */})); + assert.equal(query.toString(), heresql(function() { + /* + SELECT * FROM `attachments` WHERE `width` / `height` > 16 / 9 AND `gmt_deleted` IS NULL + */ + })); }); it('aggregate functions should be formatted without star', async function() { const query = Post.include('authors') .maximum('posts.createdAt'); - assert.equal(query.toString(), heresql(function() {/* - SELECT MAX(`posts`.`gmt_create`) AS `maximum` FROM `articles` AS `posts` LEFT JOIN `users` AS `authors` ON `posts`.`userId` = `authors`.`id` WHERE `posts`.`gmt_deleted` IS NULL - */})); + assert.equal(query.toString(), heresql(function() { + /* + SELECT MAX(`posts`.`gmt_create`) AS `maximum` FROM `articles` AS `posts` LEFT JOIN `users` AS `authors` ON `posts`.`userId` = `authors`.`id` WHERE `posts`.`gmt_deleted` IS NULL + */ + })); }); }); }); diff --git a/test/unit/drivers/sqljs/index.test.js b/test/unit/drivers/sqljs/index.test.js index 9af59f86..3cd05e6a 100644 --- a/test/unit/drivers/sqljs/index.test.js +++ b/test/unit/drivers/sqljs/index.test.js @@ -6,7 +6,7 @@ const fs = require('fs').promises; const dayjs = require('dayjs'); const { heresql } = require('../../../../src/utils/string'); -const { default: SqlJSDriver } = require('../../../../src/drivers/sqljs/index.js'); +const { default: SqlJSDriver } = require('../../../../src/drivers/sqljs'); const { INTEGER, BIGINT, STRING, DATE, BOOLEAN, JSONB } = SqlJSDriver.DataTypes; diff --git a/test/unit/hint.test.js b/test/unit/hint.test.js index c625cdc6..dd24aa65 100644 --- a/test/unit/hint.test.js +++ b/test/unit/hint.test.js @@ -2,9 +2,10 @@ const assert = require('assert').strict; -const { connect, Bone } = require('../..'); - -const { Hint, IndexHint, INDEX_HINT_TYPE, INDEX_HINT_SCOPE } = require('../../src/hint'); +const { + connect, Bone, + Hint, IndexHint, INDEX_HINT_TYPE, INDEX_HINT_SCOPE, +} = require('../..'); describe('Hint', () => { it('text= should strip comment syntax', () => { @@ -48,7 +49,7 @@ describe('IndexHint', () => { describe('MySQL', async () => { class Post extends Bone { - static table = 'articles' + static table = 'articles'; } before(async function() { diff --git a/test/unit/realm.test.js b/test/unit/realm.test.js index dca357d2..5e06c4b1 100644 --- a/test/unit/realm.test.js +++ b/test/unit/realm.test.js @@ -2,7 +2,7 @@ const assert = require('assert').strict; const path = require('path'); -const Realm = require('../../index'); +const Realm = require('../..'); const { connect, Bone, DataTypes, Logger, Spell, SqliteDriver, SequelizeBone } = Realm; const attributes = { @@ -148,7 +148,7 @@ describe('=> Realm', () => { it('should hold models as object in realm.models', async function() { class User extends Bone {} class Post extends Bone { - static table = 'articles' + static table = 'articles'; } const realm = new Realm({ port: process.env.MYSQL_PORT, @@ -1078,7 +1078,7 @@ describe('=> Realm', () => { static attributes = { id: DataTypes.BIGINT, name: DataTypes.STRING, - } + }; static initialize() { this.hasMany('recipients', { foreignKey: 'clientId' }); } @@ -1090,7 +1090,7 @@ describe('=> Realm', () => { clientId: DataTypes.BIGINT, name: DataTypes.STRING, address: DataTypes.STRING, - } + }; static initialize() { this.belongsTo('client', { foreignkey: 'clientId' }); } @@ -1148,7 +1148,7 @@ describe('=> Realm', () => { }); class Post extends Bone { - static table = 'articles' + static table = 'articles'; } await assert.doesNotReject(async function() { const realm2 = new Realm({ diff --git a/test/unit/utils/index.test.js b/test/unit/utils/index.test.js index 52df76f7..5adc18d4 100644 --- a/test/unit/utils/index.test.js +++ b/test/unit/utils/index.test.js @@ -1,8 +1,8 @@ 'use strict'; const assert = require('assert').strict; -const { Bone, sequelize } = require('../../..'); -const { compose, getPropertyNames, logger, isBone } = require('../../../src/utils'); +const { Bone, sequelize, isBone } = require('../../..'); +const { compose, getPropertyNames, logger } = require('../../../src/utils'); describe('=> compose', function() { it('should return a default function if nothing to compose', function() { diff --git a/test/unit/utils/string.test.js b/test/unit/utils/string.test.ts similarity index 63% rename from test/unit/utils/string.test.js rename to test/unit/utils/string.test.ts index b9e6c226..1760a3bc 100644 --- a/test/unit/utils/string.test.js +++ b/test/unit/utils/string.test.ts @@ -1,15 +1,16 @@ -'use strict'; -const assert = require('assert').strict; -const { heresql } = require('../../../src/utils/string'); +import { strict as assert } from 'assert'; +import { heresql } from '../../..'; describe('=> heresql', function() { it('should accept function comment', function() { - assert.equal(heresql(function() {/* + assert.equal(heresql(function() { + /* SELECT 1, 2, now() - */}), 'SELECT 1, 2, now()'); + */ + }), 'SELECT 1, 2, now()'); }); it('should accept template literal', function() { diff --git a/tsconfig.browser.json b/tsconfig.browser.json index dd87ad3d..b82d6882 100644 --- a/tsconfig.browser.json +++ b/tsconfig.browser.json @@ -4,10 +4,5 @@ "target": "ES5", "module": "ESNext", "outDir": "dist", - "declaration": true, - "allowJs": true, - }, - "include": [ - "./src", - ] -} \ No newline at end of file + } +} diff --git a/tsconfig.json b/tsconfig.json index c5e0b2ef..1b3ae529 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,31 @@ { + "extends": "@tsconfig/node16", "compilerOptions": { - "target": "es2018", - "module": "CommonJS", - "moduleResolution": "Node", - "experimentalDecorators": true, + "allowJs": true, + "allowSyntheticDefaultImports": true, + "declaration": true, + "downlevelIteration": true, "emitDecoratorMetadata": true, + "experimentalDecorators": true, "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "sourceMap": true, + "importHelpers": true, + "module": "CommonJS", + "moduleResolution": "Node", "noUnusedLocals": true, + "outDir": "lib", + "skipLibCheck": true, + "sourceMap": true, "strictNullChecks": true, - "declaration": false, - "downlevelIteration": true, - "importHelpers": true, + "target": "ES2020", }, + "include": [ + "src/**/*" + ], "exclude": [ "dist", + "docs", + "lib", + "node_modules", "Readme.md" ] }