@kizunajs/express 2.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.MD +9 -0
- package/README.md +42 -0
- package/dist/index.cjs +156 -0
- package/dist/index.d.cts +68 -0
- package/dist/index.d.mts +68 -0
- package/dist/index.mjs +154 -0
- package/package.json +74 -0
package/LICENSE.MD
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sondre Ørland
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @kizunajs/express
|
|
2
|
+
|
|
3
|
+
`@kizunajs/express` connects a Kizuna API to an Express 5 application. It handles routing, request validation, body parsing, and error formatting, all driven by what you declared.
|
|
4
|
+
|
|
5
|
+
**Requires Express >= 5.**
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pnpm add @kizunajs/express express
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// kizuna.config.ts
|
|
17
|
+
import { defineConfig } from 'kizunajs';
|
|
18
|
+
import { expressAdapter } from '@kizunajs/express';
|
|
19
|
+
import { routes } from './src/routes';
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
adapter: expressAdapter(),
|
|
23
|
+
routes,
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// src/index.ts
|
|
29
|
+
import express from 'express';
|
|
30
|
+
import kizuna from '../kizuna.config';
|
|
31
|
+
|
|
32
|
+
const app = express();
|
|
33
|
+
app.use(express.json());
|
|
34
|
+
|
|
35
|
+
kizuna.api.mount(app);
|
|
36
|
+
|
|
37
|
+
app.listen(3000);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Documentation
|
|
41
|
+
|
|
42
|
+
[Express adapter](https://kizunajs.com/docs/adapters/express)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let express = require("express");
|
|
3
|
+
let node_stream = require("node:stream");
|
|
4
|
+
let kizunajs_adapter = require("kizunajs/adapter");
|
|
5
|
+
//#region src/server.ts
|
|
6
|
+
const writeStream = (open, res, validate) => {
|
|
7
|
+
const controller = new AbortController();
|
|
8
|
+
res.on("close", () => {
|
|
9
|
+
if (!res.writableFinished) controller.abort();
|
|
10
|
+
});
|
|
11
|
+
res.flushHeaders();
|
|
12
|
+
const stream = open({
|
|
13
|
+
signal: controller.signal,
|
|
14
|
+
validate
|
|
15
|
+
});
|
|
16
|
+
(0, node_stream.pipeline)(node_stream.Readable.fromWeb(stream), res, () => void 0);
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Write a web `Response` to a node response. Plugins answer in web terms to stay
|
|
20
|
+
* adapter-agnostic, so the translation belongs here.
|
|
21
|
+
*/
|
|
22
|
+
const writeWebResponse = async (response, res) => {
|
|
23
|
+
if (!(response instanceof globalThis.Response)) return;
|
|
24
|
+
res.status(response.status);
|
|
25
|
+
response.headers.forEach((value, name) => res.setHeader(name, value));
|
|
26
|
+
if (!response.body) {
|
|
27
|
+
res.end();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
node_stream.Readable.fromWeb(response.body).pipe(res);
|
|
31
|
+
};
|
|
32
|
+
const adapter = (0, kizunajs_adapter.createAdapter)({
|
|
33
|
+
buildHandlerContext: (adapterRequest, { res }) => ({
|
|
34
|
+
req: adapterRequest.request,
|
|
35
|
+
res
|
|
36
|
+
}),
|
|
37
|
+
respond: (result, { res, next, formatError, responseValidation }) => {
|
|
38
|
+
if (result.kind === "handler-error") {
|
|
39
|
+
next(result.error);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (result.kind === "raw-response") {
|
|
43
|
+
writeWebResponse(result.response, res);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (res.headersSent) return;
|
|
47
|
+
if (result.kind === "not-found" || result.kind === "method-not-allowed") {
|
|
48
|
+
next();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const rendered = (0, kizunajs_adapter.renderJsonResult)(result, formatError, res.req);
|
|
52
|
+
for (const [key, value] of Object.entries(rendered.headers)) res.setHeader(key, value);
|
|
53
|
+
if (rendered.stream) {
|
|
54
|
+
res.status(rendered.status);
|
|
55
|
+
if (res.req.method === "HEAD") {
|
|
56
|
+
res.end();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
writeStream(rendered.stream, res, responseValidation);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (rendered.body === void 0) res.status(rendered.status).end();
|
|
63
|
+
else if (rendered.raw) {
|
|
64
|
+
const body = rendered.body;
|
|
65
|
+
res.status(rendered.status).send(typeof body === "string" || Buffer.isBuffer(body) ? body : Buffer.from(body));
|
|
66
|
+
} else res.status(rendered.status).json(rendered.body);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* Mount a Kizuna API onto an Express app.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* api.mount(app);
|
|
74
|
+
*/
|
|
75
|
+
function mountExpress(api, app, options) {
|
|
76
|
+
const guards = api[kizunajs_adapter.GUARDS_META];
|
|
77
|
+
const schemes = api[kizunajs_adapter.SCHEMES_META];
|
|
78
|
+
const requestContext = api[kizunajs_adapter.REQUEST_CONTEXT_META];
|
|
79
|
+
const pluginExports = (0, kizunajs_adapter.pluginExportsOf)(api);
|
|
80
|
+
const jobsMeta = api[kizunajs_adapter.JOBS_META];
|
|
81
|
+
const jobRunner = (0, kizunajs_adapter.jobRunnerFrom)(jobsMeta);
|
|
82
|
+
const expressRouter = (0, express.Router)();
|
|
83
|
+
const mountRoute = (routeKey, route, routes, router) => {
|
|
84
|
+
expressRouter[route.method.toLowerCase()](route.path, (req, _res, next) => {
|
|
85
|
+
req.kizunaRoute = route;
|
|
86
|
+
next();
|
|
87
|
+
}, async (req, res, next) => {
|
|
88
|
+
const adapterRequest = {
|
|
89
|
+
request: req,
|
|
90
|
+
method: req.method,
|
|
91
|
+
resolution: {
|
|
92
|
+
kind: "pre-resolved",
|
|
93
|
+
routeKey,
|
|
94
|
+
route,
|
|
95
|
+
params: req.params
|
|
96
|
+
},
|
|
97
|
+
query: req.query,
|
|
98
|
+
headers: req.headers,
|
|
99
|
+
readBody: () => req.body
|
|
100
|
+
};
|
|
101
|
+
await adapter.handle({
|
|
102
|
+
routes,
|
|
103
|
+
router,
|
|
104
|
+
request: adapterRequest,
|
|
105
|
+
responseContext: {
|
|
106
|
+
res,
|
|
107
|
+
next,
|
|
108
|
+
formatError: options?.formatError,
|
|
109
|
+
responseValidation: options?.responseValidation
|
|
110
|
+
},
|
|
111
|
+
guards,
|
|
112
|
+
schemes,
|
|
113
|
+
requestContext,
|
|
114
|
+
pluginExports,
|
|
115
|
+
jobs: jobRunner,
|
|
116
|
+
responseValidation: options?.responseValidation
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
const mountLane = (routes, router) => {
|
|
121
|
+
const declaredRoutes = [...adapter.eachRoute(routes, router)].sort((left, right) => Number(right.route.method === "HEAD") - Number(left.route.method === "HEAD"));
|
|
122
|
+
for (const { routeKey, route } of declaredRoutes) mountRoute(routeKey, route, routes, router);
|
|
123
|
+
};
|
|
124
|
+
mountLane(api.routes, api[kizunajs_adapter.ROUTER_META]);
|
|
125
|
+
mountLane((0, kizunajs_adapter.pluginRoutesOf)(api), (0, kizunajs_adapter.pluginRouterOf)(api));
|
|
126
|
+
if (jobsMeta) {
|
|
127
|
+
const routes = (0, kizunajs_adapter.jobRoutes)(jobsMeta);
|
|
128
|
+
const router = (0, kizunajs_adapter.jobRouter)(jobsMeta);
|
|
129
|
+
for (const [routeKey, route] of Object.entries(routes)) mountRoute(routeKey, route, routes, router);
|
|
130
|
+
}
|
|
131
|
+
app.use(expressRouter);
|
|
132
|
+
return expressRouter;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Serve an API on Express. What it takes is what every route is served with.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* // kizuna.config.ts
|
|
139
|
+
* export default defineConfig({
|
|
140
|
+
* adapter: expressAdapter(),
|
|
141
|
+
* routes,
|
|
142
|
+
* });
|
|
143
|
+
*
|
|
144
|
+
* // src/index.ts
|
|
145
|
+
* kizuna.api.mount(app);
|
|
146
|
+
*/
|
|
147
|
+
const expressAdapter = (defaults) => ({
|
|
148
|
+
name: "express",
|
|
149
|
+
mount: (api, app, options) => mountExpress(api, app, {
|
|
150
|
+
...defaults,
|
|
151
|
+
...options
|
|
152
|
+
})
|
|
153
|
+
});
|
|
154
|
+
//#endregion
|
|
155
|
+
exports.expressAdapter = expressAdapter;
|
|
156
|
+
exports.mountExpress = mountExpress;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Request, Response, Router } from "express";
|
|
2
|
+
import { Adapter, ApiWithRouter, ErrorFormatter, GUARDS_META, JOBS_META, REQUEST_CONTEXT_META, RouteDefinition, Routes, SCHEMES_META } from "kizunajs/adapter";
|
|
3
|
+
|
|
4
|
+
//#region src/server.d.ts
|
|
5
|
+
type ExpressApi<R extends Routes = Routes> = ApiWithRouter<R> & {
|
|
6
|
+
readonly [GUARDS_META]?: unknown;
|
|
7
|
+
readonly [SCHEMES_META]?: unknown;
|
|
8
|
+
readonly [REQUEST_CONTEXT_META]?: unknown;
|
|
9
|
+
readonly [JOBS_META]?: unknown;
|
|
10
|
+
/**
|
|
11
|
+
* Register every route on an Express app or router.
|
|
12
|
+
*/
|
|
13
|
+
mount: (app: AppLike, options?: ExpressOptions) => Router;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The Express request and response passed to each handler.
|
|
17
|
+
*/
|
|
18
|
+
interface ExpressHandlerContext {
|
|
19
|
+
req: Request;
|
|
20
|
+
res: Response;
|
|
21
|
+
}
|
|
22
|
+
declare global {
|
|
23
|
+
namespace Express {
|
|
24
|
+
interface Request {
|
|
25
|
+
kizunaRoute?: RouteDefinition;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
interface ExpressOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Validate handler return values against the route's response schemas.
|
|
32
|
+
* Mismatches surface as 500 errors. Enable in development.
|
|
33
|
+
*
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
responseValidation?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Reshape error (status >= 400) response bytes before they are sent. See
|
|
39
|
+
* {@link ErrorFormatter}.
|
|
40
|
+
*/
|
|
41
|
+
formatError?: ErrorFormatter<Request>;
|
|
42
|
+
}
|
|
43
|
+
interface AppLike {
|
|
44
|
+
use: (router: Router) => unknown;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Mount a Kizuna API onto an Express app.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* api.mount(app);
|
|
51
|
+
*/
|
|
52
|
+
declare function mountExpress(api: ExpressApi, app: AppLike, options?: ExpressOptions): Router;
|
|
53
|
+
/**
|
|
54
|
+
* Serve an API on Express. What it takes is what every route is served with.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // kizuna.config.ts
|
|
58
|
+
* export default defineConfig({
|
|
59
|
+
* adapter: expressAdapter(),
|
|
60
|
+
* routes,
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* // src/index.ts
|
|
64
|
+
* kizuna.api.mount(app);
|
|
65
|
+
*/
|
|
66
|
+
declare const expressAdapter: (defaults?: ExpressOptions) => Adapter<ExpressHandlerContext, [app: AppLike, options?: ExpressOptions], Router>;
|
|
67
|
+
//#endregion
|
|
68
|
+
export { AppLike, ExpressApi, ExpressHandlerContext, ExpressOptions, expressAdapter, mountExpress };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Request, Response, Router } from "express";
|
|
2
|
+
import { Adapter, ApiWithRouter, ErrorFormatter, GUARDS_META, JOBS_META, REQUEST_CONTEXT_META, RouteDefinition, Routes, SCHEMES_META } from "kizunajs/adapter";
|
|
3
|
+
|
|
4
|
+
//#region src/server.d.ts
|
|
5
|
+
type ExpressApi<R extends Routes = Routes> = ApiWithRouter<R> & {
|
|
6
|
+
readonly [GUARDS_META]?: unknown;
|
|
7
|
+
readonly [SCHEMES_META]?: unknown;
|
|
8
|
+
readonly [REQUEST_CONTEXT_META]?: unknown;
|
|
9
|
+
readonly [JOBS_META]?: unknown;
|
|
10
|
+
/**
|
|
11
|
+
* Register every route on an Express app or router.
|
|
12
|
+
*/
|
|
13
|
+
mount: (app: AppLike, options?: ExpressOptions) => Router;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The Express request and response passed to each handler.
|
|
17
|
+
*/
|
|
18
|
+
interface ExpressHandlerContext {
|
|
19
|
+
req: Request;
|
|
20
|
+
res: Response;
|
|
21
|
+
}
|
|
22
|
+
declare global {
|
|
23
|
+
namespace Express {
|
|
24
|
+
interface Request {
|
|
25
|
+
kizunaRoute?: RouteDefinition;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
interface ExpressOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Validate handler return values against the route's response schemas.
|
|
32
|
+
* Mismatches surface as 500 errors. Enable in development.
|
|
33
|
+
*
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
responseValidation?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Reshape error (status >= 400) response bytes before they are sent. See
|
|
39
|
+
* {@link ErrorFormatter}.
|
|
40
|
+
*/
|
|
41
|
+
formatError?: ErrorFormatter<Request>;
|
|
42
|
+
}
|
|
43
|
+
interface AppLike {
|
|
44
|
+
use: (router: Router) => unknown;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Mount a Kizuna API onto an Express app.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* api.mount(app);
|
|
51
|
+
*/
|
|
52
|
+
declare function mountExpress(api: ExpressApi, app: AppLike, options?: ExpressOptions): Router;
|
|
53
|
+
/**
|
|
54
|
+
* Serve an API on Express. What it takes is what every route is served with.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // kizuna.config.ts
|
|
58
|
+
* export default defineConfig({
|
|
59
|
+
* adapter: expressAdapter(),
|
|
60
|
+
* routes,
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* // src/index.ts
|
|
64
|
+
* kizuna.api.mount(app);
|
|
65
|
+
*/
|
|
66
|
+
declare const expressAdapter: (defaults?: ExpressOptions) => Adapter<ExpressHandlerContext, [app: AppLike, options?: ExpressOptions], Router>;
|
|
67
|
+
//#endregion
|
|
68
|
+
export { AppLike, ExpressApi, ExpressHandlerContext, ExpressOptions, expressAdapter, mountExpress };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { Readable, pipeline } from "node:stream";
|
|
3
|
+
import { GUARDS_META, JOBS_META, REQUEST_CONTEXT_META, ROUTER_META, SCHEMES_META, createAdapter, jobRouter, jobRoutes, jobRunnerFrom, pluginExportsOf, pluginRouterOf, pluginRoutesOf, renderJsonResult } from "kizunajs/adapter";
|
|
4
|
+
//#region src/server.ts
|
|
5
|
+
const writeStream = (open, res, validate) => {
|
|
6
|
+
const controller = new AbortController();
|
|
7
|
+
res.on("close", () => {
|
|
8
|
+
if (!res.writableFinished) controller.abort();
|
|
9
|
+
});
|
|
10
|
+
res.flushHeaders();
|
|
11
|
+
const stream = open({
|
|
12
|
+
signal: controller.signal,
|
|
13
|
+
validate
|
|
14
|
+
});
|
|
15
|
+
pipeline(Readable.fromWeb(stream), res, () => void 0);
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Write a web `Response` to a node response. Plugins answer in web terms to stay
|
|
19
|
+
* adapter-agnostic, so the translation belongs here.
|
|
20
|
+
*/
|
|
21
|
+
const writeWebResponse = async (response, res) => {
|
|
22
|
+
if (!(response instanceof globalThis.Response)) return;
|
|
23
|
+
res.status(response.status);
|
|
24
|
+
response.headers.forEach((value, name) => res.setHeader(name, value));
|
|
25
|
+
if (!response.body) {
|
|
26
|
+
res.end();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
Readable.fromWeb(response.body).pipe(res);
|
|
30
|
+
};
|
|
31
|
+
const adapter = createAdapter({
|
|
32
|
+
buildHandlerContext: (adapterRequest, { res }) => ({
|
|
33
|
+
req: adapterRequest.request,
|
|
34
|
+
res
|
|
35
|
+
}),
|
|
36
|
+
respond: (result, { res, next, formatError, responseValidation }) => {
|
|
37
|
+
if (result.kind === "handler-error") {
|
|
38
|
+
next(result.error);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (result.kind === "raw-response") {
|
|
42
|
+
writeWebResponse(result.response, res);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (res.headersSent) return;
|
|
46
|
+
if (result.kind === "not-found" || result.kind === "method-not-allowed") {
|
|
47
|
+
next();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const rendered = renderJsonResult(result, formatError, res.req);
|
|
51
|
+
for (const [key, value] of Object.entries(rendered.headers)) res.setHeader(key, value);
|
|
52
|
+
if (rendered.stream) {
|
|
53
|
+
res.status(rendered.status);
|
|
54
|
+
if (res.req.method === "HEAD") {
|
|
55
|
+
res.end();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
writeStream(rendered.stream, res, responseValidation);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (rendered.body === void 0) res.status(rendered.status).end();
|
|
62
|
+
else if (rendered.raw) {
|
|
63
|
+
const body = rendered.body;
|
|
64
|
+
res.status(rendered.status).send(typeof body === "string" || Buffer.isBuffer(body) ? body : Buffer.from(body));
|
|
65
|
+
} else res.status(rendered.status).json(rendered.body);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
/**
|
|
69
|
+
* Mount a Kizuna API onto an Express app.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* api.mount(app);
|
|
73
|
+
*/
|
|
74
|
+
function mountExpress(api, app, options) {
|
|
75
|
+
const guards = api[GUARDS_META];
|
|
76
|
+
const schemes = api[SCHEMES_META];
|
|
77
|
+
const requestContext = api[REQUEST_CONTEXT_META];
|
|
78
|
+
const pluginExports = pluginExportsOf(api);
|
|
79
|
+
const jobsMeta = api[JOBS_META];
|
|
80
|
+
const jobRunner = jobRunnerFrom(jobsMeta);
|
|
81
|
+
const expressRouter = Router();
|
|
82
|
+
const mountRoute = (routeKey, route, routes, router) => {
|
|
83
|
+
expressRouter[route.method.toLowerCase()](route.path, (req, _res, next) => {
|
|
84
|
+
req.kizunaRoute = route;
|
|
85
|
+
next();
|
|
86
|
+
}, async (req, res, next) => {
|
|
87
|
+
const adapterRequest = {
|
|
88
|
+
request: req,
|
|
89
|
+
method: req.method,
|
|
90
|
+
resolution: {
|
|
91
|
+
kind: "pre-resolved",
|
|
92
|
+
routeKey,
|
|
93
|
+
route,
|
|
94
|
+
params: req.params
|
|
95
|
+
},
|
|
96
|
+
query: req.query,
|
|
97
|
+
headers: req.headers,
|
|
98
|
+
readBody: () => req.body
|
|
99
|
+
};
|
|
100
|
+
await adapter.handle({
|
|
101
|
+
routes,
|
|
102
|
+
router,
|
|
103
|
+
request: adapterRequest,
|
|
104
|
+
responseContext: {
|
|
105
|
+
res,
|
|
106
|
+
next,
|
|
107
|
+
formatError: options?.formatError,
|
|
108
|
+
responseValidation: options?.responseValidation
|
|
109
|
+
},
|
|
110
|
+
guards,
|
|
111
|
+
schemes,
|
|
112
|
+
requestContext,
|
|
113
|
+
pluginExports,
|
|
114
|
+
jobs: jobRunner,
|
|
115
|
+
responseValidation: options?.responseValidation
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
const mountLane = (routes, router) => {
|
|
120
|
+
const declaredRoutes = [...adapter.eachRoute(routes, router)].sort((left, right) => Number(right.route.method === "HEAD") - Number(left.route.method === "HEAD"));
|
|
121
|
+
for (const { routeKey, route } of declaredRoutes) mountRoute(routeKey, route, routes, router);
|
|
122
|
+
};
|
|
123
|
+
mountLane(api.routes, api[ROUTER_META]);
|
|
124
|
+
mountLane(pluginRoutesOf(api), pluginRouterOf(api));
|
|
125
|
+
if (jobsMeta) {
|
|
126
|
+
const routes = jobRoutes(jobsMeta);
|
|
127
|
+
const router = jobRouter(jobsMeta);
|
|
128
|
+
for (const [routeKey, route] of Object.entries(routes)) mountRoute(routeKey, route, routes, router);
|
|
129
|
+
}
|
|
130
|
+
app.use(expressRouter);
|
|
131
|
+
return expressRouter;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Serve an API on Express. What it takes is what every route is served with.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* // kizuna.config.ts
|
|
138
|
+
* export default defineConfig({
|
|
139
|
+
* adapter: expressAdapter(),
|
|
140
|
+
* routes,
|
|
141
|
+
* });
|
|
142
|
+
*
|
|
143
|
+
* // src/index.ts
|
|
144
|
+
* kizuna.api.mount(app);
|
|
145
|
+
*/
|
|
146
|
+
const expressAdapter = (defaults) => ({
|
|
147
|
+
name: "express",
|
|
148
|
+
mount: (api, app, options) => mountExpress(api, app, {
|
|
149
|
+
...defaults,
|
|
150
|
+
...options
|
|
151
|
+
})
|
|
152
|
+
});
|
|
153
|
+
//#endregion
|
|
154
|
+
export { expressAdapter, mountExpress };
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kizunajs/express",
|
|
3
|
+
"version": "2.0.0-beta.0",
|
|
4
|
+
"description": "Express adapter for Kizuna.js",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"kizunajs",
|
|
7
|
+
"typescript",
|
|
8
|
+
"zod",
|
|
9
|
+
"openapi",
|
|
10
|
+
"spec-driven",
|
|
11
|
+
"type-safe",
|
|
12
|
+
"rest",
|
|
13
|
+
"api",
|
|
14
|
+
"express",
|
|
15
|
+
"adapter",
|
|
16
|
+
"server"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"homepage": "https://kizunajs.com/docs/adapters/express",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/kizunajs/kizuna.git",
|
|
23
|
+
"directory": "packages/express"
|
|
24
|
+
},
|
|
25
|
+
"bugs": "https://github.com/kizunajs/kizuna/issues",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=24"
|
|
30
|
+
},
|
|
31
|
+
"main": "./dist/index.cjs",
|
|
32
|
+
"module": "./dist/index.mjs",
|
|
33
|
+
"types": "./dist/index.d.mts",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"import": {
|
|
37
|
+
"types": "./dist/index.d.mts",
|
|
38
|
+
"default": "./dist/index.mjs"
|
|
39
|
+
},
|
|
40
|
+
"require": {
|
|
41
|
+
"types": "./dist/index.d.cts",
|
|
42
|
+
"default": "./dist/index.cjs"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"kizuna": {
|
|
47
|
+
"entries": {
|
|
48
|
+
".": "server"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"dist"
|
|
53
|
+
],
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"express": "^5.0.0",
|
|
59
|
+
"zod": "^4.0.0",
|
|
60
|
+
"kizunajs": "2.0.0-beta.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/express": "^5.0.0",
|
|
64
|
+
"express": "^5.0.0",
|
|
65
|
+
"tsdown": "^0.21.0",
|
|
66
|
+
"typescript": "^6.0.3",
|
|
67
|
+
"zod": "^4.0.0",
|
|
68
|
+
"kizunajs": "2.0.0-beta.0"
|
|
69
|
+
},
|
|
70
|
+
"scripts": {
|
|
71
|
+
"build": "tsdown src/index.ts --format esm,cjs --dts --clean --external kizunajs --external express --external zod",
|
|
72
|
+
"typecheck": "tsc --noEmit"
|
|
73
|
+
}
|
|
74
|
+
}
|