UNPKG

8.23 kBJavaScriptView Raw
1(function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3 typeof define === 'function' && define.amd ? define(factory) :
4 (global = global || self, global.AbstractNestedRouter = factory());
5}(this, function () { 'use strict';
6
7 var defaultExport = /*@__PURE__*/(function (Error) {
8 function defaultExport(route, path) {
9 var message = "Unreachable '" + route + "', segment '" + path + "' is not defined";
10 Error.call(this, message);
11 this.message = message;
12 }
13
14 if ( Error ) defaultExport.__proto__ = Error;
15 defaultExport.prototype = Object.create( Error && Error.prototype );
16 defaultExport.prototype.constructor = defaultExport;
17
18 return defaultExport;
19 }(Error));
20
21 function buildMatcher(path, parent) {
22 var regex;
23
24 var _isSplat;
25
26 var _priority = -100;
27
28 var keys = [];
29 regex = path.replace(/[-$.]/g, '\\$&').replace(/\(/g, '(?:').replace(/\)/g, ')?').replace(/([:*]\w+)(?:<([^<>]+?)>)?/g, function (_, key, expr) {
30 keys.push(key.substr(1));
31
32 if (key.charAt() === ':') {
33 _priority += 100;
34 return ("((?!#)" + (expr || '[^#/]+?') + ")");
35 }
36
37 _isSplat = true;
38 _priority += 500;
39 return ("((?!#)" + (expr || '[^#]+?') + ")");
40 });
41
42 try {
43 regex = new RegExp(("^" + regex + "$"));
44 } catch (e) {
45 throw new TypeError(("Invalid route expression, given '" + parent + "'"));
46 }
47
48 var _hashed = path.includes('#') ? 0.5 : 1;
49
50 var _depth = path.length * _priority * _hashed;
51
52 return {
53 keys: keys,
54 regex: regex,
55 _depth: _depth,
56 _isSplat: _isSplat
57 };
58 }
59 var PathMatcher = function PathMatcher(path, parent) {
60 var ref = buildMatcher(path, parent);
61 var keys = ref.keys;
62 var regex = ref.regex;
63 var _depth = ref._depth;
64 var _isSplat = ref._isSplat;
65 return {
66 _isSplat: _isSplat,
67 _depth: _depth,
68 match: function (value) {
69 var matches = value.match(regex);
70
71 if (matches) {
72 return keys.reduce(function (prev, cur, i) {
73 prev[cur] = typeof matches[i + 1] === 'string' ? decodeURIComponent(matches[i + 1]) : null;
74 return prev;
75 }, {});
76 }
77 }
78 };
79 };
80
81 PathMatcher.push = function push (key, prev, leaf, parent) {
82 var root = prev[key] || (prev[key] = {});
83
84 if (!root.pattern) {
85 root.pattern = new PathMatcher(key, parent);
86 root.route = (leaf || '').replace(/\/$/, '') || '/';
87 }
88
89 prev.keys = prev.keys || [];
90
91 if (!prev.keys.includes(key)) {
92 prev.keys.push(key);
93 PathMatcher.sort(prev);
94 }
95
96 return root;
97 };
98
99 PathMatcher.sort = function sort (root) {
100 root.keys.sort(function (a, b) {
101 return root[a].pattern._depth - root[b].pattern._depth;
102 });
103 };
104
105 function merge(path, parent) {
106 return ("" + (parent && parent !== '/' ? parent : '') + (path || ''));
107 }
108 function walk(path, cb) {
109 var matches = path.match(/<[^<>]*\/[^<>]*>/);
110
111 if (matches) {
112 throw new TypeError(("RegExp cannot contain slashes, given '" + matches + "'"));
113 }
114
115 var parts = path !== '/' ? path.split('/') : [''];
116 var root = [];
117 parts.some(function (x, i) {
118 var parent = root.concat(x).join('/') || null;
119 var segment = parts.slice(i + 1).join('/') || null;
120 var retval = cb(("/" + x), parent, segment ? ((x ? ("/" + x) : '') + "/" + segment) : null);
121 root.push(x);
122 return retval;
123 });
124 }
125 function reduce(key, root, _seen) {
126 var params = {};
127 var out = [];
128 var splat;
129 walk(key, function (x, leaf, extra) {
130 var found;
131
132 if (!root.keys) {
133 throw new defaultExport(key, x);
134 }
135
136 root.keys.some(function (k) {
137 if (_seen.includes(k)) { return false; }
138 var ref = root[k].pattern;
139 var match = ref.match;
140 var _isSplat = ref._isSplat;
141 var matches = match(_isSplat ? extra || x : x);
142
143 if (matches) {
144 Object.assign(params, matches);
145
146 if (root[k].route) {
147 var routeInfo = Object.assign({}, root[k].info); // properly handle exact-routes!
148
149 var hasMatch = false;
150
151 if (routeInfo.exact) {
152 hasMatch = extra === null;
153 } else {
154 hasMatch = x && leaf === null || x === leaf || _isSplat || !extra;
155 }
156
157 routeInfo.matches = hasMatch;
158 routeInfo.params = Object.assign({}, params);
159 routeInfo.route = root[k].route;
160 routeInfo.path = _isSplat ? extra : leaf || x;
161 out.push(routeInfo);
162 }
163
164 if (extra === null && !root[k].keys) {
165 return true;
166 }
167
168 if (k !== '/') { _seen.push(k); }
169 splat = _isSplat;
170 root = root[k];
171 found = true;
172 return true;
173 }
174
175 return false;
176 });
177
178 if (!(found || root.keys.some(function (k) { return root[k].pattern.match(x); }))) {
179 throw new defaultExport(key, x);
180 }
181
182 return splat || !found;
183 });
184 return out;
185 }
186 function find(path, routes, retries) {
187 var get = reduce.bind(null, path, routes);
188 var set = [];
189
190 while (retries > 0) {
191 retries -= 1;
192
193 try {
194 return get(set);
195 } catch (e) {
196 if (retries > 0) {
197 return get(set);
198 }
199
200 throw e;
201 }
202 }
203 }
204 function add(path, routes, parent, routeInfo) {
205 var fullpath = merge(path, parent);
206 var root = routes;
207 var key;
208
209 if (routeInfo && routeInfo.nested !== true) {
210 key = routeInfo.key;
211 delete routeInfo.key;
212 }
213
214 walk(fullpath, function (x, leaf) {
215 root = PathMatcher.push(x, root, leaf, fullpath);
216
217 if (x !== '/') {
218 root.info = root.info || Object.assign({}, routeInfo);
219 }
220 });
221 root.info = root.info || Object.assign({}, routeInfo);
222
223 if (key) {
224 root.info.key = key;
225 }
226
227 return fullpath;
228 }
229 function rm(path, routes, parent) {
230 var fullpath = merge(path, parent);
231 var root = routes;
232 var leaf = null;
233 var key = null;
234 walk(fullpath, function (x) {
235 if (!root) {
236 leaf = null;
237 return true;
238 }
239
240 key = x;
241 leaf = x === '/' ? routes['/'] : root;
242
243 if (!leaf.keys) {
244 throw new defaultExport(path, x);
245 }
246
247 root = root[x];
248 });
249
250 if (!(leaf && key)) {
251 throw new defaultExport(path, key);
252 }
253
254 delete leaf[key];
255
256 if (key === '/') {
257 delete leaf.info;
258 delete leaf.route;
259 }
260
261 var offset = leaf.keys.indexOf(key);
262
263 if (offset !== -1) {
264 leaf.keys.splice(leaf.keys.indexOf(key), 1);
265 PathMatcher.sort(leaf);
266 }
267 }
268
269 var Router = function Router() {
270 var routes = {};
271 var stack = [];
272 return {
273 resolve: function (path, cb) {
274 var ref = path.split(/(?=[#?])/);
275 var uri = ref[0];
276 var hash = ref[1];
277 var query = ref[2];
278 var segments = uri.substr(1).split('/');
279 var prefix = [];
280 var seen = [];
281 segments.some(function (key) {
282 var sub = prefix.concat(("/" + key)).join('');
283 if (key.length) { prefix.push(("/" + key)); }
284
285 try {
286 var next = find(sub, routes, 1);
287 cb(null, next.filter(function (x) {
288 if (!seen.includes(x.route)) {
289 seen.push(x.route);
290 return true;
291 }
292
293 return false;
294 }));
295 } catch (e) {
296 cb(e, []);
297 return true;
298 }
299
300 return false;
301 });
302
303 if (hash) {
304 cb(null, find(("" + uri + hash), routes, 1));
305 }
306 },
307 mount: function (path, cb) {
308 if (path !== '/') {
309 stack.push(path);
310 }
311
312 cb();
313 stack.pop();
314 },
315 find: function (path, retries) { return find(path, routes, retries === true ? 2 : retries || 1); },
316 add: function (path, routeInfo) { return add(path, routes, stack.join(''), routeInfo); },
317 rm: function (path) { return rm(path, routes, stack.join('')); }
318 };
319 };
320
321 return Router;
322
323}));