@alepha/react 0.9.1 → 0.9.3

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.
@@ -41,7 +41,10 @@ const NestedView = (props: NestedViewProps) => {
41
41
 
42
42
  useRouterEvents(
43
43
  {
44
- onEnd: ({ state }) => {
44
+ onEnd: ({ state, context }) => {
45
+ if (app) {
46
+ app.context = context;
47
+ }
45
48
  if (!state.layers[index]?.cache) {
46
49
  setView(state.layers[index]?.element);
47
50
  }
@@ -57,7 +60,13 @@ const NestedView = (props: NestedViewProps) => {
57
60
  const element = view ?? props.children ?? null;
58
61
 
59
62
  return (
60
- <ErrorBoundary fallback={app.context.onError!}>{element}</ErrorBoundary>
63
+ <ErrorBoundary
64
+ fallback={(error) => {
65
+ return app.context.onError?.(error, app.context) as ReactNode;
66
+ }}
67
+ >
68
+ {element}
69
+ </ErrorBoundary>
61
70
  );
62
71
  };
63
72
 
@@ -1,4 +1,6 @@
1
- export default function NotFoundPage() {
1
+ import type { CSSProperties } from "react";
2
+
3
+ export default function NotFoundPage(props: { style?: CSSProperties }) {
2
4
  return (
3
5
  <div
4
6
  style={{
@@ -10,6 +12,7 @@ export default function NotFoundPage() {
10
12
  textAlign: "center",
11
13
  fontFamily: "sans-serif",
12
14
  padding: "1rem",
15
+ ...props.style,
13
16
  }}
14
17
  >
15
18
  <h1 style={{ fontSize: "1rem", marginBottom: "0.5rem" }}>
@@ -0,0 +1,4 @@
1
+ import type { Alepha } from "@alepha/core";
2
+ import { createContext } from "react";
3
+
4
+ export const AlephaContext = createContext<Alepha | undefined>(undefined);
@@ -1,4 +1,3 @@
1
- import type { Alepha } from "@alepha/core";
2
1
  import { createContext } from "react";
3
2
  import type {
4
3
  PageReactContext,
@@ -6,7 +5,6 @@ import type {
6
5
  } from "../providers/PageDescriptorProvider.ts";
7
6
 
8
7
  export interface RouterContextValue {
9
- alepha: Alepha;
10
8
  state: RouterState;
11
9
  context: PageReactContext;
12
10
  }
@@ -3,7 +3,6 @@ import {
3
3
  createDescriptor,
4
4
  Descriptor,
5
5
  KIND,
6
- NotImplementedError,
7
6
  type Static,
8
7
  type TSchema,
9
8
  } from "@alepha/core";
@@ -11,6 +10,7 @@ import type { ServerRequest } from "@alepha/server";
11
10
  import type { ServerRouteCache } from "@alepha/server-cache";
12
11
  import type { FC, ReactNode } from "react";
13
12
  import type { ClientOnlyProps } from "../components/ClientOnly.tsx";
13
+ import type { Redirection } from "../errors/Redirection.ts";
14
14
  import type { PageReactContext } from "../providers/PageDescriptorProvider.ts";
15
15
 
16
16
  /**
@@ -109,13 +109,52 @@ export interface PageDescriptorOptions<
109
109
 
110
110
  can?: () => boolean;
111
111
 
112
- errorHandler?: (error: Error) => ReactNode;
113
-
114
112
  /**
115
- * If true, the page will be rendered on the build time.
116
- * Works only with viteAlepha plugin.
113
+ * Catch any error from the `resolve` function or during `rendering`.
114
+ *
115
+ * Expected to return one of the following:
116
+ * - a ReactNode to render an error page
117
+ * - a Redirection to redirect the user
118
+ * - undefined to let the error propagate
119
+ *
120
+ * If not defined, the error will be thrown and handled by the server or client error handler.
121
+ * If a leaf $page does not define an error handler, the error can be caught by parent pages.
117
122
  *
123
+ * @example Catch a 404 from API and render a custom not found component:
124
+ * ```ts
125
+ * resolve: async ({ params, query }) => {
126
+ * api.fetch("/api/resource", { params, query });
127
+ * },
128
+ * errorHandler: (error, context) => {
129
+ * if (HttpError.is(error, 404)) {
130
+ * return <ResourceNotFound />;
131
+ * }
132
+ * }
133
+ * ```
134
+ *
135
+ * @example Catch an 401 error and redirect the user to the login page:
136
+ * ```ts
137
+ * resolve: async ({ params, query }) => {
138
+ * // but the user is not authenticated
139
+ * api.fetch("/api/resource", { params, query });
140
+ * },
141
+ * errorHandler: (error, context) => {
142
+ * if (HttpError.is(error, 401)) {
143
+ * // throwing a Redirection is also valid!
144
+ * return new Redirection("/login");
145
+ * }
146
+ * }
147
+ * ```
148
+ */
149
+ errorHandler?: ErrorHandler;
150
+
151
+ /**
152
+ * If true, the page will be considered as a static page, immutable and cacheable.
118
153
  * Replace boolean by an object to define static entries. (e.g. list of params/query)
154
+ *
155
+ * For now, it only works with `@alepha/vite` which can pre-render the page at build time.
156
+ *
157
+ * It will act as timeless cached page server-side. You can use `cache` to configure the cache behavior.
119
158
  */
120
159
  static?:
121
160
  | boolean
@@ -123,21 +162,44 @@ export interface PageDescriptorOptions<
123
162
  entries?: Array<Partial<PageRequestConfig<TConfig>>>;
124
163
  };
125
164
 
165
+ cache?: ServerRouteCache;
166
+
126
167
  /**
127
- * If true, the page will be rendered on the client-side.
168
+ * If true, force the page to be rendered only on the client-side.
169
+ * It uses the `<ClientOnly/>` component to render the page.
128
170
  */
129
171
  client?: boolean | ClientOnlyProps;
130
172
 
131
- afterHandler?: (request: ServerRequest) => any;
173
+ /**
174
+ * Called before the server response is sent to the client.
175
+ */
176
+ onServerResponse?: (request: ServerRequest) => any;
132
177
 
133
- cache?: ServerRouteCache;
178
+ /**
179
+ * Called when user leaves the page. (browser only)
180
+ */
181
+ onLeave?: () => void;
134
182
  }
135
183
 
184
+ export type ErrorHandler = (
185
+ error: Error,
186
+ context: PageReactContext,
187
+ ) => ReactNode | Redirection | undefined;
188
+
136
189
  export class PageDescriptor<
137
190
  TConfig extends PageConfigSchema = PageConfigSchema,
138
191
  TProps extends object = TPropsDefault,
139
192
  TPropsParent extends object = TPropsParentDefault,
140
193
  > extends Descriptor<PageDescriptorOptions<TConfig, TProps, TPropsParent>> {
194
+ protected onInit() {
195
+ if (this.options.static) {
196
+ this.options.cache ??= {
197
+ provider: "memory",
198
+ ttl: [1, "week"],
199
+ };
200
+ }
201
+ }
202
+
141
203
  public get name(): string {
142
204
  return this.options.name ?? this.config.propertyKey;
143
205
  }
@@ -149,7 +211,7 @@ export class PageDescriptor<
149
211
  public async render(
150
212
  options?: PageDescriptorRenderOptions,
151
213
  ): Promise<PageDescriptorRenderResult> {
152
- throw new NotImplementedError("");
214
+ throw new Error("render method is not implemented in this environment");
153
215
  }
154
216
  }
155
217
 
@@ -1,6 +1,6 @@
1
1
  import type { HrefLike } from "../hooks/RouterHookApi.ts";
2
2
 
3
- export class RedirectionError extends Error {
3
+ export class Redirection extends Error {
4
4
  public readonly page: HrefLike;
5
5
 
6
6
  constructor(page: HrefLike) {
@@ -1,6 +1,7 @@
1
1
  import type { PageDescriptor } from "../descriptors/$page.ts";
2
2
  import type {
3
3
  AnchorProps,
4
+ PageDescriptorProvider,
4
5
  PageReactContext,
5
6
  PageRoute,
6
7
  RouterState,
@@ -10,7 +11,7 @@ import type {
10
11
  RouterGoOptions,
11
12
  } from "../providers/ReactBrowserProvider.ts";
12
13
 
13
- export class RouterHookApi {
14
+ export class RouterHookApi<T extends object> {
14
15
  constructor(
15
16
  private readonly pages: PageRoute[],
16
17
  private readonly context: PageReactContext,
@@ -18,9 +19,26 @@ export class RouterHookApi {
18
19
  private readonly layer: {
19
20
  path: string;
20
21
  },
22
+ private readonly pageApi: PageDescriptorProvider,
21
23
  private readonly browser?: ReactBrowserProvider,
22
24
  ) {}
23
25
 
26
+ public path(
27
+ name: keyof VirtualRouter<T>,
28
+ config: {
29
+ params?: Record<string, string>;
30
+ query?: Record<string, string>;
31
+ } = {},
32
+ ): string {
33
+ return this.pageApi.pathname(name as string, {
34
+ params: {
35
+ ...this.context.params,
36
+ ...config.params,
37
+ },
38
+ query: config.query,
39
+ });
40
+ }
41
+
24
42
  public getURL(): URL {
25
43
  if (!this.browser) {
26
44
  return this.context.url;
@@ -95,48 +113,54 @@ export class RouterHookApi {
95
113
  }
96
114
 
97
115
  public async go(path: string, options?: RouterGoOptions): Promise<void>;
98
- public async go<T extends object>(
116
+ public async go(
99
117
  path: keyof VirtualRouter<T>,
100
118
  options?: RouterGoOptions,
101
119
  ): Promise<void>;
102
- public async go(path: string, options?: RouterGoOptions): Promise<void> {
120
+ public async go(
121
+ path: string | keyof VirtualRouter<T>,
122
+ options?: RouterGoOptions,
123
+ ): Promise<void> {
103
124
  for (const page of this.pages) {
104
125
  if (page.name === path) {
105
- path = page.path ?? "";
106
- break;
126
+ await this.browser?.go(
127
+ this.path(path as keyof VirtualRouter<T>, options),
128
+ options,
129
+ );
130
+ return;
107
131
  }
108
132
  }
109
133
 
110
- await this.browser?.go(this.createHref(path, this.layer, options), options);
134
+ await this.browser?.go(path as string, options);
111
135
  }
112
136
 
113
137
  public anchor(
114
138
  path: string,
115
139
  options?: { params?: Record<string, any> },
116
140
  ): AnchorProps;
117
- public anchor<T extends object>(
141
+ public anchor(
118
142
  path: keyof VirtualRouter<T>,
119
143
  options?: { params?: Record<string, any> },
120
144
  ): AnchorProps;
121
145
  public anchor(
122
- path: string,
146
+ path: string | keyof VirtualRouter<T>,
123
147
  options: { params?: Record<string, any> } = {},
124
148
  ): AnchorProps {
149
+ let href = path as string;
125
150
  for (const page of this.pages) {
126
151
  if (page.name === path) {
127
- path = page.path ?? "";
152
+ href = this.path(path as keyof VirtualRouter<T>, options);
128
153
  break;
129
154
  }
130
155
  }
131
156
 
132
- const href = this.createHref(path, this.layer, options);
133
157
  return {
134
158
  href,
135
159
  onClick: (ev: any) => {
136
160
  ev.stopPropagation();
137
161
  ev.preventDefault();
138
162
 
139
- this.go(path, options).catch(console.error);
163
+ this.go(href, options).catch(console.error);
140
164
  },
141
165
  };
142
166
  }
@@ -6,7 +6,7 @@ import type { HrefLike } from "./RouterHookApi.ts";
6
6
  import { useRouter } from "./useRouter.ts";
7
7
  import { useRouterEvents } from "./useRouterEvents.ts";
8
8
 
9
- export const useActive = (path: HrefLike): UseActiveHook => {
9
+ export const useActive = (path?: HrefLike): UseActiveHook => {
10
10
  const router = useRouter();
11
11
  const ctx = useContext(RouterContext);
12
12
  const layer = useContext(RouterLayerContext);
@@ -14,29 +14,36 @@ export const useActive = (path: HrefLike): UseActiveHook => {
14
14
  throw new Error("useRouter must be used within a RouterProvider");
15
15
  }
16
16
 
17
- let name: string | undefined;
18
- if (typeof path === "object" && path.options.name) {
19
- name = path.options.name;
20
- }
21
-
22
17
  const [current, setCurrent] = useState(ctx.state.pathname);
23
- const href = useMemo(() => router.createHref(path, layer), [path, layer]);
18
+ const href = useMemo(
19
+ () => router.createHref(path ?? "", layer),
20
+ [path, layer],
21
+ );
22
+
24
23
  const [isPending, setPending] = useState(false);
25
- const isActive = current === href;
26
24
 
27
- useRouterEvents({
28
- onEnd: ({ state }) => setCurrent(state.pathname),
29
- });
25
+ // TODO: loose [default] or strict
26
+ // TODO: startWith: true (e.g. /p/1 should match /p/1/2)
27
+ const isActive =
28
+ current === href || current === `${href}/` || `${current}/` === href;
29
+
30
+ useRouterEvents(
31
+ {
32
+ onEnd: ({ state }) => {
33
+ path ? setCurrent(state.pathname) : undefined;
34
+ },
35
+ },
36
+ [path],
37
+ );
30
38
 
31
39
  return {
32
- name,
33
40
  isPending,
34
41
  isActive,
35
42
  anchorProps: {
36
43
  href,
37
- onClick: (ev: any) => {
38
- ev.stopPropagation();
39
- ev.preventDefault();
44
+ onClick: (ev?: any) => {
45
+ ev?.stopPropagation();
46
+ ev?.preventDefault();
40
47
  if (isActive) return;
41
48
  if (isPending) return;
42
49
 
@@ -1,12 +1,12 @@
1
1
  import type { Alepha } from "@alepha/core";
2
2
  import { useContext } from "react";
3
- import { RouterContext } from "../contexts/RouterContext.ts";
3
+ import { AlephaContext } from "../contexts/AlephaContext.ts";
4
4
 
5
5
  export const useAlepha = (): Alepha => {
6
- const routerContext = useContext(RouterContext);
7
- if (!routerContext) {
8
- throw new Error("useAlepha must be used within a RouterProvider");
6
+ const alepha = useContext(AlephaContext);
7
+ if (!alepha) {
8
+ throw new Error("useAlepha must be used within an AlephaContext.Provider");
9
9
  }
10
10
 
11
- return routerContext.alepha;
11
+ return alepha;
12
12
  };
@@ -4,9 +4,11 @@ import {
4
4
  LinkProvider,
5
5
  } from "@alepha/server-links";
6
6
  import { useInject } from "./useInject.ts";
7
+ import { useStore } from "./useStore.ts";
7
8
 
8
9
  export const useClient = <T extends object>(
9
10
  _scope?: ClientScope,
10
11
  ): HttpVirtualClient<T> => {
12
+ useStore("user" as any);
11
13
  return useInject(LinkProvider).client<T>();
12
14
  };
@@ -1,12 +1,9 @@
1
1
  import type { Service } from "@alepha/core";
2
- import { useContext, useMemo } from "react";
3
- import { RouterContext } from "../contexts/RouterContext.ts";
2
+ import { useMemo } from "react";
3
+ import { useAlepha } from "./useAlepha.ts";
4
4
 
5
- export const useInject = <T extends object>(clazz: Service<T>): T => {
6
- const ctx = useContext(RouterContext);
7
- if (!ctx) {
8
- throw new Error("useRouter must be used within a <RouterProvider>");
9
- }
5
+ export const useInject = <T extends object>(service: Service<T>): T => {
6
+ const alepha = useAlepha();
10
7
 
11
- return useMemo(() => ctx.alepha.inject(clazz), []);
8
+ return useMemo(() => alepha.inject(service), []);
12
9
  };
@@ -1,6 +1,6 @@
1
1
  import type { Alepha, Static, TObject } from "@alepha/core";
2
- import { useContext, useEffect, useState } from "react";
3
- import { RouterContext } from "../contexts/RouterContext.ts";
2
+ import { useEffect, useState } from "react";
3
+ import { useAlepha } from "./useAlepha.ts";
4
4
  import { useRouter } from "./useRouter.ts";
5
5
 
6
6
  export interface UseQueryParamsHookOptions {
@@ -13,21 +13,18 @@ export const useQueryParams = <T extends TObject>(
13
13
  schema: T,
14
14
  options: UseQueryParamsHookOptions = {},
15
15
  ): [Static<T>, (data: Static<T>) => void] => {
16
- const ctx = useContext(RouterContext);
17
- if (!ctx) {
18
- throw new Error("useQueryParams must be used within a RouterProvider");
19
- }
16
+ const alepha = useAlepha();
20
17
 
21
18
  const key = options.key ?? "q";
22
19
  const router = useRouter();
23
20
  const querystring = router.query[key];
24
21
 
25
22
  const [queryParams, setQueryParams] = useState(
26
- decode(ctx.alepha, schema, router.query[key]),
23
+ decode(alepha, schema, router.query[key]),
27
24
  );
28
25
 
29
26
  useEffect(() => {
30
- setQueryParams(decode(ctx.alepha, schema, querystring));
27
+ setQueryParams(decode(alepha, schema, querystring));
31
28
  }, [querystring]);
32
29
 
33
30
  return [
@@ -35,7 +32,7 @@ export const useQueryParams = <T extends TObject>(
35
32
  (queryParams: Static<T>) => {
36
33
  setQueryParams(queryParams);
37
34
  router.setQueryParams((data) => {
38
- return { ...data, [key]: encode(ctx.alepha, schema, queryParams) };
35
+ return { ...data, [key]: encode(alepha, schema, queryParams) };
39
36
  });
40
37
  },
41
38
  ];
@@ -4,8 +4,10 @@ import { RouterLayerContext } from "../contexts/RouterLayerContext.ts";
4
4
  import { PageDescriptorProvider } from "../providers/PageDescriptorProvider.ts";
5
5
  import { ReactBrowserProvider } from "../providers/ReactBrowserProvider.ts";
6
6
  import { RouterHookApi } from "./RouterHookApi.ts";
7
+ import { useAlepha } from "./useAlepha.ts";
7
8
 
8
- export const useRouter = (): RouterHookApi => {
9
+ export const useRouter = <T extends object>(): RouterHookApi<T> => {
10
+ const alepha = useAlepha();
9
11
  const ctx = useContext(RouterContext);
10
12
  const layer = useContext(RouterLayerContext);
11
13
  if (!ctx || !layer) {
@@ -13,7 +15,7 @@ export const useRouter = (): RouterHookApi => {
13
15
  }
14
16
 
15
17
  const pages = useMemo(() => {
16
- return ctx.alepha.inject(PageDescriptorProvider).getPages();
18
+ return alepha.inject(PageDescriptorProvider).getPages();
17
19
  }, []);
18
20
 
19
21
  return useMemo(
@@ -23,9 +25,8 @@ export const useRouter = (): RouterHookApi => {
23
25
  ctx.context,
24
26
  ctx.state,
25
27
  layer,
26
- ctx.alepha.isBrowser()
27
- ? ctx.alepha.inject(ReactBrowserProvider)
28
- : undefined,
28
+ alepha.inject(PageDescriptorProvider),
29
+ alepha.isBrowser() ? alepha.inject(ReactBrowserProvider) : undefined,
29
30
  ),
30
31
  [layer],
31
32
  );
@@ -1,22 +1,22 @@
1
- import { useContext, useEffect } from "react";
2
- import { RouterContext } from "../contexts/RouterContext.ts";
3
- import type { RouterState } from "../providers/PageDescriptorProvider.ts";
1
+ import { useEffect } from "react";
2
+ import type {
3
+ PageReactContext,
4
+ RouterState,
5
+ } from "../providers/PageDescriptorProvider.ts";
6
+ import { useAlepha } from "./useAlepha.ts";
4
7
 
5
8
  export const useRouterEvents = (
6
9
  opts: {
7
10
  onBegin?: (ev: { state: RouterState }) => void;
8
- onEnd?: (ev: { state: RouterState }) => void;
11
+ onEnd?: (ev: { state: RouterState; context: PageReactContext }) => void;
9
12
  onError?: (ev: { state: RouterState; error: Error }) => void;
10
13
  } = {},
11
14
  deps: any[] = [],
12
15
  ) => {
13
- const ctx = useContext(RouterContext);
14
- if (!ctx) {
15
- throw new Error("useRouter must be used within a RouterProvider");
16
- }
16
+ const alepha = useAlepha();
17
17
 
18
18
  useEffect(() => {
19
- if (!ctx.alepha.isBrowser()) {
19
+ if (!alepha.isBrowser()) {
20
20
  return;
21
21
  }
22
22
 
@@ -27,7 +27,7 @@ export const useRouterEvents = (
27
27
 
28
28
  if (onBegin) {
29
29
  subs.push(
30
- ctx.alepha.on("react:transition:begin", {
30
+ alepha.on("react:transition:begin", {
31
31
  callback: onBegin,
32
32
  }),
33
33
  );
@@ -35,7 +35,7 @@ export const useRouterEvents = (
35
35
 
36
36
  if (onEnd) {
37
37
  subs.push(
38
- ctx.alepha.on("react:transition:end", {
38
+ alepha.on("react:transition:end", {
39
39
  callback: onEnd,
40
40
  }),
41
41
  );
@@ -43,7 +43,7 @@ export const useRouterEvents = (
43
43
 
44
44
  if (onError) {
45
45
  subs.push(
46
- ctx.alepha.on("react:transition:error", {
46
+ alepha.on("react:transition:error", {
47
47
  callback: onError,
48
48
  }),
49
49
  );
@@ -5,13 +5,15 @@ import type { RouterState } from "../providers/PageDescriptorProvider.ts";
5
5
  import { useRouterEvents } from "./useRouterEvents.ts";
6
6
 
7
7
  export const useRouterState = (): RouterState => {
8
- const ctx = useContext(RouterContext);
8
+ const router = useContext(RouterContext);
9
9
  const layer = useContext(RouterLayerContext);
10
- if (!ctx || !layer) {
11
- throw new Error("useRouter must be used within a RouterProvider");
10
+ if (!router || !layer) {
11
+ throw new Error(
12
+ "useRouterState must be used within a RouterContext.Provider",
13
+ );
12
14
  }
13
15
 
14
- const [state, setState] = useState(ctx.state);
16
+ const [state, setState] = useState(router.state);
15
17
 
16
18
  useRouterEvents({
17
19
  onEnd: ({ state }) => setState({ ...state }),
@@ -0,0 +1,93 @@
1
+ import type { Alepha } from "@alepha/core";
2
+ import {
3
+ type FetchOptions,
4
+ HttpClient,
5
+ type RequestConfigSchema,
6
+ } from "@alepha/server";
7
+ import {
8
+ type HttpClientLink,
9
+ LinkProvider,
10
+ type VirtualAction,
11
+ } from "@alepha/server-links";
12
+ import { useEffect, useState } from "react";
13
+ import { useAlepha } from "./useAlepha.ts";
14
+ import { useInject } from "./useInject.ts";
15
+
16
+ export const useSchema = <TConfig extends RequestConfigSchema>(
17
+ action: VirtualAction<TConfig>,
18
+ ): UseSchemaReturn<TConfig> => {
19
+ const name = action.name;
20
+ const alepha = useAlepha();
21
+ const httpClient = useInject(HttpClient);
22
+ const linkProvider = useInject(LinkProvider);
23
+ const [schema, setSchema] = useState<UseSchemaReturn<TConfig>>(
24
+ ssrSchemaLoading(alepha, name) as UseSchemaReturn<TConfig>,
25
+ );
26
+
27
+ useEffect(() => {
28
+ if (!schema.loading) {
29
+ return;
30
+ }
31
+
32
+ const opts: FetchOptions = {
33
+ cache: true,
34
+ };
35
+
36
+ httpClient
37
+ .fetch(`${linkProvider.URL_LINKS}/${name}/schema`, {}, opts)
38
+ .then((it) => setSchema(it.data as UseSchemaReturn<TConfig>));
39
+ }, [name]);
40
+
41
+ return schema;
42
+ };
43
+
44
+ export type UseSchemaReturn<TConfig extends RequestConfigSchema> = TConfig & {
45
+ loading: boolean;
46
+ };
47
+
48
+ // ---------------------------------------------------------------------------------------------------------------------
49
+
50
+ /**
51
+ * Get an action schema during server-side rendering (SSR) or client-side rendering (CSR).
52
+ */
53
+ export const ssrSchemaLoading = (alepha: Alepha, name: string) => {
54
+ // server-side rendering (SSR) context
55
+ if (!alepha.isBrowser()) {
56
+ // get user links
57
+ const links =
58
+ alepha.context.get<{ links: HttpClientLink[] }>("links")?.links ?? [];
59
+
60
+ // check if user can access the link
61
+ const can = links.find((it) => it.name === name);
62
+
63
+ // yes!
64
+ if (can) {
65
+ // user-links have no schema, so we need to get it from the provider
66
+ const schema = alepha
67
+ .inject(LinkProvider)
68
+ .links?.find((it) => it.name === name)?.schema;
69
+
70
+ // oh, we have a schema!
71
+ if (schema) {
72
+ // attach to user link, it will be used in the client during hydration :)
73
+ can.schema = schema;
74
+ return schema;
75
+ }
76
+ }
77
+ return { loading: true };
78
+ }
79
+
80
+ // browser side rendering (CSR) context
81
+ // check if we have the schema already loaded
82
+ const schema = alepha
83
+ .inject(LinkProvider)
84
+ .links?.find((it) => it.name === name)?.schema;
85
+
86
+ // yes!
87
+ if (schema) {
88
+ return schema;
89
+ }
90
+
91
+ // no, we need to load it
92
+ return { loading: true };
93
+ };