// import { cloneDeep } from 'lodash-es' // import { QueryCommmand, isQueryCommand, isComparisonCommand, QUERY_COMMANDS_LITERAL } from '../commands/query' import { UpdateCommand, isUpdateCommand, UPDATE_COMMANDS_LITERAL } from '../commands/update' import { LogicCommand } from '../commands/logic' import { SYMBOL_UNSET_FIELD_NAME } from '../helper/symbol' import { getType, } from '../utils/type' import { operatorToString } from '../operator-map' import { flattenQueryObject, encodeInternalDataType, mergeConditionAfterEncode } from './common' export type IQueryCondition = Record | LogicCommand export interface IUpdateCondition { [key: string]: any, } interface IPushModifiers { $each?: any[], $position?: number, } export class UpdateSerializer { private constructor() { } static encode(query: IQueryCondition | UpdateCommand): IUpdateCondition { const stringifier = new UpdateSerializer() return stringifier.encodeUpdate(query) } encodeUpdate(query: IQueryCondition | UpdateCommand): IUpdateCondition { if (isUpdateCommand(query)) { return this.encodeUpdateCommand(query) } else if (getType(query) === 'object') { return this.encodeUpdateObject(query) } else { return query } } encodeUpdateCommand(query: UpdateCommand): IQueryCondition { if (query.fieldName === SYMBOL_UNSET_FIELD_NAME) { throw new Error(`Cannot encode a comparison command with unset field name`) } switch (query.operator) { case UPDATE_COMMANDS_LITERAL.SET: case UPDATE_COMMANDS_LITERAL.REMOVE: case UPDATE_COMMANDS_LITERAL.INC: case UPDATE_COMMANDS_LITERAL.MUL: { return this.encodeFieldUpdateCommand(query) } case UPDATE_COMMANDS_LITERAL.PUSH: case UPDATE_COMMANDS_LITERAL.POP: case UPDATE_COMMANDS_LITERAL.SHIFT: case UPDATE_COMMANDS_LITERAL.UNSHIFT: { return this.encodeArrayUpdateCommand(query) } default: { return this.encodeFieldUpdateCommand(query) } } } encodeFieldUpdateCommand(query: UpdateCommand): IQueryCondition { const $op = operatorToString(query.operator) switch (query.operator) { case UPDATE_COMMANDS_LITERAL.REMOVE: { return { [$op]: { [query.fieldName as string]: '', }, } } case UPDATE_COMMANDS_LITERAL.SET: case UPDATE_COMMANDS_LITERAL.INC: case UPDATE_COMMANDS_LITERAL.MUL: default: { return { [$op]: { [query.fieldName as string]: query.operands[0], }, } } } } encodeArrayUpdateCommand(query: UpdateCommand): IQueryCondition { const $op = operatorToString(query.operator) switch (query.operator) { case UPDATE_COMMANDS_LITERAL.PUSH: { const modifiers: IPushModifiers = { $each: query.operands.map(encodeInternalDataType), } return { [$op]: { [query.fieldName as string]: modifiers, }, } } case UPDATE_COMMANDS_LITERAL.UNSHIFT: { const modifiers: IPushModifiers = { $each: query.operands.map(encodeInternalDataType), $position: 0, } return { [$op]: { [query.fieldName as string]: modifiers, }, } } case UPDATE_COMMANDS_LITERAL.POP: { return { [$op]: { [query.fieldName as string]: 1, }, } } case UPDATE_COMMANDS_LITERAL.SHIFT: { return { [$op]: { [query.fieldName as string]: -1, }, } } default: { return { [$op]: { [query.fieldName as string]: encodeInternalDataType(query.operands), }, } } } } encodeUpdateObject(query: Record): IQueryCondition { const flattened = flattenQueryObject(query) for (const key in flattened) { if (/^\$/.test(key)) continue let val = flattened[key] if (isUpdateCommand(val)) { flattened[key] = val._setFieldName(key) const condition = this.encodeUpdateCommand(flattened[key]) mergeConditionAfterEncode(flattened, condition, key) } else { // $set flattened[key] = val = encodeInternalDataType(val) const $setCommand = new UpdateCommand(UPDATE_COMMANDS_LITERAL.SET, [val], key) const condition = this.encodeUpdateCommand($setCommand) mergeConditionAfterEncode(flattened, condition, key) } } return flattened } } /** { a: { a1: _.set({ a11: 'test' }), a2: _.inc(10) }, } => { a: { a1: $set({ a11: 'test' }), a2: $inc(10) } } => { 'a.a1': $set({ a11: 'test' }), 'a.a2': $inc(10) } => { $set: { 'a.a1': { a11: 'test' } }, $inc: { 'a.a2': 10 } } */