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