rethinkts
Version:
A model system for RethinkDB, written in and for TypeScript.
130 lines (103 loc) • 3.84 kB
text/typescript
import { Term } from 'rethinkdbdash';
import { ColumnInfo } from './column';
import { assignWithArrays, pick as _pick } from './utils';
import { RethinkAdapter } from './adapter';
import { Hooks } from './hooks';
import { RelationshipInfo } from './relationships';
export const modelInfoKey = Symbol('rethink model info');
export interface ModelCtor<T> {
new(): T;
}
export interface IndexInfo {
name: string;
keys: string[] | ((r: Term<any>) => any);
options?: {
multi?: boolean;
geo?: boolean;
}
}
export interface ModelInfo {
database: string;
table: string;
columns: ColumnInfo[];
indexes: IndexInfo[];
primaryKey: string;
tags: Map<string, Set<string>>;
relationships: RelationshipInfo[];
}
export function createModelInfo(target: ModelCtor<any>, ...objs: Partial<ModelInfo>[]): ModelInfo {
return target[modelInfoKey] = assignWithArrays(<Partial<ModelInfo>> {
columns: [],
indexes: [],
relationships: [],
primaryKey: 'id',
tags: new Map<string, Set<string>>(),
}, target[modelInfoKey], ...objs);
}
/**
* Model decorator
*/
export function Model(info: Partial<ModelInfo>): ClassDecorator {
return (target: ModelCtor<any>) => {
const modelInfo = createModelInfo(target, info);
target.prototype.toJSON = function () {
return [...modelInfo.columns, ...modelInfo.relationships]
.filter(column => this[column.key] !== undefined)
.reduce((t, column) => ({ ...t, [column.key]: this[column.key] }), {});
};
};
}
function getKeys(ctor: ModelCtor<any>, tagsOrKeys: string[] | string): string[] {
const modelTags = Model.getInfo(ctor).tags;
let keys: string[];
if (Array.isArray(tagsOrKeys)) {
keys = tagsOrKeys;
} else {
keys = [tagsOrKeys];
}
return keys.reduce((keys, tagOrKey) => {
if (modelTags.has(tagOrKey)) {
return [...keys, ...modelTags.get(tagOrKey)];
}
return [...keys, tagOrKey];
}, []);
}
export namespace Model {
export function construct<M>(ctor: ModelCtor<M>, data: Partial<M>): M {
return Model.assign(new ctor(), data);
}
export function assign<M>(model: M, ...sources: Partial<M>[]): M {
return Object.assign(model, ...sources);
}
export function pickAssign<M>(model: M, tagsOrKeys: string[] | string, ...sources: Partial<M>[]): M {
const ctor = <ModelCtor<M>> model.constructor;
const keys = getKeys(ctor, tagsOrKeys) as Array<keyof M>;
return Model.assign(model, ...sources.map(source => _pick(source, ...keys)));
}
export function pick<M>(model: M, ...tagsOrKeys: string[]): Partial<M> {
const ctor = <ModelCtor<M>> model.constructor;
const modelTags = Model.getInfo(ctor).tags;
const data = tagsOrKeys.reduce((target, tagOrKey) => {
if (modelTags.has(tagOrKey)) {
return { ...target, ..._pick<M, any>(model, ...modelTags.get(tagOrKey)) };
}
return { ...target, [tagOrKey]: model[tagOrKey] };
}, {});
return Model.construct(ctor, data);
}
export function without<M>(model: M, ...tagsOrKeys: string[]): Partial<M> {
const ctor = <ModelCtor<M>> model.constructor;
const keys = getKeys(ctor, tagsOrKeys);
const pickKeys = Object.keys(model)
.filter(key => !keys.includes(key));
return Model.construct(ctor, _pick<M, any>(model, ...pickKeys));
}
export function getInfo(ctor: ModelCtor<any>): ModelInfo {
return ctor[modelInfoKey];
}
export function notify(model: any, hook: Hooks, ...args: any[]): any {
if (model[hook]) {
return model[hook](...args);
}
}
}