@je-es/server 0.0.1

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/README.md ADDED
@@ -0,0 +1,802 @@
1
+ <!-- ╔══════════════════════════════ BEG ══════════════════════════════╗ -->
2
+
3
+ <br>
4
+ <div align="center">
5
+ <p>
6
+ <img src="./assets/img/logo.png" alt="logo" style="" height="70" />
7
+ </p>
8
+ </div>
9
+
10
+ <div align="center">
11
+ <img src="https://img.shields.io/badge/v-0.0.1-black"/>
12
+ <a href="https://github.com/maysara-elshewehy">
13
+ </a>
14
+ <a href="https://github.com/je-es/server"> <img src="https://img.shields.io/badge/🔥-@je--es/server-black"/> </a>
15
+ </div>
16
+
17
+ <div align="center">
18
+ <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/>
19
+ <br>
20
+ </div>
21
+
22
+ <!-- ╚═════════════════════════════════════════════════════════════════╝ -->
23
+
24
+
25
+
26
+ <!-- ╔══════════════════════════════ DOC ══════════════════════════════╗ -->
27
+
28
+ - ## Quick Start 🔥
29
+
30
+ - ### commands
31
+
32
+ ```bash
33
+ # install
34
+ space i @je-es/server
35
+
36
+ # manage
37
+ space test # run server tests
38
+ space build # build server files to ./dist
39
+ space start # start server
40
+ ```
41
+
42
+ - ### Basic Server
43
+
44
+ ```typescript
45
+ // import
46
+ import { server } from '@je-es/server';
47
+ ```
48
+
49
+ ```typescript
50
+ // create
51
+ const app = server({
52
+ port : 3000,
53
+ routes : [
54
+ {
55
+ method : 'GET',
56
+ path : '/',
57
+ handler : (c) => c.json({ message: 'Hello World!' })
58
+ },
59
+ {
60
+ method : 'GET',
61
+ path : '/users/:id',
62
+ handler : (c) => {
63
+ const userId = c.params.id;
64
+ return c.json({ userId, name: 'John Doe' });
65
+ }
66
+ },
67
+ {
68
+ method : 'POST',
69
+ path : '/users',
70
+ handler : (c) => {
71
+ const userData = c.body;
72
+ return c.json({ created: true, data: userData });
73
+ }
74
+ }
75
+ ]
76
+ });
77
+ ```
78
+
79
+ ```typescript
80
+ // start
81
+ await app.start();
82
+ ```
83
+
84
+ > use `space start` to run your server.
85
+
86
+ ```typescript
87
+ > space start
88
+
89
+ → URL: http://localhost:3000
90
+ → Environment: test
91
+ → Routes: N
92
+ ...
93
+ ```
94
+
95
+ - ### With Database
96
+
97
+ ```typescript
98
+ import { server, sqliteTable, integer, text } from '@je-es/server';
99
+
100
+ // Define your schema - all Drizzle types exported from @je-es/server!
101
+ const users = sqliteTable('users', {
102
+ id : integer('id').primaryKey(),
103
+ name : text('name').notNull(),
104
+ email : text('email').notNull()
105
+ });
106
+
107
+ const app = server({
108
+ port : 3000,
109
+ database: {
110
+ connection : './my_app.db', // File-based SQLite database
111
+ schema : { users }
112
+ },
113
+ routes : [
114
+ {
115
+ method : 'GET',
116
+ path : '/users',
117
+ handler : async (c) => {
118
+ const allUsers = await c.db.select().from(users);
119
+ return c.json(allUsers);
120
+ }
121
+ },
122
+ {
123
+ method : 'POST',
124
+ path : '/users',
125
+ handler : async (c) => {
126
+ const newUser = await c.db
127
+ .insert(users)
128
+ .values(c.body)
129
+ .returning();
130
+ return c.json(newUser[0]);
131
+ }
132
+ }
133
+ ]
134
+ });
135
+
136
+ await app.start();
137
+ // Data persists in ./my_app.db file!
138
+ ```
139
+
140
+ - ### With Security
141
+
142
+ ```typescript
143
+ import { server } from '@je-es/server';
144
+
145
+ const app = server({
146
+ port : 3000,
147
+ security: {
148
+ // Rate limiting
149
+ rateLimit: {
150
+ max : 100,
151
+ windowMs : 60000, // 1 minute
152
+ message : 'Too many requests, please try again later'
153
+ },
154
+ // CORS configuration
155
+ cors: {
156
+ origin : ['http://localhost:3000', 'https://example.com'],
157
+ credentials : true,
158
+ methods : ['GET', 'POST', 'PUT', 'DELETE'],
159
+ allowedHeaders : ['Content-Type', 'Authorization']
160
+ }
161
+ },
162
+ routes : [
163
+ {
164
+ method : 'GET',
165
+ path : '/protected',
166
+ handler : (c) => c.json({ message: 'This route is protected!' })
167
+ }
168
+ ]
169
+ });
170
+
171
+ await app.start();
172
+ ```
173
+
174
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
175
+
176
+ - ## API Reference
177
+
178
+ - ### Server Configuration
179
+
180
+ ```typescript
181
+ import { server, type ServerConfig } from '@je-es/server';
182
+
183
+ const config: ServerConfig = {
184
+ // Basic settings
185
+ port : 3000,
186
+ hostname : 'localhost',
187
+
188
+ // Request handling
189
+ requestTimeout : 30000, // 30 seconds
190
+ maxRequestSize : 10485760, // 10MB
191
+ gracefulShutdownTimeout : 10000, // 10 seconds
192
+
193
+ // Logging
194
+ logging: {
195
+ level : 'info', // 'debug' | 'info' | 'warn' | 'error'
196
+ pretty : false // Enable pretty printing
197
+ },
198
+
199
+ // Database
200
+ database: {
201
+ type : 'bun-sql',
202
+ connection : ':memory:',
203
+ schema : {}
204
+ },
205
+
206
+ // Security
207
+ security: {
208
+ rateLimit : true,
209
+ cors : true,
210
+ csrf : true
211
+ },
212
+
213
+ // Routes
214
+ routes: [],
215
+
216
+ // Lifecycle hooks
217
+ onShutdown: async () => {
218
+ console.log('Server shutting down...');
219
+ }
220
+ };
221
+
222
+ const app = server(config);
223
+ await app.start();
224
+ ```
225
+
226
+ - ### Route Definition
227
+
228
+ ```typescript
229
+ import { type RouteDefinition, type AppContext } from '@je-es/server';
230
+
231
+ // Single HTTP method
232
+ const route: RouteDefinition = {
233
+ method : 'GET',
234
+ path : '/users/:id',
235
+ handler : (c: AppContext) => {
236
+ return c.json({ id: c.params.id });
237
+ }
238
+ };
239
+
240
+ // Multiple HTTP methods
241
+ const multiMethodRoute: RouteDefinition = {
242
+ method : ['GET', 'POST'],
243
+ path : '/api/resource',
244
+ handler : (c: AppContext) => {
245
+ if (c.request.method === 'GET') {
246
+ return c.json({ method : 'GET' });
247
+ }
248
+ return c.json({ method : 'POST', body: c.body });
249
+ }
250
+ };
251
+
252
+ // Dynamic routes with nested parameters
253
+ const nestedRoute: RouteDefinition = {
254
+ method : 'GET',
255
+ path : '/posts/:postId/comments/:commentId',
256
+ handler : (c: AppContext) => {
257
+ return c.json({
258
+ postId: c.params.postId,
259
+ commentId: c.params.commentId
260
+ });
261
+ }
262
+ };
263
+ ```
264
+
265
+ - ### Context API
266
+
267
+ ```typescript
268
+ import { type AppContext } from '@je-es/server';
269
+
270
+ // Response methods
271
+ handler : (c: AppContext) => {
272
+ // JSON response
273
+ c.json({ data: 'value' }, 200);
274
+
275
+ // Text response
276
+ c.text('Hello World', 200);
277
+
278
+ // HTML response
279
+ c.html('<h1>Hello</h1>', 200);
280
+
281
+ // Redirect
282
+ c.redirect('/new-location', 302);
283
+
284
+ // File response
285
+ c.file('./path/to/file.pdf', 'application/pdf');
286
+
287
+ // Chain status
288
+ c.status(201).json({ created: true });
289
+ }
290
+
291
+ // Request data
292
+ handler : (c: AppContext) => {
293
+ const params = c.params; // URL parameters
294
+ const query = c.query; // Query string
295
+ const body = c.body; // Request body
296
+ const headers = c.headers; // Request headers
297
+ const db = c.db; // Database instance
298
+ const logger = c.logger; // Logger instance
299
+ const requestId = c.requestId; // Unique request ID
300
+ }
301
+
302
+ // Headers
303
+ handler : (c: AppContext) => {
304
+ c.setHeader('X-Custom-Header', 'value');
305
+ const auth = c.getHeader('Authorization');
306
+ }
307
+
308
+ // Cookies
309
+ handler : (c: AppContext) => {
310
+ c.setCookie('session', 'abc123', {
311
+ maxAge: 3600,
312
+ httpOnly: true,
313
+ secure: true,
314
+ sameSite: 'Strict'
315
+ });
316
+
317
+ const session = c.getCookie('session');
318
+ c.deleteCookie('session');
319
+ }
320
+ ```
321
+
322
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
323
+
324
+ - ## Security Features
325
+
326
+ - ### Rate Limiting
327
+
328
+ ```typescript
329
+ const app = server({
330
+ security: {
331
+ rateLimit: {
332
+ max: 100, // Max requests per window
333
+ windowMs: 60000, // Time window in milliseconds
334
+ keyGenerator: (c) => {
335
+ // Custom key generation (default: IP address)
336
+ return c.headers.get('x-api-key') || c.request.ip;
337
+ },
338
+ message: 'Rate limit exceeded'
339
+ }
340
+ }
341
+ });
342
+ ```
343
+
344
+ - ### CORS Configuration
345
+
346
+ ```typescript
347
+ const app = server({
348
+ security: {
349
+ cors: {
350
+ // Allow specific origins
351
+ origin: ['http://localhost:3000', 'https://example.com'],
352
+
353
+ // Or use a function
354
+ origin: (origin) => {
355
+ return origin.endsWith('.example.com');
356
+ },
357
+
358
+ // Or allow all
359
+ origin: '*',
360
+
361
+ methods: ['GET', 'POST', 'PUT', 'DELETE'],
362
+ allowedHeaders: ['Content-Type', 'Authorization'],
363
+ credentials: true,
364
+ maxAge: 86400 // 24 hours
365
+ }
366
+ }
367
+ });
368
+ ```
369
+
370
+ - ### CSRF Protection
371
+
372
+ ```typescript
373
+ import { SecurityManager } from '@je-es/server';
374
+
375
+ const security = new SecurityManager();
376
+
377
+ // Generate CSRF token
378
+ const token = security.generateCsrfToken('session-id', 3600000); // 1 hour TTL
379
+
380
+ // Validate CSRF token
381
+ const isValid = security.validateCsrfToken(token, 'session-id');
382
+ ```
383
+
384
+ - ### Input Sanitization
385
+
386
+ ```typescript
387
+ import { SecurityManager } from '@je-es/server';
388
+
389
+ const security = new SecurityManager();
390
+
391
+ // Sanitize HTML
392
+ const cleanHtml = security.sanitizeHtml('<script>alert("xss")</script>');
393
+ // Output: &lt;script&gt;alert(&quot;xss&quot;)&lt;&#x2F;script&gt;
394
+
395
+ // Sanitize SQL
396
+ const cleanSql = security.sanitizeSql("'; DROP TABLE users--");
397
+ // Output: ''; DROP TABLE users--
398
+ ```
399
+
400
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
401
+
402
+ - ## Database Support
403
+
404
+ - ### Single Database
405
+
406
+ ```typescript
407
+ import { server } from '@je-es/server';
408
+
409
+ const app = server({
410
+ database: {
411
+ connection: './my_app.db' // ✅ File-based SQLite - data persists!
412
+ // or ':memory:' for in-memory database
413
+ }
414
+ });
415
+
416
+ // Access in routes via c.db
417
+ ```
418
+
419
+ - ### Multiple Databases
420
+
421
+ ```typescript
422
+ import { server } from '@je-es/server';
423
+
424
+ const app = server({
425
+ database: [
426
+ {
427
+ name: 'default',
428
+ connection: './main.db' // Main database file
429
+ },
430
+ {
431
+ name: 'analytics',
432
+ connection: './analytics.db' // Analytics database file
433
+ }
434
+ ],
435
+ routes : [
436
+ {
437
+ method : 'GET',
438
+ path : '/data',
439
+ handler : async (c) => {
440
+ // Access default database
441
+ const users = await c.db.select().from(usersTable);
442
+
443
+ // Access named databases
444
+ const mainDb = app.db.get('default');
445
+ const analyticsDb = app.db.get('analytics');
446
+
447
+ return c.json({ users });
448
+ }
449
+ }
450
+ ]
451
+ });
452
+ ```
453
+
454
+ - ### Complete Database Example
455
+
456
+ ```typescript
457
+ import {
458
+ server,
459
+ sqliteTable,
460
+ integer,
461
+ text,
462
+ real,
463
+ eq,
464
+ and,
465
+ like
466
+ } from '@je-es/server';
467
+
468
+ // Define schema with all column types
469
+ const products = sqliteTable('products', {
470
+ id: integer('id').primaryKey(),
471
+ name: text('name').notNull(),
472
+ description: text('description'),
473
+ price: real('price').notNull(),
474
+ stock: integer('stock').default(0)
475
+ });
476
+
477
+ const app = server({
478
+ database: {
479
+ connection: './store.db',
480
+ schema: { products }
481
+ },
482
+ routes : [
483
+ {
484
+ method : 'GET',
485
+ path : '/products',
486
+ handler : async (c) => {
487
+ // Query with filters
488
+ const allProducts = await c.db
489
+ .select()
490
+ .from(products)
491
+ .where(and(
492
+ eq(products.stock, 0),
493
+ like(products.name, '%laptop%')
494
+ ));
495
+
496
+ return c.json(allProducts);
497
+ }
498
+ },
499
+ {
500
+ method : 'POST',
501
+ path : '/products',
502
+ handler : async (c) => {
503
+ // Insert new product
504
+ const newProduct = await c.db
505
+ .insert(products)
506
+ .values(c.body)
507
+ .returning();
508
+
509
+ return c.json(newProduct[0]);
510
+ }
511
+ },
512
+ {
513
+ method : 'PUT',
514
+ path : '/products/:id',
515
+ handler : async (c) => {
516
+ // Update product
517
+ const updated = await c.db
518
+ .update(products)
519
+ .set(c.body)
520
+ .where(eq(products.id, parseInt(c.params.id)))
521
+ .returning();
522
+
523
+ return c.json(updated[0]);
524
+ }
525
+ },
526
+ {
527
+ method : 'DELETE',
528
+ path : '/products/:id',
529
+ handler : async (c) => {
530
+ // Delete product
531
+ await c.db
532
+ .delete(products)
533
+ .where(eq(products.id, parseInt(c.params.id)));
534
+
535
+ return c.json({ deleted: true });
536
+ }
537
+ }
538
+ ]
539
+ });
540
+
541
+ await app.start();
542
+ // All data saved to ./store.db and persists across restarts!
543
+ ```
544
+
545
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
546
+
547
+ - ## Advanced Features
548
+
549
+ - ### Logging
550
+
551
+ ```typescript
552
+ import { server, Logger } from '@je-es/server';
553
+
554
+ // Enable logging
555
+ const app = server({
556
+ logging: {
557
+ level: 'debug', // 'debug' | 'info' | 'warn' | 'error' | 'fatal'
558
+ pretty: true // Pretty print for development
559
+ }
560
+ });
561
+
562
+ // Use logger in routes
563
+ const route = {
564
+ method : 'GET',
565
+ path : '/test',
566
+ handler : (c) => {
567
+ c.logger?.info({ userId: 123 }, 'User accessed endpoint');
568
+ c.logger?.warn({ attempt: 3 }, 'Suspicious activity');
569
+ c.logger?.error({ error: 'DB connection failed' }, 'Database error');
570
+ return c.json({ ok: true });
571
+ }
572
+ };
573
+ ```
574
+
575
+ - ### Cookie Management
576
+
577
+ ```typescript
578
+ const app = server({
579
+ routes : [
580
+ {
581
+ method : 'POST',
582
+ path : '/login',
583
+ handler : (c) => {
584
+ // Set cookie with options
585
+ c.setCookie('session', 'user-token-123', {
586
+ maxAge: 3600, // 1 hour
587
+ expires: new Date('2025-12-31'),
588
+ path : '/',
589
+ domain: 'example.com',
590
+ secure: true, // HTTPS only
591
+ httpOnly: true, // No JavaScript access
592
+ sameSite: 'Strict' // CSRF protection
593
+ });
594
+
595
+ return c.json({ loggedIn: true });
596
+ }
597
+ },
598
+ {
599
+ method : 'GET',
600
+ path : '/profile',
601
+ handler : (c) => {
602
+ const session = c.getCookie('session');
603
+ if (!session) {
604
+ return c.status(401).json({ error: 'Unauthorized' });
605
+ }
606
+ return c.json({ session });
607
+ }
608
+ },
609
+ {
610
+ method : 'POST',
611
+ path : '/logout',
612
+ handler : (c) => {
613
+ c.deleteCookie('session');
614
+ return c.json({ loggedOut: true });
615
+ }
616
+ }
617
+ ]
618
+ });
619
+ ```
620
+
621
+ - ### Dynamic Routing
622
+
623
+ ```typescript
624
+ const app = server({
625
+ routes : [
626
+ // Simple parameter
627
+ {
628
+ method : 'GET',
629
+ path : '/users/:id',
630
+ handler : (c) => c.json({ userId: c.params.id })
631
+ },
632
+
633
+ // Multiple parameters
634
+ {
635
+ method : 'GET',
636
+ path : '/posts/:postId/comments/:commentId',
637
+ handler : (c) => c.json({
638
+ postId: c.params.postId,
639
+ commentId: c.params.commentId
640
+ })
641
+ },
642
+
643
+ // Complex patterns
644
+ {
645
+ method : 'GET',
646
+ path : '/api/:version/:resource',
647
+ handler : (c) => c.json({
648
+ version: c.params.version,
649
+ resource: c.params.resource
650
+ })
651
+ }
652
+ ]
653
+ });
654
+ ```
655
+
656
+ - ### Health Checks
657
+
658
+ ```typescript
659
+ // Built-in health endpoints are automatically available
660
+
661
+ // GET /health
662
+ // Response:
663
+ {
664
+ status: 'healthy',
665
+ timestamp: '2025-11-28T10:00:00.000Z',
666
+ uptime: 3600,
667
+ activeRequests: 5
668
+ }
669
+
670
+ // GET /readiness
671
+ // Response:
672
+ {
673
+ ready: true,
674
+ checks: {
675
+ database: 'connected', // or 'not configured'
676
+ activeRequests: 5
677
+ },
678
+ timestamp: '2025-11-28T10:00:00.000Z'
679
+ }
680
+ ```
681
+
682
+ - ### Graceful Shutdown
683
+
684
+ ```typescript
685
+ const app = server({
686
+ gracefulShutdownTimeout: 10000, // 10 seconds
687
+ onShutdown: async () => {
688
+ console.log('Cleaning up resources...');
689
+ // Close external connections, flush logs, etc.
690
+ }
691
+ });
692
+
693
+ await app.start();
694
+
695
+ // Handle signals
696
+ process.on('SIGTERM', async () => {
697
+ await app.stop();
698
+ process.exit(0);
699
+ });
700
+
701
+ process.on('SIGINT', async () => {
702
+ await app.stop();
703
+ process.exit(0);
704
+ });
705
+ ```
706
+
707
+ - ### Dynamic Routes
708
+
709
+ ```typescript
710
+ const app = server({
711
+ routes : [
712
+ {
713
+ method : 'GET',
714
+ path : '/initial',
715
+ handler : (c) => c.json({ route: 'initial' })
716
+ }
717
+ ]
718
+ });
719
+
720
+ await app.start();
721
+
722
+ // Add routes after server starts
723
+ app.addRoute({
724
+ method : 'POST',
725
+ path : '/dynamic',
726
+ handler : (c) => c.json({ route: 'dynamic', body: c.body })
727
+ });
728
+
729
+ // Get all registered routes
730
+ const routes = app.getRoutes();
731
+ console.log(routes);
732
+ ```
733
+
734
+ - ### Request Timeout
735
+
736
+ ```typescript
737
+ const app = server({
738
+ requestTimeout : 5000, // 5 seconds
739
+ routes : [
740
+ {
741
+ method : 'GET',
742
+ path : '/slow',
743
+ handler : async (c) => {
744
+ // If this takes more than 5 seconds, request will timeout
745
+ await someSlowOperation();
746
+ return c.json({ done: true });
747
+ }
748
+ }
749
+ ]
750
+ });
751
+ ```
752
+
753
+ - ### Custom Error Handling
754
+
755
+ ```typescript
756
+ import { AppError, ValidationError } from '@je-es/server';
757
+
758
+ const app = server({
759
+ routes : [
760
+ {
761
+ method : 'POST',
762
+ path : '/validate',
763
+ handler : (c) => {
764
+ if (!c.body?.email) {
765
+ throw new ValidationError('Email is required');
766
+ }
767
+
768
+ if (!c.body.email.includes('@')) {
769
+ throw new AppError('Invalid email format', 400, 'INVALID_EMAIL');
770
+ }
771
+
772
+ return c.json({ valid: true });
773
+ }
774
+ }
775
+ ]
776
+ });
777
+
778
+ // Error responses are automatically formatted:
779
+ {
780
+ error : 'Email is required',
781
+ code : 'VALIDATION_ERROR',
782
+ requestId : 'unique-request-id'
783
+ }
784
+ ```
785
+
786
+ <!-- ╚═════════════════════════════════════════════════════════════════╝ -->
787
+
788
+
789
+
790
+ <!-- ╔══════════════════════════════ END ══════════════════════════════╗ -->
791
+
792
+ <br>
793
+ <div align="center">
794
+ <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/>
795
+ <br>
796
+ </div>
797
+ <br>
798
+ <div align="center">
799
+ <a href="https://github.com/solution-lib/space"><img src="https://img.shields.io/badge/by-Space-black"/></a>
800
+ </div>
801
+
802
+ <!-- ╚═════════════════════════════════════════════════════════════════╝ -->