@alepha/react 0.6.10 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/dist/index.browser.cjs +21 -20
  3. package/dist/index.browser.js +2 -3
  4. package/dist/index.cjs +168 -82
  5. package/dist/index.d.ts +415 -232
  6. package/dist/index.js +146 -62
  7. package/dist/{useActive-4QlZKGbw.cjs → useRouterState-AdK-XeM2.cjs} +358 -170
  8. package/dist/{useActive-ClUsghB5.js → useRouterState-qoMq7Y9J.js} +358 -172
  9. package/package.json +11 -10
  10. package/src/components/ClientOnly.tsx +35 -0
  11. package/src/components/ErrorBoundary.tsx +72 -0
  12. package/src/components/ErrorViewer.tsx +161 -0
  13. package/src/components/Link.tsx +10 -4
  14. package/src/components/NestedView.tsx +28 -4
  15. package/src/descriptors/$page.ts +143 -38
  16. package/src/errors/RedirectionError.ts +4 -1
  17. package/src/hooks/RouterHookApi.ts +58 -35
  18. package/src/hooks/useAlepha.ts +12 -0
  19. package/src/hooks/useClient.ts +8 -6
  20. package/src/hooks/useInject.ts +3 -9
  21. package/src/hooks/useQueryParams.ts +4 -7
  22. package/src/hooks/useRouter.ts +6 -0
  23. package/src/index.browser.ts +1 -1
  24. package/src/index.shared.ts +11 -4
  25. package/src/index.ts +7 -4
  26. package/src/providers/BrowserRouterProvider.ts +27 -33
  27. package/src/providers/PageDescriptorProvider.ts +90 -40
  28. package/src/providers/ReactBrowserProvider.ts +21 -27
  29. package/src/providers/ReactServerProvider.ts +215 -77
  30. package/dist/index.browser.cjs.map +0 -1
  31. package/dist/index.browser.js.map +0 -1
  32. package/dist/index.cjs.map +0 -1
  33. package/dist/index.js.map +0 -1
  34. package/dist/useActive-4QlZKGbw.cjs.map +0 -1
  35. package/dist/useActive-ClUsghB5.js.map +0 -1
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import * as _alepha_core from '@alepha/core';
2
- import { Alepha, Static as Static$1, TSchema as TSchema$1, KIND, OPTIONS, Async, Class, TObject as TObject$1 } from '@alepha/core';
3
- import * as _alepha_server from '@alepha/server';
4
- import { HttpClientLink, HttpClient, ServerRouterProvider, ServerHandler, ServerRequest } from '@alepha/server';
5
- import * as react from 'react';
6
- import react__default, { ReactNode, FC, AnchorHTMLAttributes } from 'react';
2
+ import { TSchema as TSchema$1, KIND, OPTIONS, Static as Static$1, Async, Alepha, Service, TObject as TObject$1 } from '@alepha/core';
3
+ import { ServerRoute, ApiLinksResponse, HttpClient, ClientScope, HttpVirtualClient, ServerRouterProvider, ServerTimingProvider, ServerHandler, ServerRequest } from '@alepha/server';
4
+ import * as React from 'react';
5
+ import React__default, { PropsWithChildren, ReactNode, FC, ErrorInfo, AnchorHTMLAttributes } from 'react';
7
6
  import { Root } from 'react-dom/client';
8
7
  import { RouterProvider, Route } from '@alepha/router';
9
8
  import * as react_jsx_runtime from 'react/jsx-runtime';
@@ -126,7 +125,294 @@ interface TSchema extends TKind, SchemaOptions {
126
125
  static: unknown;
127
126
  }
128
127
 
128
+ interface ClientOnlyProps {
129
+ fallback?: ReactNode;
130
+ disabled?: boolean;
131
+ }
132
+ /**
133
+ * A small utility component that renders its children only on the client side.
134
+ *
135
+ * Optionally, you can provide a fallback React node that will be rendered.
136
+ *
137
+ * You should use this component when
138
+ * - you have code that relies on browser-specific APIs
139
+ * - you want to avoid server-side rendering for a specific part of your application
140
+ * - you want to prevent pre-rendering of a component
141
+ */
142
+ declare const ClientOnly: (props: PropsWithChildren<ClientOnlyProps>) => ReactNode;
143
+
144
+ declare const KEY = "PAGE";
145
+ interface PageConfigSchema {
146
+ query?: TSchema$1;
147
+ params?: TSchema$1;
148
+ }
149
+ type TPropsDefault = any;
150
+ type TPropsParentDefault = {};
151
+ interface PageDescriptorOptions<TConfig extends PageConfigSchema = PageConfigSchema, TProps extends object = TPropsDefault, TPropsParent extends object = TPropsParentDefault> extends Pick<ServerRoute, "cache"> {
152
+ /**
153
+ * Name your page.
154
+ *
155
+ * @default Descriptor key
156
+ */
157
+ name?: string;
158
+ /**
159
+ * Optional description of the page.
160
+ */
161
+ description?: string;
162
+ /**
163
+ * Add a pathname to the page.
164
+ *
165
+ * Pathname can contain parameters, like `/post/:slug`.
166
+ *
167
+ * @default ""
168
+ */
169
+ path?: string;
170
+ /**
171
+ * Add an input schema to define:
172
+ * - `params`: parameters from the pathname.
173
+ * - `query`: query parameters from the URL.
174
+ */
175
+ schema?: TConfig;
176
+ /**
177
+ * Load data before rendering the page.
178
+ *
179
+ * This function receives
180
+ * - the request context and
181
+ * - the parent props (if page has a parent)
182
+ *
183
+ * In SSR, the returned data will be serialized and sent to the client, then reused during the client-side hydration.
184
+ *
185
+ * Resolve can be stopped by throwing an error, which will be handled by the `errorHandler` function.
186
+ * It's common to throw a `NotFoundError` to display a 404 page.
187
+ *
188
+ * RedirectError can be thrown to redirect the user to another page.
189
+ */
190
+ resolve?: (context: PageResolve<TConfig, TPropsParent>) => Async<TProps>;
191
+ /**
192
+ * The component to render when the page is loaded.
193
+ *
194
+ * If `lazy` is defined, this will be ignored.
195
+ * Prefer using `lazy` to improve the initial loading time.
196
+ */
197
+ component?: FC<TProps & TPropsParent>;
198
+ /**
199
+ * Lazy load the component when the page is loaded.
200
+ *
201
+ * It's recommended to use this for components to improve the initial loading time
202
+ * and enable code-splitting.
203
+ */
204
+ lazy?: () => Promise<{
205
+ default: FC<TProps & TPropsParent>;
206
+ }>;
207
+ /**
208
+ * Set some children pages and make the page a parent page.
209
+ *
210
+ * /!\ Parent page can't be rendered directly. /!\
211
+ *
212
+ * If you still want to render at this pathname, add a child page with an empty path.
213
+ */
214
+ children?: Array<{
215
+ [OPTIONS]: PageDescriptorOptions;
216
+ }>;
217
+ parent?: {
218
+ [OPTIONS]: PageDescriptorOptions<PageConfigSchema, TPropsParent>;
219
+ };
220
+ can?: () => boolean;
221
+ head?: Head$1 | ((props: TProps, previous?: Head$1) => Head$1);
222
+ errorHandler?: (error: Error) => ReactNode;
223
+ prerender?: boolean | {
224
+ entries?: Array<Partial<PageRequestConfig<TConfig>>>;
225
+ };
226
+ /**
227
+ * If true, the page will be rendered on the client-side.
228
+ */
229
+ client?: boolean | ClientOnlyProps;
230
+ }
231
+ interface PageDescriptor<TConfig extends PageConfigSchema = PageConfigSchema, TProps extends object = TPropsDefault, TPropsParent extends object = TPropsParentDefault> {
232
+ [KIND]: typeof KEY;
233
+ [OPTIONS]: PageDescriptorOptions<TConfig, TProps, TPropsParent>;
234
+ /**
235
+ * For testing or build purposes, this will render the page (with or without the HTML layout) and return the HTML and context.
236
+ * Only valid for server-side rendering, it will throw an error if called on the client-side.
237
+ */
238
+ render: (options?: PageDescriptorRenderOptions) => Promise<PageDescriptorRenderResult>;
239
+ }
240
+ /**
241
+ * Main descriptor for defining a React route in the application.
242
+ */
243
+ declare const $page: {
244
+ <TConfig extends PageConfigSchema = PageConfigSchema, TProps extends object = any, TPropsParent extends object = TPropsParentDefault>(options: PageDescriptorOptions<TConfig, TProps, TPropsParent>): PageDescriptor<TConfig, TProps, TPropsParent>;
245
+ [KIND]: string;
246
+ };
247
+ interface PageDescriptorRenderOptions {
248
+ params?: Record<string, string>;
249
+ query?: Record<string, string>;
250
+ withLayout?: boolean;
251
+ }
252
+ interface PageDescriptorRenderResult {
253
+ html: string;
254
+ context: PageReactContext;
255
+ }
129
256
  interface Head$1 {
257
+ title?: string;
258
+ description?: string;
259
+ titleSeparator?: string;
260
+ htmlAttributes?: Record<string, string>;
261
+ bodyAttributes?: Record<string, string>;
262
+ meta?: Array<{
263
+ name: string;
264
+ content: string;
265
+ }>;
266
+ keywords?: string[];
267
+ author?: string;
268
+ robots?: string;
269
+ themeColor?: string;
270
+ viewport?: string | {
271
+ width?: string;
272
+ height?: string;
273
+ initialScale?: string;
274
+ maximumScale?: string;
275
+ userScalable?: "no" | "yes" | "0" | "1";
276
+ interactiveWidget?: "resizes-visual" | "resizes-content" | "overlays-content";
277
+ };
278
+ og?: {
279
+ title?: string;
280
+ description?: string;
281
+ image?: string;
282
+ url?: string;
283
+ type?: string;
284
+ };
285
+ twitter?: {
286
+ card?: string;
287
+ title?: string;
288
+ description?: string;
289
+ image?: string;
290
+ site?: string;
291
+ };
292
+ }
293
+ interface PageRequestConfig<TConfig extends PageConfigSchema = PageConfigSchema> {
294
+ params: TConfig["params"] extends TSchema$1 ? Static$1<TConfig["params"]> : Record<string, string>;
295
+ query: TConfig["query"] extends TSchema$1 ? Static$1<TConfig["query"]> : Record<string, string>;
296
+ }
297
+ type PageResolve<TConfig extends PageConfigSchema = PageConfigSchema, TPropsParent extends object = TPropsParentDefault> = PageRequestConfig<TConfig> & TPropsParent & PageReactContext;
298
+
299
+ declare const envSchema$2: _alepha_core.TObject<{
300
+ REACT_STRICT_MODE: TBoolean;
301
+ }>;
302
+ declare module "@alepha/core" {
303
+ interface Env extends Partial<Static$1<typeof envSchema$2>> {
304
+ }
305
+ }
306
+ declare class PageDescriptorProvider {
307
+ protected readonly log: _alepha_core.Logger;
308
+ protected readonly env: {
309
+ REACT_STRICT_MODE: boolean;
310
+ };
311
+ protected readonly alepha: Alepha;
312
+ protected readonly pages: PageRoute[];
313
+ getPages(): PageRoute[];
314
+ page(name: string): PageRoute;
315
+ url(name: string, options?: {
316
+ params?: Record<string, string>;
317
+ base?: string;
318
+ }): URL;
319
+ root(state: RouterState, context: PageReactContext): ReactNode;
320
+ createLayers(route: PageRoute, request: PageRequest): Promise<CreateLayersResult>;
321
+ protected getErrorHandler(route: PageRoute): ((error: Error) => ReactNode) | undefined;
322
+ protected createElement(page: PageRoute, props: Record<string, any>): Promise<ReactNode>;
323
+ protected fillHead(page: PageRoute, ctx: PageRequest, props: Record<string, any>): void;
324
+ renderError(error: Error): ReactNode;
325
+ renderEmptyView(): ReactNode;
326
+ href(page: {
327
+ options: {
328
+ name?: string;
329
+ };
330
+ }, params?: Record<string, any>): string;
331
+ compile(path: string, params?: Record<string, string>): string;
332
+ protected renderView(index: number, path: string, view: ReactNode | undefined, page: PageRoute): ReactNode;
333
+ protected readonly configure: _alepha_core.HookDescriptor<"configure">;
334
+ protected map(pages: Array<{
335
+ value: {
336
+ [OPTIONS]: PageDescriptorOptions;
337
+ };
338
+ }>, target: {
339
+ [OPTIONS]: PageDescriptorOptions;
340
+ }): PageRouteEntry;
341
+ add(entry: PageRouteEntry): void;
342
+ protected createMatch(page: PageRoute): string;
343
+ protected _next: number;
344
+ protected nextId(): string;
345
+ }
346
+ declare const isPageRoute: (it: any) => it is PageRoute;
347
+ interface PageRouteEntry extends Omit<PageDescriptorOptions, "children" | "parent"> {
348
+ children?: PageRouteEntry[];
349
+ }
350
+ interface PageRoute extends PageRouteEntry {
351
+ type: "page";
352
+ name: string;
353
+ parent?: PageRoute;
354
+ match: string;
355
+ }
356
+ interface Layer {
357
+ config?: {
358
+ query?: Record<string, any>;
359
+ params?: Record<string, any>;
360
+ context?: Record<string, any>;
361
+ };
362
+ name: string;
363
+ props?: Record<string, any>;
364
+ error?: Error;
365
+ part?: string;
366
+ element: ReactNode;
367
+ index: number;
368
+ path: string;
369
+ }
370
+ type PreviousLayerData = Omit<Layer, "element" | "index" | "path">;
371
+ interface AnchorProps {
372
+ href: string;
373
+ onClick: (ev: any) => any;
374
+ }
375
+ interface RouterState {
376
+ pathname: string;
377
+ search: string;
378
+ layers: Array<Layer>;
379
+ }
380
+ interface TransitionOptions {
381
+ state?: RouterState;
382
+ previous?: PreviousLayerData[];
383
+ context?: PageReactContext;
384
+ }
385
+ interface RouterStackItem {
386
+ route: PageRoute;
387
+ config?: Record<string, any>;
388
+ props?: Record<string, any>;
389
+ error?: Error;
390
+ }
391
+ interface RouterRenderResult {
392
+ state: RouterState;
393
+ context: PageReactContext;
394
+ redirect?: string;
395
+ }
396
+ interface PageRequest extends PageReactContext {
397
+ params: Record<string, any>;
398
+ query: Record<string, string>;
399
+ previous?: PreviousLayerData[];
400
+ }
401
+ interface CreateLayersResult extends RouterState {
402
+ redirect?: string;
403
+ }
404
+ /**
405
+ * It's like RouterState, but publicly available in React context.
406
+ * This is where we store all plugin data!
407
+ */
408
+ interface PageReactContext {
409
+ url: URL;
410
+ head: Head$1;
411
+ onError: (error: Error) => ReactNode;
412
+ links?: ApiLinksResponse;
413
+ }
414
+
415
+ interface Head {
130
416
  title?: string;
131
417
  htmlAttributes?: Record<string, string>;
132
418
  bodyAttributes?: Record<string, string>;
@@ -136,14 +422,14 @@ interface Head$1 {
136
422
  }>;
137
423
  }
138
424
  declare class ServerHeadProvider {
139
- renderHead(template: string, head: Head$1): string;
425
+ renderHead(template: string, head: Head): string;
140
426
  mergeAttributes(existing: string, attrs: Record<string, string>): string;
141
427
  parseAttributes(attrStr: string): Record<string, string>;
142
428
  escapeHtml(str: string): string;
143
429
  }
144
430
 
145
431
  declare class BrowserHeadProvider {
146
- renderHead(document: Document, head: Head$1): void;
432
+ renderHead(document: Document, head: Head): void;
147
433
  }
148
434
 
149
435
  interface BrowserRoute extends Route {
@@ -156,7 +442,7 @@ declare class BrowserRouterProvider extends RouterProvider<BrowserRoute> {
156
442
  add(entry: PageRouteEntry): void;
157
443
  protected readonly configure: _alepha_core.HookDescriptor<"configure">;
158
444
  transition(url: URL, options?: TransitionOptions): Promise<RouterRenderResult>;
159
- root(state: RouterState, context?: PageReactContext): ReactNode;
445
+ root(state: RouterState, context: PageReactContext): ReactNode;
160
446
  }
161
447
 
162
448
  declare const envSchema$1: _alepha_core.TObject<{
@@ -193,10 +479,7 @@ declare class ReactBrowserProvider {
193
479
  protected render(options?: {
194
480
  url?: string;
195
481
  previous?: PreviousLayerData[];
196
- }): Promise<{
197
- url: string;
198
- head: Head;
199
- }>;
482
+ }): Promise<RouterRenderResult>;
200
483
  /**
201
484
  * Get embedded layers from the server.
202
485
  *
@@ -218,17 +501,45 @@ declare class ReactBrowserProvider {
218
501
  interface RouterGoOptions {
219
502
  replace?: boolean;
220
503
  match?: TransitionOptions;
504
+ params?: Record<string, string>;
221
505
  }
222
506
  interface ReactHydrationState {
223
- layers?: PreviousLayerData[];
224
- links?: HttpClientLink[];
507
+ layers?: Array<PreviousLayerData>;
508
+ links?: ApiLinksResponse;
225
509
  }
226
510
 
511
+ interface NestedViewProps {
512
+ children?: ReactNode;
513
+ }
514
+ /**
515
+ * A component that renders the current view of the nested router layer.
516
+ *
517
+ * To be simple, it renders the `element` of the current child page of a parent page.
518
+ *
519
+ * @example
520
+ * ```tsx
521
+ * import { NestedView } from "@alepha/react";
522
+ *
523
+ * class App {
524
+ * parent = $page({
525
+ * component: () => <NestedView />,
526
+ * });
527
+ *
528
+ * child = $page({
529
+ * parent: this.root,
530
+ * component: () => <div>Child Page</div>,
531
+ * });
532
+ * }
533
+ * ```
534
+ */
535
+ declare const NestedView: (props: NestedViewProps) => react_jsx_runtime.JSX.Element;
536
+
227
537
  declare class RouterHookApi {
538
+ private readonly pages;
228
539
  private readonly state;
229
540
  private readonly layer;
230
541
  private readonly browser?;
231
- constructor(state: RouterState, layer: {
542
+ constructor(pages: PageRoute[], state: RouterState, layer: {
232
543
  path: string;
233
544
  }, browser?: ReactBrowserProvider | undefined);
234
545
  /**
@@ -264,29 +575,24 @@ declare class RouterHookApi {
264
575
  */
265
576
  createHref(pathname: HrefLike, layer?: {
266
577
  path: string;
578
+ }, options?: {
579
+ params?: Record<string, any>;
267
580
  }): string;
268
- /**
269
- *
270
- * @param path
271
- * @param options
272
- */
273
- go(path: HrefLike, options?: RouterGoOptions): Promise<void>;
274
- /**
275
- *
276
- * @param path
277
- */
278
- createAnchorProps(path: string): AnchorProps;
581
+ go(path: string, options?: RouterGoOptions): Promise<void>;
582
+ go<T extends object>(path: keyof VirtualRouter<T>, options?: RouterGoOptions): Promise<void>;
583
+ anchor(path: string, options?: {
584
+ params?: Record<string, any>;
585
+ }): AnchorProps;
586
+ anchor<T extends object>(path: keyof VirtualRouter<T>, options?: {
587
+ params?: Record<string, any>;
588
+ }): AnchorProps;
279
589
  /**
280
590
  * Set query params.
281
591
  *
282
592
  * @param record
283
593
  * @param options
284
594
  */
285
- setQueryParams(record: Record<string, any>, options?: {
286
- /**
287
- * If true, this will merge current query params with the new ones.
288
- */
289
- merge?: boolean;
595
+ setQueryParams(record: Record<string, any> | ((queryParams: Record<string, any>) => Record<string, any>), options?: {
290
596
  /**
291
597
  * If true, this will add a new entry to the history stack.
292
598
  */
@@ -299,190 +605,57 @@ type HrefLike = string | {
299
605
  name?: string;
300
606
  };
301
607
  };
302
-
303
- declare const KEY = "PAGE";
304
- interface PageConfigSchema {
305
- query?: TSchema$1;
306
- params?: TSchema$1;
307
- }
308
- type TPropsDefault = any;
309
- type TPropsParentDefault = object;
310
- interface PageDescriptorOptions<TConfig extends PageConfigSchema = PageConfigSchema, TProps extends object = TPropsDefault, TPropsParent extends object = TPropsParentDefault> {
311
- name?: string;
312
- path?: string;
313
- schema?: TConfig;
314
- resolve?: (config: PageResolve<TConfig, TPropsParent>) => Async<TProps>;
315
- component?: FC<TProps & TPropsParent>;
316
- lazy?: () => Promise<{
317
- default: FC<TProps & TPropsParent>;
318
- }>;
319
- children?: Array<{
320
- [OPTIONS]: PageDescriptorOptions;
321
- }>;
322
- parent?: {
323
- [OPTIONS]: PageDescriptorOptions<any, TPropsParent>;
324
- };
325
- can?: () => boolean;
326
- head?: Head | ((props: TProps, previous?: Head) => Head);
327
- errorHandler?: FC<{
328
- error: Error;
329
- url: string;
330
- }>;
331
- }
332
- interface PageDescriptor<TConfig extends PageConfigSchema = PageConfigSchema, TProps extends object = TPropsDefault, TPropsParent extends object = TPropsParentDefault> {
333
- [KIND]: typeof KEY;
334
- [OPTIONS]: PageDescriptorOptions<TConfig, TProps, TPropsParent>;
335
- render: (options?: {
336
- params?: Record<string, string>;
337
- query?: Record<string, string>;
338
- }) => Promise<string>;
339
- go: () => void;
340
- createAnchorProps: (routerHook: RouterHookApi) => {
341
- href: string;
342
- onClick: () => void;
343
- };
344
- can: () => boolean;
345
- }
346
- declare const $page: {
347
- <TConfig extends PageConfigSchema = PageConfigSchema, TProps extends object = any, TPropsParent extends object = object>(options: PageDescriptorOptions<TConfig, TProps, TPropsParent>): PageDescriptor<TConfig, TProps, TPropsParent>;
348
- [KIND]: string;
608
+ type VirtualRouter<T> = {
609
+ [K in keyof T as T[K] extends PageDescriptor ? K : never]: T[K];
349
610
  };
350
- interface Head {
351
- title?: string;
352
- titleSeparator?: string;
353
- htmlAttributes?: Record<string, string>;
354
- bodyAttributes?: Record<string, string>;
355
- meta?: Array<{
356
- name: string;
357
- content: string;
358
- }>;
359
- }
360
- interface PageRequestConfig<TConfig extends PageConfigSchema = PageConfigSchema> {
361
- params: TConfig["params"] extends TSchema$1 ? Static$1<TConfig["params"]> : Record<string, string>;
362
- query: TConfig["query"] extends TSchema$1 ? Static$1<TConfig["query"]> : Record<string, string>;
363
- }
364
- type PageResolve<TConfig extends PageConfigSchema = PageConfigSchema, TPropsParent extends object = TPropsParentDefault> = PageRequestConfig<TConfig> & TPropsParent & PageResolveContext;
365
- interface PageResolveContext {
366
- url: URL;
367
- head: Head;
368
- }
369
611
 
370
- declare class PageDescriptorProvider {
371
- protected readonly log: _alepha_core.Logger;
372
- protected readonly alepha: Alepha;
373
- protected readonly pages: PageRoute[];
374
- getPages(): PageRoute[];
375
- page(name: string): PageRoute;
376
- root(state: RouterState, context?: PageReactContext): ReactNode;
377
- createLayers(route: PageRoute, request: PageRequest): Promise<CreateLayersResult>;
378
- protected getErrorHandler(route: PageRoute): react.FC<{
379
- error: Error;
380
- url: string;
381
- }> | undefined;
382
- protected createElement(page: PageRoute, props: Record<string, any>): Promise<ReactNode>;
383
- protected fillHead(page: PageRoute, ctx: PageRequest, props: Record<string, any>): void;
384
- renderError(e: Error): ReactNode;
385
- renderEmptyView(): ReactNode;
386
- href(page: {
387
- options: {
388
- name?: string;
389
- };
390
- }, params?: Record<string, any>): string;
391
- compile(path: string, params?: Record<string, string>): string;
392
- protected renderView(index: number, path: string, view?: ReactNode): ReactNode;
393
- protected readonly configure: _alepha_core.HookDescriptor<"configure">;
394
- protected map(pages: Array<{
395
- value: {
396
- [OPTIONS]: PageDescriptorOptions;
397
- };
398
- }>, target: {
399
- [OPTIONS]: PageDescriptorOptions;
400
- }): PageRouteEntry;
401
- add(entry: PageRouteEntry): void;
402
- protected createMatch(page: PageRoute): string;
403
- protected _next: number;
404
- protected nextId(): string;
405
- }
406
- declare const isPageRoute: (it: any) => it is PageRoute;
407
- interface PageRouteEntry extends Omit<PageDescriptorOptions, "children" | "parent"> {
408
- children?: PageRouteEntry[];
409
- }
410
- interface PageRoute extends PageRouteEntry {
411
- type: "page";
412
- name: string;
413
- parent?: PageRoute;
414
- match: string;
415
- }
416
- interface Layer {
417
- config?: {
418
- query?: Record<string, any>;
419
- params?: Record<string, any>;
420
- context?: Record<string, any>;
421
- };
422
- name: string;
423
- props?: Record<string, any>;
424
- error?: Error;
425
- part?: string;
426
- element: ReactNode;
427
- index: number;
428
- path: string;
429
- }
430
- type PreviousLayerData = Omit<Layer, "element">;
431
- interface AnchorProps {
432
- href?: string;
433
- onClick?: (ev: any) => any;
434
- }
435
- interface RouterState {
436
- pathname: string;
437
- search: string;
438
- layers: Array<Layer>;
439
- head: Head;
612
+ declare class RedirectionError extends Error {
613
+ readonly page: HrefLike;
614
+ constructor(page: HrefLike);
440
615
  }
441
- interface TransitionOptions {
442
- state?: RouterState;
443
- previous?: PreviousLayerData[];
444
- context?: PageReactContext;
616
+
617
+ /**
618
+ * Props for the ErrorBoundary component.
619
+ */
620
+ interface ErrorBoundaryProps {
621
+ /**
622
+ * Fallback React node to render when an error is caught.
623
+ * If not provided, a default error message will be shown.
624
+ */
625
+ fallback: (error: Error) => ReactNode;
626
+ /**
627
+ * Optional callback that receives the error and error info.
628
+ * Use this to log errors to a monitoring service.
629
+ */
630
+ onError?: (error: Error, info: ErrorInfo) => void;
445
631
  }
446
- interface RouterStackItem {
447
- route: PageRoute;
448
- config?: Record<string, any>;
449
- props?: Record<string, any>;
632
+ /**
633
+ * State of the ErrorBoundary component.
634
+ */
635
+ interface ErrorBoundaryState {
450
636
  error?: Error;
451
637
  }
452
- interface RouterRenderResult {
453
- redirect?: string;
454
- layers: Layer[];
455
- head: Head;
456
- element: ReactNode;
457
- }
458
- interface PageRequest extends PageReactContext {
459
- url: URL;
460
- params: Record<string, any>;
461
- query: Record<string, string>;
462
- head: Head;
463
- previous?: PreviousLayerData[];
464
- }
465
- interface CreateLayersResult extends RouterState {
466
- redirect?: string;
467
- }
468
- interface PageReactContext {
469
- links?: HttpClientLink[];
470
- }
471
-
472
- interface NestedViewProps {
473
- children?: ReactNode;
474
- }
475
638
  /**
476
- * Nested view component
477
- *
478
- * @param props
479
- * @constructor
639
+ * A reusable error boundary for catching rendering errors
640
+ * in any part of the React component tree.
480
641
  */
481
- declare const NestedView: (props: NestedViewProps) => string | number | bigint | boolean | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null;
642
+ declare class ErrorBoundary extends React__default.Component<PropsWithChildren<ErrorBoundaryProps>, ErrorBoundaryState> {
643
+ constructor(props: ErrorBoundaryProps);
644
+ /**
645
+ * Update state so the next render shows the fallback UI.
646
+ */
647
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState;
648
+ /**
649
+ * Lifecycle method called when an error is caught.
650
+ * You can log the error or perform side effects here.
651
+ */
652
+ componentDidCatch(error: Error, info: ErrorInfo): void;
653
+ render(): ReactNode;
654
+ }
482
655
 
483
656
  interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
484
657
  to: string | PageDescriptor;
485
- children?: react__default.ReactNode;
658
+ children?: React__default.ReactNode;
486
659
  }
487
660
  declare const Link: (props: LinkProps) => react_jsx_runtime.JSX.Element | null;
488
661
 
@@ -491,18 +664,27 @@ interface RouterContextValue {
491
664
  state: RouterState;
492
665
  context: PageReactContext;
493
666
  }
494
- declare const RouterContext: react.Context<RouterContextValue | undefined>;
667
+ declare const RouterContext: React.Context<RouterContextValue | undefined>;
495
668
 
496
669
  interface RouterLayerContextValue {
497
670
  index: number;
498
671
  path: string;
499
672
  }
500
- declare const RouterLayerContext: react.Context<RouterLayerContextValue | undefined>;
673
+ declare const RouterLayerContext: React.Context<RouterLayerContextValue | undefined>;
501
674
 
502
- declare const useInject: <T extends object>(clazz: Class<T>) => T;
675
+ declare const useActive: (path: HrefLike) => UseActiveHook;
676
+ interface UseActiveHook {
677
+ isActive: boolean;
678
+ anchorProps: AnchorProps;
679
+ isPending: boolean;
680
+ name?: string;
681
+ }
503
682
 
504
- declare const useClient: () => HttpClient;
505
- declare const useApi: <T extends object>() => _alepha_server.HttpVirtualClient<T>;
683
+ declare const useAlepha: () => Alepha;
684
+
685
+ declare const useClient: <T extends object>(_scope?: ClientScope) => HttpVirtualClient<T>;
686
+
687
+ declare const useInject: <T extends object>(clazz: Service<T>) => T;
506
688
 
507
689
  interface UseQueryParamsHookOptions {
508
690
  format?: "base64" | "querystring";
@@ -528,23 +710,10 @@ declare const useRouterEvents: (opts?: {
528
710
 
529
711
  declare const useRouterState: () => RouterState;
530
712
 
531
- declare const useActive: (path: HrefLike) => UseActiveHook;
532
- interface UseActiveHook {
533
- isActive: boolean;
534
- anchorProps: AnchorProps;
535
- isPending: boolean;
536
- name?: string;
537
- }
538
-
539
- declare class RedirectionError extends Error {
540
- readonly page: HrefLike;
541
- constructor(page: HrefLike);
542
- }
543
-
544
713
  declare const envSchema: TObject<{
545
714
  REACT_SERVER_DIST: TString;
546
715
  REACT_SERVER_PREFIX: TString;
547
- REACT_SSR_ENABLED: TBoolean;
716
+ REACT_SSR_ENABLED: TOptional<TBoolean>;
548
717
  REACT_ROOT_ID: TString;
549
718
  }>;
550
719
  declare module "@alepha/core" {
@@ -562,31 +731,42 @@ declare class ReactServerProvider {
562
731
  protected readonly serverStaticProvider: ServerStaticProvider;
563
732
  protected readonly serverRouterProvider: ServerRouterProvider;
564
733
  protected readonly headProvider: ServerHeadProvider;
734
+ protected readonly serverTimingProvider: ServerTimingProvider;
565
735
  protected readonly env: {
736
+ REACT_SSR_ENABLED?: boolean | undefined;
566
737
  REACT_ROOT_ID: string;
567
738
  REACT_SERVER_DIST: string;
568
739
  REACT_SERVER_PREFIX: string;
569
- REACT_SSR_ENABLED: boolean;
570
740
  };
571
741
  protected readonly ROOT_DIV_REGEX: RegExp;
572
- protected readonly configure: _alepha_core.HookDescriptor<"configure">;
573
- protected registerPages(templateLoader: () => Promise<string | undefined>): Promise<void>;
742
+ readonly onConfigure: _alepha_core.HookDescriptor<"configure">;
743
+ get template(): string | undefined;
744
+ protected registerPages(templateLoader: TemplateLoader): Promise<void>;
574
745
  protected getPublicDirectory(): string;
575
746
  protected configureStaticServer(root: string): Promise<void>;
576
- protected configureVite(): Promise<void>;
577
- protected createRenderFunction(name: string): (options?: {
747
+ protected configureVite(ssrEnabled: boolean): Promise<void>;
748
+ /**
749
+ * For testing purposes, creates a render function that can be used.
750
+ */
751
+ protected createRenderFunction(name: string, withIndex?: boolean): (options?: {
578
752
  params?: Record<string, string>;
579
753
  query?: Record<string, string>;
580
- }) => Promise<string>;
581
- protected createHandler(page: PageRoute, templateLoader: () => Promise<string | undefined>): ServerHandler;
582
- fillTemplate(response: {
754
+ }) => Promise<{
755
+ context: PageRequest;
756
+ html: string;
757
+ }>;
758
+ protected createHandler(page: PageRoute, templateLoader: TemplateLoader): ServerHandler;
759
+ renderToHtml(template: string, state: RouterState, context: PageReactContext): string;
760
+ protected fillTemplate(response: {
583
761
  html: string;
584
762
  }, app: string, script: string): void;
585
763
  }
764
+ type TemplateLoader = () => Promise<string | undefined>;
586
765
 
587
766
  declare module "@alepha/core" {
588
767
  interface Hooks {
589
768
  "react:browser:render": {
769
+ state: RouterState;
590
770
  context: PageReactContext;
591
771
  hydration?: ReactHydrationState;
592
772
  };
@@ -596,6 +776,7 @@ declare module "@alepha/core" {
596
776
  };
597
777
  "react:transition:begin": {
598
778
  state: RouterState;
779
+ context: PageReactContext;
599
780
  };
600
781
  "react:transition:success": {
601
782
  state: RouterState;
@@ -603,9 +784,11 @@ declare module "@alepha/core" {
603
784
  "react:transition:error": {
604
785
  error: Error;
605
786
  state: RouterState;
787
+ context: PageReactContext;
606
788
  };
607
789
  "react:transition:end": {
608
790
  state: RouterState;
791
+ context: PageReactContext;
609
792
  };
610
793
  }
611
794
  }
@@ -614,4 +797,4 @@ declare class ReactModule {
614
797
  constructor();
615
798
  }
616
799
 
617
- export { $page, type AnchorProps, type CreateLayersResult, type Head, type HrefLike, type Layer, Link, NestedView, type PageConfigSchema, type PageDescriptor, type PageDescriptorOptions, PageDescriptorProvider, type PageReactContext, type PageRequest, type PageRequestConfig, type PageResolve, type PageResolveContext, type PageRoute, type PageRouteEntry, type PreviousLayerData, ReactBrowserProvider, type ReactHydrationState, ReactModule, ReactServerProvider, RedirectionError, RouterContext, type RouterContextValue, type RouterGoOptions, RouterHookApi, RouterLayerContext, type RouterLayerContextValue, type RouterRenderResult, type RouterStackItem, type RouterState, type TPropsDefault, type TPropsParentDefault, type TransitionOptions, type UseActiveHook, type UseQueryParamsHookOptions, envSchema, isPageRoute, useActive, useApi, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState };
800
+ export { $page, type AnchorProps, ClientOnly, type CreateLayersResult, ErrorBoundary, type Head$1 as Head, type HrefLike, type Layer, Link, NestedView, type PageConfigSchema, type PageDescriptor, type PageDescriptorOptions, PageDescriptorProvider, type PageDescriptorRenderOptions, type PageDescriptorRenderResult, type PageReactContext, type PageRequest, type PageRequestConfig, type PageResolve, type PageRoute, type PageRouteEntry, type PreviousLayerData, ReactBrowserProvider, type ReactHydrationState, ReactModule, ReactServerProvider, RedirectionError, RouterContext, type RouterContextValue, type RouterGoOptions, RouterHookApi, RouterLayerContext, type RouterLayerContextValue, type RouterRenderResult, type RouterStackItem, type RouterState, type TPropsDefault, type TPropsParentDefault, type TransitionOptions, type UseActiveHook, type UseQueryParamsHookOptions, type VirtualRouter, envSchema, isPageRoute, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState };