next-rest-framework
Advanced tools
Comparing version
@@ -1,5 +0,4 @@ | ||
export * from './docs-route-handler'; | ||
export * from './route-handler'; | ||
export * from './docs-route'; | ||
export * from './route'; | ||
export * from './route-operation'; | ||
export * from './rpc-route-handler'; | ||
export { TypedNextResponse } from './typed-next-response'; | ||
export * from './rpc-route'; |
@@ -17,8 +17,5 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.TypedNextResponse = void 0; | ||
__exportStar(require("./docs-route-handler"), exports); | ||
__exportStar(require("./route-handler"), exports); | ||
__exportStar(require("./docs-route"), exports); | ||
__exportStar(require("./route"), exports); | ||
__exportStar(require("./route-operation"), exports); | ||
__exportStar(require("./rpc-route-handler"), exports); | ||
var typed_next_response_1 = require("./typed-next-response"); | ||
Object.defineProperty(exports, "TypedNextResponse", { enumerable: true, get: function () { return typed_next_response_1.TypedNextResponse; } }); | ||
__exportStar(require("./rpc-route"), exports); |
@@ -1,4 +0,4 @@ | ||
import { type BaseStatus, type BaseQuery, type InputObject, type OutputObject, type BaseContentType, type Modify, type AnyCase, type OpenApiOperation } from '../types'; | ||
import { type NextRequest, type NextResponse } from 'next/server'; | ||
import { type z } from 'zod'; | ||
import { type BaseStatus, type BaseQuery, type OutputObject, type BaseContentType, type Modify, type AnyCase, type OpenApiOperation, type BaseParams } from '../types'; | ||
import { NextResponse, type NextRequest } from 'next/server'; | ||
import { type ZodSchema, type z } from 'zod'; | ||
import { type ValidMethod } from '../constants'; | ||
@@ -8,2 +8,12 @@ import { type I18NConfig } from 'next/dist/server/config-shared'; | ||
import { type NextURL } from 'next/dist/server/web/next-url'; | ||
type TypedNextRequest<Body, Query extends BaseQuery> = Modify<NextRequest, { | ||
json: () => Promise<Body>; | ||
method: ValidMethod; | ||
nextUrl: Modify<NextURL, { | ||
searchParams: Modify<URLSearchParams, { | ||
get: (key: keyof Query) => string | null; | ||
getAll: (key: keyof Query) => string[]; | ||
}>; | ||
}>; | ||
}>; | ||
type TypedHeaders<ContentType extends BaseContentType> = Modify<Record<string, string>, { | ||
@@ -30,3 +40,3 @@ [K in AnyCase<'Content-Type'>]?: ContentType; | ||
declare const INTERNALS: unique symbol; | ||
export declare class TypedNextResponse<Body, Status extends BaseStatus, ContentType extends BaseContentType> extends Response { | ||
export declare class TypedNextResponseType<Body, Status extends BaseStatus, ContentType extends BaseContentType> extends Response { | ||
[INTERNALS]: { | ||
@@ -41,57 +51,58 @@ cookies: ResponseCookies; | ||
get cookies(): ResponseCookies; | ||
static json<Body, Status extends BaseStatus, ContentType extends BaseContentType>(body: Body, init?: TypedResponseInit<Status, ContentType>): TypedNextResponse<Body, Status, ContentType>; | ||
static redirect<Status extends BaseStatus, ContentType extends BaseContentType>(url: string | NextURL | URL, init?: number | TypedResponseInit<Status, ContentType>): TypedNextResponse<unknown, Status, ContentType>; | ||
static rewrite<Status extends BaseStatus, ContentType extends BaseContentType>(destination: string | NextURL | URL, init?: TypedMiddlewareResponseInit<Status>): TypedNextResponse<unknown, Status, ContentType>; | ||
static next<Status extends BaseStatus, ContentType extends BaseContentType>(init?: TypedMiddlewareResponseInit<Status>): TypedNextResponse<unknown, Status, ContentType>; | ||
static json<Body, Status extends BaseStatus, ContentType extends BaseContentType>(body: Body, init?: TypedResponseInit<Status, ContentType>): TypedNextResponseType<Body, Status, ContentType>; | ||
static redirect<Status extends BaseStatus, ContentType extends BaseContentType>(url: string | NextURL | URL, init?: number | TypedResponseInit<Status, ContentType>): TypedNextResponseType<unknown, Status, ContentType>; | ||
static rewrite<Status extends BaseStatus, ContentType extends BaseContentType>(destination: string | NextURL | URL, init?: TypedMiddlewareResponseInit<Status>): TypedNextResponseType<unknown, Status, ContentType>; | ||
static next<Status extends BaseStatus, ContentType extends BaseContentType>(init?: TypedMiddlewareResponseInit<Status>): TypedNextResponseType<unknown, Status, ContentType>; | ||
} | ||
type TypedNextRequest<Body, Query extends BaseQuery> = Modify<NextRequest, { | ||
json: () => Promise<Body>; | ||
method: ValidMethod; | ||
nextUrl: Modify<NextURL, { | ||
searchParams: Modify<URLSearchParams, { | ||
get: (key: keyof Query) => string | null; | ||
getAll: (key: keyof Query) => string[]; | ||
}>; | ||
}>; | ||
}>; | ||
type RouteHandler<Body = unknown, Query extends BaseQuery = BaseQuery, ResponseBody = unknown, Status extends BaseStatus = BaseStatus, ContentType extends BaseContentType = BaseContentType, Output extends ReadonlyArray<OutputObject<ResponseBody, Status, ContentType>> = ReadonlyArray<OutputObject<ResponseBody, Status, ContentType>>, TypedResponse = TypedNextResponse<z.infer<Output[number]['schema']>, Output[number]['status'], Output[number]['contentType']> | NextResponse<z.infer<Output[number]['schema']>> | void> = (req: TypedNextRequest<Body, Query>, context: { | ||
params: Record<string, string>; | ||
export declare const TypedNextResponse: typeof TypedNextResponseType; | ||
type TypedRouteHandler<Body = unknown, Query extends BaseQuery = BaseQuery, Params extends BaseParams = BaseParams, ResponseBody = unknown, Status extends BaseStatus = BaseStatus, ContentType extends BaseContentType = BaseContentType, Outputs extends ReadonlyArray<OutputObject<ResponseBody, Status, ContentType>> = ReadonlyArray<OutputObject<ResponseBody, Status, ContentType>>, TypedResponse = TypedNextResponseType<z.infer<Outputs[number]['schema']>, Outputs[number]['status'], Outputs[number]['contentType']> | NextResponse<z.infer<Outputs[number]['schema']>> | void> = (req: TypedNextRequest<Body, Query>, context: { | ||
params: Params; | ||
}) => Promise<TypedResponse> | TypedResponse; | ||
type RouteOutputs<Middleware extends boolean = false, Body = unknown, Query extends BaseQuery = BaseQuery> = <ResponseBody, Status extends BaseStatus, ContentType extends BaseContentType, Output extends ReadonlyArray<OutputObject<ResponseBody, Status, ContentType>>>(params?: Output) => { | ||
handler: (callback?: RouteHandler<Body, Query, ResponseBody, Status, ContentType, Output>) => RouteOperationDefinition; | ||
} & (Middleware extends true ? { | ||
middleware: (callback?: RouteHandler<unknown, BaseQuery, ResponseBody, Status, ContentType, Output>) => { | ||
handler: (callback?: RouteHandler<Body, Query, ResponseBody, Status, ContentType, Output>) => RouteOperationDefinition; | ||
}; | ||
} : Record<string, unknown>); | ||
type RouteInput<Middleware extends boolean = false> = <Body, Query extends BaseQuery>(params?: InputObject<Body, Query>) => { | ||
outputs: RouteOutputs<Middleware, Body, Query>; | ||
handler: (callback?: RouteHandler<Body, Query>) => RouteOperationDefinition; | ||
} & (Middleware extends true ? { | ||
middleware: (callback?: RouteHandler) => { | ||
outputs: RouteOutputs<false, Body, Query>; | ||
handler: (callback?: RouteHandler<Body, Query>) => RouteOperationDefinition; | ||
}; | ||
} : Record<string, unknown>); | ||
type NextRouteHandler = (req: NextRequest, context: { | ||
params: BaseQuery; | ||
params: BaseParams; | ||
}) => Promise<NextResponse> | NextResponse | Promise<void> | void; | ||
export interface RouteOperationDefinition { | ||
_meta: { | ||
openApiOperation?: OpenApiOperation; | ||
input?: InputObject; | ||
outputs?: readonly OutputObject[]; | ||
middleware?: NextRouteHandler; | ||
handler?: NextRouteHandler; | ||
}; | ||
interface InputObject<Body = unknown, Query = BaseQuery, Params = BaseParams> { | ||
contentType?: BaseContentType; | ||
body?: ZodSchema<Body>; | ||
query?: ZodSchema<Query>; | ||
params?: ZodSchema<Params>; | ||
} | ||
type RouteOperation = (openApiOperation?: OpenApiOperation) => { | ||
input: RouteInput<true>; | ||
outputs: RouteOutputs<true>; | ||
middleware: (middleware?: RouteHandler) => { | ||
handler: (callback?: RouteHandler) => RouteOperationDefinition; | ||
export interface RouteOperationDefinition<Method extends keyof typeof ValidMethod = keyof typeof ValidMethod> { | ||
openApiOperation?: OpenApiOperation; | ||
method: Method; | ||
input?: InputObject; | ||
outputs?: readonly OutputObject[]; | ||
middleware?: NextRouteHandler; | ||
handler?: NextRouteHandler; | ||
} | ||
export declare const routeOperation: <Method extends "GET" | "PUT" | "POST" | "DELETE" | "OPTIONS" | "HEAD" | "PATCH">({ openApiOperation, method }: { | ||
openApiOperation?: OpenApiOperation | undefined; | ||
method: Method; | ||
}) => { | ||
input: <Body_1, Query extends BaseQuery, Params extends BaseParams>(input: InputObject<Body_1, Query, Params>) => { | ||
outputs: <ResponseBody, Status extends number, ContentType extends import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes, Outputs extends readonly OutputObject<ResponseBody, Status, ContentType>[]>(outputs: Outputs) => { | ||
middleware: (middleware: TypedRouteHandler<unknown, BaseQuery, BaseParams, ResponseBody, Status, ContentType, Outputs, void | TypedNextResponseType<z.TypeOf<Outputs[number]["schema"]>, Outputs[number]["status"], Outputs[number]["contentType"]> | NextResponse<z.TypeOf<Outputs[number]["schema"]>>>) => { | ||
handler: (handler: TypedRouteHandler<Body_1, Query, Params, ResponseBody, Status, ContentType, Outputs, void | TypedNextResponseType<z.TypeOf<Outputs[number]["schema"]>, Outputs[number]["status"], Outputs[number]["contentType"]> | NextResponse<z.TypeOf<Outputs[number]["schema"]>>>) => RouteOperationDefinition<Method>; | ||
}; | ||
handler: (handler: TypedRouteHandler<Body_1, Query, Params, ResponseBody, Status, ContentType, Outputs, void | TypedNextResponseType<z.TypeOf<Outputs[number]["schema"]>, Outputs[number]["status"], Outputs[number]["contentType"]> | NextResponse<z.TypeOf<Outputs[number]["schema"]>>>) => RouteOperationDefinition<Method>; | ||
}; | ||
middleware: (middleware: TypedRouteHandler) => { | ||
outputs: <ResponseBody_1, Status_1 extends number, ContentType_1 extends import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes, Outputs_1 extends readonly OutputObject<ResponseBody_1, Status_1, ContentType_1>[]>(outputs: Outputs_1) => { | ||
handler: (handler: TypedRouteHandler<Body_1, Query, Params, ResponseBody_1, Status_1, ContentType_1, Outputs_1, void | TypedNextResponseType<z.TypeOf<Outputs_1[number]["schema"]>, Outputs_1[number]["status"], Outputs_1[number]["contentType"]> | NextResponse<z.TypeOf<Outputs_1[number]["schema"]>>>) => RouteOperationDefinition<Method>; | ||
}; | ||
handler: (handler: TypedRouteHandler<Body_1, Query, BaseParams, unknown, number, import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes, readonly OutputObject<unknown, number, import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes>[], void | NextResponse<unknown> | TypedNextResponseType<unknown, number, import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes>>) => RouteOperationDefinition<Method>; | ||
}; | ||
handler: (handler: TypedRouteHandler<Body_1, Query, BaseParams, unknown, number, import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes, readonly OutputObject<unknown, number, import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes>[], void | NextResponse<unknown> | TypedNextResponseType<unknown, number, import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes>>) => RouteOperationDefinition<Method>; | ||
}; | ||
handler: (callback?: RouteHandler) => RouteOperationDefinition; | ||
outputs: <ResponseBody_2, Status_2 extends number, ContentType_2 extends import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes, Outputs_2 extends readonly OutputObject<ResponseBody_2, Status_2, ContentType_2>[]>(outputs: Outputs_2) => { | ||
middleware: (middleware: TypedRouteHandler<unknown, BaseQuery, BaseParams, ResponseBody_2, Status_2, ContentType_2, Outputs_2, void | TypedNextResponseType<z.TypeOf<Outputs_2[number]["schema"]>, Outputs_2[number]["status"], Outputs_2[number]["contentType"]> | NextResponse<z.TypeOf<Outputs_2[number]["schema"]>>>) => { | ||
handler: (handler: TypedRouteHandler<unknown, BaseQuery, BaseParams, ResponseBody_2, Status_2, ContentType_2, Outputs_2, void | TypedNextResponseType<z.TypeOf<Outputs_2[number]["schema"]>, Outputs_2[number]["status"], Outputs_2[number]["contentType"]> | NextResponse<z.TypeOf<Outputs_2[number]["schema"]>>>) => RouteOperationDefinition<Method>; | ||
}; | ||
handler: (handler: TypedRouteHandler<unknown, BaseQuery, BaseParams, ResponseBody_2, Status_2, ContentType_2, Outputs_2, void | TypedNextResponseType<z.TypeOf<Outputs_2[number]["schema"]>, Outputs_2[number]["status"], Outputs_2[number]["contentType"]> | NextResponse<z.TypeOf<Outputs_2[number]["schema"]>>>) => RouteOperationDefinition<Method>; | ||
}; | ||
middleware: (middleware: TypedRouteHandler) => { | ||
handler: (handler: TypedRouteHandler) => RouteOperationDefinition<Method>; | ||
}; | ||
handler: (handler: TypedRouteHandler) => RouteOperationDefinition<Method>; | ||
}; | ||
export declare const routeOperation: RouteOperation; | ||
export {}; |
"use strict"; | ||
/* eslint-disable @typescript-eslint/no-invalid-void-type */ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.routeOperation = void 0; | ||
const routeOperation = (openApiOperation) => { | ||
const createConfig = (input, outputs, middleware, handler) => ({ | ||
_meta: { | ||
exports.routeOperation = exports.TypedNextResponse = void 0; | ||
const server_1 = require("next/server"); | ||
// @ts-expect-error - Keep the original NextResponse functionality with custom types. | ||
exports.TypedNextResponse = server_1.NextResponse; | ||
const routeOperation = ({ openApiOperation, method }) => { | ||
const createOperation = ({ input, outputs, middleware: _middleware, handler: _handler }) => { | ||
const middleware = _middleware; | ||
const handler = _handler; | ||
return { | ||
openApiOperation, | ||
method, | ||
input, | ||
outputs, | ||
middleware: middleware, | ||
handler: handler | ||
} | ||
}); | ||
middleware, | ||
handler | ||
}; | ||
}; | ||
return { | ||
@@ -19,26 +25,26 @@ input: (input) => ({ | ||
middleware: (middleware) => ({ | ||
handler: (handler) => createConfig(input, outputs, middleware, handler) | ||
handler: (handler) => createOperation({ input, outputs, middleware, handler }) | ||
}), | ||
handler: (handler) => createConfig(input, outputs, undefined, handler) | ||
handler: (handler) => createOperation({ input, outputs, handler }) | ||
}), | ||
middleware: (middleware) => ({ | ||
outputs: (outputs) => ({ | ||
handler: (handler) => createConfig(input, outputs, middleware, handler) | ||
handler: (handler) => createOperation({ input, outputs, middleware, handler }) | ||
}), | ||
handler: (handler) => createConfig(input, undefined, middleware, handler) | ||
handler: (handler) => createOperation({ input, middleware, handler }) | ||
}), | ||
handler: (handler) => createConfig(input, undefined, undefined, handler) | ||
handler: (handler) => createOperation({ input, handler }) | ||
}), | ||
outputs: (outputs) => ({ | ||
middleware: (middleware) => ({ | ||
handler: (handler) => createConfig(undefined, outputs, middleware, handler) | ||
handler: (handler) => createOperation({ outputs, middleware, handler }) | ||
}), | ||
handler: (handler) => createConfig(undefined, outputs, undefined, handler) | ||
handler: (handler) => createOperation({ outputs, handler }) | ||
}), | ||
middleware: (middleware) => ({ | ||
handler: (handler) => createConfig(undefined, undefined, middleware, handler) | ||
handler: (handler) => createOperation({ middleware, handler }) | ||
}), | ||
handler: (handler) => createConfig(undefined, undefined, undefined, handler) | ||
handler: (handler) => createOperation({ handler }) | ||
}; | ||
}; | ||
exports.routeOperation = routeOperation; |
@@ -63,2 +63,3 @@ #!/usr/bin/env node | ||
.filter((file) => isAllowedRoute((0, shared_1.getRouteName)(file))); | ||
const getCleanedRpcRoutes = (files) => files.filter((file) => file.endsWith('rpc/[operationId]/route.ts')); | ||
/* | ||
@@ -74,2 +75,3 @@ * Clean and filter the API routes to paths: | ||
.filter((file) => isAllowedRoute((0, shared_1.getApiRouteName)(file))); | ||
const getCleanedRpcApiRoutes = (files) => files.filter((file) => file.endsWith('rpc/[operationId].ts')); | ||
const isPathItem = (obj) => typeof obj === 'object'; | ||
@@ -81,4 +83,6 @@ let paths = {}; | ||
if ((0, fs_1.existsSync)(path)) { | ||
const routes = getCleanedRoutes((0, shared_1.getNestedFiles)(path, '')); | ||
await Promise.all(routes.map(async (route) => { | ||
const files = (0, shared_1.getNestedFiles)(path, ''); | ||
const routes = getCleanedRoutes(files); | ||
const rpcRoutes = getCleanedRpcRoutes(files); | ||
await Promise.all([...routes, ...rpcRoutes].map(async (route) => { | ||
const res = await Promise.resolve(`${(0, path_1.join)(process.cwd(), distDir, 'server/app', route)}`).then(s => __importStar(require(s))); | ||
@@ -88,3 +92,3 @@ Object.entries(res.routeModule.userland) | ||
.forEach(([_key, handler]) => { | ||
const data = handler.getPaths((0, shared_1.getRouteName)(route)); | ||
const data = handler._getPaths((0, shared_1.getRouteName)(route)); | ||
if (isPathItem(data)) { | ||
@@ -104,6 +108,8 @@ paths = { ...paths, ...data }; | ||
if ((0, fs_1.existsSync)(path)) { | ||
const apiRoutes = getCleanedApiRoutes((0, shared_1.getNestedFiles)(path, '')); | ||
await Promise.all(apiRoutes.map(async (apiRoute) => { | ||
const files = (0, shared_1.getNestedFiles)(path, ''); | ||
const apiRoutes = getCleanedApiRoutes(files); | ||
const rpcApiRoutes = getCleanedRpcApiRoutes(files); | ||
await Promise.all([...apiRoutes, ...rpcApiRoutes].map(async (apiRoute) => { | ||
const res = await Promise.resolve(`${(0, path_1.join)(process.cwd(), distDir, 'server/pages/api', apiRoute)}`).then(s => __importStar(require(s))); | ||
const data = res.default.getPaths((0, shared_1.getApiRouteName)(apiRoute)); | ||
const data = res.default._getPaths((0, shared_1.getApiRouteName)(apiRoute)); | ||
if (isPathItem(data)) { | ||
@@ -121,3 +127,3 @@ paths = { ...paths, ...data }; | ||
} | ||
return { paths: (0, shared_1.sortObjectByKeys)(paths) }; | ||
return { paths }; | ||
}; | ||
@@ -141,3 +147,3 @@ const findConfig = async ({ distDir, configPath }) => { | ||
.forEach(([_key, handler]) => { | ||
const _config = handler.nextRestFrameworkConfig; | ||
const _config = handler._nextRestFrameworkConfig; | ||
if (_config) { | ||
@@ -169,3 +175,3 @@ config = _config; | ||
const res = await Promise.resolve(`${(0, path_1.join)(process.cwd(), distDir, 'server/pages/api', file)}`).then(s => __importStar(require(s))); | ||
const _config = res.default.nextRestFrameworkConfig; | ||
const _config = res.default._nextRestFrameworkConfig; | ||
if (_config) { | ||
@@ -197,2 +203,11 @@ config = _config; | ||
}; | ||
const sortObjectByKeys = (obj) => { | ||
const unordered = { ...obj }; | ||
return Object.keys(unordered) | ||
.sort() | ||
.reduce((_obj, key) => { | ||
_obj[key] = unordered[key]; | ||
return _obj; | ||
}, {}); | ||
}; | ||
// Sync the `openapi.json` file from generated paths from the build output. | ||
@@ -210,7 +225,14 @@ const validateOpenApiSpecFromBuild = async ({ distDir, configPath }) => { | ||
console.log(chalk_1.default.yellowBright('Next REST Framework config found!')); | ||
const paths = await generatePathsFromBuild({ config, distDir }); | ||
const { paths = {}, schemas = {} } = await generatePathsFromBuild({ | ||
config, | ||
distDir | ||
}); | ||
const path = (0, path_1.join)(process.cwd(), 'public', config.openApiJsonPath); | ||
const components = Object.keys(schemas).length | ||
? { components: { schemas: sortObjectByKeys(schemas) } } | ||
: {}; | ||
const newSpec = (0, lodash_1.merge)({ | ||
openapi: constants_1.OPEN_API_VERSION | ||
}, config.openApiObject, { paths }); | ||
openapi: constants_1.OPEN_API_VERSION, | ||
paths: sortObjectByKeys(paths) | ||
}, components, config.openApiObject); | ||
try { | ||
@@ -217,0 +239,0 @@ const data = (0, fs_1.readFileSync)(path); |
@@ -1,1 +0,1 @@ | ||
export * from './rpc-client'; | ||
export { rpcClient } from './rpc-client'; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
__exportStar(require("./rpc-client"), exports); | ||
exports.rpcClient = void 0; | ||
var rpc_client_1 = require("./rpc-client"); | ||
Object.defineProperty(exports, "rpcClient", { enumerable: true, get: function () { return rpc_client_1.rpcClient; } }); |
@@ -1,7 +0,12 @@ | ||
import { type OperationDefinition } from '../shared'; | ||
export type Client<T extends Record<string, OperationDefinition<any, any>>> = { | ||
[key in keyof T]: T[key]; | ||
import { type RpcOperationDefinition } from '../shared'; | ||
type RpcRequestInit = Omit<RequestInit, 'method' | 'body'>; | ||
export type RpcClient<T extends Record<string, RpcOperationDefinition<any, any, any>>> = { | ||
[key in keyof T]: T[key] & { | ||
_meta: never; | ||
}; | ||
}; | ||
export declare const rpcClient: <T extends Record<string, OperationDefinition<any, any>>>({ url }: { | ||
export declare const rpcClient: <T extends Record<string, RpcOperationDefinition<any, any, any>>>({ url: _url, init }: { | ||
url: string; | ||
}) => Client<T>; | ||
init?: RpcRequestInit | undefined; | ||
}) => RpcClient<T>; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.rpcClient = void 0; | ||
const fetcher = async (body, options) => { | ||
const fetcher = async ({ url, body, init }) => { | ||
const opts = { | ||
...init, | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
'X-RPC-Operation': options.operationId | ||
...init?.headers, | ||
'Content-Type': 'application/json' | ||
} | ||
}; | ||
const res = await fetch(options.url, body ? { ...opts, body: JSON.stringify(body) } : opts); | ||
if (body) { | ||
opts.body = JSON.stringify(body); | ||
} | ||
const res = await fetch(url, opts); | ||
if (!res.ok) { | ||
const error = await res.json(); | ||
throw new Error(error.message); | ||
throw new Error(error); | ||
} | ||
return await res.json(); | ||
}; | ||
const rpcClient = ({ url }) => { | ||
const rpcClient = ({ url: _url, init }) => { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
@@ -25,3 +29,5 @@ return new Proxy({}, { | ||
return async (body) => { | ||
return await fetcher(body, { url, operationId: prop }); | ||
const baseUrl = _url.endsWith('/') ? _url : `${_url}/`; | ||
const url = `${baseUrl}${prop}`; | ||
return await fetcher({ url, body, init }); | ||
}; | ||
@@ -28,0 +34,0 @@ } |
@@ -1,3 +0,4 @@ | ||
export { docsApiRouteHandler, apiRouteHandler, apiRouteOperation, rpcApiRouteHandler } from './pages-router'; | ||
export { docsRouteHandler, routeHandler, routeOperation, rpcRouteHandler, TypedNextResponse } from './app-router'; | ||
export { docsApiRoute, apiRoute, apiRouteOperation, rpcApiRoute } from './pages-router'; | ||
export { docsRoute, route, routeOperation, rpcRoute, TypedNextResponse } from './app-router'; | ||
export { rpcOperation } from './shared'; | ||
export * from './deprecated'; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.rpcOperation = exports.TypedNextResponse = exports.rpcRouteHandler = exports.routeOperation = exports.routeHandler = exports.docsRouteHandler = exports.rpcApiRouteHandler = exports.apiRouteOperation = exports.apiRouteHandler = exports.docsApiRouteHandler = void 0; | ||
exports.rpcOperation = exports.TypedNextResponse = exports.rpcRoute = exports.routeOperation = exports.route = exports.docsRoute = exports.rpcApiRoute = exports.apiRouteOperation = exports.apiRoute = exports.docsApiRoute = void 0; | ||
var pages_router_1 = require("./pages-router"); | ||
Object.defineProperty(exports, "docsApiRouteHandler", { enumerable: true, get: function () { return pages_router_1.docsApiRouteHandler; } }); | ||
Object.defineProperty(exports, "apiRouteHandler", { enumerable: true, get: function () { return pages_router_1.apiRouteHandler; } }); | ||
Object.defineProperty(exports, "docsApiRoute", { enumerable: true, get: function () { return pages_router_1.docsApiRoute; } }); | ||
Object.defineProperty(exports, "apiRoute", { enumerable: true, get: function () { return pages_router_1.apiRoute; } }); | ||
Object.defineProperty(exports, "apiRouteOperation", { enumerable: true, get: function () { return pages_router_1.apiRouteOperation; } }); | ||
Object.defineProperty(exports, "rpcApiRouteHandler", { enumerable: true, get: function () { return pages_router_1.rpcApiRouteHandler; } }); | ||
Object.defineProperty(exports, "rpcApiRoute", { enumerable: true, get: function () { return pages_router_1.rpcApiRoute; } }); | ||
var app_router_1 = require("./app-router"); | ||
Object.defineProperty(exports, "docsRouteHandler", { enumerable: true, get: function () { return app_router_1.docsRouteHandler; } }); | ||
Object.defineProperty(exports, "routeHandler", { enumerable: true, get: function () { return app_router_1.routeHandler; } }); | ||
Object.defineProperty(exports, "docsRoute", { enumerable: true, get: function () { return app_router_1.docsRoute; } }); | ||
Object.defineProperty(exports, "route", { enumerable: true, get: function () { return app_router_1.route; } }); | ||
Object.defineProperty(exports, "routeOperation", { enumerable: true, get: function () { return app_router_1.routeOperation; } }); | ||
Object.defineProperty(exports, "rpcRouteHandler", { enumerable: true, get: function () { return app_router_1.rpcRouteHandler; } }); | ||
Object.defineProperty(exports, "rpcRoute", { enumerable: true, get: function () { return app_router_1.rpcRoute; } }); | ||
Object.defineProperty(exports, "TypedNextResponse", { enumerable: true, get: function () { return app_router_1.TypedNextResponse; } }); | ||
var shared_1 = require("./shared"); | ||
Object.defineProperty(exports, "rpcOperation", { enumerable: true, get: function () { return shared_1.rpcOperation; } }); | ||
__exportStar(require("./deprecated"), exports); |
import { type ValidMethod } from '../constants'; | ||
import { type InputObject, type OutputObject, type Modify, type AnyCase, type BaseQuery, type BaseStatus, type BaseContentType, type OpenApiOperation } from '../types'; | ||
import { type OutputObject, type Modify, type AnyCase, type BaseQuery, type BaseStatus, type BaseContentType, type OpenApiOperation } from '../types'; | ||
import { type NextApiRequest, type NextApiHandler, type NextApiResponse } from 'next/types'; | ||
import { type z } from 'zod'; | ||
import { type ZodSchema, type z } from 'zod'; | ||
type TypedNextApiRequest<Body, Query> = Modify<NextApiRequest, { | ||
@@ -25,37 +25,46 @@ body: Body; | ||
}>; | ||
type ApiRouteHandler<Body = unknown, Query extends BaseQuery = BaseQuery, ResponseBody = unknown, Status extends BaseStatus = BaseStatus, ContentType extends BaseContentType = BaseContentType, Outputs extends ReadonlyArray<OutputObject<ResponseBody, Status, ContentType>> = ReadonlyArray<OutputObject<ResponseBody, Status, ContentType>>> = (req: TypedNextApiRequest<Body, Query>, res: TypedNextApiResponse<z.infer<Outputs[number]['schema']>, Outputs[number]['status'], Outputs[number]['contentType']>) => Promise<void> | void; | ||
type ApiRouteOutputs<Middleware extends boolean = false, Body = unknown, Query extends BaseQuery = BaseQuery> = <ResponseBody, Status extends BaseStatus, ContentType extends BaseContentType, Outputs extends ReadonlyArray<OutputObject<ResponseBody, Status, ContentType>>>(params?: Outputs) => { | ||
handler: (callback?: ApiRouteHandler<Body, Query, ResponseBody, Status, ContentType, Outputs>) => ApiRouteOperationDefinition; | ||
} & (Middleware extends true ? { | ||
middleware: (callback?: ApiRouteHandler<unknown, BaseQuery, ResponseBody, Status, ContentType, Outputs>) => { | ||
handler: (callback?: ApiRouteHandler<Body, Query, ResponseBody, Status, ContentType, Outputs>) => ApiRouteOperationDefinition; | ||
type TypedApiRouteHandler<Body = unknown, Query extends BaseQuery = BaseQuery, ResponseBody = unknown, Status extends BaseStatus = BaseStatus, ContentType extends BaseContentType = BaseContentType, Outputs extends ReadonlyArray<OutputObject<ResponseBody, Status, ContentType>> = ReadonlyArray<OutputObject<ResponseBody, Status, ContentType>>> = (req: TypedNextApiRequest<Body, Query>, res: TypedNextApiResponse<z.infer<Outputs[number]['schema']>, Outputs[number]['status'], Outputs[number]['contentType']>) => Promise<void> | void; | ||
interface InputObject<Body = unknown, Query = BaseQuery> { | ||
contentType?: BaseContentType; | ||
body?: ZodSchema<Body>; | ||
query?: ZodSchema<Query>; | ||
} | ||
export interface ApiRouteOperationDefinition { | ||
openApiOperation?: OpenApiOperation; | ||
method: keyof typeof ValidMethod; | ||
input?: InputObject; | ||
outputs?: readonly OutputObject[]; | ||
middleware?: NextApiHandler; | ||
handler?: NextApiHandler; | ||
} | ||
export declare const apiRouteOperation: <Method extends "GET" | "PUT" | "POST" | "DELETE" | "OPTIONS" | "HEAD" | "PATCH">({ openApiOperation, method }: { | ||
openApiOperation?: OpenApiOperation | undefined; | ||
method: Method; | ||
}) => { | ||
input: <Body_1, Query extends BaseQuery>(input: InputObject<Body_1, Query>) => { | ||
outputs: <ResponseBody, Status extends number, ContentType extends import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes, Outputs extends readonly OutputObject<ResponseBody, Status, ContentType>[]>(outputs: Outputs) => { | ||
middleware: (middleware: TypedApiRouteHandler<unknown, BaseQuery, ResponseBody, Status, ContentType, Outputs>) => { | ||
handler: (handler: TypedApiRouteHandler<Body_1, Query, ResponseBody, Status, ContentType, Outputs>) => ApiRouteOperationDefinition; | ||
}; | ||
handler: (handler: TypedApiRouteHandler<Body_1, Query, ResponseBody, Status, ContentType, Outputs>) => ApiRouteOperationDefinition; | ||
}; | ||
middleware: (middleware: TypedApiRouteHandler) => { | ||
outputs: <ResponseBody_1, Status_1 extends number, ContentType_1 extends import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes, Outputs_1 extends readonly OutputObject<ResponseBody_1, Status_1, ContentType_1>[]>(outputs: Outputs_1) => { | ||
handler: (handler: TypedApiRouteHandler<Body_1, Query, ResponseBody_1, Status_1, ContentType_1, Outputs_1>) => ApiRouteOperationDefinition; | ||
}; | ||
handler: (handler: TypedApiRouteHandler<Body_1, Query, unknown, number, import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes, readonly OutputObject<unknown, number, import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes>[]>) => ApiRouteOperationDefinition; | ||
}; | ||
handler: (handler: TypedApiRouteHandler<Body_1, Query, unknown, number, import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes, readonly OutputObject<unknown, number, import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes>[]>) => ApiRouteOperationDefinition; | ||
}; | ||
} : Record<string, unknown>); | ||
type ApiRouteInput<Middleware extends boolean = false> = <Body, Query extends BaseQuery>(params?: InputObject<Body, Query>) => { | ||
outputs: ApiRouteOutputs<Middleware, Body, Query>; | ||
handler: (callback?: ApiRouteHandler<Body, Query>) => ApiRouteOperationDefinition; | ||
} & (Middleware extends true ? { | ||
middleware: (callback?: ApiRouteHandler) => { | ||
outputs: ApiRouteOutputs<false, Body, Query>; | ||
handler: (callback?: ApiRouteHandler<Body, Query>) => ApiRouteOperationDefinition; | ||
outputs: <ResponseBody_2, Status_2 extends number, ContentType_2 extends import("../types").AnyContentTypeWithAutocompleteForMostCommonOnes, Outputs_2 extends readonly OutputObject<ResponseBody_2, Status_2, ContentType_2>[]>(outputs: Outputs_2) => { | ||
middleware: (middleware: TypedApiRouteHandler<unknown, BaseQuery, ResponseBody_2, Status_2, ContentType_2, Outputs_2>) => { | ||
handler: (handler: TypedApiRouteHandler<unknown, BaseQuery, ResponseBody_2, Status_2, ContentType_2, Outputs_2>) => ApiRouteOperationDefinition; | ||
}; | ||
handler: (handler: TypedApiRouteHandler<unknown, BaseQuery, ResponseBody_2, Status_2, ContentType_2, Outputs_2>) => ApiRouteOperationDefinition; | ||
}; | ||
} : Record<string, unknown>); | ||
export interface ApiRouteOperationDefinition { | ||
_meta: { | ||
openApiOperation?: OpenApiOperation; | ||
input?: InputObject; | ||
outputs?: readonly OutputObject[]; | ||
middleware?: NextApiHandler; | ||
handler?: NextApiHandler; | ||
middleware: (middleware: TypedApiRouteHandler) => { | ||
handler: (handler: TypedApiRouteHandler) => ApiRouteOperationDefinition; | ||
}; | ||
} | ||
type ApiRouteOperation = (openApiOperation?: OpenApiOperation) => { | ||
input: ApiRouteInput<true>; | ||
outputs: ApiRouteOutputs<true>; | ||
middleware: (middleware?: ApiRouteHandler) => { | ||
handler: (callback?: ApiRouteHandler) => ApiRouteOperationDefinition; | ||
}; | ||
handler: (callback?: ApiRouteHandler) => ApiRouteOperationDefinition; | ||
handler: (handler: TypedApiRouteHandler) => ApiRouteOperationDefinition; | ||
}; | ||
export declare const apiRouteOperation: ApiRouteOperation; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.apiRouteOperation = void 0; | ||
const apiRouteOperation = (openApiOperation) => { | ||
const createConfig = (input, outputs, middleware, handler) => ({ | ||
_meta: { | ||
const apiRouteOperation = ({ openApiOperation, method }) => { | ||
const createOperation = ({ input, outputs, middleware: _middleware, handler: _handler }) => { | ||
const middleware = _middleware; | ||
const handler = _handler; | ||
return { | ||
openApiOperation, | ||
method, | ||
input, | ||
outputs, | ||
middleware: middleware, | ||
handler: handler | ||
} | ||
}); | ||
middleware, | ||
handler | ||
}; | ||
}; | ||
return { | ||
@@ -18,26 +21,26 @@ input: (input) => ({ | ||
middleware: (middleware) => ({ | ||
handler: (handler) => createConfig(input, outputs, middleware, handler) | ||
handler: (handler) => createOperation({ input, outputs, middleware, handler }) | ||
}), | ||
handler: (handler) => createConfig(input, outputs, undefined, handler) | ||
handler: (handler) => createOperation({ input, outputs, handler }) | ||
}), | ||
middleware: (middleware) => ({ | ||
outputs: (outputs) => ({ | ||
handler: (handler) => createConfig(input, outputs, middleware, handler) | ||
handler: (handler) => createOperation({ input, outputs, middleware, handler }) | ||
}), | ||
handler: (handler) => createConfig(input, undefined, middleware, handler) | ||
handler: (handler) => createOperation({ input, middleware, handler }) | ||
}), | ||
handler: (handler) => createConfig(input, undefined, undefined, handler) | ||
handler: (handler) => createOperation({ input, handler }) | ||
}), | ||
outputs: (outputs) => ({ | ||
middleware: (middleware) => ({ | ||
handler: (handler) => createConfig(undefined, outputs, middleware, handler) | ||
handler: (handler) => createOperation({ outputs, middleware, handler }) | ||
}), | ||
handler: (handler) => createConfig(undefined, outputs, undefined, handler) | ||
handler: (handler) => createOperation({ outputs, handler }) | ||
}), | ||
middleware: (middleware) => ({ | ||
handler: (handler) => createConfig(undefined, undefined, middleware, handler) | ||
handler: (handler) => createOperation({ middleware, handler }) | ||
}), | ||
handler: (handler) => createConfig(undefined, undefined, undefined, handler) | ||
handler: (handler) => createOperation({ handler }) | ||
}; | ||
}; | ||
exports.apiRouteOperation = apiRouteOperation; |
@@ -1,4 +0,4 @@ | ||
export * from './api-route-handler'; | ||
export * from './api-route'; | ||
export * from './api-route-operation'; | ||
export * from './docs-api-route-handler'; | ||
export * from './rpc-api-route-handler'; | ||
export * from './docs-api-route'; | ||
export * from './rpc-api-route'; |
@@ -17,5 +17,5 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
__exportStar(require("./api-route-handler"), exports); | ||
__exportStar(require("./api-route"), exports); | ||
__exportStar(require("./api-route-operation"), exports); | ||
__exportStar(require("./docs-api-route-handler"), exports); | ||
__exportStar(require("./rpc-api-route-handler"), exports); | ||
__exportStar(require("./docs-api-route"), exports); | ||
__exportStar(require("./rpc-api-route"), exports); |
@@ -10,3 +10,3 @@ "use strict"; | ||
const logInitInfo = ({ config, baseUrl, url }) => { | ||
const configsEqual = (0, lodash_1.isEqualWith)(global.nextRestFrameworkConfig, config); | ||
const configsEqual = (0, lodash_1.isEqualWith)(global._nextRestFrameworkConfig, config); | ||
const logReservedPaths = () => { | ||
@@ -16,4 +16,4 @@ console.info(chalk_1.default.yellowBright(`Docs: ${url} | ||
}; | ||
if (!global.nextRestFrameworkConfig) { | ||
global.nextRestFrameworkConfig = config; | ||
if (!global._nextRestFrameworkConfig) { | ||
global._nextRestFrameworkConfig = config; | ||
console.info(chalk_1.default.green('Next REST Framework initialized! 🚀')); | ||
@@ -24,3 +24,3 @@ logReservedPaths(); | ||
console.info(chalk_1.default.green('Next REST Framework config changed, re-initializing!')); | ||
global.nextRestFrameworkConfig = config; | ||
global._nextRestFrameworkConfig = config; | ||
logReservedPaths(); | ||
@@ -27,0 +27,0 @@ } |
import { type NextRestFrameworkConfig, type OpenApiPathItem, type OpenApiOperation } from '../types'; | ||
import { type OpenAPIV3_1 } from 'openapi-types'; | ||
import { ValidMethod } from '../constants'; | ||
import { type ApiRouteParams } from '../pages-router'; | ||
import { type RouteParams } from '../app-router'; | ||
import { type OperationDefinition } from './rpc-operation'; | ||
import { type ApiRouteOperationDefinition } from '../pages-router'; | ||
import { type RouteOperationDefinition } from '../app-router'; | ||
import { type RpcOperationDefinition } from './rpc-operation'; | ||
export declare const getNestedFiles: (basePath: string, dir: string) => string[]; | ||
@@ -34,8 +34,11 @@ export declare const isWildcardMatch: ({ pattern, path }: { | ||
export declare const isValidMethod: (x: unknown) => x is ValidMethod; | ||
export declare const getOasDataFromMethodHandlers: ({ methodHandlers, route }: { | ||
methodHandlers: RouteParams | ApiRouteParams; | ||
export declare const getOasDataFromOperations: ({ operations, options, route }: { | ||
operations: Record<string, RouteOperationDefinition | ApiRouteOperationDefinition>; | ||
options?: { | ||
openApiPath?: OpenApiPathItem | undefined; | ||
} | undefined; | ||
route: string; | ||
}) => NrfOasData; | ||
export declare const getOasDataFromRpcOperations: ({ operations, options, route }: { | ||
operations: Record<string, OperationDefinition<any, any>>; | ||
export declare const getOasDataFromRpcOperations: ({ operations, options, route: _route }: { | ||
operations: Record<string, RpcOperationDefinition<any, any, any>>; | ||
options?: { | ||
@@ -42,0 +45,0 @@ openApiPath?: OpenApiPathItem | undefined; |
@@ -29,3 +29,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getOasDataFromRpcOperations = exports.getOasDataFromMethodHandlers = exports.isValidMethod = exports.syncOpenApiSpec = exports.generateOpenApiSpec = exports.fetchOasDataFromDev = exports.sortObjectByKeys = exports.logIgnoredPaths = exports.getApiRouteName = exports.getRouteName = exports.isWildcardMatch = exports.getNestedFiles = void 0; | ||
exports.getOasDataFromRpcOperations = exports.getOasDataFromOperations = exports.isValidMethod = exports.syncOpenApiSpec = exports.generateOpenApiSpec = exports.fetchOasDataFromDev = exports.sortObjectByKeys = exports.logIgnoredPaths = exports.getApiRouteName = exports.getRouteName = exports.isWildcardMatch = exports.getNestedFiles = void 0; | ||
const path_1 = require("path"); | ||
@@ -78,4 +78,9 @@ const constants_1 = require("../constants"); | ||
const sortObjectByKeys = (obj) => { | ||
const sortedEntries = Object.entries(obj).sort((a, b) => a[0].localeCompare(b[0])); | ||
return Object.fromEntries(sortedEntries); | ||
const unordered = { ...obj }; | ||
return Object.keys(unordered) | ||
.sort() | ||
.reduce((_obj, key) => { | ||
_obj[key] = unordered[key]; | ||
return _obj; | ||
}, {}); | ||
}; | ||
@@ -112,3 +117,5 @@ exports.sortObjectByKeys = sortObjectByKeys; | ||
.filter(isAllowedRoute); | ||
const getCleanedRpcRoutes = (files) => files.filter((file) => file.endsWith('rpc/[operationId]/route.ts')); | ||
let routes = []; | ||
let rpcRoutes = []; | ||
/* | ||
@@ -127,2 +134,3 @@ * Clean and filter the API routes to paths: | ||
.filter(isAllowedRoute); | ||
const getCleanedRpcApiRoutes = (files) => files.filter((file) => file.endsWith('rpc/[operationId].ts')); | ||
try { | ||
@@ -132,3 +140,5 @@ // Scan `app` folder. | ||
if ((0, fs_1.existsSync)(path)) { | ||
routes = getCleanedRoutes((0, exports.getNestedFiles)(path, '')); | ||
const files = (0, exports.getNestedFiles)(path, ''); | ||
routes = getCleanedRoutes(files); | ||
rpcRoutes = getCleanedRpcRoutes(files); | ||
} | ||
@@ -139,3 +149,5 @@ else { | ||
if ((0, fs_1.existsSync)(path)) { | ||
routes = getCleanedRoutes((0, exports.getNestedFiles)(path, '')); | ||
const files = (0, exports.getNestedFiles)(path, ''); | ||
routes = getCleanedRoutes(files); | ||
rpcRoutes = getCleanedRpcRoutes(files); | ||
} | ||
@@ -146,2 +158,3 @@ } | ||
let apiRoutes = []; | ||
let rpcApiRoutes = []; | ||
try { | ||
@@ -151,3 +164,5 @@ // Scan `pages/api` folder. | ||
if ((0, fs_1.existsSync)(path)) { | ||
apiRoutes = getCleanedApiRoutes((0, exports.getNestedFiles)(path, '')); | ||
const files = (0, exports.getNestedFiles)(path, ''); | ||
apiRoutes = getCleanedApiRoutes(files); | ||
rpcApiRoutes = getCleanedRpcApiRoutes(files); | ||
} | ||
@@ -158,3 +173,5 @@ else { | ||
if ((0, fs_1.existsSync)(path)) { | ||
apiRoutes = getCleanedApiRoutes((0, exports.getNestedFiles)(path, '')); | ||
const files = (0, exports.getNestedFiles)(path, ''); | ||
apiRoutes = getCleanedApiRoutes(files); | ||
rpcApiRoutes = getCleanedRpcApiRoutes(files); | ||
} | ||
@@ -170,3 +187,3 @@ } | ||
// Call the API routes to get the OpenAPI paths. | ||
await Promise.all([...routes, ...apiRoutes].map(async (route) => { | ||
await Promise.all([...routes, ...rpcRoutes, ...apiRoutes, ...rpcApiRoutes].map(async (route) => { | ||
const url = `${baseUrl}${route}`; | ||
@@ -211,4 +228,4 @@ const fetchWithMethod = async (method) => { | ||
return { | ||
paths: (0, exports.sortObjectByKeys)(paths), | ||
schemas: (0, exports.sortObjectByKeys)(schemas) | ||
paths, | ||
schemas | ||
}; | ||
@@ -234,10 +251,12 @@ }; | ||
exports.generateOpenApiSpec = generateOpenApiSpec; | ||
const syncOpenApiSpec = async ({ config, nrfOasData: { paths, schemas } }) => { | ||
const syncOpenApiSpec = async ({ config, nrfOasData: { paths = {}, schemas = {} } }) => { | ||
const path = (0, path_1.join)(process.cwd(), 'public', config.openApiJsonPath); | ||
const components = Object.keys(schemas).length | ||
? { components: { schemas: (0, exports.sortObjectByKeys)(schemas) } } | ||
: {}; | ||
const newSpec = (0, lodash_1.merge)({ | ||
openapi: constants_1.OPEN_API_VERSION, | ||
info: config.openApiObject.info, | ||
paths, | ||
components: { schemas } | ||
}, config.openApiObject); | ||
paths: (0, exports.sortObjectByKeys)(paths) | ||
}, components, config.openApiObject); | ||
try { | ||
@@ -262,7 +281,6 @@ const data = (0, fs_1.readFileSync)(path); | ||
exports.isValidMethod = isValidMethod; | ||
const getOasDataFromMethodHandlers = ({ methodHandlers, route }) => { | ||
const { openApiPath } = methodHandlers; | ||
const getOasDataFromOperations = ({ operations, options, route }) => { | ||
const paths = {}; | ||
paths[route] = { | ||
...openApiPath | ||
...options?.openApiPath | ||
}; | ||
@@ -272,19 +290,12 @@ const requestBodySchemas = {}; | ||
const capitalize = (str) => str[0].toUpperCase() + str.slice(1); | ||
const getSchemaNameFromRoute = () => { | ||
const routeComponents = route | ||
.split('/') | ||
.filter((c) => !c.startsWith('{') && c !== ''); | ||
if (routeComponents.length === 0) { | ||
return ''; | ||
Object.entries(operations).forEach(([operationId, { openApiOperation, method: _method, input, outputs }]) => { | ||
if (!(0, exports.isValidMethod)(_method)) { | ||
return; | ||
} | ||
const lastComponent = routeComponents[routeComponents.length - 1]; | ||
return lastComponent.charAt(0).toUpperCase() + lastComponent.slice(1); | ||
}; | ||
Object.entries(methodHandlers) | ||
.filter(([method]) => (0, exports.isValidMethod)(method)) | ||
.forEach(([_method, { _meta: { openApiOperation, input, outputs } }]) => { | ||
const method = _method.toLowerCase(); | ||
const generatedOperationObject = {}; | ||
const method = _method?.toLowerCase(); | ||
const generatedOperationObject = { | ||
operationId | ||
}; | ||
if (input?.body && input?.contentType) { | ||
const key = `${capitalize(method)}${getSchemaNameFromRoute()}RequestBody`; | ||
const key = `${capitalize(operationId)}RequestBody`; | ||
const schema = (0, schemas_1.getJsonSchema)({ schema: input.body }); | ||
@@ -306,5 +317,10 @@ requestBodySchemas[method] = { | ||
} | ||
const mapResponses = (outputs) => outputs.reduce((obj, { status, contentType, schema, name }, i) => { | ||
const usedStatusCodes = []; | ||
generatedOperationObject.responses = outputs?.reduce((obj, { status, contentType, schema, name }) => { | ||
const occurrenceOfStatusCode = usedStatusCodes.includes(status) | ||
? usedStatusCodes.filter((s) => s === status).length + 1 | ||
: ''; | ||
const key = name ?? | ||
`${capitalize(method)}${getSchemaNameFromRoute()}${status >= 400 ? 'Error' : ''}ResponseBody${i > 0 ? i + 1 : ''}`; | ||
`${capitalize(operationId)}${status}ResponseBody${occurrenceOfStatusCode}`; | ||
usedStatusCodes.push(status); | ||
responseBodySchemas[method] = [ | ||
@@ -342,6 +358,2 @@ ...(responseBodySchemas[method] ?? []), | ||
}); | ||
generatedOperationObject.responses = { | ||
...mapResponses(outputs?.filter(({ status }) => status < 400) ?? []), | ||
...mapResponses(outputs?.filter(({ status }) => status >= 400) ?? []) | ||
}; | ||
const pathParameters = route | ||
@@ -367,3 +379,4 @@ .match(/{([^}]+)}/g) | ||
.map((key) => { | ||
const schema = input.query.shape[key]; | ||
const schema = input.query | ||
.shape[key]; | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
@@ -408,88 +421,59 @@ return { | ||
}; | ||
exports.getOasDataFromMethodHandlers = getOasDataFromMethodHandlers; | ||
const getOasDataFromRpcOperations = ({ operations, options, route }) => { | ||
exports.getOasDataFromOperations = getOasDataFromOperations; | ||
const getOasDataFromRpcOperations = ({ operations, options, route: _route }) => { | ||
const paths = {}; | ||
const requestBodySchemas = {}; | ||
const responseBodySchemas = {}; | ||
const capitalize = (str) => str[0].toUpperCase() + str.slice(1); | ||
Object.entries(operations).forEach(([operation, { _meta: { input, outputs } }]) => { | ||
Object.entries(operations).forEach(([operationId, { _meta: { openApiOperation, input, outputs } }]) => { | ||
const route = _route + `/${operationId}`; | ||
paths[route] = { | ||
...options?.openApiPath | ||
}; | ||
const generatedOperationObject = { | ||
operationId | ||
}; | ||
if (input) { | ||
const key = `${capitalize(operation)}Body`; | ||
requestBodySchemas[operation] = { | ||
const key = `${capitalize(operationId)}RequestBody`; | ||
const ref = `#/components/schemas/${key}`; | ||
requestBodySchemas[operationId] = { | ||
key, | ||
ref: `#/components/schemas/${key}`, | ||
ref, | ||
schema: (0, schemas_1.getJsonSchema)({ schema: input }) | ||
}; | ||
} | ||
if (outputs) { | ||
responseBodySchemas[operation] = outputs.reduce((acc, curr, i) => { | ||
const key = curr.name ?? | ||
`${capitalize(operation)}Response${i > 0 ? i + 1 : ''}`; | ||
return [ | ||
...acc, | ||
{ | ||
key, | ||
ref: `#/components/schemas/${key}`, | ||
schema: (0, schemas_1.getJsonSchema)({ schema: curr.schema }) | ||
} | ||
]; | ||
}, []); | ||
} | ||
}); | ||
const requestBodySchemaRefMapping = Object.entries(requestBodySchemas).reduce((acc, [key, val]) => { | ||
acc[key] = val.ref; | ||
return acc; | ||
}, {}); | ||
const responseBodySchemaRefMapping = Object.entries(requestBodySchemas).reduce((acc, [key, val]) => { | ||
acc[key] = val.ref; | ||
return acc; | ||
}, {}); | ||
const rpcOperation = { | ||
description: 'RPC endpoint', | ||
tags: ['RPC'], | ||
operationId: 'rpcCall', | ||
parameters: [ | ||
{ | ||
name: 'X-RPC-Operation', | ||
in: 'header', | ||
schema: { | ||
type: 'string' | ||
}, | ||
required: true, | ||
description: 'The RPC operation to call.' | ||
} | ||
], | ||
// eslint-disable-next-line | ||
requestBody: { | ||
content: { | ||
'application/json': { | ||
schema: { | ||
discriminator: { | ||
propertyName: 'X-RPC-Operation', | ||
mapping: requestBodySchemaRefMapping | ||
}, | ||
oneOf: Object.values(requestBodySchemas).map(({ ref }) => ({ | ||
$ref: ref | ||
})) | ||
} | ||
} | ||
} | ||
}, | ||
responses: { | ||
'200': { | ||
description: 'Successful response', | ||
generatedOperationObject.requestBody = { | ||
content: { | ||
'application/json': { | ||
schema: { | ||
discriminator: { | ||
propertyName: 'X-RPC-Operation', | ||
mapping: responseBodySchemaRefMapping | ||
}, | ||
oneOf: Object.values(responseBodySchemas).flatMap((val) => [ | ||
...val.map(({ ref }) => ({ $ref: ref })) | ||
]) | ||
$ref: ref | ||
} | ||
} | ||
} | ||
}, | ||
'500': { | ||
}; | ||
} | ||
generatedOperationObject.responses = outputs?.reduce((obj, { schema, name }, i) => { | ||
const key = name ?? | ||
`${capitalize(operationId)}ResponseBody${i > 0 ? i + 1 : ''}`; | ||
responseBodySchemas[operationId] = [ | ||
...(responseBodySchemas[operationId] ?? []), | ||
{ | ||
key, | ||
ref: `#/components/schemas/${key}`, | ||
schema: (0, schemas_1.getJsonSchema)({ schema }) | ||
} | ||
]; | ||
return Object.assign(obj, { | ||
200: { | ||
description: key, | ||
content: { | ||
'application/json': { | ||
schema: { | ||
$ref: `#/components/schemas/${key}` | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
}, { | ||
500: { | ||
description: constants_1.DEFAULT_ERRORS.unexpectedError, | ||
@@ -504,11 +488,8 @@ content: { | ||
} | ||
} | ||
}; | ||
const paths = { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
[route]: (0, lodash_1.merge)({ | ||
...operations.openApiPath, | ||
post: (0, lodash_1.merge)(rpcOperation, options?.openApiOperation) | ||
}, options?.openApiPath) | ||
}; | ||
}); | ||
paths[route] = { | ||
...paths[route], | ||
['post']: (0, lodash_1.merge)(generatedOperationObject, openApiOperation) | ||
}; | ||
}); | ||
const requestBodySchemaMapping = Object.values(requestBodySchemas).reduce((acc, { key, schema }) => { | ||
@@ -536,7 +517,4 @@ acc[key] = schema; | ||
}; | ||
return { | ||
paths, | ||
schemas | ||
}; | ||
return { paths, schemas }; | ||
}; | ||
exports.getOasDataFromRpcOperations = getOasDataFromRpcOperations; |
import { type z, type ZodSchema } from 'zod'; | ||
import { type OpenApiOperation } from '../types'; | ||
interface OutputObject { | ||
@@ -6,39 +7,40 @@ schema: ZodSchema; | ||
} | ||
type OperationHandler<Input = unknown, Outputs extends readonly OutputObject[] = readonly OutputObject[]> = (params: z.infer<ZodSchema<Input>>) => Promise<z.infer<Outputs[number]['schema']>> | z.infer<Outputs[number]['schema']>; | ||
interface OperationDefinitionMeta<Input, Outputs extends readonly OutputObject[]> { | ||
input?: ZodSchema<Input | unknown>; | ||
outputs?: Outputs; | ||
middleware?: OperationHandler<Input, Outputs>; | ||
handler?: OperationHandler<Input, Outputs>; | ||
type RpcOperationHandler<Input = unknown, Outputs extends readonly OutputObject[] = readonly OutputObject[]> = (params: z.infer<ZodSchema<Input>>) => Promise<z.infer<Outputs[number]['schema']>> | z.infer<Outputs[number]['schema']>; | ||
interface OperationDefinitionMeta { | ||
openApiOperation?: OpenApiOperation; | ||
input?: ZodSchema; | ||
outputs?: readonly OutputObject[]; | ||
middleware?: RpcOperationHandler; | ||
handler?: RpcOperationHandler; | ||
} | ||
export type OperationDefinition<Input = unknown, Outputs extends readonly OutputObject[] = readonly OutputObject[], HasInput extends boolean = true> = (HasInput extends true ? (body: z.infer<ZodSchema<Input>>) => Promise<z.infer<Outputs[number]['schema']>> : () => Promise<z.infer<Outputs[number]['schema']>>) & { | ||
_meta: OperationDefinitionMeta<Input, Outputs>; | ||
export type RpcOperationDefinition<Input = unknown, Outputs extends readonly OutputObject[] = readonly OutputObject[], HasInput extends boolean = false, TypedResponse = Promise<z.infer<Outputs[number]['schema']>>> = (HasInput extends true ? (body: z.infer<ZodSchema<Input>>) => TypedResponse : () => TypedResponse) & { | ||
_meta: OperationDefinitionMeta; | ||
}; | ||
export declare const rpcOperation: () => { | ||
export declare const rpcOperation: (openApiOperation?: OpenApiOperation) => { | ||
input: <Input>(input: z.ZodType<Input, z.ZodTypeDef, Input>) => { | ||
outputs: <Output extends readonly OutputObject[]>(outputs: Output) => { | ||
middleware: (middleware: OperationHandler<Input, Output>) => { | ||
handler: (handler: OperationHandler<Input, Output>) => OperationDefinition<Input, Output, true>; | ||
middleware: (middleware: RpcOperationHandler<unknown, Output>) => { | ||
handler: (handler: RpcOperationHandler<Input, Output>) => RpcOperationDefinition<Input, Output, true, Promise<z.TypeOf<Output[number]["schema"]>>>; | ||
}; | ||
handler: (handler: OperationHandler<Input, Output>) => OperationDefinition<Input, Output, true>; | ||
handler: (handler: RpcOperationHandler<Input, Output>) => RpcOperationDefinition<Input, Output, true, Promise<z.TypeOf<Output[number]["schema"]>>>; | ||
}; | ||
middleware: (middleware: OperationHandler<Input, readonly OutputObject[]>) => { | ||
middleware: (middleware: RpcOperationHandler) => { | ||
outputs: <Output_1 extends readonly OutputObject[]>(outputs: Output_1) => { | ||
handler: (handler: OperationHandler<Input, Output_1>) => OperationDefinition<Input, readonly OutputObject[], true>; | ||
handler: (handler: RpcOperationHandler<Input, Output_1>) => RpcOperationDefinition<Input, readonly OutputObject[], true, Promise<any>>; | ||
}; | ||
handler: (handler: OperationHandler<Input, readonly OutputObject[]>) => OperationDefinition<Input, readonly OutputObject[], true>; | ||
handler: (handler: RpcOperationHandler<Input, readonly OutputObject[]>) => RpcOperationDefinition<Input, readonly OutputObject[], true, Promise<any>>; | ||
}; | ||
handler: (handler: OperationHandler<Input, readonly OutputObject[]>) => OperationDefinition<Input, readonly OutputObject[], true>; | ||
handler: (handler: RpcOperationHandler<Input, readonly OutputObject[]>) => RpcOperationDefinition<Input, readonly OutputObject[], true, Promise<any>>; | ||
}; | ||
outputs: <Output_2 extends readonly OutputObject[]>(outputs: Output_2) => { | ||
middleware: (middleware: OperationHandler<unknown, Output_2>) => { | ||
handler: (handler: OperationHandler<unknown, Output_2>) => OperationDefinition<unknown, Output_2, false>; | ||
middleware: (middleware: RpcOperationHandler<unknown, Output_2>) => { | ||
handler: (handler: RpcOperationHandler<unknown, Output_2>) => RpcOperationDefinition<unknown, Output_2, false, Promise<z.TypeOf<Output_2[number]["schema"]>>>; | ||
}; | ||
handler: (handler: OperationHandler<unknown, Output_2>) => OperationDefinition<unknown, Output_2, false>; | ||
handler: (handler: RpcOperationHandler<unknown, Output_2>) => RpcOperationDefinition<unknown, Output_2, false, Promise<z.TypeOf<Output_2[number]["schema"]>>>; | ||
}; | ||
middleware: (middleware: OperationHandler) => { | ||
handler: (handler: OperationHandler) => OperationDefinition<unknown, readonly OutputObject[], false>; | ||
middleware: (middleware: RpcOperationHandler) => { | ||
handler: (handler: RpcOperationHandler) => RpcOperationDefinition<unknown, readonly OutputObject[], false, Promise<any>>; | ||
}; | ||
handler: (handler: OperationHandler) => OperationDefinition<unknown, readonly OutputObject[], false>; | ||
handler: (handler: RpcOperationHandler) => RpcOperationDefinition<unknown, readonly OutputObject[], false, Promise<any>>; | ||
}; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.rpcOperation = void 0; | ||
const rpcOperation = () => { | ||
function createOperation(input, outputs, middleware, handler) { | ||
const schemas_1 = require("./schemas"); | ||
const constants_1 = require("../constants"); | ||
const rpcOperation = (openApiOperation) => { | ||
function createOperation({ input, outputs, middleware, handler }) { | ||
const meta = { | ||
openApiOperation, | ||
input, | ||
@@ -12,4 +15,25 @@ outputs, | ||
}; | ||
const callOperation = async (body) => { | ||
if (middleware) { | ||
const _res = await middleware(body); | ||
if (_res) { | ||
return _res; | ||
} | ||
} | ||
if (input) { | ||
const { valid, errors } = await (0, schemas_1.validateSchema)({ | ||
schema: input, | ||
obj: body | ||
}); | ||
if (!valid) { | ||
throw Error(`${constants_1.DEFAULT_ERRORS.invalidRequestBody}: ${errors}`); | ||
} | ||
} | ||
if (!handler) { | ||
throw Error('Handler not found.'); | ||
} | ||
return await handler(body); | ||
}; | ||
if (input === undefined) { | ||
const operation = async () => { }; | ||
const operation = async () => await callOperation(); | ||
operation._meta = meta; | ||
@@ -19,3 +43,3 @@ return operation; | ||
else { | ||
const operation = async (_body) => { }; | ||
const operation = async (body) => await callOperation(body); | ||
operation._meta = meta; | ||
@@ -29,26 +53,54 @@ return operation; | ||
middleware: (middleware) => ({ | ||
handler: (handler) => createOperation(input, outputs, middleware, handler) | ||
handler: (handler) => createOperation({ | ||
input, | ||
outputs, | ||
middleware, | ||
handler | ||
}) | ||
}), | ||
handler: (handler) => createOperation(input, outputs, undefined, handler) | ||
handler: (handler) => createOperation({ | ||
input, | ||
outputs, | ||
handler | ||
}) | ||
}), | ||
middleware: (middleware) => ({ | ||
outputs: (outputs) => ({ | ||
handler: (handler) => createOperation(input, outputs, middleware, handler) | ||
handler: (handler) => createOperation({ | ||
input, | ||
outputs, | ||
middleware, | ||
handler | ||
}) | ||
}), | ||
handler: (handler) => createOperation(input, undefined, middleware, handler) | ||
handler: (handler) => createOperation({ | ||
input, | ||
middleware, | ||
handler | ||
}) | ||
}), | ||
handler: (handler) => createOperation(input, undefined, undefined, handler) | ||
handler: (handler) => createOperation({ | ||
input, | ||
handler | ||
}) | ||
}), | ||
outputs: (outputs) => ({ | ||
middleware: (middleware) => ({ | ||
handler: (handler) => createOperation(undefined, outputs, middleware, handler) | ||
handler: (handler) => createOperation({ | ||
outputs, | ||
middleware, | ||
handler | ||
}) | ||
}), | ||
handler: (handler) => createOperation(undefined, outputs, undefined, handler) | ||
handler: (handler) => createOperation({ | ||
outputs, | ||
handler | ||
}) | ||
}), | ||
middleware: (middleware) => ({ | ||
handler: (handler) => createOperation(undefined, undefined, middleware, handler) | ||
handler: (handler) => createOperation({ middleware, handler }) | ||
}), | ||
handler: (handler) => createOperation(undefined, undefined, undefined, handler) | ||
handler: (handler) => createOperation({ handler }) | ||
}; | ||
}; | ||
exports.rpcOperation = rpcOperation; |
@@ -25,2 +25,3 @@ "use strict"; | ||
return (0, zod_to_json_schema_1.zodToJsonSchema)(schema, { | ||
$refStrategy: 'none', | ||
target: 'openApi3' | ||
@@ -27,0 +28,0 @@ }); |
@@ -46,7 +46,3 @@ import { type OpenAPIV3_1 } from 'openapi-types'; | ||
export type BaseQuery = Record<string, string | string[]>; | ||
export interface InputObject<Body = unknown, Query = BaseQuery> { | ||
contentType?: BaseContentType; | ||
body?: ZodSchema<Body>; | ||
query?: ZodSchema<Query>; | ||
} | ||
export type BaseParams = Record<string, string>; | ||
export interface OutputObject<Body = unknown, Status extends BaseStatus = BaseStatus, ContentType extends BaseContentType = BaseContentType> { | ||
@@ -59,3 +55,3 @@ schema: ZodSchema<Body>; | ||
export type OpenApiPathItem = Pick<OpenAPIV3_1.PathItemObject, 'summary' | 'description' | 'servers' | 'parameters'>; | ||
export type OpenApiOperation = Pick<OpenAPIV3_1.OperationObject, 'tags' | 'summary' | 'description' | 'externalDocs' | 'operationId' | 'parameters' | 'callbacks' | 'deprecated' | 'security' | 'servers'>; | ||
export type OpenApiOperation = Pick<OpenAPIV3_1.OperationObject, 'tags' | 'summary' | 'description' | 'externalDocs' | 'parameters' | 'callbacks' | 'deprecated' | 'security' | 'servers'>; | ||
export type Modify<T, R> = Omit<T, keyof R> & R; | ||
@@ -62,0 +58,0 @@ export type AnyCase<T extends string> = T | Uppercase<T> | Lowercase<T>; |
{ | ||
"name": "next-rest-framework", | ||
"version": "4.0.0", | ||
"version": "4.1.0", | ||
"description": "Next REST Framework - Type-safe, self-documenting APIs for Next.js", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
185
README.md
@@ -39,6 +39,7 @@ <p align="center"> | ||
- [Pages Router:](#pages-router-1) | ||
- [Client](#client) | ||
- [RPC](#rpc) | ||
- [App Router:](#app-router-2) | ||
- [Pages Router:](#pages-router-2) | ||
- [Client](#client) | ||
- [Client](#client-1) | ||
- [API reference](#api-reference) | ||
@@ -108,5 +109,5 @@ - [Docs handler options](#docs-handler-options) | ||
import { docsRouteHandler } from 'next-rest-framework'; | ||
import { docsRoute } from 'next-rest-framework'; | ||
export const GET = docsRouteHandler(); | ||
export const { GET } = docsRoute(); | ||
``` | ||
@@ -119,5 +120,5 @@ | ||
import { docsApiRouteHandler } from 'next-rest-framework'; | ||
import { docsApiRoute } from 'next-rest-framework'; | ||
export default docsApiRouteHandler(); | ||
export default docsApiRoute(); | ||
``` | ||
@@ -136,7 +137,3 @@ | ||
import { | ||
TypedNextResponse, | ||
routeHandler, | ||
routeOperation | ||
} from 'next-rest-framework'; | ||
import { TypedNextResponse, route, routeOperation } from 'next-rest-framework'; | ||
import { z } from 'zod'; | ||
@@ -153,7 +150,9 @@ | ||
// Example App Router route handler with GET/POST handlers. | ||
const handler = routeHandler({ | ||
GET: routeOperation({ | ||
export const { GET, POST } = route({ | ||
getTodos: routeOperation({ | ||
method: 'GET', | ||
// Optional OpenAPI operation documentation. | ||
operationId: 'getTodos', | ||
tags: ['example-api', 'todos', 'app-router'] | ||
openApiOperation: { | ||
tags: ['example-api', 'todos', 'app-router'] | ||
} | ||
}) | ||
@@ -181,6 +180,8 @@ // Output schema for strictly-typed responses and OpenAPI documentation. | ||
POST: routeOperation({ | ||
createTodo: routeOperation({ | ||
method: 'POST', | ||
// Optional OpenAPI operation documentation. | ||
operationId: 'createTodo', | ||
tags: ['example-api', 'todos', 'app-router'] | ||
openApiOperation: { | ||
tags: ['example-api', 'todos', 'app-router'] | ||
} | ||
}) | ||
@@ -207,11 +208,13 @@ // Input schema for strictly-typed request, request validation and OpenAPI documentation. | ||
]) | ||
// Optional middleware logic executed before request validation. | ||
.middleware((req) => { | ||
if (!req.headers.get('authorization')) { | ||
// Type-checked response. | ||
return TypedNextResponse.json('Unauthorized', { | ||
status: 401 | ||
}); | ||
.middleware( | ||
// Optional middleware logic executed before request validation. | ||
(req) => { | ||
if (!req.headers.get('authorization')) { | ||
// Type-checked response. | ||
return TypedNextResponse.json('Unauthorized', { | ||
status: 401 | ||
}); | ||
} | ||
} | ||
}) | ||
) | ||
.handler(async (req) => { | ||
@@ -226,4 +229,2 @@ const { name } = await req.json(); // Strictly-typed request. | ||
}); | ||
export { handler as GET, handler as POST }; | ||
``` | ||
@@ -238,3 +239,3 @@ | ||
import { apiRouteHandler, apiRouteOperation } from 'next-rest-framework'; | ||
import { apiRoute, apiRouteOperation } from 'next-rest-framework'; | ||
import { z } from 'zod'; | ||
@@ -251,7 +252,9 @@ | ||
// Example Pages Router API route with GET/POST handlers. | ||
export default apiRouteHandler({ | ||
GET: apiRouteOperation({ | ||
export default apiRoute({ | ||
getTodos: apiRouteOperation({ | ||
method: 'GET', | ||
// Optional OpenAPI operation documentation. | ||
operationId: 'getTodos', | ||
tags: ['example-api', 'todos', 'pages-router'] | ||
openApiOperation: { | ||
tags: ['example-api', 'todos', 'pages-router'] | ||
} | ||
}) | ||
@@ -277,6 +280,8 @@ // Output schema for strictly-typed responses and OpenAPI documentation. | ||
POST: apiRouteOperation({ | ||
createTodo: apiRouteOperation({ | ||
method: 'POST', | ||
// Optional OpenAPI operation documentation. | ||
operationId: 'createTodo', | ||
tags: ['example-api', 'todos', 'pages-router'] | ||
openApiOperation: { | ||
tags: ['example-api', 'todos', 'pages-router'] | ||
} | ||
}) | ||
@@ -316,5 +321,13 @@ // Input schema for strictly-typed request, request validation and OpenAPI documentation. | ||
All of above type-safe endpoints will be now auto-generated to your OpenAPI spec and exposed in the documentation: | ||
 | ||
##### Client | ||
To achieve end-to-end type-safety, you can use any client implementation that relies on the generated OpenAPI specification, e.g. [openapi-client-axios](https://github.com/openapistack/openapi-client-axios). | ||
#### [RPC](#rpc) | ||
You can also define your APIs with RPC route handlers that also auto-generates the OpenAPI spec and provides a type-safe API client for end-to-end type safety. | ||
You can also define your APIs with RPC route handlers that also auto-generate the OpenAPI spec. The RPC endpoints can be consumed with the type-safe API client for end-to-end type safety. | ||
@@ -326,3 +339,3 @@ ##### App Router: | ||
import { rpcOperation, rpcRouteHandler } from 'next-rest-framework'; | ||
import { rpcOperation, rpcRoute } from 'next-rest-framework'; | ||
import { z } from 'zod'; | ||
@@ -338,4 +351,10 @@ | ||
const todoSchema = z.object({ | ||
id: z.number(), | ||
name: z.string(), | ||
completed: z.boolean() | ||
}); | ||
// Example App Router RPC handler. | ||
export const POST = rpcRouteHandler({ | ||
const { POST, client } = rpcRoute({ | ||
getTodos: rpcOperation() | ||
@@ -345,9 +364,3 @@ // Output schema for strictly-typed responses and OpenAPI documentation. | ||
{ | ||
schema: z.array( | ||
z.object({ | ||
id: z.number(), | ||
name: z.string(), | ||
completed: z.boolean() | ||
}) | ||
) | ||
schema: z.array(todoSchema) | ||
} | ||
@@ -369,7 +382,3 @@ ]) | ||
{ | ||
schema: z.object({ | ||
id: z.number(), | ||
name: z.string(), | ||
completed: z.boolean() | ||
}) | ||
schema: todoSchema | ||
} | ||
@@ -397,6 +406,9 @@ ]) | ||
// Output schema for strictly-typed responses and OpenAPI documentation. | ||
.outputs([{ schema: z.object({ message: z.string() }) }]) | ||
.handler(async ({ name }) => { | ||
.outputs([{ schema: todoSchema }]) | ||
.handler(async ({ name: _name }) => { | ||
// Create todo. | ||
const todo = { id: 2, name: _name, completed: false }; | ||
// Type-checked response. | ||
return { message: `New TODO created: ${name}` }; | ||
return todo; | ||
}), | ||
@@ -426,3 +438,5 @@ | ||
export type AppRouterRpcClient = typeof POST.client; | ||
export { POST }; | ||
export type AppRouterRpcClient = typeof client; | ||
``` | ||
@@ -435,6 +449,6 @@ | ||
import { rpcApiRouteHandler } from 'next-rest-framework'; | ||
import { rpcApiRoute } from 'next-rest-framework'; | ||
// Example Pages Router RPC handler. | ||
const handler = rpcApiRouteHandler({ | ||
const handler = rpcApiRoute({ | ||
// ... | ||
@@ -446,21 +460,24 @@ // Exactly the same as the App Router example. | ||
export type RpcApiRouteClient = typeof handler.client; | ||
export type RpcClient = typeof handler.client; | ||
``` | ||
You can also use the `rpcOperation` function outside the `rpcRouteHandler` function and call it server-side anywhere in your code, just like you would call a Next.js [Server Action](https://nextjs.org/docs/app/api-reference/functions/server-actions). | ||
The RPC routes will also be included in your OpenAPI spec now. Note that the `rpcOperation` definitions can be also be placed outside the `rpcRouteHandler` if you do not want to expose them as public APIs as long as they're called server-side. | ||
##### Client | ||
The strongly-typed RPC operations can be called inside inside React server components and server actions like any functions: | ||
```typescript | ||
import { rpcClient } from 'next-rest-framework/client'; | ||
import { type AppRouterRpcClient } from 'app/api/routes/rpc/route'; | ||
'use server'; | ||
// Works both on server and client. | ||
const client = rpcClient<AppRouterRpcClient>({ | ||
url: 'http://localhost:3000/api/routes/rpc' | ||
}); | ||
import { client } from 'app/api/rpc/route'; | ||
// Simple example - the client can be easily integrated with any data fetching framework, like React Query or RTKQ. | ||
export default async function Page() { | ||
const data = await client.getTodos(); | ||
const todos = await client.getTodos(); | ||
const createTodo = async (name: string) => { | ||
'use server'; | ||
return client.createTodo({ name }); | ||
}; | ||
// ... | ||
@@ -470,6 +487,38 @@ } | ||
All of above type-safe endpoints will be now auto-generated to your OpenAPI spec and exposed in the documentation: | ||
For client-rendered components you can use the strongly-typed `rpcClient` or use server actions from the above example: | ||
 | ||
```typescript | ||
'use client'; | ||
import { useState } from 'react'; | ||
import { rpcClient } from 'next-rest-framework/rpc-client'; | ||
import { type RpcClient } from 'app/api/rpc/route'; | ||
const client = rpcClient<RpcClient>({ | ||
url: 'http://localhost:3000/api/rpc' | ||
}); | ||
export default function Page() { | ||
// ... | ||
useEffect(() => { | ||
client | ||
.getTodos() | ||
.then(() => { | ||
// ... | ||
}) | ||
.catch(console.error); | ||
}, []); | ||
const createTodo = async (name: string) => { | ||
'use server'; | ||
return client.createTodo({ name }); | ||
}; | ||
// ... | ||
} | ||
``` | ||
The `rpcClient` calls can also be easily integrated with any data fetching framework, like React Query or RTKQ. | ||
## [API reference](#api-reference) | ||
@@ -476,0 +525,0 @@ |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
235922
12.62%2521
7.97%664
7.97%2
-33.33%