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