@hecom/specials
Version:
A tool to manage general or special handle.
232 lines (216 loc) • 6.75 kB
text/typescript
export const DEFAULT_HANDLE = '__default__';
export const SPECIAL_PART = '__special__';
export const CHILD_PART = '__child__';
export const kId = 'id';
export const kSpecial = 'special';
export const kHandle = 'handle';
export const kPriority = 'priority';
export const PRIORITY = {
LOW: -255,
DEFAULT: 0,
HIGH: 255,
};
export type PathKey = string | number | void | null;
export type Path = PathKey | PathKey[];
export type StateFunc<S> = (state?: S) => boolean;
export type ResultFunc = () => any;
export type HandleResult<R> = R | ResultFunc | void;
export type HandleFunc<P, R> = (params?: P) => HandleResult<R>;
export type HandleId = number;
export interface Special<S, P, R> {
id: HandleId;
special: StateFunc<S>;
handle: HandleFunc<P, R>;
priority: number;
}
export interface Item<S, P, R> {
[DEFAULT_HANDLE]?: R | HandleFunc<P, R>;
[SPECIAL_PART]: Special<S, P, R>[];
[CHILD_PART]: {
[key: string]: Item<S, P, R>;
};
}
export type Root<S, P, R> = Item<S, P, R>;
export interface Instance<S, P, R> {
getStorage: () => Root<S, P, R>;
clearStorage: () => void;
checkIsDefault: (path: Path, state?: S, params?: P) => boolean;
get: (path: Path, state?: S, params?: P) => HandleResult<R>;
registerDefault: (path: Path, handle: R | HandleFunc<P, R>) => void;
registerSpecial: (path: Path, special: StateFunc<S>, handle: HandleFunc<P, R>, priority?: number) => HandleId;
unregister: (path: Path, handleId?: HandleId) => boolean;
}
export function getInstance<S, P, R>(): Instance<S, P, R> {
const root: Root<S, P, R> = generateNode();
return {
getStorage: function () {
return root;
},
clearStorage: function () {
delete root[DEFAULT_HANDLE];
root[SPECIAL_PART] = [];
root[CHILD_PART] = {};
},
checkIsDefault: function (path: Path, state?: S, params?: P) {
return checkIsDefault(root, path, state, params);
},
get: function (path: Path, state?: S, params?: P) {
return get(root, path, state, params) as HandleResult<R>;
},
registerDefault: function (path: Path, handle: R | HandleFunc<P, R>) {
return registerDefault(root, path, handle);
},
registerSpecial: function (path: Path, special: StateFunc<S>, handle: HandleFunc<P, R>, priority: number = PRIORITY.DEFAULT) {
return registerSpecial(root, path, special, handle, priority);
},
unregister: function (path: Path, handleId?: HandleId) {
return unregister(root, path, handleId);
},
};
}
export function validPath(path: Path): string[] {
const paths = Array.isArray(path) ? path : [path];
const validPath: string[] = [];
paths.forEach(function (i) {
if (i && i !== DEFAULT_HANDLE) {
validPath.push(String(i));
}
});
return validPath;
}
export function generateNode<S, P, R>(): Item<S, P, R> {
return {
[SPECIAL_PART]: [],
[CHILD_PART]: {},
};
}
/**
* Find handle in root object with path.
* If it does not exist, get the default handle in current or above level.
*/
function get<S, P, R>(
obj: Root<S, P, R>,
path: Path,
state?: S,
params?: P,
onlyCheckDefault = false
): HandleResult<R> | boolean {
const paths = validPath(path);
const items = [obj];
paths.reduce((prv, cur) => {
if (!prv) {
return prv;
} else {
prv[CHILD_PART][cur] && items.push(prv[CHILD_PART][cur]);
return prv[CHILD_PART][cur];
}
}, obj);
// Special Check
for (let i = items.length - 1; i >= 0; i--) {
const specs = items[i][SPECIAL_PART].filter(cur => cur[kSpecial](state));
if (specs.length > 0) {
if (onlyCheckDefault) {
return false;
} else {
const result = specs.reduce((prv, cur) => prv[kPriority] < cur[kPriority] ? cur : prv);
const handle = result[kHandle];
if (params && typeof handle === 'function') {
return handle(params);
} else {
return handle as ResultFunc;
}
}
}
}
if (onlyCheckDefault) {
return true;
}
// Regular Check
for (let i = items.length - 1; i >= 0; i--) {
const item = items[i][DEFAULT_HANDLE];
if (item) {
if (params && typeof item === 'function') {
const func = item as HandleFunc<P, R>;
return func(params);
} else {
return item as R;
}
}
}
}
function checkIsDefault<S, P, R>(
obj: Root<S, P, R>,
path: Path,
state?: S,
params?: P
): boolean {
return get(obj, path, state, params, true) as boolean;
}
/**
* Register a default handle in root object.
*/
function registerDefault<S, P, R>(
obj: Root<S, P, R>,
path: Path,
handle: R | HandleFunc<P, R>
): void {
const paths = validPath(path);
const item = paths.reduce((prv, cur) => {
if (!prv[CHILD_PART][cur]) {
prv[CHILD_PART][cur] = generateNode();
}
return prv[CHILD_PART][cur];
}, obj);
item[DEFAULT_HANDLE] = handle;
}
/**
* Register a special handle in root object.
*/
function registerSpecial<S, P, R>(
obj: Root<S, P, R>,
path: Path,
special: StateFunc<S>,
handle: HandleFunc<P, R>,
priority: number
): HandleId {
const paths = validPath(path);
const item = paths.reduce((prv, cur) => {
if (!prv[CHILD_PART][cur]) {
prv[CHILD_PART][cur] = generateNode();
}
return prv[CHILD_PART][cur];
}, obj);
const arr = item[SPECIAL_PART];
const handleId = arr.length === 0 ? 1 : arr[arr.length - 1][kId] + 1;
arr.push({
[kId]: handleId,
[kSpecial]: special,
[kHandle]: handle,
[kPriority]: priority,
});
return handleId;
}
/**
* Unregister a handle in root object.
*/
function unregister<S, P, R>(
obj: Root<S, P, R>,
path: Path,
handleId?: HandleId
): boolean {
const paths = validPath(path);
const item = paths.reduce((prv, cur) => prv[CHILD_PART][cur] || generateNode(), obj);
if (handleId) {
const len = item[SPECIAL_PART].length;
item[SPECIAL_PART] = item[SPECIAL_PART]
.filter(cur => cur[kId] !== handleId);
return item[SPECIAL_PART].length !== len;
} else {
if (item[DEFAULT_HANDLE]) {
delete item[DEFAULT_HANDLE];
return true;
} else {
return false;
}
}
}