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.
Files changed (158) hide show
  1. package/README.md +4 -0
  2. package/bin/cli.js +673 -0
  3. package/dist/component.d.ts +1 -1
  4. package/dist/context.d.ts +4 -3
  5. package/dist/index.client.d.ts +11 -0
  6. package/dist/index.d.ts +9 -89
  7. package/dist/index.js +396 -67
  8. package/dist/index.js.map +1 -0
  9. package/dist/index.mjs +392 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/dist/jsx-runtime/jsx-runtime.d.ts +0 -1
  12. package/dist/jsx-runtime.d.ts +1 -1
  13. package/dist/renderer.d.ts +0 -10
  14. package/dist/server-renderer.d.ts +0 -3
  15. package/dist/server-types.d.ts +42 -0
  16. package/package.json +69 -50
  17. package/templates/basic-app/index.html +6 -6
  18. package/templates/basic-app/package.json +15 -11
  19. package/templates/basic-app/postcss.config.js +0 -1
  20. package/templates/basic-app/src/main.tsx +1 -10
  21. package/templates/basic-app/tailwind.config.js +2 -23
  22. package/templates/basic-app/tsconfig.json +4 -17
  23. package/templates/basic-app/vite.config.ts +3 -54
  24. package/templates/fullstack-app/api/hello.ts +18 -0
  25. package/templates/fullstack-app/api/users/[id].ts +73 -0
  26. package/templates/fullstack-app/api/users/index.ts +32 -0
  27. package/templates/fullstack-app/package.json +31 -0
  28. package/templates/fullstack-app/server.ts +46 -0
  29. package/templates/fullstack-app/src/pages/index.tsx +59 -0
  30. package/templates/ssr-template/vite.config.ts +1 -11
  31. package/bin/cli.cjs +0 -16
  32. package/bin/cli.mjs +0 -237
  33. package/dist/backend/api-utils.d.ts +0 -38
  34. package/dist/backend/api-utils.js +0 -135
  35. package/dist/backend/auth.d.ts +0 -134
  36. package/dist/backend/auth.js +0 -387
  37. package/dist/backend/database.d.ts +0 -27
  38. package/dist/backend/database.js +0 -91
  39. package/dist/backend/model.d.ts +0 -43
  40. package/dist/backend/model.js +0 -178
  41. package/dist/backend/router.d.ts +0 -27
  42. package/dist/backend/router.js +0 -137
  43. package/dist/backend/server.d.ts +0 -19
  44. package/dist/backend/server.js +0 -268
  45. package/dist/backend/types.d.ts +0 -217
  46. package/dist/backend/types.js +0 -1
  47. package/dist/batch.js +0 -22
  48. package/dist/cli/index.d.ts +0 -2
  49. package/dist/cli/index.js +0 -215
  50. package/dist/component.js +0 -84
  51. package/dist/components/Counter.js +0 -2
  52. package/dist/context.js +0 -18
  53. package/dist/frontend-hamroun.es.js +0 -1378
  54. package/dist/frontend-hamroun.umd.js +0 -66
  55. package/dist/hooks.js +0 -164
  56. package/dist/jsx-runtime/index.d.ts +0 -11
  57. package/dist/jsx-runtime/index.js +0 -19
  58. package/dist/jsx-runtime/jsx-dev-runtime.js +0 -1
  59. package/dist/jsx-runtime/jsx-runtime.js +0 -95
  60. package/dist/jsx-runtime.js +0 -192
  61. package/dist/renderer.js +0 -51
  62. package/dist/server-renderer.js +0 -102
  63. package/dist/types.js +0 -1
  64. package/dist/vdom.js +0 -27
  65. package/scripts/build-cli.js +0 -1107
  66. package/scripts/generate.js +0 -134
  67. package/src/backend/api-utils.ts +0 -178
  68. package/src/backend/auth.ts +0 -544
  69. package/src/backend/database.ts +0 -104
  70. package/src/backend/model.ts +0 -198
  71. package/src/backend/router.ts +0 -176
  72. package/src/backend/server.ts +0 -330
  73. package/src/backend/types.ts +0 -257
  74. package/src/batch.ts +0 -24
  75. package/src/cli/index.js +0 -554
  76. package/src/cli/index.ts +0 -257
  77. package/src/component.ts +0 -98
  78. package/src/components/Counter.tsx +0 -4
  79. package/src/context.ts +0 -29
  80. package/src/hooks.ts +0 -211
  81. package/src/index.ts +0 -144
  82. package/src/jsx-runtime/index.ts +0 -27
  83. package/src/jsx-runtime/jsx-dev-runtime.ts +0 -0
  84. package/src/jsx-runtime/jsx-runtime.ts +0 -104
  85. package/src/jsx-runtime.ts +0 -226
  86. package/src/renderer.ts +0 -55
  87. package/src/server-renderer.ts +0 -114
  88. package/src/shims.d.ts +0 -20
  89. package/src/types/bcrypt.d.ts +0 -30
  90. package/src/types/jsonwebtoken.d.ts +0 -55
  91. package/src/types.d.ts +0 -26
  92. package/src/types.ts +0 -21
  93. package/src/vdom.ts +0 -34
  94. package/templates/basic/.eslintignore +0 -5
  95. package/templates/basic/.eslintrc.json +0 -25
  96. package/templates/basic/docs/rapport_pfe.aux +0 -27
  97. package/templates/basic/docs/rapport_pfe.log +0 -399
  98. package/templates/basic/docs/rapport_pfe.out +0 -10
  99. package/templates/basic/docs/rapport_pfe.pdf +0 -0
  100. package/templates/basic/docs/rapport_pfe.tex +0 -68
  101. package/templates/basic/docs/rapport_pfe.toc +0 -14
  102. package/templates/basic/index.html +0 -12
  103. package/templates/basic/jsconfig.json +0 -14
  104. package/templates/basic/package.json +0 -20
  105. package/templates/basic/postcss.config.js +0 -7
  106. package/templates/basic/src/App.js +0 -105
  107. package/templates/basic/src/App.tsx +0 -65
  108. package/templates/basic/src/api.ts +0 -58
  109. package/templates/basic/src/components/Counter.tsx +0 -26
  110. package/templates/basic/src/components/Header.tsx +0 -9
  111. package/templates/basic/src/components/TodoList.tsx +0 -90
  112. package/templates/basic/src/main.css +0 -3
  113. package/templates/basic/src/main.js +0 -11
  114. package/templates/basic/src/main.ts +0 -20
  115. package/templates/basic/src/main.tsx +0 -144
  116. package/templates/basic/src/server.ts +0 -99
  117. package/templates/basic/tailwind.config.js +0 -32
  118. package/templates/basic/tsconfig.json +0 -20
  119. package/templates/basic/tsconfig.node.json +0 -10
  120. package/templates/basic/vite.config.js +0 -18
  121. package/templates/basic/vite.config.ts +0 -86
  122. package/templates/basic-app/src/App.js +0 -105
  123. package/templates/basic-app/src/App.tsx +0 -143
  124. package/templates/basic-app/src/api.ts +0 -58
  125. package/templates/basic-app/src/components/Counter.tsx +0 -26
  126. package/templates/basic-app/src/components/Header.tsx +0 -9
  127. package/templates/basic-app/src/components/TodoList.tsx +0 -90
  128. package/templates/basic-app/src/main.js +0 -10
  129. package/templates/basic-app/src/main.ts +0 -21
  130. package/templates/basic-app/src/react/index.ts +0 -35
  131. package/templates/basic-app/src/react/jsx-dev-runtime.ts +0 -13
  132. package/templates/basic-app/src/react/jsx-runtime.ts +0 -12
  133. package/templates/basic-app/src/server.ts +0 -99
  134. package/templates/basic-app/src/shims.ts +0 -9
  135. package/templates/basic-app/tsconfig.node.json +0 -10
  136. package/templates/basic-app/vite.config.js +0 -22
  137. package/templates/full-stack/.env.example +0 -11
  138. package/templates/full-stack/README.md +0 -51
  139. package/templates/full-stack/index.html +0 -12
  140. package/templates/full-stack/jsconfig.json +0 -14
  141. package/templates/full-stack/package.json +0 -20
  142. package/templates/full-stack/src/App.js +0 -105
  143. package/templates/full-stack/src/client/App.tsx +0 -50
  144. package/templates/full-stack/src/client/components/Header.tsx +0 -42
  145. package/templates/full-stack/src/client/components/UserList.tsx +0 -29
  146. package/templates/full-stack/src/client/main.tsx +0 -5
  147. package/templates/full-stack/src/main.css +0 -3
  148. package/templates/full-stack/src/main.js +0 -11
  149. package/templates/full-stack/src/main.ts +0 -20
  150. package/templates/full-stack/src/server/index.ts +0 -99
  151. package/templates/full-stack/src/server/routes/auth.ts +0 -39
  152. package/templates/full-stack/src/server/routes/users.ts +0 -48
  153. package/templates/full-stack/src/shims.ts +0 -9
  154. package/templates/full-stack/tsconfig.json +0 -20
  155. package/templates/full-stack/tsconfig.node.json +0 -10
  156. package/templates/full-stack/tsconfig.server.json +0 -15
  157. package/templates/full-stack/vite.config.js +0 -18
  158. package/templates/full-stack/vite.config.ts +0 -85
@@ -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
- };
@@ -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 };
@@ -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
- }