neweb
Version:
[![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Coverage percentage][coveralls-image]][coveralls-url] [ • 12.8 kB
text/typescript
import o, { Onemitter } from "onemitter";
import uid = require("uid-safe");
export interface IGlobalStoreDataTypeConfig {
persistant: boolean;
lifetime: number;
}
export interface IGlobalStoreObjectTypeConfig {
lifetime: number;
}
export type IGlobalStoreActionFn<
ACTIONS extends {
[index: string]: { params: ACTIONS[ACTIONTYPE]["params"], args: ACTIONS[ACTIONTYPE]["args"] };
},
ACTIONTYPE extends keyof ACTIONS
> = (
store: GlobalStore<any, any, any>,
params: ACTIONS[ACTIONTYPE]["params"],
args: ACTIONS[ACTIONTYPE]["args"],
) => void | Promise<void>;
export interface IGlobalStoreActionResolver<ACTIONS extends {
[index: string]: { params: ACTIONS[ACTIONTYPE]["params"], args: ACTIONS[ACTIONTYPE]["args"] };
}, ACTIONTYPE extends keyof ACTIONS> {
action: IGlobalStoreActionFn<ACTIONS, ACTIONTYPE>;
}
export interface IGlobalStoreConfig<T, O, ACTIONS extends {
[index: string]: { params: any, args: any };
}> {
storePath: string;
dataTypes: { [P in keyof T]: IGlobalStoreDataTypeConfig };
objectsTypes: { [P in keyof O]: IGlobalStoreObjectTypeConfig };
actions: { [P in keyof ACTIONS]: IGlobalStoreActionResolver<ACTIONS, P> };
}
export interface IGlobalStoreObject<T, DATATYPES, OBJECTSTYPES, ACTIONSTYPES> {
object: T;
dependents: Array<IGlobalStoreItem<DATATYPES, OBJECTSTYPES, ACTIONSTYPES>>;
createdAt: Date;
}
export interface IGlobalStoreDataItem<DATATYPES> {
type: "data";
dataType: keyof DATATYPES;
id: string | string[];
}
export interface IGlobalStoreActionItem<ACTIONSTYPES> {
type: "action";
actionType: keyof ACTIONSTYPES;
id: string | string[];
}
export interface IGlobalStoreObjectItem<OBJECTSTYPES> {
type: "object";
objectType: keyof OBJECTSTYPES;
id: string | string[];
}
export type IGlobalStoreItem<DATATYPES, OBJECTSTYPES, ACTIONSTYPES> = IGlobalStoreActionItem<ACTIONSTYPES> |
IGlobalStoreDataItem<DATATYPES> | IGlobalStoreObjectItem<OBJECTSTYPES>;
export type IGlobalStoreParentItem<DATATYPES, OBJECTSTYPES> =
IGlobalStoreDataItem<DATATYPES> | IGlobalStoreObjectItem<OBJECTSTYPES>;
class GlobalStore<T, O,
ACTIONS extends { [index: string]: { params: any, args: any } }> {
protected data: {
[P in keyof T]: {
[index: string]: {
createdAt: Date;
updatedAt: Date;
emitter: Onemitter<T[P]>;
dependents: Array<IGlobalStoreItem<T, O, ACTIONS>>;
parent: IGlobalStoreParentItem<T, O>;
};
};
} = {} as any;
protected objects: { [P in keyof O]: {
[index: string]: IGlobalStoreObject<O[P], T, O, ACTIONS>;
} } = {} as any;
protected actions: { [P in keyof ACTIONS]: {
[index: string]: {
action: IGlobalStoreActionFn<ACTIONS, P>;
parent: IGlobalStoreParentItem<T, O>;
};
} } = {} as any;
protected dataTypes: { [P in keyof T]: IGlobalStoreDataTypeConfig } = {} as any;
protected objectsTypes: { [P in keyof O]: IGlobalStoreObjectTypeConfig } = {} as any;
constructor(protected config: IGlobalStoreConfig<T, O, ACTIONS>) {
this.dataTypes = this.config.dataTypes;
this.objectsTypes = this.config.objectsTypes;
for (const typeName of Object.keys(this.config.dataTypes)) {
(this.data as any)[typeName] = {};
}
for (const typeName of Object.keys(this.config.objectsTypes)) {
(this.objects as any)[typeName] = {};
}
for (const typeName of Object.keys(this.config.actions)) {
(this.actions as any)[typeName] = {};
}
(this.objects as any).store = {};
(this.objects as any).store.root = {
createdAt: new Date(),
dependents: [],
object: this,
};
}
public async dump() {
return {
actions: this.actions,
objects: this.objects,
data: this.data,
};
}
public async setObject<P extends keyof O>(
type: P,
id: string | string[],
parent: IGlobalStoreParentItem<T, O>,
object: O[P]): Promise<void> {
if (Array.isArray(id)) {
id = id.join("~");
}
if (this.objects[type][id]) {
await this.removeObject(type, id);
}
const parentItem = await this.getItem(parent);
parentItem.dependents.push({
type: "object",
objectType: type,
id,
});
this.objects[type][id] = {
createdAt: new Date(),
dependents: [],
object,
};
}
public async removeObjectByInstance<P extends keyof O>(type: P, instance: O[P]) {
const objectId = Object.keys(this.objects[type]).find((key) => this.objects[type][key].object === instance);
if (objectId) {
delete this.objects[type][objectId];
}
}
public async getObject<P extends keyof O>(type: P, id: string | string[]): Promise<O[P]> {
if (Array.isArray(id)) {
id = id.join("~");
}
if (!this.objects[type][id]) {
throw new Error("Not found object `" + type + "` with id " + id);
}
return this.objects[type][id].object;
}
public async hasObject<P extends keyof O>(type: P, id: string | string[]): Promise<boolean> {
if (Array.isArray(id)) {
id = id.join("~");
}
if (!this.objects[type][id]) {
return false;
}
return true;
}
public async removeObject(type: keyof O, id: string | string[]) {
if (Array.isArray(id)) {
id = id.join("~");
}
if (this.objects[type][id]) {
delete this.objects[type][id];
}
}
public getEmitter(type: keyof T, id: string | string[]): Onemitter<T[keyof T]> {
if (Array.isArray(id)) {
id = id.join("~");
}
if (!this.data[type][id]) {
this.createData(type, id);
}
return this.data[type][id].emitter;
}
public async findId<P extends keyof T>(type: P, filter: (value: T[P]) => boolean): Promise<string> {
const id = Object.keys(this.data[type]).find((v) => this.data[type][v].emitter.has()
&& filter(this.data[type][v].emitter.get()));
if (!id) {
throw new Error("Not found value by filter " + filter);
}
return id;
}
public async create<P extends keyof T>(
type: P, id: string | string[],
parent: IGlobalStoreParentItem<T, O>,
value: T[P]) {
if (Array.isArray(id)) {
id = id.join("~");
}
await this.createData(type, id);
const parentItem = await this.getItem(parent);
parentItem.dependents.push({
type: "data",
dataType: type,
id,
});
this.data[type][id].parent = parent;
this.data[type][id].emitter.emit(value);
}
public async set<P extends keyof T>(type: P, keys: string | string[], value: T[P]): Promise<void> {
let id: string;
if (Array.isArray(keys)) {
id = keys.join("~");
} else {
id = keys;
keys = [keys];
}
if (!this.data[type][id]) {
throw new Error("Not found data with type " + type + " and id " + id);
}
this.data[type][id].updatedAt = new Date();
this.data[type][id].emitter.emit(value);
}
public async has(type: keyof T, keys: string | string[]): Promise<boolean> {
const id = Array.isArray(keys) ? keys.join("~") : keys;
const hasLocal = !!this.data[type][id] && this.data[type][id].emitter.has();
if (hasLocal) {
return true;
}
return false;
}
public async getMaybe<P extends keyof T>(type: P, id: string | string[]): Promise<T[P] | undefined> {
if (Array.isArray(id)) {
id = id.join("~");
}
if (!this.data[type][id] || !this.data[type][id].emitter.has()) {
return undefined;
}
return this.data[type][id].emitter.get();
}
public async get<P extends keyof T>(type: P, id: string | string[]): Promise<T[P]> {
if (Array.isArray(id)) {
id = id.join("~");
}
if (this.data[type][id]) {
return this.data[type][id].emitter.get();
}
throw new Error("Not found object " + type + " with id " + id);
}
public async remove(type: keyof T, id: string | string[]): Promise<void> {
if (Array.isArray(id)) {
id = id.join("~");
}
await Promise.all(this.data[type][id].dependents.map((dependent) => {
switch (dependent.type) {
case "data":
return this.remove(dependent.dataType, dependent.id);
case "object":
return this.removeObject(dependent.objectType, dependent.id);
case "action":
return this.removeAction(dependent.actionType, dependent.id);
}
}));
if (this.data[type][id]) {
delete this.data[type][id];
}
}
public createContainer(params: {
key: (...args: any[]) => {
type: keyof T;
keys: string[] | string;
};
}) {
return {
get: (...args: any[]) => {
const p = params.key(...args);
return this.getEmitter(p.type, p.keys);
},
set: (...args: any[]) => {
const value = args.pop();
const p = params.key(...args);
return this.set(p.type, p.keys, value);
},
};
}
public async removeAction<P extends keyof ACTIONS>(eventName: P, id: string | string[]) {
if (Array.isArray(id)) {
id = id.join("~");
}
delete this.actions[eventName][id];
}
public async action<P extends keyof ACTIONS>(
eventName: P,
parent: IGlobalStoreParentItem<T, O>,
params: ACTIONS[P]["params"]):
Promise<(args: ACTIONS[P]["args"]) => void | Promise<void>> {
const actionId = await this.generateId();
const action = ((
store: GlobalStore<any, any, any>,
type: P,
id: string | string[],
parameters: ACTIONS[P]["params"],
args: ACTIONS[P]["args"],
) => {
if (Array.isArray(id)) {
id = id.join("~");
}
if (store.actions[type][id]) {
return store.config.actions[type].action(store, parameters, args);
}
return;
}).bind(undefined, this, eventName, actionId, params);
this.actions[eventName][actionId] = {
action,
parent,
};
const parentItem = await this.getItem(parent);
parentItem.dependents.push({
type: "action",
actionType: eventName,
id: actionId,
});
return action;
}
public async dispatch<P extends keyof ACTIONS>(
eventName: P,
parent: IGlobalStoreParentItem<T, O>,
params: ACTIONS[P]["params"], args: ACTIONS[P]["args"]): Promise<void> {
if (this.getItem(parent)) {
await this.config.actions[eventName].action(this, params, args);
}
}
protected async getItem(item: IGlobalStoreItem<T, O, ACTIONS>): Promise<{
dependents: Array<IGlobalStoreItem<T, O, ACTIONS>>;
}> {
const id = Array.isArray(item.id) ? item.id.join("~") : item.id;
if (item.type === "data") {
return this.data[item.dataType][id];
}
if (item.type === "object") {
return this.objects[item.objectType][id];
}
throw new Error("Unknown type " + item);
}
protected createData(type: keyof T, id: string) {
this.data[type][id] = {
createdAt: new Date(),
updatedAt: new Date(),
emitter: o(),
dependents: [],
parent: {} as any,
};
}
protected async generateId() {
const id = new Date().getTime().toString() + await uid(7);
return id;
}
}
export default GlobalStore;