filamentjs 0.1.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 (56) hide show
  1. package/.ai/instructions.md +44 -0
  2. package/README.md +273 -0
  3. package/dist/src/application.d.ts +76 -0
  4. package/dist/src/application.d.ts.map +1 -0
  5. package/dist/src/application.js +313 -0
  6. package/dist/src/application.js.map +1 -0
  7. package/dist/src/example.d.ts +2 -0
  8. package/dist/src/example.d.ts.map +1 -0
  9. package/dist/src/example.js +110 -0
  10. package/dist/src/example.js.map +1 -0
  11. package/dist/src/index.d.ts +6 -0
  12. package/dist/src/index.d.ts.map +1 -0
  13. package/dist/src/index.js +5 -0
  14. package/dist/src/index.js.map +1 -0
  15. package/dist/src/response.d.ts +18 -0
  16. package/dist/src/response.d.ts.map +1 -0
  17. package/dist/src/response.js +46 -0
  18. package/dist/src/response.js.map +1 -0
  19. package/dist/src/router.d.ts +18 -0
  20. package/dist/src/router.d.ts.map +1 -0
  21. package/dist/src/router.js +35 -0
  22. package/dist/src/router.js.map +1 -0
  23. package/dist/src/types.d.ts +47 -0
  24. package/dist/src/types.d.ts.map +1 -0
  25. package/dist/src/types.js +6 -0
  26. package/dist/src/types.js.map +1 -0
  27. package/dist/tests/application.test.d.ts +2 -0
  28. package/dist/tests/application.test.d.ts.map +1 -0
  29. package/dist/tests/application.test.js +390 -0
  30. package/dist/tests/application.test.js.map +1 -0
  31. package/dist/tests/response.test.d.ts +2 -0
  32. package/dist/tests/response.test.d.ts.map +1 -0
  33. package/dist/tests/response.test.js +224 -0
  34. package/dist/tests/response.test.js.map +1 -0
  35. package/dist/tests/router.test.d.ts +2 -0
  36. package/dist/tests/router.test.d.ts.map +1 -0
  37. package/dist/tests/router.test.js +101 -0
  38. package/dist/tests/router.test.js.map +1 -0
  39. package/examples/01-blog-api.ts +182 -0
  40. package/examples/02-api-versioning.ts +167 -0
  41. package/examples/03-performance-controls.ts +240 -0
  42. package/examples/04-observability.ts +322 -0
  43. package/examples/05-content-negotiation.ts +268 -0
  44. package/examples/README.md +274 -0
  45. package/package.json +29 -0
  46. package/src/application.ts +390 -0
  47. package/src/example.ts +154 -0
  48. package/src/index.ts +16 -0
  49. package/src/response.ts +56 -0
  50. package/src/router.ts +47 -0
  51. package/src/types.ts +85 -0
  52. package/tests/README.md +56 -0
  53. package/tests/application.test.ts +493 -0
  54. package/tests/response.test.ts +255 -0
  55. package/tests/router.test.ts +111 -0
  56. package/tsconfig.json +19 -0
@@ -0,0 +1,44 @@
1
+ # Filament AI Development Guidelines
2
+
3
+ ## Architecture-specific patterns
4
+
5
+ - **Meta merging**: Shallow merge with defaults - **arrays replace, not merge**
6
+ - **Response flow order**: Middleware → Handler → Transformers → Finalizers
7
+ - Transformers only run on success
8
+ - Finalizers always run (even on errors)
9
+ - **Type pattern**: All classes use `<T extends FrameworkMeta>` generic
10
+
11
+ ## Coding standards
12
+
13
+ - Lines under 80 columns when possible
14
+ - Comments are to document design and usage, not to repeat code.
15
+ - When updating code, ensure that the READMEs and test cases are also up to date whith your changes.
16
+
17
+ ### Typescript
18
+
19
+ - imports should be in this order: Native, public libraries, local modules. Alphabetize within this order. Local modules may be difficult to alphabetize, just do your best.
20
+
21
+ ### Markdown
22
+
23
+ - Markdowns should have an empty line after all headers and before all lists.
24
+
25
+ ### Testing (Non-standard tooling)
26
+
27
+ - **Assertions**: `test-battery` library, NOT chai/assert
28
+ - **Test framework**: Node.js native test runner (`node:test`), NOT mocha/jest.
29
+ - Favour the native name `suite`, not its aliases like `describe`. Use
30
+ `test-battery` to handle the `test` part.
31
+ - Integration tests use real HTTP servers on high-numbered ports (9876+)
32
+
33
+ ```typescript
34
+ import { describe, it } from 'node:test';
35
+ import TestBattery from 'test-battery';
36
+ ```
37
+
38
+ ## Protected files
39
+
40
+ DO NOT modify: `/dist/*`, `package-lock.json`, `tsconfig.json` (ask first)
41
+
42
+ ## Adding route methods
43
+
44
+ Follow pattern in `application.ts:62-81` - must update Route type and add handler
package/README.md ADDED
@@ -0,0 +1,273 @@
1
+ # Filament
2
+
3
+ A TypeScript API framework with metadata-driven middleware. Similar in purpose to Express but organized around typed endpoint metadata that controls middleware behavior.
4
+
5
+ ## Why Filament?
6
+
7
+ ### Best Features
8
+
9
+ - **Type-Safe Metadata**: Full TypeScript support means your middleware logic is validated at compile time
10
+ - **Zero Runtime Overhead**: Metadata inspection is fast—no reflection or complex routing logic
11
+ - **Predictable Execution**: Registration order is everything—no magic, no surprises
12
+ - **Immutable Request Context**: Middleware can't accidentally corrupt endpoint metadata
13
+ - **Flexible Post-Processing**: Handle errors, transform responses, and finalize requests with dedicated hooks
14
+ - **Express-Familiar API**: If you know Express, you know Filament—intuitive and approachable
15
+ - **Minimal Dependencies**: Lightweight framework perfect for microservices and APIs
16
+ - **Strongly Typed Middleware**: Know exactly what metadata your middleware needs before writing a single line
17
+
18
+ ## Core Concepts
19
+
20
+ ### 1. Endpoint Metadata (`EndpointMeta`)
21
+
22
+ Every endpoint has metadata that describes its requirements and behavior. Middleware inspects this metadata to decide whether and how to execute.
23
+
24
+ ```typescript
25
+ interface AppMeta extends FrameworkMeta {
26
+ requiresAuth: boolean;
27
+ rateLimit: number;
28
+ logLevel: 'debug' | 'info' | 'error';
29
+ tags: string[];
30
+ }
31
+ ```
32
+
33
+ ### 2. Default Metadata
34
+
35
+ You define a complete default metadata object when creating your app. Individual endpoints can override specific properties using `Partial<T>`.
36
+
37
+ ### 3. Single Middleware Chain
38
+
39
+ Middleware runs in registration order. Each middleware inspects `req.endpointMeta` to decide what to do.
40
+
41
+ ### 4. Post-Request Processing
42
+
43
+ Three types of post-request handlers:
44
+
45
+ - **Error Handlers**: Run when errors occur
46
+ - **Response Transformers**: Modify successful responses
47
+ - **Finalizers**: Always run, regardless of success/failure
48
+
49
+ ## Quick Start
50
+
51
+ ```typescript
52
+ import { createApp, FrameworkMeta } from 'filamentjs';
53
+
54
+ // Define your metadata interface
55
+ interface AppMeta extends FrameworkMeta {
56
+ requiresAuth: boolean;
57
+ rateLimit: number;
58
+ logLevel: 'debug' | 'info' | 'error';
59
+ tags: string[];
60
+ }
61
+
62
+ // Create default metadata
63
+ const defaultMeta: AppMeta = {
64
+ requiresAuth: false,
65
+ rateLimit: 100,
66
+ logLevel: 'info',
67
+ tags: [],
68
+ };
69
+
70
+ // Create app
71
+ const app = createApp<AppMeta>(defaultMeta);
72
+
73
+ // Add middleware that inspects metadata
74
+ app.use(async (req, res, next) => {
75
+ if (req.endpointMeta.requiresAuth) {
76
+ const token = req.headers.authorization;
77
+ if (!token) {
78
+ res.status(401).json({ error: 'Unauthorized' });
79
+ return;
80
+ }
81
+ // validate token...
82
+ }
83
+ await next();
84
+ });
85
+
86
+ // Define endpoints with custom metadata
87
+ app.get('/public', {}, async (req, res) => {
88
+ res.json({ message: 'Public endpoint' });
89
+ });
90
+
91
+ app.get('/admin',
92
+ { requiresAuth: true, logLevel: 'debug' },
93
+ async (req, res) => {
94
+ res.json({ message: 'Admin panel' });
95
+ }
96
+ );
97
+
98
+ // Start server
99
+ app.listen(3000);
100
+ ```
101
+
102
+ ## API Reference
103
+
104
+ ### `createApp<T>(defaultMeta: T): Application<T>`
105
+
106
+ Creates a new application instance with typed metadata.
107
+
108
+ **Parameters:**
109
+
110
+ - `defaultMeta`: Complete implementation of your metadata interface
111
+
112
+ **Returns:** Application instance
113
+
114
+ ### Application Methods
115
+
116
+ #### HTTP Methods
117
+
118
+ ```typescript
119
+ app.get(path: string, meta: Partial<T>, handler: AsyncRequestHandler): void
120
+ app.post(path: string, meta: Partial<T>, handler: AsyncRequestHandler): void
121
+ app.put(path: string, meta: Partial<T>, handler: AsyncRequestHandler): void
122
+ app.patch(path: string, meta: Partial<T>, handler: AsyncRequestHandler): void
123
+ app.delete(path: string, meta: Partial<T>, handler: AsyncRequestHandler): void
124
+ ```
125
+
126
+ #### Middleware Registration
127
+
128
+ ```typescript
129
+ app.use(middleware: AsyncRequestHandler): void
130
+ app.use(path: string, middleware: AsyncRequestHandler): void
131
+ ```
132
+
133
+ #### Post-Request Handlers
134
+
135
+ ```typescript
136
+ app.onError(handler: ErrorHandler<T>): void
137
+ app.onTransform(handler: ResponseTransformer<T>): void
138
+ app.onFinalize(handler: Finalizer<T>): void
139
+ ```
140
+
141
+ #### Server Control
142
+
143
+ ```typescript
144
+ app.listen(port: number, callback?: () => void): void
145
+ app.close(): Promise<void>
146
+ ```
147
+
148
+ ## Request Object
149
+
150
+ ```typescript
151
+ interface Request<T extends FrameworkMeta> {
152
+ method: HttpMethod;
153
+ path: string;
154
+ params: Record<string, string>; // Path parameters
155
+ query: Record<string, string | string[]>; // Query parameters
156
+ headers: Record<string, string | string[] | undefined>;
157
+ body?: unknown; // Parsed JSON body
158
+ endpointMeta: Readonly<T>; // Endpoint metadata (read-only)
159
+ }
160
+ ```
161
+
162
+ ## Response Object
163
+
164
+ ```typescript
165
+ interface Response {
166
+ status(code: number): Response;
167
+ setHeader(name: string, value: string | string[]): Response;
168
+ json(data: unknown): void;
169
+ send(data: string | Buffer): void;
170
+ end(): void;
171
+ }
172
+ ```
173
+
174
+ ## Request Lifecycle
175
+
176
+ ```text
177
+ 1. Incoming Request
178
+
179
+ 2. Route Matching → req.endpointMeta populated (readonly)
180
+
181
+ 3. Middleware Chain (in registration order)
182
+ - Each middleware inspects req.endpointMeta
183
+ - Decides whether to execute logic
184
+ - await next() continues chain
185
+
186
+ 4. Route Handler executes
187
+
188
+ 5. [If Success] Response Transformers (sequential, awaited)
189
+
190
+ 6. [If Error anywhere] Error Handlers (sequential, awaited)
191
+
192
+ 7. Finalizers (always run, sequential, awaited)
193
+
194
+ 8. Response sent
195
+ ```
196
+
197
+ ## Path Parameters
198
+
199
+ Supports Express-style path parameters:
200
+
201
+ ```typescript
202
+ app.get('/users/:id', {}, async (req, res) => {
203
+ const userId = req.params.id; // string
204
+ res.json({ userId });
205
+ });
206
+
207
+ app.get('/posts/:postId/comments/:commentId', {}, async (req, res) => {
208
+ const { postId, commentId } = req.params;
209
+ res.json({ postId, commentId });
210
+ });
211
+ ```
212
+
213
+ ## Metadata Merging
214
+
215
+ - Endpoint metadata is merged with defaults using shallow merge
216
+ - Arrays **always replace** (not concatenate)
217
+ - Metadata is immutable at runtime
218
+
219
+ ```typescript
220
+ const defaultMeta = {
221
+ requiresAuth: false,
222
+ tags: ['default'],
223
+ };
224
+
225
+ app.get('/endpoint',
226
+ { requiresAuth: true, tags: ['custom'] }, // tags replaces, not appends
227
+ handler
228
+ );
229
+ // Result: { requiresAuth: true, tags: ['custom'] }
230
+ ```
231
+
232
+ ## Error Handling
233
+
234
+ Errors thrown anywhere in the request lifecycle are caught and passed to error handlers:
235
+
236
+ ```typescript
237
+ app.onError(async (err, req, res) => {
238
+ console.error('Error:', err);
239
+ res.status(500).json({ error: err.message });
240
+ });
241
+ ```
242
+
243
+ ## Complete Example
244
+
245
+ See `src/example.ts` for a complete working example with:
246
+
247
+ - Authentication middleware
248
+ - Rate limiting middleware
249
+ - Logging middleware
250
+ - Multiple endpoints with different metadata
251
+ - Response transformers
252
+ - Error handling
253
+ - Finalizers
254
+
255
+ ## Design Decisions
256
+
257
+ 1. **Immutable Metadata**: `req.endpointMeta` is read-only to prevent middleware from creating hidden dependencies
258
+ 2. **Async by Default**: All handlers support `async/await`
259
+ 3. **Registration Order**: Middleware runs in strict registration order
260
+ 4. **Path Parameters**: Typed as `Record<string, string>` (no advanced type inference)
261
+ 5. **Array Replacement**: Arrays in metadata always replace (never merge)
262
+
263
+ ## TypeScript
264
+
265
+ Full TypeScript support with strict typing:
266
+
267
+ - Generic `Application<T>` for typed metadata
268
+ - Type-safe request/response objects
269
+ - Compile-time validation of metadata interfaces
270
+
271
+ ## License
272
+
273
+ ISC
@@ -0,0 +1,76 @@
1
+ import { FrameworkMeta, AsyncRequestHandler, ErrorHandler, Finalizer, ResponseTransformer } from './types.js';
2
+ /**
3
+ * Main Application class for Filament
4
+ */
5
+ export declare class Application<T extends FrameworkMeta> {
6
+ private routes;
7
+ private middlewares;
8
+ private errorHandlers;
9
+ private finalizers;
10
+ private transformers;
11
+ private defaultMeta;
12
+ private server?;
13
+ constructor(defaultMeta: T);
14
+ /**
15
+ * Register a route
16
+ */
17
+ private route;
18
+ /**
19
+ * Merge partial meta with defaults
20
+ */
21
+ private mergeMeta;
22
+ get(path: string, meta: Partial<T>, handler: AsyncRequestHandler<T>): void;
23
+ post(path: string, meta: Partial<T>, handler: AsyncRequestHandler<T>): void;
24
+ put(path: string, meta: Partial<T>, handler: AsyncRequestHandler<T>): void;
25
+ patch(path: string, meta: Partial<T>, handler: AsyncRequestHandler<T>): void;
26
+ delete(path: string, meta: Partial<T>, handler: AsyncRequestHandler<T>): void;
27
+ /**
28
+ * Register middleware
29
+ */
30
+ use(pathOrHandler: string | AsyncRequestHandler<T>, handler?: AsyncRequestHandler<T>): void;
31
+ /**
32
+ * Register error handler
33
+ */
34
+ onError(handler: ErrorHandler<T>): void;
35
+ /**
36
+ * Register finalizer
37
+ */
38
+ onFinalize(handler: Finalizer<T>): void;
39
+ /**
40
+ * Register response transformer
41
+ */
42
+ onTransform(handler: ResponseTransformer<T>): void;
43
+ /**
44
+ * Execute middleware chain
45
+ */
46
+ private executeMiddlewareChain;
47
+ /**
48
+ * Execute error handlers
49
+ */
50
+ private executeErrorHandlers;
51
+ /**
52
+ * Execute response transformers
53
+ */
54
+ private executeTransformers;
55
+ /**
56
+ * Execute finalizers
57
+ */
58
+ private executeFinalizers;
59
+ /**
60
+ * Handle incoming HTTP request
61
+ */
62
+ private handleRequest;
63
+ /**
64
+ * Start the server
65
+ */
66
+ listen(port: number): Promise<number>;
67
+ /**
68
+ * Stop the server
69
+ */
70
+ close(): Promise<void>;
71
+ }
72
+ /**
73
+ * Factory function to create an application
74
+ */
75
+ export declare function createApp<T extends FrameworkMeta>(defaultMeta: T): Application<T>;
76
+ //# sourceMappingURL=application.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"application.d.ts","sourceRoot":"","sources":["../../src/application.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,aAAa,EAIb,mBAAmB,EACnB,YAAY,EACZ,SAAS,EACT,mBAAmB,EAGpB,MAAM,YAAY,CAAC;AAIpB;;GAEG;AACH,qBAAa,WAAW,CAAC,CAAC,SAAS,aAAa;IAC9C,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,MAAM,CAAC,CAAc;gBAEjB,WAAW,EAAE,CAAC;IAI1B;;OAEG;IACH,OAAO,CAAC,KAAK;IAmBb;;OAEG;IACH,OAAO,CAAC,SAAS;IASjB,GAAG,CACD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAChB,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC9B,IAAI;IAIP,IAAI,CACF,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAChB,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC9B,IAAI;IAIP,GAAG,CACD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAChB,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC9B,IAAI;IAIP,KAAK,CACH,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAChB,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC9B,IAAI;IAIP,MAAM,CACJ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAChB,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC9B,IAAI;IAIP;;OAEG;IACH,GAAG,CACD,aAAa,EAAE,MAAM,GAAG,mBAAmB,CAAC,CAAC,CAAC,EAC9C,OAAO,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC/B,IAAI;IAQP;;OAEG;IACH,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI;IAIvC;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI;IAIvC;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,IAAI;IAIlD;;OAEG;YACW,sBAAsB;IAmBpC;;OAEG;YACW,oBAAoB;IAmBlC;;OAEG;YACW,mBAAmB;IAWjC;;OAEG;YACW,iBAAiB;IAW/B;;OAEG;YACW,aAAa;IAsH3B;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA6B3C;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAYvB;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,aAAa,EAAE,WAAW,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAEjF"}
@@ -0,0 +1,313 @@
1
+ import http from 'http';
2
+ import { URL } from 'url';
3
+ import { ResponseImpl } from './response.js';
4
+ import { pathToRegex, matchPath } from './router.js';
5
+ /**
6
+ * Main Application class for Filament
7
+ */
8
+ export class Application {
9
+ constructor(defaultMeta) {
10
+ this.routes = [];
11
+ this.middlewares = [];
12
+ this.errorHandlers = [];
13
+ this.finalizers = [];
14
+ this.transformers = [];
15
+ this.defaultMeta = defaultMeta;
16
+ }
17
+ /**
18
+ * Register a route
19
+ */
20
+ route(method, path, meta, handler) {
21
+ const { pattern, paramNames } = pathToRegex(path);
22
+ const mergedMeta = this.mergeMeta(meta);
23
+ this.routes.push({
24
+ method,
25
+ path,
26
+ pattern,
27
+ paramNames,
28
+ meta: mergedMeta,
29
+ handler,
30
+ });
31
+ }
32
+ /**
33
+ * Merge partial meta with defaults
34
+ */
35
+ mergeMeta(partial) {
36
+ // Shallow merge - arrays replace
37
+ return {
38
+ ...this.defaultMeta,
39
+ ...partial,
40
+ };
41
+ }
42
+ // HTTP method helpers
43
+ get(path, meta, handler) {
44
+ this.route('GET', path, meta, handler);
45
+ }
46
+ post(path, meta, handler) {
47
+ this.route('POST', path, meta, handler);
48
+ }
49
+ put(path, meta, handler) {
50
+ this.route('PUT', path, meta, handler);
51
+ }
52
+ patch(path, meta, handler) {
53
+ this.route('PATCH', path, meta, handler);
54
+ }
55
+ delete(path, meta, handler) {
56
+ this.route('DELETE', path, meta, handler);
57
+ }
58
+ /**
59
+ * Register middleware
60
+ */
61
+ use(pathOrHandler, handler) {
62
+ if (typeof pathOrHandler === 'string' && handler) {
63
+ this.middlewares.push({ path: pathOrHandler, handler });
64
+ }
65
+ else if (typeof pathOrHandler === 'function') {
66
+ this.middlewares.push({ handler: pathOrHandler });
67
+ }
68
+ }
69
+ /**
70
+ * Register error handler
71
+ */
72
+ onError(handler) {
73
+ this.errorHandlers.push(handler);
74
+ }
75
+ /**
76
+ * Register finalizer
77
+ */
78
+ onFinalize(handler) {
79
+ this.finalizers.push(handler);
80
+ }
81
+ /**
82
+ * Register response transformer
83
+ */
84
+ onTransform(handler) {
85
+ this.transformers.push(handler);
86
+ }
87
+ /**
88
+ * Execute middleware chain
89
+ */
90
+ async executeMiddlewareChain(req, res, middlewares) {
91
+ let currentIndex = 0;
92
+ const next = async () => {
93
+ if (currentIndex >= middlewares.length) {
94
+ return;
95
+ }
96
+ const middleware = middlewares[currentIndex++];
97
+ await middleware(req, res, next);
98
+ };
99
+ await next();
100
+ }
101
+ /**
102
+ * Execute error handlers
103
+ */
104
+ async executeErrorHandlers(err, req, res) {
105
+ for (const handler of this.errorHandlers) {
106
+ try {
107
+ await handler(err, req, res, async () => { });
108
+ }
109
+ catch (handlerError) {
110
+ console.error('Error in error handler:', handlerError);
111
+ }
112
+ }
113
+ // If no error handler sent a response, send default error
114
+ if (!res.headersSent) {
115
+ res.status(500).json({ error: 'Internal Server Error' });
116
+ }
117
+ }
118
+ /**
119
+ * Execute response transformers
120
+ */
121
+ async executeTransformers(req, res) {
122
+ for (const transformer of this.transformers) {
123
+ try {
124
+ await transformer(req, res);
125
+ }
126
+ catch (err) {
127
+ console.error('Error in response transformer:', err);
128
+ throw err;
129
+ }
130
+ }
131
+ }
132
+ /**
133
+ * Execute finalizers
134
+ */
135
+ async executeFinalizers(req, res) {
136
+ for (const finalizer of this.finalizers) {
137
+ try {
138
+ await finalizer(req, res);
139
+ }
140
+ catch (err) {
141
+ console.error('Error in finalizer:', err);
142
+ // Finalizers should not block response
143
+ }
144
+ }
145
+ }
146
+ /**
147
+ * Handle incoming HTTP request
148
+ */
149
+ async handleRequest(nodeReq, nodeRes) {
150
+ const url = new URL(nodeReq.url || '/', `http://${nodeReq.headers.host || 'localhost'}`);
151
+ const method = (nodeReq.method || 'GET').toUpperCase();
152
+ const path = url.pathname;
153
+ // Find matching route
154
+ let matchedRoute;
155
+ let params = {};
156
+ for (const route of this.routes) {
157
+ if (route.method !== method)
158
+ continue;
159
+ const match = matchPath(path, route.pattern, route.paramNames);
160
+ if (match) {
161
+ matchedRoute = route;
162
+ params = match.params;
163
+ break;
164
+ }
165
+ }
166
+ if (!matchedRoute) {
167
+ nodeRes.statusCode = 404;
168
+ nodeRes.end(JSON.stringify({ error: 'Not Found' }));
169
+ return;
170
+ }
171
+ // Parse query parameters
172
+ const query = {};
173
+ url.searchParams.forEach((value, key) => {
174
+ const existing = query[key];
175
+ if (existing) {
176
+ query[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
177
+ }
178
+ else {
179
+ query[key] = value;
180
+ }
181
+ });
182
+ // Parse headers
183
+ const headers = {};
184
+ Object.entries(nodeReq.headers).forEach(([key, value]) => {
185
+ headers[key] = value;
186
+ });
187
+ // Create request object
188
+ const req = {
189
+ method,
190
+ path,
191
+ params,
192
+ query,
193
+ headers,
194
+ endpointMeta: matchedRoute.meta,
195
+ _startTime: Date.now(),
196
+ };
197
+ // Read body for POST/PUT/PATCH
198
+ if (['POST', 'PUT', 'PATCH'].includes(method)) {
199
+ const chunks = [];
200
+ for await (const chunk of nodeReq) {
201
+ chunks.push(chunk);
202
+ }
203
+ const bodyStr = Buffer.concat(chunks).toString();
204
+ if (bodyStr) {
205
+ try {
206
+ req.body = JSON.parse(bodyStr);
207
+ }
208
+ catch {
209
+ req.body = bodyStr;
210
+ }
211
+ }
212
+ }
213
+ // Create response object
214
+ const res = new ResponseImpl((finalRes) => {
215
+ nodeRes.statusCode = finalRes.statusCode;
216
+ Object.entries(finalRes.headers).forEach(([key, value]) => {
217
+ nodeRes.setHeader(key, value);
218
+ });
219
+ if (finalRes.body) {
220
+ nodeRes.end(finalRes.body);
221
+ }
222
+ else {
223
+ nodeRes.end();
224
+ }
225
+ });
226
+ let hadError = false;
227
+ try {
228
+ // Filter applicable middleware (by path if specified)
229
+ const applicableMiddleware = this.middlewares
230
+ .filter((mw) => !mw.path || path.startsWith(mw.path))
231
+ .map((mw) => mw.handler);
232
+ // Execute middleware chain
233
+ await this.executeMiddlewareChain(req, res, applicableMiddleware);
234
+ // If response already sent by middleware, skip handler
235
+ if (!res.headersSent) {
236
+ // Execute route handler
237
+ await matchedRoute.handler(req, res, async () => { });
238
+ // Execute response transformers (only on success)
239
+ if (!res.headersSent) {
240
+ await this.executeTransformers(req, res);
241
+ }
242
+ }
243
+ }
244
+ catch (err) {
245
+ hadError = true;
246
+ await this.executeErrorHandlers(err, req, res);
247
+ }
248
+ finally {
249
+ // Always execute finalizers
250
+ await this.executeFinalizers(req, res);
251
+ // Ensure response is sent
252
+ if (!res.headersSent) {
253
+ res.end();
254
+ }
255
+ }
256
+ }
257
+ /**
258
+ * Start the server
259
+ */
260
+ async listen(port) {
261
+ return new Promise((resolve, reject) => {
262
+ this.server = http.createServer((req, res) => {
263
+ this.handleRequest(req, res).catch((err) => {
264
+ console.error('Unhandled error in request handler:', err);
265
+ if (!res.headersSent) {
266
+ res.statusCode = 500;
267
+ res.end(JSON.stringify({ error: 'Internal Server Error' }));
268
+ }
269
+ });
270
+ });
271
+ this.server.on('error', (err) => {
272
+ if (err.code === 'EADDRINUSE') {
273
+ reject(new Error(`Port ${port} is already in use`));
274
+ }
275
+ else if (err.code === 'EACCES') {
276
+ reject(new Error(`Permission denied: cannot listen on port ${port}`));
277
+ }
278
+ else {
279
+ reject(new Error(`Failed to start server on port ${port}: ${err.message}`));
280
+ }
281
+ });
282
+ this.server.listen(port, () => {
283
+ console.log(`Server listening on port ${port}`);
284
+ resolve(port);
285
+ });
286
+ });
287
+ }
288
+ /**
289
+ * Stop the server
290
+ */
291
+ close() {
292
+ return new Promise((resolve, reject) => {
293
+ if (this.server) {
294
+ this.server.close((err) => {
295
+ if (err)
296
+ reject(err);
297
+ else
298
+ resolve();
299
+ });
300
+ }
301
+ else {
302
+ resolve();
303
+ }
304
+ });
305
+ }
306
+ }
307
+ /**
308
+ * Factory function to create an application
309
+ */
310
+ export function createApp(defaultMeta) {
311
+ return new Application(defaultMeta);
312
+ }
313
+ //# sourceMappingURL=application.js.map