stx-router 0.2.8
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/LICENSE.md +21 -0
- package/README.md +233 -0
- package/dist/client.d.ts +19 -0
- package/dist/codegen.d.ts +2 -0
- package/dist/file-router.d.ts +12 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +887 -0
- package/dist/index.js.map +16 -0
- package/dist/matcher.d.ts +3 -0
- package/dist/middleware.d.ts +28 -0
- package/dist/named-routes.d.ts +10 -0
- package/dist/nested-layouts.d.ts +1 -0
- package/dist/types.d.ts +97 -0
- package/package.json +50 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Stacks.js
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
[![npm version][npm-version-src]][npm-version-href]
|
|
4
|
+
[![GitHub Actions][github-actions-src]][github-actions-href]
|
|
5
|
+
[](http://commitizen.github.io/cz-cli/)
|
|
6
|
+
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
7
|
+
|
|
8
|
+
# stx-router
|
|
9
|
+
|
|
10
|
+
File-based router for STX — `.stx` template discovery, nested layouts, typed route params, middleware, and client-side SPA navigation.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun add stx-router
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- **File-based routing** — scans `pages/` for `.stx` files and builds a route table automatically
|
|
21
|
+
- **Dynamic params** — `[id].stx` becomes `:id`, `[[slug]].stx` becomes optional, `[...path].stx` becomes catch-all
|
|
22
|
+
- **Nested layouts** — `_layout.stx` files are resolved up the directory tree
|
|
23
|
+
- **Named routes** — Laravel-style `route('users.show', { id: 1 })` URL generation
|
|
24
|
+
- **Middleware** — Nuxt-like navigation middleware with `navigateTo` / `abortNavigation`
|
|
25
|
+
- **Client SPA navigation** — built-in script with View Transitions API, prefetching, and cache
|
|
26
|
+
- **Type generation** — auto-generates `.stx/route-types.d.ts` from discovered routes
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### File-Based Router
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { createRouter } from 'stx-router'
|
|
34
|
+
|
|
35
|
+
// Scan pages/ directory and build route table
|
|
36
|
+
const router = createRouter('./my-app', {
|
|
37
|
+
pagesDir: 'pages',
|
|
38
|
+
extensions: ['.stx'],
|
|
39
|
+
layouts: true,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Match a URL
|
|
43
|
+
const match = router.match('/blog/hello-world')
|
|
44
|
+
// => { route: { pattern: '/blog/:slug', filePath: 'pages/blog/[slug].stx', ... }, params: { slug: 'hello-world' } }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Route Matching
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { filePathToPattern, patternToRegex, matchRoute } from 'stx-router'
|
|
51
|
+
|
|
52
|
+
// Convert file path to URL pattern
|
|
53
|
+
filePathToPattern('pages/blog/[slug].stx', 'pages')
|
|
54
|
+
// => '/blog/:slug'
|
|
55
|
+
|
|
56
|
+
// Convert pattern to regex
|
|
57
|
+
const { regex, params } = patternToRegex('/blog/:slug')
|
|
58
|
+
|
|
59
|
+
// Match against routes
|
|
60
|
+
const result = matchRoute('/blog/hello', routes)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Named Routes
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { defineRoutes, route } from 'stx-router'
|
|
67
|
+
|
|
68
|
+
defineRoutes({
|
|
69
|
+
'home': '/',
|
|
70
|
+
'users.show': '/users/:id',
|
|
71
|
+
'posts.index': '/blog',
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
route('users.show', { id: 42 })
|
|
75
|
+
// => '/users/42'
|
|
76
|
+
|
|
77
|
+
route('users.show', { id: 42, tab: 'posts' })
|
|
78
|
+
// => '/users/42?tab=posts'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Middleware
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { defineMiddleware, navigateTo, abortNavigation } from 'stx-router'
|
|
85
|
+
|
|
86
|
+
// Auth guard
|
|
87
|
+
const authMiddleware = defineMiddleware(
|
|
88
|
+
(context) => {
|
|
89
|
+
const token = context.cookies.get('auth_token')
|
|
90
|
+
if (!token) {
|
|
91
|
+
return navigateTo('/login', { redirectCode: 302 })
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{ mode: 'server' },
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
// Role check
|
|
98
|
+
const adminMiddleware = defineMiddleware((context) => {
|
|
99
|
+
if (!context.state.isAdmin) {
|
|
100
|
+
return abortNavigation({ statusCode: 403, message: 'Forbidden' })
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Middleware Registry
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import {
|
|
109
|
+
registerMiddleware,
|
|
110
|
+
runMiddleware,
|
|
111
|
+
createMiddlewareContext,
|
|
112
|
+
createRouteLocation,
|
|
113
|
+
loadMiddlewareFromDirectory,
|
|
114
|
+
} from 'stx-router'
|
|
115
|
+
|
|
116
|
+
// Register middleware
|
|
117
|
+
registerMiddleware('auth', authMiddleware)
|
|
118
|
+
|
|
119
|
+
// Auto-discover middleware from directory
|
|
120
|
+
await loadMiddlewareFromDirectory('./my-app', 'middleware')
|
|
121
|
+
|
|
122
|
+
// Run middleware pipeline
|
|
123
|
+
const to = createRouteLocation('/dashboard', {}, {})
|
|
124
|
+
const context = createMiddlewareContext(to, null, request)
|
|
125
|
+
const result = await runMiddleware(['auth', 'admin'], context)
|
|
126
|
+
|
|
127
|
+
if (!result.passed) {
|
|
128
|
+
// Handle redirect or abort
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Nested Layouts
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { resolveLayoutChain } from 'stx-router'
|
|
136
|
+
|
|
137
|
+
// Given:
|
|
138
|
+
// pages/_layout.stx (root layout)
|
|
139
|
+
// pages/admin/_layout.stx (admin layout)
|
|
140
|
+
// pages/admin/users.stx
|
|
141
|
+
|
|
142
|
+
resolveLayoutChain('pages/admin/users.stx', 'pages')
|
|
143
|
+
// => ['pages/_layout.stx', 'pages/admin/_layout.stx']
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Client-Side SPA Navigation
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { getRouterScript } from 'stx-router'
|
|
150
|
+
|
|
151
|
+
// Get the client-side SPA router script
|
|
152
|
+
const script = getRouterScript()
|
|
153
|
+
|
|
154
|
+
// Inject into HTML — provides:
|
|
155
|
+
// - Click interception for internal links
|
|
156
|
+
// - History API navigation with popstate
|
|
157
|
+
// - View Transitions API support
|
|
158
|
+
// - Page prefetching on hover
|
|
159
|
+
// - Response caching
|
|
160
|
+
// - Active link class management
|
|
161
|
+
// - Loading indicator
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Route Type Generation
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { generateRouteTypes } from 'stx-router'
|
|
168
|
+
|
|
169
|
+
// Generate TypeScript declarations from routes
|
|
170
|
+
generateRouteTypes(routes, '.stx')
|
|
171
|
+
// => writes .stx/route-types.d.ts
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Error Pages
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { findErrorPage } from 'stx-router'
|
|
178
|
+
|
|
179
|
+
// Find error page template
|
|
180
|
+
findErrorPage('pages', 404)
|
|
181
|
+
// => 'pages/404.stx' (or 'pages/error.stx' as fallback)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## API
|
|
185
|
+
|
|
186
|
+
| Export | Category | Description |
|
|
187
|
+
| ------ | -------- | ----------- |
|
|
188
|
+
| `Router` | File Router | Class that scans pages directory and builds route table |
|
|
189
|
+
| `createRouter(baseDir, config?)` | File Router | Factory for `Router` |
|
|
190
|
+
| `findErrorPage(pagesDir, statusCode)` | File Router | Find error template (404.stx, error.stx) |
|
|
191
|
+
| `formatRoutes(routes, baseDir)` | File Router | Pretty-print route table |
|
|
192
|
+
| `filePathToPattern(filePath, pagesDir)` | Pattern Matching | Convert file path to URL pattern |
|
|
193
|
+
| `patternToRegex(pattern)` | Pattern Matching | Convert URL pattern to regex with named params |
|
|
194
|
+
| `matchRoute(pathname, routes)` | Pattern Matching | Match URL against route table |
|
|
195
|
+
| `defineRoute(name, path, params?)` | Named Routes | Register a named route |
|
|
196
|
+
| `defineRoutes(definitions)` | Named Routes | Register multiple named routes |
|
|
197
|
+
| `route(name, params?, absolute?)` | Named Routes | Generate URL from named route |
|
|
198
|
+
| `setAppUrl(url)` | Named Routes | Set base URL for absolute route generation |
|
|
199
|
+
| `resetRoutes()` | Named Routes | Clear all registered routes |
|
|
200
|
+
| `defineMiddleware(handler, options?)` | Middleware | Create middleware definition |
|
|
201
|
+
| `registerMiddleware(name, middleware)` | Middleware | Add to global registry |
|
|
202
|
+
| `getMiddleware(name)` | Middleware | Get middleware by name |
|
|
203
|
+
| `hasMiddleware(name)` | Middleware | Check if middleware exists |
|
|
204
|
+
| `clearMiddleware()` | Middleware | Clear registry |
|
|
205
|
+
| `getMiddlewareNames()` | Middleware | List registered names |
|
|
206
|
+
| `loadMiddlewareFromDirectory(baseDir, dir?)` | Middleware | Auto-discover middleware files |
|
|
207
|
+
| `runMiddleware(names, context)` | Middleware | Execute middleware pipeline |
|
|
208
|
+
| `createMiddlewareContext(to, from, request?)` | Middleware | Build middleware context |
|
|
209
|
+
| `createRouteLocation(pathname, params, meta, search?)` | Middleware | Build route location |
|
|
210
|
+
| `navigateTo(path, options?)` | Middleware | Create redirect result |
|
|
211
|
+
| `abortNavigation(error)` | Middleware | Create abort result |
|
|
212
|
+
| `createServerCookieManager(request, headers)` | Middleware | Server-side cookie manager |
|
|
213
|
+
| `createClientCookieManager()` | Middleware | Client-side cookie manager |
|
|
214
|
+
| `createStorageManager()` | Middleware | localStorage wrapper |
|
|
215
|
+
| `resolveLayoutChain(routeFilePath, pagesDir)` | Layouts | Collect `_layout.stx` files up directory tree |
|
|
216
|
+
| `generateRouteTypes(routes, outputDir)` | Codegen | Write `.stx/route-types.d.ts` |
|
|
217
|
+
| `getRouterScript()` | Client | Get client-side SPA navigation script |
|
|
218
|
+
|
|
219
|
+
## Documentation
|
|
220
|
+
|
|
221
|
+
- [Full Documentation](https://stx.sh)
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
MIT
|
|
226
|
+
|
|
227
|
+
<!-- Badges -->
|
|
228
|
+
[npm-version-src]: https://img.shields.io/npm/v/stx-router?style=flat-square
|
|
229
|
+
[npm-version-href]: https://npmjs.com/package/stx-router
|
|
230
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/stx-router?style=flat-square
|
|
231
|
+
[npm-downloads-href]: https://npmjs.com/package/stx-router
|
|
232
|
+
[github-actions-src]: https://img.shields.io/github/actions/workflow/status/stacksjs/stx/ci.yml?style=flat-square&branch=main
|
|
233
|
+
[github-actions-href]: https://github.com/stacksjs/stx/actions?query=workflow%3Aci
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STX Router - Canonical SPA Router
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for client-side navigation.
|
|
5
|
+
* Injected via @stxRouter directive or auto-loaded with the signals runtime.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Click interception for internal links
|
|
9
|
+
* - History API pushState navigation
|
|
10
|
+
* - View Transitions API with CSS fade fallback
|
|
11
|
+
* - <head> style swapping (prevents unstyled flash)
|
|
12
|
+
* - Smart script filtering (skip signals runtime, layout guards)
|
|
13
|
+
* - Page prefetching on hover
|
|
14
|
+
* - Response caching (5-minute TTL)
|
|
15
|
+
* - Active link class management (data-stx-link)
|
|
16
|
+
* - Loading indicator bar
|
|
17
|
+
* - Configurable via window.STX_ROUTER_OPTIONS or window.__stxRouterConfig
|
|
18
|
+
*/
|
|
19
|
+
export declare function getRouterScript(): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Route, RouteMatch, RouterConfig } from './types';
|
|
2
|
+
export declare function createRouter(baseDir: string, config?: RouterConfig): Router;
|
|
3
|
+
export declare function findErrorPage(pagesDir: string, statusCode: number): string | null;
|
|
4
|
+
export declare function formatRoutes(routes: Route[], baseDir: string): string[];
|
|
5
|
+
export declare class Router {
|
|
6
|
+
routes: Route[];
|
|
7
|
+
constructor(baseDir: string, config?: RouterConfig);
|
|
8
|
+
match(pathname: string): RouteMatch | null;
|
|
9
|
+
resolve(name: string, params?: Record<string, string>): string;
|
|
10
|
+
addRoute(route: Route): void;
|
|
11
|
+
getLayout(route: Route): string | null;
|
|
12
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Types
|
|
2
|
+
export type {
|
|
3
|
+
AbortNavigationResult,
|
|
4
|
+
CookieManager,
|
|
5
|
+
CookieOptions,
|
|
6
|
+
MiddlewareContext,
|
|
7
|
+
MiddlewareMode,
|
|
8
|
+
MiddlewareOptions,
|
|
9
|
+
MiddlewareResult,
|
|
10
|
+
NavigateToOptions,
|
|
11
|
+
NavigateToResult,
|
|
12
|
+
NavigationError,
|
|
13
|
+
NavigationResult,
|
|
14
|
+
Route,
|
|
15
|
+
RouteLocation,
|
|
16
|
+
RouteMatch,
|
|
17
|
+
RouteMiddlewareDefinition,
|
|
18
|
+
RouteMiddlewareHandler,
|
|
19
|
+
RouterConfig,
|
|
20
|
+
StorageManager,
|
|
21
|
+
} from './types';
|
|
22
|
+
// File-based router
|
|
23
|
+
export { createRouter, findErrorPage, formatRoutes, Router } from './file-router';
|
|
24
|
+
// Route pattern matching
|
|
25
|
+
export { filePathToPattern, matchRoute, patternToRegex } from './matcher';
|
|
26
|
+
// Named routes
|
|
27
|
+
export { defineRoute, defineRoutes, resetRoutes, route, setAppUrl } from './named-routes';
|
|
28
|
+
// Middleware system
|
|
29
|
+
export {
|
|
30
|
+
abortNavigation,
|
|
31
|
+
clearMiddleware,
|
|
32
|
+
createClientCookieManager,
|
|
33
|
+
createMiddlewareContext,
|
|
34
|
+
createRouteLocation,
|
|
35
|
+
createServerCookieManager,
|
|
36
|
+
createStorageManager,
|
|
37
|
+
defineMiddleware,
|
|
38
|
+
getMiddleware,
|
|
39
|
+
getMiddlewareNames,
|
|
40
|
+
hasMiddleware,
|
|
41
|
+
loadMiddlewareFromDirectory,
|
|
42
|
+
navigateTo,
|
|
43
|
+
registerMiddleware,
|
|
44
|
+
runMiddleware,
|
|
45
|
+
} from './middleware';
|
|
46
|
+
// Nested layouts
|
|
47
|
+
export { resolveLayoutChain } from './nested-layouts';
|
|
48
|
+
// Route type generation
|
|
49
|
+
export { generateRouteTypes } from './codegen';
|
|
50
|
+
// Client-side SPA navigation
|
|
51
|
+
export { getRouterScript } from './client';
|