@pyreon/router 0.24.4 → 0.24.6

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/src/not-found.ts DELETED
@@ -1,75 +0,0 @@
1
- import type { ComponentFn, Props, VNodeChild } from '@pyreon/core'
2
- import { ErrorBoundary, h } from '@pyreon/core'
3
-
4
- // ─── NotFound symbol + throw ────────────────────────────────────────────────
5
-
6
- const NOT_FOUND = Symbol.for('pyreon.notFound')
7
-
8
- /**
9
- * Throw inside a route loader or component to trigger the nearest
10
- * NotFoundBoundary. Inspired by Next.js's `notFound()`.
11
- *
12
- * @example
13
- * ```ts
14
- * // In a loader:
15
- * loader: async ({ params }) => {
16
- * const user = await fetchUser(params.id)
17
- * if (!user) notFound()
18
- * return user
19
- * }
20
- * ```
21
- */
22
- export function notFound(message?: string): never {
23
- const err = new Error(message ?? 'Not Found')
24
- ;(err as unknown as Record<symbol, unknown>)[NOT_FOUND] = true
25
- throw err
26
- }
27
-
28
- /** Check if an error is a NotFoundError thrown by `notFound()`. */
29
- export function isNotFoundError(err: unknown): boolean {
30
- return (
31
- typeof err === 'object' &&
32
- err !== null &&
33
- (err as Record<string | symbol, unknown>)[NOT_FOUND] === true
34
- )
35
- }
36
-
37
- // ─── NotFoundBoundary ──────────────────────────────────────────────────────
38
-
39
- export interface NotFoundBoundaryProps extends Props {
40
- /** Component or VNode to render when notFound() is thrown */
41
- fallback: ComponentFn | VNodeChild
42
- children?: VNodeChild
43
- }
44
-
45
- /**
46
- * Catches `notFound()` errors from child route components or loaders
47
- * and renders the fallback. Wraps Pyreon's ErrorBoundary with notFound
48
- * detection — non-notFound errors propagate to parent error boundaries.
49
- *
50
- * @example
51
- * ```tsx
52
- * <NotFoundBoundary fallback={<NotFoundPage />}>
53
- * <RouterView />
54
- * </NotFoundBoundary>
55
- * ```
56
- */
57
- export const NotFoundBoundary: ComponentFn<NotFoundBoundaryProps> = (props) => {
58
- return h(
59
- ErrorBoundary,
60
- {
61
- fallback: (err: unknown, reset: () => void) => {
62
- if (!isNotFoundError(err)) {
63
- // Re-throw non-notFound errors so they propagate
64
- throw err
65
- }
66
- const fb = props.fallback
67
- if (typeof fb === 'function' && fb.length <= 1) {
68
- return h(fb as ComponentFn, { error: err, reset })
69
- }
70
- return fb as VNodeChild
71
- },
72
- },
73
- props.children,
74
- )
75
- }
package/src/redirect.ts DELETED
@@ -1,63 +0,0 @@
1
- // ─── Redirect symbol + throw ────────────────────────────────────────────────
2
-
3
- const REDIRECT = Symbol.for('pyreon.redirect')
4
-
5
- /** Standard redirect status codes. 307/308 preserve the request method, 302/303 don't. */
6
- export type RedirectStatus = 301 | 302 | 303 | 307 | 308
7
-
8
- interface RedirectInfo {
9
- url: string
10
- status: RedirectStatus
11
- }
12
-
13
- /**
14
- * Throw inside a route loader to redirect the navigation server-side
15
- * (during SSR returns a 302/307 `Location:` response) and client-side
16
- * (during CSR triggers `router.replace()` before the layout renders).
17
- *
18
- * The auth-gate use case: replaces the fragile `onMount + router.push()`
19
- * workaround. `onMount` doesn't fire reliably under nested-layout dev SSR +
20
- * hydration — so the layout renders briefly before the push happens, leaking
21
- * authenticated UI to unauthenticated users. `redirect()` runs in the loader
22
- * BEFORE the layout's component is invoked, so the unauthenticated UI never
23
- * mounts in the first place.
24
- *
25
- * @example
26
- * ```ts
27
- * // src/routes/app/_layout.tsx
28
- * export const loader = async ({ request }) => {
29
- * const session = await getSession(request)
30
- * if (!session) redirect('/login')
31
- * return { user: session.user }
32
- * }
33
- * ```
34
- *
35
- * @param url - Target URL (typically a path like `/login` or absolute URL for cross-origin).
36
- * @param status - HTTP redirect status. Default `307` (Temporary Redirect, method-preserving).
37
- * Use `301`/`308` for permanent moves, `302`/`303` to force GET on the target.
38
- */
39
- export function redirect(url: string, status: RedirectStatus = 307): never {
40
- const err = new Error(`Redirect to ${url}`)
41
- ;(err as unknown as Record<symbol, RedirectInfo>)[REDIRECT] = { url, status }
42
- throw err
43
- }
44
-
45
- /** Check if an error is a RedirectError thrown by `redirect()`. */
46
- export function isRedirectError(err: unknown): boolean {
47
- return (
48
- typeof err === 'object' &&
49
- err !== null &&
50
- typeof (err as Record<symbol, unknown>)[REDIRECT] === 'object'
51
- )
52
- }
53
-
54
- /**
55
- * Extract the redirect URL and status from a thrown RedirectError. Returns
56
- * `null` if `err` isn't a RedirectError. Used by the router's loader-runner
57
- * (CSR) and the SSR handler to convert the thrown error into the right kind
58
- * of response (a `router.replace()` call or a `302`/`307` Response).
59
- */
60
- export function getRedirectInfo(err: unknown): RedirectInfo | null {
61
- if (!isRedirectError(err)) return null
62
- return (err as Record<symbol, RedirectInfo>)[REDIRECT] ?? null
63
- }