@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 +9 -9
- package/.gitattributes +2 -2
- package/.github/dependabot.yml +24 -24
- package/.github/workflows/bump.yml +8 -8
- package/.github/workflows/dependabot.yml +11 -11
- package/.github/workflows/publish.yml +16 -16
- package/README.md +217 -217
- package/build/client/index.d.ts +1 -1
- package/build/client/index.js +10 -10
- package/package.json +3 -3
- package/src/client/constants.ts +21 -21
- package/src/client/index.ts +195 -195
- package/src/client/router/index.ts +293 -293
- package/src/client/router/node.ts +121 -121
- package/src/client/types.ts +105 -105
- package/src/constants.ts +3 -3
- package/test/index.ts +648 -648
- package/tsconfig.json +8 -8
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
|
package/.github/dependabot.yml
CHANGED
|
@@ -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
|
package/build/client/index.d.ts
CHANGED
|
@@ -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';
|
package/build/client/index.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import * as
|
|
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
|
|
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[
|
|
12
|
-
this.#route = this[
|
|
11
|
+
this.#parameters = this[reactivity_14d67e3b96364d0fa5456fd677e2fba30.SIGNAL](_p0);
|
|
12
|
+
this.#route = this[reactivity_14d67e3b96364d0fa5456fd677e2fba30.SIGNAL](_p1);
|
|
13
13
|
}
|
|
14
14
|
get parameters() {
|
|
15
|
-
return
|
|
15
|
+
return reactivity_14d67e3b96364d0fa5456fd677e2fba30.read(this.#parameters);
|
|
16
16
|
}
|
|
17
17
|
set parameters(_v0) {
|
|
18
|
-
|
|
18
|
+
reactivity_14d67e3b96364d0fa5456fd677e2fba30.write(this.#parameters, _v0);
|
|
19
19
|
}
|
|
20
20
|
get route() {
|
|
21
|
-
return
|
|
21
|
+
return reactivity_14d67e3b96364d0fa5456fd677e2fba30.read(this.#route);
|
|
22
22
|
}
|
|
23
23
|
set route(_v1) {
|
|
24
|
-
|
|
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
|
|
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 =
|
|
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": "
|
|
6
|
-
"@esportsplus/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.
|
|
32
|
+
"version": "0.6.2",
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "tsc",
|
|
35
35
|
"build:test": "vite build --config test/vite.config.ts",
|