arcanajs 2.5.0 → 2.5.2

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 (91) hide show
  1. package/framework/cli/index.d.ts +1 -0
  2. package/framework/cli/index.js +204 -0
  3. package/framework/cli/templates.d.ts +6 -0
  4. package/framework/cli/templates.js +60 -0
  5. package/framework/cli/webpack.config.d.ts +3 -0
  6. package/framework/cli/webpack.config.js +310 -0
  7. package/framework/lib/client/index.d.ts +59 -0
  8. package/framework/lib/client/index.js +97 -0
  9. package/framework/lib/config/index.d.ts +46 -0
  10. package/framework/lib/config/index.js +115 -0
  11. package/framework/lib/global.d.ts +65 -0
  12. package/framework/lib/index.d.ts +19 -0
  13. package/framework/lib/index.js +59 -0
  14. package/framework/lib/server/ArcanaJSMiddleware.d.ts +24 -0
  15. package/framework/lib/server/ArcanaJSMiddleware.js +114 -0
  16. package/framework/lib/server/ArcanaJSServer.d.ts +55 -0
  17. package/framework/lib/server/ArcanaJSServer.js +441 -0
  18. package/framework/lib/server/ControllerBinder.d.ts +4 -0
  19. package/framework/lib/server/ControllerBinder.js +32 -0
  20. package/framework/lib/server/CsrfMiddleware.d.ts +2 -0
  21. package/framework/lib/server/CsrfMiddleware.js +34 -0
  22. package/framework/lib/server/DynamicRouter.d.ts +2 -0
  23. package/framework/lib/server/DynamicRouter.js +50 -0
  24. package/framework/lib/server/ResponseHandlerMiddleware.d.ts +27 -0
  25. package/framework/lib/server/ResponseHandlerMiddleware.js +30 -0
  26. package/framework/lib/server/Router.d.ts +94 -0
  27. package/framework/lib/server/Router.js +203 -0
  28. package/framework/lib/server/default-index.html +12 -0
  29. package/framework/lib/server.d.ts +33 -0
  30. package/framework/lib/server.js +69 -0
  31. package/framework/lib/shared/components/Body.d.ts +6 -0
  32. package/framework/lib/shared/components/Body.js +8 -0
  33. package/framework/lib/shared/components/Head.d.ts +4 -0
  34. package/framework/lib/shared/components/Head.js +125 -0
  35. package/framework/lib/shared/components/Link.d.ts +7 -0
  36. package/framework/lib/shared/components/Link.js +27 -0
  37. package/framework/lib/shared/components/NavLink.d.ts +9 -0
  38. package/framework/lib/shared/components/NavLink.js +13 -0
  39. package/framework/lib/shared/components/Page.d.ts +6 -0
  40. package/framework/lib/shared/components/Page.js +10 -0
  41. package/framework/lib/shared/context/HeadContext.d.ts +6 -0
  42. package/framework/lib/shared/context/HeadContext.js +5 -0
  43. package/framework/lib/shared/context/PageContext.d.ts +1 -0
  44. package/framework/lib/shared/context/PageContext.js +5 -0
  45. package/framework/lib/shared/context/RouterContext.d.ts +15 -0
  46. package/framework/lib/shared/context/RouterContext.js +10 -0
  47. package/framework/lib/shared/core/ArcanaJSApp.d.ts +14 -0
  48. package/framework/lib/shared/core/ArcanaJSApp.js +153 -0
  49. package/framework/lib/shared/hooks/useHead.d.ts +1 -0
  50. package/framework/lib/shared/hooks/useHead.js +7 -0
  51. package/framework/lib/shared/hooks/useLocation.d.ts +5 -0
  52. package/framework/lib/shared/hooks/useLocation.js +13 -0
  53. package/framework/lib/shared/hooks/usePage.d.ts +1 -0
  54. package/framework/lib/shared/hooks/usePage.js +7 -0
  55. package/framework/lib/shared/hooks/useParams.d.ts +1 -0
  56. package/framework/lib/shared/hooks/useParams.js +13 -0
  57. package/framework/lib/shared/hooks/useQuery.d.ts +1 -0
  58. package/framework/lib/shared/hooks/useQuery.js +9 -0
  59. package/framework/lib/shared/hooks/useRouter.d.ts +1 -0
  60. package/framework/lib/shared/hooks/useRouter.js +13 -0
  61. package/framework/lib/shared/utils/createSingletonContext.d.ts +11 -0
  62. package/framework/lib/shared/utils/createSingletonContext.js +21 -0
  63. package/framework/lib/shared/views/ErrorPage.d.ts +7 -0
  64. package/framework/lib/shared/views/ErrorPage.js +12 -0
  65. package/framework/lib/shared/views/NotFoundPage.d.ts +5 -0
  66. package/framework/lib/shared/views/NotFoundPage.js +11 -0
  67. package/framework/lib/types.d.ts +174 -0
  68. package/framework/lib/types.js +8 -0
  69. package/framework/templates/arcanajs.config.ts +44 -0
  70. package/framework/templates/package.json +15 -0
  71. package/framework/templates/postcss.config.js +6 -0
  72. package/framework/templates/public/arcanajs.png +0 -0
  73. package/framework/templates/public/arcanajs.svg +12 -0
  74. package/framework/templates/public/favicon.ico +0 -0
  75. package/framework/templates/src/arcanajs.d.ts +8 -0
  76. package/framework/templates/src/client/globals.css +199 -0
  77. package/framework/templates/src/client/index.tsx +7 -0
  78. package/framework/templates/src/db/mongo.ts +10 -0
  79. package/framework/templates/src/db/mongoose.ts +12 -0
  80. package/framework/templates/src/db/mysql.ts +15 -0
  81. package/framework/templates/src/db/postgres.ts +8 -0
  82. package/framework/templates/src/server/controllers/HomeController.ts +7 -0
  83. package/framework/templates/src/server/controllers/UsersController.ts +37 -0
  84. package/framework/templates/src/server/index.ts +35 -0
  85. package/framework/templates/src/server/routes/api.ts +6 -0
  86. package/framework/templates/src/server/routes/web.ts +7 -0
  87. package/framework/templates/src/views/ErrorPage.tsx +136 -0
  88. package/framework/templates/src/views/HomePage.tsx +344 -0
  89. package/framework/templates/src/views/NotFoundPage.tsx +108 -0
  90. package/framework/templates/tsconfig.json +27 -0
  91. package/package.json +1 -1
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.hydrateArcanaJS = void 0;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const client_1 = require("react-dom/client");
9
+ const HeadContext_1 = require("../shared/context/HeadContext");
10
+ const ArcanaJSApp_1 = require("../shared/core/ArcanaJSApp");
11
+ const ErrorPage_1 = __importDefault(require("../shared/views/ErrorPage"));
12
+ const NotFoundPage_1 = __importDefault(require("../shared/views/NotFoundPage"));
13
+ // ============================================================================
14
+ // Client Hydration Function
15
+ // ============================================================================
16
+ /**
17
+ * Hydrate the ArcanaJS application on the client side
18
+ *
19
+ * This function initializes the React application on the client,
20
+ * hydrating the server-rendered HTML with client-side interactivity.
21
+ *
22
+ * @param viewsOrContext - Either a views registry object or a webpack require.context
23
+ * @param layout - Optional layout component to wrap all pages
24
+ * @param options - Optional navigation configuration options
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * // Using webpack require.context (recommended)
29
+ * import { hydrateArcanaJS } from 'arcanajs/client';
30
+ *
31
+ * const views = require.context('./views', false, /\.tsx$/);
32
+ * hydrateArcanaJS(views);
33
+ * ```
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * // Using manual views registry
38
+ * import { hydrateArcanaJS } from 'arcanajs/client';
39
+ * import HomePage from './views/HomePage';
40
+ * import AboutPage from './views/AboutPage';
41
+ *
42
+ * hydrateArcanaJS({
43
+ * HomePage,
44
+ * AboutPage,
45
+ * });
46
+ * ```
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * // With navigation options
51
+ * import { hydrateArcanaJS } from 'arcanajs/client';
52
+ *
53
+ * const views = require.context('./views', false, /\.tsx$/);
54
+ * hydrateArcanaJS(views, undefined, {
55
+ * onNavigate: (url) => {
56
+ * // Track page views
57
+ * gtag('event', 'page_view', { page_path: url });
58
+ * }
59
+ * });
60
+ * ```
61
+ */
62
+ const hydrateArcanaJS = (viewsOrContext, layout, options) => {
63
+ let views = {};
64
+ if (viewsOrContext.keys && typeof viewsOrContext.keys === "function") {
65
+ viewsOrContext.keys().forEach((key) => {
66
+ const viewName = key.replace(/^\.\/(.*)\.tsx$/, "$1");
67
+ views[viewName] = viewsOrContext(key).default;
68
+ });
69
+ }
70
+ else {
71
+ views = viewsOrContext;
72
+ }
73
+ // Add default error views if not present
74
+ if (!views["NotFoundPage"]) {
75
+ views["NotFoundPage"] = NotFoundPage_1.default;
76
+ }
77
+ if (!views["ErrorPage"]) {
78
+ views["ErrorPage"] = ErrorPage_1.default;
79
+ }
80
+ const container = document.getElementById("root");
81
+ const dataScript = document.getElementById("__ARCANAJS_DATA__");
82
+ // Client-side HeadManager (noop for push, as Head handles client updates via useEffect)
83
+ const headManager = {
84
+ tags: [],
85
+ push: () => { },
86
+ };
87
+ if (container && dataScript) {
88
+ try {
89
+ const { page, data, params, csrfToken } = JSON.parse(dataScript.textContent || "{}");
90
+ (0, client_1.hydrateRoot)(container, (0, jsx_runtime_1.jsx)(HeadContext_1.HeadContext.Provider, { value: headManager, children: (0, jsx_runtime_1.jsx)(ArcanaJSApp_1.ArcanaJSApp, { initialPage: page, initialData: data, initialParams: params, csrfToken: csrfToken, views: views, layout: layout, onNavigate: options?.onNavigate || (() => { }) }) }));
91
+ }
92
+ catch (e) {
93
+ console.error("Failed to parse initial data", e);
94
+ }
95
+ }
96
+ };
97
+ exports.hydrateArcanaJS = hydrateArcanaJS;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * ArcanaJS Configuration System
3
+ *
4
+ * Provides utilities for loading and validating ArcanaJS configuration files.
5
+ */
6
+ import type { ArcanaJSUserConfig, ResolvedArcanaJSConfig } from "../types";
7
+ /**
8
+ * Define a configuration object with type safety
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * export default defineConfig({
13
+ * server: {
14
+ * port: 3000,
15
+ * },
16
+ * });
17
+ * ```
18
+ */
19
+ export declare function defineConfig(config: ArcanaJSUserConfig): ArcanaJSUserConfig;
20
+ /**
21
+ * Load ArcanaJS configuration from project root
22
+ *
23
+ * Searches for:
24
+ * - arcanajs.config.js
25
+ * - arcanajs.config.ts
26
+ * - arcanajs.config.mjs
27
+ *
28
+ * @param root - Project root directory (defaults to process.cwd())
29
+ * @returns User configuration object or empty object if not found
30
+ */
31
+ export declare function loadConfig(root?: string): Promise<ArcanaJSUserConfig>;
32
+ /**
33
+ * Resolve user configuration with defaults
34
+ *
35
+ * @param userConfig - User-provided configuration
36
+ * @param root - Project root directory
37
+ * @returns Fully resolved configuration with all defaults applied
38
+ */
39
+ export declare function resolveConfig(userConfig: ArcanaJSUserConfig, root?: string): ResolvedArcanaJSConfig;
40
+ /**
41
+ * Load and resolve configuration in one step
42
+ *
43
+ * @param root - Project root directory
44
+ * @returns Fully resolved configuration
45
+ */
46
+ export declare function loadAndResolveConfig(root?: string): Promise<ResolvedArcanaJSConfig>;
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ /**
3
+ * ArcanaJS Configuration System
4
+ *
5
+ * Provides utilities for loading and validating ArcanaJS configuration files.
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.defineConfig = defineConfig;
12
+ exports.loadConfig = loadConfig;
13
+ exports.resolveConfig = resolveConfig;
14
+ exports.loadAndResolveConfig = loadAndResolveConfig;
15
+ const fs_1 = __importDefault(require("fs"));
16
+ const path_1 = __importDefault(require("path"));
17
+ /**
18
+ * Define a configuration object with type safety
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * export default defineConfig({
23
+ * server: {
24
+ * port: 3000,
25
+ * },
26
+ * });
27
+ * ```
28
+ */
29
+ function defineConfig(config) {
30
+ return config;
31
+ }
32
+ /**
33
+ * Load ArcanaJS configuration from project root
34
+ *
35
+ * Searches for:
36
+ * - arcanajs.config.js
37
+ * - arcanajs.config.ts
38
+ * - arcanajs.config.mjs
39
+ *
40
+ * @param root - Project root directory (defaults to process.cwd())
41
+ * @returns User configuration object or empty object if not found
42
+ */
43
+ async function loadConfig(root = process.cwd()) {
44
+ const configFiles = [
45
+ "arcanajs.config.js",
46
+ "arcanajs.config.ts",
47
+ "arcanajs.config.mjs",
48
+ ];
49
+ for (const configFile of configFiles) {
50
+ const configPath = path_1.default.resolve(root, configFile);
51
+ if (fs_1.default.existsSync(configPath)) {
52
+ try {
53
+ // For TypeScript files, we need ts-node or tsx
54
+ if (configFile.endsWith(".ts")) {
55
+ try {
56
+ require("tsx/cjs");
57
+ }
58
+ catch {
59
+ try {
60
+ require("ts-node/register");
61
+ }
62
+ catch {
63
+ console.warn(`Found ${configFile} but ts-node/tsx is not available. Skipping.`);
64
+ continue;
65
+ }
66
+ }
67
+ }
68
+ const config = require(configPath);
69
+ return config.default || config;
70
+ }
71
+ catch (error) {
72
+ console.error(`Error loading config from ${configPath}:`, error);
73
+ }
74
+ }
75
+ }
76
+ return {};
77
+ }
78
+ /**
79
+ * Resolve user configuration with defaults
80
+ *
81
+ * @param userConfig - User-provided configuration
82
+ * @param root - Project root directory
83
+ * @returns Fully resolved configuration with all defaults applied
84
+ */
85
+ function resolveConfig(userConfig, root = process.cwd()) {
86
+ const mode = process.env.NODE_ENV || "development";
87
+ return {
88
+ root,
89
+ mode,
90
+ server: {
91
+ port: userConfig.server?.port || process.env.PORT || 3000,
92
+ staticDir: userConfig.server?.staticDir || "public",
93
+ distDir: userConfig.server?.distDir || "dist/public",
94
+ },
95
+ build: {
96
+ outDir: userConfig.build?.outDir || "dist",
97
+ sourcemap: userConfig.build?.sourcemap ?? mode === "development",
98
+ minify: userConfig.build?.minify ?? mode === "production",
99
+ },
100
+ views: {
101
+ dir: userConfig.views?.dir || "src/views",
102
+ layout: userConfig.views?.layout,
103
+ },
104
+ };
105
+ }
106
+ /**
107
+ * Load and resolve configuration in one step
108
+ *
109
+ * @param root - Project root directory
110
+ * @returns Fully resolved configuration
111
+ */
112
+ async function loadAndResolveConfig(root = process.cwd()) {
113
+ const userConfig = await loadConfig(root);
114
+ return resolveConfig(userConfig, root);
115
+ }
@@ -0,0 +1,65 @@
1
+ // ============================================================================
2
+ // CSS Module Declarations
3
+ // ============================================================================
4
+
5
+ // Global CSS files
6
+ declare module "*.css";
7
+
8
+ // CSS Modules
9
+ declare module "*.module.css" {
10
+ const classes: { readonly [key: string]: string };
11
+ export default classes;
12
+ }
13
+
14
+ // ============================================================================
15
+ // Express Augmentation
16
+ // ============================================================================
17
+
18
+ declare global {
19
+ var __non_webpack_require__: NodeJS.Require;
20
+ namespace Express {
21
+ interface Request {
22
+ /**
23
+ * Normalized DB object optionally attached to the request by ArcanaJSServer.
24
+ * It may be either the raw client, or an object like `{ client, db, close }`.
25
+ */
26
+ db?: any;
27
+ }
28
+ interface Response {
29
+ /**
30
+ * Render a page component with data
31
+ * @param page - Name of the page component to render
32
+ * @param data - Data to pass to the page component
33
+ */
34
+ renderPage(page: string, data?: any): void;
35
+
36
+ /**
37
+ * Send a success JSON response
38
+ * @param data - Data to send in the response
39
+ * @param message - Optional success message
40
+ * @param status - HTTP status code (default: 200)
41
+ */
42
+ success(
43
+ data?: string | object | null,
44
+ message?: string,
45
+ status?: number
46
+ ): Response;
47
+
48
+ /**
49
+ * Send an error JSON response
50
+ * @param message - Error message
51
+ * @param status - HTTP status code (default: 500)
52
+ * @param error - Error details
53
+ * @param data - Additional error data
54
+ */
55
+ error(
56
+ message?: string,
57
+ status?: number,
58
+ error?: string | object | null,
59
+ data?: string | object | null
60
+ ): Response;
61
+ }
62
+ }
63
+ }
64
+
65
+ export {};
@@ -0,0 +1,19 @@
1
+ /// <reference path="./global.d.ts" />
2
+ export * from "./shared/components/Body";
3
+ export * from "./shared/components/Head";
4
+ export * from "./shared/components/Link";
5
+ export * from "./shared/components/NavLink";
6
+ export * from "./shared/components/Page";
7
+ export * from "./shared/context/HeadContext";
8
+ export * from "./shared/context/PageContext";
9
+ export * from "./shared/context/RouterContext";
10
+ export * from "./shared/core/ArcanaJSApp";
11
+ export * from "./shared/hooks/useHead";
12
+ export * from "./shared/hooks/useLocation";
13
+ export * from "./shared/hooks/usePage";
14
+ export * from "./shared/hooks/useParams";
15
+ export * from "./shared/hooks/useQuery";
16
+ export * from "./shared/hooks/useRouter";
17
+ export { default as ErrorPage } from "./shared/views/ErrorPage";
18
+ export { default as NotFoundPage } from "./shared/views/NotFoundPage";
19
+ export * from "./types";
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ /// <reference path="./global.d.ts" />
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
16
+ };
17
+ var __importDefault = (this && this.__importDefault) || function (mod) {
18
+ return (mod && mod.__esModule) ? mod : { "default": mod };
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.NotFoundPage = exports.ErrorPage = void 0;
22
+ // ============================================================================
23
+ // Component Exports
24
+ // ============================================================================
25
+ __exportStar(require("./shared/components/Body"), exports);
26
+ __exportStar(require("./shared/components/Head"), exports);
27
+ __exportStar(require("./shared/components/Link"), exports);
28
+ __exportStar(require("./shared/components/NavLink"), exports);
29
+ __exportStar(require("./shared/components/Page"), exports);
30
+ // ============================================================================
31
+ // Context Exports
32
+ // ============================================================================
33
+ __exportStar(require("./shared/context/HeadContext"), exports);
34
+ __exportStar(require("./shared/context/PageContext"), exports);
35
+ __exportStar(require("./shared/context/RouterContext"), exports);
36
+ // ============================================================================
37
+ // Core Exports
38
+ // ============================================================================
39
+ __exportStar(require("./shared/core/ArcanaJSApp"), exports);
40
+ // ============================================================================
41
+ // Hook Exports
42
+ // ============================================================================
43
+ __exportStar(require("./shared/hooks/useHead"), exports);
44
+ __exportStar(require("./shared/hooks/useLocation"), exports);
45
+ __exportStar(require("./shared/hooks/usePage"), exports);
46
+ __exportStar(require("./shared/hooks/useParams"), exports);
47
+ __exportStar(require("./shared/hooks/useQuery"), exports);
48
+ __exportStar(require("./shared/hooks/useRouter"), exports);
49
+ // ============================================================================
50
+ // Default Error Views
51
+ // ============================================================================
52
+ var ErrorPage_1 = require("./shared/views/ErrorPage");
53
+ Object.defineProperty(exports, "ErrorPage", { enumerable: true, get: function () { return __importDefault(ErrorPage_1).default; } });
54
+ var NotFoundPage_1 = require("./shared/views/NotFoundPage");
55
+ Object.defineProperty(exports, "NotFoundPage", { enumerable: true, get: function () { return __importDefault(NotFoundPage_1).default; } });
56
+ // ============================================================================
57
+ // Type Exports
58
+ // ============================================================================
59
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,24 @@
1
+ import { NextFunction, Request, Response } from "express";
2
+ import React from "react";
3
+ declare global {
4
+ namespace Express {
5
+ interface Response {
6
+ /**
7
+ * Renders a React page using ArcanaJS SSR.
8
+ *
9
+ * @param page - The name of the page component to render.
10
+ * @param data - Initial data to pass to the page component (default: {}).
11
+ * @param params - Route parameters (default: {}).
12
+ * @returns The Express Response object.
13
+ */
14
+ renderPage(page: string, data?: any, params?: Record<string, string>): Response;
15
+ }
16
+ }
17
+ }
18
+ interface ArcanaJSOptions {
19
+ views: Record<string, React.FC<any>>;
20
+ indexFile?: string;
21
+ layout?: React.FC<any>;
22
+ }
23
+ export declare const createArcanaJSMiddleware: (options: ArcanaJSOptions) => (req: Request, res: Response, next: NextFunction) => void;
24
+ export {};
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createArcanaJSMiddleware = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const react_1 = __importDefault(require("react"));
9
+ const server_1 = require("react-dom/server");
10
+ const HeadContext_1 = require("../shared/context/HeadContext");
11
+ const ArcanaJSApp_1 = require("../shared/core/ArcanaJSApp");
12
+ const DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
13
+ <html lang="en">
14
+ <head>
15
+ <meta charset="UTF-8" />
16
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
17
+ <!--HEAD_CONTENT-->
18
+ </head>
19
+ <body>
20
+ <div id="root"><!--APP_CONTENT--></div>
21
+ <!--ARCANAJS_DATA_SCRIPT-->
22
+ </body>
23
+ </html>`;
24
+ // Helper to prevent XSS in initial data
25
+ const safeStringify = (obj) => {
26
+ return JSON.stringify(obj).replace(/</g, "\\u003c");
27
+ };
28
+ const createArcanaJSMiddleware = (options) => {
29
+ const { views, indexFile, layout } = options;
30
+ let cachedIndexHtml = null;
31
+ const getIndexHtml = (callback) => {
32
+ if (process.env.NODE_ENV === "production" && cachedIndexHtml) {
33
+ return callback(null, cachedIndexHtml);
34
+ }
35
+ if (indexFile && fs_1.default.existsSync(indexFile)) {
36
+ fs_1.default.readFile(indexFile, "utf8", (err, htmlData) => {
37
+ if (!err && process.env.NODE_ENV === "production") {
38
+ cachedIndexHtml = htmlData;
39
+ }
40
+ callback(err, htmlData);
41
+ });
42
+ }
43
+ else {
44
+ if (process.env.NODE_ENV === "production") {
45
+ cachedIndexHtml = DEFAULT_HTML_TEMPLATE;
46
+ }
47
+ callback(null, DEFAULT_HTML_TEMPLATE);
48
+ }
49
+ };
50
+ return (req, res, next) => {
51
+ res.renderPage = (page, data = {}, params = {}) => {
52
+ const csrfToken = res.locals.csrfToken;
53
+ if (req.get("X-ArcanaJS-Request") || req.query.format === "json") {
54
+ return res.json({ page, data, params, csrfToken });
55
+ }
56
+ try {
57
+ const headTags = [];
58
+ const headManager = {
59
+ tags: headTags,
60
+ push: (nodes) => headTags.push(nodes),
61
+ };
62
+ const appHtml = (0, server_1.renderToString)(react_1.default.createElement(HeadContext_1.HeadContext.Provider, { value: headManager }, react_1.default.createElement(ArcanaJSApp_1.ArcanaJSApp, {
63
+ initialPage: page,
64
+ initialData: data,
65
+ initialParams: params,
66
+ initialUrl: req.path,
67
+ csrfToken: csrfToken,
68
+ views: views,
69
+ layout: layout,
70
+ })));
71
+ const headHtml = (0, server_1.renderToString)(react_1.default.createElement(react_1.default.Fragment, null, ...headTags));
72
+ getIndexHtml((err, htmlData) => {
73
+ if (err) {
74
+ console.error("Error reading index.html", err);
75
+ return res.status(500).send("Server Error");
76
+ }
77
+ const scriptContent = safeStringify({
78
+ page,
79
+ data,
80
+ params,
81
+ csrfToken,
82
+ });
83
+ const scriptTag = `<script id="__ARCANAJS_DATA__" type="application/json">${scriptContent}</script>`;
84
+ const hmrScript = process.env.ARCANA_HMR_PORT
85
+ ? `
86
+ <script>
87
+ (function() {
88
+ const socket = new WebSocket("ws://localhost:${process.env.ARCANA_HMR_PORT}");
89
+ socket.onmessage = function(event) {
90
+ const data = JSON.parse(event.data);
91
+ if (data.type === "reload") {
92
+ window.location.reload();
93
+ }
94
+ };
95
+ })();
96
+ </script>`
97
+ : "";
98
+ const html = htmlData
99
+ .replace("<!--HEAD_CONTENT-->", headHtml)
100
+ .replace("<!--APP_CONTENT-->", appHtml)
101
+ .replace("<!--ARCANAJS_DATA_SCRIPT-->", scriptTag + hmrScript);
102
+ res.send(html);
103
+ });
104
+ }
105
+ catch (error) {
106
+ console.error("SSR Error:", error);
107
+ return res.status(500).send("Internal Server Error");
108
+ }
109
+ return res;
110
+ };
111
+ next();
112
+ };
113
+ };
114
+ exports.createArcanaJSMiddleware = createArcanaJSMiddleware;
@@ -0,0 +1,55 @@
1
+ import { Express, RequestHandler } from "express";
2
+ import React from "react";
3
+ import { Router as ArcanaRouter } from "./Router";
4
+ export interface ArcanaJSConfig<TDb = any> {
5
+ port?: number | string;
6
+ views?: Record<string, React.FC<any>>;
7
+ viewsDir?: string;
8
+ viewsContext?: any;
9
+ routes?: RequestHandler | RequestHandler[] | ArcanaRouter | ArcanaRouter[];
10
+ /** API routes can be provided separately from web routes */
11
+ apiRoutes?: RequestHandler | RequestHandler[] | ArcanaRouter | ArcanaRouter[];
12
+ /** Base path under which API routes will be mounted (default: '/api') */
13
+ apiBase?: string;
14
+ staticDir?: string;
15
+ distDir?: string;
16
+ indexFile?: string;
17
+ layout?: React.FC<any>;
18
+ /** Optional function to establish a DB connection. Should return a Promise resolving to the DB client/connection. */
19
+ dbConnect?: () => Promise<TDb> | TDb;
20
+ /** Automatically register SIGINT/SIGTERM handlers to call stop(). Default: true */
21
+ autoHandleSignals?: boolean;
22
+ }
23
+ declare global {
24
+ namespace Express {
25
+ interface Request {
26
+ /**
27
+ * Normalized DB object optionally attached to the request by ArcanaJSServer.
28
+ * It may be either the raw client, or an object like `{ client, db, close }`.
29
+ */
30
+ db?: any;
31
+ }
32
+ }
33
+ }
34
+ export declare class ArcanaJSServer<TDb = any> {
35
+ app: Express;
36
+ private config;
37
+ private serverInstance?;
38
+ private _sigintHandler?;
39
+ private _sigtermHandler?;
40
+ constructor(config: ArcanaJSConfig<TDb>);
41
+ /**
42
+ * Normalize different DB client shapes into a single object exposing:
43
+ * { client?: any, db?: any, close: async () => void }
44
+ */
45
+ private normalizeDb;
46
+ private initialize;
47
+ private loadViewsFromContext;
48
+ private loadViewsFromAlias;
49
+ private discoverViews;
50
+ start(): void;
51
+ /**
52
+ * Stop the HTTP server and close DB connection if present.
53
+ */
54
+ stop(): Promise<void>;
55
+ }