frontend-hamroun 1.2.25 → 1.2.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -8,5 +8,10 @@ export type { Context } from './context';
8
8
  export type { VNode } from './types';
9
9
  export type { Server, ServerConfig, User, DbConfig, MiddlewareFunction } from './server-types';
10
10
  export declare const server: {
11
- getServer(): Promise<any>;
11
+ getServer(): Promise<typeof import("./server/index.js")>;
12
12
  };
13
+ export { Database } from './server/database.js';
14
+ export { AuthService } from './server/auth.js';
15
+ export { ApiRouter } from './server/api-router.js';
16
+ export { requestLogger, errorHandler, notFoundHandler, rateLimit } from './server/middleware.js';
17
+ export { Server as serverModule } from './server/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontend-hamroun",
3
- "version": "1.2.25",
3
+ "version": "1.2.26",
4
4
  "description": "A lightweight full-stack JavaScript framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -2,6 +2,14 @@ import express from 'express';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import fs from 'fs';
5
+ import dotenv from 'dotenv';
6
+ import { createServer } from 'http';
7
+ import { Server as SocketServer } from 'socket.io';
8
+ import compression from 'compression';
9
+ import cors from 'cors';
10
+
11
+ // Load environment variables
12
+ dotenv.config();
5
13
 
6
14
  // Get __dirname equivalent in ESM
7
15
  const __filename = fileURLToPath(import.meta.url);
@@ -10,9 +18,29 @@ const __dirname = path.dirname(__filename);
10
18
  // Create Express app
11
19
  const app = express();
12
20
  const PORT = process.env.PORT || 3000;
21
+ const httpServer = createServer(app);
22
+
23
+ // Create socket.io server
24
+ const io = new SocketServer(httpServer, {
25
+ cors: {
26
+ origin: process.env.NODE_ENV === 'production' ? false : '*',
27
+ methods: ['GET', 'POST']
28
+ }
29
+ });
13
30
 
14
- // Middleware
31
+ // Middleware setup
15
32
  app.use(express.json());
33
+ app.use(express.urlencoded({ extended: true }));
34
+ app.use(compression()); // Add compression for better performance
35
+ app.use(cors()); // Enable CORS for API access
36
+
37
+ // Add request logging in development mode
38
+ if (process.env.NODE_ENV !== 'production') {
39
+ app.use((req, res, next) => {
40
+ console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
41
+ next();
42
+ });
43
+ }
16
44
 
17
45
  // For production, serve the built files
18
46
  if (process.env.NODE_ENV === 'production') {
@@ -22,7 +50,22 @@ if (process.env.NODE_ENV === 'production') {
22
50
  console.log('Running in development mode - Vite handles the frontend');
23
51
  }
24
52
 
25
- // API endpoints
53
+ // Global in-memory store for demo purposes
54
+ // In a real app, you would use a database
55
+ const store = {
56
+ users: [
57
+ { id: 1, name: 'User 1', email: 'user1@example.com' },
58
+ { id: 2, name: 'User 2', email: 'user2@example.com' },
59
+ { id: 3, name: 'User 3', email: 'user3@example.com' }
60
+ ],
61
+ posts: [
62
+ { id: 1, title: 'Post 1', content: 'Content for post 1', authorId: 1 },
63
+ { id: 2, title: 'Post 2', content: 'Content for post 2', authorId: 2 },
64
+ { id: 3, title: 'Post 3', content: 'Content for post 3', authorId: 1 }
65
+ ]
66
+ };
67
+
68
+ // API endpoints - Core data
26
69
  app.get('/api/hello', (req, res) => {
27
70
  res.json({
28
71
  message: "Hello from the API!",
@@ -31,11 +74,100 @@ app.get('/api/hello', (req, res) => {
31
74
  "Server-side rendering",
32
75
  "API routes",
33
76
  "Component-based UI",
34
- "React-like development experience"
77
+ "React-like development experience",
78
+ "WebSocket integration",
79
+ "Database connectivity",
80
+ "Authentication"
35
81
  ]
36
82
  });
37
83
  });
38
84
 
85
+ // Dynamic API route loading
86
+ const apiDir = path.join(__dirname, 'api');
87
+ if (fs.existsSync(apiDir)) {
88
+ console.log('Loading API routes from directory...');
89
+
90
+ // Function to map file paths to API routes
91
+ const registerApiRoutes = async (dir, routePrefix = '') => {
92
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
93
+
94
+ for (const entry of entries) {
95
+ const fullPath = path.join(dir, entry.name);
96
+
97
+ if (entry.isDirectory()) {
98
+ // Process directories recursively
99
+ await registerApiRoutes(fullPath, `${routePrefix}/${entry.name}`);
100
+ } else if (entry.name.endsWith('.js') || entry.name.endsWith('.ts')) {
101
+ // Process API file
102
+ try {
103
+ const fileName = entry.name.replace(/\.[^/.]+$/, ""); // Remove extension
104
+ const routePath = fileName === 'index'
105
+ ? routePrefix
106
+ : fileName.startsWith('[') && fileName.endsWith(']')
107
+ ? `${routePrefix}/:${fileName.slice(1, -1)}` // Convert [param] to :param
108
+ : `${routePrefix}/${fileName}`;
109
+
110
+ // Import the route module
111
+ const module = await import(`file://${fullPath}`);
112
+
113
+ // Register handlers for HTTP methods
114
+ ['get', 'post', 'put', 'delete', 'patch'].forEach(method => {
115
+ if (typeof module[method] === 'function') {
116
+ console.log(` Registered ${method.toUpperCase()} ${routePath}`);
117
+ app[method](`/api${routePath}`, module[method]);
118
+ }
119
+ });
120
+
121
+ // Special case for 'delete' which might be named 'delete_' in some files
122
+ if (typeof module['delete_'] === 'function') {
123
+ console.log(` Registered DELETE ${routePath}`);
124
+ app.delete(`/api${routePath}`, module['delete_']);
125
+ }
126
+ } catch (err) {
127
+ console.error(`Error loading API route ${fullPath}:`, err);
128
+ }
129
+ }
130
+ }
131
+ };
132
+
133
+ // Start registering API routes
134
+ try {
135
+ await registerApiRoutes(apiDir);
136
+ console.log('API routes loaded successfully');
137
+ } catch (err) {
138
+ console.error('Error loading API routes:', err);
139
+ }
140
+ } else {
141
+ console.log('API directory not found, skipping API route loading');
142
+ }
143
+
144
+ // WebSocket setup
145
+ io.on('connection', (socket) => {
146
+ console.log('Client connected:', socket.id);
147
+
148
+ // Send welcome message
149
+ socket.emit('welcome', {
150
+ message: 'Connected to WebSocket server',
151
+ time: new Date().toISOString()
152
+ });
153
+
154
+ // Handle client messages
155
+ socket.on('message', (data) => {
156
+ console.log('Received message:', data);
157
+ // Broadcast to all clients
158
+ io.emit('broadcast', {
159
+ from: socket.id,
160
+ data,
161
+ time: new Date().toISOString()
162
+ });
163
+ });
164
+
165
+ // Handle disconnect
166
+ socket.on('disconnect', () => {
167
+ console.log('Client disconnected:', socket.id);
168
+ });
169
+ });
170
+
39
171
  // In production or if using SSR, handle all requests
40
172
  app.get('*', (req, res, next) => {
41
173
  // Skip API routes
@@ -57,9 +189,19 @@ app.get('*', (req, res, next) => {
57
189
  }
58
190
  });
59
191
 
192
+ // Error handling middleware
193
+ app.use((err, req, res, next) => {
194
+ console.error('Server error:', err);
195
+ res.status(500).json({
196
+ error: process.env.NODE_ENV === 'production'
197
+ ? 'Internal server error'
198
+ : err.message
199
+ });
200
+ });
201
+
60
202
  // Start server
61
- app.listen(PORT, () => {
62
- console.log(`API Server running on http://localhost:${PORT}`);
203
+ httpServer.listen(PORT, () => {
204
+ console.log(`Server running on http://localhost:${PORT}`);
63
205
 
64
206
  if (process.env.NODE_ENV !== 'production') {
65
207
  console.log(`Frontend development server will run on http://localhost:5173`);
@@ -1,6 +1,6 @@
1
1
  # Frontend Hamroun SSR Template
2
2
 
3
- This is a simple server-side rendering (SSR) example using Frontend Hamroun.
3
+ This is a comprehensive server-side rendering (SSR) example using Frontend Hamroun.
4
4
 
5
5
  ## Getting Started
6
6
 
@@ -16,26 +16,123 @@ This is a simple server-side rendering (SSR) example using Frontend Hamroun.
16
16
 
17
17
  3. Open your browser at http://localhost:3000
18
18
 
19
- ## How it Works
19
+ ## Core Features
20
20
 
21
- This example demonstrates:
21
+ This template demonstrates:
22
22
 
23
- - Server-side rendering of components
24
- - A simple API endpoint
25
- - Basic client-side interactivity after SSR
23
+ ### Server-Side Rendering
24
+ - Pre-rendering of components on the server
25
+ - Hydration of server-rendered content on the client
26
+ - Data fetching during server rendering
27
+ - AI-powered meta tag generation
26
28
 
27
- The server renders the component on each request using `renderToString`, then sends the HTML to the client. A small script adds interactivity to the rendered HTML on the client side.
29
+ ### API Integration
30
+ - RESTful API endpoints
31
+ - Dynamic API routing
32
+ - API middleware for validation and security
33
+ - File-based API structure
28
34
 
29
- ## Adding More Components
35
+ ### Database Integration
36
+ - MongoDB, MySQL, and PostgreSQL support
37
+ - ORM-like query interface
38
+ - Connection pooling
39
+ - Transaction support
30
40
 
31
- To add more components, create them directly in the server.js file or in separate files and import them. Use the `createElement` function from 'frontend-hamroun/jsx-runtime' to create elements.
41
+ ### Authentication & Authorization
42
+ - JWT-based authentication
43
+ - Role-based access control
44
+ - Password hashing and validation
45
+ - Token refresh mechanism
32
46
 
33
- ## Advanced Usage
47
+ ### Performance Optimization
48
+ - Caching strategies
49
+ - Response compression
50
+ - Static asset optimization
51
+ - Efficient metadata handling
34
52
 
35
- For more complex applications, you may want to:
53
+ ## Implementation Examples
36
54
 
37
- 1. Add a build step with a bundler like Vite or Webpack
38
- 2. Use a routing solution for more complex page structures
39
- 3. Set up database connections and more robust API endpoints
55
+ ### Creating Components
40
56
 
41
- Check the Frontend Hamroun documentation for more examples and advanced usage patterns.
57
+ Use the `jsx` or `createElement` function from 'frontend-hamroun':
58
+
59
+ ```jsx
60
+ import { jsx } from 'frontend-hamroun';
61
+
62
+ export default function MyComponent(props) {
63
+ return jsx('div', { className: "container" }, [
64
+ jsx('h1', {}, "Hello World"),
65
+ jsx('p', {}, `Props value: ${props.value}`)
66
+ ]);
67
+ }
68
+ ```
69
+
70
+ ### Creating API Routes
71
+
72
+ Create files in the `api` directory following this pattern:
73
+
74
+ ```typescript
75
+ // api/users/index.ts
76
+ import { Request, Response } from 'express';
77
+
78
+ export const get = (req: Request, res: Response) => {
79
+ res.json({ users: [...] });
80
+ };
81
+
82
+ export const post = (req: Request, res: Response) => {
83
+ // Create user
84
+ res.status(201).json({ success: true });
85
+ };
86
+ ```
87
+
88
+ ### Database Usage
89
+
90
+ ```typescript
91
+ import { Server } from 'frontend-hamroun/server';
92
+
93
+ const server = new Server({
94
+ db: {
95
+ url: process.env.DATABASE_URL,
96
+ type: 'mongodb' // or 'mysql', 'postgres'
97
+ }
98
+ });
99
+
100
+ // Get typed database instance
101
+ const db = server.getDatabase();
102
+ const users = await db.query('SELECT * FROM users'); // For SQL
103
+ const docs = await db.getMongoDb().collection('users').find().toArray(); // For MongoDB
104
+ ```
105
+
106
+ ### Authentication
107
+
108
+ ```typescript
109
+ import { AuthService } from 'frontend-hamroun/server';
110
+
111
+ const auth = new AuthService({
112
+ secret: process.env.JWT_SECRET,
113
+ expiresIn: '24h'
114
+ });
115
+
116
+ // Protect routes
117
+ app.get('/api/protected', auth.requireAuth(), (req, res) => {
118
+ res.json({ message: "Authenticated!" });
119
+ });
120
+
121
+ // Create user & login
122
+ const hashedPassword = await auth.hashPassword(password);
123
+ const token = auth.generateToken(user);
124
+ ```
125
+
126
+ ### Advanced Middleware
127
+
128
+ ```typescript
129
+ import { rateLimit, requestLogger, errorHandler } from 'frontend-hamroun/server';
130
+
131
+ app.use(requestLogger);
132
+ app.use('/api', rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
133
+ app.use(errorHandler);
134
+ ```
135
+
136
+ ## Next Steps
137
+
138
+ Explore the full API documentation for more advanced features and customization options.
@@ -2,6 +2,14 @@ import express from 'express';
2
2
  import { fileURLToPath } from 'url';
3
3
  import { dirname, join } from 'path';
4
4
  import { renderToString } from 'frontend-hamroun';
5
+ import { Database } from 'frontend-hamroun';
6
+ import { AuthService } from 'frontend-hamroun';
7
+ import { requestLogger, errorHandler, notFoundHandler, rateLimit } from 'frontend-hamroun';
8
+ import dotenv from 'dotenv';
9
+ import fetch from 'node-fetch';
10
+
11
+ // Load environment variables
12
+ dotenv.config();
5
13
 
6
14
  // Get directory name in ESM
7
15
  const __filename = fileURLToPath(import.meta.url);
@@ -11,6 +19,67 @@ const __dirname = dirname(__filename);
11
19
  const app = express();
12
20
  const port = process.env.PORT ? parseInt(process.env.PORT) : 3000;
13
21
 
22
+ // Add middleware
23
+ app.use(express.json());
24
+ app.use(express.urlencoded({ extended: true }));
25
+ app.use(requestLogger);
26
+
27
+ // Rate limiting for API routes
28
+ app.use('/api', rateLimit({
29
+ windowMs: 15 * 60 * 1000, // 15 minutes
30
+ max: 100 // limit each IP to 100 requests per windowMs
31
+ }));
32
+
33
+ // Configure database if connection string is provided
34
+ let db = null;
35
+ if (process.env.DATABASE_URL) {
36
+ db = new Database({
37
+ url: process.env.DATABASE_URL,
38
+ type: (process.env.DATABASE_TYPE || 'mongodb') as 'mongodb' | 'mysql' | 'postgres'
39
+ });
40
+
41
+ // Connect to database
42
+ try {
43
+ await db.connect();
44
+ console.log('Database connected successfully');
45
+ } catch (error) {
46
+ console.error('Database connection failed:', error);
47
+ }
48
+ }
49
+
50
+ // Configure auth if secret is provided
51
+ let auth = null;
52
+ if (process.env.JWT_SECRET) {
53
+ auth = new AuthService({
54
+ secret: process.env.JWT_SECRET,
55
+ expiresIn: process.env.JWT_EXPIRES_IN || '24h'
56
+ });
57
+
58
+ // Add auth middleware
59
+ app.use(auth.initialize());
60
+
61
+ // Example protected route
62
+ app.get('/api/protected', auth.requireAuth(), (req, res) => {
63
+ res.json({ message: 'Protected route accessed successfully' });
64
+ });
65
+
66
+ // Example role-based protection
67
+ app.get('/api/admin', auth.requireRoles(['admin']), (req, res) => {
68
+ res.json({ message: 'Admin route accessed successfully' });
69
+ });
70
+
71
+ // Login route
72
+ app.post('/api/login', async (req, res) => {
73
+ const { username, password } = req.body;
74
+
75
+ // In a real app, fetch user from database
76
+ const user = { id: 1, username, roles: ['user'] };
77
+ const token = auth.generateToken(user);
78
+
79
+ res.json({ token, user: { id: user.id, username: user.username, roles: user.roles } });
80
+ });
81
+ }
82
+
14
83
  // Serve static files from public directory
15
84
  app.use(express.static(join(__dirname, 'public')));
16
85
 
@@ -23,6 +92,42 @@ app.get('/api/page-data', (req, res) => {
23
92
  });
24
93
  });
25
94
 
95
+ // Meta tag generation function (using local logic for simplicity)
96
+ async function generateMetaTags(pageContent) {
97
+ // Extract title from page content
98
+ const title = pageContent.split('\n')[0].replace(/[#*]/g, '').trim() ||
99
+ 'Frontend Hamroun SSR Page';
100
+
101
+ // Generate description from content
102
+ const description = pageContent.substring(0, 150) + '...';
103
+
104
+ // Extract keywords
105
+ const keywords = pageContent
106
+ .toLowerCase()
107
+ .replace(/[^\w\s]/g, '')
108
+ .split(/\s+/)
109
+ .filter(w => w.length > 3)
110
+ .slice(0, 5)
111
+ .join(', ');
112
+
113
+ return {
114
+ title,
115
+ description,
116
+ keywords
117
+ };
118
+ }
119
+
120
+ // Helper function to check if file exists
121
+ async function fileExists(path) {
122
+ try {
123
+ const fs = await import('fs/promises');
124
+ await fs.access(path);
125
+ return true;
126
+ } catch {
127
+ return false;
128
+ }
129
+ }
130
+
26
131
  // Implement basic SSR without relying on complex server functionality
27
132
  app.get('*', async (req, res) => {
28
133
  try {
@@ -60,6 +165,17 @@ app.get('*', async (req, res) => {
60
165
  // Import the component
61
166
  const { default: PageComponent } = await import(componentPath);
62
167
 
168
+ // Generate page content for meta tags
169
+ const pageContent = `
170
+ Frontend Hamroun SSR Page
171
+ This is a server-rendered page using the Frontend Hamroun framework.
172
+ Path: ${req.path}
173
+ Timestamp: ${new Date().toISOString()}
174
+ `;
175
+
176
+ // Generate meta tags
177
+ const metaTags = await generateMetaTags(pageContent);
178
+
63
179
  // Render the component to string
64
180
  const content = renderToString(PageComponent());
65
181
 
@@ -70,15 +186,36 @@ app.get('*', async (req, res) => {
70
186
  <head>
71
187
  <meta charset="UTF-8">
72
188
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
73
- <title>Frontend Hamroun SSR</title>
189
+
190
+ <!-- Generated Meta Tags -->
191
+ <title>${metaTags.title}</title>
192
+ <meta name="description" content="${metaTags.description}">
193
+ <meta name="keywords" content="${metaTags.keywords}">
194
+
195
+ <!-- Open Graph Meta Tags -->
196
+ <meta property="og:title" content="${metaTags.title}">
197
+ <meta property="og:description" content="${metaTags.description}">
198
+ <meta property="og:type" content="website">
199
+ <meta property="og:url" content="${req.protocol}://${req.get('host')}${req.originalUrl}">
200
+
74
201
  <!-- Import Tailwind-like styles for quick styling -->
75
202
  <link href="https://cdn.jsdelivr.net/npm/daisyui@3.7.4/dist/full.css" rel="stylesheet" type="text/css" />
76
203
  <script src="https://cdn.tailwindcss.com"></script>
204
+
77
205
  <!-- Client-side script for hydration -->
78
206
  <script type="module" src="/assets/client.js"></script>
79
207
  </head>
80
208
  <body>
81
209
  <div id="app">${content}</div>
210
+
211
+ <!-- Add initial state for hydration -->
212
+ <script>
213
+ window.__INITIAL_STATE__ = ${JSON.stringify({
214
+ path: req.path,
215
+ timestamp: new Date().toISOString(),
216
+ metaTags
217
+ })};
218
+ </script>
82
219
  </body>
83
220
  </html>
84
221
  `);
@@ -100,24 +237,39 @@ app.get('*', async (req, res) => {
100
237
  }
101
238
  });
102
239
 
103
- // Helper function to check if file exists
104
- async function fileExists(path) {
105
- try {
106
- const fs = await import('fs/promises');
107
- await fs.access(path);
108
- return true;
109
- } catch {
110
- return false;
240
+ // Add error handler middleware
241
+ app.use(errorHandler);
242
+
243
+ // Add not found handler for API routes that weren't caught
244
+ app.use(notFoundHandler);
245
+
246
+ // Graceful shutdown function to close database connections
247
+ function gracefulShutdown() {
248
+ console.log('Shutting down server...');
249
+
250
+ // Close database connection if it exists
251
+ if (db) {
252
+ db.disconnect()
253
+ .then(() => console.log('Database disconnected'))
254
+ .catch(err => console.error('Error disconnecting from database:', err))
255
+ .finally(() => process.exit(0));
256
+ } else {
257
+ process.exit(0);
111
258
  }
112
259
  }
113
260
 
114
261
  // Start the server
115
- app.listen(port, () => {
262
+ const server = app.listen(port, () => {
116
263
  console.log(`Server running at http://localhost:${port}`);
264
+ console.log(`Available API endpoints:`);
265
+ console.log(` - GET /api/page-data`);
266
+ console.log(` - POST /api/login`);
267
+ if (process.env.JWT_SECRET) {
268
+ console.log(` - GET /api/protected (requires authentication)`);
269
+ console.log(` - GET /api/admin (requires admin role)`);
270
+ }
117
271
  });
118
272
 
119
273
  // Handle graceful shutdown
120
- process.on('SIGINT', () => {
121
- console.log('Shutting down server...');
122
- process.exit(0);
123
- });
274
+ process.on('SIGINT', gracefulShutdown);
275
+ process.on('SIGTERM', gracefulShutdown);