fibrae 0.1.0
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/dist/components.d.ts +40 -0
- package/dist/components.js +63 -0
- package/dist/components.js.map +1 -0
- package/dist/core.d.ts +25 -0
- package/dist/core.js +46 -0
- package/dist/core.js.map +1 -0
- package/dist/dom.d.ts +16 -0
- package/dist/dom.js +67 -0
- package/dist/dom.js.map +1 -0
- package/dist/fiber-render.d.ts +33 -0
- package/dist/fiber-render.js +1069 -0
- package/dist/fiber-render.js.map +1 -0
- package/dist/h.d.ts +19 -0
- package/dist/h.js +26 -0
- package/dist/h.js.map +1 -0
- package/dist/hydration.d.ts +30 -0
- package/dist/hydration.js +375 -0
- package/dist/hydration.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx-runtime/index.d.ts +29 -0
- package/dist/jsx-runtime/index.js +61 -0
- package/dist/jsx-runtime/index.js.map +1 -0
- package/dist/render.d.ts +19 -0
- package/dist/render.js +325 -0
- package/dist/render.js.map +1 -0
- package/dist/router/History.d.ts +129 -0
- package/dist/router/History.js +241 -0
- package/dist/router/History.js.map +1 -0
- package/dist/router/Link.d.ts +52 -0
- package/dist/router/Link.js +131 -0
- package/dist/router/Link.js.map +1 -0
- package/dist/router/Navigator.d.ts +108 -0
- package/dist/router/Navigator.js +225 -0
- package/dist/router/Navigator.js.map +1 -0
- package/dist/router/Route.d.ts +65 -0
- package/dist/router/Route.js +143 -0
- package/dist/router/Route.js.map +1 -0
- package/dist/router/Router.d.ts +167 -0
- package/dist/router/Router.js +328 -0
- package/dist/router/Router.js.map +1 -0
- package/dist/router/RouterBuilder.d.ts +128 -0
- package/dist/router/RouterBuilder.js +112 -0
- package/dist/router/RouterBuilder.js.map +1 -0
- package/dist/router/RouterOutlet.d.ts +57 -0
- package/dist/router/RouterOutlet.js +132 -0
- package/dist/router/RouterOutlet.js.map +1 -0
- package/dist/router/RouterState.d.ts +102 -0
- package/dist/router/RouterState.js +94 -0
- package/dist/router/RouterState.js.map +1 -0
- package/dist/router/index.d.ts +28 -0
- package/dist/router/index.js +31 -0
- package/dist/router/index.js.map +1 -0
- package/dist/runtime.d.ts +55 -0
- package/dist/runtime.js +68 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scope-utils.d.ts +14 -0
- package/dist/scope-utils.js +29 -0
- package/dist/scope-utils.js.map +1 -0
- package/dist/server.d.ts +112 -0
- package/dist/server.js +313 -0
- package/dist/server.js.map +1 -0
- package/dist/shared.d.ts +136 -0
- package/dist/shared.js +53 -0
- package/dist/shared.js.map +1 -0
- package/dist/tracking.d.ts +23 -0
- package/dist/tracking.js +53 -0
- package/dist/tracking.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigator service - type-safe route-aware navigation.
|
|
3
|
+
*
|
|
4
|
+
* Provides route-aware navigation on top of History:
|
|
5
|
+
* - nav.go("routeName", { path: {...}, searchParams: {...} })
|
|
6
|
+
* - nav.back, nav.forward
|
|
7
|
+
* - nav.isActive("routeName", params) for active link detection
|
|
8
|
+
* - currentRoute Atom reflects matched route info
|
|
9
|
+
*
|
|
10
|
+
* Design: Navigator uses History internally but provides route-aware API.
|
|
11
|
+
* It knows about routes and can build URLs from route names.
|
|
12
|
+
*/
|
|
13
|
+
import * as Effect from "effect/Effect";
|
|
14
|
+
import * as Context from "effect/Context";
|
|
15
|
+
import * as Layer from "effect/Layer";
|
|
16
|
+
import * as Option from "effect/Option";
|
|
17
|
+
import { Atom, Registry as AtomRegistry } from "@effect-atom/atom";
|
|
18
|
+
import { History } from "./History.js";
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Service Tag
|
|
21
|
+
// =============================================================================
|
|
22
|
+
/**
|
|
23
|
+
* Navigator service tag for Effect dependency injection.
|
|
24
|
+
*/
|
|
25
|
+
export class Navigator extends Context.Tag("fibrae/Navigator")() {
|
|
26
|
+
}
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Helpers
|
|
29
|
+
// =============================================================================
|
|
30
|
+
/**
|
|
31
|
+
* Parse search params from URL search string.
|
|
32
|
+
*/
|
|
33
|
+
function parseSearchParams(search) {
|
|
34
|
+
const params = {};
|
|
35
|
+
const searchParams = new URLSearchParams(search);
|
|
36
|
+
searchParams.forEach((value, key) => {
|
|
37
|
+
params[key] = value;
|
|
38
|
+
});
|
|
39
|
+
return params;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Build search string from params object.
|
|
43
|
+
*/
|
|
44
|
+
function buildSearchString(params) {
|
|
45
|
+
const searchParams = new URLSearchParams();
|
|
46
|
+
for (const [key, value] of Object.entries(params)) {
|
|
47
|
+
if (value !== undefined && value !== null) {
|
|
48
|
+
searchParams.set(key, String(value));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const str = searchParams.toString();
|
|
52
|
+
return str ? `?${str}` : "";
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Strip basePath prefix from pathname for route matching.
|
|
56
|
+
*/
|
|
57
|
+
function stripBasePath(pathname, basePath) {
|
|
58
|
+
if (!basePath || basePath === "/") {
|
|
59
|
+
return pathname;
|
|
60
|
+
}
|
|
61
|
+
// Normalize: remove trailing slash from basePath
|
|
62
|
+
const normalizedBase = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
63
|
+
if (pathname.startsWith(normalizedBase)) {
|
|
64
|
+
const stripped = pathname.slice(normalizedBase.length);
|
|
65
|
+
// Ensure we return "/" not "" for root
|
|
66
|
+
return stripped || "/";
|
|
67
|
+
}
|
|
68
|
+
return pathname;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Match current location against router and return CurrentRoute.
|
|
72
|
+
*/
|
|
73
|
+
function matchLocation(router, location, basePath = "") {
|
|
74
|
+
const pathname = stripBasePath(location.pathname, basePath);
|
|
75
|
+
const match = router.matchRoute(pathname);
|
|
76
|
+
if (Option.isNone(match)) {
|
|
77
|
+
return Option.none();
|
|
78
|
+
}
|
|
79
|
+
return Option.some({
|
|
80
|
+
routeName: match.value.route.name,
|
|
81
|
+
params: match.value.params,
|
|
82
|
+
searchParams: parseSearchParams(location.search),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Find a route by name in the router.
|
|
87
|
+
*/
|
|
88
|
+
function findRouteByName(router, name) {
|
|
89
|
+
for (const group of router.groups) {
|
|
90
|
+
for (const route of group.routes) {
|
|
91
|
+
if (route.name === name) {
|
|
92
|
+
return Option.some(route);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return Option.none();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Create a Navigator layer for the given router.
|
|
100
|
+
*
|
|
101
|
+
* Features:
|
|
102
|
+
* - Type-safe navigation by route name
|
|
103
|
+
* - Automatic URL building via route.interpolate
|
|
104
|
+
* - Tracks current matched route in an Atom
|
|
105
|
+
* - Delegates to History for actual navigation
|
|
106
|
+
* - Supports basePath for apps mounted at non-root paths
|
|
107
|
+
*/
|
|
108
|
+
export function NavigatorLive(router, options = {}) {
|
|
109
|
+
const basePath = options.basePath ?? "";
|
|
110
|
+
return Layer.effect(Navigator, Effect.gen(function* () {
|
|
111
|
+
const history = yield* History;
|
|
112
|
+
const registry = yield* AtomRegistry.AtomRegistry;
|
|
113
|
+
// Get initial location and match
|
|
114
|
+
const initialLocation = yield* Atom.get(history.location);
|
|
115
|
+
const initialRoute = matchLocation(router, initialLocation, basePath);
|
|
116
|
+
// Create currentRoute Atom
|
|
117
|
+
const currentRouteAtom = Atom.make(initialRoute);
|
|
118
|
+
// Create a derived effect that updates currentRoute when location changes
|
|
119
|
+
// This is a reactive subscription
|
|
120
|
+
const updateCurrentRoute = (location) => {
|
|
121
|
+
const matched = matchLocation(router, location, basePath);
|
|
122
|
+
registry.set(currentRouteAtom, matched);
|
|
123
|
+
};
|
|
124
|
+
// Note: The actual subscription to location changes should be handled
|
|
125
|
+
// by by component rendering - currentRoute will be updated when go/back/forward are called
|
|
126
|
+
const service = {
|
|
127
|
+
basePath,
|
|
128
|
+
currentRoute: currentRouteAtom,
|
|
129
|
+
go: (routeName, options = {}) => Effect.gen(function* () {
|
|
130
|
+
const route = findRouteByName(router, routeName);
|
|
131
|
+
if (Option.isNone(route)) {
|
|
132
|
+
yield* Effect.logWarning(`Route not found: ${routeName}`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// Build URL from route and params
|
|
136
|
+
const pathParams = options.path ?? {};
|
|
137
|
+
const routePathname = route.value.interpolate(pathParams);
|
|
138
|
+
// Prepend basePath to route pathname
|
|
139
|
+
const pathname = basePath + routePathname;
|
|
140
|
+
const search = options.searchParams
|
|
141
|
+
? buildSearchString(options.searchParams)
|
|
142
|
+
: "";
|
|
143
|
+
const url = `${pathname}${search}`;
|
|
144
|
+
// Navigate
|
|
145
|
+
if (options.replace) {
|
|
146
|
+
yield* history.replace(url);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
yield* history.push(url);
|
|
150
|
+
}
|
|
151
|
+
// Update currentRoute
|
|
152
|
+
const newLocation = yield* Atom.get(history.location);
|
|
153
|
+
updateCurrentRoute(newLocation);
|
|
154
|
+
}),
|
|
155
|
+
back: Effect.gen(function* () {
|
|
156
|
+
yield* history.back;
|
|
157
|
+
// Wait a tick for popstate to fire, then update
|
|
158
|
+
yield* Effect.sleep("10 millis");
|
|
159
|
+
const location = yield* Atom.get(history.location);
|
|
160
|
+
updateCurrentRoute(location);
|
|
161
|
+
}),
|
|
162
|
+
forward: Effect.gen(function* () {
|
|
163
|
+
yield* history.forward;
|
|
164
|
+
// Wait a tick for popstate to fire, then update
|
|
165
|
+
yield* Effect.sleep("10 millis");
|
|
166
|
+
const location = yield* Atom.get(history.location);
|
|
167
|
+
updateCurrentRoute(location);
|
|
168
|
+
}),
|
|
169
|
+
isActive: (routeName, params) => Effect.gen(function* () {
|
|
170
|
+
const current = yield* Atom.get(currentRouteAtom);
|
|
171
|
+
if (Option.isNone(current)) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
if (current.value.routeName !== routeName) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
// If params provided, check they match
|
|
178
|
+
if (params) {
|
|
179
|
+
for (const [key, value] of Object.entries(params)) {
|
|
180
|
+
if (current.value.params[key] !== value) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}),
|
|
187
|
+
};
|
|
188
|
+
return service;
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
// =============================================================================
|
|
192
|
+
// Convenience Accessors
|
|
193
|
+
// =============================================================================
|
|
194
|
+
/**
|
|
195
|
+
* Navigate to a route by name.
|
|
196
|
+
*/
|
|
197
|
+
export const go = (routeName, options) => Effect.gen(function* () {
|
|
198
|
+
const nav = yield* Navigator;
|
|
199
|
+
yield* nav.go(routeName, options);
|
|
200
|
+
});
|
|
201
|
+
/**
|
|
202
|
+
* Go back in history.
|
|
203
|
+
*/
|
|
204
|
+
/* is-tree-shakable-suppress */
|
|
205
|
+
export const back = Effect.gen(function* () {
|
|
206
|
+
const nav = yield* Navigator;
|
|
207
|
+
yield* nav.back;
|
|
208
|
+
});
|
|
209
|
+
/**
|
|
210
|
+
* Go forward in history.
|
|
211
|
+
*/
|
|
212
|
+
/* is-tree-shakable-suppress */
|
|
213
|
+
export const forward = Effect.gen(function* () {
|
|
214
|
+
const nav = yield* Navigator;
|
|
215
|
+
yield* nav.forward;
|
|
216
|
+
});
|
|
217
|
+
/**
|
|
218
|
+
* Get current route info.
|
|
219
|
+
*/
|
|
220
|
+
/* is-tree-shakable-suppress */
|
|
221
|
+
export const getCurrentRoute = Effect.gen(function* () {
|
|
222
|
+
const nav = yield* Navigator;
|
|
223
|
+
return yield* Atom.get(nav.currentRoute);
|
|
224
|
+
});
|
|
225
|
+
//# sourceMappingURL=Navigator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Navigator.js","sourceRoot":"","sources":["../../src/router/Navigator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,OAAO,EAAwB,MAAM,cAAc,CAAC;AAyE7D,gFAAgF;AAChF,cAAc;AACd,gFAAgF;AAEhF;;GAEG;AACH,MAAM,OAAO,SAAU,SAAQ,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAG3D;CAAG;AAEN,gFAAgF;AAChF,UAAU;AACV,gFAAgF;AAEhF;;GAEG;AACH,SAAS,iBAAiB,CAAC,MAAc;IACvC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,MAAM,YAAY,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;IACjD,YAAY,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QAClC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACtB,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB,CAAC,MAA+B;IACxD,MAAM,YAAY,GAAG,IAAI,eAAe,EAAE,CAAC;IAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAC1C,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IACD,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC;IACpC,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9B,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CAAC,QAAgB,EAAE,QAAgB;IACvD,IAAI,CAAC,QAAQ,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;QAClC,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,iDAAiD;IACjD,MAAM,cAAc,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACjF,IAAI,QAAQ,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QACxC,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QACvD,uCAAuC;QACvC,OAAO,QAAQ,IAAI,GAAG,CAAC;IACzB,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CACpB,MAAc,EACd,QAAyB,EACzB,WAAmB,EAAE;IAErB,MAAM,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC1C,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC;QACjB,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI;QACjC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM;QAC1B,YAAY,EAAE,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC;KACjD,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,MAAc,EAAE,IAAY;IACnD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACjC,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBACxB,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;AACvB,CAAC;AAcD;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAc,EACd,UAA4B,EAAE;IAE9B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;IAExC,OAAO,KAAK,CAAC,MAAM,CACjB,SAAS,EACT,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC;QAC/B,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC;QAElD,iCAAiC;QACjC,MAAM,eAAe,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC1D,MAAM,YAAY,GAAG,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,QAAQ,CAAC,CAAC;QAEtE,2BAA2B;QAC3B,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAEjD,0EAA0E;QAC1E,kCAAkC;QAClC,MAAM,kBAAkB,GAAG,CAAC,QAAyB,EAAE,EAAE;YACvD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC1D,QAAQ,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;QAC1C,CAAC,CAAC;QAEF,sEAAsE;QACtE,2FAA2F;QAE3F,MAAM,OAAO,GAAqB;YAChC,QAAQ;YACR,YAAY,EAAE,gBAAgB;YAE9B,EAAE,EAAE,CAAC,SAAS,EAAE,OAAO,GAAG,EAAE,EAAE,EAAE,CAC9B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAClB,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;gBACjD,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;oBACzB,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,oBAAoB,SAAS,EAAE,CAAC,CAAC;oBAC1D,OAAO;gBACT,CAAC;gBAED,kCAAkC;gBAClC,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,IAAK,EAA8B,CAAC;gBACnE,MAAM,aAAa,GAAG,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;gBAC1D,qCAAqC;gBACrC,MAAM,QAAQ,GAAG,QAAQ,GAAG,aAAa,CAAC;gBAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY;oBACjC,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,YAAY,CAAC;oBACzC,CAAC,CAAC,EAAE,CAAC;gBACP,MAAM,GAAG,GAAG,GAAG,QAAQ,GAAG,MAAM,EAAE,CAAC;gBAEnC,WAAW;gBACX,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;oBACpB,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBAC9B,CAAC;qBAAM,CAAC;oBACN,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC3B,CAAC;gBAED,sBAAsB;gBACtB,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACtD,kBAAkB,CAAC,WAAW,CAAC,CAAC;YAClC,CAAC,CAAC;YAEJ,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;gBACxB,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;gBACpB,gDAAgD;gBAChD,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;gBACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACnD,kBAAkB,CAAC,QAAQ,CAAC,CAAC;YAC/B,CAAC,CAAC;YAEF,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAC3B,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;gBACvB,gDAAgD;gBAChD,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;gBACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACnD,kBAAkB,CAAC,QAAQ,CAAC,CAAC;YAC/B,CAAC,CAAC;YAEF,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAC9B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;gBAClD,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC3B,OAAO,KAAK,CAAC;gBACf,CAAC;gBAED,IAAI,OAAO,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;oBAC1C,OAAO,KAAK,CAAC;gBACf,CAAC;gBAED,uCAAuC;gBACvC,IAAI,MAAM,EAAE,CAAC;oBACX,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;wBAClD,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC;4BACxC,OAAO,KAAK,CAAC;wBACf,CAAC;oBACH,CAAC;gBACH,CAAC;gBAED,OAAO,IAAI,CAAC;YACd,CAAC,CAAC;SACL,CAAC;QAEF,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC,CACH,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,wBAAwB;AACxB,gFAAgF;AAEhF;;GAEG;AACH,MAAM,CAAC,MAAM,EAAE,GAAG,CAChB,SAAiB,EACjB,OAAyB,EAC0C,EAAE,CACrE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC;IAC7B,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AACpC,CAAC,CAAC,CAAC;AAEL;;GAEG;AACH,+BAA+B;AAC/B,MAAM,CAAC,MAAM,IAAI,GAAsE,MAAM,CAAC,GAAG,CAC/F,QAAQ,CAAC;IACP,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC;IAC7B,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;AAClB,CAAC,CACF,CAAC;AAEF;;GAEG;AACH,+BAA+B;AAC/B,MAAM,CAAC,MAAM,OAAO,GAAsE,MAAM,CAAC,GAAG,CAClG,QAAQ,CAAC;IACP,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC;IAC7B,KAAK,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC;AACrB,CAAC,CACF,CAAC;AAEF;;GAEG;AACH,+BAA+B;AAC/B,MAAM,CAAC,MAAM,eAAe,GAIxB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACtB,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC;IAC7B,OAAO,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route declaration module for Fibrae router.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors Effect HttpApiEndpoint patterns:
|
|
5
|
+
* - Route.get("name", "/path") for static routes
|
|
6
|
+
* - Route.get("name")`/path/${param}` for dynamic routes with template literals
|
|
7
|
+
* - Route.param for schema-validated path parameters
|
|
8
|
+
* - .setSearchParams for query string validation
|
|
9
|
+
*/
|
|
10
|
+
import * as Schema from "effect/Schema";
|
|
11
|
+
import * as Option from "effect/Option";
|
|
12
|
+
/**
|
|
13
|
+
* Annotation symbol for storing parameter name in schema metadata.
|
|
14
|
+
* Mirrors HttpApiSchema.AnnotationParam pattern.
|
|
15
|
+
*/
|
|
16
|
+
export declare const AnnotationParam: unique symbol;
|
|
17
|
+
/**
|
|
18
|
+
* Represents a single route with path and optional search params validation.
|
|
19
|
+
* This is immutable data that describes a route.
|
|
20
|
+
*/
|
|
21
|
+
export interface Route<Name extends string = string, PathParams extends Record<string, unknown> = Record<string, unknown>, SearchParams extends Record<string, unknown> = Record<string, unknown>> {
|
|
22
|
+
readonly name: Name;
|
|
23
|
+
readonly path: string;
|
|
24
|
+
readonly pathSchema: Option.Option<Schema.Schema<PathParams>>;
|
|
25
|
+
readonly searchSchema: Option.Option<Schema.Schema<SearchParams>>;
|
|
26
|
+
/**
|
|
27
|
+
* Match a pathname against this route.
|
|
28
|
+
* Returns the decoded path parameters if matched, None otherwise.
|
|
29
|
+
*/
|
|
30
|
+
readonly match: (pathname: string) => Option.Option<PathParams>;
|
|
31
|
+
/**
|
|
32
|
+
* Build a URL from path parameters.
|
|
33
|
+
* Throws if required params are missing.
|
|
34
|
+
*/
|
|
35
|
+
readonly interpolate: (params: PathParams) => string;
|
|
36
|
+
/**
|
|
37
|
+
* Set search parameter schema for this route.
|
|
38
|
+
*/
|
|
39
|
+
readonly setSearchParams: <NewSearch extends Record<string, unknown>>(schema: Schema.Schema<NewSearch>) => Route<Name, PathParams, NewSearch>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Constructor for building routes with different method types.
|
|
43
|
+
* Supports both static paths and template literal syntax.
|
|
44
|
+
*/
|
|
45
|
+
export interface RouteConstructor {
|
|
46
|
+
<const Name extends string>(name: Name, path: string): Route<Name, {}, {}>;
|
|
47
|
+
<const Name extends string>(name: Name): <const T extends readonly any[]>(segments: TemplateStringsArray, ...params: T) => Route<Name, Record<string, unknown>, {}>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Route.get("name", "/path") creates a GET route
|
|
51
|
+
* Route.get("name")`/path/${param}` creates a GET route with typed path params
|
|
52
|
+
*/
|
|
53
|
+
export declare const get: RouteConstructor;
|
|
54
|
+
/**
|
|
55
|
+
* Route.post("name", "/path") creates a POST route
|
|
56
|
+
* Route.post("name")`/path/${param}` creates a POST route with typed path params
|
|
57
|
+
*/
|
|
58
|
+
export declare const post: RouteConstructor;
|
|
59
|
+
/**
|
|
60
|
+
* Route.param("name", schema) creates a path parameter with validation.
|
|
61
|
+
* Use in template literals: Route.get("name")`/posts/${Route.param("id", Schema.NumberFromString)}`
|
|
62
|
+
*
|
|
63
|
+
* Stores the parameter name in the schema's annotations for extraction during template literal parsing.
|
|
64
|
+
*/
|
|
65
|
+
export declare function param<T>(name: string, schema: Schema.Schema<T>): Schema.Schema<T>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route declaration module for Fibrae router.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors Effect HttpApiEndpoint patterns:
|
|
5
|
+
* - Route.get("name", "/path") for static routes
|
|
6
|
+
* - Route.get("name")`/path/${param}` for dynamic routes with template literals
|
|
7
|
+
* - Route.param for schema-validated path parameters
|
|
8
|
+
* - .setSearchParams for query string validation
|
|
9
|
+
*/
|
|
10
|
+
import * as Schema from "effect/Schema";
|
|
11
|
+
import * as Option from "effect/Option";
|
|
12
|
+
/**
|
|
13
|
+
* Annotation symbol for storing parameter name in schema metadata.
|
|
14
|
+
* Mirrors HttpApiSchema.AnnotationParam pattern.
|
|
15
|
+
*/
|
|
16
|
+
export const AnnotationParam = Symbol.for("fibrae/Route/AnnotationParam");
|
|
17
|
+
/**
|
|
18
|
+
* Parse path template to extract param names and build a URL pattern.
|
|
19
|
+
*
|
|
20
|
+
* Example: "/posts/:id/comments/:commentId" → { paramNames: ["id", "commentId"], pattern: ... }
|
|
21
|
+
*/
|
|
22
|
+
function parsePathTemplate(segments, schemas) {
|
|
23
|
+
let path = segments[0];
|
|
24
|
+
const paramNames = [];
|
|
25
|
+
const pathSchemaObj = {};
|
|
26
|
+
for (let i = 0; i < schemas.length; i++) {
|
|
27
|
+
const schema = schemas[i];
|
|
28
|
+
// Get param name from schema annotation (if provided) or use index
|
|
29
|
+
const paramName = getParamName(schema) ?? String(i);
|
|
30
|
+
paramNames.push(paramName);
|
|
31
|
+
pathSchemaObj[paramName] = schema;
|
|
32
|
+
path += `:${paramName}${segments[i + 1]}`;
|
|
33
|
+
}
|
|
34
|
+
const pathSchema = paramNames.length > 0
|
|
35
|
+
? Option.some(Schema.Struct(pathSchemaObj))
|
|
36
|
+
: Option.none();
|
|
37
|
+
return { path, paramNames, pathSchema };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get param name from a Schema's annotations.
|
|
41
|
+
* Mirrors HttpApiSchema.getParam pattern.
|
|
42
|
+
*/
|
|
43
|
+
function getParamName(schema) {
|
|
44
|
+
const ast = schema.ast;
|
|
45
|
+
const annotations = ast.annotations;
|
|
46
|
+
const paramAnnotation = annotations[AnnotationParam];
|
|
47
|
+
return paramAnnotation?.name;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Match a pathname against a route pattern.
|
|
51
|
+
* Pattern: "/posts/:id/comments/:commentId"
|
|
52
|
+
* Pathname: "/posts/123/comments/456"
|
|
53
|
+
* Returns: { id: "123", commentId: "456" }
|
|
54
|
+
*/
|
|
55
|
+
function matchPath(pattern, pathname, pathSchema) {
|
|
56
|
+
// Convert pattern with :params to regex
|
|
57
|
+
const regexPattern = pattern.replace(/:(\w+)/g, "(?<$1>[^/]+)");
|
|
58
|
+
const regex = new RegExp(`^${regexPattern}/?$`);
|
|
59
|
+
const match = pathname.match(regex);
|
|
60
|
+
if (!match) {
|
|
61
|
+
return Option.none();
|
|
62
|
+
}
|
|
63
|
+
const params = match.groups ?? {};
|
|
64
|
+
// Decode and validate with schema if present
|
|
65
|
+
if (Option.isSome(pathSchema)) {
|
|
66
|
+
try {
|
|
67
|
+
const decoded = Schema.decodeSync(pathSchema.value)(params);
|
|
68
|
+
return Option.some(decoded);
|
|
69
|
+
}
|
|
70
|
+
catch (_) {
|
|
71
|
+
return Option.none();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return Option.some(params);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Build a URL from a route pattern and parameters.
|
|
78
|
+
* Pattern: "/posts/:id/comments/:commentId"
|
|
79
|
+
* Params: { id: 123, commentId: 456 }
|
|
80
|
+
* Returns: "/posts/123/comments/456"
|
|
81
|
+
*/
|
|
82
|
+
function interpolatePath(pattern, params) {
|
|
83
|
+
return pattern.replace(/:(\w+)/g, (_, key) => {
|
|
84
|
+
const value = params[key];
|
|
85
|
+
if (value === undefined) {
|
|
86
|
+
throw new Error(`Missing required parameter: ${key}`);
|
|
87
|
+
}
|
|
88
|
+
return String(value);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Create a route with static path.
|
|
93
|
+
*/
|
|
94
|
+
function makeRoute(_name, path, pathSchema = Option.none(), searchSchema = Option.none()) {
|
|
95
|
+
return {
|
|
96
|
+
name: _name,
|
|
97
|
+
path,
|
|
98
|
+
pathSchema,
|
|
99
|
+
searchSchema,
|
|
100
|
+
match: (pathname) => matchPath(path, pathname, pathSchema),
|
|
101
|
+
interpolate: (params) => interpolatePath(path, params),
|
|
102
|
+
setSearchParams: (schema) => makeRoute(_name, path, pathSchema, Option.some(schema)),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Create a route getter function supporting both static and template literal syntax.
|
|
107
|
+
*/
|
|
108
|
+
function makeGetter() {
|
|
109
|
+
return ((name, path) => {
|
|
110
|
+
// Static path case
|
|
111
|
+
if (typeof path === "string") {
|
|
112
|
+
return makeRoute(name, path);
|
|
113
|
+
}
|
|
114
|
+
// Return template literal handler
|
|
115
|
+
return (segments, ...schemas) => {
|
|
116
|
+
const { path: parsedPath, pathSchema } = parsePathTemplate(segments, Array.from(schemas));
|
|
117
|
+
return makeRoute(name, parsedPath, pathSchema);
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Route.get("name", "/path") creates a GET route
|
|
123
|
+
* Route.get("name")`/path/${param}` creates a GET route with typed path params
|
|
124
|
+
*/
|
|
125
|
+
export const get = makeGetter();
|
|
126
|
+
/**
|
|
127
|
+
* Route.post("name", "/path") creates a POST route
|
|
128
|
+
* Route.post("name")`/path/${param}` creates a POST route with typed path params
|
|
129
|
+
*/
|
|
130
|
+
export const post = makeGetter();
|
|
131
|
+
/**
|
|
132
|
+
* Route.param("name", schema) creates a path parameter with validation.
|
|
133
|
+
* Use in template literals: Route.get("name")`/posts/${Route.param("id", Schema.NumberFromString)}`
|
|
134
|
+
*
|
|
135
|
+
* Stores the parameter name in the schema's annotations for extraction during template literal parsing.
|
|
136
|
+
*/
|
|
137
|
+
export function param(name, schema) {
|
|
138
|
+
const annotations = {
|
|
139
|
+
[AnnotationParam]: { name, schema }
|
|
140
|
+
};
|
|
141
|
+
return schema.annotations(annotations);
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=Route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Route.js","sourceRoot":"","sources":["../../src/router/Route.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router module for organizing and matching routes.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors Effect HttpApiGroup/HttpApi patterns:
|
|
5
|
+
* - Router.group("name") creates a group for organizing routes
|
|
6
|
+
* - Router.make("name") creates the top-level router
|
|
7
|
+
* - Routes are added via .add(route)
|
|
8
|
+
* - Router holds the complete route tree for efficient matching
|
|
9
|
+
*
|
|
10
|
+
* SSR Integration:
|
|
11
|
+
* - Router.serverLayer() - For SSR rendering with loaders
|
|
12
|
+
* - Router.browserLayer() - For client hydration with initial state
|
|
13
|
+
*/
|
|
14
|
+
import type { Route } from "./Route.js";
|
|
15
|
+
import * as Option from "effect/Option";
|
|
16
|
+
import * as Layer from "effect/Layer";
|
|
17
|
+
import * as Context from "effect/Context";
|
|
18
|
+
import { Registry as AtomRegistry } from "@effect-atom/atom";
|
|
19
|
+
import { History } from "./History.js";
|
|
20
|
+
import { Navigator } from "./Navigator.js";
|
|
21
|
+
import { RouterHandlers } from "./RouterBuilder.js";
|
|
22
|
+
import type { VElement } from "../shared.js";
|
|
23
|
+
/**
|
|
24
|
+
* A group of routes for organizational purposes.
|
|
25
|
+
* Groups provide namespacing for handler implementation.
|
|
26
|
+
*/
|
|
27
|
+
export interface RouteGroup<Name extends string = string> {
|
|
28
|
+
readonly name: Name;
|
|
29
|
+
readonly routes: readonly Route[];
|
|
30
|
+
readonly add: (route: Route) => RouteGroup<Name>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The complete router holding all route groups and enabling route matching.
|
|
34
|
+
*/
|
|
35
|
+
export interface Router<Name extends string = string> {
|
|
36
|
+
readonly name: Name;
|
|
37
|
+
readonly groups: readonly RouteGroup[];
|
|
38
|
+
readonly add: (group: RouteGroup) => Router<Name>;
|
|
39
|
+
/**
|
|
40
|
+
* Match a pathname against all routes in the router.
|
|
41
|
+
* Returns the matched route, group name, and decoded path parameters.
|
|
42
|
+
*/
|
|
43
|
+
readonly matchRoute: (pathname: string) => Option.Option<{
|
|
44
|
+
readonly groupName: string;
|
|
45
|
+
readonly route: Route;
|
|
46
|
+
readonly params: Record<string, unknown>;
|
|
47
|
+
}>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Create a route group with the given name.
|
|
51
|
+
* Routes are added via group.add(route).
|
|
52
|
+
*/
|
|
53
|
+
export declare function group<const Name extends string>(name: Name): RouteGroup<Name>;
|
|
54
|
+
/**
|
|
55
|
+
* Create a router with the given name.
|
|
56
|
+
* Groups are added via router.add(group).
|
|
57
|
+
*/
|
|
58
|
+
export declare function make<const Name extends string>(name: Name): Router<Name>;
|
|
59
|
+
/**
|
|
60
|
+
* Options for server-side rendering layer.
|
|
61
|
+
*/
|
|
62
|
+
export interface ServerLayerOptions {
|
|
63
|
+
/** The router instance */
|
|
64
|
+
readonly router: Router;
|
|
65
|
+
/** Current request pathname */
|
|
66
|
+
readonly pathname: string;
|
|
67
|
+
/** Current request search string (with or without leading ?) */
|
|
68
|
+
readonly search?: string;
|
|
69
|
+
/** Base path prefix for the app (e.g., "/ssr/router") */
|
|
70
|
+
readonly basePath?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Options for browser/client hydration layer.
|
|
74
|
+
*/
|
|
75
|
+
export interface BrowserLayerOptions {
|
|
76
|
+
/** The router instance */
|
|
77
|
+
readonly router: Router;
|
|
78
|
+
/**
|
|
79
|
+
* @deprecated Use atom hydration instead. RouterStateAtom is automatically
|
|
80
|
+
* hydrated from __FIBRAE_STATE__ and used by RouterOutlet.
|
|
81
|
+
*/
|
|
82
|
+
readonly initialState?: DehydratedRouterState;
|
|
83
|
+
/** Base path prefix for the app (e.g., "/ssr/router") */
|
|
84
|
+
readonly basePath?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Dehydrated state from SSR for hydration.
|
|
88
|
+
* Contains matched route info and loader data.
|
|
89
|
+
*/
|
|
90
|
+
export interface DehydratedRouterState {
|
|
91
|
+
/** Name of the matched route */
|
|
92
|
+
readonly routeName: string;
|
|
93
|
+
/** Decoded path parameters */
|
|
94
|
+
readonly params: Record<string, unknown>;
|
|
95
|
+
/** Search parameters */
|
|
96
|
+
readonly searchParams: Record<string, string>;
|
|
97
|
+
/** Data returned by the loader */
|
|
98
|
+
readonly loaderData: unknown;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Result of SSR rendering a route.
|
|
102
|
+
*/
|
|
103
|
+
export interface SSRRouteResult {
|
|
104
|
+
/** The rendered VElement */
|
|
105
|
+
readonly element: VElement;
|
|
106
|
+
/** Dehydrated state for client hydration */
|
|
107
|
+
readonly dehydratedState: DehydratedRouterState;
|
|
108
|
+
}
|
|
109
|
+
declare const CurrentRouteElement_base: Context.TagClass<CurrentRouteElement, "fibrae/CurrentRouteElement", {
|
|
110
|
+
readonly element: VElement;
|
|
111
|
+
readonly state: DehydratedRouterState;
|
|
112
|
+
}>;
|
|
113
|
+
/**
|
|
114
|
+
* Service tag for the current route's rendered element.
|
|
115
|
+
* Used by SSR to provide the matched route's component.
|
|
116
|
+
*/
|
|
117
|
+
export declare class CurrentRouteElement extends CurrentRouteElement_base {
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Create a server-side layer for SSR rendering.
|
|
121
|
+
*
|
|
122
|
+
* This layer:
|
|
123
|
+
* 1. Matches the pathname against the router
|
|
124
|
+
* 2. Runs the matched route's loader
|
|
125
|
+
* 3. Renders the component with loader data
|
|
126
|
+
* 4. Provides the rendered element and dehydrated state
|
|
127
|
+
*
|
|
128
|
+
* Usage in SSR:
|
|
129
|
+
* ```typescript
|
|
130
|
+
* const serverLayer = Router.serverLayer({
|
|
131
|
+
* router: AppRouter,
|
|
132
|
+
* pathname: "/posts/42",
|
|
133
|
+
* search: "?sort=date",
|
|
134
|
+
* basePath: "/ssr/router"
|
|
135
|
+
* });
|
|
136
|
+
*
|
|
137
|
+
* const { element, dehydratedState } = yield* Router.CurrentRouteElement;
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export declare function serverLayer(options: ServerLayerOptions): Layer.Layer<CurrentRouteElement | History | Navigator, unknown, RouterHandlers | AtomRegistry.AtomRegistry>;
|
|
141
|
+
/**
|
|
142
|
+
* Create a browser layer for client-side hydration.
|
|
143
|
+
*
|
|
144
|
+
* This layer:
|
|
145
|
+
* 1. Sets up browser history with popstate listener
|
|
146
|
+
* 2. Checks RouterStateAtom for hydrated SSR state
|
|
147
|
+
* 3. If hydrated, uses that for initial render (skips loader)
|
|
148
|
+
* 4. Provides Navigator for subsequent navigation
|
|
149
|
+
*
|
|
150
|
+
* SSR hydration works automatically via atom hydration - no need to
|
|
151
|
+
* pass initialState manually. The RouterStateAtom is hydrated from
|
|
152
|
+
* __FIBRAE_STATE__ before this layer is created.
|
|
153
|
+
*
|
|
154
|
+
* Usage in client:
|
|
155
|
+
* ```typescript
|
|
156
|
+
* // Hydrate atoms first (includes RouterStateAtom)
|
|
157
|
+
* hydrate(container, app, window.__FIBRAE_STATE__);
|
|
158
|
+
*
|
|
159
|
+
* // Browser layer reads from hydrated RouterStateAtom
|
|
160
|
+
* const browserLayer = Router.browserLayer({
|
|
161
|
+
* router: AppRouter,
|
|
162
|
+
* basePath: "/ssr/router"
|
|
163
|
+
* });
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
export declare function browserLayer(options: BrowserLayerOptions): Layer.Layer<History | Navigator | CurrentRouteElement, unknown, AtomRegistry.AtomRegistry | RouterHandlers>;
|
|
167
|
+
export {};
|