@devp0nt/route0 1.0.0-next.4 → 1.0.0-next.41

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.
@@ -0,0 +1,561 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var index_exports = {};
20
+ __export(index_exports, {
21
+ Route0: () => Route0,
22
+ Routes: () => Routes
23
+ });
24
+ module.exports = __toCommonJS(index_exports);
25
+ class Route0 {
26
+ definition;
27
+ pathDefinition;
28
+ paramsDefinition;
29
+ searchDefinition;
30
+ baseUrl;
31
+ constructor(definition, config = {}) {
32
+ this.definition = definition;
33
+ this.pathDefinition = Route0._getPathDefinitionBydefinition(definition);
34
+ this.paramsDefinition = Route0._getParamsDefinitionBydefinition(definition);
35
+ this.searchDefinition = Route0._getSearchDefinitionBydefinition(definition);
36
+ const { baseUrl } = config;
37
+ if (baseUrl && typeof baseUrl === "string" && baseUrl.length) {
38
+ this.baseUrl = baseUrl;
39
+ } else {
40
+ const g = globalThis;
41
+ if (typeof g?.location?.origin === "string" && g.location.origin.length > 0) {
42
+ this.baseUrl = g.location.origin;
43
+ } else {
44
+ this.baseUrl = "https://example.com";
45
+ }
46
+ }
47
+ }
48
+ static create(definition, config) {
49
+ if (typeof definition === "function") {
50
+ return definition.clone(config);
51
+ }
52
+ if (typeof definition === "object") {
53
+ return definition.clone(config);
54
+ }
55
+ const original = new Route0(definition, config);
56
+ const callable = original.get.bind(original);
57
+ Object.setPrototypeOf(callable, original);
58
+ Object.defineProperty(callable, Symbol.toStringTag, {
59
+ value: original.definition
60
+ });
61
+ return callable;
62
+ }
63
+ static from(definition) {
64
+ if (typeof definition === "function") {
65
+ return definition;
66
+ }
67
+ const original = typeof definition === "object" ? definition : new Route0(definition);
68
+ const callable = original.get.bind(original);
69
+ Object.setPrototypeOf(callable, original);
70
+ Object.defineProperty(callable, Symbol.toStringTag, {
71
+ value: original.definition
72
+ });
73
+ return callable;
74
+ }
75
+ static _splitPathDefinitionAndSearchTailDefinition(definition) {
76
+ const i = definition.indexOf("&");
77
+ if (i === -1) return { pathDefinition: definition, searchTailDefinition: "" };
78
+ return {
79
+ pathDefinition: definition.slice(0, i),
80
+ searchTailDefinition: definition.slice(i)
81
+ };
82
+ }
83
+ static _getAbsPath(baseUrl, pathWithSearch) {
84
+ return new URL(pathWithSearch, baseUrl).toString().replace(/\/$/, "");
85
+ }
86
+ static _getPathDefinitionBydefinition(definition) {
87
+ const { pathDefinition } = Route0._splitPathDefinitionAndSearchTailDefinition(definition);
88
+ return pathDefinition;
89
+ }
90
+ static _getParamsDefinitionBydefinition(definition) {
91
+ const { pathDefinition } = Route0._splitPathDefinitionAndSearchTailDefinition(definition);
92
+ const matches = Array.from(pathDefinition.matchAll(/:([A-Za-z0-9_]+)/g));
93
+ const paramsDefinition = Object.fromEntries(matches.map((m) => [m[1], true]));
94
+ const keysCount = Object.keys(paramsDefinition).length;
95
+ if (keysCount === 0) {
96
+ return void 0;
97
+ }
98
+ return paramsDefinition;
99
+ }
100
+ static _getSearchDefinitionBydefinition(definition) {
101
+ const { searchTailDefinition } = Route0._splitPathDefinitionAndSearchTailDefinition(definition);
102
+ if (!searchTailDefinition) {
103
+ return void 0;
104
+ }
105
+ const keys = searchTailDefinition.split("&").filter(Boolean);
106
+ const searchDefinition = Object.fromEntries(keys.map((k) => [k, true]));
107
+ const keysCount = Object.keys(searchDefinition).length;
108
+ if (keysCount === 0) {
109
+ return void 0;
110
+ }
111
+ return searchDefinition;
112
+ }
113
+ extend(suffixDefinition) {
114
+ const { pathDefinition: parentPathDefinition } = Route0._splitPathDefinitionAndSearchTailDefinition(this.definition);
115
+ const { pathDefinition: suffixPathDefinition, searchTailDefinition: suffixSearchTailDefinition } = Route0._splitPathDefinitionAndSearchTailDefinition(suffixDefinition);
116
+ const pathDefinition = `${parentPathDefinition}/${suffixPathDefinition}`.replace(/\/{2,}/g, "/");
117
+ const definition = `${pathDefinition}${suffixSearchTailDefinition}`;
118
+ return Route0.create(definition, { baseUrl: this.baseUrl });
119
+ }
120
+ // implementation
121
+ get(...args) {
122
+ const { searchInput, paramsInput, absInput } = (() => {
123
+ if (args.length === 0) {
124
+ return { searchInput: {}, paramsInput: {}, absInput: false };
125
+ }
126
+ const input = args[0];
127
+ if (typeof input !== "object" || input === null) {
128
+ return { searchInput: {}, paramsInput: {}, absInput: false };
129
+ }
130
+ const { search, abs, ...params } = input;
131
+ return { searchInput: search || {}, paramsInput: params, absInput: abs ?? false };
132
+ })();
133
+ const neededParamsKeys = this.paramsDefinition ? Object.keys(this.paramsDefinition) : [];
134
+ const providedParamsKeys = Object.keys(paramsInput);
135
+ const notProvidedKeys = neededParamsKeys.filter((k) => !providedParamsKeys.includes(k));
136
+ if (notProvidedKeys.length) {
137
+ Object.assign(paramsInput, Object.fromEntries(notProvidedKeys.map((k) => [k, "undefined"])));
138
+ }
139
+ let url = this.pathDefinition;
140
+ url = url.replace(/:([A-Za-z0-9_]+)/g, (_m, k) => encodeURIComponent(String(paramsInput?.[k] ?? "")));
141
+ const searchInputStringified = Object.fromEntries(Object.entries(searchInput).map(([k, v]) => [k, String(v)]));
142
+ url = [url, new URLSearchParams(searchInputStringified).toString()].filter(Boolean).join("?");
143
+ url = url.replace(/\/{2,}/g, "/");
144
+ url = absInput ? Route0._getAbsPath(this.baseUrl, url) : url;
145
+ return url;
146
+ }
147
+ // implementation
148
+ flat(...args) {
149
+ const { searchInput, paramsInput, absInput } = (() => {
150
+ if (args.length === 0) {
151
+ return { searchInput: {}, paramsInput: {}, absInput: false };
152
+ }
153
+ const input = args[0];
154
+ if (typeof input !== "object" || input === null) {
155
+ return { searchInput: {}, paramsInput: {}, absInput: args[1] ?? false };
156
+ }
157
+ const paramsKeys = this.getParamsKeys();
158
+ const paramsInput2 = paramsKeys.reduce((acc, key) => {
159
+ if (input[key] !== void 0) {
160
+ acc[key] = input[key];
161
+ }
162
+ return acc;
163
+ }, {});
164
+ const searchKeys = this.getSearchKeys();
165
+ const searchInput2 = Object.keys(input).filter((k) => {
166
+ if (searchKeys.includes(k)) {
167
+ return true;
168
+ }
169
+ if (paramsKeys.includes(k)) {
170
+ return false;
171
+ }
172
+ return true;
173
+ }).reduce((acc, key) => {
174
+ acc[key] = input[key];
175
+ return acc;
176
+ }, {});
177
+ return { searchInput: searchInput2, paramsInput: paramsInput2, absInput: args[1] ?? false };
178
+ })();
179
+ return this.get({ ...paramsInput, search: searchInput, abs: absInput });
180
+ }
181
+ getParamsKeys() {
182
+ return Object.keys(this.paramsDefinition || {});
183
+ }
184
+ getSearchKeys() {
185
+ return Object.keys(this.searchDefinition || {});
186
+ }
187
+ getFlatKeys() {
188
+ return [...this.getSearchKeys(), ...this.getParamsKeys()];
189
+ }
190
+ getDefinition() {
191
+ return this.pathDefinition;
192
+ }
193
+ clone(config) {
194
+ return Route0.create(this.definition, config);
195
+ }
196
+ getRegexBaseStrictString() {
197
+ return this.pathDefinition.replace(/:(\w+)/g, "___PARAM___").replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/___PARAM___/g, "([^/]+)");
198
+ }
199
+ getRegexBaseString() {
200
+ return this.getRegexBaseStrictString().replace(/\/+$/, "") + "/?";
201
+ }
202
+ getRegexStrictString() {
203
+ return `^${this.getRegexBaseStrictString()}$`;
204
+ }
205
+ getRegexString() {
206
+ return `^${this.getRegexBaseString()}$`;
207
+ }
208
+ getRegexStrict() {
209
+ return new RegExp(this.getRegexStrictString());
210
+ }
211
+ getRegex() {
212
+ return new RegExp(this.getRegexString());
213
+ }
214
+ static getRegexStrictStringGroup(routes) {
215
+ const patterns = routes.map((route) => route.getRegexStrictString()).join("|");
216
+ return `(${patterns})`;
217
+ }
218
+ static getRegexStrictGroup(routes) {
219
+ const patterns = this.getRegexStrictStringGroup(routes);
220
+ return new RegExp(`^(${patterns})$`);
221
+ }
222
+ static getRegexStringGroup(routes) {
223
+ const patterns = routes.map((route) => route.getRegexString()).join("|");
224
+ return `(${patterns})`;
225
+ }
226
+ static getRegexGroup(routes) {
227
+ const patterns = this.getRegexStringGroup(routes);
228
+ return new RegExp(`^(${patterns})$`);
229
+ }
230
+ static toRelLocation(location) {
231
+ return {
232
+ ...location,
233
+ abs: false,
234
+ origin: void 0,
235
+ href: void 0,
236
+ port: void 0,
237
+ host: void 0,
238
+ hostname: void 0
239
+ };
240
+ }
241
+ static toAbsLocation(location, baseUrl) {
242
+ const relLoc = Route0.toRelLocation(location);
243
+ const url = new URL(relLoc.hrefRel, baseUrl);
244
+ return {
245
+ ...location,
246
+ abs: true,
247
+ origin: url.origin,
248
+ href: url.href,
249
+ port: url.port,
250
+ host: url.host,
251
+ hostname: url.hostname
252
+ };
253
+ }
254
+ static getLocation(hrefOrHrefRelOrLocation) {
255
+ if (hrefOrHrefRelOrLocation instanceof URL) {
256
+ return Route0.getLocation(hrefOrHrefRelOrLocation.href);
257
+ }
258
+ if (typeof hrefOrHrefRelOrLocation !== "string") {
259
+ hrefOrHrefRelOrLocation = hrefOrHrefRelOrLocation.href || hrefOrHrefRelOrLocation.hrefRel;
260
+ }
261
+ const abs = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(hrefOrHrefRelOrLocation);
262
+ const base = abs ? void 0 : "http://example.com";
263
+ const url = new URL(hrefOrHrefRelOrLocation, base);
264
+ const searchParams = Object.fromEntries(url.searchParams.entries());
265
+ let pathname = url.pathname;
266
+ if (pathname.length > 1 && pathname.endsWith("/")) {
267
+ pathname = pathname.slice(0, -1);
268
+ }
269
+ const hrefRel = pathname + url.search + url.hash;
270
+ const location = {
271
+ pathname,
272
+ search: url.search,
273
+ hash: url.hash,
274
+ origin: abs ? url.origin : void 0,
275
+ href: abs ? url.href : void 0,
276
+ hrefRel,
277
+ abs,
278
+ // extra host-related fields (available even for relative with dummy base)
279
+ host: abs ? url.host : void 0,
280
+ hostname: abs ? url.hostname : void 0,
281
+ port: abs ? url.port || void 0 : void 0,
282
+ // specific to UnknownLocation
283
+ searchParams,
284
+ params: void 0,
285
+ route: void 0,
286
+ exact: false,
287
+ parent: false,
288
+ children: false
289
+ };
290
+ return location;
291
+ }
292
+ getLocation(hrefOrHrefRelOrLocation) {
293
+ if (hrefOrHrefRelOrLocation instanceof URL) {
294
+ return this.getLocation(hrefOrHrefRelOrLocation.href);
295
+ }
296
+ if (typeof hrefOrHrefRelOrLocation !== "string") {
297
+ hrefOrHrefRelOrLocation = hrefOrHrefRelOrLocation.href || hrefOrHrefRelOrLocation.hrefRel;
298
+ }
299
+ const location = Route0.getLocation(hrefOrHrefRelOrLocation);
300
+ location.route = this.definition;
301
+ location.params = {};
302
+ const pathname = location.pathname.length > 1 && location.pathname.endsWith("/") ? location.pathname.slice(0, -1) : location.pathname;
303
+ const paramNames = [];
304
+ const def = this.pathDefinition.length > 1 && this.pathDefinition.endsWith("/") ? this.pathDefinition.slice(0, -1) : this.pathDefinition;
305
+ def.replace(/:([A-Za-z0-9_]+)/g, (_m, name) => {
306
+ paramNames.push(String(name));
307
+ return "";
308
+ });
309
+ const exactRe = new RegExp(`^${this.getRegexBaseString()}$`);
310
+ const parentRe = new RegExp(`^${this.getRegexBaseString()}(?:/.*)?$`);
311
+ const exactMatch = pathname.match(exactRe);
312
+ if (exactMatch) {
313
+ const values = exactMatch.slice(1);
314
+ const params = Object.fromEntries(paramNames.map((n, i) => [n, decodeURIComponent(values[i] ?? "")]));
315
+ location.params = params;
316
+ } else {
317
+ location.params = {};
318
+ }
319
+ const exact = !!exactMatch;
320
+ const parent = !exact && parentRe.test(pathname);
321
+ const getParts = (path) => path === "/" ? ["/"] : path.split("/").filter(Boolean);
322
+ const defParts = getParts(def);
323
+ const pathParts = getParts(pathname);
324
+ let isPrefix = true;
325
+ if (pathParts.length > defParts.length) {
326
+ isPrefix = false;
327
+ } else {
328
+ for (let i = 0; i < pathParts.length; i++) {
329
+ const defPart = defParts[i];
330
+ const pathPart = pathParts[i];
331
+ if (!defPart) {
332
+ isPrefix = false;
333
+ break;
334
+ }
335
+ if (defPart.startsWith(":")) continue;
336
+ if (defPart !== pathPart) {
337
+ isPrefix = false;
338
+ break;
339
+ }
340
+ }
341
+ }
342
+ const children = !exact && isPrefix;
343
+ return {
344
+ ...location,
345
+ exact,
346
+ parent,
347
+ children
348
+ };
349
+ }
350
+ isSame(other) {
351
+ return this.pathDefinition.replace(/:([A-Za-z0-9_]+)/g, "__PARAM__") === other.pathDefinition.replace(/:([A-Za-z0-9_]+)/g, "__PARAM__");
352
+ }
353
+ static isSame(a, b) {
354
+ if (!a) {
355
+ if (!b) return true;
356
+ return false;
357
+ }
358
+ if (!b) {
359
+ return false;
360
+ }
361
+ return Route0.create(a).isSame(Route0.create(b));
362
+ }
363
+ isChildren(other) {
364
+ if (!other) return false;
365
+ other = Route0.create(other);
366
+ const getParts = (path) => path === "/" ? ["/"] : path.split("/").filter(Boolean);
367
+ if (other.pathDefinition === "/" && this.pathDefinition !== "/") {
368
+ return true;
369
+ }
370
+ const thisParts = getParts(this.pathDefinition);
371
+ const otherParts = getParts(other.pathDefinition);
372
+ if (thisParts.length <= otherParts.length) return false;
373
+ for (let i = 0; i < otherParts.length; i++) {
374
+ const otherPart = otherParts[i];
375
+ const thisPart = thisParts[i];
376
+ if (otherPart.startsWith(":")) continue;
377
+ if (otherPart !== thisPart) return false;
378
+ }
379
+ return true;
380
+ }
381
+ isParent(other) {
382
+ if (!other) return false;
383
+ other = Route0.create(other);
384
+ const getParts = (path) => path === "/" ? ["/"] : path.split("/").filter(Boolean);
385
+ if (this.pathDefinition === "/" && other.pathDefinition !== "/") {
386
+ return true;
387
+ }
388
+ const thisParts = getParts(this.pathDefinition);
389
+ const otherParts = getParts(other.pathDefinition);
390
+ if (thisParts.length >= otherParts.length) return false;
391
+ for (let i = 0; i < thisParts.length; i++) {
392
+ const thisPart = thisParts[i];
393
+ const otherPart = otherParts[i];
394
+ if (thisPart.startsWith(":")) continue;
395
+ if (thisPart !== otherPart) return false;
396
+ }
397
+ return true;
398
+ }
399
+ isConflict(other) {
400
+ if (!other) return false;
401
+ other = Route0.create(other);
402
+ const getParts = (path) => {
403
+ if (path === "/") return ["/"];
404
+ return path.split("/").filter(Boolean);
405
+ };
406
+ const thisParts = getParts(this.pathDefinition);
407
+ const otherParts = getParts(other.pathDefinition);
408
+ if (thisParts.length !== otherParts.length) {
409
+ return false;
410
+ }
411
+ for (let i = 0; i < thisParts.length; i++) {
412
+ const thisPart = thisParts[i];
413
+ const otherPart = otherParts[i];
414
+ if (thisPart.startsWith(":") && otherPart.startsWith(":")) {
415
+ continue;
416
+ }
417
+ if (thisPart.startsWith(":") || otherPart.startsWith(":")) {
418
+ continue;
419
+ }
420
+ if (thisPart !== otherPart) {
421
+ return false;
422
+ }
423
+ }
424
+ return true;
425
+ }
426
+ isMoreSpecificThan(other) {
427
+ if (!other) return false;
428
+ other = Route0.create(other);
429
+ const getParts = (path) => {
430
+ if (path === "/") return ["/"];
431
+ return path.split("/").filter(Boolean);
432
+ };
433
+ const thisParts = getParts(this.pathDefinition);
434
+ const otherParts = getParts(other.pathDefinition);
435
+ for (let i = 0; i < Math.min(thisParts.length, otherParts.length); i++) {
436
+ const thisIsStatic = !thisParts[i].startsWith(":");
437
+ const otherIsStatic = !otherParts[i].startsWith(":");
438
+ if (thisIsStatic && !otherIsStatic) return true;
439
+ if (!thisIsStatic && otherIsStatic) return false;
440
+ }
441
+ return this.pathDefinition < other.pathDefinition;
442
+ }
443
+ }
444
+ class Routes {
445
+ routes;
446
+ pathsOrdering;
447
+ keysOrdering;
448
+ ordered;
449
+ _;
450
+ constructor({
451
+ routes,
452
+ isHydrated = false,
453
+ pathsOrdering,
454
+ keysOrdering,
455
+ ordered
456
+ }) {
457
+ this.routes = isHydrated ? routes : Routes.hydrate(routes);
458
+ if (!pathsOrdering || !keysOrdering || !ordered) {
459
+ const ordering = Routes.makeOrdering(this.routes);
460
+ this.pathsOrdering = ordering.pathsOrdering;
461
+ this.keysOrdering = ordering.keysOrdering;
462
+ this.ordered = this.keysOrdering.map((key) => this.routes[key]);
463
+ } else {
464
+ this.pathsOrdering = pathsOrdering;
465
+ this.keysOrdering = keysOrdering;
466
+ this.ordered = ordered;
467
+ }
468
+ this._ = {
469
+ getLocation: this.getLocation.bind(this),
470
+ override: this.override.bind(this),
471
+ pathsOrdering: this.pathsOrdering,
472
+ keysOrdering: this.keysOrdering,
473
+ ordered: this.ordered
474
+ };
475
+ }
476
+ static create(routes) {
477
+ const instance = new Routes({ routes });
478
+ return Routes.prettify(instance);
479
+ }
480
+ static prettify(instance) {
481
+ Object.setPrototypeOf(instance, Routes.prototype);
482
+ Object.defineProperty(instance, Symbol.toStringTag, {
483
+ value: "Routes"
484
+ });
485
+ Object.assign(instance, {
486
+ override: instance.override.bind(instance)
487
+ });
488
+ Object.assign(instance, instance.routes);
489
+ return instance;
490
+ }
491
+ static hydrate(routes) {
492
+ const result = {};
493
+ for (const key in routes) {
494
+ if (Object.prototype.hasOwnProperty.call(routes, key)) {
495
+ const value = routes[key];
496
+ result[key] = typeof value === "string" ? Route0.create(value) : value;
497
+ }
498
+ }
499
+ return result;
500
+ }
501
+ getLocation(hrefOrHrefRelOrLocation) {
502
+ const input = hrefOrHrefRelOrLocation;
503
+ for (const route of this.ordered) {
504
+ const loc = route.getLocation(hrefOrHrefRelOrLocation);
505
+ if (loc.exact) {
506
+ return loc;
507
+ }
508
+ }
509
+ return typeof input === "string" ? Route0.getLocation(input) : Route0.getLocation(input);
510
+ }
511
+ static makeOrdering(routes) {
512
+ const hydrated = Routes.hydrate(routes);
513
+ const entries = Object.entries(hydrated);
514
+ const getParts = (path) => {
515
+ if (path === "/") return ["/"];
516
+ return path.split("/").filter(Boolean);
517
+ };
518
+ entries.sort(([_keyA, routeA], [_keyB, routeB]) => {
519
+ const partsA = getParts(routeA.pathDefinition);
520
+ const partsB = getParts(routeB.pathDefinition);
521
+ if (partsA.length !== partsB.length) {
522
+ return partsA.length - partsB.length;
523
+ }
524
+ if (routeA.isConflict(routeB)) {
525
+ if (routeA.isMoreSpecificThan(routeB)) return -1;
526
+ if (routeB.isMoreSpecificThan(routeA)) return 1;
527
+ }
528
+ return routeA.pathDefinition.localeCompare(routeB.pathDefinition);
529
+ });
530
+ const pathsOrdering = entries.map(([_key, route]) => route.definition);
531
+ const keysOrdering = entries.map(([_key, route]) => _key);
532
+ return { pathsOrdering, keysOrdering };
533
+ }
534
+ override(config) {
535
+ const newRoutes = {};
536
+ for (const key in this.routes) {
537
+ if (Object.prototype.hasOwnProperty.call(this.routes, key)) {
538
+ newRoutes[key] = this.routes[key].clone(config);
539
+ }
540
+ }
541
+ const instance = new Routes({
542
+ routes: newRoutes,
543
+ isHydrated: true,
544
+ pathsOrdering: this.pathsOrdering,
545
+ keysOrdering: this.keysOrdering,
546
+ ordered: this.keysOrdering.map((key) => newRoutes[key])
547
+ });
548
+ return Routes.prettify(instance);
549
+ }
550
+ static _ = {
551
+ prettify: Routes.prettify.bind(Routes),
552
+ hydrate: Routes.hydrate.bind(Routes),
553
+ makeOrdering: Routes.makeOrdering.bind(Routes)
554
+ };
555
+ }
556
+ // Annotate the CommonJS export names for ESM import in node:
557
+ 0 && (module.exports = {
558
+ Route0,
559
+ Routes
560
+ });
561
+ //# sourceMappingURL=index.cjs.map