navigation-stack 0.1.3 → 0.2.0

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 (77) hide show
  1. package/.github/workflows/main.yml +39 -39
  2. package/README.md +128 -27
  3. package/lib/cjs/{LocationStateStorage.js → LocationDataStorage.js} +5 -5
  4. package/lib/cjs/addBeforeLocationChangeListener.js +7 -0
  5. package/lib/cjs/beforeLocationChangeListeners.js +51 -0
  6. package/lib/cjs/createMiddlewares.js +21 -17
  7. package/lib/cjs/index.js +9 -9
  8. package/lib/cjs/middleware/createBasePathMiddleware.js +2 -2
  9. package/lib/cjs/middleware/createBeforeLocationChangeListenerMiddleware.js +39 -0
  10. package/lib/cjs/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +12 -14
  11. package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +62 -29
  12. package/lib/cjs/middleware/createTransformLocationMiddleware.js +2 -2
  13. package/lib/cjs/navigationBlockers.js +55 -47
  14. package/lib/cjs/normalizeInputLocation.js +1 -0
  15. package/lib/cjs/parseLocationUrl.js +2 -0
  16. package/lib/cjs/parseQueryFromSearch.js +1 -1
  17. package/lib/cjs/session/BrowserSession.js +229 -0
  18. package/lib/cjs/session/MemorySession.js +223 -0
  19. package/lib/cjs/{environment/ServerEnvironment.js → session/ServerSession.js} +28 -16
  20. package/lib/esm/{LocationStateStorage.js → LocationDataStorage.js} +4 -4
  21. package/lib/esm/addBeforeLocationChangeListener.js +2 -0
  22. package/lib/esm/beforeLocationChangeListeners.js +44 -0
  23. package/lib/esm/createMiddlewares.js +21 -17
  24. package/lib/esm/index.js +4 -4
  25. package/lib/esm/middleware/createBasePathMiddleware.js +2 -2
  26. package/lib/esm/middleware/createBeforeLocationChangeListenerMiddleware.js +34 -0
  27. package/lib/esm/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +11 -13
  28. package/lib/esm/middleware/createNavigationBlockerMiddleware.js +62 -29
  29. package/lib/esm/middleware/createTransformLocationMiddleware.js +2 -2
  30. package/lib/esm/navigationBlockers.js +55 -47
  31. package/lib/esm/normalizeInputLocation.js +1 -0
  32. package/lib/esm/parseLocationUrl.js +2 -0
  33. package/lib/esm/parseQueryFromSearch.js +1 -1
  34. package/lib/esm/session/BrowserSession.js +223 -0
  35. package/lib/esm/session/MemorySession.js +217 -0
  36. package/lib/esm/{environment/ServerEnvironment.js → session/ServerSession.js} +27 -15
  37. package/lib/index.d.ts +64 -59
  38. package/package.json +4 -4
  39. package/src/{LocationStateStorage.js → LocationDataStorage.js} +4 -4
  40. package/src/addBeforeLocationChangeListener.js +2 -0
  41. package/src/beforeLocationChangeListeners.js +54 -0
  42. package/src/createMiddlewares.js +21 -17
  43. package/src/index.js +4 -4
  44. package/src/middleware/createBasePathMiddleware.js +2 -2
  45. package/src/middleware/createBeforeLocationChangeListenerMiddleware.js +40 -0
  46. package/src/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +12 -14
  47. package/src/middleware/createNavigationBlockerMiddleware.js +68 -28
  48. package/src/middleware/createTransformLocationMiddleware.js +2 -2
  49. package/src/navigationBlockers.js +68 -49
  50. package/src/normalizeInputLocation.js +1 -0
  51. package/src/parseLocationUrl.js +2 -0
  52. package/src/parseQueryFromSearch.js +1 -1
  53. package/src/session/BrowserSession.js +225 -0
  54. package/src/session/MemorySession.js +219 -0
  55. package/src/{environment/ServerEnvironment.js → session/ServerSession.js} +28 -15
  56. package/test/{LocationStateStorage.test.js → LocationDataStorage.test.js} +6 -6
  57. package/test/createMiddlewares.test.js +2 -2
  58. package/test/helpers.js +1 -1
  59. package/test/index.test.js +3 -3
  60. package/test/middleware/createBasePathMiddleware.test.js +7 -7
  61. package/test/middleware/createBeforeLocationChangeListenerMiddleware.test.js +141 -0
  62. package/test/middleware/createNavigationBlockerMiddleware.test.js +96 -97
  63. package/test/middleware/createTransformLocationMiddleware.test.js +1 -1
  64. package/test/normalizeInputLocation.test.js +3 -0
  65. package/test/parseLocationUrl.test.js +2 -0
  66. package/test/{environment/BrowserEnvironment.test.js → session/BrowserSession.test.js} +35 -18
  67. package/test/session/MemorySession.test.js +244 -0
  68. package/test/session/ServerSession.test.js +23 -0
  69. package/types/index.d.ts +64 -59
  70. package/lib/cjs/environment/BrowserEnvironment.js +0 -111
  71. package/lib/cjs/environment/MemoryEnvironment.js +0 -150
  72. package/lib/esm/environment/BrowserEnvironment.js +0 -104
  73. package/lib/esm/environment/MemoryEnvironment.js +0 -143
  74. package/src/environment/BrowserEnvironment.js +0 -109
  75. package/src/environment/MemoryEnvironment.js +0 -151
  76. package/test/environment/MemoryEnvironment.test.js +0 -218
  77. package/test/environment/ServerEnvironment.test.js +0 -23
@@ -1,39 +1,54 @@
1
1
  import delay from 'delay';
2
2
 
3
- import BrowserEnvironment from '../../src/environment/BrowserEnvironment';
3
+ import BrowserSession from '../../src/session/BrowserSession';
4
4
 
5
- describe('BrowserEnvironment', () => {
5
+ describe('BrowserSession', () => {
6
6
  beforeEach(() => {
7
7
  window.history.replaceState(null, null, '/');
8
8
  });
9
9
 
10
10
  it('should parse the initial location', () => {
11
11
  window.history.replaceState(null, null, '/foo?bar=baz#qux');
12
- const environment = new BrowserEnvironment();
12
+ const session = new BrowserSession();
13
13
 
14
- expect(environment.init()).to.eql({
15
- action: 'POP',
14
+ expect(session.navigation.init()).to.deep.include({
15
+ action: 'INIT',
16
16
  pathname: '/foo',
17
17
  search: '?bar=baz',
18
18
  query: {
19
19
  bar: 'baz',
20
20
  },
21
21
  hash: '#qux',
22
- key: undefined,
23
22
  index: 0,
24
23
  delta: 0,
25
24
  state: undefined,
26
25
  });
27
26
  });
28
27
 
28
+ it('should require initialization', () => {
29
+ const session = new BrowserSession();
30
+
31
+ expect(() =>
32
+ session.navigation.navigate({
33
+ action: 'PUSH',
34
+ pathname: '/bar',
35
+ search: '?search',
36
+ hash: '#hash',
37
+ state: { the: 'state' },
38
+ }),
39
+ ).to.throw('Browser session must be initialized before navigation');
40
+ });
41
+
29
42
  it('should support basic navigation', async () => {
30
43
  window.history.replaceState(null, null, '/foo');
31
- const environment = new BrowserEnvironment();
44
+ const session = new BrowserSession();
32
45
 
33
46
  const listener = sinon.spy();
34
- environment.subscribe(listener);
47
+ session.navigation.subscribe(listener);
35
48
 
36
- const barLocation = environment.navigate({
49
+ session.navigation.init();
50
+
51
+ const barLocation = session.navigation.navigate({
37
52
  action: 'PUSH',
38
53
  pathname: '/bar',
39
54
  search: '?search',
@@ -58,7 +73,7 @@ describe('BrowserEnvironment', () => {
58
73
  expect(barLocation.key).not.to.be.empty();
59
74
 
60
75
  expect(
61
- environment.navigate({
76
+ session.navigation.navigate({
62
77
  action: 'PUSH',
63
78
  pathname: '/baz',
64
79
  search: '',
@@ -74,7 +89,7 @@ describe('BrowserEnvironment', () => {
74
89
  expect(window.location.pathname).to.equal('/baz');
75
90
 
76
91
  expect(
77
- environment.navigate({
92
+ session.navigation.navigate({
78
93
  action: 'REPLACE',
79
94
  pathname: '/qux',
80
95
  search: '',
@@ -91,7 +106,7 @@ describe('BrowserEnvironment', () => {
91
106
  expect(window.location.pathname).to.equal('/qux');
92
107
  expect(listener).not.to.have.been.called();
93
108
 
94
- environment.shift(-1);
109
+ session.navigation.shift(-1);
95
110
  await delay(20);
96
111
 
97
112
  expect(window.location).to.include({
@@ -124,18 +139,20 @@ describe('BrowserEnvironment', () => {
124
139
  delta: -1,
125
140
  state: undefined,
126
141
  });
142
+
127
143
  listener.resetHistory();
128
144
  });
129
145
 
130
146
  it('should support subscribing and unsubscribing', async () => {
131
- const environment = new BrowserEnvironment();
132
- environment.navigate({
147
+ const session = new BrowserSession();
148
+ session.navigation.init();
149
+ session.navigation.navigate({
133
150
  action: 'PUSH',
134
151
  pathname: '/bar',
135
152
  search: '',
136
153
  hash: '',
137
154
  });
138
- environment.navigate({
155
+ session.navigation.navigate({
139
156
  action: 'PUSH',
140
157
  pathname: '/baz',
141
158
  search: '',
@@ -143,9 +160,9 @@ describe('BrowserEnvironment', () => {
143
160
  });
144
161
 
145
162
  const listener = sinon.spy();
146
- const unsubscribe = environment.subscribe(listener);
163
+ const unsubscribe = session.navigation.subscribe(listener);
147
164
 
148
- environment.shift(-1);
165
+ session.navigation.shift(-1);
149
166
  await delay(20);
150
167
 
151
168
  expect(listener).to.have.been.calledOnce();
@@ -157,7 +174,7 @@ describe('BrowserEnvironment', () => {
157
174
 
158
175
  unsubscribe();
159
176
 
160
- environment.shift(-1);
177
+ session.navigation.shift(-1);
161
178
  await delay(20);
162
179
 
163
180
  expect(listener).not.to.have.been.called();
@@ -0,0 +1,244 @@
1
+ import MemorySession from '../../src/session/MemorySession';
2
+
3
+ describe('MemorySession', () => {
4
+ it('should parse the initial location', () => {
5
+ const session = new MemorySession('/foo?bar=baz#qux');
6
+
7
+ expect(session.navigation.init()).to.deep.include({
8
+ action: 'INIT',
9
+ pathname: '/foo',
10
+ search: '?bar=baz',
11
+ query: {
12
+ bar: 'baz',
13
+ },
14
+ hash: '#qux',
15
+ index: 0,
16
+ delta: 0,
17
+ });
18
+ });
19
+
20
+ it('should support basic navigation', () => {
21
+ const session = new MemorySession('/foo');
22
+
23
+ const listener = sinon.spy();
24
+ session.navigation.subscribe(listener);
25
+
26
+ const barLocation = session.navigation.navigate({
27
+ action: 'PUSH',
28
+ pathname: '/bar',
29
+ state: { the: 'state' },
30
+ });
31
+
32
+ expect(barLocation).to.deep.include({
33
+ action: 'PUSH',
34
+ pathname: '/bar',
35
+ index: 1,
36
+ delta: 1,
37
+ state: { the: 'state' },
38
+ });
39
+ expect(barLocation.key).not.to.be.empty();
40
+
41
+ expect(
42
+ session.navigation.navigate({ action: 'PUSH', pathname: '/baz' }),
43
+ ).to.include({
44
+ action: 'PUSH',
45
+ pathname: '/baz',
46
+ index: 2,
47
+ delta: 1,
48
+ });
49
+
50
+ expect(
51
+ session.navigation.navigate({ action: 'REPLACE', pathname: '/qux' }),
52
+ ).to.include({
53
+ action: 'REPLACE',
54
+ pathname: '/qux',
55
+ index: 2,
56
+ delta: 0,
57
+ });
58
+
59
+ expect(listener).not.to.have.been.called();
60
+
61
+ session.navigation.shift(-1);
62
+
63
+ expect(listener).to.have.been.calledOnce();
64
+ expect(listener.firstCall.args[0]).to.deep.include({
65
+ action: 'POP',
66
+ pathname: '/bar',
67
+ key: barLocation.key,
68
+ index: 1,
69
+ delta: -1,
70
+ state: { the: 'state' },
71
+ });
72
+ });
73
+
74
+ it('should support subscribing and unsubscribing', () => {
75
+ const session = new MemorySession('/foo');
76
+ session.navigation.navigate({ action: 'PUSH', pathname: '/bar' });
77
+ session.navigation.navigate({ action: 'PUSH', pathname: '/baz' });
78
+
79
+ const listener = sinon.spy();
80
+ const unsubscribe = session.navigation.subscribe(listener);
81
+
82
+ session.navigation.shift(-1);
83
+
84
+ expect(listener).to.have.been.calledOnce();
85
+ expect(listener.firstCall.args[0]).to.include({
86
+ action: 'POP',
87
+ pathname: '/bar',
88
+ });
89
+ listener.resetHistory();
90
+
91
+ unsubscribe();
92
+
93
+ session.navigation.shift(-1);
94
+
95
+ expect(listener).not.to.have.been.called();
96
+ });
97
+
98
+ it('should respect stack bounds', () => {
99
+ const session = new MemorySession('/foo');
100
+ session.navigation.navigate({ action: 'PUSH', pathname: '/bar' });
101
+ session.navigation.navigate({ action: 'PUSH', pathname: '/baz' });
102
+
103
+ const listener = sinon.spy();
104
+ session.navigation.subscribe(listener);
105
+
106
+ session.navigation.shift(-390);
107
+
108
+ expect(listener).to.have.been.calledOnce();
109
+ expect(listener.firstCall.args[0]).to.include({
110
+ action: 'POP',
111
+ pathname: '/foo',
112
+ delta: -2,
113
+ });
114
+ listener.resetHistory();
115
+
116
+ session.navigation.shift(-1);
117
+
118
+ expect(listener).not.to.have.been.called();
119
+
120
+ session.navigation.shift(+22);
121
+
122
+ expect(listener).to.have.been.calledOnce();
123
+ expect(listener.firstCall.args[0]).to.include({
124
+ action: 'POP',
125
+ pathname: '/baz',
126
+ delta: 2,
127
+ });
128
+ listener.resetHistory();
129
+
130
+ session.navigation.shift(+1);
131
+
132
+ expect(listener).not.to.have.been.called();
133
+ });
134
+
135
+ it('should reset forward entries on push', () => {
136
+ const session = new MemorySession('/foo');
137
+ session.navigation.navigate({ action: 'PUSH', pathname: '/bar' });
138
+ session.navigation.navigate({ action: 'PUSH', pathname: '/baz' });
139
+ session.navigation.shift(-2);
140
+ session.navigation.navigate({ action: 'REPLACE', pathname: '/qux' });
141
+
142
+ const listener = sinon.spy();
143
+ session.navigation.subscribe(listener);
144
+
145
+ session.navigation.shift(+1);
146
+
147
+ expect(listener).to.have.been.calledOnce();
148
+ expect(listener.firstCall.args[0]).to.include({
149
+ action: 'POP',
150
+ pathname: '/bar',
151
+ delta: 1,
152
+ });
153
+ });
154
+
155
+ it('should not reset forward entries on replace', () => {
156
+ const session = new MemorySession('/foo');
157
+ session.navigation.navigate({ action: 'PUSH', pathname: '/bar' });
158
+ session.navigation.navigate({ action: 'PUSH', pathname: '/baz' });
159
+ session.navigation.shift(-2);
160
+ session.navigation.navigate({ action: 'PUSH', pathname: '/qux' });
161
+
162
+ const listener = sinon.spy();
163
+ session.navigation.subscribe(listener);
164
+
165
+ session.navigation.shift(+1);
166
+
167
+ expect(listener).not.to.have.been.called();
168
+ });
169
+
170
+ describe('persistence', () => {
171
+ // beforeEach(() => {
172
+ // window.sessionStorage.clear();
173
+ // });
174
+
175
+ it('should support persistence', () => {
176
+ // function save(key, data) {
177
+ // window.sessionStorage.setItem(key, data);
178
+ // }
179
+ //
180
+ // function load(key) {
181
+ // return window.sessionStorage.getItem(key);
182
+ // }
183
+
184
+ const storage = {};
185
+
186
+ const save = (key, data) => {
187
+ storage[key] = data;
188
+ };
189
+
190
+ const load = (key) => {
191
+ return storage[key];
192
+ };
193
+
194
+ const session1 = new MemorySession('/foo', { save, load });
195
+ expect(session1.navigation.init()).to.include({
196
+ pathname: '/foo',
197
+ });
198
+
199
+ session1.navigation.navigate({ action: 'PUSH', pathname: '/bar' });
200
+ session1.navigation.navigate({ action: 'PUSH', pathname: '/baz' });
201
+ session1.navigation.shift(-1);
202
+
203
+ const session2 = new MemorySession('/foo', { save, load });
204
+ expect(session2.navigation.init()).to.include({
205
+ pathname: '/bar',
206
+ });
207
+
208
+ session2.navigation.shift(+1);
209
+ expect(session2.navigation.init()).to.include({
210
+ pathname: '/baz',
211
+ });
212
+ });
213
+
214
+ it('should ignore broken session storage entry', () => {
215
+ // function save(key, data) {
216
+ // window.sessionStorage.setItem(key, data);
217
+ // }
218
+ //
219
+ // function load(key) {
220
+ // return window.sessionStorage.getItem(key);
221
+ // }
222
+
223
+ const storage = {};
224
+
225
+ const save = (key, data) => {
226
+ storage[key] = data;
227
+ };
228
+
229
+ const load = (key) => {
230
+ return storage[key];
231
+ };
232
+
233
+ save(
234
+ // `stack` should have sufficient items so that the `index` wouldn't be out of bounds.
235
+ JSON.stringify({ stack: [], index: 2 }),
236
+ );
237
+
238
+ const session = new MemorySession('/foo', { save, load });
239
+ expect(session.navigation.init()).to.include({
240
+ pathname: '/foo',
241
+ });
242
+ });
243
+ });
244
+ });
@@ -0,0 +1,23 @@
1
+ import ServerSession from '../../src/session/ServerSession';
2
+
3
+ describe('ServerSession', () => {
4
+ it('should parse the initial location', () => {
5
+ const session = new ServerSession('/foo?bar=baz#qux');
6
+
7
+ expect(session.navigation.init()).to.deep.include({
8
+ action: 'INIT',
9
+ pathname: '/foo',
10
+ search: '?bar=baz',
11
+ query: {
12
+ bar: 'baz',
13
+ },
14
+ hash: '#qux',
15
+ });
16
+ });
17
+
18
+ it('should have dummy support for subscriptions', () => {
19
+ const session = new ServerSession('/foo?bar=baz#qux');
20
+ const unsubscribe = session.navigation.subscribe();
21
+ expect(unsubscribe).to.not.throw();
22
+ });
23
+ });
package/types/index.d.ts CHANGED
@@ -13,12 +13,9 @@ export type InputLocationQuery = Record<
13
13
 
14
14
  export interface Location<TState = any> {
15
15
  /**
16
- * 'PUSH' or 'REPLACE' if the location was reached via FarceActions.push or
17
- * FarceActions.replace respectively; 'POP' on the initial location, or if
18
- * the location was reached via the browser back or forward buttons or
19
- * via FarceActions.shift
16
+ * See the README on the `action` property of `location`.
20
17
  */
21
- action: 'PUSH' | 'REPLACE' | 'POP';
18
+ action: 'PUSH' | 'REPLACE' | 'POP' | 'INIT';
22
19
  /**
23
20
  * the path name; as on window.location e.g. '/foo'
24
21
  */
@@ -36,9 +33,9 @@ export interface Location<TState = any> {
36
33
  */
37
34
  hash: string;
38
35
  /**
39
- * if present, a unique key identifying the current history entry
36
+ * a unique key identifying the current history entry
40
37
  */
41
- key?: string;
38
+ key: string;
42
39
  /**
43
40
  * the current index of the history entry, starting at 0 for the initial
44
41
  * entry; this increments on FarceActions.push but not on
@@ -46,12 +43,11 @@ export interface Location<TState = any> {
46
43
  */
47
44
  index: number;
48
45
  /**
49
- * the difference between the current index and the index of the previous
50
- * location
46
+ * the difference between the current index and the index of the previous location
51
47
  */
52
48
  delta: number;
53
49
  /**
54
- * additional location state that is not part of the URL
50
+ * any additional location state that the application might explicitly define and store
55
51
  */
56
52
  state: TState;
57
53
  }
@@ -70,11 +66,15 @@ export interface InputLocationObject {
70
66
  export interface LocationBase {
71
67
  pathname: Location['pathname'];
72
68
  search: Location['search'];
73
- query?: Query;
69
+ query: Query;
74
70
  hash: Location['hash'];
75
71
  state?: Location['state'];
76
72
  }
77
73
 
74
+ export interface NavigationLocation extends LocationBase {
75
+ action: 'PUSH' | 'REPLACE';
76
+ }
77
+
78
78
  /**
79
79
  * Location descriptor string:
80
80
  * store.dispatch(FarceActions.push('/foo?bar=baz#qux'));
@@ -109,13 +109,20 @@ export type NavigationBlockerResult =
109
109
  | Promise<NavigationBlockerSyncResult>;
110
110
 
111
111
  /**
112
- * The navigation listener function receives the `location` to which the user
113
- * is attempting to navigate.
112
+ * Navigation blocker function receives a `location` to which the application (or the user) is attempting to navigate.
114
113
  *
115
- * The `location` argument is `null` when the web browser tab is about to be closed.
114
+ * * The `location` argument is `null` when the web browser tab is about to be closed.
115
+ * * The `location` argument is of type `NavigationLocation` when a `.push()` or `.replace()` action is blocked.
116
+ * * The `location` argument is of type `Location` when blocking a navigation that was initiated outside of the application code.
117
+ * For example, when the user clicks "Back" or "Forward" button in a web browser.
116
118
  */
117
119
  export interface NavigationBlocker {
118
- (location: Location | LocationBase | null): NavigationBlockerResult;
120
+ (location: Location | NavigationLocation | null): NavigationBlockerResult;
121
+ }
122
+
123
+ // I dunno why did they use an `interface` here.
124
+ export interface BeforeLocationChangeListener {
125
+ (location: Location): void;
119
126
  }
120
127
 
121
128
  export function addBasePath<L extends InputLocation>(
@@ -131,15 +138,20 @@ export function getLocationUrl(location: InputLocationObject): string;
131
138
  export function parseLocationUrl(locationUrl: string): LocationBase;
132
139
 
133
140
  export function createMiddlewares(
134
- environment: Environment,
141
+ session: SessionBase,
135
142
  options?: CreateMiddlewaresOptions,
136
143
  ): Middleware[];
137
144
 
138
145
  export function addNavigationBlocker(
139
- environment: EnvironmentBase,
146
+ session: SessionBase,
140
147
  blocker: NavigationBlocker,
141
148
  ): () => void;
142
149
 
150
+ export function addBeforeLocationChangeListener(
151
+ session: SessionBase,
152
+ listener: BeforeLocationChangeListener,
153
+ ): () => void;
154
+
143
155
  export const ActionTypes: {
144
156
  INIT: '@@navigation-stack/INIT';
145
157
  PUSH: '@@navigation-stack/PUSH';
@@ -190,78 +202,71 @@ export const Actions: {
190
202
 
191
203
  type BeforeDestroyListener = () => boolean | undefined;
192
204
 
193
- export interface Environment {
205
+ interface SessionNavigation {
194
206
  init(): void;
195
207
 
196
208
  // Subscribes to changes in location,
197
209
  // excluding ones that happened as a result of calling `.navigate()`.
198
210
  subscribe(listener: (location: Location) => void): () => void;
199
211
 
200
- navigate(location: LocationBase): Location;
212
+ navigate(location: NavigationLocation): Location;
201
213
 
202
214
  shift(delta: number): void;
203
-
204
- addBeforeDestroyListener(listener: BeforeDestroyListener): void;
205
-
206
- getState(key: string): string | null;
207
- removeState(key: string): void;
208
- setState(key: string, value: string): void;
209
215
  }
210
216
 
211
- // This is just a copy-paste of the `Environment` interface above.
212
- declare abstract class EnvironmentBase implements Environment {
213
- init(): void;
214
-
215
- // Subscribes to changes in location,
216
- // excluding ones that happened as a result of calling `.navigate()`.
217
- subscribe(listener: (location: Location) => void): () => void;
217
+ interface SessionDataStorage {
218
+ get(key: string): string | null;
219
+ remove(key: string): void;
220
+ set(key: string, value: string): void;
221
+ }
218
222
 
219
- navigate(location: LocationBase): Location;
223
+ export interface Session {
224
+ navigation: SessionNavigation;
225
+ dataStorage: SessionDataStorage;
226
+ addBeforeDestroyListener(listener: BeforeDestroyListener): void;
220
227
 
221
- shift(delta: number): void;
228
+ // These're internal variables that're manually set under the hood.
229
+ // _beforeLocationChangeListenersList?: Array<BeforeLocationChangeListener>;
230
+ // _navigationBlockersList?: Array<NavigationBlocker>;
231
+ // _removeBeforeDestroyListener?: () => void;
232
+ // _navigationBlockersEvaluationStatus?: { cancelled?: boolean };
233
+ }
222
234
 
235
+ // This is just a copy-paste of the `session` interface above.
236
+ declare abstract class SessionBase implements Session {
237
+ navigation: SessionNavigation;
238
+ dataStorage: SessionDataStorage;
223
239
  addBeforeDestroyListener(listener: BeforeDestroyListener): void;
224
240
 
225
- getState(key: string): string | null;
226
- removeState(key: string): void;
227
- setState(key: string, value: string): void;
241
+ // These're internal variables that're manually set under the hood.
242
+ // _beforeLocationChangeListenersList?: Array<BeforeLocationChangeListener>;
243
+ // _navigationBlockersList?: Array<NavigationBlocker>;
244
+ // _removeBeforeDestroyListener?: () => void;
245
+ // _navigationBlockersEvaluationStatus?: { cancelled?: boolean };
228
246
  }
229
247
 
230
- export class BrowserEnvironment extends EnvironmentBase {}
248
+ export class BrowserSession extends SessionBase {}
231
249
 
232
- export interface MemoryEnvironmentOptions<MemoryEnvironmentState = any> {
233
- save?: (state: MemoryEnvironmentState) => void;
234
- load?: () => MemoryEnvironmentState | undefined | null;
250
+ export interface MemorySessionOptions {
251
+ save?: (data: string) => void;
252
+ load?: () => string | undefined | null;
235
253
  }
236
254
 
237
- export class ServerEnvironment extends EnvironmentBase {
255
+ export class ServerSession extends SessionBase {
238
256
  constructor(initialLocation: InputLocation);
239
257
  }
240
258
 
241
- export class MemoryEnvironment extends EnvironmentBase {
259
+ export class MemorySession extends SessionBase {
242
260
  constructor(
243
261
  initialLocation: InputLocation,
244
- options?: MemoryEnvironmentOptions,
262
+ options?: MemorySessionOptions,
245
263
  );
246
264
  }
247
265
 
248
- export interface QueryMiddlewareOptions {
249
- stringify(query: InputLocationQuery): string;
250
- parse(str: string): Query;
251
- }
252
-
253
- export function createQueryMiddleware(
254
- options: QueryMiddlewareOptions,
255
- ): Middleware;
256
-
257
- export const queryMiddleware: Middleware;
258
-
259
- export function createBasePathMiddleware(basePath?: string): Middleware;
260
-
261
266
  export const locationReducer: Reducer<Location, Action>;
262
267
 
263
- export class LocationStateStorage {
264
- constructor(environment: Environment, options?: { namespace?: string });
268
+ export class LocationDataStorage {
269
+ constructor(session: Session, options?: { namespace?: string });
265
270
 
266
271
  get(location: Location, key: string): any;
267
272
  set(location: Location, key: string, value: any): void;