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.
- package/framework/cli/index.d.ts +1 -0
- package/framework/cli/index.js +204 -0
- package/framework/cli/templates.d.ts +6 -0
- package/framework/cli/templates.js +60 -0
- package/framework/cli/webpack.config.d.ts +3 -0
- package/framework/cli/webpack.config.js +310 -0
- package/framework/lib/client/index.d.ts +59 -0
- package/framework/lib/client/index.js +97 -0
- package/framework/lib/config/index.d.ts +46 -0
- package/framework/lib/config/index.js +115 -0
- package/framework/lib/global.d.ts +65 -0
- package/framework/lib/index.d.ts +19 -0
- package/framework/lib/index.js +59 -0
- package/framework/lib/server/ArcanaJSMiddleware.d.ts +24 -0
- package/framework/lib/server/ArcanaJSMiddleware.js +114 -0
- package/framework/lib/server/ArcanaJSServer.d.ts +55 -0
- package/framework/lib/server/ArcanaJSServer.js +441 -0
- package/framework/lib/server/ControllerBinder.d.ts +4 -0
- package/framework/lib/server/ControllerBinder.js +32 -0
- package/framework/lib/server/CsrfMiddleware.d.ts +2 -0
- package/framework/lib/server/CsrfMiddleware.js +34 -0
- package/framework/lib/server/DynamicRouter.d.ts +2 -0
- package/framework/lib/server/DynamicRouter.js +50 -0
- package/framework/lib/server/ResponseHandlerMiddleware.d.ts +27 -0
- package/framework/lib/server/ResponseHandlerMiddleware.js +30 -0
- package/framework/lib/server/Router.d.ts +94 -0
- package/framework/lib/server/Router.js +203 -0
- package/framework/lib/server/default-index.html +12 -0
- package/framework/lib/server.d.ts +33 -0
- package/framework/lib/server.js +69 -0
- package/framework/lib/shared/components/Body.d.ts +6 -0
- package/framework/lib/shared/components/Body.js +8 -0
- package/framework/lib/shared/components/Head.d.ts +4 -0
- package/framework/lib/shared/components/Head.js +125 -0
- package/framework/lib/shared/components/Link.d.ts +7 -0
- package/framework/lib/shared/components/Link.js +27 -0
- package/framework/lib/shared/components/NavLink.d.ts +9 -0
- package/framework/lib/shared/components/NavLink.js +13 -0
- package/framework/lib/shared/components/Page.d.ts +6 -0
- package/framework/lib/shared/components/Page.js +10 -0
- package/framework/lib/shared/context/HeadContext.d.ts +6 -0
- package/framework/lib/shared/context/HeadContext.js +5 -0
- package/framework/lib/shared/context/PageContext.d.ts +1 -0
- package/framework/lib/shared/context/PageContext.js +5 -0
- package/framework/lib/shared/context/RouterContext.d.ts +15 -0
- package/framework/lib/shared/context/RouterContext.js +10 -0
- package/framework/lib/shared/core/ArcanaJSApp.d.ts +14 -0
- package/framework/lib/shared/core/ArcanaJSApp.js +153 -0
- package/framework/lib/shared/hooks/useHead.d.ts +1 -0
- package/framework/lib/shared/hooks/useHead.js +7 -0
- package/framework/lib/shared/hooks/useLocation.d.ts +5 -0
- package/framework/lib/shared/hooks/useLocation.js +13 -0
- package/framework/lib/shared/hooks/usePage.d.ts +1 -0
- package/framework/lib/shared/hooks/usePage.js +7 -0
- package/framework/lib/shared/hooks/useParams.d.ts +1 -0
- package/framework/lib/shared/hooks/useParams.js +13 -0
- package/framework/lib/shared/hooks/useQuery.d.ts +1 -0
- package/framework/lib/shared/hooks/useQuery.js +9 -0
- package/framework/lib/shared/hooks/useRouter.d.ts +1 -0
- package/framework/lib/shared/hooks/useRouter.js +13 -0
- package/framework/lib/shared/utils/createSingletonContext.d.ts +11 -0
- package/framework/lib/shared/utils/createSingletonContext.js +21 -0
- package/framework/lib/shared/views/ErrorPage.d.ts +7 -0
- package/framework/lib/shared/views/ErrorPage.js +12 -0
- package/framework/lib/shared/views/NotFoundPage.d.ts +5 -0
- package/framework/lib/shared/views/NotFoundPage.js +11 -0
- package/framework/lib/types.d.ts +174 -0
- package/framework/lib/types.js +8 -0
- package/framework/templates/arcanajs.config.ts +44 -0
- package/framework/templates/package.json +15 -0
- package/framework/templates/postcss.config.js +6 -0
- package/framework/templates/public/arcanajs.png +0 -0
- package/framework/templates/public/arcanajs.svg +12 -0
- package/framework/templates/public/favicon.ico +0 -0
- package/framework/templates/src/arcanajs.d.ts +8 -0
- package/framework/templates/src/client/globals.css +199 -0
- package/framework/templates/src/client/index.tsx +7 -0
- package/framework/templates/src/db/mongo.ts +10 -0
- package/framework/templates/src/db/mongoose.ts +12 -0
- package/framework/templates/src/db/mysql.ts +15 -0
- package/framework/templates/src/db/postgres.ts +8 -0
- package/framework/templates/src/server/controllers/HomeController.ts +7 -0
- package/framework/templates/src/server/controllers/UsersController.ts +37 -0
- package/framework/templates/src/server/index.ts +35 -0
- package/framework/templates/src/server/routes/api.ts +6 -0
- package/framework/templates/src/server/routes/web.ts +7 -0
- package/framework/templates/src/views/ErrorPage.tsx +136 -0
- package/framework/templates/src/views/HomePage.tsx +344 -0
- package/framework/templates/src/views/NotFoundPage.tsx +108 -0
- package/framework/templates/tsconfig.json +27 -0
- 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
|
+
}
|