@asenajs/ergenecore 1.0.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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +636 -0
  3. package/dist/index.d.ts +24 -0
  4. package/dist/index.js +25 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/lib/Ergenecore.d.ts +598 -0
  7. package/dist/lib/Ergenecore.js +1406 -0
  8. package/dist/lib/Ergenecore.js.map +1 -0
  9. package/dist/lib/ErgenecoreContextWrapper.d.ts +190 -0
  10. package/dist/lib/ErgenecoreContextWrapper.js +387 -0
  11. package/dist/lib/ErgenecoreContextWrapper.js.map +1 -0
  12. package/dist/lib/ErgenecoreWebsocketAdapter.d.ts +68 -0
  13. package/dist/lib/ErgenecoreWebsocketAdapter.js +233 -0
  14. package/dist/lib/ErgenecoreWebsocketAdapter.js.map +1 -0
  15. package/dist/lib/defaults/ConfigService.d.ts +39 -0
  16. package/dist/lib/defaults/ConfigService.js +30 -0
  17. package/dist/lib/defaults/ConfigService.js.map +1 -0
  18. package/dist/lib/defaults/MiddlewareService.d.ts +39 -0
  19. package/dist/lib/defaults/MiddlewareService.js +30 -0
  20. package/dist/lib/defaults/MiddlewareService.js.map +1 -0
  21. package/dist/lib/defaults/StaticServeService.d.ts +24 -0
  22. package/dist/lib/defaults/StaticServeService.js +23 -0
  23. package/dist/lib/defaults/StaticServeService.js.map +1 -0
  24. package/dist/lib/defaults/ValidationService.d.ts +35 -0
  25. package/dist/lib/defaults/ValidationService.js +34 -0
  26. package/dist/lib/defaults/ValidationService.js.map +1 -0
  27. package/dist/lib/defaults/index.d.ts +25 -0
  28. package/dist/lib/defaults/index.js +26 -0
  29. package/dist/lib/defaults/index.js.map +1 -0
  30. package/dist/lib/errors.d.ts +84 -0
  31. package/dist/lib/errors.js +108 -0
  32. package/dist/lib/errors.js.map +1 -0
  33. package/dist/lib/index.d.ts +36 -0
  34. package/dist/lib/index.js +37 -0
  35. package/dist/lib/index.js.map +1 -0
  36. package/dist/lib/middlewares/CorsMiddleware.d.ts +147 -0
  37. package/dist/lib/middlewares/CorsMiddleware.js +163 -0
  38. package/dist/lib/middlewares/CorsMiddleware.js.map +1 -0
  39. package/dist/lib/middlewares/RateLimiterMiddleware.d.ts +330 -0
  40. package/dist/lib/middlewares/RateLimiterMiddleware.js +300 -0
  41. package/dist/lib/middlewares/RateLimiterMiddleware.js.map +1 -0
  42. package/dist/lib/types/Handler.d.ts +65 -0
  43. package/dist/lib/types/Handler.js +10 -0
  44. package/dist/lib/types/Handler.js.map +1 -0
  45. package/dist/lib/types/StaticServeExtras.d.ts +29 -0
  46. package/dist/lib/types/StaticServeExtras.js +2 -0
  47. package/dist/lib/types/StaticServeExtras.js.map +1 -0
  48. package/dist/lib/types/Validation.d.ts +7 -0
  49. package/dist/lib/types/Validation.js +2 -0
  50. package/dist/lib/types/Validation.js.map +1 -0
  51. package/dist/lib/types/index.d.ts +24 -0
  52. package/dist/lib/types/index.js +25 -0
  53. package/dist/lib/types/index.js.map +1 -0
  54. package/dist/lib/utils/factory.d.ts +121 -0
  55. package/dist/lib/utils/factory.js +144 -0
  56. package/dist/lib/utils/factory.js.map +1 -0
  57. package/package.json +47 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 AsenaJs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,636 @@
1
+ # Asena Ergenecore Adapter
2
+
3
+ **Blazing-fast** native Bun adapter for [Asena.js](https://github.com/asenajs/asena) - Built exclusively with Bun's native APIs for maximum performance.
4
+
5
+ ## Features
6
+
7
+ - ⚡ **Zero Dependencies** - Pure Bun native APIs (except Zod for validation)
8
+ - 🚀 **SIMD-Accelerated** - Native Bun router with SIMD-optimized matching
9
+ - 🔌 **Full Adapter Support** - HTTP, WebSocket, Middleware, Validation
10
+ - 📦 **Zero-Copy File Serving** - Bun.file() for optimal static file performance
11
+ - 🎯 **TypeScript First** - Complete type safety out of the box
12
+ - ✅ **90%+ Test Coverage** - Thoroughly tested with 212+ passing tests
13
+ - 🧩 **Controller-Based** - Seamless integration with Asena decorators
14
+
15
+ ## Why Ergenecore?
16
+
17
+ Ergenecore is the **fastest** Asena adapter because it:
18
+ - Uses Bun's **native HTTP server** (`Bun.serve()`)
19
+ - Leverages **SIMD-accelerated routing** (no framework overhead)
20
+ - Implements **zero-copy** file serving with `Bun.file()`
21
+ - Has **no external dependencies** (except Zod)
22
+ - Is optimized for **Bun runtime** exclusively
23
+
24
+ ## Requirements
25
+
26
+ - [Bun](https://bun.sh) v1.3 or higher
27
+ - TypeScript v5.8.2 or higher
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ bun add @asenajs/ergenecore
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ### Basic Server Setup
38
+
39
+ ```typescript
40
+ import { AsenaServerFactory } from '@asenajs/asena';
41
+ import { createErgenecoreAdapter } from '@asenajs/ergenecore/factory';
42
+ import { logger } from './logger';
43
+
44
+ // Create adapter with factory function
45
+ const adapter = createErgenecoreAdapter();
46
+
47
+ // Create and start server
48
+ const server = await AsenaServerFactory.create({
49
+ adapter,
50
+ logger,
51
+ port: 3000
52
+ });
53
+
54
+ await server.start();
55
+ ```
56
+
57
+ ### Controller Example
58
+
59
+ ```typescript
60
+ import { Controller } from '@asenajs/asena/server';
61
+ import { Get, Post, Put, Delete } from '@asenajs/asena/web';
62
+ import type { Context } from '@asenajs/ergenecore/types';
63
+
64
+ @Controller('/users')
65
+ export class UserController {
66
+ @Get({ path: '/' })
67
+ async list(context: Context) {
68
+ const page = context.getQuery('page') || '1';
69
+ return context.send({ users: [], page });
70
+ }
71
+
72
+ @Get({ path: '/:id' })
73
+ async getById(context: Context) {
74
+ const id = context.getParam('id');
75
+ return context.send({ id, name: 'John Doe' });
76
+ }
77
+
78
+ @Post({ path: '/' })
79
+ async create(context: Context) {
80
+ const body = await context.getBody();
81
+ return context.send({ created: true, data: body }, 201);
82
+ }
83
+
84
+ @Put({ path: '/:id' })
85
+ async update(context: Context) {
86
+ const id = context.getParam('id');
87
+ const body = await context.getBody();
88
+ return context.send({ id, updated: true, data: body });
89
+ }
90
+
91
+ @Delete({ path: '/:id' })
92
+ async delete(context: Context) {
93
+ const id = context.getParam('id');
94
+ return context.send({ id, deleted: true }, 204);
95
+ }
96
+ }
97
+ ```
98
+
99
+ ## API Documentation
100
+
101
+ ### Factory Functions
102
+
103
+ #### `createErgenecoreAdapter(options?)`
104
+
105
+ Creates a new Ergenecore adapter instance with optional configuration.
106
+
107
+ ```typescript
108
+ import { AsenaServerFactory } from '@asenajs/asena';
109
+ import { createErgenecoreAdapter } from '@asenajs/ergenecore/factory';
110
+ import { logger } from './logger';
111
+
112
+ const adapter = createErgenecoreAdapter({
113
+ hostname: 'localhost',
114
+ enableWebSocket: true
115
+ });
116
+
117
+ const server = await AsenaServerFactory.create({
118
+ adapter,
119
+ logger,
120
+ port: 3000
121
+ });
122
+
123
+ await server.start();
124
+ ```
125
+
126
+ **Options:**
127
+ - `hostname` (string): Server hostname (default: undefined)
128
+ - `logger` (ServerLogger): Custom logger instance (optional, can be passed to AsenaServerFactory)
129
+ - `enableWebSocket` (boolean): Enable WebSocket support (default: true)
130
+ - `websocketAdapter` (ErgenecoreWebsocketAdapter): Custom WebSocket adapter
131
+
132
+ #### `createProductionAdapter(options?)`
133
+
134
+ Creates a production-optimized adapter with sensible defaults.
135
+
136
+ ```typescript
137
+ import { AsenaServerFactory } from '@asenajs/asena';
138
+ import { createProductionAdapter } from '@asenajs/ergenecore/factory';
139
+ import { logger } from './logger';
140
+
141
+ const adapter = createProductionAdapter({
142
+ hostname: '0.0.0.0'
143
+ });
144
+
145
+ const server = await AsenaServerFactory.create({
146
+ adapter,
147
+ logger,
148
+ port: 8080
149
+ });
150
+
151
+ await server.start();
152
+ ```
153
+
154
+ #### `createDevelopmentAdapter(options?)`
155
+
156
+ Creates a development-friendly adapter with verbose logging.
157
+
158
+ ```typescript
159
+ import { AsenaServerFactory } from '@asenajs/asena';
160
+ import { createDevelopmentAdapter } from '@asenajs/ergenecore/factory';
161
+ import { logger } from './logger';
162
+
163
+ const adapter = createDevelopmentAdapter();
164
+
165
+ const server = await AsenaServerFactory.create({
166
+ adapter,
167
+ logger,
168
+ port: 3000
169
+ });
170
+
171
+ await server.start();
172
+ ```
173
+
174
+ ### Context API
175
+
176
+ The `Context` type provides access to request/response handling:
177
+
178
+ ```typescript
179
+ import type { Context } from '@asenajs/ergenecore/types';
180
+
181
+ // Get route parameters
182
+ const id = context.getParam('id');
183
+
184
+ // Get query parameters
185
+ const page = context.getQuery('page');
186
+
187
+ // Get request body
188
+ const body = await context.getBody();
189
+
190
+ // Get headers
191
+ const auth = context.getHeader('authorization');
192
+
193
+ // Send JSON response
194
+ return context.send({ success: true });
195
+
196
+ // Send with custom status
197
+ return context.send({ error: 'Not found' }, 404);
198
+
199
+ // Set cookies
200
+ context.setCookie('session', 'abc123', {
201
+ httpOnly: true,
202
+ secure: true,
203
+ maxAge: 86400
204
+ });
205
+
206
+ // Get cookies
207
+ const session = context.getCookie('session');
208
+ ```
209
+
210
+ ### Middleware
211
+
212
+ #### Custom Middleware
213
+
214
+ ```typescript
215
+ import { Middleware } from '@asenajs/asena/server';
216
+ import { Controller } from '@asenajs/asena/server';
217
+ import { Get } from '@asenajs/asena/web';
218
+ import { MiddlewareService, type Context } from '@asenajs/ergenecore';
219
+
220
+ @Middleware()
221
+ export class AuthMiddleware extends MiddlewareService {
222
+ async handle(context: Context, next: () => Promise<void>): Promise<any> {
223
+ const token = context.getHeader('authorization');
224
+
225
+ if (!token) {
226
+ return context.send({ error: 'Unauthorized' }, 401);
227
+ }
228
+
229
+ // Verify token and set user in context
230
+ context.setValue('userId', 123);
231
+
232
+ await next();
233
+ }
234
+ }
235
+
236
+ // Use in route
237
+ @Controller('/admin')
238
+ export class AdminController {
239
+ @Get({ path: '/dashboard', middlewares: [AuthMiddleware] })
240
+ async dashboard(context: Context) {
241
+ const userId = context.getValue('userId');
242
+ return context.send({ userId, message: 'Admin dashboard' });
243
+ }
244
+ }
245
+ ```
246
+
247
+ #### CORS Middleware
248
+
249
+ Ergenecore comes with a built-in CORS middleware for handling Cross-Origin Resource Sharing:
250
+
251
+ ```typescript
252
+ import { Middleware } from '@asenajs/asena/server';
253
+ import { CorsMiddleware } from '@asenajs/ergenecore';
254
+
255
+ // Allow all origins (default)
256
+ @Middleware()
257
+ export class GlobalCors extends CorsMiddleware {
258
+ constructor() {
259
+ super(); // Defaults to { origin: '*' }
260
+ }
261
+ }
262
+
263
+ // Whitelist specific origins
264
+ @Middleware()
265
+ export class RestrictedCors extends CorsMiddleware {
266
+ constructor() {
267
+ super({
268
+ origin: ['https://example.com', 'https://app.example.com'],
269
+ credentials: true,
270
+ methods: ['GET', 'POST', 'PUT', 'DELETE'],
271
+ allowedHeaders: ['Content-Type', 'Authorization'],
272
+ exposedHeaders: ['X-Total-Count'],
273
+ maxAge: 86400 // 24 hours
274
+ });
275
+ }
276
+ }
277
+
278
+ // Dynamic origin validation
279
+ @Middleware()
280
+ export class DynamicCors extends CorsMiddleware {
281
+ constructor() {
282
+ super({
283
+ origin: (origin: string) => {
284
+ // Allow all subdomains of example.com
285
+ return origin.endsWith('.example.com') || origin === 'https://example.com';
286
+ },
287
+ credentials: true
288
+ });
289
+ }
290
+ }
291
+
292
+ // Use globally in server config
293
+ import { Config } from '@asenajs/asena/server';
294
+ import { ConfigService } from '@asenajs/ergenecore';
295
+
296
+ @Config()
297
+ export class ServerConfig extends ConfigService {
298
+ middlewares = [GlobalCors];
299
+ }
300
+
301
+ // Or use per-route
302
+ @Controller('/api')
303
+ export class ApiController {
304
+ @Get({ path: '/public', middlewares: [RestrictedCors] })
305
+ async publicData(context: Context) {
306
+ return context.send({ data: 'public' });
307
+ }
308
+ }
309
+ ```
310
+
311
+ #### Rate Limiter Middleware
312
+
313
+ Ergenecore includes a Token Bucket-based rate limiter for controlling request rates and preventing abuse:
314
+
315
+ ```typescript
316
+ import { Middleware } from '@asenajs/asena/server';
317
+ import { RateLimiterMiddleware } from '@asenajs/ergenecore';
318
+
319
+ // General API rate limiter (100 requests per minute)
320
+ @Middleware()
321
+ export class ApiRateLimiter extends RateLimiterMiddleware {
322
+ constructor() {
323
+ super({
324
+ capacity: 100,
325
+ refillRate: 100 / 60, // 100 requests per minute
326
+ });
327
+ }
328
+ }
329
+
330
+ // Strict rate limiter for sensitive endpoints (5 requests per minute)
331
+ @Middleware()
332
+ export class StrictRateLimiter extends RateLimiterMiddleware {
333
+ constructor() {
334
+ super({
335
+ capacity: 5,
336
+ refillRate: 5 / 60, // 5 requests per minute
337
+ message: 'Too many login attempts. Please try again later.',
338
+ });
339
+ }
340
+ }
341
+
342
+ // Custom rate limiter with advanced options
343
+ @Middleware()
344
+ export class CustomRateLimiter extends RateLimiterMiddleware {
345
+ constructor() {
346
+ super({
347
+ capacity: 50,
348
+ refillRate: 50 / 60, // 50 requests per minute
349
+
350
+ // Rate limit by user ID instead of IP
351
+ keyGenerator: (ctx) => ctx.state.user?.id || 'anonymous',
352
+
353
+ // Skip rate limiting for admin users
354
+ skip: (ctx) => ctx.state.user?.role === 'admin',
355
+
356
+ // Expensive operations cost more tokens
357
+ cost: (ctx) => ctx.req.url.includes('/search') ? 5 : 1,
358
+
359
+ // Custom response message
360
+ message: 'Rate limit exceeded. Please slow down.',
361
+ statusCode: 429,
362
+
363
+ // Cleanup inactive buckets every 60 seconds
364
+ cleanupInterval: 60000,
365
+ bucketTTL: 600000 // 10 minutes
366
+ });
367
+ }
368
+ }
369
+
370
+ // Use globally
371
+ import { Config } from '@asenajs/asena/server';
372
+ import { ConfigService } from '@asenajs/ergenecore';
373
+
374
+ @Config()
375
+ export class ServerConfig extends ConfigService {
376
+ middlewares = [ApiRateLimiter];
377
+ }
378
+
379
+ // Use per-controller
380
+ @Controller('/api', { middlewares: [ApiRateLimiter] })
381
+ export class ApiController {
382
+ @Get({ path: '/users' })
383
+ async getUsers(context: Context) {
384
+ return context.send({ users: [] });
385
+ }
386
+ }
387
+
388
+ // Use per-route for stricter limits
389
+ @Controller('/auth')
390
+ export class AuthController {
391
+ @Post({ path: '/login', middlewares: [StrictRateLimiter] })
392
+ async login(context: Context) {
393
+ const body = await context.getBody();
394
+ // Login logic
395
+ return context.send({ token: 'abc123' });
396
+ }
397
+ }
398
+ ```
399
+
400
+ **Rate Limiter Options:**
401
+ - `capacity` (number): Maximum token bucket size (burst capacity). Default: `100`
402
+ - `refillRate` (number): Tokens refilled per second. Default: `10`
403
+ - `keyGenerator` (function): Custom function to identify clients. Default: IP-based
404
+ - `message` (string): Custom error message. Default: `'Rate limit exceeded. Please try again later.'`
405
+ - `statusCode` (number): HTTP status code for rate limit response. Default: `429`
406
+ - `cost` (number | function): Token cost per request. Default: `1`
407
+ - `skip` (function): Skip rate limiting based on context. Default: `undefined`
408
+ - `cleanupInterval` (number): Cleanup interval in milliseconds. Default: `60000` (1 minute)
409
+ - `bucketTTL` (number): Inactive bucket TTL in milliseconds. Default: `600000` (10 minutes)
410
+
411
+ **Rate Limit Headers:**
412
+
413
+ The middleware automatically sets standard rate limit headers:
414
+ - `X-RateLimit-Limit`: Requests allowed per minute
415
+ - `X-RateLimit-Remaining`: Remaining tokens in bucket
416
+ - `X-RateLimit-Reset`: Unix timestamp when bucket resets
417
+ - `Retry-After`: Seconds to wait before retrying (on 429 response)
418
+
419
+ ### Validation with Zod
420
+
421
+ ```typescript
422
+ import { Controller, Middleware } from '@asenajs/asena/server';
423
+ import { Post } from '@asenajs/asena/web';
424
+ import { ValidationService, type Context, type ValidationSchema, type ValidationSchemaWithHook } from '@asenajs/ergenecore';
425
+ import { z } from 'zod';
426
+
427
+ @Middleware({ validator: true })
428
+ export class CreateUserValidator extends ValidationService {
429
+ json(): ValidationSchema | ValidationSchemaWithHook {
430
+ return z.object({
431
+ name: z.string({ message: 'Name is required' }).min(3).max(50),
432
+ email: z.string({ message: 'Email is required' }).email(),
433
+ age: z.number({ message: 'Age must be a number' }).min(18)
434
+ });
435
+ }
436
+ }
437
+
438
+ // Use in controller
439
+ @Controller('/users')
440
+ export class UserController {
441
+ @Post({ path: '/', validator: CreateUserValidator })
442
+ async create(context: Context) {
443
+ const body = await context.getBody();
444
+
445
+ // Body is automatically validated by Asena
446
+ return context.send({ created: true, data: body }, 201);
447
+ }
448
+ }
449
+ ```
450
+
451
+ **Note:** The `json()` method can also return a Promise for async validation schemas:
452
+ ```typescript
453
+ async json(): Promise<ValidationSchema | ValidationSchemaWithHook> {
454
+ return z.object({ /* schema */ });
455
+ }
456
+ ```
457
+
458
+ ### Combining Middleware and Validation
459
+
460
+ ```typescript
461
+ import { Controller, Middleware } from '@asenajs/asena/server';
462
+ import { Post } from '@asenajs/asena/web';
463
+ import {
464
+ MiddlewareService,
465
+ ValidationService,
466
+ type Context,
467
+ type ValidationSchema,
468
+ type ValidationSchemaWithHook
469
+ } from '@asenajs/ergenecore';
470
+ import { z } from 'zod';
471
+
472
+ // Authentication middleware
473
+ @Middleware()
474
+ export class AuthMiddleware extends MiddlewareService {
475
+ async handle(context: Context, next: () => Promise<void>): Promise<any> {
476
+ const token = context.getHeader('authorization');
477
+
478
+ if (!token) {
479
+ return context.send({ error: 'Unauthorized' }, 401);
480
+ }
481
+
482
+ // Verify token and set user
483
+ context.setValue('userId', 123);
484
+ await next();
485
+ }
486
+ }
487
+
488
+ // Validation
489
+ @Middleware({ validator: true })
490
+ export class CreatePostValidator extends ValidationService {
491
+ json(): ValidationSchema | ValidationSchemaWithHook {
492
+ return z.object({
493
+ title: z.string({ message: 'Title is required' }).min(5),
494
+ content: z.string({ message: 'Content is required' }).min(10)
495
+ });
496
+ }
497
+ }
498
+
499
+ // Controller with both middleware and validation
500
+ @Controller('/posts')
501
+ export class PostController {
502
+ @Post({ path: '/', validator: CreatePostValidator, middlewares: [AuthMiddleware] })
503
+ async create(context: Context) {
504
+ const body = await context.getBody();
505
+ const userId = context.getValue('userId');
506
+
507
+ return context.send({
508
+ created: true,
509
+ userId,
510
+ post: body
511
+ }, 201);
512
+ }
513
+ }
514
+ ```
515
+
516
+ ### WebSocket Support
517
+
518
+ ```typescript
519
+ import { WebSocket } from '@asenajs/asena/web-socket';
520
+ import { AsenaWebSocketService } from '@asenajs/asena/web-socket';
521
+ import type { Socket } from '@asenajs/asena/web-socket';
522
+
523
+ @WebSocket({ path: '/chat', name: 'ChatSocket' })
524
+ export class ChatSocket extends AsenaWebSocketService<void> {
525
+ protected async onOpen(ws: Socket<void>): Promise<void> {
526
+ console.log('Client connected:', ws.id);
527
+ ws.send('Welcome to chat!');
528
+ }
529
+
530
+ protected async onMessage(ws: Socket<void>, message: string): Promise<void> {
531
+ console.log('Received:', message);
532
+
533
+ // Echo message back to client
534
+ ws.send(`Echo: ${message}`);
535
+ }
536
+
537
+ protected async onClose(ws: Socket<void>): Promise<void> {
538
+ console.log('Client disconnected:', ws.id);
539
+ }
540
+ }
541
+ ```
542
+
543
+ ### Static File Serving
544
+
545
+ ```typescript
546
+ import { Controller } from '@asenajs/asena/server';
547
+ import { Get } from '@asenajs/asena/web';
548
+ import { StaticServe, StaticServeService } from '@asenajs/asena/static';
549
+ import type { Context } from '@asenajs/ergenecore/types';
550
+
551
+ @StaticServe({ root: './public' })
552
+ export class StaticMiddleware extends StaticServeService {
553
+ rewriteRequestPath(path: string): string {
554
+ return path.replace(/^\/static\/|^static\//, '');
555
+ }
556
+
557
+ onFound(_path: string, _context: Context): void | Promise<void> {
558
+ console.log('File found and served');
559
+ }
560
+
561
+ onNotFound(path: string, context: Context): void | Promise<void> {
562
+ console.log(`${path} not found, requested: ${context.getRequest().url}`);
563
+ }
564
+ }
565
+
566
+ // Use in controller
567
+ @Controller('/static')
568
+ export class StaticController {
569
+ @Get({ path: '/*', staticServe: StaticMiddleware })
570
+ static() {}
571
+ }
572
+ ```
573
+
574
+ ### Error Handling
575
+
576
+ ```typescript
577
+ import { Config } from '@asenajs/asena/server';
578
+ import { Inject } from '@asenajs/asena/ioc';
579
+ import { ConfigService, type Context } from '@asenajs/ergenecore';
580
+
581
+ @Config()
582
+ export class ServerConfig extends ConfigService {
583
+ onError(error: Error, context: Context): Response | Promise<Response> {
584
+ console.error('Error:', error);
585
+
586
+ return context.send({
587
+ error: error.message,
588
+ stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
589
+ }, 500);
590
+ }
591
+ }
592
+
593
+ // With custom exception mapper
594
+ export class ExceptionMapper {
595
+ map(error: Error, context: Context): Response {
596
+ // Custom error mapping logic
597
+ if (error.name === 'ValidationError') {
598
+ return context.send({ error: 'Validation failed', details: error.message }, 400);
599
+ }
600
+
601
+ return context.send({ error: 'Internal server error' }, 500);
602
+ }
603
+ }
604
+
605
+ @Config()
606
+ export class ServerConfigWithMapper extends ConfigService {
607
+ @Inject('ExceptionMapper')
608
+ private mapper: ExceptionMapper;
609
+
610
+ onError(error: Error, context: Context): Response | Promise<Response> {
611
+ return this.mapper.map(error, context);
612
+ }
613
+ }
614
+ ```
615
+
616
+ ## Contributing
617
+
618
+ Contributions are welcome! Please follow these guidelines:
619
+
620
+ 1. Follow the **TDD approach** (write tests first)
621
+ 2. Maintain **90%+ test coverage**
622
+ 3. Use **Bun native APIs** only (no external dependencies except Zod)
623
+ 4. Follow the **CLAUDE.md** guidelines
624
+
625
+ ## License
626
+
627
+ MIT License - see [LICENSE](LICENSE) for details.
628
+
629
+ ## Related Projects
630
+
631
+ - [Asena.js](https://asena.sh) - The core framework
632
+ - [Bun](https://bun.sh) - The JavaScript runtime
633
+
634
+ ---
635
+
636
+ **Built with ⚡ by the Asena.js team**
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Asena Ergenecore - Native Bun Adapter for Asenajs
3
+ *
4
+ * This is the main entry point for the package.
5
+ * All public APIs are exported from lib/index.ts
6
+ *
7
+ * @module @asenajs/ergenecore
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { AsenaServerFactory } from '@asenajs/asena';
12
+ * import { createErgenecoreAdapter, type Context } from '@asenajs/ergenecore';
13
+ *
14
+ * const adapter = createErgenecoreAdapter();
15
+ * const server = await AsenaServerFactory.create({
16
+ * adapter,
17
+ * logger,
18
+ * port: 3000
19
+ * });
20
+ *
21
+ * await server.start();
22
+ * ```
23
+ */
24
+ export * from './lib/index';
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Asena Ergenecore - Native Bun Adapter for Asenajs
3
+ *
4
+ * This is the main entry point for the package.
5
+ * All public APIs are exported from lib/index.ts
6
+ *
7
+ * @module @asenajs/ergenecore
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { AsenaServerFactory } from '@asenajs/asena';
12
+ * import { createErgenecoreAdapter, type Context } from '@asenajs/ergenecore';
13
+ *
14
+ * const adapter = createErgenecoreAdapter();
15
+ * const server = await AsenaServerFactory.create({
16
+ * adapter,
17
+ * logger,
18
+ * port: 3000
19
+ * });
20
+ *
21
+ * await server.start();
22
+ * ```
23
+ */
24
+ export * from './lib/index';
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,cAAc,aAAa,CAAC"}