@alepha/react 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/auth/chunk-DhGyd7sr.js +28 -0
  2. package/dist/auth/index.browser.js +394 -114
  3. package/dist/auth/index.browser.js.map +1 -1
  4. package/dist/auth/index.cjs +80 -1927
  5. package/dist/auth/index.cjs.map +1 -1
  6. package/dist/auth/index.d.cts +1130 -420
  7. package/dist/auth/index.d.ts +1130 -420
  8. package/dist/auth/index.js +72 -1918
  9. package/dist/auth/index.js.map +1 -1
  10. package/dist/core/chunk-DhGyd7sr.js +28 -0
  11. package/dist/core/index.browser.js +79 -79
  12. package/dist/core/index.browser.js.map +1 -1
  13. package/dist/core/index.cjs +89 -85
  14. package/dist/core/index.cjs.map +1 -1
  15. package/dist/core/index.d.cts +1654 -154
  16. package/dist/core/index.d.ts +1654 -154
  17. package/dist/core/index.js +79 -79
  18. package/dist/core/index.js.map +1 -1
  19. package/dist/form/chunk-DhGyd7sr.js +28 -0
  20. package/dist/form/index.cjs +28 -8
  21. package/dist/form/index.cjs.map +1 -1
  22. package/dist/form/index.d.cts +215 -7
  23. package/dist/form/index.d.ts +215 -7
  24. package/dist/form/index.js +18 -3
  25. package/dist/form/index.js.map +1 -1
  26. package/dist/head/chunk-DhGyd7sr.js +28 -0
  27. package/dist/head/index.browser.js +385 -59
  28. package/dist/head/index.browser.js.map +1 -1
  29. package/dist/head/index.cjs +12 -8
  30. package/dist/head/index.cjs.map +1 -1
  31. package/dist/head/index.d.cts +1230 -24
  32. package/dist/head/index.d.ts +1230 -29
  33. package/dist/head/index.js +2 -2
  34. package/dist/head/index.js.map +1 -1
  35. package/dist/i18n/chunk-DhGyd7sr.js +28 -0
  36. package/dist/i18n/index.cjs +33 -20
  37. package/dist/i18n/index.cjs.map +1 -1
  38. package/dist/i18n/index.d.cts +282 -13
  39. package/dist/i18n/index.d.ts +282 -13
  40. package/dist/i18n/index.js +23 -14
  41. package/dist/i18n/index.js.map +1 -1
  42. package/dist/websocket/index.cjs +21 -8
  43. package/dist/websocket/index.cjs.map +1 -1
  44. package/dist/websocket/index.js +11 -2
  45. package/dist/websocket/index.js.map +1 -1
  46. package/package.json +7 -6
  47. package/src/auth/index.browser.ts +3 -6
  48. package/src/auth/index.shared.ts +0 -1
  49. package/src/auth/index.ts +3 -16
  50. package/src/auth/providers/ReactAuthProvider.ts +1 -614
  51. package/src/auth/services/ReactAuth.ts +6 -17
  52. package/src/core/descriptors/$page.ts +1 -1
  53. package/src/core/index.browser.ts +1 -0
  54. package/src/core/index.native.ts +21 -0
  55. package/src/core/index.shared-router.ts +15 -0
  56. package/src/core/index.shared.ts +0 -14
  57. package/src/core/index.ts +1 -0
  58. package/src/core/services/ReactRouter.ts +2 -2
  59. package/src/form/errors/FormValidationError.ts +20 -0
  60. package/src/form/hooks/useForm.ts +1 -1
  61. package/src/form/index.ts +1 -0
  62. package/src/head/providers/BrowserHeadProvider.ts +1 -1
  63. package/src/i18n/descriptors/$dictionary.ts +7 -3
  64. package/src/i18n/providers/I18nProvider.ts +9 -10
  65. package/src/websocket/hooks/useRoom.tsx +21 -2
  66. package/dist/auth/index.d.cts.map +0 -1
  67. package/dist/auth/index.d.ts.map +0 -1
  68. package/dist/core/index.d.cts.map +0 -1
  69. package/dist/core/index.d.ts.map +0 -1
  70. package/dist/form/index.d.cts.map +0 -1
  71. package/dist/form/index.d.ts.map +0 -1
  72. package/dist/head/index.d.cts.map +0 -1
  73. package/dist/head/index.d.ts.map +0 -1
  74. package/dist/i18n/index.d.cts.map +0 -1
  75. package/dist/i18n/index.d.ts.map +0 -1
  76. package/dist/websocket/index.d.cts.map +0 -1
  77. package/dist/websocket/index.d.ts.map +0 -1
  78. package/src/auth/descriptors/$auth.ts +0 -436
  79. package/src/auth/descriptors/$authApple.ts +0 -8
  80. package/src/auth/descriptors/$authGithub.ts +0 -81
  81. package/src/auth/descriptors/$authGoogle.ts +0 -38
  82. package/src/auth/errors/SessionExpiredError.ts +0 -6
  83. package/src/auth/schemas/tokenResponseSchema.ts +0 -11
  84. package/src/auth/schemas/tokensSchema.ts +0 -21
  85. package/src/auth/schemas/userinfoResponseSchema.ts +0 -10
@@ -0,0 +1,28 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+
27
+ //#endregion
28
+ export { };
@@ -1,13 +1,17 @@
1
1
  import { $atom, $env, $hook, $inject, $module, $use, Alepha, AlephaError, Atom, Descriptor, KIND, createDescriptor, t } from "alepha";
2
2
  import { AlephaDateTime, DateTimeProvider } from "alepha/datetime";
3
- import { AlephaServer, HttpClient } from "alepha/server";
4
- import { AlephaServerLinks, LinkProvider, apiLinksResponseSchema } from "alepha/server/links";
3
+ import { AlephaServer, HttpClient, ServerProvider, ServerRouterProvider, ServerTimingProvider } from "alepha/server";
4
+ import { AlephaServerCache } from "alepha/server/cache";
5
+ import { AlephaServerLinks, LinkProvider, ServerLinksProvider } from "alepha/server/links";
5
6
  import { $logger } from "alepha/logger";
6
- import { RouterProvider } from "alepha/router";
7
7
  import React, { StrictMode, createContext, createElement, memo, use, useContext, useEffect, useMemo, useRef, useState } from "react";
8
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
- import { createRoot, hydrateRoot } from "react-dom/client";
10
- import { userAccountInfoSchema } from "alepha/security";
9
+ import { existsSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { ServerStaticProvider } from "alepha/server/static";
12
+ import { renderToString } from "react-dom/server";
13
+ import { RouterProvider } from "alepha/router";
14
+ import { alephaServerAuthRoutes, tokenResponseSchema, userinfoResponseSchema } from "alepha/server/auth";
11
15
 
12
16
  //#region src/core/services/ReactPageService.ts
13
17
  var ReactPageService = class {
@@ -67,7 +71,7 @@ var ReactPageService = class {
67
71
  * const userProfile = $page({
68
72
  * path: "/users/:id",
69
73
  * schema: {
70
- * params: t.object({ id: t.int() }),
74
+ * params: t.object({ id: t.integer() }),
71
75
  * query: t.object({ tab: t.optional(t.text()) })
72
76
  * },
73
77
  * resolve: async ({ params }) => {
@@ -144,31 +148,6 @@ var PageDescriptor = class extends Descriptor {
144
148
  };
145
149
  $page[KIND] = PageDescriptor;
146
150
 
147
- //#endregion
148
- //#region src/core/components/NotFound.tsx
149
- function NotFoundPage(props) {
150
- return /* @__PURE__ */ jsx("div", {
151
- style: {
152
- height: "100vh",
153
- display: "flex",
154
- flexDirection: "column",
155
- justifyContent: "center",
156
- alignItems: "center",
157
- textAlign: "center",
158
- fontFamily: "sans-serif",
159
- padding: "1rem",
160
- ...props.style
161
- },
162
- children: /* @__PURE__ */ jsx("h1", {
163
- style: {
164
- fontSize: "1rem",
165
- marginBottom: "0.5rem"
166
- },
167
- children: "404 - This page does not exist"
168
- })
169
- });
170
- }
171
-
172
151
  //#endregion
173
152
  //#region src/core/components/ClientOnly.tsx
174
153
  /**
@@ -598,12 +577,37 @@ function parseAnimation(animationLike, state, type = "enter") {
598
577
  }
599
578
  }
600
579
 
580
+ //#endregion
581
+ //#region src/core/components/NotFound.tsx
582
+ function NotFoundPage(props) {
583
+ return /* @__PURE__ */ jsx("div", {
584
+ style: {
585
+ height: "100vh",
586
+ display: "flex",
587
+ flexDirection: "column",
588
+ justifyContent: "center",
589
+ alignItems: "center",
590
+ textAlign: "center",
591
+ fontFamily: "sans-serif",
592
+ padding: "1rem",
593
+ ...props.style
594
+ },
595
+ children: /* @__PURE__ */ jsx("h1", {
596
+ style: {
597
+ fontSize: "1rem",
598
+ marginBottom: "0.5rem"
599
+ },
600
+ children: "404 - This page does not exist"
601
+ })
602
+ });
603
+ }
604
+
601
605
  //#endregion
602
606
  //#region src/core/providers/ReactPageProvider.ts
603
- const envSchema$1 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
607
+ const envSchema$2 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
604
608
  var ReactPageProvider = class {
605
609
  log = $logger();
606
- env = $env(envSchema$1);
610
+ env = $env(envSchema$2);
607
611
  alepha = $inject(Alepha);
608
612
  pages = [];
609
613
  getPages() {
@@ -921,6 +925,336 @@ const isPageRoute = (it) => {
921
925
  return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
922
926
  };
923
927
 
928
+ //#endregion
929
+ //#region src/core/providers/ReactServerProvider.ts
930
+ const envSchema$1 = t.object({
931
+ REACT_SSR_ENABLED: t.optional(t.boolean()),
932
+ REACT_ROOT_ID: t.text({ default: "root" }),
933
+ REACT_SERVER_TEMPLATE: t.optional(t.text({ size: "rich" }))
934
+ });
935
+ /**
936
+ * React server provider configuration atom
937
+ */
938
+ const reactServerOptions = $atom({
939
+ name: "alepha.react.server.options",
940
+ schema: t.object({
941
+ publicDir: t.string(),
942
+ staticServer: t.object({
943
+ disabled: t.boolean(),
944
+ path: t.string({ description: "URL path where static files will be served." })
945
+ })
946
+ }),
947
+ default: {
948
+ publicDir: "public",
949
+ staticServer: {
950
+ disabled: false,
951
+ path: "/"
952
+ }
953
+ }
954
+ });
955
+ var ReactServerProvider = class {
956
+ log = $logger();
957
+ alepha = $inject(Alepha);
958
+ env = $env(envSchema$1);
959
+ pageApi = $inject(ReactPageProvider);
960
+ serverProvider = $inject(ServerProvider);
961
+ serverStaticProvider = $inject(ServerStaticProvider);
962
+ serverRouterProvider = $inject(ServerRouterProvider);
963
+ serverTimingProvider = $inject(ServerTimingProvider);
964
+ ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
965
+ preprocessedTemplate = null;
966
+ options = $use(reactServerOptions);
967
+ /**
968
+ * Configure the React server provider.
969
+ */
970
+ onConfigure = $hook({
971
+ on: "configure",
972
+ handler: async () => {
973
+ const ssrEnabled = this.alepha.descriptors($page).length > 0 && this.env.REACT_SSR_ENABLED !== false;
974
+ this.alepha.state.set("alepha.react.server.ssr", ssrEnabled);
975
+ if (this.alepha.isViteDev()) {
976
+ await this.configureVite(ssrEnabled);
977
+ return;
978
+ }
979
+ let root = "";
980
+ if (!this.alepha.isServerless()) {
981
+ root = this.getPublicDirectory();
982
+ if (!root) this.log.warn("Missing static files, static file server will be disabled");
983
+ else {
984
+ this.log.debug(`Using static files from: ${root}`);
985
+ await this.configureStaticServer(root);
986
+ }
987
+ }
988
+ if (ssrEnabled) {
989
+ await this.registerPages(async () => this.template);
990
+ this.log.info("SSR OK");
991
+ return;
992
+ }
993
+ this.log.info("SSR is disabled, use History API fallback");
994
+ this.serverRouterProvider.createRoute({
995
+ path: "*",
996
+ handler: async ({ url, reply }) => {
997
+ if (url.pathname.includes(".")) {
998
+ reply.headers["content-type"] = "text/plain";
999
+ reply.body = "Not Found";
1000
+ reply.status = 404;
1001
+ return;
1002
+ }
1003
+ reply.headers["content-type"] = "text/html";
1004
+ return this.template;
1005
+ }
1006
+ });
1007
+ }
1008
+ });
1009
+ get template() {
1010
+ return this.alepha.env.REACT_SERVER_TEMPLATE ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
1011
+ }
1012
+ async registerPages(templateLoader) {
1013
+ const template = await templateLoader();
1014
+ if (template) this.preprocessedTemplate = this.preprocessTemplate(template);
1015
+ for (const page of this.pageApi.getPages()) {
1016
+ if (page.children?.length) continue;
1017
+ this.log.debug(`+ ${page.match} -> ${page.name}`);
1018
+ this.serverRouterProvider.createRoute({
1019
+ ...page,
1020
+ schema: void 0,
1021
+ method: "GET",
1022
+ path: page.match,
1023
+ handler: this.createHandler(page, templateLoader)
1024
+ });
1025
+ }
1026
+ }
1027
+ /**
1028
+ * Get the public directory path where static files are located.
1029
+ */
1030
+ getPublicDirectory() {
1031
+ const maybe = [join(process.cwd(), `dist/${this.options.publicDir}`), join(process.cwd(), this.options.publicDir)];
1032
+ for (const it of maybe) if (existsSync(it)) return it;
1033
+ return "";
1034
+ }
1035
+ /**
1036
+ * Configure the static file server to serve files from the given root directory.
1037
+ */
1038
+ async configureStaticServer(root) {
1039
+ await this.serverStaticProvider.createStaticServer({
1040
+ root,
1041
+ cacheControl: {
1042
+ maxAge: 3600,
1043
+ immutable: true
1044
+ },
1045
+ ...this.options.staticServer
1046
+ });
1047
+ }
1048
+ /**
1049
+ * Configure Vite for SSR.
1050
+ */
1051
+ async configureVite(ssrEnabled) {
1052
+ if (!ssrEnabled) return;
1053
+ this.log.info("SSR (dev) OK");
1054
+ const url = `http://${process.env.SERVER_HOST}:${process.env.SERVER_PORT}`;
1055
+ await this.registerPages(() => fetch(`${url}/index.html`).then((it) => it.text()).catch(() => void 0));
1056
+ }
1057
+ /**
1058
+ * For testing purposes, creates a render function that can be used.
1059
+ */
1060
+ async render(name, options = {}) {
1061
+ const page = this.pageApi.page(name);
1062
+ const url = new URL(this.pageApi.url(name, options));
1063
+ const state = {
1064
+ url,
1065
+ params: options.params ?? {},
1066
+ query: options.query ?? {},
1067
+ onError: () => null,
1068
+ layers: [],
1069
+ meta: {}
1070
+ };
1071
+ this.log.trace("Rendering", { url });
1072
+ await this.alepha.events.emit("react:server:render:begin", { state });
1073
+ const { redirect } = await this.pageApi.createLayers(page, state);
1074
+ if (redirect) return {
1075
+ state,
1076
+ html: "",
1077
+ redirect
1078
+ };
1079
+ if (!options.html) {
1080
+ this.alepha.state.set("alepha.react.router.state", state);
1081
+ return {
1082
+ state,
1083
+ html: renderToString(this.pageApi.root(state))
1084
+ };
1085
+ }
1086
+ const template = this.template ?? "";
1087
+ const html = this.renderToHtml(template, state, options.hydration);
1088
+ if (html instanceof Redirection) return {
1089
+ state,
1090
+ html: "",
1091
+ redirect
1092
+ };
1093
+ const result = {
1094
+ state,
1095
+ html
1096
+ };
1097
+ await this.alepha.events.emit("react:server:render:end", result);
1098
+ return result;
1099
+ }
1100
+ createHandler(route, templateLoader) {
1101
+ return async (serverRequest) => {
1102
+ const { url, reply, query, params } = serverRequest;
1103
+ const template = await templateLoader();
1104
+ if (!template) throw new AlephaError("Missing template for SSR rendering");
1105
+ this.log.trace("Rendering page", { name: route.name });
1106
+ const state = {
1107
+ url,
1108
+ params,
1109
+ query,
1110
+ onError: () => null,
1111
+ layers: []
1112
+ };
1113
+ if (this.alepha.has(ServerLinksProvider)) this.alepha.state.set("alepha.server.request.apiLinks", await this.alepha.inject(ServerLinksProvider).getUserApiLinks({
1114
+ user: serverRequest.user,
1115
+ authorization: serverRequest.headers.authorization
1116
+ }));
1117
+ let target = route;
1118
+ while (target) {
1119
+ if (route.can && !route.can()) {
1120
+ reply.status = 403;
1121
+ reply.headers["content-type"] = "text/plain";
1122
+ return "Forbidden";
1123
+ }
1124
+ target = target.parent;
1125
+ }
1126
+ await this.alepha.events.emit("react:server:render:begin", {
1127
+ request: serverRequest,
1128
+ state
1129
+ });
1130
+ this.serverTimingProvider.beginTiming("createLayers");
1131
+ const { redirect } = await this.pageApi.createLayers(route, state);
1132
+ this.serverTimingProvider.endTiming("createLayers");
1133
+ if (redirect) return reply.redirect(redirect);
1134
+ reply.headers["content-type"] = "text/html";
1135
+ reply.headers["cache-control"] = "no-store, no-cache, must-revalidate, proxy-revalidate";
1136
+ reply.headers.pragma = "no-cache";
1137
+ reply.headers.expires = "0";
1138
+ const html = this.renderToHtml(template, state);
1139
+ if (html instanceof Redirection) {
1140
+ reply.redirect(typeof html.redirect === "string" ? html.redirect : this.pageApi.href(html.redirect));
1141
+ return;
1142
+ }
1143
+ const event = {
1144
+ request: serverRequest,
1145
+ state,
1146
+ html
1147
+ };
1148
+ await this.alepha.events.emit("react:server:render:end", event);
1149
+ route.onServerResponse?.(serverRequest);
1150
+ this.log.trace("Page rendered", { name: route.name });
1151
+ return event.html;
1152
+ };
1153
+ }
1154
+ renderToHtml(template, state, hydration = true) {
1155
+ const element = this.pageApi.root(state);
1156
+ this.alepha.state.set("alepha.react.router.state", state);
1157
+ this.serverTimingProvider.beginTiming("renderToString");
1158
+ let app = "";
1159
+ try {
1160
+ app = renderToString(element);
1161
+ } catch (error) {
1162
+ this.log.error("renderToString has failed, fallback to error handler", error);
1163
+ const element$1 = state.onError(error, state);
1164
+ if (element$1 instanceof Redirection) return element$1;
1165
+ app = renderToString(element$1);
1166
+ this.log.debug("Error handled successfully with fallback");
1167
+ }
1168
+ this.serverTimingProvider.endTiming("renderToString");
1169
+ const response = { html: template };
1170
+ if (hydration) {
1171
+ const { request, context, ...store } = this.alepha.context.als?.getStore() ?? {};
1172
+ const hydrationData = {
1173
+ ...store,
1174
+ "alepha.react.router.state": void 0,
1175
+ layers: state.layers.map((it) => ({
1176
+ ...it,
1177
+ error: it.error ? {
1178
+ ...it.error,
1179
+ name: it.error.name,
1180
+ message: it.error.message,
1181
+ stack: !this.alepha.isProduction() ? it.error.stack : void 0
1182
+ } : void 0,
1183
+ index: void 0,
1184
+ path: void 0,
1185
+ element: void 0,
1186
+ route: void 0
1187
+ }))
1188
+ };
1189
+ const script = `<script>window.__ssr=${JSON.stringify(hydrationData)}<\/script>`;
1190
+ this.fillTemplate(response, app, script);
1191
+ }
1192
+ return response.html;
1193
+ }
1194
+ preprocessTemplate(template) {
1195
+ const bodyCloseIndex = template.match(/<\/body>/i)?.index ?? template.length;
1196
+ const beforeScript = template.substring(0, bodyCloseIndex);
1197
+ const afterScript = template.substring(bodyCloseIndex);
1198
+ const rootDivMatch = beforeScript.match(this.ROOT_DIV_REGEX);
1199
+ if (rootDivMatch) {
1200
+ const beforeDiv = beforeScript.substring(0, rootDivMatch.index);
1201
+ const afterDivStart = rootDivMatch.index + rootDivMatch[0].length;
1202
+ const afterDiv = beforeScript.substring(afterDivStart);
1203
+ return {
1204
+ beforeApp: `${beforeDiv}<div${rootDivMatch[1]} id="${this.env.REACT_ROOT_ID}"${rootDivMatch[2]}>`,
1205
+ afterApp: `</div>${afterDiv}`,
1206
+ beforeScript: "",
1207
+ afterScript
1208
+ };
1209
+ }
1210
+ const bodyMatch = beforeScript.match(/<body([^>]*)>/i);
1211
+ if (bodyMatch) {
1212
+ const beforeBody = beforeScript.substring(0, bodyMatch.index + bodyMatch[0].length);
1213
+ const afterBody = beforeScript.substring(bodyMatch.index + bodyMatch[0].length);
1214
+ return {
1215
+ beforeApp: `${beforeBody}<div id="${this.env.REACT_ROOT_ID}">`,
1216
+ afterApp: `</div>${afterBody}`,
1217
+ beforeScript: "",
1218
+ afterScript
1219
+ };
1220
+ }
1221
+ return {
1222
+ beforeApp: `<div id="${this.env.REACT_ROOT_ID}">`,
1223
+ afterApp: `</div>`,
1224
+ beforeScript,
1225
+ afterScript
1226
+ };
1227
+ }
1228
+ fillTemplate(response, app, script) {
1229
+ if (!this.preprocessedTemplate) this.preprocessedTemplate = this.preprocessTemplate(response.html);
1230
+ response.html = this.preprocessedTemplate.beforeApp + app + this.preprocessedTemplate.afterApp + script + this.preprocessedTemplate.afterScript;
1231
+ }
1232
+ };
1233
+
1234
+ //#endregion
1235
+ //#region src/core/services/ReactPageServerService.ts
1236
+ var ReactPageServerService = class extends ReactPageService {
1237
+ reactServerProvider = $inject(ReactServerProvider);
1238
+ serverProvider = $inject(ServerProvider);
1239
+ async render(name, options = {}) {
1240
+ return this.reactServerProvider.render(name, options);
1241
+ }
1242
+ async fetch(pathname, options = {}) {
1243
+ const response = await fetch(`${this.serverProvider.hostname}/${pathname}`);
1244
+ const html = await response.text();
1245
+ if (options?.html) return {
1246
+ html,
1247
+ response
1248
+ };
1249
+ const match = html.match(this.reactServerProvider.ROOT_DIV_REGEX);
1250
+ if (match) return {
1251
+ html: match[3],
1252
+ response
1253
+ };
1254
+ throw new AlephaError("Invalid HTML response");
1255
+ }
1256
+ };
1257
+
924
1258
  //#endregion
925
1259
  //#region src/core/providers/ReactBrowserRouterProvider.ts
926
1260
  var ReactBrowserRouterProvider = class extends RouterProvider {
@@ -1167,26 +1501,6 @@ var ReactBrowserProvider = class {
1167
1501
  });
1168
1502
  };
1169
1503
 
1170
- //#endregion
1171
- //#region src/core/providers/ReactBrowserRendererProvider.ts
1172
- var ReactBrowserRendererProvider = class {
1173
- log = $logger();
1174
- root;
1175
- onBrowserRender = $hook({
1176
- on: "react:browser:render",
1177
- handler: async ({ hydration, root, element }) => {
1178
- if (hydration?.layers) {
1179
- this.root = hydrateRoot(root, element);
1180
- this.log.info("Hydrated root element");
1181
- } else {
1182
- this.root ??= createRoot(root);
1183
- this.root.render(element);
1184
- this.log.info("Created root element");
1185
- }
1186
- }
1187
- });
1188
- };
1189
-
1190
1504
  //#endregion
1191
1505
  //#region src/core/services/ReactRouter.ts
1192
1506
  var ReactRouter = class {
@@ -1213,7 +1527,7 @@ var ReactRouter = class {
1213
1527
  path(name, config = {}) {
1214
1528
  return this.pageApi.pathname(name, {
1215
1529
  params: {
1216
- ...this.state.params,
1530
+ ...this.state?.params,
1217
1531
  ...config.params
1218
1532
  },
1219
1533
  query: config.query
@@ -1301,47 +1615,31 @@ var ReactRouter = class {
1301
1615
  };
1302
1616
 
1303
1617
  //#endregion
1304
- //#region src/core/index.browser.ts
1618
+ //#region src/core/index.ts
1619
+ /**
1620
+ * Provides full-stack React development with declarative routing, server-side rendering, and client-side hydration.
1621
+ *
1622
+ * The React module enables building modern React applications using the `$page` descriptor on class properties.
1623
+ * It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
1624
+ * type safety and schema validation for route parameters and data.
1625
+ *
1626
+ * @see {@link $page}
1627
+ * @module alepha.react
1628
+ */
1305
1629
  const AlephaReact = $module({
1306
1630
  name: "alepha.react",
1307
1631
  descriptors: [$page],
1308
1632
  services: [
1633
+ ReactServerProvider,
1309
1634
  ReactPageProvider,
1310
- ReactBrowserRouterProvider,
1311
- ReactBrowserProvider,
1312
1635
  ReactRouter,
1313
- ReactBrowserRendererProvider,
1314
- ReactPageService
1636
+ ReactPageService,
1637
+ ReactPageServerService
1315
1638
  ],
1316
- register: (alepha) => alepha.with(AlephaDateTime).with(AlephaServer).with(AlephaServerLinks).with(ReactPageProvider).with(ReactBrowserProvider).with(ReactBrowserRouterProvider).with(ReactBrowserRendererProvider).with(ReactRouter)
1317
- });
1318
-
1319
- //#endregion
1320
- //#region src/auth/schemas/tokensSchema.ts
1321
- const tokensSchema = t.object({
1322
- provider: t.text(),
1323
- access_token: t.text({ size: "rich" }),
1324
- issued_at: t.number(),
1325
- expires_in: t.optional(t.number()),
1326
- refresh_token: t.optional(t.text({ size: "rich" })),
1327
- refresh_token_expires_in: t.optional(t.number()),
1328
- refresh_expires_in: t.optional(t.number({ description: "Alias of `refresh_token_expires_in` for compatibility with some providers." })),
1329
- id_token: t.optional(t.text({ size: "rich" })),
1330
- scope: t.optional(t.text())
1331
- });
1332
-
1333
- //#endregion
1334
- //#region src/auth/schemas/tokenResponseSchema.ts
1335
- const tokenResponseSchema = t.extend(tokensSchema, {
1336
- user: userAccountInfoSchema,
1337
- api: apiLinksResponseSchema
1338
- });
1339
-
1340
- //#endregion
1341
- //#region src/auth/schemas/userinfoResponseSchema.ts
1342
- const userinfoResponseSchema = t.object({
1343
- user: t.optional(userAccountInfoSchema),
1344
- api: apiLinksResponseSchema
1639
+ register: (alepha) => alepha.with(AlephaDateTime).with(AlephaServer).with(AlephaServerCache).with(AlephaServerLinks).with({
1640
+ provide: ReactPageService,
1641
+ use: ReactPageServerService
1642
+ }).with(ReactServerProvider).with(ReactPageProvider).with(ReactRouter)
1345
1643
  });
1346
1644
 
1347
1645
  //#endregion
@@ -1349,18 +1647,10 @@ const userinfoResponseSchema = t.object({
1349
1647
  /**
1350
1648
  * Browser, SSR friendly, service to handle authentication.
1351
1649
  */
1352
- var ReactAuth = class ReactAuth {
1650
+ var ReactAuth = class {
1353
1651
  log = $logger();
1354
1652
  alepha = $inject(Alepha);
1355
1653
  httpClient = $inject(HttpClient);
1356
- static path = {
1357
- login: "/oauth/login",
1358
- callback: "/oauth/callback",
1359
- logout: "/oauth/logout",
1360
- token: "/_auth/token",
1361
- refresh: "/_auth/refresh",
1362
- userinfo: "/_auth/userinfo"
1363
- };
1364
1654
  onBeginTransition = $hook({
1365
1655
  on: "react:transition:begin",
1366
1656
  handler: async (event) => {
@@ -1382,14 +1672,14 @@ var ReactAuth = class ReactAuth {
1382
1672
  return this.alepha.state.get("alepha.server.request.user");
1383
1673
  }
1384
1674
  async ping() {
1385
- const { data } = await this.httpClient.fetch(ReactAuth.path.userinfo, { schema: { response: userinfoResponseSchema } });
1675
+ const { data } = await this.httpClient.fetch(alephaServerAuthRoutes.userinfo, { schema: { response: userinfoResponseSchema } });
1386
1676
  this.alepha.state.set("alepha.server.request.apiLinks", data.api);
1387
1677
  this.alepha.state.set("alepha.server.request.user", data.user);
1388
1678
  return data.user;
1389
1679
  }
1390
1680
  async login(provider, options) {
1391
1681
  if (options.username || options.password) {
1392
- const { data } = await this.httpClient.fetch(`${options.hostname || ""}${ReactAuth.path.token}?provider=${provider}`, {
1682
+ const { data } = await this.httpClient.fetch(`${options.hostname || ""}${alephaServerAuthRoutes.token}?provider=${provider}`, {
1393
1683
  method: "POST",
1394
1684
  body: JSON.stringify({
1395
1685
  username: options.username,
@@ -1405,27 +1695,20 @@ var ReactAuth = class ReactAuth {
1405
1695
  if (this.alepha.isBrowser()) {
1406
1696
  const browser = this.alepha.inject(ReactBrowserProvider);
1407
1697
  const redirect = options.redirect || (browser.transitioning ? window.location.origin + browser.transitioning.to : window.location.href);
1408
- const href = `${window.location.origin}${ReactAuth.path.login}?provider=${provider}&redirect_uri=${encodeURIComponent(redirect)}`;
1698
+ const href = `${window.location.origin}${alephaServerAuthRoutes.login}?provider=${provider}&redirect_uri=${encodeURIComponent(redirect)}`;
1409
1699
  if (browser.transitioning) throw new Redirection(href);
1410
1700
  else {
1411
1701
  window.location.href = href;
1412
1702
  return {};
1413
1703
  }
1414
1704
  }
1415
- throw new Redirection(`${ReactAuth.path.login}?provider=${provider}&redirect_uri=${options.redirect || "/"}`);
1705
+ throw new Redirection(`${alephaServerAuthRoutes.login}?provider=${provider}&redirect_uri=${options.redirect || "/"}`);
1416
1706
  }
1417
1707
  logout() {
1418
- window.location.href = `${ReactAuth.path.logout}?post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}`;
1708
+ window.location.href = `${alephaServerAuthRoutes.logout}?post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}`;
1419
1709
  }
1420
1710
  };
1421
1711
 
1422
- //#endregion
1423
- //#region src/auth/errors/SessionExpiredError.ts
1424
- var SessionExpiredError = class extends AlephaError {
1425
- name = "SessionExpiredError";
1426
- status = 401;
1427
- };
1428
-
1429
1712
  //#endregion
1430
1713
  //#region src/auth/hooks/useAuth.ts
1431
1714
  const useAuth = () => {
@@ -1449,12 +1732,9 @@ const useAuth = () => {
1449
1732
  //#region src/auth/index.browser.ts
1450
1733
  const AlephaReactAuth = $module({
1451
1734
  name: "alepha.react.auth",
1452
- descriptors: [],
1453
- register: (alepha) => {
1454
- alepha.with(ReactAuth);
1455
- }
1735
+ services: [ReactAuth]
1456
1736
  });
1457
1737
 
1458
1738
  //#endregion
1459
- export { AlephaReactAuth, ReactAuth, SessionExpiredError, useAuth };
1739
+ export { AlephaReactAuth, ReactAuth, useAuth };
1460
1740
  //# sourceMappingURL=index.browser.js.map