@fishka/express 0.9.15 → 0.9.17
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 +39 -46
- package/dist/cjs/api.types.d.ts +4 -4
- package/dist/cjs/api.types.js.map +1 -1
- package/dist/cjs/route-table.d.ts +18 -13
- package/dist/cjs/route-table.js +6 -6
- package/dist/cjs/route-table.js.map +1 -1
- package/dist/cjs/router.d.ts +37 -27
- package/dist/cjs/router.js +92 -82
- package/dist/cjs/router.js.map +1 -1
- package/dist/cjs/utils/type-validators.d.ts +22 -15
- package/dist/cjs/utils/type-validators.js +25 -14
- package/dist/cjs/utils/type-validators.js.map +1 -1
- package/dist/esm/api.types.d.ts +4 -4
- package/dist/esm/api.types.js.map +1 -1
- package/dist/esm/route-table.d.ts +18 -13
- package/dist/esm/route-table.js +6 -6
- package/dist/esm/route-table.js.map +1 -1
- package/dist/esm/router.d.ts +37 -27
- package/dist/esm/router.js +92 -82
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/utils/type-validators.d.ts +22 -15
- package/dist/esm/utils/type-validators.js +21 -11
- package/dist/esm/utils/type-validators.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npm install @fishka/express
|
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
14
|
import express from 'express';
|
|
15
|
-
import { createRouteTable,
|
|
15
|
+
import { createRouteTable, transform, toInt } from '@fishka/express';
|
|
16
16
|
import { assertString } from '@fishka/assertions';
|
|
17
17
|
|
|
18
18
|
const app = express();
|
|
@@ -21,13 +21,10 @@ app.use(express.json());
|
|
|
21
21
|
const routes = createRouteTable(app);
|
|
22
22
|
|
|
23
23
|
// GET /users/:id - with typed path params
|
|
24
|
-
routes.get('users/:id', {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
name: 'John',
|
|
29
|
-
}),
|
|
30
|
-
});
|
|
24
|
+
routes.get('users/:id', async ctx => ({
|
|
25
|
+
id: ctx.path('id', transform(toInt())), // number - validated inline
|
|
26
|
+
name: 'John',
|
|
27
|
+
}));
|
|
31
28
|
|
|
32
29
|
// GET /users - list all users
|
|
33
30
|
routes.get('users', async () => [
|
|
@@ -36,10 +33,10 @@ routes.get('users', async () => [
|
|
|
36
33
|
]);
|
|
37
34
|
|
|
38
35
|
// POST /users - with body validation
|
|
39
|
-
routes.post
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
36
|
+
routes.post('users', async ctx => ({
|
|
37
|
+
id: 1,
|
|
38
|
+
name: ctx.body({ name: v => assertString(v, 'name required') }).name
|
|
39
|
+
}));
|
|
43
40
|
|
|
44
41
|
// DELETE /users/:id
|
|
45
42
|
routes.delete('users/:id', async () => {});
|
|
@@ -49,29 +46,26 @@ app.listen(3000);
|
|
|
49
46
|
|
|
50
47
|
## URL Parameter Validation
|
|
51
48
|
|
|
52
|
-
Use `
|
|
49
|
+
Use `transform()` to validate and transform path/query parameters. All operators are composable:
|
|
53
50
|
|
|
54
51
|
```typescript
|
|
55
|
-
import {
|
|
56
|
-
|
|
57
|
-
routes.get('users/:id', {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
sort: param(oneOf('asc', 'desc')), // enum
|
|
65
|
-
search: param(minLength(3)), // string min 3 chars
|
|
66
|
-
},
|
|
67
|
-
run: async ctx => ({
|
|
68
|
-
id: ctx.path.id, // number
|
|
69
|
-
page: ctx.query.page, // number
|
|
70
|
-
sort: ctx.query.sort, // 'asc' | 'desc'
|
|
71
|
-
}),
|
|
72
|
-
});
|
|
52
|
+
import { transform, toInt, minLength, matches, min, range, oneOf } from '@fishka/express';
|
|
53
|
+
|
|
54
|
+
routes.get('users/:id', async ctx => ({
|
|
55
|
+
id: ctx.path('id', transform(toInt())), // string → number (required)
|
|
56
|
+
page: ctx.query('page', transform(toInt(), min(1))), // number >= 1, optional
|
|
57
|
+
limit: ctx.query('limit', transform(toInt(), range(1, 100))), // number 1-100, optional
|
|
58
|
+
sort: ctx.query('sort', transform(oneOf('asc', 'desc'))), // enum, optional
|
|
59
|
+
search: ctx.query('search', transform(minLength(3))), // string min 3 chars, optional
|
|
60
|
+
}));
|
|
73
61
|
```
|
|
74
62
|
|
|
63
|
+
### Without Validators
|
|
64
|
+
|
|
65
|
+
- `ctx.path('name')` - returns string (throws 400 if missing)
|
|
66
|
+
- `ctx.query('name')` - returns string | undefined (returns undefined if missing/empty)
|
|
67
|
+
- Validators receive raw values (including undefined/null/empty) and can enforce requiredness
|
|
68
|
+
|
|
75
69
|
### Available Operators
|
|
76
70
|
|
|
77
71
|
**Transformations (string → T):**
|
|
@@ -93,23 +87,23 @@ routes.get('users/:id', {
|
|
|
93
87
|
- `range(min, max)` - value range
|
|
94
88
|
|
|
95
89
|
**Generic:**
|
|
96
|
-
- `
|
|
90
|
+
- `transform(...ops)` - chain of validators/transformers
|
|
91
|
+
- `assert(predicate, msg)` - custom validation with predicate
|
|
92
|
+
- `validator(fn)` - custom validator returning string|undefined
|
|
97
93
|
- `map(fn)` - transform value
|
|
98
94
|
|
|
99
95
|
### Optional Parameters
|
|
100
96
|
|
|
101
|
-
Use `
|
|
97
|
+
Query parameters are optional by default. Use `ctx.query()` without a validator to get optional string values:
|
|
102
98
|
|
|
103
99
|
```typescript
|
|
104
|
-
import {
|
|
100
|
+
import { transform, toInt } from '@fishka/express';
|
|
105
101
|
|
|
106
|
-
routes.get('users', {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const page = ctx.query.page ?? 1;
|
|
112
|
-
},
|
|
102
|
+
routes.get('users', async ctx => {
|
|
103
|
+
const page = ctx.query('page', transform(toInt())) ?? 1; // number | undefined
|
|
104
|
+
const search = ctx.query('search'); // string | undefined
|
|
105
|
+
|
|
106
|
+
return { page, search };
|
|
113
107
|
});
|
|
114
108
|
```
|
|
115
109
|
|
|
@@ -163,7 +157,7 @@ Full initialization with TLS context, validation, and error handling:
|
|
|
163
157
|
|
|
164
158
|
```typescript
|
|
165
159
|
import express from 'express';
|
|
166
|
-
import { createRouteTable, createTlsMiddleware, catchAllMiddleware,
|
|
160
|
+
import { createRouteTable, createTlsMiddleware, catchAllMiddleware, transform, toInt } from '@fishka/express';
|
|
167
161
|
|
|
168
162
|
const app = express();
|
|
169
163
|
|
|
@@ -178,10 +172,9 @@ const routes = createRouteTable(app);
|
|
|
178
172
|
|
|
179
173
|
routes.get('health', async () => ({ status: 'UP' }));
|
|
180
174
|
|
|
181
|
-
routes.get('users/:id', {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
});
|
|
175
|
+
routes.get('users/:id', async ctx => ({
|
|
176
|
+
id: ctx.path('id', transform(toInt()))
|
|
177
|
+
}));
|
|
185
178
|
|
|
186
179
|
// 4. Error handler - catches middleware/parsing errors
|
|
187
180
|
app.use(catchAllMiddleware);
|
package/dist/cjs/api.types.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/** Validator function that validates and returns typed value */
|
|
2
|
-
export type
|
|
1
|
+
/** Validator function that validates and returns a typed value */
|
|
2
|
+
export type ParamValidator<T> = (value: unknown) => T;
|
|
3
3
|
/** Map of param name to type validator */
|
|
4
|
-
export type
|
|
4
|
+
export type ParamValidatorMap = Record<string, ParamValidator<unknown>>;
|
|
5
5
|
/** Infer validated types from validator map */
|
|
6
|
-
export type
|
|
6
|
+
export type ValidatedParams<T extends ParamValidatorMap | undefined> = T extends ParamValidatorMap ? {
|
|
7
7
|
[K in keyof T]: ReturnType<T[K]>;
|
|
8
8
|
} : Record<string, never>;
|
|
9
9
|
export declare class HttpError extends Error {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.types.js","sourceRoot":"","sources":["../../src/api.types.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"api.types.js","sourceRoot":"","sources":["../../src/api.types.ts"],"names":[],"mappings":";;;AAwDA,gCAEC;AA2BD,4BAEC;AAvFD,mDAAkD;AAalD,MAAa,SAAU,SAAQ,KAAK;IAClC,YACkB,MAAc,EAC9B,OAAe,EACC,OAAiC;QAEjD,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,WAAM,GAAN,MAAM,CAAQ;QAEd,YAAO,GAAP,OAAO,CAA0B;QAGjD,qDAAqD;QACrD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACnD,CAAC;CACF;AAVD,8BAUC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,SAAgB,UAAU,CAAC,KAAc,EAAE,MAAc,EAAE,OAAe;IACxE,IAAA,yBAAY,EAAC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAC5D,CAAC;AA0BD,gFAAgF;AAChF,SAAgB,QAAQ,CAAc,MAAS;IAC7C,OAAO,EAAE,MAAM,EAAE,CAAC;AACpB,CAAC"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { TypedValidatorMap } from './api.types';
|
|
2
1
|
import { DeleteEndpoint, GetEndpoint, PatchEndpoint, PostEndpoint, PutEndpoint, RequestContext, ResponseOrValue } from './router';
|
|
3
2
|
import { ExpressRouter } from './utils/express.utils';
|
|
4
3
|
/**
|
|
@@ -8,19 +7,25 @@ import { ExpressRouter } from './utils/express.utils';
|
|
|
8
7
|
export declare class RouteTable {
|
|
9
8
|
private readonly app;
|
|
10
9
|
constructor(app: ExpressRouter);
|
|
11
|
-
/** Register GET endpoint
|
|
12
|
-
get<Result
|
|
13
|
-
/** Register GET endpoint with function shorthand. */
|
|
10
|
+
/** Register a GET endpoint. */
|
|
11
|
+
get<Result>(path: string, endpoint: GetEndpoint<Result>): this;
|
|
12
|
+
/** Register a GET endpoint with function shorthand. */
|
|
14
13
|
get<Result>(path: string, run: (ctx: RequestContext) => ResponseOrValue<Result> | Promise<ResponseOrValue<Result>>): this;
|
|
15
|
-
/** Register POST endpoint
|
|
16
|
-
post<
|
|
17
|
-
/** Register
|
|
18
|
-
|
|
19
|
-
/** Register
|
|
20
|
-
|
|
21
|
-
/** Register
|
|
22
|
-
|
|
23
|
-
/** Register
|
|
14
|
+
/** Register a POST endpoint. */
|
|
15
|
+
post<Result = void>(path: string, endpoint: PostEndpoint<Result>): this;
|
|
16
|
+
/** Register a POST endpoint with function shorthand. */
|
|
17
|
+
post<Result = void>(path: string, run: (ctx: RequestContext) => ResponseOrValue<Result> | Promise<ResponseOrValue<Result>>): this;
|
|
18
|
+
/** Register a PATCH endpoint. */
|
|
19
|
+
patch<Result = void>(path: string, endpoint: PatchEndpoint<Result>): this;
|
|
20
|
+
/** Register a PATCH endpoint with function shorthand. */
|
|
21
|
+
patch<Result = void>(path: string, run: (ctx: RequestContext) => ResponseOrValue<Result> | Promise<ResponseOrValue<Result>>): this;
|
|
22
|
+
/** Register a PUT endpoint. */
|
|
23
|
+
put<Result = void>(path: string, endpoint: PutEndpoint<Result>): this;
|
|
24
|
+
/** Register a PUT endpoint with function shorthand. */
|
|
25
|
+
put<Result = void>(path: string, run: (ctx: RequestContext) => ResponseOrValue<Result> | Promise<ResponseOrValue<Result>>): this;
|
|
26
|
+
/** Register a DELETE endpoint with a full endpoint object. */
|
|
27
|
+
delete(path: string, endpoint: DeleteEndpoint): this;
|
|
28
|
+
/** Register a DELETE endpoint with function shorthand. */
|
|
24
29
|
delete(path: string, run: (ctx: RequestContext) => void | Promise<void>): this;
|
|
25
30
|
}
|
|
26
31
|
/**
|
package/dist/cjs/route-table.js
CHANGED
|
@@ -16,18 +16,18 @@ class RouteTable {
|
|
|
16
16
|
(0, router_1.mountGet)(this.app, path, endpoint);
|
|
17
17
|
return this;
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
post(path, endpointOrRun) {
|
|
20
|
+
const endpoint = typeof endpointOrRun === 'function' ? { run: endpointOrRun } : endpointOrRun;
|
|
21
21
|
(0, router_1.mountPost)(this.app, path, endpoint);
|
|
22
22
|
return this;
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
patch(path, endpointOrRun) {
|
|
25
|
+
const endpoint = typeof endpointOrRun === 'function' ? { run: endpointOrRun } : endpointOrRun;
|
|
26
26
|
(0, router_1.mountPatch)(this.app, path, endpoint);
|
|
27
27
|
return this;
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
put(path, endpointOrRun) {
|
|
30
|
+
const endpoint = typeof endpointOrRun === 'function' ? { run: endpointOrRun } : endpointOrRun;
|
|
31
31
|
(0, router_1.mountPut)(this.app, path, endpoint);
|
|
32
32
|
return this;
|
|
33
33
|
}
|
|
@@ -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":";;;AAwGA,4CAEC;AA1GD,qCAakB;AAGlB;;;GAGG;AACH,MAAa,UAAU;IACrB,YAA6B,GAAkB;QAAlB,QAAG,GAAH,GAAG,CAAe;IAAG,CAAC;IAQnD,GAAG,CACD,IAAY,EACZ,aAA0H;QAE1H,MAAM,QAAQ,GAAG,OAAO,aAAa,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;QAC9F,IAAA,iBAAQ,EAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,QAAgC,CAAC,CAAC;QAC3D,OAAO,IAAI,CAAC;IACd,CAAC;IAQD,IAAI,CACF,IAAY,EACZ,aAA2H;QAE3H,MAAM,QAAQ,GAAG,OAAO,aAAa,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;QAC9F,IAAA,kBAAS,EAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,QAAiC,CAAC,CAAC;QAC7D,OAAO,IAAI,CAAC;IACd,CAAC;IAQD,KAAK,CACH,IAAY,EACZ,aAA4H;QAE5H,MAAM,QAAQ,GAAG,OAAO,aAAa,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;QAC9F,IAAA,mBAAU,EAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,QAAkC,CAAC,CAAC;QAC/D,OAAO,IAAI,CAAC;IACd,CAAC;IAQD,GAAG,CACD,IAAY,EACZ,aAA0H;QAE1H,MAAM,QAAQ,GAAG,OAAO,aAAa,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;QAC9F,IAAA,iBAAQ,EAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,QAAgC,CAAC,CAAC;QAC3D,OAAO,IAAI,CAAC;IACd,CAAC;IAQD,MAAM,CACJ,IAAY,EACZ,aAA+E;QAE/E,MAAM,QAAQ,GAAG,OAAO,aAAa,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;QAC9F,IAAA,oBAAW,EAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,QAA0B,CAAC,CAAC;QACxD,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AA7ED,gCA6EC;AAED;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,GAAkB;IACjD,OAAO,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;AAC7B,CAAC"}
|
package/dist/cjs/router.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Assertion
|
|
2
|
-
import { ApiResponse,
|
|
1
|
+
import { Assertion } from '@fishka/assertions';
|
|
2
|
+
import { ApiResponse, ParamValidator } 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,19 +10,36 @@ 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
|
|
14
|
-
/** Parsed and validated request body (for POST/PATCH/PUT handlers). */
|
|
15
|
-
body: Body;
|
|
13
|
+
export interface RequestContext {
|
|
16
14
|
/** Express Request object. */
|
|
17
15
|
req: ExpressRequest;
|
|
18
16
|
/** Express Response object. */
|
|
19
17
|
res: ExpressResponse;
|
|
20
18
|
/** Authenticated user (if any). Populated by auth middleware. */
|
|
21
19
|
authUser?: AuthUser;
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Get and validate a path parameter.
|
|
22
|
+
* @param name - Name of the path parameter
|
|
23
|
+
* @param validator - Optional validator. If not provided, returns the raw string value.
|
|
24
|
+
* @returns Validated value of type T (or string if no validator)
|
|
25
|
+
* @throws {HttpError} 400 Bad Request if validation fails
|
|
26
|
+
*/
|
|
27
|
+
path<T = string>(name: string, validator?: ParamValidator<T>): T;
|
|
28
|
+
/**
|
|
29
|
+
* Get and validate a query parameter.
|
|
30
|
+
* @param name - Name of the query parameter
|
|
31
|
+
* @param validator - Optional validator. If not provided, returns the raw string value or undefined.
|
|
32
|
+
* @returns Validated value of type T, or undefined if parameter is not present
|
|
33
|
+
* @throws {HttpError} 400 Bad Request if validation fails
|
|
34
|
+
*/
|
|
35
|
+
query<T = string>(name: string, validator?: ParamValidator<T>): T | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Get and validate the request body.
|
|
38
|
+
* @param validator - Validator function or object assertion
|
|
39
|
+
* @returns Validated body of type T
|
|
40
|
+
* @throws {HttpError} 400 Bad Request if validation fails
|
|
41
|
+
*/
|
|
42
|
+
body<T>(validator: Assertion<T>): T;
|
|
26
43
|
/**
|
|
27
44
|
* Generic state storage for middleware to attach data.
|
|
28
45
|
* Allows middleware to pass information to handlers and other middleware.
|
|
@@ -30,31 +47,24 @@ export interface RequestContext<Body = void, PathParams extends TypedValidatorMa
|
|
|
30
47
|
state: Map<string, unknown>;
|
|
31
48
|
}
|
|
32
49
|
/** Base interface with common endpoint properties. */
|
|
33
|
-
export interface EndpointBase<
|
|
34
|
-
/** Path parameter validators (typed). */
|
|
35
|
-
$path?: PathParams;
|
|
36
|
-
/** Query parameter validators (typed). */
|
|
37
|
-
$query?: QueryParams;
|
|
50
|
+
export interface EndpointBase<Result = unknown> {
|
|
38
51
|
/** Optional middleware to execute before the handler. */
|
|
39
52
|
middlewares?: Array<EndpointMiddleware>;
|
|
40
53
|
/** Handler function. Can be sync or async. */
|
|
41
|
-
run: (ctx: RequestContext
|
|
54
|
+
run: (ctx: RequestContext) => ResponseOrValue<Result> | Promise<ResponseOrValue<Result>>;
|
|
42
55
|
}
|
|
43
56
|
/** Descriptor for GET list routes. */
|
|
44
|
-
export type GetListEndpoint<ResultElementType
|
|
57
|
+
export type GetListEndpoint<ResultElementType> = EndpointBase<Array<ResultElementType>>;
|
|
45
58
|
/** Descriptor for GET routes. */
|
|
46
|
-
export type GetEndpoint<Result
|
|
59
|
+
export type GetEndpoint<Result> = EndpointBase<Result>;
|
|
47
60
|
/** Descriptor for POST routes. */
|
|
48
|
-
export
|
|
49
|
-
/** Request body validator. */
|
|
50
|
-
$body: Body extends object ? ObjectAssertion<Body> : Assertion<Body>;
|
|
51
|
-
}
|
|
61
|
+
export type PostEndpoint<Result = void> = EndpointBase<Result>;
|
|
52
62
|
/** Same as POST. Used for full object updates. */
|
|
53
|
-
export type PutEndpoint<
|
|
63
|
+
export type PutEndpoint<Result = void> = EndpointBase<Result>;
|
|
54
64
|
/** Same as PUT. While PUT is used for the whole object update, PATCH is used for a partial update. */
|
|
55
|
-
export type PatchEndpoint<
|
|
65
|
+
export type PatchEndpoint<Result = void> = EndpointBase<Result>;
|
|
56
66
|
/** Descriptor for DELETE routes. */
|
|
57
|
-
export type DeleteEndpoint
|
|
67
|
+
export type DeleteEndpoint = EndpointBase<void>;
|
|
58
68
|
/** Union type for all route registration info objects. */
|
|
59
69
|
export type RouteRegistrationInfo = ({
|
|
60
70
|
method: 'get';
|
|
@@ -77,11 +87,11 @@ export type RouteRegistrationInfo = ({
|
|
|
77
87
|
/** Registers a GET route. */
|
|
78
88
|
export declare const mountGet: (app: ExpressRouter, path: string, endpoint: GetEndpoint<unknown> | GetListEndpoint<unknown>) => void;
|
|
79
89
|
/** Registers a POST route. */
|
|
80
|
-
export declare const mountPost: <
|
|
90
|
+
export declare const mountPost: <Result>(app: ExpressRouter, path: string, endpoint: PostEndpoint<Result>) => void;
|
|
81
91
|
/** Registers a PATCH route. */
|
|
82
|
-
export declare const mountPatch: <
|
|
92
|
+
export declare const mountPatch: <Result>(app: ExpressRouter, path: string, endpoint: PatchEndpoint<Result>) => void;
|
|
83
93
|
/** Registers a PUT route. */
|
|
84
|
-
export declare const mountPut: <
|
|
94
|
+
export declare const mountPut: <Result>(app: ExpressRouter, path: string, endpoint: PutEndpoint<Result>) => void;
|
|
85
95
|
/** Registers a DELETE route. */
|
|
86
96
|
export declare const mountDelete: (app: ExpressRouter, path: string, endpoint: DeleteEndpoint) => void;
|
|
87
97
|
/** Mounts a route with the given method, endpoint, and path. */
|
package/dist/cjs/router.js
CHANGED
|
@@ -42,9 +42,94 @@ const error_handling_1 = require("./error-handling");
|
|
|
42
42
|
const http_status_codes_1 = require("./http-status-codes");
|
|
43
43
|
const thread_local_storage_1 = require("./thread-local/thread-local-storage");
|
|
44
44
|
const conversion_utils_1 = require("./utils/conversion.utils");
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
/** Implementation of RequestContext with caching for validated parameters. */
|
|
46
|
+
class RequestContextImpl {
|
|
47
|
+
constructor(
|
|
48
|
+
/** Express request object. */
|
|
49
|
+
req,
|
|
50
|
+
/** Express response object. */
|
|
51
|
+
res,
|
|
52
|
+
/** Authenticated user (if any). */
|
|
53
|
+
authUser,
|
|
54
|
+
/** Request-scoped state storage. */
|
|
55
|
+
state = new Map()) {
|
|
56
|
+
this.req = req;
|
|
57
|
+
this.res = res;
|
|
58
|
+
this.authUser = authUser;
|
|
59
|
+
this.state = state;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Validates a parameter with optional validator and caching.
|
|
63
|
+
* @param name Parameter name.
|
|
64
|
+
* @param rawValue Raw parameter value from request.
|
|
65
|
+
* @param validator Optional validator function.
|
|
66
|
+
* @param cache Cache map for this parameter type.
|
|
67
|
+
* @param isRequired Whether parameter is required (path=true, query=false).
|
|
68
|
+
* @returns Validated value or undefined for optional missing parameters.
|
|
69
|
+
*/
|
|
70
|
+
validateParam(name, rawValue, validator, isRequired) {
|
|
71
|
+
try {
|
|
72
|
+
let result;
|
|
73
|
+
if (validator) {
|
|
74
|
+
// Pass value to validator even if it's undefined/null/empty
|
|
75
|
+
result = validator(rawValue);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Without validator
|
|
79
|
+
if (rawValue === undefined || rawValue === null || rawValue === '') {
|
|
80
|
+
if (isRequired) {
|
|
81
|
+
(0, api_types_1.assertHttp)(false, http_status_codes_1.HTTP_BAD_REQUEST, `Missing required parameter: ${name}`);
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
result = rawValue;
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (error instanceof api_types_1.HttpError)
|
|
91
|
+
throw error;
|
|
92
|
+
throw new api_types_1.HttpError(http_status_codes_1.HTTP_BAD_REQUEST, (0, assertions_1.getMessageFromError)(error));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
path(name, validator) {
|
|
96
|
+
const rawValue = this.req.params[name];
|
|
97
|
+
const result = this.validateParam(name, rawValue, validator, true);
|
|
98
|
+
(0, api_types_1.assertHttp)(result !== undefined, http_status_codes_1.HTTP_BAD_REQUEST, `Missing required path parameter: ${name}`);
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
query(name, validator) {
|
|
102
|
+
const parsedUrl = url.parse(this.req.url, true);
|
|
103
|
+
const rawValue = parsedUrl.query[name];
|
|
104
|
+
const value = Array.isArray(rawValue) ? rawValue[0] : rawValue;
|
|
105
|
+
return this.validateParam(name, value, validator, false);
|
|
106
|
+
}
|
|
107
|
+
body(validator) {
|
|
108
|
+
const apiRequest = this.req.body;
|
|
109
|
+
try {
|
|
110
|
+
// Handle validation based on whether the validator is an object or function
|
|
111
|
+
if (typeof validator === 'function') {
|
|
112
|
+
// It's a ValueAssertion (function) - call it directly
|
|
113
|
+
validator(apiRequest);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// It's an ObjectAssertion - use validateObject
|
|
117
|
+
const objectValidator = validator;
|
|
118
|
+
const isEmptyValidator = Object.keys(objectValidator).length === 0;
|
|
119
|
+
const errorMessage = (0, assertions_1.validateObject)(apiRequest, objectValidator, `${http_status_codes_1.HTTP_BAD_REQUEST}: request body`, {
|
|
120
|
+
failOnUnknownFields: !isEmptyValidator,
|
|
121
|
+
});
|
|
122
|
+
(0, api_types_1.assertHttp)(!errorMessage, http_status_codes_1.HTTP_BAD_REQUEST, errorMessage || 'Request body validation failed');
|
|
123
|
+
}
|
|
124
|
+
return apiRequest;
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (error instanceof api_types_1.HttpError)
|
|
128
|
+
throw error;
|
|
129
|
+
throw new api_types_1.HttpError(http_status_codes_1.HTTP_BAD_REQUEST, (0, assertions_1.getMessageFromError)(error));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
48
133
|
/** Registers a GET route. */
|
|
49
134
|
const mountGet = (app, path, endpoint) => mount(app, { method: 'get', endpoint, path });
|
|
50
135
|
exports.mountGet = mountGet;
|
|
@@ -96,48 +181,12 @@ function createRouteHandler(method, endpoint) {
|
|
|
96
181
|
res.send(response);
|
|
97
182
|
};
|
|
98
183
|
}
|
|
99
|
-
/**
|
|
100
|
-
* @Internal
|
|
101
|
-
* Validates and builds typed path/query parameters using $path and $query validators.
|
|
102
|
-
*/
|
|
103
|
-
function buildValidatedParams(req, $path, $query) {
|
|
104
|
-
const pathResult = {};
|
|
105
|
-
const queryResult = {};
|
|
106
|
-
try {
|
|
107
|
-
// Validate path params
|
|
108
|
-
if ($path) {
|
|
109
|
-
for (const [key, validator] of Object.entries($path)) {
|
|
110
|
-
const value = req.params[key];
|
|
111
|
-
pathResult[key] = validator(value);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
// Validate query params
|
|
115
|
-
if ($query) {
|
|
116
|
-
const parsedUrl = url.parse(req.url, true);
|
|
117
|
-
for (const [key, validator] of Object.entries($query)) {
|
|
118
|
-
const rawValue = parsedUrl.query[key];
|
|
119
|
-
const value = Array.isArray(rawValue) ? rawValue[0] : rawValue;
|
|
120
|
-
queryResult[key] = validator(value);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
if (error instanceof api_types_1.HttpError)
|
|
126
|
-
throw error;
|
|
127
|
-
throw new api_types_1.HttpError(http_status_codes_1.HTTP_BAD_REQUEST, (0, assertions_1.getMessageFromError)(error));
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
path: pathResult,
|
|
131
|
-
query: queryResult,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
184
|
/**
|
|
135
185
|
* @Internal
|
|
136
186
|
* Runs GET handler with optional middleware.
|
|
137
187
|
*/
|
|
138
188
|
async function executeGetEndpoint(route, req, res) {
|
|
139
|
-
const
|
|
140
|
-
const requestContext = newRequestContext(undefined, req, res, validated.path, validated.query);
|
|
189
|
+
const requestContext = new RequestContextImpl(req, res);
|
|
141
190
|
return await executeWithMiddleware(() => route.run(requestContext), route.middlewares || [], requestContext);
|
|
142
191
|
}
|
|
143
192
|
/**
|
|
@@ -145,8 +194,7 @@ async function executeGetEndpoint(route, req, res) {
|
|
|
145
194
|
* Runs DELETE handler with optional middleware.
|
|
146
195
|
*/
|
|
147
196
|
async function executeDeleteEndpoint(route, req, res) {
|
|
148
|
-
const
|
|
149
|
-
const requestContext = newRequestContext(undefined, req, res, validated.path, validated.query);
|
|
197
|
+
const requestContext = new RequestContextImpl(req, res);
|
|
150
198
|
await executeWithMiddleware(() => route.run(requestContext), route.middlewares || [], requestContext);
|
|
151
199
|
return undefined;
|
|
152
200
|
}
|
|
@@ -155,32 +203,8 @@ async function executeDeleteEndpoint(route, req, res) {
|
|
|
155
203
|
* Runs POST/PUT/PATCH handler with optional middleware.
|
|
156
204
|
*/
|
|
157
205
|
async function executeBodyEndpoint(route, req, res) {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
// Handle validation based on whether the validator is an object or function
|
|
162
|
-
if (typeof validator === 'function') {
|
|
163
|
-
// It's a ValueAssertion (function) - call it directly
|
|
164
|
-
validator(apiRequest);
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
// It's an ObjectAssertion - use validateObject
|
|
168
|
-
const objectValidator = validator;
|
|
169
|
-
const isEmptyValidator = Object.keys(objectValidator).length === 0;
|
|
170
|
-
const errorMessage = (0, assertions_1.validateObject)(apiRequest, objectValidator, `${http_status_codes_1.HTTP_BAD_REQUEST}: request body`, {
|
|
171
|
-
failOnUnknownFields: !isEmptyValidator,
|
|
172
|
-
});
|
|
173
|
-
(0, api_types_1.assertHttp)(!errorMessage, http_status_codes_1.HTTP_BAD_REQUEST, errorMessage || 'Request body validation failed');
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
177
|
-
if (error instanceof api_types_1.HttpError)
|
|
178
|
-
throw error;
|
|
179
|
-
throw new api_types_1.HttpError(http_status_codes_1.HTTP_BAD_REQUEST, (0, assertions_1.getMessageFromError)(error));
|
|
180
|
-
}
|
|
181
|
-
const validated = buildValidatedParams(req, route.$path, route.$query);
|
|
182
|
-
const requestContext = newRequestContext(apiRequest, req, res, validated.path, validated.query);
|
|
183
|
-
return await executeWithMiddleware(() => route.run(requestContext), (route.middlewares || []), requestContext);
|
|
206
|
+
const requestContext = new RequestContextImpl(req, res);
|
|
207
|
+
return await executeWithMiddleware(() => route.run(requestContext), route.middlewares || [], requestContext);
|
|
184
208
|
}
|
|
185
209
|
/**
|
|
186
210
|
* @Internal
|
|
@@ -197,18 +221,4 @@ async function executeWithMiddleware(run, middlewares, context) {
|
|
|
197
221
|
};
|
|
198
222
|
return await current(0);
|
|
199
223
|
}
|
|
200
|
-
/**
|
|
201
|
-
* @Internal
|
|
202
|
-
* Creates a new RequestContext instance.
|
|
203
|
-
*/
|
|
204
|
-
function newRequestContext(requestBody, req, res, validatedPath, validatedQuery) {
|
|
205
|
-
return {
|
|
206
|
-
body: requestBody,
|
|
207
|
-
req,
|
|
208
|
-
res,
|
|
209
|
-
path: validatedPath,
|
|
210
|
-
query: validatedQuery,
|
|
211
|
-
state: new Map(),
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
224
|
//# sourceMappingURL=router.js.map
|
package/dist/cjs/router.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/router.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/router.ts"],"names":[],"mappings":"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"}
|