@stone-js/use-react 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,13 +1,63 @@
1
- import { isNotEmpty, isEmpty, InitializationError, isMetaClassModule, isMetaFactoryModule, isFunctionModule, isObjectLikeModule, isFunction, Logger, hasMetadata, getMetadata, isMatchedAdapter, mergeBlueprints, stoneBlueprint, classDecoratorLegacyWrapper, setMetadata, methodDecoratorLegacyWrapper, addMetadata, LIFECYCLE_HOOK_KEY, addBlueprint } from '@stone-js/core';
1
+ import { InitializationError, isEmpty, isNotEmpty, isMetaClassModule, isMetaFactoryModule, isObjectLikeModule, isFunction, isFunctionModule, mergeBlueprints, stoneBlueprint, classDecoratorLegacyWrapper, setMetadata, methodDecoratorLegacyWrapper, addMetadata, LIFECYCLE_HOOK_KEY, hasMetadata, getMetadata, isMatchedAdapter, Logger, addBlueprint } from '@stone-js/core';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import { renderToString } from 'react-dom/server';
4
- import { createContext, StrictMode, useContext, useState, useEffect } from 'react';
5
- import { createRoot, hydrateRoot } from 'react-dom/client';
6
- import { OutgoingHttpResponse, RedirectResponse, MetaStaticFileMiddleware, MetaCompressionMiddleware } from '@stone-js/http-core';
7
- import { OutgoingBrowserResponse, RedirectBrowserResponse } from '@stone-js/browser-core';
4
+ import { createContext, StrictMode, useContext, useMemo, useState, useEffect } from 'react';
5
+ import { hydrateRoot, createRoot } from 'react-dom/client';
8
6
  import { Config } from '@stone-js/config';
9
- import { GET, NAVIGATION_EVENT, NODE_CONSOLE_PLATFORM, Router, RouteEvent } from '@stone-js/router';
10
- import { BROWSER_PLATFORM } from '@stone-js/browser-adapter';
7
+ import { GET, NODE_CONSOLE_PLATFORM, Router, RouteEvent } from '@stone-js/router';
8
+ import { RedirectResponse, OutgoingHttpResponse, MetaCompressionMiddleware, MetaStaticFileMiddleware } from '@stone-js/http-core';
9
+
10
+ /**
11
+ * Custom error for react operations.
12
+ */
13
+ class UseReactError extends InitializationError {
14
+ constructor(message, options) {
15
+ super(message, options);
16
+ this.name = 'UseReactError';
17
+ }
18
+ }
19
+
20
+ /**
21
+ * A useReact event handler for processing incoming events
22
+ * For single event handler.
23
+ *
24
+ * Multiple event handlers will be processed by the router.
25
+ *
26
+ * @template IncomingEventType - The type representing the incoming event.
27
+ * @template OutgoingResponseType - The type representing the outgoing response.
28
+ */
29
+ class UseReactEventHandler {
30
+ blueprint;
31
+ /**
32
+ * Constructs a `UseReactEventHandler` instance.
33
+ *
34
+ * @param options - The UseReactEventHandler options including blueprint.
35
+ */
36
+ constructor({ blueprint }) {
37
+ this.blueprint = blueprint;
38
+ }
39
+ /**
40
+ * Handle an incoming event.
41
+ *
42
+ * @returns The outgoing response.
43
+ */
44
+ handle() {
45
+ return this.getComponentEventHandler();
46
+ }
47
+ /**
48
+ * Get the component event handler.
49
+ *
50
+ * @returns The component event handler.
51
+ * @throws {UseReactError} If the component event handler is missing.
52
+ */
53
+ getComponentEventHandler() {
54
+ const handler = this.blueprint.get('stone.useReact.componentEventHandler');
55
+ if (isEmpty(handler)) {
56
+ throw new UseReactError('The component event handler is missing.');
57
+ }
58
+ return handler;
59
+ }
60
+ }
11
61
 
12
62
  /**
13
63
  * Stone DOM Attribute.
@@ -271,16 +321,6 @@ const StoneError = () => {
271
321
  return (jsxs(Fragment, { children: [jsx("h1", { children: "An error occured" }), jsx("p", { children: "Sorry, something went wrong." })] }));
272
322
  };
273
323
 
274
- /**
275
- * Custom error for react operations.
276
- */
277
- class UseReactError extends InitializationError {
278
- constructor(message, options) {
279
- super(message, options);
280
- this.name = 'UseReactError';
281
- }
282
- }
283
-
284
324
  /**
285
325
  * Build the React application for the current route.
286
326
  * Or for the main handler if the route is not defined.
@@ -486,7 +526,7 @@ const htmlTemplate = (blueprint) => {
486
526
  * @returns True if the application is running on the server side, false otherwise.
487
527
  */
488
528
  function isSSR() {
489
- return import.meta.env.SSR || typeof window === 'undefined';
529
+ return typeof window === 'undefined';
490
530
  }
491
531
  /**
492
532
  * Execute the handler.
@@ -552,11 +592,11 @@ function getBrowserContent(app, component, layout, snapshot, head) {
552
592
  * @param container - The service container.
553
593
  * @param event - The incoming browser event.
554
594
  * @param head - The head context.
555
- * @returns A promise that resolves when the content is hydrated.
595
+ * @returns The server response content as a string.
556
596
  */
557
- async function getServerContent(component, data, container, event, head) {
597
+ function getServerContent(component, data, container, event, head) {
558
598
  const html = renderToString(component).concat('\n<!--app-html-->');
559
- const template = await htmlTemplate(container.make('blueprint'));
599
+ const template = htmlTemplate(container.make('blueprint'));
560
600
  const snapshot = snapshotResponse(event, container, data).concat('\n<!--app-head-->');
561
601
  return applyHeadContextToHtmlString(head ?? {}, template)
562
602
  .replace('<!--app-html-->', html)
@@ -713,87 +753,30 @@ class ReactRuntime {
713
753
  const MetaReactRuntime = { module: ReactRuntime, isClass: true, alias: 'reactRuntime', singleton: true };
714
754
 
715
755
  /**
716
- * Class representing an UseReactBrowserErrorHandler.
756
+ * Class representing an UseReactUseReactKernelErrorHandler.
717
757
  *
718
- * Adapter level error handler for React applications.
758
+ * Kernel level error handler for React applications.
719
759
  */
720
- class UseReactBrowserErrorHandler {
721
- logger;
760
+ class UseReactKernelErrorHandler {
722
761
  blueprint;
723
762
  /**
724
- * Create an UseReactBrowserErrorHandler.
763
+ * Create an UseReactUseReactKernelErrorHandler.
725
764
  *
726
- * @param options - UseReactBrowserErrorHandler options.
765
+ * @param options - UseReactUseReactKernelErrorHandler options.
727
766
  */
728
767
  constructor({ blueprint }) {
729
768
  this.blueprint = blueprint;
730
- this.logger = Logger.getInstance();
731
769
  }
732
770
  /**
733
771
  * Handle an error.
734
772
  *
735
773
  * @param error - The error to handle.
736
- * @param context - The context of the adapter.
737
- * @returns The raw response.
738
- */
739
- async handle(error, context) {
740
- this.logger.error(error.message, { error });
741
- return context
742
- .rawResponseBuilder
743
- .add('render', async () => await this.renderError(error, context));
744
- }
745
- /**
746
- * Get the error body.
747
- *
748
- * @param error - The error to handle.
749
- * @returns The error body.
750
- */
751
- async renderError(error, context) {
752
- const app = await buildAdapterErrorComponent(this.blueprint, context, error.statusCode ?? 500, error);
753
- // Render the component
754
- renderReactApp(app, this.blueprint);
755
- }
756
- }
757
-
758
- /**
759
- * A useReact event handler for processing incoming events
760
- * For single event handler.
761
- *
762
- * Multiple event handlers will be processed by the router.
763
- *
764
- * @template IncomingEventType - The type representing the incoming event.
765
- * @template OutgoingResponseType - The type representing the outgoing response.
766
- */
767
- class UseReactEventHandler {
768
- blueprint;
769
- /**
770
- * Constructs a `UseReactEventHandler` instance.
771
- *
772
- * @param options - The UseReactEventHandler options including blueprint.
773
- */
774
- constructor({ blueprint }) {
775
- this.blueprint = blueprint;
776
- }
777
- /**
778
- * Handle an incoming event.
779
- *
780
- * @returns The outgoing response.
781
- */
782
- handle() {
783
- return this.getComponentEventHandler();
784
- }
785
- /**
786
- * Get the component event handler.
787
- *
788
- * @returns The component event handler.
789
- * @throws {UseReactError} If the component event handler is missing.
774
+ * @returns The outgoing http response.
790
775
  */
791
- getComponentEventHandler() {
792
- const handler = this.blueprint.get('stone.useReact.componentEventHandler');
793
- if (isEmpty(handler)) {
794
- throw new UseReactError('The component event handler is missing.');
795
- }
796
- return handler;
776
+ handle(error) {
777
+ const metavalue = this.blueprint.get(`stone.useReact.errorPages.${String(error?.name)}`) ??
778
+ this.blueprint.get('stone.useReact.errorPages.default', {});
779
+ return { content: { ...metavalue, error }, statusCode: error?.statusCode ?? 500 };
797
780
  }
798
781
  }
799
782
 
@@ -819,7 +802,7 @@ async function preparePage(event, response, container, snapshot) {
819
802
  const component = await buildPageComponent(event, container, componentType, data, response.statusCode);
820
803
  const appComponent = await buildAppComponent(event, container, componentType, layout, data, response.statusCode);
821
804
  response.setContent(isSSR()
822
- ? await getServerContent(appComponent, snapshotData, container, event, head)
805
+ ? getServerContent(appComponent, snapshotData, container, event, head)
823
806
  : getBrowserContent(appComponent, component, layout, snapshot, head));
824
807
  }
825
808
  /**
@@ -845,7 +828,7 @@ async function prepareErrorPage(event, response, container, snapshot) {
845
828
  const component = await buildPageComponent(event, container, componentType, data, response.statusCode, error);
846
829
  const appComponent = await buildAppComponent(event, container, componentType, layout, data, response.statusCode, error);
847
830
  response.setContent(isSSR()
848
- ? await getServerContent(appComponent, snapshotData, container, event, head)
831
+ ? getServerContent(appComponent, snapshotData, container, event, head)
849
832
  : getBrowserContent(appComponent, component, layout, snapshot, head));
850
833
  }
851
834
  /**
@@ -888,108 +871,6 @@ async function onPreparingResponse({ event, response, container }) {
888
871
  }
889
872
  }
890
873
 
891
- /**
892
- * Class representing an UseReactUseReactKernelErrorHandler.
893
- *
894
- * Kernel level error handler for React applications.
895
- */
896
- class UseReactKernelErrorHandler {
897
- blueprint;
898
- /**
899
- * Create an UseReactUseReactKernelErrorHandler.
900
- *
901
- * @param options - UseReactUseReactKernelErrorHandler options.
902
- */
903
- constructor({ blueprint }) {
904
- this.blueprint = blueprint;
905
- }
906
- /**
907
- * Handle an error.
908
- *
909
- * @param error - The error to handle.
910
- * @returns The outgoing http response.
911
- */
912
- handle(error) {
913
- const metavalue = this.blueprint.get(`stone.useReact.errorPages.${String(error?.name)}`) ??
914
- this.blueprint.get('stone.useReact.errorPages.default', {});
915
- return { content: { ...metavalue, error }, statusCode: error?.statusCode ?? 500 };
916
- }
917
- }
918
-
919
- /**
920
- * Create an UseReact response.
921
- *
922
- * @param options - The options for creating the response.
923
- * @returns The React response.
924
- */
925
- const reactResponse = async (options) => {
926
- if (isNotEmpty(options) &&
927
- (isNotEmpty(options.url) ||
928
- (isNotEmpty(options.content) && isNotEmpty(options.content.redirect)))) {
929
- return await reactRedirectResponse(options);
930
- }
931
- return import.meta.env.SSR
932
- ? OutgoingHttpResponse.create(options)
933
- : OutgoingBrowserResponse.create(options);
934
- };
935
- /**
936
- * Create an UseReact redirect response.
937
- *
938
- * @param options - The options for creating the response.
939
- * @returns The React redirect response.
940
- */
941
- const reactRedirectResponse = async (options) => {
942
- return import.meta.env.SSR
943
- ? RedirectResponse.create({ statusCode: 302, ...options })
944
- : RedirectBrowserResponse.create({ statusCode: 302, ...options });
945
- };
946
-
947
- /**
948
- * Class representing an UseReactServerErrorHandler.
949
- *
950
- * Adapter level error handler for React applications.
951
- */
952
- class UseReactServerErrorHandler {
953
- logger;
954
- blueprint;
955
- /**
956
- * Create an UseReactServerErrorHandler.
957
- *
958
- * @param options - UseReactServerErrorHandler options.
959
- */
960
- constructor({ blueprint }) {
961
- this.blueprint = blueprint;
962
- this.logger = Logger.getInstance();
963
- }
964
- /**
965
- * Handle an error.
966
- *
967
- * @param error - The error to handle.
968
- * @param context - The context of the adapter.
969
- * @returns The raw response.
970
- */
971
- async handle(error, context) {
972
- this.logger.error(error.message, { error });
973
- return context
974
- .rawResponseBuilder
975
- .add('statusCode', error.statusCode ?? 500)
976
- .add('headers', new Headers({ 'Content-Type': 'text/html' }))
977
- .add('body', await this.getErrorBody(error, context));
978
- }
979
- /**
980
- * Get the error body.
981
- *
982
- * @param error - The error to handle.
983
- * @returns The error body.
984
- */
985
- async getErrorBody(error, context) {
986
- const statusCode = error.statusCode ?? 500;
987
- const template = await htmlTemplate(this.blueprint);
988
- const ClientApp = await buildAdapterErrorComponent(this.blueprint, context, statusCode, error);
989
- return template.replace('<!--app-html-->', renderToString(ClientApp));
990
- }
991
- }
992
-
993
874
  /**
994
875
  * Use React Service Provider.
995
876
  */
@@ -1059,39 +940,93 @@ function defineAdapterErrorPage(module, options) {
1059
940
  }
1060
941
 
1061
942
  /**
1062
- * Utility function to define a page.
943
+ * Default blueprint for a React-based Stone.js application.
1063
944
  *
1064
- * @param module - The EventHandler module.
1065
- * @param options - Page definition options.
1066
- * @returns The UseReactBlueprint.
945
+ * - Defines middleware, lifecycle hooks, and the default HTML template path.
1067
946
  */
1068
- function definePage(module, options) {
1069
- return {
1070
- stone: {
1071
- router: {
1072
- definitions: [
1073
- {
1074
- ...options,
1075
- method: GET,
1076
- methods: [],
1077
- children: undefined,
1078
- handler: {
1079
- module,
1080
- isComponent: true,
1081
- layout: options?.layout,
1082
- isClass: options?.isClass,
1083
- isFactory: options?.isClass !== true
1084
- }
1085
- }
1086
- ]
1087
- }
1088
- }
1089
- };
1090
- }
1091
- /**
1092
- * Utility function to define a page layout.
1093
- *
1094
- * @param module - The layout module.
947
+ const internalUseReactBlueprint = {
948
+ stone: {
949
+ useReact: {},
950
+ services: [MetaReactRuntime],
951
+ providers: [MetaUseReactServiceProvider]
952
+ }
953
+ };
954
+
955
+ /**
956
+ * Defines a Stone React app using a factory-based or class-based main handler.
957
+ *
958
+ * @param moduleOrOptions - A factory function or class constructor for the main page.
959
+ * @param optionsOrBlueprints - Optional application-level configuration.
960
+ * @param maybeBlueprints - Additional blueprints to merge.
961
+ * @returns A fully merged Stone blueprint.
962
+ */
963
+ function defineStoneReactApp(moduleOrOptions = {}, optionsOrBlueprints, maybeBlueprints) {
964
+ let module;
965
+ let options = {};
966
+ let blueprints = [];
967
+ // Pattern: defineStoneReactApp(handler, options?, blueprints?)
968
+ if (isFunctionModule(moduleOrOptions)) {
969
+ module = moduleOrOptions;
970
+ if (isObjectLikeModule(optionsOrBlueprints)) {
971
+ options = optionsOrBlueprints;
972
+ blueprints = Array.isArray(maybeBlueprints) ? maybeBlueprints : [];
973
+ }
974
+ }
975
+ else if (isObjectLikeModule(moduleOrOptions)) { // Pattern: defineStoneReactApp(options, blueprints?)
976
+ options = moduleOrOptions;
977
+ blueprints = Array.isArray(optionsOrBlueprints) ? optionsOrBlueprints : [];
978
+ }
979
+ const stonePart = {
980
+ ...options,
981
+ useReact: {
982
+ ...options.useReact
983
+ }
984
+ };
985
+ if (isNotEmpty(module)) {
986
+ stonePart.useReact.componentEventHandler = {
987
+ module,
988
+ isComponent: true,
989
+ isClass: options.isClass,
990
+ isFactory: options.isClass !== true
991
+ };
992
+ }
993
+ return mergeBlueprints(stoneBlueprint, internalUseReactBlueprint, ...blueprints, { stone: stonePart });
994
+ }
995
+
996
+ /**
997
+ * Utility function to define a page.
998
+ *
999
+ * @param module - The EventHandler module.
1000
+ * @param options - Page definition options.
1001
+ * @returns The UseReactBlueprint.
1002
+ */
1003
+ function definePage(module, options) {
1004
+ return {
1005
+ stone: {
1006
+ router: {
1007
+ definitions: [
1008
+ {
1009
+ ...options,
1010
+ method: GET,
1011
+ methods: [],
1012
+ children: undefined,
1013
+ handler: {
1014
+ module,
1015
+ isComponent: true,
1016
+ layout: options?.layout,
1017
+ isClass: options?.isClass,
1018
+ isFactory: options?.isClass !== true
1019
+ }
1020
+ }
1021
+ ]
1022
+ }
1023
+ }
1024
+ };
1025
+ }
1026
+ /**
1027
+ * Utility function to define a page layout.
1028
+ *
1029
+ * @param module - The layout module.
1095
1030
  * @param options - Optional page layout definition options.
1096
1031
  * @returns The UseReactBlueprint.
1097
1032
  */
@@ -1164,595 +1099,503 @@ const REACT_ADAPTER_ERROR_PAGE_KEY = Symbol.for('ReactAdapterErrorPage');
1164
1099
  const STONE_REACT_APP_KEY = Symbol.for('StoneReactApp');
1165
1100
 
1166
1101
  /**
1167
- * Adapter Middleware for handling outgoing responses and rendering them in the browser.
1168
- */
1169
- class BrowserResponseMiddleware {
1170
- isRendered;
1171
- blueprint;
1172
- /**
1173
- * Create a BrowserResponseMiddleware.
1174
- *
1175
- * @param {blueprint} options - Options for creating the BrowserResponseMiddleware.
1176
- */
1177
- constructor({ blueprint }) {
1178
- this.blueprint = blueprint;
1179
- this.isRendered = blueprint.has('stone.useReact.reactRoot');
1180
- }
1181
- /**
1182
- * Handles the outgoing response, processes it, and invokes the next middleware in the pipeline.
1183
- *
1184
- * @param context - The adapter context containing the raw event, execution context, and other data.
1185
- * @param next - The next middleware to be invoked in the pipeline.
1186
- * @returns A promise resolving to the processed context.
1187
- * @throws {NodeHttpAdapterError} If required components are missing in the context.
1188
- */
1189
- async handle(context, next) {
1190
- const rawResponseBuilder = await next(context);
1191
- this.ensureValidContext(context, rawResponseBuilder);
1192
- return rawResponseBuilder.add('render', async () => await this.renderComponent(context.outgoingResponse));
1193
- }
1194
- /**
1195
- * Ensures the context and response builder have the required components.
1196
- */
1197
- ensureValidContext(context, rawResponseBuilder) {
1198
- if (context.rawEvent === undefined ||
1199
- context.incomingEvent === undefined ||
1200
- context.outgoingResponse === undefined ||
1201
- rawResponseBuilder?.add === undefined) {
1202
- throw new UseReactError('The context is missing required components.');
1203
- }
1204
- }
1205
- /**
1206
- * Renders the provided React content.
1207
- *
1208
- * @param response - The response object.
1209
- */
1210
- async renderComponent(response) {
1211
- if (isEmpty(response)) {
1212
- throw new UseReactError('No response provided for rendering.');
1213
- }
1214
- const content = response.content;
1215
- const targetUrl = response.targetUrl;
1216
- if (isNotEmpty(targetUrl)) {
1217
- return this.handleRedirect(targetUrl);
1218
- }
1219
- if (isNotEmpty(content?.head)) {
1220
- applyHeadContextToDom(document, content.head);
1221
- }
1222
- if (content?.ssr === true && !this.isRendered && isNotEmpty(content?.app)) {
1223
- return this.hydrateReactApp(content.app);
1224
- }
1225
- if (isNotEmpty(content?.app) && (!this.isRendered || content?.fullRender === true)) {
1226
- return this.renderReactApp(content.app);
1227
- }
1228
- if (isNotEmpty(content?.component)) {
1229
- return await this.dispatchComponentToOutlet(content.component);
1230
- }
1231
- throw new UseReactError('Invalid content provided for rendering.');
1232
- }
1233
- /**
1234
- * Handles navigation redirection.
1235
- *
1236
- * @param path - The path to redirect to.
1237
- */
1238
- handleRedirect(path) {
1239
- window.history.pushState({ path }, '', path);
1240
- window.dispatchEvent(new Event(NAVIGATION_EVENT));
1241
- }
1242
- /**
1243
- * Hydrates the React app when SSR is enabled.
1244
- *
1245
- * @param app - The React app to hydrate.
1246
- */
1247
- hydrateReactApp(app) {
1248
- hydrateReactApp(app, this.blueprint);
1249
- }
1250
- /**
1251
- * Renders the React app.
1252
- *
1253
- * @param app - The React app to render.
1254
- */
1255
- renderReactApp(app) {
1256
- renderReactApp(app, this.blueprint);
1257
- }
1258
- /**
1259
- * Dispatches a component to the layout outlet when no layout is defined.
1260
- *
1261
- * @param component - The component to dispatch.
1262
- */
1263
- async dispatchComponentToOutlet(component) {
1264
- window.dispatchEvent(new CustomEvent(STONE_PAGE_EVENT_OUTLET, { detail: component }));
1265
- }
1266
- }
1267
- /**
1268
- * Meta Middleware for processing browser responses.
1102
+ * A class decorator for defining a class as a React Handler layout.
1103
+ *
1104
+ * @param options - Configuration options for the layout definition.
1105
+ * @returns A method decorator to be applied to a class method.
1106
+ *
1107
+ * @example
1108
+ * ```typescript
1109
+ * import { ErrorPage } from '@stone-js/use-react';
1110
+ *
1111
+ * @ErrorPage({ error: 'UserNotFoundError' })
1112
+ * class UserErrorPage {
1113
+ * render({ error }) {
1114
+ * return <h1>User name: {error.message}</h1>;
1115
+ * }
1116
+ * }
1117
+ * ```
1269
1118
  */
1270
- const MetaBrowserResponseMiddleware = { module: BrowserResponseMiddleware, isClass: true };
1119
+ const ErrorPage = (options) => {
1120
+ return classDecoratorLegacyWrapper((_target, context) => {
1121
+ setMetadata(context, REACT_ERROR_PAGE_KEY, { ...options, isClass: true });
1122
+ });
1123
+ };
1271
1124
 
1272
1125
  /**
1273
- * Blueprint middleware to dynamically set lifecycle hooks for react.
1274
- *
1275
- * @param context - The configuration context containing modules and blueprint.
1276
- * @param next - The next pipeline function to continue processing.
1277
- * @returns The updated blueprint or a promise resolving to it.
1126
+ * Hook decorator to mark a method as a lifecycle hook
1127
+ * And automatically add it to the global lifecycle hook registry.
1278
1128
  *
1279
1129
  * @example
1280
1130
  * ```typescript
1281
- * SetUseReactHooksMiddleware(context, next)
1131
+ * class MyClass {
1132
+ * // ...
1133
+ * @Hook('onPreparingPage')
1134
+ * onPreparingPage () {}
1135
+ * }
1282
1136
  * ```
1137
+ *
1138
+ * @param name - The name of the lifecycle hook.
1139
+ * @returns A class decorator function that sets the metadata using the provided options.
1283
1140
  */
1284
- const SetUseReactHooksMiddleware = (context, next) => {
1285
- if (context.blueprint.get('stone.adapter.platform') !== NODE_CONSOLE_PLATFORM) {
1286
- context
1287
- .blueprint
1288
- .add('stone.lifecycleHooks.onPreparingResponse', [onPreparingResponse]);
1289
- }
1290
- return next(context);
1141
+ const Hook = (name) => {
1142
+ return methodDecoratorLegacyWrapper((_target, context) => {
1143
+ addMetadata(context, LIFECYCLE_HOOK_KEY, { name, method: context.name });
1144
+ });
1291
1145
  };
1146
+
1292
1147
  /**
1293
- * Blueprint middleware to set BrowserResponseMiddleware for the Browser adapter.
1294
- *
1295
- * The MetaBrowserResponseMiddleware is an adapter middleware and is useful
1296
- * for handling outgoing responses and rendering them in the browser.
1148
+ * A class decorator for defining a class as a React Page layout.
1297
1149
  *
1298
- * @param context - The configuration context containing modules and blueprint.
1299
- * @param next - The next pipeline function to continue processing.
1300
- * @returns The updated blueprint or a promise resolving to it.
1150
+ * @param options - Configuration options for the layout definition.
1151
+ * @returns A method decorator to be applied to a class method.
1301
1152
  *
1302
1153
  * @example
1303
1154
  * ```typescript
1304
- * SetBrowserResponseMiddlewareMiddleware(context, next)
1155
+ * import { PageLayout } from '@stone-js/use-react';
1156
+ *
1157
+ * @PageLayout({ name: 'UserPageLayout' })
1158
+ * class UserPageLayout {
1159
+ * render({ data }) {
1160
+ * return <h1>User name: {data.name}</h1>;
1161
+ * }
1162
+ * }
1305
1163
  * ```
1306
1164
  */
1307
- const SetBrowserResponseMiddlewareMiddleware = async (context, next) => {
1308
- if (context.blueprint.get('stone.adapter.platform') === BROWSER_PLATFORM) {
1309
- context.blueprint.add('stone.adapter.middleware', [MetaBrowserResponseMiddleware]);
1310
- }
1311
- return await next(context);
1165
+ const PageLayout = (options) => {
1166
+ return classDecoratorLegacyWrapper((_target, context) => {
1167
+ setMetadata(context, REACT_PAGE_LAYOUT_KEY, { ...options, isClass: true });
1168
+ });
1312
1169
  };
1170
+
1313
1171
  /**
1314
- * Blueprint middleware to process and register kernel error page definitions from modules.
1172
+ * A class decorator for defining a class as a React Handler layout.
1315
1173
  *
1316
- * @param context - The configuration context containing modules and blueprint.
1317
- * @param next - The next pipeline function to continue processing.
1318
- * @returns The updated blueprint or a promise resolving to it.
1174
+ * @param options - Configuration options for the layout definition.
1175
+ * @returns A method decorator to be applied to a class method.
1319
1176
  *
1320
1177
  * @example
1321
1178
  * ```typescript
1322
- * SetReactKernelErrorPageMiddleware(context, next)
1179
+ * import { AdapterErrorPage } from '@stone-js/use-react';
1180
+ *
1181
+ * @AdapterErrorPage({ error: 'UserNotFoundError' })
1182
+ * class UserAdapterErrorPage {
1183
+ * render({ error }) {
1184
+ * return <h1>User name: {error.message}</h1>;
1185
+ * }
1186
+ * }
1323
1187
  * ```
1324
1188
  */
1325
- const SetReactKernelErrorPageMiddleware = (context, next) => {
1326
- context
1327
- .blueprint
1328
- .set('stone.kernel.errorHandlers.default', { module: UseReactKernelErrorHandler, isClass: true });
1329
- context
1330
- .modules
1331
- .filter(module => hasMetadata(module, REACT_ERROR_PAGE_KEY))
1332
- .forEach(module => {
1333
- const { error, layout } = getMetadata(module, REACT_ERROR_PAGE_KEY, { error: 'default' });
1334
- Array(error).flat().forEach(name => {
1335
- context
1336
- .blueprint
1337
- .set(`stone.useReact.errorPages.${name}`, { layout, module, isClass: true });
1338
- });
1339
- });
1340
- // Process both eager and lazy loaded error pages
1341
- Object
1342
- .keys(context.blueprint.get('stone.useReact.errorPages', {}))
1343
- .forEach((name) => {
1344
- context
1345
- .blueprint
1346
- .set(`stone.kernel.errorHandlers.${name}`, { module: UseReactKernelErrorHandler, isClass: true });
1189
+ const AdapterErrorPage = (options) => {
1190
+ return classDecoratorLegacyWrapper((_target, context) => {
1191
+ setMetadata(context, REACT_ADAPTER_ERROR_PAGE_KEY, { ...options, isClass: true });
1347
1192
  });
1348
- return next(context);
1349
1193
  };
1194
+
1350
1195
  /**
1351
- * Blueprint middleware to process and register adapter error page definitions from modules.
1196
+ * Decorator to set the status code of the response.
1352
1197
  *
1353
- * @param context - The configuration context containing modules and blueprint.
1354
- * @param next - The next pipeline function to continue processing.
1355
- * @returns The updated blueprint or a promise resolving to it.
1198
+ * @param statusCode - The status code of the response.
1199
+ * @param headers - The headers for the response.
1200
+ * @returns A method decorator.
1356
1201
  *
1357
1202
  * @example
1358
1203
  * ```typescript
1359
- * SetReactAdapterErrorPageMiddleware(context, next)
1204
+ * import { Page, PageStatus } from '@stone-js/use-react';
1205
+ *
1206
+ * @Page('/user-profile')
1207
+ * class UserPage {
1208
+ * @PageStatus()
1209
+ * handle() {
1210
+ * return { name: 'John Doe' };
1211
+ * }
1212
+ * }
1360
1213
  * ```
1361
1214
  */
1362
- const SetReactAdapterErrorPageMiddleware = (context, next) => {
1363
- const UseReactAdapterErrorHandler = import.meta.env.SSR
1364
- ? UseReactServerErrorHandler
1365
- : UseReactBrowserErrorHandler;
1366
- context
1367
- .blueprint
1368
- .set('stone.adapter.errorHandlers.default', { module: UseReactAdapterErrorHandler, isClass: true });
1369
- context
1370
- .modules
1371
- .filter(module => hasMetadata(module, REACT_ADAPTER_ERROR_PAGE_KEY))
1372
- .forEach(module => {
1373
- const { error, layout, adapterAlias, platform } = getMetadata(module, REACT_ADAPTER_ERROR_PAGE_KEY, { error: 'default' });
1374
- if (isMatchedAdapter(context.blueprint, platform, adapterAlias)) {
1375
- Array(error).flat().forEach(name => {
1376
- context
1377
- .blueprint
1378
- .set(`stone.useReact.adapterErrorPages.${name}`, { isClass: true, layout, module });
1379
- });
1380
- }
1381
- });
1382
- // Process both eager and lazy loaded error pages
1383
- Object
1384
- .keys(context.blueprint.get('stone.useReact.adapterErrorPages', {}))
1385
- .forEach((name) => {
1386
- context
1387
- .blueprint
1388
- .set(`stone.adapter.errorHandlers.${name}`, { module: UseReactAdapterErrorHandler, isClass: true });
1215
+ const PageStatus = (statusCode = 200, headers = {}) => {
1216
+ return methodDecoratorLegacyWrapper((target, _context) => {
1217
+ return async function (...args) {
1218
+ const content = await target.apply(this, args);
1219
+ return { content, statusCode, headers };
1220
+ };
1389
1221
  });
1390
- return next(context);
1391
1222
  };
1223
+
1392
1224
  /**
1393
- * Blueprint middleware to set StaticFileMiddleware for SSR adapter.
1225
+ * A class decorator for defining a class as a React Page route action.
1226
+ * Uses the `Match` decorator internally to register the route with the HTTP `GET` method.
1394
1227
  *
1395
- * @param context - The configuration context containing modules and blueprint.
1396
- * @param next - The next pipeline function to continue processing.
1397
- * @returns The updated blueprint or a promise resolving to it.
1228
+ * @param options - Configuration options for the route definition, excluding the `methods` property.
1229
+ * @returns A method decorator to be applied to a class method.
1398
1230
  *
1399
1231
  * @example
1400
1232
  * ```typescript
1401
- * SetSSRStaticFileMiddleware(context, next)
1402
- * ```
1403
- */
1404
- const SetSSRStaticFileMiddleware = async (context, next) => {
1405
- import.meta.env.SSR && context.blueprint.add('stone.kernel.middleware', [MetaStaticFileMiddleware]);
1406
- return await next(context);
1407
- };
1408
- /**
1409
- * Blueprint middleware to set CompressionMiddleware for SSR adapter.
1233
+ * import { Page } from '@stone-js/use-react';
1410
1234
  *
1411
- * @param context - The configuration context containing modules and blueprint.
1412
- * @param next - The next pipeline function to continue processing.
1413
- * @returns The updated blueprint or a promise resolving to it.
1235
+ * @Page('/user-profile')
1236
+ * class UserPage {
1237
+ * handle({ event }): Record<string, string> {
1238
+ * return { name: 'Jane Doe' };
1239
+ * }
1414
1240
  *
1415
- * @example
1416
- * ```typescript
1417
- * SetSSRCompressionMiddleware(context, next)
1241
+ * render({ data }) {
1242
+ * return <h1>User name: {data.name}</h1>;
1243
+ * }
1244
+ * }
1418
1245
  * ```
1419
1246
  */
1420
- const SetSSRCompressionMiddleware = async (context, next) => {
1421
- import.meta.env.SSR && context.blueprint.add('stone.kernel.middleware', [MetaCompressionMiddleware]);
1422
- return await next(context);
1247
+ const Page = (path, options = {}) => {
1248
+ return classDecoratorLegacyWrapper((target, context) => {
1249
+ setMetadata(context, REACT_PAGE_KEY, {
1250
+ ...options,
1251
+ path,
1252
+ method: GET,
1253
+ methods: [],
1254
+ handler: { isClass: true, isComponent: true, layout: options.layout, module: target }
1255
+ });
1256
+ });
1423
1257
  };
1258
+
1424
1259
  /**
1425
- * Blueprint middleware to process and register route definitions from modules.
1260
+ * Decorator to create a snapshot of the current data.
1426
1261
  *
1427
- * @param context - The configuration context containing modules and blueprint.
1428
- * @param next - The next pipeline function to continue processing.
1429
- * @returns The updated blueprint or a promise resolving to it.
1262
+ * @param name - The name of the snapshot.
1263
+ * @returns A method decorator.
1430
1264
  *
1431
1265
  * @example
1432
1266
  * ```typescript
1433
- * SetReactRouteDefinitionsMiddleware(context, next)
1267
+ * import { Service } from '@stone-js/core';
1268
+ * import { Snapshot } from '@stone-js/use-react';
1269
+ *
1270
+ * @Service({ alias: 'userService' })
1271
+ * class UserService {
1272
+ * @Snapshot()
1273
+ * showProfile() {
1274
+ * return { name: 'John Doe' };
1275
+ * }
1276
+ * }
1434
1277
  * ```
1435
1278
  */
1436
- const SetReactRouteDefinitionsMiddleware = (context, next) => {
1437
- context
1438
- .modules
1439
- .filter(module => hasMetadata(module, REACT_PAGE_KEY))
1440
- .forEach(module => {
1441
- const options = getMetadata(module, REACT_PAGE_KEY, { path: '/' });
1442
- const definition = {
1443
- ...options,
1444
- handler: { ...options.handler, module }
1279
+ const Snapshot = (name) => {
1280
+ return methodDecoratorLegacyWrapper((target, context) => {
1281
+ return async function (...args) {
1282
+ name = name ?? `${String(Object.getPrototypeOf(this).constructor.name)}.${String(context.name)}`;
1283
+ return await ReactRuntime.instance?.snapshot(name, () => target.apply(this, args));
1445
1284
  };
1446
- context.blueprint.add('stone.router.definitions', [definition]);
1447
1285
  });
1448
- return next(context);
1449
1286
  };
1287
+
1450
1288
  /**
1451
- * Blueprint middleware to process and register layout definitions from modules.
1452
- *
1453
- * @param context - The configuration context containing modules and blueprint.
1454
- * @param next - The next pipeline function to continue processing.
1455
- * @returns The updated blueprint or a promise resolving to it.
1289
+ * Sets the error handler for the React adapter and registers error pages.
1456
1290
  *
1457
- * @example
1458
- * ```typescript
1459
- * SetReactPageLayoutMiddleware(context, next)
1460
- * ```
1291
+ * @param errorHandler - The error handler to set for the React adapter.
1292
+ * @param context - The blueprint context containing modules and blueprint.
1293
+ * @returns The updated blueprint context with the error handler and error pages set.
1461
1294
  */
1462
- const SetReactPageLayoutMiddleware = (context, next) => {
1295
+ function setUseReactAdapterErrorHandler(errorHandler, context) {
1296
+ context
1297
+ .blueprint
1298
+ .set('stone.adapter.errorHandlers.default', { module: errorHandler, isClass: true });
1463
1299
  context
1464
1300
  .modules
1465
- .filter(module => hasMetadata(module, REACT_PAGE_LAYOUT_KEY))
1301
+ .filter(module => hasMetadata(module, REACT_ADAPTER_ERROR_PAGE_KEY))
1466
1302
  .forEach(module => {
1467
- const { name = 'default' } = getMetadata(module, REACT_PAGE_LAYOUT_KEY, { name: 'default' });
1468
- context.blueprint.set(`stone.useReact.layout.${name}`, { isClass: true, module });
1303
+ const { error, layout, adapterAlias, platform } = getMetadata(module, REACT_ADAPTER_ERROR_PAGE_KEY, { error: 'default' });
1304
+ if (isMatchedAdapter(context.blueprint, platform, adapterAlias)) {
1305
+ Array(error).flat().forEach(name => {
1306
+ context
1307
+ .blueprint
1308
+ .set(`stone.useReact.adapterErrorPages.${name}`, { isClass: true, layout, module });
1309
+ });
1310
+ }
1469
1311
  });
1470
- return next(context);
1471
- };
1312
+ // Process both eager and lazy loaded error pages
1313
+ Object
1314
+ .keys(context.blueprint.get('stone.useReact.adapterErrorPages', {}))
1315
+ .forEach((name) => {
1316
+ context
1317
+ .blueprint
1318
+ .set(`stone.adapter.errorHandlers.${name}`, { module: errorHandler, isClass: true });
1319
+ });
1320
+ return context;
1321
+ }
1322
+
1472
1323
  /**
1473
- * Blueprint middleware to set the UseReact as the main event handler for the application.
1474
- *
1475
- * Set as fallback event handler if none of the other event handlers are registered.
1476
- *
1477
- * @param context - The configuration context containing modules and blueprint.
1478
- * @param next - The next function in the pipeline.
1479
- * @returns The updated blueprint.
1324
+ * Create an UseReact response.
1480
1325
  *
1481
- * @example
1482
- * ```typescript
1483
- * SetUseReactEventHandlerMiddleware({ modules, blueprint }, next);
1484
- * ```
1326
+ * @param options - The options for creating the response.
1327
+ * @returns The React response.
1485
1328
  */
1486
- async function SetUseReactEventHandlerMiddleware(context, next) {
1487
- const blueprint = await next(context);
1488
- const module = context.modules.find(module => hasMetadata(module, STONE_REACT_APP_KEY));
1489
- blueprint.setIf('stone.kernel.eventHandler', { module: UseReactEventHandler, isClass: true });
1490
- if (isNotEmpty(module)) {
1491
- blueprint.set('stone.useReact.componentEventHandler', { module, isComponent: true, isClass: true });
1329
+ const reactResponse = (options) => {
1330
+ if (isNotEmpty(options) &&
1331
+ (isNotEmpty(options.url) ||
1332
+ (isNotEmpty(options.content) && isNotEmpty(options.content.redirect)))) {
1333
+ return reactRedirectResponse(options);
1492
1334
  }
1493
- return blueprint;
1494
- }
1335
+ return OutgoingHttpResponse.create(options);
1336
+ };
1495
1337
  /**
1496
- * Configuration for react processing middleware.
1338
+ * Create an UseReact redirect response.
1497
1339
  *
1498
- * This array defines a list of middleware pipes, each with a `pipe` function and a `priority`.
1499
- * These pipes are executed in the order of their priority values, with lower values running first.
1340
+ * @param options - The options for creating the response.
1341
+ * @returns The React redirect response.
1500
1342
  */
1501
- const metaUseReactBlueprintMiddleware = [
1502
- { module: SetSSRStaticFileMiddleware, priority: 10 },
1503
- { module: SetUseReactHooksMiddleware, priority: 10 },
1504
- { module: SetSSRCompressionMiddleware, priority: 10 },
1505
- { module: SetReactPageLayoutMiddleware, priority: 10 },
1506
- { module: SetUseReactEventHandlerMiddleware, priority: 2 },
1507
- { module: SetReactKernelErrorPageMiddleware, priority: 10 },
1508
- { module: SetReactAdapterErrorPageMiddleware, priority: 10 },
1509
- { module: SetReactRouteDefinitionsMiddleware, priority: 10 },
1510
- { module: SetBrowserResponseMiddlewareMiddleware, priority: 10 }
1511
- ];
1343
+ const reactRedirectResponse = (options) => {
1344
+ return RedirectResponse.create({ statusCode: 302, ...options });
1345
+ };
1512
1346
 
1513
1347
  /**
1514
- * Default blueprint for a React-based Stone.js application.
1348
+ * Class representing an UseReactServerErrorHandler.
1515
1349
  *
1516
- * - Defines middleware, lifecycle hooks, and the default HTML template path.
1350
+ * Adapter level error handler for React applications.
1517
1351
  */
1518
- const useReactBlueprint = {
1519
- stone: {
1520
- useReact: {},
1521
- blueprint: {
1522
- middleware: metaUseReactBlueprintMiddleware
1523
- },
1524
- services: [MetaReactRuntime],
1525
- providers: [MetaUseReactServiceProvider]
1352
+ class UseReactServerErrorHandler {
1353
+ logger;
1354
+ blueprint;
1355
+ /**
1356
+ * Create an UseReactServerErrorHandler.
1357
+ *
1358
+ * @param options - UseReactServerErrorHandler options.
1359
+ */
1360
+ constructor({ blueprint }) {
1361
+ this.blueprint = blueprint;
1362
+ this.logger = Logger.getInstance();
1526
1363
  }
1527
- };
1364
+ /**
1365
+ * Handle an error.
1366
+ *
1367
+ * @param error - The error to handle.
1368
+ * @param context - The context of the adapter.
1369
+ * @returns The raw response.
1370
+ */
1371
+ async handle(error, context) {
1372
+ this.logger.error(error.message, { error });
1373
+ return context
1374
+ .rawResponseBuilder
1375
+ .add('statusCode', error.statusCode ?? 500)
1376
+ .add('headers', new Headers({ 'Content-Type': 'text/html' }))
1377
+ .add('body', await this.getErrorBody(error, context));
1378
+ }
1379
+ /**
1380
+ * Get the error body.
1381
+ *
1382
+ * @param error - The error to handle.
1383
+ * @returns The error body.
1384
+ */
1385
+ async getErrorBody(error, context) {
1386
+ const statusCode = error.statusCode ?? 500;
1387
+ const template = htmlTemplate(this.blueprint);
1388
+ const ClientApp = await buildAdapterErrorComponent(this.blueprint, context, statusCode, error);
1389
+ return template.replace('<!--app-html-->', renderToString(ClientApp));
1390
+ }
1391
+ }
1528
1392
 
1529
1393
  /**
1530
- * Defines a Stone React app using a factory-based or class-based main handler.
1394
+ * Blueprint middleware to dynamically set lifecycle hooks for react.
1531
1395
  *
1532
- * @param moduleOrOptions - A factory function or class constructor for the main page.
1533
- * @param optionsOrBlueprints - Optional application-level configuration.
1534
- * @param maybeBlueprints - Additional blueprints to merge.
1535
- * @returns A fully merged Stone blueprint.
1396
+ * @param context - The configuration context containing modules and blueprint.
1397
+ * @param next - The next pipeline function to continue processing.
1398
+ * @returns The updated blueprint or a promise resolving to it.
1399
+ *
1400
+ * @example
1401
+ * ```typescript
1402
+ * SetUseReactHooksMiddleware(context, next)
1403
+ * ```
1536
1404
  */
1537
- function defineStoneReactApp(moduleOrOptions = {}, optionsOrBlueprints, maybeBlueprints) {
1538
- let module;
1539
- let options = {};
1540
- let blueprints = [];
1541
- // Pattern: defineStoneReactApp(handler, options?, blueprints?)
1542
- if (isFunctionModule(moduleOrOptions)) {
1543
- module = moduleOrOptions;
1544
- if (isObjectLikeModule(optionsOrBlueprints)) {
1545
- options = optionsOrBlueprints;
1546
- blueprints = Array.isArray(maybeBlueprints) ? maybeBlueprints : [];
1547
- }
1548
- }
1549
- else if (isObjectLikeModule(moduleOrOptions)) { // Pattern: defineStoneReactApp(options, blueprints?)
1550
- options = moduleOrOptions;
1551
- blueprints = Array.isArray(optionsOrBlueprints) ? optionsOrBlueprints : [];
1552
- }
1553
- const stonePart = {
1554
- ...options,
1555
- useReact: {
1556
- ...options.useReact
1557
- }
1558
- };
1559
- if (isNotEmpty(module)) {
1560
- stonePart.useReact.componentEventHandler = {
1561
- module,
1562
- isComponent: true,
1563
- isClass: options.isClass,
1564
- isFactory: options.isClass !== true
1565
- };
1405
+ const SetUseReactHooksMiddleware = (context, next) => {
1406
+ const currentPlatform = context.blueprint.get('stone.adapter.platform', '');
1407
+ const ignorePlatforms = context.blueprint.get('stone.useReact.ignorePlatforms', []);
1408
+ if (!ignorePlatforms.includes(currentPlatform)) {
1409
+ context
1410
+ .blueprint
1411
+ .add('stone.lifecycleHooks.onPreparingResponse', [onPreparingResponse]);
1566
1412
  }
1567
- return mergeBlueprints(stoneBlueprint, useReactBlueprint, ...blueprints, { stone: stonePart });
1568
- }
1569
-
1413
+ return next(context);
1414
+ };
1570
1415
  /**
1571
- * A class decorator for defining a class as a React Handler layout.
1416
+ * Blueprint middleware to process and register kernel error page definitions from modules.
1572
1417
  *
1573
- * @param options - Configuration options for the layout definition.
1574
- * @returns A method decorator to be applied to a class method.
1418
+ * @param context - The configuration context containing modules and blueprint.
1419
+ * @param next - The next pipeline function to continue processing.
1420
+ * @returns The updated blueprint or a promise resolving to it.
1575
1421
  *
1576
1422
  * @example
1577
1423
  * ```typescript
1578
- * import { AdapterErrorPage } from '@stone-js/use-react';
1579
- *
1580
- * @AdapterErrorPage({ error: 'UserNotFoundError' })
1581
- * class UserAdapterErrorPage {
1582
- * render({ error }) {
1583
- * return <h1>User name: {error.message}</h1>;
1584
- * }
1585
- * }
1424
+ * SetReactKernelErrorPageMiddleware(context, next)
1586
1425
  * ```
1587
1426
  */
1588
- const AdapterErrorPage = (options) => {
1589
- return classDecoratorLegacyWrapper((_target, context) => {
1590
- setMetadata(context, REACT_ADAPTER_ERROR_PAGE_KEY, { ...options, isClass: true });
1427
+ const SetReactKernelErrorPageMiddleware = (context, next) => {
1428
+ context
1429
+ .blueprint
1430
+ .set('stone.kernel.errorHandlers.default', { module: UseReactKernelErrorHandler, isClass: true });
1431
+ context
1432
+ .modules
1433
+ .filter(module => hasMetadata(module, REACT_ERROR_PAGE_KEY))
1434
+ .forEach(module => {
1435
+ const { error, layout } = getMetadata(module, REACT_ERROR_PAGE_KEY, { error: 'default' });
1436
+ Array(error).flat().forEach(name => {
1437
+ context
1438
+ .blueprint
1439
+ .set(`stone.useReact.errorPages.${name}`, { layout, module, isClass: true });
1440
+ });
1441
+ });
1442
+ // Process both eager and lazy loaded error pages
1443
+ Object
1444
+ .keys(context.blueprint.get('stone.useReact.errorPages', {}))
1445
+ .forEach((name) => {
1446
+ context
1447
+ .blueprint
1448
+ .set(`stone.kernel.errorHandlers.${name}`, { module: UseReactKernelErrorHandler, isClass: true });
1591
1449
  });
1450
+ return next(context);
1592
1451
  };
1593
-
1594
1452
  /**
1595
- * A class decorator for defining a class as a React Handler layout.
1453
+ * Blueprint middleware to process and register route definitions from modules.
1596
1454
  *
1597
- * @param options - Configuration options for the layout definition.
1598
- * @returns A method decorator to be applied to a class method.
1455
+ * @param context - The configuration context containing modules and blueprint.
1456
+ * @param next - The next pipeline function to continue processing.
1457
+ * @returns The updated blueprint or a promise resolving to it.
1599
1458
  *
1600
1459
  * @example
1601
1460
  * ```typescript
1602
- * import { ErrorPage } from '@stone-js/use-react';
1603
- *
1604
- * @ErrorPage({ error: 'UserNotFoundError' })
1605
- * class UserErrorPage {
1606
- * render({ error }) {
1607
- * return <h1>User name: {error.message}</h1>;
1608
- * }
1609
- * }
1461
+ * SetReactRouteDefinitionsMiddleware(context, next)
1610
1462
  * ```
1611
1463
  */
1612
- const ErrorPage = (options) => {
1613
- return classDecoratorLegacyWrapper((_target, context) => {
1614
- setMetadata(context, REACT_ERROR_PAGE_KEY, { ...options, isClass: true });
1464
+ const SetReactRouteDefinitionsMiddleware = (context, next) => {
1465
+ context
1466
+ .modules
1467
+ .filter(module => hasMetadata(module, REACT_PAGE_KEY))
1468
+ .forEach(module => {
1469
+ const options = getMetadata(module, REACT_PAGE_KEY, { path: '/' });
1470
+ const definition = {
1471
+ ...options,
1472
+ handler: { ...options.handler, module }
1473
+ };
1474
+ context.blueprint.add('stone.router.definitions', [definition]);
1615
1475
  });
1476
+ return next(context);
1616
1477
  };
1617
-
1618
1478
  /**
1619
- * Hook decorator to mark a method as a lifecycle hook
1620
- * And automatically add it to the global lifecycle hook registry.
1479
+ * Blueprint middleware to process and register layout definitions from modules.
1480
+ *
1481
+ * @param context - The configuration context containing modules and blueprint.
1482
+ * @param next - The next pipeline function to continue processing.
1483
+ * @returns The updated blueprint or a promise resolving to it.
1621
1484
  *
1622
1485
  * @example
1623
1486
  * ```typescript
1624
- * class MyClass {
1625
- * // ...
1626
- * @Hook('onPreparingPage')
1627
- * onPreparingPage () {}
1628
- * }
1487
+ * SetReactPageLayoutMiddleware(context, next)
1629
1488
  * ```
1630
- *
1631
- * @param name - The name of the lifecycle hook.
1632
- * @returns A class decorator function that sets the metadata using the provided options.
1633
1489
  */
1634
- const Hook = (name) => {
1635
- return methodDecoratorLegacyWrapper((_target, context) => {
1636
- addMetadata(context, LIFECYCLE_HOOK_KEY, { name, method: context.name });
1490
+ const SetReactPageLayoutMiddleware = (context, next) => {
1491
+ context
1492
+ .modules
1493
+ .filter(module => hasMetadata(module, REACT_PAGE_LAYOUT_KEY))
1494
+ .forEach(module => {
1495
+ const { name = 'default' } = getMetadata(module, REACT_PAGE_LAYOUT_KEY, { name: 'default' });
1496
+ context.blueprint.set(`stone.useReact.layout.${name}`, { isClass: true, module });
1637
1497
  });
1498
+ return next(context);
1638
1499
  };
1639
-
1640
1500
  /**
1641
- * A class decorator for defining a class as a React Page route action.
1642
- * Uses the `Match` decorator internally to register the route with the HTTP `GET` method.
1501
+ * Blueprint middleware to set the UseReact as the main event handler for the application.
1643
1502
  *
1644
- * @param options - Configuration options for the route definition, excluding the `methods` property.
1645
- * @returns A method decorator to be applied to a class method.
1503
+ * Set as fallback event handler if none of the other event handlers are registered.
1504
+ *
1505
+ * @param context - The configuration context containing modules and blueprint.
1506
+ * @param next - The next function in the pipeline.
1507
+ * @returns The updated blueprint.
1646
1508
  *
1647
1509
  * @example
1648
1510
  * ```typescript
1649
- * import { Page } from '@stone-js/use-react';
1650
- *
1651
- * @Page('/user-profile')
1652
- * class UserPage {
1653
- * handle({ event }): Record<string, string> {
1654
- * return { name: 'Jane Doe' };
1655
- * }
1656
- *
1657
- * render({ data }) {
1658
- * return <h1>User name: {data.name}</h1>;
1659
- * }
1660
- * }
1511
+ * SetUseReactEventHandlerMiddleware({ modules, blueprint }, next);
1661
1512
  * ```
1662
1513
  */
1663
- const Page = (path, options = {}) => {
1664
- return classDecoratorLegacyWrapper((target, context) => {
1665
- setMetadata(context, REACT_PAGE_KEY, {
1666
- ...options,
1667
- path,
1668
- method: GET,
1669
- methods: [],
1670
- handler: { isClass: true, isComponent: true, layout: options.layout, module: target }
1671
- });
1672
- });
1673
- };
1514
+ async function SetUseReactEventHandlerMiddleware(context, next) {
1515
+ const blueprint = await next(context);
1516
+ const module = context.modules.find(module => hasMetadata(module, STONE_REACT_APP_KEY));
1517
+ blueprint.setIf('stone.kernel.eventHandler', { module: UseReactEventHandler, isClass: true });
1518
+ if (isNotEmpty(module)) {
1519
+ blueprint.set('stone.useReact.componentEventHandler', { module, isComponent: true, isClass: true });
1520
+ }
1521
+ return blueprint;
1522
+ }
1674
1523
 
1675
1524
  /**
1676
- * A class decorator for defining a class as a React Page layout.
1525
+ * Blueprint middleware to process and register adapter error page definitions from modules.
1677
1526
  *
1678
- * @param options - Configuration options for the layout definition.
1679
- * @returns A method decorator to be applied to a class method.
1527
+ * @param context - The configuration context containing modules and blueprint.
1528
+ * @param next - The next pipeline function to continue processing.
1529
+ * @returns The updated blueprint or a promise resolving to it.
1680
1530
  *
1681
1531
  * @example
1682
1532
  * ```typescript
1683
- * import { PageLayout } from '@stone-js/use-react';
1684
- *
1685
- * @PageLayout({ name: 'UserPageLayout' })
1686
- * class UserPageLayout {
1687
- * render({ data }) {
1688
- * return <h1>User name: {data.name}</h1>;
1689
- * }
1690
- * }
1533
+ * SetReactAdapterErrorPageMiddleware(context, next)
1691
1534
  * ```
1692
1535
  */
1693
- const PageLayout = (options) => {
1694
- return classDecoratorLegacyWrapper((_target, context) => {
1695
- setMetadata(context, REACT_PAGE_LAYOUT_KEY, { ...options, isClass: true });
1696
- });
1536
+ const SetReactAdapterErrorPageMiddleware = (context, next) => {
1537
+ return next(setUseReactAdapterErrorHandler(UseReactServerErrorHandler, context));
1697
1538
  };
1698
-
1699
1539
  /**
1700
- * Decorator to set the status code of the response.
1540
+ * Blueprint middleware to set StaticFileMiddleware for SSR adapter.
1701
1541
  *
1702
- * @param statusCode - The status code of the response.
1703
- * @param headers - The headers for the response.
1704
- * @returns A method decorator.
1542
+ * @param context - The configuration context containing modules and blueprint.
1543
+ * @param next - The next pipeline function to continue processing.
1544
+ * @returns The updated blueprint or a promise resolving to it.
1705
1545
  *
1706
1546
  * @example
1707
1547
  * ```typescript
1708
- * import { Page, PageStatus } from '@stone-js/use-react';
1709
- *
1710
- * @Page('/user-profile')
1711
- * class UserPage {
1712
- * @PageStatus()
1713
- * handle() {
1714
- * return { name: 'John Doe' };
1715
- * }
1716
- * }
1548
+ * SetSSRStaticFileMiddleware(context, next)
1717
1549
  * ```
1718
1550
  */
1719
- const PageStatus = (statusCode = 200, headers = {}) => {
1720
- return methodDecoratorLegacyWrapper((target, _context) => {
1721
- return async function (...args) {
1722
- const content = await target.apply(this, args);
1723
- return { content, statusCode, headers };
1724
- };
1725
- });
1551
+ const SetSSRStaticFileMiddleware = async (context, next) => {
1552
+ context.blueprint.add('stone.kernel.middleware', [MetaStaticFileMiddleware]);
1553
+ return await next(context);
1726
1554
  };
1727
-
1728
1555
  /**
1729
- * Decorator to create a snapshot of the current data.
1556
+ * Blueprint middleware to set CompressionMiddleware for SSR adapter.
1730
1557
  *
1731
- * @param name - The name of the snapshot.
1732
- * @returns A method decorator.
1558
+ * @param context - The configuration context containing modules and blueprint.
1559
+ * @param next - The next pipeline function to continue processing.
1560
+ * @returns The updated blueprint or a promise resolving to it.
1733
1561
  *
1734
1562
  * @example
1735
1563
  * ```typescript
1736
- * import { Service } from '@stone-js/core';
1737
- * import { Snapshot } from '@stone-js/use-react';
1738
- *
1739
- * @Service({ alias: 'userService' })
1740
- * class UserService {
1741
- * @Snapshot()
1742
- * showProfile() {
1743
- * return { name: 'John Doe' };
1744
- * }
1745
- * }
1564
+ * SetSSRCompressionMiddleware(context, next)
1746
1565
  * ```
1747
1566
  */
1748
- const Snapshot = (name) => {
1749
- return methodDecoratorLegacyWrapper((target, context) => {
1750
- return async function (...args) {
1751
- name = name ?? `${String(Object.getPrototypeOf(this).constructor.name)}.${String(context.name)}`;
1752
- return await ReactRuntime.instance?.snapshot(name, () => target.apply(this, args));
1753
- };
1754
- });
1567
+ const SetSSRCompressionMiddleware = async (context, next) => {
1568
+ context.blueprint.add('stone.kernel.middleware', [MetaCompressionMiddleware]);
1569
+ return await next(context);
1755
1570
  };
1571
+ /**
1572
+ * Configuration for react processing middleware.
1573
+ *
1574
+ * This array defines a list of middleware pipes, each with a `pipe` function and a `priority`.
1575
+ * These pipes are executed in the order of their priority values, with lower values running first.
1576
+ */
1577
+ const metaServerUseReactBlueprintMiddleware = [
1578
+ { module: SetSSRStaticFileMiddleware, priority: 10 },
1579
+ { module: SetUseReactHooksMiddleware, priority: 10 },
1580
+ { module: SetSSRCompressionMiddleware, priority: 10 },
1581
+ { module: SetReactPageLayoutMiddleware, priority: 10 },
1582
+ { module: SetUseReactEventHandlerMiddleware, priority: 2 },
1583
+ { module: SetReactKernelErrorPageMiddleware, priority: 10 },
1584
+ { module: SetReactAdapterErrorPageMiddleware, priority: 10 },
1585
+ { module: SetReactRouteDefinitionsMiddleware, priority: 10 }
1586
+ ];
1587
+
1588
+ /**
1589
+ * Middleware for the React blueprint.
1590
+ */
1591
+ internalUseReactBlueprint.stone.useReact.ignorePlatforms = [NODE_CONSOLE_PLATFORM];
1592
+ internalUseReactBlueprint.stone.blueprint = { middleware: metaServerUseReactBlueprintMiddleware };
1593
+ /**
1594
+ * Default blueprint for a React-based Stone.js application.
1595
+ *
1596
+ * - Defines middleware, lifecycle hooks, and the default HTML template path.
1597
+ */
1598
+ const useReactBlueprint = internalUseReactBlueprint;
1756
1599
 
1757
1600
  /**
1758
1601
  * UseReact decorator.
@@ -1784,16 +1627,26 @@ const StoneClient = ({ children }) => {
1784
1627
  /**
1785
1628
  * Internal link component using Stone.js router.
1786
1629
  */
1787
- const InternalLink = ({ to, href, noRel, children, className, defaultNav, selectedClass = 'selected', ariaCurrentValue = 'page', rel = 'noopener noreferrer' }) => {
1630
+ const StoneLink = ({ to, href, noRel, external, children, ariaCurrentValue = 'page', selectedClass = 'selected', ...rest }) => {
1631
+ const isExternal = external === true;
1632
+ const shouldHandleNav = !isExternal && isNotEmpty(to);
1788
1633
  const router = useContext(StoneContext).container.resolve(Router);
1789
- const path = isObjectLikeModule(to) ? router.generate(to) : to ?? href;
1634
+ const path = useMemo(() => {
1635
+ return isObjectLikeModule(to) ? router.generate(to) : to ?? href ?? '#';
1636
+ }, [to, href, router]);
1790
1637
  const [currentRoute, setCurrentRoute] = useState(router.getCurrentRoute());
1791
1638
  const selectedClassName = currentRoute?.path === path ? selectedClass : undefined;
1792
- const elemClassName = [className, selectedClassName].filter(Boolean).join(' ').trim();
1639
+ const elemClassName = [rest.className, selectedClassName].filter(Boolean).join(' ').trim();
1793
1640
  const handleClick = (event) => {
1641
+ rest.onClick?.(event);
1642
+ if (event.defaultPrevented || isExternal)
1643
+ return;
1794
1644
  event.preventDefault();
1795
- router.navigate(to ?? '');
1645
+ isNotEmpty(to) && router.navigate(to);
1796
1646
  };
1647
+ if (isEmpty(to) && isEmpty(href)) {
1648
+ Logger.warn('StoneLink: missing "to" or "href"');
1649
+ }
1797
1650
  useEffect(() => {
1798
1651
  const routerEventHandler = (event) => {
1799
1652
  setCurrentRoute(event.get('route'));
@@ -1803,19 +1656,13 @@ const InternalLink = ({ to, href, noRel, children, className, defaultNav, select
1803
1656
  router.off(RouteEvent.ROUTED, routerEventHandler);
1804
1657
  };
1805
1658
  }, [router]);
1806
- return defaultNav === true
1807
- ? (jsx("a", { href: path, className: elemClassName, "aria-current": ariaCurrentValue, rel: noRel !== undefined ? undefined : rel, children: children }))
1808
- : (jsx("button", { onClick: handleClick, className: elemClassName, "aria-current": ariaCurrentValue, rel: noRel !== undefined ? undefined : rel, children: children }));
1809
- };
1810
- /**
1811
- * External link component rendering a regular <a> tag.
1812
- */
1813
- const ExternalLink = ({ to, href, noRel, target, children, className, ariaCurrentValue = 'page', rel = 'noopener noreferrer' }) => (jsx("a", { target: target, className: className, "aria-current": ariaCurrentValue, rel: noRel !== undefined ? undefined : rel, href: typeof to === 'string' ? to : href, children: children }));
1814
- /**
1815
- * Main StoneLink component delegating to internal or external versions.
1816
- */
1817
- const StoneLink = (props) => {
1818
- return props.external === true ? jsx(ExternalLink, { ...props }) : jsx(InternalLink, { ...props });
1659
+ return (
1660
+ // eslint-disable-next-line react/jsx-no-target-blank
1661
+ jsx("a", { ...rest, href: path, className: elemClassName, target: isExternal ? '_blank' : rest.target, "aria-current": isNotEmpty(selectedClassName) ? ariaCurrentValue : undefined, rel: noRel === true
1662
+ ? undefined
1663
+ : isExternal
1664
+ ? 'noopener noreferrer'
1665
+ : rest.rel, onClick: shouldHandleNav ? handleClick : rest.onClick, children: children }));
1819
1666
  };
1820
1667
 
1821
1668
  /**
@@ -1830,7 +1677,7 @@ const StoneLink = (props) => {
1830
1677
  * @param options - The options to create the Stone Outlet.
1831
1678
  * @returns The Stone Outlet component.
1832
1679
  */
1833
- const StoneOutlet = ({ children }) => {
1680
+ const StoneOutlet = ({ children, ...rest }) => {
1834
1681
  const [currentView, setCurrentView] = useState(children);
1835
1682
  useEffect(() => {
1836
1683
  const eventName = STONE_PAGE_EVENT_OUTLET;
@@ -1842,7 +1689,7 @@ const StoneOutlet = ({ children }) => {
1842
1689
  window.addEventListener(eventName, handleEvent);
1843
1690
  return () => window.removeEventListener(eventName, handleEvent);
1844
1691
  }, []);
1845
- return jsx("div", { "data-stone-outlet": 'true', children: currentView });
1692
+ return jsx("div", { ...rest, "data-stone-outlet": 'true', children: currentView });
1846
1693
  };
1847
1694
 
1848
1695
  /**
@@ -1856,4 +1703,4 @@ const StoneServer = ({ children }) => {
1856
1703
  return isServer() ? jsx(Fragment, { children: children }) : jsx(Fragment, {});
1857
1704
  };
1858
1705
 
1859
- export { AdapterErrorPage, BrowserResponseMiddleware, ErrorPage, Hook, MetaBrowserResponseMiddleware, MetaReactRuntime, MetaUseReactServiceProvider, Page, PageLayout, PageStatus, REACT_ADAPTER_ERROR_PAGE_KEY, REACT_ERROR_PAGE_KEY, REACT_PAGE_KEY, REACT_PAGE_LAYOUT_KEY, ReactRuntime, STONE_DOM_ATTR, STONE_PAGE_EVENT_OUTLET, STONE_REACT_APP_KEY, STONE_SNAPSHOT, SetBrowserResponseMiddlewareMiddleware, SetReactAdapterErrorPageMiddleware, SetReactKernelErrorPageMiddleware, SetReactPageLayoutMiddleware, SetReactRouteDefinitionsMiddleware, SetSSRCompressionMiddleware, SetSSRStaticFileMiddleware, SetUseReactEventHandlerMiddleware, SetUseReactHooksMiddleware, Snapshot, StoneClient, StoneContext, StoneError, StoneLink, StoneOutlet, StonePage, StoneServer, UseReact, UseReactBrowserErrorHandler, UseReactError, UseReactEventHandler, UseReactKernelErrorHandler, UseReactServerErrorHandler, UseReactServiceProvider, applyHeadContextToDom, applyHeadContextToHtmlString, applyMeta, buildAdapterErrorComponent, buildAppComponent, buildLayoutComponent, buildPageComponent, defineAdapterErrorPage, defineErrorPage, definePage, definePageLayout, defineStoneReactApp, executeHandler, executeHooks, getAppRootElement, getBrowserContent, getResponseSnapshot, getServerContent, htmlTemplate, hydrateReactApp, isClient, isSSR, isServer, metaUseReactBlueprintMiddleware, onPreparingResponse, prepareErrorPage, prepareFallbackErrorPage, preparePage, reactRedirectResponse, reactResponse, renderReactApp, renderStoneSnapshot, resolveComponent, resolveLazyComponent, snapshotResponse, useReactBlueprint };
1706
+ export { AdapterErrorPage, ErrorPage, Hook, MetaReactRuntime, MetaUseReactServiceProvider, Page, PageLayout, PageStatus, REACT_ADAPTER_ERROR_PAGE_KEY, REACT_ERROR_PAGE_KEY, REACT_PAGE_KEY, REACT_PAGE_LAYOUT_KEY, ReactRuntime, STONE_DOM_ATTR, STONE_PAGE_EVENT_OUTLET, STONE_REACT_APP_KEY, STONE_SNAPSHOT, SetReactAdapterErrorPageMiddleware, SetReactKernelErrorPageMiddleware, SetReactPageLayoutMiddleware, SetReactRouteDefinitionsMiddleware, SetSSRCompressionMiddleware, SetSSRStaticFileMiddleware, SetUseReactEventHandlerMiddleware, SetUseReactHooksMiddleware, Snapshot, StoneClient, StoneContext, StoneError, StoneLink, StoneOutlet, StonePage, StoneServer, UseReact, UseReactError, UseReactEventHandler, UseReactKernelErrorHandler, UseReactServerErrorHandler, UseReactServiceProvider, applyHeadContextToDom, applyHeadContextToHtmlString, applyMeta, buildAdapterErrorComponent, buildAppComponent, buildLayoutComponent, buildPageComponent, defineAdapterErrorPage, defineErrorPage, definePage, definePageLayout, defineStoneReactApp, executeHandler, executeHooks, getAppRootElement, getBrowserContent, getResponseSnapshot, getServerContent, htmlTemplate, hydrateReactApp, internalUseReactBlueprint, isClient, isSSR, isServer, metaServerUseReactBlueprintMiddleware, onPreparingResponse, prepareErrorPage, prepareFallbackErrorPage, preparePage, reactRedirectResponse, reactResponse, renderReactApp, renderStoneSnapshot, resolveComponent, resolveLazyComponent, setUseReactAdapterErrorHandler, snapshotResponse, useReactBlueprint };