@ilha/router 0.1.1 → 0.2.1
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/README.md +389 -40
- package/dist/index.d.ts +128 -17
- package/dist/index.js +357 -62
- package/dist/vite.js +125 -13
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { HydratableOptions, Island } from "ilha";
|
|
|
4
4
|
interface RouteRecord {
|
|
5
5
|
pattern: string;
|
|
6
6
|
island: Island<any, any>;
|
|
7
|
+
/** Merged loader chain (layouts outer→inner, then page) — `undefined` if no loaders. */
|
|
8
|
+
loader?: Loader<any>;
|
|
7
9
|
}
|
|
8
10
|
interface RouteSnapshot {
|
|
9
11
|
path: string;
|
|
@@ -18,8 +20,55 @@ interface AppError {
|
|
|
18
20
|
}
|
|
19
21
|
type LayoutHandler = (children: Island<any, any>) => Island<any, any>;
|
|
20
22
|
type ErrorHandler = (error: AppError, route: RouteSnapshot) => Island<any, any>;
|
|
23
|
+
interface LoaderContext {
|
|
24
|
+
params: Record<string, string>;
|
|
25
|
+
request: Request;
|
|
26
|
+
url: URL;
|
|
27
|
+
signal: AbortSignal;
|
|
28
|
+
}
|
|
29
|
+
type Loader<T> = (ctx: LoaderContext) => Promise<T> | T;
|
|
30
|
+
/**
|
|
31
|
+
* Identity function for declaring a loader. Exists purely as a type anchor and
|
|
32
|
+
* a marker for the Vite plugin to detect by export name.
|
|
33
|
+
*/
|
|
34
|
+
declare function loader<T>(fn: Loader<T>): Loader<T>;
|
|
35
|
+
/** Extract the return type of a loader. */
|
|
36
|
+
type InferLoader<L> = L extends Loader<infer T> ? Awaited<T> : never;
|
|
37
|
+
/**
|
|
38
|
+
* Merge multiple loader return types into a single object type.
|
|
39
|
+
* Later loaders override earlier ones on key collision — matching runtime merge.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* type PageInput = MergeLoaders<[typeof rootLayoutLoad, typeof sectionLayoutLoad, typeof pageLoad]>;
|
|
43
|
+
*/
|
|
44
|
+
type MergeLoaders<Ls extends readonly Loader<any>[]> = Ls extends readonly [infer First extends Loader<any>, ...infer Rest extends readonly Loader<any>[]] ? Rest extends readonly [] ? InferLoader<First> : Omit<InferLoader<First>, keyof MergeLoaders<Rest>> & MergeLoaders<Rest> : {};
|
|
45
|
+
declare class Redirect {
|
|
46
|
+
readonly __ilhaRedirect: true;
|
|
47
|
+
readonly to: string;
|
|
48
|
+
readonly status: number;
|
|
49
|
+
constructor(to: string, status?: number);
|
|
50
|
+
}
|
|
51
|
+
declare class LoaderError {
|
|
52
|
+
readonly __ilhaLoaderError: true;
|
|
53
|
+
readonly status: number;
|
|
54
|
+
readonly message: string;
|
|
55
|
+
constructor(status: number, message: string);
|
|
56
|
+
}
|
|
57
|
+
declare function redirect(to: string, status?: number): never;
|
|
58
|
+
declare function error(status: number, message: string): never;
|
|
59
|
+
/**
|
|
60
|
+
* Compose a list of loaders into a single loader. Later loaders win on key
|
|
61
|
+
* collision (page loader overrides layout loader for the same key). All loaders
|
|
62
|
+
* run concurrently within a chain since they share the same abort signal and
|
|
63
|
+
* request — re-fetching is cheap with a request-scoped cache (future work).
|
|
64
|
+
*
|
|
65
|
+
* For v1 we run them in parallel via `Promise.all`. If a loader throws a
|
|
66
|
+
* `Redirect` or `LoaderError`, the composed loader re-throws it unchanged.
|
|
67
|
+
*/
|
|
68
|
+
declare function composeLoaders<Ls extends readonly Loader<any>[]>(loaders: Ls): Loader<MergeLoaders<Ls>>;
|
|
21
69
|
declare function wrapLayout(layout: LayoutHandler, page: Island<any, any>): Island<any, any>;
|
|
22
70
|
declare function wrapError(handler: ErrorHandler, page: Island<any, any>): Island<any, any>;
|
|
71
|
+
declare function defineLayout(layout: LayoutHandler): LayoutHandler;
|
|
23
72
|
interface NavigateOptions {
|
|
24
73
|
replace?: boolean;
|
|
25
74
|
}
|
|
@@ -32,12 +81,65 @@ interface MountOptions {
|
|
|
32
81
|
hydrate?: boolean;
|
|
33
82
|
registry?: Record<string, Island<any, any>>;
|
|
34
83
|
}
|
|
84
|
+
/** Response envelope returned by `renderResponse` — lets the host app handle redirects. */
|
|
85
|
+
type RenderResponse = {
|
|
86
|
+
kind: "html";
|
|
87
|
+
html: string;
|
|
88
|
+
status?: number;
|
|
89
|
+
} | {
|
|
90
|
+
kind: "redirect";
|
|
91
|
+
to: string;
|
|
92
|
+
status: number;
|
|
93
|
+
} | {
|
|
94
|
+
kind: "error";
|
|
95
|
+
status: number;
|
|
96
|
+
message: string;
|
|
97
|
+
html: string;
|
|
98
|
+
};
|
|
35
99
|
interface RouterBuilder {
|
|
36
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Register a route. The optional `loader` is the merged loader chain
|
|
102
|
+
* (layout loaders outer→inner followed by the page loader) produced by
|
|
103
|
+
* the FS-routing codegen.
|
|
104
|
+
*/
|
|
105
|
+
route(pattern: string, island: Island<any, any>, loader?: Loader<any>): RouterBuilder;
|
|
106
|
+
/**
|
|
107
|
+
* Attach (or replace) a loader on an already-registered route pattern.
|
|
108
|
+
* Used by the `ilha:loaders` virtual module to wire server-only loaders
|
|
109
|
+
* onto the client-safe `pageRouter` at SSR time. No-op if the pattern
|
|
110
|
+
* was never registered via `.route()`.
|
|
111
|
+
*/
|
|
112
|
+
attachLoader(pattern: string, loader: Loader<any>): RouterBuilder;
|
|
37
113
|
prime(): void;
|
|
38
114
|
mount(target: string | Element, options?: MountOptions): () => void;
|
|
39
115
|
render(url: string | URL): string;
|
|
40
|
-
renderHydratable(url: string | URL, registry: Record<string, Island<any, any>>, options?: HydratableRenderOptions): Promise<string>;
|
|
116
|
+
renderHydratable(url: string | URL, registry: Record<string, Island<any, any>>, options?: HydratableRenderOptions, request?: Request): Promise<string>;
|
|
117
|
+
/**
|
|
118
|
+
* Like `renderHydratable` but surfaces loader redirects and errors as
|
|
119
|
+
* structured responses instead of baking them into HTML. Prefer this from
|
|
120
|
+
* host server code so you can emit proper 302 / 4xx responses.
|
|
121
|
+
*/
|
|
122
|
+
renderResponse(url: string | URL, registry: Record<string, Island<any, any>>, options?: HydratableRenderOptions, request?: Request): Promise<RenderResponse>;
|
|
123
|
+
/**
|
|
124
|
+
* Run the loader chain for a given URL without rendering. Used by the
|
|
125
|
+
* `/__ilha/loader` endpoint the Vite plugin exposes for client-side
|
|
126
|
+
* navigation. Returns the raw loader result, a redirect sentinel, or an
|
|
127
|
+
* error sentinel.
|
|
128
|
+
*/
|
|
129
|
+
runLoader(url: string | URL, request?: Request): Promise<{
|
|
130
|
+
kind: "data";
|
|
131
|
+
data: Record<string, unknown>;
|
|
132
|
+
} | {
|
|
133
|
+
kind: "redirect";
|
|
134
|
+
to: string;
|
|
135
|
+
status: number;
|
|
136
|
+
} | {
|
|
137
|
+
kind: "error";
|
|
138
|
+
status: number;
|
|
139
|
+
message: string;
|
|
140
|
+
} | {
|
|
141
|
+
kind: "not-found";
|
|
142
|
+
}>;
|
|
41
143
|
/**
|
|
42
144
|
* Hydrate the application - combines prime(), mount(), and router.mount() into one call.
|
|
43
145
|
* @param registry - The island registry from ilha:registry
|
|
@@ -46,6 +148,14 @@ interface RouterBuilder {
|
|
|
46
148
|
*/
|
|
47
149
|
hydrate(registry: Record<string, Island<any, any>>, options?: HydrateOptions): () => void;
|
|
48
150
|
}
|
|
151
|
+
/** Path of the loader endpoint served by the Vite plugin / production adapter. */
|
|
152
|
+
declare const LOADER_ENDPOINT = "/__ilha/loader";
|
|
153
|
+
/**
|
|
154
|
+
* Prefetch loader data for a given path. Safe to call repeatedly — a single
|
|
155
|
+
* inflight request is reused until it either resolves (and is consumed by
|
|
156
|
+
* navigation) or is superseded by another prefetch.
|
|
157
|
+
*/
|
|
158
|
+
declare function prefetch(pathWithSearch: string): void;
|
|
49
159
|
declare const routePath: {
|
|
50
160
|
(): string;
|
|
51
161
|
(value: string): void;
|
|
@@ -84,23 +194,19 @@ declare function useRoute(): {
|
|
|
84
194
|
* Prime route context signals from the current `location` so that islands
|
|
85
195
|
* hydrated by `ilha.mount()` see the correct route values on their first
|
|
86
196
|
* render — preventing a mismatch morph that would destroy hydrated bindings.
|
|
87
|
-
*
|
|
88
|
-
* Call this **before** `ilha.mount()` and **after** all routes have been
|
|
89
|
-
* registered (i.e. after the `router().route(…).route(…)` chain).
|
|
90
|
-
*
|
|
91
|
-
* ```ts
|
|
92
|
-
* import { mount } from "ilha";
|
|
93
|
-
* import { pageRouter } from "ilha:pages";
|
|
94
|
-
* import { registry } from "ilha:registry";
|
|
95
|
-
*
|
|
96
|
-
* pageRouter.prime(); // ← sync signals first
|
|
97
|
-
* mount(registry, { root: … }); // ← then hydrate islands
|
|
98
|
-
* pageRouter.mount("#app", { hydrate: true });
|
|
99
|
-
* ```
|
|
100
197
|
*/
|
|
101
198
|
declare function prime(): void;
|
|
102
199
|
declare function navigate(to: string, opts?: NavigateOptions): void;
|
|
103
|
-
|
|
200
|
+
interface LinkInterceptionOptions {
|
|
201
|
+
/**
|
|
202
|
+
* Prefetch loader data on `mouseenter` for eligible links. Links opt in via
|
|
203
|
+
* the `data-prefetch` attribute (set `data-prefetch="false"` to opt out a
|
|
204
|
+
* specific link even when the framework is configured to prefetch by default).
|
|
205
|
+
* Default: `true` — prefetches on hover for any link with `data-prefetch`.
|
|
206
|
+
*/
|
|
207
|
+
prefetch?: boolean;
|
|
208
|
+
}
|
|
209
|
+
declare function enableLinkInterception(root?: Element | Document, options?: LinkInterceptionOptions): () => void;
|
|
104
210
|
declare const RouterView: Island<Record<string, unknown>, Record<string, never>>;
|
|
105
211
|
declare const RouterLink: Island<Record<string, unknown>, {
|
|
106
212
|
[x: string]: never;
|
|
@@ -115,11 +221,16 @@ declare const _default: {
|
|
|
115
221
|
isActive: typeof isActive;
|
|
116
222
|
enableLinkInterception: typeof enableLinkInterception;
|
|
117
223
|
prime: typeof prime;
|
|
224
|
+
prefetch: typeof prefetch;
|
|
118
225
|
RouterView: Island<Record<string, unknown>, Record<string, never>>;
|
|
119
226
|
RouterLink: Island<Record<string, unknown>, {
|
|
120
227
|
[x: string]: never;
|
|
121
228
|
href: string;
|
|
122
229
|
} & Record<"label", string>>;
|
|
230
|
+
loader: typeof loader;
|
|
231
|
+
redirect: typeof redirect;
|
|
232
|
+
error: typeof error;
|
|
233
|
+
composeLoaders: typeof composeLoaders;
|
|
123
234
|
};
|
|
124
235
|
//#endregion
|
|
125
|
-
export { AppError, ErrorHandler, HydratableRenderOptions, HydrateOptions, LayoutHandler, MountOptions, NavigateOptions, RouteRecord, RouteSnapshot, RouterBuilder, RouterLink, RouterView, _default as default, enableLinkInterception, isActive, navigate, prime, routeHash, routeParams, routePath, routeSearch, router, useRoute, wrapError, wrapLayout };
|
|
236
|
+
export { AppError, ErrorHandler, HydratableRenderOptions, HydrateOptions, InferLoader, LOADER_ENDPOINT, LayoutHandler, LinkInterceptionOptions, Loader, LoaderContext, LoaderError, MergeLoaders, MountOptions, NavigateOptions, Redirect, RenderResponse, RouteRecord, RouteSnapshot, RouterBuilder, RouterLink, RouterView, composeLoaders, _default as default, defineLayout, enableLinkInterception, error, isActive, loader, navigate, prefetch, prime, redirect, routeHash, routeParams, routePath, routeSearch, router, useRoute, wrapError, wrapLayout };
|