frontend-hamroun 1.1.90 → 1.2.0

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 (92) hide show
  1. package/dist/{src/backend → backend}/api-utils.d.ts +2 -2
  2. package/dist/backend/api-utils.js +135 -0
  3. package/dist/backend/auth.js +387 -0
  4. package/dist/{src/backend → backend}/database.d.ts +1 -1
  5. package/dist/backend/database.js +91 -0
  6. package/dist/{src/backend → backend}/model.d.ts +2 -2
  7. package/dist/backend/model.js +176 -0
  8. package/dist/{src/backend → backend}/router.d.ts +1 -1
  9. package/dist/backend/router.js +137 -0
  10. package/dist/backend/server.js +268 -0
  11. package/dist/batch.js +22 -0
  12. package/dist/cli/index.js +1 -0
  13. package/dist/{src/component.d.ts → component.d.ts} +1 -1
  14. package/dist/component.js +84 -0
  15. package/dist/components/Counter.js +2 -0
  16. package/dist/context.js +20 -0
  17. package/dist/frontend-hamroun.es.js +1680 -0
  18. package/dist/frontend-hamroun.es.js.map +1 -0
  19. package/dist/frontend-hamroun.umd.js +2 -0
  20. package/dist/frontend-hamroun.umd.js.map +1 -0
  21. package/dist/hooks.js +164 -0
  22. package/dist/index.d.ts +46 -0
  23. package/dist/index.js +52 -355
  24. package/dist/jsx-runtime/index.d.ts +9 -0
  25. package/dist/jsx-runtime/index.js +16 -0
  26. package/dist/jsx-runtime/jsx-dev-runtime.js +1 -0
  27. package/dist/jsx-runtime/jsx-runtime.js +91 -0
  28. package/dist/{src/jsx-runtime.d.ts → jsx-runtime.d.ts} +1 -1
  29. package/dist/jsx-runtime.js +192 -0
  30. package/dist/renderer.js +51 -0
  31. package/dist/{src/server-renderer.d.ts → server-renderer.d.ts} +3 -0
  32. package/dist/server-renderer.js +102 -0
  33. package/dist/vdom.js +27 -0
  34. package/package.json +38 -52
  35. package/scripts/generate.js +134 -0
  36. package/src/backend/api-utils.ts +178 -0
  37. package/src/backend/auth.ts +543 -0
  38. package/src/backend/database.ts +104 -0
  39. package/src/backend/model.ts +196 -0
  40. package/src/backend/router.ts +176 -0
  41. package/src/backend/server.ts +330 -0
  42. package/src/backend/types.ts +257 -0
  43. package/src/batch.ts +24 -0
  44. package/src/cli/index.js +22 -40
  45. package/src/component.ts +98 -0
  46. package/src/components/Counter.tsx +4 -0
  47. package/src/context.ts +32 -0
  48. package/src/hooks.ts +211 -0
  49. package/src/index.ts +113 -0
  50. package/src/jsx-runtime/index.ts +24 -0
  51. package/src/jsx-runtime/jsx-dev-runtime.ts +0 -0
  52. package/src/jsx-runtime/jsx-runtime.ts +99 -0
  53. package/src/jsx-runtime.ts +226 -0
  54. package/src/renderer.ts +55 -0
  55. package/src/server-renderer.ts +114 -0
  56. package/src/types/bcrypt.d.ts +30 -0
  57. package/src/types/jsonwebtoken.d.ts +55 -0
  58. package/src/types.d.ts +26 -0
  59. package/src/types.ts +21 -0
  60. package/src/vdom.ts +34 -0
  61. package/templates/basic-app/package.json +17 -15
  62. package/templates/basic-app/postcss.config.js +1 -0
  63. package/templates/basic-app/src/App.tsx +65 -0
  64. package/templates/basic-app/src/api.ts +58 -0
  65. package/templates/basic-app/src/components/Counter.tsx +26 -0
  66. package/templates/basic-app/src/components/Header.tsx +9 -0
  67. package/templates/basic-app/src/components/TodoList.tsx +90 -0
  68. package/templates/basic-app/src/main.ts +20 -0
  69. package/templates/basic-app/src/server.ts +99 -0
  70. package/templates/basic-app/tailwind.config.js +23 -2
  71. package/bin/cli.js +0 -371
  72. package/dist/index.js.map +0 -1
  73. package/dist/index.mjs +0 -139269
  74. package/dist/index.mjs.map +0 -1
  75. package/dist/src/index.d.ts +0 -16
  76. package/dist/test/setupTests.d.ts +0 -4
  77. /package/dist/{src/backend → backend}/auth.d.ts +0 -0
  78. /package/dist/{src/backend → backend}/server.d.ts +0 -0
  79. /package/dist/{src/backend → backend}/types.d.ts +0 -0
  80. /package/dist/{test/backend.test.d.ts → backend/types.js} +0 -0
  81. /package/dist/{src/batch.d.ts → batch.d.ts} +0 -0
  82. /package/dist/{src/cli → cli}/index.d.ts +0 -0
  83. /package/dist/{src/components → components}/Counter.d.ts +0 -0
  84. /package/dist/{src/context.d.ts → context.d.ts} +0 -0
  85. /package/dist/{src/hooks.d.ts → hooks.d.ts} +0 -0
  86. /package/dist/{src/jsx-runtime → jsx-runtime}/jsx-dev-runtime.d.ts +0 -0
  87. /package/dist/{src/jsx-runtime → jsx-runtime}/jsx-runtime.d.ts +0 -0
  88. /package/dist/{src/renderer.d.ts → renderer.d.ts} +0 -0
  89. /package/dist/{src/types.d.ts → types.d.ts} +0 -0
  90. /package/dist/{test/mockTest.d.ts → types.js} +0 -0
  91. /package/dist/{src/vdom.d.ts → vdom.d.ts} +0 -0
  92. /package/{dist/test/mongooseSetup.d.ts → src/cli/index.ts} +0 -0
@@ -0,0 +1,176 @@
1
+ import mongoose from 'mongoose';
2
+ /**
3
+ * Creates a data model from a Mongoose schema
4
+ * @param name Model name
5
+ * @param schema Mongoose schema definition
6
+ * @returns Model implementation with CRUD operations
7
+ */
8
+ export function createModel(name, schema) {
9
+ // Create the Mongoose model
10
+ const mongooseModel = mongoose.model(name, schema);
11
+ // Return the model implementation with CRUD operations
12
+ return {
13
+ getAll: async (options) => {
14
+ try {
15
+ const { page = 1, limit = 10, sort = '_id', order = 'desc' } = options || {};
16
+ const skip = (page - 1) * limit;
17
+ const sortOrder = order === 'asc' ? 1 : -1;
18
+ const sortOptions = { [sort]: sortOrder };
19
+ const [results, total] = await Promise.all([
20
+ mongooseModel.find()
21
+ .sort(sortOptions)
22
+ .skip(skip)
23
+ .limit(limit)
24
+ .exec(),
25
+ mongooseModel.countDocuments().exec()
26
+ ]);
27
+ const totalPages = Math.ceil(total / limit);
28
+ return {
29
+ data: results,
30
+ pagination: {
31
+ total,
32
+ totalPages,
33
+ currentPage: page,
34
+ limit,
35
+ hasNextPage: page < totalPages,
36
+ hasPrevPage: page > 1
37
+ }
38
+ };
39
+ }
40
+ catch (error) {
41
+ console.error(`Error in ${name}.getAll:`, error);
42
+ throw new Error(`Failed to retrieve ${name} records: ${error.message}`);
43
+ }
44
+ },
45
+ getById: async (id) => {
46
+ try {
47
+ // Validate ObjectId format to prevent DB errors
48
+ if (!mongoose.isValidObjectId(id)) {
49
+ return null;
50
+ }
51
+ return await mongooseModel.findById(id).exec();
52
+ }
53
+ catch (error) {
54
+ console.error(`Error in ${name}.getById:`, error);
55
+ throw new Error(`Failed to retrieve ${name} by ID: ${error.message}`);
56
+ }
57
+ },
58
+ create: async (data) => {
59
+ try {
60
+ const newDocument = new mongooseModel(data);
61
+ return await newDocument.save();
62
+ }
63
+ catch (error) {
64
+ console.error(`Error in ${name}.create:`, error);
65
+ throw new Error(`Failed to create ${name}: ${error.message}`);
66
+ }
67
+ },
68
+ createMany: async (data) => {
69
+ try {
70
+ return await mongooseModel.insertMany(data);
71
+ }
72
+ catch (error) {
73
+ console.error(`Error in ${name}.createMany:`, error);
74
+ throw new Error(`Failed to create multiple ${name} records: ${error.message}`);
75
+ }
76
+ },
77
+ update: async (id, data) => {
78
+ try {
79
+ // Validate ObjectId format to prevent DB errors
80
+ if (!mongoose.isValidObjectId(id)) {
81
+ return null;
82
+ }
83
+ return await mongooseModel.findByIdAndUpdate(id, { $set: data }, { new: true, runValidators: true }).exec();
84
+ }
85
+ catch (error) {
86
+ console.error(`Error in ${name}.update:`, error);
87
+ throw new Error(`Failed to update ${name}: ${error.message}`);
88
+ }
89
+ },
90
+ delete: async (id) => {
91
+ try {
92
+ // Validate ObjectId format to prevent DB errors
93
+ if (!mongoose.isValidObjectId(id)) {
94
+ return false;
95
+ }
96
+ const result = await mongooseModel.findByIdAndDelete(id).exec();
97
+ return result !== null;
98
+ }
99
+ catch (error) {
100
+ console.error(`Error in ${name}.delete:`, error);
101
+ throw new Error(`Failed to delete ${name}: ${error.message}`);
102
+ }
103
+ },
104
+ find: async (query, options) => {
105
+ try {
106
+ const { page = 1, limit = 10, sort = '_id', order = 'desc' } = options || {};
107
+ const skip = (page - 1) * limit;
108
+ const sortOrder = order === 'asc' ? 1 : -1;
109
+ const sortOptions = { [sort]: sortOrder };
110
+ const [results, total] = await Promise.all([
111
+ mongooseModel.find(query)
112
+ .sort(sortOptions)
113
+ .skip(skip)
114
+ .limit(limit)
115
+ .exec(),
116
+ mongooseModel.countDocuments(query).exec()
117
+ ]);
118
+ const totalPages = Math.ceil(total / limit);
119
+ return {
120
+ data: results,
121
+ pagination: {
122
+ total,
123
+ totalPages,
124
+ currentPage: page,
125
+ limit,
126
+ hasNextPage: page < totalPages,
127
+ hasPrevPage: page > 1
128
+ }
129
+ };
130
+ }
131
+ catch (error) {
132
+ console.error(`Error in ${name}.find:`, error);
133
+ throw new Error(`Failed to find ${name} records: ${error.message}`);
134
+ }
135
+ },
136
+ count: async (query) => {
137
+ try {
138
+ return await mongooseModel.countDocuments(query || {}).exec();
139
+ }
140
+ catch (error) {
141
+ console.error(`Error in ${name}.count:`, error);
142
+ throw new Error(`Failed to count ${name} records: ${error.message}`);
143
+ }
144
+ },
145
+ findOne: async (query) => {
146
+ try {
147
+ return await mongooseModel.findOne(query).exec();
148
+ }
149
+ catch (error) {
150
+ console.error(`Error in ${name}.findOne:`, error);
151
+ throw new Error(`Failed to find ${name} record: ${error.message}`);
152
+ }
153
+ }
154
+ };
155
+ }
156
+ /**
157
+ * Schema field types for simplified schema creation
158
+ */
159
+ export const FieldTypes = {
160
+ String: { type: String },
161
+ Number: { type: Number },
162
+ Boolean: { type: Boolean },
163
+ Date: { type: Date },
164
+ ObjectId: { type: String }, // Fallback to String instead of using Schema.Types.ObjectId
165
+ // Helper functions for common field patterns
166
+ Required: (fieldType) => ({ ...fieldType, required: true }),
167
+ Unique: (fieldType) => ({ ...fieldType, unique: true }),
168
+ Ref: (model) => ({
169
+ type: String, // Fallback to String type for tests
170
+ ref: model
171
+ }),
172
+ Enum: (values) => ({ type: String, enum: values }),
173
+ Default: (fieldType, defaultValue) => ({ ...fieldType, default: defaultValue }),
174
+ // Array field type
175
+ Array: (fieldType) => ({ type: [fieldType] })
176
+ };
@@ -24,4 +24,4 @@ export declare function createApiRouter(routeConfig: Record<string, {
24
24
  method: 'get' | 'post' | 'put' | 'delete' | 'patch';
25
25
  handler: RequestHandler;
26
26
  }>, options?: RouteOptions): Router;
27
- export {};
27
+ export { Router };
@@ -0,0 +1,137 @@
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 };
@@ -0,0 +1,268 @@
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
+ }
package/dist/batch.js ADDED
@@ -0,0 +1,22 @@
1
+ export let isBatching = false;
2
+ const queue = [];
3
+ export function batchUpdates(fn) {
4
+ if (isBatching) {
5
+ queue.push(fn);
6
+ return;
7
+ }
8
+ isBatching = true;
9
+ try {
10
+ fn();
11
+ while (queue.length > 0) {
12
+ const nextFn = queue.shift();
13
+ nextFn?.();
14
+ }
15
+ }
16
+ finally {
17
+ isBatching = false;
18
+ }
19
+ }
20
+ export function getIsBatching() {
21
+ return isBatching;
22
+ }
@@ -0,0 +1 @@
1
+ "use strict";
@@ -8,7 +8,7 @@ export declare class Component {
8
8
  setState(newState: any): Promise<void>;
9
9
  private _replayEvents;
10
10
  private _deepCloneWithEvents;
11
- update(): Promise<HTMLElement | Text>;
11
+ update(): Promise<Text | HTMLElement>;
12
12
  private _updateElement;
13
13
  render(): any;
14
14
  }