@thi.ng/router 4.0.5 → 4.0.7

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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2024-03-27T09:53:45Z
3
+ - **Last updated**: 2024-04-11T12:32:44Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![Mastodon Follow](https://img.shields.io/mastodon/follow/109331703950160316?domain=https%3A%2F%2Fmastodon.thi.ng&style=social)](https://mastodon.thi.ng/@toxi)
8
8
 
9
9
  > [!NOTE]
10
- > This is one of 190 standalone projects, maintained as part
10
+ > This is one of 192 standalone projects, maintained as part
11
11
  > of the [@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo
12
12
  > and anti-framework.
13
13
  >
@@ -57,7 +57,13 @@ Generic trie-based router with support for wildcards, route param validation/coe
57
57
  yarn add @thi.ng/router
58
58
  ```
59
59
 
60
- ES module import:
60
+ ESM import:
61
+
62
+ ```ts
63
+ import * as rou from "@thi.ng/router";
64
+ ```
65
+
66
+ Browser ESM import:
61
67
 
62
68
  ```html
63
69
  <script type="module" src="https://cdn.skypack.dev/@thi.ng/router"></script>
@@ -65,12 +71,6 @@ ES module import:
65
71
 
66
72
  [Skypack documentation](https://docs.skypack.dev/)
67
73
 
68
- For Node.js REPL:
69
-
70
- ```js
71
- const router = await import("@thi.ng/router");
72
- ```
73
-
74
74
  Package sizes (brotli'd, pre-treeshake): ESM: 1.94 KB
75
75
 
76
76
  ## Dependencies
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/router",
3
- "version": "4.0.5",
3
+ "version": "4.0.7",
4
4
  "description": "Generic trie-based router with support for wildcards, route param validation/coercion, auth",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -36,10 +36,10 @@
36
36
  "tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
37
37
  },
38
38
  "dependencies": {
39
- "@thi.ng/api": "^8.9.31",
40
- "@thi.ng/checks": "^3.5.5",
41
- "@thi.ng/equiv": "^2.1.53",
42
- "@thi.ng/errors": "^2.5.2",
39
+ "@thi.ng/api": "^8.10.1",
40
+ "@thi.ng/checks": "^3.6.1",
41
+ "@thi.ng/equiv": "^2.1.55",
42
+ "@thi.ng/errors": "^2.5.4",
43
43
  "tslib": "^2.6.2"
44
44
  },
45
45
  "devDependencies": {
@@ -98,5 +98,5 @@
98
98
  ],
99
99
  "year": 2014
100
100
  },
101
- "gitHead": "feb3b24654f2c931cd3c3308c1c0c807ee14d0e4\n"
101
+ "gitHead": "18a0c063a7b33d790e5bc2486c106f45f663ac28\n"
102
102
  }
package/basic.d.ts DELETED
@@ -1,46 +0,0 @@
1
- import type { Event, INotify, IObjectOf, Listener, SomeRequired } from "@thi.ng/api";
2
- import { type AugmentedRoute, type Route, type RouteMatch, type RouteParamValidator, type RouterEventType, type RouterOpts } from "./api.js";
3
- import { Trie } from "./trie.js";
4
- export declare class BasicRouter<T = any> implements INotify<RouterEventType> {
5
- opts: RouterOpts<T>;
6
- current: RouteMatch | undefined;
7
- protected index: Record<string, AugmentedRoute>;
8
- protected routes: Trie<AugmentedRoute>;
9
- constructor(config: RouterOpts<T>);
10
- addListener(id: RouterEventType, fn: Listener<RouterEventType>, scope?: any): boolean;
11
- removeListener(id: RouterEventType, fn: Listener<RouterEventType>, scope?: any): boolean;
12
- notify(event: Event<RouterEventType>): boolean;
13
- start(): void;
14
- addRoutes(routes: Route[]): void;
15
- /**
16
- * Main router function. Attempts to match given input string against all
17
- * configured routes. Before returning, triggers {@link EVENT_ROUTE_CHANGED}
18
- * with return value as well. If none of the routes matches, emits
19
- * {@link EVENT_ROUTE_FAILED} and then falls back to configured default
20
- * route.
21
- *
22
- * @remarks
23
- * See {@link RouteAuthenticator} for details about `ctx` handling.
24
- *
25
- * @param src - route path to match
26
- * @param ctx - arbitrary user context
27
- */
28
- route(src: string, ctx?: T): RouteMatch | undefined;
29
- /**
30
- * Returns a formatted version of given {@link RouteMatch}, incl. any
31
- * params, or alternatively a registered route ID (and optional route
32
- * params). Throws an error if an invalid route `id` is provided.
33
- *
34
- * @param id -
35
- * @param params -
36
- * @param rest -
37
- */
38
- format(id: string, params?: any, rest?: string[]): string;
39
- format(match: SomeRequired<RouteMatch, "id">): string;
40
- routeForID(id: string): AugmentedRoute | undefined;
41
- protected augmentRoute(route: Route): AugmentedRoute;
42
- protected matchRoutes(src: string, ctx?: T): RouteMatch | undefined;
43
- protected validateRouteParams(params: any, validators: IObjectOf<Partial<RouteParamValidator>>): boolean;
44
- protected handleRouteFailure(): boolean;
45
- }
46
- //# sourceMappingURL=basic.d.ts.map
package/basic.js DELETED
@@ -1,233 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
- var __decorateClass = (decorators, target, key, kind) => {
4
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
- if (decorator = decorators[i])
7
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
- if (kind && result)
9
- __defProp(target, key, result);
10
- return result;
11
- };
12
- import { INotifyMixin } from "@thi.ng/api/mixins/inotify";
13
- import { isString } from "@thi.ng/checks/is-string";
14
- import { equiv } from "@thi.ng/equiv";
15
- import { assert } from "@thi.ng/errors/assert";
16
- import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
17
- import { illegalArity } from "@thi.ng/errors/illegal-arity";
18
- import { illegalState } from "@thi.ng/errors/illegal-state";
19
- import {
20
- EVENT_ROUTE_CHANGED,
21
- EVENT_ROUTE_FAILED
22
- } from "./api.js";
23
- import { Trie } from "./trie.js";
24
- let BasicRouter = class {
25
- opts;
26
- current;
27
- index = {};
28
- routes = new Trie();
29
- constructor(config) {
30
- this.opts = {
31
- authenticator: (match) => match,
32
- prefix: "/",
33
- separator: "/",
34
- trim: true,
35
- ...config
36
- };
37
- this.addRoutes(this.opts.routes);
38
- assert(
39
- this.routeForID(this.opts.default) !== void 0,
40
- `missing config for default route: '${this.opts.default}'`
41
- );
42
- if (config.initial) {
43
- const route = this.routeForID(config.initial);
44
- assert(
45
- route !== void 0,
46
- `missing config for initial route: ${this.opts.initial}`
47
- );
48
- assert(!route.params, "initial route MUST not be parametric");
49
- }
50
- }
51
- // @ts-ignore: arguments
52
- // prettier-ignore
53
- addListener(id, fn, scope) {
54
- }
55
- // @ts-ignore: arguments
56
- // prettier-ignore
57
- removeListener(id, fn, scope) {
58
- }
59
- // @ts-ignore: arguments
60
- notify(event) {
61
- }
62
- start() {
63
- if (this.opts.initial) {
64
- const route = this.routeForID(this.opts.initial);
65
- this.current = { id: route.id, params: {} };
66
- this.notify({ id: EVENT_ROUTE_CHANGED, value: this.current });
67
- }
68
- }
69
- addRoutes(routes) {
70
- for (let r of routes) {
71
- try {
72
- const route = this.augmentRoute(r);
73
- this.routes.set(route.match, route);
74
- this.index[route.id] = route;
75
- } catch (e) {
76
- illegalArgs(
77
- `error in route "${r.id}": ${e.origMessage}`
78
- );
79
- }
80
- }
81
- }
82
- /**
83
- * Main router function. Attempts to match given input string against all
84
- * configured routes. Before returning, triggers {@link EVENT_ROUTE_CHANGED}
85
- * with return value as well. If none of the routes matches, emits
86
- * {@link EVENT_ROUTE_FAILED} and then falls back to configured default
87
- * route.
88
- *
89
- * @remarks
90
- * See {@link RouteAuthenticator} for details about `ctx` handling.
91
- *
92
- * @param src - route path to match
93
- * @param ctx - arbitrary user context
94
- */
95
- route(src, ctx) {
96
- if (this.opts.trim && src.charAt(src.length - 1) === this.opts.separator) {
97
- src = src.substring(0, src.length - 1);
98
- }
99
- src = src.substring(this.opts.prefix.length);
100
- let match = this.matchRoutes(src, ctx);
101
- if (!match) {
102
- this.notify({ id: EVENT_ROUTE_FAILED, value: src });
103
- if (!this.handleRouteFailure()) {
104
- return;
105
- }
106
- const route = this.routeForID(this.opts.default);
107
- match = { id: route.id, redirect: true };
108
- }
109
- if (!equiv(match, this.current)) {
110
- this.current = match;
111
- this.notify({ id: EVENT_ROUTE_CHANGED, value: match });
112
- }
113
- return match;
114
- }
115
- format(...args) {
116
- let [id, params, rest] = args;
117
- let match;
118
- switch (args.length) {
119
- case 3:
120
- match = { id, params, rest };
121
- break;
122
- case 2:
123
- match = { id, params };
124
- break;
125
- case 1:
126
- match = isString(id) ? { id } : id;
127
- break;
128
- default:
129
- illegalArity(args.length);
130
- }
131
- const route = this.routeForID(match.id);
132
- if (route) {
133
- const params2 = match.params;
134
- let parts = route.match.map((x) => {
135
- if (isRouteParam(x)) {
136
- const id2 = x.substring(1);
137
- const p = params2?.[id2];
138
- if (p == null) {
139
- illegalArgs(`missing value for param '${id2}'`);
140
- }
141
- return p;
142
- }
143
- return x;
144
- });
145
- if (route.rest >= 0)
146
- parts = parts.slice(0, route.rest).concat(match.rest || []);
147
- return this.opts.prefix + parts.join(this.opts.separator);
148
- } else {
149
- illegalArgs(`invalid route ID: ${match.id}`);
150
- }
151
- }
152
- routeForID(id) {
153
- return this.index[id];
154
- }
155
- augmentRoute(route) {
156
- const match = isString(route.match) ? route.match.split(this.opts.separator).filter((x) => !!x) : route.match;
157
- const existing = this.routes.get(match);
158
- if (existing) {
159
- illegalArgs(
160
- `duplicate route: ${match} (id: ${route.id}, conflicts with: ${existing.id})`
161
- );
162
- }
163
- let hasParams = false;
164
- const params = match.reduce((acc, x, i) => {
165
- if (isRouteParam(x)) {
166
- hasParams = true;
167
- acc[i] = x.substring(1);
168
- }
169
- return acc;
170
- }, {});
171
- return {
172
- ...route,
173
- match,
174
- params: hasParams ? params : void 0,
175
- rest: match.indexOf("+")
176
- };
177
- }
178
- matchRoutes(src, ctx) {
179
- const curr = src.split(this.opts.separator);
180
- const route = this.routes.get(curr);
181
- if (!route)
182
- return;
183
- let params;
184
- if (route.params) {
185
- params = Object.entries(route.params).reduce(
186
- (acc, [i, k]) => (acc[k] = curr[+i], acc),
187
- {}
188
- );
189
- }
190
- if (route.validate && !this.validateRouteParams(params, route.validate)) {
191
- return;
192
- }
193
- const rest = route.rest >= 0 ? curr.slice(route.rest) : void 0;
194
- let match = {
195
- id: route.id,
196
- params,
197
- rest
198
- };
199
- if (route.auth) {
200
- match = this.opts.authenticator(match, route, ctx);
201
- if (match && !this.index[match.id]) {
202
- illegalState(
203
- "auth handler returned invalid route ID: " + match.id
204
- );
205
- }
206
- }
207
- return match;
208
- }
209
- validateRouteParams(params, validators) {
210
- for (let id in validators) {
211
- if (params[id] !== void 0) {
212
- const val = validators[id];
213
- if (val.coerce) {
214
- params[id] = val.coerce(params[id]);
215
- }
216
- if (val.check && !val.check(params[id])) {
217
- return false;
218
- }
219
- }
220
- }
221
- return true;
222
- }
223
- handleRouteFailure() {
224
- return true;
225
- }
226
- };
227
- BasicRouter = __decorateClass([
228
- INotifyMixin
229
- ], BasicRouter);
230
- const isRouteParam = (x) => x[0] === "?";
231
- export {
232
- BasicRouter
233
- };
package/history.d.ts DELETED
@@ -1,31 +0,0 @@
1
- import type { Fn } from "@thi.ng/api";
2
- import type { HTMLRouterConfig } from "./api.js";
3
- import { BasicRouter } from "./basic.js";
4
- export declare class HTMLRouter<T = any> extends BasicRouter<T> {
5
- protected currentPath: string;
6
- protected popHandler: Fn<PopStateEvent, void>;
7
- protected hashHandler: EventListener;
8
- protected useFragment: boolean;
9
- protected ignoreHashChange: boolean;
10
- constructor(config: HTMLRouterConfig);
11
- start(): void;
12
- release(): void;
13
- /**
14
- * Like {@link BasicRouter.route}, but takes additional arg to control if
15
- * this routing operation should manipulate the browser's `history`.
16
- *
17
- * @remarks
18
- * If called from userland, this normally is true (also default). However,
19
- * we want to avoid this if called from this router's own event handlers.
20
- *
21
- * @param src -
22
- * @param ctx -
23
- * @param pushState -
24
- */
25
- route(src: string, ctx?: T, pushState?: boolean): import("./api.js").RouteMatch | undefined;
26
- routeTo(route: string, ctx?: T): void;
27
- protected handlePopChange(): Fn<PopStateEvent, void>;
28
- protected handleHashChange(): EventListener;
29
- protected handleRouteFailure(): boolean;
30
- }
31
- //# sourceMappingURL=history.d.ts.map
package/history.js DELETED
@@ -1,91 +0,0 @@
1
- import { equiv } from "@thi.ng/equiv";
2
- import { BasicRouter } from "./basic.js";
3
- class HTMLRouter extends BasicRouter {
4
- currentPath;
5
- popHandler;
6
- hashHandler;
7
- useFragment;
8
- ignoreHashChange;
9
- constructor(config) {
10
- super({ prefix: config.useFragment ? "#/" : "/", ...config });
11
- this.useFragment = config.useFragment !== false;
12
- this.ignoreHashChange = false;
13
- }
14
- start() {
15
- window.addEventListener("popstate", this.handlePopChange());
16
- if (this.useFragment) {
17
- window.addEventListener("hashchange", this.handleHashChange());
18
- }
19
- if (this.opts.initial) {
20
- const route = this.routeForID(this.opts.initial);
21
- this.route(this.format({ id: route.id }));
22
- } else {
23
- this.route(this.useFragment ? location.hash : location.pathname);
24
- }
25
- }
26
- release() {
27
- window.removeEventListener("popstate", this.popHandler);
28
- if (this.useFragment) {
29
- window.removeEventListener("hashchange", this.hashHandler);
30
- }
31
- }
32
- /**
33
- * Like {@link BasicRouter.route}, but takes additional arg to control if
34
- * this routing operation should manipulate the browser's `history`.
35
- *
36
- * @remarks
37
- * If called from userland, this normally is true (also default). However,
38
- * we want to avoid this if called from this router's own event handlers.
39
- *
40
- * @param src -
41
- * @param ctx -
42
- * @param pushState -
43
- */
44
- route(src, ctx, pushState = true) {
45
- const old = this.current;
46
- const route = super.route(src, ctx);
47
- if (route && !equiv(route, old)) {
48
- this.currentPath = this.format(route);
49
- if (pushState) {
50
- history.pushState(this.currentPath, "", this.currentPath);
51
- }
52
- }
53
- return route;
54
- }
55
- routeTo(route, ctx) {
56
- if (this.useFragment) {
57
- location.hash = route;
58
- }
59
- this.route(route, ctx);
60
- }
61
- handlePopChange() {
62
- return this.popHandler = this.popHandler || ((e) => {
63
- this.route(
64
- e.state || (this.useFragment ? location.hash : location.pathname),
65
- void 0,
66
- false
67
- );
68
- }).bind(this);
69
- }
70
- handleHashChange() {
71
- return this.hashHandler = this.hashHandler || ((e) => {
72
- if (!this.ignoreHashChange) {
73
- const hash = e.newURL.substring(e.newURL.indexOf("#"));
74
- if (hash !== this.currentPath) {
75
- this.route(hash, void 0, false);
76
- }
77
- }
78
- }).bind(this);
79
- }
80
- handleRouteFailure() {
81
- this.ignoreHashChange = true;
82
- location.hash = this.format({
83
- id: this.routeForID(this.opts.default).id
84
- });
85
- this.ignoreHashChange = false;
86
- return true;
87
- }
88
- }
89
- export {
90
- HTMLRouter
91
- };