@alepha/react 0.9.2 → 0.9.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/README.md +46 -0
- package/dist/index.browser.js +378 -325
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +570 -458
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +305 -213
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +304 -212
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +567 -460
- package/dist/index.js.map +1 -1
- package/package.json +16 -13
- package/src/components/ErrorViewer.tsx +1 -1
- package/src/components/Link.tsx +4 -24
- package/src/components/NestedView.tsx +20 -9
- package/src/components/NotFound.tsx +5 -2
- package/src/descriptors/$page.ts +86 -12
- package/src/errors/Redirection.ts +13 -0
- package/src/hooks/useActive.ts +28 -30
- package/src/hooks/useAlepha.ts +16 -2
- package/src/hooks/useClient.ts +7 -2
- package/src/hooks/useInject.ts +4 -1
- package/src/hooks/useQueryParams.ts +9 -6
- package/src/hooks/useRouter.ts +18 -30
- package/src/hooks/useRouterEvents.ts +7 -4
- package/src/hooks/useRouterState.ts +8 -20
- package/src/hooks/useSchema.ts +10 -15
- package/src/hooks/useStore.ts +9 -8
- package/src/index.browser.ts +11 -11
- package/src/index.shared.ts +4 -5
- package/src/index.ts +21 -30
- package/src/providers/ReactBrowserProvider.ts +155 -65
- package/src/providers/ReactBrowserRouterProvider.ts +132 -0
- package/src/providers/{PageDescriptorProvider.ts → ReactPageProvider.ts} +164 -112
- package/src/providers/ReactServerProvider.ts +100 -68
- package/src/{hooks/RouterHookApi.ts → services/ReactRouter.ts} +75 -61
- package/src/contexts/RouterContext.ts +0 -14
- package/src/errors/RedirectionError.ts +0 -10
- package/src/providers/BrowserRouterProvider.ts +0 -146
- package/src/providers/ReactBrowserRenderer.ts +0 -93
package/dist/index.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { $env, $hook, $inject, $
|
|
2
|
-
import { AlephaServer, HttpClient, ServerRouterProvider, ServerTimingProvider
|
|
1
|
+
import { $env, $hook, $inject, $module, Alepha, AlephaError, Descriptor, KIND, createDescriptor, t } from "@alepha/core";
|
|
2
|
+
import { AlephaServer, HttpClient, ServerRouterProvider, ServerTimingProvider } from "@alepha/server";
|
|
3
3
|
import { AlephaServerCache } from "@alepha/server-cache";
|
|
4
4
|
import { AlephaServerLinks, LinkProvider, ServerLinksProvider } from "@alepha/server-links";
|
|
5
|
+
import { $logger } from "@alepha/logger";
|
|
5
6
|
import React, { StrictMode, createContext, createElement, useContext, useEffect, useMemo, useState } from "react";
|
|
6
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
import { RouterProvider } from "@alepha/router";
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { ServerStaticProvider } from "@alepha/server-static";
|
|
11
11
|
import { renderToString } from "react-dom/server";
|
|
12
|
+
import { DateTimeProvider } from "@alepha/datetime";
|
|
13
|
+
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
14
|
+
import { RouterProvider } from "@alepha/router";
|
|
12
15
|
|
|
13
16
|
//#region src/descriptors/$page.ts
|
|
14
17
|
/**
|
|
@@ -18,6 +21,12 @@ const $page = (options) => {
|
|
|
18
21
|
return createDescriptor(PageDescriptor, options);
|
|
19
22
|
};
|
|
20
23
|
var PageDescriptor = class extends Descriptor {
|
|
24
|
+
onInit() {
|
|
25
|
+
if (this.options.static) this.options.cache ??= {
|
|
26
|
+
provider: "memory",
|
|
27
|
+
ttl: [1, "week"]
|
|
28
|
+
};
|
|
29
|
+
}
|
|
21
30
|
get name() {
|
|
22
31
|
return this.options.name ?? this.config.propertyKey;
|
|
23
32
|
}
|
|
@@ -26,7 +35,13 @@ var PageDescriptor = class extends Descriptor {
|
|
|
26
35
|
* Only valid for server-side rendering, it will throw an error if called on the client-side.
|
|
27
36
|
*/
|
|
28
37
|
async render(options) {
|
|
29
|
-
throw new
|
|
38
|
+
throw new Error("render method is not implemented in this environment");
|
|
39
|
+
}
|
|
40
|
+
match(url) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
pathname(config) {
|
|
44
|
+
return this.options.path || "";
|
|
30
45
|
}
|
|
31
46
|
};
|
|
32
47
|
$page[KIND] = PageDescriptor;
|
|
@@ -80,7 +95,7 @@ const ErrorViewer = ({ error, alepha }) => {
|
|
|
80
95
|
heading: {
|
|
81
96
|
fontSize: "20px",
|
|
82
97
|
fontWeight: "bold",
|
|
83
|
-
marginBottom: "
|
|
98
|
+
marginBottom: "10px"
|
|
84
99
|
},
|
|
85
100
|
name: {
|
|
86
101
|
fontSize: "16px",
|
|
@@ -199,28 +214,54 @@ const ErrorViewerProduction = () => {
|
|
|
199
214
|
});
|
|
200
215
|
};
|
|
201
216
|
|
|
202
|
-
//#endregion
|
|
203
|
-
//#region src/contexts/RouterContext.ts
|
|
204
|
-
const RouterContext = createContext(void 0);
|
|
205
|
-
|
|
206
217
|
//#endregion
|
|
207
218
|
//#region src/contexts/RouterLayerContext.ts
|
|
208
219
|
const RouterLayerContext = createContext(void 0);
|
|
209
220
|
|
|
221
|
+
//#endregion
|
|
222
|
+
//#region src/errors/Redirection.ts
|
|
223
|
+
/**
|
|
224
|
+
* Used for Redirection during the page loading.
|
|
225
|
+
*
|
|
226
|
+
* Depends on the context, it can be thrown or just returned.
|
|
227
|
+
*/
|
|
228
|
+
var Redirection = class extends Error {
|
|
229
|
+
redirect;
|
|
230
|
+
constructor(redirect) {
|
|
231
|
+
super("Redirection");
|
|
232
|
+
this.redirect = redirect;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
210
236
|
//#endregion
|
|
211
237
|
//#region src/contexts/AlephaContext.ts
|
|
212
238
|
const AlephaContext = createContext(void 0);
|
|
213
239
|
|
|
214
240
|
//#endregion
|
|
215
241
|
//#region src/hooks/useAlepha.ts
|
|
242
|
+
/**
|
|
243
|
+
* Main Alepha hook.
|
|
244
|
+
*
|
|
245
|
+
* It provides access to the Alepha instance within a React component.
|
|
246
|
+
*
|
|
247
|
+
* With Alepha, you can access the core functionalities of the framework:
|
|
248
|
+
*
|
|
249
|
+
* - alepha.state() for state management
|
|
250
|
+
* - alepha.inject() for dependency injection
|
|
251
|
+
* - alepha.emit() for event handling
|
|
252
|
+
* etc...
|
|
253
|
+
*/
|
|
216
254
|
const useAlepha = () => {
|
|
217
255
|
const alepha = useContext(AlephaContext);
|
|
218
|
-
if (!alepha) throw new
|
|
256
|
+
if (!alepha) throw new AlephaError("Hook 'useAlepha()' must be used within an AlephaContext.Provider");
|
|
219
257
|
return alepha;
|
|
220
258
|
};
|
|
221
259
|
|
|
222
260
|
//#endregion
|
|
223
261
|
//#region src/hooks/useRouterEvents.ts
|
|
262
|
+
/**
|
|
263
|
+
* Subscribe to various router events.
|
|
264
|
+
*/
|
|
224
265
|
const useRouterEvents = (opts = {}, deps = []) => {
|
|
225
266
|
const alepha = useAlepha();
|
|
226
267
|
useEffect(() => {
|
|
@@ -293,17 +334,22 @@ var ErrorBoundary_default = ErrorBoundary;
|
|
|
293
334
|
* ```
|
|
294
335
|
*/
|
|
295
336
|
const NestedView = (props) => {
|
|
296
|
-
const app = useContext(RouterContext);
|
|
297
337
|
const layer = useContext(RouterLayerContext);
|
|
298
338
|
const index = layer?.index ?? 0;
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
339
|
+
const alepha = useAlepha();
|
|
340
|
+
const state = alepha.state("react.router.state");
|
|
341
|
+
if (!state) throw new Error("<NestedView/> must be used inside a RouterLayerContext.");
|
|
342
|
+
const [view, setView] = useState(state.layers[index]?.element);
|
|
343
|
+
useRouterEvents({ onEnd: ({ state: state$1 }) => {
|
|
344
|
+
if (!state$1.layers[index]?.cache) setView(state$1.layers[index]?.element);
|
|
345
|
+
} }, []);
|
|
304
346
|
const element = view ?? props.children ?? null;
|
|
305
347
|
return /* @__PURE__ */ jsx(ErrorBoundary_default, {
|
|
306
|
-
fallback:
|
|
348
|
+
fallback: (error) => {
|
|
349
|
+
const result = state.onError(error, state);
|
|
350
|
+
if (result instanceof Redirection) return "Redirection inside ErrorBoundary is not allowed.";
|
|
351
|
+
return result;
|
|
352
|
+
},
|
|
307
353
|
children: element
|
|
308
354
|
});
|
|
309
355
|
};
|
|
@@ -311,7 +357,7 @@ var NestedView_default = NestedView;
|
|
|
311
357
|
|
|
312
358
|
//#endregion
|
|
313
359
|
//#region src/components/NotFound.tsx
|
|
314
|
-
function NotFoundPage() {
|
|
360
|
+
function NotFoundPage(props) {
|
|
315
361
|
return /* @__PURE__ */ jsx("div", {
|
|
316
362
|
style: {
|
|
317
363
|
height: "100vh",
|
|
@@ -321,34 +367,25 @@ function NotFoundPage() {
|
|
|
321
367
|
alignItems: "center",
|
|
322
368
|
textAlign: "center",
|
|
323
369
|
fontFamily: "sans-serif",
|
|
324
|
-
padding: "1rem"
|
|
370
|
+
padding: "1rem",
|
|
371
|
+
...props.style
|
|
325
372
|
},
|
|
326
373
|
children: /* @__PURE__ */ jsx("h1", {
|
|
327
374
|
style: {
|
|
328
375
|
fontSize: "1rem",
|
|
329
376
|
marginBottom: "0.5rem"
|
|
330
377
|
},
|
|
331
|
-
children: "This page does not exist"
|
|
378
|
+
children: "404 - This page does not exist"
|
|
332
379
|
})
|
|
333
380
|
});
|
|
334
381
|
}
|
|
335
382
|
|
|
336
383
|
//#endregion
|
|
337
|
-
//#region src/
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
constructor(page) {
|
|
341
|
-
super("Redirection");
|
|
342
|
-
this.page = page;
|
|
343
|
-
}
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
//#endregion
|
|
347
|
-
//#region src/providers/PageDescriptorProvider.ts
|
|
348
|
-
const envSchema$1 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
|
|
349
|
-
var PageDescriptorProvider = class {
|
|
384
|
+
//#region src/providers/ReactPageProvider.ts
|
|
385
|
+
const envSchema$2 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
|
|
386
|
+
var ReactPageProvider = class {
|
|
350
387
|
log = $logger();
|
|
351
|
-
env = $env(envSchema$
|
|
388
|
+
env = $env(envSchema$2);
|
|
352
389
|
alepha = $inject(Alepha);
|
|
353
390
|
pages = [];
|
|
354
391
|
getPages() {
|
|
@@ -358,7 +395,7 @@ var PageDescriptorProvider = class {
|
|
|
358
395
|
for (const page of this.pages) if (page.name === name) return page;
|
|
359
396
|
throw new Error(`Page ${name} not found`);
|
|
360
397
|
}
|
|
361
|
-
|
|
398
|
+
pathname(name, options = {}) {
|
|
362
399
|
const page = this.page(name);
|
|
363
400
|
if (!page) throw new Error(`Page ${name} not found`);
|
|
364
401
|
let url = page.path ?? "";
|
|
@@ -368,22 +405,28 @@ var PageDescriptorProvider = class {
|
|
|
368
405
|
parent = parent.parent;
|
|
369
406
|
}
|
|
370
407
|
url = this.compile(url, options.params ?? {});
|
|
371
|
-
|
|
408
|
+
if (options.query) {
|
|
409
|
+
const query = new URLSearchParams(options.query);
|
|
410
|
+
if (query.toString()) url += `?${query.toString()}`;
|
|
411
|
+
}
|
|
412
|
+
return url.replace(/\/\/+/g, "/") || "/";
|
|
372
413
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
414
|
+
url(name, options = {}) {
|
|
415
|
+
return new URL(this.pathname(name, options), options.host ?? `http://localhost`);
|
|
416
|
+
}
|
|
417
|
+
root(state) {
|
|
418
|
+
const root = createElement(AlephaContext.Provider, { value: this.alepha }, createElement(NestedView_default, {}, state.layers[0]?.element));
|
|
378
419
|
if (this.env.REACT_STRICT_MODE) return createElement(StrictMode, {}, root);
|
|
379
420
|
return root;
|
|
380
421
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
422
|
+
/**
|
|
423
|
+
* Create a new RouterState based on a given route and request.
|
|
424
|
+
* This method resolves the layers for the route, applying any query and params schemas defined in the route.
|
|
425
|
+
* It also handles errors and redirects.
|
|
426
|
+
*/
|
|
427
|
+
async createLayers(route, state, previous = []) {
|
|
384
428
|
let context = {};
|
|
385
429
|
const stack = [{ route }];
|
|
386
|
-
request.onError = (error) => this.renderError(error);
|
|
387
430
|
let parent = route.parent;
|
|
388
431
|
while (parent) {
|
|
389
432
|
stack.unshift({ route: parent });
|
|
@@ -395,19 +438,18 @@ var PageDescriptorProvider = class {
|
|
|
395
438
|
const route$1 = it.route;
|
|
396
439
|
const config = {};
|
|
397
440
|
try {
|
|
398
|
-
config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query,
|
|
441
|
+
config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, state.query) : {};
|
|
399
442
|
} catch (e) {
|
|
400
443
|
it.error = e;
|
|
401
444
|
break;
|
|
402
445
|
}
|
|
403
446
|
try {
|
|
404
|
-
config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params,
|
|
447
|
+
config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, state.params) : {};
|
|
405
448
|
} catch (e) {
|
|
406
449
|
it.error = e;
|
|
407
450
|
break;
|
|
408
451
|
}
|
|
409
452
|
it.config = { ...config };
|
|
410
|
-
const previous = request.previous;
|
|
411
453
|
if (previous?.[i] && !forceRefresh && previous[i].name === route$1.name) {
|
|
412
454
|
const url = (str) => str ? str.replace(/\/\/+/g, "/") : "/";
|
|
413
455
|
const prev = JSON.stringify({
|
|
@@ -433,7 +475,7 @@ var PageDescriptorProvider = class {
|
|
|
433
475
|
if (!route$1.resolve) continue;
|
|
434
476
|
try {
|
|
435
477
|
const props = await route$1.resolve?.({
|
|
436
|
-
...
|
|
478
|
+
...state,
|
|
437
479
|
...config,
|
|
438
480
|
...context
|
|
439
481
|
}) ?? {};
|
|
@@ -443,13 +485,8 @@ var PageDescriptorProvider = class {
|
|
|
443
485
|
...props
|
|
444
486
|
};
|
|
445
487
|
} catch (e) {
|
|
446
|
-
if (e instanceof
|
|
447
|
-
|
|
448
|
-
redirect: typeof e.page === "string" ? e.page : this.href(e.page),
|
|
449
|
-
pathname,
|
|
450
|
-
search
|
|
451
|
-
};
|
|
452
|
-
this.log.error(e);
|
|
488
|
+
if (e instanceof Redirection) return { redirect: e.redirect };
|
|
489
|
+
this.log.error("Page resolver has failed", e);
|
|
453
490
|
it.error = e;
|
|
454
491
|
break;
|
|
455
492
|
}
|
|
@@ -464,44 +501,59 @@ var PageDescriptorProvider = class {
|
|
|
464
501
|
acc += it.route.path ? this.compile(it.route.path, params) : "";
|
|
465
502
|
const path = acc.replace(/\/+/, "/");
|
|
466
503
|
const localErrorHandler = this.getErrorHandler(it.route);
|
|
467
|
-
if (localErrorHandler)
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
504
|
+
if (localErrorHandler) {
|
|
505
|
+
const onErrorParent = state.onError;
|
|
506
|
+
state.onError = (error, context$1) => {
|
|
507
|
+
const result = localErrorHandler(error, context$1);
|
|
508
|
+
if (result === void 0) return onErrorParent(error, context$1);
|
|
509
|
+
return result;
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
if (!it.error) try {
|
|
513
|
+
const element = await this.createElement(it.route, {
|
|
514
|
+
...props,
|
|
515
|
+
...context
|
|
516
|
+
});
|
|
517
|
+
state.layers.push({
|
|
518
|
+
name: it.route.name,
|
|
519
|
+
props,
|
|
520
|
+
part: it.route.path,
|
|
521
|
+
config: it.config,
|
|
522
|
+
element: this.renderView(i + 1, path, element, it.route),
|
|
523
|
+
index: i + 1,
|
|
524
|
+
path,
|
|
525
|
+
route: it.route,
|
|
526
|
+
cache: it.cache
|
|
527
|
+
});
|
|
528
|
+
} catch (e) {
|
|
529
|
+
it.error = e;
|
|
530
|
+
}
|
|
531
|
+
if (it.error) try {
|
|
532
|
+
let element = await state.onError(it.error, state);
|
|
533
|
+
if (element === void 0) throw it.error;
|
|
534
|
+
if (element instanceof Redirection) return { redirect: element.redirect };
|
|
535
|
+
if (element === null) element = this.renderError(it.error);
|
|
536
|
+
state.layers.push({
|
|
472
537
|
props,
|
|
473
538
|
error: it.error,
|
|
474
539
|
name: it.route.name,
|
|
475
540
|
part: it.route.path,
|
|
476
541
|
config: it.config,
|
|
477
|
-
element: this.renderView(i + 1, path, element
|
|
542
|
+
element: this.renderView(i + 1, path, element, it.route),
|
|
478
543
|
index: i + 1,
|
|
479
544
|
path,
|
|
480
545
|
route: it.route
|
|
481
546
|
});
|
|
482
547
|
break;
|
|
548
|
+
} catch (e) {
|
|
549
|
+
if (e instanceof Redirection) return { redirect: e.redirect };
|
|
550
|
+
throw e;
|
|
483
551
|
}
|
|
484
|
-
const element = await this.createElement(it.route, {
|
|
485
|
-
...props,
|
|
486
|
-
...context
|
|
487
|
-
});
|
|
488
|
-
layers.push({
|
|
489
|
-
name: it.route.name,
|
|
490
|
-
props,
|
|
491
|
-
part: it.route.path,
|
|
492
|
-
config: it.config,
|
|
493
|
-
element: this.renderView(i + 1, path, element, it.route),
|
|
494
|
-
index: i + 1,
|
|
495
|
-
path,
|
|
496
|
-
route: it.route,
|
|
497
|
-
cache: it.cache
|
|
498
|
-
});
|
|
499
552
|
}
|
|
500
|
-
return {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
};
|
|
553
|
+
return { state };
|
|
554
|
+
}
|
|
555
|
+
createRedirectionLayer(redirect) {
|
|
556
|
+
return { redirect };
|
|
505
557
|
}
|
|
506
558
|
getErrorHandler(route) {
|
|
507
559
|
if (route.errorHandler) return route.errorHandler;
|
|
@@ -558,6 +610,7 @@ var PageDescriptorProvider = class {
|
|
|
558
610
|
let hasNotFoundHandler = false;
|
|
559
611
|
const pages = this.alepha.descriptors($page);
|
|
560
612
|
const hasParent = (it) => {
|
|
613
|
+
if (it.options.parent) return true;
|
|
561
614
|
for (const page of pages) {
|
|
562
615
|
const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
|
|
563
616
|
if (children.includes(it)) return true;
|
|
@@ -573,7 +626,7 @@ var PageDescriptorProvider = class {
|
|
|
573
626
|
name: "notFound",
|
|
574
627
|
cache: true,
|
|
575
628
|
component: NotFoundPage,
|
|
576
|
-
|
|
629
|
+
onServerResponse: ({ reply }) => {
|
|
577
630
|
reply.status = 404;
|
|
578
631
|
}
|
|
579
632
|
});
|
|
@@ -581,6 +634,12 @@ var PageDescriptorProvider = class {
|
|
|
581
634
|
});
|
|
582
635
|
map(pages, target) {
|
|
583
636
|
const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
|
|
637
|
+
const getChildrenFromParent = (it) => {
|
|
638
|
+
const children$1 = [];
|
|
639
|
+
for (const page of pages) if (page.options.parent === it) children$1.push(page);
|
|
640
|
+
return children$1;
|
|
641
|
+
};
|
|
642
|
+
children.push(...getChildrenFromParent(target));
|
|
584
643
|
return {
|
|
585
644
|
...target.options,
|
|
586
645
|
name: target.name,
|
|
@@ -620,217 +679,9 @@ const isPageRoute = (it) => {
|
|
|
620
679
|
return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
|
|
621
680
|
};
|
|
622
681
|
|
|
623
|
-
//#endregion
|
|
624
|
-
//#region src/providers/BrowserRouterProvider.ts
|
|
625
|
-
var BrowserRouterProvider = class extends RouterProvider {
|
|
626
|
-
log = $logger();
|
|
627
|
-
alepha = $inject(Alepha);
|
|
628
|
-
pageDescriptorProvider = $inject(PageDescriptorProvider);
|
|
629
|
-
add(entry) {
|
|
630
|
-
this.pageDescriptorProvider.add(entry);
|
|
631
|
-
}
|
|
632
|
-
configure = $hook({
|
|
633
|
-
on: "configure",
|
|
634
|
-
handler: async () => {
|
|
635
|
-
for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
|
|
636
|
-
path: page.match,
|
|
637
|
-
page
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
});
|
|
641
|
-
async transition(url, options = {}) {
|
|
642
|
-
const { pathname, search } = url;
|
|
643
|
-
const state = {
|
|
644
|
-
pathname,
|
|
645
|
-
search,
|
|
646
|
-
layers: []
|
|
647
|
-
};
|
|
648
|
-
const context = {
|
|
649
|
-
url,
|
|
650
|
-
query: {},
|
|
651
|
-
params: {},
|
|
652
|
-
onError: () => null,
|
|
653
|
-
...options.context ?? {}
|
|
654
|
-
};
|
|
655
|
-
await this.alepha.emit("react:transition:begin", {
|
|
656
|
-
state,
|
|
657
|
-
context
|
|
658
|
-
});
|
|
659
|
-
try {
|
|
660
|
-
const previous = options.previous;
|
|
661
|
-
const { route, params } = this.match(pathname);
|
|
662
|
-
const query = {};
|
|
663
|
-
if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
|
|
664
|
-
context.query = query;
|
|
665
|
-
context.params = params ?? {};
|
|
666
|
-
context.previous = previous;
|
|
667
|
-
if (isPageRoute(route)) {
|
|
668
|
-
const result = await this.pageDescriptorProvider.createLayers(route.page, context);
|
|
669
|
-
if (result.redirect) return {
|
|
670
|
-
redirect: result.redirect,
|
|
671
|
-
state,
|
|
672
|
-
context
|
|
673
|
-
};
|
|
674
|
-
state.layers = result.layers;
|
|
675
|
-
}
|
|
676
|
-
if (state.layers.length === 0) state.layers.push({
|
|
677
|
-
name: "not-found",
|
|
678
|
-
element: createElement(NotFoundPage),
|
|
679
|
-
index: 0,
|
|
680
|
-
path: "/"
|
|
681
|
-
});
|
|
682
|
-
await this.alepha.emit("react:transition:success", {
|
|
683
|
-
state,
|
|
684
|
-
context
|
|
685
|
-
});
|
|
686
|
-
} catch (e) {
|
|
687
|
-
this.log.error(e);
|
|
688
|
-
state.layers = [{
|
|
689
|
-
name: "error",
|
|
690
|
-
element: this.pageDescriptorProvider.renderError(e),
|
|
691
|
-
index: 0,
|
|
692
|
-
path: "/"
|
|
693
|
-
}];
|
|
694
|
-
await this.alepha.emit("react:transition:error", {
|
|
695
|
-
error: e,
|
|
696
|
-
state,
|
|
697
|
-
context
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
if (options.state) {
|
|
701
|
-
options.state.layers = state.layers;
|
|
702
|
-
options.state.pathname = state.pathname;
|
|
703
|
-
options.state.search = state.search;
|
|
704
|
-
}
|
|
705
|
-
await this.alepha.emit("react:transition:end", {
|
|
706
|
-
state: options.state,
|
|
707
|
-
context
|
|
708
|
-
});
|
|
709
|
-
return {
|
|
710
|
-
context,
|
|
711
|
-
state
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
root(state, context) {
|
|
715
|
-
return this.pageDescriptorProvider.root(state, context);
|
|
716
|
-
}
|
|
717
|
-
};
|
|
718
|
-
|
|
719
|
-
//#endregion
|
|
720
|
-
//#region src/providers/ReactBrowserProvider.ts
|
|
721
|
-
var ReactBrowserProvider = class {
|
|
722
|
-
log = $logger();
|
|
723
|
-
client = $inject(LinkProvider);
|
|
724
|
-
alepha = $inject(Alepha);
|
|
725
|
-
router = $inject(BrowserRouterProvider);
|
|
726
|
-
root;
|
|
727
|
-
transitioning;
|
|
728
|
-
state = {
|
|
729
|
-
layers: [],
|
|
730
|
-
pathname: "",
|
|
731
|
-
search: ""
|
|
732
|
-
};
|
|
733
|
-
get document() {
|
|
734
|
-
return window.document;
|
|
735
|
-
}
|
|
736
|
-
get history() {
|
|
737
|
-
return window.history;
|
|
738
|
-
}
|
|
739
|
-
get location() {
|
|
740
|
-
return window.location;
|
|
741
|
-
}
|
|
742
|
-
get url() {
|
|
743
|
-
let url = this.location.pathname + this.location.search;
|
|
744
|
-
if (import.meta?.env?.BASE_URL) {
|
|
745
|
-
url = url.replace(import.meta.env?.BASE_URL, "");
|
|
746
|
-
if (!url.startsWith("/")) url = `/${url}`;
|
|
747
|
-
}
|
|
748
|
-
return url;
|
|
749
|
-
}
|
|
750
|
-
pushState(url, replace) {
|
|
751
|
-
let path = url;
|
|
752
|
-
if (import.meta?.env?.BASE_URL) path = (import.meta.env?.BASE_URL + path).replaceAll("//", "/");
|
|
753
|
-
if (replace) this.history.replaceState({}, "", path);
|
|
754
|
-
else this.history.pushState({}, "", path);
|
|
755
|
-
}
|
|
756
|
-
async invalidate(props) {
|
|
757
|
-
const previous = [];
|
|
758
|
-
if (props) {
|
|
759
|
-
const [key] = Object.keys(props);
|
|
760
|
-
const value = props[key];
|
|
761
|
-
for (const layer of this.state.layers) {
|
|
762
|
-
if (layer.props?.[key]) {
|
|
763
|
-
previous.push({
|
|
764
|
-
...layer,
|
|
765
|
-
props: {
|
|
766
|
-
...layer.props,
|
|
767
|
-
[key]: value
|
|
768
|
-
}
|
|
769
|
-
});
|
|
770
|
-
break;
|
|
771
|
-
}
|
|
772
|
-
previous.push(layer);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
await this.render({ previous });
|
|
776
|
-
}
|
|
777
|
-
async go(url, options = {}) {
|
|
778
|
-
const result = await this.render({ url });
|
|
779
|
-
if (result.context.url.pathname !== url) {
|
|
780
|
-
this.pushState(result.context.url.pathname);
|
|
781
|
-
return;
|
|
782
|
-
}
|
|
783
|
-
if (options.replace) {
|
|
784
|
-
this.pushState(url);
|
|
785
|
-
return;
|
|
786
|
-
}
|
|
787
|
-
this.pushState(url);
|
|
788
|
-
}
|
|
789
|
-
async render(options = {}) {
|
|
790
|
-
const previous = options.previous ?? this.state.layers;
|
|
791
|
-
const url = options.url ?? this.url;
|
|
792
|
-
this.transitioning = { to: url };
|
|
793
|
-
const result = await this.router.transition(new URL(`http://localhost${url}`), {
|
|
794
|
-
previous,
|
|
795
|
-
state: this.state
|
|
796
|
-
});
|
|
797
|
-
if (result.redirect) return await this.render({ url: result.redirect });
|
|
798
|
-
this.transitioning = void 0;
|
|
799
|
-
return result;
|
|
800
|
-
}
|
|
801
|
-
/**
|
|
802
|
-
* Get embedded layers from the server.
|
|
803
|
-
*/
|
|
804
|
-
getHydrationState() {
|
|
805
|
-
try {
|
|
806
|
-
if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
|
|
807
|
-
} catch (error) {
|
|
808
|
-
console.error(error);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
ready = $hook({
|
|
812
|
-
on: "ready",
|
|
813
|
-
handler: async () => {
|
|
814
|
-
const hydration = this.getHydrationState();
|
|
815
|
-
const previous = hydration?.layers ?? [];
|
|
816
|
-
if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
|
|
817
|
-
const { context } = await this.render({ previous });
|
|
818
|
-
await this.alepha.emit("react:browser:render", {
|
|
819
|
-
state: this.state,
|
|
820
|
-
context,
|
|
821
|
-
hydration
|
|
822
|
-
});
|
|
823
|
-
window.addEventListener("popstate", () => {
|
|
824
|
-
if (this.state.pathname === this.url) return;
|
|
825
|
-
this.render();
|
|
826
|
-
});
|
|
827
|
-
}
|
|
828
|
-
});
|
|
829
|
-
};
|
|
830
|
-
|
|
831
682
|
//#endregion
|
|
832
683
|
//#region src/providers/ReactServerProvider.ts
|
|
833
|
-
const envSchema = t.object({
|
|
684
|
+
const envSchema$1 = t.object({
|
|
834
685
|
REACT_SERVER_DIST: t.string({ default: "public" }),
|
|
835
686
|
REACT_SERVER_PREFIX: t.string({ default: "" }),
|
|
836
687
|
REACT_SSR_ENABLED: t.optional(t.boolean()),
|
|
@@ -840,11 +691,11 @@ const envSchema = t.object({
|
|
|
840
691
|
var ReactServerProvider = class {
|
|
841
692
|
log = $logger();
|
|
842
693
|
alepha = $inject(Alepha);
|
|
843
|
-
|
|
694
|
+
pageApi = $inject(ReactPageProvider);
|
|
844
695
|
serverStaticProvider = $inject(ServerStaticProvider);
|
|
845
696
|
serverRouterProvider = $inject(ServerRouterProvider);
|
|
846
697
|
serverTimingProvider = $inject(ServerTimingProvider);
|
|
847
|
-
env = $env(envSchema);
|
|
698
|
+
env = $env(envSchema$1);
|
|
848
699
|
ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
|
|
849
700
|
onConfigure = $hook({
|
|
850
701
|
on: "configure",
|
|
@@ -891,7 +742,7 @@ var ReactServerProvider = class {
|
|
|
891
742
|
return this.alepha.env.REACT_SERVER_TEMPLATE ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
|
|
892
743
|
}
|
|
893
744
|
async registerPages(templateLoader) {
|
|
894
|
-
for (const page of this.
|
|
745
|
+
for (const page of this.pageApi.getPages()) {
|
|
895
746
|
if (page.children?.length) continue;
|
|
896
747
|
this.log.debug(`+ ${page.match} -> ${page.name}`);
|
|
897
748
|
this.serverRouterProvider.createRoute({
|
|
@@ -925,24 +776,30 @@ var ReactServerProvider = class {
|
|
|
925
776
|
*/
|
|
926
777
|
createRenderFunction(name, withIndex = false) {
|
|
927
778
|
return async (options = {}) => {
|
|
928
|
-
const page = this.
|
|
929
|
-
const url = new URL(this.
|
|
930
|
-
const
|
|
779
|
+
const page = this.pageApi.page(name);
|
|
780
|
+
const url = new URL(this.pageApi.url(name, options));
|
|
781
|
+
const entry = {
|
|
931
782
|
url,
|
|
932
783
|
params: options.params ?? {},
|
|
933
784
|
query: options.query ?? {},
|
|
934
|
-
|
|
935
|
-
|
|
785
|
+
onError: () => null,
|
|
786
|
+
layers: []
|
|
936
787
|
};
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
788
|
+
const state = entry;
|
|
789
|
+
this.log.trace("Rendering", { url });
|
|
790
|
+
await this.alepha.emit("react:server:render:begin", { state });
|
|
791
|
+
const { redirect } = await this.pageApi.createLayers(page, state);
|
|
792
|
+
if (redirect) throw new AlephaError("Redirection is not supported in this context");
|
|
793
|
+
if (!withIndex && !options.html) {
|
|
794
|
+
this.alepha.state("react.router.state", state);
|
|
795
|
+
return {
|
|
796
|
+
state,
|
|
797
|
+
html: renderToString(this.pageApi.root(state))
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
const html = this.renderToHtml(this.template ?? "", state, options.hydration);
|
|
801
|
+
if (html instanceof Redirection) throw new Error("Redirection is not supported in this context");
|
|
944
802
|
const result = {
|
|
945
|
-
context,
|
|
946
803
|
state,
|
|
947
804
|
html
|
|
948
805
|
};
|
|
@@ -950,30 +807,27 @@ var ReactServerProvider = class {
|
|
|
950
807
|
return result;
|
|
951
808
|
};
|
|
952
809
|
}
|
|
953
|
-
createHandler(
|
|
810
|
+
createHandler(route, templateLoader) {
|
|
954
811
|
return async (serverRequest) => {
|
|
955
812
|
const { url, reply, query, params } = serverRequest;
|
|
956
813
|
const template = await templateLoader();
|
|
957
814
|
if (!template) throw new Error("Template not found");
|
|
958
|
-
|
|
815
|
+
this.log.trace("Rendering page", { name: route.name });
|
|
816
|
+
const entry = {
|
|
959
817
|
url,
|
|
960
818
|
params,
|
|
961
819
|
query,
|
|
962
|
-
|
|
963
|
-
|
|
820
|
+
onError: () => null,
|
|
821
|
+
layers: []
|
|
964
822
|
};
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
}));
|
|
972
|
-
this.alepha.context.set("links", context.links);
|
|
973
|
-
}
|
|
974
|
-
let target = page;
|
|
823
|
+
const state = entry;
|
|
824
|
+
if (this.alepha.has(ServerLinksProvider)) this.alepha.state("api", await this.alepha.inject(ServerLinksProvider).getUserApiLinks({
|
|
825
|
+
user: serverRequest.user,
|
|
826
|
+
authorization: serverRequest.headers.authorization
|
|
827
|
+
}));
|
|
828
|
+
let target = route;
|
|
975
829
|
while (target) {
|
|
976
|
-
if (
|
|
830
|
+
if (route.can && !route.can()) {
|
|
977
831
|
reply.status = 403;
|
|
978
832
|
reply.headers["content-type"] = "text/plain";
|
|
979
833
|
return "Forbidden";
|
|
@@ -982,50 +836,60 @@ var ReactServerProvider = class {
|
|
|
982
836
|
}
|
|
983
837
|
await this.alepha.emit("react:server:render:begin", {
|
|
984
838
|
request: serverRequest,
|
|
985
|
-
|
|
839
|
+
state
|
|
986
840
|
});
|
|
987
841
|
this.serverTimingProvider.beginTiming("createLayers");
|
|
988
|
-
const
|
|
842
|
+
const { redirect } = await this.pageApi.createLayers(route, state);
|
|
989
843
|
this.serverTimingProvider.endTiming("createLayers");
|
|
990
|
-
if (
|
|
844
|
+
if (redirect) return reply.redirect(redirect);
|
|
991
845
|
reply.headers["content-type"] = "text/html";
|
|
992
846
|
reply.headers["cache-control"] = "no-store, no-cache, must-revalidate, proxy-revalidate";
|
|
993
847
|
reply.headers.pragma = "no-cache";
|
|
994
848
|
reply.headers.expires = "0";
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
849
|
+
const html = this.renderToHtml(template, state);
|
|
850
|
+
if (html instanceof Redirection) {
|
|
851
|
+
reply.redirect(typeof html.redirect === "string" ? html.redirect : this.pageApi.href(html.redirect));
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
const event = {
|
|
998
855
|
request: serverRequest,
|
|
999
|
-
context,
|
|
1000
856
|
state,
|
|
1001
857
|
html
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
|
|
858
|
+
};
|
|
859
|
+
await this.alepha.emit("react:server:render:end", event);
|
|
860
|
+
route.onServerResponse?.(serverRequest);
|
|
861
|
+
this.log.trace("Page rendered", { name: route.name });
|
|
862
|
+
return event.html;
|
|
1005
863
|
};
|
|
1006
864
|
}
|
|
1007
|
-
renderToHtml(template, state,
|
|
1008
|
-
const element = this.
|
|
865
|
+
renderToHtml(template, state, hydration = true) {
|
|
866
|
+
const element = this.pageApi.root(state);
|
|
867
|
+
this.alepha.state("react.router.state", state);
|
|
1009
868
|
this.serverTimingProvider.beginTiming("renderToString");
|
|
1010
869
|
let app = "";
|
|
1011
870
|
try {
|
|
1012
871
|
app = renderToString(element);
|
|
1013
872
|
} catch (error) {
|
|
1014
|
-
this.log.error("
|
|
1015
|
-
|
|
873
|
+
this.log.error("renderToString has failed, fallback to error handler", error);
|
|
874
|
+
const element$1 = state.onError(error, state);
|
|
875
|
+
if (element$1 instanceof Redirection) return element$1;
|
|
876
|
+
app = renderToString(element$1);
|
|
877
|
+
this.log.debug("Error handled successfully with fallback");
|
|
1016
878
|
}
|
|
1017
879
|
this.serverTimingProvider.endTiming("renderToString");
|
|
1018
880
|
const response = { html: template };
|
|
1019
881
|
if (hydration) {
|
|
882
|
+
const { request, context,...store } = this.alepha.context.als?.getStore() ?? {};
|
|
1020
883
|
const hydrationData = {
|
|
1021
|
-
|
|
884
|
+
...store,
|
|
885
|
+
"react.router.state": void 0,
|
|
1022
886
|
layers: state.layers.map((it) => ({
|
|
1023
887
|
...it,
|
|
1024
888
|
error: it.error ? {
|
|
1025
889
|
...it.error,
|
|
1026
890
|
name: it.error.name,
|
|
1027
891
|
message: it.error.message,
|
|
1028
|
-
stack: it.error.stack
|
|
892
|
+
stack: !this.alepha.isProduction() ? it.error.stack : void 0
|
|
1029
893
|
} : void 0,
|
|
1030
894
|
index: void 0,
|
|
1031
895
|
path: void 0,
|
|
@@ -1033,7 +897,7 @@ var ReactServerProvider = class {
|
|
|
1033
897
|
route: void 0
|
|
1034
898
|
}))
|
|
1035
899
|
};
|
|
1036
|
-
const script = `<script>window.__ssr=${JSON.stringify(hydrationData)}
|
|
900
|
+
const script = `<script>window.__ssr=${JSON.stringify(hydrationData)}<\/script>`;
|
|
1037
901
|
this.fillTemplate(response, app, script);
|
|
1038
902
|
}
|
|
1039
903
|
return response.html;
|
|
@@ -1045,26 +909,269 @@ var ReactServerProvider = class {
|
|
|
1045
909
|
else {
|
|
1046
910
|
const bodyOpenTag = /<body([^>]*)>/i;
|
|
1047
911
|
if (bodyOpenTag.test(response.html)) response.html = response.html.replace(bodyOpenTag, (match) => {
|
|
1048
|
-
return `${match}
|
|
912
|
+
return `${match}<div id="${this.env.REACT_ROOT_ID}">${app}</div>`;
|
|
1049
913
|
});
|
|
1050
914
|
}
|
|
1051
915
|
const bodyCloseTagRegex = /<\/body>/i;
|
|
1052
|
-
if (bodyCloseTagRegex.test(response.html)) response.html = response.html.replace(bodyCloseTagRegex, `${script}
|
|
916
|
+
if (bodyCloseTagRegex.test(response.html)) response.html = response.html.replace(bodyCloseTagRegex, `${script}</body>`);
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
//#endregion
|
|
921
|
+
//#region src/providers/ReactBrowserRouterProvider.ts
|
|
922
|
+
var ReactBrowserRouterProvider = class extends RouterProvider {
|
|
923
|
+
log = $logger();
|
|
924
|
+
alepha = $inject(Alepha);
|
|
925
|
+
pageApi = $inject(ReactPageProvider);
|
|
926
|
+
add(entry) {
|
|
927
|
+
this.pageApi.add(entry);
|
|
928
|
+
}
|
|
929
|
+
configure = $hook({
|
|
930
|
+
on: "configure",
|
|
931
|
+
handler: async () => {
|
|
932
|
+
for (const page of this.pageApi.getPages()) if (page.component || page.lazy) this.push({
|
|
933
|
+
path: page.match,
|
|
934
|
+
page
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
async transition(url, previous = []) {
|
|
939
|
+
const { pathname, search } = url;
|
|
940
|
+
const entry = {
|
|
941
|
+
url,
|
|
942
|
+
query: {},
|
|
943
|
+
params: {},
|
|
944
|
+
layers: [],
|
|
945
|
+
onError: () => null
|
|
946
|
+
};
|
|
947
|
+
const state = entry;
|
|
948
|
+
await this.alepha.emit("react:transition:begin", { state });
|
|
949
|
+
try {
|
|
950
|
+
const { route, params } = this.match(pathname);
|
|
951
|
+
const query = {};
|
|
952
|
+
if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
|
|
953
|
+
state.query = query;
|
|
954
|
+
state.params = params ?? {};
|
|
955
|
+
if (isPageRoute(route)) {
|
|
956
|
+
const { redirect } = await this.pageApi.createLayers(route.page, state, previous);
|
|
957
|
+
if (redirect) return redirect;
|
|
958
|
+
}
|
|
959
|
+
if (state.layers.length === 0) state.layers.push({
|
|
960
|
+
name: "not-found",
|
|
961
|
+
element: createElement(NotFoundPage),
|
|
962
|
+
index: 0,
|
|
963
|
+
path: "/"
|
|
964
|
+
});
|
|
965
|
+
await this.alepha.emit("react:transition:success", { state });
|
|
966
|
+
} catch (e) {
|
|
967
|
+
this.log.error("Transition has failed", e);
|
|
968
|
+
state.layers = [{
|
|
969
|
+
name: "error",
|
|
970
|
+
element: this.pageApi.renderError(e),
|
|
971
|
+
index: 0,
|
|
972
|
+
path: "/"
|
|
973
|
+
}];
|
|
974
|
+
await this.alepha.emit("react:transition:error", {
|
|
975
|
+
error: e,
|
|
976
|
+
state
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
if (previous) for (let i = 0; i < previous.length; i++) {
|
|
980
|
+
const layer = previous[i];
|
|
981
|
+
if (state.layers[i]?.name !== layer.name) this.pageApi.page(layer.name)?.onLeave?.();
|
|
982
|
+
}
|
|
983
|
+
await this.alepha.emit("react:transition:end", { state });
|
|
984
|
+
this.alepha.state("react.router.state", state);
|
|
985
|
+
}
|
|
986
|
+
root(state) {
|
|
987
|
+
return this.pageApi.root(state);
|
|
1053
988
|
}
|
|
1054
989
|
};
|
|
1055
990
|
|
|
1056
991
|
//#endregion
|
|
1057
|
-
//#region src/
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
992
|
+
//#region src/providers/ReactBrowserProvider.ts
|
|
993
|
+
const envSchema = t.object({ REACT_ROOT_ID: t.string({ default: "root" }) });
|
|
994
|
+
var ReactBrowserProvider = class {
|
|
995
|
+
env = $env(envSchema);
|
|
996
|
+
log = $logger();
|
|
997
|
+
client = $inject(LinkProvider);
|
|
998
|
+
alepha = $inject(Alepha);
|
|
999
|
+
router = $inject(ReactBrowserRouterProvider);
|
|
1000
|
+
dateTimeProvider = $inject(DateTimeProvider);
|
|
1001
|
+
root;
|
|
1002
|
+
options = { scrollRestoration: "top" };
|
|
1003
|
+
getRootElement() {
|
|
1004
|
+
const root = this.document.getElementById(this.env.REACT_ROOT_ID);
|
|
1005
|
+
if (root) return root;
|
|
1006
|
+
const div = this.document.createElement("div");
|
|
1007
|
+
div.id = this.env.REACT_ROOT_ID;
|
|
1008
|
+
this.document.body.prepend(div);
|
|
1009
|
+
return div;
|
|
1010
|
+
}
|
|
1011
|
+
transitioning;
|
|
1012
|
+
get state() {
|
|
1013
|
+
return this.alepha.state("react.router.state");
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Accessor for Document DOM API.
|
|
1017
|
+
*/
|
|
1018
|
+
get document() {
|
|
1019
|
+
return window.document;
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Accessor for History DOM API.
|
|
1023
|
+
*/
|
|
1024
|
+
get history() {
|
|
1025
|
+
return window.history;
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Accessor for Location DOM API.
|
|
1029
|
+
*/
|
|
1030
|
+
get location() {
|
|
1031
|
+
return window.location;
|
|
1032
|
+
}
|
|
1033
|
+
get base() {
|
|
1034
|
+
const base = import.meta.env?.BASE_URL;
|
|
1035
|
+
if (!base || base === "/") return "";
|
|
1036
|
+
return base;
|
|
1037
|
+
}
|
|
1038
|
+
get url() {
|
|
1039
|
+
const url = this.location.pathname + this.location.search;
|
|
1040
|
+
if (this.base) return url.replace(this.base, "");
|
|
1041
|
+
return url;
|
|
1042
|
+
}
|
|
1043
|
+
pushState(path, replace) {
|
|
1044
|
+
const url = this.base + path;
|
|
1045
|
+
if (replace) this.history.replaceState({}, "", url);
|
|
1046
|
+
else this.history.pushState({}, "", url);
|
|
1047
|
+
}
|
|
1048
|
+
async invalidate(props) {
|
|
1049
|
+
const previous = [];
|
|
1050
|
+
this.log.trace("Invalidating layers");
|
|
1051
|
+
if (props) {
|
|
1052
|
+
const [key] = Object.keys(props);
|
|
1053
|
+
const value = props[key];
|
|
1054
|
+
for (const layer of this.state.layers) {
|
|
1055
|
+
if (layer.props?.[key]) {
|
|
1056
|
+
previous.push({
|
|
1057
|
+
...layer,
|
|
1058
|
+
props: {
|
|
1059
|
+
...layer.props,
|
|
1060
|
+
[key]: value
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
break;
|
|
1064
|
+
}
|
|
1065
|
+
previous.push(layer);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
await this.render({ previous });
|
|
1069
|
+
}
|
|
1070
|
+
async go(url, options = {}) {
|
|
1071
|
+
this.log.trace(`Going to ${url}`, {
|
|
1072
|
+
url,
|
|
1073
|
+
options
|
|
1074
|
+
});
|
|
1075
|
+
await this.render({
|
|
1076
|
+
url,
|
|
1077
|
+
previous: options.force ? [] : this.state.layers
|
|
1078
|
+
});
|
|
1079
|
+
if (this.state.url.pathname + this.state.url.search !== url) {
|
|
1080
|
+
this.pushState(this.state.url.pathname + this.state.url.search);
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
this.pushState(url, options.replace);
|
|
1084
|
+
}
|
|
1085
|
+
async render(options = {}) {
|
|
1086
|
+
const previous = options.previous ?? this.state.layers;
|
|
1087
|
+
const url = options.url ?? this.url;
|
|
1088
|
+
const start = this.dateTimeProvider.now();
|
|
1089
|
+
this.transitioning = {
|
|
1090
|
+
to: url,
|
|
1091
|
+
from: this.state?.url.pathname
|
|
1092
|
+
};
|
|
1093
|
+
this.log.debug("Transitioning...", { to: url });
|
|
1094
|
+
const redirect = await this.router.transition(new URL(`http://localhost${url}`), previous);
|
|
1095
|
+
if (redirect) {
|
|
1096
|
+
this.log.info("Redirecting to", { redirect });
|
|
1097
|
+
return await this.render({ url: redirect });
|
|
1098
|
+
}
|
|
1099
|
+
const ms = this.dateTimeProvider.now().diff(start);
|
|
1100
|
+
this.log.info(`Transition OK [${ms}ms]`, this.transitioning);
|
|
1101
|
+
this.transitioning = void 0;
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Get embedded layers from the server.
|
|
1105
|
+
*/
|
|
1106
|
+
getHydrationState() {
|
|
1107
|
+
try {
|
|
1108
|
+
if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
console.error(error);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
onTransitionEnd = $hook({
|
|
1114
|
+
on: "react:transition:end",
|
|
1115
|
+
handler: () => {
|
|
1116
|
+
if (this.options.scrollRestoration === "top" && typeof window !== "undefined") {
|
|
1117
|
+
this.log.trace("Restoring scroll position to top");
|
|
1118
|
+
window.scrollTo(0, 0);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
ready = $hook({
|
|
1123
|
+
on: "ready",
|
|
1124
|
+
handler: async () => {
|
|
1125
|
+
const hydration = this.getHydrationState();
|
|
1126
|
+
const previous = hydration?.layers ?? [];
|
|
1127
|
+
if (hydration) {
|
|
1128
|
+
for (const [key, value] of Object.entries(hydration)) if (key !== "layers") this.alepha.state(key, value);
|
|
1129
|
+
}
|
|
1130
|
+
await this.render({ previous });
|
|
1131
|
+
const element = this.router.root(this.state);
|
|
1132
|
+
if (hydration?.layers) {
|
|
1133
|
+
this.root = hydrateRoot(this.getRootElement(), element);
|
|
1134
|
+
this.log.info("Hydrated root element");
|
|
1135
|
+
} else {
|
|
1136
|
+
this.root ??= createRoot(this.getRootElement());
|
|
1137
|
+
this.root.render(element);
|
|
1138
|
+
this.log.info("Created root element");
|
|
1139
|
+
}
|
|
1140
|
+
window.addEventListener("popstate", () => {
|
|
1141
|
+
if (this.base + this.state.url.pathname === this.location.pathname) return;
|
|
1142
|
+
this.log.debug("Popstate event triggered - rendering new state", { url: this.location.pathname + this.location.search });
|
|
1143
|
+
this.render();
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
//#endregion
|
|
1150
|
+
//#region src/services/ReactRouter.ts
|
|
1151
|
+
var ReactRouter = class {
|
|
1152
|
+
alepha = $inject(Alepha);
|
|
1153
|
+
pageApi = $inject(ReactPageProvider);
|
|
1154
|
+
get state() {
|
|
1155
|
+
return this.alepha.state("react.router.state");
|
|
1156
|
+
}
|
|
1157
|
+
get pages() {
|
|
1158
|
+
return this.pageApi.getPages();
|
|
1159
|
+
}
|
|
1160
|
+
get browser() {
|
|
1161
|
+
if (this.alepha.isBrowser()) return this.alepha.inject(ReactBrowserProvider);
|
|
1162
|
+
return void 0;
|
|
1163
|
+
}
|
|
1164
|
+
path(name, config = {}) {
|
|
1165
|
+
return this.pageApi.pathname(name, {
|
|
1166
|
+
params: {
|
|
1167
|
+
...this.state.params,
|
|
1168
|
+
...config.params
|
|
1169
|
+
},
|
|
1170
|
+
query: config.query
|
|
1171
|
+
});
|
|
1065
1172
|
}
|
|
1066
1173
|
getURL() {
|
|
1067
|
-
if (!this.browser) return this.
|
|
1174
|
+
if (!this.browser) return this.state.url;
|
|
1068
1175
|
return new URL(this.location.href);
|
|
1069
1176
|
}
|
|
1070
1177
|
get location() {
|
|
@@ -1075,11 +1182,11 @@ var RouterHookApi = class {
|
|
|
1075
1182
|
return this.state;
|
|
1076
1183
|
}
|
|
1077
1184
|
get pathname() {
|
|
1078
|
-
return this.state.pathname;
|
|
1185
|
+
return this.state.url.pathname;
|
|
1079
1186
|
}
|
|
1080
1187
|
get query() {
|
|
1081
1188
|
const query = {};
|
|
1082
|
-
for (const [key, value] of new URLSearchParams(this.state.search).entries()) query[key] = String(value);
|
|
1189
|
+
for (const [key, value] of new URLSearchParams(this.state.url.search).entries()) query[key] = String(value);
|
|
1083
1190
|
return query;
|
|
1084
1191
|
}
|
|
1085
1192
|
async back() {
|
|
@@ -1091,39 +1198,33 @@ var RouterHookApi = class {
|
|
|
1091
1198
|
async invalidate(props) {
|
|
1092
1199
|
await this.browser?.invalidate(props);
|
|
1093
1200
|
}
|
|
1094
|
-
/**
|
|
1095
|
-
* Create a valid href for the given pathname.
|
|
1096
|
-
*
|
|
1097
|
-
* @param pathname
|
|
1098
|
-
* @param layer
|
|
1099
|
-
*/
|
|
1100
|
-
createHref(pathname, layer = this.layer, options = {}) {
|
|
1101
|
-
if (typeof pathname === "object") pathname = pathname.options.path ?? "";
|
|
1102
|
-
if (options.params) for (const [key, value] of Object.entries(options.params)) pathname = pathname.replace(`:${key}`, String(value));
|
|
1103
|
-
return pathname.startsWith("/") ? pathname : `${layer.path}/${pathname}`.replace(/\/\/+/g, "/");
|
|
1104
|
-
}
|
|
1105
1201
|
async go(path, options) {
|
|
1106
1202
|
for (const page of this.pages) if (page.name === path) {
|
|
1107
|
-
|
|
1108
|
-
|
|
1203
|
+
await this.browser?.go(this.path(path, options), options);
|
|
1204
|
+
return;
|
|
1109
1205
|
}
|
|
1110
|
-
await this.browser?.go(
|
|
1206
|
+
await this.browser?.go(path, options);
|
|
1111
1207
|
}
|
|
1112
1208
|
anchor(path, options = {}) {
|
|
1209
|
+
let href = path;
|
|
1113
1210
|
for (const page of this.pages) if (page.name === path) {
|
|
1114
|
-
|
|
1211
|
+
href = this.path(path, options);
|
|
1115
1212
|
break;
|
|
1116
1213
|
}
|
|
1117
|
-
const href = this.createHref(path, this.layer, options);
|
|
1118
1214
|
return {
|
|
1119
|
-
href,
|
|
1215
|
+
href: this.base(href),
|
|
1120
1216
|
onClick: (ev) => {
|
|
1121
1217
|
ev.stopPropagation();
|
|
1122
1218
|
ev.preventDefault();
|
|
1123
|
-
this.go(
|
|
1219
|
+
this.go(href, options).catch(console.error);
|
|
1124
1220
|
}
|
|
1125
1221
|
};
|
|
1126
1222
|
}
|
|
1223
|
+
base(path) {
|
|
1224
|
+
const base = import.meta.env?.BASE_URL;
|
|
1225
|
+
if (!base || base === "/") return path;
|
|
1226
|
+
return base + path;
|
|
1227
|
+
}
|
|
1127
1228
|
/**
|
|
1128
1229
|
* Set query params.
|
|
1129
1230
|
*
|
|
@@ -1139,90 +1240,131 @@ var RouterHookApi = class {
|
|
|
1139
1240
|
}
|
|
1140
1241
|
};
|
|
1141
1242
|
|
|
1243
|
+
//#endregion
|
|
1244
|
+
//#region src/hooks/useInject.ts
|
|
1245
|
+
/**
|
|
1246
|
+
* Hook to inject a service instance.
|
|
1247
|
+
* It's a wrapper of `useAlepha().inject(service)` with a memoization.
|
|
1248
|
+
*/
|
|
1249
|
+
const useInject = (service) => {
|
|
1250
|
+
const alepha = useAlepha();
|
|
1251
|
+
return useMemo(() => alepha.inject(service), []);
|
|
1252
|
+
};
|
|
1253
|
+
|
|
1142
1254
|
//#endregion
|
|
1143
1255
|
//#region src/hooks/useRouter.ts
|
|
1256
|
+
/**
|
|
1257
|
+
* Use this hook to access the React Router instance.
|
|
1258
|
+
*
|
|
1259
|
+
* You can add a type parameter to specify the type of your application.
|
|
1260
|
+
* This will allow you to use the router in a typesafe way.
|
|
1261
|
+
*
|
|
1262
|
+
* @example
|
|
1263
|
+
* class App {
|
|
1264
|
+
* home = $page();
|
|
1265
|
+
* }
|
|
1266
|
+
*
|
|
1267
|
+
* const router = useRouter<App>();
|
|
1268
|
+
* router.go("home"); // typesafe
|
|
1269
|
+
*/
|
|
1144
1270
|
const useRouter = () => {
|
|
1145
|
-
|
|
1146
|
-
const ctx = useContext(RouterContext);
|
|
1147
|
-
const layer = useContext(RouterLayerContext);
|
|
1148
|
-
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
1149
|
-
const pages = useMemo(() => {
|
|
1150
|
-
return alepha.inject(PageDescriptorProvider).getPages();
|
|
1151
|
-
}, []);
|
|
1152
|
-
return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, alepha.isBrowser() ? alepha.inject(ReactBrowserProvider) : void 0), [layer]);
|
|
1271
|
+
return useInject(ReactRouter);
|
|
1153
1272
|
};
|
|
1154
1273
|
|
|
1155
1274
|
//#endregion
|
|
1156
1275
|
//#region src/components/Link.tsx
|
|
1157
1276
|
const Link = (props) => {
|
|
1158
|
-
React.useContext(RouterContext);
|
|
1159
1277
|
const router = useRouter();
|
|
1160
|
-
const
|
|
1161
|
-
if (!to) return null;
|
|
1162
|
-
const can = typeof props.to === "string" ? void 0 : props.to.options.can;
|
|
1163
|
-
if (can && !can()) return null;
|
|
1164
|
-
const name = typeof props.to === "string" ? void 0 : props.to.options.name;
|
|
1165
|
-
const anchorProps = {
|
|
1166
|
-
...props,
|
|
1167
|
-
to: void 0
|
|
1168
|
-
};
|
|
1278
|
+
const { to,...anchorProps } = props;
|
|
1169
1279
|
return /* @__PURE__ */ jsx("a", {
|
|
1170
1280
|
...router.anchor(to),
|
|
1171
1281
|
...anchorProps,
|
|
1172
|
-
children: props.children
|
|
1282
|
+
children: props.children
|
|
1173
1283
|
});
|
|
1174
1284
|
};
|
|
1175
1285
|
var Link_default = Link;
|
|
1176
1286
|
|
|
1287
|
+
//#endregion
|
|
1288
|
+
//#region src/hooks/useStore.ts
|
|
1289
|
+
/**
|
|
1290
|
+
* Hook to access and mutate the Alepha state.
|
|
1291
|
+
*/
|
|
1292
|
+
const useStore = (key, defaultValue) => {
|
|
1293
|
+
const alepha = useAlepha();
|
|
1294
|
+
useMemo(() => {
|
|
1295
|
+
if (defaultValue != null && alepha.state(key) == null) alepha.state(key, defaultValue);
|
|
1296
|
+
}, [defaultValue]);
|
|
1297
|
+
const [state, setState] = useState(alepha.state(key));
|
|
1298
|
+
useEffect(() => {
|
|
1299
|
+
if (!alepha.isBrowser()) return;
|
|
1300
|
+
return alepha.on("state:mutate", (ev) => {
|
|
1301
|
+
if (ev.key === key) setState(ev.value);
|
|
1302
|
+
});
|
|
1303
|
+
}, []);
|
|
1304
|
+
return [state, (value) => {
|
|
1305
|
+
alepha.state(key, value);
|
|
1306
|
+
}];
|
|
1307
|
+
};
|
|
1308
|
+
|
|
1309
|
+
//#endregion
|
|
1310
|
+
//#region src/hooks/useRouterState.ts
|
|
1311
|
+
const useRouterState = () => {
|
|
1312
|
+
const [state] = useStore("react.router.state");
|
|
1313
|
+
if (!state) throw new AlephaError("Missing react router state");
|
|
1314
|
+
return state;
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1177
1317
|
//#endregion
|
|
1178
1318
|
//#region src/hooks/useActive.ts
|
|
1179
|
-
const useActive = (
|
|
1319
|
+
const useActive = (args) => {
|
|
1180
1320
|
const router = useRouter();
|
|
1181
|
-
const ctx = useContext(RouterContext);
|
|
1182
|
-
const layer = useContext(RouterLayerContext);
|
|
1183
|
-
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
1184
|
-
let name;
|
|
1185
|
-
if (typeof path === "object" && path.options.name) name = path.options.name;
|
|
1186
|
-
const [current, setCurrent] = useState(ctx.state.pathname);
|
|
1187
|
-
const href = useMemo(() => router.createHref(path, layer), [path, layer]);
|
|
1188
1321
|
const [isPending, setPending] = useState(false);
|
|
1189
|
-
const
|
|
1190
|
-
|
|
1322
|
+
const state = useRouterState();
|
|
1323
|
+
const current = state.url.pathname;
|
|
1324
|
+
const options = typeof args === "string" ? { href: args } : {
|
|
1325
|
+
...args,
|
|
1326
|
+
href: args.href
|
|
1327
|
+
};
|
|
1328
|
+
const href = options.href;
|
|
1329
|
+
let isActive = current === href || current === `${href}/` || `${current}/` === href;
|
|
1330
|
+
if (options.startWith && !isActive) isActive = current.startsWith(href);
|
|
1191
1331
|
return {
|
|
1192
|
-
name,
|
|
1193
1332
|
isPending,
|
|
1194
1333
|
isActive,
|
|
1195
1334
|
anchorProps: {
|
|
1196
|
-
href,
|
|
1197
|
-
onClick: (ev) => {
|
|
1198
|
-
ev
|
|
1199
|
-
ev
|
|
1335
|
+
href: router.base(href),
|
|
1336
|
+
onClick: async (ev) => {
|
|
1337
|
+
ev?.stopPropagation();
|
|
1338
|
+
ev?.preventDefault();
|
|
1200
1339
|
if (isActive) return;
|
|
1201
1340
|
if (isPending) return;
|
|
1202
1341
|
setPending(true);
|
|
1203
|
-
|
|
1342
|
+
try {
|
|
1343
|
+
await router.go(href);
|
|
1344
|
+
} finally {
|
|
1204
1345
|
setPending(false);
|
|
1205
|
-
}
|
|
1346
|
+
}
|
|
1206
1347
|
}
|
|
1207
1348
|
}
|
|
1208
1349
|
};
|
|
1209
1350
|
};
|
|
1210
1351
|
|
|
1211
|
-
//#endregion
|
|
1212
|
-
//#region src/hooks/useInject.ts
|
|
1213
|
-
const useInject = (service) => {
|
|
1214
|
-
const alepha = useAlepha();
|
|
1215
|
-
return useMemo(() => alepha.inject(service), []);
|
|
1216
|
-
};
|
|
1217
|
-
|
|
1218
1352
|
//#endregion
|
|
1219
1353
|
//#region src/hooks/useClient.ts
|
|
1220
|
-
|
|
1221
|
-
|
|
1354
|
+
/**
|
|
1355
|
+
* Hook to get a virtual client for the specified scope.
|
|
1356
|
+
*
|
|
1357
|
+
* It's the React-hook version of `$client()`, from `AlephaServerLinks` module.
|
|
1358
|
+
*/
|
|
1359
|
+
const useClient = (scope) => {
|
|
1360
|
+
return useInject(LinkProvider).client(scope);
|
|
1222
1361
|
};
|
|
1223
1362
|
|
|
1224
1363
|
//#endregion
|
|
1225
1364
|
//#region src/hooks/useQueryParams.ts
|
|
1365
|
+
/**
|
|
1366
|
+
* Not well tested. Use with caution.
|
|
1367
|
+
*/
|
|
1226
1368
|
const useQueryParams = (schema, options = {}) => {
|
|
1227
1369
|
const alepha = useAlepha();
|
|
1228
1370
|
const key = options.key ?? "q";
|
|
@@ -1253,29 +1395,17 @@ const decode = (alepha, schema, data) => {
|
|
|
1253
1395
|
}
|
|
1254
1396
|
};
|
|
1255
1397
|
|
|
1256
|
-
//#endregion
|
|
1257
|
-
//#region src/hooks/useRouterState.ts
|
|
1258
|
-
const useRouterState = () => {
|
|
1259
|
-
const router = useContext(RouterContext);
|
|
1260
|
-
const layer = useContext(RouterLayerContext);
|
|
1261
|
-
if (!router || !layer) throw new Error("useRouterState must be used within a RouterContext.Provider");
|
|
1262
|
-
const [state, setState] = useState(router.state);
|
|
1263
|
-
useRouterEvents({ onEnd: ({ state: state$1 }) => setState({ ...state$1 }) });
|
|
1264
|
-
return state;
|
|
1265
|
-
};
|
|
1266
|
-
|
|
1267
1398
|
//#endregion
|
|
1268
1399
|
//#region src/hooks/useSchema.ts
|
|
1269
1400
|
const useSchema = (action) => {
|
|
1270
1401
|
const name = action.name;
|
|
1271
1402
|
const alepha = useAlepha();
|
|
1272
1403
|
const httpClient = useInject(HttpClient);
|
|
1273
|
-
const linkProvider = useInject(LinkProvider);
|
|
1274
1404
|
const [schema, setSchema] = useState(ssrSchemaLoading(alepha, name));
|
|
1275
1405
|
useEffect(() => {
|
|
1276
1406
|
if (!schema.loading) return;
|
|
1277
1407
|
const opts = { cache: true };
|
|
1278
|
-
httpClient.fetch(`${
|
|
1408
|
+
httpClient.fetch(`${LinkProvider.path.apiLinks}/${name}/schema`, {}, opts).then((it) => setSchema(it.data));
|
|
1279
1409
|
}, [name]);
|
|
1280
1410
|
return schema;
|
|
1281
1411
|
};
|
|
@@ -1284,10 +1414,10 @@ const useSchema = (action) => {
|
|
|
1284
1414
|
*/
|
|
1285
1415
|
const ssrSchemaLoading = (alepha, name) => {
|
|
1286
1416
|
if (!alepha.isBrowser()) {
|
|
1287
|
-
const
|
|
1288
|
-
const can =
|
|
1417
|
+
const linkProvider = alepha.inject(LinkProvider);
|
|
1418
|
+
const can = linkProvider.getServerLinks().find((link) => link.name === name);
|
|
1289
1419
|
if (can) {
|
|
1290
|
-
const schema$1 =
|
|
1420
|
+
const schema$1 = linkProvider.links.find((it) => it.name === name)?.schema;
|
|
1291
1421
|
if (schema$1) {
|
|
1292
1422
|
can.schema = schema$1;
|
|
1293
1423
|
return schema$1;
|
|
@@ -1295,34 +1425,11 @@ const ssrSchemaLoading = (alepha, name) => {
|
|
|
1295
1425
|
}
|
|
1296
1426
|
return { loading: true };
|
|
1297
1427
|
}
|
|
1298
|
-
const schema = alepha.inject(LinkProvider).links
|
|
1428
|
+
const schema = alepha.inject(LinkProvider).links.find((it) => it.name === name)?.schema;
|
|
1299
1429
|
if (schema) return schema;
|
|
1300
1430
|
return { loading: true };
|
|
1301
1431
|
};
|
|
1302
1432
|
|
|
1303
|
-
//#endregion
|
|
1304
|
-
//#region src/hooks/useStore.ts
|
|
1305
|
-
/**
|
|
1306
|
-
* Hook to access and mutate the Alepha state.
|
|
1307
|
-
*/
|
|
1308
|
-
const useStore = (key) => {
|
|
1309
|
-
const alepha = useAlepha();
|
|
1310
|
-
const [state, setState] = useState(alepha.state(key));
|
|
1311
|
-
useEffect(() => {
|
|
1312
|
-
if (!alepha.isBrowser()) return;
|
|
1313
|
-
return alepha.on("state:mutate", (ev) => {
|
|
1314
|
-
if (ev.key === key) setState(ev.value);
|
|
1315
|
-
});
|
|
1316
|
-
}, []);
|
|
1317
|
-
if (!alepha.isBrowser()) {
|
|
1318
|
-
const value = alepha.context.get(key);
|
|
1319
|
-
if (value !== null) return [value, (_) => {}];
|
|
1320
|
-
}
|
|
1321
|
-
return [state, (value) => {
|
|
1322
|
-
alepha.state(key, value);
|
|
1323
|
-
}];
|
|
1324
|
-
};
|
|
1325
|
-
|
|
1326
1433
|
//#endregion
|
|
1327
1434
|
//#region src/index.ts
|
|
1328
1435
|
/**
|
|
@@ -1340,12 +1447,12 @@ const AlephaReact = $module({
|
|
|
1340
1447
|
descriptors: [$page],
|
|
1341
1448
|
services: [
|
|
1342
1449
|
ReactServerProvider,
|
|
1343
|
-
|
|
1344
|
-
|
|
1450
|
+
ReactPageProvider,
|
|
1451
|
+
ReactRouter
|
|
1345
1452
|
],
|
|
1346
|
-
register: (alepha) => alepha.with(AlephaServer).with(AlephaServerCache).with(AlephaServerLinks).with(ReactServerProvider).with(
|
|
1453
|
+
register: (alepha) => alepha.with(AlephaServer).with(AlephaServerCache).with(AlephaServerLinks).with(ReactServerProvider).with(ReactPageProvider).with(ReactRouter)
|
|
1347
1454
|
});
|
|
1348
1455
|
|
|
1349
1456
|
//#endregion
|
|
1350
|
-
export { $page, AlephaContext, AlephaReact, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor,
|
|
1457
|
+
export { $page, AlephaContext, AlephaReact, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, ErrorViewer_default as ErrorViewer, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, ReactBrowserProvider, ReactPageProvider, ReactRouter, ReactServerProvider, Redirection, RouterLayerContext, isPageRoute, ssrSchemaLoading, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState, useSchema, useStore };
|
|
1351
1458
|
//# sourceMappingURL=index.js.map
|