@tinkoff/router 0.1.64 → 0.1.68

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.
package/README.md CHANGED
@@ -66,8 +66,25 @@ export const myGuard: NavigationGuard = async ({ to }) => {
66
66
 
67
67
  // if nothing is returned, the transition will be performed as usual
68
68
  };
69
+
70
+ router.registerGuard(myGuard);
69
71
  ```
70
72
 
73
+ #### Rules
74
+
75
+ - guards are asynchronous and it execution will be awaited inside routing
76
+ - all guards are running in parallel and they are all awaited
77
+ - if several guars return something then the result from a guard that was registered early will be used
78
+
79
+ #### Possible result
80
+
81
+ The behaviour of routing depends on the result of executing guards functions and there result might be next:
82
+
83
+ - if all of the guards returns `undefined` than navigation will continue executing
84
+ - if any of the guards returns `false` than navigation is getting blocked and next action differs on server and client
85
+ - if any of the guards returns `string` it is considered as url to which redirect should be happen
86
+ - if any of the guards returns `NavigateOptions` interface, `url` property from it is considered as url to which redirect should be happen
87
+
71
88
  ### Transitions hooks
72
89
 
73
90
  Transition hooks allow you to perform your asynchronous actions at different stages of the transition.
@@ -78,8 +95,27 @@ import { NavigationHook } from '@tinkoff/router';
78
95
  export const myHook: NavigationHook = async ({ from, to, url, fromUrl }) => {
79
96
  console.log(`navigating from ${from} to route ${to}`);
80
97
  };
98
+
99
+ router.registerHook('beforeNavigate', myHook);
81
100
  ```
82
101
 
102
+ #### Rules
103
+
104
+ - all hooks from the same event are running in parallel
105
+ - most of the hooks are asynchronous and are awaited inside router
106
+ - if some error happens when running hook it will be logged to console but wont affect navigation (except for the `beforeResolve` hook - error for it will be rethrown)
107
+
108
+ #### List of available hooks
109
+
110
+ Async hooks:
111
+
112
+ - [navigate hooks](#navigate-hooks) - asynchronous hooks only for navigate calls
113
+ - [updateCurrentRoute hooks](#updatecurrentroute-hooks) - asynchronous hooks only for updateCurrentRoute calls
114
+
115
+ Sync hooks:
116
+
117
+ - `change` - runs when any of changes to current route\url happens
118
+
83
119
  ## API
84
120
 
85
121
  ### Getting data about the current route or url
@@ -102,12 +138,20 @@ router.navigate('/test');
102
138
  router.navigate({ url: './test', query: { a: '1' } });
103
139
  ```
104
140
 
105
- Transition hooks:
141
+ ##### navigate hooks
106
142
 
107
143
  - beforeResolve
108
144
  - beforeNavigate
109
145
  - afterNavigate
110
146
 
147
+ ##### navigate workflow
148
+
149
+ 1. `beforeResolve` hook
150
+ 2. [guards](#router-guards)
151
+ 3. `beforeNavigate`
152
+ 4. `change`
153
+ 5. `afterNavigate`
154
+
111
155
  #### updateCurrentRoute
112
156
 
113
157
  The transition is based on the current route (therefore this method cannot be called on the server) and allows you to simply update some data for the current page
@@ -117,11 +161,17 @@ router.updateCurrentRoute({ params: { id: 'abc' } });
117
161
  router.updateCurrentRoute({ query: { a: '1' } });
118
162
  ```
119
163
 
120
- Hooks:
164
+ ##### updateCurrentRoute hooks
121
165
 
122
166
  - beforeUpdateCurrent
123
167
  - afterUpdateCurrent
124
168
 
169
+ ##### updateCurrentRoute workflow
170
+
171
+ 1. `beforeUpdateCurrent`
172
+ 2. `change`
173
+ 3. `afterUpdateCurrent`
174
+
125
175
  ### Working with query
126
176
 
127
177
  #### query option
@@ -161,6 +211,11 @@ router.updateCurrentRoute({ query: { a: undefined, c: 'c' }, preserveQuery: true
161
211
  router.getCurrentUrl().query; // { b: 'b', c: 'c' }
162
212
  ```
163
213
 
214
+ ### Constructor options
215
+
216
+ - `trailingSlash` - do router should force all urls to end with slash. If `true` - force trailing slash for every path, `false` - force no trailing slash, `undefined` - trailing slash is specified by request and both trailing and not trailing slashes are used. By default value if `undefined`
217
+ - `mergeSlashes` - replace several consecutive slashes by single slashes (slashes after protocol are still be with `//` after protocol name). By default is `false` - no merge for slashes.
218
+
164
219
  ### Integration with React
165
220
 
166
221
  Library has some useful React hooks and components for working with routing
@@ -229,3 +284,26 @@ export const WrapLink = () => {
229
284
  return <Link url="/test/">Click me</Link>;
230
285
  };
231
286
  ```
287
+
288
+ ## How to
289
+
290
+ ### Load route config from external api
291
+
292
+ Use [transition hook](#transitions-hooks) `beforeResolve` and load routes config based on url.
293
+
294
+ ```ts
295
+ router.registerHook('beforeResolve', async (navigation) => {
296
+ const route = await routeResolve(navigation);
297
+
298
+ if (route) {
299
+ router.addRoute(routeTransform(route));
300
+ }
301
+ });
302
+ ```
303
+
304
+ ### App behind proxy
305
+
306
+ Router doesn't support proxy setup directly. But proxy still can be used with some limitations:
307
+
308
+ - setup proxy server to pass requests to app with rewriting request and response paths. (E.g. for [nginx](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_redirect))
309
+ - it wont work as expected on spa navigation on client, so only option in this case is use the `NoSpaRouter`
@@ -220,17 +220,16 @@ class AbstractRouter {
220
220
  await this.runHooks('afterUpdateCurrent', navigation);
221
221
  }
222
222
  async navigate(navigateOptions) {
223
- return this.internalNavigate(navigateOptions, {});
223
+ return this.internalNavigate(typeof navigateOptions === 'string' ? { url: navigateOptions } : navigateOptions, {});
224
224
  }
225
225
  async internalNavigate(navigateOptions, { history }) {
226
226
  var _a;
227
- const options = typeof navigateOptions === 'string' ? { url: navigateOptions } : navigateOptions;
228
- const { url, replace, params, navigateState, code } = options;
227
+ const { url, replace, params, navigateState, code } = navigateOptions;
229
228
  const prevNavigation = (_a = this.currentNavigation) !== null && _a !== void 0 ? _a : this.lastNavigation;
230
229
  if (!url && !prevNavigation) {
231
230
  throw new Error('Navigate url should be specified and cannot be omitted for first navigation');
232
231
  }
233
- const resolvedUrl = this.resolveUrl(options);
232
+ const resolvedUrl = this.resolveUrl(navigateOptions);
234
233
  const { to: from, url: fromUrl } = prevNavigation !== null && prevNavigation !== void 0 ? prevNavigation : {};
235
234
  let navigation = {
236
235
  type: 'navigate',
package/lib/index.es.js CHANGED
@@ -220,17 +220,16 @@ class AbstractRouter {
220
220
  await this.runHooks('afterUpdateCurrent', navigation);
221
221
  }
222
222
  async navigate(navigateOptions) {
223
- return this.internalNavigate(navigateOptions, {});
223
+ return this.internalNavigate(typeof navigateOptions === 'string' ? { url: navigateOptions } : navigateOptions, {});
224
224
  }
225
225
  async internalNavigate(navigateOptions, { history }) {
226
226
  var _a;
227
- const options = typeof navigateOptions === 'string' ? { url: navigateOptions } : navigateOptions;
228
- const { url, replace, params, navigateState, code } = options;
227
+ const { url, replace, params, navigateState, code } = navigateOptions;
229
228
  const prevNavigation = (_a = this.currentNavigation) !== null && _a !== void 0 ? _a : this.lastNavigation;
230
229
  if (!url && !prevNavigation) {
231
230
  throw new Error('Navigate url should be specified and cannot be omitted for first navigation');
232
231
  }
233
- const resolvedUrl = this.resolveUrl(options);
232
+ const resolvedUrl = this.resolveUrl(navigateOptions);
234
233
  const { to: from, url: fromUrl } = prevNavigation !== null && prevNavigation !== void 0 ? prevNavigation : {};
235
234
  let navigation = {
236
235
  type: 'navigate',
@@ -668,9 +667,11 @@ class RouteTree {
668
667
 
669
668
  class Router extends AbstractRouter {
670
669
  constructor(options) {
670
+ var _a;
671
671
  super(options);
672
672
  this.blocked = false;
673
673
  this.tree = new RouteTree(options.routes);
674
+ this.defaultRedirectCode = (_a = options.defaultRedirectCode) !== null && _a !== void 0 ? _a : 308;
674
675
  this.history = new ServerHistory();
675
676
  }
676
677
  async dehydrate() {
@@ -680,13 +681,16 @@ class Router extends AbstractRouter {
680
681
  });
681
682
  return this.lastNavigation;
682
683
  }
683
- async run(navigation) {
684
- if (this.redirectCode) {
685
- return this.redirect(navigation, { url: navigation.url.path, code: this.redirectCode });
686
- }
684
+ async internalNavigate(navigateOptions, internalOptions) {
687
685
  // any navigation after initial should be considered as redirects
688
686
  if (this.getCurrentRoute()) {
689
- return this.redirect(navigation, { url: navigation.url.path, code: navigation.code });
687
+ return this.redirect(this.lastNavigation, navigateOptions);
688
+ }
689
+ return super.internalNavigate(navigateOptions, internalOptions);
690
+ }
691
+ async run(navigation) {
692
+ if (this.redirectCode) {
693
+ return this.redirect(navigation, { url: navigation.url.href, code: this.redirectCode });
690
694
  }
691
695
  await super.run(navigation);
692
696
  }
@@ -713,13 +717,13 @@ class Router extends AbstractRouter {
713
717
  normalizePathname(pathname) {
714
718
  const normalized = super.normalizePathname(pathname);
715
719
  if (normalized !== pathname) {
716
- this.redirectCode = 308;
720
+ this.redirectCode = this.defaultRedirectCode;
717
721
  }
718
722
  return normalized;
719
723
  }
720
724
  resolveUrl(options) {
721
725
  if (options.url && isInvalidUrl(options.url)) {
722
- this.redirectCode = 308;
726
+ this.redirectCode = this.defaultRedirectCode;
723
727
  }
724
728
  return super.resolveUrl(options);
725
729
  }
package/lib/index.js CHANGED
@@ -236,17 +236,16 @@ class AbstractRouter {
236
236
  await this.runHooks('afterUpdateCurrent', navigation);
237
237
  }
238
238
  async navigate(navigateOptions) {
239
- return this.internalNavigate(navigateOptions, {});
239
+ return this.internalNavigate(typeof navigateOptions === 'string' ? { url: navigateOptions } : navigateOptions, {});
240
240
  }
241
241
  async internalNavigate(navigateOptions, { history }) {
242
242
  var _a;
243
- const options = typeof navigateOptions === 'string' ? { url: navigateOptions } : navigateOptions;
244
- const { url, replace, params, navigateState, code } = options;
243
+ const { url, replace, params, navigateState, code } = navigateOptions;
245
244
  const prevNavigation = (_a = this.currentNavigation) !== null && _a !== void 0 ? _a : this.lastNavigation;
246
245
  if (!url && !prevNavigation) {
247
246
  throw new Error('Navigate url should be specified and cannot be omitted for first navigation');
248
247
  }
249
- const resolvedUrl = this.resolveUrl(options);
248
+ const resolvedUrl = this.resolveUrl(navigateOptions);
250
249
  const { to: from, url: fromUrl } = prevNavigation !== null && prevNavigation !== void 0 ? prevNavigation : {};
251
250
  let navigation = {
252
251
  type: 'navigate',
@@ -684,9 +683,11 @@ class RouteTree {
684
683
 
685
684
  class Router extends AbstractRouter {
686
685
  constructor(options) {
686
+ var _a;
687
687
  super(options);
688
688
  this.blocked = false;
689
689
  this.tree = new RouteTree(options.routes);
690
+ this.defaultRedirectCode = (_a = options.defaultRedirectCode) !== null && _a !== void 0 ? _a : 308;
690
691
  this.history = new ServerHistory();
691
692
  }
692
693
  async dehydrate() {
@@ -696,13 +697,16 @@ class Router extends AbstractRouter {
696
697
  });
697
698
  return this.lastNavigation;
698
699
  }
699
- async run(navigation) {
700
- if (this.redirectCode) {
701
- return this.redirect(navigation, { url: navigation.url.path, code: this.redirectCode });
702
- }
700
+ async internalNavigate(navigateOptions, internalOptions) {
703
701
  // any navigation after initial should be considered as redirects
704
702
  if (this.getCurrentRoute()) {
705
- return this.redirect(navigation, { url: navigation.url.path, code: navigation.code });
703
+ return this.redirect(this.lastNavigation, navigateOptions);
704
+ }
705
+ return super.internalNavigate(navigateOptions, internalOptions);
706
+ }
707
+ async run(navigation) {
708
+ if (this.redirectCode) {
709
+ return this.redirect(navigation, { url: navigation.url.href, code: this.redirectCode });
706
710
  }
707
711
  await super.run(navigation);
708
712
  }
@@ -729,13 +733,13 @@ class Router extends AbstractRouter {
729
733
  normalizePathname(pathname) {
730
734
  const normalized = super.normalizePathname(pathname);
731
735
  if (normalized !== pathname) {
732
- this.redirectCode = 308;
736
+ this.redirectCode = this.defaultRedirectCode;
733
737
  }
734
738
  return normalized;
735
739
  }
736
740
  resolveUrl(options) {
737
741
  if (options.url && url.isInvalidUrl(options.url)) {
738
- this.redirectCode = 308;
742
+ this.redirectCode = this.defaultRedirectCode;
739
743
  }
740
744
  return super.resolveUrl(options);
741
745
  }
@@ -16,6 +16,7 @@ export interface Options {
16
16
  afterUpdateCurrent?: NavigationHook[];
17
17
  guards?: NavigationGuard[];
18
18
  onChange?: NavigationSyncHook[];
19
+ defaultRedirectCode?: number;
19
20
  }
20
21
  interface InternalOptions {
21
22
  history?: boolean;
@@ -44,7 +45,7 @@ export declare abstract class AbstractRouter {
44
45
  protected internalUpdateCurrentRoute(updateRouteOptions: UpdateCurrentRouteOptions, { history }: InternalOptions): Promise<void>;
45
46
  protected runUpdateCurrentRoute(navigation: Navigation): Promise<void>;
46
47
  navigate(navigateOptions: NavigateOptions | string): Promise<void>;
47
- protected internalNavigate(navigateOptions: NavigateOptions | string, { history }: InternalOptions): Promise<void>;
48
+ protected internalNavigate(navigateOptions: NavigateOptions, { history }: InternalOptions): Promise<void>;
48
49
  protected runNavigate(navigation: Navigation): Promise<void>;
49
50
  protected run(navigation: Navigation): Promise<void>;
50
51
  resolve(resolveOptions: NavigateOptions | string, options?: Parameters<AbstractRouter['resolveRoute']>[1]): import("../types").NavigationRoute;
@@ -1,16 +1,14 @@
1
1
  import type { Options } from './abstract';
2
2
  import { AbstractRouter } from './abstract';
3
- import type { Navigation, NavigateOptions, HookName, NavigationHook } from '../types';
3
+ import type { Navigation, NavigateOptions, HookName } from '../types';
4
4
  export declare class Router extends AbstractRouter {
5
+ protected defaultRedirectCode: number;
5
6
  protected blocked: boolean;
6
7
  protected redirectCode?: number;
7
- constructor(options: Options & {
8
- onRedirect: (navigation: Navigation) => Promise<void>;
9
- onNotFound: NavigationHook;
10
- onBlock: NavigationHook;
11
- });
8
+ constructor(options: Options);
12
9
  protected onRedirect: (navigation: Navigation) => Promise<void>;
13
10
  dehydrate(): Promise<Navigation>;
11
+ protected internalNavigate(navigateOptions: NavigateOptions, internalOptions: any): Promise<void>;
14
12
  protected run(navigation: Navigation): Promise<void>;
15
13
  protected redirect(navigation: Navigation, target: NavigateOptions): Promise<void>;
16
14
  protected notfound(navigation: Navigation): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinkoff/router",
3
- "version": "0.1.64",
3
+ "version": "0.1.68",
4
4
  "description": "router",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "repository": {
16
16
  "type": "git",
17
- "url": "git@github.com:TinkoffCreditSystems/tramvai.git"
17
+ "url": "git@github.com:Tinkoff/tramvai.git"
18
18
  },
19
19
  "scripts": {
20
20
  "build": "tramvai-build --for-publish",