UNPKG

quaerateum

Version:

Simple typescript ORM for node.js based on data-mapper, unit-of-work and identity-map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JS.

215 lines (166 loc) 5.97 kB
import * as fastEqual from 'fast-deep-equal'; import * as clone from 'clone'; import { MetadataStorage } from '../metadata'; import { EntityData, EntityMetadata, EntityProperty, IEntity, IEntityType, IPrimaryKey } from '../decorators'; import { ArrayCollection, Collection, ReferenceType } from '../entity'; export class Utils { static isObject<T = Record<string, any>>(o: any): o is T { return !!o && typeof o === 'object' && !Array.isArray(o); } static isString(s: any): s is string { return typeof s === 'string'; } static equals(a: any, b: any): boolean { return fastEqual(a, b); } static unique<T = string>(items: T[]): T[] { return [...new Set(items)]; } static flatten<T>(arrays: T[][]): T[] { return [].concat(...arrays as any[]); } static merge(target: any, ...sources: any[]): any { if (!sources.length) { return target; } const source = sources.shift(); if (Utils.isObject(target) && Utils.isObject(source)) { Object.entries(source).forEach(([key, value]) => { if (Utils.isObject(value)) { if (!target[key]) { Object.assign(target, { [key]: {} }); } Utils.merge(target[key], value); } else { Object.assign(target, { [key]: value }); } }); } return Utils.merge(target, ...sources); } static diff(a: Record<string, any>, b: Record<string, any>): Record<keyof (typeof a & typeof b), any> { const ret: Record<string, any> = {}; Object.keys(b).forEach(k => { if (Utils.equals(a[k], b[k])) { return; } ret[k] = b[k]; }); return ret; } static diffEntities<T extends IEntityType<T>>(a: T, b: T): EntityData<T> { return Utils.diff(Utils.prepareEntity(a), Utils.prepareEntity(b)) as EntityData<T>; } static prepareEntity<T extends IEntityType<T>>(e: T): EntityData<T> { const metadata = MetadataStorage.getMetadata(); const meta = metadata[e.constructor.name]; const ret = Utils.copy(e); delete ret.__initialized; // remove collections and references Object.values(meta.properties).forEach(prop => { const pk = () => metadata[prop.type].primaryKey; const name = prop.name as keyof T; if (e[name] as any instanceof ArrayCollection || (Utils.isEntity(e[name]) && !e[name][pk()])) { return delete ret[name]; } if (Utils.isEntity(e[name])) { return ret[prop.name] = ret[prop.name][pk()]; } }); // remove unknown properties Object.keys(e).forEach(prop => { if (!meta.properties[prop] || meta.properties[prop].persist === false) { delete ret[prop]; } }); return ret; } static copy(entity: any): any { return clone(entity); } static renameKey(payload: any, from: string, to: string): void { if (Utils.isObject(payload) && from in payload && !(to in payload)) { payload[to] = payload[from]; delete payload[from]; } } static getParamNames(func: Function | string): string[] { const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; const ARGUMENT_NAMES = /([^\s,]+)/g; const fnStr = func.toString().replace(STRIP_COMMENTS, ''); // strip comments let paramsStr = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')); // extract params paramsStr = paramsStr.replace(/{[^}]+}/g, '{}'); // simplify object default values like `a = { ... }` paramsStr = paramsStr.replace(/\[[^\]]+]/g, '[]'); // simplify array default values like `a = [ ... ]` const result = paramsStr.match(ARGUMENT_NAMES) as string[]; if (result === null) { return []; } // handle class with no constructor if (result.length > 0 && result[0] === 'class') { return []; } // strip default values for (let i = 0; i < result.length; i++) { if (result[i].includes('=')) { result[i] = result[i].split('=')[0]; result.splice(i + 1, 1); } } return result.filter(i => i); // filter out empty strings } static isPrimaryKey(key: any): key is IPrimaryKey { return typeof key === 'string' || typeof key === 'number' || Utils.isObjectID(key); } static extractPK(data: any, meta?: EntityMetadata): IPrimaryKey | null { if (Utils.isPrimaryKey(data)) { return data; } if (Utils.isObject(data) && meta) { return data[meta.primaryKey] || data[meta.serializedPrimaryKey] || null; } return null; } static isEntity<T = IEntity>(data: any): data is T { return Utils.isObject(data) && !!data.__entity; } static isObjectID(key: any) { return Utils.isObject(key) && key.constructor.name === 'ObjectID'; } static className(classOrName: string | Function): string { if (typeof classOrName === 'string') { return classOrName; } return classOrName.name; } /** * uses some dark magic to get source path to caller where decorator is used */ static lookupPathFromDecorator(meta: EntityMetadata): void { if (meta.path) { return; } // use some dark magic to get source path to caller const stack = new Error().stack!.split('\n'); const line = stack.find(line => line.includes('__decorate'))!; if (line) { meta.path = line.match(/\((.*):\d+:\d+\)/)![1]; } } static getObjectType(value: any): string { const objectType = Object.prototype.toString.call(value); return objectType.match(/\[object (\w+)]/)![1].toLowerCase(); } static async runSerial<T = any, U = any>(items: Iterable<U>, cb: (item: U) => Promise<T>): Promise<T[]> { const ret = []; for (const item of items) { ret.push(await cb(item)); } return ret; } static isCollection(item: any, prop?: EntityProperty, type?: ReferenceType): item is Collection<IEntity> { if (!(item instanceof Collection)) { return false; } return !(prop && type) || prop.reference === type; } }