@esportsplus/routing 0.6.0 → 0.6.2

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/.editorconfig CHANGED
@@ -1,9 +1,9 @@
1
- root = true
2
-
3
- [*]
4
- indent_style = space
5
- indent_size = 4
6
- charset = utf-8
7
- trim_trailing_whitespace = true
8
- insert_final_newline = true
9
- end_of_line = lf
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = space
5
+ indent_size = 4
6
+ charset = utf-8
7
+ trim_trailing_whitespace = true
8
+ insert_final_newline = true
9
+ end_of_line = lf
package/.gitattributes CHANGED
@@ -1,2 +1,2 @@
1
- # Auto detect text files and perform LF normalization
2
- * text=auto
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
@@ -1,25 +1,25 @@
1
- # To get started with Dependabot version updates, you'll need to specify which
2
- # package ecosystems to update and where the package manifests are located.
3
- # Please see the documentation for all configuration options:
4
- # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
-
6
- version: 2
7
-
8
- registries:
9
- npm-npmjs:
10
- token: ${{secrets.NPM_TOKEN}}
11
- type: npm-registry
12
- url: https://registry.npmjs.org
13
-
14
- updates:
15
- - package-ecosystem: "npm"
16
- directory: "/"
17
- groups:
18
- production-dependencies:
19
- dependency-type: "production"
20
- development-dependencies:
21
- dependency-type: "development"
22
- registries:
23
- - npm-npmjs
24
- schedule:
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
+
6
+ version: 2
7
+
8
+ registries:
9
+ npm-npmjs:
10
+ token: ${{secrets.NPM_TOKEN}}
11
+ type: npm-registry
12
+ url: https://registry.npmjs.org
13
+
14
+ updates:
15
+ - package-ecosystem: "npm"
16
+ directory: "/"
17
+ groups:
18
+ production-dependencies:
19
+ dependency-type: "production"
20
+ development-dependencies:
21
+ dependency-type: "development"
22
+ registries:
23
+ - npm-npmjs
24
+ schedule:
25
25
  interval: "daily"
@@ -1,9 +1,9 @@
1
- name: bump version
2
-
3
- on:
4
- push:
5
- branches: '**' # only trigger on branches, not on tags
6
-
7
- jobs:
8
- bump:
1
+ name: bump version
2
+
3
+ on:
4
+ push:
5
+ branches: '**' # only trigger on branches, not on tags
6
+
7
+ jobs:
8
+ bump:
9
9
  uses: esportsplus/typescript/.github/workflows/bump.yml@main
@@ -1,12 +1,12 @@
1
- name: dependabot automerge
2
-
3
- on:
4
- pull_request:
5
- types: [opened, synchronize, labeled]
6
- workflow_dispatch:
7
-
8
- jobs:
9
- automerge:
10
- secrets:
11
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
1
+ name: dependabot automerge
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize, labeled]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ automerge:
10
+ secrets:
11
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
12
12
  uses: esportsplus/typescript/.github/workflows/dependabot.yml@main
@@ -1,16 +1,16 @@
1
- name: publish to npm
2
-
3
- on:
4
- release:
5
- types: [published]
6
- workflow_dispatch:
7
- workflow_run:
8
- workflows: [bump version]
9
- types:
10
- - completed
11
-
12
- jobs:
13
- publish:
14
- secrets:
15
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
16
- uses: esportsplus/typescript/.github/workflows/publish.yml@main
1
+ name: publish to npm
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+ workflow_run:
8
+ workflows: [bump version]
9
+ types:
10
+ - completed
11
+
12
+ jobs:
13
+ publish:
14
+ secrets:
15
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
16
+ uses: esportsplus/typescript/.github/workflows/publish.yml@main
package/README.md CHANGED
@@ -1,217 +1,217 @@
1
- # @esportsplus/routing
2
-
3
- Type-safe client-side router with radix tree matching, middleware pipelines, and reactive navigation.
4
-
5
- ## Install
6
-
7
- ```bash
8
- pnpm add @esportsplus/routing
9
- ```
10
-
11
- ## Features
12
-
13
- - Type-safe route names and path parameters
14
- - Radix tree matching (static > params > wildcards)
15
- - Composable middleware pipeline
16
- - Reactive navigation via `@esportsplus/reactivity`
17
- - Named routes with URI generation
18
- - Route factories for modular definitions
19
- - Subdomain routing
20
- - HTTP method routing (GET, POST, PUT, DELETE)
21
-
22
- ## Usage
23
-
24
- ### Define Routes
25
-
26
- ```typescript
27
- import { router, Middleware, Next, Request, Route, RouteFactory } from '@esportsplus/routing/client';
28
-
29
- type Response = HTMLElement;
30
-
31
- // Route factory for modular definitions
32
- const homeRoutes: RouteFactory<Response> = (r) => r
33
- .get({
34
- name: 'home',
35
- path: '/',
36
- responder: (req) => renderHome()
37
- })
38
- .get({
39
- name: 'about',
40
- path: '/about',
41
- responder: (req) => renderAbout()
42
- });
43
-
44
- const userRoutes: RouteFactory<Response> = (r) => r
45
- .get({
46
- name: 'user',
47
- path: '/users/:id',
48
- responder: (req) => renderUser(req.data.parameters?.id)
49
- })
50
- .get({
51
- name: 'user.settings',
52
- path: '/users/:id/settings',
53
- middleware: [authMiddleware],
54
- responder: (req) => renderSettings(req.data.parameters?.id)
55
- });
56
- ```
57
-
58
- ### Create Router
59
-
60
- ```typescript
61
- // Compose route factories
62
- const app = router(homeRoutes, userRoutes);
63
-
64
- // Navigate
65
- app.redirect('home');
66
- app.redirect('user', { id: 123 });
67
-
68
- // Generate URIs
69
- app.uri('user', { id: 456 }); // '#/users/456'
70
-
71
- // History navigation
72
- app.back();
73
- app.forward();
74
- ```
75
-
76
- ### Middleware
77
-
78
- ```typescript
79
- const authMiddleware: Middleware<Response> = (req, next) => {
80
- if (!isAuthenticated()) {
81
- return renderLogin();
82
- }
83
- return next(req);
84
- };
85
-
86
- const loggerMiddleware: Middleware<Response> = (req, next) => {
87
- console.log(`${req.method} ${req.path}`);
88
- return next(req);
89
- };
90
-
91
- // Apply global middleware and dispatch
92
- app.middleware(loggerMiddleware).dispatch;
93
- ```
94
-
95
- ### Reactive Matching
96
-
97
- ```typescript
98
- // Create fallback route
99
- const notFound: Route<Response> = {
100
- name: 'not-found',
101
- path: null,
102
- pipeline: pipeline<Request<Response>, Response>(),
103
- subdomain: null
104
- };
105
-
106
- // Middleware that reactively matches routes
107
- const matchMiddleware = app.middleware.match(notFound);
108
-
109
- // Compose and dispatch
110
- app.middleware(matchMiddleware, loggerMiddleware).dispatch;
111
- ```
112
-
113
- ### Route Groups
114
-
115
- ```typescript
116
- const apiRoutes: RouteFactory<Response> = (r) => r
117
- .group({
118
- path: '/api/v1',
119
- middleware: [apiAuth]
120
- })
121
- .routes((r) => r
122
- .get({
123
- name: 'api.users',
124
- path: '/users',
125
- responder: handleUsers
126
- })
127
- .post({
128
- name: 'api.users.create',
129
- path: '/users',
130
- responder: handleCreateUser
131
- })
132
- );
133
- ```
134
-
135
- ### Path Parameters
136
-
137
- ```typescript
138
- // Required parameter
139
- .get({ name: 'user', path: '/users/:id', responder })
140
-
141
- // Optional parameter (prefix with ?)
142
- .get({ name: 'archive', path: '/posts/?:year/?:month', responder })
143
-
144
- // Wildcard (captures rest of path)
145
- .get({ name: 'files', path: '/files/*:path', responder })
146
- ```
147
-
148
- ### Subdomain Routing
149
-
150
- ```typescript
151
- const adminRoutes: RouteFactory<Response> = (r) => r
152
- .get({
153
- name: 'admin.dashboard',
154
- path: '/dashboard',
155
- subdomain: 'admin',
156
- responder: renderAdminDashboard
157
- });
158
- ```
159
-
160
- ## Types
161
-
162
- ```typescript
163
- // Route factory function
164
- type RouteFactory<T> = (router: Router<T, any>) => Router<T, RouteRegistry>;
165
-
166
- // Middleware function
167
- type Middleware<T> = (input: Request<T>, next: Next<T>) => T;
168
-
169
- // Next function in middleware chain
170
- type Next<T> = (input: Request<T>) => T;
171
-
172
- // Request object
173
- type Request<T> = {
174
- data: Record<PropertyKey, unknown> & { parameters?: Record<string, unknown>; route?: Route<T> };
175
- hostname: string;
176
- href: string;
177
- method: string;
178
- origin: string;
179
- path: string;
180
- port: string;
181
- protocol: string;
182
- query: Record<string, unknown>;
183
- subdomain?: string;
184
- };
185
-
186
- // Route definition
187
- type Route<T> = {
188
- name: string | null;
189
- path: string | null;
190
- pipeline: Pipeline<Request<T>, T>;
191
- subdomain: string | null;
192
- };
193
- ```
194
-
195
- ## Route Matching Priority
196
-
197
- 1. **Static paths** - exact match (`/users`)
198
- 2. **Parameters** - dynamic segments (`/users/:id`)
199
- 3. **Wildcards** - catch-all (`/files/*:path`)
200
-
201
- Static paths always take precedence over parameterized paths for the same position.
202
-
203
- ## Hash-Based Navigation
204
-
205
- Routes use hash-based URLs (`#/path`) for client-side navigation without server configuration.
206
-
207
- ```typescript
208
- // URL: https://example.com/#/users/123?tab=profile
209
-
210
- request.path // '/users/123'
211
- request.query // { tab: 'profile' }
212
- request.hostname // 'example.com'
213
- ```
214
-
215
- ## License
216
-
217
- MIT
1
+ # @esportsplus/routing
2
+
3
+ Type-safe client-side router with radix tree matching, middleware pipelines, and reactive navigation.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @esportsplus/routing
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - Type-safe route names and path parameters
14
+ - Radix tree matching (static > params > wildcards)
15
+ - Composable middleware pipeline
16
+ - Reactive navigation via `@esportsplus/reactivity`
17
+ - Named routes with URI generation
18
+ - Route factories for modular definitions
19
+ - Subdomain routing
20
+ - HTTP method routing (GET, POST, PUT, DELETE)
21
+
22
+ ## Usage
23
+
24
+ ### Define Routes
25
+
26
+ ```typescript
27
+ import { router, Middleware, Next, Request, Route, RouteFactory } from '@esportsplus/routing/client';
28
+
29
+ type Response = HTMLElement;
30
+
31
+ // Route factory for modular definitions
32
+ const homeRoutes: RouteFactory<Response> = (r) => r
33
+ .get({
34
+ name: 'home',
35
+ path: '/',
36
+ responder: (req) => renderHome()
37
+ })
38
+ .get({
39
+ name: 'about',
40
+ path: '/about',
41
+ responder: (req) => renderAbout()
42
+ });
43
+
44
+ const userRoutes: RouteFactory<Response> = (r) => r
45
+ .get({
46
+ name: 'user',
47
+ path: '/users/:id',
48
+ responder: (req) => renderUser(req.data.parameters?.id)
49
+ })
50
+ .get({
51
+ name: 'user.settings',
52
+ path: '/users/:id/settings',
53
+ middleware: [authMiddleware],
54
+ responder: (req) => renderSettings(req.data.parameters?.id)
55
+ });
56
+ ```
57
+
58
+ ### Create Router
59
+
60
+ ```typescript
61
+ // Compose route factories
62
+ const app = router(homeRoutes, userRoutes);
63
+
64
+ // Navigate
65
+ app.redirect('home');
66
+ app.redirect('user', { id: 123 });
67
+
68
+ // Generate URIs
69
+ app.uri('user', { id: 456 }); // '#/users/456'
70
+
71
+ // History navigation
72
+ app.back();
73
+ app.forward();
74
+ ```
75
+
76
+ ### Middleware
77
+
78
+ ```typescript
79
+ const authMiddleware: Middleware<Response> = (req, next) => {
80
+ if (!isAuthenticated()) {
81
+ return renderLogin();
82
+ }
83
+ return next(req);
84
+ };
85
+
86
+ const loggerMiddleware: Middleware<Response> = (req, next) => {
87
+ console.log(`${req.method} ${req.path}`);
88
+ return next(req);
89
+ };
90
+
91
+ // Apply global middleware and dispatch
92
+ app.middleware(loggerMiddleware).dispatch;
93
+ ```
94
+
95
+ ### Reactive Matching
96
+
97
+ ```typescript
98
+ // Create fallback route
99
+ const notFound: Route<Response> = {
100
+ name: 'not-found',
101
+ path: null,
102
+ pipeline: pipeline<Request<Response>, Response>(),
103
+ subdomain: null
104
+ };
105
+
106
+ // Middleware that reactively matches routes
107
+ const matchMiddleware = app.middleware.match(notFound);
108
+
109
+ // Compose and dispatch
110
+ app.middleware(matchMiddleware, loggerMiddleware).dispatch;
111
+ ```
112
+
113
+ ### Route Groups
114
+
115
+ ```typescript
116
+ const apiRoutes: RouteFactory<Response> = (r) => r
117
+ .group({
118
+ path: '/api/v1',
119
+ middleware: [apiAuth]
120
+ })
121
+ .routes((r) => r
122
+ .get({
123
+ name: 'api.users',
124
+ path: '/users',
125
+ responder: handleUsers
126
+ })
127
+ .post({
128
+ name: 'api.users.create',
129
+ path: '/users',
130
+ responder: handleCreateUser
131
+ })
132
+ );
133
+ ```
134
+
135
+ ### Path Parameters
136
+
137
+ ```typescript
138
+ // Required parameter
139
+ .get({ name: 'user', path: '/users/:id', responder })
140
+
141
+ // Optional parameter (prefix with ?)
142
+ .get({ name: 'archive', path: '/posts/?:year/?:month', responder })
143
+
144
+ // Wildcard (captures rest of path)
145
+ .get({ name: 'files', path: '/files/*:path', responder })
146
+ ```
147
+
148
+ ### Subdomain Routing
149
+
150
+ ```typescript
151
+ const adminRoutes: RouteFactory<Response> = (r) => r
152
+ .get({
153
+ name: 'admin.dashboard',
154
+ path: '/dashboard',
155
+ subdomain: 'admin',
156
+ responder: renderAdminDashboard
157
+ });
158
+ ```
159
+
160
+ ## Types
161
+
162
+ ```typescript
163
+ // Route factory function
164
+ type RouteFactory<T> = (router: Router<T, any>) => Router<T, RouteRegistry>;
165
+
166
+ // Middleware function
167
+ type Middleware<T> = (input: Request<T>, next: Next<T>) => T;
168
+
169
+ // Next function in middleware chain
170
+ type Next<T> = (input: Request<T>) => T;
171
+
172
+ // Request object
173
+ type Request<T> = {
174
+ data: Record<PropertyKey, unknown> & { parameters?: Record<string, unknown>; route?: Route<T> };
175
+ hostname: string;
176
+ href: string;
177
+ method: string;
178
+ origin: string;
179
+ path: string;
180
+ port: string;
181
+ protocol: string;
182
+ query: Record<string, unknown>;
183
+ subdomain?: string;
184
+ };
185
+
186
+ // Route definition
187
+ type Route<T> = {
188
+ name: string | null;
189
+ path: string | null;
190
+ pipeline: Pipeline<Request<T>, T>;
191
+ subdomain: string | null;
192
+ };
193
+ ```
194
+
195
+ ## Route Matching Priority
196
+
197
+ 1. **Static paths** - exact match (`/users`)
198
+ 2. **Parameters** - dynamic segments (`/users/:id`)
199
+ 3. **Wildcards** - catch-all (`/files/*:path`)
200
+
201
+ Static paths always take precedence over parameterized paths for the same position.
202
+
203
+ ## Hash-Based Navigation
204
+
205
+ Routes use hash-based URLs (`#/path`) for client-side navigation without server configuration.
206
+
207
+ ```typescript
208
+ // URL: https://example.com/#/users/123?tab=profile
209
+
210
+ request.path // '/users/123'
211
+ request.query // { tab: 'profile' }
212
+ request.hostname // 'example.com'
213
+ ```
214
+
215
+ ## License
216
+
217
+ MIT
@@ -14,4 +14,4 @@ declare const router: <const Factories extends readonly RouteFactory<any>[]>(...
14
14
  uri: <RouteName extends keyof AccumulateRoutes<Factories>>(name: RouteName, ...values: ExtractRequiredParams<RoutePath<AccumulateRoutes<Factories>, RouteName>> extends never ? ExtractOptionalParams<RoutePath<AccumulateRoutes<Factories>, RouteName>> extends never ? [] : [params?: PathParamsObject<RoutePath<AccumulateRoutes<Factories>, RouteName>>] : [params: PathParamsObject<RoutePath<AccumulateRoutes<Factories>, RouteName>>]) => string;
15
15
  };
16
16
  export { router };
17
- export type { Middleware, Next, Request, Route, RouteFactory } from './types.js';
17
+ export type { Middleware, Next, Request, Route, Router, RouteFactory } from './types.js';
@@ -1,27 +1,27 @@
1
- import * as reactivity_fa31f85817714b2e94bba1bba5e367420 from '@esportsplus/reactivity';
1
+ import * as reactivity_14d67e3b96364d0fa5456fd677e2fba30 from '@esportsplus/reactivity';
2
2
  import { effect, root } from '@esportsplus/reactivity';
3
3
  import { Router } from './router/index.js';
4
4
  import pipeline from '@esportsplus/pipeline';
5
5
  import { PACKAGE_NAME } from './constants.js';
6
- class ReactiveObject_fa31f85817714b2e94bba1bba5e367421 extends reactivity_fa31f85817714b2e94bba1bba5e367420.ReactiveObject {
6
+ class ReactiveObject_14d67e3b96364d0fa5456fd677e2fba31 extends reactivity_14d67e3b96364d0fa5456fd677e2fba30.ReactiveObject {
7
7
  #parameters;
8
8
  #route;
9
9
  constructor(_p0, _p1) {
10
10
  super(null);
11
- this.#parameters = this[reactivity_fa31f85817714b2e94bba1bba5e367420.SIGNAL](_p0);
12
- this.#route = this[reactivity_fa31f85817714b2e94bba1bba5e367420.SIGNAL](_p1);
11
+ this.#parameters = this[reactivity_14d67e3b96364d0fa5456fd677e2fba30.SIGNAL](_p0);
12
+ this.#route = this[reactivity_14d67e3b96364d0fa5456fd677e2fba30.SIGNAL](_p1);
13
13
  }
14
14
  get parameters() {
15
- return reactivity_fa31f85817714b2e94bba1bba5e367420.read(this.#parameters);
15
+ return reactivity_14d67e3b96364d0fa5456fd677e2fba30.read(this.#parameters);
16
16
  }
17
17
  set parameters(_v0) {
18
- reactivity_fa31f85817714b2e94bba1bba5e367420.write(this.#parameters, _v0);
18
+ reactivity_14d67e3b96364d0fa5456fd677e2fba30.write(this.#parameters, _v0);
19
19
  }
20
20
  get route() {
21
- return reactivity_fa31f85817714b2e94bba1bba5e367420.read(this.#route);
21
+ return reactivity_14d67e3b96364d0fa5456fd677e2fba30.read(this.#route);
22
22
  }
23
23
  set route(_v1) {
24
- reactivity_fa31f85817714b2e94bba1bba5e367420.write(this.#route, _v1);
24
+ reactivity_14d67e3b96364d0fa5456fd677e2fba30.write(this.#route, _v1);
25
25
  }
26
26
  }
27
27
  let cache = [], location = window.location;
@@ -80,7 +80,7 @@ function middleware(request, router) {
80
80
  return route.pipeline.dispatch(request);
81
81
  };
82
82
  host.match = (fallback) => {
83
- let state = new ReactiveObject_fa31f85817714b2e94bba1bba5e367421(undefined, undefined);
83
+ let state = new ReactiveObject_14d67e3b96364d0fa5456fd677e2fba31(undefined, undefined);
84
84
  if (fallback === undefined) {
85
85
  throw new Error(`${PACKAGE_NAME}: fallback route does not exist`);
86
86
  }
@@ -120,7 +120,7 @@ function onpopstate() {
120
120
  }
121
121
  }
122
122
  const router = (...factories) => {
123
- let instance = factories.reduce((r, factory) => factory(r), new Router()), request = reactivity_fa31f85817714b2e94bba1bba5e367420.reactive(Object.assign(href(), { data: {} }));
123
+ let instance = factories.reduce((r, factory) => factory(r), new Router()), request = reactivity_14d67e3b96364d0fa5456fd677e2fba30.reactive(Object.assign(href(), { data: {} }));
124
124
  if (cache.push(request) === 1) {
125
125
  window.addEventListener('hashchange', onpopstate);
126
126
  }
package/package.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "author": "ICJR",
3
3
  "dependencies": {
4
4
  "@esportsplus/pipeline": "^1.2.2",
5
- "@esportsplus/reactivity": "file:/../reactivity",
6
- "@esportsplus/typescript": "file:/../typescript",
5
+ "@esportsplus/reactivity": "^0.29.20",
6
+ "@esportsplus/typescript": "^0.28.2",
7
7
  "@esportsplus/utilities": "^0.27.2"
8
8
  },
9
9
  "exports": {
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "type": "module",
31
31
  "types": "./build/index.d.ts",
32
- "version": "0.6.0",
32
+ "version": "0.6.2",
33
33
  "scripts": {
34
34
  "build": "tsc",
35
35
  "build:test": "vite build --config test/vite.config.ts",