@asenajs/ergenecore 1.5.0 → 3.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.
- package/README.md +20 -5
- package/dist/lib/Ergenecore.d.ts +70 -63
- package/dist/lib/Ergenecore.js +359 -351
- package/dist/lib/Ergenecore.js.map +1 -1
- package/dist/lib/ErgenecoreContextWrapper.js +4 -1
- package/dist/lib/ErgenecoreContextWrapper.js.map +1 -1
- package/dist/lib/ErgenecoreWebsocketAdapter.js +14 -2
- package/dist/lib/ErgenecoreWebsocketAdapter.js.map +1 -1
- package/dist/lib/defaults/ConfigService.d.ts +19 -14
- package/dist/lib/defaults/ConfigService.js +5 -2
- package/dist/lib/defaults/ConfigService.js.map +1 -1
- package/dist/lib/defaults/MiddlewareService.d.ts +6 -2
- package/dist/lib/defaults/MiddlewareService.js.map +1 -1
- package/dist/lib/defaults/ValidationService.js.map +1 -1
- package/dist/lib/errors.d.ts +25 -85
- package/dist/lib/errors.js +34 -95
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/middlewares/RateLimiterMiddleware.d.ts +11 -2
- package/dist/lib/middlewares/RateLimiterMiddleware.js +29 -2
- package/dist/lib/middlewares/RateLimiterMiddleware.js.map +1 -1
- package/dist/lib/stream/SSEStreamWriter.js +6 -1
- package/dist/lib/stream/SSEStreamWriter.js.map +1 -1
- package/dist/lib/stream/StreamWriter.js +9 -2
- package/dist/lib/stream/StreamWriter.js.map +1 -1
- package/dist/lib/utils/factory.d.ts +10 -0
- package/dist/lib/utils/factory.js +2 -2
- package/dist/lib/utils/factory.js.map +1 -1
- package/package.json +15 -8
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Asena Ergenecore Adapter
|
|
2
2
|
|
|
3
|
+
[](https://asena.sh)
|
|
4
|
+
[](https://bun.sh)
|
|
5
|
+
|
|
3
6
|
**Blazing-fast** native Bun adapter for [Asena.js](https://github.com/asenajs/asena) - Built exclusively with Bun's native APIs for maximum performance.
|
|
4
7
|
|
|
5
8
|
Built on Bun runtime for exceptional performance:
|
|
@@ -17,7 +20,7 @@ Built on Bun runtime for exceptional performance:
|
|
|
17
20
|
|
|
18
21
|
## Features
|
|
19
22
|
|
|
20
|
-
- ⚡ **Zero Dependencies** -
|
|
23
|
+
- ⚡ **Zero Dependencies** - no runtime dependencies at all; Zod is a peer your project owns
|
|
21
24
|
- 🚀 **SIMD-Accelerated** - Native Bun router with SIMD-optimized matching
|
|
22
25
|
- 🔌 **Full Adapter Support** - HTTP, WebSocket, Middleware, Validation
|
|
23
26
|
- 📦 **Zero-Copy File Serving** - Bun.file() for optimal static file performance
|
|
@@ -31,20 +34,31 @@ Ergenecore is the **fastest** Asena adapter because it:
|
|
|
31
34
|
- Uses Bun's **native HTTP server** (`Bun.serve()`)
|
|
32
35
|
- Leverages **SIMD-accelerated routing** (no framework overhead)
|
|
33
36
|
- Implements **zero-copy** file serving with `Bun.file()`
|
|
34
|
-
- Has **no
|
|
37
|
+
- Has **no runtime dependencies at all** (Zod is a peer your project owns)
|
|
35
38
|
- Is optimized for **Bun runtime** exclusively
|
|
36
39
|
|
|
37
40
|
## Requirements
|
|
38
41
|
|
|
39
42
|
- [Bun](https://bun.sh) v1.3.12 or higher
|
|
43
|
+
- [@asenajs/asena](https://github.com/AsenaJs/Asena) v0.10.0 or higher (peer dependency)
|
|
44
|
+
- [Zod](https://zod.dev) v4.3.6 or higher (peer dependency)
|
|
40
45
|
- TypeScript v5.8.2 or higher
|
|
41
46
|
|
|
42
47
|
## Installation
|
|
43
48
|
|
|
44
49
|
```bash
|
|
45
|
-
bun add @asenajs/ergenecore
|
|
50
|
+
bun add @asenajs/ergenecore zod
|
|
46
51
|
```
|
|
47
52
|
|
|
53
|
+
`zod` is a **peer dependency** - the adapter defines the validation contract, your project owns the
|
|
54
|
+
library and its version.
|
|
55
|
+
|
|
56
|
+
| Package manager | Command |
|
|
57
|
+
|:--|:--|
|
|
58
|
+
| bun, npm 7+, pnpm 8+ | `bun add zod` — peers auto-install, but declare them anyway so they survive a clean install |
|
|
59
|
+
| yarn 1 | `yarn add zod` — **required**, yarn 1 does not install peers for you |
|
|
60
|
+
|
|
61
|
+
|
|
48
62
|
## Quick Start
|
|
49
63
|
|
|
50
64
|
### Basic Server Setup
|
|
@@ -609,7 +623,8 @@ import { isValidationError } from '@asenajs/asena/adapter';
|
|
|
609
623
|
export class ExceptionMapper {
|
|
610
624
|
map(error: Error, context: Context): Response {
|
|
611
625
|
// Request validation failures arrive here like any other error.
|
|
612
|
-
// Check this before
|
|
626
|
+
// Check this before the generic `isHttpException()` branch - a ValidationError is an
|
|
627
|
+
// HTTP exception too, so the generic branch would otherwise swallow it
|
|
613
628
|
if (isValidationError(error)) {
|
|
614
629
|
return context.send({ error: 'Validation failed', details: error.issues }, 400);
|
|
615
630
|
}
|
|
@@ -635,7 +650,7 @@ Contributions are welcome! Please follow these guidelines:
|
|
|
635
650
|
|
|
636
651
|
1. Follow the **TDD approach** (write tests first)
|
|
637
652
|
2. Maintain **90%+ test coverage**
|
|
638
|
-
3. Use **Bun native APIs** only (no
|
|
653
|
+
3. Use **Bun native APIs** only (no runtime dependencies; Zod is a peer)
|
|
639
654
|
4. Follow the **CLAUDE.md** guidelines
|
|
640
655
|
|
|
641
656
|
## License
|
package/dist/lib/Ergenecore.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { WebsocketRouteParams } from '@asenajs/asena/adapter';
|
|
2
|
-
import { AsenaAdapter, type AsenaServeOptions, type AsenaStartOptions, type BaseMiddleware, type ErrorHandler, type RouteParams } from '@asenajs/asena/adapter';
|
|
2
|
+
import { AsenaAdapter, type AsenaServeOptions, type AsenaStartOptions, type BaseMiddleware, type ErrorHandler, type NotFoundHandler, type RouteParams } from '@asenajs/asena/adapter';
|
|
3
3
|
import { type ServerLogger } from '@asenajs/asena/logger';
|
|
4
4
|
import type { GlobalMiddlewareConfig } from '@asenajs/asena/server/config';
|
|
5
5
|
import { ErgenecoreWebsocketAdapter } from './ErgenecoreWebsocketAdapter';
|
|
@@ -50,6 +50,15 @@ export declare class Ergenecore extends AsenaAdapter<Context, ValidationSchemaWi
|
|
|
50
50
|
* Error handler function
|
|
51
51
|
*/
|
|
52
52
|
private errorHandler?;
|
|
53
|
+
/**
|
|
54
|
+
* Handler for requests that matched no route
|
|
55
|
+
*/
|
|
56
|
+
private notFoundHandler?;
|
|
57
|
+
/**
|
|
58
|
+
* Whether the adapter logs errors before handing them to the application.
|
|
59
|
+
* Set false when your own handler already logs with a correlation id.
|
|
60
|
+
*/
|
|
61
|
+
private readonly logErrors;
|
|
53
62
|
/**
|
|
54
63
|
* Global middlewares with route configuration
|
|
55
64
|
* Structure: Array<{ middleware, config }>
|
|
@@ -72,7 +81,7 @@ export declare class Ergenecore extends AsenaAdapter<Context, ValidationSchemaWi
|
|
|
72
81
|
* @param logger - Server logger instance
|
|
73
82
|
* @param websocketAdapter - WebSocket adapter instance (optional)
|
|
74
83
|
*/
|
|
75
|
-
constructor(logger: ServerLogger, websocketAdapter?: ErgenecoreWebsocketAdapter);
|
|
84
|
+
constructor(logger: ServerLogger, websocketAdapter?: ErgenecoreWebsocketAdapter, logErrors?: boolean);
|
|
76
85
|
/**
|
|
77
86
|
* Registers a WebSocket route (deferred registration)
|
|
78
87
|
*
|
|
@@ -164,6 +173,11 @@ export declare class Ergenecore extends AsenaAdapter<Context, ValidationSchemaWi
|
|
|
164
173
|
/**
|
|
165
174
|
* Stops the server
|
|
166
175
|
*
|
|
176
|
+
* The HTTP socket closes first and the WebSocket layer second - the reverse of the order
|
|
177
|
+
* start() brought them up, since the transport is initialised *after* Bun.serve(). A close
|
|
178
|
+
* handler running during the drain may still broadcast, so the transport has to outlive the
|
|
179
|
+
* sockets that use it.
|
|
180
|
+
*
|
|
167
181
|
* @param closeActiveConnections - Whether to close active connections
|
|
168
182
|
*/
|
|
169
183
|
stop(closeActiveConnections?: boolean): Promise<void>;
|
|
@@ -173,6 +187,12 @@ export declare class Ergenecore extends AsenaAdapter<Context, ValidationSchemaWi
|
|
|
173
187
|
* @param errorHandler - Error handler function
|
|
174
188
|
*/
|
|
175
189
|
onError(errorHandler: ErrorHandler<Context>): void;
|
|
190
|
+
/**
|
|
191
|
+
* Registers the handler for requests that match no route.
|
|
192
|
+
*
|
|
193
|
+
* Separate from {@link onError}: a missing route is a routing outcome, not a thrown error.
|
|
194
|
+
*/
|
|
195
|
+
onNotFound(notFoundHandler: NotFoundHandler<Context>): void;
|
|
176
196
|
/**
|
|
177
197
|
* Sets serve options
|
|
178
198
|
*
|
|
@@ -223,6 +243,54 @@ export declare class Ergenecore extends AsenaAdapter<Context, ValidationSchemaWi
|
|
|
223
243
|
* ```
|
|
224
244
|
*/
|
|
225
245
|
private buildWebSocketRoutes;
|
|
246
|
+
/**
|
|
247
|
+
* Answers a request nothing served: an unmatched route, or a static file that does not exist.
|
|
248
|
+
*
|
|
249
|
+
* Both go through the application's `onNotFound` so an app has one place to shape its 404,
|
|
250
|
+
* and both fall back to the same JSON envelope the hono adapter uses.
|
|
251
|
+
*
|
|
252
|
+
* Deliberately NOT routed to `onError` - that hook is for errors the application threw.
|
|
253
|
+
*/
|
|
254
|
+
private respondToUnmatched;
|
|
255
|
+
private resolveUnmatchedResponse;
|
|
256
|
+
/**
|
|
257
|
+
* Records a request the framework itself answered with a 404.
|
|
258
|
+
*
|
|
259
|
+
* Only reached when the application has no `onNotFound`, or when its hook declined or threw -
|
|
260
|
+
* an application that answered its own 404 already knows about the request. Without this an
|
|
261
|
+
* unmatched route was the one outcome that produced no output at any level, which is exactly
|
|
262
|
+
* the traffic (bots, probes, a typo in a deployed client) an operator needs to see.
|
|
263
|
+
*
|
|
264
|
+
* `info`, not `warn`: a scanner walking /wp-admin, /.env and /phpmyadmin must not be able to
|
|
265
|
+
* fill the warning stream. Not `debug` either - a 404 nobody can see is how a mistyped route
|
|
266
|
+
* survives to production.
|
|
267
|
+
*/
|
|
268
|
+
private logUnmatched;
|
|
269
|
+
/**
|
|
270
|
+
* Logs an error before the application's handler sees it, at a level that matches the
|
|
271
|
+
* response the caller will get.
|
|
272
|
+
*
|
|
273
|
+
* This adapter used to log only when NO error handler was registered - which is to say,
|
|
274
|
+
* never in a real application, since every app configures `onError`. A 500 answered
|
|
275
|
+
* nothing to stdout and the stack was gone. The level split mirrors the hono adapter:
|
|
276
|
+
* 5xx is ours to fix and gets a stack, 4xx is the caller's and would otherwise let a bot
|
|
277
|
+
* scanning for /wp-admin flood the error stream.
|
|
278
|
+
*/
|
|
279
|
+
private logHandledError;
|
|
280
|
+
/**
|
|
281
|
+
* Produces the response for a thrown error, in the same order the hono adapter uses.
|
|
282
|
+
*
|
|
283
|
+
* The application's handler gets first refusal on *every* error, `HttpException` included.
|
|
284
|
+
* Previously each catch block answered an `HttpException` straight from `getResponse()` and
|
|
285
|
+
* only consulted `errorHandler` for everything else, so an app could not reshape its own
|
|
286
|
+
* 401/403/404 envelopes. Worse, only one of the five catch blocks carried the
|
|
287
|
+
* `!isValidationError` exemption, so a `ValidationError` thrown on the fast path never
|
|
288
|
+
* reached `onError` at all - the exact opposite of why ValidationError exists.
|
|
289
|
+
*
|
|
290
|
+
* Falls back to the exception's own response (or a 500) when there is no handler, when the
|
|
291
|
+
* handler returns nothing, or when the handler itself throws.
|
|
292
|
+
*/
|
|
293
|
+
private respondToError;
|
|
226
294
|
/**
|
|
227
295
|
* Executes middleware chain with recursive next() pattern
|
|
228
296
|
*
|
|
@@ -342,7 +410,6 @@ export declare class Ergenecore extends AsenaAdapter<Context, ValidationSchemaWi
|
|
|
342
410
|
* - Error handling
|
|
343
411
|
*
|
|
344
412
|
* @param route - Route parameters
|
|
345
|
-
* @param _commonMiddlewares - Common middlewares for this route group (for future optimization)
|
|
346
413
|
* @returns Bun-compatible route handler
|
|
347
414
|
*/
|
|
348
415
|
private createRouteHandler;
|
|
@@ -438,66 +505,6 @@ export declare class Ergenecore extends AsenaAdapter<Context, ValidationSchemaWi
|
|
|
438
505
|
* ```
|
|
439
506
|
*/
|
|
440
507
|
private buildStaticFileHeaders;
|
|
441
|
-
/**
|
|
442
|
-
* Extracts base path from a route path
|
|
443
|
-
*
|
|
444
|
-
* Removes dynamic segments (parameters and wildcards) from the path
|
|
445
|
-
* to find the static base path for grouping routes.
|
|
446
|
-
*
|
|
447
|
-
* @param path - Route path (e.g., "/api/users/:id")
|
|
448
|
-
* @returns Base path without dynamic segments (e.g., "/api/users")
|
|
449
|
-
*
|
|
450
|
-
* @example
|
|
451
|
-
* ```typescript
|
|
452
|
-
* extractBasePath('/api/users/:id') // => '/api/users'
|
|
453
|
-
* extractBasePath('/api/users/:id/posts/:postId') // => '/api/users'
|
|
454
|
-
* extractBasePath('/static/*') // => '/static'
|
|
455
|
-
* extractBasePath('/') // => '/'
|
|
456
|
-
* ```
|
|
457
|
-
*/
|
|
458
|
-
private extractBasePath;
|
|
459
|
-
/**
|
|
460
|
-
* Extracts common middlewares across multiple routes
|
|
461
|
-
*
|
|
462
|
-
* Identifies middlewares that are present in ALL routes and can be
|
|
463
|
-
* moved to group level for optimization.
|
|
464
|
-
*
|
|
465
|
-
* @param routes - Array of routes to analyze
|
|
466
|
-
* @returns Array of common middleware instances
|
|
467
|
-
*
|
|
468
|
-
* @example
|
|
469
|
-
* ```typescript
|
|
470
|
-
* extractCommonMiddlewares([
|
|
471
|
-
* { middlewares: [auth, log, rate] },
|
|
472
|
-
* { middlewares: [auth, log] },
|
|
473
|
-
* { middlewares: [auth, log] }
|
|
474
|
-
* ]) // => [auth, log]
|
|
475
|
-
* ```
|
|
476
|
-
*/
|
|
477
|
-
private extractCommonMiddlewares;
|
|
478
|
-
/**
|
|
479
|
-
* Groups routes by their base path
|
|
480
|
-
*
|
|
481
|
-
* Creates a map of base paths to routes for optimization.
|
|
482
|
-
* Routes with the same base path will share common middlewares.
|
|
483
|
-
*
|
|
484
|
-
* @param routes - Array of routes to group
|
|
485
|
-
* @returns Map of base paths to route arrays
|
|
486
|
-
*
|
|
487
|
-
* @example
|
|
488
|
-
* ```typescript
|
|
489
|
-
* groupRoutesByBasePath([
|
|
490
|
-
* { path: '/api/users' },
|
|
491
|
-
* { path: '/api/users/:id' },
|
|
492
|
-
* { path: '/api/posts' }
|
|
493
|
-
* ])
|
|
494
|
-
* // => Map {
|
|
495
|
-
* // '/api/users' => [route1, route2],
|
|
496
|
-
* // '/api/posts' => [route3]
|
|
497
|
-
* // }
|
|
498
|
-
* ```
|
|
499
|
-
*/
|
|
500
|
-
private groupRoutesByBasePath;
|
|
501
508
|
get hostname(): any;
|
|
502
509
|
set hostname(value: any);
|
|
503
510
|
/**
|