🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
Book a DemoInstallSign in
Socket

next-rest-framework

Package Overview
Dependencies
Maintainers
1
Versions
86
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

next-rest-framework - npm Package Compare versions

Comparing version

to
4.1.0

dist/app-router/docs-route.d.ts

7

dist/app-router/index.d.ts

@@ -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": [

@@ -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:
![Next REST Framework docs](./docs/static/img/docs-screenshot.jpg)
##### 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:
![Next REST Framework docs](./docs/static/img/docs-screenshot.jpg)
```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