From 4090743dbfdc11881eb80982f0f81511d63a9405 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Mon, 7 Aug 2017 17:05:00 +0800 Subject: [PATCH 01/19] refactor(PredicateProvider): PredicateProvider now support multiple tables, prepare for Association query --- src/storage/Database.ts | 50 +++++++-- src/storage/helper/create-predicate.ts | 7 +- src/storage/modules/PredicateProvider.ts | 9 +- .../storage/modules/PredicateProvider.spec.ts | 60 +++++----- test/specs/storage/modules/Selector.spec.ts | 104 ++++++++++-------- 5 files changed, 140 insertions(+), 90 deletions(-) diff --git a/src/storage/Database.ts b/src/storage/Database.ts index 529bad59..fda96c4f 100644 --- a/src/storage/Database.ts +++ b/src/storage/Database.ts @@ -7,7 +7,7 @@ import * as Exception from '../exception' import * as typeDefinition from './helper/definition' import Version from '../version' import { Traversable } from '../shared' -import { Mutation, Selector, QueryToken, PredicateProvider } from './modules' +import { Mutation, Selector, QueryToken, PredicateProvider, Tables } from './modules' import { dispose, contextTableName, fieldIdentifier, hiddenColName } from './symbols' import { forEach, clone, contains, tryCatch, hasOwn, getType, assert, identity, warn } from '../utils' import { createPredicate, createPkClause, mergeTransactionResult, predicatableQuery, lfFactory } from './helper' @@ -189,7 +189,8 @@ export class Database { }) const mut = { ...(entity as any), ...hiddenPayload } - const predicate = createPredicate(table, clause) + const tables = this.getAssoTablesDef(db, tableName) + const predicate = createPredicate(tables, tableName, clause) const query = predicatableQuery(db, table, predicate!, StatementType.Update) forEach(mut, (val, key) => { @@ -216,8 +217,9 @@ export class Database { return this.database$ .concatMap(db => { const [ table ] = Database.getTables(db, tableName) + const tables = this.getAssoTablesDef(db, tableName) const column = table[pk!] - const provider = new PredicateProvider(table, clause) + const provider = new PredicateProvider(tables, tableName, clause) const prefetch = predicatableQuery(db, table, provider.getPredicate(), StatementType.Select, column) @@ -269,7 +271,8 @@ export class Database { return this.database$.concatMap((db) => { const [ table ] = Database.getTables(db, tableName) - const predicate = createPredicate(table, clause.where) + const tables = this.getAssoTablesDef(db, tableName) + const predicate = createPredicate(tables, tableName, clause.where) const queries: lf.query.Builder[] = [] const removedIds: any = [] @@ -422,7 +425,8 @@ export class Database { predicatableQuery(db, table!, null, StatementType.Select, ...columns) joinInfo.forEach((info: JoinInfo) => - query.leftOuterJoin(info.table, info.predicate)) + query.leftOuterJoin(info.table, info.predicate) + ) const orderDesc = (clause.orderBy || []).map(desc => { return { @@ -440,7 +444,8 @@ export class Database { mainTable: table! } const { limit, skip } = clause - const provider = new PredicateProvider(table!, clause.where) + const tables = this.getAssoTablesDef(db, tableName, table) + const provider = new PredicateProvider(tables, tableName, clause.where) return new Selector(db, query, matcher, provider, limit, skip, orderDesc) }) @@ -531,7 +536,8 @@ export class Database { const suffix = (context[tableName] || 0) + 1 context[tableName] = suffix const contextName = contextTableName(tableName, suffix) - const currentTable = Database.getTables(db, tableName)[0].as(contextName) + const [ originTable ] = Database.getTables(db, tableName) + const currentTable = originTable.as(contextName) const handleAdvanced = (ret: any, key: string, defs: Association | ColumnDef) => { if (!ret.advanced) { @@ -546,13 +552,17 @@ export class Database { } else { const { where, type } = defs as Association rootDefinition[key] = typeDefinition.revise(type!, ret.definition) - const [ predicate, err ] = tryCatch(createPredicate)(currentTable, where(ret.table)) + const tables = this.getAssoTablesDef(db, tableName) + tables[contextName] = currentTable + + const [ predicate, err ] = tryCatch(createPredicate)(tables, contextName, where(ret.table)) if (err) { warn( `Failed to build predicate, since ${err.message}` + - `, on table: ${ret.table.getName()}` + `, on table: ${ ret.table.getName() }` ) } + const joinLink = predicate ? [{ table: ret.table, predicate }, ...ret.joinInfo] : ret.joinInfo @@ -722,7 +732,8 @@ export class Database { entities.forEach(entity => { const pkVal = entity[pk] const clause = createPkClause(pk, pkVal) - const predicate = createPredicate(table, clause) + const tables = this.getAssoTablesDef(db, tableName) + const predicate = createPredicate(tables, tableName, clause) const query = predicatableQuery(db, table, predicate!, StatementType.Delete) queryCollection.push(query) @@ -732,7 +743,8 @@ export class Database { const get = (where: Predicate | null = null) => { const [ table ] = Database.getTables(db, tableName) - const [ predicate, err ] = tryCatch(createPredicate)(table, where) + const tables = this.getAssoTablesDef(db, tableName) + const [ predicate, err ] = tryCatch(createPredicate)(tables, tableName, where) if (err) { return Observable.throw(err) } @@ -745,6 +757,22 @@ export class Database { } } + private getAssoTablesDef(db: lf.Database, tableName: string, defaultTable: null | lf.schema.Table = null) { + const schema = this.findSchema(tableName) + const tables: Tables = Object.create(null) + if (!defaultTable) { + const [ table ] = Database.getTables(db, tableName) + tables[tableName] = table + } else { + tables[tableName] = defaultTable + } + + schema.associations.forEach((def, k) => { + tables[k] = Database.getTables(db, def.name)[0] + }) + return tables + } + private executor(db: lf.Database, queries: lf.query.Builder[]) { const tx = db.createTransaction() const handler = { diff --git a/src/storage/helper/create-predicate.ts b/src/storage/helper/create-predicate.ts index 78c12503..7fa7e848 100644 --- a/src/storage/helper/create-predicate.ts +++ b/src/storage/helper/create-predicate.ts @@ -1,7 +1,6 @@ -import * as lf from 'lovefield' -import { PredicateProvider } from '../modules/PredicateProvider' +import { PredicateProvider, Tables } from '../modules/PredicateProvider' import { Predicate } from '../../interface' -export function createPredicate(table: lf.schema.Table, clause: Predicate | null = null) { - return clause ? new PredicateProvider(table, clause).getPredicate() : null +export function createPredicate(tables: Tables, tableName: string, clause: Predicate | null = null) { + return clause ? new PredicateProvider(tables, tableName, clause).getPredicate() : null } diff --git a/src/storage/modules/PredicateProvider.ts b/src/storage/modules/PredicateProvider.ts index c6684262..8f053609 100644 --- a/src/storage/modules/PredicateProvider.ts +++ b/src/storage/modules/PredicateProvider.ts @@ -67,10 +67,17 @@ const compoundPredicateFactory = { }, } +export interface Tables { + [index: string]: lf.schema.Table +} + export class PredicateProvider { + private table = this.tables[this.tableName] + constructor( - private table: lf.schema.Table, + private tables: Tables, + private tableName: string, private meta?: Predicate ) { } diff --git a/test/specs/storage/modules/PredicateProvider.spec.ts b/test/specs/storage/modules/PredicateProvider.spec.ts index 0da574b4..542b29c8 100644 --- a/test/specs/storage/modules/PredicateProvider.spec.ts +++ b/test/specs/storage/modules/PredicateProvider.spec.ts @@ -1,7 +1,7 @@ import * as lf from 'lovefield' import { describe, it, beforeEach } from 'tman' import { expect } from 'chai' -import { PredicateProvider, lfFactory, DataStoreType } from '../../../index' +import { PredicateProvider, lfFactory, DataStoreType, Predicate } from '../../../index' export default describe('PredicateProvider test', () => { const dataLength = 1000 @@ -14,8 +14,13 @@ export default describe('PredicateProvider test', () => { let db: lf.Database let table: lf.schema.Table + let tableDef: { TestPredicateProvider: lf.schema.Table } let version = 1 + function predicateFactory(desc: Predicate) { + return new PredicateProvider(tableDef, 'TestPredicateProvider', desc) + } + beforeEach(function* () { const schemaBuilder = lf.schema.create('PredicateProviderDatabase', version++) const db$ = lfFactory(schemaBuilder, { @@ -47,12 +52,15 @@ export default describe('PredicateProvider test', () => { nullable: i >= 300 ? null : false })) } + + tableDef = { TestPredicateProvider: table } + yield db.insert().into(table).values(rows).exec() }) describe('PredicateProvider#getPredicate', () => { it('invalid key should be ignored', function* () { - const fn = () => new PredicateProvider(table, { + const fn = () => predicateFactory({ nonExist: 'whatever' }).getPredicate() @@ -63,14 +71,14 @@ export default describe('PredicateProvider test', () => { }) it('empty meta should ok', function* () { - const predicate = new PredicateProvider(table, {}).getPredicate() + const predicate = predicateFactory({}).getPredicate() expect(predicate).to.be.null const result = yield execQuery(db, table, predicate) expect(result).to.have.lengthOf(1000) }) it('literal value should ok', function* () { - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ time1: 20 }).getPredicate() @@ -81,7 +89,7 @@ export default describe('PredicateProvider test', () => { }) it('$ne should ok', function* () { - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ time1: { $ne: 20 } @@ -94,7 +102,7 @@ export default describe('PredicateProvider test', () => { }) it('$lt should ok', function* () { - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ time1: { $lt: 20 } @@ -107,7 +115,7 @@ export default describe('PredicateProvider test', () => { }) it('$lte should ok', function* () { - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ time1: { $lte: 19 } @@ -120,7 +128,7 @@ export default describe('PredicateProvider test', () => { }) it('$gt should ok', function* () { - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ time2: { $gt: 20 } @@ -133,7 +141,7 @@ export default describe('PredicateProvider test', () => { }) it('$gte should ok', function* () { - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ time2: { $gte: 21 } @@ -147,7 +155,7 @@ export default describe('PredicateProvider test', () => { it('$match should ok', function* () { const regExp = /\:(\d{0,1}1$)/ - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ name: { $match: regExp } @@ -161,7 +169,7 @@ export default describe('PredicateProvider test', () => { it('$notMatch should ok', function* () { const regExp = /\:(\d{0,1}1$)/ - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ name: { $notMatch: regExp } @@ -175,7 +183,7 @@ export default describe('PredicateProvider test', () => { }) it('$between should ok', function* () { - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ time1: { $between: [1, 20] } @@ -188,7 +196,7 @@ export default describe('PredicateProvider test', () => { }) it('$has should ok', function* () { - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ times: { $has: 'times: 10' } @@ -204,7 +212,7 @@ export default describe('PredicateProvider test', () => { it('$in should ok', function* () { const seed = [10, 20, 30, 10000] - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ time1: { $in: seed } @@ -217,7 +225,7 @@ export default describe('PredicateProvider test', () => { }) it('$isNull should ok', function* () { - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ nullable: { $isNull: true } @@ -230,7 +238,7 @@ export default describe('PredicateProvider test', () => { }) it('$isNotNull should ok', function* () { - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ nullable: { $isNotNull: true } @@ -243,7 +251,7 @@ export default describe('PredicateProvider test', () => { }) it('$not should ok', function* () { - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ $not: { time1: 0 } @@ -255,7 +263,7 @@ export default describe('PredicateProvider test', () => { }) it('$and should ok', function* () { - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ time1: { $and: { $lt: 200, @@ -271,7 +279,7 @@ export default describe('PredicateProvider test', () => { }) it('$or should ok', function* () { - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ time1: { $or: { $gte: dataLength - 50, @@ -287,7 +295,7 @@ export default describe('PredicateProvider test', () => { }) it('non-compound predicates should be combined with $and', function* () { - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ $or: { time1: { $gte: 0, $lt: 50 }, time2: { $gt: 0, $lte: 50 } @@ -306,7 +314,7 @@ export default describe('PredicateProvider test', () => { }) it('compoundPredicate should skip null/undefined property', function* () { - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ time1: { $or: { $gte: dataLength - 50, @@ -323,7 +331,7 @@ export default describe('PredicateProvider test', () => { it('complex PredicateDescription should ok', function* () { const reg = /\:(\d{0,1}1$)/ - const predicate = new PredicateProvider(table, { + const predicate = predicateFactory({ time1: { $or: { $gte: dataLength - 50, @@ -357,15 +365,15 @@ export default describe('PredicateProvider test', () => { describe('PredicateProvider#toString', () => { it('convert empty PredicateProvider to empty string', () => { - expect(new PredicateProvider(table, {}).toString()).to.equal('') + expect(predicateFactory({}).toString()).to.equal('') }) it('convert to string representation of the predicate', () => { - expect(new PredicateProvider(table, { notExist: 20 }).toString()).to.equal('') + expect(predicateFactory({ notExist: 20 }).toString()).to.equal('') - expect(new PredicateProvider(table, { time1: 20 }).toString()).to.equal('{"time1":20}') + expect(predicateFactory({ time1: 20 }).toString()).to.equal('{"time1":20}') - expect(new PredicateProvider(table, { + expect(predicateFactory({ $or: { time1: { $gte: 0, $lt: 50 }, time2: { $gt: 0, $lte: 50 } diff --git a/test/specs/storage/modules/Selector.spec.ts b/test/specs/storage/modules/Selector.spec.ts index 24bfe533..11ef353f 100644 --- a/test/specs/storage/modules/Selector.spec.ts +++ b/test/specs/storage/modules/Selector.spec.ts @@ -10,6 +10,7 @@ import { lfFactory, ShapeMatcher, PredicateProvider, + Predicate, TokenConcatFailed } from '../../../index' @@ -25,12 +26,17 @@ interface Fixture { export default describe('Selector test', () => { let db: lf.Database let table: lf.schema.Table + let tableDef: { TestSelectMetadata: lf.schema.Table } let version = 1 let tableShape: ShapeMatcher let storeData: any[] let subscription: Subscription + function predicateFactory(desc: Predicate) { + return new PredicateProvider(tableDef, 'TestSelectMetadata', desc) + } + beforeEach(function * () { const schemaBuilder = lf.schema.create('SelectorTest', version ++) const db$ = lfFactory(schemaBuilder, { @@ -93,6 +99,8 @@ export default describe('Selector test', () => { } } } + + tableDef = { TestSelectMetadata: table } }) afterEach(() => { @@ -116,7 +124,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }) + predicateFactory({ time: { $gte: 50 } }) ) const results = yield selector.values() @@ -131,7 +139,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 50 } }), + predicateFactory({ time: { $gt: 50 } }), 20, 20 ) const result = yield selector.values() @@ -147,7 +155,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 50 } }), + predicateFactory({ time: { $gt: 50 } }), 20, 20 ) @@ -160,7 +168,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }), + predicateFactory({ time: { $gte: 50 } }), null, null, [ { column: table['priority'], orderBy: lf.Order.ASC }, @@ -187,7 +195,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }) + predicateFactory({ time: { $gte: 50 } }) ) const newName = 'test name change' @@ -211,7 +219,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, {}), + predicateFactory({}), ) const row = { _id: '_id:939.5', name: 'name:939.5', time: 939.5, priority: 10 } @@ -240,7 +248,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }) + predicateFactory({ time: { $gte: 50 } }) ) const spy = sinon.spy((): void => void 0) @@ -268,7 +276,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }) + predicateFactory({ time: { $gte: 50 } }) ) const newName = 'test name change' @@ -307,7 +315,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }) + predicateFactory({ time: { $gte: 50 } }) ) const changes = selector.changes() @@ -345,7 +353,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $eq: impossibleTime } }), + predicateFactory({ time: { $eq: impossibleTime } }), 1, 0 // 添加 limit, skip 以模仿实际使用场景 ) @@ -386,7 +394,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 50 } }), + predicateFactory({ time: { $gt: 50 } }), 20, 20 ) @@ -415,7 +423,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 50 } }), + predicateFactory({ time: { $gt: 50 } }), 20, 20, [ { column: table['priority'], orderBy: lf.Order.DESC }, @@ -473,7 +481,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 50 } }), + predicateFactory({ time: { $gt: 50 } }), 20, 20 ) @@ -514,7 +522,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 50 } }), + predicateFactory({ time: { $gt: 50 } }), 20, 20 ) @@ -544,7 +552,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 960 } }), + predicateFactory({ time: { $gt: 960 } }), 20, 20 ) @@ -574,7 +582,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $lt: 0 } }), + predicateFactory({ time: { $lt: 0 } }), 20 ) @@ -616,7 +624,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 50 } }) + predicateFactory({ time: { $gt: 50 } }) ) const spy = sinon.spy() @@ -634,7 +642,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 10000 } }) + predicateFactory({ time: { $gt: 10000 } }) ) const spy = sinon.spy() @@ -652,7 +660,7 @@ export default describe('Selector test', () => { const selector = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 50 } }), + predicateFactory({ time: { $gt: 50 } }), 20, 20 ) @@ -679,12 +687,12 @@ export default describe('Selector test', () => { selector1 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $lt: 50 } }) + predicateFactory({ time: { $lt: 50 } }) ) selector2 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { + predicateFactory({ time: { $gte: 50, $lt: 100 @@ -694,14 +702,14 @@ export default describe('Selector test', () => { const select1And2 = selector1.combine(selector2) - selector3 = new Selector(db, db.select().from(table), tableShape, new PredicateProvider(table, { + selector3 = new Selector(db, db.select().from(table), tableShape, predicateFactory({ time: { $gte: 100, $lt: 150 } })) - selector4 = new Selector(db, db.select().from(table), tableShape, new PredicateProvider(table, { + selector4 = new Selector(db, db.select().from(table), tableShape, predicateFactory({ time: { $gte: 150, $lt: 200 @@ -775,13 +783,13 @@ export default describe('Selector test', () => { const selector5 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 50 } }), + predicateFactory({ time: { $gt: 50 } }), 20, 20 ) const selector6 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 100 } }), + predicateFactory({ time: { $gt: 100 } }), 20, 20 ) @@ -853,13 +861,13 @@ export default describe('Selector test', () => { const _selector1 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }) + predicateFactory({ time: { $gte: 50 } }) ) const _selector2 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $lte: 250 } }) + predicateFactory({ time: { $lte: 250 } }) ) const selector = _selector1.combine(_selector2) @@ -885,34 +893,34 @@ export default describe('Selector test', () => { selector1 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }), + predicateFactory({ time: { $gte: 50 } }), 20, 0 ) selector2 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }), + predicateFactory({ time: { $gte: 50 } }), 20, 20 ) selector3 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }), + predicateFactory({ time: { $gte: 50 } }), 20, 40 ) selector4 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }), + predicateFactory({ time: { $gte: 50 } }), 10, 60 ) selector5 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }), + predicateFactory({ time: { $gte: 50 } }), 20, 70 ) @@ -1001,7 +1009,7 @@ export default describe('Selector test', () => { const selector6 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $lt: 930 } }), + predicateFactory({ time: { $lt: 930 } }), 20, 0, [ { column: table['priority'], orderBy: lf.Order.DESC }, @@ -1011,7 +1019,7 @@ export default describe('Selector test', () => { const selector7 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $lt: 930 } }), + predicateFactory({ time: { $lt: 930 } }), 20, 20, [ { column: table['priority'], orderBy: lf.Order.DESC }, @@ -1052,13 +1060,13 @@ export default describe('Selector test', () => { const selector6 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }), + predicateFactory({ time: { $gte: 50 } }), 10, 0 ) const selector7 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }), + predicateFactory({ time: { $gte: 50 } }), 20, 11 ) @@ -1071,13 +1079,13 @@ export default describe('Selector test', () => { const selector6 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 50 } }), + predicateFactory({ time: { $gt: 50 } }), 20, 0 ) const selector7 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }), + predicateFactory({ time: { $gte: 50 } }), 20, 20 ) @@ -1097,14 +1105,14 @@ export default describe('Selector test', () => { const selector7 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }), + predicateFactory({ time: { $gte: 50 } }), 20, 20 ) const selector6_1 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 50 } }), + predicateFactory({ time: { $gt: 50 } }), 20, 0 ) const selector7_1 = new Selector(db, @@ -1127,13 +1135,13 @@ export default describe('Selector test', () => { const selector6 = new Selector(db, db.select(table['name']).from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 50 } }), + predicateFactory({ time: { $gt: 50 } }), 20, 0 ) const selector7 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 50 } }), + predicateFactory({ time: { $gt: 50 } }), 20, 20 ) @@ -1147,13 +1155,13 @@ export default describe('Selector test', () => { const selector6 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 50 } }), + predicateFactory({ time: { $gt: 50 } }), 20, 0 ) const selector7 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gt: 50 } }), + predicateFactory({ time: { $gt: 50 } }), 20, 40 ) @@ -1180,14 +1188,14 @@ export default describe('Selector test', () => { selector1 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }), + predicateFactory({ time: { $gte: 50 } }), 20, 0 ) .map(mapFn) selector2 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }), + predicateFactory({ time: { $gte: 50 } }), 20, 20 ) .map(mapFn) @@ -1197,7 +1205,7 @@ export default describe('Selector test', () => { selector3 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }), + predicateFactory({ time: { $gte: 50 } }), 20, 40 ) .map(mapFn2) @@ -1205,7 +1213,7 @@ export default describe('Selector test', () => { selector4 = new Selector(db, db.select().from(table), tableShape, - new PredicateProvider(table, { time: { $gte: 50 } }), + predicateFactory({ time: { $gte: 50 } }), 20, 60 ) .map(mapFn3) From c2afc4454b789a4fdbb5b8244c5f3e91e7546c14 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 9 Aug 2017 18:09:54 +0800 Subject: [PATCH 02/19] feat(PredicateProvider): support deep nested association query style(Database): rename buildAssoTablesDef to buildTablesStructure --- src/interface/index.ts | 7 ++ src/storage/Database.ts | 82 ++++++++++--------- src/storage/helper/create-predicate.ts | 6 +- src/storage/modules/PredicateProvider.ts | 50 ++++++++--- test/schemas/Organization.ts | 20 +++++ test/schemas/Project.ts | 17 +++- test/schemas/Task.ts | 9 +- test/schemas/index.ts | 3 + test/specs/storage/Database.public.spec.ts | 33 +++++++- .../storage/modules/PredicateProvider.spec.ts | 4 +- test/specs/storage/modules/Selector.spec.ts | 4 +- test/utils/generators/task-generator.ts | 9 +- 12 files changed, 176 insertions(+), 68 deletions(-) create mode 100644 test/schemas/Organization.ts diff --git a/src/interface/index.ts b/src/interface/index.ts index 03b6926f..c399fc76 100644 --- a/src/interface/index.ts +++ b/src/interface/index.ts @@ -174,3 +174,10 @@ export type Predicate = { } export { StatementType, JoinMode, LeafType, Relationship, DataStoreType, RDBType } + +export interface TablesStruct { + [index: string]: { + table: lf.schema.Table + contextName?: string + } +} diff --git a/src/storage/Database.ts b/src/storage/Database.ts index fda96c4f..341751a3 100644 --- a/src/storage/Database.ts +++ b/src/storage/Database.ts @@ -3,18 +3,19 @@ import { ErrorObservable } from 'rxjs/observable/ErrorObservable' import { Subscription } from 'rxjs/Subscription' import { ConnectableObservable } from 'rxjs/observable/ConnectableObservable' import * as lf from 'lovefield' +import { __assign as assign } from 'tslib' import * as Exception from '../exception' import * as typeDefinition from './helper/definition' import Version from '../version' import { Traversable } from '../shared' -import { Mutation, Selector, QueryToken, PredicateProvider, Tables } from './modules' +import { Mutation, Selector, QueryToken, PredicateProvider } from './modules' import { dispose, contextTableName, fieldIdentifier, hiddenColName } from './symbols' import { forEach, clone, contains, tryCatch, hasOwn, getType, assert, identity, warn } from '../utils' import { createPredicate, createPkClause, mergeTransactionResult, predicatableQuery, lfFactory } from './helper' import { Relationship, RDBType, DataStoreType, LeafType, StatementType, JoinMode } from '../interface/enum' import { Record, Field, JoinInfo, Query, Clause, Predicate } from '../interface' import { SchemaDef, ColumnDef, ParsedSchema, Association, ScopedHandler } from '../interface' -import { ColumnLeaf, NavigatorLeaf, ExecutorResult, UpsertContext, SelectContext } from '../interface' +import { ColumnLeaf, NavigatorLeaf, ExecutorResult, UpsertContext, SelectContext, TablesStruct } from '../interface' export class Database { @@ -189,7 +190,7 @@ export class Database { }) const mut = { ...(entity as any), ...hiddenPayload } - const tables = this.getAssoTablesDef(db, tableName) + const tables = this.buildTablesStructure(table) const predicate = createPredicate(tables, tableName, clause) const query = predicatableQuery(db, table, predicate!, StatementType.Update) @@ -217,7 +218,7 @@ export class Database { return this.database$ .concatMap(db => { const [ table ] = Database.getTables(db, tableName) - const tables = this.getAssoTablesDef(db, tableName) + const tables = this.buildTablesStructure(table) const column = table[pk!] const provider = new PredicateProvider(tables, tableName, clause) const prefetch = @@ -271,7 +272,7 @@ export class Database { return this.database$.concatMap((db) => { const [ table ] = Database.getTables(db, tableName) - const tables = this.getAssoTablesDef(db, tableName) + const tables = this.buildTablesStructure(table) const predicate = createPredicate(tables, tableName, clause.where) const queries: lf.query.Builder[] = [] @@ -419,14 +420,19 @@ export class Database { const containKey = containFields ? contains(pk, clause.fields!) : true const fields: Set = containFields ? new Set(clause.fields) : new Set(schema.columns.keys()) - const { table, columns, joinInfo, definition } = - this.traverseQueryFields(db, tableName, fields, containKey, !containFields, [], {}, mode) + const { table, columns, joinInfo, definition, contextName } = + this.traverseQueryFields(db, tableName, fields, containKey, !containFields, [], {}, {}, mode) const query = predicatableQuery(db, table!, null, StatementType.Select, ...columns) - joinInfo.forEach((info: JoinInfo) => - query.leftOuterJoin(info.table, info.predicate) - ) + const tablesStruct: TablesStruct = Object.create(null) + + joinInfo.forEach((info: JoinInfo) => { + const predicate = info.predicate + if (predicate) { + query.leftOuterJoin(info.table, predicate) + } + }) const orderDesc = (clause.orderBy || []).map(desc => { return { @@ -444,8 +450,7 @@ export class Database { mainTable: table! } const { limit, skip } = clause - const tables = this.getAssoTablesDef(db, tableName, table) - const provider = new PredicateProvider(tables, tableName, clause.where) + const provider = new PredicateProvider(tablesStruct, contextName, clause.where) return new Selector(db, query, matcher, provider, limit, skip, orderDesc) }) @@ -500,6 +505,7 @@ export class Database { glob: boolean, path: string[] = [], context: Record = {}, + tablesStruct: TablesStruct = Object.create(null), mode: JoinMode ) { const schema = this.findSchema(tableName) @@ -509,8 +515,8 @@ export class Database { const columns: lf.schema.Column[] = [] const joinInfo: JoinInfo[] = [] - if (mode === JoinMode.imlicit && contains(tableName, path)) { - return { columns, joinInfo, advanced: false, table: null, definition: null } + if (mode === JoinMode.imlicit && contains(tableName, path)) { // thinking mode: implicit & explicit + return { columns, joinInfo, advanced: false, table: null, definition: null, contextName: tableName } } else { path.push(tableName) } @@ -546,23 +552,23 @@ export class Database { columns.push(...ret.columns) assert(!rootDefinition[key], Exception.AliasConflict(key, tableName)) - if ((defs as ColumnDef).column) { rootDefinition[key] = defs } else { const { where, type } = defs as Association rootDefinition[key] = typeDefinition.revise(type!, ret.definition) - const tables = this.getAssoTablesDef(db, tableName) - tables[contextName] = currentTable - - const [ predicate, err ] = tryCatch(createPredicate)(tables, contextName, where(ret.table)) - if (err) { + tablesStruct = this.buildTablesStructure(currentTable, contextName) + tablesStruct[`${ contextName }@${ key }`] = { + table: ret.table, + contextName: ret.contextName + } + const [ predicate, e ] = tryCatch(createPredicate)(tablesStruct, contextName, where(ret.table)) + if (e) { warn( - `Failed to build predicate, since ${err.message}` + + `Failed to build predicate, since ${e.message}` + `, on table: ${ ret.table.getName() }` ) } - const joinLink = predicate ? [{ table: ret.table, predicate }, ...ret.joinInfo] : ret.joinInfo @@ -611,14 +617,14 @@ export class Database { case LeafType.navigator: const { containKey, fields, assocaiation } = ctx.leaf as NavigatorLeaf const ret = - this.traverseQueryFields(db, assocaiation.name, new Set(fields), containKey, glob, path.slice(0), context, mode) + this.traverseQueryFields(db, assocaiation.name, new Set(fields), containKey, glob, path.slice(0), context, tablesStruct, mode) handleAdvanced(ret, ctx.key, assocaiation) ctx.skip() break } }) - return { columns, joinInfo, advanced: true, table: currentTable, definition: rootDefinition } + return { columns, joinInfo, advanced: true, table: currentTable, definition: rootDefinition, contextName } } private traverseCompound( @@ -732,7 +738,7 @@ export class Database { entities.forEach(entity => { const pkVal = entity[pk] const clause = createPkClause(pk, pkVal) - const tables = this.getAssoTablesDef(db, tableName) + const tables = this.buildTablesStructure(table) const predicate = createPredicate(tables, tableName, clause) const query = predicatableQuery(db, table, predicate!, StatementType.Delete) @@ -743,7 +749,7 @@ export class Database { const get = (where: Predicate | null = null) => { const [ table ] = Database.getTables(db, tableName) - const tables = this.getAssoTablesDef(db, tableName) + const tables = this.buildTablesStructure(table) const [ predicate, err ] = tryCatch(createPredicate)(tables, tableName, where) if (err) { return Observable.throw(err) @@ -757,20 +763,20 @@ export class Database { } } - private getAssoTablesDef(db: lf.Database, tableName: string, defaultTable: null | lf.schema.Table = null) { - const schema = this.findSchema(tableName) - const tables: Tables = Object.create(null) - if (!defaultTable) { - const [ table ] = Database.getTables(db, tableName) - tables[tableName] = table + private buildTablesStructure(defaultTable: lf.schema.Table, aliasName?: string, tablesStruct: TablesStruct = Object.create(null)) { + if (aliasName) { + tablesStruct[aliasName] = { + table: defaultTable, + contextName: aliasName + } } else { - tables[tableName] = defaultTable + const tableName = defaultTable.getName() + tablesStruct[tableName] = { + table: defaultTable, + contextName: tableName + } } - - schema.associations.forEach((def, k) => { - tables[k] = Database.getTables(db, def.name)[0] - }) - return tables + return tablesStruct } private executor(db: lf.Database, queries: lf.query.Builder[]) { diff --git a/src/storage/helper/create-predicate.ts b/src/storage/helper/create-predicate.ts index 7fa7e848..3c477e69 100644 --- a/src/storage/helper/create-predicate.ts +++ b/src/storage/helper/create-predicate.ts @@ -1,6 +1,6 @@ -import { PredicateProvider, Tables } from '../modules/PredicateProvider' -import { Predicate } from '../../interface' +import { PredicateProvider } from '../modules/PredicateProvider' +import { Predicate, TablesStruct } from '../../interface' -export function createPredicate(tables: Tables, tableName: string, clause: Predicate | null = null) { +export function createPredicate(tables: TablesStruct, tableName: string, clause: Predicate | null = null) { return clause ? new PredicateProvider(tables, tableName, clause).getPredicate() : null } diff --git a/src/storage/modules/PredicateProvider.ts b/src/storage/modules/PredicateProvider.ts index 8f053609..e3522c5e 100644 --- a/src/storage/modules/PredicateProvider.ts +++ b/src/storage/modules/PredicateProvider.ts @@ -1,6 +1,6 @@ import * as lf from 'lovefield' import { forEach, warn } from '../../utils' -import { ValueLiteral, VaildEqType, Predicate, PredicateMeta } from '../../interface' +import { ValueLiteral, VaildEqType, Predicate, PredicateMeta, TablesStruct } from '../../interface' const predicateFactory = { @@ -67,21 +67,23 @@ const compoundPredicateFactory = { }, } -export interface Tables { - [index: string]: lf.schema.Table -} - export class PredicateProvider { - private table = this.tables[this.tableName] + private table: lf.schema.Table | null constructor( - private tables: Tables, + private tables: TablesStruct, private tableName: string, private meta?: Predicate - ) { } + ) { + const tableDef = this.tables[this.tableName] + this.table = tableDef ? tableDef.table : null + } getPredicate(): lf.Predicate | null { + if (!this.table) { + return null + } const predicates = this.meta ? this.normalizeMeta(this.meta) : [] if (predicates.length) { if (predicates.length === 1) { @@ -100,6 +102,7 @@ export class PredicateProvider { } private normalizeMeta(meta: Predicate, column?: lf.schema.Column): lf.Predicate[] { + const table = this.table! const buildSinglePred = (col: lf.schema.Column, val: any, key: string): lf.Predicate => this.checkMethod(key) ? predicateFactory[key](col, val) : col.eq(val as ValueLiteral) @@ -113,14 +116,26 @@ export class PredicateProvider { nestedPreds = this.normalizeMeta(val as Predicate, column) resultPred = compoundPredicateFactory[key](nestedPreds) } else if (this.checkPredicate(val)) { - nestedPreds = this.normalizeMeta(val as any, this.table[key]) + nestedPreds = this.normalizeMeta(val as any, table[key]) resultPred = compoundPredicateFactory['$and'](nestedPreds) } else { - const _column = column || this.table[key] + const keys: string[] = key.split('.') + let _column: lf.schema.Column + if (!column) { + if (keys.length === 1) { + _column = table[key] + } else { + const columnKey = keys.pop()! + const tableName = this.getAliasTableName(keys) + _column = this.tables[tableName].table[columnKey] + } + } else { + _column = column + } if (_column) { resultPred = buildSinglePred(_column, val, key) } else { - warn(`Failed to build predicate, since column: ${key} is not exist, on table: ${this.table.getName()}`) + warn(`Failed to build predicate, since column: ${key} is not exist, on table: ${table.getName()}`) return } } @@ -131,6 +146,19 @@ export class PredicateProvider { return predicates } + private getAliasTableName(keys: string[]) { + let { length } = keys + let ctxName = this.tableName + let resultKey = this.tableName + while (length > 0) { + const localKey = keys.shift()! + resultKey = `${ ctxName }@${ localKey }` + ctxName = this.tables[resultKey].contextName! + length = keys.length + } + return resultKey + } + private checkMethod(methodName: string) { return typeof predicateFactory[methodName] === 'function' } diff --git a/test/schemas/Organization.ts b/test/schemas/Organization.ts new file mode 100644 index 00000000..f04a0e06 --- /dev/null +++ b/test/schemas/Organization.ts @@ -0,0 +1,20 @@ +import { TeambitionTypes, Database, RDBType } from '../index' + +export interface OrganizationSchema { + _id: TeambitionTypes.OrganizationId + name: string + isArchived: boolean +} + +export default (db: Database) => db.defineSchema('Organization', { + _id: { + type: RDBType.STRING, + primaryKey: true + }, + name: { + type: RDBType.STRING + }, + isArchived: { + type: RDBType.BOOLEAN + } +}) diff --git a/test/schemas/Project.ts b/test/schemas/Project.ts index 282c74f4..406b25a5 100644 --- a/test/schemas/Project.ts +++ b/test/schemas/Project.ts @@ -1,16 +1,22 @@ import { TeambitionTypes, Database, RDBType, Relationship } from '../index' - +import { OrganizationSchema } from './Organization' export interface ProjectSchema { _id: TeambitionTypes.ProjectId + _organizationId: string name: string isArchived: boolean posts: any[] + organization: OrganizationSchema } + export default (db: Database) => db.defineSchema('Project', { _id: { type: RDBType.STRING, primaryKey: true }, + _organizationId: { + type: RDBType.STRING + }, name: { type: RDBType.STRING }, @@ -25,5 +31,14 @@ export default (db: Database) => db.defineSchema('Project', { _id: ref.belongTo }) } + }, + organization: { + type: Relationship.oneToOne, + virtual: { + name: 'Organization', + where: (organizationTable) => ({ + _organizationId: organizationTable._id + }) + } } }) diff --git a/test/schemas/Task.ts b/test/schemas/Task.ts index a28919ed..20681079 100644 --- a/test/schemas/Task.ts +++ b/test/schemas/Task.ts @@ -1,5 +1,5 @@ import { RDBType, Relationship } from '../index' -import { TeambitionTypes, Database, SubtaskSchema } from '../index' +import { TeambitionTypes, Database, SubtaskSchema, ProjectSchema } from '../index' export interface TaskSchema { _id: TeambitionTypes.TaskId @@ -12,12 +12,7 @@ export interface TaskSchema { _stageId: TeambitionTypes.StageId _tasklistId: TeambitionTypes.TasklistId accomplished: string - project?: { - _id: TeambitionTypes.ProjectId - name: string, - isArchived: boolean, - posts?: any[] - } + project?: ProjectSchema subtasks: SubtaskSchema[] subtasksCount: number created: string, diff --git a/test/schemas/index.ts b/test/schemas/index.ts index e422c22b..b0ac5613 100644 --- a/test/schemas/index.ts +++ b/test/schemas/index.ts @@ -4,6 +4,7 @@ import ProjectSelectMetadata from './Project' import SubtaskSelectMetadata from './Subtask' import TaskSelectMetadata from './Task' import PostSelectMetadata from './Post' +import OrganizationSelectMetadata from './Organization' import EngineerSelectMetadata from './Engineer' import ModuleSelectMetadata from './Module' import ProgramSelectMetadata from './Program' @@ -16,10 +17,12 @@ export default (db: Database) => { EngineerSelectMetadata(db) ModuleSelectMetadata(db) ProgramSelectMetadata(db) + OrganizationSelectMetadata(db) } export { ProjectSchema } from './Project' export { PostSchema } from './Post' +export { OrganizationSchema } from './Organization' export { SubtaskSchema } from './Subtask' export { TaskSchema } from './Task' export { ProgramSchema } from './Program' diff --git a/test/specs/storage/Database.public.spec.ts b/test/specs/storage/Database.public.spec.ts index 25bd2981..4c9634fc 100644 --- a/test/specs/storage/Database.public.spec.ts +++ b/test/specs/storage/Database.public.spec.ts @@ -9,7 +9,7 @@ import schemaFactory from '../../schemas' import { TestFixture2 } from '../../schemas/Test' import { scenarioGen, programGen, postGen, taskGen, subtaskGen } from '../../utils/generators' import { RDBType, DataStoreType, Database, clone, forEach, JoinMode } from '../../index' -import { TaskSchema, ProjectSchema, PostSchema, ModuleSchema, ProgramSchema, SubtaskSchema } from '../../index' +import { TaskSchema, ProjectSchema, PostSchema, ModuleSchema, ProgramSchema, SubtaskSchema, OrganizationSchema } from '../../index' import { InvalidQuery, NonExistentTable, InvalidType, PrimaryKeyNotProvided, NotConnected, Selector } from '../../index' use(SinonChai) @@ -463,6 +463,7 @@ export default describe('Database Testcase: ', () => { const projects: ProjectSchema[] = [] const posts: PostSchema[] = [] const tasks: TaskSchema[] = [] + const organizations: OrganizationSchema[] = [] associationFixture.forEach(f => { forEach(f, (value, key) => { @@ -472,6 +473,9 @@ export default describe('Database Testcase: ', () => { if (value.posts) { posts.push(...value.posts) } + if (value.organization) { + organizations.push(value.organization) + } projects.push(value) } }) @@ -482,7 +486,8 @@ export default describe('Database Testcase: ', () => { database.insert('Task', tasks), database.insert('Subtask', subtasks), database.insert('Project', projects), - database.insert('Post', posts) + database.insert('Post', posts), + database.insert('Organization', organizations) ] Observable.forkJoin(...queries).subscribe(() => { @@ -505,6 +510,28 @@ export default describe('Database Testcase: ', () => { expect(keys(result.project)).to.deep.equal(refFields) }) + it('should get association with nested Association query', function* () { + const queryToken = database.get('Task', { + where: { 'project._id': innerTarget.project._id } + }) + + const results = yield queryToken.values() + const [ result ] = results + expect(results.length).to.equal(1) + expect(result).to.deep.equal(innerTarget) + }) + + it('should get association with deep nested Association query', function* () { + const queryToken = database.get('Task', { + where: { 'project.organization._id': innerTarget.project._organizationId } + }) + + const results = yield queryToken.values() + const [ result ] = results + expect(results.length).to.equal(1) + expect(result).to.deep.equal(innerTarget) + }) + it('should apply `skip` clause on multi joined query', function* () { const result = yield database.get('Task', { skip: 20 }).values() @@ -757,7 +784,7 @@ export default describe('Database Testcase: ', () => { } }) - const errSpy = sinon.spy((): void => void 0) + const errSpy = sinon.spy() tmpDB.connect() tmpDB.update(T, { id: 1 }, { diff --git a/test/specs/storage/modules/PredicateProvider.spec.ts b/test/specs/storage/modules/PredicateProvider.spec.ts index 542b29c8..f70d6db1 100644 --- a/test/specs/storage/modules/PredicateProvider.spec.ts +++ b/test/specs/storage/modules/PredicateProvider.spec.ts @@ -14,7 +14,7 @@ export default describe('PredicateProvider test', () => { let db: lf.Database let table: lf.schema.Table - let tableDef: { TestPredicateProvider: lf.schema.Table } + let tableDef: { TestPredicateProvider: { table: lf.schema.Table } } let version = 1 function predicateFactory(desc: Predicate) { @@ -53,7 +53,7 @@ export default describe('PredicateProvider test', () => { })) } - tableDef = { TestPredicateProvider: table } + tableDef = { TestPredicateProvider: { table } } yield db.insert().into(table).values(rows).exec() }) diff --git a/test/specs/storage/modules/Selector.spec.ts b/test/specs/storage/modules/Selector.spec.ts index 11ef353f..fd7fa653 100644 --- a/test/specs/storage/modules/Selector.spec.ts +++ b/test/specs/storage/modules/Selector.spec.ts @@ -26,7 +26,7 @@ interface Fixture { export default describe('Selector test', () => { let db: lf.Database let table: lf.schema.Table - let tableDef: { TestSelectMetadata: lf.schema.Table } + let tableDef: { TestSelectMetadata: { table: lf.schema.Table } } let version = 1 let tableShape: ShapeMatcher @@ -100,7 +100,7 @@ export default describe('Selector test', () => { } } - tableDef = { TestSelectMetadata: table } + tableDef = { TestSelectMetadata: { table } } }) afterEach(() => { diff --git a/test/utils/generators/task-generator.ts b/test/utils/generators/task-generator.ts index b1210df2..a6ce08c0 100644 --- a/test/utils/generators/task-generator.ts +++ b/test/utils/generators/task-generator.ts @@ -14,6 +14,7 @@ export default function (limit: number) { const _projectId = uuid() const _stageId = uuid() const _creatorId = uuid() + const _organizationId = uuid() const _executorId = random.rnd(20) ? uuid() : _creatorId const involves = [ _executorId ] if (_creatorId !== _executorId) { @@ -35,7 +36,13 @@ export default function (limit: number) { _id: _projectId, name: 'project name: ' + uuid(), isArchived: true, - posts: postGen(5, _projectId) + posts: postGen(5, _projectId), + _organizationId: _organizationId, + organization: { + _id: _organizationId, + name: 'organization name: ' + uuid(), + isArchived: false, + } }, involveMembers: involveMembersGen(15, involves), created: moment().add(6 - random.number(0, 12), 'month').add(30 - random.number(0, 30), 'day').toISOString() From 8dc5817d93465dfb31d725fd917a8ccc10f05041 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 9 Aug 2017 18:16:48 +0800 Subject: [PATCH 03/19] feat(Database): support association query without association fields --- package.json | 4 +- src/storage/Database.ts | 49 +++++++++++++++++++--- test/specs/storage/Database.public.spec.ts | 16 ++++++- 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index ccb00cd9..f26a100f 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "clean_dist_cjs": "rm -rf ./dist/cjs", "clean_dist_es": "rm -rf ./dist/es", "check_circular_dependencies": "madge ./dist/cjs --circular", - "compile_cjs": " tsc dist/cjs/src/index.ts dist/cjs/src/proxy/index.ts -m commonjs --outDir dist/cjs --sourcemap --target ES5 -d --diagnostics --pretty --strict --noImplicitReturns --noUnusedLocals --noUnusedParameters --suppressImplicitAnyIndexErrors --moduleResolution node --noEmitHelpers --importHelpers --lib es5,es2015.iterable,es2015.collection,es2015.promise,dom", - "compile_module_es": "tsc dist/es/src/index.ts dist/es/src/proxy/index.ts -m ES2015 --outDir dist/es --sourcemap --target ES5 -d --diagnostics --pretty --strict --noImplicitReturns --noUnusedLocals --noUnusedParameters --suppressImplicitAnyIndexErrors --moduleResolution node --noEmitHelpers --importHelpers --lib es5,es2015.iterable,es2015.collection,es2015.promise,dom", + "compile_cjs": " tsc dist/cjs/src/index.ts dist/cjs/src/proxy/index.ts -m commonjs --outDir dist/cjs --sourcemap --target ES5 -d --diagnostics --pretty --strict --noImplicitReturns --noUnusedLocals --noUnusedParameters --suppressImplicitAnyIndexErrors --moduleResolution node --noEmitHelpers --importHelpers --downlevelIteration --lib es5,es2015.iterable,es2015.collection,es2015.promise,dom", + "compile_module_es": "tsc dist/es/src/index.ts dist/es/src/proxy/index.ts -m ES2015 --outDir dist/es --sourcemap --target ES5 -d --diagnostics --pretty --strict --noImplicitReturns --noUnusedLocals --noUnusedParameters --suppressImplicitAnyIndexErrors --moduleResolution node --noEmitHelpers --importHelpers --downlevelIteration --lib es5,es2015.iterable,es2015.collection,es2015.promise,dom", "copy_src_cjs": "shx mkdir -p ./dist/cjs/src && shx cp -r ./src/* ./dist/cjs/src", "copy_src_es": "shx mkdir -p ./dist/es/src && shx cp -r ./src/* ./dist/es/src", "cover": "rm -rf ./.nyc_output ./coverage && NODE_ENV=test nyc --reporter=html --reporter=lcov --exclude=node_modules --exclude=spec-js/test --exclude=spec-js/src/storage/lovefield.js --exclude=spec-js/src/shared/Logger.js --exclude=spec-js/src/utils/option.js --exclude=spec-js/src/utils/valid.js tman --mocha spec-js/test/run.js", diff --git a/src/storage/Database.ts b/src/storage/Database.ts index 341751a3..eec138b0 100644 --- a/src/storage/Database.ts +++ b/src/storage/Database.ts @@ -420,12 +420,12 @@ export class Database { const containKey = containFields ? contains(pk, clause.fields!) : true const fields: Set = containFields ? new Set(clause.fields) : new Set(schema.columns.keys()) + const tablesStruct: TablesStruct = Object.create(null) const { table, columns, joinInfo, definition, contextName } = - this.traverseQueryFields(db, tableName, fields, containKey, !containFields, [], {}, {}, mode) + this.traverseQueryFields(db, tableName, fields, containKey, !containFields, [], {}, tablesStruct, mode) const query = predicatableQuery(db, table!, null, StatementType.Select, ...columns) - - const tablesStruct: TablesStruct = Object.create(null) + const relatedTables = this.getAllRelatedTables(tableName, contextName) joinInfo.forEach((info: JoinInfo) => { const predicate = info.predicate @@ -434,6 +434,8 @@ export class Database { } }) + this.paddingTableStruct(db, relatedTables, tablesStruct) + const orderDesc = (clause.orderBy || []).map(desc => { return { column: table![desc.fieldName], @@ -545,6 +547,8 @@ export class Database { const [ originTable ] = Database.getTables(db, tableName) const currentTable = originTable.as(contextName) + assign(tablesStruct, this.buildTablesStructure(currentTable, contextName)) + const handleAdvanced = (ret: any, key: string, defs: Association | ColumnDef) => { if (!ret.advanced) { return @@ -557,7 +561,6 @@ export class Database { } else { const { where, type } = defs as Association rootDefinition[key] = typeDefinition.revise(type!, ret.definition) - tablesStruct = this.buildTablesStructure(currentTable, contextName) tablesStruct[`${ contextName }@${ key }`] = { table: ret.table, contextName: ret.contextName @@ -623,7 +626,6 @@ export class Database { break } }) - return { columns, joinInfo, advanced: true, table: currentTable, definition: rootDefinition, contextName } } @@ -779,6 +781,43 @@ export class Database { return tablesStruct } + private getAllRelatedTables(tableName: string, contextName: string) { + const tablesStructure = new Map() + const schemas = [ + { + schema: this.findSchema(tableName), + relatedTo: contextName + } + ] + while (schemas.length) { + const { schema, relatedTo } = schemas.pop()! + for (const [ key, val ] of schema.associations) { + const relatedName = val.name + if (!tablesStructure.has(relatedName)) { + const path = `${ relatedTo }@${ key }` + tablesStructure.set(relatedName, path) + schemas.push({ + schema: this.findSchema(relatedName), + relatedTo: relatedName + }) + } + } + } + return tablesStructure + } + + private paddingTableStruct(db: lf.Database, tables: Map, tablesStruct: TablesStruct): TablesStruct { + forEach(tablesStruct, structure => { + const tableName = structure.table.getName() + tables.delete(tableName) + }) + tables.forEach((key, tableName) => { + const [ table ] = Database.getTables(db, tableName) + tablesStruct[key] = { table, contextName: tableName } + }) + return tablesStruct + } + private executor(db: lf.Database, queries: lf.query.Builder[]) { const tx = db.createTransaction() const handler = { diff --git a/test/specs/storage/Database.public.spec.ts b/test/specs/storage/Database.public.spec.ts index 4c9634fc..d91a398a 100644 --- a/test/specs/storage/Database.public.spec.ts +++ b/test/specs/storage/Database.public.spec.ts @@ -4,6 +4,7 @@ import { describe, it, beforeEach, afterEach } from 'tman' import { expect, assert, use } from 'chai' import * as sinon from 'sinon' import * as SinonChai from 'sinon-chai' + import { uuid, checkExecutorResult } from '../../utils' import schemaFactory from '../../schemas' import { TestFixture2 } from '../../schemas/Test' @@ -510,7 +511,7 @@ export default describe('Database Testcase: ', () => { expect(keys(result.project)).to.deep.equal(refFields) }) - it('should get association with nested Association query', function* () { + it('should get association by nested Association query', function* () { const queryToken = database.get('Task', { where: { 'project._id': innerTarget.project._id } }) @@ -521,7 +522,7 @@ export default describe('Database Testcase: ', () => { expect(result).to.deep.equal(innerTarget) }) - it('should get association with deep nested Association query', function* () { + it('should get association by deep nested Association query', function* () { const queryToken = database.get('Task', { where: { 'project.organization._id': innerTarget.project._organizationId } }) @@ -532,6 +533,17 @@ export default describe('Database Testcase: ', () => { expect(result).to.deep.equal(innerTarget) }) + it('should get nothing by deep nested Association query without association fields', function* () { + const fields = ['_id', 'content'] + const queryToken = database.get('Task', { + fields, + where: { 'project.organization._id': innerTarget.project._organizationId } + }) + + const results = yield queryToken.values() + expect(results.length).to.equal(0) + }) + it('should apply `skip` clause on multi joined query', function* () { const result = yield database.get('Task', { skip: 20 }).values() From fa160116995077eae6fd6ab4c0e7fb1ae3b61b07 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 9 Aug 2017 23:19:10 +0800 Subject: [PATCH 04/19] test(PredicateProvider): add test for tableName missmatch in PredicateProvider --- test/specs/storage/modules/PredicateProvider.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/specs/storage/modules/PredicateProvider.spec.ts b/test/specs/storage/modules/PredicateProvider.spec.ts index f70d6db1..0ff236e3 100644 --- a/test/specs/storage/modules/PredicateProvider.spec.ts +++ b/test/specs/storage/modules/PredicateProvider.spec.ts @@ -70,6 +70,12 @@ export default describe('PredicateProvider test', () => { expect(result).deep.equal(expectResult) }) + it('should get null when tablesStructure is not contain tableName', () => { + const predicateProvider = new PredicateProvider({}, 'whatever', { _id: 1 }) + const result = predicateProvider.getPredicate() + expect(result).to.be.null + }) + it('empty meta should ok', function* () { const predicate = predicateFactory({}).getPredicate() expect(predicate).to.be.null From f193d6c5900510c07eb5ca554647e5ba83b211fe Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 10 Aug 2017 19:55:54 +0800 Subject: [PATCH 05/19] feat(Database): support nested predicate key --- src/storage/Database.ts | 136 +++++++++++++++++++-- src/storage/modules/PredicateProvider.ts | 26 ++-- src/utils/concat.ts | 9 ++ src/utils/index.ts | 1 + test/specs/storage/Database.public.spec.ts | 37 +++++- test/specs/utils/utils.spec.ts | 26 +++- 6 files changed, 215 insertions(+), 20 deletions(-) create mode 100644 src/utils/concat.ts diff --git a/src/storage/Database.ts b/src/storage/Database.ts index eec138b0..7ea8b1e9 100644 --- a/src/storage/Database.ts +++ b/src/storage/Database.ts @@ -8,7 +8,7 @@ import * as Exception from '../exception' import * as typeDefinition from './helper/definition' import Version from '../version' import { Traversable } from '../shared' -import { Mutation, Selector, QueryToken, PredicateProvider } from './modules' +import { Mutation, Selector, QueryToken, PredicateProvider, checkPredicate, predicateOperatorNames } from './modules' import { dispose, contextTableName, fieldIdentifier, hiddenColName } from './symbols' import { forEach, clone, contains, tryCatch, hasOwn, getType, assert, identity, warn } from '../utils' import { createPredicate, createPkClause, mergeTransactionResult, predicatableQuery, lfFactory } from './helper' @@ -226,7 +226,11 @@ export class Database { return Observable.fromPromise(prefetch.exec()) .concatMap((scopedIds) => { - const query = predicatableQuery(db, table, provider.getPredicate(), StatementType.Delete) + const predicate = provider.getPredicate() + if (!predicate) { + warn(`The result of parsed Predicate is null, you are deleting all ${ tableName } Table!`) + } + const query = predicatableQuery(db, table, predicate, StatementType.Delete) scopedIds.forEach((entity: any) => this.storedIds.delete(fieldIdentifier(tableName, entity[pk!]))) @@ -419,13 +423,17 @@ export class Database { const containFields = !!clause.fields const containKey = containFields ? contains(pk, clause.fields!) : true - const fields: Set = containFields ? new Set(clause.fields) : new Set(schema.columns.keys()) + const [ additionJoinInfo ] = tryCatch(this.buildJoinFieldsFromPredicate.bind(this))(clause.where!, tableName) + + const fields = containFields ? clause.fields : Array.from(schema.columns.keys()) + const newFields = containFields && additionJoinInfo ? this.mergeFields(fields!, [ additionJoinInfo as Field ]) : fields + const fieldsSet: Set = new Set(newFields) const tablesStruct: TablesStruct = Object.create(null) + const { table, columns, joinInfo, definition, contextName } = - this.traverseQueryFields(db, tableName, fields, containKey, !containFields, [], {}, tablesStruct, mode) + this.traverseQueryFields(db, tableName, fieldsSet, containKey, !containFields, [], {}, tablesStruct, mode) const query = predicatableQuery(db, table!, null, StatementType.Select, ...columns) - const relatedTables = this.getAllRelatedTables(tableName, contextName) joinInfo.forEach((info: JoinInfo) => { const predicate = info.predicate @@ -434,7 +442,7 @@ export class Database { } }) - this.paddingTableStruct(db, relatedTables, tablesStruct) + this.paddingTablesStruct(db, this.getAllRelatedTables(tableName, contextName), tablesStruct) const orderDesc = (clause.orderBy || []).map(desc => { return { @@ -806,7 +814,7 @@ export class Database { return tablesStructure } - private paddingTableStruct(db: lf.Database, tables: Map, tablesStruct: TablesStruct): TablesStruct { + private paddingTablesStruct(db: lf.Database, tables: Map, tablesStruct: TablesStruct): TablesStruct { forEach(tablesStruct, structure => { const tableName = structure.table.getName() tables.delete(tableName) @@ -818,6 +826,120 @@ export class Database { return tablesStruct } + private buildJoinFieldsFromPredicate(predicate: Predicate, tableName: string) { + let result = Object.create(null) + let schema = this.findSchema(tableName) + + const buildJoinInfo = (keys: string[]) => { + return keys.reduce((acc, k, currentIndex) => { + if (currentIndex < keys.length - 1) { + const _newTableName = schema.associations.get(k)!.name + schema = this.findSchema(_newTableName) + const pk = schema.pk + const f = currentIndex !== keys.length - 2 ? Object.create(null) : keys[currentIndex + 1] + acc[k] = [pk] + if (f !== pk) { + acc[k].push(f) + } + return f + } + }, result) + } + + forEach(predicate, (val, key) => { + if (!predicateOperatorNames.has(key)) { + if (checkPredicate(val)) { + const keys = key.split('.') + const newTableName = this.getTablenameFromNestedPredicate(key, tableName) + if (keys.length > 1) { + buildJoinInfo(keys) + } else { + result[key] = [schema.pk, this.buildJoinFieldsFromPredicate(val, newTableName)] + } + } else { + const keys = key.split('.') + if (keys.length > 1) { + buildJoinInfo(keys) + } else { + result = key + } + } + } + }) + + return typeof result === 'object' && Object.keys(result).length ? result : null + } + + private mergeFields(fields: Field[], predicateFields: Field[]) { + const newFields = this.buildFieldsTree(fields) + predicateFields = this.buildFieldsTree(predicateFields) + + const nestedFields = newFields[newFields.length - 1] + const nestedPredicateFields = predicateFields[predicateFields.length - 1] + forEach(predicateFields, val => { + if (typeof val === 'string') { + if (newFields.indexOf(val) === -1) { + newFields.push(val) + } + } + }) + + if (typeof nestedPredicateFields === 'object') { + if (typeof nestedFields === 'object') { + forEach(nestedPredicateFields, (val, key) => { + const existedVal = nestedFields[key] + if (existedVal) { + this.mergeFields(existedVal, val) + } else { + nestedFields[key] = val + } + }) + } else { + newFields.push(nestedPredicateFields) + } + } + return newFields + } + + private buildFieldsTree(fields: Field[]) { + const dist: Field[] = [] + const nestedFields: { [index: string]: Field[] }[] = [] + forEach(fields, field => { + if (typeof field === 'string') { + dist.push(field) + } else { + nestedFields.push(field) + } + }) + + const nestedField = nestedFields.reduce((acc, field) => { + forEach(field, (val, key) => { + const existedVal = acc[key] + if (existedVal) { + acc[key] = this.mergeFields(existedVal, val) + } else { + acc[key] = val + } + }) + return acc + }, {}) + if (Object.keys(nestedField).length) { + dist.push(nestedField) + } + return dist + } + + private getTablenameFromNestedPredicate(def: string, tableName: string) { + const defs = def.split('.') + let newTableName: string + let schema = this.findSchema(tableName) + forEach(defs, de => { + newTableName = schema.associations.get(de)!.name + schema = this.findSchema(newTableName) + }) + return newTableName! + } + private executor(db: lf.Database, queries: lf.query.Builder[]) { const tx = db.createTransaction() const handler = { diff --git a/src/storage/modules/PredicateProvider.ts b/src/storage/modules/PredicateProvider.ts index e3522c5e..edc072be 100644 --- a/src/storage/modules/PredicateProvider.ts +++ b/src/storage/modules/PredicateProvider.ts @@ -1,5 +1,5 @@ import * as lf from 'lovefield' -import { forEach, warn } from '../../utils' +import { forEach, warn, concat } from '../../utils' import { ValueLiteral, VaildEqType, Predicate, PredicateMeta, TablesStruct } from '../../interface' const predicateFactory = { @@ -67,6 +67,9 @@ const compoundPredicateFactory = { }, } +export const predicateOperatorNames = + new Set(concat(Object.keys(predicateFactory), Object.keys(compoundPredicateFactory))) + export class PredicateProvider { private table: lf.schema.Table | null @@ -101,7 +104,7 @@ export class PredicateProvider { return pred ? JSON.stringify(this.meta) : '' } - private normalizeMeta(meta: Predicate, column?: lf.schema.Column): lf.Predicate[] { + private normalizeMeta(meta: Predicate, column?: lf.schema.Column, parentKey?: string): lf.Predicate[] { const table = this.table! const buildSinglePred = (col: lf.schema.Column, val: any, key: string): lf.Predicate => this.checkMethod(key) ? predicateFactory[key](col, val) : col.eq(val as ValueLiteral) @@ -115,10 +118,13 @@ export class PredicateProvider { if (this.checkCompound(key)) { nestedPreds = this.normalizeMeta(val as Predicate, column) resultPred = compoundPredicateFactory[key](nestedPreds) - } else if (this.checkPredicate(val)) { - nestedPreds = this.normalizeMeta(val as any, table[key]) + } else if (checkPredicate(val)) { + nestedPreds = this.normalizeMeta(val as any, table[key], key) resultPred = compoundPredicateFactory['$and'](nestedPreds) } else { + if (parentKey && !this.checkMethod(key)) { + key = `${ parentKey }.${ key }` + } const keys: string[] = key.split('.') let _column: lf.schema.Column if (!column) { @@ -167,11 +173,11 @@ export class PredicateProvider { return typeof compoundPredicateFactory[methodName] === 'function' } - private checkPredicate(val: Partial> | ValueLiteral) { - return val && typeof val === 'object' && - !(val instanceof Array) && - !(val instanceof RegExp) && - !(val instanceof (lf.schema as any).BaseColumn) - } +} +export function checkPredicate(val: Partial> | ValueLiteral) { + return val && typeof val === 'object' && + !(val instanceof Array) && + !(val instanceof RegExp) && + !(val instanceof (lf.schema as any).BaseColumn) } diff --git a/src/utils/concat.ts b/src/utils/concat.ts new file mode 100644 index 00000000..f3d25d1c --- /dev/null +++ b/src/utils/concat.ts @@ -0,0 +1,9 @@ +import { forEach } from './for-each' + +// side effect +export function concat(target: T[], patch: T[]) { + forEach(patch, p => { + target.push(p) + }) + return target +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 60cfed79..cdf57df9 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -9,4 +9,5 @@ export * from './for-each' export * from './identity' export * from './get-type' export * from './try-catch' +export * from './concat' export { warn } from './warn' diff --git a/test/specs/storage/Database.public.spec.ts b/test/specs/storage/Database.public.spec.ts index d91a398a..6230fd58 100644 --- a/test/specs/storage/Database.public.spec.ts +++ b/test/specs/storage/Database.public.spec.ts @@ -533,7 +533,7 @@ export default describe('Database Testcase: ', () => { expect(result).to.deep.equal(innerTarget) }) - it('should get nothing by deep nested Association query without association fields', function* () { + it('should get value by deep nested Association query without association fields', function* () { const fields = ['_id', 'content'] const queryToken = database.get('Task', { fields, @@ -541,7 +541,40 @@ export default describe('Database Testcase: ', () => { }) const results = yield queryToken.values() - expect(results.length).to.equal(0) + + expect(results.length).to.equal(1) + }) + + it('should get value by deep nested Association query without nested association fields', function* () { + const fields = ['_id', 'content'] + const queryToken = database.get('Task', { + fields, + where: { + project: { + 'organization._id': innerTarget.project._organizationId + } + } + }) + + const results = yield queryToken.values() + + expect(results.length).to.equal(1) + }) + + it('should merge fields when get value by deep nested Association query without nested association fields', function* () { + const fields = ['_id', 'content', { project: ['_id'] }, { project: [ { organization: [ '_id' ] } ] }] + const queryToken = database.get('Task', { + fields, + where: { + 'project.organization': { + _id: innerTarget.project._organizationId + } + } + }) + + const results = yield queryToken.values() + + expect(results.length).to.equal(1) }) it('should apply `skip` clause on multi joined query', function* () { diff --git a/test/specs/utils/utils.spec.ts b/test/specs/utils/utils.spec.ts index 079f5252..68a6bd5d 100644 --- a/test/specs/utils/utils.spec.ts +++ b/test/specs/utils/utils.spec.ts @@ -1,4 +1,4 @@ -import { forEach, clone, getType, assert, hash } from '../../index' +import { forEach, clone, getType, assert, hash, concat } from '../../index' import { describe, it } from 'tman' import { expect } from 'chai' @@ -407,4 +407,28 @@ export default describe('Utils Testcase: ', () => { }) + describe('Func: concat', () => { + it('should return target', () => { + const target: number[] = [] + const result = concat(target, [1, 2, 3]) + expect(result).to.equal(target) + }) + + it('concat result should be correct', () => { + const target = ['foo', 'bar'] + const patch = [1, 2, 3] + const expected = target.concat(patch as any) + const result = concat(target, patch as any) + expect(result).to.deep.equal(expected) + }) + + it('should have side effect', () => { + const target = ['foo', 'bar'] + const patch = [1, 2, 3] + const expected = target.concat(patch as any) + concat(target, patch as any) + expect(target).to.deep.equal(expected) + }) + }) + }) From 96261a7d52c8117fdfac5c1aabe9d9b62fa731e0 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 16 Aug 2017 23:57:30 +0800 Subject: [PATCH 06/19] fix(Database#remove): stop passing Clause to PredicateProvider, instead of passing Predicate --- src/storage/Database.ts | 8 ++++---- test/specs/storage/Database.public.spec.ts | 4 +--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/storage/Database.ts b/src/storage/Database.ts index 7ea8b1e9..24bef0ee 100644 --- a/src/storage/Database.ts +++ b/src/storage/Database.ts @@ -13,7 +13,7 @@ import { dispose, contextTableName, fieldIdentifier, hiddenColName } from './sym import { forEach, clone, contains, tryCatch, hasOwn, getType, assert, identity, warn } from '../utils' import { createPredicate, createPkClause, mergeTransactionResult, predicatableQuery, lfFactory } from './helper' import { Relationship, RDBType, DataStoreType, LeafType, StatementType, JoinMode } from '../interface/enum' -import { Record, Field, JoinInfo, Query, Clause, Predicate } from '../interface' +import { Record, Field, JoinInfo, Query, Predicate } from '../interface' import { SchemaDef, ColumnDef, ParsedSchema, Association, ScopedHandler } from '../interface' import { ColumnLeaf, NavigatorLeaf, ExecutorResult, UpsertContext, SelectContext, TablesStruct } from '../interface' @@ -267,7 +267,7 @@ export class Database { }) } - remove(tableName: string, clause: Clause = {}): Observable { + remove(tableName: string, clause: Predicate = {}): Observable { const [schema, err] = tryCatch(this.findSchema)(tableName) if (err) { return Observable.throw(err) @@ -277,7 +277,7 @@ export class Database { return this.database$.concatMap((db) => { const [ table ] = Database.getTables(db, tableName) const tables = this.buildTablesStructure(table) - const predicate = createPredicate(tables, tableName, clause.where) + const predicate = createPredicate(tables, tableName, clause) const queries: lf.query.Builder[] = [] const removedIds: any = [] @@ -749,7 +749,7 @@ export class Database { const pkVal = entity[pk] const clause = createPkClause(pk, pkVal) const tables = this.buildTablesStructure(table) - const predicate = createPredicate(tables, tableName, clause) + const predicate = createPredicate(tables, tableName, clause.where) const query = predicatableQuery(db, table, predicate!, StatementType.Delete) queryCollection.push(query) diff --git a/test/specs/storage/Database.public.spec.ts b/test/specs/storage/Database.public.spec.ts index 6230fd58..e9c4dff6 100644 --- a/test/specs/storage/Database.public.spec.ts +++ b/test/specs/storage/Database.public.spec.ts @@ -1121,9 +1121,7 @@ export default describe('Database Testcase: ', () => { const execRet1 = yield database.upsert('Program', program) yield database.delete('Program', { - where: { - _id: program._id - } + _id: program._id }) const execRet2 = yield database.upsert('Program', program) From 7f4ecd289cb40b978a2f354f2032cdb1b63cfa0c Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 17 Aug 2017 11:01:14 +0800 Subject: [PATCH 07/19] chore: display report coverage after run coverage --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f26a100f..e86fe155 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "compile_module_es": "tsc dist/es/src/index.ts dist/es/src/proxy/index.ts -m ES2015 --outDir dist/es --sourcemap --target ES5 -d --diagnostics --pretty --strict --noImplicitReturns --noUnusedLocals --noUnusedParameters --suppressImplicitAnyIndexErrors --moduleResolution node --noEmitHelpers --importHelpers --downlevelIteration --lib es5,es2015.iterable,es2015.collection,es2015.promise,dom", "copy_src_cjs": "shx mkdir -p ./dist/cjs/src && shx cp -r ./src/* ./dist/cjs/src", "copy_src_es": "shx mkdir -p ./dist/es/src && shx cp -r ./src/* ./dist/es/src", - "cover": "rm -rf ./.nyc_output ./coverage && NODE_ENV=test nyc --reporter=html --reporter=lcov --exclude=node_modules --exclude=spec-js/test --exclude=spec-js/src/storage/lovefield.js --exclude=spec-js/src/shared/Logger.js --exclude=spec-js/src/utils/option.js --exclude=spec-js/src/utils/valid.js tman --mocha spec-js/test/run.js", + "cover": "rm -rf ./.nyc_output ./coverage && NODE_ENV=test nyc --reporter=html --reporter=lcov --exclude=node_modules --exclude=spec-js/test --exclude=spec-js/src/storage/lovefield.js --exclude=spec-js/src/shared/Logger.js --exclude=spec-js/src/utils/option.js --exclude=spec-js/src/utils/valid.js tman --mocha spec-js/test/run.js && nyc report", "lint": "tslint -c tslint.json src/*.ts --project ./tsconfig.json --type-check src/**/*.ts ./test/**/*.ts -e ./test/e2e/*.ts", "publish_all": "ts-node ./tools/publish.ts", "start": "webpack-dev-server --inline --colors --progress --port 3000", From 5ce4099011e7483e70df34653c2493abb26be9ed Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 17 Aug 2017 11:53:35 +0800 Subject: [PATCH 08/19] feat(Database): warn if delete/remove/update all table --- src/storage/Database.ts | 9 ++++ src/storage/helper/create-predicate.ts | 4 +- test/specs/storage/Database.public.spec.ts | 53 ++++++++++++++++++++-- 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/storage/Database.ts b/src/storage/Database.ts index 24bef0ee..8d150894 100644 --- a/src/storage/Database.ts +++ b/src/storage/Database.ts @@ -192,6 +192,11 @@ export class Database { const mut = { ...(entity as any), ...hiddenPayload } const tables = this.buildTablesStructure(table) const predicate = createPredicate(tables, tableName, clause) + + if (!predicate) { + warn(`The result of parsed Predicate is null, you are deleting all ${ tableName } Table!`) + } + const query = predicatableQuery(db, table, predicate!, StatementType.Update) forEach(mut, (val, key) => { @@ -279,6 +284,10 @@ export class Database { const tables = this.buildTablesStructure(table) const predicate = createPredicate(tables, tableName, clause) + if (!predicate) { + warn(`The result of parsed Predicate is null, you are removing all ${ tableName } Tables!`) + } + const queries: lf.query.Builder[] = [] const removedIds: any = [] queries.push(predicatableQuery(db, table, predicate!, StatementType.Delete)) diff --git a/src/storage/helper/create-predicate.ts b/src/storage/helper/create-predicate.ts index 3c477e69..27d435e3 100644 --- a/src/storage/helper/create-predicate.ts +++ b/src/storage/helper/create-predicate.ts @@ -1,6 +1,6 @@ import { PredicateProvider } from '../modules/PredicateProvider' import { Predicate, TablesStruct } from '../../interface' -export function createPredicate(tables: TablesStruct, tableName: string, clause: Predicate | null = null) { - return clause ? new PredicateProvider(tables, tableName, clause).getPredicate() : null +export function createPredicate(tables: TablesStruct, tableName: string, clause: Predicate) { + return new PredicateProvider(tables, tableName, clause).getPredicate() } diff --git a/test/specs/storage/Database.public.spec.ts b/test/specs/storage/Database.public.spec.ts index e9c4dff6..24e290ba 100644 --- a/test/specs/storage/Database.public.spec.ts +++ b/test/specs/storage/Database.public.spec.ts @@ -9,7 +9,7 @@ import { uuid, checkExecutorResult } from '../../utils' import schemaFactory from '../../schemas' import { TestFixture2 } from '../../schemas/Test' import { scenarioGen, programGen, postGen, taskGen, subtaskGen } from '../../utils/generators' -import { RDBType, DataStoreType, Database, clone, forEach, JoinMode } from '../../index' +import { RDBType, DataStoreType, Database, clone, forEach, JoinMode, Logger } from '../../index' import { TaskSchema, ProjectSchema, PostSchema, ModuleSchema, ProgramSchema, SubtaskSchema, OrganizationSchema } from '../../index' import { InvalidQuery, NonExistentTable, InvalidType, PrimaryKeyNotProvided, NotConnected, Selector } from '../../index' @@ -545,7 +545,7 @@ export default describe('Database Testcase: ', () => { expect(results.length).to.equal(1) }) - it('should get value by deep nested Association query without nested association fields', function* () { + it('should get value by deep nested Association query without association fields', function* () { const fields = ['_id', 'content'] const queryToken = database.get('Task', { fields, @@ -561,7 +561,7 @@ export default describe('Database Testcase: ', () => { expect(results.length).to.equal(1) }) - it('should merge fields when get value by deep nested Association query without nested association fields', function* () { + it('should merge fields when get value by deep nested Association query with nested association fields', function* () { const fields = ['_id', 'content', { project: ['_id'] }, { project: [ { organization: [ '_id' ] } ] }] const queryToken = database.get('Task', { fields, @@ -577,6 +577,22 @@ export default describe('Database Testcase: ', () => { expect(results.length).to.equal(1) }) + it('should merge fields when get value by deep nested Association query without nested association fields', function* () { + const fields = ['_id', 'content', { subtasks: ['_id'] }] + const queryToken = database.get('Task', { + fields, + where: { + 'project.organization': { + _id: innerTarget.project._organizationId + } + } + }) + + const results = yield queryToken.values() + + expect(results.length).to.equal(1) + }) + it('should apply `skip` clause on multi joined query', function* () { const result = yield database.get('Task', { skip: 20 }).values() @@ -915,6 +931,19 @@ export default describe('Database Testcase: ', () => { } }) + it('should warn if predicate is null', function* () { + const spy = sinon.spy(Logger, 'warn') + + yield database.delete('Task') + + const result = yield database.get('Task').values() + + expect(result).deep.equal([]) + expect(spy.callCount).to.equal(1) + + spy.restore() + }) + }) describe('Method: upsert', () => { @@ -1280,6 +1309,24 @@ export default describe('Database Testcase: ', () => { } }) + it('should warn if predicate is null', function* () { + const spy = sinon.spy(Logger, 'warn') + const [ program ] = programGen(1, 1) + + yield database.upsert('Engineer', program.owner) + const ret1 = yield database.get('Engineer').values() + const execRet = yield database.remove('Engineer') + const ret2 = yield database.get('Engineer').values() + + expect(spy.callCount).to.equal(1) + + checkExecutorResult(execRet, 0, 1, 0) + expect(ret1).have.lengthOf(1) + expect(ret2).to.deep.equal([]) + + spy.restore() + }) + }) describe('case: Relational Scenario', () => { From 4f0b23776a417688fa99a401927c7afc7ffb123a Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 17 Aug 2017 12:19:36 +0800 Subject: [PATCH 09/19] feat(utils/keys): use keys as alias for Object.keys --- src/shared/Traversable.ts | 4 ++-- src/storage/Database.ts | 8 ++++---- src/storage/modules/Mutation.ts | 10 ++++----- src/storage/modules/PredicateProvider.ts | 20 +++++++++--------- src/utils/for-each.ts | 7 ++++--- src/utils/index.ts | 1 + src/utils/keys.ts | 1 + test/specs/utils/utils.spec.ts | 26 +++++++++++++++++++++++- 8 files changed, 52 insertions(+), 25 deletions(-) create mode 100644 src/utils/keys.ts diff --git a/src/shared/Traversable.ts b/src/shared/Traversable.ts index 24953a6a..93305dc3 100644 --- a/src/shared/Traversable.ts +++ b/src/shared/Traversable.ts @@ -1,4 +1,4 @@ -import { forEach, getType } from '../utils' +import { forEach, getType, keys } from '../utils' import { TraverseContext } from '../interface' export class Traversable { @@ -29,7 +29,7 @@ export class Traversable { } else if (target && typeof target.keys === 'function') { return Array.from(target.keys()) } else { - return (typeof target === 'object' && target !== null) || Array.isArray(target) ? Object.keys(target) : [] + return (typeof target === 'object' && target !== null) || Array.isArray(target) ? keys(target) : [] } } } diff --git a/src/storage/Database.ts b/src/storage/Database.ts index 8d150894..b41154ea 100644 --- a/src/storage/Database.ts +++ b/src/storage/Database.ts @@ -10,7 +10,7 @@ import Version from '../version' import { Traversable } from '../shared' import { Mutation, Selector, QueryToken, PredicateProvider, checkPredicate, predicateOperatorNames } from './modules' import { dispose, contextTableName, fieldIdentifier, hiddenColName } from './symbols' -import { forEach, clone, contains, tryCatch, hasOwn, getType, assert, identity, warn } from '../utils' +import { forEach, clone, contains, tryCatch, hasOwn, getType, assert, identity, warn, keys as objKeys } from '../utils' import { createPredicate, createPkClause, mergeTransactionResult, predicatableQuery, lfFactory } from './helper' import { Relationship, RDBType, DataStoreType, LeafType, StatementType, JoinMode } from '../interface/enum' import { Record, Field, JoinInfo, Query, Predicate } from '../interface' @@ -53,7 +53,7 @@ export class Database { const advanced = !this.schemaDefs.has(tableName) && !this.connected assert(advanced, Exception.UnmodifiableTable()) - const hasPK = Object.keys(schema) + const hasPK = objKeys(schema) .some((key: string) => schema[key].primaryKey === true) assert(hasPK, Exception.PrimaryKeyNotProvided()) @@ -876,7 +876,7 @@ export class Database { } }) - return typeof result === 'object' && Object.keys(result).length ? result : null + return typeof result === 'object' && objKeys(result).length ? result : null } private mergeFields(fields: Field[], predicateFields: Field[]) { @@ -932,7 +932,7 @@ export class Database { }) return acc }, {}) - if (Object.keys(nestedField).length) { + if (objKeys(nestedField).length) { dist.push(nestedField) } return dist diff --git a/src/storage/modules/Mutation.ts b/src/storage/modules/Mutation.ts index 8a3a1d21..d1487563 100644 --- a/src/storage/modules/Mutation.ts +++ b/src/storage/modules/Mutation.ts @@ -1,4 +1,4 @@ -import { forEach, assert, warn } from '../../utils' +import { forEach, assert, warn, keys } from '../../utils' import { fieldIdentifier } from '../symbols' import * as Exception from '../../exception' @@ -21,7 +21,7 @@ export class Mutation { } static aggregate(db: lf.Database, insert: Mutation[], update: Mutation[]) { - const keys: any[] = [] + const ks: any[] = [] const insertQueries: lf.query.Insert[] = [] const map = new Map() @@ -31,7 +31,7 @@ export class Mutation { const tableName = table.getName() const acc = map.get(tableName) - keys.push(fieldIdentifier(tableName, curr.refId())) + ks.push(fieldIdentifier(tableName, curr.refId())) if (acc) { acc.push(row) @@ -50,13 +50,13 @@ export class Mutation { const updateQueries: lf.query.Update[] = [] for (let i = 0; i < update.length; i++) { - if (Object.keys(update[i].params).length > 0) { + if (keys(update[i].params).length > 0) { updateQueries.push(update[i].toUpdater()) } } return { - contextIds: keys, + contextIds: ks, queries: insertQueries.concat(updateQueries as any[]) } } diff --git a/src/storage/modules/PredicateProvider.ts b/src/storage/modules/PredicateProvider.ts index edc072be..bd8c5b20 100644 --- a/src/storage/modules/PredicateProvider.ts +++ b/src/storage/modules/PredicateProvider.ts @@ -1,5 +1,5 @@ import * as lf from 'lovefield' -import { forEach, warn, concat } from '../../utils' +import { forEach, warn, concat, keys } from '../../utils' import { ValueLiteral, VaildEqType, Predicate, PredicateMeta, TablesStruct } from '../../interface' const predicateFactory = { @@ -68,7 +68,7 @@ const compoundPredicateFactory = { } export const predicateOperatorNames = - new Set(concat(Object.keys(predicateFactory), Object.keys(compoundPredicateFactory))) + new Set(concat(keys(predicateFactory), keys(compoundPredicateFactory))) export class PredicateProvider { @@ -125,14 +125,14 @@ export class PredicateProvider { if (parentKey && !this.checkMethod(key)) { key = `${ parentKey }.${ key }` } - const keys: string[] = key.split('.') + const ks: string[] = key.split('.') let _column: lf.schema.Column if (!column) { - if (keys.length === 1) { + if (ks.length === 1) { _column = table[key] } else { - const columnKey = keys.pop()! - const tableName = this.getAliasTableName(keys) + const columnKey = ks.pop()! + const tableName = this.getAliasTableName(ks) _column = this.tables[tableName].table[columnKey] } } else { @@ -152,15 +152,15 @@ export class PredicateProvider { return predicates } - private getAliasTableName(keys: string[]) { - let { length } = keys + private getAliasTableName(ks: string[]) { + let { length } = ks let ctxName = this.tableName let resultKey = this.tableName while (length > 0) { - const localKey = keys.shift()! + const localKey = ks.shift()! resultKey = `${ ctxName }@${ localKey }` ctxName = this.tables[resultKey].contextName! - length = keys.length + length = ks.length } return resultKey } diff --git a/src/utils/for-each.ts b/src/utils/for-each.ts index 6774f9f9..6cc77ce4 100644 --- a/src/utils/for-each.ts +++ b/src/utils/for-each.ts @@ -1,4 +1,5 @@ import { noop } from './noop' +import { keys } from './keys' export function forEach (target: Array, eachFunc: (val: T, key: number) => void, inverse?: boolean): void @@ -40,12 +41,12 @@ export function forEach (target: any, eachFunc: (val: any, key: any) => any, inv } } else if (typeof target === 'object') { - const keys = Object.keys(target) + const ks = keys(target) let key: string - length = keys.length + length = ks.length let i = -1 while (++i < length) { - key = keys[i] + key = ks[i] if (eachFunc(target[key], key) === false) { break } diff --git a/src/utils/index.ts b/src/utils/index.ts index cdf57df9..e6cf222d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -10,4 +10,5 @@ export * from './identity' export * from './get-type' export * from './try-catch' export * from './concat' +export * from './keys' export { warn } from './warn' diff --git a/src/utils/keys.ts b/src/utils/keys.ts new file mode 100644 index 00000000..43f018b3 --- /dev/null +++ b/src/utils/keys.ts @@ -0,0 +1 @@ +export const keys = (target: any) => Object.keys(target) diff --git a/test/specs/utils/utils.spec.ts b/test/specs/utils/utils.spec.ts index 68a6bd5d..286615c3 100644 --- a/test/specs/utils/utils.spec.ts +++ b/test/specs/utils/utils.spec.ts @@ -1,4 +1,4 @@ -import { forEach, clone, getType, assert, hash, concat } from '../../index' +import { forEach, clone, getType, assert, hash, concat, keys } from '../../index' import { describe, it } from 'tman' import { expect } from 'chai' @@ -431,4 +431,28 @@ export default describe('Utils Testcase: ', () => { }) }) + describe('Func: keys', () => { + it('should return object keys', () => { + const target = { + foo: 1, + bar: 2 + } + + expect(keys(target)).to.deep.equal(Object.keys(target)) + }) + + it('should not get property keys in prototype', () => { + function a (this: any) { + this.foo = 1 + this.bar = 2 + } + + a.prototype.whatever = 3 + + const target: any = new (a as any) + expect(keys(target).length).to.equal(2) + expect(keys(target).indexOf('wahtever')).to.equal(-1) + }) + }) + }) From 1355358f046bab67ed468150ac837ce287c9181d Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 31 Aug 2017 14:19:39 +0800 Subject: [PATCH 10/19] fix: workaround for yarn bug: 4290 --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index e86fe155..b54f2336 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "copy_src_es": "shx mkdir -p ./dist/es/src && shx cp -r ./src/* ./dist/es/src", "cover": "rm -rf ./.nyc_output ./coverage && NODE_ENV=test nyc --reporter=html --reporter=lcov --exclude=node_modules --exclude=spec-js/test --exclude=spec-js/src/storage/lovefield.js --exclude=spec-js/src/shared/Logger.js --exclude=spec-js/src/utils/option.js --exclude=spec-js/src/utils/valid.js tman --mocha spec-js/test/run.js && nyc report", "lint": "tslint -c tslint.json src/*.ts --project ./tsconfig.json --type-check src/**/*.ts ./test/**/*.ts -e ./test/e2e/*.ts", + "postinstall": "cd ./node_modules/.bin && shx ln -sf ../typescript/bin/tsc ./tsc", "publish_all": "ts-node ./tools/publish.ts", "start": "webpack-dev-server --inline --colors --progress --port 3000", "start-demo": "webpack-dev-server --config ./example/webpack.config.js --inline --colors --progress --port 3001 --open", From 31fd983b76c4d6f6348d7cde31d645ef7fb0e0f5 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Sat, 19 Aug 2017 23:43:59 +0800 Subject: [PATCH 11/19] fix(association-query): fix for reviews --- src/storage/Database.ts | 32 +++++++++++------------- src/storage/modules/PredicateProvider.ts | 19 +++++++------- src/storage/symbols/field-identifier.ts | 4 +-- src/storage/symbols/index.ts | 1 + src/storage/symbols/relate-identifier.ts | 5 ++++ 5 files changed, 32 insertions(+), 29 deletions(-) create mode 100644 src/storage/symbols/relate-identifier.ts diff --git a/src/storage/Database.ts b/src/storage/Database.ts index b41154ea..26003d9b 100644 --- a/src/storage/Database.ts +++ b/src/storage/Database.ts @@ -3,7 +3,6 @@ import { ErrorObservable } from 'rxjs/observable/ErrorObservable' import { Subscription } from 'rxjs/Subscription' import { ConnectableObservable } from 'rxjs/observable/ConnectableObservable' import * as lf from 'lovefield' -import { __assign as assign } from 'tslib' import * as Exception from '../exception' import * as typeDefinition from './helper/definition' import Version from '../version' @@ -534,7 +533,7 @@ export class Database { const columns: lf.schema.Column[] = [] const joinInfo: JoinInfo[] = [] - if (mode === JoinMode.imlicit && contains(tableName, path)) { // thinking mode: implicit & explicit + if (mode === JoinMode.imlicit && contains(tableName, path)) { return { columns, joinInfo, advanced: false, table: null, definition: null, contextName: tableName } } else { path.push(tableName) @@ -564,7 +563,7 @@ export class Database { const [ originTable ] = Database.getTables(db, tableName) const currentTable = originTable.as(contextName) - assign(tablesStruct, this.buildTablesStructure(currentTable, contextName)) + this.buildTablesStructure(currentTable, contextName, tablesStruct) const handleAdvanced = (ret: any, key: string, defs: Association | ColumnDef) => { if (!ret.advanced) { @@ -578,7 +577,7 @@ export class Database { } else { const { where, type } = defs as Association rootDefinition[key] = typeDefinition.revise(type!, ret.definition) - tablesStruct[`${ contextName }@${ key }`] = { + tablesStruct[fieldIdentifier(contextName, key)] = { table: ret.table, contextName: ret.contextName } @@ -811,7 +810,7 @@ export class Database { for (const [ key, val ] of schema.associations) { const relatedName = val.name if (!tablesStructure.has(relatedName)) { - const path = `${ relatedTo }@${ key }` + const path = fieldIdentifier(relatedTo, key) tablesStructure.set(relatedName, path) schemas.push({ schema: this.findSchema(relatedName), @@ -828,7 +827,7 @@ export class Database { const tableName = structure.table.getName() tables.delete(tableName) }) - tables.forEach((key, tableName) => { + forEach(tables, (key, tableName) => { const [ table ] = Database.getTables(db, tableName) tablesStruct[key] = { table, contextName: tableName } }) @@ -842,8 +841,8 @@ export class Database { const buildJoinInfo = (keys: string[]) => { return keys.reduce((acc, k, currentIndex) => { if (currentIndex < keys.length - 1) { - const _newTableName = schema.associations.get(k)!.name - schema = this.findSchema(_newTableName) + const associatedTable = schema.associations.get(k)!.name + schema = this.findSchema(associatedTable) const pk = schema.pk const f = currentIndex !== keys.length - 2 ? Object.create(null) : keys[currentIndex + 1] acc[k] = [pk] @@ -859,7 +858,7 @@ export class Database { if (!predicateOperatorNames.has(key)) { if (checkPredicate(val)) { const keys = key.split('.') - const newTableName = this.getTablenameFromNestedPredicate(key, tableName) + const newTableName = this.getTableNameFromNestedPredicate(key, tableName) if (keys.length > 1) { buildJoinInfo(keys) } else { @@ -938,15 +937,12 @@ export class Database { return dist } - private getTablenameFromNestedPredicate(def: string, tableName: string) { - const defs = def.split('.') - let newTableName: string - let schema = this.findSchema(tableName) - forEach(defs, de => { - newTableName = schema.associations.get(de)!.name - schema = this.findSchema(newTableName) - }) - return newTableName! + private getTableNameFromNestedPredicate(defs: string, tableName: string) { + const defsArr = defs.split('.') + return defsArr.reduce((prev, def) => { + const assocaiatedTable = prev.schema.associations.get(def)!.name + return { schema: this.findSchema(assocaiatedTable), tableName: assocaiatedTable } + }, { schema: this.findSchema(tableName), tableName: tableName }).tableName } private executor(db: lf.Database, queries: lf.query.Builder[]) { diff --git a/src/storage/modules/PredicateProvider.ts b/src/storage/modules/PredicateProvider.ts index bd8c5b20..231cad9d 100644 --- a/src/storage/modules/PredicateProvider.ts +++ b/src/storage/modules/PredicateProvider.ts @@ -1,4 +1,5 @@ import * as lf from 'lovefield' +import { relateIdentifier, fieldIdentifier } from '../symbols' import { forEach, warn, concat, keys } from '../../utils' import { ValueLiteral, VaildEqType, Predicate, PredicateMeta, TablesStruct } from '../../interface' @@ -123,25 +124,25 @@ export class PredicateProvider { resultPred = compoundPredicateFactory['$and'](nestedPreds) } else { if (parentKey && !this.checkMethod(key)) { - key = `${ parentKey }.${ key }` + key = relateIdentifier(parentKey, key) } const ks: string[] = key.split('.') - let _column: lf.schema.Column + let targetCol: lf.schema.Column if (!column) { if (ks.length === 1) { - _column = table[key] + targetCol = table[key] } else { const columnKey = ks.pop()! const tableName = this.getAliasTableName(ks) - _column = this.tables[tableName].table[columnKey] + targetCol = this.tables[tableName].table[columnKey] } } else { - _column = column + targetCol = column } - if (_column) { - resultPred = buildSinglePred(_column, val, key) + if (targetCol) { + resultPred = buildSinglePred(targetCol, val, key) } else { - warn(`Failed to build predicate, since column: ${key} is not exist, on table: ${table.getName()}`) + warn(`Failed to build predicate, since column: ${key} is not existed, on table: ${table.getName()}`) return } } @@ -158,7 +159,7 @@ export class PredicateProvider { let resultKey = this.tableName while (length > 0) { const localKey = ks.shift()! - resultKey = `${ ctxName }@${ localKey }` + resultKey = fieldIdentifier(ctxName, localKey) ctxName = this.tables[resultKey].contextName! length = ks.length } diff --git a/src/storage/symbols/field-identifier.ts b/src/storage/symbols/field-identifier.ts index fd165240..6418347c 100644 --- a/src/storage/symbols/field-identifier.ts +++ b/src/storage/symbols/field-identifier.ts @@ -1,5 +1,5 @@ -export const link = '@' +export const fieldLink = '@' export function fieldIdentifier(tableName: string, val: string) { - return `${tableName}${link}${val}` + return `${tableName}${fieldLink}${val}` } diff --git a/src/storage/symbols/index.ts b/src/storage/symbols/index.ts index bfa1af75..4fee1a5c 100644 --- a/src/storage/symbols/index.ts +++ b/src/storage/symbols/index.ts @@ -2,3 +2,4 @@ export * from './dispose' export * from './context-table' export * from './hidden-columns' export * from './field-identifier' +export * from './relate-identifier' diff --git a/src/storage/symbols/relate-identifier.ts b/src/storage/symbols/relate-identifier.ts new file mode 100644 index 00000000..e2163e67 --- /dev/null +++ b/src/storage/symbols/relate-identifier.ts @@ -0,0 +1,5 @@ +export const relatedLink = '.' + +export function relateIdentifier(tableName: string, relatedName: string) { + return `${tableName}${relatedLink}${relatedName}` +} From 62c62529b7bf31b11e6238a8567c4f64a1c7d329 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 31 Aug 2017 18:05:17 +0800 Subject: [PATCH 12/19] feat(Database#get): restrict Associated Field position in Fields --- src/exception/database.ts | 3 +++ src/storage/Database.ts | 18 ++++++++++++++++++ test/specs/storage/Database.public.spec.ts | 7 ++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/exception/database.ts b/src/exception/database.ts index b9f7b080..99e9647b 100644 --- a/src/exception/database.ts +++ b/src/exception/database.ts @@ -41,3 +41,6 @@ export const DatabaseIsNotEmpty = export const NotConnected = () => new ReactiveDBException('Method: dispose cannnot be invoked before database is connected.') + +export const AssociatedFieldsPostionError = + () => new ReactiveDBException(`Associated fields description must be the last item in Fields`) diff --git a/src/storage/Database.ts b/src/storage/Database.ts index 26003d9b..f1583050 100644 --- a/src/storage/Database.ts +++ b/src/storage/Database.ts @@ -155,6 +155,8 @@ export class Database { } get(tableName: string, query: Query = {}, mode: JoinMode = JoinMode.imlicit): QueryToken { + const checkResult = this.checkAssociateFields(query.fields) + assert(checkResult, Exception.AssociatedFieldsPostionError()) const selector$ = this.buildSelector(tableName, query, mode) return new QueryToken(selector$) } @@ -945,6 +947,22 @@ export class Database { }, { schema: this.findSchema(tableName), tableName: tableName }).tableName } + private checkAssociateFields(fields?: Fields[]) { + let result = true + if (fields) { + forEach(fields, (field, index): boolean | void => { + const isObject = typeof field === 'object' + if (isObject) { + if (index !== fields.length - 1) { + result = false + return false + } + } + }) + } + return result + } + private executor(db: lf.Database, queries: lf.query.Builder[]) { const tx = db.createTransaction() const handler = { diff --git a/test/specs/storage/Database.public.spec.ts b/test/specs/storage/Database.public.spec.ts index 24e290ba..aac7ff5b 100644 --- a/test/specs/storage/Database.public.spec.ts +++ b/test/specs/storage/Database.public.spec.ts @@ -11,7 +11,7 @@ import { TestFixture2 } from '../../schemas/Test' import { scenarioGen, programGen, postGen, taskGen, subtaskGen } from '../../utils/generators' import { RDBType, DataStoreType, Database, clone, forEach, JoinMode, Logger } from '../../index' import { TaskSchema, ProjectSchema, PostSchema, ModuleSchema, ProgramSchema, SubtaskSchema, OrganizationSchema } from '../../index' -import { InvalidQuery, NonExistentTable, InvalidType, PrimaryKeyNotProvided, NotConnected, Selector } from '../../index' +import { InvalidQuery, NonExistentTable, InvalidType, PrimaryKeyNotProvided, NotConnected, Selector, AssociatedFieldsPostionError } from '../../index' use(SinonChai) @@ -448,6 +448,11 @@ export default describe('Database Testcase: ', () => { expect(sqlA).to.deep.equal(sqlB) }) + it('should throw when associated fields in a wrong position', () => { + const fun = () => database.get('Task', { fields: ['_id', { project: ['_id'] }, 'content'] }) + expect(fun).to.throw(AssociatedFieldsPostionError().message) + }) + describe('case: Associations', () => { let associationFixture: TaskSchema[] = [] From 46b3fa0bf92843a35a851a8fe7d3ddce7cfe8014 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 31 Aug 2017 18:06:17 +0800 Subject: [PATCH 13/19] refactor(Database): introduce merge-fields in utils, use merge fields to merge query.fields & fields built from predicate --- src/exception/database.ts | 3 + src/storage/Database.ts | 120 ++++++++------------- src/utils/index.ts | 1 + src/utils/merge-fields.ts | 45 ++++++++ test/specs/storage/Database.public.spec.ts | 28 ++++- test/specs/utils/utils.spec.ts | 114 +++++++++++++++++++- 6 files changed, 230 insertions(+), 81 deletions(-) create mode 100644 src/utils/merge-fields.ts diff --git a/src/exception/database.ts b/src/exception/database.ts index 99e9647b..cdba490c 100644 --- a/src/exception/database.ts +++ b/src/exception/database.ts @@ -42,5 +42,8 @@ export const DatabaseIsNotEmpty = export const NotConnected = () => new ReactiveDBException('Method: dispose cannnot be invoked before database is connected.') +export const FieldMustBeArray = + (field: any) => new ReactiveDBException(`Field must be Array, but got: ${ JSON.stringify(field) }`) + export const AssociatedFieldsPostionError = () => new ReactiveDBException(`Associated fields description must be the last item in Fields`) diff --git a/src/storage/Database.ts b/src/storage/Database.ts index f1583050..9b9bc417 100644 --- a/src/storage/Database.ts +++ b/src/storage/Database.ts @@ -9,7 +9,7 @@ import Version from '../version' import { Traversable } from '../shared' import { Mutation, Selector, QueryToken, PredicateProvider, checkPredicate, predicateOperatorNames } from './modules' import { dispose, contextTableName, fieldIdentifier, hiddenColName } from './symbols' -import { forEach, clone, contains, tryCatch, hasOwn, getType, assert, identity, warn, keys as objKeys } from '../utils' +import { forEach, clone, contains, tryCatch, hasOwn, getType, assert, identity, warn, keys as objKeys, mergeFields } from '../utils' import { createPredicate, createPkClause, mergeTransactionResult, predicatableQuery, lfFactory } from './helper' import { Relationship, RDBType, DataStoreType, LeafType, StatementType, JoinMode } from '../interface/enum' import { Record, Field, JoinInfo, Query, Predicate } from '../interface' @@ -433,11 +433,19 @@ export class Database { const containFields = !!clause.fields const containKey = containFields ? contains(pk, clause.fields!) : true - const [ additionJoinInfo ] = tryCatch(this.buildJoinFieldsFromPredicate.bind(this))(clause.where!, tableName) + const [ additionJoinInfo, err ] = clause.where ? tryCatch(this.buildJoinFieldsFromPredicate)(clause.where, tableName) : [ null, null ] + + if (err) { + warn('Build addition join info from predicate failed', err.message) + } const fields = containFields ? clause.fields : Array.from(schema.columns.keys()) - const newFields = containFields && additionJoinInfo ? this.mergeFields(fields!, [ additionJoinInfo as Field ]) : fields - const fieldsSet: Set = new Set(newFields) + + if (containFields && additionJoinInfo) { + mergeFields(fields!, [ additionJoinInfo as Field ]) + } + + const fieldsSet: Set = new Set(fields) const tablesStruct: TablesStruct = Object.create(null) const { table, columns, joinInfo, definition, contextName } = @@ -836,24 +844,30 @@ export class Database { return tablesStruct } - private buildJoinFieldsFromPredicate(predicate: Predicate, tableName: string) { + private buildJoinFieldsFromPredicate = (predicate: Predicate, tableName: string) => { let result = Object.create(null) let schema = this.findSchema(tableName) const buildJoinInfo = (keys: string[]) => { return keys.reduce((acc, k, currentIndex) => { - if (currentIndex < keys.length - 1) { - const associatedTable = schema.associations.get(k)!.name - schema = this.findSchema(associatedTable) - const pk = schema.pk - const f = currentIndex !== keys.length - 2 ? Object.create(null) : keys[currentIndex + 1] - acc[k] = [pk] - if (f !== pk) { - acc[k].push(f) - } - return f + if (currentIndex === keys.length - 1) { + return acc + } + const fieldObject = acc.result + const associatedTable = schema.associations.get(k)!.name + schema = this.findSchema(associatedTable) + const pk = schema.pk + const nextField = keys[currentIndex + 1] + const f = currentIndex !== keys.length - 2 || schema.associations.get(nextField) ? Object.create(null) : nextField + const fields = fieldObject[k] = [pk] + if (f !== pk) { + fields.push(f) + } + if (typeof f === 'string') { + return { key: f, result: fieldObject } } - }, result) + return { result: f, key: nextField } + }, { result, key: tableName }) } forEach(predicate, (val, key) => { @@ -862,9 +876,18 @@ export class Database { const keys = key.split('.') const newTableName = this.getTableNameFromNestedPredicate(key, tableName) if (keys.length > 1) { - buildJoinInfo(keys) + const joinInfo = buildJoinInfo(keys) + const fields = this.buildJoinFieldsFromPredicate(val, newTableName) + const results = joinInfo.result[joinInfo.key] = [schema.pk] + if (fields) { + results.push(fields) + } } else { - result[key] = [schema.pk, this.buildJoinFieldsFromPredicate(val, newTableName)] + const joinFields = this.buildJoinFieldsFromPredicate(val, newTableName) + const fields = result[key] = [schema.pk] + if (joinFields) { + fields.push(joinFields) + } } } else { const keys = key.split('.') @@ -880,65 +903,6 @@ export class Database { return typeof result === 'object' && objKeys(result).length ? result : null } - private mergeFields(fields: Field[], predicateFields: Field[]) { - const newFields = this.buildFieldsTree(fields) - predicateFields = this.buildFieldsTree(predicateFields) - - const nestedFields = newFields[newFields.length - 1] - const nestedPredicateFields = predicateFields[predicateFields.length - 1] - forEach(predicateFields, val => { - if (typeof val === 'string') { - if (newFields.indexOf(val) === -1) { - newFields.push(val) - } - } - }) - - if (typeof nestedPredicateFields === 'object') { - if (typeof nestedFields === 'object') { - forEach(nestedPredicateFields, (val, key) => { - const existedVal = nestedFields[key] - if (existedVal) { - this.mergeFields(existedVal, val) - } else { - nestedFields[key] = val - } - }) - } else { - newFields.push(nestedPredicateFields) - } - } - return newFields - } - - private buildFieldsTree(fields: Field[]) { - const dist: Field[] = [] - const nestedFields: { [index: string]: Field[] }[] = [] - forEach(fields, field => { - if (typeof field === 'string') { - dist.push(field) - } else { - nestedFields.push(field) - } - }) - - const nestedField = nestedFields.reduce((acc, field) => { - forEach(field, (val, key) => { - const existedVal = acc[key] - if (existedVal) { - acc[key] = this.mergeFields(existedVal, val) - } else { - acc[key] = val - } - }) - return acc - }, {}) - if (objKeys(nestedField).length) { - dist.push(nestedField) - } - return dist - } - private getTableNameFromNestedPredicate(defs: string, tableName: string) { const defsArr = defs.split('.') return defsArr.reduce((prev, def) => { @@ -947,7 +911,7 @@ export class Database { }, { schema: this.findSchema(tableName), tableName: tableName }).tableName } - private checkAssociateFields(fields?: Fields[]) { + private checkAssociateFields(fields?: Field[]) { let result = true if (fields) { forEach(fields, (field, index): boolean | void => { diff --git a/src/utils/index.ts b/src/utils/index.ts index e6cf222d..4526861f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -11,4 +11,5 @@ export * from './get-type' export * from './try-catch' export * from './concat' export * from './keys' +export * from './merge-fields' export { warn } from './warn' diff --git a/src/utils/merge-fields.ts b/src/utils/merge-fields.ts new file mode 100644 index 00000000..45ecfc19 --- /dev/null +++ b/src/utils/merge-fields.ts @@ -0,0 +1,45 @@ +import { forEach } from './for-each' +import { FieldMustBeArray, AssociatedFieldsPostionError } from '../exception' + +export const mergeFields = (targetFields: any[], patchFields: any[]): void => { + if (!Array.isArray(targetFields)) { + throw FieldMustBeArray(targetFields) + } + if (!Array.isArray(patchFields)) { + throw FieldMustBeArray(patchFields) + } + + let targetLength = targetFields.length + const fieldLength = patchFields.length + forEach(patchFields, (field: any, index: number) => { + if (typeof field === 'object') { + if (index !== fieldLength - 1) { + throw AssociatedFieldsPostionError() + } + const lastTargetField = targetFields[targetLength - 1] + if (typeof lastTargetField === 'object') { + forEach(field, (val: any, key: string) => { + const targetField = lastTargetField[key] + if (targetField) { + mergeFields(targetField, val) + } else { + lastTargetField[key] = val + } + }) + } else { + targetFields.push(field) + targetLength = targetFields.length + } + } else { + const lastField = targetFields[targetLength - 1] + if (targetFields.indexOf(field) === -1) { + if (typeof lastField === 'object') { + targetFields.splice(targetLength - 1, 0, field) + } else { + targetFields.push(field) + } + targetLength = targetFields.length + } + } + }) +} diff --git a/test/specs/storage/Database.public.spec.ts b/test/specs/storage/Database.public.spec.ts index aac7ff5b..c2745282 100644 --- a/test/specs/storage/Database.public.spec.ts +++ b/test/specs/storage/Database.public.spec.ts @@ -546,8 +546,10 @@ export default describe('Database Testcase: ', () => { }) const results = yield queryToken.values() + const [ result ] = results expect(results.length).to.equal(1) + expect(result.project.organization._id).to.equal(innerTarget.project._organizationId) }) it('should get value by deep nested Association query without association fields', function* () { @@ -562,12 +564,13 @@ export default describe('Database Testcase: ', () => { }) const results = yield queryToken.values() - + const [ result ] = results expect(results.length).to.equal(1) + expect(result.project.organization._id).to.equal(innerTarget.project._organizationId) }) it('should merge fields when get value by deep nested Association query with nested association fields', function* () { - const fields = ['_id', 'content', { project: ['_id'] }, { project: [ { organization: [ '_id' ] } ] }] + const fields = ['_id', 'content', { project: [ { organization: [ '_id' ] } ] }] const queryToken = database.get('Task', { fields, where: { @@ -578,8 +581,10 @@ export default describe('Database Testcase: ', () => { }) const results = yield queryToken.values() + const [ result ] = results expect(results.length).to.equal(1) + expect(result.project.organization._id).to.equal(innerTarget.project._organizationId) }) it('should merge fields when get value by deep nested Association query without nested association fields', function* () { @@ -594,8 +599,27 @@ export default describe('Database Testcase: ', () => { }) const results = yield queryToken.values() + const [ result ] = results expect(results.length).to.equal(1) + expect(result.project.organization._id).to.equal(innerTarget.project._organizationId) + }) + + it('should warn if build additional join info from predicate failed', function* () { + const fields = ['_id', 'content'] + const queryToken = database.get('Task', { + fields, + where: { 'project._organization._id': innerTarget.project._organizationId } + }) + + const spy = sinon.spy(Logger, 'warn') + + yield queryToken.values() + + // warning in PredicateProvider and Database + expect(spy.callCount).to.equal(2) + + spy.restore() }) it('should apply `skip` clause on multi joined query', function* () { diff --git a/test/specs/utils/utils.spec.ts b/test/specs/utils/utils.spec.ts index 286615c3..2bda672c 100644 --- a/test/specs/utils/utils.spec.ts +++ b/test/specs/utils/utils.spec.ts @@ -1,4 +1,4 @@ -import { forEach, clone, getType, assert, hash, concat, keys } from '../../index' +import { forEach, clone, getType, assert, hash, concat, keys, mergeFields, FieldMustBeArray, AssociatedFieldsPostionError } from '../../index' import { describe, it } from 'tman' import { expect } from 'chai' @@ -455,4 +455,116 @@ export default describe('Utils Testcase: ', () => { }) }) + describe('Func: mergeFields', () => { + it('should merge string fields', () => { + const target = ['1', '2', '3'] + const patch = ['4', '2', '5'] + mergeFields(target, patch) + expect(target).to.deep.equal([ + '1', '2', '3', '4', '5' + ]) + }) + + it('should merge fields with object field', () => { + const extenalField = { + bar: ['7', '8', '9'] + } + + const target = ['1', '2', '3'] + const patch = ['4', '2', '5', extenalField] + mergeFields(target, patch) + expect(target).to.deep.equal([ + '1', '2', '3', '4', '5', extenalField + ]) + }) + + it('should merge extenal fields', () => { + const extenalField = { + bar: ['7', '8', '9'] + } + + const target = ['1', '2', '3', extenalField] + const patch = ['4', '2', '5', { bar: ['10', '9', '12'] }] + mergeFields(target, patch) + expect(target).to.deep.equal([ + '1', '2', '3', '4', '5', extenalField + ]) + + expect(extenalField.bar).to.deep.equal([ + '7', '8', '9', '10', '12' + ]) + }) + + it('should deep merge all fields', () => { + const target = ['id1', 'id2', { + foo: ['1', '2', '3'], + bar: ['4', '5', '6', { + task: ['_id', 'content', { + project: ['_id', 'name', { + organization: ['_id', 'name'] + }] + }] + }] + }] + + const patch = ['id3', 'id1', { + foo1: ['1', '2', '3'], + bar: ['8', '12', { + task: ['_id', 'content', '_tasklistId', { + project: ['_organizationId', { + organization: ['created'] + }], + stage: ['_id', { + tasklist: ['_id'] + }] + }] + }] + }] + + mergeFields(target, patch) + + expect(target).to.deep.equal(['id1', 'id2', 'id3', { + foo: ['1', '2', '3'], + bar: ['4', '5', '6', '8', '12', + { + task: ['_id', 'content', '_tasklistId', + { + project: ['_id', 'name', '_organizationId', + { + organization: ['_id', 'name', 'created'] + } + ], + stage: [ + '_id', + { + tasklist: [ + '_id' + ] + } + ] + } + ] + } + ], + foo1: ['1', '2', '3'] + }]) + }) + + it('should throw if field is not a array', () => { + const target = ['1', '2'] + const patch = { 0: '1', 1: '3', length: 2 } + const fun = () => mergeFields(target, patch as any) + const fun1 = () => mergeFields(patch as any, target) + expect(fun).to.throw(FieldMustBeArray(patch).message) + expect(fun1).to.throw(FieldMustBeArray(patch).message) + }) + + it('should throw if associated fields not in right position', () => { + const target = ['1', '2'] + const patch = ['3', '4', { bar: ['6'] }, { baz: ['9'] }] + const fun = () => mergeFields(target, patch) + expect(fun).to.throw(AssociatedFieldsPostionError().message) + }) + }) + }) From 4509849eba048a8a32ee9ab5d45f91e911c04345 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Fri, 1 Sep 2017 11:41:17 +0800 Subject: [PATCH 14/19] test(TaskSchema): add tasklist schema, add more nested predicate case --- test/schemas/Task.ts | 12 +++++++- test/schemas/Tasklist.ts | 31 +++++++++++++++++++++ test/schemas/index.ts | 3 ++ test/specs/storage/Database.public.spec.ts | 17 ++++++++---- test/utils/generators/task-generator.ts | 32 ++++++++++++++-------- 5 files changed, 76 insertions(+), 19 deletions(-) create mode 100644 test/schemas/Tasklist.ts diff --git a/test/schemas/Task.ts b/test/schemas/Task.ts index 20681079..36fa87d6 100644 --- a/test/schemas/Task.ts +++ b/test/schemas/Task.ts @@ -1,5 +1,5 @@ import { RDBType, Relationship } from '../index' -import { TeambitionTypes, Database, SubtaskSchema, ProjectSchema } from '../index' +import { TeambitionTypes, Database, SubtaskSchema, ProjectSchema, TasklistSchema } from '../index' export interface TaskSchema { _id: TeambitionTypes.TaskId @@ -13,6 +13,7 @@ export interface TaskSchema { _tasklistId: TeambitionTypes.TasklistId accomplished: string project?: ProjectSchema + tasklist: TasklistSchema subtasks: SubtaskSchema[] subtasksCount: number created: string, @@ -64,6 +65,15 @@ export default (db: Database) => { } } }, + tasklist: { + type: Relationship.oneToOne, + virtual: { + name: 'Tasklist', + where: ref => ({ + _tasklistId: ref._id + }) + } + }, subtasks: { type: Relationship.oneToMany, virtual: { diff --git a/test/schemas/Tasklist.ts b/test/schemas/Tasklist.ts new file mode 100644 index 00000000..82b55584 --- /dev/null +++ b/test/schemas/Tasklist.ts @@ -0,0 +1,31 @@ +import { Database, RDBType, Relationship } from '../index' +import { ProjectSchema } from './index' + +export interface TasklistSchema { + _id: string + _projectId: string + name: string + project: ProjectSchema +} + +export default (db: Database) => db.defineSchema('Tasklist', { + _id: { + type: RDBType.STRING, + primaryKey: true + }, + _projectId: { + type: RDBType.STRING + }, + name: { + type: RDBType.STRING + }, + project: { + type: Relationship.oneToOne, + virtual: { + name: 'Project', + where: projectTable => ({ + _projectId: projectTable._id + }) + } + } +}) diff --git a/test/schemas/index.ts b/test/schemas/index.ts index b0ac5613..0dd4c7a6 100644 --- a/test/schemas/index.ts +++ b/test/schemas/index.ts @@ -3,6 +3,7 @@ import { Database } from '../index' import ProjectSelectMetadata from './Project' import SubtaskSelectMetadata from './Subtask' import TaskSelectMetadata from './Task' +import TasklistSelectMetadata from './Tasklist' import PostSelectMetadata from './Post' import OrganizationSelectMetadata from './Organization' import EngineerSelectMetadata from './Engineer' @@ -18,6 +19,7 @@ export default (db: Database) => { ModuleSelectMetadata(db) ProgramSelectMetadata(db) OrganizationSelectMetadata(db) + TasklistSelectMetadata(db) } export { ProjectSchema } from './Project' @@ -25,6 +27,7 @@ export { PostSchema } from './Post' export { OrganizationSchema } from './Organization' export { SubtaskSchema } from './Subtask' export { TaskSchema } from './Task' +export { TasklistSchema } from './Tasklist' export { ProgramSchema } from './Program' export { ModuleSchema } from './Module' export { EngineerSchema } from './Engineer' diff --git a/test/specs/storage/Database.public.spec.ts b/test/specs/storage/Database.public.spec.ts index c2745282..72426d94 100644 --- a/test/specs/storage/Database.public.spec.ts +++ b/test/specs/storage/Database.public.spec.ts @@ -10,7 +10,7 @@ import schemaFactory from '../../schemas' import { TestFixture2 } from '../../schemas/Test' import { scenarioGen, programGen, postGen, taskGen, subtaskGen } from '../../utils/generators' import { RDBType, DataStoreType, Database, clone, forEach, JoinMode, Logger } from '../../index' -import { TaskSchema, ProjectSchema, PostSchema, ModuleSchema, ProgramSchema, SubtaskSchema, OrganizationSchema } from '../../index' +import { TaskSchema, ProjectSchema, PostSchema, ModuleSchema, ProgramSchema, SubtaskSchema, OrganizationSchema, TasklistSchema } from '../../index' import { InvalidQuery, NonExistentTable, InvalidType, PrimaryKeyNotProvided, NotConnected, Selector, AssociatedFieldsPostionError } from '../../index' use(SinonChai) @@ -26,6 +26,7 @@ export default describe('Database Testcase: ', () => { tasks.forEach(t => { delete t.project delete t.subtasks + delete t.tasklist }) const refreshDB = () => { @@ -470,6 +471,7 @@ export default describe('Database Testcase: ', () => { const posts: PostSchema[] = [] const tasks: TaskSchema[] = [] const organizations: OrganizationSchema[] = [] + const tasklists: TasklistSchema[] = [] associationFixture.forEach(f => { forEach(f, (value, key) => { @@ -483,6 +485,8 @@ export default describe('Database Testcase: ', () => { organizations.push(value.organization) } projects.push(value) + } else if (key === 'tasklist') { + tasklists.push(value) } }) tasks.push(f) @@ -493,7 +497,8 @@ export default describe('Database Testcase: ', () => { database.insert('Subtask', subtasks), database.insert('Project', projects), database.insert('Post', posts), - database.insert('Organization', organizations) + database.insert('Organization', organizations), + database.insert('Tasklist', tasklists) ] Observable.forkJoin(...queries).subscribe(() => { @@ -569,13 +574,13 @@ export default describe('Database Testcase: ', () => { expect(result.project.organization._id).to.equal(innerTarget.project._organizationId) }) - it('should merge fields when get value by deep nested Association query with nested association fields', function* () { - const fields = ['_id', 'content', { project: [ { organization: [ '_id' ] } ] }] + it('should merge fields if a nested association in the WHERE clause', function* () { + const fields = ['_id', 'content', { project: [ '_id', { organization: [ '_id' ] } ] }] const queryToken = database.get('Task', { fields, where: { - 'project.organization': { - _id: innerTarget.project._organizationId + 'tasklist.project': { + 'organization._id': innerTarget.project._organizationId } } }) diff --git a/test/utils/generators/task-generator.ts b/test/utils/generators/task-generator.ts index a6ce08c0..16adaea7 100644 --- a/test/utils/generators/task-generator.ts +++ b/test/utils/generators/task-generator.ts @@ -15,35 +15,43 @@ export default function (limit: number) { const _stageId = uuid() const _creatorId = uuid() const _organizationId = uuid() + const _tasklistId = uuid() const _executorId = random.rnd(20) ? uuid() : _creatorId const involves = [ _executorId ] if (_creatorId !== _executorId) { involves.push(_creatorId) } const subtasks = subtaskGen(random.number(1, 20), _id) + const project = { + _id: _projectId, + name: 'project name: ' + uuid(), + isArchived: true, + posts: postGen(5, _projectId), + _organizationId: _organizationId, + organization: { + _id: _organizationId, + name: 'organization name: ' + uuid(), + isArchived: false, + } + } result.push({ _id, _projectId, _stageId, _creatorId, _executorId, - _tasklistId: uuid(), + _tasklistId, _sourceId: null, accomplished: null, subtasks, subtasksCount: subtasks.length, content: 'content: ' + uuid(), note: 'note: ' + uuid(), - project: { - _id: _projectId, - name: 'project name: ' + uuid(), - isArchived: true, - posts: postGen(5, _projectId), - _organizationId: _organizationId, - organization: { - _id: _organizationId, - name: 'organization name: ' + uuid(), - isArchived: false, - } + tasklist: { + _id: _tasklistId, + name: 'tasklist name' + uuid(), + _projectId, + project }, + project, involveMembers: involveMembersGen(15, involves), created: moment().add(6 - random.number(0, 12), 'month').add(30 - random.number(0, 30), 'day').toISOString() }) From b93e9d3e157fc7f471f42f32fa8b795bc154e89d Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 6 Sep 2017 10:09:28 +0800 Subject: [PATCH 15/19] refactor(PredicateProvidor): use new parse-predicate helper to parse predicate reduce the logics in Database#buildSelector and PredicateProvider#normalizeMeta --- src/storage/Database.ts | 109 +++---- src/storage/helper/index.ts | 1 + src/storage/helper/parse-predicate.ts | 49 +++ src/storage/modules/PredicateProvider.ts | 65 ++-- src/storage/symbols/index.ts | 1 - src/storage/symbols/relate-identifier.ts | 5 - src/utils/try-catch.ts | 4 +- test/schemas/Organization.ts | 4 + test/specs/index.ts | 1 + test/specs/storage/Database.public.spec.ts | 40 ++- .../storage/helper/parse-predicate.spec.ts | 289 ++++++++++++++++++ test/specs/storage/modules/Selector.spec.ts | 25 +- test/utils/generators/task-generator.ts | 1 + 13 files changed, 482 insertions(+), 112 deletions(-) create mode 100644 src/storage/helper/parse-predicate.ts delete mode 100644 src/storage/symbols/relate-identifier.ts create mode 100644 test/specs/storage/helper/parse-predicate.spec.ts diff --git a/src/storage/Database.ts b/src/storage/Database.ts index 9b9bc417..2a54bcab 100644 --- a/src/storage/Database.ts +++ b/src/storage/Database.ts @@ -7,10 +7,10 @@ import * as Exception from '../exception' import * as typeDefinition from './helper/definition' import Version from '../version' import { Traversable } from '../shared' -import { Mutation, Selector, QueryToken, PredicateProvider, checkPredicate, predicateOperatorNames } from './modules' +import { Mutation, Selector, QueryToken, PredicateProvider, checkPredicate } from './modules' import { dispose, contextTableName, fieldIdentifier, hiddenColName } from './symbols' -import { forEach, clone, contains, tryCatch, hasOwn, getType, assert, identity, warn, keys as objKeys, mergeFields } from '../utils' -import { createPredicate, createPkClause, mergeTransactionResult, predicatableQuery, lfFactory } from './helper' +import { forEach, clone, contains, tryCatch, hasOwn, getType, assert, identity, warn, keys as objKeys, mergeFields, concat } from '../utils' +import { createPredicate, createPkClause, mergeTransactionResult, predicatableQuery, lfFactory, parsePredicate } from './helper' import { Relationship, RDBType, DataStoreType, LeafType, StatementType, JoinMode } from '../interface/enum' import { Record, Field, JoinInfo, Query, Predicate } from '../interface' import { SchemaDef, ColumnDef, ParsedSchema, Association, ScopedHandler } from '../interface' @@ -431,9 +431,14 @@ export class Database { const schema = this.findSchema(tableName) const pk = schema.pk const containFields = !!clause.fields + let predicate: Predicate const containKey = containFields ? contains(pk, clause.fields!) : true - const [ additionJoinInfo, err ] = clause.where ? tryCatch(this.buildJoinFieldsFromPredicate)(clause.where, tableName) : [ null, null ] + const [ additionJoinInfo, err ] = !clause.where ? [ null, null ] : + tryCatch(() => { + predicate = parsePredicate(clause.where!) + return this.buildJoinFieldsFromPredicate(predicate, tableName) + })() if (err) { warn('Build addition join info from predicate failed', err.message) @@ -442,7 +447,7 @@ export class Database { const fields = containFields ? clause.fields : Array.from(schema.columns.keys()) if (containFields && additionJoinInfo) { - mergeFields(fields!, [ additionJoinInfo as Field ]) + mergeFields(fields!, additionJoinInfo) } const fieldsSet: Set = new Set(fields) @@ -454,9 +459,9 @@ export class Database { predicatableQuery(db, table!, null, StatementType.Select, ...columns) joinInfo.forEach((info: JoinInfo) => { - const predicate = info.predicate - if (predicate) { - query.leftOuterJoin(info.table, predicate) + const pred = info.predicate + if (pred) { + query.leftOuterJoin(info.table, pred) } }) @@ -478,8 +483,7 @@ export class Database { mainTable: table! } const { limit, skip } = clause - const provider = new PredicateProvider(tablesStruct, contextName, clause.where) - + const provider = new PredicateProvider(tablesStruct, contextName, predicate!) return new Selector(db, query, matcher, provider, limit, skip, orderDesc) }) } @@ -844,71 +848,44 @@ export class Database { return tablesStruct } - private buildJoinFieldsFromPredicate = (predicate: Predicate, tableName: string) => { - let result = Object.create(null) - let schema = this.findSchema(tableName) - - const buildJoinInfo = (keys: string[]) => { - return keys.reduce((acc, k, currentIndex) => { - if (currentIndex === keys.length - 1) { - return acc - } - const fieldObject = acc.result - const associatedTable = schema.associations.get(k)!.name - schema = this.findSchema(associatedTable) - const pk = schema.pk - const nextField = keys[currentIndex + 1] - const f = currentIndex !== keys.length - 2 || schema.associations.get(nextField) ? Object.create(null) : nextField - const fields = fieldObject[k] = [pk] - if (f !== pk) { - fields.push(f) - } - if (typeof f === 'string') { - return { key: f, result: fieldObject } - } - return { result: f, key: nextField } - }, { result, key: tableName }) - } + private buildJoinFieldsFromPredicate(predicate: Predicate, tableName: string) { + const result = Object.create(null) + const subJoinInfos: any[] = [] + const schema = this.findSchema(tableName) forEach(predicate, (val, key) => { - if (!predicateOperatorNames.has(key)) { - if (checkPredicate(val)) { - const keys = key.split('.') - const newTableName = this.getTableNameFromNestedPredicate(key, tableName) - if (keys.length > 1) { - const joinInfo = buildJoinInfo(keys) - const fields = this.buildJoinFieldsFromPredicate(val, newTableName) - const results = joinInfo.result[joinInfo.key] = [schema.pk] - if (fields) { - results.push(fields) - } - } else { - const joinFields = this.buildJoinFieldsFromPredicate(val, newTableName) - const fields = result[key] = [schema.pk] - if (joinFields) { - fields.push(joinFields) - } - } - } else { - const keys = key.split('.') - if (keys.length > 1) { - buildJoinInfo(keys) - } else { - result = key + if (checkPredicate(val)) { + const newTableName = this.getTableNameFromNestedPredicate(key, tableName) + if (newTableName !== tableName) { + const joinFields = this.buildJoinFieldsFromPredicate(val, newTableName) + const fields = result[key] = [schema.pk] + if (joinFields) { + concat(fields, joinFields) } } + } else if (Array.isArray(val)) { + forEach(val, (subPredicate) => { + const subJoinInfo = this.buildJoinFieldsFromPredicate(subPredicate, tableName) + mergeFields(subJoinInfos, subJoinInfo!) + }) } }) - - return typeof result === 'object' && objKeys(result).length ? result : null + if (typeof result === 'object' && objKeys(result).length) { + return concat([result], subJoinInfos) + } + if (subJoinInfos.length) { + return subJoinInfos + } + return null } private getTableNameFromNestedPredicate(defs: string, tableName: string) { - const defsArr = defs.split('.') - return defsArr.reduce((prev, def) => { - const assocaiatedTable = prev.schema.associations.get(def)!.name - return { schema: this.findSchema(assocaiatedTable), tableName: assocaiatedTable } - }, { schema: this.findSchema(tableName), tableName: tableName }).tableName + const schema = this.findSchema(tableName) + const associatedDef = schema.associations.get(defs) + if (!associatedDef) { + return tableName + } + return associatedDef.name } private checkAssociateFields(fields?: Field[]) { diff --git a/src/storage/helper/index.ts b/src/storage/helper/index.ts index 8d78b62b..ebe8f93e 100644 --- a/src/storage/helper/index.ts +++ b/src/storage/helper/index.ts @@ -6,4 +6,5 @@ export * from './predicatable-query' export * from './merge-transaction-result' export * from './db-factory' export * from './graph' +export * from './parse-predicate' export { definition } diff --git a/src/storage/helper/parse-predicate.ts b/src/storage/helper/parse-predicate.ts new file mode 100644 index 00000000..cdefca7b --- /dev/null +++ b/src/storage/helper/parse-predicate.ts @@ -0,0 +1,49 @@ +import { Predicate } from '../../interface' +import { forEach } from '../../utils' +import { checkPredicate, predicateOperators } from '../modules/PredicateProvider' + +export function parsePredicate(predicate: Predicate) { + let lastLeaf: Object + function parse (predicateMeta: any) { + const target = Object.create(null) + lastLeaf = target + forEach(predicateMeta, (val, key) => { + if (predicateOperators.has(key)) { + lastLeaf[key] = val + } else { + const ks: string[] = key.split('.') + const length = ks.length + if (length > 1) { + ks.reduce((acc, cur, index) => { + if (index === length - 1) { + if (checkPredicate(val)) { + const subLeaf = parse(val) + acc[cur] = subLeaf + lastLeaf = subLeaf + } else { + acc[cur] = val + } + } else { + const newLeaf = Object.create(null) + acc[cur] = newLeaf + return newLeaf + } + }, lastLeaf) + } else if (checkPredicate(val)) { + lastLeaf[key] = parse(val) + } else if (Array.isArray(val)) { + lastLeaf[key] = parseArray(val) + } else { + lastLeaf[key] = val + } + } + }) + return target + } + + function parseArray(predicates: Predicate[]) { + return predicates.map(pred => parse(pred)) + } + + return parse(predicate) +} diff --git a/src/storage/modules/PredicateProvider.ts b/src/storage/modules/PredicateProvider.ts index 231cad9d..4bcecb33 100644 --- a/src/storage/modules/PredicateProvider.ts +++ b/src/storage/modules/PredicateProvider.ts @@ -1,5 +1,5 @@ import * as lf from 'lovefield' -import { relateIdentifier, fieldIdentifier } from '../symbols' +import { fieldIdentifier } from '../symbols' import { forEach, warn, concat, keys } from '../../utils' import { ValueLiteral, VaildEqType, Predicate, PredicateMeta, TablesStruct } from '../../interface' @@ -54,6 +54,8 @@ const predicateFactory = { }, } +export const predicateOperators = new Set(keys(predicateFactory)) + const compoundPredicateFactory = { $and (predicates: lf.Predicate[]): lf.Predicate { return lf.op.and(...predicates) @@ -68,9 +70,6 @@ const compoundPredicateFactory = { }, } -export const predicateOperatorNames = - new Set(concat(keys(predicateFactory), keys(compoundPredicateFactory))) - export class PredicateProvider { private table: lf.schema.Table | null @@ -105,36 +104,44 @@ export class PredicateProvider { return pred ? JSON.stringify(this.meta) : '' } - private normalizeMeta(meta: Predicate, column?: lf.schema.Column, parentKey?: string): lf.Predicate[] { - const table = this.table! + private normalizeMeta(meta: Predicate, column?: lf.schema.Column, parentKey?: string, tableName = this.tableName): lf.Predicate[] { + const identifier = parentKey ? fieldIdentifier(tableName, parentKey) : tableName + const struct = parentKey ? this.tables[identifier] : { table: this.table!, contextName: this.tableName } + let table: lf.schema.Table + let contextName: string | undefined + if (!struct) { + const parentStruct = this.tables[tableName] + table = parentStruct.table + contextName = parentStruct.contextName + } else { + table = struct.table + contextName = struct.contextName + } const buildSinglePred = (col: lf.schema.Column, val: any, key: string): lf.Predicate => this.checkMethod(key) ? predicateFactory[key](col, val) : col.eq(val as ValueLiteral) const predicates: lf.Predicate[] = [] - forEach(meta, (val, key) => { + forEach(meta, (val: any, key) => { let nestedPreds: lf.Predicate[] let resultPred: lf.Predicate if (this.checkCompound(key)) { - nestedPreds = this.normalizeMeta(val as Predicate, column) + nestedPreds = (Array.isArray(val) ? val : [val]).reduce((acc: Predicate[], pred: Predicate) => { + return concat(acc, this.normalizeMeta(pred as Predicate, column, parentKey, contextName)) + }, []) resultPred = compoundPredicateFactory[key](nestedPreds) } else if (checkPredicate(val)) { - nestedPreds = this.normalizeMeta(val as any, table[key], key) + nestedPreds = this.normalizeMeta(val as any, table[key], key, contextName) resultPred = compoundPredicateFactory['$and'](nestedPreds) } else { - if (parentKey && !this.checkMethod(key)) { - key = relateIdentifier(parentKey, key) - } - const ks: string[] = key.split('.') - let targetCol: lf.schema.Column + let targetCol: lf.schema.Column | null = null if (!column) { - if (ks.length === 1) { - targetCol = table[key] + if (struct) { + targetCol = struct.table[key] } else { - const columnKey = ks.pop()! - const tableName = this.getAliasTableName(ks) - targetCol = this.tables[tableName].table[columnKey] + this.warn(parentKey!, table.getName()) + return } } else { targetCol = column @@ -142,30 +149,16 @@ export class PredicateProvider { if (targetCol) { resultPred = buildSinglePred(targetCol, val, key) } else { - warn(`Failed to build predicate, since column: ${key} is not existed, on table: ${table.getName()}`) + this.warn(key, table.getName()) return } } predicates.push(resultPred) }) - return predicates } - private getAliasTableName(ks: string[]) { - let { length } = ks - let ctxName = this.tableName - let resultKey = this.tableName - while (length > 0) { - const localKey = ks.shift()! - resultKey = fieldIdentifier(ctxName, localKey) - ctxName = this.tables[resultKey].contextName! - length = ks.length - } - return resultKey - } - private checkMethod(methodName: string) { return typeof predicateFactory[methodName] === 'function' } @@ -174,6 +167,10 @@ export class PredicateProvider { return typeof compoundPredicateFactory[methodName] === 'function' } + private warn(key: string, tableName: string) { + warn(`Failed to build predicate, since column: ${key} is not existed, on table: ${tableName}`) + } + } export function checkPredicate(val: Partial> | ValueLiteral) { diff --git a/src/storage/symbols/index.ts b/src/storage/symbols/index.ts index 4fee1a5c..bfa1af75 100644 --- a/src/storage/symbols/index.ts +++ b/src/storage/symbols/index.ts @@ -2,4 +2,3 @@ export * from './dispose' export * from './context-table' export * from './hidden-columns' export * from './field-identifier' -export * from './relate-identifier' diff --git a/src/storage/symbols/relate-identifier.ts b/src/storage/symbols/relate-identifier.ts deleted file mode 100644 index e2163e67..00000000 --- a/src/storage/symbols/relate-identifier.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const relatedLink = '.' - -export function relateIdentifier(tableName: string, relatedName: string) { - return `${tableName}${relatedLink}${relatedName}` -} diff --git a/src/utils/try-catch.ts b/src/utils/try-catch.ts index e2f4882c..fa770d57 100644 --- a/src/utils/try-catch.ts +++ b/src/utils/try-catch.ts @@ -1,8 +1,8 @@ // think it as Either[T, Error] -export function tryCatch(this: any, fn: (...args: any[]) => T) { +export function tryCatch(fn: (...args: any[]) => T) { return (...args: any[]): [T | null, null | Error] => { try { - return [fn.apply(this, args), null] + return [fn.apply(null, args), null] } catch (e) { return [null, e] } diff --git a/test/schemas/Organization.ts b/test/schemas/Organization.ts index f04a0e06..5e7303a4 100644 --- a/test/schemas/Organization.ts +++ b/test/schemas/Organization.ts @@ -4,6 +4,7 @@ export interface OrganizationSchema { _id: TeambitionTypes.OrganizationId name: string isArchived: boolean + expireDate: number } export default (db: Database) => db.defineSchema('Organization', { @@ -16,5 +17,8 @@ export default (db: Database) => db.defineSchema('Organizati }, isArchived: { type: RDBType.BOOLEAN + }, + expireDate: { + type: RDBType.NUMBER } }) diff --git a/test/specs/index.ts b/test/specs/index.ts index 5a05820b..86e329ac 100644 --- a/test/specs/index.ts +++ b/test/specs/index.ts @@ -9,3 +9,4 @@ export * from './shared/Traversable.spec' export * from './utils/utils.spec' export * from './storage/helper/definition.spec' export * from './storage/helper/graph.spec' +export * from './storage/helper/parse-predicate.spec' diff --git a/test/specs/storage/Database.public.spec.ts b/test/specs/storage/Database.public.spec.ts index 72426d94..f90ba660 100644 --- a/test/specs/storage/Database.public.spec.ts +++ b/test/specs/storage/Database.public.spec.ts @@ -610,6 +610,40 @@ export default describe('Database Testcase: ', () => { expect(result.project.organization._id).to.equal(innerTarget.project._organizationId) }) + it('should get value by nested predicate in compound operator', function* () { + const fields = ['_id', 'content', { subtasks: ['_id'], project: ['_id', { organization: ['_id', 'expireDate'] }] }] + const day = 24 * 60 * 60 * 1000 - 2 + const queryToken = database.get('Task', { + fields, + where: { + $and: [ + { + 'project.organization': { + expireDate: { + $gt: innerTarget.project.organization.expireDate - 10 * day + } + } + }, + { + 'project.organization': { + expireDate: { + $lt: innerTarget.project.organization.expireDate - 8 * day + } + } + } + ] + } + }) + + const results = yield queryToken.values() + expect(results.length).to.equal(2) + + results.forEach((task: TaskSchema) => { + expect(task.project.organization.expireDate).gt(innerTarget.project.organization.expireDate - 10 * day) + expect(task.project.organization.expireDate).lt(innerTarget.project.organization.expireDate - 8 * day) + }) + }) + it('should warn if build additional join info from predicate failed', function* () { const fields = ['_id', 'content'] const queryToken = database.get('Task', { @@ -1008,13 +1042,15 @@ export default describe('Database Testcase: ', () => { const posts: PostSchema[] = postGen(10, null) const execRet = yield database.upsert('Post', posts) - const rets = yield database.get('Post', { + const queryToken = database.get('Post', { where: { _id: { $in: posts.map(p => p._id) } } - }).values() + }) + + const rets = yield queryToken.values() expect(posts).to.deep.equal(rets) checkExecutorResult(execRet, 10) diff --git a/test/specs/storage/helper/parse-predicate.spec.ts b/test/specs/storage/helper/parse-predicate.spec.ts new file mode 100644 index 00000000..ee250e50 --- /dev/null +++ b/test/specs/storage/helper/parse-predicate.spec.ts @@ -0,0 +1,289 @@ +import { describe, it } from 'tman' +import { expect } from 'chai' + +import { parsePredicate } from '../../../index' + +export default describe('Helper - parsePredicate TestCase', () => { + + const expected = { + task: { + tasklist: { + project: { + organization: { + _id: 'xxx' + } + } + } + } + } + + it('should parse normal predicate', () => { + const meta = { + task: { + tasklist: { + project: { + organization: { + _id: 'xxx' + } + } + } + } + } + const metaString = JSON.stringify(meta) + const result = parsePredicate(meta) + + expect(result).to.deep.equal(expected) + expect(JSON.stringify(result)).to.equal(metaString) + }) + + it('should parse nested predicate', () => { + const meta = { + task: { + 'tasklist.project.organization._id': 'xxx' + } + } + + const result = parsePredicate(meta) + + expect(result).to.deep.equal(expected) + }) + + it('should parse mutiple levels nested predicate #1', () => { + const meta = { + 'task.tasklist': { + 'project.organization': { + _id: 'xxx' + } + } + } + + const result = parsePredicate(meta) + + expect(result).to.deep.equal(expected) + }) + + it('should parse mutiple levels nested predicate #2', () => { + const meta = { + task: { + 'tasklist.project.organization': { + _id: 'xxx' + } + } + } + + const result = parsePredicate(meta) + + expect(result).to.deep.equal(expected) + }) + + it('should parse mutiple levels nested predicate #3', () => { + const meta = { + task: { + 'tasklist.project': { + 'organization._id': 'xxx' + } + } + } + + const result = parsePredicate(meta) + + expect(result).to.deep.equal(expected) + }) + + it('should parse with predicate operators', () => { + const meta = { + task: { + 'tasklist._id': { + $in: ['xxx', 'yyy'] + } + } + } + + const result = parsePredicate(meta) + + expect(result).to.deep.equal({ + task: { + tasklist: { + _id: { + $in: ['xxx', 'yyy'] + } + } + } + }) + }) + + it('should parse compound nested predicate', () => { + const meta = { + task: { + $or: [ + { + 'tasklist.project': { + 'organization._id': 'xxx' + } + }, + { + 'tasklist.project': { + 'organization._id': 'yyy' + } + } + ] + } + } + + const result = parsePredicate(meta) + + expect(result).to.deep.equal({ + task: { + $or: [ + { + tasklist: { + project: { + organization: { + _id: 'xxx' + } + } + } + }, + { + tasklist: { + project: { + organization: { + _id: 'yyy' + } + } + } + } + ] + } + }) + }) + + it('should parse compound nested predicate with operator #1', () => { + const reg = /e/g + + const meta = { + task: { + $and: [ + { + 'tasklist.project': { + 'organization._id': 'xxx' + } + }, + { + 'tasklist.project': { + 'organization': { + _id: { + $match: reg + } + } + } + } + ] + } + } + + const result = parsePredicate(meta) + + expect(result).to.deep.equal({ + task: { + $and: [ + { + tasklist: { + project: { + organization: { + _id: 'xxx' + } + } + } + }, + { + tasklist: { + project: { + organization: { + _id: { + $match: reg + } + } + } + } + } + ] + } + }) + }) + + it('should parse compound nested predicate with operator #2', () => { + const reg = /e/g + const now = Date.now() + + const meta = { + task: { + $and: [ + { + 'tasklist.project': { + $and: [ + { 'organization._id': 'xxx' }, + { + organization: { + expireDate: { + $gte: now + } + } + } + ] + } + }, + { + 'tasklist.project': { + 'organization': { + _id: { + $match: reg + } + } + } + } + ] + } + } + + const result = parsePredicate(meta) + + expect(result).to.deep.equal({ + task: { + $and: [ + { + tasklist: { + project: { + $and: [ + { + organization: { + _id: 'xxx' + } + }, + { + organization: { + expireDate: { + $gte: now + } + } + } + ] + } + } + }, + { + tasklist: { + project: { + organization: { + _id: { + $match: reg + } + } + } + } + } + ] + } + }) + }) +}) diff --git a/test/specs/storage/modules/Selector.spec.ts b/test/specs/storage/modules/Selector.spec.ts index fd7fa653..e25ee3a7 100644 --- a/test/specs/storage/modules/Selector.spec.ts +++ b/test/specs/storage/modules/Selector.spec.ts @@ -11,7 +11,8 @@ import { ShapeMatcher, PredicateProvider, Predicate, - TokenConcatFailed + TokenConcatFailed, + Logger } from '../../../index' use(SinonChai) @@ -24,6 +25,7 @@ interface Fixture { } export default describe('Selector test', () => { + const dataLength = 1000 let db: lf.Database let table: lf.schema.Table let tableDef: { TestSelectMetadata: { table: lf.schema.Table } } @@ -60,7 +62,7 @@ export default describe('Selector test', () => { const rows: lf.Row[] = [] storeData = [] - for (let i = 0; i < 1000; i ++) { + for (let i = 0; i < dataLength; i ++) { const priority = Math.ceil(i / 100) const row = { _id: `_id:${i}`, @@ -190,6 +192,25 @@ export default describe('Selector test', () => { }) }) + it('should ignore predicate when build predicate failed and give a warning', function* () { + const err = new TypeError('not happy') + + const spy = sinon.spy(Logger, 'warn') + + const selector = new Selector(db, + db.select().from(table), + tableShape, + predicateFactory({ get time() { throw err } }), + ) + + const results = yield selector.values() + + expect(results.length).to.equal(dataLength) + expect(spy.callCount).to.equal(1) + + spy.restore() + }) + describe('Selector.prototype.changes', () => { it('observe should ok', done => { const selector = new Selector(db, diff --git a/test/utils/generators/task-generator.ts b/test/utils/generators/task-generator.ts index 16adaea7..02e31a7e 100644 --- a/test/utils/generators/task-generator.ts +++ b/test/utils/generators/task-generator.ts @@ -32,6 +32,7 @@ export default function (limit: number) { _id: _organizationId, name: 'organization name: ' + uuid(), isArchived: false, + expireDate: moment().add(limit, 'day').valueOf() } } result.push({ From 2ccf2eece0598bd0abe50939e5e5a3d358bb0d62 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 17 Aug 2017 11:00:21 +0800 Subject: [PATCH 16/19] chore: yarn upgrade --- package.json | 42 +-- yarn.lock | 996 ++++++++++++++++++++++++++++----------------------- 2 files changed, 568 insertions(+), 470 deletions(-) diff --git a/package.json b/package.json index b54f2336..0d1316cf 100644 --- a/package.json +++ b/package.json @@ -60,42 +60,42 @@ }, "license": "MIT", "devDependencies": { - "@types/chai": "^4.0.1", - "@types/node": "^8.0.14", + "@types/chai": "^4.0.4", + "@types/node": "^8.0.26", "@types/shelljs": "^0.7.4", "@types/sinon": "^2.3.3", - "@types/sinon-chai": "^2.7.28", - "awesome-typescript-loader": "^3.2.1", - "chai": "^4.1.0", + "@types/sinon-chai": "^2.7.29", + "awesome-typescript-loader": "^3.2.3", + "chai": "^4.1.2", "coveralls": "^2.13.1", - "css-loader": "^0.28.4", + "css-loader": "^0.28.7", "extract-text-webpack-plugin": "^3.0.0", - "happypack": "^3.0.3", - "html-webpack-plugin": "^2.29.0", - "madge": "^2.0.0", + "happypack": "^4.0.0-beta.5", + "html-webpack-plugin": "^2.30.1", + "madge": "^2.2.0", "moment": "^2.18.1", "node-watch": "^0.5.5", - "npm-run-all": "^4.0.2", - "nyc": "^11.0.3", + "npm-run-all": "^4.1.1", + "nyc": "^11.2.0", "raw-loader": "^0.5.1", - "rxjs": "^5.4.2", + "rxjs": "^5.4.3", "shelljs": "^0.7.8", "shx": "^0.2.2", - "sinon": "^3.0.0", - "sinon-chai": "^2.11.0", + "sinon": "^3.2.1", + "sinon-chai": "^2.13.0", "source-map-loader": "^0.2.1", "style-loader": "^0.18.2", - "tman": "^1.7.1", - "ts-node": "^3.2.0", - "tslint": "^5.5.0", + "tman": "^1.7.2", + "ts-node": "^3.3.0", + "tslint": "^5.7.0", "tslint-eslint-rules": "^4.1.1", "tslint-loader": "^3.5.3", - "typescript": "^2.4.2", - "webpack": "^3.3.0", - "webpack-dev-server": "^2.5.1" + "typescript": "^2.5.2", + "webpack": "^3.5.5", + "webpack-dev-server": "^2.7.1" }, "dependencies": { - "@types/lovefield": "^2.0.32", + "@types/lovefield": "^2.1.1", "lovefield": "2.1.12", "nesthydrationjs": "^1.0.2" }, diff --git a/yarn.lock b/yarn.lock index 267fbb3f..5eb6c949 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,32 +2,28 @@ # yarn lockfile v1 -"@types/chai@*", "@types/chai@^4.0.1": - version "4.0.1" - resolved "https://registry.npmjs.org/@types/chai/-/chai-4.0.1.tgz#37fea779617cfec3fd2b19a0247e8bbdd5133bf6" +"@types/chai@*", "@types/chai@^4.0.4": + version "4.0.4" + resolved "https://registry.npmjs.org/@types/chai/-/chai-4.0.4.tgz#fe86315d9a66827feeb16f73bc954688ec950e18" "@types/glob@*": - version "5.0.30" - resolved "https://registry.npmjs.org/@types/glob/-/glob-5.0.30.tgz#1026409c5625a8689074602808d082b2867b8a51" + version "5.0.32" + resolved "https://registry.npmjs.org/@types/glob/-/glob-5.0.32.tgz#aec5cfe987c72f099fdb1184452986aa506d5e8f" dependencies: "@types/minimatch" "*" "@types/node" "*" -"@types/lovefield@^2.0.32": - version "2.1.0" - resolved "https://registry.npmjs.org/@types/lovefield/-/lovefield-2.1.0.tgz#513d88b343c137c033706d4d684a8003256fe485" +"@types/lovefield@^2.1.1": + version "2.1.1" + resolved "https://registry.npmjs.org/@types/lovefield/-/lovefield-2.1.1.tgz#056c375d5289d85d83eb4c45036cfeba1d2c21d4" "@types/minimatch@*": - version "2.0.29" - resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" - -"@types/node@*": - version "8.0.19" - resolved "https://registry.npmjs.org/@types/node/-/node-8.0.19.tgz#e46e2b0243de7d03f15b26b45c59ebb84f657a4e" + version "3.0.1" + resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.1.tgz#b683eb60be358304ef146f5775db4c0e3696a550" -"@types/node@^8.0.14": - version "8.0.14" - resolved "https://registry.npmjs.org/@types/node/-/node-8.0.14.tgz#4a19dc6bb61d16c01cbadc7b30ac23518fff176b" +"@types/node@*", "@types/node@^8.0.26": + version "8.0.26" + resolved "https://registry.npmjs.org/@types/node/-/node-8.0.26.tgz#4d58be925306fd22b1141085535a0268b8beb189" "@types/shelljs@^0.7.4": version "0.7.4" @@ -36,9 +32,9 @@ "@types/glob" "*" "@types/node" "*" -"@types/sinon-chai@^2.7.28": - version "2.7.28" - resolved "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-2.7.28.tgz#c9f775dee3bd27717f3aa94af3c0c9348e8a8c32" +"@types/sinon-chai@^2.7.29": + version "2.7.29" + resolved "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-2.7.29.tgz#4db01497e2dd1908b2bd30d1782f456353f5f723" dependencies: "@types/chai" "*" "@types/sinon" "*" @@ -52,10 +48,10 @@ abbrev@1: resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" accepts@~1.3.3: - version "1.3.3" - resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" + version "1.3.4" + resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" dependencies: - mime-types "~2.1.11" + mime-types "~2.1.16" negotiator "0.6.1" acorn-dynamic-import@^2.0.0: @@ -124,24 +120,24 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" -ansi-styles@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.1.0.tgz#09c202d5c917ec23188caa5c9cb9179cd9547750" +ansi-styles@^3.1.0, ansi-styles@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" dependencies: - color-convert "^1.0.0" + color-convert "^1.9.0" any-promise@^1.0.0: version "1.3.0" resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" anymatch@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" + version "1.3.2" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" dependencies: - arrify "^1.0.0" micromatch "^2.1.5" + normalize-path "^2.0.0" -app-module-path@~1.1.0: +app-module-path@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/app-module-path/-/app-module-path-1.1.0.tgz#a6ac5368450f209b9f5b86e9a3e4a6ab6fe7531c" @@ -318,12 +314,12 @@ autoprefixer@^6.3.1: postcss "^5.2.16" postcss-value-parser "^3.2.3" -awesome-typescript-loader@^3.2.1: - version "3.2.1" - resolved "https://registry.npmjs.org/awesome-typescript-loader/-/awesome-typescript-loader-3.2.1.tgz#600f5d552da3e5501e3e5c19aa3e8986059f8947" +awesome-typescript-loader@^3.2.3: + version "3.2.3" + resolved "https://registry.npmjs.org/awesome-typescript-loader/-/awesome-typescript-loader-3.2.3.tgz#aa2119b7c808a031e2b28945b031450a8975367f" dependencies: colors "^1.1.2" - enhanced-resolve "^3.1.0" + enhanced-resolve "3.3.0" loader-utils "^1.1.0" lodash "^4.17.4" micromatch "^3.0.3" @@ -339,25 +335,25 @@ aws4@^1.2.1: version "1.6.0" resolved "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -babel-code-frame@^6.11.0, babel-code-frame@^6.22.0: - version "6.22.0" - resolved "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" +babel-code-frame@^6.11.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: + version "6.26.0" + resolved "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" dependencies: - chalk "^1.1.0" + chalk "^1.1.3" esutils "^2.0.2" - js-tokens "^3.0.0" + js-tokens "^3.0.2" babel-generator@^6.18.0: - version "6.25.0" - resolved "https://registry.npmjs.org/babel-generator/-/babel-generator-6.25.0.tgz#33a1af70d5f2890aeb465a4a7793c1df6a9ea9fc" + version "6.26.0" + resolved "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" dependencies: babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-types "^6.25.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" detect-indent "^4.0.0" jsesc "^1.3.0" - lodash "^4.2.0" - source-map "^0.5.0" + lodash "^4.17.4" + source-map "^0.5.6" trim-right "^1.0.1" babel-messages@^6.23.0: @@ -366,49 +362,55 @@ babel-messages@^6.23.0: dependencies: babel-runtime "^6.22.0" -babel-runtime@^6.22.0: - version "6.23.0" - resolved "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" +babel-runtime@^6.0.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: core-js "^2.4.0" - regenerator-runtime "^0.10.0" + regenerator-runtime "^0.11.0" babel-template@^6.16.0: - version "6.25.0" - resolved "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071" + version "6.26.0" + resolved "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" dependencies: - babel-runtime "^6.22.0" - babel-traverse "^6.25.0" - babel-types "^6.25.0" - babylon "^6.17.2" - lodash "^4.2.0" + babel-runtime "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + lodash "^4.17.4" -babel-traverse@^6.18.0, babel-traverse@^6.25.0: - version "6.25.0" - resolved "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz#2257497e2fcd19b89edc13c4c91381f9512496f1" +babel-traverse@^6.18.0, babel-traverse@^6.26.0: + version "6.26.0" + resolved "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" dependencies: - babel-code-frame "^6.22.0" + babel-code-frame "^6.26.0" babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-types "^6.25.0" - babylon "^6.17.2" - debug "^2.2.0" - globals "^9.0.0" - invariant "^2.2.0" - lodash "^4.2.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + debug "^2.6.8" + globals "^9.18.0" + invariant "^2.2.2" + lodash "^4.17.4" -babel-types@^6.18.0, babel-types@^6.25.0: - version "6.25.0" - resolved "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz#70afb248d5660e5d18f811d91c8303b54134a18e" +babel-types@^6.18.0, babel-types@^6.26.0: + version "6.26.0" + resolved "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" dependencies: - babel-runtime "^6.22.0" + babel-runtime "^6.26.0" esutils "^2.0.2" - lodash "^4.2.0" - to-fast-properties "^1.0.1" + lodash "^4.17.4" + to-fast-properties "^1.0.3" -babylon@^6.17.0, babylon@^6.17.2, babylon@^6.17.4: - version "6.17.4" - resolved "https://registry.npmjs.org/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a" +babylon@^6.17.0, babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + +babylon@~6.8.1: + version "6.8.4" + resolved "https://registry.npmjs.org/babylon/-/babylon-6.8.4.tgz#097306b8dabae95159225cf29b3ea55912053180" + dependencies: + babel-runtime "^6.0.0" balanced-match@^0.4.2: version "0.4.2" @@ -451,8 +453,8 @@ big.js@^3.1.3: resolved "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz#4cada2193652eb3ca9ec8e55c9015669c9806978" binary-extensions@^1.0.0: - version "1.8.0" - resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774" + version "1.10.0" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0" block-stream@*: version "0.0.9" @@ -465,8 +467,8 @@ bluebird@^3.4.7: resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.7" - resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.11.7.tgz#ddb048e50d9482790094c13eb3fcfc833ce7ab46" + version "4.11.8" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" bonjour@^3.5.0: version "3.5.0" @@ -583,8 +585,8 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: electron-to-chromium "^1.2.7" buffer-indexof@^1.0.0: - version "1.1.0" - resolved "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.0.tgz#f54f647c4f4e25228baa656a2e57e43d5f270982" + version "1.1.1" + resolved "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" buffer-xor@^1.0.2: version "1.0.3" @@ -673,8 +675,8 @@ caniuse-api@^1.5.2: lodash.uniq "^4.5.0" caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000702" - resolved "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000702.tgz#a60cd30f3ef44ae7b5ed12e9d9b70d4bff6352cb" + version "1.0.30000721" + resolved "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000721.tgz#cdc52efe8f82dd13916615b78e86f704ece61802" caseless@~0.11.0: version "0.11.0" @@ -691,18 +693,18 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chai@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/chai/-/chai-4.1.0.tgz#331a0391b55c3af8740ae9c3b7458bc1c3805e6d" +chai@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c" dependencies: assertion-error "^1.0.1" check-error "^1.0.1" - deep-eql "^2.0.1" + deep-eql "^3.0.0" get-func-name "^2.0.0" pathval "^1.0.0" type-detect "^4.0.0" -chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: +chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -712,9 +714,9 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.0.1.tgz#dbec49436d2ae15f536114e76d14656cdbc0f44d" +chalk@^2.0.0, chalk@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e" dependencies: ansi-styles "^3.1.0" escape-string-regexp "^1.0.5" @@ -820,7 +822,7 @@ collection-visit@^0.2.1: map-visit "^0.1.5" object-visit "^0.3.4" -color-convert@^1.0.0, color-convert@^1.3.0: +color-convert@^1.3.0, color-convert@^1.9.0: version "1.9.0" resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" dependencies: @@ -862,27 +864,23 @@ combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" -commander@2.9.x, commander@~2.9.0: +commander@2.11.x, commander@^2.11.0, commander@^2.6.0, commander@^2.8.1, commander@^2.9.0, commander@~2.11.0: + version "2.11.0" + resolved "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + +commander@2.9.0: version "2.9.0" resolved "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" dependencies: graceful-readlink ">= 1.0.0" -commander@^2.8.1, commander@^2.9.0, commander@~2.11.0: - version "2.11.0" - resolved "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" - -commander@~2.6.0: - version "2.6.0" - resolved "https://registry.npmjs.org/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" - commander@~2.8.1: version "2.8.1" resolved "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4" dependencies: graceful-readlink ">= 1.0.0" -commondir@^1.0.1: +commondir@1.0.1, commondir@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -891,10 +889,10 @@ component-emitter@^1.2.1: resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" compressible@~2.0.10: - version "2.0.10" - resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.10.tgz#feda1c7f7617912732b29bf8cf26252a20b9eecd" + version "2.0.11" + resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.11.tgz#16718a75de283ed8e604041625a2064586797d8a" dependencies: - mime-db ">= 1.27.0 < 2" + mime-db ">= 1.29.0 < 2" compression@^1.5.2: version "1.7.0" @@ -955,10 +953,10 @@ copy-descriptor@^0.1.0: resolved "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" core-js@^2.4.0: - version "2.4.1" - resolved "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" + version "2.5.0" + resolved "https://registry.npmjs.org/core-js/-/core-js-2.5.0.tgz#569c050918be6486b3837552028ae0466b717086" -core-util-is@~1.0.0: +core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -979,7 +977,7 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" -create-hash@^1.1.0, create-hash@^1.1.1, create-hash@^1.1.2: +create-hash@^1.1.0, create-hash@^1.1.2: version "1.1.3" resolved "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" dependencies: @@ -999,14 +997,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-spawn@^4, cross-spawn@^4.0.0: +cross-spawn@^4: version "4.0.2" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" dependencies: lru-cache "^4.0.1" which "^1.2.9" -cross-spawn@^5.0.1: +cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" dependencies: @@ -1039,9 +1037,9 @@ css-color-names@0.0.4: version "0.0.4" resolved "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" -css-loader@^0.28.4: - version "0.28.4" - resolved "https://registry.npmjs.org/css-loader/-/css-loader-0.28.4.tgz#6cf3579192ce355e8b38d5f42dd7a1f2ec898d0f" +css-loader@^0.28.7: + version "0.28.7" + resolved "https://registry.npmjs.org/css-loader/-/css-loader-0.28.7.tgz#5f2ee989dd32edd907717f953317656160999c1b" dependencies: babel-code-frame "^6.11.0" css-selector-tokenizer "^0.7.0" @@ -1056,7 +1054,7 @@ css-loader@^0.28.4: postcss-modules-scope "^1.0.0" postcss-modules-values "^1.1.0" postcss-value-parser "^3.3.0" - source-list-map "^0.1.7" + source-list-map "^2.0.0" css-select@^1.1.0: version "1.2.0" @@ -1153,33 +1151,33 @@ debug-log@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f" -debug@2.6.7: - version "2.6.7" - resolved "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e" +debug@2.2.0, debug@~2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" dependencies: - ms "2.0.0" + ms "0.7.1" -debug@2.6.8, debug@^2.2.0, debug@^2.3.3, debug@^2.6.3, debug@^2.6.8: +debug@2.6.8, debug@^2.2.0, debug@^2.3.3, debug@^2.6.3, debug@^2.6.6, debug@^2.6.8: version "2.6.8" resolved "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" dependencies: ms "2.0.0" -debug@~2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" +debug@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/debug/-/debug-3.0.1.tgz#0564c612b521dc92d9f2988f0549e34f9c98db64" dependencies: - ms "0.7.1" + ms "2.0.0" decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" -deep-eql@^2.0.1: - version "2.0.2" - resolved "https://registry.npmjs.org/deep-eql/-/deep-eql-2.0.2.tgz#b1bac06e56f0a76777686d50c9feb75c2ed7679a" +deep-eql@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.0.tgz#b9162a49cf4b54d911425975ac95d03e56448471" dependencies: - type-detect "^3.0.0" + type-detect "^4.0.0" deep-equal@^1.0.1: version "1.0.1" @@ -1241,18 +1239,18 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" -depd@1.1.0, depd@~1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" +depd@1.1.1, depd@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" -dependency-tree@5.9.1: - version "5.9.1" - resolved "https://registry.npmjs.org/dependency-tree/-/dependency-tree-5.9.1.tgz#0bbcb3e06b14b69a04fb97efd19ea00d92392a26" +dependency-tree@5.11.0: + version "5.11.0" + resolved "https://registry.npmjs.org/dependency-tree/-/dependency-tree-5.11.0.tgz#928464d6f9273607d3f66b9a57e259e635667755" dependencies: - commander "~2.6.0" - debug "~2.2.0" - filing-cabinet "^1.8.0" - precinct "^3.6.0" + commander "^2.6.0" + debug "^2.2.0" + filing-cabinet "^1.9.0" + precinct "^3.8.0" des.js@^1.0.0: version "1.0.0" @@ -1291,11 +1289,11 @@ detective-cjs@^2.0.0: ast-module-types "^2.3.2" node-source-walk "^3.0.0" -detective-es6@^1.1.6: - version "1.1.6" - resolved "https://registry.npmjs.org/detective-es6/-/detective-es6-1.1.6.tgz#5d9140566eeff12c54fe6ab3936650d43bceb0a6" +detective-es6@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/detective-es6/-/detective-es6-1.2.0.tgz#6b9b3bd547fd8f21f89502f626e45ed2a3276fdc" dependencies: - node-source-walk "^3.0.0" + node-source-walk "^3.3.0" detective-less@1.0.0: version "1.0.0" @@ -1325,6 +1323,14 @@ detective-stylus@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/detective-stylus/-/detective-stylus-1.0.0.tgz#50aee7db8babb990381f010c63fabba5b58e54cd" +detective-typescript@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/detective-typescript/-/detective-typescript-1.0.1.tgz#6affa0d4bf8ca500194f30c28be445c99fecf81d" + dependencies: + node-source-walk "3.2.0" + typescript "2.0.10" + typescript-eslint-parser "1.0.2" + diff@^3.1.0, diff@^3.2.0, diff@~3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/diff/-/diff-3.3.0.tgz#056695150d7aa93237ca7e378ac3b1682b7963b9" @@ -1342,8 +1348,8 @@ dns-equal@^1.0.0: resolved "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" dns-packet@^1.0.1: - version "1.1.1" - resolved "https://registry.npmjs.org/dns-packet/-/dns-packet-1.1.1.tgz#2369d45038af045f3898e6fa56862aed3f40296c" + version "1.2.2" + resolved "https://registry.npmjs.org/dns-packet/-/dns-packet-1.2.2.tgz#a8a26bec7646438963fc86e06f8f8b16d6c8bf7a" dependencies: ip "^1.1.0" safe-buffer "^5.0.1" @@ -1420,8 +1426,8 @@ ee-first@1.1.1: resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" electron-to-chromium@^1.2.7: - version "1.3.16" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.16.tgz#d0e026735754770901ae301a21664cba45d92f7d" + version "1.3.18" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.18.tgz#3dcc99da3e6b665f6abbc71c28ad51a2cd731a9c" elliptic@^6.0.0: version "6.4.0" @@ -1443,7 +1449,7 @@ encodeurl@~1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" -enhanced-resolve@^3.1.0, enhanced-resolve@^3.3.0: +enhanced-resolve@3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.3.0.tgz#950964ecc7f0332a42321b673b38dc8ff15535b3" dependencies: @@ -1452,6 +1458,15 @@ enhanced-resolve@^3.1.0, enhanced-resolve@^3.3.0: object-assign "^4.0.1" tapable "^0.2.5" +enhanced-resolve@^3.4.0: + version "3.4.1" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.4.0" + object-assign "^4.0.1" + tapable "^0.2.7" + enhanced-resolve@~3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.0.3.tgz#df14c06b5fc5eecade1094c9c5a12b4b3edc0b62" @@ -1478,13 +1493,14 @@ error-ex@^1.2.0: is-arrayish "^0.2.1" es-abstract@^1.4.3: - version "1.7.0" - resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c" + version "1.8.1" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.8.1.tgz#fd85a3bdfa67786ce7be7e1584678e119cd70c04" dependencies: es-to-primitive "^1.1.1" - function-bind "^1.1.0" + function-bind "^1.1.1" + has "^1.0.1" is-callable "^1.1.3" - is-regex "^1.0.3" + is-regex "^1.0.4" es-to-primitive@^1.1.1: version "1.1.1" @@ -1495,8 +1511,8 @@ es-to-primitive@^1.1.1: is-symbol "^1.0.1" es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14: - version "0.10.24" - resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.24.tgz#a55877c9924bc0c8d9bd3c2cbe17495ac1709b14" + version "0.10.30" + resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.30.tgz#7141a16836697dbabfaaaeee41495ce29f52c939" dependencies: es6-iterator "2" es6-symbol "~3.1" @@ -1647,17 +1663,18 @@ eventsource@0.1.6: original ">=0.0.5" evp_bytestokey@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz#497b66ad9fef65cd7c08a6180824ba1476b66e53" + version "1.0.2" + resolved "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.2.tgz#f66bb88ecd57f71a766821e20283ea38c68bf80a" dependencies: - create-hash "^1.1.1" + md5.js "^1.3.4" + safe-buffer "^5.1.1" -execa@^0.5.0: - version "0.5.1" - resolved "https://registry.npmjs.org/execa/-/execa-0.5.1.tgz#de3fb85cb8d6e91c85bcbceb164581785cb57b36" +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" dependencies: - cross-spawn "^4.0.0" - get-stream "^2.2.0" + cross-spawn "^5.0.1" + get-stream "^3.0.0" is-stream "^1.1.0" npm-run-path "^2.0.0" p-finally "^1.0.0" @@ -1689,8 +1706,8 @@ expand-range@^1.8.1: fill-range "^2.1.0" express@^4.13.3: - version "4.15.3" - resolved "https://registry.npmjs.org/express/-/express-4.15.3.tgz#bab65d0f03aa80c358408972fc700f916944b662" + version "4.15.4" + resolved "https://registry.npmjs.org/express/-/express-4.15.4.tgz#032e2253489cf8fce02666beca3d11ed7a2daed1" dependencies: accepts "~1.3.3" array-flatten "1.1.1" @@ -1698,23 +1715,23 @@ express@^4.13.3: content-type "~1.0.2" cookie "0.3.1" cookie-signature "1.0.6" - debug "2.6.7" - depd "~1.1.0" + debug "2.6.8" + depd "~1.1.1" encodeurl "~1.0.1" escape-html "~1.0.3" etag "~1.8.0" - finalhandler "~1.0.3" + finalhandler "~1.0.4" fresh "0.5.0" merge-descriptors "1.0.1" methods "~1.1.2" on-finished "~2.3.0" parseurl "~1.3.1" path-to-regexp "0.1.7" - proxy-addr "~1.1.4" - qs "6.4.0" + proxy-addr "~1.1.5" + qs "6.5.0" range-parser "~1.2.0" - send "0.15.3" - serve-static "1.12.3" + send "0.15.4" + serve-static "1.12.4" setprototypeof "1.0.3" statuses "~1.3.1" type-is "~1.6.15" @@ -1759,9 +1776,9 @@ extract-text-webpack-plugin@^3.0.0: schema-utils "^0.3.0" webpack-sources "^1.0.1" -extsprintf@1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" +extsprintf@1.3.0, extsprintf@^1.2.0: + version "1.3.0" + resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" fast-deep-equal@^1.0.0: version "1.0.0" @@ -1795,22 +1812,23 @@ filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" -filing-cabinet@^1.8.0: - version "1.8.0" - resolved "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-1.8.0.tgz#7d0e8c39b06e16532cee14cadde41b1fa0276265" +filing-cabinet@^1.9.0: + version "1.9.0" + resolved "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-1.9.0.tgz#043caf1925e355b03a8d5096f4084d3f403008b6" dependencies: - app-module-path "~1.1.0" - commander "~2.8.1" - debug "~2.2.0" + app-module-path "^1.1.0" + commander "^2.8.1" + debug "^2.2.0" enhanced-resolve "~3.0.3" is-relative-path "^1.0.1" module-definition "^2.2.4" module-lookup-amd "^4.0.2" - object-assign "~4.0.1" - resolve "~1.1.7" - resolve-dependency-path "~1.0.2" - sass-lookup "^1.0.2" + object-assign "^4.0.1" + resolve "^1.1.7" + resolve-dependency-path "^1.0.2" + sass-lookup "^1.1.0" stylus-lookup "^1.0.1" + typescript "^2.4.2" fill-range@^2.1.0: version "2.2.3" @@ -1831,11 +1849,11 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" -finalhandler@~1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89" +finalhandler@~1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.4.tgz#18574f2e7c4b98b8ae3b230c21f201f31bdb3fb7" dependencies: - debug "2.6.7" + debug "2.6.8" encodeurl "~1.0.1" escape-html "~1.0.3" on-finished "~2.3.0" @@ -1968,9 +1986,9 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" -function-bind@^1.0.2, function-bind@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" +function-bind@^1.0.2, function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" gauge@~2.7.3: version "2.7.4" @@ -2014,12 +2032,9 @@ get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" -get-stream@^2.2.0: - version "2.3.1" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" - dependencies: - object-assign "^4.0.1" - pinkie-promise "^2.0.0" +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" get-value@^2.0.3, get-value@^2.0.5, get-value@^2.0.6: version "2.0.6" @@ -2055,7 +2070,7 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@~7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^9.0.0: +globals@^9.18.0: version "9.18.0" resolved "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" @@ -2083,7 +2098,7 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2: version "1.0.1" resolved "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" -graphviz@^0.0.8: +graphviz@0.0.8: version "0.0.8" resolved "https://registry.npmjs.org/graphviz/-/graphviz-0.0.8.tgz#e599e40733ef80e1653bfe89a5f031ecf2aa4aaa" dependencies: @@ -2103,14 +2118,13 @@ handlebars@^4.0.3: optionalDependencies: uglify-js "^2.6" -happypack@^3.0.3: - version "3.1.0" - resolved "https://registry.npmjs.org/happypack/-/happypack-3.1.0.tgz#8bc55e3701bacff718d3889cb88b5021641cad59" +happypack@^4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.npmjs.org/happypack/-/happypack-4.0.0-beta.5.tgz#dbedb5c5dce86ebfc1acc8804e8abc61f773091d" dependencies: async "1.5.0" json-stringify-safe "5.0.1" - loader-utils "0.2.16" - mkdirp "0.5.1" + loader-utils "1.1.0" serialize-error "^2.1.0" har-schema@^1.0.5: @@ -2175,6 +2189,13 @@ hash-base@^2.0.0: dependencies: inherits "^2.0.1" +hash-base@^3.0.0: + version "3.0.4" + resolved "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.3" resolved "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" @@ -2229,21 +2250,21 @@ html-entities@^1.2.0: resolved "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" html-minifier@^3.2.3: - version "3.5.2" - resolved "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.2.tgz#d73bc3ff448942408818ce609bf3fb0ea7ef4eb7" + version "3.5.3" + resolved "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.3.tgz#4a275e3b1a16639abb79b4c11191ff0d0fcf1ab9" dependencies: camel-case "3.0.x" clean-css "4.1.x" - commander "2.9.x" + commander "2.11.x" he "1.1.x" ncname "1.0.x" param-case "2.1.x" relateurl "0.2.x" uglify-js "3.0.x" -html-webpack-plugin@^2.29.0: - version "2.29.0" - resolved "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-2.29.0.tgz#e987f421853d3b6938c8c4c8171842e5fd17af23" +html-webpack-plugin@^2.30.1: + version "2.30.1" + resolved "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-2.30.1.tgz#7f9c421b7ea91ec460f56527d78df484ee7537d5" dependencies: bluebird "^3.4.7" html-minifier "^3.2.3" @@ -2265,11 +2286,11 @@ http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" -http-errors@~1.6.1: - version "1.6.1" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257" +http-errors@~1.6.1, http-errors@~1.6.2: + version "1.6.2" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" dependencies: - depd "1.1.0" + depd "1.1.1" inherits "2.0.3" setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" @@ -2363,7 +2384,7 @@ interpret@^1.0.0: version "1.0.3" resolved "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90" -invariant@^2.2.0: +invariant@^2.2.2: version "2.2.2" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" dependencies: @@ -2373,13 +2394,13 @@ invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" -ip@^1.1.0: +ip@^1.1.0, ip@^1.1.5: version "1.1.5" resolved "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" -ipaddr.js@1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec" +ipaddr.js@1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0" is-absolute-url@^2.0.0: version "2.1.0" @@ -2426,22 +2447,20 @@ is-date-object@^1.0.1: resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" is-descriptor@^0.1.0: - version "0.1.5" - resolved "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.5.tgz#e3fb8b4ab65f3a37373388e18b401d78c58cbea7" + version "0.1.6" + resolved "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" dependencies: is-accessor-descriptor "^0.1.6" is-data-descriptor "^0.1.4" - kind-of "^3.0.2" - lazy-cache "^2.0.2" + kind-of "^5.0.0" is-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.0.tgz#d6ec686f238f6b02f23757abe12cf6b2ea2790f9" + version "1.0.1" + resolved "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.1.tgz#2c6023599bde2de9d5d2c8b9a9d94082036b6ef2" dependencies: is-accessor-descriptor "^0.1.6" is-data-descriptor "^0.1.4" - kind-of "^3.0.2" - lazy-cache "^2.0.2" + kind-of "^5.0.0" is-dotfile@^1.0.0: version "1.0.3" @@ -2494,8 +2513,8 @@ is-glob@^3.1.0: is-extglob "^2.1.0" is-my-json-valid@^2.12.4: - version "2.16.0" - resolved "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz#f079dd9bfdae65ee2038aae8acbc86ab109e3693" + version "2.16.1" + resolved "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" dependencies: generate-function "^2.0.0" generate-object-property "^1.1.0" @@ -2558,15 +2577,15 @@ is-property@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" -is-regex@^1.0.3: +is-regex@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" dependencies: has "^1.0.1" is-relative-path@^1.0.1, is-relative-path@~1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/is-relative-path/-/is-relative-path-1.0.1.tgz#ac72793a2d60c049e50676e04a24a8d8f263dc26" + version "1.0.2" + resolved "https://registry.npmjs.org/is-relative-path/-/is-relative-path-1.0.2.tgz#091b46a0d67c1ed0fe85f1f8cfdde006bb251d46" is-stream@^1.1.0: version "1.1.0" @@ -2626,15 +2645,15 @@ istanbul-lib-hook@^1.0.7: dependencies: append-transform "^0.4.0" -istanbul-lib-instrument@^1.7.4: - version "1.7.4" - resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.4.tgz#e9fd920e4767f3d19edc765e2d6b3f5ccbd0eea8" +istanbul-lib-instrument@^1.8.0: + version "1.8.0" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.8.0.tgz#66f6c9421cc9ec4704f76f2db084ba9078a2b532" dependencies: babel-generator "^6.18.0" babel-template "^6.16.0" babel-traverse "^6.18.0" babel-types "^6.18.0" - babylon "^6.17.4" + babylon "^6.18.0" istanbul-lib-coverage "^1.1.1" semver "^5.3.0" @@ -2658,8 +2677,8 @@ istanbul-lib-source-maps@^1.2.1: source-map "^0.5.3" istanbul-reports@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.1.1.tgz#042be5c89e175bc3f86523caab29c014e77fee4e" + version "1.1.2" + resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.1.2.tgz#0fb2e3f6aa9922bd3ce45d05d8ab4d5e8e07bd4f" dependencies: handlebars "^4.0.3" @@ -2667,7 +2686,7 @@ js-base64@^2.1.9: version "2.1.9" resolved "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce" -js-tokens@^3.0.0: +js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" @@ -2705,8 +2724,8 @@ jsesc@~0.5.0: resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" json-loader@^0.5.4: - version "0.5.4" - resolved "https://registry.npmjs.org/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de" + version "0.5.7" + resolved "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" json-schema-traverse@^0.3.0: version "0.3.1" @@ -2747,13 +2766,13 @@ jsonpointer@^4.0.0: resolved "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" jsprim@^1.2.2: - version "1.4.0" - resolved "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" + version "1.4.1" + resolved "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" dependencies: assert-plus "1.0.0" - extsprintf "1.0.2" + extsprintf "1.3.0" json-schema "0.2.3" - verror "1.3.6" + verror "1.10.0" just-extend@^1.1.22: version "1.1.22" @@ -2771,6 +2790,10 @@ kind-of@^4.0.0: dependencies: is-buffer "^1.1.5" +kind-of@^5.0.0: + version "5.0.2" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-5.0.2.tgz#f57bec933d9a2209ffa96c5c08343607b7035fda" + lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" @@ -2821,22 +2844,22 @@ loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" -loader-utils@0.2.16, loader-utils@^0.2.16, loader-utils@~0.2.2: - version "0.2.16" - resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.16.tgz#f08632066ed8282835dff88dfb52704765adee6d" +loader-utils@1.1.0, loader-utils@^1.0.2, loader-utils@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" dependencies: big.js "^3.1.3" emojis-list "^2.0.0" json5 "^0.5.0" - object-assign "^4.0.1" -loader-utils@^1.0.2, loader-utils@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" +loader-utils@^0.2.16, loader-utils@~0.2.2: + version "0.2.17" + resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" dependencies: big.js "^3.1.3" emojis-list "^2.0.0" json5 "^0.5.0" + object-assign "^4.0.1" locate-path@^2.0.0: version "2.0.0" @@ -2853,6 +2876,16 @@ lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" +lodash.tostring@^4.0.0: + version "4.1.4" + resolved "https://registry.npmjs.org/lodash.tostring/-/lodash.tostring-4.1.4.tgz#560c27d1f8eadde03c2cce198fef5c031d8298fb" + +lodash.unescape@4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.0.tgz#36debfc492b81478471ef974cd3783e202eb6cef" + dependencies: + lodash.tostring "^4.0.0" + lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -2861,7 +2894,7 @@ lodash@4.13.1: version "4.13.1" resolved "https://registry.npmjs.org/lodash/-/lodash-4.13.1.tgz#83e4b10913f48496d4d16fec4a560af2ee744b68" -lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0: +lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4: version "4.17.4" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -2875,6 +2908,10 @@ log-symbols@^1.0.2: dependencies: chalk "^1.0.0" +loglevel@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.4.1.tgz#95b383f91a3c2756fd4ab093667e4309161f2bcd" + lolex@^1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6" @@ -2922,22 +2959,22 @@ macaddress@^0.2.8: version "0.2.8" resolved "https://registry.npmjs.org/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" -madge@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/madge/-/madge-2.0.0.tgz#794170cc8a7755da2bb850d06e4219a395424814" - dependencies: - chalk "^1.1.3" - commander "^2.9.0" - commondir "^1.0.1" - debug "^2.2.0" - dependency-tree "5.9.1" - graphviz "^0.0.8" - mz "^2.4.0" +madge@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/madge/-/madge-2.2.0.tgz#560ae7fe73a31a9e784fe402d9b3f79c039527cc" + dependencies: + chalk "1.1.3" + commander "2.9.0" + commondir "1.0.1" + debug "2.2.0" + dependency-tree "5.11.0" + graphviz "0.0.8" + mz "2.4.0" ora "1.2.0" pluralize "4.0.0" pretty-ms "2.1.0" - rc "^1.1.6" - walkdir "^0.0.11" + rc "1.1.6" + walkdir "0.0.11" make-error@^1.1.1: version "1.3.0" @@ -2976,6 +3013,13 @@ md5-o-matic@^0.1.1: version "0.1.1" resolved "https://registry.npmjs.org/md5-o-matic/-/md5-o-matic-0.1.1.tgz#822bccd65e117c514fab176b25945d54100a03c3" +md5.js@^1.3.4: + version "1.3.4" + resolved "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -2993,6 +3037,12 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" +memory-streams@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/memory-streams/-/memory-streams-0.1.2.tgz#273ff777ab60fec599b116355255282cca2c50c2" + dependencies: + readable-stream "~1.0.2" + meow@^3.3.0: version "3.7.0" resolved "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" @@ -3065,23 +3115,27 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -"mime-db@>= 1.27.0 < 2", mime-db@~1.27.0: - version "1.27.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" +"mime-db@>= 1.29.0 < 2": + version "1.30.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" -mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7: - version "2.1.15" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" +mime-db@~1.29.0: + version "1.29.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.29.0.tgz#48d26d235589651704ac5916ca06001914266878" + +mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.7: + version "2.1.16" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.16.tgz#2b858a52e5ecd516db897ac2be87487830698e23" dependencies: - mime-db "~1.27.0" + mime-db "~1.29.0" mime@1.3.4: version "1.3.4" resolved "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" mime@^1.3.4: - version "1.3.6" - resolved "https://registry.npmjs.org/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0" + version "1.4.0" + resolved "https://registry.npmjs.org/mime/-/mime-1.4.0.tgz#69e9e0db51d44f2a3b56e48b7817d7d137f1a343" mimic-fn@^1.0.0: version "1.1.0" @@ -3101,7 +3155,7 @@ minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@0.0.8, minimist@~0.0.1: +minimist@0.0.8: version "0.0.8" resolved "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -3113,6 +3167,10 @@ minimist@1.2.0, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + mixin-deep@^1.1.3: version "1.2.0" resolved "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.2.0.tgz#d02b8c6f8b6d4b8f5982d3fd009c4919851c3fe2" @@ -3124,7 +3182,7 @@ mkdirp@0.3.x: version "0.3.5" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" -mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -3171,17 +3229,17 @@ multicast-dns@^6.0.1: dns-packet "^1.0.1" thunky "^0.1.0" -mz@^2.4.0: - version "2.6.0" - resolved "https://registry.npmjs.org/mz/-/mz-2.6.0.tgz#c8b8521d958df0a4f2768025db69c719ee4ef1ce" +mz@2.4.0: + version "2.4.0" + resolved "https://registry.npmjs.org/mz/-/mz-2.4.0.tgz#987ba9624d89395388c37cb4741e2caf4dd13b1a" dependencies: any-promise "^1.0.0" object-assign "^4.0.1" thenify-all "^1.0.0" nan@^2.3.0: - version "2.6.2" - resolved "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45" + version "2.7.0" + resolved "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46" nanomatch@^1.2.0: version "1.2.0" @@ -3285,9 +3343,15 @@ node-pre-gyp@^0.6.36: tar "^2.2.1" tar-pack "^3.4.0" -node-source-walk@^3.0.0, node-source-walk@^3.2.0: - version "3.2.1" - resolved "https://registry.npmjs.org/node-source-walk/-/node-source-walk-3.2.1.tgz#12bb1b9a3fb1873f785c4a7547b6034f953f669f" +node-source-walk@3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/node-source-walk/-/node-source-walk-3.2.0.tgz#3c605cc53abdee4b45ab65e947dfb1db7c90f0e3" + dependencies: + babylon "~6.8.1" + +node-source-walk@^3.0.0, node-source-walk@^3.2.0, node-source-walk@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/node-source-walk/-/node-source-walk-3.3.0.tgz#ad18e35bfdb3d0b6f7e0e4aff1e78f846a3b8873" dependencies: babylon "^6.17.0" @@ -3317,7 +3381,7 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.1: +normalize-path@^2.0.0, normalize-path@^2.0.1: version "2.1.1" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" dependencies: @@ -3336,14 +3400,16 @@ normalize-url@^1.4.0: query-string "^4.1.0" sort-keys "^1.0.0" -npm-run-all@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.0.2.tgz#a84669348e6db6ccbe052200b4cdb6bfe034a4fe" +npm-run-all@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.1.tgz#3095cf3f3cacf57fcb662b210ab10c609af6ddbb" dependencies: - chalk "^1.1.3" - cross-spawn "^5.0.1" - minimatch "^3.0.2" - ps-tree "^1.0.1" + ansi-styles "^3.2.0" + chalk "^2.1.0" + cross-spawn "^5.1.0" + memory-streams "^0.1.2" + minimatch "^3.0.4" + ps-tree "^1.1.0" read-pkg "^2.0.0" shell-quote "^1.6.1" string.prototype.padend "^3.0.0" @@ -3377,9 +3443,9 @@ number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" -nyc@^11.0.3: - version "11.1.0" - resolved "https://registry.npmjs.org/nyc/-/nyc-11.1.0.tgz#d6b3c5e16892a25af63138ba484676aa8a22eda7" +nyc@^11.2.0: + version "11.2.0" + resolved "https://registry.npmjs.org/nyc/-/nyc-11.2.0.tgz#8bfe9a3ddb7be5c5f9fa5ab6e6949c18b7d34ca1" dependencies: archy "^1.0.0" arrify "^1.0.1" @@ -3393,7 +3459,7 @@ nyc@^11.0.3: glob "^7.0.6" istanbul-lib-coverage "^1.1.1" istanbul-lib-hook "^1.0.7" - istanbul-lib-instrument "^1.7.4" + istanbul-lib-instrument "^1.8.0" istanbul-lib-report "^1.1.1" istanbul-lib-source-maps "^1.2.1" istanbul-reports "^1.1.1" @@ -3417,10 +3483,6 @@ object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" -object-assign@~4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz#99504456c3598b5cad4fc59c26e8a9bb107fe0bd" - object-copy@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" @@ -3447,10 +3509,10 @@ object.omit@^2.0.0: is-extendable "^0.1.1" object.pick@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/object.pick/-/object.pick-1.2.0.tgz#b5392bee9782da6d9fb7d6afaf539779f1234c2b" + version "1.3.0" + resolved "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" dependencies: - isobject "^2.1.0" + isobject "^3.0.1" obuf@^1.0.0, obuf@^1.1.1: version "1.1.1" @@ -3533,10 +3595,10 @@ os-locale@^1.4.0: lcid "^1.0.0" os-locale@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/os-locale/-/os-locale-2.0.0.tgz#15918ded510522b81ee7ae5a309d54f639fc39a4" + version "2.1.0" + resolved "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" dependencies: - execa "^0.5.0" + execa "^0.7.0" lcid "^1.0.0" mem "^1.1.0" @@ -3681,8 +3743,8 @@ pause-stream@0.0.11: through "~2.3" pbkdf2@^3.0.3: - version "3.0.12" - resolved "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.12.tgz#be36785c5067ea48d806ff923288c5f750b6b8a2" + version "3.0.13" + resolved "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.13.tgz#c37d295531e786b1da3e3eadc840426accb0ae25" dependencies: create-hash "^1.1.2" create-hmac "^1.1.4" @@ -3977,28 +4039,29 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0 supports-color "^3.2.3" postcss@^6.0.1: - version "6.0.7" - resolved "https://registry.npmjs.org/postcss/-/postcss-6.0.7.tgz#6a097477c46d13d0560a817d69abc0bae549d0a0" + version "6.0.10" + resolved "https://registry.npmjs.org/postcss/-/postcss-6.0.10.tgz#c311b89734483d87a91a56dc9e53f15f4e6e84e4" dependencies: - chalk "^2.0.1" - source-map "^0.5.6" - supports-color "^4.2.0" + chalk "^2.1.0" + source-map "^0.5.7" + supports-color "^4.2.1" -precinct@^3.6.0: - version "3.6.0" - resolved "https://registry.npmjs.org/precinct/-/precinct-3.6.0.tgz#7d8f857e6737533332f429e7760c0e28ab1063f6" +precinct@^3.8.0: + version "3.8.0" + resolved "https://registry.npmjs.org/precinct/-/precinct-3.8.0.tgz#259a9490a85477a1f26989fbdf2fb26c44f2475a" dependencies: - commander "^2.9.0" - debug "^2.2.0" + commander "^2.11.0" + debug "^3.0.1" detective-amd "^2.4.0" detective-cjs "^2.0.0" - detective-es6 "^1.1.6" + detective-es6 "^1.2.0" detective-less "1.0.0" detective-sass "^2.0.0" detective-scss "^1.0.0" detective-stylus "^1.0.0" + detective-typescript "^1.0.0" module-definition "^2.2.4" - node-source-walk "^3.2.0" + node-source-walk "^3.3.0" prelude-ls@~1.1.2: version "1.1.2" @@ -4035,18 +4098,18 @@ process@^0.11.0: version "0.11.10" resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" -proxy-addr@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz#27e545f6960a44a627d9b44467e35c1b6b4ce2f3" +proxy-addr@~1.1.5: + version "1.1.5" + resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz#71c0ee3b102de3f202f3b64f608d173fcba1a918" dependencies: forwarded "~0.1.0" - ipaddr.js "1.3.0" + ipaddr.js "1.4.0" prr@~0.0.0: version "0.0.0" resolved "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" -ps-tree@^1.0.1: +ps-tree@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz#b421b24140d6203f1ed3c76996b4427b08e8c014" dependencies: @@ -4078,14 +4141,18 @@ q@^1.1.2: version "1.5.0" resolved "https://registry.npmjs.org/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1" -qs@6.4.0, qs@~6.4.0: - version "6.4.0" - resolved "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" +qs@6.5.0: + version "6.5.0" + resolved "https://registry.npmjs.org/qs/-/qs-6.5.0.tgz#8d04954d364def3efc55b5a0793e1e2c8b1e6e49" qs@~6.3.0: version "6.3.2" resolved "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + query-string@^4.1.0: version "4.3.4" resolved "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" @@ -4130,7 +4197,16 @@ raw-loader@^0.5.1: version "0.5.1" resolved "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" -rc@^1.1.6, rc@^1.1.7: +rc@1.1.6: + version "1.1.6" + resolved "https://registry.npmjs.org/rc/-/rc-1.1.6.tgz#43651b76b6ae53b5c802f1151fa3fc3b059969c9" + dependencies: + deep-extend "~0.4.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~1.0.4" + +rc@^1.1.7: version "1.2.1" resolved "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95" dependencies: @@ -4169,7 +4245,7 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -readable-stream@1.0: +readable-stream@1.0, readable-stream@~1.0.2: version "1.0.34" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" dependencies: @@ -4230,9 +4306,9 @@ regenerate@^1.2.1: version "1.3.2" resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260" -regenerator-runtime@^0.10.0: - version "0.10.5" - resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" +regenerator-runtime@^0.11.0: + version "0.11.0" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1" regex-cache@^0.4.2: version "0.4.3" @@ -4274,8 +4350,8 @@ relateurl@0.2.x: resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" remove-trailing-separator@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz#69b062d978727ad14dc6b56ba4ab772fd8d70511" + version "1.1.0" + resolved "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" renderkid@^2.0.1: version "2.0.1" @@ -4377,7 +4453,7 @@ requires-port@1.0.x, requires-port@1.x.x: version "1.0.0" resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" -resolve-dependency-path@~1.0.2: +resolve-dependency-path@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/resolve-dependency-path/-/resolve-dependency-path-1.0.2.tgz#6abe93a6de3e4f9dce7b5e8261e1f47aa1af4dc2" @@ -4389,16 +4465,12 @@ resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" -resolve@^1.1.6, resolve@^1.3.2: - version "1.3.3" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2: + version "1.4.0" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" dependencies: path-parse "^1.0.5" -resolve@~1.1.7: - version "1.1.7" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" - restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -4429,13 +4501,13 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^2.0.0" inherits "^2.0.1" -rxjs@^5.4.2: - version "5.4.2" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-5.4.2.tgz#2a3236fcbf03df57bae06fd6972fd99e5c08fcf7" +rxjs@^5.4.3: + version "5.4.3" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz#0758cddee6033d68e0fd53676f0f3596ce3d483f" dependencies: symbol-observable "^1.0.1" -safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -4443,9 +4515,9 @@ samsam@1.x, samsam@^1.1.3: version "1.2.1" resolved "https://registry.npmjs.org/samsam/-/samsam-1.2.1.tgz#edd39093a3184370cb859243b2bdf255e7d8ea67" -sass-lookup@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/sass-lookup/-/sass-lookup-1.0.2.tgz#c15c4f7261dc2adb11b3d5912ded6b08a14b21c8" +sass-lookup@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/sass-lookup/-/sass-lookup-1.1.0.tgz#da44a21beea5955f14effdb81bdee2751b6d15e2" dependencies: commander "~2.8.1" is-relative-path "~1.0.0" @@ -4465,27 +4537,27 @@ select-hose@^2.0.0: resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" selfsigned@^1.9.1: - version "1.9.1" - resolved "https://registry.npmjs.org/selfsigned/-/selfsigned-1.9.1.tgz#cdda4492d70d486570f87c65546023558e1dfa5a" + version "1.10.1" + resolved "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.1.tgz#bf8cb7b83256c4551e31347c6311778db99eec52" dependencies: node-forge "0.6.33" "semver@2 || 3 || 4 || 5", semver@^5.3.0: - version "5.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + version "5.4.1" + resolved "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" -send@0.15.3: - version "0.15.3" - resolved "https://registry.npmjs.org/send/-/send-0.15.3.tgz#5013f9f99023df50d1bd9892c19e3defd1d53309" +send@0.15.4: + version "0.15.4" + resolved "https://registry.npmjs.org/send/-/send-0.15.4.tgz#985faa3e284b0273c793364a35c6737bd93905b9" dependencies: - debug "2.6.7" - depd "~1.1.0" + debug "2.6.8" + depd "~1.1.1" destroy "~1.0.4" encodeurl "~1.0.1" escape-html "~1.0.3" etag "~1.8.0" fresh "0.5.0" - http-errors "~1.6.1" + http-errors "~1.6.2" mime "1.3.4" ms "2.0.0" on-finished "~2.3.0" @@ -4508,14 +4580,14 @@ serve-index@^1.7.2: mime-types "~2.1.15" parseurl "~1.3.1" -serve-static@1.12.3: - version "1.12.3" - resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz#9f4ba19e2f3030c547f8af99107838ec38d5b1e2" +serve-static@1.12.4: + version "1.12.4" + resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.12.4.tgz#9b6aa98eeb7253c4eedc4c1f6fdbca609901a961" dependencies: encodeurl "~1.0.1" escape-html "~1.0.3" parseurl "~1.3.1" - send "0.15.3" + send "0.15.4" set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" @@ -4593,13 +4665,13 @@ signal-exit@^3.0.0, signal-exit@^3.0.1, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" -sinon-chai@^2.11.0: - version "2.12.0" - resolved "https://registry.npmjs.org/sinon-chai/-/sinon-chai-2.12.0.tgz#da71e9642ef7b893ba3cf2af806396a00aa45927" +sinon-chai@^2.13.0: + version "2.13.0" + resolved "https://registry.npmjs.org/sinon-chai/-/sinon-chai-2.13.0.tgz#b9a42e801c20234bfc2f43b29e6f4f61b60990c4" -sinon@3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/sinon/-/sinon-3.0.0.tgz#f6919755c8c705e0b4ae977e8351bbcbaf6d91de" +sinon@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/sinon/-/sinon-3.2.1.tgz#d8adabd900730fd497788a027049c64b08be91c2" dependencies: diff "^3.1.0" formatio "1.2.0" @@ -4648,16 +4720,16 @@ sntp@1.x.x: dependencies: hoek "2.x.x" -sockjs-client@1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.2.tgz#f0212a8550e4c9468c8cceaeefd2e3493c033ad5" +sockjs-client@1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12" dependencies: - debug "^2.2.0" + debug "^2.6.6" eventsource "0.1.6" faye-websocket "~0.11.0" inherits "^2.0.1" json3 "^3.3.2" - url-parse "^1.1.1" + url-parse "^1.1.8" sockjs@0.3.18: version "0.3.18" @@ -4672,10 +4744,6 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -source-list-map@^0.1.7: - version "0.1.8" - resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" - source-list-map@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" @@ -4698,8 +4766,8 @@ source-map-resolve@^0.5.0: urix "^0.1.0" source-map-support@^0.4.0, source-map-support@^0.4.15: - version "0.4.15" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1" + version "0.4.16" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.16.tgz#16fecf98212467d017d586a2af68d628b9421cd8" dependencies: source-map "^0.5.6" @@ -4707,9 +4775,9 @@ source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" -source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3: - version "0.5.6" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" +source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: + version "0.5.7" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" source-map@^0.4.4: version "0.4.4" @@ -4922,6 +4990,10 @@ strip-json-comments@^2.0.0, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" +strip-json-comments@~1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" + style-loader@^0.18.2: version "0.18.2" resolved "https://registry.npmjs.org/style-loader/-/style-loader-0.18.2.tgz#cc31459afbcd6d80b7220ee54b291a9fd66ff5eb" @@ -4941,15 +5013,21 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.1.2, supports-color@^3.2.3: +supports-color@^3.1.1, supports-color@^3.1.2, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" dependencies: has-flag "^1.0.0" -supports-color@^4.0.0, supports-color@^4.2.0, supports-color@~4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-4.2.0.tgz#ad986dc7eb2315d009b4d77c8169c2231a684037" +supports-color@^4.0.0, supports-color@^4.2.1: + version "4.3.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-4.3.0.tgz#0fa3755bb961136cf75ff2ee3eb775822c04a31b" + dependencies: + has-flag "^2.0.0" + +supports-color@~4.2.1: + version "4.2.1" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-4.2.1.tgz#65a4bb2631e90e02420dba5554c375a4754bb836" dependencies: has-flag "^2.0.0" @@ -4969,9 +5047,9 @@ symbol-observable@^1.0.1: version "1.0.4" resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" -tapable@^0.2.5, tapable@~0.2.5: - version "0.2.6" - resolved "https://registry.npmjs.org/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d" +tapable@^0.2.5, tapable@^0.2.7: + version "0.2.8" + resolved "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" tar-pack@^3.4.0: version "3.4.0" @@ -5028,35 +5106,39 @@ through@2, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" -thunks@~4.8.0: - version "4.8.0" - resolved "https://registry.npmjs.org/thunks/-/thunks-4.8.0.tgz#63d5fff7f6a957d6953c46dc72829dd5462a8ab4" +thunks@~4.8.1: + version "4.8.1" + resolved "https://registry.npmjs.org/thunks/-/thunks-4.8.1.tgz#c9678b370bbaf54f369ea9fdbce2af99f3ae3ec9" thunky@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e" +time-stamp@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357" + timers-browserify@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.2.tgz#ab4883cf597dcd50af211349a00fbca56ac86b86" + version "2.0.4" + resolved "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.4.tgz#96ca53f4b794a5e7c0e1bd7cc88a372298fa01e6" dependencies: setimmediate "^1.0.4" -tman@^1.7.1: - version "1.7.1" - resolved "https://registry.npmjs.org/tman/-/tman-1.7.1.tgz#e6d65286b4c9a9fe044f69af32199481b772fb48" +tman@^1.7.2: + version "1.7.2" + resolved "https://registry.npmjs.org/tman/-/tman-1.7.2.tgz#9f1a7842f49e817b09c288337fc3944ee63edf12" dependencies: commander "~2.11.0" diff "~3.3.0" glob "~7.1.2" - supports-color "~4.2.0" - thunks "~4.8.0" + supports-color "~4.2.1" + thunks "~4.8.1" to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" -to-fast-properties@^1.0.1: +to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" @@ -5111,9 +5193,9 @@ trim-right@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" -ts-node@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/ts-node/-/ts-node-3.2.0.tgz#9814f0c0141784900cf12fef1197ad4b7f4d23d1" +ts-node@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-3.3.0.tgz#c13c6a3024e30be1180dd53038fc209289d4bf69" dependencies: arrify "^1.0.0" chalk "^2.0.0" @@ -5123,7 +5205,7 @@ ts-node@^3.2.0: mkdirp "^0.5.1" source-map-support "^0.4.0" tsconfig "^6.0.0" - v8flags "^2.0.11" + v8flags "^3.0.0" yn "^2.0.0" tsconfig@^6.0.0: @@ -5155,9 +5237,9 @@ tslint-loader@^3.5.3: rimraf "^2.4.4" semver "^5.3.0" -tslint@^5.5.0: - version "5.5.0" - resolved "https://registry.npmjs.org/tslint/-/tslint-5.5.0.tgz#10e8dab3e3061fa61e9442e8cee3982acf20a6aa" +tslint@^5.7.0: + version "5.7.0" + resolved "https://registry.npmjs.org/tslint/-/tslint-5.7.0.tgz#c25e0d0c92fa1201c2bc30e844e08e682b4f3552" dependencies: babel-code-frame "^6.22.0" colors "^1.1.2" @@ -5168,15 +5250,15 @@ tslint@^5.5.0: resolve "^1.3.2" semver "^5.3.0" tslib "^1.7.1" - tsutils "^2.5.1" + tsutils "^2.8.1" tsutils@^1.4.0: version "1.9.1" resolved "https://registry.npmjs.org/tsutils/-/tsutils-1.9.1.tgz#b9f9ab44e55af9681831d5f28d0aeeaf5c750cb0" -tsutils@^2.5.1: - version "2.7.1" - resolved "https://registry.npmjs.org/tsutils/-/tsutils-2.7.1.tgz#411a0e9466525a2b2869260a55620d7292155e24" +tsutils@^2.8.1: + version "2.8.2" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-2.8.2.tgz#2c1486ba431260845b0ac6f902afd9d708a8ea6a" dependencies: tslib "^1.7.1" @@ -5204,10 +5286,6 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/type-detect/-/type-detect-3.0.0.tgz#46d0cc8553abb7b13a352b0d6dea2fd58f2d9b55" - type-detect@^4.0.0: version "4.0.3" resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz#0e3f2670b44099b0b46c284d136a7ef49c74c2ea" @@ -5219,15 +5297,30 @@ type-is@~1.6.15: media-typer "0.3.0" mime-types "~2.1.15" +typescript-eslint-parser@1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-1.0.2.tgz#fd2abacf2ee3d9382ab3e449c8762b6beae4d0d7" + dependencies: + lodash.unescape "4.0.0" + object-assign "^4.0.1" + +typescript@2.0.10: + version "2.0.10" + resolved "https://registry.npmjs.org/typescript/-/typescript-2.0.10.tgz#ccdd4ed86fd5550a407101a0814012e1b3fac3dd" + typescript@^2.4.2: - version "2.4.2" - resolved "https://registry.npmjs.org/typescript/-/typescript-2.4.2.tgz#f8395f85d459276067c988aa41837a8f82870844" + version "2.5.1" + resolved "https://registry.npmjs.org/typescript/-/typescript-2.5.1.tgz#ce7cc93ada3de19475cc9d17e3adea7aee1832aa" + +typescript@^2.5.2: + version "2.5.2" + resolved "https://registry.npmjs.org/typescript/-/typescript-2.5.2.tgz#038a95f7d9bbb420b1bf35ba31d4c5c1dd3ffe34" uglify-js@3.0.x: - version "3.0.25" - resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.0.25.tgz#3dc190b0ee437497e449bc6f785665b06afbe052" + version "3.0.28" + resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.0.28.tgz#96b8495f0272944787b5843a1679aa326640d5f7" dependencies: - commander "~2.9.0" + commander "~2.11.0" source-map "~0.5.1" uglify-js@^2.6, uglify-js@^2.8.29: @@ -5312,7 +5405,7 @@ url-parse@1.0.x: querystringify "0.0.x" requires-port "1.0.x" -url-parse@^1.1.1: +url-parse@^1.1.8: version "1.1.9" resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.1.9.tgz#c67f1d775d51f0a18911dd7b3ffad27bb9e5bd19" dependencies: @@ -5368,9 +5461,9 @@ uuid@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" -v8flags@^2.0.11: - version "2.1.1" - resolved "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" +v8flags@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/v8flags/-/v8flags-3.0.0.tgz#4be9604488e0c4123645def705b1848d16b8e01f" dependencies: user-home "^1.1.1" @@ -5389,11 +5482,13 @@ vendors@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" -verror@1.3.6: - version "1.3.6" - resolved "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" +verror@1.10.0: + version "1.10.0" + resolved "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" dependencies: - extsprintf "1.0.2" + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" vm-browserify@0.0.4: version "0.0.4" @@ -5401,7 +5496,7 @@ vm-browserify@0.0.4: dependencies: indexof "0.0.1" -walkdir@^0.0.11: +walkdir@0.0.11: version "0.0.11" resolved "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz#a16d025eb931bd03b52f308caed0f40fcebe9532" @@ -5420,17 +5515,18 @@ wbuf@^1.1.0, wbuf@^1.7.2: minimalistic-assert "^1.0.0" webpack-dev-middleware@^1.11.0: - version "1.11.0" - resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.11.0.tgz#09691d0973a30ad1f82ac73a12e2087f0a4754f9" + version "1.12.0" + resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.12.0.tgz#d34efefb2edda7e1d3b5dbe07289513219651709" dependencies: memory-fs "~0.4.1" mime "^1.3.4" path-is-absolute "^1.0.0" range-parser "^1.0.3" + time-stamp "^2.0.0" -webpack-dev-server@^2.5.1: - version "2.5.1" - resolved "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.5.1.tgz#a02e726a87bb603db5d71abb7d6d2649bf10c769" +webpack-dev-server@^2.7.1: + version "2.7.1" + resolved "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.7.1.tgz#21580f5a08cd065c71144cf6f61c345bca59a8b8" dependencies: ansi-html "0.0.7" bonjour "^3.5.0" @@ -5442,12 +5538,14 @@ webpack-dev-server@^2.5.1: html-entities "^1.2.0" http-proxy-middleware "~0.17.4" internal-ip "^1.2.0" + ip "^1.1.5" + loglevel "^1.4.1" opn "4.0.2" portfinder "^1.0.9" selfsigned "^1.9.1" serve-index "^1.7.2" sockjs "0.3.18" - sockjs-client "1.1.2" + sockjs-client "1.1.4" spdy "^3.4.1" strip-ansi "^3.0.0" supports-color "^3.1.1" @@ -5461,16 +5559,16 @@ webpack-sources@^1.0.1: source-list-map "^2.0.0" source-map "~0.5.3" -webpack@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/webpack/-/webpack-3.3.0.tgz#ce2f9e076566aba91f74887133a883fd7da187bc" +webpack@^3.5.5: + version "3.5.5" + resolved "https://registry.npmjs.org/webpack/-/webpack-3.5.5.tgz#3226f09fc8b3e435ff781e7af34f82b68b26996c" dependencies: acorn "^5.0.0" acorn-dynamic-import "^2.0.0" ajv "^5.1.5" ajv-keywords "^2.0.0" async "^2.1.2" - enhanced-resolve "^3.3.0" + enhanced-resolve "^3.4.0" escope "^3.6.0" interpret "^1.0.0" json-loader "^0.5.4" @@ -5481,12 +5579,12 @@ webpack@^3.3.0: mkdirp "~0.5.0" node-libs-browser "^2.0.0" source-map "^0.5.3" - supports-color "^3.1.0" - tapable "~0.2.5" + supports-color "^4.2.1" + tapable "^0.2.7" uglifyjs-webpack-plugin "^0.4.6" watchpack "^1.4.0" webpack-sources "^1.0.1" - yargs "^6.0.0" + yargs "^8.0.2" websocket-driver@>=0.5.1: version "0.6.5" @@ -5511,8 +5609,8 @@ which-module@^2.0.0: resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" which@^1.2.4, which@^1.2.9: - version "1.2.14" - resolved "https://registry.npmjs.org/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" + version "1.3.0" + resolved "https://registry.npmjs.org/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" dependencies: isexe "^2.0.0" @@ -5609,7 +5707,7 @@ yargs@^6.0.0: y18n "^3.2.1" yargs-parser "^4.2.0" -yargs@^8.0.1: +yargs@^8.0.1, yargs@^8.0.2: version "8.0.2" resolved "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" dependencies: From a07455d57946daaea5747ace7a1312a443c34083 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 6 Sep 2017 18:43:32 +0800 Subject: [PATCH 17/19] docs(QueryDescription): update docs, add fields description & update interface names --- docs/API-description/QueryDescription.md | 78 ++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/docs/API-description/QueryDescription.md b/docs/API-description/QueryDescription.md index dff1ba5b..77d4b31f 100644 --- a/docs/API-description/QueryDescription.md +++ b/docs/API-description/QueryDescription.md @@ -1,14 +1,14 @@ -## QueryDescription +## Query ```ts -interface QueryDescription extends ClauseDescription { - fields?: FieldsValue[] +interface Query extends Clause { + fields?: Field[] limit?: number skip?: number orderBy?: OrderDescription[] } -interface ClauseDescription { +interface Clause { where?: PredicateDescription } @@ -26,7 +26,7 @@ interface OrderDescription { fields - 查询哪些字段,数组,合法值为字符串或字面量对象 + 查询哪些字段,数组,合法值为字符串或字面量对象, 详细定义: fields limit @@ -80,6 +80,72 @@ interface OrderDescription { } ``` +

Fields

+ +### Description + +fields 是一个数组,其中的值可为 string | Field 它的形状为: + +```ts +type Field = [...keyof T[], VirtualField] + +type Field = { + [key in keyof T]: [...keyof (key in keyof T), Field] +} +``` +> 注,源码里面并没有 VirtualField 这个类型,这里只是为了描述它的形状而用 `VirtualField` 来表达 + +其中,可以包含一个 `VirtualField` 类型的元素,用来查询`关联表`的数据,它必须位于数组的最后,不然会抛出一个类型错误。 +`VirtualField` 的每一个字段的 key 都对应着查询的数据表上对应的 schema 上定义了 `virtual` 的字段,对应的值则是对应的 `Table` 的形状。 + +### example: + +```ts +// query +database.get('Task', { + fields: ['_id', 'content', { + project: ['_id', 'name', { + organization: ['_id'] + }] + }] +}) + +// schema definations +database.defineSchema('Task', { + _id: { + type: RDBType.string, + primaryKey: true + }, + project: { + type: Relationship.oneToOne, + virtual: { + name: 'Project', + where: ref => { + return { + _projectId: ref._id + } + } + } + }, +}) + +database.defineSchema('Project', { + _id: { + type: RDBType.string, + primaryKey: true + }, + organization: { + type: Relationship.oneToOne, + virtual: { + name: 'Organization', + where: (organizationTable) => ({ + _organizationId: organizationTable._id + }) + } + } +}) +``` +

OrderDescription

@@ -167,7 +233,7 @@ interface PredicateMeta { } } ``` -默认表示 `lf.op.and(taskTable.dueDate.lte(...), taskTable.startDate.gte(...) )` +默认表示 `dueDate` 小于或等于七天后 ***并且*** `startDate` 大于或等于一天后 ### *example:* ```ts From d78d3a17c24fd561a2c9b349e7514abd377618b8 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 7 Sep 2017 14:30:36 +0800 Subject: [PATCH 18/19] chore: code cleanup feat(Database): Only navigation properties in query now valid --- src/exception/database.ts | 3 -- src/interface/index.ts | 5 +-- src/storage/Database.ts | 46 ++++++++------------- src/storage/modules/Selector.ts | 12 +++--- test/specs/storage/Database.public.spec.ts | 21 ++-------- test/specs/storage/modules/Selector.spec.ts | 5 +-- 6 files changed, 29 insertions(+), 63 deletions(-) diff --git a/src/exception/database.ts b/src/exception/database.ts index cdba490c..fc97919e 100644 --- a/src/exception/database.ts +++ b/src/exception/database.ts @@ -6,9 +6,6 @@ export const NonExistentTable = export const UnmodifiableTable = () => new ReactiveDBException(`Method: defineSchema cannot be invoked since schema is existed or database is connected`) -export const InvalidQuery = - () => new ReactiveDBException('Only navigation properties were included in query.') - export const AliasConflict = (column: string, tableName: string) => new ReactiveDBException(`Definition conflict, Column: \`${column}\` on table: ${tableName}.`) diff --git a/src/interface/index.ts b/src/interface/index.ts index c399fc76..6877ecd5 100644 --- a/src/interface/index.ts +++ b/src/interface/index.ts @@ -130,10 +130,7 @@ export type SchemaDisposeFunction = export interface ShapeMatcher { mainTable: lf.schema.Table - pk: { - name: string, - queried: boolean - } + pk: string definition: Object } diff --git a/src/storage/Database.ts b/src/storage/Database.ts index 2a54bcab..0b1742bd 100644 --- a/src/storage/Database.ts +++ b/src/storage/Database.ts @@ -173,7 +173,7 @@ export class Database { } return this.database$ - .concatMap(db => { + .concatMap(db => { const entity = clone(raw) const [ table ] = Database.getTables(db, tableName) const columnMapper = schema!.mapper @@ -290,7 +290,7 @@ export class Database { } const queries: lf.query.Builder[] = [] - const removedIds: any = [] + const removedIds: string[] = [] queries.push(predicatableQuery(db, table, predicate!, StatementType.Delete)) const prefetch = predicatableQuery(db, table, predicate!, StatementType.Select) @@ -433,7 +433,6 @@ export class Database { const containFields = !!clause.fields let predicate: Predicate - const containKey = containFields ? contains(pk, clause.fields!) : true const [ additionJoinInfo, err ] = !clause.where ? [ null, null ] : tryCatch(() => { predicate = parsePredicate(clause.where!) @@ -444,24 +443,30 @@ export class Database { warn('Build addition join info from predicate failed', err.message) } - const fields = containFields ? clause.fields : Array.from(schema.columns.keys()) + let fields: Field[] + if (containFields) { + fields = [ schema.pk, ...clause.fields! ] + } else { + fields = Array.from(schema.columns.keys()) + } if (containFields && additionJoinInfo) { - mergeFields(fields!, additionJoinInfo) + mergeFields(fields, additionJoinInfo) } - const fieldsSet: Set = new Set(fields) + const fieldsSet = new Set(fields) + const tablesStruct: TablesStruct = Object.create(null) const { table, columns, joinInfo, definition, contextName } = - this.traverseQueryFields(db, tableName, fieldsSet, containKey, !containFields, [], {}, tablesStruct, mode) + this.traverseQueryFields(db, tableName, fieldsSet, !containFields, [], {}, tablesStruct, mode) const query = predicatableQuery(db, table!, null, StatementType.Select, ...columns) joinInfo.forEach((info: JoinInfo) => { - const pred = info.predicate - if (pred) { - query.leftOuterJoin(info.table, pred) + const joinPredicate = info.predicate + if (joinPredicate) { + query.leftOuterJoin(info.table, joinPredicate) } }) @@ -475,10 +480,7 @@ export class Database { }) const matcher = { - pk: { - name: pk, - queried: containKey - }, + pk, definition, mainTable: table! } @@ -533,7 +535,6 @@ export class Database { db: lf.Database, tableName: string, fieldsValue: Set, - hasKey: boolean, glob: boolean, path: string[] = [], context: Record = {}, @@ -560,17 +561,6 @@ export class Database { navigators.push(nav) }) - const onlyNavigator = Array.from(fieldsValue.keys()) - .every(key => contains(key, navigators)) - assert(!onlyNavigator, Exception.InvalidQuery()) - - if (!hasKey) { - // 保证主键一定比关联字段更早的被遍历到 - const fields = Array.from(fieldsValue) - fields.unshift(schema.pk) - fieldsValue = new Set(fields) - } - const suffix = (context[tableName] || 0) + 1 context[tableName] = suffix const contextName = contextTableName(tableName, suffix) @@ -648,9 +638,9 @@ export class Database { handleAdvanced({ columns: [column], advanced: true }, ctx.key, columnDef) break case LeafType.navigator: - const { containKey, fields, assocaiation } = ctx.leaf as NavigatorLeaf + const { fields, assocaiation } = ctx.leaf as NavigatorLeaf const ret = - this.traverseQueryFields(db, assocaiation.name, new Set(fields), containKey, glob, path.slice(0), context, tablesStruct, mode) + this.traverseQueryFields(db, assocaiation.name, new Set(fields), glob, path.slice(0), context, tablesStruct, mode) handleAdvanced(ret, ctx.key, assocaiation) ctx.skip() break diff --git a/src/storage/modules/Selector.ts b/src/storage/modules/Selector.ts index ebe3635c..cd31ba51 100644 --- a/src/storage/modules/Selector.ts +++ b/src/storage/modules/Selector.ts @@ -126,7 +126,7 @@ export class Selector { } const { pk, mainTable } = this.shape - const column = mainTable[pk.name] + const column = mainTable[pk] const rangeQuery = predicatableQuery(this.db, mainTable, predicate, StatementType.Select, column) if (this.orderDescriptions && this.orderDescriptions.length) { @@ -187,7 +187,7 @@ export class Selector { values(): Observable | never { if (typeof this.limit !== 'undefined' || typeof this.skip !== 'undefined') { const p = this.rangeQuery.exec() - .then(r => r.map(v => v[this.shape.pk.name])) + .then(r => r.map(v => v[this.shape.pk])) .then(pks => this.getValue(this.getQuery(this.inPKs(pks)))) return this.mapFn(Observable.fromPromise(p)) } else { @@ -229,15 +229,15 @@ export class Selector { private inPKs(pks: (string | number)[]): lf.Predicate { const { pk, mainTable } = this.shape - return mainTable[pk.name].in(pks) + return mainTable[pk].in(pks) } private getValue(query: lf.query.Select) { return query.exec() .then((rows: any[]) => { const result = graph(rows, this.shape.definition) - const col = this.shape.pk.name - return !this.shape.pk.queried ? this.removeKey(result, col) : result + const col = this.shape.pk + return !this.shape.pk ? this.removeKey(result, col) : result }) } @@ -276,7 +276,7 @@ export class Selector { const listener = () => { return rangeQuery.exec() .then((r) => { - observer.next(r.map(v => v[this.shape.pk.name])) + observer.next(r.map(v => v[this.shape.pk])) }) .catch(e => observer.error(e)) } diff --git a/test/specs/storage/Database.public.spec.ts b/test/specs/storage/Database.public.spec.ts index f90ba660..5dbf8739 100644 --- a/test/specs/storage/Database.public.spec.ts +++ b/test/specs/storage/Database.public.spec.ts @@ -11,7 +11,7 @@ import { TestFixture2 } from '../../schemas/Test' import { scenarioGen, programGen, postGen, taskGen, subtaskGen } from '../../utils/generators' import { RDBType, DataStoreType, Database, clone, forEach, JoinMode, Logger } from '../../index' import { TaskSchema, ProjectSchema, PostSchema, ModuleSchema, ProgramSchema, SubtaskSchema, OrganizationSchema, TasklistSchema } from '../../index' -import { InvalidQuery, NonExistentTable, InvalidType, PrimaryKeyNotProvided, NotConnected, Selector, AssociatedFieldsPostionError } from '../../index' +import { NonExistentTable, InvalidType, PrimaryKeyNotProvided, NotConnected, Selector, AssociatedFieldsPostionError } from '../../index' use(SinonChai) @@ -543,7 +543,7 @@ export default describe('Database Testcase: ', () => { expect(result).to.deep.equal(innerTarget) }) - it('should get value by deep nested Association query without association fields', function* () { + it('should get value by deep nested Association query without association fields #1', function* () { const fields = ['_id', 'content'] const queryToken = database.get('Task', { fields, @@ -557,7 +557,7 @@ export default describe('Database Testcase: ', () => { expect(result.project.organization._id).to.equal(innerTarget.project._organizationId) }) - it('should get value by deep nested Association query without association fields', function* () { + it('should get value by deep nested Association query without association fields #2', function* () { const fields = ['_id', 'content'] const queryToken = database.get('Task', { fields, @@ -695,21 +695,6 @@ export default describe('Database Testcase: ', () => { }) }) - it('should throw if only navigator was included in query', function* () { - - try { - yield database.get('Task', { - fields: [ 'project', 'subtasks' ], - where: { _id: innerTarget._id } - }).values() - - throw new Error('error path reached') - } catch (err) { - const standardErr = InvalidQuery() - expect(err.message).to.equal(standardErr.message) - } - }) - it('should throw if failed to build where-clause, and treat it as an empty where-clause', function* () { let result: any[] diff --git a/test/specs/storage/modules/Selector.spec.ts b/test/specs/storage/modules/Selector.spec.ts index e25ee3a7..db0e0ff8 100644 --- a/test/specs/storage/modules/Selector.spec.ts +++ b/test/specs/storage/modules/Selector.spec.ts @@ -78,10 +78,7 @@ export default describe('Selector test', () => { tableShape = { mainTable: table, - pk: { - queried: true, - name: '_id' - }, + pk: '_id', definition: { _id: { column: '_id', From 9e7db4a8bb326788ecfeb144270664903514f8de Mon Sep 17 00:00:00 2001 From: Saviio Date: Thu, 14 Sep 2017 16:52:05 +0800 Subject: [PATCH 19/19] chore: fix code-style --- src/exception/database.ts | 4 +- src/interface/enum.ts | 2 +- src/interface/index.ts | 2 +- src/shared/Traversable.ts | 2 +- src/storage/Database.ts | 208 ++-- src/storage/helper/check-associated-fields.ts | 15 + src/storage/helper/create-predicate.ts | 6 - src/storage/helper/index.ts | 6 +- src/storage/helper/parse-predicate.ts | 49 - src/storage/helper/table-struct.ts | 33 + src/storage/modules/PredicateProvider.ts | 94 +- src/storage/modules/ProxySelector.ts | 1 + src/storage/modules/Selector.ts | 3 +- src/utils/merge-fields.ts | 8 +- test/specs/index.ts | 1 - test/specs/storage/Database.public.spec.ts | 32 +- .../storage/helper/parse-predicate.spec.ts | 289 ----- test/specs/storage/modules/Mutation.spec.ts | 4 +- .../storage/modules/PredicateProvider.spec.ts | 1026 +++++++++++------ test/specs/utils/utils.spec.ts | 8 +- 20 files changed, 905 insertions(+), 888 deletions(-) create mode 100644 src/storage/helper/check-associated-fields.ts delete mode 100644 src/storage/helper/create-predicate.ts delete mode 100644 src/storage/helper/parse-predicate.ts create mode 100644 src/storage/helper/table-struct.ts delete mode 100644 test/specs/storage/helper/parse-predicate.spec.ts diff --git a/src/exception/database.ts b/src/exception/database.ts index fc97919e..41097fc2 100644 --- a/src/exception/database.ts +++ b/src/exception/database.ts @@ -39,8 +39,8 @@ export const DatabaseIsNotEmpty = export const NotConnected = () => new ReactiveDBException('Method: dispose cannnot be invoked before database is connected.') -export const FieldMustBeArray = +export const IncorrectFieldType = (field: any) => new ReactiveDBException(`Field must be Array, but got: ${ JSON.stringify(field) }`) -export const AssociatedFieldsPostionError = +export const IncorrectAssocFieldDescription = () => new ReactiveDBException(`Associated fields description must be the last item in Fields`) diff --git a/src/interface/enum.ts b/src/interface/enum.ts index ecbd0256..0f5a3663 100644 --- a/src/interface/enum.ts +++ b/src/interface/enum.ts @@ -31,7 +31,7 @@ export enum StatementType { } export enum JoinMode { - imlicit = 2001, + implicit = 2001, explicit = 2002 } diff --git a/src/interface/index.ts b/src/interface/index.ts index 6877ecd5..2c23d5d7 100644 --- a/src/interface/index.ts +++ b/src/interface/index.ts @@ -172,7 +172,7 @@ export type Predicate = { export { StatementType, JoinMode, LeafType, Relationship, DataStoreType, RDBType } -export interface TablesStruct { +export interface TableStruct { [index: string]: { table: lf.schema.Table contextName?: string diff --git a/src/shared/Traversable.ts b/src/shared/Traversable.ts index 93305dc3..b290ea45 100644 --- a/src/shared/Traversable.ts +++ b/src/shared/Traversable.ts @@ -1,7 +1,7 @@ import { forEach, getType, keys } from '../utils' import { TraverseContext } from '../interface' -export class Traversable { +export class Traversable { private ctxgen: (key: any, val: any, ctx: TraverseContext) => T | boolean diff --git a/src/storage/Database.ts b/src/storage/Database.ts index 0b1742bd..23cee6d8 100644 --- a/src/storage/Database.ts +++ b/src/storage/Database.ts @@ -7,14 +7,15 @@ import * as Exception from '../exception' import * as typeDefinition from './helper/definition' import Version from '../version' import { Traversable } from '../shared' -import { Mutation, Selector, QueryToken, PredicateProvider, checkPredicate } from './modules' +import { Mutation, Selector, QueryToken, PredicateProvider } from './modules' import { dispose, contextTableName, fieldIdentifier, hiddenColName } from './symbols' -import { forEach, clone, contains, tryCatch, hasOwn, getType, assert, identity, warn, keys as objKeys, mergeFields, concat } from '../utils' -import { createPredicate, createPkClause, mergeTransactionResult, predicatableQuery, lfFactory, parsePredicate } from './helper' +import { forEach, clone, contains, tryCatch, hasOwn, getType, assert, identity, warn, keys as objKeys, mergeFields } from '../utils' +import { checkAssociateFields, createPkClause, mergeTransactionResult, predicatableQuery, lfFactory, tableStruct } from './helper' import { Relationship, RDBType, DataStoreType, LeafType, StatementType, JoinMode } from '../interface/enum' -import { Record, Field, JoinInfo, Query, Predicate } from '../interface' -import { SchemaDef, ColumnDef, ParsedSchema, Association, ScopedHandler } from '../interface' -import { ColumnLeaf, NavigatorLeaf, ExecutorResult, UpsertContext, SelectContext, TablesStruct } from '../interface' +import { + Record, Field, JoinInfo, Query, Predicate, SchemaDef, ColumnDef, ParsedSchema, Association, ScopedHandler, + ColumnLeaf, NavigatorLeaf, ExecutorResult, UpsertContext, SelectContext, TableStruct +} from '../interface' export class Database { @@ -154,9 +155,9 @@ export class Database { }) } - get(tableName: string, query: Query = {}, mode: JoinMode = JoinMode.imlicit): QueryToken { - const checkResult = this.checkAssociateFields(query.fields) - assert(checkResult, Exception.AssociatedFieldsPostionError()) + get(tableName: string, query: Query = {}, mode: JoinMode = JoinMode.implicit): QueryToken { + const checkResult = checkAssociateFields(query.fields) + assert(checkResult, Exception.IncorrectAssocFieldDescription()) const selector$ = this.buildSelector(tableName, query, mode) return new QueryToken(selector$) } @@ -191,12 +192,9 @@ export class Database { }) const mut = { ...(entity as any), ...hiddenPayload } - const tables = this.buildTablesStructure(table) - const predicate = createPredicate(tables, tableName, clause) - - if (!predicate) { - warn(`The result of parsed Predicate is null, you are deleting all ${ tableName } Table!`) - } + const tables = tableStruct.build(table) + const predicate = PredicateProvider.create(tables, tableName, clause) + PredicateProvider.checkNull(predicate, tableName) const query = predicatableQuery(db, table, predicate!, StatementType.Update) @@ -224,7 +222,7 @@ export class Database { return this.database$ .concatMap(db => { const [ table ] = Database.getTables(db, tableName) - const tables = this.buildTablesStructure(table) + const tables = tableStruct.build(table) const column = table[pk!] const provider = new PredicateProvider(tables, tableName, clause) const prefetch = @@ -233,9 +231,7 @@ export class Database { return Observable.fromPromise(prefetch.exec()) .concatMap((scopedIds) => { const predicate = provider.getPredicate() - if (!predicate) { - warn(`The result of parsed Predicate is null, you are deleting all ${ tableName } Table!`) - } + PredicateProvider.checkNull(predicate, tableName) const query = predicatableQuery(db, table, predicate, StatementType.Delete) scopedIds.forEach((entity: any) => @@ -274,7 +270,7 @@ export class Database { } remove(tableName: string, clause: Predicate = {}): Observable { - const [schema, err] = tryCatch(this.findSchema)(tableName) + const [ schema, err ] = tryCatch(this.findSchema)(tableName) if (err) { return Observable.throw(err) } @@ -282,12 +278,9 @@ export class Database { return this.database$.concatMap((db) => { const [ table ] = Database.getTables(db, tableName) - const tables = this.buildTablesStructure(table) - const predicate = createPredicate(tables, tableName, clause) - - if (!predicate) { - warn(`The result of parsed Predicate is null, you are removing all ${ tableName } Tables!`) - } + const tables = tableStruct.build(table) + const predicate = PredicateProvider.create(tables, tableName, clause) + PredicateProvider.checkNull(predicate, tableName) const queries: lf.query.Builder[] = [] const removedIds: string[] = [] @@ -431,35 +424,33 @@ export class Database { const schema = this.findSchema(tableName) const pk = schema.pk const containFields = !!clause.fields - let predicate: Predicate - const [ additionJoinInfo, err ] = !clause.where ? [ null, null ] : - tryCatch(() => { - predicate = parsePredicate(clause.where!) - return this.buildJoinFieldsFromPredicate(predicate, tableName) - })() + const [ ret, err ] = !clause.where + ? [null, null] + : tryCatch(() => { + const builtPredicate = PredicateProvider.parse(clause.where!) + const extraJoinInfo = this.traversePredicate(builtPredicate, tableName) + return [builtPredicate, extraJoinInfo] + })() if (err) { - warn('Build addition join info from predicate failed', err.message) + warn('Failed to build additional join info from predicate, ', err.message) } + const [ predicate, additionJoinInfo ] = ret ? ret : [null, null] - let fields: Field[] - if (containFields) { - fields = [ schema.pk, ...clause.fields! ] - } else { - fields = Array.from(schema.columns.keys()) - } + const fields = containFields + ? [schema.pk, ...clause.fields!] + : Array.from(schema.columns.keys()) if (containFields && additionJoinInfo) { mergeFields(fields, additionJoinInfo) } const fieldsSet = new Set(fields) - - const tablesStruct: TablesStruct = Object.create(null) + const struct: TableStruct = Object.create(null) const { table, columns, joinInfo, definition, contextName } = - this.traverseQueryFields(db, tableName, fieldsSet, !containFields, [], {}, tablesStruct, mode) + this.traverseQueryFields(db, tableName, fieldsSet, !containFields, [], {}, struct, mode) const query = predicatableQuery(db, table!, null, StatementType.Select, ...columns) @@ -470,7 +461,7 @@ export class Database { } }) - this.paddingTablesStruct(db, this.getAllRelatedTables(tableName, contextName), tablesStruct) + tableStruct.pad(db, this.getAllRelatedTables(tableName, contextName), struct) const orderDesc = (clause.orderBy || []).map(desc => { return { @@ -479,13 +470,9 @@ export class Database { } }) - const matcher = { - pk, - definition, - mainTable: table! - } + const matcher = { pk, definition, mainTable: table! } const { limit, skip } = clause - const provider = new PredicateProvider(tablesStruct, contextName, predicate!) + const provider = new PredicateProvider(struct, contextName, predicate!) return new Selector(db, query, matcher, provider, limit, skip, orderDesc) }) } @@ -538,7 +525,7 @@ export class Database { glob: boolean, path: string[] = [], context: Record = {}, - tablesStruct: TablesStruct = Object.create(null), + struct: TableStruct = {}, mode: JoinMode ) { const schema = this.findSchema(tableName) @@ -548,7 +535,7 @@ export class Database { const columns: lf.schema.Column[] = [] const joinInfo: JoinInfo[] = [] - if (mode === JoinMode.imlicit && contains(tableName, path)) { + if (mode === JoinMode.implicit && contains(tableName, path)) { return { columns, joinInfo, advanced: false, table: null, definition: null, contextName: tableName } } else { path.push(tableName) @@ -567,7 +554,7 @@ export class Database { const [ originTable ] = Database.getTables(db, tableName) const currentTable = originTable.as(contextName) - this.buildTablesStructure(currentTable, contextName, tablesStruct) + tableStruct.build(currentTable, contextName, struct) const handleAdvanced = (ret: any, key: string, defs: Association | ColumnDef) => { if (!ret.advanced) { @@ -581,14 +568,14 @@ export class Database { } else { const { where, type } = defs as Association rootDefinition[key] = typeDefinition.revise(type!, ret.definition) - tablesStruct[fieldIdentifier(contextName, key)] = { + struct[fieldIdentifier(contextName, key)] = { table: ret.table, contextName: ret.contextName } - const [ predicate, e ] = tryCatch(createPredicate)(tablesStruct, contextName, where(ret.table)) - if (e) { + const [ predicate, err ] = tryCatch(PredicateProvider.create)(struct, contextName, where(ret.table)) + if (err) { warn( - `Failed to build predicate, since ${e.message}` + + `Failed to build predicate, since ${err.message}` + `, on table: ${ ret.table.getName() }` ) } @@ -640,7 +627,7 @@ export class Database { case LeafType.navigator: const { fields, assocaiation } = ctx.leaf as NavigatorLeaf const ret = - this.traverseQueryFields(db, assocaiation.name, new Set(fields), glob, path.slice(0), context, tablesStruct, mode) + this.traverseQueryFields(db, assocaiation.name, new Set(fields), glob, path.slice(0), context, struct, mode) handleAdvanced(ret, ctx.key, assocaiation) ctx.skip() break @@ -760,8 +747,8 @@ export class Database { entities.forEach(entity => { const pkVal = entity[pk] const clause = createPkClause(pk, pkVal) - const tables = this.buildTablesStructure(table) - const predicate = createPredicate(tables, tableName, clause.where) + const tables = tableStruct.build(table) + const predicate = PredicateProvider.create(tables, tableName, clause.where) const query = predicatableQuery(db, table, predicate!, StatementType.Delete) queryCollection.push(query) @@ -771,8 +758,8 @@ export class Database { const get = (where: Predicate | null = null) => { const [ table ] = Database.getTables(db, tableName) - const tables = this.buildTablesStructure(table) - const [ predicate, err ] = tryCatch(createPredicate)(tables, tableName, where) + const struct = tableStruct.build(table) + const [ predicate, err ] = tryCatch(PredicateProvider.create)(struct, tableName, where) if (err) { return Observable.throw(err) } @@ -785,30 +772,13 @@ export class Database { } } - private buildTablesStructure(defaultTable: lf.schema.Table, aliasName?: string, tablesStruct: TablesStruct = Object.create(null)) { - if (aliasName) { - tablesStruct[aliasName] = { - table: defaultTable, - contextName: aliasName - } - } else { - const tableName = defaultTable.getName() - tablesStruct[tableName] = { - table: defaultTable, - contextName: tableName - } - } - return tablesStruct - } - private getAllRelatedTables(tableName: string, contextName: string) { const tablesStructure = new Map() - const schemas = [ - { - schema: this.findSchema(tableName), - relatedTo: contextName - } - ] + const schemas = [{ + schema: this.findSchema(tableName), + relatedTo: contextName + }] + while (schemas.length) { const { schema, relatedTo } = schemas.pop()! for (const [ key, val ] of schema.associations) { @@ -826,74 +796,46 @@ export class Database { return tablesStructure } - private paddingTablesStruct(db: lf.Database, tables: Map, tablesStruct: TablesStruct): TablesStruct { - forEach(tablesStruct, structure => { - const tableName = structure.table.getName() - tables.delete(tableName) - }) - forEach(tables, (key, tableName) => { - const [ table ] = Database.getTables(db, tableName) - tablesStruct[key] = { table, contextName: tableName } - }) - return tablesStruct - } - - private buildJoinFieldsFromPredicate(predicate: Predicate, tableName: string) { + private traversePredicate(predicate: Predicate, tableName: string) { const result = Object.create(null) - const subJoinInfos: any[] = [] const schema = this.findSchema(tableName) - forEach(predicate, (val, key) => { - if (checkPredicate(val)) { - const newTableName = this.getTableNameFromNestedPredicate(key, tableName) - if (newTableName !== tableName) { - const joinFields = this.buildJoinFieldsFromPredicate(val, newTableName) - const fields = result[key] = [schema.pk] - if (joinFields) { - concat(fields, joinFields) - } + const traversable = new Traversable(predicate) + + traversable.context((_, __, ctx) => { + if (!ctx || ctx.isRoot || ctx.type() === 'Array' || typeof ctx.key === 'number') { + return false + } + return ctx + }) + + traversable.forEach((ctx, node) => { + if (PredicateProvider.check(node)) { + const nextTableName = this.findAssociatedTableName(ctx.key, tableName) + if (nextTableName !== tableName) { + const assocFields = this.traversePredicate(node, nextTableName) || [] + result[ctx.key] = [schema.pk].concat(assocFields) + ctx.skip() } - } else if (Array.isArray(val)) { - forEach(val, (subPredicate) => { - const subJoinInfo = this.buildJoinFieldsFromPredicate(subPredicate, tableName) - mergeFields(subJoinInfos, subJoinInfo!) - }) } }) + if (typeof result === 'object' && objKeys(result).length) { - return concat([result], subJoinInfos) - } - if (subJoinInfos.length) { - return subJoinInfos + return [result] } + return null } - private getTableNameFromNestedPredicate(defs: string, tableName: string) { + private findAssociatedTableName(def: string, tableName: string) { const schema = this.findSchema(tableName) - const associatedDef = schema.associations.get(defs) + const associatedDef = schema.associations.get(def) if (!associatedDef) { return tableName } return associatedDef.name } - private checkAssociateFields(fields?: Field[]) { - let result = true - if (fields) { - forEach(fields, (field, index): boolean | void => { - const isObject = typeof field === 'object' - if (isObject) { - if (index !== fields.length - 1) { - result = false - return false - } - } - }) - } - return result - } - private executor(db: lf.Database, queries: lf.query.Builder[]) { const tx = db.createTransaction() const handler = { diff --git a/src/storage/helper/check-associated-fields.ts b/src/storage/helper/check-associated-fields.ts new file mode 100644 index 00000000..bb10bfb0 --- /dev/null +++ b/src/storage/helper/check-associated-fields.ts @@ -0,0 +1,15 @@ +import { Field } from '../../interface' + +export function checkAssociateFields(fields?: Field[]) { + if (fields) { + for (let i = 0; i < fields.length; i++) { + const field = fields[i] + const isObject = typeof field === 'object' + if (isObject && i !== fields.length - 1) { + return false + } + } + } + + return true +} diff --git a/src/storage/helper/create-predicate.ts b/src/storage/helper/create-predicate.ts deleted file mode 100644 index 27d435e3..00000000 --- a/src/storage/helper/create-predicate.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { PredicateProvider } from '../modules/PredicateProvider' -import { Predicate, TablesStruct } from '../../interface' - -export function createPredicate(tables: TablesStruct, tableName: string, clause: Predicate) { - return new PredicateProvider(tables, tableName, clause).getPredicate() -} diff --git a/src/storage/helper/index.ts b/src/storage/helper/index.ts index ebe8f93e..2bda4690 100644 --- a/src/storage/helper/index.ts +++ b/src/storage/helper/index.ts @@ -1,10 +1,10 @@ import * as definition from './definition' +import * as tableStruct from './table-struct' export * from './create-pk-clause' -export * from './create-predicate' export * from './predicatable-query' export * from './merge-transaction-result' export * from './db-factory' export * from './graph' -export * from './parse-predicate' -export { definition } +export * from './check-associated-fields' +export { definition, tableStruct } diff --git a/src/storage/helper/parse-predicate.ts b/src/storage/helper/parse-predicate.ts deleted file mode 100644 index cdefca7b..00000000 --- a/src/storage/helper/parse-predicate.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Predicate } from '../../interface' -import { forEach } from '../../utils' -import { checkPredicate, predicateOperators } from '../modules/PredicateProvider' - -export function parsePredicate(predicate: Predicate) { - let lastLeaf: Object - function parse (predicateMeta: any) { - const target = Object.create(null) - lastLeaf = target - forEach(predicateMeta, (val, key) => { - if (predicateOperators.has(key)) { - lastLeaf[key] = val - } else { - const ks: string[] = key.split('.') - const length = ks.length - if (length > 1) { - ks.reduce((acc, cur, index) => { - if (index === length - 1) { - if (checkPredicate(val)) { - const subLeaf = parse(val) - acc[cur] = subLeaf - lastLeaf = subLeaf - } else { - acc[cur] = val - } - } else { - const newLeaf = Object.create(null) - acc[cur] = newLeaf - return newLeaf - } - }, lastLeaf) - } else if (checkPredicate(val)) { - lastLeaf[key] = parse(val) - } else if (Array.isArray(val)) { - lastLeaf[key] = parseArray(val) - } else { - lastLeaf[key] = val - } - } - }) - return target - } - - function parseArray(predicates: Predicate[]) { - return predicates.map(pred => parse(pred)) - } - - return parse(predicate) -} diff --git a/src/storage/helper/table-struct.ts b/src/storage/helper/table-struct.ts new file mode 100644 index 00000000..a6ee24b3 --- /dev/null +++ b/src/storage/helper/table-struct.ts @@ -0,0 +1,33 @@ +import { forEach } from '../../utils' +import { TableStruct } from '../../interface' + +export function pad(db: lf.Database, tables: Map, struct: TableStruct) { + forEach(struct, inner => { + const tableName = inner.table.getName() + tables.delete(tableName) + }) + + forEach(tables, (key, tableName) => { + const table = db.getSchema().table(tableName) + struct[key] = { table, contextName: tableName } + }) + + return struct +} + +export function build(table: lf.schema.Table, alias?: string, struct: TableStruct = {}) { + if (alias) { + struct[alias] = { + table: table, + contextName: alias + } + } else { + const tableName = table.getName() + struct[tableName] = { + table: table, + contextName: tableName + } + } + + return struct +} diff --git a/src/storage/modules/PredicateProvider.ts b/src/storage/modules/PredicateProvider.ts index 4bcecb33..da990c47 100644 --- a/src/storage/modules/PredicateProvider.ts +++ b/src/storage/modules/PredicateProvider.ts @@ -1,7 +1,7 @@ import * as lf from 'lovefield' import { fieldIdentifier } from '../symbols' import { forEach, warn, concat, keys } from '../../utils' -import { ValueLiteral, VaildEqType, Predicate, PredicateMeta, TablesStruct } from '../../interface' +import { ValueLiteral, VaildEqType, Predicate, PredicateMeta, TableStruct } from '../../interface' const predicateFactory = { @@ -75,7 +75,7 @@ export class PredicateProvider { private table: lf.schema.Table | null constructor( - private tables: TablesStruct, + private tables: TableStruct, private tableName: string, private meta?: Predicate ) { @@ -83,6 +83,72 @@ export class PredicateProvider { this.table = tableDef ? tableDef.table : null } + static check(val: Partial> | ValueLiteral) { + return val && typeof val === 'object' && + !(val instanceof Array) && + !(val instanceof RegExp) && + !(val instanceof (lf.schema as any).BaseColumn) + } + + static checkNull(pred: lf.Predicate | null, tableName: string) { + if (!pred) { + warn(`The result of parsed predicate is null, you\'re trying to remove all records from Table: ${ tableName }!`) + } + } + + static create(struct: TableStruct, tableName: string, clause: Predicate) { + return new PredicateProvider(struct, tableName, clause).getPredicate() + } + + static parse(predicate: Predicate) { + let lastLeaf: Object + + function parsePredicate (predicateMeta: any) { + const target = Object.create(null) + lastLeaf = target + + forEach(predicateMeta, (val, key) => { + if (predicateOperators.has(key)) { + lastLeaf[key] = val + } else { + const ks: string[] = key.split('.') + const length = ks.length + if (length > 1) { + ks.reduce((acc, cur, index) => { + if (index === length - 1) { + if (PredicateProvider.check(val)) { + const subLeaf = parsePredicate(val) + acc[cur] = subLeaf + lastLeaf = subLeaf + } else { + acc[cur] = val + } + } else { + const newLeaf = Object.create(null) + acc[cur] = newLeaf + return newLeaf + } + }, lastLeaf) + } else if (PredicateProvider.check(val)) { + lastLeaf[key] = parsePredicate(val) + } else if (Array.isArray(val)) { + lastLeaf[key] = parseArray(val) + } else { + lastLeaf[key] = val + } + } + }) + + return target + } + + function parseArray(predicates: Predicate[]) { + return predicates.map(pred => parsePredicate(pred)) + } + + return parsePredicate(predicate) + } + getPredicate(): lf.Predicate | null { if (!this.table) { return null @@ -117,8 +183,9 @@ export class PredicateProvider { table = struct.table contextName = struct.contextName } + const buildSinglePred = (col: lf.schema.Column, val: any, key: string): lf.Predicate => - this.checkMethod(key) ? predicateFactory[key](col, val) : col.eq(val as ValueLiteral) + PredicateProvider.checkMethod(key) ? predicateFactory[key](col, val) : col.eq(val as ValueLiteral) const predicates: lf.Predicate[] = [] @@ -126,12 +193,12 @@ export class PredicateProvider { let nestedPreds: lf.Predicate[] let resultPred: lf.Predicate - if (this.checkCompound(key)) { + if (PredicateProvider.checkCompound(key)) { nestedPreds = (Array.isArray(val) ? val : [val]).reduce((acc: Predicate[], pred: Predicate) => { return concat(acc, this.normalizeMeta(pred as Predicate, column, parentKey, contextName)) }, []) resultPred = compoundPredicateFactory[key](nestedPreds) - } else if (checkPredicate(val)) { + } else if (PredicateProvider.check(val)) { nestedPreds = this.normalizeMeta(val as any, table[key], key, contextName) resultPred = compoundPredicateFactory['$and'](nestedPreds) } else { @@ -140,7 +207,7 @@ export class PredicateProvider { if (struct) { targetCol = struct.table[key] } else { - this.warn(parentKey!, table.getName()) + PredicateProvider.warn(parentKey!, table.getName()) return } } else { @@ -149,7 +216,7 @@ export class PredicateProvider { if (targetCol) { resultPred = buildSinglePred(targetCol, val, key) } else { - this.warn(key, table.getName()) + PredicateProvider.warn(key, table.getName()) return } } @@ -159,23 +226,16 @@ export class PredicateProvider { return predicates } - private checkMethod(methodName: string) { + private static checkMethod(methodName: string) { return typeof predicateFactory[methodName] === 'function' } - private checkCompound(methodName: string) { + private static checkCompound(methodName: string) { return typeof compoundPredicateFactory[methodName] === 'function' } - private warn(key: string, tableName: string) { + private static warn(key: string, tableName: string) { warn(`Failed to build predicate, since column: ${key} is not existed, on table: ${tableName}`) } } - -export function checkPredicate(val: Partial> | ValueLiteral) { - return val && typeof val === 'object' && - !(val instanceof Array) && - !(val instanceof RegExp) && - !(val instanceof (lf.schema as any).BaseColumn) -} diff --git a/src/storage/modules/ProxySelector.ts b/src/storage/modules/ProxySelector.ts index c36f390c..1a499259 100644 --- a/src/storage/modules/ProxySelector.ts +++ b/src/storage/modules/ProxySelector.ts @@ -34,4 +34,5 @@ export class ProxySelector { this.mapFn = fn return this as any as ProxySelector } + } diff --git a/src/storage/modules/Selector.ts b/src/storage/modules/Selector.ts index cd31ba51..7a53d89a 100644 --- a/src/storage/modules/Selector.ts +++ b/src/storage/modules/Selector.ts @@ -245,12 +245,13 @@ export class Selector { if (this.predicateBuildErr) { return additional ? this.query.where(additional) : this.query } - // !this.predicateBuildErr const preds: lf.Predicate[] = [] + if (this.predicateProvider) { preds.push(this.predicateProvider.getPredicate()!) } + if (additional) { preds.push(additional) } diff --git a/src/utils/merge-fields.ts b/src/utils/merge-fields.ts index 45ecfc19..5e95551e 100644 --- a/src/utils/merge-fields.ts +++ b/src/utils/merge-fields.ts @@ -1,12 +1,12 @@ import { forEach } from './for-each' -import { FieldMustBeArray, AssociatedFieldsPostionError } from '../exception' +import { IncorrectFieldType, IncorrectAssocFieldDescription } from '../exception' export const mergeFields = (targetFields: any[], patchFields: any[]): void => { if (!Array.isArray(targetFields)) { - throw FieldMustBeArray(targetFields) + throw IncorrectFieldType(targetFields) } if (!Array.isArray(patchFields)) { - throw FieldMustBeArray(patchFields) + throw IncorrectFieldType(patchFields) } let targetLength = targetFields.length @@ -14,7 +14,7 @@ export const mergeFields = (targetFields: any[], patchFields: any[]): void => { forEach(patchFields, (field: any, index: number) => { if (typeof field === 'object') { if (index !== fieldLength - 1) { - throw AssociatedFieldsPostionError() + throw IncorrectAssocFieldDescription() } const lastTargetField = targetFields[targetLength - 1] if (typeof lastTargetField === 'object') { diff --git a/test/specs/index.ts b/test/specs/index.ts index 86e329ac..5a05820b 100644 --- a/test/specs/index.ts +++ b/test/specs/index.ts @@ -9,4 +9,3 @@ export * from './shared/Traversable.spec' export * from './utils/utils.spec' export * from './storage/helper/definition.spec' export * from './storage/helper/graph.spec' -export * from './storage/helper/parse-predicate.spec' diff --git a/test/specs/storage/Database.public.spec.ts b/test/specs/storage/Database.public.spec.ts index 5dbf8739..24e848ac 100644 --- a/test/specs/storage/Database.public.spec.ts +++ b/test/specs/storage/Database.public.spec.ts @@ -11,7 +11,7 @@ import { TestFixture2 } from '../../schemas/Test' import { scenarioGen, programGen, postGen, taskGen, subtaskGen } from '../../utils/generators' import { RDBType, DataStoreType, Database, clone, forEach, JoinMode, Logger } from '../../index' import { TaskSchema, ProjectSchema, PostSchema, ModuleSchema, ProgramSchema, SubtaskSchema, OrganizationSchema, TasklistSchema } from '../../index' -import { NonExistentTable, InvalidType, PrimaryKeyNotProvided, NotConnected, Selector, AssociatedFieldsPostionError } from '../../index' +import { NonExistentTable, InvalidType, PrimaryKeyNotProvided, NotConnected, Selector, IncorrectAssocFieldDescription } from '../../index' use(SinonChai) @@ -451,7 +451,7 @@ export default describe('Database Testcase: ', () => { it('should throw when associated fields in a wrong position', () => { const fun = () => database.get('Task', { fields: ['_id', { project: ['_id'] }, 'content'] }) - expect(fun).to.throw(AssociatedFieldsPostionError().message) + expect(fun).to.throw(IncorrectAssocFieldDescription().message) }) describe('case: Associations', () => { @@ -574,7 +574,7 @@ export default describe('Database Testcase: ', () => { expect(result.project.organization._id).to.equal(innerTarget.project._organizationId) }) - it('should merge fields if a nested association in the WHERE clause', function* () { + it('should merge fields if a nested association in the WHERE clause #1', function* () { const fields = ['_id', 'content', { project: [ '_id', { organization: [ '_id' ] } ] }] const queryToken = database.get('Task', { fields, @@ -592,25 +592,7 @@ export default describe('Database Testcase: ', () => { expect(result.project.organization._id).to.equal(innerTarget.project._organizationId) }) - it('should merge fields when get value by deep nested Association query without nested association fields', function* () { - const fields = ['_id', 'content', { subtasks: ['_id'] }] - const queryToken = database.get('Task', { - fields, - where: { - 'project.organization': { - _id: innerTarget.project._organizationId - } - } - }) - - const results = yield queryToken.values() - const [ result ] = results - - expect(results.length).to.equal(1) - expect(result.project.organization._id).to.equal(innerTarget.project._organizationId) - }) - - it('should get value by nested predicate in compound operator', function* () { + it('should merge fields if a nested association in the WHERE clause #2', function* () { const fields = ['_id', 'content', { subtasks: ['_id'], project: ['_id', { organization: ['_id', 'expireDate'] }] }] const day = 24 * 60 * 60 * 1000 - 2 const queryToken = database.get('Task', { @@ -644,7 +626,11 @@ export default describe('Database Testcase: ', () => { }) }) - it('should warn if build additional join info from predicate failed', function* () { + it('should be able to handle implici self-join', () => { + // todo + }) + + it('should warn if failed to build additional join from predicate', function* () { const fields = ['_id', 'content'] const queryToken = database.get('Task', { fields, diff --git a/test/specs/storage/helper/parse-predicate.spec.ts b/test/specs/storage/helper/parse-predicate.spec.ts deleted file mode 100644 index ee250e50..00000000 --- a/test/specs/storage/helper/parse-predicate.spec.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { describe, it } from 'tman' -import { expect } from 'chai' - -import { parsePredicate } from '../../../index' - -export default describe('Helper - parsePredicate TestCase', () => { - - const expected = { - task: { - tasklist: { - project: { - organization: { - _id: 'xxx' - } - } - } - } - } - - it('should parse normal predicate', () => { - const meta = { - task: { - tasklist: { - project: { - organization: { - _id: 'xxx' - } - } - } - } - } - const metaString = JSON.stringify(meta) - const result = parsePredicate(meta) - - expect(result).to.deep.equal(expected) - expect(JSON.stringify(result)).to.equal(metaString) - }) - - it('should parse nested predicate', () => { - const meta = { - task: { - 'tasklist.project.organization._id': 'xxx' - } - } - - const result = parsePredicate(meta) - - expect(result).to.deep.equal(expected) - }) - - it('should parse mutiple levels nested predicate #1', () => { - const meta = { - 'task.tasklist': { - 'project.organization': { - _id: 'xxx' - } - } - } - - const result = parsePredicate(meta) - - expect(result).to.deep.equal(expected) - }) - - it('should parse mutiple levels nested predicate #2', () => { - const meta = { - task: { - 'tasklist.project.organization': { - _id: 'xxx' - } - } - } - - const result = parsePredicate(meta) - - expect(result).to.deep.equal(expected) - }) - - it('should parse mutiple levels nested predicate #3', () => { - const meta = { - task: { - 'tasklist.project': { - 'organization._id': 'xxx' - } - } - } - - const result = parsePredicate(meta) - - expect(result).to.deep.equal(expected) - }) - - it('should parse with predicate operators', () => { - const meta = { - task: { - 'tasklist._id': { - $in: ['xxx', 'yyy'] - } - } - } - - const result = parsePredicate(meta) - - expect(result).to.deep.equal({ - task: { - tasklist: { - _id: { - $in: ['xxx', 'yyy'] - } - } - } - }) - }) - - it('should parse compound nested predicate', () => { - const meta = { - task: { - $or: [ - { - 'tasklist.project': { - 'organization._id': 'xxx' - } - }, - { - 'tasklist.project': { - 'organization._id': 'yyy' - } - } - ] - } - } - - const result = parsePredicate(meta) - - expect(result).to.deep.equal({ - task: { - $or: [ - { - tasklist: { - project: { - organization: { - _id: 'xxx' - } - } - } - }, - { - tasklist: { - project: { - organization: { - _id: 'yyy' - } - } - } - } - ] - } - }) - }) - - it('should parse compound nested predicate with operator #1', () => { - const reg = /e/g - - const meta = { - task: { - $and: [ - { - 'tasklist.project': { - 'organization._id': 'xxx' - } - }, - { - 'tasklist.project': { - 'organization': { - _id: { - $match: reg - } - } - } - } - ] - } - } - - const result = parsePredicate(meta) - - expect(result).to.deep.equal({ - task: { - $and: [ - { - tasklist: { - project: { - organization: { - _id: 'xxx' - } - } - } - }, - { - tasklist: { - project: { - organization: { - _id: { - $match: reg - } - } - } - } - } - ] - } - }) - }) - - it('should parse compound nested predicate with operator #2', () => { - const reg = /e/g - const now = Date.now() - - const meta = { - task: { - $and: [ - { - 'tasklist.project': { - $and: [ - { 'organization._id': 'xxx' }, - { - organization: { - expireDate: { - $gte: now - } - } - } - ] - } - }, - { - 'tasklist.project': { - 'organization': { - _id: { - $match: reg - } - } - } - } - ] - } - } - - const result = parsePredicate(meta) - - expect(result).to.deep.equal({ - task: { - $and: [ - { - tasklist: { - project: { - $and: [ - { - organization: { - _id: 'xxx' - } - }, - { - organization: { - expireDate: { - $gte: now - } - } - } - ] - } - } - }, - { - tasklist: { - project: { - organization: { - _id: { - $match: reg - } - } - } - } - } - ] - } - }) - }) -}) diff --git a/test/specs/storage/modules/Mutation.spec.ts b/test/specs/storage/modules/Mutation.spec.ts index c1b2e7cf..a1821866 100644 --- a/test/specs/storage/modules/Mutation.spec.ts +++ b/test/specs/storage/modules/Mutation.spec.ts @@ -96,7 +96,7 @@ export default describe('Mutation Testcase: ', () => { describe('Static Method: aggregate', () => { - it('should be able to transform mutations to queries which will be executed as update statement', () => { + it('should be able to transform mutation into query which will be executed as update statement', () => { const muts = fixture.map((item, index) => { const mut = new Mutation(database, table, item) mut.withId('id', index) @@ -112,7 +112,7 @@ export default describe('Mutation Testcase: ', () => { expect(queries).have.lengthOf(3) }) - it('should be able to transform mutations to queries which will be executed as insert statement', () => { + it('should be able to transform mutation to query which will be executed as insert statement', () => { const muts = fixture.map((item, index) => { return new Mutation(database, table, item).withId('id', index) }) diff --git a/test/specs/storage/modules/PredicateProvider.spec.ts b/test/specs/storage/modules/PredicateProvider.spec.ts index 0ff236e3..8f685197 100644 --- a/test/specs/storage/modules/PredicateProvider.spec.ts +++ b/test/specs/storage/modules/PredicateProvider.spec.ts @@ -1,390 +1,714 @@ import * as lf from 'lovefield' -import { describe, it, beforeEach } from 'tman' -import { expect } from 'chai' -import { PredicateProvider, lfFactory, DataStoreType, Predicate } from '../../../index' - -export default describe('PredicateProvider test', () => { - const dataLength = 1000 - - const execQuery = (_db: any, _table: any, pred?: any) => - _db.select() - .from(_table) - .where(pred) - .exec() - - let db: lf.Database - let table: lf.schema.Table - let tableDef: { TestPredicateProvider: { table: lf.schema.Table } } - let version = 1 - - function predicateFactory(desc: Predicate) { - return new PredicateProvider(tableDef, 'TestPredicateProvider', desc) - } - - beforeEach(function* () { - const schemaBuilder = lf.schema.create('PredicateProviderDatabase', version++) - const db$ = lfFactory(schemaBuilder, { - storeType: DataStoreType.MEMORY, - enableInspector: false - }) - const tableBuilder = schemaBuilder.createTable('TestPredicateProvider') - tableBuilder.addColumn('_id', lf.Type.STRING) - .addColumn('name', lf.Type.STRING) - .addColumn('time1', lf.Type.NUMBER) - .addColumn('time2', lf.Type.NUMBER) - .addColumn('times', lf.Type.STRING) - .addColumn('nullable', lf.Type.BOOLEAN) - .addPrimaryKey(['_id']) - .addNullable(['nullable']) - - db$.connect() - db = yield db$.do(r => { - table = r.getSchema().table('TestPredicateProvider') - }) - const rows: lf.Row[] = [] - for (let i = 0; i < dataLength; i ++) { - rows.push(table.createRow({ - _id: `_id:${i}`, - name: `name:${i}`, - time1: i, - time2: dataLength - i, - times: [i - 1, i , i + 1].map(r => `times: ${r}`).join('|') , - nullable: i >= 300 ? null : false - })) - } - - tableDef = { TestPredicateProvider: { table } } - - yield db.insert().into(table).values(rows).exec() - }) - - describe('PredicateProvider#getPredicate', () => { - it('invalid key should be ignored', function* () { - const fn = () => predicateFactory({ - nonExist: 'whatever' - }).getPredicate() - - const expectResult = yield execQuery(db, table) - const result = yield execQuery(db, table, fn()) - - expect(result).deep.equal(expectResult) - }) - - it('should get null when tablesStructure is not contain tableName', () => { - const predicateProvider = new PredicateProvider({}, 'whatever', { _id: 1 }) - const result = predicateProvider.getPredicate() - expect(result).to.be.null - }) - - it('empty meta should ok', function* () { - const predicate = predicateFactory({}).getPredicate() - expect(predicate).to.be.null - const result = yield execQuery(db, table, predicate) - expect(result).to.have.lengthOf(1000) - }) - - it('literal value should ok', function* () { - const predicate = predicateFactory({ - time1: 20 - }).getPredicate() - - const result = yield execQuery(db, table, predicate) - - expect(result).to.have.lengthOf(1) - expect(result[0]['time1']).to.equal(20) - }) - - it('$ne should ok', function* () { - const predicate = predicateFactory({ - time1: { - $ne: 20 - } - }).getPredicate() - - const result = yield execQuery(db, table, predicate) - - expect(result).to.have.lengthOf(dataLength - 1) - result.forEach((r: any) => expect(r['time1'] === 20).to.be.false) - }) - - it('$lt should ok', function* () { - const predicate = predicateFactory({ - time1: { - $lt: 20 - } - }).getPredicate() - - const result = yield execQuery(db, table, predicate) - - expect(result).to.have.lengthOf(20) - result.forEach((r: any) => expect(r['time1'] < 20).to.be.true) - }) - - it('$lte should ok', function* () { - const predicate = predicateFactory({ - time1: { - $lte: 19 - } - }).getPredicate() - - const result = yield execQuery(db, table, predicate) - - expect(result).to.have.lengthOf(20) - result.forEach((r: any) => expect(r['time1'] <= 19).to.be.true) - }) - - it('$gt should ok', function* () { - const predicate = predicateFactory({ - time2: { - $gt: 20 - } - }).getPredicate() - - const result = yield execQuery(db, table, predicate) - - expect(result).to.have.lengthOf(dataLength - 20) - result.forEach((r: any) => expect(r['time2'] > 20).to.be.true) - }) - - it('$gte should ok', function* () { - const predicate = predicateFactory({ - time2: { - $gte: 21 - } - }).getPredicate() - - const result = yield execQuery(db, table, predicate) - - expect(result).to.have.lengthOf(dataLength - 20) - result.forEach((r: any) => expect(r['time2'] >= 21).to.be.true) - }) - - it('$match should ok', function* () { - const regExp = /\:(\d{0,1}1$)/ - const predicate = predicateFactory({ - name: { - $match: regExp - } - }).getPredicate() - - const result = yield execQuery(db, table, predicate) - - expect(result).to.have.lengthOf(10) - result.forEach((r: any) => expect(regExp.test(r['name'])).to.be.true) - }) - - it('$notMatch should ok', function* () { - const regExp = /\:(\d{0,1}1$)/ - const predicate = predicateFactory({ - name: { - $notMatch: regExp - } - }).getPredicate() - - const result = yield execQuery(db, table, predicate) - - // 上一个测试中结果长度是 10 - expect(result).to.have.lengthOf(dataLength - 10) - result.forEach((r: any) => expect(regExp.test(r['name'])).to.be.false) - }) - - it('$between should ok', function* () { - const predicate = predicateFactory({ - time1: { - $between: [1, 20] - } - }).getPredicate() - - const result = yield execQuery(db, table, predicate) - - expect(result).to.have.lengthOf(20) - result.forEach((r: any) => expect(r['time1'] > 0 && r['time1'] <= 20).to.be.true) - }) - - it('$has should ok', function* () { - const predicate = predicateFactory({ - times: { - $has: 'times: 10' +import { describe, it, beforeEach, afterEach } from 'tman' +import { expect, use } from 'chai' +import * as sinon from 'sinon' +import * as SinonChai from 'sinon-chai' +import { PredicateProvider, lfFactory, DataStoreType, Predicate, Logger } from '../../../index' + +use(SinonChai) + +export default describe('PredicateProvider Testcase: ', () => { + + describe('Class: PredicateProvider', () => { + const dataLength = 1000 + + const execQuery = (_db: any, _table: any, pred?: any) => + _db.select() + .from(_table) + .where(pred) + .exec() + + let db: lf.Database + let table: lf.schema.Table + let tableDef: { TestPredicateProvider: { table: lf.schema.Table } } + let version = 1 + + function predicateFactory(desc: Predicate) { + return new PredicateProvider(tableDef, 'TestPredicateProvider', desc) + } + + beforeEach(function* () { + const schemaBuilder = lf.schema.create('PredicateProviderDatabase', version++) + const db$ = lfFactory(schemaBuilder, { + storeType: DataStoreType.MEMORY, + enableInspector: false + }) + const tableBuilder = schemaBuilder.createTable('TestPredicateProvider') + tableBuilder.addColumn('_id', lf.Type.STRING) + .addColumn('name', lf.Type.STRING) + .addColumn('time1', lf.Type.NUMBER) + .addColumn('time2', lf.Type.NUMBER) + .addColumn('times', lf.Type.STRING) + .addColumn('nullable', lf.Type.BOOLEAN) + .addPrimaryKey(['_id']) + .addNullable(['nullable']) + + db$.connect() + db = yield db$.do(r => { + table = r.getSchema().table('TestPredicateProvider') + }) + const rows: lf.Row[] = [] + for (let i = 0; i < dataLength; i ++) { + rows.push(table.createRow({ + _id: `_id:${i}`, + name: `name:${i}`, + time1: i, + time2: dataLength - i, + times: [i - 1, i , i + 1].map(r => `times: ${r}`).join('|') , + nullable: i >= 300 ? null : false + })) } - }).getPredicate() - const result = yield execQuery(db, table, predicate) + tableDef = { TestPredicateProvider: { table } } - expect(result).to.have.lengthOf(3) - result.forEach((r: any) => { - expect(r.times.match(/times: 10\b/)).to.not.be.null + yield db.insert().into(table).values(rows).exec() }) - }) - - it('$in should ok', function* () { - const seed = [10, 20, 30, 10000] - const predicate = predicateFactory({ - time1: { - $in: seed - } - }).getPredicate() - - const result = yield execQuery(db, table, predicate) - - expect(result).to.have.lengthOf(3) - result.forEach((r: any) => expect(seed.indexOf(r['time1']) !== -1).to.be.true) - }) - - it('$isNull should ok', function* () { - const predicate = predicateFactory({ - nullable: { - $isNull: true - } - }).getPredicate() - const result = yield execQuery(db, table, predicate) + describe('Method: getPredicate', () => { + it('invalid key should be ignored', function* () { + const fn = () => predicateFactory({ + nonExist: 'whatever' + }).getPredicate() + + const expectResult = yield execQuery(db, table) + const result = yield execQuery(db, table, fn()) + + expect(result).deep.equal(expectResult) + }) + + it('should get null when tablesStructure is not contain tableName', () => { + const predicateProvider = new PredicateProvider({}, 'whatever', { _id: 1 }) + const result = predicateProvider.getPredicate() + expect(result).to.be.null + }) + + it('empty meta should ok', function* () { + const predicate = predicateFactory({}).getPredicate() + expect(predicate).to.be.null + const result = yield execQuery(db, table, predicate) + expect(result).to.have.lengthOf(1000) + }) + + it('literal value should ok', function* () { + const predicate = predicateFactory({ + time1: 20 + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(1) + expect(result[0]['time1']).to.equal(20) + }) + + it('$ne should ok', function* () { + const predicate = predicateFactory({ + time1: { + $ne: 20 + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(dataLength - 1) + result.forEach((r: any) => expect(r['time1'] === 20).to.be.false) + }) + + it('$lt should ok', function* () { + const predicate = predicateFactory({ + time1: { + $lt: 20 + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(20) + result.forEach((r: any) => expect(r['time1'] < 20).to.be.true) + }) + + it('$lte should ok', function* () { + const predicate = predicateFactory({ + time1: { + $lte: 19 + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(20) + result.forEach((r: any) => expect(r['time1'] <= 19).to.be.true) + }) + + it('$gt should ok', function* () { + const predicate = predicateFactory({ + time2: { + $gt: 20 + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(dataLength - 20) + result.forEach((r: any) => expect(r['time2'] > 20).to.be.true) + }) + + it('$gte should ok', function* () { + const predicate = predicateFactory({ + time2: { + $gte: 21 + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(dataLength - 20) + result.forEach((r: any) => expect(r['time2'] >= 21).to.be.true) + }) + + it('$match should ok', function* () { + const regExp = /\:(\d{0,1}1$)/ + const predicate = predicateFactory({ + name: { + $match: regExp + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(10) + result.forEach((r: any) => expect(regExp.test(r['name'])).to.be.true) + }) + + it('$notMatch should ok', function* () { + const regExp = /\:(\d{0,1}1$)/ + const predicate = predicateFactory({ + name: { + $notMatch: regExp + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + // 上一个测试中结果长度是 10 + expect(result).to.have.lengthOf(dataLength - 10) + result.forEach((r: any) => expect(regExp.test(r['name'])).to.be.false) + }) + + it('$between should ok', function* () { + const predicate = predicateFactory({ + time1: { + $between: [1, 20] + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(20) + result.forEach((r: any) => expect(r['time1'] > 0 && r['time1'] <= 20).to.be.true) + }) + + it('$has should ok', function* () { + const predicate = predicateFactory({ + times: { + $has: 'times: 10' + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(3) + result.forEach((r: any) => { + expect(r.times.match(/times: 10\b/)).to.not.be.null + }) + }) + + it('$in should ok', function* () { + const seed = [10, 20, 30, 10000] + const predicate = predicateFactory({ + time1: { + $in: seed + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(3) + result.forEach((r: any) => expect(seed.indexOf(r['time1']) !== -1).to.be.true) + }) + + it('$isNull should ok', function* () { + const predicate = predicateFactory({ + nullable: { + $isNull: true + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(700) + result.forEach((r: any) => expect(r['nullable']).to.be.null) + }) + + it('$isNotNull should ok', function* () { + const predicate = predicateFactory({ + nullable: { + $isNotNull: true + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(300) + result.forEach((r: any) => expect(r['nullable']).to.not.be.null) + }) + + it('$not should ok', function* () { + const predicate = predicateFactory({ + $not: { + time1: 0 + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(dataLength - 1) + }) + + it('$and should ok', function* () { + const predicate = predicateFactory({ + time1: { + $and: { + $lt: 200, + $gte: 50 + } + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(150) + result.forEach((r: any) => expect(r['time1'] >= 50 && r['time1'] < 200).to.be.true) + }) + + it('$or should ok', function* () { + const predicate = predicateFactory({ + time1: { + $or: { + $gte: dataLength - 50, + $lt: 50 + } + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(100) + result.forEach((r: any) => expect(r['time1'] >= dataLength - 50 || r['time1'] < 50).to.be.true) + }) + + it('non-compound predicates should be combined with $and', function* () { + const predicate = predicateFactory({ + $or: { + time1: { $gte: 0, $lt: 50 }, + time2: { $gt: 0, $lte: 50 } + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(100) + result.forEach((row: any) => { + expect( + (row.time1 >= 0 && row.time1 < 50) + || (row.time2 <= 50 && row.time2 > 0) + ).to.be.true + }) + }) + + it('compoundPredicate should skip null/undefined property', function* () { + const predicate = predicateFactory({ + time1: { + $or: { + $gte: dataLength - 50, + $lt: null, + } + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(50) + result.forEach((r: any) => expect(r['time1'] >= dataLength - 50).to.be.true) + }) + + it('complex PredicateDescription should ok', function* () { + const reg = /\:(\d{0,1}1$)/ + const predicate = predicateFactory({ + time1: { + $or: { + $gte: dataLength - 50, + $lt: 50 + } + }, + time2: { + $and: { + $gte: dataLength / 2, + $lt: dataLength + } + }, + name: { + $match: reg + } + }).getPredicate() + + const result = yield execQuery(db, table, predicate) + + expect(result).to.have.lengthOf(5) + + result.forEach((r: any) => { + const pred1 = r['time1'] >= dataLength - 50 || r['time1'] < 50 + const pred2 = r['time2'] >= dataLength / 2 && r['time2'] < dataLength + const pred3 = reg.test(r['name']) + + expect(pred1 && pred2 && pred3).to.be.true + }) + }) + }) - expect(result).to.have.lengthOf(700) - result.forEach((r: any) => expect(r['nullable']).to.be.null) - }) + describe('Method: toString', () => { + it('convert empty PredicateProvider to empty string', () => { + expect(predicateFactory({}).toString()).to.equal('') + }) - it('$isNotNull should ok', function* () { - const predicate = predicateFactory({ - nullable: { - $isNotNull: true - } - }).getPredicate() + it('convert to string representation of the predicate', () => { + expect(predicateFactory({ notExist: 20 }).toString()).to.equal('') - const result = yield execQuery(db, table, predicate) + expect(predicateFactory({ time1: 20 }).toString()).to.equal('{"time1":20}') - expect(result).to.have.lengthOf(300) - result.forEach((r: any) => expect(r['nullable']).to.not.be.null) - }) + expect(predicateFactory({ + $or: { + time1: { $gte: 0, $lt: 50 }, + time2: { $gt: 0, $lte: 50 } + } + }).toString()).to.equal('{"$or":{"time1":{"$gte":0,"$lt":50},"time2":{"$gt":0,"$lte":50}}}') + }) + }) - it('$not should ok', function* () { - const predicate = predicateFactory({ - $not: { - time1: 0 - } - }).getPredicate() + describe('Static Method: parse', () => { + let expected: any = null + + beforeEach(() => { + expected = { + task: { + tasklist: { + project: { + organization: { + _id: 'xxx' + } + } + } + } + } + }) + + it('should parse normal predicate', () => { + const meta = { + task: { + tasklist: { + project: { + organization: { + _id: 'xxx' + } + } + } + } + } + const metaString = JSON.stringify(meta) + const result = PredicateProvider.parse(meta) + + expect(result).to.deep.equal(expected) + expect(JSON.stringify(result)).to.equal(metaString) + }) + + it('should parse nested predicate', () => { + const meta = { + task: { + 'tasklist.project.organization._id': 'xxx' + } + } - const result = yield execQuery(db, table, predicate) + const result = PredicateProvider.parse(meta) - expect(result).to.have.lengthOf(dataLength - 1) - }) + expect(result).to.deep.equal(expected) + }) - it('$and should ok', function* () { - const predicate = predicateFactory({ - time1: { - $and: { - $lt: 200, - $gte: 50 + it('should parse mutiple levels nested predicate #1', () => { + const meta = { + 'task.tasklist': { + 'project.organization': { + _id: 'xxx' + } + } } - } - }).getPredicate() - const result = yield execQuery(db, table, predicate) + const result = PredicateProvider.parse(meta) - expect(result).to.have.lengthOf(150) - result.forEach((r: any) => expect(r['time1'] >= 50 && r['time1'] < 200).to.be.true) - }) + expect(result).to.deep.equal(expected) + }) - it('$or should ok', function* () { - const predicate = predicateFactory({ - time1: { - $or: { - $gte: dataLength - 50, - $lt: 50 + it('should parse mutiple levels nested predicate #2', () => { + const meta = { + task: { + 'tasklist.project.organization': { + _id: 'xxx' + } + } } - } - }).getPredicate() - - const result = yield execQuery(db, table, predicate) - expect(result).to.have.lengthOf(100) - result.forEach((r: any) => expect(r['time1'] >= dataLength - 50 || r['time1'] < 50).to.be.true) - }) - - it('non-compound predicates should be combined with $and', function* () { - const predicate = predicateFactory({ - $or: { - time1: { $gte: 0, $lt: 50 }, - time2: { $gt: 0, $lte: 50 } - } - }).getPredicate() + const result = PredicateProvider.parse(meta) - const result = yield execQuery(db, table, predicate) + expect(result).to.deep.equal(expected) + }) - expect(result).to.have.lengthOf(100) - result.forEach((row: any) => { - expect( - (row.time1 >= 0 && row.time1 < 50) - || (row.time2 <= 50 && row.time2 > 0) - ).to.be.true - }) - }) - - it('compoundPredicate should skip null/undefined property', function* () { - const predicate = predicateFactory({ - time1: { - $or: { - $gte: dataLength - 50, - $lt: null, + it('should parse mutiple levels nested predicate #3', () => { + const meta = { + task: { + 'tasklist.project': { + 'organization._id': 'xxx' + } + } } - } - }).getPredicate() - const result = yield execQuery(db, table, predicate) + const result = PredicateProvider.parse(meta) - expect(result).to.have.lengthOf(50) - result.forEach((r: any) => expect(r['time1'] >= dataLength - 50).to.be.true) - }) + expect(result).to.deep.equal(expected) + }) - it('complex PredicateDescription should ok', function* () { - const reg = /\:(\d{0,1}1$)/ - const predicate = predicateFactory({ - time1: { - $or: { - $gte: dataLength - 50, - $lt: 50 + it('should parse with predicate operators', () => { + const meta = { + task: { + 'tasklist._id': { + $in: ['xxx', 'yyy'] + } + } } - }, - time2: { - $and: { - $gte: dataLength / 2, - $lt: dataLength + + const result = PredicateProvider.parse(meta) + + expect(result).to.deep.equal({ + task: { + tasklist: { + _id: { + $in: ['xxx', 'yyy'] + } + } + } + }) + }) + + it('should parse compound nested predicate', () => { + const meta = { + task: { + $or: [ + { + 'tasklist.project': { + 'organization._id': 'xxx' + } + }, + { + 'tasklist.project': { + 'organization._id': 'yyy' + } + } + ] + } } - }, - name: { - $match: reg - } - }).getPredicate() - const result = yield execQuery(db, table, predicate) + const result = PredicateProvider.parse(meta) + + expect(result).to.deep.equal({ + task: { + $or: [ + { + tasklist: { + project: { + organization: { + _id: 'xxx' + } + } + } + }, + { + tasklist: { + project: { + organization: { + _id: 'yyy' + } + } + } + } + ] + } + }) + }) + + it('should parse compound nested predicate with operator #1', () => { + const reg = /e/g + + const meta = { + task: { + $and: [ + { + 'tasklist.project': { + 'organization._id': 'xxx' + } + }, + { + 'tasklist.project': { + 'organization': { + _id: { + $match: reg + } + } + } + } + ] + } + } - expect(result).to.have.lengthOf(5) + const result = PredicateProvider.parse(meta) + + expect(result).to.deep.equal({ + task: { + $and: [ + { + tasklist: { + project: { + organization: { + _id: 'xxx' + } + } + } + }, + { + tasklist: { + project: { + organization: { + _id: { + $match: reg + } + } + } + } + } + ] + } + }) + }) + + it('should parse compound nested predicate with operator #2', () => { + const reg = /e/g + const now = Date.now() + + const meta = { + task: { + $and: [ + { + 'tasklist.project': { + $and: [ + { 'organization._id': 'xxx' }, + { + organization: { + expireDate: { + $gte: now + } + } + } + ] + } + }, + { + 'tasklist.project': { + 'organization': { + _id: { + $match: reg + } + } + } + } + ] + } + } - result.forEach((r: any) => { - const pred1 = r['time1'] >= dataLength - 50 || r['time1'] < 50 - const pred2 = r['time2'] >= dataLength / 2 && r['time2'] < dataLength - const pred3 = reg.test(r['name']) + const result = PredicateProvider.parse(meta) + + expect(result).to.deep.equal({ + task: { + $and: [ + { + tasklist: { + project: { + $and: [ + { + organization: { + _id: 'xxx' + } + }, + { + organization: { + expireDate: { + $gte: now + } + } + } + ] + } + } + }, + { + tasklist: { + project: { + organization: { + _id: { + $match: reg + } + } + } + } + } + ] + } + }) + }) + }) - expect(pred1 && pred2 && pred3).to.be.true + describe('Static Method: check', () => { + it('should be able to check if input is valid', () => { + expect(!!PredicateProvider.check(null)).to.equal(false) + expect(!!PredicateProvider.check(false)).to.equal(false) + expect(!!PredicateProvider.check([] as any)).to.equal(false) + expect(!!PredicateProvider.check({ $match: /\d/ })).to.equal(true) + expect(!!PredicateProvider.check({ $and: [] })).to.equal(true) + }) }) - }) - }) - describe('PredicateProvider#toString', () => { - it('convert empty PredicateProvider to empty string', () => { - expect(predicateFactory({}).toString()).to.equal('') - }) + describe('Static Method: checkNull', () => { + let spy: any = null - it('convert to string representation of the predicate', () => { - expect(predicateFactory({ notExist: 20 }).toString()).to.equal('') + beforeEach(() => { + spy = sinon.spy(Logger, 'warn') + }) - expect(predicateFactory({ time1: 20 }).toString()).to.equal('{"time1":20}') + afterEach(() => { + spy.restore() + }) + + it('should warn if failed to build predicate', () => { + PredicateProvider.checkNull(null, 'Foo') + expect(spy).be.called + }) + }) - expect(predicateFactory({ - $or: { - time1: { $gte: 0, $lt: 50 }, - time2: { $gt: 0, $lte: 50 } - } - }).toString()).to.equal('{"$or":{"time1":{"$gte":0,"$lt":50},"time2":{"$gt":0,"$lte":50}}}') - }) }) + }) diff --git a/test/specs/utils/utils.spec.ts b/test/specs/utils/utils.spec.ts index 2bda672c..5405b3ac 100644 --- a/test/specs/utils/utils.spec.ts +++ b/test/specs/utils/utils.spec.ts @@ -1,4 +1,4 @@ -import { forEach, clone, getType, assert, hash, concat, keys, mergeFields, FieldMustBeArray, AssociatedFieldsPostionError } from '../../index' +import { forEach, clone, getType, assert, hash, concat, keys, mergeFields, IncorrectFieldType, IncorrectAssocFieldDescription } from '../../index' import { describe, it } from 'tman' import { expect } from 'chai' @@ -555,15 +555,15 @@ export default describe('Utils Testcase: ', () => { const patch = { 0: '1', 1: '3', length: 2 } const fun = () => mergeFields(target, patch as any) const fun1 = () => mergeFields(patch as any, target) - expect(fun).to.throw(FieldMustBeArray(patch).message) - expect(fun1).to.throw(FieldMustBeArray(patch).message) + expect(fun).to.throw(IncorrectFieldType(patch).message) + expect(fun1).to.throw(IncorrectFieldType(patch).message) }) it('should throw if associated fields not in right position', () => { const target = ['1', '2'] const patch = ['3', '4', { bar: ['6'] }, { baz: ['9'] }] const fun = () => mergeFields(target, patch) - expect(fun).to.throw(AssociatedFieldsPostionError().message) + expect(fun).to.throw(IncorrectAssocFieldDescription().message) }) })