@fishka/express 0.9.13 → 0.9.15
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 +108 -38
- package/dist/cjs/api.types.d.ts +8 -21
- package/dist/cjs/api.types.js +1 -21
- package/dist/cjs/api.types.js.map +1 -1
- package/dist/cjs/error-handling.d.ts +72 -3
- package/dist/cjs/error-handling.js +107 -7
- package/dist/cjs/error-handling.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/route-table.d.ts +15 -7
- package/dist/cjs/route-table.js +3 -0
- package/dist/cjs/route-table.js.map +1 -1
- package/dist/cjs/router.d.ts +25 -33
- package/dist/cjs/router.js +40 -60
- package/dist/cjs/router.js.map +1 -1
- package/dist/cjs/utils/type-validators.d.ts +58 -0
- package/dist/cjs/utils/type-validators.js +122 -0
- package/dist/cjs/utils/type-validators.js.map +1 -0
- package/dist/esm/api.types.d.ts +8 -21
- package/dist/esm/api.types.js +0 -18
- package/dist/esm/api.types.js.map +1 -1
- package/dist/esm/error-handling.d.ts +72 -3
- package/dist/esm/error-handling.js +106 -7
- package/dist/esm/error-handling.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/route-table.d.ts +15 -7
- package/dist/esm/route-table.js +3 -0
- package/dist/esm/route-table.js.map +1 -1
- package/dist/esm/router.d.ts +25 -33
- package/dist/esm/router.js +42 -62
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/utils/type-validators.d.ts +58 -0
- package/dist/esm/utils/type-validators.js +102 -0
- package/dist/esm/utils/type-validators.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,9 +1,78 @@
|
|
|
1
1
|
import { NextFunction } from 'express';
|
|
2
2
|
import { ExpressFunction, ExpressRequest, ExpressResponse } from './utils/express.utils';
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* @Internal
|
|
5
|
+
* Wraps a route handler to catch and convert errors to API responses.
|
|
6
|
+
* Applied automatically to all routes registered via createRouteTable().
|
|
7
|
+
*
|
|
8
|
+
* Catches:
|
|
9
|
+
* - Errors thrown in validators ($path, $query, $body)
|
|
10
|
+
* - Errors thrown in the run() handler
|
|
11
|
+
* - Errors thrown in endpoint middlewares
|
|
12
|
+
*
|
|
13
|
+
* Logs errors to console (error level for 5xx).
|
|
14
|
+
*/
|
|
4
15
|
export declare function catchRouteErrors(fn: ExpressFunction): ExpressFunction;
|
|
5
16
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
17
|
+
* Express error-handling middleware (4 parameters) for catching errors.
|
|
18
|
+
* Can be mounted at any level - global, path-specific, or router-specific.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // Global
|
|
22
|
+
* app.use(catchAllMiddleware);
|
|
23
|
+
*
|
|
24
|
+
* // Path-specific
|
|
25
|
+
* app.use('/api', catchAllMiddleware);
|
|
26
|
+
*
|
|
27
|
+
* // Router-specific
|
|
28
|
+
* const router = express.Router();
|
|
29
|
+
* router.use(catchAllMiddleware);
|
|
30
|
+
*
|
|
31
|
+
* Catches:
|
|
32
|
+
* - Errors from Express middleware (passed via next(error))
|
|
33
|
+
* - JSON parsing errors (SyntaxError) - returns 400
|
|
34
|
+
* - Any errors that escape catchRouteErrors
|
|
35
|
+
*
|
|
36
|
+
* Note: Individual routes are already wrapped with catchRouteErrors(),
|
|
37
|
+
* so this middleware primarily catches middleware and parsing errors.
|
|
8
38
|
*/
|
|
9
39
|
export declare function catchAllMiddleware(error: unknown, _: ExpressRequest, res: ExpressResponse, next: NextFunction): Promise<void>;
|
|
40
|
+
/** Options for installProcessHandlers(). */
|
|
41
|
+
export interface ProcessHandlersOptions {
|
|
42
|
+
/** Custom handler for uncaught exceptions. Called before default logging. */
|
|
43
|
+
onUncaughtException?: (error: Error) => void;
|
|
44
|
+
/** Custom handler for unhandled promise rejections. Called before default logging. */
|
|
45
|
+
onUnhandledRejection?: (reason: unknown) => void;
|
|
46
|
+
/** Async cleanup function called on shutdown signals. */
|
|
47
|
+
onShutdown?: () => Promise<void>;
|
|
48
|
+
/** Force exit timeout in ms if shutdown hangs. Default: 10000 */
|
|
49
|
+
shutdownTimeout?: number;
|
|
50
|
+
/** Signals to handle for graceful shutdown. Default: ['SIGTERM', 'SIGINT'] */
|
|
51
|
+
shutdownSignals?: NodeJS.Signals[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Installs process-level handlers for errors and graceful shutdown.
|
|
55
|
+
* Call once at application startup, before app.listen().
|
|
56
|
+
*
|
|
57
|
+
* Error handling:
|
|
58
|
+
* - Uncaught exceptions (sync throws outside Express middleware)
|
|
59
|
+
* - Unhandled promise rejections (forgotten await, missing .catch())
|
|
60
|
+
*
|
|
61
|
+
* Graceful shutdown:
|
|
62
|
+
* - SIGTERM (Docker/K8s/systemd stop)
|
|
63
|
+
* - SIGINT (Ctrl+C)
|
|
64
|
+
* - Timeout protection (force exit if shutdown hangs)
|
|
65
|
+
* - Double-shutdown prevention
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* installProcessHandlers({
|
|
69
|
+
* onUncaughtException: (error) => sendToMonitoring(error),
|
|
70
|
+
* onUnhandledRejection: (reason) => sendToMonitoring(reason),
|
|
71
|
+
* onShutdown: async () => {
|
|
72
|
+
* await database.close();
|
|
73
|
+
* await server.close();
|
|
74
|
+
* },
|
|
75
|
+
* shutdownTimeout: 15000,
|
|
76
|
+
* });
|
|
77
|
+
*/
|
|
78
|
+
export declare function installProcessHandlers(options?: ProcessHandlersOptions): void;
|
|
@@ -3,6 +3,12 @@ import { HttpError } from './api.types';
|
|
|
3
3
|
import { HTTP_BAD_REQUEST, HTTP_INTERNAL_SERVER_ERROR } from './http-status-codes';
|
|
4
4
|
import { getRequestLocalStorage } from './thread-local/thread-local-storage';
|
|
5
5
|
import { wrapAsApiResponse } from './utils/conversion.utils';
|
|
6
|
+
/**
|
|
7
|
+
* Converts any error into a standardized API response format.
|
|
8
|
+
* - HttpError: Uses the error's status code and message
|
|
9
|
+
* - Other errors: Returns 500 with the error message or 'Internal error'
|
|
10
|
+
* Attaches requestId from thread-local storage if available.
|
|
11
|
+
*/
|
|
6
12
|
function buildApiResponse(error) {
|
|
7
13
|
const tls = getRequestLocalStorage();
|
|
8
14
|
const requestId = tls?.requestId;
|
|
@@ -28,7 +34,18 @@ function buildApiResponse(error) {
|
|
|
28
34
|
}
|
|
29
35
|
return response;
|
|
30
36
|
}
|
|
31
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* @Internal
|
|
39
|
+
* Wraps a route handler to catch and convert errors to API responses.
|
|
40
|
+
* Applied automatically to all routes registered via createRouteTable().
|
|
41
|
+
*
|
|
42
|
+
* Catches:
|
|
43
|
+
* - Errors thrown in validators ($path, $query, $body)
|
|
44
|
+
* - Errors thrown in the run() handler
|
|
45
|
+
* - Errors thrown in endpoint middlewares
|
|
46
|
+
*
|
|
47
|
+
* Logs errors to console (error level for 5xx).
|
|
48
|
+
*/
|
|
32
49
|
export function catchRouteErrors(fn) {
|
|
33
50
|
return async (req, res, next) => {
|
|
34
51
|
try {
|
|
@@ -39,17 +56,33 @@ export function catchRouteErrors(fn) {
|
|
|
39
56
|
if (apiResponse.status >= HTTP_INTERNAL_SERVER_ERROR) {
|
|
40
57
|
console.error(`catchRouteErrors: ${req.path}`, error);
|
|
41
58
|
}
|
|
42
|
-
else {
|
|
43
|
-
console.log(`catchRouteErrors: ${req.path}`, error);
|
|
44
|
-
}
|
|
45
59
|
res.status(apiResponse.status);
|
|
46
60
|
res.send(apiResponse);
|
|
47
61
|
}
|
|
48
62
|
};
|
|
49
63
|
}
|
|
50
64
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
65
|
+
* Express error-handling middleware (4 parameters) for catching errors.
|
|
66
|
+
* Can be mounted at any level - global, path-specific, or router-specific.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* // Global
|
|
70
|
+
* app.use(catchAllMiddleware);
|
|
71
|
+
*
|
|
72
|
+
* // Path-specific
|
|
73
|
+
* app.use('/api', catchAllMiddleware);
|
|
74
|
+
*
|
|
75
|
+
* // Router-specific
|
|
76
|
+
* const router = express.Router();
|
|
77
|
+
* router.use(catchAllMiddleware);
|
|
78
|
+
*
|
|
79
|
+
* Catches:
|
|
80
|
+
* - Errors from Express middleware (passed via next(error))
|
|
81
|
+
* - JSON parsing errors (SyntaxError) - returns 400
|
|
82
|
+
* - Any errors that escape catchRouteErrors
|
|
83
|
+
*
|
|
84
|
+
* Note: Individual routes are already wrapped with catchRouteErrors(),
|
|
85
|
+
* so this middleware primarily catches middleware and parsing errors.
|
|
53
86
|
*/
|
|
54
87
|
export async function catchAllMiddleware(error, _, res, next) {
|
|
55
88
|
if (!error) {
|
|
@@ -59,9 +92,75 @@ export async function catchAllMiddleware(error, _, res, next) {
|
|
|
59
92
|
// Report as critical. This kind of error should never happen.
|
|
60
93
|
console.error('catchAllMiddleware:', getMessageFromError(error));
|
|
61
94
|
const apiResponse = error instanceof SyntaxError // JSON body parsing error.
|
|
62
|
-
? buildApiResponse(
|
|
95
|
+
? buildApiResponse(new HttpError(HTTP_BAD_REQUEST, `Failed to parse request: ${error.message}`))
|
|
63
96
|
: buildApiResponse(error);
|
|
64
97
|
res.status(apiResponse.status);
|
|
65
98
|
res.send(apiResponse);
|
|
66
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Installs process-level handlers for errors and graceful shutdown.
|
|
102
|
+
* Call once at application startup, before app.listen().
|
|
103
|
+
*
|
|
104
|
+
* Error handling:
|
|
105
|
+
* - Uncaught exceptions (sync throws outside Express middleware)
|
|
106
|
+
* - Unhandled promise rejections (forgotten await, missing .catch())
|
|
107
|
+
*
|
|
108
|
+
* Graceful shutdown:
|
|
109
|
+
* - SIGTERM (Docker/K8s/systemd stop)
|
|
110
|
+
* - SIGINT (Ctrl+C)
|
|
111
|
+
* - Timeout protection (force exit if shutdown hangs)
|
|
112
|
+
* - Double-shutdown prevention
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* installProcessHandlers({
|
|
116
|
+
* onUncaughtException: (error) => sendToMonitoring(error),
|
|
117
|
+
* onUnhandledRejection: (reason) => sendToMonitoring(reason),
|
|
118
|
+
* onShutdown: async () => {
|
|
119
|
+
* await database.close();
|
|
120
|
+
* await server.close();
|
|
121
|
+
* },
|
|
122
|
+
* shutdownTimeout: 15000,
|
|
123
|
+
* });
|
|
124
|
+
*/
|
|
125
|
+
export function installProcessHandlers(options) {
|
|
126
|
+
// Error handlers
|
|
127
|
+
process.on('uncaughtException', (error) => {
|
|
128
|
+
options?.onUncaughtException?.(error);
|
|
129
|
+
console.error('CRITICAL - Uncaught Exception:', error);
|
|
130
|
+
});
|
|
131
|
+
process.on('unhandledRejection', (reason) => {
|
|
132
|
+
options?.onUnhandledRejection?.(reason);
|
|
133
|
+
console.error('CRITICAL - Unhandled Rejection:', reason);
|
|
134
|
+
});
|
|
135
|
+
// Graceful shutdown
|
|
136
|
+
const onShutdown = options?.onShutdown;
|
|
137
|
+
if (onShutdown) {
|
|
138
|
+
let isShuttingDown = false;
|
|
139
|
+
const signals = options?.shutdownSignals ?? ['SIGTERM', 'SIGINT'];
|
|
140
|
+
const timeout = options?.shutdownTimeout ?? 10000;
|
|
141
|
+
const shutdown = async (signal) => {
|
|
142
|
+
if (isShuttingDown)
|
|
143
|
+
return;
|
|
144
|
+
isShuttingDown = true;
|
|
145
|
+
console.log(`${signal} received, shutting down gracefully...`);
|
|
146
|
+
const timer = setTimeout(() => {
|
|
147
|
+
console.error('Shutdown timeout, forcing exit');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}, timeout);
|
|
150
|
+
try {
|
|
151
|
+
await onShutdown();
|
|
152
|
+
clearTimeout(timer);
|
|
153
|
+
process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
console.error('Shutdown error:', err);
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
for (const signal of signals) {
|
|
162
|
+
process.on(signal, () => shutdown(signal));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
67
166
|
//# sourceMappingURL=error-handling.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-handling.js","sourceRoot":"","sources":["../../src/error-handling.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EAAe,SAAS,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACnF,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAG7D,SAAS,gBAAgB,CAAC,KAAc;IACtC,MAAM,GAAG,GAAG,sBAAsB,EAAE,CAAC;IACrC,MAAM,SAAS,GAAG,GAAG,EAAE,SAAS,CAAC;IACjC,IAAI,QAA0C,CAAC;IAE/C,IAAI,KAAK,YAAY,SAAS,EAAE,CAAC;QAC/B,QAAQ,GAAG;YACT,GAAG,iBAAiB,CAAC,SAAS,CAAC;YAC/B,KAAK,EAAE,KAAK,CAAC,OAAO;YACpB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,MAAM,YAAY,GAAG,mBAAmB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACpD,QAAQ,GAAG;YACT,GAAG,iBAAiB,CAAC,SAAS,CAAC;YAC/B,KAAK,EAAE,YAAY,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,gBAAgB;YAChF,MAAM,EAAE,0BAA0B;SACnC,CAAC;IACJ,CAAC;IAED,IAAI,SAAS,EAAE,CAAC;QACd,QAAQ,CAAC,SAAS,GAAG,SAAS,CAAC;IACjC,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"error-handling.js","sourceRoot":"","sources":["../../src/error-handling.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/index.d.ts
CHANGED
package/dist/esm/index.js
CHANGED
|
@@ -13,4 +13,5 @@ export * from './router';
|
|
|
13
13
|
export * from './thread-local/thread-local-storage';
|
|
14
14
|
export * from './thread-local/thread-local-storage-middleware';
|
|
15
15
|
export * from './utils/express.utils';
|
|
16
|
+
export * from './utils/type-validators';
|
|
16
17
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qCAAqC,CAAC;AACpD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,qCAAqC,CAAC;AACpD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qCAAqC,CAAC;AACpD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,qCAAqC,CAAC;AACpD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TypedValidatorMap } from './api.types';
|
|
1
2
|
import { DeleteEndpoint, GetEndpoint, PatchEndpoint, PostEndpoint, PutEndpoint, RequestContext, ResponseOrValue } from './router';
|
|
2
3
|
import { ExpressRouter } from './utils/express.utils';
|
|
3
4
|
/**
|
|
@@ -7,13 +8,20 @@ import { ExpressRouter } from './utils/express.utils';
|
|
|
7
8
|
export declare class RouteTable {
|
|
8
9
|
private readonly app;
|
|
9
10
|
constructor(app: ExpressRouter);
|
|
10
|
-
|
|
11
|
-
get<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
/** Register GET endpoint with full type inference for path/query params. */
|
|
12
|
+
get<Result, PathParams extends TypedValidatorMap = TypedValidatorMap, QueryParams extends TypedValidatorMap = TypedValidatorMap>(path: string, endpoint: GetEndpoint<Result, PathParams, QueryParams>): this;
|
|
13
|
+
/** Register GET endpoint with function shorthand. */
|
|
14
|
+
get<Result>(path: string, run: (ctx: RequestContext) => ResponseOrValue<Result> | Promise<ResponseOrValue<Result>>): this;
|
|
15
|
+
/** Register POST endpoint with full type inference for path/query params. */
|
|
16
|
+
post<Body, Result = void, PathParams extends TypedValidatorMap = TypedValidatorMap, QueryParams extends TypedValidatorMap = TypedValidatorMap>(path: string, endpoint: PostEndpoint<Body, Result, PathParams, QueryParams>): this;
|
|
17
|
+
/** Register PATCH endpoint with full type inference for path/query params. */
|
|
18
|
+
patch<Body, Result = void, PathParams extends TypedValidatorMap = TypedValidatorMap, QueryParams extends TypedValidatorMap = TypedValidatorMap>(path: string, endpoint: PatchEndpoint<Body, Result, PathParams, QueryParams>): this;
|
|
19
|
+
/** Register PUT endpoint with full type inference for path/query params. */
|
|
20
|
+
put<Body, Result = void, PathParams extends TypedValidatorMap = TypedValidatorMap, QueryParams extends TypedValidatorMap = TypedValidatorMap>(path: string, endpoint: PutEndpoint<Body, Result, PathParams, QueryParams>): this;
|
|
21
|
+
/** Register DELETE endpoint with full endpoint object. */
|
|
22
|
+
delete<PathParams extends TypedValidatorMap = TypedValidatorMap, QueryParams extends TypedValidatorMap = TypedValidatorMap>(path: string, endpoint: DeleteEndpoint<PathParams, QueryParams>): this;
|
|
23
|
+
/** Register DELETE endpoint with function shorthand. */
|
|
24
|
+
delete(path: string, run: (ctx: RequestContext) => void | Promise<void>): this;
|
|
17
25
|
}
|
|
18
26
|
/**
|
|
19
27
|
* Factory function to create a new route table.
|
package/dist/esm/route-table.js
CHANGED
|
@@ -12,14 +12,17 @@ export class RouteTable {
|
|
|
12
12
|
mountGet(this.app, path, endpoint);
|
|
13
13
|
return this;
|
|
14
14
|
}
|
|
15
|
+
/** Register POST endpoint with full type inference for path/query params. */
|
|
15
16
|
post(path, endpoint) {
|
|
16
17
|
mountPost(this.app, path, endpoint);
|
|
17
18
|
return this;
|
|
18
19
|
}
|
|
20
|
+
/** Register PATCH endpoint with full type inference for path/query params. */
|
|
19
21
|
patch(path, endpoint) {
|
|
20
22
|
mountPatch(this.app, path, endpoint);
|
|
21
23
|
return this;
|
|
22
24
|
}
|
|
25
|
+
/** Register PUT endpoint with full type inference for path/query params. */
|
|
23
26
|
put(path, endpoint) {
|
|
24
27
|
mountPut(this.app, path, endpoint);
|
|
25
28
|
return this;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-table.js","sourceRoot":"","sources":["../../src/route-table.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"route-table.js","sourceRoot":"","sources":["../../src/route-table.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,WAAW,EACX,QAAQ,EACR,UAAU,EACV,SAAS,EACT,QAAQ,GAMT,MAAM,UAAU,CAAC;AAGlB;;;GAGG;AACH,MAAM,OAAO,UAAU;IACrB,YAA6B,GAAkB;QAAlB,QAAG,GAAH,GAAG,CAAe;IAAG,CAAC;IAYnD,GAAG,CAKD,IAAY,EACZ,aAEyF;QAEzF,MAAM,QAAQ,GAAG,OAAO,aAAa,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;QAC9F,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,QAAgC,CAAC,CAAC;QAC3D,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6EAA6E;IAC7E,IAAI,CAKF,IAAY,EAAE,QAA6D;QAC3E,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,QAA4C,CAAC,CAAC;QACxE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8EAA8E;IAC9E,KAAK,CAKH,IAAY,EAAE,QAA8D;QAC5E,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,QAA6C,CAAC,CAAC;QAC1E,OAAO,IAAI,CAAC;IACd,CAAC;IAED,4EAA4E;IAC5E,GAAG,CAKD,IAAY,EAAE,QAA4D;QAC1E,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,QAA2C,CAAC,CAAC;QACtE,OAAO,IAAI,CAAC;IACd,CAAC;IAWD,MAAM,CAIJ,IAAY,EACZ,aAAwG;QAExG,MAAM,QAAQ,GAAG,OAAO,aAAa,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;QAC9F,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,QAA0B,CAAC,CAAC;QACxD,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAkB;IACjD,OAAO,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;AAC7B,CAAC"}
|
package/dist/esm/router.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Assertion, ObjectAssertion } from '@fishka/assertions';
|
|
2
|
-
import { ApiResponse,
|
|
2
|
+
import { ApiResponse, InferValidated, TypedValidatorMap } from './api.types';
|
|
3
3
|
import { AuthUser } from './auth/auth.types';
|
|
4
4
|
import { ExpressRequest, ExpressResponse, ExpressRouter } from './utils/express.utils';
|
|
5
5
|
/** Express API allows handlers to return a response in the raw form. */
|
|
@@ -10,7 +10,7 @@ export type ResponseOrValue<ResponseEntity> = ApiResponse<ResponseEntity> | Resp
|
|
|
10
10
|
*/
|
|
11
11
|
export type EndpointMiddleware<Context = RequestContext> = (run: () => Promise<unknown>, context: Context) => Promise<unknown>;
|
|
12
12
|
/** Generic request context passed to all handlers. Database-agnostic and extensible. */
|
|
13
|
-
export interface RequestContext<Body = void> {
|
|
13
|
+
export interface RequestContext<Body = void, PathParams extends TypedValidatorMap = TypedValidatorMap, QueryParams extends TypedValidatorMap = TypedValidatorMap> {
|
|
14
14
|
/** Parsed and validated request body (for POST/PATCH/PUT handlers). */
|
|
15
15
|
body: Body;
|
|
16
16
|
/** Express Request object. */
|
|
@@ -19,18 +19,10 @@ export interface RequestContext<Body = void> {
|
|
|
19
19
|
res: ExpressResponse;
|
|
20
20
|
/** Authenticated user (if any). Populated by auth middleware. */
|
|
21
21
|
authUser?: AuthUser;
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
params: {
|
|
27
|
-
get(key: string): string;
|
|
28
|
-
tryGet(key: string): string | undefined;
|
|
29
|
-
};
|
|
30
|
-
/** Query parameter access. */
|
|
31
|
-
query: {
|
|
32
|
-
get(key: string): string | undefined;
|
|
33
|
-
};
|
|
22
|
+
/** Validated path parameters (typed from $path validators). */
|
|
23
|
+
path: InferValidated<PathParams>;
|
|
24
|
+
/** Validated query parameters (typed from $query validators). */
|
|
25
|
+
query: InferValidated<QueryParams>;
|
|
34
26
|
/**
|
|
35
27
|
* Generic state storage for middleware to attach data.
|
|
36
28
|
* Allows middleware to pass information to handlers and other middleware.
|
|
@@ -38,47 +30,47 @@ export interface RequestContext<Body = void> {
|
|
|
38
30
|
state: Map<string, unknown>;
|
|
39
31
|
}
|
|
40
32
|
/** Base interface with common endpoint properties. */
|
|
41
|
-
export interface EndpointBase<
|
|
42
|
-
/** Path parameter
|
|
43
|
-
$path?:
|
|
44
|
-
/** Query parameter
|
|
45
|
-
$query?:
|
|
33
|
+
export interface EndpointBase<PathParams extends TypedValidatorMap = TypedValidatorMap, QueryParams extends TypedValidatorMap = TypedValidatorMap, Body = void, Result = unknown> {
|
|
34
|
+
/** Path parameter validators (typed). */
|
|
35
|
+
$path?: PathParams;
|
|
36
|
+
/** Query parameter validators (typed). */
|
|
37
|
+
$query?: QueryParams;
|
|
46
38
|
/** Optional middleware to execute before the handler. */
|
|
47
39
|
middlewares?: Array<EndpointMiddleware>;
|
|
48
|
-
/** Handler function. */
|
|
49
|
-
run: (ctx:
|
|
40
|
+
/** Handler function. Can be sync or async. */
|
|
41
|
+
run: (ctx: RequestContext<Body, PathParams, QueryParams>) => ResponseOrValue<Result> | Promise<ResponseOrValue<Result>>;
|
|
50
42
|
}
|
|
51
43
|
/** Descriptor for GET list routes. */
|
|
52
|
-
export type GetListEndpoint<ResultElementType> = EndpointBase<
|
|
44
|
+
export type GetListEndpoint<ResultElementType, PathParams extends TypedValidatorMap = TypedValidatorMap, QueryParams extends TypedValidatorMap = TypedValidatorMap> = EndpointBase<PathParams, QueryParams, void, Array<ResultElementType>>;
|
|
53
45
|
/** Descriptor for GET routes. */
|
|
54
|
-
export type GetEndpoint<Result> = EndpointBase<
|
|
46
|
+
export type GetEndpoint<Result, PathParams extends TypedValidatorMap = TypedValidatorMap, QueryParams extends TypedValidatorMap = TypedValidatorMap> = EndpointBase<PathParams, QueryParams, void, Result>;
|
|
55
47
|
/** Descriptor for POST routes. */
|
|
56
|
-
export interface PostEndpoint<Body, Result = void> extends EndpointBase<
|
|
48
|
+
export interface PostEndpoint<Body, Result = void, PathParams extends TypedValidatorMap = TypedValidatorMap, QueryParams extends TypedValidatorMap = TypedValidatorMap> extends EndpointBase<PathParams, QueryParams, Body, Result> {
|
|
57
49
|
/** Request body validator. */
|
|
58
50
|
$body: Body extends object ? ObjectAssertion<Body> : Assertion<Body>;
|
|
59
51
|
}
|
|
60
52
|
/** Same as POST. Used for full object updates. */
|
|
61
|
-
export type PutEndpoint<Body, Result = void> = PostEndpoint<Body, Result>;
|
|
53
|
+
export type PutEndpoint<Body, Result = void, PathParams extends TypedValidatorMap = TypedValidatorMap, QueryParams extends TypedValidatorMap = TypedValidatorMap> = PostEndpoint<Body, Result, PathParams, QueryParams>;
|
|
62
54
|
/** Same as PUT. While PUT is used for the whole object update, PATCH is used for a partial update. */
|
|
63
|
-
export type PatchEndpoint<Body, Result = void> = PutEndpoint<Body, Result>;
|
|
55
|
+
export type PatchEndpoint<Body, Result = void, PathParams extends TypedValidatorMap = TypedValidatorMap, QueryParams extends TypedValidatorMap = TypedValidatorMap> = PutEndpoint<Body, Result, PathParams, QueryParams>;
|
|
64
56
|
/** Descriptor for DELETE routes. */
|
|
65
|
-
export type DeleteEndpoint = EndpointBase<
|
|
57
|
+
export type DeleteEndpoint<PathParams extends TypedValidatorMap = TypedValidatorMap, QueryParams extends TypedValidatorMap = TypedValidatorMap> = EndpointBase<PathParams, QueryParams, void, void>;
|
|
66
58
|
/** Union type for all route registration info objects. */
|
|
67
59
|
export type RouteRegistrationInfo = ({
|
|
68
60
|
method: 'get';
|
|
69
|
-
|
|
61
|
+
endpoint: GetEndpoint<unknown> | GetListEndpoint<unknown>;
|
|
70
62
|
} | {
|
|
71
63
|
method: 'post';
|
|
72
|
-
|
|
64
|
+
endpoint: PostEndpoint<unknown>;
|
|
73
65
|
} | {
|
|
74
66
|
method: 'patch';
|
|
75
|
-
|
|
67
|
+
endpoint: PatchEndpoint<unknown>;
|
|
76
68
|
} | {
|
|
77
69
|
method: 'put';
|
|
78
|
-
|
|
70
|
+
endpoint: PutEndpoint<unknown>;
|
|
79
71
|
} | {
|
|
80
72
|
method: 'delete';
|
|
81
|
-
|
|
73
|
+
endpoint: DeleteEndpoint;
|
|
82
74
|
}) & {
|
|
83
75
|
path: string;
|
|
84
76
|
};
|
|
@@ -93,4 +85,4 @@ export declare const mountPut: <Body, Result>(app: ExpressRouter, path: string,
|
|
|
93
85
|
/** Registers a DELETE route. */
|
|
94
86
|
export declare const mountDelete: (app: ExpressRouter, path: string, endpoint: DeleteEndpoint) => void;
|
|
95
87
|
/** Mounts a route with the given method, endpoint, and path. */
|
|
96
|
-
export declare function mount(app: ExpressRouter, { method,
|
|
88
|
+
export declare function mount(app: ExpressRouter, { method, endpoint, path }: RouteRegistrationInfo): void;
|
package/dist/esm/router.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getMessageFromError, validateObject } from '@fishka/assertions';
|
|
2
2
|
import * as url from 'url';
|
|
3
|
-
import { assertHttp, HttpError
|
|
3
|
+
import { assertHttp, HttpError } from './api.types';
|
|
4
4
|
import { catchRouteErrors } from './error-handling';
|
|
5
5
|
import { HTTP_BAD_REQUEST, HTTP_OK } from './http-status-codes';
|
|
6
6
|
import { getRequestLocalStorage } from './thread-local/thread-local-storage';
|
|
@@ -9,19 +9,19 @@ import { wrapAsApiResponse } from './utils/conversion.utils';
|
|
|
9
9
|
// Internal implementation details
|
|
10
10
|
// ============================================================================
|
|
11
11
|
/** Registers a GET route. */
|
|
12
|
-
export const mountGet = (app, path, endpoint) => mount(app, { method: 'get',
|
|
12
|
+
export const mountGet = (app, path, endpoint) => mount(app, { method: 'get', endpoint, path });
|
|
13
13
|
/** Registers a POST route. */
|
|
14
|
-
export const mountPost = (app, path, endpoint) => mount(app, { method: 'post',
|
|
14
|
+
export const mountPost = (app, path, endpoint) => mount(app, { method: 'post', endpoint: endpoint, path });
|
|
15
15
|
/** Registers a PATCH route. */
|
|
16
|
-
export const mountPatch = (app, path, endpoint) => mount(app, { method: 'patch',
|
|
16
|
+
export const mountPatch = (app, path, endpoint) => mount(app, { method: 'patch', endpoint: endpoint, path });
|
|
17
17
|
/** Registers a PUT route. */
|
|
18
|
-
export const mountPut = (app, path, endpoint) => mount(app, { method: 'put',
|
|
18
|
+
export const mountPut = (app, path, endpoint) => mount(app, { method: 'put', endpoint: endpoint, path });
|
|
19
19
|
/** Registers a DELETE route. */
|
|
20
|
-
export const mountDelete = (app, path, endpoint) => mount(app, { method: 'delete',
|
|
20
|
+
export const mountDelete = (app, path, endpoint) => mount(app, { method: 'delete', endpoint, path });
|
|
21
21
|
/** Mounts a route with the given method, endpoint, and path. */
|
|
22
|
-
export function mount(app, { method,
|
|
22
|
+
export function mount(app, { method, endpoint, path }) {
|
|
23
23
|
const fullPath = path.startsWith('/') ? path : `/${path}`;
|
|
24
|
-
const handler = createRouteHandler(method,
|
|
24
|
+
const handler = createRouteHandler(method, endpoint);
|
|
25
25
|
app[method](fullPath, catchRouteErrors(handler));
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
@@ -56,51 +56,46 @@ function createRouteHandler(method, endpoint) {
|
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
58
|
* @Internal
|
|
59
|
-
* Validates
|
|
59
|
+
* Validates and builds typed path/query parameters using $path and $query validators.
|
|
60
60
|
*/
|
|
61
|
-
function
|
|
61
|
+
function buildValidatedParams(req, $path, $query) {
|
|
62
|
+
const pathResult = {};
|
|
63
|
+
const queryResult = {};
|
|
62
64
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
callValueAssertion(value, globalValidator, `${HTTP_BAD_REQUEST}`);
|
|
69
|
-
}
|
|
70
|
-
// Run Local Validation.
|
|
71
|
-
const validator = $path?.[key];
|
|
72
|
-
if (validator) {
|
|
73
|
-
callValueAssertion(value, validator, `${HTTP_BAD_REQUEST}`);
|
|
65
|
+
// Validate path params
|
|
66
|
+
if ($path) {
|
|
67
|
+
for (const [key, validator] of Object.entries($path)) {
|
|
68
|
+
const value = req.params[key];
|
|
69
|
+
pathResult[key] = validator(value);
|
|
74
70
|
}
|
|
75
71
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// We only validate if it's a single value or handle array in validator.
|
|
84
|
-
// For simplicity, we pass value as is (unknown) to assertion.
|
|
85
|
-
callValueAssertion(value, globalValidator, `${HTTP_BAD_REQUEST}`);
|
|
86
|
-
}
|
|
87
|
-
const validator = $query?.[key];
|
|
88
|
-
if (validator) {
|
|
89
|
-
callValueAssertion(value, validator, `${HTTP_BAD_REQUEST}`);
|
|
72
|
+
// Validate query params
|
|
73
|
+
if ($query) {
|
|
74
|
+
const parsedUrl = url.parse(req.url, true);
|
|
75
|
+
for (const [key, validator] of Object.entries($query)) {
|
|
76
|
+
const rawValue = parsedUrl.query[key];
|
|
77
|
+
const value = Array.isArray(rawValue) ? rawValue[0] : rawValue;
|
|
78
|
+
queryResult[key] = validator(value);
|
|
90
79
|
}
|
|
91
80
|
}
|
|
92
81
|
}
|
|
93
82
|
catch (error) {
|
|
83
|
+
if (error instanceof HttpError)
|
|
84
|
+
throw error;
|
|
94
85
|
throw new HttpError(HTTP_BAD_REQUEST, getMessageFromError(error));
|
|
95
86
|
}
|
|
87
|
+
return {
|
|
88
|
+
path: pathResult,
|
|
89
|
+
query: queryResult,
|
|
90
|
+
};
|
|
96
91
|
}
|
|
97
92
|
/**
|
|
98
93
|
* @Internal
|
|
99
94
|
* Runs GET handler with optional middleware.
|
|
100
95
|
*/
|
|
101
96
|
async function executeGetEndpoint(route, req, res) {
|
|
102
|
-
const
|
|
103
|
-
|
|
97
|
+
const validated = buildValidatedParams(req, route.$path, route.$query);
|
|
98
|
+
const requestContext = newRequestContext(undefined, req, res, validated.path, validated.query);
|
|
104
99
|
return await executeWithMiddleware(() => route.run(requestContext), route.middlewares || [], requestContext);
|
|
105
100
|
}
|
|
106
101
|
/**
|
|
@@ -108,8 +103,8 @@ async function executeGetEndpoint(route, req, res) {
|
|
|
108
103
|
* Runs DELETE handler with optional middleware.
|
|
109
104
|
*/
|
|
110
105
|
async function executeDeleteEndpoint(route, req, res) {
|
|
111
|
-
const
|
|
112
|
-
|
|
106
|
+
const validated = buildValidatedParams(req, route.$path, route.$query);
|
|
107
|
+
const requestContext = newRequestContext(undefined, req, res, validated.path, validated.query);
|
|
113
108
|
await executeWithMiddleware(() => route.run(requestContext), route.middlewares || [], requestContext);
|
|
114
109
|
return undefined;
|
|
115
110
|
}
|
|
@@ -123,12 +118,11 @@ async function executeBodyEndpoint(route, req, res) {
|
|
|
123
118
|
try {
|
|
124
119
|
// Handle validation based on whether the validator is an object or function
|
|
125
120
|
if (typeof validator === 'function') {
|
|
126
|
-
// It's a ValueAssertion (function)
|
|
127
|
-
|
|
121
|
+
// It's a ValueAssertion (function) - call it directly
|
|
122
|
+
validator(apiRequest);
|
|
128
123
|
}
|
|
129
124
|
else {
|
|
130
125
|
// It's an ObjectAssertion - use validateObject
|
|
131
|
-
// We strictly assume it is an object because of the type definition (function | object)
|
|
132
126
|
const objectValidator = validator;
|
|
133
127
|
const isEmptyValidator = Object.keys(objectValidator).length === 0;
|
|
134
128
|
const errorMessage = validateObject(apiRequest, objectValidator, `${HTTP_BAD_REQUEST}: request body`, {
|
|
@@ -142,9 +136,8 @@ async function executeBodyEndpoint(route, req, res) {
|
|
|
142
136
|
throw error;
|
|
143
137
|
throw new HttpError(HTTP_BAD_REQUEST, getMessageFromError(error));
|
|
144
138
|
}
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
requestContext.body = req.body;
|
|
139
|
+
const validated = buildValidatedParams(req, route.$path, route.$query);
|
|
140
|
+
const requestContext = newRequestContext(apiRequest, req, res, validated.path, validated.query);
|
|
148
141
|
return await executeWithMiddleware(() => route.run(requestContext), (route.middlewares || []), requestContext);
|
|
149
142
|
}
|
|
150
143
|
/**
|
|
@@ -166,26 +159,13 @@ async function executeWithMiddleware(run, middlewares, context) {
|
|
|
166
159
|
* @Internal
|
|
167
160
|
* Creates a new RequestContext instance.
|
|
168
161
|
*/
|
|
169
|
-
function newRequestContext(requestBody, req, res) {
|
|
162
|
+
function newRequestContext(requestBody, req, res, validatedPath, validatedQuery) {
|
|
170
163
|
return {
|
|
171
164
|
body: requestBody,
|
|
172
165
|
req,
|
|
173
166
|
res,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const value = req.params[key];
|
|
177
|
-
assertHttp(value, HTTP_BAD_REQUEST, `Path parameter '${key}' not found`);
|
|
178
|
-
return value;
|
|
179
|
-
},
|
|
180
|
-
tryGet: (key) => req.params[key],
|
|
181
|
-
},
|
|
182
|
-
query: {
|
|
183
|
-
get: (key) => {
|
|
184
|
-
const parsedUrl = url.parse(req.url, true);
|
|
185
|
-
const value = parsedUrl.query[key];
|
|
186
|
-
return Array.isArray(value) ? value[0] : value;
|
|
187
|
-
},
|
|
188
|
-
},
|
|
167
|
+
path: validatedPath,
|
|
168
|
+
query: validatedQuery,
|
|
189
169
|
state: new Map(),
|
|
190
170
|
};
|
|
191
171
|
}
|