frontend-hamroun 1.2.64 → 1.2.65

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 (2) hide show
  1. package/README.md +972 -216
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -13,36 +13,59 @@ A lightweight full-stack JavaScript framework with Virtual DOM and hooks impleme
13
13
 
14
14
  ## 📋 Table of Contents
15
15
 
16
- - [Installation](#installation)
17
- - [Quick Start](#quick-start)
18
- - [Basic Usage](#basic-usage)
19
- - [Features](#features)
20
- - [Client-side Features](#client-side-features)
21
- - [Server-Side Features](#server-side-features)
22
- - [Server-Side Rendering](#server-side-rendering)
23
- - [Performance Features](#performance-features)
24
- - [CLI Tools](#cli-tools)
25
- - [TypeScript Support](#typescript-support)
26
- - [Browser Compatibility](#browser-compatibility)
27
- - [Documentation](#documentation)
28
- - [License](#license)
29
-
30
- ## 🚀 Installation
16
+ - [Introduction](#-introduction)
17
+ - [Installation](#-installation)
18
+ - [Quick Start](#-quick-start)
19
+ - [Core Concepts](#-core-concepts)
20
+ - [Frontend Features](#-frontend-features)
21
+ - [Backend Features](#-backend-features)
22
+ - [API Reference](#-api-reference)
23
+ - [CLI Tools](#-cli-tools)
24
+ - [Advanced Usage](#-advanced-usage)
25
+ - [TypeScript Support](#-typescript-support)
26
+ - [Browser Compatibility](#-browser-compatibility)
27
+ - [FAQ](#-frequently-asked-questions)
28
+ - [License](#-license)
29
+
30
+ ## 🚀 Introduction
31
+
32
+ Frontend Hamroun is a lightweight (~5KB gzipped) JavaScript framework designed for building modern web applications. It combines React-like frontend development with powerful backend capabilities in a single, unified package.
33
+
34
+ ### Key Features
35
+
36
+ - **Efficient Virtual DOM**: Minimizes DOM operations with intelligent diffing
37
+ - **Complete Hooks API**: useState, useEffect, useRef, useMemo, and more
38
+ - **Full-Stack Solution**: Unified frontend and backend development
39
+ - **Server-Side Rendering**: Optimized SSR with hydration
40
+ - **File-Based Routing**: Intuitive API endpoint creation
41
+ - **Database Integration**: Built-in support for MongoDB, MySQL, PostgreSQL
42
+ - **TypeScript Support**: Full type definitions and excellent DX
43
+ - **Automatic Batch Updates**: Efficient state management
44
+ - **CLI Tooling**: Scaffolding for components, pages, and API routes
45
+
46
+ ## 📦 Installation
31
47
 
32
48
  ```bash
49
+ # Using npm
33
50
  npm install frontend-hamroun
51
+
52
+ # Using yarn
53
+ yarn add frontend-hamroun
54
+
55
+ # Using pnpm
56
+ pnpm add frontend-hamroun
34
57
  ```
35
58
 
36
59
  ## 🏁 Quick Start
37
60
 
38
61
  <details>
39
- <summary><b>Create a new project using CLI (Click to expand)</b></summary>
62
+ <summary><b>Method 1: Create a new project with CLI</b></summary>
40
63
 
41
64
  ```bash
42
- # Using the create-frontend-app command
65
+ # Using npx
43
66
  npx create-frontend-app my-app
44
67
 
45
- # Or using the frontend-hamroun CLI
68
+ # Or with the frontend-hamroun CLI
46
69
  npx frontend-hamroun create my-app
47
70
  ```
48
71
 
@@ -53,135 +76,217 @@ cd my-app
53
76
  npm install
54
77
  npm run dev
55
78
  ```
79
+
80
+ This will scaffold a new project with all the necessary configuration.
56
81
  </details>
57
82
 
58
83
  <details>
59
- <summary><b>Add to an existing project (Click to expand)</b></summary>
84
+ <summary><b>Method 2: Add to an existing project</b></summary>
60
85
 
61
86
  ```bash
62
87
  # Install the package
63
88
  npm install frontend-hamroun
64
89
 
65
- # Import components in your code
90
+ # Import and use in your code
66
91
  import { render, useState } from 'frontend-hamroun';
67
92
  ```
68
- </details>
69
-
70
- ## 💻 Basic Usage
71
93
 
72
94
  ```jsx
95
+ // Create a simple app
73
96
  import { render, useState } from 'frontend-hamroun';
74
97
 
75
- function App() {
98
+ function Counter() {
76
99
  const [count, setCount] = useState(0);
100
+
77
101
  return (
78
102
  <div>
79
103
  <h1>Count: {count}</h1>
80
- <button onClick={() => setCount(count + 1)}>Increment</button>
104
+ <button onClick={() => setCount(count + 1)}>
105
+ Increment
106
+ </button>
81
107
  </div>
82
108
  );
83
109
  }
84
110
 
85
- render(<App />, document.getElementById('root'));
111
+ render(<Counter />, document.getElementById('root'));
86
112
  ```
113
+ </details>
87
114
 
88
- ## Features
89
-
90
- <table>
91
- <tr>
92
- <td><b>🔍 Lightweight Core</b><br>Under 5KB gzipped</td>
93
- <td><b>🌐 Full-Stack</b><br>Client and server in one</td>
94
- <td><b>⚡ Virtual DOM</b><br>Efficient rendering</td>
95
- </tr>
96
- <tr>
97
- <td><b>🪝 Hooks API</b><br>Complete hook system</td>
98
- <td><b>📊 Context API</b><br>Simple state management</td>
99
- <td><b>🖥️ SSR</b><br>Server-side rendering</td>
100
- </tr>
101
- <tr>
102
- <td><b>🗄️ Database</b><br>Multiple DB support</td>
103
- <td><b>🔐 Authentication</b><br>Built-in JWT</td>
104
- <td><b>🧩 TypeScript</b><br>Full type definitions</td>
105
- </tr>
106
- </table>
107
-
108
- ## 🧩 Client-side Features
109
-
110
- <details open>
111
- <summary><b>Component Development</b></summary>
115
+ ## 🧠 Core Concepts
112
116
 
113
- ```jsx
114
- import { useState, useEffect } from 'frontend-hamroun';
117
+ ### Virtual DOM
115
118
 
116
- function Counter() {
119
+ Frontend Hamroun uses a lightweight Virtual DOM implementation to efficiently update the real DOM. It only applies the minimal necessary changes by:
120
+
121
+ ```jsx
122
+ // Virtual DOM diffing occurs automatically
123
+ function App() {
117
124
  const [count, setCount] = useState(0);
125
+ return (
126
+ <div>
127
+ <h1>Counter</h1>
128
+ <p>Count: {count}</p> {/* Only this text node updates */}
129
+ <button onClick={() => setCount(count + 1)}>Increment</button>
130
+ </div>
131
+ );
132
+ }
133
+ ```
134
+
135
+ ### Component Model
136
+
137
+ Components are the building blocks of your UI. Each component encapsulates its own logic and rendering:
138
+
139
+ ```jsx
140
+ // Function components with hooks
141
+ function Greeting({ name }) {
142
+ // State management
143
+ const [clicked, setClicked] = useState(false);
118
144
 
145
+ // Side effects
119
146
  useEffect(() => {
120
- document.title = `Count: ${count}`;
121
- return () => document.title = 'App';
122
- }, [count]);
147
+ document.title = `Hello, ${name}`;
148
+ return () => { document.title = 'App'; };
149
+ }, [name]);
123
150
 
124
151
  return (
125
- <div className="counter">
126
- <h2>Counter: {count}</h2>
127
- <button onClick={() => setCount(count + 1)}>+</button>
128
- <button onClick={() => setCount(count - 1)}>-</button>
152
+ <div>
153
+ <h1>{clicked ? `Thanks, ${name}!` : `Hello, ${name}!`}</h1>
154
+ <button onClick={() => setClicked(true)}>
155
+ {clicked ? 'Clicked!' : 'Click me'}
156
+ </button>
129
157
  </div>
130
158
  );
131
159
  }
132
160
  ```
133
- </details>
161
+
162
+ ## 🎨 Frontend Features
163
+
164
+ ### Hooks System
134
165
 
135
166
  <details>
136
- <summary><b>Hooks API</b></summary>
167
+ <summary><b>State Management with useState</b></summary>
137
168
 
138
- ### useState
139
169
  ```jsx
140
- const [count, setCount] = useState(0);
141
- setCount(count + 1); // Direct update
142
- setCount(prev => prev + 1); // Functional update
143
- ```
170
+ import { useState } from 'frontend-hamroun';
144
171
 
145
- ### useEffect
146
- ```jsx
147
- useEffect(() => {
148
- // Effect logic
149
- const subscription = api.subscribe();
172
+ function Counter() {
173
+ const [count, setCount] = useState(0);
150
174
 
151
- return () => {
152
- // Cleanup logic
153
- subscription.unsubscribe();
154
- };
155
- }, [dependency]); // Dependency array
175
+ function increment() {
176
+ setCount(count + 1);
177
+ }
178
+
179
+ function decrement() {
180
+ setCount(count - 1);
181
+ }
182
+
183
+ // Functional updates for derived state
184
+ function double() {
185
+ setCount(prevCount => prevCount * 2);
186
+ }
187
+
188
+ return (
189
+ <div>
190
+ <h2>Count: {count}</h2>
191
+ <button onClick={increment}>+</button>
192
+ <button onClick={decrement}>-</button>
193
+ <button onClick={double}>×2</button>
194
+ </div>
195
+ );
196
+ }
156
197
  ```
198
+ </details>
157
199
 
158
- ### useMemo
159
- ```jsx
160
- const expensiveValue = useMemo(() => {
161
- return computeExpensiveValue(a, b);
162
- }, [a, b]); // Recomputes only when a or b changes
163
- ```
200
+ <details>
201
+ <summary><b>Side Effects with useEffect</b></summary>
164
202
 
165
- ### useRef
166
203
  ```jsx
167
- const inputRef = useRef(null);
168
- // Access the DOM element directly
169
- useEffect(() => {
170
- inputRef.current.focus();
171
- }, []);
172
- return <input ref={inputRef} />;
204
+ import { useState, useEffect } from 'frontend-hamroun';
205
+
206
+ function UserProfile({ userId }) {
207
+ const [user, setUser] = useState(null);
208
+ const [loading, setLoading] = useState(true);
209
+
210
+ useEffect(() => {
211
+ // Reset state when userId changes
212
+ setLoading(true);
213
+
214
+ // Fetch user data
215
+ fetch(`/api/users/${userId}`)
216
+ .then(res => res.json())
217
+ .then(data => {
218
+ setUser(data);
219
+ setLoading(false);
220
+ })
221
+ .catch(err => {
222
+ console.error(err);
223
+ setLoading(false);
224
+ });
225
+
226
+ // Cleanup function runs on component unmount or before effect re-runs
227
+ return () => {
228
+ // Cancel any pending requests or subscriptions
229
+ console.log('Cleaning up effect for userId:', userId);
230
+ };
231
+ }, [userId]); // Only re-run when userId changes
232
+
233
+ if (loading) return <div>Loading...</div>;
234
+ if (!user) return <div>User not found</div>;
235
+
236
+ return (
237
+ <div>
238
+ <h1>{user.name}</h1>
239
+ <p>Email: {user.email}</p>
240
+ </div>
241
+ );
242
+ }
173
243
  ```
244
+ </details>
245
+
246
+ <details>
247
+ <summary><b>Performance Optimization with useMemo and useRef</b></summary>
174
248
 
175
- ### useErrorBoundary
176
249
  ```jsx
177
- const [error, resetError] = useErrorBoundary();
250
+ import { useState, useMemo, useRef } from 'frontend-hamroun';
178
251
 
179
- if (error) {
252
+ function ExpensiveCalculation({ items, filter }) {
253
+ const [selectedId, setSelectedId] = useState(null);
254
+
255
+ // Cache expensive calculation results, only recalculate when dependencies change
256
+ const filteredItems = useMemo(() => {
257
+ console.log('Filtering items...');
258
+ return items.filter(item => item.name.includes(filter));
259
+ }, [items, filter]);
260
+
261
+ // Create a persistent reference that doesn't trigger re-renders
262
+ const lastRenderTime = useRef(Date.now());
263
+
264
+ console.log(`Time since last render: ${Date.now() - lastRenderTime.current}ms`);
265
+ lastRenderTime.current = Date.now();
266
+
267
+ // DOM element references
268
+ const listRef = useRef(null);
269
+
270
+ function scrollToTop() {
271
+ listRef.current.scrollTop = 0;
272
+ }
273
+
180
274
  return (
181
- <div className="error-boundary">
182
- <h2>Something went wrong</h2>
183
- <p>{error.message}</p>
184
- <button onClick={resetError}>Try again</button>
275
+ <div>
276
+ <button onClick={scrollToTop}>Scroll to top</button>
277
+ <div ref={listRef} style={{ height: '200px', overflow: 'auto' }}>
278
+ {filteredItems.map(item => (
279
+ <div
280
+ key={item.id}
281
+ onClick={() => setSelectedId(item.id)}
282
+ style={{
283
+ background: item.id === selectedId ? 'lightblue' : 'white'
284
+ }}
285
+ >
286
+ {item.name}
287
+ </div>
288
+ ))}
289
+ </div>
185
290
  </div>
186
291
  );
187
292
  }
@@ -189,55 +294,176 @@ if (error) {
189
294
  </details>
190
295
 
191
296
  <details>
192
- <summary><b>Context API</b></summary>
297
+ <summary><b>Context API for State Management</b></summary>
193
298
 
194
299
  ```jsx
195
- // Create context
300
+ import { createContext, useContext, useState } from 'frontend-hamroun';
301
+
302
+ // Create a context with default value
196
303
  const ThemeContext = createContext('light');
197
304
 
198
- // Provider
199
- function App() {
305
+ // Provider component to supply context value
306
+ function ThemeProvider({ children }) {
200
307
  const [theme, setTheme] = useState('light');
201
308
 
309
+ const toggleTheme = () => {
310
+ setTheme(theme === 'light' ? 'dark' : 'light');
311
+ };
312
+
202
313
  return (
203
- <ThemeContext.Provider value={theme}>
204
- <Header />
205
- <Main />
206
- <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
207
- Toggle theme
208
- </button>
314
+ <ThemeContext.Provider value={{ theme, toggleTheme }}>
315
+ {children}
209
316
  </ThemeContext.Provider>
210
317
  );
211
318
  }
212
319
 
213
- // Consumer
214
- function Header() {
215
- const theme = useContext(ThemeContext);
216
- return <header className={theme}><h1>My App</h1></header>;
320
+ // Consumer component using the context value
321
+ function ThemedButton() {
322
+ const { theme, toggleTheme } = useContext(ThemeContext);
323
+
324
+ return (
325
+ <button
326
+ onClick={toggleTheme}
327
+ style={{
328
+ background: theme === 'light' ? '#fff' : '#333',
329
+ color: theme === 'light' ? '#333' : '#fff',
330
+ border: '1px solid #ccc',
331
+ padding: '8px 16px',
332
+ }}
333
+ >
334
+ Switch to {theme === 'light' ? 'dark' : 'light'} mode
335
+ </button>
336
+ );
337
+ }
338
+
339
+ // Usage in application
340
+ function App() {
341
+ return (
342
+ <ThemeProvider>
343
+ <div>
344
+ <h1>Themed Application</h1>
345
+ <ThemedButton />
346
+ </div>
347
+ </ThemeProvider>
348
+ );
349
+ }
350
+ ```
351
+ </details>
352
+
353
+ <details>
354
+ <summary><b>Error Boundaries with useErrorBoundary</b></summary>
355
+
356
+ ```jsx
357
+ import { useErrorBoundary } from 'frontend-hamroun';
358
+
359
+ function ErrorBoundary({ children }) {
360
+ const [error, resetError] = useErrorBoundary();
361
+
362
+ if (error) {
363
+ return (
364
+ <div className="error-boundary">
365
+ <h2>Something went wrong</h2>
366
+ <p>{error.message}</p>
367
+ <button onClick={resetError}>Try again</button>
368
+ </div>
369
+ );
370
+ }
371
+
372
+ return children;
373
+ }
374
+
375
+ // Usage
376
+ function App() {
377
+ return (
378
+ <ErrorBoundary>
379
+ <UserProfile userId="123" />
380
+ </ErrorBoundary>
381
+ );
217
382
  }
218
383
  ```
219
384
  </details>
220
385
 
221
- ## 🖥️ Server-Side Features
386
+ ### Batch Updates for Efficiency
387
+
388
+ Frontend Hamroun automatically batches state updates within event handlers and can manually batch other updates with `batchUpdates`:
389
+
390
+ ```jsx
391
+ import { useState, batchUpdates } from 'frontend-hamroun';
392
+
393
+ function Form() {
394
+ const [name, setName] = useState('');
395
+ const [email, setEmail] = useState('');
396
+ const [isSubmitting, setSubmitting] = useState(false);
397
+ const [errors, setErrors] = useState({});
398
+
399
+ async function handleSubmit(e) {
400
+ e.preventDefault();
401
+
402
+ // Group multiple state updates into a single render
403
+ batchUpdates(() => {
404
+ setSubmitting(true);
405
+ setErrors({});
406
+ });
407
+
408
+ try {
409
+ const response = await fetch('/api/users', {
410
+ method: 'POST',
411
+ headers: { 'Content-Type': 'application/json' },
412
+ body: JSON.stringify({ name, email })
413
+ });
414
+
415
+ const result = await response.json();
416
+
417
+ batchUpdates(() => {
418
+ setSubmitting(false);
419
+ setName('');
420
+ setEmail('');
421
+ });
422
+ } catch (error) {
423
+ batchUpdates(() => {
424
+ setSubmitting(false);
425
+ setErrors({ submit: error.message });
426
+ });
427
+ }
428
+ }
429
+
430
+ return (
431
+ <form onSubmit={handleSubmit}>
432
+ {/* Form fields */}
433
+ <button type="submit" disabled={isSubmitting}>
434
+ {isSubmitting ? 'Submitting...' : 'Submit'}
435
+ </button>
436
+ </form>
437
+ );
438
+ }
439
+ ```
440
+
441
+ ## 🖥️ Backend Features
442
+
443
+ ### Express Server Integration
222
444
 
223
445
  <details>
224
- <summary><b>Express Server Integration</b></summary>
446
+ <summary><b>Server Setup</b></summary>
225
447
 
226
448
  ```js
227
449
  import { server } from 'frontend-hamroun/server';
228
450
 
229
451
  const app = await server.createServer({
230
452
  port: 3000,
231
- apiDir: './api',
232
- pagesDir: './pages',
233
- staticDir: './public',
453
+ apiDir: './api', // Directory for API routes
454
+ pagesDir: './pages', // Directory for page components
455
+ staticDir: './public', // Directory for static files
456
+
457
+ // Database configuration
234
458
  db: {
235
459
  url: process.env.DATABASE_URL,
236
- type: 'mongodb' // or 'mysql', 'postgres'
460
+ type: 'mongodb' // mongodb, mysql, or postgres
237
461
  },
462
+
463
+ // Authentication configuration
238
464
  auth: {
239
465
  secret: process.env.JWT_SECRET,
240
- expiresIn: '7d'
466
+ expiresIn: '7d' // Token expiration time
241
467
  }
242
468
  });
243
469
 
@@ -246,239 +472,769 @@ console.log('Server running at http://localhost:3000');
246
472
  ```
247
473
  </details>
248
474
 
475
+ ### File-Based API Routing
476
+
249
477
  <details>
250
478
  <summary><b>API Routes</b></summary>
251
479
 
252
480
  ```js
253
- // api/users.js - Automatically mapped to /api/users
481
+ // api/users.js (automatically maps to /api/users)
254
482
  export async function get(req, res) {
483
+ // GET /api/users - List all users
255
484
  const users = await req.db.collection('users').find().toArray();
256
485
  res.json(users);
257
486
  }
258
487
 
259
488
  export async function post(req, res) {
489
+ // POST /api/users - Create a new user
260
490
  const { name, email } = req.body;
261
491
 
262
492
  if (!name || !email) {
263
493
  return res.status(400).json({ error: 'Name and email are required' });
264
494
  }
265
495
 
266
- const result = await req.db.collection('users').insertOne({ name, email });
267
- res.status(201).json(result);
496
+ const result = await req.db.collection('users').insertOne({
497
+ name,
498
+ email,
499
+ createdAt: new Date()
500
+ });
501
+
502
+ res.status(201).json({ id: result.insertedId });
268
503
  }
269
504
  ```
270
- </details>
271
-
272
- <details>
273
- <summary><b>Database Support (MongoDB, MySQL, PostgreSQL)</b></summary>
274
-
275
- #### MongoDB Example
276
505
 
277
506
  ```js
507
+ // api/users/[id].js (automatically maps to /api/users/:id)
278
508
  export async function get(req, res) {
509
+ // GET /api/users/:id - Get user by ID
510
+ const { id } = req.params;
511
+
512
+ try {
513
+ const user = await req.db.collection('users').findOne({
514
+ _id: new ObjectId(id)
515
+ });
516
+
517
+ if (!user) {
518
+ return res.status(404).json({ error: 'User not found' });
519
+ }
520
+
521
+ res.json(user);
522
+ } catch (error) {
523
+ res.status(500).json({ error: 'Server error' });
524
+ }
525
+ }
526
+
527
+ export async function put(req, res) {
528
+ // PUT /api/users/:id - Update user
529
+ const { id } = req.params;
530
+ const { name, email } = req.body;
531
+
532
+ await req.db.collection('users').updateOne(
533
+ { _id: new ObjectId(id) },
534
+ { $set: { name, email, updatedAt: new Date() } }
535
+ );
536
+
537
+ res.json({ success: true });
538
+ }
539
+
540
+ export async function del(req, res) {
541
+ // DELETE /api/users/:id - Delete user
542
+ // Note: 'delete' is a reserved word, so we use 'del'
279
543
  const { id } = req.params;
280
- const user = await req.db.collection('users').findOne({
544
+
545
+ await req.db.collection('users').deleteOne({
281
546
  _id: new ObjectId(id)
282
547
  });
283
548
 
284
- if (!user) {
285
- return res.status(404).json({ error: 'User not found' });
286
- }
549
+ res.status(204).end();
550
+ }
551
+ ```
552
+ </details>
553
+
554
+ ### Database Integration
555
+
556
+ <details>
557
+ <summary><b>MongoDB Example</b></summary>
558
+
559
+ ```js
560
+ // api/posts.js
561
+ export async function get(req, res) {
562
+ // Complex MongoDB aggregation
563
+ const posts = await req.db.collection('posts')
564
+ .aggregate([
565
+ { $match: { published: true } },
566
+ { $sort: { createdAt: -1 } },
567
+ { $limit: 10 },
568
+ { $lookup: {
569
+ from: 'users',
570
+ localField: 'authorId',
571
+ foreignField: '_id',
572
+ as: 'author'
573
+ }},
574
+ { $unwind: '$author' },
575
+ { $project: {
576
+ title: 1,
577
+ content: 1,
578
+ createdAt: 1,
579
+ 'author.name': 1,
580
+ 'author.email': 1
581
+ }}
582
+ ])
583
+ .toArray();
287
584
 
288
- res.json(user);
585
+ res.json(posts);
289
586
  }
290
587
  ```
588
+ </details>
291
589
 
292
- #### MySQL Example
590
+ <details>
591
+ <summary><b>MySQL Example</b></summary>
293
592
 
294
593
  ```js
594
+ // api/products.js
295
595
  export async function get(req, res) {
296
- const [users] = await req.db.execute(
297
- 'SELECT * FROM users WHERE active = ?',
298
- [true]
299
- );
300
- res.json(users);
596
+ // Complex SQL query with joins
597
+ const [products] = await req.db.execute(`
598
+ SELECT
599
+ p.id,
600
+ p.name,
601
+ p.price,
602
+ c.name as categoryName
603
+ FROM
604
+ products p
605
+ JOIN
606
+ categories c ON p.category_id = c.id
607
+ WHERE
608
+ p.active = ?
609
+ ORDER BY
610
+ p.created_at DESC
611
+ LIMIT 20
612
+ `, [true]);
613
+
614
+ res.json(products);
301
615
  }
302
616
  ```
617
+ </details>
303
618
 
304
- #### PostgreSQL Example
619
+ <details>
620
+ <summary><b>PostgreSQL Example</b></summary>
305
621
 
306
622
  ```js
623
+ // api/analytics.js
307
624
  export async function get(req, res) {
308
- const result = await req.db.query(
309
- 'SELECT * FROM users WHERE role = $1',
310
- ['admin']
311
- );
625
+ // Advanced PostgreSQL features
626
+ const result = await req.db.query(`
627
+ WITH monthly_sales AS (
628
+ SELECT
629
+ date_trunc('month', order_date) as month,
630
+ SUM(total_amount) as revenue
631
+ FROM
632
+ orders
633
+ WHERE
634
+ order_date > NOW() - INTERVAL '1 year'
635
+ GROUP BY
636
+ date_trunc('month', order_date)
637
+ )
638
+ SELECT
639
+ month,
640
+ revenue,
641
+ lag(revenue) OVER (ORDER BY month) as prev_month_revenue,
642
+ round((revenue - lag(revenue) OVER (ORDER BY month)) /
643
+ lag(revenue) OVER (ORDER BY month) * 100, 2) as growth_percent
644
+ FROM
645
+ monthly_sales
646
+ ORDER BY
647
+ month
648
+ `);
649
+
312
650
  res.json(result.rows);
313
651
  }
314
652
  ```
315
653
  </details>
316
654
 
655
+ ### Authentication System
656
+
317
657
  <details>
318
- <summary><b>Authentication</b></summary>
658
+ <summary><b>JWT Authentication</b></summary>
319
659
 
320
660
  ```js
321
661
  // api/auth/login.js
322
662
  export async function post(req, res) {
323
- const { username, password } = req.body;
324
- const user = await req.db.collection('users').findOne({ username });
663
+ const { email, password } = req.body;
325
664
 
326
- if (!user || !await req.auth.comparePasswords(password, user.password)) {
327
- return res.status(401).json({ error: 'Invalid credentials' });
665
+ // Validate input
666
+ if (!email || !password) {
667
+ return res.status(400).json({ error: 'Email and password are required' });
328
668
  }
329
669
 
330
- const token = req.auth.generateToken(user);
331
- res.json({
332
- token,
333
- user: { id: user._id, username: user.username }
334
- });
670
+ try {
671
+ // Find user by email
672
+ const user = await req.db.collection('users').findOne({ email });
673
+
674
+ // Check if user exists and password is correct
675
+ if (!user || !await req.auth.comparePasswords(password, user.password)) {
676
+ return res.status(401).json({ error: 'Invalid credentials' });
677
+ }
678
+
679
+ // Generate JWT token
680
+ const token = req.auth.generateToken({
681
+ id: user._id,
682
+ name: user.name,
683
+ email: user.email,
684
+ roles: user.roles || ['user']
685
+ });
686
+
687
+ // Return token and user info (excluding sensitive data)
688
+ res.json({
689
+ token,
690
+ user: {
691
+ id: user._id,
692
+ name: user.name,
693
+ email: user.email,
694
+ roles: user.roles || ['user']
695
+ }
696
+ });
697
+ } catch (error) {
698
+ res.status(500).json({ error: 'Authentication failed' });
699
+ }
335
700
  }
701
+ ```
702
+
703
+ ```js
704
+ // api/auth/register.js
705
+ export async function post(req, res) {
706
+ const { name, email, password } = req.body;
707
+
708
+ // Validate input
709
+ if (!name || !email || !password) {
710
+ return res.status(400).json({
711
+ error: 'Name, email, and password are required'
712
+ });
713
+ }
714
+
715
+ try {
716
+ // Check if user already exists
717
+ const existingUser = await req.db.collection('users').findOne({ email });
718
+ if (existingUser) {
719
+ return res.status(409).json({ error: 'Email already in use' });
720
+ }
721
+
722
+ // Hash password
723
+ const hashedPassword = await req.auth.hashPassword(password);
724
+
725
+ // Create user
726
+ const result = await req.db.collection('users').insertOne({
727
+ name,
728
+ email,
729
+ password: hashedPassword,
730
+ roles: ['user'],
731
+ createdAt: new Date()
732
+ });
733
+
734
+ res.status(201).json({
735
+ id: result.insertedId,
736
+ name,
737
+ email,
738
+ roles: ['user']
739
+ });
740
+ } catch (error) {
741
+ res.status(500).json({ error: 'Registration failed' });
742
+ }
743
+ }
744
+ ```
745
+
746
+ ```js
747
+ // middleware/requireAuth.js - Protected route middleware
748
+ export default function requireAuth(req, res, next) {
749
+ const token = req.headers.authorization?.split(' ')[1];
750
+
751
+ if (!token) {
752
+ return res.status(401).json({ error: 'Authentication required' });
753
+ }
754
+
755
+ try {
756
+ // Verify token
757
+ const decoded = req.auth.verifyToken(token);
758
+ req.user = decoded; // Attach user to request
759
+ next();
760
+ } catch (error) {
761
+ return res.status(401).json({ error: 'Invalid or expired token' });
762
+ }
763
+ }
764
+
765
+ // api/profile.js - Protected route example
766
+ import requireAuth from '../middleware/requireAuth.js';
767
+
768
+ export const middleware = [requireAuth];
336
769
 
337
- // Protected route
338
- // api/profile.js
339
770
  export async function get(req, res) {
340
- // req.auth.requireAuth middleware automatically added
341
- // req.user contains the authenticated user
342
- res.json(req.user);
771
+ // req.user is available from requireAuth middleware
772
+ const { id } = req.user;
773
+
774
+ const profile = await req.db.collection('users').findOne(
775
+ { _id: new ObjectId(id) },
776
+ { projection: { password: 0 } } // Exclude password
777
+ );
778
+
779
+ res.json(profile);
343
780
  }
344
781
  ```
345
782
  </details>
346
783
 
347
784
  ## 🔄 Server-Side Rendering
348
785
 
786
+ Frontend Hamroun provides built-in server-side rendering capabilities to improve performance and SEO:
787
+
349
788
  <details>
350
- <summary><b>SSR Implementation</b></summary>
789
+ <summary><b>Server-Side Rendering Setup</b></summary>
351
790
 
352
791
  ```jsx
353
- // Server-side
792
+ // server.js
793
+ import express from 'express';
354
794
  import { renderToString } from 'frontend-hamroun/ssr';
355
795
  import App from './App';
356
796
 
797
+ const app = express();
798
+ app.use(express.static('public'));
799
+
357
800
  app.get('*', async (req, res) => {
801
+ // Render app to string
358
802
  const html = await renderToString(<App url={req.url} />);
359
803
 
804
+ // Send complete HTML document
360
805
  res.send(`
361
806
  <!DOCTYPE html>
362
807
  <html>
363
808
  <head>
364
809
  <title>My SSR App</title>
810
+ <meta charset="UTF-8">
811
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
365
812
  <link rel="stylesheet" href="/styles.css">
366
813
  </head>
367
814
  <body>
368
815
  <div id="root">${html}</div>
369
- <script src="/client.js"></script>
816
+ <script src="/bundle.js"></script>
370
817
  </body>
371
818
  </html>
372
819
  `);
373
820
  });
374
821
 
375
- // Client-side
822
+ app.listen(3000, () => {
823
+ console.log('Server running at http://localhost:3000');
824
+ });
825
+
826
+ // client.js (for hydration)
376
827
  import { hydrate } from 'frontend-hamroun';
377
828
  import App from './App';
378
829
 
830
+ // Hydrate the app in the browser
379
831
  hydrate(<App url={window.location.pathname} />, document.getElementById('root'));
380
832
  ```
381
833
  </details>
382
834
 
383
- ## Performance Features
835
+ ## 🛠️ CLI Tools
836
+
837
+ Frontend Hamroun includes a powerful CLI for scaffolding projects, components, and API routes:
384
838
 
385
839
  <details>
386
- <summary><b>Batch Updates</b></summary>
840
+ <summary><b>Project Creation</b></summary>
387
841
 
388
- Group multiple state updates together to prevent unnecessary re-renders:
842
+ ```bash
843
+ # Create a new project with interactive prompts
844
+ npx frontend-hamroun create my-app
389
845
 
390
- ```jsx
391
- import { batchUpdates } from 'frontend-hamroun';
392
-
393
- function handleSubmit() {
394
- batchUpdates(() => {
395
- setSubmitting(true);
396
- setFormData({ name: '', email: '' });
397
- setErrors({});
398
- setSubmitCount(c => c + 1);
399
- });
400
- }
846
+ # Create with specific template
847
+ npx frontend-hamroun create my-app --template fullstack-app
401
848
  ```
402
- </details>
403
849
 
404
- ## 🛠️ CLI Tools
850
+ Available templates:
851
+ - `basic-app`: Minimal client-side setup
852
+ - `ssr-template`: Server-side rendering with hydration
853
+ - `fullstack-app`: Complete solution with frontend, backend, auth, and DB
854
+ </details>
405
855
 
406
856
  <details>
407
- <summary><b>Project Creation and Component Generation</b></summary>
857
+ <summary><b>Component Generation</b></summary>
408
858
 
409
859
  ```bash
410
- # Create a new project
411
- npx frontend-hamroun create my-app
412
-
413
860
  # Generate a new component
414
861
  npx frontend-hamroun add:component Button
415
862
 
416
- # Create a new page
417
- npx frontend-hamroun add:page Home
863
+ # Generate a TypeScript component
864
+ npx frontend-hamroun add:component UserProfile --typescript
418
865
 
419
- # Generate an API route
420
- npx frontend-hamroun add:api users --methods=get,post,put,delete
866
+ # Specify path and hooks
867
+ npx frontend-hamroun add:component Sidebar --path=src/layout --hooks=useState,useEffect
421
868
  ```
422
869
  </details>
423
870
 
424
871
  <details>
425
- <summary><b>Project Templates</b></summary>
426
-
427
- 1. **Basic App**: Client-side SPA with essential features
428
- - Quick setup
429
- - No build step in development
430
- - Perfect for learning the framework
431
-
432
- 2. **SSR Template**: Server-side rendering with hydration
433
- - SEO-friendly
434
- - Fast initial load
435
- - Express server included
436
-
437
- 3. **Fullstack App**: Complete solution with API, authentication, and database
438
- - API routes included
439
- - Database integration
440
- - Authentication ready
872
+ <summary><b>API Route Generation</b></summary>
873
+
874
+ ```bash
875
+ # Generate API route
876
+ npx frontend-hamroun add:api products
877
+
878
+ # Specify HTTP methods and auth requirement
879
+ npx frontend-hamroun add:api orders --methods=get,post --auth
880
+ ```
881
+ </details>
882
+
883
+ ## 🧩 Advanced Usage
884
+
885
+ <details>
886
+ <summary><b>Custom Hooks</b></summary>
887
+
888
+ ```jsx
889
+ import { useState, useEffect } from 'frontend-hamroun';
890
+
891
+ // Custom hook for fetching data
892
+ function useFetch(url, options = {}) {
893
+ const [data, setData] = useState(null);
894
+ const [loading, setLoading] = useState(true);
895
+ const [error, setError] = useState(null);
896
+
897
+ useEffect(() => {
898
+ setLoading(true);
899
+ setError(null);
900
+
901
+ fetch(url, options)
902
+ .then(response => {
903
+ if (!response.ok) {
904
+ throw new Error(`HTTP error ${response.status}`);
905
+ }
906
+ return response.json();
907
+ })
908
+ .then(json => {
909
+ setData(json);
910
+ setLoading(false);
911
+ })
912
+ .catch(err => {
913
+ setError(err.message);
914
+ setLoading(false);
915
+ });
916
+ }, [url]);
917
+
918
+ return { data, loading, error };
919
+ }
920
+
921
+ // Usage
922
+ function UserList() {
923
+ const { data, loading, error } = useFetch('/api/users');
924
+
925
+ if (loading) return <div>Loading...</div>;
926
+ if (error) return <div>Error: {error}</div>;
927
+
928
+ return (
929
+ <ul>
930
+ {data.map(user => (
931
+ <li key={user.id}>{user.name}</li>
932
+ ))}
933
+ </ul>
934
+ );
935
+ }
936
+ ```
937
+ </details>
938
+
939
+ <details>
940
+ <summary><b>Performance Optimization Techniques</b></summary>
941
+
942
+ ```jsx
943
+ import { useMemo, useState, useRef, batchUpdates } from 'frontend-hamroun';
944
+
945
+ function OptimizedList({ items }) {
946
+ const [filter, setFilter] = useState('');
947
+ const [sortOrder, setSortOrder] = useState('asc');
948
+ const prevItems = useRef(items);
949
+
950
+ // Memoize expensive calculations
951
+ const processedItems = useMemo(() => {
952
+ console.log('Processing items...');
953
+ let result = [...items];
954
+
955
+ // Apply filter
956
+ if (filter) {
957
+ result = result.filter(item =>
958
+ item.name.toLowerCase().includes(filter.toLowerCase())
959
+ );
960
+ }
961
+
962
+ // Apply sorting
963
+ result.sort((a, b) => {
964
+ const comparison = a.name.localeCompare(b.name);
965
+ return sortOrder === 'asc' ? comparison : -comparison;
966
+ });
967
+
968
+ return result;
969
+ }, [items, filter, sortOrder]);
970
+
971
+ // Check for changed items with more control than dependency arrays
972
+ if (items !== prevItems.current) {
973
+ console.log('Items array reference changed');
974
+ prevItems.current = items;
975
+ }
976
+
977
+ // Batch multiple state updates together
978
+ const toggleSortAndClear = () => {
979
+ batchUpdates(() => {
980
+ setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
981
+ setFilter('');
982
+ });
983
+ };
984
+
985
+ return (
986
+ <div>
987
+ <div>
988
+ <input
989
+ type="text"
990
+ value={filter}
991
+ onChange={e => setFilter(e.target.value)}
992
+ placeholder="Filter items..."
993
+ />
994
+ <button onClick={toggleSortAndClear}>
995
+ Toggle Sort ({sortOrder})
996
+ </button>
997
+ </div>
998
+
999
+ {/* Using key for efficient list rendering */}
1000
+ <ul>
1001
+ {processedItems.map(item => (
1002
+ <li key={item.id}>{item.name}</li>
1003
+ ))}
1004
+ </ul>
1005
+ </div>
1006
+ );
1007
+ }
1008
+ ```
441
1009
  </details>
442
1010
 
443
1011
  ## 📝 TypeScript Support
444
1012
 
1013
+ Frontend Hamroun is built with TypeScript and comes with complete type definitions:
1014
+
445
1015
  <details>
446
- <summary><b>Type-Safe Components</b></summary>
1016
+ <summary><b>Type-Safe Component Example</b></summary>
447
1017
 
448
1018
  ```tsx
449
- import { useState } from 'frontend-hamroun';
1019
+ import { useState, useEffect } from 'frontend-hamroun';
450
1020
 
451
- interface UserProps {
1021
+ // Define props interface
1022
+ interface UserProfileProps {
452
1023
  id: number;
453
1024
  name: string;
454
1025
  email: string;
1026
+ role: 'admin' | 'user' | 'guest';
1027
+ onStatusChange?: (id: number, active: boolean) => void;
455
1028
  }
456
1029
 
457
- function UserProfile({ id, name, email }: UserProps) {
458
- const [isEditing, setEditing] = useState(false);
1030
+ // Define state interface
1031
+ interface UserProfileState {
1032
+ isActive: boolean;
1033
+ isEditing: boolean;
1034
+ formData: {
1035
+ name: string;
1036
+ email: string;
1037
+ };
1038
+ }
1039
+
1040
+ function UserProfile({
1041
+ id,
1042
+ name,
1043
+ email,
1044
+ role,
1045
+ onStatusChange
1046
+ }: UserProfileProps) {
1047
+ // Type-safe state
1048
+ const [state, setState] = useState<UserProfileState>({
1049
+ isActive: true,
1050
+ isEditing: false,
1051
+ formData: {
1052
+ name,
1053
+ email
1054
+ }
1055
+ });
1056
+
1057
+ // Type-safe event handlers
1058
+ const toggleStatus = (): void => {
1059
+ const newStatus = !state.isActive;
1060
+ setState(prev => ({
1061
+ ...prev,
1062
+ isActive: newStatus
1063
+ }));
1064
+
1065
+ if (onStatusChange) {
1066
+ onStatusChange(id, newStatus);
1067
+ }
1068
+ };
1069
+
1070
+ // Type-safe refs
1071
+ const formRef = useRef<HTMLFormElement>(null);
459
1072
 
460
1073
  return (
461
1074
  <div className="user-profile">
462
1075
  <h2>{name}</h2>
463
- <p>{email}</p>
464
- <button onClick={() => setEditing(!isEditing)}>
465
- {isEditing ? 'Cancel' : 'Edit'}
1076
+ <p>Email: {email}</p>
1077
+ <p>Role: {role}</p>
1078
+ <p>Status: {state.isActive ? 'Active' : 'Inactive'}</p>
1079
+
1080
+ <button onClick={toggleStatus}>
1081
+ {state.isActive ? 'Deactivate' : 'Activate'}
466
1082
  </button>
1083
+
1084
+ {state.isEditing ? (
1085
+ <form ref={formRef}>
1086
+ {/* Form fields */}
1087
+ </form>
1088
+ ) : (
1089
+ <button onClick={() => setState(prev => ({
1090
+ ...prev,
1091
+ isEditing: true
1092
+ }))}>
1093
+ Edit
1094
+ </button>
1095
+ )}
467
1096
  </div>
468
1097
  );
469
1098
  }
1099
+
1100
+ // Usage with type checking
1101
+ const App = () => (
1102
+ <div>
1103
+ <UserProfile
1104
+ id={1}
1105
+ name="John Doe"
1106
+ email="john@example.com"
1107
+ role="admin"
1108
+ onStatusChange={(id, active) => {
1109
+ console.log(`User ${id} status changed to ${active}`);
1110
+ }}
1111
+ />
1112
+
1113
+ {/* This would cause TypeScript errors */}
1114
+ {/*
1115
+ <UserProfile
1116
+ id="1" // Error: string is not assignable to number
1117
+ name="Jane Doe"
1118
+ role="manager" // Error: 'manager' is not assignable
1119
+ />
1120
+ */}
1121
+ </div>
1122
+ );
1123
+ ```
1124
+ </details>
1125
+
1126
+ <details>
1127
+ <summary><b>Type-Safe API Routes</b></summary>
1128
+
1129
+ ```ts
1130
+ // types.ts
1131
+ export interface User {
1132
+ id?: string;
1133
+ name: string;
1134
+ email: string;
1135
+ password?: string;
1136
+ role: 'admin' | 'user';
1137
+ createdAt?: Date;
1138
+ updatedAt?: Date;
1139
+ }
1140
+
1141
+ // api/users/[id].ts
1142
+ import type { User } from '../../types';
1143
+
1144
+ export async function get(req, res) {
1145
+ const { id } = req.params;
1146
+
1147
+ try {
1148
+ const user = await req.db.collection('users').findOne({
1149
+ _id: new ObjectId(id)
1150
+ }) as User;
1151
+
1152
+ if (!user) {
1153
+ return res.status(404).json({ error: 'User not found' });
1154
+ }
1155
+
1156
+ // Omit sensitive data
1157
+ const { password, ...safeUser } = user;
1158
+
1159
+ res.json(safeUser);
1160
+ } catch (error) {
1161
+ res.status(500).json({ error: 'Server error' });
1162
+ }
1163
+ }
1164
+
1165
+ export async function put(req, res) {
1166
+ const { id } = req.params;
1167
+ const { name, email, role } = req.body as Partial<User>;
1168
+
1169
+ // Validate that role is a valid enum value
1170
+ if (role && !['admin', 'user'].includes(role)) {
1171
+ return res.status(400).json({ error: 'Invalid role' });
1172
+ }
1173
+
1174
+ try {
1175
+ await req.db.collection('users').updateOne(
1176
+ { _id: new ObjectId(id) },
1177
+ {
1178
+ $set: {
1179
+ ...(name && { name }),
1180
+ ...(email && { email }),
1181
+ ...(role && { role }),
1182
+ updatedAt: new Date()
1183
+ }
1184
+ }
1185
+ );
1186
+
1187
+ res.json({ success: true });
1188
+ } catch (error) {
1189
+ res.status(500).json({ error: 'Server error' });
1190
+ }
1191
+ }
470
1192
  ```
471
1193
  </details>
472
1194
 
473
1195
  ## 🌐 Browser Compatibility
474
1196
 
475
- Frontend Hamroun works in all modern browsers:
476
- - Chrome, Firefox, Safari, Edge (latest 2 versions)
477
- - IE11 with appropriate transpilation and polyfills
1197
+ Frontend Hamroun supports all modern browsers out of the box:
1198
+
1199
+ - Chrome (latest 2 versions)
1200
+ - Firefox (latest 2 versions)
1201
+ - Safari (latest 2 versions)
1202
+ - Edge (latest 2 versions)
1203
+
1204
+ For older browsers like IE11, you'll need:
1205
+ - Appropriate polyfills
1206
+ - Transpilation to ES5
1207
+ - CSS compatibility work
1208
+
1209
+ ## ❓ Frequently Asked Questions
1210
+
1211
+ <details>
1212
+ <summary><b>How does Frontend Hamroun compare to React?</b></summary>
1213
+
1214
+ Frontend Hamroun offers a React-like API but with additional built-in features:
1215
+ - Smaller bundle size (~5KB vs. 42KB+)
1216
+ - Integrated backend capabilities
1217
+ - Built-in server-side rendering
1218
+ - Database integrations
1219
+ - Authentication system
478
1220
 
479
- ## 📚 Documentation
1221
+ For React developers, the learning curve is minimal.
1222
+ </details>
480
1223
 
481
- For complete documentation, visit our [GitHub repository](https://github.com/hamroun/frontend-hamroun).
1224
+ <details>
1225
+ <summary><b>Can I use it with existing React components?</b></summary>
1226
+
1227
+ While Frontend Hamroun's API is similar to React's, they have different internal implementations. You can:
1228
+ - Port React components to Frontend Hamroun with minimal changes
1229
+ - Use React components in dedicated sections with a compatibility layer
1230
+ - Share non-UI code and logic between both frameworks
1231
+ </details>
1232
+
1233
+ <details>
1234
+ <summary><b>Does it support static site generation (SSG)?</b></summary>
1235
+
1236
+ Yes, Frontend Hamroun provides tools for static site generation. Use the `renderToString` API to pre-render pages at build time.
1237
+ </details>
482
1238
 
483
1239
  ## 📄 License
484
1240
 
@@ -487,5 +1243,5 @@ MIT © Hamroun
487
1243
  ---
488
1244
 
489
1245
  <p align="center">
490
- <a href="#-installation">⬆️ Back to top</a>
1246
+ <a href="#-table-of-contents">⬆️ Back to top</a>
491
1247
  </p>