frontend-hamroun 1.2.15 ā 1.2.17
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/README.md +4 -0
- package/bin/cli.js +673 -0
- package/dist/component.d.ts +1 -1
- package/dist/context.d.ts +4 -3
- package/dist/index.client.d.ts +11 -0
- package/dist/index.d.ts +9 -89
- package/dist/index.js +396 -67
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +392 -0
- package/dist/index.mjs.map +1 -0
- package/dist/jsx-runtime/jsx-runtime.d.ts +0 -1
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/renderer.d.ts +0 -10
- package/dist/server-renderer.d.ts +0 -3
- package/dist/server-types.d.ts +42 -0
- package/package.json +69 -50
- package/templates/basic-app/index.html +6 -6
- package/templates/basic-app/package.json +15 -11
- package/templates/basic-app/postcss.config.js +0 -1
- package/templates/basic-app/src/main.tsx +1 -10
- package/templates/basic-app/tailwind.config.js +2 -23
- package/templates/basic-app/tsconfig.json +4 -17
- package/templates/basic-app/vite.config.ts +3 -54
- package/templates/fullstack-app/api/hello.ts +18 -0
- package/templates/fullstack-app/api/users/[id].ts +73 -0
- package/templates/fullstack-app/api/users/index.ts +32 -0
- package/templates/fullstack-app/package.json +31 -0
- package/templates/fullstack-app/server.ts +46 -0
- package/templates/fullstack-app/src/pages/index.tsx +59 -0
- package/templates/ssr-template/vite.config.ts +1 -11
- package/bin/cli.cjs +0 -16
- package/bin/cli.mjs +0 -237
- package/dist/backend/api-utils.d.ts +0 -38
- package/dist/backend/api-utils.js +0 -135
- package/dist/backend/auth.d.ts +0 -134
- package/dist/backend/auth.js +0 -387
- package/dist/backend/database.d.ts +0 -27
- package/dist/backend/database.js +0 -91
- package/dist/backend/model.d.ts +0 -43
- package/dist/backend/model.js +0 -178
- package/dist/backend/router.d.ts +0 -27
- package/dist/backend/router.js +0 -137
- package/dist/backend/server.d.ts +0 -19
- package/dist/backend/server.js +0 -268
- package/dist/backend/types.d.ts +0 -217
- package/dist/backend/types.js +0 -1
- package/dist/batch.js +0 -22
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +0 -215
- package/dist/component.js +0 -84
- package/dist/components/Counter.js +0 -2
- package/dist/context.js +0 -18
- package/dist/frontend-hamroun.es.js +0 -1378
- package/dist/frontend-hamroun.umd.js +0 -66
- package/dist/hooks.js +0 -164
- package/dist/jsx-runtime/index.d.ts +0 -11
- package/dist/jsx-runtime/index.js +0 -19
- package/dist/jsx-runtime/jsx-dev-runtime.js +0 -1
- package/dist/jsx-runtime/jsx-runtime.js +0 -95
- package/dist/jsx-runtime.js +0 -192
- package/dist/renderer.js +0 -51
- package/dist/server-renderer.js +0 -102
- package/dist/types.js +0 -1
- package/dist/vdom.js +0 -27
- package/scripts/build-cli.js +0 -1107
- package/scripts/generate.js +0 -134
- package/src/backend/api-utils.ts +0 -178
- package/src/backend/auth.ts +0 -544
- package/src/backend/database.ts +0 -104
- package/src/backend/model.ts +0 -198
- package/src/backend/router.ts +0 -176
- package/src/backend/server.ts +0 -330
- package/src/backend/types.ts +0 -257
- package/src/batch.ts +0 -24
- package/src/cli/index.js +0 -554
- package/src/cli/index.ts +0 -257
- package/src/component.ts +0 -98
- package/src/components/Counter.tsx +0 -4
- package/src/context.ts +0 -29
- package/src/hooks.ts +0 -211
- package/src/index.ts +0 -144
- package/src/jsx-runtime/index.ts +0 -27
- package/src/jsx-runtime/jsx-dev-runtime.ts +0 -0
- package/src/jsx-runtime/jsx-runtime.ts +0 -104
- package/src/jsx-runtime.ts +0 -226
- package/src/renderer.ts +0 -55
- package/src/server-renderer.ts +0 -114
- package/src/shims.d.ts +0 -20
- package/src/types/bcrypt.d.ts +0 -30
- package/src/types/jsonwebtoken.d.ts +0 -55
- package/src/types.d.ts +0 -26
- package/src/types.ts +0 -21
- package/src/vdom.ts +0 -34
- package/templates/basic/.eslintignore +0 -5
- package/templates/basic/.eslintrc.json +0 -25
- package/templates/basic/docs/rapport_pfe.aux +0 -27
- package/templates/basic/docs/rapport_pfe.log +0 -399
- package/templates/basic/docs/rapport_pfe.out +0 -10
- package/templates/basic/docs/rapport_pfe.pdf +0 -0
- package/templates/basic/docs/rapport_pfe.tex +0 -68
- package/templates/basic/docs/rapport_pfe.toc +0 -14
- package/templates/basic/index.html +0 -12
- package/templates/basic/jsconfig.json +0 -14
- package/templates/basic/package.json +0 -20
- package/templates/basic/postcss.config.js +0 -7
- package/templates/basic/src/App.js +0 -105
- package/templates/basic/src/App.tsx +0 -65
- package/templates/basic/src/api.ts +0 -58
- package/templates/basic/src/components/Counter.tsx +0 -26
- package/templates/basic/src/components/Header.tsx +0 -9
- package/templates/basic/src/components/TodoList.tsx +0 -90
- package/templates/basic/src/main.css +0 -3
- package/templates/basic/src/main.js +0 -11
- package/templates/basic/src/main.ts +0 -20
- package/templates/basic/src/main.tsx +0 -144
- package/templates/basic/src/server.ts +0 -99
- package/templates/basic/tailwind.config.js +0 -32
- package/templates/basic/tsconfig.json +0 -20
- package/templates/basic/tsconfig.node.json +0 -10
- package/templates/basic/vite.config.js +0 -18
- package/templates/basic/vite.config.ts +0 -86
- package/templates/basic-app/src/App.js +0 -105
- package/templates/basic-app/src/App.tsx +0 -143
- package/templates/basic-app/src/api.ts +0 -58
- package/templates/basic-app/src/components/Counter.tsx +0 -26
- package/templates/basic-app/src/components/Header.tsx +0 -9
- package/templates/basic-app/src/components/TodoList.tsx +0 -90
- package/templates/basic-app/src/main.js +0 -10
- package/templates/basic-app/src/main.ts +0 -21
- package/templates/basic-app/src/react/index.ts +0 -35
- package/templates/basic-app/src/react/jsx-dev-runtime.ts +0 -13
- package/templates/basic-app/src/react/jsx-runtime.ts +0 -12
- package/templates/basic-app/src/server.ts +0 -99
- package/templates/basic-app/src/shims.ts +0 -9
- package/templates/basic-app/tsconfig.node.json +0 -10
- package/templates/basic-app/vite.config.js +0 -22
- package/templates/full-stack/.env.example +0 -11
- package/templates/full-stack/README.md +0 -51
- package/templates/full-stack/index.html +0 -12
- package/templates/full-stack/jsconfig.json +0 -14
- package/templates/full-stack/package.json +0 -20
- package/templates/full-stack/src/App.js +0 -105
- package/templates/full-stack/src/client/App.tsx +0 -50
- package/templates/full-stack/src/client/components/Header.tsx +0 -42
- package/templates/full-stack/src/client/components/UserList.tsx +0 -29
- package/templates/full-stack/src/client/main.tsx +0 -5
- package/templates/full-stack/src/main.css +0 -3
- package/templates/full-stack/src/main.js +0 -11
- package/templates/full-stack/src/main.ts +0 -20
- package/templates/full-stack/src/server/index.ts +0 -99
- package/templates/full-stack/src/server/routes/auth.ts +0 -39
- package/templates/full-stack/src/server/routes/users.ts +0 -48
- package/templates/full-stack/src/shims.ts +0 -9
- package/templates/full-stack/tsconfig.json +0 -20
- package/templates/full-stack/tsconfig.node.json +0 -10
- package/templates/full-stack/tsconfig.server.json +0 -15
- package/templates/full-stack/vite.config.js +0 -18
- package/templates/full-stack/vite.config.ts +0 -85
package/src/backend/model.ts
DELETED
@@ -1,198 +0,0 @@
|
|
1
|
-
import mongoose, { Schema, Document, SortOrder } from 'mongoose';
|
2
|
-
import { Model, PaginationOptions, PaginatedResult } from './types';
|
3
|
-
import * as bcryptModule from 'bcrypt';
|
4
|
-
const bcrypt = bcryptModule.default || bcryptModule;
|
5
|
-
|
6
|
-
/**
|
7
|
-
* Creates a data model from a Mongoose schema
|
8
|
-
* @param name Model name
|
9
|
-
* @param schema Mongoose schema definition
|
10
|
-
* @returns Model implementation with CRUD operations
|
11
|
-
*/
|
12
|
-
export function createModel<T extends Document>(
|
13
|
-
name: string,
|
14
|
-
schema: Schema<T>
|
15
|
-
): Model<T> {
|
16
|
-
// Create the Mongoose model
|
17
|
-
const mongooseModel = mongoose.model<T>(name, schema);
|
18
|
-
|
19
|
-
// Return the model implementation with CRUD operations
|
20
|
-
return {
|
21
|
-
getAll: async (options?: PaginationOptions): Promise<PaginatedResult<T>> => {
|
22
|
-
try {
|
23
|
-
const { page = 1, limit = 10, sort = '_id', order = 'desc' } = options || {};
|
24
|
-
|
25
|
-
const skip = (page - 1) * limit;
|
26
|
-
const sortOrder = order === 'asc' ? 1 : -1;
|
27
|
-
const sortOptions: Record<string, SortOrder> = { [sort]: sortOrder };
|
28
|
-
|
29
|
-
const [results, total] = await Promise.all([
|
30
|
-
mongooseModel.find()
|
31
|
-
.sort(sortOptions as any)
|
32
|
-
.skip(skip)
|
33
|
-
.limit(limit)
|
34
|
-
.exec(),
|
35
|
-
mongooseModel.countDocuments().exec()
|
36
|
-
]);
|
37
|
-
|
38
|
-
const totalPages = Math.ceil(total / limit);
|
39
|
-
|
40
|
-
return {
|
41
|
-
data: results,
|
42
|
-
pagination: {
|
43
|
-
total,
|
44
|
-
totalPages,
|
45
|
-
currentPage: page,
|
46
|
-
limit,
|
47
|
-
hasNextPage: page < totalPages,
|
48
|
-
hasPrevPage: page > 1
|
49
|
-
}
|
50
|
-
};
|
51
|
-
} catch (error) {
|
52
|
-
console.error(`Error in ${name}.getAll:`, error);
|
53
|
-
throw new Error(`Failed to retrieve ${name} records: ${(error as Error).message}`);
|
54
|
-
}
|
55
|
-
},
|
56
|
-
|
57
|
-
getById: async (id: string): Promise<T | null> => {
|
58
|
-
try {
|
59
|
-
// Validate ObjectId format to prevent DB errors
|
60
|
-
if (!mongoose.isValidObjectId(id)) {
|
61
|
-
return null;
|
62
|
-
}
|
63
|
-
return await mongooseModel.findById(id).exec();
|
64
|
-
} catch (error) {
|
65
|
-
console.error(`Error in ${name}.getById:`, error);
|
66
|
-
throw new Error(`Failed to retrieve ${name} by ID: ${(error as Error).message}`);
|
67
|
-
}
|
68
|
-
},
|
69
|
-
|
70
|
-
create: async (data: Partial<T>): Promise<T> => {
|
71
|
-
try {
|
72
|
-
const newDocument = new mongooseModel(data);
|
73
|
-
return await newDocument.save();
|
74
|
-
} catch (error) {
|
75
|
-
console.error(`Error in ${name}.create:`, error);
|
76
|
-
throw new Error(`Failed to create ${name}: ${(error as Error).message}`);
|
77
|
-
}
|
78
|
-
},
|
79
|
-
|
80
|
-
createMany: async (data: Partial<T>[]): Promise<T[]> => {
|
81
|
-
try {
|
82
|
-
return await mongooseModel.insertMany(data) as unknown as T[];
|
83
|
-
} catch (error) {
|
84
|
-
console.error(`Error in ${name}.createMany:`, error);
|
85
|
-
throw new Error(`Failed to create multiple ${name} records: ${(error as Error).message}`);
|
86
|
-
}
|
87
|
-
},
|
88
|
-
|
89
|
-
update: async (id: string, data: Partial<T>): Promise<T | null> => {
|
90
|
-
try {
|
91
|
-
// Validate ObjectId format to prevent DB errors
|
92
|
-
if (!mongoose.isValidObjectId(id)) {
|
93
|
-
return null;
|
94
|
-
}
|
95
|
-
return await mongooseModel.findByIdAndUpdate(
|
96
|
-
id,
|
97
|
-
{ $set: data },
|
98
|
-
{ new: true, runValidators: true }
|
99
|
-
).exec();
|
100
|
-
} catch (error) {
|
101
|
-
console.error(`Error in ${name}.update:`, error);
|
102
|
-
throw new Error(`Failed to update ${name}: ${(error as Error).message}`);
|
103
|
-
}
|
104
|
-
},
|
105
|
-
|
106
|
-
delete: async (id: string): Promise<boolean> => {
|
107
|
-
try {
|
108
|
-
// Validate ObjectId format to prevent DB errors
|
109
|
-
if (!mongoose.isValidObjectId(id)) {
|
110
|
-
return false;
|
111
|
-
}
|
112
|
-
const result = await mongooseModel.findByIdAndDelete(id).exec();
|
113
|
-
return result !== null;
|
114
|
-
} catch (error) {
|
115
|
-
console.error(`Error in ${name}.delete:`, error);
|
116
|
-
throw new Error(`Failed to delete ${name}: ${(error as Error).message}`);
|
117
|
-
}
|
118
|
-
},
|
119
|
-
|
120
|
-
find: async (query: Record<string, any>, options?: PaginationOptions): Promise<PaginatedResult<T>> => {
|
121
|
-
try {
|
122
|
-
const { page = 1, limit = 10, sort = '_id', order = 'desc' } = options || {};
|
123
|
-
|
124
|
-
const skip = (page - 1) * limit;
|
125
|
-
const sortOrder = order === 'asc' ? 1 : -1;
|
126
|
-
const sortOptions: Record<string, SortOrder> = { [sort]: sortOrder };
|
127
|
-
|
128
|
-
const [results, total] = await Promise.all([
|
129
|
-
mongooseModel.find(query)
|
130
|
-
.sort(sortOptions as any)
|
131
|
-
.skip(skip)
|
132
|
-
.limit(limit)
|
133
|
-
.exec(),
|
134
|
-
mongooseModel.countDocuments(query).exec()
|
135
|
-
]);
|
136
|
-
|
137
|
-
const totalPages = Math.ceil(total / limit);
|
138
|
-
|
139
|
-
return {
|
140
|
-
data: results,
|
141
|
-
pagination: {
|
142
|
-
total,
|
143
|
-
totalPages,
|
144
|
-
currentPage: page,
|
145
|
-
limit,
|
146
|
-
hasNextPage: page < totalPages,
|
147
|
-
hasPrevPage: page > 1
|
148
|
-
}
|
149
|
-
};
|
150
|
-
} catch (error) {
|
151
|
-
console.error(`Error in ${name}.find:`, error);
|
152
|
-
throw new Error(`Failed to find ${name} records: ${(error as Error).message}`);
|
153
|
-
}
|
154
|
-
},
|
155
|
-
|
156
|
-
count: async (query?: Record<string, any>): Promise<number> => {
|
157
|
-
try {
|
158
|
-
return await mongooseModel.countDocuments(query || {}).exec();
|
159
|
-
} catch (error) {
|
160
|
-
console.error(`Error in ${name}.count:`, error);
|
161
|
-
throw new Error(`Failed to count ${name} records: ${(error as Error).message}`);
|
162
|
-
}
|
163
|
-
},
|
164
|
-
|
165
|
-
findOne: async (query: Record<string, any>): Promise<T | null> => {
|
166
|
-
try {
|
167
|
-
return await mongooseModel.findOne(query).exec();
|
168
|
-
} catch (error) {
|
169
|
-
console.error(`Error in ${name}.findOne:`, error);
|
170
|
-
throw new Error(`Failed to find ${name} record: ${(error as Error).message}`);
|
171
|
-
}
|
172
|
-
}
|
173
|
-
};
|
174
|
-
}
|
175
|
-
|
176
|
-
/**
|
177
|
-
* Schema field types for simplified schema creation
|
178
|
-
*/
|
179
|
-
export const FieldTypes = {
|
180
|
-
String: { type: String },
|
181
|
-
Number: { type: Number },
|
182
|
-
Boolean: { type: Boolean },
|
183
|
-
Date: { type: Date },
|
184
|
-
ObjectId: { type: String }, // Fallback to String instead of using Schema.Types.ObjectId
|
185
|
-
|
186
|
-
// Helper functions for common field patterns
|
187
|
-
Required: (fieldType: any) => ({ ...fieldType, required: true }),
|
188
|
-
Unique: (fieldType: any) => ({ ...fieldType, unique: true }),
|
189
|
-
Ref: (model: string) => ({
|
190
|
-
type: String, // Fallback to String type for tests
|
191
|
-
ref: model
|
192
|
-
}),
|
193
|
-
Enum: (values: any[]) => ({ type: String, enum: values }),
|
194
|
-
Default: (fieldType: any, defaultValue: any) => ({ ...fieldType, default: defaultValue }),
|
195
|
-
|
196
|
-
// Array field type
|
197
|
-
Array: (fieldType: any) => ({ type: [fieldType] })
|
198
|
-
};
|
package/src/backend/router.ts
DELETED
@@ -1,176 +0,0 @@
|
|
1
|
-
import express, { Router, Request, Response, NextFunction } from 'express';
|
2
|
-
import { Model, RouteContext } from './types';
|
3
|
-
|
4
|
-
// Handler types
|
5
|
-
type RequestHandler = (context: RouteContext) => Promise<any> | any;
|
6
|
-
type ErrorHandler = (error: any, context: RouteContext) => Promise<any> | any;
|
7
|
-
|
8
|
-
// Default error handler with improved formatting and consistent response
|
9
|
-
const defaultErrorHandler = (error: any, { res }: RouteContext) => {
|
10
|
-
console.error('API Error:', error);
|
11
|
-
const status = error.status || error.statusCode || 500;
|
12
|
-
const message = error.message || 'Internal server error';
|
13
|
-
res.status(status).json({
|
14
|
-
success: false,
|
15
|
-
error: message,
|
16
|
-
stack: process.env.NODE_ENV !== 'production' ? error.stack : undefined
|
17
|
-
});
|
18
|
-
};
|
19
|
-
|
20
|
-
/**
|
21
|
-
* Options for API route creation
|
22
|
-
*/
|
23
|
-
interface RouteOptions {
|
24
|
-
middleware?: ((req: Request, res: Response, next: NextFunction) => void)[];
|
25
|
-
errorHandler?: ErrorHandler;
|
26
|
-
}
|
27
|
-
|
28
|
-
/**
|
29
|
-
* Creates a router for a data model with CRUD endpoints
|
30
|
-
* @param model The data model
|
31
|
-
* @param options Router options
|
32
|
-
*/
|
33
|
-
export function createModelRouter<T>(
|
34
|
-
model: Model<T>,
|
35
|
-
options: RouteOptions = {}
|
36
|
-
): Router {
|
37
|
-
const router = express.Router();
|
38
|
-
const { middleware = [], errorHandler = defaultErrorHandler } = options;
|
39
|
-
|
40
|
-
// Apply middleware
|
41
|
-
middleware.forEach(mw => router.use(mw));
|
42
|
-
|
43
|
-
// Helper to wrap handlers with error handling
|
44
|
-
const handleRoute = (handler: RequestHandler) => async (req: Request, res: Response, next: NextFunction) => {
|
45
|
-
try {
|
46
|
-
const context: RouteContext = { req, res, next, params: req.params, query: req.query, body: req.body };
|
47
|
-
return await handler(context);
|
48
|
-
} catch (error) {
|
49
|
-
const context: RouteContext = { req, res, next, params: req.params, query: req.query, body: req.body };
|
50
|
-
return errorHandler(error, context);
|
51
|
-
}
|
52
|
-
};
|
53
|
-
|
54
|
-
// GET all items with pagination
|
55
|
-
router.get('/', handleRoute(async ({ req, res }) => {
|
56
|
-
const paginationOptions = req.pagination || { page: 1, limit: 10 };
|
57
|
-
const result = await model.getAll(paginationOptions);
|
58
|
-
res.json({
|
59
|
-
success: true,
|
60
|
-
...result
|
61
|
-
});
|
62
|
-
}));
|
63
|
-
|
64
|
-
// GET item by ID
|
65
|
-
router.get('/:id', handleRoute(async ({ params, res }) => {
|
66
|
-
const item = await model.getById(params.id);
|
67
|
-
if (!item) {
|
68
|
-
res.status(404).json({
|
69
|
-
success: false,
|
70
|
-
error: 'Item not found'
|
71
|
-
});
|
72
|
-
return;
|
73
|
-
}
|
74
|
-
res.json({
|
75
|
-
success: true,
|
76
|
-
data: item
|
77
|
-
});
|
78
|
-
}));
|
79
|
-
|
80
|
-
// POST new item
|
81
|
-
router.post('/', handleRoute(async ({ body, res }) => {
|
82
|
-
const newItem = await model.create(body);
|
83
|
-
res.status(201).json({
|
84
|
-
success: true,
|
85
|
-
data: newItem,
|
86
|
-
message: 'Item created successfully'
|
87
|
-
});
|
88
|
-
}));
|
89
|
-
|
90
|
-
// PUT update item
|
91
|
-
router.put('/:id', handleRoute(async ({ params, body, res }) => {
|
92
|
-
const updatedItem = await model.update(params.id, body);
|
93
|
-
if (!updatedItem) {
|
94
|
-
res.status(404).json({
|
95
|
-
success: false,
|
96
|
-
error: 'Item not found'
|
97
|
-
});
|
98
|
-
return;
|
99
|
-
}
|
100
|
-
res.json({
|
101
|
-
success: true,
|
102
|
-
data: updatedItem,
|
103
|
-
message: 'Item updated successfully'
|
104
|
-
});
|
105
|
-
}));
|
106
|
-
|
107
|
-
// DELETE item
|
108
|
-
router.delete('/:id', handleRoute(async ({ params, res }) => {
|
109
|
-
const success = await model.delete(params.id);
|
110
|
-
if (!success) {
|
111
|
-
res.status(404).json({
|
112
|
-
success: false,
|
113
|
-
error: 'Item not found'
|
114
|
-
});
|
115
|
-
return;
|
116
|
-
}
|
117
|
-
res.json({
|
118
|
-
success: true,
|
119
|
-
message: 'Item deleted successfully'
|
120
|
-
});
|
121
|
-
}));
|
122
|
-
|
123
|
-
return router;
|
124
|
-
}
|
125
|
-
|
126
|
-
/**
|
127
|
-
* Creates a custom API router
|
128
|
-
* @param routeConfig Custom route configuration
|
129
|
-
* @param options Router options
|
130
|
-
*/
|
131
|
-
export function createApiRouter(
|
132
|
-
routeConfig: Record<string, {
|
133
|
-
method: 'get' | 'post' | 'put' | 'delete' | 'patch',
|
134
|
-
handler: RequestHandler
|
135
|
-
}>,
|
136
|
-
options: RouteOptions = {}
|
137
|
-
): Router {
|
138
|
-
const router = express.Router();
|
139
|
-
const { middleware = [], errorHandler = defaultErrorHandler } = options;
|
140
|
-
|
141
|
-
// Apply middleware
|
142
|
-
middleware.forEach(mw => router.use(mw));
|
143
|
-
|
144
|
-
// Helper to wrap handlers with error handling
|
145
|
-
const handleRoute = (handler: RequestHandler) => async (req: Request, res: Response, next: NextFunction) => {
|
146
|
-
try {
|
147
|
-
const context: RouteContext = { req, res, next, params: req.params, query: req.query, body: req.body };
|
148
|
-
|
149
|
-
// Check if response is already sent before calling handler
|
150
|
-
if (res.headersSent) {
|
151
|
-
return;
|
152
|
-
}
|
153
|
-
|
154
|
-
return await handler(context);
|
155
|
-
} catch (error) {
|
156
|
-
// If response is already sent, just log the error
|
157
|
-
if (res.headersSent) {
|
158
|
-
console.error('Error occurred after response was sent:', error);
|
159
|
-
return;
|
160
|
-
}
|
161
|
-
|
162
|
-
const context: RouteContext = { req, res, next, params: req.params, query: req.query, body: req.body };
|
163
|
-
return errorHandler(error, context);
|
164
|
-
}
|
165
|
-
};
|
166
|
-
|
167
|
-
// Create routes from config
|
168
|
-
Object.entries(routeConfig).forEach(([path, config]) => {
|
169
|
-
const { method, handler } = config;
|
170
|
-
router[method](path, handleRoute(handler));
|
171
|
-
});
|
172
|
-
|
173
|
-
return router;
|
174
|
-
}
|
175
|
-
|
176
|
-
export { Router };
|
package/src/backend/server.ts
DELETED
@@ -1,330 +0,0 @@
|
|
1
|
-
import express, { Express, Request, Response, NextFunction } from 'express';
|
2
|
-
import path from 'path';
|
3
|
-
import compression from 'compression';
|
4
|
-
import helmet from 'helmet';
|
5
|
-
import morgan from 'morgan';
|
6
|
-
import { renderToString } from '../server-renderer';
|
7
|
-
import { RouterOptions, HamrounServerOptions, DatabaseOptions } from './types';
|
8
|
-
import { DatabaseConnector } from './database';
|
9
|
-
import fs from 'fs';
|
10
|
-
|
11
|
-
/**
|
12
|
-
* Creates an Express server configured for Frontend Hamroun applications
|
13
|
-
* @param options Server configuration options
|
14
|
-
* @returns Configured Express application
|
15
|
-
*/
|
16
|
-
export function createServer(options: HamrounServerOptions = {}): Express {
|
17
|
-
const app = express();
|
18
|
-
const {
|
19
|
-
port = 3000,
|
20
|
-
staticDir = 'public',
|
21
|
-
enableCors = true,
|
22
|
-
apiPrefix = '/api',
|
23
|
-
ssrEnabled = true,
|
24
|
-
middlewares = [],
|
25
|
-
enableCompression = true,
|
26
|
-
enableHelmet = true,
|
27
|
-
logFormat = 'dev',
|
28
|
-
trustProxy = false,
|
29
|
-
showErrorDetails = process.env.NODE_ENV !== 'production',
|
30
|
-
} = options;
|
31
|
-
|
32
|
-
// Trust proxy if enabled
|
33
|
-
if (trustProxy) {
|
34
|
-
app.set('trust proxy', trustProxy);
|
35
|
-
}
|
36
|
-
|
37
|
-
// Basic middleware
|
38
|
-
app.use(express.json());
|
39
|
-
app.use(express.urlencoded({ extended: true }));
|
40
|
-
|
41
|
-
// Compression middleware
|
42
|
-
if (enableCompression) {
|
43
|
-
app.use(compression());
|
44
|
-
}
|
45
|
-
|
46
|
-
// Security middleware
|
47
|
-
if (enableHelmet) {
|
48
|
-
app.use(helmet({
|
49
|
-
contentSecurityPolicy: options.disableCSP ? false : undefined,
|
50
|
-
}));
|
51
|
-
}
|
52
|
-
|
53
|
-
// Request logging
|
54
|
-
if (logFormat) {
|
55
|
-
app.use(morgan(logFormat));
|
56
|
-
}
|
57
|
-
|
58
|
-
// CORS if enabled
|
59
|
-
if (enableCors) {
|
60
|
-
app.use((req: Request, res: Response, next: NextFunction) => {
|
61
|
-
res.header('Access-Control-Allow-Origin', '*');
|
62
|
-
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
|
63
|
-
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
|
64
|
-
if (req.method === 'OPTIONS') {
|
65
|
-
return res.sendStatus(200);
|
66
|
-
}
|
67
|
-
next();
|
68
|
-
});
|
69
|
-
}
|
70
|
-
|
71
|
-
// Apply custom middlewares
|
72
|
-
middlewares.forEach((middleware: any) => app.use(middleware));
|
73
|
-
|
74
|
-
// Serve static files with appropriate caching
|
75
|
-
if (staticDir) {
|
76
|
-
const staticPath = path.resolve(process.cwd(), staticDir);
|
77
|
-
if (fs.existsSync(staticPath)) {
|
78
|
-
app.use(express.static(staticPath, {
|
79
|
-
maxAge: options.staticCacheAge || '1d',
|
80
|
-
etag: true,
|
81
|
-
}));
|
82
|
-
console.log(`š Serving static files from: ${staticPath}`);
|
83
|
-
} else {
|
84
|
-
console.warn(`ā ļø Static directory not found: ${staticPath}`);
|
85
|
-
}
|
86
|
-
}
|
87
|
-
|
88
|
-
// Database connection reference
|
89
|
-
let dbConnector: DatabaseConnector | null = null;
|
90
|
-
|
91
|
-
// Method to connect to database with better error handling
|
92
|
-
app.connectToDatabase = async (dbOptions: DatabaseOptions): Promise<DatabaseConnector> => {
|
93
|
-
try {
|
94
|
-
if (dbConnector && dbConnector.isConnected()) {
|
95
|
-
console.log('ā
Using existing database connection');
|
96
|
-
return dbConnector;
|
97
|
-
}
|
98
|
-
|
99
|
-
dbConnector = new DatabaseConnector(dbOptions);
|
100
|
-
await dbConnector.connect();
|
101
|
-
console.log('ā
Database connected successfully');
|
102
|
-
|
103
|
-
// Add disconnect handler for graceful shutdown
|
104
|
-
process.on('SIGTERM', async () => {
|
105
|
-
if (dbConnector && dbConnector.isConnected()) {
|
106
|
-
await dbConnector.disconnect();
|
107
|
-
console.log('Database connection closed');
|
108
|
-
}
|
109
|
-
});
|
110
|
-
|
111
|
-
return dbConnector;
|
112
|
-
} catch (error) {
|
113
|
-
console.error('ā Failed to connect to database:', error);
|
114
|
-
throw error;
|
115
|
-
}
|
116
|
-
};
|
117
|
-
|
118
|
-
// Store registered routes for better management
|
119
|
-
const routes: Record<string, any> = {};
|
120
|
-
const ssrRoutes: Record<string, any> = {};
|
121
|
-
|
122
|
-
// Method to register API routes with better integration of auth middleware
|
123
|
-
app.registerApi = (routePath: string, router: any, routerOptions: RouterOptions = {}) => {
|
124
|
-
try {
|
125
|
-
const { prefix = apiPrefix } = routerOptions;
|
126
|
-
const fullPath = path.posix.join(prefix, routePath).replace(/\\/g, '/');
|
127
|
-
app.use(fullPath, router);
|
128
|
-
routes[fullPath] = { router, options: routerOptions };
|
129
|
-
console.log(`š API registered: ${fullPath}`);
|
130
|
-
} catch (error) {
|
131
|
-
console.error(`ā Failed to register API at ${routePath}:`, error);
|
132
|
-
}
|
133
|
-
return app;
|
134
|
-
};
|
135
|
-
|
136
|
-
// Method to register SSR handler with better error handling
|
137
|
-
app.registerSSR = (routePath: string, component: any, options: any = {}) => {
|
138
|
-
if (!ssrEnabled) {
|
139
|
-
console.log(`ā ļø SSR disabled: skipping registration of ${routePath}`);
|
140
|
-
return app;
|
141
|
-
}
|
142
|
-
|
143
|
-
ssrRoutes[routePath] = { component, options };
|
144
|
-
|
145
|
-
app.get(routePath, async (req: Request, res: Response, next: NextFunction) => {
|
146
|
-
try {
|
147
|
-
// Skip SSR if requested via query param
|
148
|
-
if (req.query.nossr === 'true') {
|
149
|
-
return next();
|
150
|
-
}
|
151
|
-
|
152
|
-
// Setup props for the component
|
153
|
-
const props = {
|
154
|
-
req,
|
155
|
-
res,
|
156
|
-
params: req.params,
|
157
|
-
query: req.query,
|
158
|
-
user: (req as any).user,
|
159
|
-
...options.props
|
160
|
-
};
|
161
|
-
|
162
|
-
// Server-side render the component
|
163
|
-
const html = await renderToString(component(props));
|
164
|
-
|
165
|
-
// Send the full HTML document
|
166
|
-
res.send(`
|
167
|
-
<!DOCTYPE html>
|
168
|
-
<html lang="${options.lang || 'en'}">
|
169
|
-
<head>
|
170
|
-
<meta charset="UTF-8">
|
171
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
172
|
-
<title>${options.title || 'Frontend Hamroun App'}</title>
|
173
|
-
${options.meta ? options.meta.map((meta: any) =>
|
174
|
-
`<meta ${Object.entries(meta).map(([k, v]) => `${k}="${v}"`).join(' ')}>`)
|
175
|
-
.join('\n ') : ''}
|
176
|
-
${options.head || ''}
|
177
|
-
${options.styles ? `<style>${options.styles}</style>` : ''}
|
178
|
-
${options.styleSheets ? options.styleSheets.map((sheet: string) =>
|
179
|
-
`<link rel="stylesheet" href="${sheet}">`).join('\n ') : ''}
|
180
|
-
</head>
|
181
|
-
<body ${options.bodyAttributes || ''}>
|
182
|
-
<div id="${options.rootId || 'root'}">${html}</div>
|
183
|
-
<script>
|
184
|
-
window.__INITIAL_DATA__ = ${JSON.stringify(options.initialData || {})};
|
185
|
-
</script>
|
186
|
-
${options.scripts ? options.scripts.map((script: string) =>
|
187
|
-
`<script src="${script}"></script>`).join('\n ') : ''}
|
188
|
-
</body>
|
189
|
-
</html>
|
190
|
-
`);
|
191
|
-
} catch (error) {
|
192
|
-
console.error('SSR Error:', error);
|
193
|
-
|
194
|
-
// If fallback is enabled, continue to next middleware
|
195
|
-
if (options.fallback) {
|
196
|
-
return next();
|
197
|
-
}
|
198
|
-
|
199
|
-
res.status(500).send('Server rendering error');
|
200
|
-
}
|
201
|
-
});
|
202
|
-
|
203
|
-
console.log(`š„ļø SSR registered: ${routePath}`);
|
204
|
-
return app;
|
205
|
-
};
|
206
|
-
|
207
|
-
// Global error handler with better formatting
|
208
|
-
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
|
209
|
-
console.error('Server error:', err);
|
210
|
-
|
211
|
-
// Determine status code
|
212
|
-
const statusCode = (err as any).statusCode || (err as any).status || 500;
|
213
|
-
|
214
|
-
// Format error response based on the request
|
215
|
-
const isApiRequest = req.path.startsWith(apiPrefix);
|
216
|
-
|
217
|
-
if (isApiRequest) {
|
218
|
-
// API error response
|
219
|
-
res.status(statusCode).json({
|
220
|
-
success: false,
|
221
|
-
error: showErrorDetails ? err.message : 'Internal Server Error',
|
222
|
-
stack: showErrorDetails ? err.stack : undefined
|
223
|
-
});
|
224
|
-
} else {
|
225
|
-
// Web error response
|
226
|
-
res.status(statusCode).send(`
|
227
|
-
<!DOCTYPE html>
|
228
|
-
<html>
|
229
|
-
<head>
|
230
|
-
<title>Error - ${statusCode}</title>
|
231
|
-
<style>
|
232
|
-
body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }
|
233
|
-
.error { background: #f8d7da; border: 1px solid #f5c6cb; padding: 1rem; border-radius: 4px; }
|
234
|
-
.stack { background: #f8f9fa; padding: 1rem; border-radius: 4px; overflow: auto; }
|
235
|
-
</style>
|
236
|
-
</head>
|
237
|
-
<body>
|
238
|
-
<h1>Error ${statusCode}</h1>
|
239
|
-
<div class="error">${showErrorDetails ? err.message : 'Internal Server Error'}</div>
|
240
|
-
${showErrorDetails && err.stack ? `<pre class="stack">${err.stack}</pre>` : ''}
|
241
|
-
</body>
|
242
|
-
</html>
|
243
|
-
`);
|
244
|
-
}
|
245
|
-
});
|
246
|
-
|
247
|
-
// 404 handler
|
248
|
-
app.use((req: Request, res: Response) => {
|
249
|
-
const isApiRequest = req.path.startsWith(apiPrefix);
|
250
|
-
|
251
|
-
if (isApiRequest) {
|
252
|
-
res.status(404).json({ success: false, error: 'Not Found' });
|
253
|
-
} else {
|
254
|
-
res.status(404).send(`
|
255
|
-
<!DOCTYPE html>
|
256
|
-
<html>
|
257
|
-
<head>
|
258
|
-
<title>404 - Not Found</title>
|
259
|
-
<style>
|
260
|
-
body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }
|
261
|
-
</style>
|
262
|
-
</head>
|
263
|
-
<body>
|
264
|
-
<h1>404 - Not Found</h1>
|
265
|
-
<p>The requested resource was not found on this server.</p>
|
266
|
-
<p><a href="/">Return to homepage</a></p>
|
267
|
-
</body>
|
268
|
-
</html>
|
269
|
-
`);
|
270
|
-
}
|
271
|
-
});
|
272
|
-
|
273
|
-
// Start the server with graceful shutdown
|
274
|
-
app.start = (callback?: () => void) => {
|
275
|
-
const server = app.listen(port, () => {
|
276
|
-
console.log(`
|
277
|
-
š Frontend Hamroun server running at http://localhost:${port}
|
278
|
-
${Object.keys(routes).length > 0 ? `\nš” Registered API Routes:
|
279
|
-
${Object.keys(routes).map(route => ` ${route}`).join('\n')}` : ''}
|
280
|
-
${Object.keys(ssrRoutes).length > 0 ? `\nš„ļø Registered SSR Routes:
|
281
|
-
${Object.keys(ssrRoutes).map(route => ` ${route}`).join('\n')}` : ''}
|
282
|
-
`);
|
283
|
-
|
284
|
-
if (callback) callback();
|
285
|
-
});
|
286
|
-
|
287
|
-
// Graceful shutdown
|
288
|
-
const gracefulShutdown = async (signal: string) => {
|
289
|
-
console.log(`${signal} signal received: closing HTTP server and cleaning up`);
|
290
|
-
|
291
|
-
server.close(async () => {
|
292
|
-
console.log('HTTP server closed');
|
293
|
-
|
294
|
-
// Close database connection if it exists
|
295
|
-
if (dbConnector && dbConnector.isConnected()) {
|
296
|
-
await dbConnector.disconnect();
|
297
|
-
console.log('Database connection closed');
|
298
|
-
}
|
299
|
-
|
300
|
-
process.exit(0);
|
301
|
-
});
|
302
|
-
|
303
|
-
// Force close after timeout
|
304
|
-
setTimeout(() => {
|
305
|
-
console.error('Could not close connections in time, forcefully shutting down');
|
306
|
-
process.exit(1);
|
307
|
-
}, 10000);
|
308
|
-
};
|
309
|
-
|
310
|
-
// Listen for termination signals
|
311
|
-
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
312
|
-
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
313
|
-
|
314
|
-
return server;
|
315
|
-
};
|
316
|
-
|
317
|
-
return app;
|
318
|
-
}
|
319
|
-
|
320
|
-
// Update the global Express Application interface
|
321
|
-
declare global {
|
322
|
-
namespace Express {
|
323
|
-
interface Application {
|
324
|
-
registerApi: (routePath: string, router: any, options?: RouterOptions) => Application;
|
325
|
-
registerSSR: (routePath: string, component: any, options?: any) => Application;
|
326
|
-
start: (callback?: () => void) => any;
|
327
|
-
connectToDatabase: (dbOptions: DatabaseOptions) => Promise<DatabaseConnector>;
|
328
|
-
}
|
329
|
-
}
|
330
|
-
}
|