@routepact/express 0.1.6
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 +257 -0
- package/dist/endpoint.utils.d.ts +42 -0
- package/dist/endpoint.utils.d.ts.map +1 -0
- package/dist/endpoint.utils.js +60 -0
- package/dist/endpoint.utils.js.map +1 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +23 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/router.utils.d.ts +11 -0
- package/dist/router.utils.d.ts.map +1 -0
- package/dist/router.utils.js +127 -0
- package/dist/router.utils.js.map +1 -0
- package/dist/types.d.ts +55 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +8 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +77 -0
- package/dist/validation.js.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# @routepact/express
|
|
2
|
+
|
|
3
|
+
Express adapter for `@routepact/core` pacts. Provides type-safe endpoint builders, request/response validation middleware, and a versioned router factory.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @routepact/express @routepact/core express zod
|
|
9
|
+
npm install -D @types/express
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Core concepts
|
|
13
|
+
|
|
14
|
+
### `defineEndpoint`
|
|
15
|
+
|
|
16
|
+
Wraps a handler with a pact, giving the `req` and `res` objects types inferred directly from the pact's Zod schemas.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { defineEndpoint } from "@routepact/express";
|
|
20
|
+
import { PostPacts } from "../shared/pacts/post.pact";
|
|
21
|
+
|
|
22
|
+
const createPost = defineEndpoint({
|
|
23
|
+
pact: PostPacts.create,
|
|
24
|
+
handler: async (req, res) => {
|
|
25
|
+
// req.body is typed as { title: string; body: string }
|
|
26
|
+
const { title, body } = req.body;
|
|
27
|
+
|
|
28
|
+
const post = await db.posts.insert({ title, body });
|
|
29
|
+
|
|
30
|
+
// res.json only accepts the shape defined by PostPacts.create.response
|
|
31
|
+
res.status(201).json(post);
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
If the pact path has parameters, `req.parsedParams` is automatically populated from `req.params` and typed from the path string. If the pact has a `query` Zod schema, the validated value is available as `req.parsedQueries`:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const getPostById = defineEndpoint({
|
|
40
|
+
pact: PostPacts.getById, // path: "/posts/:id"
|
|
41
|
+
handler: (req, res) => {
|
|
42
|
+
// req.parsedParams is typed as { id: string }
|
|
43
|
+
const { id } = req.parsedParams;
|
|
44
|
+
// ...
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const listPosts = defineEndpoint({
|
|
49
|
+
pact: PostPacts.list, // has query: z.object({ page: z.string().optional() })
|
|
50
|
+
handler: (req, res) => {
|
|
51
|
+
// req.parsedQueries is typed as { page?: string }
|
|
52
|
+
const { page } = req.parsedQueries;
|
|
53
|
+
// ...
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `createRouter`
|
|
59
|
+
|
|
60
|
+
Takes a config with a `basePath` and route groups and returns an Express router mounted at that path.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { createRouter } from "@routepact/express";
|
|
64
|
+
import {
|
|
65
|
+
createPost,
|
|
66
|
+
getPostById,
|
|
67
|
+
listPosts,
|
|
68
|
+
updatePost,
|
|
69
|
+
deletePost,
|
|
70
|
+
} from "./post.endpoints";
|
|
71
|
+
|
|
72
|
+
export const postRouter = createRouter({
|
|
73
|
+
basePath: "/api/v1",
|
|
74
|
+
routeGroups: [
|
|
75
|
+
{
|
|
76
|
+
description: "Posts",
|
|
77
|
+
endpoints: [listPosts, getPostById, createPost, updatePost, deletePost],
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Mount in your Express app
|
|
83
|
+
app.use(postRouter);
|
|
84
|
+
// Routes are now available at /api/v1/posts, /api/v1/posts/:id, etc.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Options:**
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
createRouter(config, {
|
|
91
|
+
logger: myLogger, // default: console — must have an `error` method
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The router detects and throws on duplicate route registrations (`[method] path` pairs) at startup rather than silently shadowing them.
|
|
96
|
+
|
|
97
|
+
### `defineRouteGroup`
|
|
98
|
+
|
|
99
|
+
Helper to define a route group with full type checking before passing it to `createRouter`:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { defineRouteGroup } from "@routepact/express";
|
|
103
|
+
|
|
104
|
+
const publicRoutes = defineRouteGroup({
|
|
105
|
+
description: "Public routes",
|
|
106
|
+
endpoints: [listPosts, getPostById],
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Middleware
|
|
111
|
+
|
|
112
|
+
### Group middleware
|
|
113
|
+
|
|
114
|
+
Apply Express middleware to every endpoint in a route group. Common use case: protecting a group of routes with an auth check.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { createRouter } from "@routepact/express";
|
|
118
|
+
import { requireAuth } from "./middlewares/auth";
|
|
119
|
+
import { listPosts, createPost } from "./post.endpoints";
|
|
120
|
+
|
|
121
|
+
const router = createRouter({
|
|
122
|
+
basePath: "/api/v1",
|
|
123
|
+
routeGroups: [
|
|
124
|
+
{
|
|
125
|
+
description: "Public routes",
|
|
126
|
+
endpoints: [listPosts],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
description: "Protected routes",
|
|
130
|
+
middlewares: [requireAuth],
|
|
131
|
+
endpoints: [createPost],
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Endpoint middleware (`createEndpointMiddleware`)
|
|
138
|
+
|
|
139
|
+
Type-safe middleware that augments the `req` object. The properties it returns are merged into the request type seen by the handler.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { createEndpointMiddleware, defineEndpoint } from "@routepact/express";
|
|
143
|
+
import { PostPacts } from "../shared/pacts/post.pact";
|
|
144
|
+
import { getSessionFromRequest } from "./auth";
|
|
145
|
+
|
|
146
|
+
// Declare what this middleware adds to req
|
|
147
|
+
const withUser = createEndpointMiddleware(async (req) => {
|
|
148
|
+
const session = await getSessionFromRequest(req);
|
|
149
|
+
if (!session) throw new Error("Unauthorized");
|
|
150
|
+
return { user: session.user }; // TypeScript infers { user: User }
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const createPost = defineEndpoint({
|
|
154
|
+
pact: PostPacts.create,
|
|
155
|
+
middlewares: [withUser] as const, // `as const` needed to preserve tuple type
|
|
156
|
+
handler: async (req, res) => {
|
|
157
|
+
// req.user is now typed as User — no casting needed
|
|
158
|
+
const post = await db.posts.insert({
|
|
159
|
+
...req.body.resource,
|
|
160
|
+
authorId: req.user.id,
|
|
161
|
+
});
|
|
162
|
+
res.status(201).json({ post });
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Multiple middlewares are supported and their added types are merged:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
const getPost = defineEndpoint({
|
|
171
|
+
pact: PostPacts.getById,
|
|
172
|
+
middlewares: [withUser, withRateLimit] as const,
|
|
173
|
+
handler: async (req, res) => {
|
|
174
|
+
// req has both .user (from withUser) and .rateLimit (from withRateLimit)
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Validation middleware
|
|
180
|
+
|
|
181
|
+
Request and response validation is applied automatically when a pact has the corresponding Zod schemas. You don't need to call these manually unless you want to use them outside of `createRouter`.
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import {
|
|
185
|
+
createRequestValidationMiddleware,
|
|
186
|
+
createResponseValidationMiddleware,
|
|
187
|
+
} from "@routepact/express";
|
|
188
|
+
import { z } from "zod";
|
|
189
|
+
|
|
190
|
+
// Validates req.body against the schema
|
|
191
|
+
app.use(createRequestValidationMiddleware(z.object({ name: z.string() })));
|
|
192
|
+
|
|
193
|
+
// Validates res.json(...) before sending
|
|
194
|
+
app.use(
|
|
195
|
+
createResponseValidationMiddleware(
|
|
196
|
+
z.object({ id: z.string(), name: z.string() }),
|
|
197
|
+
),
|
|
198
|
+
);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Validation errors
|
|
202
|
+
|
|
203
|
+
When validation fails, the middleware calls `next(error)` with one of these:
|
|
204
|
+
|
|
205
|
+
| Error class | Status | When |
|
|
206
|
+
| ------------------------- | ------ | -------------------------------------------------- |
|
|
207
|
+
| `RequestValidationError` | 400 | `req.body` or `req.query` fails validation |
|
|
208
|
+
| `ResponseValidationError` | 500 | `res.json({ resource })` fails the response schema |
|
|
209
|
+
|
|
210
|
+
Both expose a `cause` property with the raw `ZodError`. Register an error handler in your Express app to format them:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import {
|
|
214
|
+
ValidationError,
|
|
215
|
+
RequestValidationError,
|
|
216
|
+
ResponseValidationError,
|
|
217
|
+
} from "@routepact/express";
|
|
218
|
+
|
|
219
|
+
app.use((err, req, res, next) => {
|
|
220
|
+
if (err instanceof RequestValidationError) {
|
|
221
|
+
return res
|
|
222
|
+
.status(400)
|
|
223
|
+
.json({ message: "Invalid request", errors: err.cause.errors });
|
|
224
|
+
}
|
|
225
|
+
if (err instanceof ResponseValidationError) {
|
|
226
|
+
console.error("Response validation failed:", err.cause.errors);
|
|
227
|
+
return res.status(500).json({ message: "Internal server error" });
|
|
228
|
+
}
|
|
229
|
+
// Or catch any validation error regardless of type:
|
|
230
|
+
if (err instanceof ValidationError) {
|
|
231
|
+
return res.status(err.status).json({ message: err.message });
|
|
232
|
+
}
|
|
233
|
+
next(err);
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Type reference
|
|
238
|
+
|
|
239
|
+
| Export | Description |
|
|
240
|
+
| ------------------------------------------------------ | -------------------------------------------------------------------------- |
|
|
241
|
+
| `defineEndpoint(config)` | Creates a typed endpoint from a pact + handler |
|
|
242
|
+
| `defineRouteGroup(group)` | Creates a typed route group |
|
|
243
|
+
| `createEndpointMiddleware(handler)` | Creates a type-augmenting middleware |
|
|
244
|
+
| `createRouter(config, options?)` | Builds a versioned Express router |
|
|
245
|
+
| `createRequestValidationMiddleware(schema)` | Validates `req.body.resource` |
|
|
246
|
+
| `createResponseValidationMiddleware(resource?, meta?)` | Validates `res.json(...)` output |
|
|
247
|
+
| `ValidationError` | Base class for all validation errors — has `status` and `cause: ZodError` |
|
|
248
|
+
| `RequestValidationError` | Extends `ValidationError` — thrown on bad request, params, or query (400) |
|
|
249
|
+
| `ResponseValidationError` | Extends `ValidationError` — thrown on bad response body (status 500) |
|
|
250
|
+
| `BaseRequest<T>` | Express `Request` with typed `body`, `parsedParams`, `parsedQueries` |
|
|
251
|
+
| `BackendApiResponse<TRes, TMeta>` | Express `Response` with typed `json()` |
|
|
252
|
+
| `EndpointMiddleware<TAdds>` | A middleware that augments `req` with `TAdds` |
|
|
253
|
+
| `RouteEndpoint<TPact, TMiddlewares>` | Full endpoint type including handler and middlewares |
|
|
254
|
+
| `RouteGroup` | A group of endpoints with optional shared middleware |
|
|
255
|
+
| `RouterConfig` | Config passed to `createRouter` |
|
|
256
|
+
| `RouterOptions` | Options passed to `createRouter` — `logger` |
|
|
257
|
+
| `Logger` | `Pick<Console, "error">` — the logger interface expected by `createRouter` |
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AnyRoutePact } from "@routepact/core";
|
|
2
|
+
import type { EndpointMiddleware, EndpointMiddlewareHandler, RouteEndpoint, RouteGroup } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Defines a type-safe route endpoint with a pact, optional middlewares, and
|
|
5
|
+
* a handler whose request/response types are inferred from the pact schemas.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const getUser = createEndpoint({
|
|
9
|
+
* pact: UserPacts.getById,
|
|
10
|
+
* handler: (req, res) => { res.json({ req.body }); },
|
|
11
|
+
* });
|
|
12
|
+
*/
|
|
13
|
+
export declare function defineEndpoint<TPact extends AnyRoutePact, TMiddlewares extends readonly EndpointMiddleware<object>[] = []>(config: RouteEndpoint<TPact, TMiddlewares>): RouteEndpoint<TPact, TMiddlewares>;
|
|
14
|
+
/**
|
|
15
|
+
* Defines a type-safe route group with endpoints and optional middlewares
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const userRouteGroup = defineRouteGroup({
|
|
19
|
+
* description: "User management routes",
|
|
20
|
+
* middlewares: [] as const,
|
|
21
|
+
* endpoints: [
|
|
22
|
+
* createEndpoint({
|
|
23
|
+
* pact: UserPacts.getById,
|
|
24
|
+
* handler: (req, res) => { res.json({ req.body.resource }); }
|
|
25
|
+
* })
|
|
26
|
+
* });
|
|
27
|
+
*/
|
|
28
|
+
export declare function defineRouteGroup(group: RouteGroup): RouteGroup;
|
|
29
|
+
/**
|
|
30
|
+
* Creates a type-safe augmenting middleware that adds properties to the request object.
|
|
31
|
+
*
|
|
32
|
+
* The middleware function must return the properties it adds to the request.
|
|
33
|
+
* TypeScript enforces that the return type matches the declared type parameter.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const authMiddleware = createEndpointMiddleware(async (req) => {
|
|
37
|
+
* const session = await getSession(req);
|
|
38
|
+
* return { user: session.user };
|
|
39
|
+
* });
|
|
40
|
+
*/
|
|
41
|
+
export declare function createEndpointMiddleware<TAdds extends object>(handler: EndpointMiddlewareHandler<TAdds>): EndpointMiddleware<TAdds>;
|
|
42
|
+
//# sourceMappingURL=endpoint.utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint.utils.d.ts","sourceRoot":"","sources":["../src/endpoint.utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EACV,kBAAkB,EAClB,yBAAyB,EACzB,aAAa,EACb,UAAU,EACX,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,KAAK,SAAS,YAAY,EAC1B,YAAY,SAAS,SAAS,kBAAkB,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,EAE/D,MAAM,EAAE,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,GACzC,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,CAEpC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAE9D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,SAAS,MAAM,EAC3D,OAAO,EAAE,yBAAyB,CAAC,KAAK,CAAC,GACxC,kBAAkB,CAAC,KAAK,CAAC,CAe3B"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defines a type-safe route endpoint with a pact, optional middlewares, and
|
|
3
|
+
* a handler whose request/response types are inferred from the pact schemas.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* const getUser = createEndpoint({
|
|
7
|
+
* pact: UserPacts.getById,
|
|
8
|
+
* handler: (req, res) => { res.json({ req.body }); },
|
|
9
|
+
* });
|
|
10
|
+
*/
|
|
11
|
+
export function defineEndpoint(config) {
|
|
12
|
+
return config;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Defines a type-safe route group with endpoints and optional middlewares
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const userRouteGroup = defineRouteGroup({
|
|
19
|
+
* description: "User management routes",
|
|
20
|
+
* middlewares: [] as const,
|
|
21
|
+
* endpoints: [
|
|
22
|
+
* createEndpoint({
|
|
23
|
+
* pact: UserPacts.getById,
|
|
24
|
+
* handler: (req, res) => { res.json({ req.body.resource }); }
|
|
25
|
+
* })
|
|
26
|
+
* });
|
|
27
|
+
*/
|
|
28
|
+
export function defineRouteGroup(group) {
|
|
29
|
+
return group;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Creates a type-safe augmenting middleware that adds properties to the request object.
|
|
33
|
+
*
|
|
34
|
+
* The middleware function must return the properties it adds to the request.
|
|
35
|
+
* TypeScript enforces that the return type matches the declared type parameter.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const authMiddleware = createEndpointMiddleware(async (req) => {
|
|
39
|
+
* const session = await getSession(req);
|
|
40
|
+
* return { user: session.user };
|
|
41
|
+
* });
|
|
42
|
+
*/
|
|
43
|
+
export function createEndpointMiddleware(handler) {
|
|
44
|
+
return {
|
|
45
|
+
_type: "endpoint",
|
|
46
|
+
handler: async (req, res, next) => {
|
|
47
|
+
try {
|
|
48
|
+
const additions = await handler(req, res, next);
|
|
49
|
+
if (additions) {
|
|
50
|
+
Object.assign(req, additions);
|
|
51
|
+
}
|
|
52
|
+
next();
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
next(error);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=endpoint.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint.utils.js","sourceRoot":"","sources":["../src/endpoint.utils.ts"],"names":[],"mappings":"AAQA;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAI5B,MAA0C;IAE1C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAiB;IAChD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAAyC;IAEzC,OAAO;QACL,KAAK,EAAE,UAAU;QACjB,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YAChC,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;gBAChD,IAAI,SAAS,EAAE,CAAC;oBACd,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;gBAChC,CAAC;gBACD,IAAI,EAAE,CAAC;YACT,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,KAAK,CAAC,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type z from "zod/v4";
|
|
2
|
+
export declare class ValidationError extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
readonly cause: z.ZodError;
|
|
5
|
+
constructor(message: string, status: number, cause: z.ZodError);
|
|
6
|
+
}
|
|
7
|
+
export declare class RequestValidationError extends ValidationError {
|
|
8
|
+
constructor(cause: z.ZodError);
|
|
9
|
+
}
|
|
10
|
+
export declare class ResponseValidationError extends ValidationError {
|
|
11
|
+
constructor(cause: z.ZodError);
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAE5B,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,SAAkB,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC;gBAExB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,QAAQ;CAM/D;AAED,qBAAa,sBAAuB,SAAQ,eAAe;gBAC7C,KAAK,EAAE,CAAC,CAAC,QAAQ;CAI9B;AAED,qBAAa,uBAAwB,SAAQ,eAAe;gBAC9C,KAAK,EAAE,CAAC,CAAC,QAAQ;CAI9B"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class ValidationError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
cause;
|
|
4
|
+
constructor(message, status, cause) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "ValidationError";
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.cause = cause;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class RequestValidationError extends ValidationError {
|
|
12
|
+
constructor(cause) {
|
|
13
|
+
super("Request validation failed", 400, cause);
|
|
14
|
+
this.name = "RequestValidationError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class ResponseValidationError extends ValidationError {
|
|
18
|
+
constructor(cause) {
|
|
19
|
+
super("Response validation failed", 500, cause);
|
|
20
|
+
this.name = "ResponseValidationError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,MAAM,CAAS;IACN,KAAK,CAAa;IAEpC,YAAY,OAAe,EAAE,MAAc,EAAE,KAAiB;QAC5D,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAED,MAAM,OAAO,sBAAuB,SAAQ,eAAe;IACzD,YAAY,KAAiB;QAC3B,KAAK,CAAC,2BAA2B,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QAC/C,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED,MAAM,OAAO,uBAAwB,SAAQ,eAAe;IAC1D,YAAY,KAAiB;QAC3B,KAAK,CAAC,4BAA4B,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QAChD,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import type { RouterConfig, RouterOptions } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Sets up API routes based on the provided configuration.
|
|
5
|
+
*
|
|
6
|
+
* @param config - Router configuration containing basePath and route groups
|
|
7
|
+
* @param options - Optional configuration: logger (default: console)
|
|
8
|
+
* @returns Express router with all configured routes mounted at `config.basePath`
|
|
9
|
+
*/
|
|
10
|
+
export declare function createRouter(config: RouterConfig, options?: RouterOptions): express.Router;
|
|
11
|
+
//# sourceMappingURL=router.utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.utils.d.ts","sourceRoot":"","sources":["../src/router.utils.ts"],"names":[],"mappings":"AACA,OAAO,OAA6C,MAAM,SAAS,CAAC;AACpE,OAAO,KAAK,EAIV,YAAY,EACZ,aAAa,EACd,MAAM,YAAY,CAAC;AAkJpB;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAyBhB"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { exhaustiveGuard } from "@routepact/core";
|
|
2
|
+
import express from "express";
|
|
3
|
+
import { createParamsValidationMiddleware, createQueryValidationMiddleware, createRequestValidationMiddleware, createResponseValidationMiddleware, } from "./validation.js";
|
|
4
|
+
function validateRouteConfig(config) {
|
|
5
|
+
if (!config.basePath || !config.basePath.startsWith("/")) {
|
|
6
|
+
throw new Error("Router configuration must have a basePath starting with '/'");
|
|
7
|
+
}
|
|
8
|
+
if (!config.routeGroups || config.routeGroups.length === 0) {
|
|
9
|
+
throw new Error("Router configuration must have at least one route group");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function isEndpointMiddleware(middleware) {
|
|
13
|
+
return (typeof middleware === "object" &&
|
|
14
|
+
middleware !== null &&
|
|
15
|
+
"_type" in middleware &&
|
|
16
|
+
middleware._type === "endpoint" &&
|
|
17
|
+
"handler" in middleware);
|
|
18
|
+
}
|
|
19
|
+
function buildMiddlewareChain(endpoint) {
|
|
20
|
+
const middlewares = [];
|
|
21
|
+
for (const middleware of endpoint.middlewares ?? []) {
|
|
22
|
+
if (isEndpointMiddleware(middleware)) {
|
|
23
|
+
middlewares.push(middleware.handler);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
middlewares.push(middleware);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (endpoint.pact?.params) {
|
|
30
|
+
middlewares.push(createParamsValidationMiddleware(endpoint.pact.params));
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
middlewares.push((req, _res, next) => {
|
|
34
|
+
req.parsedParams = { ...req.params };
|
|
35
|
+
next();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (endpoint.pact?.query) {
|
|
39
|
+
middlewares.push(createQueryValidationMiddleware(endpoint.pact.query));
|
|
40
|
+
}
|
|
41
|
+
if (endpoint.pact?.request) {
|
|
42
|
+
middlewares.push(createRequestValidationMiddleware(endpoint.pact.request));
|
|
43
|
+
}
|
|
44
|
+
if (endpoint.pact?.response) {
|
|
45
|
+
middlewares.push(createResponseValidationMiddleware(endpoint.pact.response));
|
|
46
|
+
}
|
|
47
|
+
return middlewares;
|
|
48
|
+
}
|
|
49
|
+
function registerHttpMethod(router, path, method, middlewares, handler) {
|
|
50
|
+
switch (method) {
|
|
51
|
+
case "get":
|
|
52
|
+
router.get(path, ...middlewares, handler);
|
|
53
|
+
break;
|
|
54
|
+
case "post":
|
|
55
|
+
router.post(path, ...middlewares, handler);
|
|
56
|
+
break;
|
|
57
|
+
case "put":
|
|
58
|
+
router.put(path, ...middlewares, handler);
|
|
59
|
+
break;
|
|
60
|
+
case "patch":
|
|
61
|
+
router.patch(path, ...middlewares, handler);
|
|
62
|
+
break;
|
|
63
|
+
case "delete":
|
|
64
|
+
router.delete(path, ...middlewares, handler);
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
exhaustiveGuard(method);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function processEndpoint(endpoint, router, registeredRoutes, groupMiddlewares = []) {
|
|
71
|
+
const path = endpoint.pact.path;
|
|
72
|
+
const method = endpoint.pact.method.toLowerCase();
|
|
73
|
+
const routeKey = `${path}:${method}`;
|
|
74
|
+
if (registeredRoutes.has(routeKey)) {
|
|
75
|
+
throw new Error(`Duplicate route detected: [${method.toUpperCase()}] ${path}. ` +
|
|
76
|
+
`Each endpoint must have a unique combination of path and HTTP method.`);
|
|
77
|
+
}
|
|
78
|
+
registeredRoutes.add(routeKey);
|
|
79
|
+
const middlewareChain = [
|
|
80
|
+
...groupMiddlewares,
|
|
81
|
+
...buildMiddlewareChain(endpoint),
|
|
82
|
+
];
|
|
83
|
+
try {
|
|
84
|
+
registerHttpMethod(router, path, method, middlewareChain, endpoint.handler);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
throw new Error(`Failed to register endpoint [${method.toUpperCase()}] ${path}`, { cause: error });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function processRouteGroup(group, router, registeredRoutes) {
|
|
91
|
+
const groupMiddlewares = group.middlewares ?? [];
|
|
92
|
+
for (const endpoint of group.endpoints ?? []) {
|
|
93
|
+
try {
|
|
94
|
+
processEndpoint(endpoint, router, registeredRoutes, groupMiddlewares);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
throw new Error("Error processing endpoint", { cause: error });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Sets up API routes based on the provided configuration.
|
|
103
|
+
*
|
|
104
|
+
* @param config - Router configuration containing basePath and route groups
|
|
105
|
+
* @param options - Optional configuration: logger (default: console)
|
|
106
|
+
* @returns Express router with all configured routes mounted at `config.basePath`
|
|
107
|
+
*/
|
|
108
|
+
export function createRouter(config, options = {}) {
|
|
109
|
+
const { logger = console } = options;
|
|
110
|
+
validateRouteConfig(config);
|
|
111
|
+
const apiRouter = express.Router();
|
|
112
|
+
const versionPath = config.basePath;
|
|
113
|
+
const versionRouter = express.Router();
|
|
114
|
+
const registeredRoutes = new Set();
|
|
115
|
+
try {
|
|
116
|
+
for (const group of config.routeGroups) {
|
|
117
|
+
processRouteGroup(group, versionRouter, registeredRoutes);
|
|
118
|
+
}
|
|
119
|
+
apiRouter.use(versionPath, versionRouter);
|
|
120
|
+
return apiRouter;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
logger.error(`Failed to setup API routes: ${error instanceof Error ? error.message : String(error)}`);
|
|
124
|
+
throw new Error("API Router Setup Failed", { cause: error });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=router.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.utils.js","sourceRoot":"","sources":["../src/router.utils.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { AnyRoutePact, ExpectedParams, HasParams } from "@routepact/core";
|
|
2
|
+
import type { NextFunction, Request, RequestHandler, Response } from "express";
|
|
3
|
+
import type z from "zod/v4";
|
|
4
|
+
export interface AnyServerRequest<TBody = unknown> extends Request {
|
|
5
|
+
body: TBody;
|
|
6
|
+
parsedParams?: Record<string, unknown>;
|
|
7
|
+
parsedQueries?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
type PactParamsField<TPact extends AnyRoutePact> = TPact["params"] extends z.ZodType<infer TParams> ? {
|
|
10
|
+
parsedParams: Omit<ExpectedParams<TPact["path"]>, keyof TParams> & TParams;
|
|
11
|
+
} : HasParams<TPact["path"]> extends true ? {
|
|
12
|
+
parsedParams: ExpectedParams<TPact["path"]>;
|
|
13
|
+
} : {
|
|
14
|
+
parsedParams?: never;
|
|
15
|
+
};
|
|
16
|
+
type PactQueriesField<TPact extends AnyRoutePact> = TPact["query"] extends z.ZodType<infer TQuery> ? {
|
|
17
|
+
parsedQueries: TQuery;
|
|
18
|
+
} : {
|
|
19
|
+
parsedQueries?: never;
|
|
20
|
+
};
|
|
21
|
+
export type PactServerRequest<TPact extends AnyRoutePact> = Omit<AnyServerRequest<TPact["request"] extends z.ZodType<unknown> ? z.infer<TPact["request"]> : null>, "parsedParams" | "parsedQueries"> & PactParamsField<TPact> & PactQueriesField<TPact>;
|
|
22
|
+
export interface AnyServerResponse<TBody = unknown> extends Response {
|
|
23
|
+
json: (body: TBody) => this;
|
|
24
|
+
}
|
|
25
|
+
export type PactServerResponse<TPact extends AnyRoutePact> = AnyServerResponse<TPact extends {
|
|
26
|
+
response: z.ZodType<unknown>;
|
|
27
|
+
} ? z.infer<TPact["response"]> : null>;
|
|
28
|
+
export interface EndpointMiddleware<TAdds extends object = object> {
|
|
29
|
+
_type: "endpoint";
|
|
30
|
+
_adds?: TAdds;
|
|
31
|
+
handler: RequestHandler;
|
|
32
|
+
}
|
|
33
|
+
export type MergeMiddlewares<T extends readonly EndpointMiddleware<object>[]> = T extends readonly [infer First, ...infer Rest] ? First extends EndpointMiddleware<infer Adds> ? Rest extends readonly EndpointMiddleware<object>[] ? Adds & MergeMiddlewares<Rest> : Adds : {} : {};
|
|
34
|
+
export type EndpointMiddlewareHandler<TAdds extends object> = (req: AnyServerRequest, res: AnyServerResponse, next: NextFunction) => Promise<TAdds | undefined> | TAdds | undefined;
|
|
35
|
+
export type RouteEndpoint<TPact extends AnyRoutePact, TMiddlewares extends readonly EndpointMiddleware<object>[] = []> = {
|
|
36
|
+
pact: TPact;
|
|
37
|
+
middlewares?: TMiddlewares;
|
|
38
|
+
handler: (req: PactServerRequest<TPact> & MergeMiddlewares<TMiddlewares>, res: PactServerResponse<TPact>, next: () => void) => void | Promise<void>;
|
|
39
|
+
};
|
|
40
|
+
export type AnyRouteEndpoint = RouteEndpoint<AnyRoutePact, readonly EndpointMiddleware<object>[]>;
|
|
41
|
+
export interface RouteGroup {
|
|
42
|
+
description?: string;
|
|
43
|
+
middlewares?: RequestHandler[];
|
|
44
|
+
endpoints?: RouteEndpoint<AnyRoutePact, readonly EndpointMiddleware<object>[]>[];
|
|
45
|
+
}
|
|
46
|
+
export interface RouterConfig {
|
|
47
|
+
basePath: string;
|
|
48
|
+
routeGroups: RouteGroup[];
|
|
49
|
+
}
|
|
50
|
+
export type Logger = Pick<Console, "error">;
|
|
51
|
+
export interface RouterOptions {
|
|
52
|
+
logger?: Logger;
|
|
53
|
+
}
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { NextFunction, Request, Response } from "express";
|
|
2
|
+
import z from "zod/v4";
|
|
3
|
+
import type { AnyServerRequest, PactServerResponse } from "./types.js";
|
|
4
|
+
export declare function createRequestValidationMiddleware(schema: z.ZodType): (req: AnyServerRequest, _res: Response, next: NextFunction) => Promise<void>;
|
|
5
|
+
export declare function createParamsValidationMiddleware(schema: z.ZodType<Record<string, unknown>>): (req: AnyServerRequest, _res: Response, next: NextFunction) => Promise<void>;
|
|
6
|
+
export declare function createQueryValidationMiddleware(schema: z.ZodType<Record<string, unknown>>): (req: AnyServerRequest, _res: Response, next: NextFunction) => Promise<void>;
|
|
7
|
+
export declare function createResponseValidationMiddleware(schema?: z.ZodType): (_req: Request, res: PactServerResponse<any>, next: NextFunction) => Promise<void>;
|
|
8
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,CAAC,MAAM,QAAQ,CAAC;AAEvB,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEvE,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,IAE/D,KAAK,gBAAgB,EACrB,MAAM,QAAQ,EACd,MAAM,YAAY,KACjB,OAAO,CAAC,IAAI,CAAC,CAYjB;AAED,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,IAGxC,KAAK,gBAAgB,EACrB,MAAM,QAAQ,EACd,MAAM,YAAY,KACjB,OAAO,CAAC,IAAI,CAAC,CAYjB;AAED,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,IAGxC,KAAK,gBAAgB,EACrB,MAAM,QAAQ,EACd,MAAM,YAAY,KACjB,OAAO,CAAC,IAAI,CAAC,CAYjB;AAED,wBAAgB,kCAAkC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,IAEjE,MAAM,OAAO,EAEb,KAAK,kBAAkB,CAAC,GAAG,CAAC,EAC5B,MAAM,YAAY,KACjB,OAAO,CAAC,IAAI,CAAC,CAwBjB"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import z from "zod/v4";
|
|
2
|
+
import { RequestValidationError, ResponseValidationError } from "./errors.js";
|
|
3
|
+
export function createRequestValidationMiddleware(schema) {
|
|
4
|
+
return async (req, _res, next) => {
|
|
5
|
+
try {
|
|
6
|
+
req.body = schema.parse(req.body);
|
|
7
|
+
next();
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
if (error instanceof z.ZodError) {
|
|
11
|
+
next(new RequestValidationError(error));
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
next(error);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function createParamsValidationMiddleware(schema) {
|
|
20
|
+
return async (req, _res, next) => {
|
|
21
|
+
try {
|
|
22
|
+
req.parsedParams = { ...req.params, ...schema.parse(req.params) };
|
|
23
|
+
next();
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error instanceof z.ZodError) {
|
|
27
|
+
next(new RequestValidationError(error));
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
next(error);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function createQueryValidationMiddleware(schema) {
|
|
36
|
+
return async (req, _res, next) => {
|
|
37
|
+
try {
|
|
38
|
+
req.parsedQueries = schema.parse(req.query);
|
|
39
|
+
next();
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error instanceof z.ZodError) {
|
|
43
|
+
next(new RequestValidationError(error));
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
next(error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function createResponseValidationMiddleware(schema) {
|
|
52
|
+
return async (_req,
|
|
53
|
+
// biome-ignore lint/suspicious/noExplicitAny: response type not known
|
|
54
|
+
res, next) => {
|
|
55
|
+
const originalJson = res.json;
|
|
56
|
+
res.json = function (body) {
|
|
57
|
+
if (this.statusCode >= 400) {
|
|
58
|
+
return originalJson.call(this, body);
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const validated = schema ? schema.parse(body) : body;
|
|
62
|
+
return originalJson.call(this, validated);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error instanceof z.ZodError) {
|
|
66
|
+
next(new ResponseValidationError(error));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
next(error);
|
|
70
|
+
}
|
|
71
|
+
return originalJson(body);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
next();
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@routepact/express",
|
|
3
|
+
"version": "0.1.6",
|
|
4
|
+
"description": "Express adapter for type-safe route pacts — endpoint builders, request/response validation middleware, and router setup",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": "gitlab:mr5k/routepact",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -b",
|
|
21
|
+
"dev": "tsc -b --watch"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@routepact/core": "^0.1.5"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"express": ">=4.0.0",
|
|
31
|
+
"zod": ">=4.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/express": "5.0.3",
|
|
35
|
+
"express": "5.2.1",
|
|
36
|
+
"zod": "4.1.7"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"routes",
|
|
40
|
+
"typesafe",
|
|
41
|
+
"express",
|
|
42
|
+
"zod",
|
|
43
|
+
"middleware"
|
|
44
|
+
],
|
|
45
|
+
"license": "MIT"
|
|
46
|
+
}
|