baraqex 1.0.35 → 1.0.37
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/browser.js +635 -0
- package/dist/browser.js.map +7 -0
- package/dist/browser.package.json +4 -0
- package/dist/index.js +2024 -33
- package/dist/index.js.map +7 -1
- package/dist/server/index.js +1357 -432
- package/dist/server/index.js.map +7 -1
- package/dist/server/package.json +4 -0
- package/dist/wasm.js +86 -104
- package/dist/wasm.js.map +7 -1
- package/package.json +102 -86
- package/templates/ssr-template/esbuild.config.js +28 -3
- package/templates/ssr-template/package-lock.json +74 -1035
- package/templates/ssr-template/package.json +1 -0
- package/templates/ssr-template/src/App.tsx +11 -11
- package/templates/ssr-template/src/client.tsx +3 -2
- package/dist/frontend/components/WasmLoader.d.ts +0 -1
- package/dist/frontend/components/WasmLoader.d.ts.map +0 -1
- package/dist/frontend/components/WasmLoader.js +0 -2
- package/dist/frontend/components/WasmLoader.js.map +0 -1
- package/dist/frontend/hooks/useWasm.d.ts +0 -1
- package/dist/frontend/hooks/useWasm.d.ts.map +0 -1
- package/dist/frontend/hooks/useWasm.js +0 -2
- package/dist/frontend/hooks/useWasm.js.map +0 -1
- package/dist/frontend/index.d.ts +0 -1
- package/dist/frontend/index.d.ts.map +0 -1
- package/dist/frontend/index.js +0 -2
- package/dist/frontend/index.js.map +0 -1
- package/dist/index.d.ts +0 -12
- package/dist/index.d.ts.map +0 -1
- package/dist/renderComponent.d.ts +0 -9
- package/dist/renderComponent.d.ts.map +0 -1
- package/dist/renderComponent.js +0 -39
- package/dist/renderComponent.js.map +0 -1
- package/dist/server/api-router.d.ts +0 -16
- package/dist/server/api-router.d.ts.map +0 -1
- package/dist/server/api-router.js +0 -121
- package/dist/server/api-router.js.map +0 -1
- package/dist/server/auth.d.ts +0 -33
- package/dist/server/auth.d.ts.map +0 -1
- package/dist/server/auth.js +0 -74
- package/dist/server/auth.js.map +0 -1
- package/dist/server/database.d.ts +0 -25
- package/dist/server/database.d.ts.map +0 -1
- package/dist/server/database.js +0 -109
- package/dist/server/database.js.map +0 -1
- package/dist/server/index.d.ts +0 -117
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/middleware.d.ts +0 -12
- package/dist/server/middleware.d.ts.map +0 -1
- package/dist/server/middleware.js +0 -46
- package/dist/server/middleware.js.map +0 -1
- package/dist/server/server.d.ts +0 -10
- package/dist/server/server.d.ts.map +0 -1
- package/dist/server/server.js +0 -81
- package/dist/server/server.js.map +0 -1
- package/dist/server/templates.d.ts +0 -31
- package/dist/server/templates.d.ts.map +0 -1
- package/dist/server/templates.js +0 -208
- package/dist/server/templates.js.map +0 -1
- package/dist/server/types.d.ts +0 -67
- package/dist/server/types.d.ts.map +0 -1
- package/dist/server/types.js +0 -2
- package/dist/server/types.js.map +0 -1
- package/dist/server/utils.d.ts +0 -71
- package/dist/server/utils.d.ts.map +0 -1
- package/dist/server/utils.js +0 -165
- package/dist/server/utils.js.map +0 -1
- package/dist/server/wasm.d.ts +0 -29
- package/dist/server/wasm.d.ts.map +0 -1
- package/dist/server/wasm.js +0 -208
- package/dist/server/wasm.js.map +0 -1
- package/dist/server-renderer.d.ts +0 -5
- package/dist/server-renderer.d.ts.map +0 -1
- package/dist/server-renderer.js +0 -27
- package/dist/server-renderer.js.map +0 -1
- package/dist/wasm.d.ts +0 -51
- package/dist/wasm.d.ts.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -1,468 +1,1393 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return '';
|
|
28
|
-
}
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined")
|
|
6
|
+
return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
+
});
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/server/index.ts
|
|
15
|
+
import path4 from "path";
|
|
16
|
+
import fs4 from "fs";
|
|
17
|
+
|
|
18
|
+
// node_modules/frontend-hamroun/dist/index.mjs
|
|
19
|
+
var currentRender = 0;
|
|
20
|
+
var stateIndices = /* @__PURE__ */ new Map();
|
|
21
|
+
var isServer = typeof window === "undefined";
|
|
22
|
+
var serverStates = /* @__PURE__ */ new Map();
|
|
23
|
+
function prepareRender() {
|
|
24
|
+
currentRender++;
|
|
25
|
+
stateIndices.set(currentRender, 0);
|
|
26
|
+
return currentRender;
|
|
29
27
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
28
|
+
function finishRender() {
|
|
29
|
+
if (isServer) {
|
|
30
|
+
serverStates.delete(currentRender);
|
|
31
|
+
}
|
|
32
|
+
currentRender = 0;
|
|
33
|
+
}
|
|
34
|
+
function renderToString(vnode) {
|
|
35
|
+
prepareRender();
|
|
36
|
+
const html = renderNodeToString(vnode);
|
|
37
|
+
finishRender();
|
|
38
|
+
return html;
|
|
39
|
+
}
|
|
40
|
+
function renderNodeToString(vnode) {
|
|
41
|
+
var _a, _b, _c;
|
|
42
|
+
if (vnode === null || vnode === void 0)
|
|
43
|
+
return "";
|
|
44
|
+
if (typeof vnode === "boolean")
|
|
45
|
+
return "";
|
|
46
|
+
if (typeof vnode === "number" || typeof vnode === "string")
|
|
47
|
+
return escapeHtml(String(vnode));
|
|
48
|
+
if (typeof vnode.type === "function") {
|
|
49
|
+
const Component2 = vnode.type;
|
|
50
|
+
const renderedNode = Component2(vnode.props || {});
|
|
51
|
+
return renderNodeToString(renderedNode);
|
|
52
|
+
}
|
|
53
|
+
if (typeof vnode.type === "string") {
|
|
54
|
+
const tag = vnode.type;
|
|
55
|
+
let attrs = "";
|
|
56
|
+
let children = "";
|
|
57
|
+
if (vnode.props) {
|
|
58
|
+
for (const [key, value] of Object.entries(vnode.props)) {
|
|
59
|
+
if (key === "children")
|
|
60
|
+
continue;
|
|
61
|
+
if (key.startsWith("on"))
|
|
62
|
+
continue;
|
|
63
|
+
if (key === "className") {
|
|
64
|
+
attrs += ` class="${escapeHtml(value)}"`;
|
|
65
|
+
continue;
|
|
62
66
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
68
|
+
attrs += ` ${key}="${escapeHtml(String(value))}"`;
|
|
69
|
+
} else if (value === true) {
|
|
70
|
+
attrs += ` ${key}`;
|
|
67
71
|
}
|
|
68
|
-
|
|
69
|
-
this.setupApiRoutes();
|
|
70
|
-
// Setup page routes (for SSR)
|
|
71
|
-
this.setupPageRoutes();
|
|
72
|
-
// Add error handlers at the end
|
|
73
|
-
this.app.use(notFoundHandler);
|
|
74
|
-
this.app.use(errorHandler);
|
|
75
|
-
}
|
|
76
|
-
setupApiRoutes() {
|
|
77
|
-
const apiRouter = new ApiRouter(this.config.apiDir, this.auth);
|
|
78
|
-
this.app.use('/api', requestLogger, apiRouter.router);
|
|
79
|
-
// Add API documentation route
|
|
80
|
-
this.app.get('/api-docs', (req, res) => {
|
|
81
|
-
const apiDocs = this.generateApiDocs();
|
|
82
|
-
res.json(apiDocs);
|
|
83
|
-
});
|
|
72
|
+
}
|
|
84
73
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
74
|
+
const childrenArray = ((_a = vnode.props) == null ? void 0 : _a.children) ? Array.isArray(vnode.props.children) ? vnode.props.children : [vnode.props.children] : [];
|
|
75
|
+
for (const child of childrenArray) {
|
|
76
|
+
children += renderNodeToString(child);
|
|
77
|
+
}
|
|
78
|
+
const selfClosing = [
|
|
79
|
+
"area",
|
|
80
|
+
"base",
|
|
81
|
+
"br",
|
|
82
|
+
"col",
|
|
83
|
+
"embed",
|
|
84
|
+
"hr",
|
|
85
|
+
"img",
|
|
86
|
+
"input",
|
|
87
|
+
"link",
|
|
88
|
+
"meta",
|
|
89
|
+
"param",
|
|
90
|
+
"source",
|
|
91
|
+
"track",
|
|
92
|
+
"wbr"
|
|
93
|
+
];
|
|
94
|
+
if (selfClosing.includes(tag)) {
|
|
95
|
+
return `<${tag}${attrs}/>`;
|
|
96
|
+
}
|
|
97
|
+
return `<${tag}${attrs}>${children}</${tag}>`;
|
|
98
|
+
}
|
|
99
|
+
if (vnode.type === Symbol.for("react.fragment")) {
|
|
100
|
+
let fragmentOutput = "";
|
|
101
|
+
const children = Array.isArray((_b = vnode.props) == null ? void 0 : _b.children) ? vnode.props.children : ((_c = vnode.props) == null ? void 0 : _c.children) ? [vnode.props.children] : [];
|
|
102
|
+
for (const child of children) {
|
|
103
|
+
fragmentOutput += renderNodeToString(child);
|
|
104
|
+
}
|
|
105
|
+
return fragmentOutput;
|
|
106
|
+
}
|
|
107
|
+
console.warn("Unknown vnode type:", vnode.type);
|
|
108
|
+
return "";
|
|
109
|
+
}
|
|
110
|
+
function escapeHtml(text) {
|
|
111
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/server/database.ts
|
|
115
|
+
var Database = class {
|
|
116
|
+
constructor(config) {
|
|
117
|
+
this.mongoClient = null;
|
|
118
|
+
this.mongoDb = null;
|
|
119
|
+
this.mysqlPool = null;
|
|
120
|
+
this.pgPool = null;
|
|
121
|
+
this.config = config;
|
|
122
|
+
}
|
|
123
|
+
async connect() {
|
|
124
|
+
switch (this.config.type) {
|
|
125
|
+
case "mongodb":
|
|
126
|
+
await this.connectMongo();
|
|
127
|
+
break;
|
|
128
|
+
case "mysql":
|
|
129
|
+
await this.connectMysql();
|
|
130
|
+
break;
|
|
131
|
+
case "postgres":
|
|
132
|
+
await this.connectPostgres();
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
throw new Error(`Unsupported database type: ${this.config.type}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async connectMongo() {
|
|
139
|
+
try {
|
|
140
|
+
const { MongoClient } = await import("mongodb");
|
|
141
|
+
this.mongoClient = new MongoClient(this.config.url);
|
|
142
|
+
await this.mongoClient.connect();
|
|
143
|
+
this.mongoDb = this.mongoClient.db();
|
|
144
|
+
console.log("Connected to MongoDB");
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (error.code === "MODULE_NOT_FOUND") {
|
|
147
|
+
throw new Error("MongoDB driver not installed. Run: npm install mongodb");
|
|
148
|
+
}
|
|
149
|
+
console.error("MongoDB connection error:", error);
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async connectMysql() {
|
|
154
|
+
try {
|
|
155
|
+
const mysql = await import("mysql2/promise");
|
|
156
|
+
this.mysqlPool = mysql.createPool(this.config.url);
|
|
157
|
+
await this.mysqlPool.getConnection();
|
|
158
|
+
console.log("Connected to MySQL");
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (error.code === "MODULE_NOT_FOUND") {
|
|
161
|
+
throw new Error("MySQL driver not installed. Run: npm install mysql2");
|
|
162
|
+
}
|
|
163
|
+
console.error("MySQL connection error:", error);
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async connectPostgres() {
|
|
168
|
+
try {
|
|
169
|
+
const pg = await import("pg");
|
|
170
|
+
this.pgPool = new pg.Pool({ connectionString: this.config.url });
|
|
171
|
+
const client = await this.pgPool.connect();
|
|
172
|
+
client.release();
|
|
173
|
+
console.log("Connected to PostgreSQL");
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (error.code === "MODULE_NOT_FOUND") {
|
|
176
|
+
throw new Error("PostgreSQL driver not installed. Run: npm install pg @types/pg");
|
|
177
|
+
}
|
|
178
|
+
console.error("PostgreSQL connection error:", error);
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async disconnect() {
|
|
183
|
+
if (this.mongoClient) {
|
|
184
|
+
await this.mongoClient.close();
|
|
185
|
+
this.mongoClient = null;
|
|
186
|
+
this.mongoDb = null;
|
|
187
|
+
}
|
|
188
|
+
if (this.mysqlPool) {
|
|
189
|
+
await this.mysqlPool.end();
|
|
190
|
+
this.mysqlPool = null;
|
|
191
|
+
}
|
|
192
|
+
if (this.pgPool) {
|
|
193
|
+
await this.pgPool.end();
|
|
194
|
+
this.pgPool = null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Database-specific methods
|
|
198
|
+
getMongoDb() {
|
|
199
|
+
return this.mongoDb;
|
|
200
|
+
}
|
|
201
|
+
getMysqlPool() {
|
|
202
|
+
return this.mysqlPool;
|
|
203
|
+
}
|
|
204
|
+
getPgPool() {
|
|
205
|
+
return this.pgPool;
|
|
206
|
+
}
|
|
207
|
+
// Generic query method
|
|
208
|
+
async query(sql, values) {
|
|
209
|
+
switch (this.config.type) {
|
|
210
|
+
case "mongodb":
|
|
211
|
+
throw new Error("For MongoDB, use getMongoDb() instead of query()");
|
|
212
|
+
case "mysql":
|
|
213
|
+
if (!this.mysqlPool)
|
|
214
|
+
throw new Error("MySQL not connected");
|
|
215
|
+
const [rows] = await this.mysqlPool.execute(sql, values || []);
|
|
216
|
+
return rows;
|
|
217
|
+
case "postgres":
|
|
218
|
+
if (!this.pgPool)
|
|
219
|
+
throw new Error("PostgreSQL not connected");
|
|
220
|
+
const result = await this.pgPool.query(sql, values || []);
|
|
221
|
+
return result.rows;
|
|
222
|
+
default:
|
|
223
|
+
throw new Error(`Unsupported database type: ${this.config.type}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// src/server/auth.ts
|
|
229
|
+
var AuthService = class {
|
|
230
|
+
constructor(config) {
|
|
231
|
+
this.config = {
|
|
232
|
+
expiresIn: "24h",
|
|
233
|
+
...config
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
async hashPassword(password) {
|
|
237
|
+
try {
|
|
238
|
+
const bcrypt = await import("bcryptjs");
|
|
239
|
+
return bcrypt.hash(password, 10);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
if (error.code === "MODULE_NOT_FOUND") {
|
|
242
|
+
throw new Error("bcryptjs not installed. Run: npm install bcryptjs @types/bcryptjs");
|
|
243
|
+
}
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async comparePasswords(password, hashedPassword) {
|
|
248
|
+
try {
|
|
249
|
+
const bcrypt = await import("bcryptjs");
|
|
250
|
+
return bcrypt.compare(password, hashedPassword);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (error.code === "MODULE_NOT_FOUND") {
|
|
253
|
+
throw new Error("bcryptjs not installed. Run: npm install bcryptjs @types/bcryptjs");
|
|
254
|
+
}
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
generateToken(user) {
|
|
259
|
+
try {
|
|
260
|
+
const jwt = __require("jsonwebtoken");
|
|
261
|
+
const options = {};
|
|
262
|
+
if (this.config.expiresIn) {
|
|
263
|
+
options.expiresIn = this.config.expiresIn;
|
|
264
|
+
}
|
|
265
|
+
return jwt.sign(
|
|
266
|
+
{ id: user.id, username: user.username, roles: user.roles || [] },
|
|
267
|
+
this.config.secret,
|
|
268
|
+
options
|
|
269
|
+
);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
if (error.code === "MODULE_NOT_FOUND") {
|
|
272
|
+
throw new Error("jsonwebtoken not installed. Run: npm install jsonwebtoken @types/jsonwebtoken");
|
|
273
|
+
}
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
verifyToken(token) {
|
|
278
|
+
try {
|
|
279
|
+
const jwt = __require("jsonwebtoken");
|
|
280
|
+
return jwt.verify(token, this.config.secret);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (error.code === "MODULE_NOT_FOUND") {
|
|
283
|
+
throw new Error("jsonwebtoken not installed. Run: npm install jsonwebtoken @types/jsonwebtoken");
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Express middleware for authentication
|
|
289
|
+
initialize() {
|
|
290
|
+
return (req, res, next) => {
|
|
291
|
+
const token = this.extractToken(req);
|
|
292
|
+
if (token) {
|
|
293
|
+
const decoded = this.verifyToken(token);
|
|
294
|
+
if (decoded) {
|
|
295
|
+
req.user = decoded;
|
|
109
296
|
}
|
|
297
|
+
}
|
|
298
|
+
next();
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
// Express middleware for requiring authentication
|
|
302
|
+
requireAuth() {
|
|
303
|
+
return (req, res, next) => {
|
|
304
|
+
if (!req.user) {
|
|
305
|
+
return res.status(401).json({ message: "Unauthorized" });
|
|
306
|
+
}
|
|
307
|
+
return next();
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
// Express middleware for requiring specific roles
|
|
311
|
+
requireRoles(roles) {
|
|
312
|
+
return (req, res, next) => {
|
|
313
|
+
if (!req.user) {
|
|
314
|
+
return res.status(401).json({ message: "Unauthorized" });
|
|
315
|
+
}
|
|
316
|
+
const userRoles = req.user.roles || [];
|
|
317
|
+
const hasRequiredRole = roles.some((role) => userRoles.includes(role));
|
|
318
|
+
if (!hasRequiredRole) {
|
|
319
|
+
return res.status(403).json({ message: "Forbidden" });
|
|
320
|
+
}
|
|
321
|
+
return next();
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
extractToken(req) {
|
|
325
|
+
if (req.headers.authorization && req.headers.authorization.startsWith("Bearer ")) {
|
|
326
|
+
return req.headers.authorization.substring(7);
|
|
327
|
+
}
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
// src/server/api-router.ts
|
|
333
|
+
import path from "path";
|
|
334
|
+
import fs from "fs";
|
|
335
|
+
import { pathToFileURL } from "url";
|
|
336
|
+
var ApiRouter = class {
|
|
337
|
+
constructor(apiDir, auth = null) {
|
|
338
|
+
this.router = null;
|
|
339
|
+
this.apiDir = path.resolve(process.cwd(), apiDir);
|
|
340
|
+
this.auth = auth;
|
|
341
|
+
this.initializeRouter();
|
|
342
|
+
}
|
|
343
|
+
async initializeRouter() {
|
|
344
|
+
try {
|
|
345
|
+
const express = await import("express");
|
|
346
|
+
this.router = express.default.Router();
|
|
347
|
+
this.setupRoutes();
|
|
348
|
+
} catch (error) {
|
|
349
|
+
if (error.code === "MODULE_NOT_FOUND") {
|
|
350
|
+
throw new Error("Express not installed. Run: npm install express @types/express");
|
|
351
|
+
}
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
setupRoutes() {
|
|
356
|
+
if (!fs.existsSync(this.apiDir)) {
|
|
357
|
+
console.log(`API directory not found: ${this.apiDir}`);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
this.scanDirectory(this.apiDir, "");
|
|
361
|
+
}
|
|
362
|
+
scanDirectory(dirPath, routePrefix) {
|
|
363
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
364
|
+
for (const entry of entries) {
|
|
365
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
366
|
+
if (entry.isDirectory()) {
|
|
367
|
+
this.scanDirectory(fullPath, `${routePrefix}/${entry.name}`);
|
|
368
|
+
} else if (this.isRouteFile(entry.name)) {
|
|
369
|
+
const routePath = this.getRoutePath(entry.name, routePrefix);
|
|
370
|
+
this.registerRoute(fullPath, routePath);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
isRouteFile(filename) {
|
|
375
|
+
return filename.endsWith(".js") || filename.endsWith(".ts") || filename.endsWith(".mjs");
|
|
376
|
+
}
|
|
377
|
+
getRoutePath(filename, routePrefix) {
|
|
378
|
+
const baseName = path.basename(filename, path.extname(filename));
|
|
379
|
+
if (baseName === "index") {
|
|
380
|
+
return routePrefix || "/";
|
|
381
|
+
}
|
|
382
|
+
const paramMatch = baseName.match(/^\[(.+)\]$/);
|
|
383
|
+
if (paramMatch) {
|
|
384
|
+
return `${routePrefix}/:${paramMatch[1]}`;
|
|
110
385
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
386
|
+
return `${routePrefix}/${baseName}`;
|
|
387
|
+
}
|
|
388
|
+
async registerRoute(filePath, routePath) {
|
|
389
|
+
try {
|
|
390
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(filePath);
|
|
391
|
+
const fileUrl = pathToFileURL(absolutePath).href;
|
|
392
|
+
if (process.platform === "win32") {
|
|
393
|
+
const urlWithTimestamp = `${fileUrl}?t=${Date.now()}`;
|
|
394
|
+
const routeModule = await import(urlWithTimestamp);
|
|
395
|
+
if (!this.isValidRouteModule(routeModule)) {
|
|
396
|
+
console.warn(`Invalid route module: ${filePath}`);
|
|
397
|
+
return;
|
|
115
398
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
399
|
+
console.log(`Registering API route: ${routePath}`);
|
|
400
|
+
const middleware = routeModule.middleware || [];
|
|
401
|
+
for (const method of ["get", "post", "put", "delete", "patch", "options"]) {
|
|
402
|
+
const handler = routeModule[method];
|
|
403
|
+
if (typeof handler === "function") {
|
|
404
|
+
const routerMethod = this.router[method];
|
|
405
|
+
routerMethod.call(
|
|
406
|
+
this.router,
|
|
407
|
+
routePath,
|
|
408
|
+
...middleware,
|
|
409
|
+
this.wrapHandler(handler)
|
|
410
|
+
);
|
|
411
|
+
}
|
|
119
412
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const methods = ['get', 'post', 'put', 'delete', 'patch', 'options'];
|
|
126
|
-
return methods.filter(method => new RegExp(`export (async )?function ${method}\\s*\\(`).test(content) ||
|
|
127
|
-
new RegExp(`export const ${method}\\s*=`).test(content));
|
|
413
|
+
} else {
|
|
414
|
+
const routeModule = await import(fileUrl);
|
|
415
|
+
if (!this.isValidRouteModule(routeModule)) {
|
|
416
|
+
console.warn(`Invalid route module: ${filePath}`);
|
|
417
|
+
return;
|
|
128
418
|
}
|
|
129
|
-
|
|
130
|
-
|
|
419
|
+
console.log(`Registering API route: ${routePath}`);
|
|
420
|
+
const middleware = routeModule.middleware || [];
|
|
421
|
+
for (const method of ["get", "post", "put", "delete", "patch", "options"]) {
|
|
422
|
+
const handler = routeModule[method];
|
|
423
|
+
if (typeof handler === "function") {
|
|
424
|
+
const routerMethod = this.router[method];
|
|
425
|
+
routerMethod.call(
|
|
426
|
+
this.router,
|
|
427
|
+
routePath,
|
|
428
|
+
...middleware,
|
|
429
|
+
this.wrapHandler(handler)
|
|
430
|
+
);
|
|
431
|
+
}
|
|
131
432
|
}
|
|
433
|
+
}
|
|
434
|
+
} catch (error) {
|
|
435
|
+
console.error(`Error registering route (${filePath}):`, error);
|
|
132
436
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
437
|
+
}
|
|
438
|
+
isValidRouteModule(module) {
|
|
439
|
+
if (!module)
|
|
440
|
+
return false;
|
|
441
|
+
return ["get", "post", "put", "delete", "patch", "options"].some((method) => typeof module[method] === "function");
|
|
442
|
+
}
|
|
443
|
+
wrapHandler(handler) {
|
|
444
|
+
return async (req, res, next) => {
|
|
445
|
+
try {
|
|
446
|
+
await handler(req, res, next);
|
|
447
|
+
} catch (error) {
|
|
448
|
+
next(error);
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// src/server/middleware.ts
|
|
455
|
+
var requestLogger = (req, res, next) => {
|
|
456
|
+
console.log(`${(/* @__PURE__ */ new Date()).toISOString()} - ${req.method} ${req.url}`);
|
|
457
|
+
next();
|
|
458
|
+
};
|
|
459
|
+
var errorHandler = (err, req, res, next) => {
|
|
460
|
+
console.error(err.stack);
|
|
461
|
+
res.status(500).json({
|
|
462
|
+
error: {
|
|
463
|
+
message: "Internal Server Error",
|
|
464
|
+
...process.env.NODE_ENV !== "production" ? { details: err.message, stack: err.stack } : {}
|
|
150
465
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
path.join(pagesDir, `${normalizedPath}.js`),
|
|
174
|
-
path.join(pagesDir, `${normalizedPath}.ts`),
|
|
175
|
-
path.join(pagesDir, `${normalizedPath}.jsx`),
|
|
176
|
-
path.join(pagesDir, `${normalizedPath}.tsx`),
|
|
177
|
-
path.join(pagesDir, `${normalizedPath}/index.js`),
|
|
178
|
-
path.join(pagesDir, `${normalizedPath}/index.ts`),
|
|
179
|
-
path.join(pagesDir, `${normalizedPath}/index.jsx`),
|
|
180
|
-
path.join(pagesDir, `${normalizedPath}/index.tsx`)
|
|
181
|
-
];
|
|
182
|
-
for (const p of possiblePaths) {
|
|
183
|
-
if (fs.existsSync(p)) {
|
|
184
|
-
pagePath = p;
|
|
185
|
-
break;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
// Handle 404 if page not found
|
|
189
|
-
if (!pagePath) {
|
|
190
|
-
return {
|
|
191
|
-
html: templates.generateErrorPage(404, `Page not found: ${normalizedPath}`),
|
|
192
|
-
statusCode: 404
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
// Import and render the page component
|
|
196
|
-
try {
|
|
197
|
-
const pageModule = await import(pagePath);
|
|
198
|
-
if (!pageModule || !pageModule.default) {
|
|
199
|
-
throw new Error(`No default export found in ${pagePath}`);
|
|
200
|
-
}
|
|
201
|
-
const PageComponent = pageModule.default;
|
|
202
|
-
const initialProps = {
|
|
203
|
-
// Provide any initial props here
|
|
204
|
-
path: normalizedPath,
|
|
205
|
-
query: {}, // Could be parsed from URL
|
|
206
|
-
api: { serverTime: new Date().toISOString() }
|
|
207
|
-
};
|
|
208
|
-
// Render the component to HTML
|
|
209
|
-
const { html, success, error } = await renderComponent(PageComponent, initialProps);
|
|
210
|
-
if (!success) {
|
|
211
|
-
return {
|
|
212
|
-
html: templates.generateErrorPage(500, 'Failed to render page', error instanceof Error ? error : new Error('Unknown error')),
|
|
213
|
-
statusCode: 500
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
// Generate the full HTML document
|
|
217
|
-
let pageTitle = 'My App';
|
|
218
|
-
try {
|
|
219
|
-
// Try to extract title from component if it has a getTitle method
|
|
220
|
-
if (typeof PageComponent.getTitle === 'function') {
|
|
221
|
-
pageTitle = PageComponent.getTitle(initialProps);
|
|
222
|
-
}
|
|
223
|
-
else if (PageComponent.title) {
|
|
224
|
-
pageTitle = PageComponent.title;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
catch (e) {
|
|
228
|
-
// Ignore title errors
|
|
229
|
-
}
|
|
230
|
-
// Parse the component name for hydration
|
|
231
|
-
const componentName = getComponentName(pagePath, pagesDir);
|
|
232
|
-
// Generate full HTML document with our template
|
|
233
|
-
const fullHtml = templates.generateDocument(html, {
|
|
234
|
-
title: pageTitle,
|
|
235
|
-
// Get description from component if available
|
|
236
|
-
description: typeof PageComponent.getDescription === 'function'
|
|
237
|
-
? PageComponent.getDescription(initialProps)
|
|
238
|
-
: (PageComponent.description || ''),
|
|
239
|
-
// Add scripts for client-side hydration
|
|
240
|
-
scripts: ['/client.js'],
|
|
241
|
-
// Add any custom meta tags
|
|
242
|
-
meta: typeof PageComponent.getMeta === 'function'
|
|
243
|
-
? PageComponent.getMeta(initialProps)
|
|
244
|
-
: {},
|
|
245
|
-
// Add custom styles
|
|
246
|
-
styles: ['/styles.css'],
|
|
247
|
-
// Add initial data for client-side hydration
|
|
248
|
-
initialData: initialProps,
|
|
249
|
-
// Add component information for hydration
|
|
250
|
-
componentName: componentName
|
|
251
|
-
});
|
|
252
|
-
return { html: fullHtml, statusCode: 200 };
|
|
253
|
-
}
|
|
254
|
-
catch (error) {
|
|
255
|
-
console.error('Error rendering page:', error);
|
|
256
|
-
return {
|
|
257
|
-
html: templates.generateErrorPage(500, 'Error rendering page', error instanceof Error ? error : new Error('Unknown error')),
|
|
258
|
-
statusCode: 500
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
catch (error) {
|
|
263
|
-
console.error('Error finding page:', error);
|
|
264
|
-
return {
|
|
265
|
-
html: templates.generateErrorPage(500, 'Server error', error instanceof Error ? error : new Error('Unknown error')),
|
|
266
|
-
statusCode: 500
|
|
267
|
-
};
|
|
466
|
+
});
|
|
467
|
+
};
|
|
468
|
+
var notFoundHandler = (req, res) => {
|
|
469
|
+
res.status(404).json({
|
|
470
|
+
error: {
|
|
471
|
+
message: `Not Found - ${req.method} ${req.url}`
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
};
|
|
475
|
+
function rateLimit(options) {
|
|
476
|
+
const requests = /* @__PURE__ */ new Map();
|
|
477
|
+
return (req, res, next) => {
|
|
478
|
+
const ip = req.ip || req.socket.remoteAddress || "unknown";
|
|
479
|
+
const now = Date.now();
|
|
480
|
+
const reqTimes = (requests.get(ip) || []).filter((time) => now - time < options.windowMs);
|
|
481
|
+
reqTimes.push(now);
|
|
482
|
+
requests.set(ip, reqTimes);
|
|
483
|
+
if (reqTimes.length > options.max) {
|
|
484
|
+
return res.status(429).json({
|
|
485
|
+
error: {
|
|
486
|
+
message: "Too Many Requests",
|
|
487
|
+
retryAfter: Math.ceil(options.windowMs / 1e3)
|
|
268
488
|
}
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
return next();
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/server/utils.ts
|
|
496
|
+
var utils_exports = {};
|
|
497
|
+
__export(utils_exports, {
|
|
498
|
+
ensureDirectory: () => ensureDirectory,
|
|
499
|
+
generateToken: () => generateToken,
|
|
500
|
+
getEnvironmentInfo: () => getEnvironmentInfo,
|
|
501
|
+
getPagination: () => getPagination,
|
|
502
|
+
hashString: () => hashString,
|
|
503
|
+
isDirectoryEmpty: () => isDirectoryEmpty,
|
|
504
|
+
readJsonFile: () => readJsonFile,
|
|
505
|
+
safeJsonParse: () => safeJsonParse,
|
|
506
|
+
sendError: () => sendError,
|
|
507
|
+
sendSuccess: () => sendSuccess,
|
|
508
|
+
validateFields: () => validateFields,
|
|
509
|
+
validateFileUpload: () => validateFileUpload,
|
|
510
|
+
writeJsonFile: () => writeJsonFile
|
|
511
|
+
});
|
|
512
|
+
import crypto from "crypto";
|
|
513
|
+
import fs2 from "fs/promises";
|
|
514
|
+
import path2 from "path";
|
|
515
|
+
function safeJsonParse(json, fallback) {
|
|
516
|
+
try {
|
|
517
|
+
return JSON.parse(json);
|
|
518
|
+
} catch (error) {
|
|
519
|
+
return fallback;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function generateToken(length = 32) {
|
|
523
|
+
return crypto.randomBytes(length).toString("hex");
|
|
524
|
+
}
|
|
525
|
+
function hashString(input, salt = "") {
|
|
526
|
+
return crypto.createHash("sha256").update(input + salt).digest("hex");
|
|
527
|
+
}
|
|
528
|
+
function getPagination(req) {
|
|
529
|
+
const page = Math.max(1, parseInt(req.query.page) || 1);
|
|
530
|
+
const rawLimit = parseInt(req.query.limit);
|
|
531
|
+
let limit;
|
|
532
|
+
if (isNaN(rawLimit) || rawLimit <= 0) {
|
|
533
|
+
limit = 20;
|
|
534
|
+
} else {
|
|
535
|
+
limit = Math.max(1, Math.min(100, rawLimit));
|
|
536
|
+
}
|
|
537
|
+
const skip = (page - 1) * limit;
|
|
538
|
+
return { page, limit, skip };
|
|
539
|
+
}
|
|
540
|
+
function sendSuccess(res, data, message = "Success") {
|
|
541
|
+
return res.json({
|
|
542
|
+
success: true,
|
|
543
|
+
message,
|
|
544
|
+
data
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
function sendError(res, message = "An error occurred", statusCode = 400, errors) {
|
|
548
|
+
return res.status(statusCode).json({
|
|
549
|
+
success: false,
|
|
550
|
+
message,
|
|
551
|
+
errors: errors || void 0
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
function validateFields(req, requiredFields) {
|
|
555
|
+
const missing = requiredFields.filter((field) => {
|
|
556
|
+
const value = req.body[field];
|
|
557
|
+
return value === void 0 || value === null || value === "";
|
|
558
|
+
});
|
|
559
|
+
return {
|
|
560
|
+
valid: missing.length === 0,
|
|
561
|
+
missing
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function validateFileUpload(file, options = {}) {
|
|
565
|
+
if (!file) {
|
|
566
|
+
return { valid: false, error: "No file provided" };
|
|
567
|
+
}
|
|
568
|
+
if (options.maxSize && file.size > options.maxSize) {
|
|
569
|
+
return {
|
|
570
|
+
valid: false,
|
|
571
|
+
error: `File too large. Maximum size allowed is ${options.maxSize / 1024 / 1024}MB`
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
if (options.allowedTypes && options.allowedTypes.length > 0) {
|
|
575
|
+
const fileType = file.mimetype;
|
|
576
|
+
if (!options.allowedTypes.includes(fileType)) {
|
|
577
|
+
return {
|
|
578
|
+
valid: false,
|
|
579
|
+
error: `Invalid file type. Allowed types: ${options.allowedTypes.join(", ")}`
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return { valid: true };
|
|
584
|
+
}
|
|
585
|
+
function getEnvironmentInfo() {
|
|
586
|
+
return {
|
|
587
|
+
nodeVersion: process.version,
|
|
588
|
+
platform: process.platform,
|
|
589
|
+
arch: process.arch,
|
|
590
|
+
memory: process.memoryUsage(),
|
|
591
|
+
uptime: process.uptime(),
|
|
592
|
+
env: process.env.NODE_ENV || "development"
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
async function isDirectoryEmpty(dirPath) {
|
|
596
|
+
try {
|
|
597
|
+
const files = await fs2.readdir(dirPath);
|
|
598
|
+
return files.length === 0;
|
|
599
|
+
} catch (error) {
|
|
600
|
+
return true;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
async function ensureDirectory(dirPath) {
|
|
604
|
+
try {
|
|
605
|
+
await fs2.access(dirPath);
|
|
606
|
+
} catch (error) {
|
|
607
|
+
await fs2.mkdir(dirPath, { recursive: true });
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
async function writeJsonFile(filePath, data) {
|
|
611
|
+
await ensureDirectory(path2.dirname(filePath));
|
|
612
|
+
await fs2.writeFile(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
613
|
+
}
|
|
614
|
+
async function readJsonFile(filePath, defaultValue) {
|
|
615
|
+
try {
|
|
616
|
+
const data = await fs2.readFile(filePath, "utf8");
|
|
617
|
+
return safeJsonParse(data, defaultValue);
|
|
618
|
+
} catch (error) {
|
|
619
|
+
return defaultValue;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/server/templates.ts
|
|
624
|
+
var templates_exports = {};
|
|
625
|
+
__export(templates_exports, {
|
|
626
|
+
generateDocument: () => generateDocument,
|
|
627
|
+
generateErrorPage: () => generateErrorPage,
|
|
628
|
+
generateLoadingPage: () => generateLoadingPage
|
|
629
|
+
});
|
|
630
|
+
function generateDocument(content, options = {}) {
|
|
631
|
+
const {
|
|
632
|
+
title = "Frontend Hamroun App",
|
|
633
|
+
description = "",
|
|
634
|
+
lang = "en",
|
|
635
|
+
charset = "UTF-8",
|
|
636
|
+
viewport = "width=device-width, initial-scale=1.0",
|
|
637
|
+
scripts = [],
|
|
638
|
+
styles = [],
|
|
639
|
+
meta = {},
|
|
640
|
+
bodyClasses = "",
|
|
641
|
+
htmlAttrs = {},
|
|
642
|
+
bodyAttrs = {},
|
|
643
|
+
initialData = {},
|
|
644
|
+
componentName = ""
|
|
645
|
+
} = options;
|
|
646
|
+
const htmlAttributes = Object.entries(htmlAttrs).map(([key, value]) => `${key}="${escapeHtml2(value)}"`).join(" ");
|
|
647
|
+
const bodyAttributes = Object.entries(bodyAttrs).map(([key, value]) => `${key}="${escapeHtml2(value)}"`).join(" ");
|
|
648
|
+
const metaTags = [
|
|
649
|
+
`<meta charset="${charset}">`,
|
|
650
|
+
`<meta name="viewport" content="${viewport}">`,
|
|
651
|
+
description ? `<meta name="description" content="${escapeHtml2(description)}">` : ""
|
|
652
|
+
];
|
|
653
|
+
Object.entries(meta).forEach(([name, content2]) => {
|
|
654
|
+
metaTags.push(`<meta name="${escapeHtml2(name)}" content="${escapeHtml2(content2)}">`);
|
|
655
|
+
});
|
|
656
|
+
const scriptTags = scripts.map((src) => {
|
|
657
|
+
if (src.startsWith("<script")) {
|
|
658
|
+
return src;
|
|
269
659
|
}
|
|
270
|
-
|
|
271
|
-
|
|
660
|
+
return `<script src="${escapeHtml2(src)}" ${src.endsWith(".js") ? 'type="module"' : ""} defer></script>`;
|
|
661
|
+
});
|
|
662
|
+
const styleTags = styles.map((href) => {
|
|
663
|
+
if (href.startsWith("<style")) {
|
|
664
|
+
return href;
|
|
272
665
|
}
|
|
273
|
-
|
|
274
|
-
|
|
666
|
+
return `<link rel="stylesheet" href="${escapeHtml2(href)}">`;
|
|
667
|
+
});
|
|
668
|
+
const initialDataScript = Object.keys(initialData).length > 0 ? `<script id="__APP_DATA__" type="application/json">${escapeHtml2(JSON.stringify(initialData))}</script>` : "";
|
|
669
|
+
return `<!DOCTYPE html>
|
|
670
|
+
<html lang="${lang}" ${htmlAttributes}>
|
|
671
|
+
<head>
|
|
672
|
+
${metaTags.filter(Boolean).join("\n ")}
|
|
673
|
+
<title>${escapeHtml2(title)}</title>
|
|
674
|
+
${styleTags.join("\n ")}
|
|
675
|
+
</head>
|
|
676
|
+
<body class="${bodyClasses}" ${bodyAttributes}>
|
|
677
|
+
<div id="app-root" data-ssr-root${componentName ? ` data-component="${escapeHtml2(componentName)}"` : ""}>${content}</div>
|
|
678
|
+
${initialDataScript}
|
|
679
|
+
${scriptTags.join("\n ")}
|
|
680
|
+
</body>
|
|
681
|
+
</html>`;
|
|
682
|
+
}
|
|
683
|
+
function generateErrorPage(statusCode, message, error) {
|
|
684
|
+
const errorMessages = {
|
|
685
|
+
400: "Bad Request",
|
|
686
|
+
401: "Unauthorized",
|
|
687
|
+
403: "Forbidden",
|
|
688
|
+
404: "Not Found",
|
|
689
|
+
500: "Internal Server Error"
|
|
690
|
+
};
|
|
691
|
+
const title = `${statusCode} - ${errorMessages[statusCode] || "Error"}`;
|
|
692
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
693
|
+
const errorDetails = isDev && error ? `
|
|
694
|
+
<div class="error-details">
|
|
695
|
+
<pre>${escapeHtml2(error.stack || error.message)}</pre>
|
|
696
|
+
</div>
|
|
697
|
+
` : "";
|
|
698
|
+
const content = `
|
|
699
|
+
<div class="error-container">
|
|
700
|
+
<h1>${statusCode}</h1>
|
|
701
|
+
<h2>${escapeHtml2(errorMessages[statusCode] || "Error")}</h2>
|
|
702
|
+
<p>${escapeHtml2(message)}</p>
|
|
703
|
+
${errorDetails}
|
|
704
|
+
<a href="/" class="home-link">Back to Home</a>
|
|
705
|
+
</div>
|
|
706
|
+
`;
|
|
707
|
+
const styles = [`<style>
|
|
708
|
+
body {
|
|
709
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
710
|
+
display: flex;
|
|
711
|
+
justify-content: center;
|
|
712
|
+
align-items: center;
|
|
713
|
+
min-height: 100vh;
|
|
714
|
+
margin: 0;
|
|
715
|
+
background-color: #f7f7f7;
|
|
716
|
+
color: #333;
|
|
275
717
|
}
|
|
276
|
-
|
|
277
|
-
|
|
718
|
+
.error-container {
|
|
719
|
+
text-align: center;
|
|
720
|
+
padding: 2rem;
|
|
721
|
+
border-radius: 8px;
|
|
722
|
+
background-color: white;
|
|
723
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
724
|
+
max-width: 600px;
|
|
725
|
+
width: 100%;
|
|
278
726
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
resolve();
|
|
284
|
-
});
|
|
285
|
-
});
|
|
727
|
+
h1 {
|
|
728
|
+
font-size: 6rem;
|
|
729
|
+
margin: 0;
|
|
730
|
+
color: #e74c3c;
|
|
286
731
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
732
|
+
h2 {
|
|
733
|
+
margin-top: 0;
|
|
734
|
+
margin-bottom: 1rem;
|
|
735
|
+
color: #333;
|
|
736
|
+
}
|
|
737
|
+
.error-details {
|
|
738
|
+
text-align: left;
|
|
739
|
+
margin: 2rem 0;
|
|
740
|
+
padding: 1rem;
|
|
741
|
+
background-color: #f8f8f8;
|
|
742
|
+
border-radius: 4px;
|
|
743
|
+
overflow: auto;
|
|
744
|
+
}
|
|
745
|
+
pre {
|
|
746
|
+
margin: 0;
|
|
747
|
+
font-family: monospace;
|
|
748
|
+
font-size: 0.85rem;
|
|
749
|
+
white-space: pre-wrap;
|
|
750
|
+
}
|
|
751
|
+
.home-link {
|
|
752
|
+
display: inline-block;
|
|
753
|
+
margin-top: 1.5rem;
|
|
754
|
+
padding: 0.75rem 1.5rem;
|
|
755
|
+
background-color: #3498db;
|
|
756
|
+
color: white;
|
|
757
|
+
text-decoration: none;
|
|
758
|
+
border-radius: 4px;
|
|
759
|
+
transition: background-color 0.2s;
|
|
299
760
|
}
|
|
300
|
-
|
|
301
|
-
|
|
761
|
+
.home-link:hover {
|
|
762
|
+
background-color: #2980b9;
|
|
302
763
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
764
|
+
</style>`];
|
|
765
|
+
return generateDocument(content, {
|
|
766
|
+
title,
|
|
767
|
+
description: `Error ${statusCode}: ${errorMessages[statusCode] || "Error"}`,
|
|
768
|
+
bodyClasses: "error-page",
|
|
769
|
+
styles
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
function generateLoadingPage(message = "Loading...") {
|
|
773
|
+
const content = `
|
|
774
|
+
<div class="loading-container">
|
|
775
|
+
<div class="spinner"></div>
|
|
776
|
+
<p>${escapeHtml2(message)}</p>
|
|
777
|
+
</div>
|
|
778
|
+
`;
|
|
779
|
+
const styles = [`<style>
|
|
780
|
+
body {
|
|
781
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
782
|
+
display: flex;
|
|
783
|
+
justify-content: center;
|
|
784
|
+
align-items: center;
|
|
785
|
+
min-height: 100vh;
|
|
786
|
+
margin: 0;
|
|
787
|
+
background-color: #f7f7f7;
|
|
788
|
+
color: #333;
|
|
789
|
+
}
|
|
790
|
+
.loading-container {
|
|
791
|
+
text-align: center;
|
|
792
|
+
}
|
|
793
|
+
.spinner {
|
|
794
|
+
width: 50px;
|
|
795
|
+
height: 50px;
|
|
796
|
+
border: 5px solid rgba(0, 0, 0, 0.1);
|
|
797
|
+
border-radius: 50%;
|
|
798
|
+
border-top-color: #3498db;
|
|
799
|
+
animation: spin 1s ease-in-out infinite;
|
|
800
|
+
margin: 0 auto 20px;
|
|
801
|
+
}
|
|
802
|
+
@keyframes spin {
|
|
803
|
+
to { transform: rotate(360deg); }
|
|
804
|
+
}
|
|
805
|
+
</style>`];
|
|
806
|
+
return generateDocument(content, {
|
|
807
|
+
title: "Loading",
|
|
808
|
+
bodyClasses: "loading-page",
|
|
809
|
+
styles
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
function escapeHtml2(text) {
|
|
813
|
+
return String(text).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// src/server/wasm.ts
|
|
817
|
+
import fs3 from "fs/promises";
|
|
818
|
+
import path3 from "path";
|
|
819
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
820
|
+
var initialized = false;
|
|
821
|
+
async function initNodeWasm() {
|
|
822
|
+
if (initialized)
|
|
823
|
+
return;
|
|
824
|
+
try {
|
|
825
|
+
globalThis.TextEncoder = globalThis.TextEncoder || TextEncoder;
|
|
826
|
+
globalThis.TextDecoder = globalThis.TextDecoder || TextDecoder;
|
|
827
|
+
initialized = true;
|
|
828
|
+
} catch (error) {
|
|
829
|
+
console.error("[WASM] Failed to initialize Node.js WASM environment:", error);
|
|
830
|
+
throw error;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
async function loadGoRuntime(goWasmPath) {
|
|
834
|
+
if (typeof global.Go === "function") {
|
|
835
|
+
return global.Go;
|
|
836
|
+
}
|
|
837
|
+
const possiblePaths = [
|
|
838
|
+
goWasmPath,
|
|
839
|
+
"./wasm_exec.cjs",
|
|
840
|
+
"./wasm_exec.js",
|
|
841
|
+
"./public/wasm_exec.cjs",
|
|
842
|
+
"./public/wasm_exec.js",
|
|
843
|
+
"./src/wasm_exec.cjs",
|
|
844
|
+
"./src/wasm_exec.js",
|
|
845
|
+
path3.join(process.cwd(), "wasm_exec.cjs"),
|
|
846
|
+
path3.join(process.cwd(), "wasm_exec.js"),
|
|
847
|
+
path3.join(process.cwd(), "public", "wasm_exec.cjs"),
|
|
848
|
+
path3.join(process.cwd(), "public", "wasm_exec.js"),
|
|
849
|
+
path3.join(process.cwd(), "src", "wasm_exec.cjs"),
|
|
850
|
+
path3.join(process.cwd(), "src", "wasm_exec.js")
|
|
851
|
+
].filter(Boolean);
|
|
852
|
+
for (const wasmExecPath of possiblePaths) {
|
|
853
|
+
try {
|
|
854
|
+
const resolvedPath = path3.resolve(wasmExecPath);
|
|
855
|
+
try {
|
|
856
|
+
const stats = await fs3.stat(resolvedPath);
|
|
857
|
+
if (!stats.isFile()) {
|
|
858
|
+
continue;
|
|
306
859
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
860
|
+
} catch (statError) {
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
const wasmExecUrl = pathToFileURL2(resolvedPath).href;
|
|
864
|
+
if (global.Go) {
|
|
865
|
+
delete global.Go;
|
|
866
|
+
}
|
|
867
|
+
await import(wasmExecUrl);
|
|
868
|
+
if (typeof global.Go === "function") {
|
|
869
|
+
console.log(`[WASM] Loaded Go runtime from: ${resolvedPath}`);
|
|
870
|
+
return global.Go;
|
|
871
|
+
} else {
|
|
872
|
+
console.warn(`[WASM] File found but Go class not exported: ${resolvedPath}`);
|
|
873
|
+
}
|
|
874
|
+
} catch (error) {
|
|
875
|
+
console.warn(`[WASM] Error loading from ${wasmExecPath}:`, error.message);
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
throw new Error(
|
|
880
|
+
`[WASM] Could not find wasm_exec.js. Please ensure you have the Go WASM runtime file available.
|
|
881
|
+
You can get it from: https://github.com/golang/go/blob/master/misc/wasm/wasm_exec.js
|
|
882
|
+
Searched paths: ${possiblePaths.join(", ")}`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
async function loadGoWasmFromFile(wasmFilePath, options = {}) {
|
|
886
|
+
try {
|
|
887
|
+
await initNodeWasm();
|
|
888
|
+
if (options.debug) {
|
|
889
|
+
console.log("[WASM] Loading WASM from file:", wasmFilePath);
|
|
890
|
+
}
|
|
891
|
+
const GoRuntime = await loadGoRuntime(options.goWasmPath);
|
|
892
|
+
const go = new GoRuntime();
|
|
893
|
+
const wasmBuffer = await fs3.readFile(wasmFilePath);
|
|
894
|
+
const wasmModule = await WebAssembly.compile(wasmBuffer);
|
|
895
|
+
const baseImports = go.importObject;
|
|
896
|
+
const finalImportObject = {
|
|
897
|
+
...baseImports,
|
|
898
|
+
...options.importObject
|
|
899
|
+
};
|
|
900
|
+
if (options.debug) {
|
|
901
|
+
console.log("[WASM] Available import modules:", Object.keys(finalImportObject));
|
|
902
|
+
for (const [moduleName, moduleExports] of Object.entries(finalImportObject)) {
|
|
903
|
+
if (typeof moduleExports === "object" && moduleExports !== null) {
|
|
904
|
+
const exportNames = Object.keys(moduleExports);
|
|
905
|
+
console.log(`[WASM] ${moduleName} import functions:`, exportNames);
|
|
336
906
|
}
|
|
907
|
+
}
|
|
337
908
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
909
|
+
const instance = await WebAssembly.instantiate(wasmModule, finalImportObject);
|
|
910
|
+
if (options.debug) {
|
|
911
|
+
console.log("[WASM] WASM instance created, starting Go runtime...");
|
|
912
|
+
}
|
|
913
|
+
const runPromise = go.run(instance);
|
|
914
|
+
if (options.debug) {
|
|
915
|
+
console.log("[WASM] Go runtime started, waiting for initialization...");
|
|
916
|
+
}
|
|
917
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
918
|
+
if (options.debug) {
|
|
919
|
+
console.log("[WASM] Scanning global object for Go functions...");
|
|
920
|
+
}
|
|
921
|
+
const functions = {};
|
|
922
|
+
const globalObj = global;
|
|
923
|
+
const allKeys = Object.getOwnPropertyNames(globalObj);
|
|
924
|
+
const goKeys = allKeys.filter((key) => key.startsWith("go"));
|
|
925
|
+
if (options.debug) {
|
|
926
|
+
console.log('[WASM] All keys starting with "go":', goKeys);
|
|
927
|
+
}
|
|
928
|
+
for (const key of goKeys) {
|
|
929
|
+
if (typeof globalObj[key] === "function") {
|
|
930
|
+
functions[key] = globalObj[key];
|
|
931
|
+
if (options.debug) {
|
|
932
|
+
console.log(`[WASM] Found function: ${key}`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
const wasmInstance = {
|
|
937
|
+
instance,
|
|
938
|
+
module: wasmModule,
|
|
939
|
+
exports: instance.exports,
|
|
940
|
+
functions
|
|
941
|
+
};
|
|
942
|
+
if (options.onLoad) {
|
|
943
|
+
options.onLoad(wasmInstance);
|
|
944
|
+
}
|
|
945
|
+
if (options.debug) {
|
|
946
|
+
console.log(`[WASM] Module loaded successfully with ${Object.keys(functions).length} functions`);
|
|
947
|
+
console.log("[WASM] Final function list:", Object.keys(functions));
|
|
948
|
+
}
|
|
949
|
+
runPromise.catch((error) => {
|
|
950
|
+
if (options.debug) {
|
|
951
|
+
console.log("[WASM] Go program finished with error:", error);
|
|
952
|
+
}
|
|
380
953
|
});
|
|
954
|
+
return wasmInstance;
|
|
955
|
+
} catch (error) {
|
|
956
|
+
console.error("[WASM] Failed to load Go WASM module:", error);
|
|
957
|
+
throw error;
|
|
958
|
+
}
|
|
381
959
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
960
|
+
|
|
961
|
+
// src/server/index.ts
|
|
962
|
+
function getComponentName(filePath, pagesDir) {
|
|
963
|
+
try {
|
|
964
|
+
const relativePath = path4.relative(pagesDir, filePath);
|
|
965
|
+
const withoutExt = relativePath.replace(/\.[^/.]+$/, "");
|
|
966
|
+
const cleanPath = withoutExt.replace(/\/index$/, "");
|
|
967
|
+
return `/pages/${cleanPath}`;
|
|
968
|
+
} catch (e) {
|
|
969
|
+
return "";
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
var Server = class {
|
|
973
|
+
constructor(config = {}) {
|
|
974
|
+
this.server = null;
|
|
975
|
+
this.db = null;
|
|
976
|
+
this.auth = null;
|
|
977
|
+
this.config = {
|
|
978
|
+
port: 3e3,
|
|
979
|
+
apiDir: "./api",
|
|
980
|
+
pagesDir: "./pages",
|
|
981
|
+
staticDir: "./public",
|
|
982
|
+
enableCors: true,
|
|
983
|
+
...config
|
|
984
|
+
};
|
|
985
|
+
this.initializeApp();
|
|
986
|
+
}
|
|
987
|
+
async initializeApp() {
|
|
988
|
+
try {
|
|
989
|
+
const express = await import("express");
|
|
990
|
+
this.app = express.default();
|
|
991
|
+
this.app.use(express.default.json());
|
|
992
|
+
this.app.use(express.default.urlencoded({ extended: true }));
|
|
993
|
+
if (this.config.enableCors) {
|
|
994
|
+
try {
|
|
995
|
+
const cors = await import("cors");
|
|
996
|
+
this.app.use(cors.default(this.config.corsOptions));
|
|
997
|
+
} catch (error) {
|
|
998
|
+
console.warn("CORS not available - install with: npm install cors @types/cors");
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (this.config.db) {
|
|
1002
|
+
this.db = new Database(this.config.db);
|
|
1003
|
+
}
|
|
1004
|
+
if (this.config.auth) {
|
|
1005
|
+
this.auth = new AuthService({
|
|
1006
|
+
secret: this.config.auth.secret,
|
|
1007
|
+
expiresIn: this.config.auth.expiresIn
|
|
1008
|
+
});
|
|
1009
|
+
this.app.use(this.auth.initialize());
|
|
1010
|
+
}
|
|
1011
|
+
const staticPath = path4.resolve(process.cwd(), this.config.staticDir);
|
|
1012
|
+
if (fs4.existsSync(staticPath)) {
|
|
1013
|
+
this.app.use(express.default.static(staticPath));
|
|
1014
|
+
}
|
|
1015
|
+
this.setupApiRoutes();
|
|
1016
|
+
this.setupPageRoutes();
|
|
1017
|
+
this.app.use(notFoundHandler);
|
|
1018
|
+
this.app.use(errorHandler);
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
if (error.code === "MODULE_NOT_FOUND" && error.message.includes("express")) {
|
|
1021
|
+
throw new Error("Express not installed. Run: npm install express @types/express");
|
|
1022
|
+
}
|
|
1023
|
+
throw error;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
setupApiRoutes() {
|
|
1027
|
+
const apiRouter = new ApiRouter(this.config.apiDir, this.auth);
|
|
1028
|
+
this.app.use("/api", requestLogger, apiRouter.router);
|
|
1029
|
+
this.app.get("/api-docs", (req, res) => {
|
|
1030
|
+
const apiDocs = this.generateApiDocs();
|
|
1031
|
+
res.json(apiDocs);
|
|
390
1032
|
});
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
1033
|
+
}
|
|
1034
|
+
generateApiDocs() {
|
|
1035
|
+
const apiDir = path4.resolve(process.cwd(), this.config.apiDir);
|
|
1036
|
+
const docs = { endpoints: [] };
|
|
1037
|
+
if (fs4.existsSync(apiDir)) {
|
|
1038
|
+
this.scanApiDirectory(apiDir, "", docs.endpoints);
|
|
1039
|
+
}
|
|
1040
|
+
return docs;
|
|
1041
|
+
}
|
|
1042
|
+
scanApiDirectory(dirPath, routePrefix, endpoints) {
|
|
1043
|
+
const entries = fs4.readdirSync(dirPath, { withFileTypes: true });
|
|
1044
|
+
for (const entry of entries) {
|
|
1045
|
+
const fullPath = path4.join(dirPath, entry.name);
|
|
1046
|
+
if (entry.isDirectory()) {
|
|
1047
|
+
this.scanApiDirectory(fullPath, `${routePrefix}/${entry.name}`, endpoints);
|
|
1048
|
+
} else if (entry.name.endsWith(".js") || entry.name.endsWith(".ts")) {
|
|
1049
|
+
const routePath = this.getRoutePath(entry.name, routePrefix);
|
|
1050
|
+
endpoints.push({
|
|
1051
|
+
path: routePath,
|
|
1052
|
+
file: path4.relative(process.cwd(), fullPath),
|
|
1053
|
+
methods: this.detectApiMethods(fullPath)
|
|
409
1054
|
});
|
|
1055
|
+
}
|
|
410
1056
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
1057
|
+
}
|
|
1058
|
+
getRoutePath(filename, routePrefix) {
|
|
1059
|
+
const baseName = path4.basename(filename, path4.extname(filename));
|
|
1060
|
+
if (baseName === "index") {
|
|
1061
|
+
return routePrefix || "/";
|
|
1062
|
+
}
|
|
1063
|
+
const paramMatch = baseName.match(/^\[(.+)\]$/);
|
|
1064
|
+
if (paramMatch) {
|
|
1065
|
+
return `${routePrefix}/:${paramMatch[1]}`;
|
|
1066
|
+
}
|
|
1067
|
+
return `${routePrefix}/${baseName}`;
|
|
1068
|
+
}
|
|
1069
|
+
detectApiMethods(filePath) {
|
|
1070
|
+
try {
|
|
1071
|
+
const content = fs4.readFileSync(filePath, "utf8");
|
|
1072
|
+
const methods = ["get", "post", "put", "delete", "patch", "options"];
|
|
1073
|
+
return methods.filter(
|
|
1074
|
+
(method) => new RegExp(`export (async )?function ${method}\\s*\\(`).test(content) || new RegExp(`export const ${method}\\s*=`).test(content)
|
|
1075
|
+
);
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
return [];
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
setupPageRoutes() {
|
|
1081
|
+
const pagesPath = path4.resolve(process.cwd(), this.config.pagesDir);
|
|
1082
|
+
if (!fs4.existsSync(pagesPath)) {
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
this.app.get("*", async (req, res) => {
|
|
1086
|
+
try {
|
|
1087
|
+
const { html, statusCode } = await this.renderPage(req.path);
|
|
1088
|
+
res.status(statusCode || 200).send(html);
|
|
1089
|
+
} catch (error) {
|
|
1090
|
+
console.error("Error rendering page:", error);
|
|
1091
|
+
const errorHtml = generateErrorPage(500, "Server Error");
|
|
1092
|
+
res.status(500).send(errorHtml);
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
async renderPage(routePath) {
|
|
415
1097
|
try {
|
|
416
|
-
|
|
417
|
-
|
|
1098
|
+
const pagesDir = this.config.pagesDir ? path4.resolve(process.cwd(), this.config.pagesDir) : "";
|
|
1099
|
+
if (!pagesDir || !fs4.existsSync(pagesDir)) {
|
|
418
1100
|
return {
|
|
419
|
-
|
|
420
|
-
|
|
1101
|
+
html: generateErrorPage(404, "Pages directory not found"),
|
|
1102
|
+
statusCode: 404
|
|
421
1103
|
};
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
1104
|
+
}
|
|
1105
|
+
let normalizedPath = routePath;
|
|
1106
|
+
if (!normalizedPath.startsWith("/")) {
|
|
1107
|
+
normalizedPath = "/" + normalizedPath;
|
|
1108
|
+
}
|
|
1109
|
+
if (normalizedPath === "/") {
|
|
1110
|
+
normalizedPath = "/index";
|
|
1111
|
+
}
|
|
1112
|
+
let pagePath = "";
|
|
1113
|
+
const possiblePaths = [
|
|
1114
|
+
path4.join(pagesDir, `${normalizedPath}.js`),
|
|
1115
|
+
path4.join(pagesDir, `${normalizedPath}.ts`),
|
|
1116
|
+
path4.join(pagesDir, `${normalizedPath}.jsx`),
|
|
1117
|
+
path4.join(pagesDir, `${normalizedPath}.tsx`),
|
|
1118
|
+
path4.join(pagesDir, `${normalizedPath}/index.js`),
|
|
1119
|
+
path4.join(pagesDir, `${normalizedPath}/index.ts`),
|
|
1120
|
+
path4.join(pagesDir, `${normalizedPath}/index.jsx`),
|
|
1121
|
+
path4.join(pagesDir, `${normalizedPath}/index.tsx`)
|
|
1122
|
+
];
|
|
1123
|
+
for (const p of possiblePaths) {
|
|
1124
|
+
if (fs4.existsSync(p)) {
|
|
1125
|
+
pagePath = p;
|
|
1126
|
+
break;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
if (!pagePath) {
|
|
425
1130
|
return {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
error
|
|
1131
|
+
html: generateErrorPage(404, `Page not found: ${normalizedPath}`),
|
|
1132
|
+
statusCode: 404
|
|
429
1133
|
};
|
|
1134
|
+
}
|
|
1135
|
+
try {
|
|
1136
|
+
const pageModule = await import(pagePath);
|
|
1137
|
+
if (!pageModule || !pageModule.default) {
|
|
1138
|
+
throw new Error(`No default export found in ${pagePath}`);
|
|
1139
|
+
}
|
|
1140
|
+
const PageComponent = pageModule.default;
|
|
1141
|
+
const initialProps = {
|
|
1142
|
+
// Provide any initial props here
|
|
1143
|
+
path: normalizedPath,
|
|
1144
|
+
query: {},
|
|
1145
|
+
// Could be parsed from URL
|
|
1146
|
+
api: { serverTime: (/* @__PURE__ */ new Date()).toISOString() }
|
|
1147
|
+
};
|
|
1148
|
+
const { html, success, error } = await renderComponent(PageComponent, initialProps);
|
|
1149
|
+
if (!success) {
|
|
1150
|
+
return {
|
|
1151
|
+
html: generateErrorPage(500, "Failed to render page", error instanceof Error ? error : new Error("Unknown error")),
|
|
1152
|
+
statusCode: 500
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
let pageTitle = "My App";
|
|
1156
|
+
try {
|
|
1157
|
+
if (typeof PageComponent.getTitle === "function") {
|
|
1158
|
+
pageTitle = PageComponent.getTitle(initialProps);
|
|
1159
|
+
} else if (PageComponent.title) {
|
|
1160
|
+
pageTitle = PageComponent.title;
|
|
1161
|
+
}
|
|
1162
|
+
} catch (e) {
|
|
1163
|
+
}
|
|
1164
|
+
const componentName = getComponentName(pagePath, pagesDir);
|
|
1165
|
+
const fullHtml = generateDocument(html, {
|
|
1166
|
+
title: pageTitle,
|
|
1167
|
+
// Get description from component if available
|
|
1168
|
+
description: typeof PageComponent.getDescription === "function" ? PageComponent.getDescription(initialProps) : PageComponent.description || "",
|
|
1169
|
+
// Add scripts for client-side hydration
|
|
1170
|
+
scripts: ["/client.js"],
|
|
1171
|
+
// Add any custom meta tags
|
|
1172
|
+
meta: typeof PageComponent.getMeta === "function" ? PageComponent.getMeta(initialProps) : {},
|
|
1173
|
+
// Add custom styles
|
|
1174
|
+
styles: ["/styles.css"],
|
|
1175
|
+
// Add initial data for client-side hydration
|
|
1176
|
+
initialData: initialProps,
|
|
1177
|
+
// Add component information for hydration
|
|
1178
|
+
componentName
|
|
1179
|
+
});
|
|
1180
|
+
return { html: fullHtml, statusCode: 200 };
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
console.error("Error rendering page:", error);
|
|
1183
|
+
return {
|
|
1184
|
+
html: generateErrorPage(500, "Error rendering page", error instanceof Error ? error : new Error("Unknown error")),
|
|
1185
|
+
statusCode: 500
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
} catch (error) {
|
|
1189
|
+
console.error("Error finding page:", error);
|
|
1190
|
+
return {
|
|
1191
|
+
html: generateErrorPage(500, "Server error", error instanceof Error ? error : new Error("Unknown error")),
|
|
1192
|
+
statusCode: 500
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
getExpressApp() {
|
|
1197
|
+
return this.app;
|
|
1198
|
+
}
|
|
1199
|
+
getDatabase() {
|
|
1200
|
+
return this.db;
|
|
1201
|
+
}
|
|
1202
|
+
getAuth() {
|
|
1203
|
+
return this.auth;
|
|
1204
|
+
}
|
|
1205
|
+
start() {
|
|
1206
|
+
return new Promise((resolve) => {
|
|
1207
|
+
this.server = this.app.listen(this.config.port, () => {
|
|
1208
|
+
console.log(`Server running at http://localhost:${this.config.port}`);
|
|
1209
|
+
resolve();
|
|
1210
|
+
});
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
stop() {
|
|
1214
|
+
return new Promise((resolve, reject) => {
|
|
1215
|
+
if (!this.server) {
|
|
1216
|
+
return resolve();
|
|
1217
|
+
}
|
|
1218
|
+
this.server.close((err) => {
|
|
1219
|
+
if (err)
|
|
1220
|
+
return reject(err);
|
|
1221
|
+
this.server = null;
|
|
1222
|
+
resolve();
|
|
1223
|
+
});
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
restart() {
|
|
1227
|
+
return this.stop().then(() => this.start());
|
|
1228
|
+
}
|
|
1229
|
+
enableSSR(options = {}) {
|
|
1230
|
+
if (!fs4.existsSync(path4.resolve(process.cwd(), this.config.pagesDir))) {
|
|
1231
|
+
fs4.mkdirSync(path4.resolve(process.cwd(), this.config.pagesDir), { recursive: true });
|
|
1232
|
+
}
|
|
1233
|
+
if (options.hydratable) {
|
|
1234
|
+
const staticDir = path4.resolve(process.cwd(), this.config.staticDir);
|
|
1235
|
+
if (!fs4.existsSync(staticDir)) {
|
|
1236
|
+
fs4.mkdirSync(staticDir, { recursive: true });
|
|
1237
|
+
}
|
|
1238
|
+
const clientJsPath = path4.join(staticDir, "client.js");
|
|
1239
|
+
if (!fs4.existsSync(clientJsPath)) {
|
|
1240
|
+
const clientJsContent = `
|
|
1241
|
+
// Auto-generated hydration script
|
|
1242
|
+
import { hydrate } from 'frontend-hamroun';
|
|
1243
|
+
|
|
1244
|
+
// Find SSR content and hydrate it
|
|
1245
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
1246
|
+
const ssrRoots = document.querySelectorAll('[data-ssr-root]');
|
|
1247
|
+
ssrRoots.forEach(root => {
|
|
1248
|
+
const componentPath = root.getAttribute('data-component');
|
|
1249
|
+
if (componentPath) {
|
|
1250
|
+
import(componentPath).then(module => {
|
|
1251
|
+
hydrate(module.default, root);
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
});
|
|
1256
|
+
`;
|
|
1257
|
+
fs4.writeFileSync(clientJsPath, clientJsContent);
|
|
1258
|
+
}
|
|
430
1259
|
}
|
|
1260
|
+
}
|
|
1261
|
+
registerPlugin(plugin) {
|
|
1262
|
+
plugin(this);
|
|
1263
|
+
}
|
|
1264
|
+
addShutdownHandler() {
|
|
1265
|
+
const gracefulShutdown = async () => {
|
|
1266
|
+
console.log("Received shutdown signal, closing server...");
|
|
1267
|
+
try {
|
|
1268
|
+
await this.stop();
|
|
1269
|
+
if (this.db) {
|
|
1270
|
+
await this.db.disconnect();
|
|
1271
|
+
}
|
|
1272
|
+
console.log("Server shut down gracefully");
|
|
1273
|
+
process.exit(0);
|
|
1274
|
+
} catch (error) {
|
|
1275
|
+
console.error("Error during shutdown:", error);
|
|
1276
|
+
process.exit(1);
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
process.on("SIGTERM", gracefulShutdown);
|
|
1280
|
+
process.on("SIGINT", gracefulShutdown);
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
function createServer(config = {}) {
|
|
1284
|
+
return new Server(config);
|
|
1285
|
+
}
|
|
1286
|
+
function createDevServer(options = {}) {
|
|
1287
|
+
return new Server({
|
|
1288
|
+
port: options.port || 3e3,
|
|
1289
|
+
enableCors: options.enableCors !== false,
|
|
1290
|
+
apiDir: "./src/api",
|
|
1291
|
+
pagesDir: "./src/pages",
|
|
1292
|
+
staticDir: "./public"
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
function createProductionServer(config) {
|
|
1296
|
+
const server = new Server({
|
|
1297
|
+
...config,
|
|
1298
|
+
// Fix the PORT parsing
|
|
1299
|
+
port: config.port || (process.env.PORT ? parseInt(process.env.PORT) : 8080)
|
|
1300
|
+
});
|
|
1301
|
+
server.addShutdownHandler();
|
|
1302
|
+
return server;
|
|
1303
|
+
}
|
|
1304
|
+
function getRequestIp(req) {
|
|
1305
|
+
return req.ip || req.headers["x-forwarded-for"]?.split(",")[0].trim() || req.socket.remoteAddress || "";
|
|
1306
|
+
}
|
|
1307
|
+
function parseCookies(req) {
|
|
1308
|
+
const cookies = {};
|
|
1309
|
+
const cookieHeader = req.headers.cookie;
|
|
1310
|
+
if (cookieHeader) {
|
|
1311
|
+
cookieHeader.split(";").forEach((cookie) => {
|
|
1312
|
+
const [name, value] = cookie.split("=").map((c) => c.trim());
|
|
1313
|
+
cookies[name] = value;
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
return cookies;
|
|
1317
|
+
}
|
|
1318
|
+
var renderComponent = async (Component, props = {}) => {
|
|
1319
|
+
try {
|
|
1320
|
+
const html = renderToString(Component(props));
|
|
1321
|
+
return {
|
|
1322
|
+
html,
|
|
1323
|
+
success: true
|
|
1324
|
+
};
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
console.error("Error rendering component:", error);
|
|
1327
|
+
return {
|
|
1328
|
+
html: `<div class="error">Error rendering component</div>`,
|
|
1329
|
+
success: false,
|
|
1330
|
+
error
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
var server_default = {
|
|
1335
|
+
Server,
|
|
1336
|
+
createServer,
|
|
1337
|
+
createDevServer,
|
|
1338
|
+
createProductionServer,
|
|
1339
|
+
rateLimit,
|
|
1340
|
+
requestLogger,
|
|
1341
|
+
errorHandler,
|
|
1342
|
+
notFoundHandler,
|
|
1343
|
+
loadGoWasmFromFile,
|
|
1344
|
+
// Template utilities
|
|
1345
|
+
templates: templates_exports,
|
|
1346
|
+
// Server utilities
|
|
1347
|
+
utils: utils_exports,
|
|
1348
|
+
// Additional exports
|
|
1349
|
+
renderComponent,
|
|
1350
|
+
renderToString,
|
|
1351
|
+
getRequestIp,
|
|
1352
|
+
parseCookies,
|
|
1353
|
+
Database,
|
|
1354
|
+
AuthService,
|
|
1355
|
+
ApiRouter
|
|
431
1356
|
};
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
1357
|
+
export {
|
|
1358
|
+
ApiRouter,
|
|
1359
|
+
AuthService,
|
|
1360
|
+
Database,
|
|
1361
|
+
Server,
|
|
1362
|
+
createDevServer,
|
|
1363
|
+
createProductionServer,
|
|
1364
|
+
createServer,
|
|
1365
|
+
server_default as default,
|
|
1366
|
+
ensureDirectory,
|
|
1367
|
+
errorHandler,
|
|
1368
|
+
generateDocument,
|
|
1369
|
+
generateErrorPage,
|
|
1370
|
+
generateLoadingPage,
|
|
1371
|
+
generateToken,
|
|
1372
|
+
getEnvironmentInfo,
|
|
1373
|
+
getPagination,
|
|
1374
|
+
getRequestIp,
|
|
1375
|
+
hashString,
|
|
1376
|
+
initNodeWasm,
|
|
1377
|
+
isDirectoryEmpty,
|
|
1378
|
+
loadGoWasmFromFile,
|
|
1379
|
+
notFoundHandler,
|
|
1380
|
+
parseCookies,
|
|
1381
|
+
rateLimit,
|
|
1382
|
+
readJsonFile,
|
|
1383
|
+
renderComponent,
|
|
1384
|
+
renderToString,
|
|
1385
|
+
requestLogger,
|
|
1386
|
+
safeJsonParse,
|
|
1387
|
+
sendError,
|
|
1388
|
+
sendSuccess,
|
|
1389
|
+
validateFields,
|
|
1390
|
+
validateFileUpload,
|
|
1391
|
+
writeJsonFile
|
|
467
1392
|
};
|
|
468
|
-
//# sourceMappingURL=index.js.map
|
|
1393
|
+
//# sourceMappingURL=index.js.map
|