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 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
+ ![Social Card of stx](https://github.com/stacksjs/stx/blob/main/.github/art/cover.jpg)
2
+
3
+ [![npm version][npm-version-src]][npm-version-href]
4
+ [![GitHub Actions][github-actions-src]][github-actions-href]
5
+ [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](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
@@ -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,2 @@
1
+ import type { Route } from './types';
2
+ export declare function generateRouteTypes(routes: Route[], outputDir: string): void;
@@ -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
+ }
@@ -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';