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.
Files changed (70) hide show
  1. package/dist/components.d.ts +40 -0
  2. package/dist/components.js +63 -0
  3. package/dist/components.js.map +1 -0
  4. package/dist/core.d.ts +25 -0
  5. package/dist/core.js +46 -0
  6. package/dist/core.js.map +1 -0
  7. package/dist/dom.d.ts +16 -0
  8. package/dist/dom.js +67 -0
  9. package/dist/dom.js.map +1 -0
  10. package/dist/fiber-render.d.ts +33 -0
  11. package/dist/fiber-render.js +1069 -0
  12. package/dist/fiber-render.js.map +1 -0
  13. package/dist/h.d.ts +19 -0
  14. package/dist/h.js +26 -0
  15. package/dist/h.js.map +1 -0
  16. package/dist/hydration.d.ts +30 -0
  17. package/dist/hydration.js +375 -0
  18. package/dist/hydration.js.map +1 -0
  19. package/dist/index.d.ts +26 -0
  20. package/dist/index.js +28 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/jsx-runtime/index.d.ts +29 -0
  23. package/dist/jsx-runtime/index.js +61 -0
  24. package/dist/jsx-runtime/index.js.map +1 -0
  25. package/dist/render.d.ts +19 -0
  26. package/dist/render.js +325 -0
  27. package/dist/render.js.map +1 -0
  28. package/dist/router/History.d.ts +129 -0
  29. package/dist/router/History.js +241 -0
  30. package/dist/router/History.js.map +1 -0
  31. package/dist/router/Link.d.ts +52 -0
  32. package/dist/router/Link.js +131 -0
  33. package/dist/router/Link.js.map +1 -0
  34. package/dist/router/Navigator.d.ts +108 -0
  35. package/dist/router/Navigator.js +225 -0
  36. package/dist/router/Navigator.js.map +1 -0
  37. package/dist/router/Route.d.ts +65 -0
  38. package/dist/router/Route.js +143 -0
  39. package/dist/router/Route.js.map +1 -0
  40. package/dist/router/Router.d.ts +167 -0
  41. package/dist/router/Router.js +328 -0
  42. package/dist/router/Router.js.map +1 -0
  43. package/dist/router/RouterBuilder.d.ts +128 -0
  44. package/dist/router/RouterBuilder.js +112 -0
  45. package/dist/router/RouterBuilder.js.map +1 -0
  46. package/dist/router/RouterOutlet.d.ts +57 -0
  47. package/dist/router/RouterOutlet.js +132 -0
  48. package/dist/router/RouterOutlet.js.map +1 -0
  49. package/dist/router/RouterState.d.ts +102 -0
  50. package/dist/router/RouterState.js +94 -0
  51. package/dist/router/RouterState.js.map +1 -0
  52. package/dist/router/index.d.ts +28 -0
  53. package/dist/router/index.js +31 -0
  54. package/dist/router/index.js.map +1 -0
  55. package/dist/runtime.d.ts +55 -0
  56. package/dist/runtime.js +68 -0
  57. package/dist/runtime.js.map +1 -0
  58. package/dist/scope-utils.d.ts +14 -0
  59. package/dist/scope-utils.js +29 -0
  60. package/dist/scope-utils.js.map +1 -0
  61. package/dist/server.d.ts +112 -0
  62. package/dist/server.js +313 -0
  63. package/dist/server.js.map +1 -0
  64. package/dist/shared.d.ts +136 -0
  65. package/dist/shared.js +53 -0
  66. package/dist/shared.js.map +1 -0
  67. package/dist/tracking.d.ts +23 -0
  68. package/dist/tracking.js +53 -0
  69. package/dist/tracking.js.map +1 -0
  70. package/package.json +62 -0
@@ -0,0 +1,328 @@
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 * as Option from "effect/Option";
15
+ import * as Effect from "effect/Effect";
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, MemoryHistoryLive } from "./History.js";
20
+ import { Navigator, NavigatorLive } from "./Navigator.js";
21
+ import { RouterHandlers } from "./RouterBuilder.js";
22
+ import { RouterStateAtom } from "./RouterState.js";
23
+ /**
24
+ * Create a route group with the given name.
25
+ * Routes are added via group.add(route).
26
+ */
27
+ export function group(name) {
28
+ return {
29
+ name,
30
+ routes: [],
31
+ add(route) {
32
+ return {
33
+ ...this,
34
+ routes: [...this.routes, route],
35
+ };
36
+ },
37
+ };
38
+ }
39
+ /**
40
+ * Create a router with the given name.
41
+ * Groups are added via router.add(group).
42
+ */
43
+ export function make(name) {
44
+ return {
45
+ name,
46
+ groups: [],
47
+ add(g) {
48
+ return {
49
+ ...this,
50
+ groups: [...this.groups, g],
51
+ };
52
+ },
53
+ matchRoute(pathname) {
54
+ // Try to match against each route in each group
55
+ for (const g of this.groups) {
56
+ for (const route of g.routes) {
57
+ const match = route.match(pathname);
58
+ if (Option.isSome(match)) {
59
+ return Option.some({
60
+ groupName: g.name,
61
+ route,
62
+ params: match.value,
63
+ });
64
+ }
65
+ }
66
+ }
67
+ return Option.none();
68
+ },
69
+ };
70
+ }
71
+ /**
72
+ * Service tag for the current route's rendered element.
73
+ * Used by SSR to provide the matched route's component.
74
+ */
75
+ export class CurrentRouteElement extends Context.Tag("fibrae/CurrentRouteElement")() {
76
+ }
77
+ /**
78
+ * Parse search params from URL search string.
79
+ */
80
+ function parseSearchParams(search) {
81
+ const params = {};
82
+ const searchString = search.startsWith("?") ? search.slice(1) : search;
83
+ if (!searchString)
84
+ return params;
85
+ const searchParams = new URLSearchParams(searchString);
86
+ searchParams.forEach((value, key) => {
87
+ params[key] = value;
88
+ });
89
+ return params;
90
+ }
91
+ /**
92
+ * Strip basePath prefix from pathname for route matching.
93
+ */
94
+ function stripBasePath(pathname, basePath) {
95
+ if (!basePath || basePath === "/") {
96
+ return pathname;
97
+ }
98
+ // Normalize: remove trailing slash from basePath
99
+ const normalizedBase = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
100
+ if (pathname.startsWith(normalizedBase)) {
101
+ const stripped = pathname.slice(normalizedBase.length);
102
+ // Ensure we return "/" not "" for root
103
+ return stripped || "/";
104
+ }
105
+ return pathname;
106
+ }
107
+ /**
108
+ * Create a server-side layer for SSR rendering.
109
+ *
110
+ * This layer:
111
+ * 1. Matches the pathname against the router
112
+ * 2. Runs the matched route's loader
113
+ * 3. Renders the component with loader data
114
+ * 4. Provides the rendered element and dehydrated state
115
+ *
116
+ * Usage in SSR:
117
+ * ```typescript
118
+ * const serverLayer = Router.serverLayer({
119
+ * router: AppRouter,
120
+ * pathname: "/posts/42",
121
+ * search: "?sort=date",
122
+ * basePath: "/ssr/router"
123
+ * });
124
+ *
125
+ * const { element, dehydratedState } = yield* Router.CurrentRouteElement;
126
+ * ```
127
+ */
128
+ export function serverLayer(options) {
129
+ const { router, pathname, search = "", basePath = "" } = options;
130
+ const searchParams = parseSearchParams(search);
131
+ // Strip basePath from pathname for route matching
132
+ const matchPathname = stripBasePath(pathname, basePath);
133
+ // Create memory history for SSR (static, no navigation)
134
+ const historyLayer = MemoryHistoryLive({
135
+ initialPathname: pathname,
136
+ initialSearch: search ? `?${search.replace(/^\?/, "")}` : "",
137
+ });
138
+ // Create navigator layer with basePath - needs History and AtomRegistry
139
+ // We provide History here, AtomRegistry comes from outside
140
+ const navigatorLayer = Layer.provideMerge(NavigatorLive(router, { basePath }), historyLayer);
141
+ // Create route element layer
142
+ const routeElementLayer = Layer.effect(CurrentRouteElement, Effect.gen(function* () {
143
+ const routerHandlers = yield* RouterHandlers;
144
+ const registry = yield* AtomRegistry.AtomRegistry;
145
+ // Match route using stripped pathname
146
+ const match = router.matchRoute(matchPathname);
147
+ if (Option.isNone(match)) {
148
+ return yield* Effect.fail(new Error(`No route matched pathname: ${matchPathname}`));
149
+ }
150
+ const { route, params } = match.value;
151
+ // Get handler for this route
152
+ const handler = routerHandlers.getHandler(route.name);
153
+ if (Option.isNone(handler)) {
154
+ return yield* Effect.fail(new Error(`No handler found for route: ${route.name}`));
155
+ }
156
+ // Execute loader and render component
157
+ const loaderCtx = { path: params, searchParams };
158
+ const loaderData = yield* handler.value.loader(loaderCtx);
159
+ const element = handler.value.component({
160
+ loaderData,
161
+ path: params,
162
+ searchParams,
163
+ });
164
+ const state = {
165
+ routeName: route.name,
166
+ params,
167
+ searchParams,
168
+ loaderData,
169
+ };
170
+ // Set RouterStateAtom so it gets included in dehydrated state
171
+ registry.set(RouterStateAtom, Option.some(state));
172
+ return { element, state };
173
+ }));
174
+ return Layer.mergeAll(historyLayer, navigatorLayer, routeElementLayer);
175
+ }
176
+ /**
177
+ * Create a browser layer for client-side hydration.
178
+ *
179
+ * This layer:
180
+ * 1. Sets up browser history with popstate listener
181
+ * 2. Checks RouterStateAtom for hydrated SSR state
182
+ * 3. If hydrated, uses that for initial render (skips loader)
183
+ * 4. Provides Navigator for subsequent navigation
184
+ *
185
+ * SSR hydration works automatically via atom hydration - no need to
186
+ * pass initialState manually. The RouterStateAtom is hydrated from
187
+ * __FIBRAE_STATE__ before this layer is created.
188
+ *
189
+ * Usage in client:
190
+ * ```typescript
191
+ * // Hydrate atoms first (includes RouterStateAtom)
192
+ * hydrate(container, app, window.__FIBRAE_STATE__);
193
+ *
194
+ * // Browser layer reads from hydrated RouterStateAtom
195
+ * const browserLayer = Router.browserLayer({
196
+ * router: AppRouter,
197
+ * basePath: "/ssr/router"
198
+ * });
199
+ * ```
200
+ */
201
+ export function browserLayer(options) {
202
+ const { router, basePath = "" } = options;
203
+ // Import BrowserHistoryLive dynamically to avoid server-side issues
204
+ // For now, we'll use a scoped effect to create it
205
+ const historyLayer = Layer.scoped(History, Effect.gen(function* () {
206
+ const registry = yield* AtomRegistry.AtomRegistry;
207
+ const { Atom } = yield* Effect.promise(() => import("@effect-atom/atom"));
208
+ // Create location atom with initial browser location
209
+ const getBrowserLocation = () => ({
210
+ pathname: window.location.pathname,
211
+ search: window.location.search,
212
+ hash: window.location.hash,
213
+ state: window.history.state,
214
+ });
215
+ const locationAtom = Atom.make(getBrowserLocation());
216
+ // Subscribe to popstate for browser back/forward
217
+ const handlePopState = () => {
218
+ registry.set(locationAtom, getBrowserLocation());
219
+ };
220
+ window.addEventListener("popstate", handlePopState);
221
+ // Cleanup on scope close
222
+ yield* Effect.addFinalizer(() => Effect.sync(() => {
223
+ window.removeEventListener("popstate", handlePopState);
224
+ }));
225
+ // Track history index for canGoBack
226
+ let historyIndex = 0;
227
+ const parseLocation = (href, state) => {
228
+ const url = href.startsWith("/")
229
+ ? new URL(href, "http://localhost")
230
+ : new URL(href);
231
+ return {
232
+ pathname: url.pathname,
233
+ search: url.search,
234
+ hash: url.hash,
235
+ state,
236
+ };
237
+ };
238
+ return {
239
+ location: locationAtom,
240
+ push: (path, state) => Effect.sync(() => {
241
+ const location = parseLocation(path, state);
242
+ const fullPath = `${location.pathname}${location.search}${location.hash}`;
243
+ window.history.pushState(state, "", fullPath);
244
+ historyIndex++;
245
+ registry.set(locationAtom, { ...location, state });
246
+ }),
247
+ replace: (path, state) => Effect.sync(() => {
248
+ const location = parseLocation(path, state);
249
+ const fullPath = `${location.pathname}${location.search}${location.hash}`;
250
+ window.history.replaceState(state, "", fullPath);
251
+ registry.set(locationAtom, { ...location, state });
252
+ }),
253
+ back: Effect.sync(() => {
254
+ window.history.back();
255
+ }),
256
+ forward: Effect.sync(() => {
257
+ window.history.forward();
258
+ }),
259
+ go: (n) => Effect.sync(() => {
260
+ window.history.go(n);
261
+ }),
262
+ canGoBack: Effect.sync(() => historyIndex > 0),
263
+ };
264
+ }));
265
+ const navigatorLayer = NavigatorLive(router, { basePath });
266
+ // Create route element layer - checks RouterStateAtom for hydrated state
267
+ // If hydrated, uses that; otherwise matches and runs loader
268
+ const routeElementLayer = Layer.effect(CurrentRouteElement, Effect.gen(function* () {
269
+ const routerHandlers = yield* RouterHandlers;
270
+ const registry = yield* AtomRegistry.AtomRegistry;
271
+ // Check if RouterStateAtom was hydrated from SSR
272
+ const hydratedState = registry.get(RouterStateAtom);
273
+ if (Option.isSome(hydratedState)) {
274
+ // Hydration mode: reuse SSR data from RouterStateAtom
275
+ const state = hydratedState.value;
276
+ const handler = routerHandlers.getHandler(state.routeName);
277
+ if (Option.isNone(handler)) {
278
+ return yield* Effect.fail(new Error(`No handler found for route: ${state.routeName}`));
279
+ }
280
+ // Render component with SSR loader data (skip loader)
281
+ const element = handler.value.component({
282
+ loaderData: state.loaderData,
283
+ path: state.params,
284
+ searchParams: state.searchParams,
285
+ });
286
+ // Cast RouterState to DehydratedRouterState (same shape)
287
+ const dehydratedState = {
288
+ routeName: state.routeName,
289
+ params: state.params,
290
+ searchParams: state.searchParams,
291
+ loaderData: state.loaderData,
292
+ };
293
+ return { element, state: dehydratedState };
294
+ }
295
+ // Non-hydration mode: match and run loader
296
+ const pathname = window.location.pathname;
297
+ const matchPathname = stripBasePath(pathname, basePath);
298
+ const search = window.location.search;
299
+ const searchParams = parseSearchParams(search);
300
+ const match = router.matchRoute(matchPathname);
301
+ if (Option.isNone(match)) {
302
+ return yield* Effect.fail(new Error(`No route matched pathname: ${matchPathname}`));
303
+ }
304
+ const { route, params } = match.value;
305
+ const handler = routerHandlers.getHandler(route.name);
306
+ if (Option.isNone(handler)) {
307
+ return yield* Effect.fail(new Error(`No handler found for route: ${route.name}`));
308
+ }
309
+ const loaderCtx = { path: params, searchParams };
310
+ const loaderData = yield* handler.value.loader(loaderCtx);
311
+ const element = handler.value.component({
312
+ loaderData,
313
+ path: params,
314
+ searchParams,
315
+ });
316
+ const state = {
317
+ routeName: route.name,
318
+ params,
319
+ searchParams,
320
+ loaderData,
321
+ };
322
+ // Set RouterStateAtom for DI access
323
+ registry.set(RouterStateAtom, Option.some(state));
324
+ return { element, state };
325
+ }));
326
+ return Layer.mergeAll(historyLayer, Layer.provideMerge(navigatorLayer, historyLayer), routeElementLayer);
327
+ }
328
+ //# sourceMappingURL=Router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Router.js","sourceRoot":"","sources":["../../src/router/Router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * RouterBuilder module for implementing route handlers.
3
+ *
4
+ * Mirrors Effect HttpApiBuilder patterns:
5
+ * - RouterBuilder.group(router, "groupName", (handlers) => Effect.gen(...))
6
+ * - handlers.handle("routeName", { loader, component })
7
+ * - RouterBuilder.router(Router) builds the final Layer
8
+ */
9
+ import * as Effect from "effect/Effect";
10
+ import * as Context from "effect/Context";
11
+ import * as Layer from "effect/Layer";
12
+ import * as Option from "effect/Option";
13
+ import type { Router } from "./Router.js";
14
+ import type { Route } from "./Route.js";
15
+ import type { VElement } from "../shared.js";
16
+ /**
17
+ * Context for loader/component execution.
18
+ * Provides decoded path and search parameters.
19
+ */
20
+ export interface LoaderContext<PathParams extends Record<string, unknown> = Record<string, unknown>, SearchParams extends Record<string, unknown> = Record<string, unknown>> {
21
+ readonly path: PathParams;
22
+ readonly searchParams: SearchParams;
23
+ }
24
+ /**
25
+ * Component props including loader data and route parameters.
26
+ */
27
+ export interface ComponentProps<LoaderData = unknown, PathParams extends Record<string, unknown> = Record<string, unknown>, SearchParams extends Record<string, unknown> = Record<string, unknown>> {
28
+ readonly loaderData: LoaderData;
29
+ readonly path: PathParams;
30
+ readonly searchParams: SearchParams;
31
+ }
32
+ /**
33
+ * Loader return type - can be a plain value or an Effect.
34
+ * Plain values are automatically wrapped in Effect.succeed.
35
+ */
36
+ export type LoaderResult<T, E = never, R = never> = T | Effect.Effect<T, E, R>;
37
+ /**
38
+ * Handler configuration for a route.
39
+ * Loader fetches data, component renders with that data.
40
+ *
41
+ * - loader is optional - defaults to returning null
42
+ * - loader can return a plain value or an Effect (plain values auto-wrapped)
43
+ */
44
+ export interface HandlerConfig<LoaderData = unknown, PathParams extends Record<string, unknown> = Record<string, unknown>, SearchParams extends Record<string, unknown> = Record<string, unknown>, R = never, E = never> {
45
+ readonly loader?: (ctx: LoaderContext<PathParams, SearchParams>) => LoaderResult<LoaderData, E, R>;
46
+ readonly component: (props: ComponentProps<LoaderData, PathParams, SearchParams>) => VElement;
47
+ }
48
+ /**
49
+ * A registered route handler (type-erased for storage).
50
+ * Use executeRoute for type-safe execution.
51
+ */
52
+ export interface RouteHandler {
53
+ readonly routeName: string;
54
+ readonly route: Route;
55
+ readonly loader: (ctx: LoaderContext) => Effect.Effect<unknown>;
56
+ readonly component: (props: ComponentProps) => VElement;
57
+ }
58
+ /**
59
+ * Handlers builder for a route group.
60
+ * Accumulates handler registrations for routes in the group.
61
+ */
62
+ export interface GroupHandlers<GroupName extends string = string> {
63
+ readonly groupName: GroupName;
64
+ readonly handlers: readonly RouteHandler[];
65
+ /**
66
+ * Register a handler for a route in this group.
67
+ */
68
+ readonly handle: <RouteName extends string, LoaderData, PathParams extends Record<string, unknown>, SearchParams extends Record<string, unknown>, R, E>(routeName: RouteName, config: HandlerConfig<LoaderData, PathParams, SearchParams, R, E>) => GroupHandlers<GroupName>;
69
+ }
70
+ declare const RouterHandlers_base: Context.TagClass<RouterHandlers, "fibrae/RouterHandlers", {
71
+ readonly handlers: ReadonlyMap<string, RouteHandler>;
72
+ readonly getHandler: (routeName: string) => Option.Option<RouteHandler>;
73
+ }>;
74
+ /**
75
+ * Tag for the RouterHandlers service.
76
+ * Provides access to all registered handlers.
77
+ */
78
+ export declare class RouterHandlers extends RouterHandlers_base {
79
+ }
80
+ /**
81
+ * Create a Layer that provides handlers for a route group.
82
+ *
83
+ * The build callback can return either:
84
+ * - GroupHandlers directly (simple case)
85
+ * - Effect<GroupHandlers> (when you need Effect context in handlers)
86
+ *
87
+ * Usage:
88
+ * ```typescript
89
+ * // Simple - no Effect wrapper needed
90
+ * const AppRoutesLive = RouterBuilder.group(
91
+ * AppRouter,
92
+ * "app",
93
+ * (handlers) => handlers
94
+ * .handle("home", { component: () => <HomePage /> })
95
+ * .handle("posts", { loader: () => fetchPosts(), component: ... })
96
+ * )
97
+ *
98
+ * // With Effect context (when handlers need services)
99
+ * const AppRoutesLive = RouterBuilder.group(
100
+ * AppRouter,
101
+ * "app",
102
+ * (handlers) => Effect.gen(function* () {
103
+ * const config = yield* Config;
104
+ * return handlers.handle("home", { ... });
105
+ * })
106
+ * )
107
+ * ```
108
+ */
109
+ export declare function group<GroupName extends string>(router: Router, groupName: GroupName, build: (handlers: GroupHandlers<GroupName>) => GroupHandlers<GroupName>): Layer.Layer<RouterHandlers, never, never>;
110
+ export declare function group<GroupName extends string, R>(router: Router, groupName: GroupName, build: (handlers: GroupHandlers<GroupName>) => Effect.Effect<GroupHandlers<GroupName>, never, R>): Layer.Layer<RouterHandlers, never, R>;
111
+ /**
112
+ * Merge multiple group handler layers into a single RouterHandlers layer.
113
+ *
114
+ * Usage:
115
+ * ```typescript
116
+ * const AppRouterLive = RouterBuilder.router(AppRouter).pipe(
117
+ * Layer.provide(AppRoutesLive),
118
+ * Layer.provide(ApiRoutesLive)
119
+ * )
120
+ * ```
121
+ */
122
+ export declare function router(_router: Router): Layer.Layer<RouterHandlers>;
123
+ /**
124
+ * Execute a route's loader and render its component.
125
+ * Returns the rendered VElement.
126
+ */
127
+ export declare function executeRoute(handler: RouteHandler, ctx: LoaderContext): Effect.Effect<VElement>;
128
+ export {};
@@ -0,0 +1,112 @@
1
+ /**
2
+ * RouterBuilder module for implementing route handlers.
3
+ *
4
+ * Mirrors Effect HttpApiBuilder patterns:
5
+ * - RouterBuilder.group(router, "groupName", (handlers) => Effect.gen(...))
6
+ * - handlers.handle("routeName", { loader, component })
7
+ * - RouterBuilder.router(Router) builds the final Layer
8
+ */
9
+ import * as Effect from "effect/Effect";
10
+ import * as Context from "effect/Context";
11
+ import * as Layer from "effect/Layer";
12
+ import * as Option from "effect/Option";
13
+ /**
14
+ * Tag for the RouterHandlers service.
15
+ * Provides access to all registered handlers.
16
+ */
17
+ export class RouterHandlers extends Context.Tag("fibrae/RouterHandlers")() {
18
+ }
19
+ /**
20
+ * Create handlers builder for a route group.
21
+ */
22
+ function makeGroupHandlers(groupName, group) {
23
+ const routesByName = new Map(group.routes.map((r) => [r.name, r]));
24
+ const buildHandlers = (handlers) => ({
25
+ groupName,
26
+ handlers,
27
+ handle(routeName, config) {
28
+ const maybeRoute = Option.fromNullable(routesByName.get(routeName));
29
+ if (Option.isNone(maybeRoute)) {
30
+ throw new Error(`Route "${routeName}" not found in group "${groupName}"`);
31
+ }
32
+ const route = maybeRoute.value;
33
+ // Normalize loader: default to returning null, wrap plain values in Effect
34
+ const normalizedLoader = (ctx) => {
35
+ if (!config.loader) {
36
+ return Effect.succeed(null);
37
+ }
38
+ const result = config.loader(ctx);
39
+ return (Effect.isEffect(result) ? result : Effect.succeed(result));
40
+ };
41
+ const handler = {
42
+ routeName,
43
+ route,
44
+ loader: normalizedLoader,
45
+ component: config.component,
46
+ };
47
+ return buildHandlers([...handlers, handler]);
48
+ },
49
+ });
50
+ return buildHandlers([]);
51
+ }
52
+ /**
53
+ * Find a route group by name in a router.
54
+ */
55
+ function findGroup(router, groupName) {
56
+ const maybeGroup = Option.fromNullable(router.groups.find((g) => g.name === groupName));
57
+ if (Option.isNone(maybeGroup)) {
58
+ throw new Error(`Group "${groupName}" not found in router "${router.name}"`);
59
+ }
60
+ return maybeGroup.value;
61
+ }
62
+ export function group(router, groupName, build) {
63
+ const routeGroup = findGroup(router, groupName);
64
+ const initialHandlers = makeGroupHandlers(groupName, routeGroup);
65
+ return Layer.effect(RouterHandlers, Effect.gen(function* () {
66
+ const result = build(initialHandlers);
67
+ const builtHandlers = Effect.isEffect(result) ? yield* result : result;
68
+ const handlersMap = new Map(builtHandlers.handlers.map((h) => [h.routeName, h]));
69
+ return {
70
+ handlers: handlersMap,
71
+ getHandler(routeName) {
72
+ const handler = handlersMap.get(routeName);
73
+ return handler ? Option.some(handler) : Option.none();
74
+ },
75
+ };
76
+ }));
77
+ }
78
+ /**
79
+ * Merge multiple group handler layers into a single RouterHandlers layer.
80
+ *
81
+ * Usage:
82
+ * ```typescript
83
+ * const AppRouterLive = RouterBuilder.router(AppRouter).pipe(
84
+ * Layer.provide(AppRoutesLive),
85
+ * Layer.provide(ApiRoutesLive)
86
+ * )
87
+ * ```
88
+ */
89
+ export function router(_router) {
90
+ // Start with an empty handlers layer
91
+ return Layer.succeed(RouterHandlers, {
92
+ handlers: new Map(),
93
+ getHandler(_routeName) {
94
+ return Option.none();
95
+ },
96
+ });
97
+ }
98
+ /**
99
+ * Execute a route's loader and render its component.
100
+ * Returns the rendered VElement.
101
+ */
102
+ export function executeRoute(handler, ctx) {
103
+ return Effect.gen(function* () {
104
+ const loaderData = yield* handler.loader(ctx);
105
+ return handler.component({
106
+ loaderData,
107
+ path: ctx.path,
108
+ searchParams: ctx.searchParams,
109
+ });
110
+ });
111
+ }
112
+ //# sourceMappingURL=RouterBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RouterBuilder.js","sourceRoot":"","sources":["../../src/router/RouterBuilder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * RouterOutlet - Reactive route rendering component.
3
+ *
4
+ * Subscribes to Navigator.currentRoute and renders the matched route's component.
5
+ * When route changes, runs the new route's loader and renders the component.
6
+ *
7
+ * For SSR hydration, the RouterStateAtom is pre-populated and the loader is skipped
8
+ * on first render.
9
+ *
10
+ * Usage:
11
+ * ```typescript
12
+ * function App() {
13
+ * return (
14
+ * <div>
15
+ * <Nav />
16
+ * <RouterOutlet />
17
+ * </div>
18
+ * );
19
+ * }
20
+ * ```
21
+ */
22
+ import * as Stream from "effect/Stream";
23
+ import { Registry as AtomRegistry } from "@effect-atom/atom";
24
+ import { Navigator } from "./Navigator.js";
25
+ import { RouterHandlers } from "./RouterBuilder.js";
26
+ import type { VElement } from "../shared.js";
27
+ /**
28
+ * Props for RouterOutlet component.
29
+ *
30
+ * @deprecated Props are no longer needed - SSR hydration is handled via RouterStateAtom.
31
+ */
32
+ export interface RouterOutletProps {
33
+ /**
34
+ * @deprecated Use RouterStateAtom hydration instead.
35
+ * Initial loader data from SSR - skips loader on first render.
36
+ */
37
+ readonly initialLoaderData?: unknown;
38
+ /**
39
+ * @deprecated Use RouterStateAtom hydration instead.
40
+ * Initial route name from SSR - used with initialLoaderData.
41
+ */
42
+ readonly initialRouteName?: string;
43
+ }
44
+ /**
45
+ * RouterOutlet component for reactive route rendering.
46
+ *
47
+ * The RouterOutlet:
48
+ * 1. Checks if RouterStateAtom has data (SSR hydration case)
49
+ * 2. Subscribes to Navigator.currentRoute for navigation changes
50
+ * 3. When route changes, runs the matched route's loader
51
+ * 4. Updates RouterStateAtom with the full state (for DI access)
52
+ * 5. Renders the route's component with loader data
53
+ *
54
+ * For SSR hydration, the RouterStateAtom is pre-populated by the server,
55
+ * so the first render uses that data and skips the loader.
56
+ */
57
+ export declare function RouterOutlet(_props?: RouterOutletProps): Stream.Stream<VElement, unknown, Navigator | RouterHandlers | AtomRegistry.AtomRegistry>;