UNPKG

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