@kizunajs/next 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 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,40 @@
1
+ # @kizunajs/next
2
+
3
+ `@kizunajs/next` connects a Kizuna API to the Next.js App Router, as a catch-all route handler.
4
+
5
+ **Requires Next.js >= 16.**
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ pnpm add @kizunajs/next
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ // kizuna.config.ts
17
+ import { defineConfig } from 'kizunajs';
18
+ import { nextAdapter } from '@kizunajs/next';
19
+ import { routes } from './src/routes';
20
+
21
+ export default defineConfig({
22
+ adapter: nextAdapter(),
23
+ routes,
24
+ });
25
+ ```
26
+
27
+ Export the handlers from a catch-all route:
28
+
29
+ ```ts
30
+ // src/app/api/[...kizuna]/route.ts
31
+ import kizuna from '../../../../kizuna.config';
32
+
33
+ export const { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS } = kizuna.api.mount({
34
+ basePath: '/api',
35
+ });
36
+ ```
37
+
38
+ ## Documentation
39
+
40
+ [Next.js adapter](https://kizunajs.com/docs/adapters/next)
package/dist/index.cjs ADDED
@@ -0,0 +1,219 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let kizunajs_adapter = require("kizunajs/adapter");
3
+ let next_server_js = require("next/server.js");
4
+ //#region src/server.ts
5
+ const jsonResponse = (status, body, headers, raw = false) => new next_server_js.NextResponse(body === null || body === void 0 ? null : raw ? body : JSON.stringify(body), {
6
+ status,
7
+ headers
8
+ });
9
+ const handleNextRequest = async (request, routes, router, options, guards, schemes, requestContext, pluginExports, jobs) => {
10
+ const url = new URL(request.url);
11
+ const adapter = (0, kizunajs_adapter.createAdapter)({
12
+ buildHandlerContext: (adapterRequest) => ({ request: adapterRequest.request }),
13
+ respond: (result) => {
14
+ if (result.kind === "raw-response") return result.response;
15
+ const rendered = (0, kizunajs_adapter.renderJsonResult)(result, options?.formatError, request, request.method);
16
+ if (rendered.stream) return new next_server_js.NextResponse(rendered.stream({
17
+ signal: request.signal,
18
+ validate: options?.responseValidation
19
+ }), {
20
+ status: rendered.status,
21
+ headers: rendered.headers
22
+ });
23
+ return jsonResponse(rendered.status, rendered.body, rendered.headers, rendered.raw);
24
+ },
25
+ onError: async (error) => {
26
+ if (!options?.onError) {
27
+ console.error("[kizuna/next] handler error:", error);
28
+ return;
29
+ }
30
+ const override = await options.onError(error, request);
31
+ if (override) return {
32
+ kind: "raw-response",
33
+ response: override
34
+ };
35
+ }
36
+ });
37
+ if (jobs) {
38
+ const matchedJob = (0, kizunajs_adapter.matchRoute)(request.method, url.pathname, jobs.routes, options?.basePath);
39
+ if (matchedJob.kind === "matched") {
40
+ const jobRequest = {
41
+ request,
42
+ method: request.method,
43
+ resolution: {
44
+ kind: "pre-resolved",
45
+ routeKey: matchedJob.match.routeKey,
46
+ route: matchedJob.match.route,
47
+ params: matchedJob.match.params
48
+ },
49
+ query: Object.fromEntries(url.searchParams),
50
+ headers: (0, kizunajs_adapter.headersToObject)(request.headers),
51
+ readBody: (route) => (0, kizunajs_adapter.parseFetchBody)(request, route)
52
+ };
53
+ return adapter.handle({
54
+ routes: jobs.routes,
55
+ router: jobs.router,
56
+ request: jobRequest,
57
+ responseContext: {},
58
+ guards,
59
+ schemes,
60
+ requestContext,
61
+ pluginExports,
62
+ jobs: jobs.runner,
63
+ responseValidation: options?.responseValidation
64
+ });
65
+ }
66
+ }
67
+ const globalMiddleware = options?.requestMiddleware;
68
+ if (globalMiddleware && globalMiddleware.length > 0) {
69
+ const matched = (0, kizunajs_adapter.matchRoute)(request.method, url.pathname, routes, options?.basePath);
70
+ if (matched.kind === "matched") {
71
+ const middlewareRoute = {
72
+ path: matched.match.route.path,
73
+ method: matched.match.route.method
74
+ };
75
+ for (const handler of globalMiddleware) {
76
+ const result = await handler(request, middlewareRoute);
77
+ if (result instanceof Response) return new next_server_js.NextResponse(result.body, {
78
+ status: result.status,
79
+ statusText: result.statusText,
80
+ headers: result.headers
81
+ });
82
+ }
83
+ const adapterRequest = {
84
+ request,
85
+ method: request.method,
86
+ resolution: {
87
+ kind: "pre-resolved",
88
+ routeKey: matched.match.routeKey,
89
+ route: matched.match.route,
90
+ params: matched.match.params
91
+ },
92
+ query: Object.fromEntries(url.searchParams),
93
+ headers: (0, kizunajs_adapter.headersToObject)(request.headers),
94
+ readBody: (route) => (0, kizunajs_adapter.parseFetchBody)(request, route)
95
+ };
96
+ return adapter.handle({
97
+ routes,
98
+ router,
99
+ request: adapterRequest,
100
+ responseContext: {},
101
+ guards,
102
+ schemes,
103
+ requestContext,
104
+ jobs: jobs?.runner,
105
+ responseValidation: options?.responseValidation
106
+ });
107
+ }
108
+ }
109
+ const adapterRequest = {
110
+ request,
111
+ method: request.method,
112
+ resolution: {
113
+ kind: "core-match",
114
+ path: url.pathname
115
+ },
116
+ query: Object.fromEntries(url.searchParams),
117
+ headers: (0, kizunajs_adapter.headersToObject)(request.headers),
118
+ readBody: (route) => (0, kizunajs_adapter.parseFetchBody)(request, route)
119
+ };
120
+ return adapter.handle({
121
+ routes,
122
+ router,
123
+ request: adapterRequest,
124
+ responseContext: {},
125
+ guards,
126
+ schemes,
127
+ requestContext,
128
+ pluginExports,
129
+ jobs: jobs?.runner,
130
+ basePath: options?.basePath,
131
+ responseValidation: options?.responseValidation
132
+ });
133
+ };
134
+ /**
135
+ * Create endpoints for a Next.js App Router catch-all route.
136
+ *
137
+ * @example
138
+ * // app/api/[...kizuna]/route.ts
139
+ * export const { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS } = api.mount({
140
+ * basePath: '/api',
141
+ * });
142
+ */
143
+ function mountNext(api, options) {
144
+ const guards = api[kizunajs_adapter.GUARDS_META];
145
+ const schemes = api[kizunajs_adapter.SCHEMES_META];
146
+ const requestContext = api[kizunajs_adapter.REQUEST_CONTEXT_META];
147
+ const jobsMeta = api[kizunajs_adapter.JOBS_META];
148
+ const mountedJobs = jobsMeta ? {
149
+ routes: (0, kizunajs_adapter.jobRoutes)(jobsMeta),
150
+ router: (0, kizunajs_adapter.jobRouter)(jobsMeta),
151
+ runner: (0, kizunajs_adapter.jobRunnerFrom)(jobsMeta)
152
+ } : void 0;
153
+ const handlerOptions = {
154
+ basePath: options?.basePath,
155
+ onError: options?.onError,
156
+ requestMiddleware: options?.requestMiddleware,
157
+ responseValidation: options?.responseValidation
158
+ };
159
+ const pluginExports = (0, kizunajs_adapter.pluginExportsOf)(api);
160
+ const pluginRoutes = (0, kizunajs_adapter.pluginRoutesOf)(api);
161
+ const pluginRouter = (0, kizunajs_adapter.pluginRouterOf)(api);
162
+ const hasPluginRoutes = Object.keys(pluginRoutes).length > 0;
163
+ const handler = async (request) => {
164
+ if (hasPluginRoutes) {
165
+ const pathname = new URL(request.url).pathname;
166
+ if (!((0, kizunajs_adapter.matchRoute)(request.method, pathname, api.routes, options?.basePath).kind === "matched") && (0, kizunajs_adapter.matchRoute)(request.method, pathname, pluginRoutes, options?.basePath).kind === "matched") return handleNextRequest(request, pluginRoutes, pluginRouter, handlerOptions, guards, schemes, requestContext, pluginExports, void 0);
167
+ }
168
+ return handleNextRequest(request, api.routes, api[kizunajs_adapter.ROUTER_META], handlerOptions, guards, schemes, requestContext, pluginExports, mountedJobs);
169
+ };
170
+ return {
171
+ GET: handler,
172
+ HEAD: handler,
173
+ POST: handler,
174
+ PUT: handler,
175
+ PATCH: handler,
176
+ DELETE: handler,
177
+ OPTIONS: handler
178
+ };
179
+ }
180
+ /**
181
+ * Serve an API on Next. What it takes is what every route is served with. Next
182
+ * routes by file, so mounting returns the route handlers to re-export rather
183
+ * than registering them on an app.
184
+ *
185
+ * @example
186
+ * // kizuna.config.ts
187
+ * export default defineConfig({
188
+ * adapter: nextAdapter(),
189
+ * routes,
190
+ * });
191
+ *
192
+ * // src/app/api/[...kizuna]/route.ts
193
+ * export const { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS } = kizuna.api.mount({
194
+ * basePath: '/api',
195
+ * });
196
+ */
197
+ const nextAdapter = (defaults) => ({
198
+ name: "next",
199
+ mount: (api, options) => mountNext(api, {
200
+ ...defaults,
201
+ ...options
202
+ })
203
+ });
204
+ //#endregion
205
+ Object.defineProperty(exports, "NextRequest", {
206
+ enumerable: true,
207
+ get: function() {
208
+ return next_server_js.NextRequest;
209
+ }
210
+ });
211
+ Object.defineProperty(exports, "NextResponse", {
212
+ enumerable: true,
213
+ get: function() {
214
+ return next_server_js.NextResponse;
215
+ }
216
+ });
217
+ exports.handleNextRequest = handleNextRequest;
218
+ exports.mountNext = mountNext;
219
+ exports.nextAdapter = nextAdapter;
@@ -0,0 +1,107 @@
1
+ import { Adapter, ApiWithRouter, ErrorFormatter, GUARDS_META, GuardMap, JOBS_META, JobRunner, Jobs, REQUEST_CONTEXT_META, RequestContextMap, Router, Routes, SCHEMES_META } from "kizunajs/adapter";
2
+ import { SecurityScheme } from "kizunajs";
3
+ import { NextRequest, NextRequest as NextRequest$1, NextResponse, NextResponse as NextResponse$1 } from "next/server.js";
4
+
5
+ //#region src/server.d.ts
6
+ interface NextHandlerContext {
7
+ request: NextRequest$1;
8
+ }
9
+ /**
10
+ * An api's jobs paired with their handlers, both in the shape the request
11
+ * pipeline takes.
12
+ */
13
+ interface MountedJobs {
14
+ routes: Routes;
15
+ router: Router<Routes, NextHandlerContext>;
16
+ runner: JobRunner<Jobs>;
17
+ }
18
+ /**
19
+ * Passed to each middleware function as the second argument.
20
+ */
21
+ interface NextMiddlewareRoute {
22
+ path: string;
23
+ method: string;
24
+ }
25
+ type NextMiddlewareHandler = (request: NextRequest$1, route: NextMiddlewareRoute) => Response | void | Promise<Response | void>;
26
+ interface NextHandlerOptions {
27
+ basePath?: string;
28
+ /**
29
+ * Map a thrown error into a response. Return a `NextResponse` (e.g. built
30
+ * from `problemDetails(...)`) to handle the error, or `void` to fall through
31
+ * to the default 500.
32
+ */
33
+ onError?: (error: unknown, request: NextRequest$1) => NextResponse$1 | Promise<NextResponse$1> | void | Promise<void>;
34
+ /**
35
+ * Reshape error (status >= 400) response bytes before they are sent. See
36
+ * {@link ErrorFormatter}.
37
+ */
38
+ formatError?: ErrorFormatter<NextRequest$1>;
39
+ /**
40
+ * Middleware functions that run after route matching but before the handler.
41
+ * Each receives `(request, route)`; return a `Response` to short-circuit.
42
+ * Authentication belongs in a guard.
43
+ */
44
+ requestMiddleware?: Array<NextMiddlewareHandler>;
45
+ /**
46
+ * Validate handler return values against the routes' response schemas.
47
+ * Mismatches surface as 500 errors. Intended for development; disable in
48
+ * production.
49
+ *
50
+ * @default false
51
+ */
52
+ responseValidation?: boolean;
53
+ }
54
+ declare const handleNextRequest: <T extends Routes>(request: NextRequest$1, routes: T, router: Router<T, NextHandlerContext>, options?: NextHandlerOptions, guards?: GuardMap<NextHandlerContext>, schemes?: Record<string, SecurityScheme>, requestContext?: RequestContextMap<NextHandlerContext>, pluginExports?: Record<string, unknown>, jobs?: MountedJobs) => Promise<NextResponse$1>;
55
+ type HttpHandlers = {
56
+ GET: HttpHandler;
57
+ HEAD: HttpHandler;
58
+ POST: HttpHandler;
59
+ PUT: HttpHandler;
60
+ PATCH: HttpHandler;
61
+ DELETE: HttpHandler;
62
+ OPTIONS: HttpHandler;
63
+ };
64
+ type HttpHandler = (request: NextRequest$1) => Promise<NextResponse$1>;
65
+ type NextApiWithRouter = ApiWithRouter & {
66
+ readonly [GUARDS_META]?: unknown;
67
+ readonly [SCHEMES_META]?: unknown;
68
+ readonly [REQUEST_CONTEXT_META]?: unknown;
69
+ readonly [JOBS_META]?: unknown;
70
+ };
71
+ type NextApi<R extends Routes = Routes> = ApiWithRouter<R> & {
72
+ readonly [GUARDS_META]?: unknown;
73
+ readonly [SCHEMES_META]?: unknown;
74
+ readonly [REQUEST_CONTEXT_META]?: unknown;
75
+ readonly [JOBS_META]?: unknown;
76
+ mount: (options?: NextHandlerOptions) => HttpHandlers;
77
+ };
78
+ /**
79
+ * Create endpoints for a Next.js App Router catch-all route.
80
+ *
81
+ * @example
82
+ * // app/api/[...kizuna]/route.ts
83
+ * export const { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS } = api.mount({
84
+ * basePath: '/api',
85
+ * });
86
+ */
87
+ declare function mountNext(api: NextApiWithRouter, options?: NextHandlerOptions): HttpHandlers;
88
+ /**
89
+ * Serve an API on Next. What it takes is what every route is served with. Next
90
+ * routes by file, so mounting returns the route handlers to re-export rather
91
+ * than registering them on an app.
92
+ *
93
+ * @example
94
+ * // kizuna.config.ts
95
+ * export default defineConfig({
96
+ * adapter: nextAdapter(),
97
+ * routes,
98
+ * });
99
+ *
100
+ * // src/app/api/[...kizuna]/route.ts
101
+ * export const { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS } = kizuna.api.mount({
102
+ * basePath: '/api',
103
+ * });
104
+ */
105
+ declare const nextAdapter: (defaults?: NextHandlerOptions) => Adapter<NextHandlerContext, [options?: NextHandlerOptions], HttpHandlers>;
106
+ //#endregion
107
+ export { MountedJobs, NextApi, NextApiWithRouter, NextHandlerContext, NextHandlerOptions, NextMiddlewareHandler, NextMiddlewareRoute, NextRequest, NextResponse, handleNextRequest, mountNext, nextAdapter };
@@ -0,0 +1,107 @@
1
+ import { Adapter, ApiWithRouter, ErrorFormatter, GUARDS_META, GuardMap, JOBS_META, JobRunner, Jobs, REQUEST_CONTEXT_META, RequestContextMap, Router, Routes, SCHEMES_META } from "kizunajs/adapter";
2
+ import { NextRequest, NextRequest as NextRequest$1, NextResponse, NextResponse as NextResponse$1 } from "next/server.js";
3
+ import { SecurityScheme } from "kizunajs";
4
+
5
+ //#region src/server.d.ts
6
+ interface NextHandlerContext {
7
+ request: NextRequest$1;
8
+ }
9
+ /**
10
+ * An api's jobs paired with their handlers, both in the shape the request
11
+ * pipeline takes.
12
+ */
13
+ interface MountedJobs {
14
+ routes: Routes;
15
+ router: Router<Routes, NextHandlerContext>;
16
+ runner: JobRunner<Jobs>;
17
+ }
18
+ /**
19
+ * Passed to each middleware function as the second argument.
20
+ */
21
+ interface NextMiddlewareRoute {
22
+ path: string;
23
+ method: string;
24
+ }
25
+ type NextMiddlewareHandler = (request: NextRequest$1, route: NextMiddlewareRoute) => Response | void | Promise<Response | void>;
26
+ interface NextHandlerOptions {
27
+ basePath?: string;
28
+ /**
29
+ * Map a thrown error into a response. Return a `NextResponse` (e.g. built
30
+ * from `problemDetails(...)`) to handle the error, or `void` to fall through
31
+ * to the default 500.
32
+ */
33
+ onError?: (error: unknown, request: NextRequest$1) => NextResponse$1 | Promise<NextResponse$1> | void | Promise<void>;
34
+ /**
35
+ * Reshape error (status >= 400) response bytes before they are sent. See
36
+ * {@link ErrorFormatter}.
37
+ */
38
+ formatError?: ErrorFormatter<NextRequest$1>;
39
+ /**
40
+ * Middleware functions that run after route matching but before the handler.
41
+ * Each receives `(request, route)`; return a `Response` to short-circuit.
42
+ * Authentication belongs in a guard.
43
+ */
44
+ requestMiddleware?: Array<NextMiddlewareHandler>;
45
+ /**
46
+ * Validate handler return values against the routes' response schemas.
47
+ * Mismatches surface as 500 errors. Intended for development; disable in
48
+ * production.
49
+ *
50
+ * @default false
51
+ */
52
+ responseValidation?: boolean;
53
+ }
54
+ declare const handleNextRequest: <T extends Routes>(request: NextRequest$1, routes: T, router: Router<T, NextHandlerContext>, options?: NextHandlerOptions, guards?: GuardMap<NextHandlerContext>, schemes?: Record<string, SecurityScheme>, requestContext?: RequestContextMap<NextHandlerContext>, pluginExports?: Record<string, unknown>, jobs?: MountedJobs) => Promise<NextResponse$1>;
55
+ type HttpHandlers = {
56
+ GET: HttpHandler;
57
+ HEAD: HttpHandler;
58
+ POST: HttpHandler;
59
+ PUT: HttpHandler;
60
+ PATCH: HttpHandler;
61
+ DELETE: HttpHandler;
62
+ OPTIONS: HttpHandler;
63
+ };
64
+ type HttpHandler = (request: NextRequest$1) => Promise<NextResponse$1>;
65
+ type NextApiWithRouter = ApiWithRouter & {
66
+ readonly [GUARDS_META]?: unknown;
67
+ readonly [SCHEMES_META]?: unknown;
68
+ readonly [REQUEST_CONTEXT_META]?: unknown;
69
+ readonly [JOBS_META]?: unknown;
70
+ };
71
+ type NextApi<R extends Routes = Routes> = ApiWithRouter<R> & {
72
+ readonly [GUARDS_META]?: unknown;
73
+ readonly [SCHEMES_META]?: unknown;
74
+ readonly [REQUEST_CONTEXT_META]?: unknown;
75
+ readonly [JOBS_META]?: unknown;
76
+ mount: (options?: NextHandlerOptions) => HttpHandlers;
77
+ };
78
+ /**
79
+ * Create endpoints for a Next.js App Router catch-all route.
80
+ *
81
+ * @example
82
+ * // app/api/[...kizuna]/route.ts
83
+ * export const { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS } = api.mount({
84
+ * basePath: '/api',
85
+ * });
86
+ */
87
+ declare function mountNext(api: NextApiWithRouter, options?: NextHandlerOptions): HttpHandlers;
88
+ /**
89
+ * Serve an API on Next. What it takes is what every route is served with. Next
90
+ * routes by file, so mounting returns the route handlers to re-export rather
91
+ * than registering them on an app.
92
+ *
93
+ * @example
94
+ * // kizuna.config.ts
95
+ * export default defineConfig({
96
+ * adapter: nextAdapter(),
97
+ * routes,
98
+ * });
99
+ *
100
+ * // src/app/api/[...kizuna]/route.ts
101
+ * export const { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS } = kizuna.api.mount({
102
+ * basePath: '/api',
103
+ * });
104
+ */
105
+ declare const nextAdapter: (defaults?: NextHandlerOptions) => Adapter<NextHandlerContext, [options?: NextHandlerOptions], HttpHandlers>;
106
+ //#endregion
107
+ export { MountedJobs, NextApi, NextApiWithRouter, NextHandlerContext, NextHandlerOptions, NextMiddlewareHandler, NextMiddlewareRoute, NextRequest, NextResponse, handleNextRequest, mountNext, nextAdapter };
package/dist/index.mjs ADDED
@@ -0,0 +1,204 @@
1
+ import { GUARDS_META, JOBS_META, REQUEST_CONTEXT_META, ROUTER_META, SCHEMES_META, createAdapter, headersToObject, jobRouter, jobRoutes, jobRunnerFrom, matchRoute, parseFetchBody, pluginExportsOf, pluginRouterOf, pluginRoutesOf, renderJsonResult } from "kizunajs/adapter";
2
+ import { NextRequest, NextResponse, NextResponse as NextResponse$1 } from "next/server.js";
3
+ //#region src/server.ts
4
+ const jsonResponse = (status, body, headers, raw = false) => new NextResponse$1(body === null || body === void 0 ? null : raw ? body : JSON.stringify(body), {
5
+ status,
6
+ headers
7
+ });
8
+ const handleNextRequest = async (request, routes, router, options, guards, schemes, requestContext, pluginExports, jobs) => {
9
+ const url = new URL(request.url);
10
+ const adapter = createAdapter({
11
+ buildHandlerContext: (adapterRequest) => ({ request: adapterRequest.request }),
12
+ respond: (result) => {
13
+ if (result.kind === "raw-response") return result.response;
14
+ const rendered = renderJsonResult(result, options?.formatError, request, request.method);
15
+ if (rendered.stream) return new NextResponse$1(rendered.stream({
16
+ signal: request.signal,
17
+ validate: options?.responseValidation
18
+ }), {
19
+ status: rendered.status,
20
+ headers: rendered.headers
21
+ });
22
+ return jsonResponse(rendered.status, rendered.body, rendered.headers, rendered.raw);
23
+ },
24
+ onError: async (error) => {
25
+ if (!options?.onError) {
26
+ console.error("[kizuna/next] handler error:", error);
27
+ return;
28
+ }
29
+ const override = await options.onError(error, request);
30
+ if (override) return {
31
+ kind: "raw-response",
32
+ response: override
33
+ };
34
+ }
35
+ });
36
+ if (jobs) {
37
+ const matchedJob = matchRoute(request.method, url.pathname, jobs.routes, options?.basePath);
38
+ if (matchedJob.kind === "matched") {
39
+ const jobRequest = {
40
+ request,
41
+ method: request.method,
42
+ resolution: {
43
+ kind: "pre-resolved",
44
+ routeKey: matchedJob.match.routeKey,
45
+ route: matchedJob.match.route,
46
+ params: matchedJob.match.params
47
+ },
48
+ query: Object.fromEntries(url.searchParams),
49
+ headers: headersToObject(request.headers),
50
+ readBody: (route) => parseFetchBody(request, route)
51
+ };
52
+ return adapter.handle({
53
+ routes: jobs.routes,
54
+ router: jobs.router,
55
+ request: jobRequest,
56
+ responseContext: {},
57
+ guards,
58
+ schemes,
59
+ requestContext,
60
+ pluginExports,
61
+ jobs: jobs.runner,
62
+ responseValidation: options?.responseValidation
63
+ });
64
+ }
65
+ }
66
+ const globalMiddleware = options?.requestMiddleware;
67
+ if (globalMiddleware && globalMiddleware.length > 0) {
68
+ const matched = matchRoute(request.method, url.pathname, routes, options?.basePath);
69
+ if (matched.kind === "matched") {
70
+ const middlewareRoute = {
71
+ path: matched.match.route.path,
72
+ method: matched.match.route.method
73
+ };
74
+ for (const handler of globalMiddleware) {
75
+ const result = await handler(request, middlewareRoute);
76
+ if (result instanceof Response) return new NextResponse$1(result.body, {
77
+ status: result.status,
78
+ statusText: result.statusText,
79
+ headers: result.headers
80
+ });
81
+ }
82
+ const adapterRequest = {
83
+ request,
84
+ method: request.method,
85
+ resolution: {
86
+ kind: "pre-resolved",
87
+ routeKey: matched.match.routeKey,
88
+ route: matched.match.route,
89
+ params: matched.match.params
90
+ },
91
+ query: Object.fromEntries(url.searchParams),
92
+ headers: headersToObject(request.headers),
93
+ readBody: (route) => parseFetchBody(request, route)
94
+ };
95
+ return adapter.handle({
96
+ routes,
97
+ router,
98
+ request: adapterRequest,
99
+ responseContext: {},
100
+ guards,
101
+ schemes,
102
+ requestContext,
103
+ jobs: jobs?.runner,
104
+ responseValidation: options?.responseValidation
105
+ });
106
+ }
107
+ }
108
+ const adapterRequest = {
109
+ request,
110
+ method: request.method,
111
+ resolution: {
112
+ kind: "core-match",
113
+ path: url.pathname
114
+ },
115
+ query: Object.fromEntries(url.searchParams),
116
+ headers: headersToObject(request.headers),
117
+ readBody: (route) => parseFetchBody(request, route)
118
+ };
119
+ return adapter.handle({
120
+ routes,
121
+ router,
122
+ request: adapterRequest,
123
+ responseContext: {},
124
+ guards,
125
+ schemes,
126
+ requestContext,
127
+ pluginExports,
128
+ jobs: jobs?.runner,
129
+ basePath: options?.basePath,
130
+ responseValidation: options?.responseValidation
131
+ });
132
+ };
133
+ /**
134
+ * Create endpoints for a Next.js App Router catch-all route.
135
+ *
136
+ * @example
137
+ * // app/api/[...kizuna]/route.ts
138
+ * export const { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS } = api.mount({
139
+ * basePath: '/api',
140
+ * });
141
+ */
142
+ function mountNext(api, options) {
143
+ const guards = api[GUARDS_META];
144
+ const schemes = api[SCHEMES_META];
145
+ const requestContext = api[REQUEST_CONTEXT_META];
146
+ const jobsMeta = api[JOBS_META];
147
+ const mountedJobs = jobsMeta ? {
148
+ routes: jobRoutes(jobsMeta),
149
+ router: jobRouter(jobsMeta),
150
+ runner: jobRunnerFrom(jobsMeta)
151
+ } : void 0;
152
+ const handlerOptions = {
153
+ basePath: options?.basePath,
154
+ onError: options?.onError,
155
+ requestMiddleware: options?.requestMiddleware,
156
+ responseValidation: options?.responseValidation
157
+ };
158
+ const pluginExports = pluginExportsOf(api);
159
+ const pluginRoutes = pluginRoutesOf(api);
160
+ const pluginRouter = pluginRouterOf(api);
161
+ const hasPluginRoutes = Object.keys(pluginRoutes).length > 0;
162
+ const handler = async (request) => {
163
+ if (hasPluginRoutes) {
164
+ const pathname = new URL(request.url).pathname;
165
+ if (!(matchRoute(request.method, pathname, api.routes, options?.basePath).kind === "matched") && matchRoute(request.method, pathname, pluginRoutes, options?.basePath).kind === "matched") return handleNextRequest(request, pluginRoutes, pluginRouter, handlerOptions, guards, schemes, requestContext, pluginExports, void 0);
166
+ }
167
+ return handleNextRequest(request, api.routes, api[ROUTER_META], handlerOptions, guards, schemes, requestContext, pluginExports, mountedJobs);
168
+ };
169
+ return {
170
+ GET: handler,
171
+ HEAD: handler,
172
+ POST: handler,
173
+ PUT: handler,
174
+ PATCH: handler,
175
+ DELETE: handler,
176
+ OPTIONS: handler
177
+ };
178
+ }
179
+ /**
180
+ * Serve an API on Next. What it takes is what every route is served with. Next
181
+ * routes by file, so mounting returns the route handlers to re-export rather
182
+ * than registering them on an app.
183
+ *
184
+ * @example
185
+ * // kizuna.config.ts
186
+ * export default defineConfig({
187
+ * adapter: nextAdapter(),
188
+ * routes,
189
+ * });
190
+ *
191
+ * // src/app/api/[...kizuna]/route.ts
192
+ * export const { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS } = kizuna.api.mount({
193
+ * basePath: '/api',
194
+ * });
195
+ */
196
+ const nextAdapter = (defaults) => ({
197
+ name: "next",
198
+ mount: (api, options) => mountNext(api, {
199
+ ...defaults,
200
+ ...options
201
+ })
202
+ });
203
+ //#endregion
204
+ export { NextRequest, NextResponse, handleNextRequest, mountNext, nextAdapter };
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@kizunajs/next",
3
+ "version": "2.0.0-beta.0",
4
+ "description": "Next.js App Router 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
+ "nextjs",
15
+ "next",
16
+ "app-router",
17
+ "adapter"
18
+ ],
19
+ "license": "MIT",
20
+ "homepage": "https://kizunajs.com/docs/adapters/next",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/kizunajs/kizuna.git",
24
+ "directory": "packages/next"
25
+ },
26
+ "bugs": "https://github.com/kizunajs/kizuna/issues",
27
+ "type": "module",
28
+ "sideEffects": false,
29
+ "engines": {
30
+ "node": ">=24"
31
+ },
32
+ "main": "./dist/index.cjs",
33
+ "module": "./dist/index.mjs",
34
+ "types": "./dist/index.d.mts",
35
+ "exports": {
36
+ ".": {
37
+ "import": {
38
+ "types": "./dist/index.d.mts",
39
+ "default": "./dist/index.mjs"
40
+ },
41
+ "require": {
42
+ "types": "./dist/index.d.cts",
43
+ "default": "./dist/index.cjs"
44
+ }
45
+ }
46
+ },
47
+ "kizuna": {
48
+ "entries": {
49
+ ".": "server"
50
+ }
51
+ },
52
+ "files": [
53
+ "dist"
54
+ ],
55
+ "publishConfig": {
56
+ "access": "public"
57
+ },
58
+ "peerDependencies": {
59
+ "next": ">=16.0.0",
60
+ "zod": "^4.0.0",
61
+ "kizunajs": "2.0.0-beta.0"
62
+ },
63
+ "devDependencies": {
64
+ "next": "^16.3.3",
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 zod",
72
+ "typecheck": "tsc --noEmit"
73
+ }
74
+ }