@react-router-modules/runtime 1.0.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/README.md +31 -0
- package/dist/index.d.ts +360 -0
- package/dist/index.js +325 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @react-router-modules/runtime
|
|
2
|
+
|
|
3
|
+
Application assembly layer for the reactive framework. Takes modules and configuration, produces a running app with routing, slots, zones, navigation, and provider wiring.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @react-router-modules/runtime
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createRegistry } from "@react-router-modules/runtime";
|
|
15
|
+
import billingModule from "./modules/billing";
|
|
16
|
+
|
|
17
|
+
const registry = createRegistry<AppDependencies, AppSlots>({
|
|
18
|
+
stores: { auth: authStore },
|
|
19
|
+
services: { httpClient },
|
|
20
|
+
slots: { commands: [] },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
registry.register(billingModule);
|
|
24
|
+
|
|
25
|
+
const { App } = registry.resolve({
|
|
26
|
+
rootComponent: Layout,
|
|
27
|
+
indexComponent: HomePage,
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
See the [main documentation](https://github.com/kibertoad/reactive#readme) for the full guide.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { Component } from 'react';
|
|
2
|
+
import { Context } from 'react';
|
|
3
|
+
import { DataRouter } from 'react-router';
|
|
4
|
+
import { ErrorInfo } from 'react';
|
|
5
|
+
import { JSX } from 'react/jsx-runtime';
|
|
6
|
+
import { JSXElementConstructor } from 'react';
|
|
7
|
+
import { LazyModuleDescriptor } from '@react-router-modules/core';
|
|
8
|
+
import { NavigationItem } from '@react-router-modules/core';
|
|
9
|
+
import { ReactElement } from 'react';
|
|
10
|
+
import { ReactiveModuleDescriptor } from '@react-router-modules/core';
|
|
11
|
+
import { ReactiveService } from '@react-router-modules/core';
|
|
12
|
+
import { ReactNode } from 'react';
|
|
13
|
+
import { ReactPortal } from 'react';
|
|
14
|
+
import { RouteObject } from 'react-router';
|
|
15
|
+
import { SlotMap } from '@react-router-modules/core';
|
|
16
|
+
import { SlotMapOf } from '@react-router-modules/core';
|
|
17
|
+
import { StoreApi } from 'zustand';
|
|
18
|
+
import { ZoneMapOf } from '@react-router-modules/core';
|
|
19
|
+
|
|
20
|
+
export declare interface ApplicationManifest<TSlots extends SlotMapOf<TSlots> = SlotMap> {
|
|
21
|
+
/** The root React component with all providers wired */
|
|
22
|
+
readonly App: React.ComponentType;
|
|
23
|
+
/** The React Router instance — pass to <RouterProvider /> if needed */
|
|
24
|
+
readonly router: DataRouter;
|
|
25
|
+
/** Auto-generated navigation manifest from all modules */
|
|
26
|
+
readonly navigation: NavigationManifest;
|
|
27
|
+
/** Collected slot contributions from all modules */
|
|
28
|
+
readonly slots: TSlots;
|
|
29
|
+
/** Registered module summaries — use useModules() to access in components */
|
|
30
|
+
readonly modules: readonly ModuleEntry[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Collects slot contributions from all registered modules.
|
|
35
|
+
* Arrays are concatenated per slot key across modules.
|
|
36
|
+
*
|
|
37
|
+
* When defaults are provided, every key in defaults is guaranteed to exist
|
|
38
|
+
* in the result — even if no module contributes to it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildSlotsManifest<TSlots extends {
|
|
41
|
+
[K in keyof TSlots]: readonly unknown[];
|
|
42
|
+
}>(modules: readonly ReactiveModuleDescriptor<any, TSlots>[], defaults?: Partial<{
|
|
43
|
+
[K in keyof TSlots]: TSlots[K];
|
|
44
|
+
}>): TSlots;
|
|
45
|
+
|
|
46
|
+
export declare function createRegistry<TSharedDependencies extends Record<string, any>, TSlots extends SlotMapOf<TSlots> = SlotMap>(config: RegistryConfig<TSharedDependencies, TSlots>): ReactiveRegistry<TSharedDependencies, TSlots>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Type-safe accessor for module metadata.
|
|
50
|
+
* Use this when the shell defines a known meta shape and wants to read it
|
|
51
|
+
* without casting every field.
|
|
52
|
+
*
|
|
53
|
+
* Returns undefined if the module has no meta.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* interface JourneyMeta { name: string; category: string; icon: string }
|
|
57
|
+
* const meta = getModuleMeta<JourneyMeta>(mod)
|
|
58
|
+
* if (meta) console.log(meta.name) // typed, no cast
|
|
59
|
+
*/
|
|
60
|
+
export declare function getModuleMeta<TMeta extends {
|
|
61
|
+
[K in keyof TMeta]: unknown;
|
|
62
|
+
}>(entry: ModuleEntry): Readonly<TMeta> | undefined;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A summary of a registered module exposed to the shell.
|
|
66
|
+
* Includes the module's identity, metadata, and optional component.
|
|
67
|
+
*/
|
|
68
|
+
export declare interface ModuleEntry {
|
|
69
|
+
/** Unique module identifier */
|
|
70
|
+
readonly id: string;
|
|
71
|
+
/** SemVer version string */
|
|
72
|
+
readonly version: string;
|
|
73
|
+
/** Catalog metadata (description, icon, category, etc.) */
|
|
74
|
+
readonly meta?: Readonly<Record<string, unknown>>;
|
|
75
|
+
/** A React component the shell can render outside of routes */
|
|
76
|
+
readonly component?: React.ComponentType<any>;
|
|
77
|
+
/** Zone components contributed when this module is active in a workspace tab */
|
|
78
|
+
readonly zones?: Readonly<Record<string, React.ComponentType<any>>>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export declare class ModuleErrorBoundary extends Component<Props, State> {
|
|
82
|
+
state: State;
|
|
83
|
+
static getDerivedStateFromError(error: Error): State;
|
|
84
|
+
componentDidCatch(error: Error, info: ErrorInfo): void;
|
|
85
|
+
render(): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | ReactPortal | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | JSX.Element | null | undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export declare const ModulesContext: Context<readonly ModuleEntry[] | null>;
|
|
89
|
+
|
|
90
|
+
export declare interface NavigationGroup {
|
|
91
|
+
readonly group: string;
|
|
92
|
+
readonly items: readonly NavigationItem[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export declare interface NavigationManifest {
|
|
96
|
+
/** All navigation items flat */
|
|
97
|
+
readonly items: readonly NavigationItem[];
|
|
98
|
+
/** Items grouped by their group key, sorted by order within each group */
|
|
99
|
+
readonly groups: readonly NavigationGroup[];
|
|
100
|
+
/** Ungrouped items (no group key) */
|
|
101
|
+
readonly ungrouped: readonly NavigationItem[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
declare interface Props {
|
|
105
|
+
moduleId: string;
|
|
106
|
+
fallback?: ReactNode;
|
|
107
|
+
children: ReactNode;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export declare interface ReactiveRegistry<TSharedDependencies extends Record<string, any>, TSlots extends SlotMapOf<TSlots> = SlotMap> {
|
|
111
|
+
/** Register an eager module */
|
|
112
|
+
register(module: ReactiveModuleDescriptor<TSharedDependencies, TSlots>): void;
|
|
113
|
+
/** Register a lazily-loaded module */
|
|
114
|
+
registerLazy(descriptor: LazyModuleDescriptor<TSharedDependencies, TSlots>): void;
|
|
115
|
+
/**
|
|
116
|
+
* Resolve all modules and produce the application manifest.
|
|
117
|
+
* Validates dependencies and builds the route tree.
|
|
118
|
+
*/
|
|
119
|
+
resolve(options?: ResolveOptions): ApplicationManifest<TSlots>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Configuration for creating a registry.
|
|
124
|
+
*
|
|
125
|
+
* Three dependency buckets:
|
|
126
|
+
* - **stores** — zustand StoreApi instances (reactive, supports selectors)
|
|
127
|
+
* - **services** — plain objects (non-reactive, static references)
|
|
128
|
+
* - **reactiveServices** — external sources with subscribe/getSnapshot (reactive via useSyncExternalStore)
|
|
129
|
+
*/
|
|
130
|
+
export declare interface RegistryConfig<TSharedDependencies extends Record<string, any>, TSlots extends SlotMapOf<TSlots> = SlotMap> {
|
|
131
|
+
/** Zustand stores — state you own and mutate */
|
|
132
|
+
stores?: {
|
|
133
|
+
[K in keyof TSharedDependencies]?: StoreApi<TSharedDependencies[K]>;
|
|
134
|
+
};
|
|
135
|
+
/** Plain services — static utilities (http client, auth, workspace actions) */
|
|
136
|
+
services?: {
|
|
137
|
+
[K in keyof TSharedDependencies]?: TSharedDependencies[K];
|
|
138
|
+
};
|
|
139
|
+
/** Reactive external sources — things you subscribe to but don't control (call adapters, presence, websockets) */
|
|
140
|
+
reactiveServices?: {
|
|
141
|
+
[K in keyof TSharedDependencies]?: ReactiveService<TSharedDependencies[K]>;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Default slot values. Every key defined here is guaranteed to exist
|
|
145
|
+
* in the resolved slots manifest, even if no module contributes to it.
|
|
146
|
+
* Module contributions are appended to these defaults.
|
|
147
|
+
*/
|
|
148
|
+
slots?: {
|
|
149
|
+
[K in keyof TSlots]?: TSlots[K];
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export declare interface ResolveOptions {
|
|
154
|
+
/** Root layout component (renders <Outlet /> for child routes) */
|
|
155
|
+
rootComponent?: () => React.JSX.Element;
|
|
156
|
+
/**
|
|
157
|
+
* Pre-built root route — if provided, used instead of auto-creating one.
|
|
158
|
+
* Use this when you need full control over the root route config
|
|
159
|
+
* (loader, errorElement, etc.).
|
|
160
|
+
* Mutually exclusive with rootComponent/notFoundComponent/loader.
|
|
161
|
+
*/
|
|
162
|
+
rootRoute?: RouteObject;
|
|
163
|
+
/** Component for the index route (/) */
|
|
164
|
+
indexComponent?: () => React.JSX.Element;
|
|
165
|
+
/** Component for 404 / not-found */
|
|
166
|
+
notFoundComponent?: () => React.JSX.Element;
|
|
167
|
+
/**
|
|
168
|
+
* Called before every route loads — for observability, analytics, feature flags.
|
|
169
|
+
* Runs for ALL routes including public ones like /login.
|
|
170
|
+
* Throw a `redirect()` from react-router to redirect.
|
|
171
|
+
* Ignored if rootRoute is provided (configure loader on your root route instead).
|
|
172
|
+
*
|
|
173
|
+
* For auth guards, use `authenticatedRoute` instead — it creates a layout route
|
|
174
|
+
* boundary that only wraps protected routes.
|
|
175
|
+
*/
|
|
176
|
+
loader?: (args: {
|
|
177
|
+
request: Request;
|
|
178
|
+
params: Record<string, string | undefined>;
|
|
179
|
+
}) => any;
|
|
180
|
+
/**
|
|
181
|
+
* Auth boundary — a pathless layout route that guards module routes and
|
|
182
|
+
* the index route. Shell routes (login, error pages) sit outside this
|
|
183
|
+
* boundary and are NOT guarded.
|
|
184
|
+
*
|
|
185
|
+
* Follows React Router's recommended layout route pattern:
|
|
186
|
+
* ```
|
|
187
|
+
* Root (loader runs for ALL routes — observability, etc.)
|
|
188
|
+
* ├── shellRoutes (public — /login, /signup)
|
|
189
|
+
* └── _authenticated (layout — auth guard)
|
|
190
|
+
* ├── / (indexComponent)
|
|
191
|
+
* └── module routes
|
|
192
|
+
* ```
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* registry.resolve({
|
|
197
|
+
* authenticatedRoute: {
|
|
198
|
+
* loader: async () => {
|
|
199
|
+
* const res = await fetch('/api/auth/session')
|
|
200
|
+
* if (!res.ok) throw redirect('/login')
|
|
201
|
+
* return null
|
|
202
|
+
* },
|
|
203
|
+
* Component: ShellLayout,
|
|
204
|
+
* },
|
|
205
|
+
* shellRoutes: () => [
|
|
206
|
+
* { path: '/login', Component: LoginPage },
|
|
207
|
+
* ],
|
|
208
|
+
* })
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
authenticatedRoute?: {
|
|
212
|
+
/** Auth guard — throw redirect() to deny access */
|
|
213
|
+
loader: (args: {
|
|
214
|
+
request: Request;
|
|
215
|
+
params: Record<string, string | undefined>;
|
|
216
|
+
}) => any;
|
|
217
|
+
/** Layout component for authenticated pages. Defaults to <Outlet />. */
|
|
218
|
+
Component?: () => React.JSX.Element;
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Additional routes owned by the shell (login, error pages, onboarding, etc.)
|
|
222
|
+
* that sit alongside module routes at the root level.
|
|
223
|
+
*
|
|
224
|
+
* When `authenticatedRoute` is used, shell routes are NOT guarded — they
|
|
225
|
+
* are siblings of the auth layout, not children. This is the natural place
|
|
226
|
+
* for public pages like /login.
|
|
227
|
+
*/
|
|
228
|
+
shellRoutes?: () => RouteObject[];
|
|
229
|
+
/**
|
|
230
|
+
* Additional React providers to wrap around the app tree.
|
|
231
|
+
*
|
|
232
|
+
* **Nesting order:** First element is outermost. `[A, B, C]` produces:
|
|
233
|
+
* ```tsx
|
|
234
|
+
* <A>
|
|
235
|
+
* <B>
|
|
236
|
+
* <C>
|
|
237
|
+
* ...app...
|
|
238
|
+
* </C>
|
|
239
|
+
* </B>
|
|
240
|
+
* </A>
|
|
241
|
+
* ```
|
|
242
|
+
*
|
|
243
|
+
* Place providers that other providers depend on **first** in the array.
|
|
244
|
+
* For example, if your data-fetching provider reads from a theme context,
|
|
245
|
+
* list the theme provider before the data-fetching provider.
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```ts
|
|
249
|
+
* providers: [SWRConfigProvider, ThemeProvider, TooltipProvider]
|
|
250
|
+
* // Produces: <SWRConfigProvider><ThemeProvider><TooltipProvider>...app...</TooltipProvider></ThemeProvider></SWRConfigProvider>
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
providers?: React.ComponentType<{
|
|
254
|
+
children: React.ReactNode;
|
|
255
|
+
}>[];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export declare const SlotsContext: Context<object | null>;
|
|
259
|
+
|
|
260
|
+
declare interface State {
|
|
261
|
+
hasError: boolean;
|
|
262
|
+
error: Error | null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Read zone components from both the matched route hierarchy AND the
|
|
267
|
+
* currently active module (identified by `activeModuleId`).
|
|
268
|
+
*
|
|
269
|
+
* This unifies two zone contribution patterns:
|
|
270
|
+
* - **Route-based modules** contribute zones via React Router's `handle`
|
|
271
|
+
* - **Tab-based modules** contribute zones via the `zones` field on their descriptor
|
|
272
|
+
*
|
|
273
|
+
* When both sources provide a value for the same zone key, the module's
|
|
274
|
+
* contribution wins — the assumption is that the active tab's content is more
|
|
275
|
+
* relevant than the underlying route's.
|
|
276
|
+
*
|
|
277
|
+
* @param activeModuleId - The id of the module that is currently active in the
|
|
278
|
+
* workspace (e.g. the journey tab's moduleId). Pass `null` or `undefined`
|
|
279
|
+
* when no module tab is active — only route zones are returned.
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* // In the shell layout:
|
|
283
|
+
* const activeTab = getActiveTab(interactionId)
|
|
284
|
+
* const moduleId = activeTab?.type === 'native-journey' ? activeTab.journeyId : null
|
|
285
|
+
* const zones = useActiveZones<AppZones>(moduleId)
|
|
286
|
+
* const Panel = zones.contextualPanel
|
|
287
|
+
*
|
|
288
|
+
* return (
|
|
289
|
+
* <aside>{Panel ? <Panel /> : <DefaultPanel />}</aside>
|
|
290
|
+
* )
|
|
291
|
+
*/
|
|
292
|
+
export declare function useActiveZones<TZones extends ZoneMapOf<TZones>>(activeModuleId?: string | null): Partial<TZones>;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Access the list of registered modules with their metadata and components.
|
|
296
|
+
* Must be used within a <ReactiveApp /> provider tree.
|
|
297
|
+
*
|
|
298
|
+
* Use this to build discovery UIs (directory pages, search, catalogs)
|
|
299
|
+
* and to render module components in workspace tabs or panels.
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* const modules = useModules()
|
|
303
|
+
* const journeys = modules.filter(m => m.meta?.category === 'payments')
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* const mod = modules.find(m => m.id === activeTab.moduleId)
|
|
307
|
+
* if (mod?.component) return <mod.component {...props} />
|
|
308
|
+
*/
|
|
309
|
+
export declare function useModules(): readonly ModuleEntry[];
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Access the auto-generated navigation manifest from registered modules.
|
|
313
|
+
* Use this in layout components to render sidebar/nav items.
|
|
314
|
+
*/
|
|
315
|
+
export declare function useNavigation(): NavigationManifest;
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Access the collected slot contributions from all registered modules.
|
|
319
|
+
* Must be used within a <ReactiveApp /> provider tree.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* const slots = useSlots<AppSlots>()
|
|
323
|
+
* const commands = slots.commands // CommandDefinition[] from all modules
|
|
324
|
+
*/
|
|
325
|
+
export declare function useSlots<TSlots extends {
|
|
326
|
+
[K in keyof TSlots]: readonly unknown[];
|
|
327
|
+
}>(): TSlots;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Read zone components contributed by the currently matched route hierarchy.
|
|
331
|
+
*
|
|
332
|
+
* Zones are set via React Router's `handle` on individual routes.
|
|
333
|
+
* This hook walks all matched routes from root to leaf and returns a merged
|
|
334
|
+
* map where the deepest match wins for each zone key.
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* // In the shell layout:
|
|
338
|
+
* const zones = useZones<AppZones>()
|
|
339
|
+
* const DetailPanel = zones.detailPanel
|
|
340
|
+
*
|
|
341
|
+
* return (
|
|
342
|
+
* <div className="grid">
|
|
343
|
+
* <main><Outlet /></main>
|
|
344
|
+
* <aside>{DetailPanel && <DetailPanel />}</aside>
|
|
345
|
+
* </div>
|
|
346
|
+
* )
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* // In a module's route definition:
|
|
350
|
+
* {
|
|
351
|
+
* path: ':userId',
|
|
352
|
+
* Component: UserDetailPage,
|
|
353
|
+
* handle: {
|
|
354
|
+
* detailPanel: UserDetailSidebar,
|
|
355
|
+
* },
|
|
356
|
+
* }
|
|
357
|
+
*/
|
|
358
|
+
export declare function useZones<TZones extends ZoneMapOf<TZones>>(): Partial<TZones>;
|
|
359
|
+
|
|
360
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { Outlet as e, RouterProvider as t, createBrowserRouter as n, createMemoryRouter as r, useMatches as i, useRoutes as a } from "react-router";
|
|
2
|
+
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
3
|
+
import { Component as c, createContext as l, useContext as u, useMemo as d } from "react";
|
|
4
|
+
import { SharedDependenciesContext as f } from "@react-router-modules/core";
|
|
5
|
+
//#region src/validation.ts
|
|
6
|
+
function p(e, t) {
|
|
7
|
+
let n = /* @__PURE__ */ new Set();
|
|
8
|
+
for (let t of e) {
|
|
9
|
+
if (n.has(t.id)) throw Error(`[@react-router-modules/runtime] Duplicate module ID "${t.id}". Each module must have a unique ID.`);
|
|
10
|
+
n.add(t.id);
|
|
11
|
+
}
|
|
12
|
+
for (let e of t) {
|
|
13
|
+
if (n.has(e.id)) throw Error(`[@react-router-modules/runtime] Duplicate module ID "${e.id}". Each module must have a unique ID.`);
|
|
14
|
+
n.add(e.id);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function m(e, t) {
|
|
18
|
+
for (let n of e) {
|
|
19
|
+
if (n.requires) {
|
|
20
|
+
let e = n.requires.filter((e) => !t.has(e));
|
|
21
|
+
if (e.length > 0) throw Error(`[@react-router-modules/runtime] Module "${n.id}" requires dependencies not provided by the registry: ${e.map(String).join(", ")}. Available: ${[...t].join(", ") || "(none)"}`);
|
|
22
|
+
}
|
|
23
|
+
if (n.optionalRequires) {
|
|
24
|
+
let e = n.optionalRequires.filter((e) => !t.has(e));
|
|
25
|
+
e.length > 0 && console.warn(`[@react-router-modules/runtime] Module "${n.id}" has optional dependencies not provided: ${e.map(String).join(", ")}. The module will still load but may have reduced functionality.`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/navigation.ts
|
|
31
|
+
function h(e) {
|
|
32
|
+
let t = [];
|
|
33
|
+
for (let n of e) n.navigation && t.push(...n.navigation);
|
|
34
|
+
let n = [...t].sort((e, t) => {
|
|
35
|
+
let n = (e.order ?? 999) - (t.order ?? 999);
|
|
36
|
+
return n === 0 ? e.label.localeCompare(t.label) : n;
|
|
37
|
+
}), r = /* @__PURE__ */ new Map(), i = [];
|
|
38
|
+
for (let e of n) if (e.group) {
|
|
39
|
+
let t = r.get(e.group);
|
|
40
|
+
t || (t = [], r.set(e.group, t)), t.push(e);
|
|
41
|
+
} else i.push(e);
|
|
42
|
+
return {
|
|
43
|
+
items: n,
|
|
44
|
+
groups: [...r.entries()].map(([e, t]) => ({
|
|
45
|
+
group: e,
|
|
46
|
+
items: t
|
|
47
|
+
})),
|
|
48
|
+
ungrouped: i
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/slots.ts
|
|
53
|
+
function g(e, t) {
|
|
54
|
+
let n = {};
|
|
55
|
+
if (t) for (let [e, r] of Object.entries(t)) n[e] = Array.isArray(r) ? [...r] : [];
|
|
56
|
+
for (let t of e) if (t.slots) for (let [e, r] of Object.entries(t.slots)) n[e] || (n[e] = []), Array.isArray(r) && n[e].push(...r);
|
|
57
|
+
return n;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/route-builder.tsx
|
|
61
|
+
function _(e, t, n) {
|
|
62
|
+
if (n?.rootRoute) {
|
|
63
|
+
let r = [...n.rootRoute.children ?? []];
|
|
64
|
+
n?.shellRoutes && r.push(...n.shellRoutes());
|
|
65
|
+
let i = [];
|
|
66
|
+
n?.indexComponent && i.push({
|
|
67
|
+
index: !0,
|
|
68
|
+
Component: n.indexComponent
|
|
69
|
+
});
|
|
70
|
+
for (let t of e) {
|
|
71
|
+
if (!t.createRoutes) continue;
|
|
72
|
+
let e = t.createRoutes();
|
|
73
|
+
if (!e) throw Error(`[@react-router-modules/runtime] Module "${t.id}" createRoutes() returned a falsy value.`);
|
|
74
|
+
i.push(...Array.isArray(e) ? e : [e]);
|
|
75
|
+
}
|
|
76
|
+
for (let e of t) i.push(y(e));
|
|
77
|
+
return n?.authenticatedRoute ? r.push(v(n.authenticatedRoute, i)) : r.push(...i), n.rootRoute.children = r, [n.rootRoute];
|
|
78
|
+
}
|
|
79
|
+
let r = [];
|
|
80
|
+
n?.shellRoutes && r.push(...n.shellRoutes());
|
|
81
|
+
let i = [];
|
|
82
|
+
n?.indexComponent && i.push({
|
|
83
|
+
index: !0,
|
|
84
|
+
Component: n.indexComponent
|
|
85
|
+
});
|
|
86
|
+
for (let t of e) {
|
|
87
|
+
if (!t.createRoutes) continue;
|
|
88
|
+
let e = t.createRoutes();
|
|
89
|
+
if (!e) throw Error(`[@react-router-modules/runtime] Module "${t.id}" createRoutes() returned a falsy value.`);
|
|
90
|
+
i.push(...Array.isArray(e) ? e : [e]);
|
|
91
|
+
}
|
|
92
|
+
for (let e of t) i.push(y(e));
|
|
93
|
+
return n?.authenticatedRoute ? r.push(v(n.authenticatedRoute, i)) : r.push(...i), n?.notFoundComponent && r.push({
|
|
94
|
+
path: "*",
|
|
95
|
+
Component: n.notFoundComponent
|
|
96
|
+
}), [{
|
|
97
|
+
path: "/",
|
|
98
|
+
Component: n?.rootComponent,
|
|
99
|
+
loader: n?.loader,
|
|
100
|
+
children: r
|
|
101
|
+
}];
|
|
102
|
+
}
|
|
103
|
+
function v(t, n) {
|
|
104
|
+
return {
|
|
105
|
+
id: "_authenticated",
|
|
106
|
+
Component: t.Component ?? (() => /* @__PURE__ */ o(e, {})),
|
|
107
|
+
loader: t.loader,
|
|
108
|
+
children: n
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function y(e) {
|
|
112
|
+
let t = null;
|
|
113
|
+
return {
|
|
114
|
+
path: e.basePath.replace(/^\//, "") + "/*",
|
|
115
|
+
lazy: async () => {
|
|
116
|
+
if (!t) {
|
|
117
|
+
let { default: n } = await e.load();
|
|
118
|
+
if (n.createRoutes) {
|
|
119
|
+
let e = n.createRoutes();
|
|
120
|
+
t = Array.isArray(e) ? e : [e];
|
|
121
|
+
} else t = [];
|
|
122
|
+
}
|
|
123
|
+
let n = t;
|
|
124
|
+
return { Component: function() {
|
|
125
|
+
return a(n);
|
|
126
|
+
} };
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/navigation-context.tsx
|
|
132
|
+
var b = l(null);
|
|
133
|
+
function x() {
|
|
134
|
+
let e = u(b);
|
|
135
|
+
if (!e) throw Error("[@react-router-modules/runtime] useNavigation must be used within a <ReactiveApp />.");
|
|
136
|
+
return e;
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/slots-context.tsx
|
|
140
|
+
var S = l(null);
|
|
141
|
+
function C() {
|
|
142
|
+
let e = u(S);
|
|
143
|
+
if (!e) throw Error("[@react-router-modules/runtime] useSlots must be used within a <ReactiveApp />.");
|
|
144
|
+
return e;
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/modules-context.ts
|
|
148
|
+
var w = l(null);
|
|
149
|
+
function T() {
|
|
150
|
+
let e = u(w);
|
|
151
|
+
if (!e) throw Error("[@react-router-modules/runtime] useModules must be used within a <ReactiveApp />.");
|
|
152
|
+
return e;
|
|
153
|
+
}
|
|
154
|
+
function E(e) {
|
|
155
|
+
return e.meta;
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/app.tsx
|
|
159
|
+
function D({ router: e, stores: n, services: r, reactiveServices: i, navigation: a, slots: s, modules: c, providers: l }) {
|
|
160
|
+
let u = {
|
|
161
|
+
stores: n,
|
|
162
|
+
services: r,
|
|
163
|
+
reactiveServices: i
|
|
164
|
+
};
|
|
165
|
+
function p() {
|
|
166
|
+
return d(() => {
|
|
167
|
+
let n = /* @__PURE__ */ o(f, {
|
|
168
|
+
value: u,
|
|
169
|
+
children: /* @__PURE__ */ o(b, {
|
|
170
|
+
value: a,
|
|
171
|
+
children: /* @__PURE__ */ o(S, {
|
|
172
|
+
value: s,
|
|
173
|
+
children: /* @__PURE__ */ o(w, {
|
|
174
|
+
value: c,
|
|
175
|
+
children: /* @__PURE__ */ o(t, { router: e })
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
});
|
|
180
|
+
if (l) for (let e of [...l].reverse()) n = /* @__PURE__ */ o(e, { children: n });
|
|
181
|
+
return n;
|
|
182
|
+
}, []);
|
|
183
|
+
}
|
|
184
|
+
return p.displayName = "ReactiveApp", p;
|
|
185
|
+
}
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/registry.ts
|
|
188
|
+
function O(e) {
|
|
189
|
+
let t = [], i = [], a = !1, o = new Set([
|
|
190
|
+
...Object.keys(e.stores ?? {}),
|
|
191
|
+
...Object.keys(e.services ?? {}),
|
|
192
|
+
...Object.keys(e.reactiveServices ?? {})
|
|
193
|
+
]);
|
|
194
|
+
return {
|
|
195
|
+
register(e) {
|
|
196
|
+
if (a) throw Error("[@react-router-modules/runtime] Cannot register modules after resolve() has been called.");
|
|
197
|
+
t.push(e);
|
|
198
|
+
},
|
|
199
|
+
registerLazy(e) {
|
|
200
|
+
if (a) throw Error("[@react-router-modules/runtime] Cannot register modules after resolve() has been called.");
|
|
201
|
+
i.push(e);
|
|
202
|
+
},
|
|
203
|
+
resolve(s) {
|
|
204
|
+
if (a) throw Error("[@react-router-modules/runtime] resolve() can only be called once.");
|
|
205
|
+
a = !0, p(t, i), m(t, o);
|
|
206
|
+
let c = k(e);
|
|
207
|
+
for (let e of t) try {
|
|
208
|
+
e.lifecycle?.onRegister?.(c);
|
|
209
|
+
} catch (t) {
|
|
210
|
+
throw Error(`[@react-router-modules/runtime] Module "${e.id}" lifecycle.onRegister() failed: ${t instanceof Error ? t.message : String(t)}`, { cause: t });
|
|
211
|
+
}
|
|
212
|
+
let l = _(t, i, {
|
|
213
|
+
rootRoute: s?.rootRoute,
|
|
214
|
+
rootComponent: s?.rootComponent,
|
|
215
|
+
indexComponent: s?.indexComponent,
|
|
216
|
+
notFoundComponent: s?.notFoundComponent,
|
|
217
|
+
loader: s?.loader,
|
|
218
|
+
authenticatedRoute: s?.authenticatedRoute,
|
|
219
|
+
shellRoutes: s?.shellRoutes
|
|
220
|
+
}), u = typeof document < "u" ? n(l) : r(l), d = h(t), f = g(t, e.slots), v = t.map((e) => ({
|
|
221
|
+
id: e.id,
|
|
222
|
+
version: e.version,
|
|
223
|
+
meta: e.meta,
|
|
224
|
+
component: e.component,
|
|
225
|
+
zones: e.zones
|
|
226
|
+
})), y = {}, b = {}, x = {};
|
|
227
|
+
if (e.stores) for (let [t, n] of Object.entries(e.stores)) n && (y[t] = n);
|
|
228
|
+
if (e.services) for (let [t, n] of Object.entries(e.services)) n !== void 0 && (b[t] = n);
|
|
229
|
+
if (e.reactiveServices) for (let [t, n] of Object.entries(e.reactiveServices)) n && (x[t] = n);
|
|
230
|
+
return {
|
|
231
|
+
App: D({
|
|
232
|
+
router: u,
|
|
233
|
+
stores: y,
|
|
234
|
+
services: b,
|
|
235
|
+
reactiveServices: x,
|
|
236
|
+
navigation: d,
|
|
237
|
+
slots: f,
|
|
238
|
+
modules: v,
|
|
239
|
+
providers: s?.providers
|
|
240
|
+
}),
|
|
241
|
+
router: u,
|
|
242
|
+
navigation: d,
|
|
243
|
+
slots: f,
|
|
244
|
+
modules: v
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function k(e) {
|
|
250
|
+
let t = {};
|
|
251
|
+
if (e.stores) for (let [n, r] of Object.entries(e.stores)) r && (t[n] = r.getState());
|
|
252
|
+
if (e.services) for (let [n, r] of Object.entries(e.services)) r !== void 0 && (t[n] = r);
|
|
253
|
+
if (e.reactiveServices) for (let [n, r] of Object.entries(e.reactiveServices)) r && (t[n] = r.getSnapshot());
|
|
254
|
+
return t;
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/zones.ts
|
|
258
|
+
function A() {
|
|
259
|
+
let e = i(), t = {};
|
|
260
|
+
for (let n of e) {
|
|
261
|
+
let e = n.handle;
|
|
262
|
+
if (e && typeof e == "object") for (let [n, r] of Object.entries(e)) r !== void 0 && (t[n] = r);
|
|
263
|
+
}
|
|
264
|
+
return t;
|
|
265
|
+
}
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/active-zones.ts
|
|
268
|
+
function j(e) {
|
|
269
|
+
let t = A(), n = T();
|
|
270
|
+
if (!e) return t;
|
|
271
|
+
let r = n.find((t) => t.id === e);
|
|
272
|
+
return r?.zones ? {
|
|
273
|
+
...t,
|
|
274
|
+
...r.zones
|
|
275
|
+
} : t;
|
|
276
|
+
}
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/error-boundary.tsx
|
|
279
|
+
var M = class extends c {
|
|
280
|
+
state = {
|
|
281
|
+
hasError: !1,
|
|
282
|
+
error: null
|
|
283
|
+
};
|
|
284
|
+
static getDerivedStateFromError(e) {
|
|
285
|
+
return {
|
|
286
|
+
hasError: !0,
|
|
287
|
+
error: e
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
componentDidCatch(e, t) {
|
|
291
|
+
console.error(`[@react-router-modules/runtime] Module "${this.props.moduleId}" encountered an error:`, e, t);
|
|
292
|
+
}
|
|
293
|
+
render() {
|
|
294
|
+
return this.state.hasError ? this.props.fallback ? this.props.fallback : /* @__PURE__ */ s("div", {
|
|
295
|
+
style: {
|
|
296
|
+
padding: "1rem",
|
|
297
|
+
border: "1px solid #e53e3e",
|
|
298
|
+
borderRadius: "0.5rem",
|
|
299
|
+
margin: "1rem"
|
|
300
|
+
},
|
|
301
|
+
children: [/* @__PURE__ */ s("h3", {
|
|
302
|
+
style: {
|
|
303
|
+
color: "#e53e3e",
|
|
304
|
+
margin: "0 0 0.5rem 0"
|
|
305
|
+
},
|
|
306
|
+
children: [
|
|
307
|
+
"Module \"",
|
|
308
|
+
this.props.moduleId,
|
|
309
|
+
"\" encountered an error"
|
|
310
|
+
]
|
|
311
|
+
}), /* @__PURE__ */ o("pre", {
|
|
312
|
+
style: {
|
|
313
|
+
fontSize: "0.875rem",
|
|
314
|
+
color: "#718096",
|
|
315
|
+
whiteSpace: "pre-wrap"
|
|
316
|
+
},
|
|
317
|
+
children: this.state.error?.message
|
|
318
|
+
})]
|
|
319
|
+
}) : this.props.children;
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
//#endregion
|
|
323
|
+
export { M as ModuleErrorBoundary, w as ModulesContext, S as SlotsContext, g as buildSlotsManifest, O as createRegistry, E as getModuleMeta, j as useActiveZones, T as useModules, x as useNavigation, C as useSlots, A as useZones };
|
|
324
|
+
|
|
325
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/validation.ts","../src/navigation.ts","../src/slots.ts","../src/route-builder.tsx","../src/navigation-context.tsx","../src/slots-context.tsx","../src/modules-context.ts","../src/app.tsx","../src/registry.ts","../src/zones.ts","../src/active-zones.ts","../src/error-boundary.tsx"],"sourcesContent":["import type { ReactiveModuleDescriptor, LazyModuleDescriptor } from \"@react-router-modules/core\";\r\n\r\nexport function validateNoDuplicateIds(\r\n modules: ReactiveModuleDescriptor[],\r\n lazyModules: LazyModuleDescriptor[],\r\n): void {\r\n const ids = new Set<string>();\r\n for (const mod of modules) {\r\n if (ids.has(mod.id)) {\r\n throw new Error(\r\n `[@react-router-modules/runtime] Duplicate module ID \"${mod.id}\". Each module must have a unique ID.`,\r\n );\r\n }\r\n ids.add(mod.id);\r\n }\r\n for (const mod of lazyModules) {\r\n if (ids.has(mod.id)) {\r\n throw new Error(\r\n `[@react-router-modules/runtime] Duplicate module ID \"${mod.id}\". Each module must have a unique ID.`,\r\n );\r\n }\r\n ids.add(mod.id);\r\n }\r\n}\r\n\r\nexport function validateDependencies(\r\n modules: ReactiveModuleDescriptor[],\r\n availableKeys: Set<string>,\r\n): void {\r\n for (const mod of modules) {\r\n if (mod.requires) {\r\n const missing = mod.requires.filter((key) => !availableKeys.has(key as string));\r\n if (missing.length > 0) {\r\n throw new Error(\r\n `[@react-router-modules/runtime] Module \"${mod.id}\" requires dependencies not provided by the registry: ` +\r\n `${missing.map(String).join(\", \")}. ` +\r\n `Available: ${[...availableKeys].join(\", \") || \"(none)\"}`,\r\n );\r\n }\r\n }\r\n\r\n if (mod.optionalRequires) {\r\n const missing = mod.optionalRequires.filter((key) => !availableKeys.has(key as string));\r\n if (missing.length > 0) {\r\n console.warn(\r\n `[@react-router-modules/runtime] Module \"${mod.id}\" has optional dependencies not provided: ` +\r\n `${missing.map(String).join(\", \")}. The module will still load but may have reduced functionality.`,\r\n );\r\n }\r\n }\r\n }\r\n}\r\n","import type { ReactiveModuleDescriptor, NavigationItem } from \"@react-router-modules/core\";\r\nimport type { NavigationManifest, NavigationGroup } from \"./types.js\";\r\n\r\nexport function buildNavigationManifest(modules: ReactiveModuleDescriptor[]): NavigationManifest {\r\n const allItems: NavigationItem[] = [];\r\n\r\n for (const mod of modules) {\r\n if (mod.navigation) {\r\n allItems.push(...mod.navigation);\r\n }\r\n }\r\n\r\n // Sort by order (lower first), then by label alphabetically\r\n const sorted = [...allItems].sort((a, b) => {\r\n const orderDiff = (a.order ?? 999) - (b.order ?? 999);\r\n if (orderDiff !== 0) return orderDiff;\r\n return a.label.localeCompare(b.label);\r\n });\r\n\r\n // Group items\r\n const groupMap = new Map<string, NavigationItem[]>();\r\n const ungrouped: NavigationItem[] = [];\r\n\r\n for (const item of sorted) {\r\n if (item.group) {\r\n let group = groupMap.get(item.group);\r\n if (!group) {\r\n group = [];\r\n groupMap.set(item.group, group);\r\n }\r\n group.push(item);\r\n } else {\r\n ungrouped.push(item);\r\n }\r\n }\r\n\r\n const groups: NavigationGroup[] = [...groupMap.entries()].map(([group, items]) => ({\r\n group,\r\n items,\r\n }));\r\n\r\n return { items: sorted, groups, ungrouped };\r\n}\r\n","import type { ReactiveModuleDescriptor } from \"@react-router-modules/core\";\r\n\r\n/**\r\n * Collects slot contributions from all registered modules.\r\n * Arrays are concatenated per slot key across modules.\r\n *\r\n * When defaults are provided, every key in defaults is guaranteed to exist\r\n * in the result — even if no module contributes to it.\r\n */\r\nexport function buildSlotsManifest<TSlots extends { [K in keyof TSlots]: readonly unknown[] }>(\r\n modules: readonly ReactiveModuleDescriptor<any, TSlots>[],\r\n defaults?: Partial<{ [K in keyof TSlots]: TSlots[K] }>,\r\n): TSlots {\r\n const result: Record<string, unknown[]> = {};\r\n\r\n // Initialize from defaults so every declared key exists\r\n if (defaults) {\r\n for (const [key, items] of Object.entries(defaults)) {\r\n result[key] = Array.isArray(items) ? [...items] : [];\r\n }\r\n }\r\n\r\n for (const mod of modules) {\r\n if (!mod.slots) continue;\r\n for (const [key, items] of Object.entries(mod.slots)) {\r\n if (!result[key]) result[key] = [];\r\n if (Array.isArray(items)) {\r\n result[key].push(...items);\r\n }\r\n }\r\n }\r\n\r\n return result as unknown as TSlots;\r\n}\r\n","import { Outlet, useRoutes } from \"react-router\";\r\nimport type { RouteObject } from \"react-router\";\r\nimport type { ReactiveModuleDescriptor, LazyModuleDescriptor } from \"@react-router-modules/core\";\r\n\r\nexport interface RouteBuilderOptions {\r\n /**\r\n * Pre-built root route. If provided, rootComponent/notFoundComponent/loader\r\n * are ignored — configure them directly on this route instead.\r\n */\r\n rootRoute?: RouteObject;\r\n /** Component for the root layout (renders <Outlet /> for child routes) */\r\n rootComponent?: () => React.JSX.Element;\r\n /** Component for the index route (/) */\r\n indexComponent?: () => React.JSX.Element;\r\n /** Component for the 404 / not-found route */\r\n notFoundComponent?: () => React.JSX.Element;\r\n /**\r\n * Called before every route loads — for observability, feature flags, etc.\r\n * Runs for ALL routes including public ones.\r\n * Ignored if rootRoute is provided.\r\n */\r\n loader?: (args: { request: Request; params: Record<string, string | undefined> }) => any;\r\n /**\r\n * Auth boundary — a pathless layout route that wraps module routes and\r\n * the index route. Shell routes (login, error pages) sit outside this\r\n * boundary and are NOT guarded.\r\n *\r\n * Follows React Router's recommended layout route pattern.\r\n *\r\n * When provided, the route tree becomes:\r\n * ```\r\n * Root (loader runs for ALL routes)\r\n * ├── shellRoutes (public — /login, /signup, etc.)\r\n * └── _authenticated (layout — loader guards children)\r\n * ├── / (indexComponent)\r\n * └── module routes\r\n * ```\r\n *\r\n * When omitted, all routes are direct children of root (no auth boundary).\r\n */\r\n authenticatedRoute?: {\r\n /** Auth guard — throw redirect() to deny access */\r\n loader: (args: { request: Request; params: Record<string, string | undefined> }) => any;\r\n /** Layout component for authenticated pages. Defaults to <Outlet />. */\r\n Component?: () => React.JSX.Element;\r\n };\r\n /** Additional routes owned by the shell (login, error pages, etc.) */\r\n shellRoutes?: () => RouteObject[];\r\n}\r\n\r\n/**\r\n * Composes all module route subtrees into a React Router route tree.\r\n * Modules without createRoutes are skipped (headless modules).\r\n */\r\nexport function buildRouteTree(\r\n modules: ReactiveModuleDescriptor[],\r\n lazyModules: LazyModuleDescriptor[],\r\n options?: RouteBuilderOptions,\r\n): RouteObject[] {\r\n // If a custom root route is provided, use it as the base\r\n if (options?.rootRoute) {\r\n const rootChildren: RouteObject[] = [...(options.rootRoute.children ?? [])];\r\n\r\n // Shell-owned routes (login, error pages) — always direct children of root\r\n if (options?.shellRoutes) {\r\n rootChildren.push(...options.shellRoutes());\r\n }\r\n\r\n const protectedChildren: RouteObject[] = [];\r\n\r\n // Add index route if provided\r\n if (options?.indexComponent) {\r\n protectedChildren.push({\r\n index: true,\r\n Component: options.indexComponent,\r\n });\r\n }\r\n\r\n // Eager modules\r\n for (const mod of modules) {\r\n if (!mod.createRoutes) continue;\r\n const routes = mod.createRoutes();\r\n if (!routes) {\r\n throw new Error(\r\n `[@react-router-modules/runtime] Module \"${mod.id}\" createRoutes() returned a falsy value.`,\r\n );\r\n }\r\n protectedChildren.push(...(Array.isArray(routes) ? routes : [routes]));\r\n }\r\n\r\n // Lazy modules\r\n for (const lazyMod of lazyModules) {\r\n protectedChildren.push(createLazyModuleRoute(lazyMod));\r\n }\r\n\r\n if (options?.authenticatedRoute) {\r\n rootChildren.push(\r\n createAuthenticatedLayoutRoute(options.authenticatedRoute, protectedChildren),\r\n );\r\n } else {\r\n rootChildren.push(...protectedChildren);\r\n }\r\n\r\n options.rootRoute.children = rootChildren;\r\n return [options.rootRoute];\r\n }\r\n\r\n // Build root route from options\r\n const rootChildren: RouteObject[] = [];\r\n\r\n // Shell-owned routes (login, error pages) — always direct children of root\r\n if (options?.shellRoutes) {\r\n rootChildren.push(...options.shellRoutes());\r\n }\r\n\r\n const protectedChildren: RouteObject[] = [];\r\n\r\n // Add index route if provided\r\n if (options?.indexComponent) {\r\n protectedChildren.push({\r\n index: true,\r\n Component: options.indexComponent,\r\n });\r\n }\r\n\r\n // Eager modules: call createRoutes\r\n for (const mod of modules) {\r\n if (!mod.createRoutes) continue;\r\n const routes = mod.createRoutes();\r\n if (!routes) {\r\n throw new Error(\r\n `[@react-router-modules/runtime] Module \"${mod.id}\" createRoutes() returned a falsy value.`,\r\n );\r\n }\r\n protectedChildren.push(...(Array.isArray(routes) ? routes : [routes]));\r\n }\r\n\r\n // Lazy modules\r\n for (const lazyMod of lazyModules) {\r\n protectedChildren.push(createLazyModuleRoute(lazyMod));\r\n }\r\n\r\n if (options?.authenticatedRoute) {\r\n rootChildren.push(\r\n createAuthenticatedLayoutRoute(options.authenticatedRoute, protectedChildren),\r\n );\r\n } else {\r\n rootChildren.push(...protectedChildren);\r\n }\r\n\r\n // Not-found catch-all\r\n if (options?.notFoundComponent) {\r\n rootChildren.push({\r\n path: \"*\",\r\n Component: options.notFoundComponent,\r\n });\r\n }\r\n\r\n const rootRoute: RouteObject = {\r\n path: \"/\",\r\n Component: options?.rootComponent,\r\n loader: options?.loader,\r\n children: rootChildren,\r\n };\r\n\r\n return [rootRoute];\r\n}\r\n\r\nfunction createAuthenticatedLayoutRoute(\r\n auth: NonNullable<RouteBuilderOptions[\"authenticatedRoute\"]>,\r\n children: RouteObject[],\r\n): RouteObject {\r\n return {\r\n id: \"_authenticated\",\r\n Component: auth.Component ?? (() => <Outlet />),\r\n loader: auth.loader,\r\n children,\r\n };\r\n}\r\n\r\n/**\r\n * Creates a catch-all route for a lazily-loaded module.\r\n * On first navigation, the module descriptor is loaded and its routes\r\n * are rendered as descendant routes via useRoutes().\r\n */\r\nfunction createLazyModuleRoute(lazyMod: LazyModuleDescriptor): RouteObject {\r\n // Capture the loaded routes so they're resolved only once\r\n let cachedRoutes: RouteObject[] | null = null;\r\n\r\n return {\r\n path: lazyMod.basePath.replace(/^\\//, \"\") + \"/*\",\r\n lazy: async () => {\r\n if (!cachedRoutes) {\r\n const { default: descriptor } = await lazyMod.load();\r\n if (descriptor.createRoutes) {\r\n const routes = descriptor.createRoutes();\r\n cachedRoutes = Array.isArray(routes) ? routes : [routes];\r\n } else {\r\n cachedRoutes = [];\r\n }\r\n }\r\n const routes = cachedRoutes;\r\n return {\r\n Component: function LazyModule() {\r\n return useRoutes(routes);\r\n },\r\n };\r\n },\r\n };\r\n}\r\n","import { createContext, useContext } from \"react\";\r\nimport type { NavigationManifest } from \"./types.js\";\r\n\r\nexport const NavigationContext = createContext<NavigationManifest | null>(null);\r\n\r\n/**\r\n * Access the auto-generated navigation manifest from registered modules.\r\n * Use this in layout components to render sidebar/nav items.\r\n */\r\nexport function useNavigation(): NavigationManifest {\r\n const nav = useContext(NavigationContext);\r\n if (!nav) {\r\n throw new Error(\r\n \"[@react-router-modules/runtime] useNavigation must be used within a <ReactiveApp />.\",\r\n );\r\n }\r\n return nav;\r\n}\r\n","import { createContext, useContext } from \"react\";\r\n\r\nexport const SlotsContext = createContext<object | null>(null);\r\n\r\n/**\r\n * Access the collected slot contributions from all registered modules.\r\n * Must be used within a <ReactiveApp /> provider tree.\r\n *\r\n * @example\r\n * const slots = useSlots<AppSlots>()\r\n * const commands = slots.commands // CommandDefinition[] from all modules\r\n */\r\nexport function useSlots<TSlots extends { [K in keyof TSlots]: readonly unknown[] }>(): TSlots {\r\n const slots = useContext(SlotsContext);\r\n if (!slots) {\r\n throw new Error(\r\n \"[@react-router-modules/runtime] useSlots must be used within a <ReactiveApp />.\",\r\n );\r\n }\r\n return slots as TSlots;\r\n}\r\n","import { createContext, useContext } from \"react\";\r\nimport type { ModuleEntry } from \"./types.js\";\r\n\r\nexport const ModulesContext = createContext<readonly ModuleEntry[] | null>(null);\r\n\r\n/**\r\n * Access the list of registered modules with their metadata and components.\r\n * Must be used within a <ReactiveApp /> provider tree.\r\n *\r\n * Use this to build discovery UIs (directory pages, search, catalogs)\r\n * and to render module components in workspace tabs or panels.\r\n *\r\n * @example\r\n * const modules = useModules()\r\n * const journeys = modules.filter(m => m.meta?.category === 'payments')\r\n *\r\n * @example\r\n * const mod = modules.find(m => m.id === activeTab.moduleId)\r\n * if (mod?.component) return <mod.component {...props} />\r\n */\r\nexport function useModules(): readonly ModuleEntry[] {\r\n const modules = useContext(ModulesContext);\r\n if (!modules) {\r\n throw new Error(\r\n \"[@react-router-modules/runtime] useModules must be used within a <ReactiveApp />.\",\r\n );\r\n }\r\n return modules;\r\n}\r\n\r\n/**\r\n * Type-safe accessor for module metadata.\r\n * Use this when the shell defines a known meta shape and wants to read it\r\n * without casting every field.\r\n *\r\n * Returns undefined if the module has no meta.\r\n *\r\n * @example\r\n * interface JourneyMeta { name: string; category: string; icon: string }\r\n * const meta = getModuleMeta<JourneyMeta>(mod)\r\n * if (meta) console.log(meta.name) // typed, no cast\r\n */\r\nexport function getModuleMeta<TMeta extends { [K in keyof TMeta]: unknown }>(\r\n entry: ModuleEntry,\r\n): Readonly<TMeta> | undefined {\r\n return entry.meta as Readonly<TMeta> | undefined;\r\n}\r\n","import { useMemo } from \"react\";\r\nimport { RouterProvider } from \"react-router\";\r\nimport type { DataRouter } from \"react-router\";\r\nimport type { StoreApi } from \"zustand\";\r\nimport type { ReactiveService } from \"@react-router-modules/core\";\r\nimport { SharedDependenciesContext } from \"@react-router-modules/core\";\r\nimport { NavigationContext } from \"./navigation-context.js\";\r\nimport { SlotsContext } from \"./slots-context.js\";\r\nimport { ModulesContext } from \"./modules-context.js\";\r\nimport type { NavigationManifest, ModuleEntry } from \"./types.js\";\r\n\r\ninterface AppProps {\r\n router: DataRouter;\r\n stores: Record<string, StoreApi<unknown>>;\r\n services: Record<string, unknown>;\r\n reactiveServices: Record<string, ReactiveService<unknown>>;\r\n navigation: NavigationManifest;\r\n slots: object;\r\n modules: readonly ModuleEntry[];\r\n providers?: React.ComponentType<{ children: React.ReactNode }>[];\r\n}\r\n\r\nexport function createAppComponent({\r\n router,\r\n stores,\r\n services,\r\n reactiveServices,\r\n navigation,\r\n slots,\r\n modules,\r\n providers,\r\n}: AppProps) {\r\n // All values captured in closure are stable references created once at resolve() time.\r\n // Wrap in a stable object so context consumers don't re-render on parent renders.\r\n const depsValue = { stores, services, reactiveServices };\r\n\r\n function App() {\r\n const tree = useMemo(() => {\r\n let node: React.ReactNode = (\r\n <SharedDependenciesContext value={depsValue}>\r\n <NavigationContext value={navigation}>\r\n <SlotsContext value={slots}>\r\n <ModulesContext value={modules}>\r\n <RouterProvider router={router} />\r\n </ModulesContext>\r\n </SlotsContext>\r\n </NavigationContext>\r\n </SharedDependenciesContext>\r\n );\r\n\r\n // Wrap with user-supplied providers (first element = outermost wrapper)\r\n if (providers) {\r\n for (const Provider of [...providers].reverse()) {\r\n node = <Provider>{node}</Provider>;\r\n }\r\n }\r\n\r\n return node;\r\n }, []);\r\n\r\n return tree;\r\n }\r\n\r\n App.displayName = \"ReactiveApp\";\r\n return App;\r\n}\r\n","import { createBrowserRouter, createMemoryRouter } from \"react-router\";\r\nimport type { RouteObject } from \"react-router\";\r\nimport type { StoreApi } from \"zustand\";\r\nimport type {\r\n ReactiveModuleDescriptor,\r\n LazyModuleDescriptor,\r\n ReactiveService,\r\n SlotMap,\r\n SlotMapOf,\r\n} from \"@react-router-modules/core\";\r\n\r\nimport type {\r\n RegistryConfig,\r\n ApplicationManifest,\r\n NavigationManifest,\r\n ModuleEntry,\r\n} from \"./types.js\";\r\nimport { validateNoDuplicateIds, validateDependencies } from \"./validation.js\";\r\nimport { buildNavigationManifest } from \"./navigation.js\";\r\nimport { buildSlotsManifest } from \"./slots.js\";\r\nimport { buildRouteTree, type RouteBuilderOptions } from \"./route-builder.js\";\r\nimport { createAppComponent } from \"./app.js\";\r\n\r\nexport interface ReactiveRegistry<\r\n TSharedDependencies extends Record<string, any>,\r\n TSlots extends SlotMapOf<TSlots> = SlotMap,\r\n> {\r\n /** Register an eager module */\r\n register(module: ReactiveModuleDescriptor<TSharedDependencies, TSlots>): void;\r\n\r\n /** Register a lazily-loaded module */\r\n registerLazy(descriptor: LazyModuleDescriptor<TSharedDependencies, TSlots>): void;\r\n\r\n /**\r\n * Resolve all modules and produce the application manifest.\r\n * Validates dependencies and builds the route tree.\r\n */\r\n resolve(options?: ResolveOptions): ApplicationManifest<TSlots>;\r\n}\r\n\r\nexport interface ResolveOptions {\r\n /** Root layout component (renders <Outlet /> for child routes) */\r\n rootComponent?: () => React.JSX.Element;\r\n\r\n /**\r\n * Pre-built root route — if provided, used instead of auto-creating one.\r\n * Use this when you need full control over the root route config\r\n * (loader, errorElement, etc.).\r\n * Mutually exclusive with rootComponent/notFoundComponent/loader.\r\n */\r\n rootRoute?: RouteObject;\r\n\r\n /** Component for the index route (/) */\r\n indexComponent?: () => React.JSX.Element;\r\n\r\n /** Component for 404 / not-found */\r\n notFoundComponent?: () => React.JSX.Element;\r\n\r\n /**\r\n * Called before every route loads — for observability, analytics, feature flags.\r\n * Runs for ALL routes including public ones like /login.\r\n * Throw a `redirect()` from react-router to redirect.\r\n * Ignored if rootRoute is provided (configure loader on your root route instead).\r\n *\r\n * For auth guards, use `authenticatedRoute` instead — it creates a layout route\r\n * boundary that only wraps protected routes.\r\n */\r\n loader?: (args: { request: Request; params: Record<string, string | undefined> }) => any;\r\n\r\n /**\r\n * Auth boundary — a pathless layout route that guards module routes and\r\n * the index route. Shell routes (login, error pages) sit outside this\r\n * boundary and are NOT guarded.\r\n *\r\n * Follows React Router's recommended layout route pattern:\r\n * ```\r\n * Root (loader runs for ALL routes — observability, etc.)\r\n * ├── shellRoutes (public — /login, /signup)\r\n * └── _authenticated (layout — auth guard)\r\n * ├── / (indexComponent)\r\n * └── module routes\r\n * ```\r\n *\r\n * @example\r\n * ```ts\r\n * registry.resolve({\r\n * authenticatedRoute: {\r\n * loader: async () => {\r\n * const res = await fetch('/api/auth/session')\r\n * if (!res.ok) throw redirect('/login')\r\n * return null\r\n * },\r\n * Component: ShellLayout,\r\n * },\r\n * shellRoutes: () => [\r\n * { path: '/login', Component: LoginPage },\r\n * ],\r\n * })\r\n * ```\r\n */\r\n authenticatedRoute?: {\r\n /** Auth guard — throw redirect() to deny access */\r\n loader: (args: { request: Request; params: Record<string, string | undefined> }) => any;\r\n /** Layout component for authenticated pages. Defaults to <Outlet />. */\r\n Component?: () => React.JSX.Element;\r\n };\r\n\r\n /**\r\n * Additional routes owned by the shell (login, error pages, onboarding, etc.)\r\n * that sit alongside module routes at the root level.\r\n *\r\n * When `authenticatedRoute` is used, shell routes are NOT guarded — they\r\n * are siblings of the auth layout, not children. This is the natural place\r\n * for public pages like /login.\r\n */\r\n shellRoutes?: () => RouteObject[];\r\n\r\n /**\r\n * Additional React providers to wrap around the app tree.\r\n *\r\n * **Nesting order:** First element is outermost. `[A, B, C]` produces:\r\n * ```tsx\r\n * <A>\r\n * <B>\r\n * <C>\r\n * ...app...\r\n * </C>\r\n * </B>\r\n * </A>\r\n * ```\r\n *\r\n * Place providers that other providers depend on **first** in the array.\r\n * For example, if your data-fetching provider reads from a theme context,\r\n * list the theme provider before the data-fetching provider.\r\n *\r\n * @example\r\n * ```ts\r\n * providers: [SWRConfigProvider, ThemeProvider, TooltipProvider]\r\n * // Produces: <SWRConfigProvider><ThemeProvider><TooltipProvider>...app...</TooltipProvider></ThemeProvider></SWRConfigProvider>\r\n * ```\r\n */\r\n providers?: React.ComponentType<{ children: React.ReactNode }>[];\r\n}\r\n\r\nexport function createRegistry<\r\n TSharedDependencies extends Record<string, any>,\r\n TSlots extends SlotMapOf<TSlots> = SlotMap,\r\n>(\r\n config: RegistryConfig<TSharedDependencies, TSlots>,\r\n): ReactiveRegistry<TSharedDependencies, TSlots> {\r\n const modules: ReactiveModuleDescriptor<TSharedDependencies, TSlots>[] = [];\r\n const lazyModules: LazyModuleDescriptor<TSharedDependencies, TSlots>[] = [];\r\n let resolved = false;\r\n\r\n // Collect all available dependency keys from all three buckets\r\n const availableKeys = new Set<string>([\r\n ...Object.keys(config.stores ?? {}),\r\n ...Object.keys(config.services ?? {}),\r\n ...Object.keys(config.reactiveServices ?? {}),\r\n ]);\r\n\r\n return {\r\n register(module) {\r\n if (resolved) {\r\n throw new Error(\r\n \"[@react-router-modules/runtime] Cannot register modules after resolve() has been called.\",\r\n );\r\n }\r\n modules.push(module);\r\n },\r\n\r\n registerLazy(descriptor) {\r\n if (resolved) {\r\n throw new Error(\r\n \"[@react-router-modules/runtime] Cannot register modules after resolve() has been called.\",\r\n );\r\n }\r\n lazyModules.push(descriptor);\r\n },\r\n\r\n resolve(options?: ResolveOptions) {\r\n if (resolved) {\r\n throw new Error(\"[@react-router-modules/runtime] resolve() can only be called once.\");\r\n }\r\n resolved = true;\r\n\r\n // Validate — cast is safe since validation only reads structural properties (id, requires)\r\n validateNoDuplicateIds(\r\n modules as ReactiveModuleDescriptor[],\r\n lazyModules as LazyModuleDescriptor[],\r\n );\r\n validateDependencies(modules as ReactiveModuleDescriptor[], availableKeys);\r\n\r\n // Run onRegister lifecycle hooks\r\n const deps = buildDepsObject<TSharedDependencies>(config);\r\n for (const mod of modules) {\r\n try {\r\n mod.lifecycle?.onRegister?.(deps);\r\n } catch (err) {\r\n throw new Error(\r\n `[@react-router-modules/runtime] Module \"${mod.id}\" lifecycle.onRegister() failed: ${err instanceof Error ? err.message : String(err)}`,\r\n { cause: err },\r\n );\r\n }\r\n }\r\n\r\n // Build route tree\r\n const routeBuilderOptions: RouteBuilderOptions = {\r\n rootRoute: options?.rootRoute,\r\n rootComponent: options?.rootComponent,\r\n indexComponent: options?.indexComponent,\r\n notFoundComponent: options?.notFoundComponent,\r\n loader: options?.loader,\r\n authenticatedRoute: options?.authenticatedRoute,\r\n shellRoutes: options?.shellRoutes,\r\n };\r\n const routes = buildRouteTree(\r\n modules as ReactiveModuleDescriptor[],\r\n lazyModules as LazyModuleDescriptor[],\r\n routeBuilderOptions,\r\n );\r\n\r\n // Create React Router instance (use memory router when DOM is unavailable, e.g. tests)\r\n const router =\r\n typeof document !== \"undefined\" ? createBrowserRouter(routes) : createMemoryRouter(routes);\r\n\r\n // Build navigation, slots, and module entries\r\n const navigation: NavigationManifest = buildNavigationManifest(\r\n modules as ReactiveModuleDescriptor[],\r\n );\r\n const slots = buildSlotsManifest<TSlots>(modules, config.slots);\r\n const moduleEntries: ModuleEntry[] = modules.map((mod) => ({\r\n id: mod.id,\r\n version: mod.version,\r\n meta: mod.meta,\r\n component: mod.component,\r\n zones: mod.zones,\r\n }));\r\n\r\n // Build stores, services, and reactive services maps for the context\r\n const stores: Record<string, StoreApi<unknown>> = {};\r\n const services: Record<string, unknown> = {};\r\n const reactiveServices: Record<string, ReactiveService<unknown>> = {};\r\n\r\n if (config.stores) {\r\n for (const [key, store] of Object.entries(config.stores)) {\r\n if (store) stores[key] = store as StoreApi<unknown>;\r\n }\r\n }\r\n if (config.services) {\r\n for (const [key, service] of Object.entries(config.services)) {\r\n if (service !== undefined) services[key] = service;\r\n }\r\n }\r\n if (config.reactiveServices) {\r\n for (const [key, rs] of Object.entries(config.reactiveServices)) {\r\n if (rs) reactiveServices[key] = rs as ReactiveService<unknown>;\r\n }\r\n }\r\n\r\n // Create App component\r\n const App = createAppComponent({\r\n router,\r\n stores,\r\n services,\r\n reactiveServices,\r\n navigation,\r\n slots,\r\n modules: moduleEntries,\r\n providers: options?.providers,\r\n });\r\n\r\n return { App, router, navigation, slots, modules: moduleEntries };\r\n },\r\n };\r\n}\r\n\r\nfunction buildDepsObject<TSharedDependencies extends Record<string, any>>(\r\n config: RegistryConfig<TSharedDependencies, any>,\r\n): TSharedDependencies {\r\n const deps: Record<string, unknown> = {};\r\n\r\n // For stores, get current state as the deps value\r\n // (lifecycle hooks get a snapshot, components use useStore for reactivity)\r\n if (config.stores) {\r\n for (const [key, store] of Object.entries(config.stores)) {\r\n if (store) {\r\n deps[key] = (store as StoreApi<unknown>).getState();\r\n }\r\n }\r\n }\r\n if (config.services) {\r\n for (const [key, service] of Object.entries(config.services)) {\r\n if (service !== undefined) deps[key] = service;\r\n }\r\n }\r\n // For reactive services, get current snapshot\r\n if (config.reactiveServices) {\r\n for (const [key, rs] of Object.entries(config.reactiveServices)) {\r\n if (rs) {\r\n deps[key] = (rs as ReactiveService<unknown>).getSnapshot();\r\n }\r\n }\r\n }\r\n\r\n return deps as TSharedDependencies;\r\n}\r\n","import { useMatches } from \"react-router\";\r\nimport type { ZoneMapOf } from \"@react-router-modules/core\";\r\n\r\n/**\r\n * Read zone components contributed by the currently matched route hierarchy.\r\n *\r\n * Zones are set via React Router's `handle` on individual routes.\r\n * This hook walks all matched routes from root to leaf and returns a merged\r\n * map where the deepest match wins for each zone key.\r\n *\r\n * @example\r\n * // In the shell layout:\r\n * const zones = useZones<AppZones>()\r\n * const DetailPanel = zones.detailPanel\r\n *\r\n * return (\r\n * <div className=\"grid\">\r\n * <main><Outlet /></main>\r\n * <aside>{DetailPanel && <DetailPanel />}</aside>\r\n * </div>\r\n * )\r\n *\r\n * @example\r\n * // In a module's route definition:\r\n * {\r\n * path: ':userId',\r\n * Component: UserDetailPage,\r\n * handle: {\r\n * detailPanel: UserDetailSidebar,\r\n * },\r\n * }\r\n */\r\nexport function useZones<TZones extends ZoneMapOf<TZones>>(): Partial<TZones> {\r\n const matches = useMatches();\r\n const merged: Record<string, unknown> = {};\r\n for (const match of matches) {\r\n const data = (match as any).handle;\r\n if (data && typeof data === \"object\") {\r\n for (const [key, value] of Object.entries(data as Record<string, unknown>)) {\r\n if (value !== undefined) {\r\n merged[key] = value;\r\n }\r\n }\r\n }\r\n }\r\n return merged as Partial<TZones>;\r\n}\r\n","import type { ZoneMapOf } from \"@react-router-modules/core\";\r\nimport { useZones } from \"./zones.js\";\r\nimport { useModules } from \"./modules-context.js\";\r\n\r\n/**\r\n * Read zone components from both the matched route hierarchy AND the\r\n * currently active module (identified by `activeModuleId`).\r\n *\r\n * This unifies two zone contribution patterns:\r\n * - **Route-based modules** contribute zones via React Router's `handle`\r\n * - **Tab-based modules** contribute zones via the `zones` field on their descriptor\r\n *\r\n * When both sources provide a value for the same zone key, the module's\r\n * contribution wins — the assumption is that the active tab's content is more\r\n * relevant than the underlying route's.\r\n *\r\n * @param activeModuleId - The id of the module that is currently active in the\r\n * workspace (e.g. the journey tab's moduleId). Pass `null` or `undefined`\r\n * when no module tab is active — only route zones are returned.\r\n *\r\n * @example\r\n * // In the shell layout:\r\n * const activeTab = getActiveTab(interactionId)\r\n * const moduleId = activeTab?.type === 'native-journey' ? activeTab.journeyId : null\r\n * const zones = useActiveZones<AppZones>(moduleId)\r\n * const Panel = zones.contextualPanel\r\n *\r\n * return (\r\n * <aside>{Panel ? <Panel /> : <DefaultPanel />}</aside>\r\n * )\r\n */\r\nexport function useActiveZones<TZones extends ZoneMapOf<TZones>>(\r\n activeModuleId?: string | null,\r\n): Partial<TZones> {\r\n const routeZones = useZones<TZones>();\r\n const modules = useModules();\r\n\r\n if (!activeModuleId) {\r\n return routeZones;\r\n }\r\n\r\n const activeMod = modules.find((m) => m.id === activeModuleId);\r\n if (!activeMod?.zones) {\r\n return routeZones;\r\n }\r\n\r\n // Module zones override route zones for the same key\r\n return { ...routeZones, ...activeMod.zones } as Partial<TZones>;\r\n}\r\n","import { Component } from \"react\";\r\nimport type { ErrorInfo, ReactNode } from \"react\";\r\n\r\ninterface Props {\r\n moduleId: string;\r\n fallback?: ReactNode;\r\n children: ReactNode;\r\n}\r\n\r\ninterface State {\r\n hasError: boolean;\r\n error: Error | null;\r\n}\r\n\r\nexport class ModuleErrorBoundary extends Component<Props, State> {\r\n override state: State = { hasError: false, error: null };\r\n\r\n static getDerivedStateFromError(error: Error): State {\r\n return { hasError: true, error };\r\n }\r\n\r\n override componentDidCatch(error: Error, info: ErrorInfo) {\r\n console.error(\r\n `[@react-router-modules/runtime] Module \"${this.props.moduleId}\" encountered an error:`,\r\n error,\r\n info,\r\n );\r\n }\r\n\r\n override render() {\r\n if (this.state.hasError) {\r\n if (this.props.fallback) {\r\n return this.props.fallback;\r\n }\r\n return (\r\n <div\r\n style={{\r\n padding: \"1rem\",\r\n border: \"1px solid #e53e3e\",\r\n borderRadius: \"0.5rem\",\r\n margin: \"1rem\",\r\n }}\r\n >\r\n <h3 style={{ color: \"#e53e3e\", margin: \"0 0 0.5rem 0\" }}>\r\n Module "{this.props.moduleId}" encountered an error\r\n </h3>\r\n <pre style={{ fontSize: \"0.875rem\", color: \"#718096\", whiteSpace: \"pre-wrap\" }}>\r\n {this.state.error?.message}\r\n </pre>\r\n </div>\r\n );\r\n }\r\n return this.props.children;\r\n }\r\n}\r\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@react-router-modules/runtime",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/kibertoad/tanstack-react-modules.git",
|
|
7
|
+
"directory": "packages/runtime"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "vite build",
|
|
26
|
+
"dev": "vite build --watch",
|
|
27
|
+
"prepublishOnly": "pnpm build"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@react-router-modules/core": "^1.0.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/react": "^19.0.0",
|
|
34
|
+
"@types/react-dom": "^19.0.0",
|
|
35
|
+
"react": "^19.0.0",
|
|
36
|
+
"react-dom": "^19.0.0",
|
|
37
|
+
"react-router": "^7.6.0",
|
|
38
|
+
"typescript": "^6.0.2",
|
|
39
|
+
"vite": "^8.0.3",
|
|
40
|
+
"vite-plugin-dts": "^4.5.0",
|
|
41
|
+
"zustand": "^5.0.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"react": "^19.0.0",
|
|
45
|
+
"react-dom": "^19.0.0",
|
|
46
|
+
"react-router": "^7.6.0",
|
|
47
|
+
"zustand": "^5.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|