@marianmeres/simple-router 2.1.0 → 2.2.3

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/dist/index.js DELETED
@@ -1,248 +0,0 @@
1
- class SimpleRoute {
2
- route;
3
- static SPLITTER = '/';
4
- static WILDCARD = '*';
5
- _parsed;
6
- constructor(route) {
7
- this.route = route;
8
- this._parsed = SimpleRoute._parse(route);
9
- }
10
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
11
- static _escapeRegExp(str) {
12
- return str.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
13
- }
14
- static _sanitizeAndSplit(str) {
15
- const s = SimpleRoute._escapeRegExp(SimpleRoute.SPLITTER);
16
- return (`${str}`
17
- .trim()
18
- // splitter trim left and right
19
- .replace(new RegExp(`^(${s})+`), '')
20
- .replace(new RegExp(`(${s})+$`), '')
21
- .split(SimpleRoute.SPLITTER)
22
- // remove empty segments... will "normalize" multiple splitters into one
23
- .filter(Boolean));
24
- }
25
- static _parse(route) {
26
- let wasSpread = false;
27
- let wasWildcard = false;
28
- return SimpleRoute._sanitizeAndSplit(route).reduce((memo, segment, idx, all) => {
29
- let name = null;
30
- // if optional, remove trailing '?' marker
31
- let isOptional = segment.endsWith('?');
32
- if (isOptional)
33
- segment = segment.slice(0, -1);
34
- let isSpread = segment.startsWith('[...');
35
- if (isSpread) {
36
- if (isOptional) {
37
- throw new Error('Spread segment must not be marked as optional');
38
- }
39
- if (wasSpread) {
40
- throw new Error('Multiple spread segments are invalid');
41
- }
42
- wasSpread = true;
43
- segment = '[' + segment.slice(4);
44
- }
45
- let test = new RegExp('^' + SimpleRoute._escapeRegExp(segment) + '$');
46
- // catch all wildcard
47
- if (segment === SimpleRoute.WILDCARD) {
48
- if (idx < all.length - 1) {
49
- throw new Error(`Wildcard '${SimpleRoute.WILDCARD}' can be used only as a last segment`);
50
- }
51
- wasWildcard = true;
52
- }
53
- else {
54
- // starting with at least one word char within brackets...
55
- let m = segment.match(/^\[(\w.*)]$/);
56
- if (m) {
57
- name = m[1];
58
- test = /.+/;
59
- // id([0-9]+)
60
- let m2 = m[1].match(/^(\w.*)\((.+)\)$/);
61
- if (m2) {
62
- name = m2[1];
63
- test = new RegExp('^' + m2[2] + '$');
64
- }
65
- }
66
- }
67
- if (wasWildcard) {
68
- isOptional = true;
69
- test = /.*/;
70
- }
71
- memo.push({ segment, name, test, isOptional, isSpread });
72
- return memo;
73
- }, []);
74
- }
75
- static parseQueryString(str) {
76
- return `${str}`
77
- .replace(/^&/, '')
78
- .replace(/&$/, '')
79
- .split('&')
80
- .reduce((memo, kvpair) => {
81
- const [k, v] = kvpair.split('=').map(decodeURIComponent);
82
- if (k.length)
83
- memo[k] = v;
84
- return memo;
85
- }, {});
86
- }
87
- parse(url, allowQueryParams = true) {
88
- let matched = {};
89
- const qPos = url.indexOf('?');
90
- if (allowQueryParams && ~qPos) {
91
- const _backup = url;
92
- url = _backup.slice(0, qPos);
93
- matched = SimpleRoute.parseQueryString(_backup.slice(qPos + 1));
94
- }
95
- let segments = SimpleRoute._sanitizeAndSplit(url);
96
- // SPREAD PARAMS DANCING BLOCK - if there are "spread" definitions we need to adjust input
97
- // that is "group" (join) segments that were initially splitted
98
- const hasSpread = !!this._parsed.filter((v) => v.isSpread).length;
99
- if (hasSpread) {
100
- let newSegments = [];
101
- this._parsed.forEach((p, i) => {
102
- if (p.isSpread) {
103
- // there are defined segments after the "spread" definition
104
- if (this._parsed[i + 1]) {
105
- // SimpleRoute
106
- newSegments.push(segments.slice(0, this._parsed.length - i).join(SimpleRoute.SPLITTER));
107
- segments = segments.slice(this._parsed.length - i);
108
- }
109
- // there are no more defined segments
110
- else {
111
- newSegments.push(segments.join(SimpleRoute.SPLITTER));
112
- }
113
- }
114
- else {
115
- newSegments = newSegments.concat(segments.slice(0, 1));
116
- segments = segments.slice(1);
117
- }
118
- });
119
- segments = newSegments;
120
- }
121
- // return early no-op special case: first segment is a wildcard
122
- // (no need to process further)
123
- if (this._parsed?.[0]?.segment === SimpleRoute.WILDCARD) {
124
- return matched;
125
- }
126
- // minimum required (not optional) segments length
127
- const reqLen = this._parsed.reduce((memo, p, idx, arr) => {
128
- const next = arr[idx + 1];
129
- // if is not optional or has not optional still to come
130
- if (!p.isOptional || (next && !next.isOptional)) {
131
- memo++;
132
- }
133
- return memo;
134
- }, 0);
135
- // quick cheap check: if counts dont match = no match
136
- if (segments.length < reqLen) {
137
- return null;
138
- }
139
- for (const [i, s] of segments.entries()) {
140
- const p = this._parsed[i];
141
- if (!p || !p.test.test(s)) {
142
- return null;
143
- }
144
- if (p.name) {
145
- matched[decodeURIComponent(p.name)] = decodeURIComponent(s);
146
- }
147
- if (p.segment === SimpleRoute.WILDCARD) {
148
- break;
149
- }
150
- }
151
- return matched;
152
- }
153
- }
154
-
155
- class SimpleRouter {
156
- // console log debug on/off switch
157
- static debug = false;
158
- _routes = [];
159
- _catchAll;
160
- // current (last matched) route and params (in the shape { route: "...", params: {} } )
161
- _current = {
162
- route: null,
163
- params: null,
164
- label: null,
165
- };
166
- // https://svelte.dev/docs#Store_contract
167
- _subscriptions = new Set();
168
- constructor(config = null) {
169
- Object.entries(config || {}).forEach(([route, cb]) => {
170
- this.on(route, cb);
171
- });
172
- }
173
- _dbg(...a) {
174
- SimpleRouter.debug && console.log('[SimpleRouter]', ...a);
175
- }
176
- /** Will report map of registered routes and their labels. Intended for debugging. */
177
- info() {
178
- return this._routes.reduce((m, r) => {
179
- m[r[0].route] = r[3];
180
- return m;
181
- }, {});
182
- }
183
- reset() {
184
- this._routes = [];
185
- return this;
186
- }
187
- get current() {
188
- return this._current;
189
- }
190
- on(routes, cb, { label = null, allowQueryParams = true } = {}) {
191
- if (!Array.isArray(routes))
192
- routes = [routes];
193
- routes.forEach((route) => {
194
- if (route === '*') {
195
- this._catchAll = cb;
196
- }
197
- else {
198
- this._routes.push([new SimpleRoute(route), cb, allowQueryParams, label]);
199
- }
200
- });
201
- }
202
- exec(url, fallbackFn) {
203
- const dbgPrefix = `'${url}' -> `;
204
- const isFn = (v) => typeof v === 'function';
205
- for (const [route, cb, allowQueryParams, label] of this._routes) {
206
- // first match wins
207
- // parse returns null or params object (which can be empty)
208
- const params = route.parse(url, allowQueryParams);
209
- if (params) {
210
- this._publishCurrent(route.route, params, label);
211
- this._dbg(`${dbgPrefix}matches '${route.route}' with`, params);
212
- return isFn(cb) ? cb(params, route.route) : true;
213
- }
214
- }
215
- if (isFn(fallbackFn)) {
216
- this._publishCurrent(null, null, null);
217
- this._dbg(`${dbgPrefix}fallback...`);
218
- return fallbackFn();
219
- }
220
- if (isFn(this._catchAll)) {
221
- this._publishCurrent('*', null, null);
222
- this._dbg(`${dbgPrefix}catchall...`);
223
- return this._catchAll(null, '*');
224
- }
225
- this._publishCurrent(null, null, null);
226
- this._dbg(`${dbgPrefix}no match...`);
227
- return false;
228
- }
229
- _publishCurrent(route, params, label) {
230
- this._current = { route, params, label };
231
- this._subscriptions.forEach((cb) => cb(this._current));
232
- }
233
- // https://svelte.dev/docs#Store_contract
234
- subscribe(subscription) {
235
- if (typeof subscription !== 'function') {
236
- throw new TypeError('Subscription is not a function');
237
- }
238
- this._subscriptions.add(subscription);
239
- subscription(this._current);
240
- // For interoperability with RxJS Observables, the .subscribe method is also
241
- // allowed to return an object with an .unsubscribe method
242
- return {
243
- unsubscribe: () => this._subscriptions.delete(subscription),
244
- };
245
- }
246
- }
247
-
248
- export { SimpleRoute, SimpleRouter };