@tinkoff/router 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/lib/components/react/context.browser.js +7 -0
  2. package/lib/components/react/context.es.js +7 -0
  3. package/lib/components/react/context.js +13 -0
  4. package/lib/components/react/provider.browser.js +12 -0
  5. package/lib/components/react/provider.es.js +12 -0
  6. package/lib/components/react/provider.js +16 -0
  7. package/lib/components/react/useNavigate.browser.js +17 -0
  8. package/lib/components/react/useNavigate.es.js +17 -0
  9. package/lib/components/react/useNavigate.js +21 -0
  10. package/lib/components/react/useRoute.browser.js +8 -0
  11. package/lib/components/react/useRoute.es.js +8 -0
  12. package/lib/components/react/useRoute.js +12 -0
  13. package/lib/components/react/useRouter.browser.js +8 -0
  14. package/lib/components/react/useRouter.es.js +8 -0
  15. package/lib/components/react/useRouter.js +12 -0
  16. package/lib/components/react/useUrl.browser.js +8 -0
  17. package/lib/components/react/useUrl.es.js +8 -0
  18. package/lib/components/react/useUrl.js +12 -0
  19. package/lib/history/base.browser.js +11 -0
  20. package/lib/history/base.es.js +11 -0
  21. package/lib/history/base.js +15 -0
  22. package/lib/history/client.browser.js +121 -0
  23. package/lib/history/client.es.js +121 -0
  24. package/lib/history/client.js +125 -0
  25. package/lib/history/server.es.js +15 -0
  26. package/lib/history/server.js +19 -0
  27. package/lib/history/wrapper.browser.js +72 -0
  28. package/lib/history/wrapper.es.js +72 -0
  29. package/lib/history/wrapper.js +80 -0
  30. package/lib/index.browser.js +12 -1169
  31. package/lib/index.es.js +12 -1097
  32. package/lib/index.js +36 -1124
  33. package/lib/logger.browser.js +15 -0
  34. package/lib/logger.es.js +15 -0
  35. package/lib/logger.js +23 -0
  36. package/lib/router/abstract.browser.js +395 -0
  37. package/lib/router/abstract.es.js +395 -0
  38. package/lib/router/abstract.js +404 -0
  39. package/lib/router/browser.browser.js +166 -0
  40. package/lib/router/client.browser.js +129 -0
  41. package/lib/router/client.d.ts +1 -0
  42. package/lib/router/client.es.js +129 -0
  43. package/lib/router/client.js +133 -0
  44. package/lib/router/clientNoSpa.browser.js +17 -0
  45. package/lib/router/clientNoSpa.es.js +17 -0
  46. package/lib/router/clientNoSpa.js +21 -0
  47. package/lib/router/server.es.js +85 -0
  48. package/lib/router/server.js +89 -0
  49. package/lib/tree/constants.browser.js +6 -0
  50. package/lib/tree/constants.es.js +6 -0
  51. package/lib/tree/constants.js +13 -0
  52. package/lib/tree/tree.browser.js +148 -0
  53. package/lib/tree/tree.es.js +148 -0
  54. package/lib/tree/tree.js +158 -0
  55. package/lib/tree/utils.browser.js +77 -0
  56. package/lib/tree/utils.es.js +77 -0
  57. package/lib/tree/utils.js +90 -0
  58. package/lib/utils.browser.js +35 -0
  59. package/lib/utils.es.js +35 -0
  60. package/lib/utils.js +48 -0
  61. package/package.json +5 -6
@@ -2,6 +2,7 @@ import type { Options } from './abstract';
2
2
  import { AbstractRouter } from './abstract';
3
3
  import type { Navigation, NavigateOptions } from '../types';
4
4
  export declare abstract class ClientRouter extends AbstractRouter {
5
+ protected fullRehydrationInProcess: boolean;
5
6
  constructor(options: Options);
6
7
  rehydrate(navigation: Navigation): Promise<void>;
7
8
  protected resolveRoute(...options: Parameters<AbstractRouter['resolveRoute']>): import("../types").NavigationRoute;
@@ -0,0 +1,129 @@
1
+ import { parse } from '@tinkoff/url';
2
+ import { AbstractRouter } from './abstract.es.js';
3
+ import { makeNavigateOptions, isSameHost } from '../utils.es.js';
4
+ import { logger } from '../logger.es.js';
5
+ import { ClientHistory } from '../history/client.es.js';
6
+
7
+ class ClientRouter extends AbstractRouter {
8
+ constructor(options) {
9
+ super(options);
10
+ // this flag for cases when we don't have initial router state from server - CSR fallback initialization
11
+ this.fullRehydrationInProcess = null;
12
+ this.history = new ClientHistory();
13
+ this.history.listen(async ({ type, url, navigateState, replace, history }) => {
14
+ var _a;
15
+ const currentUrl = this.getCurrentUrl();
16
+ const { pathname, query } = this.resolveUrl({ url });
17
+ const isSameUrlNavigation = currentUrl.pathname === pathname;
18
+ if (type === 'updateCurrentRoute' || (!type && isSameUrlNavigation)) {
19
+ const route = (_a = this.tree) === null || _a === void 0 ? void 0 : _a.getRoute(pathname);
20
+ await this.internalUpdateCurrentRoute({
21
+ params: route === null || route === void 0 ? void 0 : route.params,
22
+ query,
23
+ replace,
24
+ navigateState,
25
+ }, { history });
26
+ }
27
+ else {
28
+ await this.internalNavigate({
29
+ url,
30
+ replace,
31
+ navigateState,
32
+ }, { history });
33
+ }
34
+ });
35
+ }
36
+ async rehydrate(navigation) {
37
+ this.fullRehydrationInProcess = !navigation.to;
38
+ logger.debug({
39
+ event: 'rehydrate',
40
+ navigation,
41
+ });
42
+ const url = parse(window.location.href);
43
+ this.currentNavigation = {
44
+ ...navigation,
45
+ type: 'navigate',
46
+ url,
47
+ };
48
+ this.lastNavigation = this.currentNavigation;
49
+ if (this.fullRehydrationInProcess) {
50
+ await this.runHooks('beforeResolve', this.currentNavigation);
51
+ const to = this.resolveRoute({ url }, { wildcard: true });
52
+ const redirect = to === null || to === void 0 ? void 0 : to.redirect;
53
+ this.currentNavigation.to = to;
54
+ if (redirect) {
55
+ return this.redirect(this.currentNavigation, makeNavigateOptions(redirect));
56
+ }
57
+ }
58
+ // rerun guard check in case it differs from server side
59
+ await this.runGuards(this.currentNavigation);
60
+ // and init any history listeners
61
+ this.history.init(this.currentNavigation);
62
+ if (this.fullRehydrationInProcess) {
63
+ this.runSyncHooks('change', this.currentNavigation);
64
+ }
65
+ this.currentNavigation = null;
66
+ // add dehydrated route to tree to prevent its loading
67
+ if (!this.fullRehydrationInProcess) {
68
+ this.addRoute({
69
+ name: navigation.to.name,
70
+ path: navigation.to.path,
71
+ config: navigation.to.config,
72
+ // in case we have loaded page from some path-changing proxy
73
+ // save this actual path as alias
74
+ alias: url.pathname,
75
+ });
76
+ }
77
+ this.fullRehydrationInProcess = null;
78
+ }
79
+ resolveRoute(...options) {
80
+ const { url } = options[0];
81
+ // navigation for other hosts should be considered as external navigation
82
+ if (url && !isSameHost(url)) {
83
+ return;
84
+ }
85
+ return super.resolveRoute(...options);
86
+ }
87
+ async notfound(navigation) {
88
+ var _a, _b;
89
+ await super.notfound(navigation);
90
+ // in case we didn't find any matched route just force hard page navigation
91
+ const prevUrl = (_b = (_a = navigation.fromUrl) === null || _a === void 0 ? void 0 : _a.href) !== null && _b !== void 0 ? _b : window.location.href;
92
+ const nextUrl = navigation.url.href;
93
+ const isNoSpaNavigation = navigation.from && !navigation.to;
94
+ // prevent redirect cycle on the same page,
95
+ // except cases when we run no-spa navigations,
96
+ // because we need hard reload in this cases
97
+ if (isNoSpaNavigation ? true : prevUrl !== nextUrl) {
98
+ if (navigation.replace) {
99
+ window.location.replace(nextUrl);
100
+ }
101
+ else {
102
+ window.location.assign(nextUrl);
103
+ }
104
+ }
105
+ // prevent routing from any continues navigation returning promise which will be not resolved
106
+ return new Promise(() => { });
107
+ }
108
+ async block(navigation) {
109
+ return this.notfound(navigation);
110
+ }
111
+ async redirect(navigation, target) {
112
+ await super.redirect(navigation, target);
113
+ // on CSR fallback initialization, if we found a redirect,
114
+ // we need to make hard reload for prevent current page rendering
115
+ if (this.fullRehydrationInProcess) {
116
+ window.location.replace(target.url);
117
+ // prevent routing from any continues navigation returning promise which will be not resolved
118
+ return new Promise(() => { });
119
+ }
120
+ return this.internalNavigate({
121
+ ...target,
122
+ replace: target.replace || navigation.replace,
123
+ }, {
124
+ redirect: true,
125
+ });
126
+ }
127
+ }
128
+
129
+ export { ClientRouter };
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var url = require('@tinkoff/url');
6
+ var abstract = require('./abstract.js');
7
+ var utils = require('../utils.js');
8
+ var logger = require('../logger.js');
9
+ var client = require('../history/client.js');
10
+
11
+ class ClientRouter extends abstract.AbstractRouter {
12
+ constructor(options) {
13
+ super(options);
14
+ // this flag for cases when we don't have initial router state from server - CSR fallback initialization
15
+ this.fullRehydrationInProcess = null;
16
+ this.history = new client.ClientHistory();
17
+ this.history.listen(async ({ type, url, navigateState, replace, history }) => {
18
+ var _a;
19
+ const currentUrl = this.getCurrentUrl();
20
+ const { pathname, query } = this.resolveUrl({ url });
21
+ const isSameUrlNavigation = currentUrl.pathname === pathname;
22
+ if (type === 'updateCurrentRoute' || (!type && isSameUrlNavigation)) {
23
+ const route = (_a = this.tree) === null || _a === void 0 ? void 0 : _a.getRoute(pathname);
24
+ await this.internalUpdateCurrentRoute({
25
+ params: route === null || route === void 0 ? void 0 : route.params,
26
+ query,
27
+ replace,
28
+ navigateState,
29
+ }, { history });
30
+ }
31
+ else {
32
+ await this.internalNavigate({
33
+ url,
34
+ replace,
35
+ navigateState,
36
+ }, { history });
37
+ }
38
+ });
39
+ }
40
+ async rehydrate(navigation) {
41
+ this.fullRehydrationInProcess = !navigation.to;
42
+ logger.logger.debug({
43
+ event: 'rehydrate',
44
+ navigation,
45
+ });
46
+ const url$1 = url.parse(window.location.href);
47
+ this.currentNavigation = {
48
+ ...navigation,
49
+ type: 'navigate',
50
+ url: url$1,
51
+ };
52
+ this.lastNavigation = this.currentNavigation;
53
+ if (this.fullRehydrationInProcess) {
54
+ await this.runHooks('beforeResolve', this.currentNavigation);
55
+ const to = this.resolveRoute({ url: url$1 }, { wildcard: true });
56
+ const redirect = to === null || to === void 0 ? void 0 : to.redirect;
57
+ this.currentNavigation.to = to;
58
+ if (redirect) {
59
+ return this.redirect(this.currentNavigation, utils.makeNavigateOptions(redirect));
60
+ }
61
+ }
62
+ // rerun guard check in case it differs from server side
63
+ await this.runGuards(this.currentNavigation);
64
+ // and init any history listeners
65
+ this.history.init(this.currentNavigation);
66
+ if (this.fullRehydrationInProcess) {
67
+ this.runSyncHooks('change', this.currentNavigation);
68
+ }
69
+ this.currentNavigation = null;
70
+ // add dehydrated route to tree to prevent its loading
71
+ if (!this.fullRehydrationInProcess) {
72
+ this.addRoute({
73
+ name: navigation.to.name,
74
+ path: navigation.to.path,
75
+ config: navigation.to.config,
76
+ // in case we have loaded page from some path-changing proxy
77
+ // save this actual path as alias
78
+ alias: url$1.pathname,
79
+ });
80
+ }
81
+ this.fullRehydrationInProcess = null;
82
+ }
83
+ resolveRoute(...options) {
84
+ const { url } = options[0];
85
+ // navigation for other hosts should be considered as external navigation
86
+ if (url && !utils.isSameHost(url)) {
87
+ return;
88
+ }
89
+ return super.resolveRoute(...options);
90
+ }
91
+ async notfound(navigation) {
92
+ var _a, _b;
93
+ await super.notfound(navigation);
94
+ // in case we didn't find any matched route just force hard page navigation
95
+ const prevUrl = (_b = (_a = navigation.fromUrl) === null || _a === void 0 ? void 0 : _a.href) !== null && _b !== void 0 ? _b : window.location.href;
96
+ const nextUrl = navigation.url.href;
97
+ const isNoSpaNavigation = navigation.from && !navigation.to;
98
+ // prevent redirect cycle on the same page,
99
+ // except cases when we run no-spa navigations,
100
+ // because we need hard reload in this cases
101
+ if (isNoSpaNavigation ? true : prevUrl !== nextUrl) {
102
+ if (navigation.replace) {
103
+ window.location.replace(nextUrl);
104
+ }
105
+ else {
106
+ window.location.assign(nextUrl);
107
+ }
108
+ }
109
+ // prevent routing from any continues navigation returning promise which will be not resolved
110
+ return new Promise(() => { });
111
+ }
112
+ async block(navigation) {
113
+ return this.notfound(navigation);
114
+ }
115
+ async redirect(navigation, target) {
116
+ await super.redirect(navigation, target);
117
+ // on CSR fallback initialization, if we found a redirect,
118
+ // we need to make hard reload for prevent current page rendering
119
+ if (this.fullRehydrationInProcess) {
120
+ window.location.replace(target.url);
121
+ // prevent routing from any continues navigation returning promise which will be not resolved
122
+ return new Promise(() => { });
123
+ }
124
+ return this.internalNavigate({
125
+ ...target,
126
+ replace: target.replace || navigation.replace,
127
+ }, {
128
+ redirect: true,
129
+ });
130
+ }
131
+ }
132
+
133
+ exports.ClientRouter = ClientRouter;
@@ -0,0 +1,17 @@
1
+ import { ClientRouter } from './client.browser.js';
2
+
3
+ const omitHash = (url) => url.href.replace(/#.*$/, '');
4
+ class NoSpaRouter extends ClientRouter {
5
+ run(navigation) {
6
+ const { type, fromUrl, url } = navigation;
7
+ // support only updateCurrentRoute or hash navigations
8
+ if (type === 'updateCurrentRoute' || omitHash(url) === omitHash(fromUrl)) {
9
+ return super.run(navigation);
10
+ }
11
+ return this.notfound(navigation);
12
+ }
13
+ // do not call any hooks as it is only supports url updates with hash
14
+ async runHooks() { }
15
+ }
16
+
17
+ export { NoSpaRouter };
@@ -0,0 +1,17 @@
1
+ import { ClientRouter } from './client.es.js';
2
+
3
+ const omitHash = (url) => url.href.replace(/#.*$/, '');
4
+ class NoSpaRouter extends ClientRouter {
5
+ run(navigation) {
6
+ const { type, fromUrl, url } = navigation;
7
+ // support only updateCurrentRoute or hash navigations
8
+ if (type === 'updateCurrentRoute' || omitHash(url) === omitHash(fromUrl)) {
9
+ return super.run(navigation);
10
+ }
11
+ return this.notfound(navigation);
12
+ }
13
+ // do not call any hooks as it is only supports url updates with hash
14
+ async runHooks() { }
15
+ }
16
+
17
+ export { NoSpaRouter };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var client = require('./client.js');
6
+
7
+ const omitHash = (url) => url.href.replace(/#.*$/, '');
8
+ class NoSpaRouter extends client.ClientRouter {
9
+ run(navigation) {
10
+ const { type, fromUrl, url } = navigation;
11
+ // support only updateCurrentRoute or hash navigations
12
+ if (type === 'updateCurrentRoute' || omitHash(url) === omitHash(fromUrl)) {
13
+ return super.run(navigation);
14
+ }
15
+ return this.notfound(navigation);
16
+ }
17
+ // do not call any hooks as it is only supports url updates with hash
18
+ async runHooks() { }
19
+ }
20
+
21
+ exports.NoSpaRouter = NoSpaRouter;
@@ -0,0 +1,85 @@
1
+ import { isInvalidUrl } from '@tinkoff/url';
2
+ import { AbstractRouter } from './abstract.es.js';
3
+ import { ServerHistory } from '../history/server.es.js';
4
+ import { RouteTree } from '../tree/tree.es.js';
5
+ import { logger } from '../logger.es.js';
6
+
7
+ class Router extends AbstractRouter {
8
+ constructor(options) {
9
+ var _a;
10
+ super(options);
11
+ this.blocked = false;
12
+ this.tree = new RouteTree(options.routes);
13
+ this.defaultRedirectCode = (_a = options.defaultRedirectCode) !== null && _a !== void 0 ? _a : 308;
14
+ this.history = new ServerHistory();
15
+ }
16
+ async dehydrate() {
17
+ logger.debug({
18
+ event: 'dehydrate',
19
+ navigation: this.lastNavigation,
20
+ });
21
+ return this.lastNavigation;
22
+ }
23
+ async internalNavigate(navigateOptions, internalOptions) {
24
+ // any navigation after initial should be considered as redirects
25
+ if (this.getCurrentRoute()) {
26
+ return this.redirect(this.lastNavigation, navigateOptions);
27
+ }
28
+ return super.internalNavigate(navigateOptions, internalOptions);
29
+ }
30
+ async run(navigation) {
31
+ if (this.redirectCode) {
32
+ return this.redirect(navigation, { url: navigation.url.href, code: this.redirectCode });
33
+ }
34
+ await super.run(navigation);
35
+ }
36
+ async redirect(navigation, target) {
37
+ logger.debug({
38
+ event: 'redirect',
39
+ navigation,
40
+ target,
41
+ });
42
+ this.blocked = true;
43
+ return this.onRedirect({
44
+ ...navigation,
45
+ from: navigation.to,
46
+ fromUrl: navigation.url,
47
+ to: null,
48
+ url: this.resolveUrl(target),
49
+ code: target.code,
50
+ });
51
+ }
52
+ async notfound(navigation) {
53
+ this.blocked = true;
54
+ return super.notfound(navigation);
55
+ }
56
+ normalizePathname(pathname) {
57
+ const normalized = super.normalizePathname(pathname);
58
+ if (normalized !== pathname) {
59
+ this.redirectCode = this.defaultRedirectCode;
60
+ }
61
+ return normalized;
62
+ }
63
+ resolveUrl(options) {
64
+ if (options.url && isInvalidUrl(options.url)) {
65
+ this.redirectCode = this.defaultRedirectCode;
66
+ }
67
+ return super.resolveUrl(options);
68
+ }
69
+ async runHooks(hookName, navigation) {
70
+ // do not run hooks if another parallel navigation has been called
71
+ if (this.blocked) {
72
+ return;
73
+ }
74
+ return super.runHooks(hookName, navigation);
75
+ }
76
+ async runGuards(navigation) {
77
+ // do not run guards if another parallel navigation has been called
78
+ if (this.blocked) {
79
+ return;
80
+ }
81
+ return super.runGuards(navigation);
82
+ }
83
+ }
84
+
85
+ export { Router };
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var url = require('@tinkoff/url');
6
+ var abstract = require('./abstract.js');
7
+ var server = require('../history/server.js');
8
+ var tree = require('../tree/tree.js');
9
+ var logger = require('../logger.js');
10
+
11
+ class Router extends abstract.AbstractRouter {
12
+ constructor(options) {
13
+ var _a;
14
+ super(options);
15
+ this.blocked = false;
16
+ this.tree = new tree.RouteTree(options.routes);
17
+ this.defaultRedirectCode = (_a = options.defaultRedirectCode) !== null && _a !== void 0 ? _a : 308;
18
+ this.history = new server.ServerHistory();
19
+ }
20
+ async dehydrate() {
21
+ logger.logger.debug({
22
+ event: 'dehydrate',
23
+ navigation: this.lastNavigation,
24
+ });
25
+ return this.lastNavigation;
26
+ }
27
+ async internalNavigate(navigateOptions, internalOptions) {
28
+ // any navigation after initial should be considered as redirects
29
+ if (this.getCurrentRoute()) {
30
+ return this.redirect(this.lastNavigation, navigateOptions);
31
+ }
32
+ return super.internalNavigate(navigateOptions, internalOptions);
33
+ }
34
+ async run(navigation) {
35
+ if (this.redirectCode) {
36
+ return this.redirect(navigation, { url: navigation.url.href, code: this.redirectCode });
37
+ }
38
+ await super.run(navigation);
39
+ }
40
+ async redirect(navigation, target) {
41
+ logger.logger.debug({
42
+ event: 'redirect',
43
+ navigation,
44
+ target,
45
+ });
46
+ this.blocked = true;
47
+ return this.onRedirect({
48
+ ...navigation,
49
+ from: navigation.to,
50
+ fromUrl: navigation.url,
51
+ to: null,
52
+ url: this.resolveUrl(target),
53
+ code: target.code,
54
+ });
55
+ }
56
+ async notfound(navigation) {
57
+ this.blocked = true;
58
+ return super.notfound(navigation);
59
+ }
60
+ normalizePathname(pathname) {
61
+ const normalized = super.normalizePathname(pathname);
62
+ if (normalized !== pathname) {
63
+ this.redirectCode = this.defaultRedirectCode;
64
+ }
65
+ return normalized;
66
+ }
67
+ resolveUrl(options) {
68
+ if (options.url && url.isInvalidUrl(options.url)) {
69
+ this.redirectCode = this.defaultRedirectCode;
70
+ }
71
+ return super.resolveUrl(options);
72
+ }
73
+ async runHooks(hookName, navigation) {
74
+ // do not run hooks if another parallel navigation has been called
75
+ if (this.blocked) {
76
+ return;
77
+ }
78
+ return super.runHooks(hookName, navigation);
79
+ }
80
+ async runGuards(navigation) {
81
+ // do not run guards if another parallel navigation has been called
82
+ if (this.blocked) {
83
+ return;
84
+ }
85
+ return super.runGuards(navigation);
86
+ }
87
+ }
88
+
89
+ exports.Router = Router;
@@ -0,0 +1,6 @@
1
+ const PARAMETER_DELIMITER = ':';
2
+ const WILDCARD_REGEXP = /\*/;
3
+ const HISTORY_FALLBACK_REGEXP = /<history-fallback>/;
4
+ const PARAM_PARSER_REGEXP = /([^(?]+)(?:\((.+)\))?(\?)?/;
5
+
6
+ export { HISTORY_FALLBACK_REGEXP, PARAMETER_DELIMITER, PARAM_PARSER_REGEXP, WILDCARD_REGEXP };
@@ -0,0 +1,6 @@
1
+ const PARAMETER_DELIMITER = ':';
2
+ const WILDCARD_REGEXP = /\*/;
3
+ const HISTORY_FALLBACK_REGEXP = /<history-fallback>/;
4
+ const PARAM_PARSER_REGEXP = /([^(?]+)(?:\((.+)\))?(\?)?/;
5
+
6
+ export { HISTORY_FALLBACK_REGEXP, PARAMETER_DELIMITER, PARAM_PARSER_REGEXP, WILDCARD_REGEXP };
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ const PARAMETER_DELIMITER = ':';
6
+ const WILDCARD_REGEXP = /\*/;
7
+ const HISTORY_FALLBACK_REGEXP = /<history-fallback>/;
8
+ const PARAM_PARSER_REGEXP = /([^(?]+)(?:\((.+)\))?(\?)?/;
9
+
10
+ exports.HISTORY_FALLBACK_REGEXP = HISTORY_FALLBACK_REGEXP;
11
+ exports.PARAMETER_DELIMITER = PARAMETER_DELIMITER;
12
+ exports.PARAM_PARSER_REGEXP = PARAM_PARSER_REGEXP;
13
+ exports.WILDCARD_REGEXP = WILDCARD_REGEXP;
@@ -0,0 +1,148 @@
1
+ import each from '@tinkoff/utils/array/each';
2
+ import find from '@tinkoff/utils/array/find';
3
+ import findIndex from '@tinkoff/utils/array/findIndex';
4
+ import { getParts, parse } from './utils.browser.js';
5
+ import { HISTORY_FALLBACK_REGEXP } from './constants.browser.js';
6
+
7
+ const createTree = (route) => {
8
+ return {
9
+ route,
10
+ children: Object.create(null),
11
+ parameters: [],
12
+ };
13
+ };
14
+ const createNavigationRoute = (route, pathname, params) => {
15
+ return {
16
+ ...route,
17
+ actualPath: pathname,
18
+ params: params !== null && params !== void 0 ? params : {},
19
+ };
20
+ };
21
+ class RouteTree {
22
+ constructor(routes) {
23
+ this.tree = createTree();
24
+ each((route) => this.addRoute(route), routes);
25
+ }
26
+ // eslint-disable-next-line max-statements
27
+ addRoute(route) {
28
+ const parts = getParts(route.path);
29
+ let currentTree = this.tree;
30
+ for (let i = 0; i < parts.length; i++) {
31
+ const part = parts[i];
32
+ const parsed = parse(part);
33
+ if (parsed.type === 1 /* PartType.historyFallback */) {
34
+ currentTree.historyFallbackRoute = route;
35
+ return;
36
+ }
37
+ if (parsed.type === 2 /* PartType.wildcard */) {
38
+ currentTree.wildcardRoute = route;
39
+ return;
40
+ }
41
+ if (parsed.type === 3 /* PartType.parameter */) {
42
+ const { paramName, regexp, optional } = parsed;
43
+ // prevent from creating new entries for same route
44
+ const found = find((par) => par.key === part, currentTree.parameters);
45
+ if (found) {
46
+ currentTree = found.tree;
47
+ }
48
+ else {
49
+ const parameter = {
50
+ key: part,
51
+ paramName,
52
+ regexp,
53
+ optional,
54
+ tree: createTree(),
55
+ };
56
+ if (regexp && !optional) {
57
+ // insert parameters with regexp before
58
+ const index = findIndex((par) => !par.regexp, currentTree.parameters);
59
+ currentTree.parameters.splice(index, 0, parameter);
60
+ }
61
+ else {
62
+ currentTree.parameters.push(parameter);
63
+ }
64
+ currentTree = parameter.tree;
65
+ }
66
+ }
67
+ else {
68
+ if (!currentTree.children[part]) {
69
+ currentTree.children[part] = createTree();
70
+ }
71
+ currentTree = currentTree.children[part];
72
+ }
73
+ }
74
+ currentTree.route = route;
75
+ }
76
+ getRoute(pathname) {
77
+ return this.findRoute(pathname, 'route');
78
+ }
79
+ getWildcard(pathname) {
80
+ return this.findRoute(pathname, 'wildcardRoute');
81
+ }
82
+ getHistoryFallback(pathname) {
83
+ const route = this.findRoute(pathname, 'historyFallbackRoute');
84
+ return (route && {
85
+ ...route,
86
+ // remove <history-fallback> from path
87
+ actualPath: route.path.replace(HISTORY_FALLBACK_REGEXP, '') || '/',
88
+ });
89
+ }
90
+ // eslint-disable-next-line max-statements
91
+ findRoute(pathname, propertyName) {
92
+ // we should use exact match only for classic route
93
+ // as special routes (for not-found and history-fallback) are defined for whole subtree
94
+ const exactMatch = propertyName === 'route';
95
+ const parts = getParts(pathname);
96
+ const queue = [
97
+ [this.tree, 0],
98
+ ];
99
+ while (queue.length) {
100
+ const [currentTree, index, params] = queue.pop();
101
+ const { children, parameters } = currentTree;
102
+ // this flag mean we can only check for optional parameters
103
+ // as we didn't find static route for this path, but still may find
104
+ // some inner route inside optional branch
105
+ // the value of parameter will be empty in this case ofc
106
+ let optionalOnly = false;
107
+ if (index >= parts.length) {
108
+ if (currentTree[propertyName]) {
109
+ return createNavigationRoute(currentTree[propertyName], pathname, params);
110
+ }
111
+ // here we cant check any options except for optional parameters
112
+ optionalOnly = true;
113
+ }
114
+ // first add to queue special routes (not-found or history-fallback) to check it after other cases, as it will be processed last
115
+ if (!exactMatch && currentTree[propertyName]) {
116
+ queue.push([currentTree, parts.length, params]);
117
+ }
118
+ const part = parts[index];
119
+ const child = children[part];
120
+ // then add checks for only optional
121
+ for (const param of parameters) {
122
+ const { optional, tree } = param;
123
+ if (optional) {
124
+ queue.push([tree, index, params]);
125
+ }
126
+ }
127
+ if (optionalOnly) {
128
+ continue;
129
+ }
130
+ // for non-optional cases
131
+ for (let i = parameters.length - 1; i >= 0; i--) {
132
+ const param = parameters[i];
133
+ const { paramName, tree, regexp } = param;
134
+ const match = regexp === null || regexp === void 0 ? void 0 : regexp.exec(part);
135
+ const paramValue = regexp ? match === null || match === void 0 ? void 0 : match[1] : part;
136
+ if (paramValue) {
137
+ queue.push([tree, index + 1, { ...params, [paramName]: paramValue }]);
138
+ }
139
+ }
140
+ if (child) {
141
+ // add checks for static child subtree last as it will be processed first after queue.pop
142
+ queue.push([child, index + 1, params]);
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ export { RouteTree };