ts-typed-api 0.2.16 → 0.2.18
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/dist/handler.js +38 -3
- package/dist/hono-cloudflare-workers.js +37 -1
- package/dist/index.d.ts +1 -1
- package/dist/object-handlers.d.ts +1 -0
- package/dist/router.d.ts +1 -0
- package/examples/simple/definitions.ts +3 -0
- package/examples/simple/server.ts +13 -4
- package/package.json +1 -1
- package/src/handler.ts +40 -3
- package/src/hono-cloudflare-workers.ts +37 -1
- package/src/index.ts +1 -1
- package/src/object-handlers.ts +1 -0
- package/src/router.ts +1 -0
- package/tests/middleware.test.ts +77 -0
- package/tests/setup.ts +14 -1
- package/tests/simple-api.test.ts +13 -0
package/dist/handler.js
CHANGED
|
@@ -56,8 +56,19 @@ function preprocessQueryParams(query, querySchema) {
|
|
|
56
56
|
return processedQuery;
|
|
57
57
|
}
|
|
58
58
|
// Helper function to create respond method for middleware compatibility
|
|
59
|
-
function createRespondFunction(routeDefinition, responseSetter) {
|
|
59
|
+
function createRespondFunction(routeDefinition, responseSetter, middlewareRes) {
|
|
60
60
|
return (status, data) => {
|
|
61
|
+
// Call any registered response callbacks
|
|
62
|
+
if (middlewareRes && middlewareRes._responseCallbacks) {
|
|
63
|
+
middlewareRes._responseCallbacks.forEach((callback) => {
|
|
64
|
+
try {
|
|
65
|
+
callback(status, data);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error('Error in response callback:', error);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
61
72
|
const responseSchema = routeDefinition.responses[status];
|
|
62
73
|
if (!responseSchema) {
|
|
63
74
|
console.error(`No response schema defined for status ${status}`);
|
|
@@ -305,6 +316,17 @@ middlewares) {
|
|
|
305
316
|
// Augment expressRes with the .respond and .setHeader methods, using TDef
|
|
306
317
|
const typedExpressRes = expressRes;
|
|
307
318
|
typedExpressRes.respond = (status, dataForResponse) => {
|
|
319
|
+
// Call any registered response callbacks from middleware
|
|
320
|
+
if (expressRes._responseCallbacks) {
|
|
321
|
+
expressRes._responseCallbacks.forEach((callback) => {
|
|
322
|
+
try {
|
|
323
|
+
callback(status, dataForResponse);
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
console.error('Error in response callback:', error);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
308
330
|
// Use the passed apiDefinition object
|
|
309
331
|
const routeSchemaForHandler = apiDefinition.endpoints[currentDomain][currentRouteKey];
|
|
310
332
|
const responseSchemaForStatus = routeSchemaForHandler.responses[status];
|
|
@@ -347,6 +369,12 @@ middlewares) {
|
|
|
347
369
|
});
|
|
348
370
|
}
|
|
349
371
|
};
|
|
372
|
+
typedExpressRes.respondContentType = (status, data, contentType) => {
|
|
373
|
+
// Set the content type header
|
|
374
|
+
typedExpressRes.setHeader('Content-Type', contentType);
|
|
375
|
+
// Send the raw data without JSON wrapping or validation
|
|
376
|
+
typedExpressRes.status(status).send(data);
|
|
377
|
+
};
|
|
350
378
|
typedExpressRes.setHeader = (name, value) => {
|
|
351
379
|
// Call the original Express setHeader method to avoid recursion
|
|
352
380
|
Object.getPrototypeOf(expressRes).setHeader.call(expressRes, name, value);
|
|
@@ -416,11 +444,18 @@ middlewares) {
|
|
|
416
444
|
middlewares.forEach(middleware => {
|
|
417
445
|
const wrappedMiddleware = async (req, res, next) => {
|
|
418
446
|
try {
|
|
419
|
-
// Add respond
|
|
447
|
+
// Add respond and onFinish methods to res for middleware compatibility
|
|
420
448
|
const middlewareRes = res;
|
|
421
449
|
middlewareRes.respond = createRespondFunction(routeDefinition, (status, data) => {
|
|
422
450
|
res.status(status).json(data);
|
|
423
|
-
});
|
|
451
|
+
}, middlewareRes);
|
|
452
|
+
middlewareRes.onResponse = (callback) => {
|
|
453
|
+
// Store callback on the underlying express response so it's accessible from TypedResponse
|
|
454
|
+
if (!res._responseCallbacks) {
|
|
455
|
+
res._responseCallbacks = [];
|
|
456
|
+
}
|
|
457
|
+
res._responseCallbacks.push(callback);
|
|
458
|
+
};
|
|
424
459
|
await middleware(req, middlewareRes, next, { domain: currentDomain, routeKey: currentRouteKey });
|
|
425
460
|
}
|
|
426
461
|
catch (error) {
|
|
@@ -273,6 +273,17 @@ function registerHonoRouteHandlers(app, apiDefinition, routeHandlers, middleware
|
|
|
273
273
|
c.ctx = c.get('ctx') || {};
|
|
274
274
|
// Add respond method to context
|
|
275
275
|
c.respond = (status, data) => {
|
|
276
|
+
// Call any registered response callbacks from middleware
|
|
277
|
+
if (c._responseCallbacks) {
|
|
278
|
+
c._responseCallbacks.forEach((callback) => {
|
|
279
|
+
try {
|
|
280
|
+
callback(status, data);
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
console.error('Error in response callback:', error);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
276
287
|
const responseSchema = routeDefinition.responses[status];
|
|
277
288
|
if (!responseSchema) {
|
|
278
289
|
console.error(`No response schema defined for status ${status} in route ${String(currentDomain)}/${String(currentRouteKey)}`);
|
|
@@ -330,6 +341,13 @@ function registerHonoRouteHandlers(app, apiDefinition, routeHandlers, middleware
|
|
|
330
341
|
};
|
|
331
342
|
const fakeRes = {
|
|
332
343
|
respond: c.respond,
|
|
344
|
+
respondContentType: (status, data, contentType) => {
|
|
345
|
+
// For Hono, set the response directly
|
|
346
|
+
c.__response = new Response(data, {
|
|
347
|
+
status: status,
|
|
348
|
+
headers: { 'Content-Type': contentType }
|
|
349
|
+
});
|
|
350
|
+
},
|
|
333
351
|
setHeader: (name, value) => {
|
|
334
352
|
c.header(name, value);
|
|
335
353
|
return fakeRes;
|
|
@@ -419,9 +437,20 @@ function registerHonoRouteHandlers(app, apiDefinition, routeHandlers, middleware
|
|
|
419
437
|
path: c.req.path,
|
|
420
438
|
originalUrl: c.req.url
|
|
421
439
|
};
|
|
422
|
-
// Create minimal res object with respond
|
|
440
|
+
// Create minimal res object with respond and onResponse methods for middleware compatibility
|
|
423
441
|
const fakeRes = {
|
|
424
442
|
respond: (status, data) => {
|
|
443
|
+
// Call any registered response callbacks
|
|
444
|
+
if (c._responseCallbacks) {
|
|
445
|
+
c._responseCallbacks.forEach((callback) => {
|
|
446
|
+
try {
|
|
447
|
+
callback(status, data);
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
console.error('Error in response callback:', error);
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
}
|
|
425
454
|
const responseSchema = routeDefinition.responses[status];
|
|
426
455
|
if (!responseSchema) {
|
|
427
456
|
console.error(`No response schema defined for status ${status}`);
|
|
@@ -475,6 +504,13 @@ function registerHonoRouteHandlers(app, apiDefinition, routeHandlers, middleware
|
|
|
475
504
|
},
|
|
476
505
|
end: () => {
|
|
477
506
|
// Perhaps do nothing or set response
|
|
507
|
+
},
|
|
508
|
+
onResponse: (callback) => {
|
|
509
|
+
// Store callback to be called when respond() is invoked
|
|
510
|
+
if (!c._responseCallbacks) {
|
|
511
|
+
c._responseCallbacks = [];
|
|
512
|
+
}
|
|
513
|
+
c._responseCallbacks.push(callback);
|
|
478
514
|
}
|
|
479
515
|
};
|
|
480
516
|
// Call Express-style middleware
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { ApiClient, FetchHttpClientAdapter } from './client';
|
|
|
2
2
|
export { generateOpenApiSpec } from './openapi';
|
|
3
3
|
export { generateOpenApiSpec as generateOpenApiSpec2 } from './openapi-self';
|
|
4
4
|
export { CreateApiDefinition, CreateResponses, ApiDefinitionSchema } from './definition';
|
|
5
|
-
export { RegisterHandlers, EndpointMiddleware, UniversalEndpointMiddleware, SimpleMiddleware, EndpointInfo } from './object-handlers';
|
|
5
|
+
export { RegisterHandlers, EndpointMiddleware, UniversalEndpointMiddleware, SimpleMiddleware, EndpointInfo, MiddlewareResponse } from './object-handlers';
|
|
6
6
|
export { File as UploadedFile } from './router';
|
|
7
7
|
export { z as ZodSchema } from 'zod';
|
|
8
8
|
export { RegisterHonoHandlers, registerHonoRouteHandlers, HonoFile, HonoFileType, honoFileSchema, HonoTypedContext, CreateTypedHonoHandlerWithContext } from './hono-cloudflare-workers';
|
|
@@ -19,6 +19,7 @@ export interface MiddlewareResponse {
|
|
|
19
19
|
json(data: any): void;
|
|
20
20
|
setHeader(name: string, value: string): void;
|
|
21
21
|
end(): void;
|
|
22
|
+
onResponse(callback: (status: number, data: any) => void): void;
|
|
22
23
|
}
|
|
23
24
|
export type EndpointMiddlewareCtx<Ctx extends Record<string, any> = Record<string, any>, TDef extends ApiDefinitionSchema = ApiDefinitionSchema> = ((req: express.Request & {
|
|
24
25
|
ctx?: Ctx;
|
package/dist/router.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ type ResponseDataForStatus<TDef extends ApiDefinitionSchema, TDomain extends key
|
|
|
14
14
|
type RespondFunction<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef['endpoints'], TRouteName extends keyof TDef['endpoints'][TDomain]> = <TStatusLocal extends keyof TDef['endpoints'][TDomain][TRouteName]['responses'] & number>(status: TStatusLocal, data: ResponseDataForStatus<TDef, TDomain, TRouteName, TStatusLocal>) => void;
|
|
15
15
|
export interface TypedResponse<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef['endpoints'], TRouteName extends keyof TDef['endpoints'][TDomain], L extends Record<string, any> = Record<string, any>> extends express.Response<any, L> {
|
|
16
16
|
respond: RespondFunction<TDef, TDomain, TRouteName>;
|
|
17
|
+
respondContentType: (status: number, data: any, contentType: string) => void;
|
|
17
18
|
setHeader: (name: string, value: string) => this;
|
|
18
19
|
json: <B = any>(body: B) => this;
|
|
19
20
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import { PrivateApiDefinition, PublicApiDefinition } from './definitions';
|
|
3
|
-
import { RegisterHandlers, EndpointMiddleware
|
|
3
|
+
import { RegisterHandlers, EndpointMiddleware } from '../../src';
|
|
4
4
|
const app = express();
|
|
5
5
|
const port = 3001;
|
|
6
6
|
app.set('etag', false);
|
|
@@ -39,7 +39,16 @@ RegisterHandlers(app, PublicApiDefinition, {
|
|
|
39
39
|
// TypeScript will give you type errors if this handler is missing
|
|
40
40
|
ping: async (req, res) => {
|
|
41
41
|
// req and res are fully typed based on the API definition
|
|
42
|
-
|
|
42
|
+
if (req.query.format === 'html') {
|
|
43
|
+
res.respondContentType(200, "<h1>pong</h1>", "text/html");
|
|
44
|
+
} else {
|
|
45
|
+
res.respond(200, "pong");
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
customHeaders: async (req, res) => {
|
|
49
|
+
res.setHeader('x-custom-test', 'test-value');
|
|
50
|
+
res.setHeader('x-another-header', 'another-value');
|
|
51
|
+
res.respond(200, { message: "headers set" });
|
|
43
52
|
}
|
|
44
53
|
},
|
|
45
54
|
status: {
|
|
@@ -59,10 +68,10 @@ RegisterHandlers(app, PublicApiDefinition, {
|
|
|
59
68
|
// Add another api definition
|
|
60
69
|
RegisterHandlers(app, PrivateApiDefinition, {
|
|
61
70
|
user: {
|
|
62
|
-
get:
|
|
71
|
+
get: async (req, res) => {
|
|
63
72
|
console.log('Fetching user', req.params.id);
|
|
64
73
|
res.respond(200, "ok");
|
|
65
|
-
}
|
|
74
|
+
}
|
|
66
75
|
}
|
|
67
76
|
}, [loggingMiddlewareTyped, authMiddleware]);
|
|
68
77
|
|
package/package.json
CHANGED
package/src/handler.ts
CHANGED
|
@@ -83,9 +83,21 @@ function preprocessQueryParams(query: any, querySchema?: z.ZodTypeAny): any {
|
|
|
83
83
|
// Helper function to create respond method for middleware compatibility
|
|
84
84
|
function createRespondFunction(
|
|
85
85
|
routeDefinition: RouteSchema,
|
|
86
|
-
responseSetter: (status: number, data: any) => void
|
|
86
|
+
responseSetter: (status: number, data: any) => void,
|
|
87
|
+
middlewareRes?: any
|
|
87
88
|
) {
|
|
88
89
|
return (status: number, data: any) => {
|
|
90
|
+
// Call any registered response callbacks
|
|
91
|
+
if (middlewareRes && middlewareRes._responseCallbacks) {
|
|
92
|
+
middlewareRes._responseCallbacks.forEach((callback: (status: number, data: any) => void) => {
|
|
93
|
+
try {
|
|
94
|
+
callback(status, data);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('Error in response callback:', error);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
89
101
|
const responseSchema = routeDefinition.responses[status];
|
|
90
102
|
|
|
91
103
|
if (!responseSchema) {
|
|
@@ -359,6 +371,17 @@ export function registerRouteHandlers<TDef extends ApiDefinitionSchema>(
|
|
|
359
371
|
const typedExpressRes = expressRes as TypedResponse<TDef, typeof currentDomain, typeof currentRouteKey>;
|
|
360
372
|
|
|
361
373
|
typedExpressRes.respond = (status, dataForResponse) => {
|
|
374
|
+
// Call any registered response callbacks from middleware
|
|
375
|
+
if ((expressRes as any)._responseCallbacks) {
|
|
376
|
+
(expressRes as any)._responseCallbacks.forEach((callback: (status: number, data: any) => void) => {
|
|
377
|
+
try {
|
|
378
|
+
callback(status, dataForResponse);
|
|
379
|
+
} catch (error) {
|
|
380
|
+
console.error('Error in response callback:', error);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
362
385
|
// Use the passed apiDefinition object
|
|
363
386
|
const routeSchemaForHandler = apiDefinition.endpoints[currentDomain][currentRouteKey] as RouteSchema;
|
|
364
387
|
const responseSchemaForStatus = routeSchemaForHandler.responses[status as number];
|
|
@@ -408,6 +431,13 @@ export function registerRouteHandlers<TDef extends ApiDefinitionSchema>(
|
|
|
408
431
|
}
|
|
409
432
|
};
|
|
410
433
|
|
|
434
|
+
typedExpressRes.respondContentType = (status: number, data: any, contentType: string) => {
|
|
435
|
+
// Set the content type header
|
|
436
|
+
typedExpressRes.setHeader('Content-Type', contentType);
|
|
437
|
+
// Send the raw data without JSON wrapping or validation
|
|
438
|
+
typedExpressRes.status(status).send(data);
|
|
439
|
+
};
|
|
440
|
+
|
|
411
441
|
typedExpressRes.setHeader = (name: string, value: string) => {
|
|
412
442
|
// Call the original Express setHeader method to avoid recursion
|
|
413
443
|
Object.getPrototypeOf(expressRes).setHeader.call(expressRes, name, value);
|
|
@@ -480,11 +510,18 @@ export function registerRouteHandlers<TDef extends ApiDefinitionSchema>(
|
|
|
480
510
|
middlewares.forEach(middleware => {
|
|
481
511
|
const wrappedMiddleware: express.RequestHandler = async (req, res, next) => {
|
|
482
512
|
try {
|
|
483
|
-
// Add respond
|
|
513
|
+
// Add respond and onFinish methods to res for middleware compatibility
|
|
484
514
|
const middlewareRes = res as any;
|
|
485
515
|
middlewareRes.respond = createRespondFunction(routeDefinition, (status, data) => {
|
|
486
516
|
res.status(status).json(data);
|
|
487
|
-
});
|
|
517
|
+
}, middlewareRes);
|
|
518
|
+
middlewareRes.onResponse = (callback: (status: number, data: any) => void) => {
|
|
519
|
+
// Store callback on the underlying express response so it's accessible from TypedResponse
|
|
520
|
+
if (!(res as any)._responseCallbacks) {
|
|
521
|
+
(res as any)._responseCallbacks = [];
|
|
522
|
+
}
|
|
523
|
+
(res as any)._responseCallbacks.push(callback);
|
|
524
|
+
};
|
|
488
525
|
await middleware(req, middlewareRes as MiddlewareResponse, next, { domain: currentDomain, routeKey: currentRouteKey } as any);
|
|
489
526
|
} catch (error) {
|
|
490
527
|
next(error);
|
|
@@ -341,6 +341,17 @@ export function registerHonoRouteHandlers<
|
|
|
341
341
|
|
|
342
342
|
// Add respond method to context
|
|
343
343
|
(c as any).respond = (status: number, data: any) => {
|
|
344
|
+
// Call any registered response callbacks from middleware
|
|
345
|
+
if ((c as any)._responseCallbacks) {
|
|
346
|
+
(c as any)._responseCallbacks.forEach((callback: (status: number, data: any) => void) => {
|
|
347
|
+
try {
|
|
348
|
+
callback(status, data);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
console.error('Error in response callback:', error);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
344
355
|
const responseSchema = routeDefinition.responses[status];
|
|
345
356
|
|
|
346
357
|
if (!responseSchema) {
|
|
@@ -408,6 +419,13 @@ export function registerHonoRouteHandlers<
|
|
|
408
419
|
|
|
409
420
|
const fakeRes = {
|
|
410
421
|
respond: (c as any).respond,
|
|
422
|
+
respondContentType: (status: number, data: any, contentType: string) => {
|
|
423
|
+
// For Hono, set the response directly
|
|
424
|
+
(c as any).__response = new Response(data, {
|
|
425
|
+
status: status,
|
|
426
|
+
headers: { 'Content-Type': contentType }
|
|
427
|
+
});
|
|
428
|
+
},
|
|
411
429
|
setHeader: (name: string, value: string) => {
|
|
412
430
|
c.header(name, value);
|
|
413
431
|
return fakeRes;
|
|
@@ -501,9 +519,20 @@ export function registerHonoRouteHandlers<
|
|
|
501
519
|
originalUrl: c.req.url
|
|
502
520
|
};
|
|
503
521
|
|
|
504
|
-
// Create minimal res object with respond
|
|
522
|
+
// Create minimal res object with respond and onResponse methods for middleware compatibility
|
|
505
523
|
const fakeRes = {
|
|
506
524
|
respond: (status: number, data: any) => {
|
|
525
|
+
// Call any registered response callbacks
|
|
526
|
+
if ((c as any)._responseCallbacks) {
|
|
527
|
+
(c as any)._responseCallbacks.forEach((callback: (status: number, data: any) => void) => {
|
|
528
|
+
try {
|
|
529
|
+
callback(status, data);
|
|
530
|
+
} catch (error) {
|
|
531
|
+
console.error('Error in response callback:', error);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
507
536
|
const responseSchema = routeDefinition.responses[status];
|
|
508
537
|
|
|
509
538
|
if (!responseSchema) {
|
|
@@ -564,6 +593,13 @@ export function registerHonoRouteHandlers<
|
|
|
564
593
|
},
|
|
565
594
|
end: () => {
|
|
566
595
|
// Perhaps do nothing or set response
|
|
596
|
+
},
|
|
597
|
+
onResponse: (callback: (status: number, data: any) => void) => {
|
|
598
|
+
// Store callback to be called when respond() is invoked
|
|
599
|
+
if (!(c as any)._responseCallbacks) {
|
|
600
|
+
(c as any)._responseCallbacks = [];
|
|
601
|
+
}
|
|
602
|
+
(c as any)._responseCallbacks.push(callback);
|
|
567
603
|
}
|
|
568
604
|
};
|
|
569
605
|
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { ApiClient, FetchHttpClientAdapter } from './client';
|
|
|
2
2
|
export { generateOpenApiSpec } from './openapi'
|
|
3
3
|
export { generateOpenApiSpec as generateOpenApiSpec2 } from './openapi-self'
|
|
4
4
|
export { CreateApiDefinition, CreateResponses, ApiDefinitionSchema } from './definition';
|
|
5
|
-
export { RegisterHandlers, EndpointMiddleware, UniversalEndpointMiddleware, SimpleMiddleware, EndpointInfo } from './object-handlers';
|
|
5
|
+
export { RegisterHandlers, EndpointMiddleware, UniversalEndpointMiddleware, SimpleMiddleware, EndpointInfo, MiddlewareResponse } from './object-handlers';
|
|
6
6
|
export { File as UploadedFile } from './router';
|
|
7
7
|
export { z as ZodSchema } from 'zod';
|
|
8
8
|
|
package/src/object-handlers.ts
CHANGED
|
@@ -43,6 +43,7 @@ export interface MiddlewareResponse {
|
|
|
43
43
|
json(data: any): void;
|
|
44
44
|
setHeader(name: string, value: string): void;
|
|
45
45
|
end(): void;
|
|
46
|
+
onResponse(callback: (status: number, data: any) => void): void;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// Unified middleware type that works for both Express and Hono with context typing
|
package/src/router.ts
CHANGED
|
@@ -59,6 +59,7 @@ export interface TypedResponse<
|
|
|
59
59
|
L extends Record<string, any> = Record<string, any>
|
|
60
60
|
> extends express.Response<any, L> {
|
|
61
61
|
respond: RespondFunction<TDef, TDomain, TRouteName>;
|
|
62
|
+
respondContentType: (status: number, data: any, contentType: string) => void;
|
|
62
63
|
setHeader: (name: string, value: string) => this;
|
|
63
64
|
json: <B = any>(body: B) => this; // Keep original json
|
|
64
65
|
}
|
package/tests/middleware.test.ts
CHANGED
|
@@ -105,4 +105,81 @@ describe.each([
|
|
|
105
105
|
).rejects.toThrow('Forbidden as expected');
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
|
+
|
|
109
|
+
describe('Response Logging Middleware', () => {
|
|
110
|
+
let consoleSpy: jest.SpyInstance;
|
|
111
|
+
|
|
112
|
+
beforeEach(() => {
|
|
113
|
+
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
afterEach(() => {
|
|
117
|
+
consoleSpy.mockRestore();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('should log response status without breaking functionality', async () => {
|
|
121
|
+
// The response logging middleware should not interfere with normal operation
|
|
122
|
+
const result = await client.callApi('public', 'ping', {}, {
|
|
123
|
+
200: ({ data }) => {
|
|
124
|
+
expect(data.message).toBe('pong');
|
|
125
|
+
return data;
|
|
126
|
+
},
|
|
127
|
+
422: ({ error }) => {
|
|
128
|
+
throw new Error(`Validation error: ${JSON.stringify(error)}`);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(result.message).toBe('pong');
|
|
133
|
+
|
|
134
|
+
// Assert that console.log was called with the expected message
|
|
135
|
+
expect(consoleSpy).toHaveBeenCalledWith('[TIMING] public.ping responded with 200');
|
|
136
|
+
expect(consoleSpy).toHaveBeenCalledWith('[Test] GET /api/v1/ping - Domain: public, Route: ping');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('should log response status for protected routes', async () => {
|
|
140
|
+
const result = await client.callApi('public', 'protected', { headers: { Authorization: 'Bearer valid-token' } }, {
|
|
141
|
+
200: ({ data }) => {
|
|
142
|
+
expect(data.message).toBe('protected content');
|
|
143
|
+
expect(data.user).toBe('testuser');
|
|
144
|
+
return data;
|
|
145
|
+
},
|
|
146
|
+
401: ({ data }) => {
|
|
147
|
+
throw new Error(`Authentication failed: ${data.error}`);
|
|
148
|
+
},
|
|
149
|
+
403: ({ data }) => {
|
|
150
|
+
throw new Error(`Forbidden: ${data.error}`);
|
|
151
|
+
},
|
|
152
|
+
422: ({ error }) => {
|
|
153
|
+
throw new Error(`Validation error: ${JSON.stringify(error)}`);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(result.user).toBe('testuser');
|
|
158
|
+
|
|
159
|
+
// Assert that console.log was called with the expected messages
|
|
160
|
+
expect(consoleSpy).toHaveBeenCalledWith('[TIMING] public.protected responded with 200');
|
|
161
|
+
expect(consoleSpy).toHaveBeenCalledWith('[Test] GET /api/v1/protected - Domain: public, Route: protected');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('should log error status codes for auth failures', async () => {
|
|
165
|
+
await expect(
|
|
166
|
+
client.callApi('public', 'protected', {}, {
|
|
167
|
+
200: ({ data }) => data,
|
|
168
|
+
401: ({ data }) => {
|
|
169
|
+
expect(data.error).toBe('No authorization header');
|
|
170
|
+
throw new Error('Authentication failed as expected');
|
|
171
|
+
},
|
|
172
|
+
403: ({ data }) => {
|
|
173
|
+
throw new Error(`Unexpected forbidden: ${data.error}`);
|
|
174
|
+
},
|
|
175
|
+
422: ({ error }) => {
|
|
176
|
+
throw new Error(`Validation error: ${JSON.stringify(error)}`);
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
).rejects.toThrow('Authentication failed as expected');
|
|
180
|
+
|
|
181
|
+
// Assert that console.log was called with the error status
|
|
182
|
+
expect(consoleSpy).toHaveBeenCalledWith('[TIMING] public.protected responded with 401');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
108
185
|
});
|
package/tests/setup.ts
CHANGED
|
@@ -13,7 +13,11 @@ import { EndpointMiddlewareCtx } from '../src/object-handlers';
|
|
|
13
13
|
const simplePublicHandlers = {
|
|
14
14
|
common: {
|
|
15
15
|
ping: async (req: any, res: any) => {
|
|
16
|
-
|
|
16
|
+
if (req.query?.format === 'html') {
|
|
17
|
+
res.respondContentType(200, "<h1>pong</h1>", "text/html");
|
|
18
|
+
} else {
|
|
19
|
+
res.respond(200, "pong");
|
|
20
|
+
}
|
|
17
21
|
},
|
|
18
22
|
customHeaders: async (req: any, res: any) => {
|
|
19
23
|
res.setHeader('X-Custom-Test', 'test-value');
|
|
@@ -477,6 +481,14 @@ const middlewareTestHandlers = {
|
|
|
477
481
|
// Generic middleware setup function
|
|
478
482
|
function setupMiddlewareApp(app: any, isHono: boolean) {
|
|
479
483
|
// Define middleware functions
|
|
484
|
+
const timingMiddleware: EndpointMiddlewareCtx<Ctx> = async (req, res, next, endpointInfo) => {
|
|
485
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
486
|
+
res.onResponse((status, _data) => {
|
|
487
|
+
console.log(`[TIMING] ${endpointInfo.domain}.${endpointInfo.routeKey} responded with ${status}`);
|
|
488
|
+
});
|
|
489
|
+
await next();
|
|
490
|
+
};
|
|
491
|
+
|
|
480
492
|
const loggingMiddleware: EndpointMiddlewareCtx<Ctx> = async (req, res, next, endpointInfo) => {
|
|
481
493
|
console.log(`[Test] ${req.method} ${req.path} - Domain: ${endpointInfo.domain}, Route: ${endpointInfo.routeKey}`);
|
|
482
494
|
await next();
|
|
@@ -505,6 +517,7 @@ function setupMiddlewareApp(app: any, isHono: boolean) {
|
|
|
505
517
|
}
|
|
506
518
|
|
|
507
519
|
const middlewares = [
|
|
520
|
+
timingMiddleware,
|
|
508
521
|
loggingMiddleware,
|
|
509
522
|
contextMiddleware,
|
|
510
523
|
authMiddleware
|
package/tests/simple-api.test.ts
CHANGED
|
@@ -27,6 +27,19 @@ describe.each([
|
|
|
27
27
|
expect(result).toBe('pong');
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
test('should ping successfully with HTML format', async () => {
|
|
31
|
+
const response = await fetch(`${baseUrl}/api/v1/public/ping?format=html`);
|
|
32
|
+
expect(response.status).toBe(200);
|
|
33
|
+
const contentType = response.headers.get('content-type');
|
|
34
|
+
if (serverName === 'Express') {
|
|
35
|
+
expect(contentType).toBe('text/html; charset=utf-8');
|
|
36
|
+
} else {
|
|
37
|
+
expect(contentType).toBe('text/html');
|
|
38
|
+
}
|
|
39
|
+
const html = await response.text();
|
|
40
|
+
expect(html).toBe('<h1>pong</h1>');
|
|
41
|
+
});
|
|
42
|
+
|
|
30
43
|
test('should handle probe1 with match=true', async () => {
|
|
31
44
|
const result = await client.callApi('status', 'probe1', {
|
|
32
45
|
query: { match: true }
|