@alepha/react 0.7.3 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3,13 +3,13 @@
3
3
  var core = require('@alepha/core');
4
4
  var server = require('@alepha/server');
5
5
  var serverCache = require('@alepha/server-cache');
6
- var useRouterState = require('./useRouterState-C2uo0jXu.cjs');
6
+ var ReactBrowserProvider = require('./ReactBrowserProvider-CXDElhnK.cjs');
7
7
  var node_fs = require('node:fs');
8
8
  var node_path = require('node:path');
9
9
  var serverStatic = require('@alepha/server-static');
10
10
  var server$1 = require('react-dom/server');
11
- require('react/jsx-runtime');
12
- require('react');
11
+ var jsxRuntime = require('react/jsx-runtime');
12
+ var React = require('react');
13
13
  require('@alepha/router');
14
14
 
15
15
  class ServerHeadProvider {
@@ -83,7 +83,7 @@ const envSchema = core.t.object({
83
83
  class ReactServerProvider {
84
84
  log = core.$logger();
85
85
  alepha = core.$inject(core.Alepha);
86
- pageDescriptorProvider = core.$inject(useRouterState.PageDescriptorProvider);
86
+ pageDescriptorProvider = core.$inject(ReactBrowserProvider.PageDescriptorProvider);
87
87
  serverStaticProvider = core.$inject(serverStatic.ServerStaticProvider);
88
88
  serverRouterProvider = core.$inject(server.ServerRouterProvider);
89
89
  headProvider = core.$inject(ServerHeadProvider);
@@ -96,7 +96,7 @@ class ReactServerProvider {
96
96
  onConfigure = core.$hook({
97
97
  name: "configure",
98
98
  handler: async () => {
99
- const pages = this.alepha.getDescriptorValues(useRouterState.$page);
99
+ const pages = this.alepha.getDescriptorValues(ReactBrowserProvider.$page);
100
100
  const ssrEnabled = pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
101
101
  this.alepha.state("ReactServerProvider.ssr", ssrEnabled);
102
102
  for (const { key, instance, value } of pages) {
@@ -354,34 +354,260 @@ class ReactServerProvider {
354
354
  }
355
355
  }
356
356
 
357
- class ReactModule {
358
- alepha = core.$inject(core.Alepha);
359
- constructor() {
360
- this.alepha.with(server.ServerModule).with(serverCache.ServerCacheModule).with(server.ServerLinksProvider).with(useRouterState.PageDescriptorProvider).with(ReactServerProvider);
357
+ class RouterHookApi {
358
+ constructor(pages, state, layer, browser) {
359
+ this.pages = pages;
360
+ this.state = state;
361
+ this.layer = layer;
362
+ this.browser = browser;
363
+ }
364
+ get current() {
365
+ return this.state;
366
+ }
367
+ get pathname() {
368
+ return this.state.pathname;
369
+ }
370
+ get query() {
371
+ const query = {};
372
+ for (const [key, value] of new URLSearchParams(
373
+ this.state.search
374
+ ).entries()) {
375
+ query[key] = String(value);
376
+ }
377
+ return query;
378
+ }
379
+ async back() {
380
+ this.browser?.history.back();
381
+ }
382
+ async forward() {
383
+ this.browser?.history.forward();
384
+ }
385
+ async invalidate(props) {
386
+ await this.browser?.invalidate(props);
387
+ }
388
+ /**
389
+ * Create a valid href for the given pathname.
390
+ *
391
+ * @param pathname
392
+ * @param layer
393
+ */
394
+ createHref(pathname, layer = this.layer, options = {}) {
395
+ if (typeof pathname === "object") {
396
+ pathname = pathname.options.path ?? "";
397
+ }
398
+ if (options.params) {
399
+ for (const [key, value] of Object.entries(options.params)) {
400
+ pathname = pathname.replace(`:${key}`, String(value));
401
+ }
402
+ }
403
+ return pathname.startsWith("/") ? pathname : `${layer.path}/${pathname}`.replace(/\/\/+/g, "/");
404
+ }
405
+ async go(path, options) {
406
+ for (const page of this.pages) {
407
+ if (page.name === path) {
408
+ path = page.path ?? "";
409
+ break;
410
+ }
411
+ }
412
+ await this.browser?.go(this.createHref(path, this.layer, options), options);
413
+ }
414
+ anchor(path, options = {}) {
415
+ for (const page of this.pages) {
416
+ if (page.name === path) {
417
+ path = page.path ?? "";
418
+ break;
419
+ }
420
+ }
421
+ const href = this.createHref(path, this.layer, options);
422
+ return {
423
+ href,
424
+ onClick: (ev) => {
425
+ ev.stopPropagation();
426
+ ev.preventDefault();
427
+ this.go(path, options).catch(console.error);
428
+ }
429
+ };
430
+ }
431
+ /**
432
+ * Set query params.
433
+ *
434
+ * @param record
435
+ * @param options
436
+ */
437
+ setQueryParams(record, options = {}) {
438
+ const func = typeof record === "function" ? record : () => record;
439
+ const search = new URLSearchParams(func(this.query)).toString();
440
+ const state = search ? `${this.pathname}?${search}` : this.pathname;
441
+ if (options.push) {
442
+ window.history.pushState({}, "", state);
443
+ } else {
444
+ window.history.replaceState({}, "", state);
445
+ }
361
446
  }
362
447
  }
363
- core.__bind(useRouterState.$page, ReactModule);
364
448
 
365
- exports.$page = useRouterState.$page;
366
- exports.ClientOnly = useRouterState.ClientOnly;
367
- exports.ErrorBoundary = useRouterState.ErrorBoundary;
368
- exports.Link = useRouterState.Link;
369
- exports.NestedView = useRouterState.NestedView;
370
- exports.PageDescriptorProvider = useRouterState.PageDescriptorProvider;
371
- exports.ReactBrowserProvider = useRouterState.ReactBrowserProvider;
372
- exports.RedirectionError = useRouterState.RedirectionError;
373
- exports.RouterContext = useRouterState.RouterContext;
374
- exports.RouterHookApi = useRouterState.RouterHookApi;
375
- exports.RouterLayerContext = useRouterState.RouterLayerContext;
376
- exports.isPageRoute = useRouterState.isPageRoute;
377
- exports.useActive = useRouterState.useActive;
378
- exports.useAlepha = useRouterState.useAlepha;
379
- exports.useClient = useRouterState.useClient;
380
- exports.useInject = useRouterState.useInject;
381
- exports.useQueryParams = useRouterState.useQueryParams;
382
- exports.useRouter = useRouterState.useRouter;
383
- exports.useRouterEvents = useRouterState.useRouterEvents;
384
- exports.useRouterState = useRouterState.useRouterState;
385
- exports.ReactModule = ReactModule;
449
+ const useRouter = () => {
450
+ const ctx = React.useContext(ReactBrowserProvider.RouterContext);
451
+ const layer = React.useContext(ReactBrowserProvider.RouterLayerContext);
452
+ if (!ctx || !layer) {
453
+ throw new Error("useRouter must be used within a RouterProvider");
454
+ }
455
+ const pages = React.useMemo(() => {
456
+ return ctx.alepha.get(ReactBrowserProvider.PageDescriptorProvider).getPages();
457
+ }, []);
458
+ return React.useMemo(
459
+ () => new RouterHookApi(
460
+ pages,
461
+ ctx.state,
462
+ layer,
463
+ ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider.ReactBrowserProvider) : void 0
464
+ ),
465
+ [layer]
466
+ );
467
+ };
468
+
469
+ const Link = (props) => {
470
+ React.useContext(ReactBrowserProvider.RouterContext);
471
+ const router = useRouter();
472
+ const to = typeof props.to === "string" ? props.to : props.to[core.OPTIONS].path;
473
+ if (!to) {
474
+ return null;
475
+ }
476
+ const can = typeof props.to === "string" ? void 0 : props.to[core.OPTIONS].can;
477
+ if (can && !can()) {
478
+ return null;
479
+ }
480
+ const name = typeof props.to === "string" ? void 0 : props.to[core.OPTIONS].name;
481
+ const anchorProps = {
482
+ ...props,
483
+ to: void 0
484
+ };
485
+ return /* @__PURE__ */ jsxRuntime.jsx("a", { ...router.anchor(to), ...anchorProps, children: props.children ?? name });
486
+ };
487
+
488
+ const useActive = (path) => {
489
+ const router = useRouter();
490
+ const ctx = React.useContext(ReactBrowserProvider.RouterContext);
491
+ const layer = React.useContext(ReactBrowserProvider.RouterLayerContext);
492
+ if (!ctx || !layer) {
493
+ throw new Error("useRouter must be used within a RouterProvider");
494
+ }
495
+ let name;
496
+ if (typeof path === "object" && path.options.name) {
497
+ name = path.options.name;
498
+ }
499
+ const [current, setCurrent] = React.useState(ctx.state.pathname);
500
+ const href = React.useMemo(() => router.createHref(path, layer), [path, layer]);
501
+ const [isPending, setPending] = React.useState(false);
502
+ const isActive = current === href;
503
+ ReactBrowserProvider.useRouterEvents({
504
+ onEnd: ({ state }) => setCurrent(state.pathname)
505
+ });
506
+ return {
507
+ name,
508
+ isPending,
509
+ isActive,
510
+ anchorProps: {
511
+ href,
512
+ onClick: (ev) => {
513
+ ev.stopPropagation();
514
+ ev.preventDefault();
515
+ if (isActive) return;
516
+ if (isPending) return;
517
+ setPending(true);
518
+ router.go(href).then(() => {
519
+ setPending(false);
520
+ });
521
+ }
522
+ }
523
+ };
524
+ };
525
+
526
+ const useInject = (clazz) => {
527
+ const ctx = React.useContext(ReactBrowserProvider.RouterContext);
528
+ if (!ctx) {
529
+ throw new Error("useRouter must be used within a <RouterProvider>");
530
+ }
531
+ return React.useMemo(() => ctx.alepha.get(clazz), []);
532
+ };
533
+
534
+ const useClient = (_scope) => {
535
+ return useInject(server.HttpClient).of();
536
+ };
537
+
538
+ const useQueryParams = (schema, options = {}) => {
539
+ const ctx = React.useContext(ReactBrowserProvider.RouterContext);
540
+ if (!ctx) {
541
+ throw new Error("useQueryParams must be used within a RouterProvider");
542
+ }
543
+ const key = options.key ?? "q";
544
+ const router = useRouter();
545
+ const querystring = router.query[key];
546
+ const [queryParams, setQueryParams] = React.useState(
547
+ decode(ctx.alepha, schema, router.query[key])
548
+ );
549
+ React.useEffect(() => {
550
+ setQueryParams(decode(ctx.alepha, schema, querystring));
551
+ }, [querystring]);
552
+ return [
553
+ queryParams,
554
+ (queryParams2) => {
555
+ setQueryParams(queryParams2);
556
+ router.setQueryParams((data) => {
557
+ return { ...data, [key]: encode(ctx.alepha, schema, queryParams2) };
558
+ });
559
+ }
560
+ ];
561
+ };
562
+ const encode = (alepha, schema, data) => {
563
+ return btoa(JSON.stringify(alepha.parse(schema, data)));
564
+ };
565
+ const decode = (alepha, schema, data) => {
566
+ try {
567
+ return alepha.parse(schema, JSON.parse(atob(decodeURIComponent(data))));
568
+ } catch (_error) {
569
+ return {};
570
+ }
571
+ };
572
+
573
+ const useRouterState = () => {
574
+ const ctx = React.useContext(ReactBrowserProvider.RouterContext);
575
+ const layer = React.useContext(ReactBrowserProvider.RouterLayerContext);
576
+ if (!ctx || !layer) {
577
+ throw new Error("useRouter must be used within a RouterProvider");
578
+ }
579
+ const [state, setState] = React.useState(ctx.state);
580
+ ReactBrowserProvider.useRouterEvents({
581
+ onEnd: ({ state: state2 }) => setState({ ...state2 })
582
+ });
583
+ return state;
584
+ };
585
+
586
+ class AlephaReact {
587
+ name = "alepha.react";
588
+ $services = (alepha) => alepha.with(server.AlephaServer).with(serverCache.AlephaServerCache).with(ReactServerProvider).with(ReactBrowserProvider.PageDescriptorProvider);
589
+ }
590
+ core.__bind(ReactBrowserProvider.$page, AlephaReact);
591
+
592
+ exports.$page = ReactBrowserProvider.$page;
593
+ exports.ClientOnly = ReactBrowserProvider.ClientOnly;
594
+ exports.ErrorBoundary = ReactBrowserProvider.ErrorBoundary;
595
+ exports.NestedView = ReactBrowserProvider.NestedView;
596
+ exports.PageDescriptorProvider = ReactBrowserProvider.PageDescriptorProvider;
597
+ exports.ReactBrowserProvider = ReactBrowserProvider.ReactBrowserProvider;
598
+ exports.RedirectionError = ReactBrowserProvider.RedirectionError;
599
+ exports.RouterContext = ReactBrowserProvider.RouterContext;
600
+ exports.RouterLayerContext = ReactBrowserProvider.RouterLayerContext;
601
+ exports.isPageRoute = ReactBrowserProvider.isPageRoute;
602
+ exports.useAlepha = ReactBrowserProvider.useAlepha;
603
+ exports.useRouterEvents = ReactBrowserProvider.useRouterEvents;
604
+ exports.AlephaReact = AlephaReact;
605
+ exports.Link = Link;
386
606
  exports.ReactServerProvider = ReactServerProvider;
387
- exports.envSchema = envSchema;
607
+ exports.RouterHookApi = RouterHookApi;
608
+ exports.useActive = useActive;
609
+ exports.useClient = useClient;
610
+ exports.useInject = useInject;
611
+ exports.useQueryParams = useQueryParams;
612
+ exports.useRouter = useRouter;
613
+ exports.useRouterState = useRouterState;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _alepha_core from '@alepha/core';
2
- import { TSchema as TSchema$1, KIND, OPTIONS, Static as Static$1, Async, Alepha, Service, TObject as TObject$1 } from '@alepha/core';
2
+ import { TSchema as TSchema$1, KIND, OPTIONS, Static, Async, Alepha, Service, TObject, Module } from '@alepha/core';
3
3
  import { ServerRoute, ServerRequest, ApiLinksResponse, HttpClient, ClientScope, HttpVirtualClient, ServerRouterProvider, ServerTimingProvider, ServerHandler } from '@alepha/server';
4
4
  import * as React from 'react';
5
5
  import React__default, { PropsWithChildren, ReactNode, FC, ErrorInfo, AnchorHTMLAttributes } from 'react';
@@ -17,10 +17,6 @@ declare const Hint: unique symbol;
17
17
  /** Symbol key applied to types */
18
18
  declare const Kind: unique symbol;
19
19
 
20
- type TReadonly<T extends TSchema> = T & {
21
- [ReadonlyKind]: 'Readonly';
22
- };
23
-
24
20
  type StringFormatOption = 'date-time' | 'time' | 'date' | 'email' | 'idn-email' | 'hostname' | 'idn-hostname' | 'ipv4' | 'ipv6' | 'uri' | 'uri-reference' | 'iri' | 'uuid' | 'iri-reference' | 'uri-template' | 'json-pointer' | 'relative-json-pointer' | 'regex' | ({} & string);
25
21
  type StringContentEncodingOption = '7bit' | '8bit' | 'binary' | 'quoted-printable' | 'base64' | ({} & string);
26
22
  interface StringOptions extends SchemaOptions {
@@ -53,49 +49,6 @@ type TOptional<T extends TSchema> = T & {
53
49
  [OptionalKind]: 'Optional';
54
50
  };
55
51
 
56
- /** Creates a static type from a TypeBox type */
57
- type Static<Type extends TSchema, Params extends unknown[] = [], Result = (Type & {
58
- params: Params;
59
- })['static']> = Result;
60
-
61
- type ReadonlyOptionalPropertyKeys<T extends TProperties> = {
62
- [K in keyof T]: T[K] extends TReadonly<TSchema> ? (T[K] extends TOptional<T[K]> ? K : never) : never;
63
- }[keyof T];
64
- type ReadonlyPropertyKeys<T extends TProperties> = {
65
- [K in keyof T]: T[K] extends TReadonly<TSchema> ? (T[K] extends TOptional<T[K]> ? never : K) : never;
66
- }[keyof T];
67
- type OptionalPropertyKeys<T extends TProperties> = {
68
- [K in keyof T]: T[K] extends TOptional<TSchema> ? (T[K] extends TReadonly<T[K]> ? never : K) : never;
69
- }[keyof T];
70
- type RequiredPropertyKeys<T extends TProperties> = keyof Omit<T, ReadonlyOptionalPropertyKeys<T> | ReadonlyPropertyKeys<T> | OptionalPropertyKeys<T>>;
71
- type ObjectStaticProperties<T extends TProperties, R extends Record<keyof any, unknown>> = Evaluate<(Readonly<Partial<Pick<R, ReadonlyOptionalPropertyKeys<T>>>> & Readonly<Pick<R, ReadonlyPropertyKeys<T>>> & Partial<Pick<R, OptionalPropertyKeys<T>>> & Required<Pick<R, RequiredPropertyKeys<T>>>)>;
72
- type ObjectStatic<T extends TProperties, P extends unknown[]> = ObjectStaticProperties<T, {
73
- [K in keyof T]: Static<T[K], P>;
74
- }>;
75
- type TPropertyKey = string | number;
76
- type TProperties = Record<TPropertyKey, TSchema>;
77
- type TAdditionalProperties = undefined | TSchema | boolean;
78
- interface ObjectOptions extends SchemaOptions {
79
- /** Additional property constraints for this object */
80
- additionalProperties?: TAdditionalProperties;
81
- /** The minimum number of properties allowed on this object */
82
- minProperties?: number;
83
- /** The maximum number of properties allowed on this object */
84
- maxProperties?: number;
85
- }
86
- interface TObject<T extends TProperties = TProperties> extends TSchema, ObjectOptions {
87
- [Kind]: 'Object';
88
- static: ObjectStatic<T, this['params']>;
89
- additionalProperties?: TAdditionalProperties;
90
- type: 'object';
91
- properties: T;
92
- required?: string[];
93
- }
94
-
95
- type Evaluate<T> = T extends infer O ? {
96
- [K in keyof O]: O[K];
97
- } : never;
98
-
99
52
  interface SchemaOptions {
100
53
  $schema?: string;
101
54
  /** Id for this schema */
@@ -292,8 +245,8 @@ interface Head$1 {
292
245
  };
293
246
  }
294
247
  interface PageRequestConfig<TConfig extends PageConfigSchema = PageConfigSchema> {
295
- params: TConfig["params"] extends TSchema$1 ? Static$1<TConfig["params"]> : Record<string, string>;
296
- query: TConfig["query"] extends TSchema$1 ? Static$1<TConfig["query"]> : Record<string, string>;
248
+ params: TConfig["params"] extends TSchema$1 ? Static<TConfig["params"]> : Record<string, string>;
249
+ query: TConfig["query"] extends TSchema$1 ? Static<TConfig["query"]> : Record<string, string>;
297
250
  }
298
251
  type PageResolve<TConfig extends PageConfigSchema = PageConfigSchema, TPropsParent extends object = TPropsParentDefault> = PageRequestConfig<TConfig> & TPropsParent & PageReactContext;
299
252
 
@@ -301,7 +254,7 @@ declare const envSchema$1: _alepha_core.TObject<{
301
254
  REACT_STRICT_MODE: TBoolean;
302
255
  }>;
303
256
  declare module "@alepha/core" {
304
- interface Env extends Partial<Static$1<typeof envSchema$1>> {
257
+ interface Env extends Partial<Static<typeof envSchema$1>> {
305
258
  }
306
259
  }
307
260
  declare class PageDescriptorProvider {
@@ -647,7 +600,7 @@ interface UseQueryParamsHookOptions {
647
600
  key?: string;
648
601
  push?: boolean;
649
602
  }
650
- declare const useQueryParams: <T extends TObject$1>(schema: T, options?: UseQueryParamsHookOptions) => [Static$1<T>, (data: Static$1<T>) => void];
603
+ declare const useQueryParams: <T extends TObject>(schema: T, options?: UseQueryParamsHookOptions) => [Static<T>, (data: Static<T>) => void];
651
604
 
652
605
  declare const useRouter: () => RouterHookApi;
653
606
 
@@ -666,14 +619,14 @@ declare const useRouterEvents: (opts?: {
666
619
 
667
620
  declare const useRouterState: () => RouterState;
668
621
 
669
- declare const envSchema: TObject<{
622
+ declare const envSchema: _alepha_core.TObject<{
670
623
  REACT_SERVER_DIST: TString;
671
624
  REACT_SERVER_PREFIX: TString;
672
625
  REACT_SSR_ENABLED: TOptional<TBoolean>;
673
626
  REACT_ROOT_ID: TString;
674
627
  }>;
675
628
  declare module "@alepha/core" {
676
- interface Env extends Partial<Static$1<typeof envSchema>> {
629
+ interface Env extends Partial<Static<typeof envSchema>> {
677
630
  }
678
631
  interface State {
679
632
  "ReactServerProvider.template"?: string;
@@ -748,9 +701,18 @@ declare module "@alepha/core" {
748
701
  };
749
702
  }
750
703
  }
751
- declare class ReactModule {
752
- protected readonly alepha: Alepha;
753
- constructor();
704
+ /**
705
+ * Alepha React Module
706
+ *
707
+ * Alepha React Module contains a router for client-side navigation and server-side rendering.
708
+ * Routes can be defined using the `$page` descriptor.
709
+ *
710
+ * @see {@link $page}
711
+ * @module alepha.react
712
+ */
713
+ declare class AlephaReact implements Module {
714
+ readonly name = "alepha.react";
715
+ readonly $services: (alepha: Alepha) => Alepha;
754
716
  }
755
717
 
756
- 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 };
718
+ export { $page, AlephaReact, 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, 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, isPageRoute, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState };