@nimel/directorr-router
Version:
Router store for directorr
338 lines (327 loc) • 13.5 kB
JavaScript
/* (c) Nikita Melnikov - MIT Licensed 2020 - now */
import { pathToRegexp, compile } from 'path-to-regexp';
import qs from 'query-string';
import { EMPTY_OBJECT, EMPTY_STRING, createActionAndEffect, action, effect, composePropertyDecorators, whenState, whenPayload, EMPTY_FUNC, whenDestroy } from '@nimel/directorr';
import { createBrowserHistory } from 'history';
const ANY_PATH = '(.*)';
const ROOT_URL = '/';
class Cache {
constructor(cacheSize = 10000) {
this.store = new Map();
this.cacheSize = cacheSize;
}
set(key, value) {
if (this.store.size > this.cacheSize) {
this.store.delete(this.store.keys().next().value);
}
return this.store.set(key, value);
}
get(key) {
return this.store.get(key);
}
has(key) {
return this.store.has(key);
}
get size() {
return this.store.size;
}
}
const CACHE_PATTERNS = new Map();
const CACHE_PATHS = new Cache();
function compileRGXP(pattern, end, strict) {
const cacheKey = EMPTY_STRING + end + strict;
if (!CACHE_PATTERNS.has(cacheKey))
CACHE_PATTERNS.set(cacheKey, new Cache());
const patternCache = CACHE_PATTERNS.get(cacheKey);
if (patternCache.has(pattern))
return patternCache.get(pattern);
const keys = [];
const reg = pathToRegexp(pattern, keys, { end, strict });
const compiledRGXP = { reg, keys };
patternCache.set(pattern, compiledRGXP);
return compiledRGXP;
}
function matchPath(pathname, urlPattern, exact = true, strict = false) {
const { reg, keys } = compileRGXP(urlPattern, exact, strict);
const patterns = reg.exec(pathname);
if (patterns) {
return {
patterns,
keys,
};
}
}
function calcParams(patterns, keys) {
const params = {};
for (let i = 0, l = keys.length, name, value; i < l; ++i) {
name = keys[i].name;
value = patterns[i + 1];
if (name) {
params[name] = value;
}
}
return params;
}
function calcPath(path, queryObject) {
if (queryObject) {
const query = qs.stringify(queryObject);
return query ? `${path}?${query}` : path;
}
return path;
}
function compilePath(path) {
if (CACHE_PATHS.has(path))
return CACHE_PATHS.get(path);
const genPath = compile(path);
CACHE_PATHS.set(path, genPath);
return genPath;
}
function generatePath(path = ROOT_URL, params = EMPTY_OBJECT) {
return path === ROOT_URL ? path : compilePath(path)(params);
}
function reloadWindow() {
window.location.reload();
}
function returnTrue() {
return true;
}
function addStoreToPayload(payload, store) {
return Object.assign(Object.assign({}, payload), { store });
}
function pickSameStore(payload, store) {
return store === payload.store;
}
const [actionRouterPush, effectRouterPush] = createActionAndEffect('ROUTER.PUSH');
const [actionRouterReplace, effectRouterReplace] = createActionAndEffect('ROUTER.REPLACE');
const [actionRouterBack, effectRouterBack] = createActionAndEffect('ROUTER.BACK');
const [actionRouterForward, effectRouterForward] = createActionAndEffect('ROUTER.FORWARD');
const [actionRouterGoTo, effectRouterGoTo] = createActionAndEffect('ROUTER.GOTO');
const [actionRouterReload, effectRouterReload] = createActionAndEffect('ROUTER.RELOAD');
const [actionRouterBlock, effectRouterBlock] = createActionAndEffect('ROUTER.BLOCK');
const [actionRouterCancelBlock, effectRouterCancelBlock] = createActionAndEffect('ROUTER.CANCEL_BLOCK');
const [actionRouterState, effectRouterState] = createActionAndEffect('ROUTER.STATE');
const actionRouterIsPattern = action('ROUTER.IS_PATTERN', addStoreToPayload);
const effectRouterIsPattern = effect(actionRouterIsPattern.type);
const actionRouterIsPatternSuccess = action('ROUTER.IS_PATTERN_SUCCESS');
const effectRouterIsPatternSuccess = composePropertyDecorators([effect(actionRouterIsPatternSuccess.type), whenState(pickSameStore)]);
const [actionHistoryPop, effectHistoryPop] = createActionAndEffect('HISTORY.POP');
const [actionHistoryPush, effectHistoryPush] = createActionAndEffect('HISTORY.PUSH');
const [actionHistoryReplace, effectHistoryReplace] = createActionAndEffect('HISTORY.REPLACE');
const DEFAULT_OPTIONS = {
exact: true,
strict: true,
};
function historyChange(urlPattern, { exact, strict } = DEFAULT_OPTIONS) {
return composePropertyDecorators([
effectHistoryPop,
effectHistoryPush,
effectHistoryReplace,
whenPayload(returnTrue, (payload) => {
const match = matchPath(payload.path, urlPattern, exact, strict);
return Object.assign(Object.assign({}, payload), { match: !!match, queryObject: Object.assign(Object.assign({}, payload.queryObject), ((match === null || match === void 0 ? void 0 : match.keys.length) && { params: calcParams(match.patterns, match.keys) })) });
}),
]);
}
const ACTION = {
POP: 'POP',
PUSH: 'PUSH',
REPLACE: 'REPLACE',
};
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function __metadata(metadataKey, metadataValue) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
}
class HistoryStore {
constructor(history = createBrowserHistory()) {
this.unsubHistory = EMPTY_FUNC;
this.handlersStack = [];
this.blockState = [EMPTY_FUNC, EMPTY_FUNC];
this.subscribe = (handler) => {
this.handlersStack = [...this.handlersStack, handler];
};
this.unsubscribe = (handler) => {
this.handlersStack = this.handlersStack.filter(h => h !== handler);
};
this.toSetState = ({ path, queryObject, state }) => {
this.path = path;
this.queryObject = queryObject;
this.state = state;
};
this.toDestroy = () => {
this.unsubHistory();
};
this.toHistoryPush = (payload) => payload;
this.toHistoryPop = (payload) => payload;
this.toHistoryReplace = (payload) => payload;
this.toPop = ({ path, queryObject, state, action }) => {
this.path = path;
this.queryObject = queryObject;
this.state = state;
this.action = action;
};
this.push = (path, queryObject = EMPTY_OBJECT, state) => ({
path,
queryObject,
state,
});
this.replace = (path, queryObject = EMPTY_OBJECT, state) => ({
path,
queryObject,
state,
});
this.back = EMPTY_FUNC;
this.reload = EMPTY_FUNC;
this.toReload = () => reloadWindow();
this.toPush = ({ path, queryObject, state }) => this.history.push(calcPath(path, queryObject), state);
this.toReplace = ({ path, queryObject, state }) => this.history.replace(calcPath(path, queryObject), state);
this.toBack = () => this.history.back();
this.goTo = (index) => ({ index });
this.toGoTo = ({ index }) => this.history.go(index);
this.forward = EMPTY_FUNC;
this.toForward = () => this.history.forward();
this.block = (blocker) => ({ blocker });
this.toBlock = ({ blocker }) => {
this.blockState = [blocker, this.history.block(blocker)];
};
this.cancelBlock = (blocker) => ({ blocker });
this.toCancelBlock = ({ blocker }) => {
const [lastBlocker, handler] = this.blockState;
if (blocker === lastBlocker) {
handler();
}
};
this.dispatchAction = (task) => {
const { location, action } = task;
const routerPayload = Object.assign(Object.assign({ path: location.pathname, queryObject: qs.parse(location.search) }, (location.state && { state: location.state })), { action });
switch (action) {
case ACTION.POP:
this.toHistoryPop(routerPayload);
break;
case ACTION.REPLACE:
this.toHistoryReplace(routerPayload);
break;
default:
this.toHistoryPush(routerPayload);
}
for (const handler of this.handlersStack) {
handler(task);
}
};
this.history = history;
const { location: { pathname, search, state }, action, } = this.history;
this.unsubHistory = this.history.listen(this.dispatchAction);
this.path = pathname;
this.queryObject = qs.parse(search);
this.state = state;
this.action = action;
}
toJSON() { }
}
__decorate([
effectRouterState,
__metadata("design:type", Object)
], HistoryStore.prototype, "toSetState", void 0);
__decorate([
whenDestroy,
__metadata("design:type", Object)
], HistoryStore.prototype, "toDestroy", void 0);
__decorate([
actionHistoryPush,
__metadata("design:type", Object)
], HistoryStore.prototype, "toHistoryPush", void 0);
__decorate([
actionHistoryPop,
__metadata("design:type", Object)
], HistoryStore.prototype, "toHistoryPop", void 0);
__decorate([
actionHistoryReplace,
__metadata("design:type", Object)
], HistoryStore.prototype, "toHistoryReplace", void 0);
__decorate([
effectHistoryPop,
effectHistoryPush,
effectHistoryReplace,
__metadata("design:type", Object)
], HistoryStore.prototype, "toPop", void 0);
__decorate([
actionRouterPush,
__metadata("design:type", Object)
], HistoryStore.prototype, "push", void 0);
__decorate([
actionRouterReplace,
__metadata("design:type", Object)
], HistoryStore.prototype, "replace", void 0);
__decorate([
actionRouterBack,
__metadata("design:type", Object)
], HistoryStore.prototype, "back", void 0);
__decorate([
actionRouterReload,
__metadata("design:type", Object)
], HistoryStore.prototype, "reload", void 0);
__decorate([
effectRouterReload,
__metadata("design:type", Object)
], HistoryStore.prototype, "toReload", void 0);
__decorate([
effectRouterPush,
__metadata("design:type", Object)
], HistoryStore.prototype, "toPush", void 0);
__decorate([
effectRouterReplace,
__metadata("design:type", Object)
], HistoryStore.prototype, "toReplace", void 0);
__decorate([
effectRouterBack,
__metadata("design:type", Object)
], HistoryStore.prototype, "toBack", void 0);
__decorate([
actionRouterGoTo,
__metadata("design:type", Object)
], HistoryStore.prototype, "goTo", void 0);
__decorate([
effectRouterGoTo,
__metadata("design:type", Object)
], HistoryStore.prototype, "toGoTo", void 0);
__decorate([
actionRouterForward,
__metadata("design:type", Object)
], HistoryStore.prototype, "forward", void 0);
__decorate([
effectRouterForward,
__metadata("design:type", Object)
], HistoryStore.prototype, "toForward", void 0);
__decorate([
actionRouterBlock,
__metadata("design:type", Object)
], HistoryStore.prototype, "block", void 0);
__decorate([
effectRouterBlock,
__metadata("design:type", Object)
], HistoryStore.prototype, "toBlock", void 0);
__decorate([
actionRouterCancelBlock,
__metadata("design:type", Object)
], HistoryStore.prototype, "cancelBlock", void 0);
__decorate([
effectRouterCancelBlock,
__metadata("design:type", Object)
], HistoryStore.prototype, "toCancelBlock", void 0);
export { ACTION, ANY_PATH, HistoryStore, actionHistoryPop, actionHistoryPush, actionHistoryReplace, actionRouterBack, actionRouterBlock, actionRouterCancelBlock, actionRouterForward, actionRouterGoTo, actionRouterIsPattern, actionRouterIsPatternSuccess, actionRouterPush, actionRouterReload, actionRouterReplace, actionRouterState, addStoreToPayload, calcPath, effectHistoryPop, effectHistoryPush, effectHistoryReplace, effectRouterBack, effectRouterBlock, effectRouterCancelBlock, effectRouterForward, effectRouterGoTo, effectRouterIsPattern, effectRouterIsPatternSuccess, effectRouterPush, effectRouterReload, effectRouterReplace, effectRouterState, generatePath, historyChange, matchPath, pickSameStore };