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,241 @@
1
+ /**
2
+ * History service - track and manage browser navigation history.
3
+ *
4
+ * Features:
5
+ * - Current location (pathname, search, hash, state)
6
+ * - Navigation methods (push, replace, back, forward, go)
7
+ * - BrowserHistoryLive - real browser history with popstate handling
8
+ * - MemoryHistoryLive - in-memory history for testing/SSR
9
+ * - Cleanly manages event listeners with Effect finalizers
10
+ *
11
+ * Design:
12
+ * - Uses Atom for reactive location updates
13
+ * - Browser history: listens to window.popstate for back/forward
14
+ * - Memory history: in-memory stack, useful for SSR/testing
15
+ * - All navigation returns Effects, integrates with Effect runtime
16
+ */
17
+ import * as Effect from "effect/Effect";
18
+ import * as Context from "effect/Context";
19
+ import * as Layer from "effect/Layer";
20
+ import { Atom, Registry as AtomRegistry } from "@effect-atom/atom";
21
+ // =============================================================================
22
+ // Service Tag
23
+ // =============================================================================
24
+ /**
25
+ * History service tag for Effect dependency injection.
26
+ */
27
+ export class History extends Context.Tag("fibrae/History")() {
28
+ }
29
+ // =============================================================================
30
+ // Browser History Implementation
31
+ // =============================================================================
32
+ /**
33
+ * Get current browser location.
34
+ */
35
+ function getBrowserLocation() {
36
+ return {
37
+ pathname: window.location.pathname,
38
+ search: window.location.search,
39
+ hash: window.location.hash,
40
+ state: window.history.state,
41
+ };
42
+ }
43
+ /**
44
+ * Parse path into location object.
45
+ */
46
+ function parseLocation(path, state) {
47
+ try {
48
+ const url = new URL(path, window.location.origin);
49
+ return {
50
+ pathname: url.pathname,
51
+ search: url.search,
52
+ hash: url.hash,
53
+ state,
54
+ };
55
+ }
56
+ catch {
57
+ return {
58
+ pathname: path,
59
+ search: "",
60
+ hash: "",
61
+ state,
62
+ };
63
+ }
64
+ }
65
+ /**
66
+ * Browser history layer - real browser history with popstate handling.
67
+ *
68
+ * Features:
69
+ * - Tracks current location in an Atom
70
+ * - Listens to popstate for back/forward
71
+ * - Provides push/replace/go navigation methods
72
+ * - Cleanly removes event listener on scope close
73
+ * - Properly cleans up event listeners on scope close
74
+ */
75
+ /* is-tree-shakable-suppress */
76
+ export const BrowserHistoryLive = Layer.scoped(History, Effect.gen(function* () {
77
+ const registry = yield* AtomRegistry.AtomRegistry;
78
+ // Create location atom with initial browser location
79
+ const locationAtom = Atom.make(getBrowserLocation());
80
+ // Subscribe to popstate for browser back/forward
81
+ const handlePopState = () => {
82
+ registry.set(locationAtom, getBrowserLocation());
83
+ };
84
+ window.addEventListener("popstate", handlePopState);
85
+ // Cleanup on scope close
86
+ yield* Effect.addFinalizer(() => Effect.sync(() => {
87
+ window.removeEventListener("popstate", handlePopState);
88
+ }));
89
+ // Track history index for canGoBack (simplified approach)
90
+ let historyIndex = 0;
91
+ const service = {
92
+ location: locationAtom,
93
+ push: (path, state) => Effect.sync(() => {
94
+ const location = parseLocation(path, state);
95
+ const href = `${location.pathname}${location.search}${location.hash}`;
96
+ window.history.pushState(state, "", href);
97
+ historyIndex++;
98
+ registry.set(locationAtom, {
99
+ ...location,
100
+ state,
101
+ });
102
+ }),
103
+ replace: (path, state) => Effect.sync(() => {
104
+ const location = parseLocation(path, state);
105
+ const href = `${location.pathname}${location.search}${location.hash}`;
106
+ window.history.replaceState(state, "", href);
107
+ registry.set(locationAtom, {
108
+ ...location,
109
+ state,
110
+ });
111
+ }),
112
+ back: Effect.sync(() => {
113
+ window.history.back();
114
+ // Note: popstate handler will update location
115
+ }),
116
+ forward: Effect.sync(() => {
117
+ window.history.forward();
118
+ // Note: popstate handler will update location
119
+ }),
120
+ go: (n) => Effect.sync(() => {
121
+ window.history.go(n);
122
+ // Note: popstate handler will update location
123
+ }),
124
+ canGoBack: Effect.sync(() => historyIndex > 0),
125
+ };
126
+ return service;
127
+ }));
128
+ /**
129
+ * Create a memory history layer - useful for testing/SSR.
130
+ *
131
+ * Features:
132
+ * - In-memory navigation stack
133
+ * - Tracks current location in an Atom
134
+ * - No browser API usage (safe for SSR/testing)
135
+ * - Supports push/replace/back/forward/go navigation
136
+ * - Optional initial location/state configuration
137
+ */
138
+ export function MemoryHistoryLive(options = {}) {
139
+ return Layer.scoped(History, Effect.gen(function* () {
140
+ const registry = yield* AtomRegistry.AtomRegistry;
141
+ // Create location atom with initial location
142
+ const initialLocation = {
143
+ pathname: options.initialPathname ?? "/",
144
+ search: options.initialSearch ?? "",
145
+ hash: options.initialHash ?? "",
146
+ state: options.initialState,
147
+ };
148
+ const locationAtom = Atom.make(initialLocation);
149
+ // Track history stack for back/forward
150
+ const historyStack = [initialLocation];
151
+ let historyIndex = 0;
152
+ const service = {
153
+ location: locationAtom,
154
+ push: (path, state) => Effect.sync(() => {
155
+ const location = parseLocation(path, state);
156
+ // Remove entries after current index
157
+ historyStack.splice(historyIndex + 1);
158
+ // Add new entry
159
+ historyStack.push(location);
160
+ historyIndex = historyStack.length - 1;
161
+ registry.set(locationAtom, location);
162
+ }),
163
+ replace: (path, state) => Effect.sync(() => {
164
+ const location = parseLocation(path, state);
165
+ // Replace current entry
166
+ historyStack[historyIndex] = location;
167
+ registry.set(locationAtom, location);
168
+ }),
169
+ back: Effect.sync(() => {
170
+ if (historyIndex > 0) {
171
+ historyIndex--;
172
+ registry.set(locationAtom, historyStack[historyIndex]);
173
+ }
174
+ }),
175
+ forward: Effect.sync(() => {
176
+ if (historyIndex < historyStack.length - 1) {
177
+ historyIndex++;
178
+ registry.set(locationAtom, historyStack[historyIndex]);
179
+ }
180
+ }),
181
+ go: (n) => Effect.sync(() => {
182
+ const newIndex = historyIndex + n;
183
+ if (newIndex >= 0 && newIndex < historyStack.length) {
184
+ historyIndex = newIndex;
185
+ registry.set(locationAtom, historyStack[historyIndex]);
186
+ }
187
+ }),
188
+ canGoBack: Effect.sync(() => historyIndex > 0),
189
+ };
190
+ return service;
191
+ }));
192
+ }
193
+ // =============================================================================
194
+ // Convenience Accessors
195
+ // =============================================================================
196
+ /**
197
+ * Get current location.
198
+ */
199
+ /* is-tree-shakable-suppress */
200
+ export const getLocation = Effect.gen(function* () {
201
+ const history = yield* History;
202
+ return yield* Atom.get(history.location);
203
+ });
204
+ /**
205
+ * Push a new location.
206
+ */
207
+ export const push = (path, state) => Effect.gen(function* () {
208
+ const history = yield* History;
209
+ yield* history.push(path, state);
210
+ });
211
+ /**
212
+ * Replace current location.
213
+ */
214
+ export const replace = (path, state) => Effect.gen(function* () {
215
+ const history = yield* History;
216
+ yield* history.replace(path, state);
217
+ });
218
+ /**
219
+ * Go back in history.
220
+ */
221
+ /* is-tree-shakable-suppress */
222
+ export const back = Effect.gen(function* () {
223
+ const history = yield* History;
224
+ yield* history.back;
225
+ });
226
+ /**
227
+ * Go forward in history.
228
+ */
229
+ /* is-tree-shakable-suppress */
230
+ export const forward = Effect.gen(function* () {
231
+ const history = yield* History;
232
+ yield* history.forward;
233
+ });
234
+ /**
235
+ * Go n entries in history.
236
+ */
237
+ export const go = (n) => Effect.gen(function* () {
238
+ const history = yield* History;
239
+ yield* history.go(n);
240
+ });
241
+ //# sourceMappingURL=History.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"History.js","sourceRoot":"","sources":["../../src/router/History.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Link component - declarative navigation.
3
+ *
4
+ * Renders an <a> element with correct href and handles click for SPA navigation.
5
+ * - <Link to="routeName">text</Link>
6
+ * - <Link to="routeName" params={{ id: 123 }}>text</Link>
7
+ * - <Link to="routeName" search={{ sort: "date" }}>text</Link>
8
+ * - <Link to="routeName" replace>text</Link>
9
+ *
10
+ * Design: href is pre-computed for SSR/accessibility. onClick prevents default
11
+ * and uses Navigator for SPA navigation.
12
+ */
13
+ import * as Effect from "effect/Effect";
14
+ import { Registry as AtomRegistry } from "@effect-atom/atom";
15
+ import { Navigator } from "./Navigator.js";
16
+ import type { Router } from "./Router.js";
17
+ import type { VElement } from "../shared.js";
18
+ /**
19
+ * Props for the Link component.
20
+ */
21
+ export interface LinkProps {
22
+ /** Route name to navigate to */
23
+ readonly to: string;
24
+ /** Path parameters for the route */
25
+ readonly params?: Record<string, unknown>;
26
+ /** Search/query parameters */
27
+ readonly search?: Record<string, unknown>;
28
+ /** Use history.replace instead of push */
29
+ readonly replace?: boolean;
30
+ /** Additional CSS class names */
31
+ readonly class?: string;
32
+ /** Active class name (default: "active") */
33
+ readonly activeClass?: string;
34
+ /** Data attributes for testing */
35
+ readonly "data-cy"?: string;
36
+ /** Children to render inside the anchor (already normalized by JSX runtime) */
37
+ readonly children?: ReadonlyArray<VElement>;
38
+ }
39
+ /**
40
+ * Create a Link component bound to a router.
41
+ *
42
+ * The Link component must know which router to use for:
43
+ * - Building hrefs from route names
44
+ * - Checking active state
45
+ *
46
+ * Usage:
47
+ * ```typescript
48
+ * const Link = createLink(appRouter);
49
+ * <Link to="posts" params={{ id: 123 }}>View Post</Link>
50
+ * ```
51
+ */
52
+ export declare function createLink(router: Router): (props: LinkProps) => Effect.Effect<VElement, never, Navigator | AtomRegistry.AtomRegistry>;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Link component - declarative navigation.
3
+ *
4
+ * Renders an <a> element with correct href and handles click for SPA navigation.
5
+ * - <Link to="routeName">text</Link>
6
+ * - <Link to="routeName" params={{ id: 123 }}>text</Link>
7
+ * - <Link to="routeName" search={{ sort: "date" }}>text</Link>
8
+ * - <Link to="routeName" replace>text</Link>
9
+ *
10
+ * Design: href is pre-computed for SSR/accessibility. onClick prevents default
11
+ * and uses Navigator for SPA navigation.
12
+ */
13
+ import * as Effect from "effect/Effect";
14
+ import * as Option from "effect/Option";
15
+ import { Atom, Registry as AtomRegistry } from "@effect-atom/atom";
16
+ import { Navigator } from "./Navigator.js";
17
+ // =============================================================================
18
+ // Helpers
19
+ // =============================================================================
20
+ /**
21
+ * Find a route by name in the router.
22
+ */
23
+ function findRouteByName(router, name) {
24
+ for (const group of router.groups) {
25
+ for (const route of group.routes) {
26
+ if (route.name === name) {
27
+ return Option.some(route);
28
+ }
29
+ }
30
+ }
31
+ return Option.none();
32
+ }
33
+ /**
34
+ * Build search string from params object.
35
+ */
36
+ function buildSearchString(params) {
37
+ const searchParams = new URLSearchParams();
38
+ for (const [key, value] of Object.entries(params)) {
39
+ if (value !== undefined && value !== null) {
40
+ searchParams.set(key, String(value));
41
+ }
42
+ }
43
+ const str = searchParams.toString();
44
+ return str ? `?${str}` : "";
45
+ }
46
+ /**
47
+ * Build the href for a route with params.
48
+ */
49
+ function buildHref(router, routeName, params, search, basePath = "") {
50
+ const route = findRouteByName(router, routeName);
51
+ if (Option.isNone(route)) {
52
+ return "#";
53
+ }
54
+ const pathname = route.value.interpolate(params ?? {});
55
+ const searchString = search ? buildSearchString(search) : "";
56
+ return `${basePath}${pathname}${searchString}`;
57
+ }
58
+ // =============================================================================
59
+ // Link Component Factory
60
+ // =============================================================================
61
+ /**
62
+ * Create a Link component bound to a router.
63
+ *
64
+ * The Link component must know which router to use for:
65
+ * - Building hrefs from route names
66
+ * - Checking active state
67
+ *
68
+ * Usage:
69
+ * ```typescript
70
+ * const Link = createLink(appRouter);
71
+ * <Link to="posts" params={{ id: 123 }}>View Post</Link>
72
+ * ```
73
+ */
74
+ export function createLink(router) {
75
+ return function Link(props) {
76
+ return Effect.gen(function* () {
77
+ const navigator = yield* Navigator;
78
+ const currentRoute = yield* Atom.get(navigator.currentRoute);
79
+ // Build href for SSR/accessibility (includes basePath)
80
+ const href = buildHref(router, props.to, props.params, props.search, navigator.basePath);
81
+ // Check if this link is active
82
+ const isActive = Option.match(currentRoute, {
83
+ onNone: () => false,
84
+ onSome: (route) => {
85
+ if (route.routeName !== props.to) {
86
+ return false;
87
+ }
88
+ // If params provided, check they match
89
+ if (props.params) {
90
+ for (const [key, value] of Object.entries(props.params)) {
91
+ if (route.params[key] !== value) {
92
+ return false;
93
+ }
94
+ }
95
+ }
96
+ return true;
97
+ },
98
+ });
99
+ // Build class string
100
+ const activeClass = props.activeClass ?? "active";
101
+ const classes = [props.class, isActive ? activeClass : null]
102
+ .filter(Boolean)
103
+ .join(" ");
104
+ // Click handler - prevent default and use Navigator
105
+ const handleClick = (e) => {
106
+ // Allow ctrl/cmd click for new tab
107
+ if (e.ctrlKey || e.metaKey || e.shiftKey) {
108
+ return;
109
+ }
110
+ e.preventDefault();
111
+ return navigator.go(props.to, {
112
+ path: props.params,
113
+ searchParams: props.search,
114
+ replace: props.replace,
115
+ });
116
+ };
117
+ // Return VElement directly - children are already normalized by JSX runtime
118
+ return {
119
+ type: "a",
120
+ props: {
121
+ href,
122
+ class: classes || undefined,
123
+ "data-cy": props["data-cy"],
124
+ onClick: handleClick,
125
+ children: [...(props.children ?? [])],
126
+ },
127
+ };
128
+ });
129
+ };
130
+ }
131
+ //# sourceMappingURL=Link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Link.js","sourceRoot":"","sources":["../../src/router/Link.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
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
+ import type { Router } from "./Router.js";
20
+ /**
21
+ * Current route info - what route is currently matched.
22
+ */
23
+ export interface CurrentRoute {
24
+ readonly routeName: string;
25
+ readonly params: Record<string, unknown>;
26
+ readonly searchParams: Record<string, string>;
27
+ }
28
+ /**
29
+ * Navigation options for go().
30
+ */
31
+ export interface NavigateOptions<PathParams extends Record<string, unknown> = Record<string, unknown>, SearchParams extends Record<string, unknown> = Record<string, unknown>> {
32
+ readonly path?: PathParams;
33
+ readonly searchParams?: SearchParams;
34
+ readonly replace?: boolean;
35
+ }
36
+ /**
37
+ * Navigator service interface.
38
+ * Provides type-safe navigation by route name.
39
+ */
40
+ export interface NavigatorService {
41
+ /**
42
+ * Base path prefix for all routes (e.g., "/ssr/router").
43
+ * Used for apps mounted at non-root paths.
44
+ */
45
+ readonly basePath: string;
46
+ /**
47
+ * Current matched route info - updates on navigation.
48
+ */
49
+ readonly currentRoute: Atom.Writable<Option.Option<CurrentRoute>, Option.Option<CurrentRoute>>;
50
+ /**
51
+ * Navigate to a route by name with optional params.
52
+ */
53
+ readonly go: (routeName: string, options?: NavigateOptions) => Effect.Effect<void, never, AtomRegistry.AtomRegistry>;
54
+ /**
55
+ * Go back in history.
56
+ */
57
+ readonly back: Effect.Effect<void, never, AtomRegistry.AtomRegistry>;
58
+ /**
59
+ * Go forward in history.
60
+ */
61
+ readonly forward: Effect.Effect<void, never, AtomRegistry.AtomRegistry>;
62
+ /**
63
+ * Check if a route is currently active.
64
+ * Optionally match specific params.
65
+ */
66
+ readonly isActive: (routeName: string, params?: Record<string, unknown>) => Effect.Effect<boolean, never, AtomRegistry.AtomRegistry>;
67
+ }
68
+ declare const Navigator_base: Context.TagClass<Navigator, "fibrae/Navigator", NavigatorService>;
69
+ /**
70
+ * Navigator service tag for Effect dependency injection.
71
+ */
72
+ export declare class Navigator extends Navigator_base {
73
+ }
74
+ /**
75
+ * Options for creating a Navigator layer.
76
+ */
77
+ export interface NavigatorOptions {
78
+ /** Base path prefix for all routes (e.g., "/ssr/router") */
79
+ readonly basePath?: string;
80
+ }
81
+ /**
82
+ * Create a Navigator layer for the given router.
83
+ *
84
+ * Features:
85
+ * - Type-safe navigation by route name
86
+ * - Automatic URL building via route.interpolate
87
+ * - Tracks current matched route in an Atom
88
+ * - Delegates to History for actual navigation
89
+ * - Supports basePath for apps mounted at non-root paths
90
+ */
91
+ export declare function NavigatorLive(router: Router, options?: NavigatorOptions): Layer.Layer<Navigator, never, History | AtomRegistry.AtomRegistry>;
92
+ /**
93
+ * Navigate to a route by name.
94
+ */
95
+ export declare const go: (routeName: string, options?: NavigateOptions) => Effect.Effect<void, never, Navigator | AtomRegistry.AtomRegistry>;
96
+ /**
97
+ * Go back in history.
98
+ */
99
+ export declare const back: Effect.Effect<void, never, Navigator | AtomRegistry.AtomRegistry>;
100
+ /**
101
+ * Go forward in history.
102
+ */
103
+ export declare const forward: Effect.Effect<void, never, Navigator | AtomRegistry.AtomRegistry>;
104
+ /**
105
+ * Get current route info.
106
+ */
107
+ export declare const getCurrentRoute: Effect.Effect<Option.Option<CurrentRoute>, never, Navigator | AtomRegistry.AtomRegistry>;
108
+ export {};