@rest-rpc/next 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rest-rpc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # rest-rpc
2
+
3
+ REST-shaped APIs with function-shaped TypeScript.
4
+
5
+ `rest-rpc` lets you define HTTP routes in a shared TypeScript contract, then use
6
+ that contract to derive typed server handlers, typed fetch clients, TanStack Query
7
+ options, and OpenAPI documents.
8
+
9
+ The API stays REST-shaped. Everyday application code can feel like function
10
+ calls.
11
+
12
+ ```ts
13
+ const todo = await api.todos.get.fetch({ id: "todo_1" });
14
+ ```
15
+
16
+ That call is still a normal HTTP request:
17
+
18
+ ```http
19
+ GET /todos/todo_1
20
+ ```
21
+
22
+ ## Packages
23
+
24
+ - `@rest-rpc/core`: contracts, fetch client, OpenAPI generation, core helpers.
25
+ - `@rest-rpc/express`: Express server adapter.
26
+ - `@rest-rpc/hono`: Hono server adapter.
27
+ - `@rest-rpc/fastify`: Fastify server adapter.
28
+ - `@rest-rpc/web`: Web `Request`/`Response` HTTP handler adapter.
29
+ - `@rest-rpc/next`: Next.js server/client adapter.
30
+ - `@rest-rpc/tanstack-query`: TanStack Query options and key helpers.
31
+
32
+ `@rest-rpc/server` contains shared adapter infrastructure and is mainly useful
33
+ for adapter authors.
34
+
35
+ ## Design
36
+
37
+ - REST routes remain explicit in the contract.
38
+ - Handler and client calls use flattened request input.
39
+ - Responses are keyed by HTTP status.
40
+ - Server adapters are thin integrations with existing frameworks.
41
+ - Schema libraries, OpenAPI UI, middleware, auth, and app structure stay your choice.
42
+
43
+ ## Documentation
44
+
45
+ Full documentation will live at [rest-rpc.dev](https://rest-rpc.dev).
@@ -0,0 +1,22 @@
1
+ import type { ApiClientFor, ApiClientOptions, Contract } from "@rest-rpc/core";
2
+ import type { HttpRouteDeclaration } from "@rest-rpc/core/contract";
3
+ type NextFetchOptions = RequestInit & {
4
+ next?: {
5
+ tags?: string[];
6
+ [key: string]: unknown;
7
+ };
8
+ };
9
+ export type NextClientFetchOptions = Omit<NextFetchOptions, "method" | "body" | "headers" | "signal">;
10
+ export type NextClientOptions = Omit<ApiClientOptions, "fetchOptions"> & {
11
+ automaticFetchTags?: {
12
+ enabled: boolean;
13
+ tagPrefix?: string;
14
+ };
15
+ fetchOptions?: NextClientFetchOptions;
16
+ };
17
+ export declare const initNextClient: <TContract extends Contract>(contract: TContract, options: NextClientOptions) => ApiClientFor<TContract>;
18
+ export declare const getGeneratedTagsForRoute: (route: HttpRouteDeclaration, options?: {
19
+ request?: Record<string, unknown>;
20
+ tagPrefix?: string;
21
+ }) => string[];
22
+ export {};
package/dist/client.js ADDED
@@ -0,0 +1,41 @@
1
+ import { getRouteCacheTags, initClient } from "@rest-rpc/core";
2
+ const createRouteCacheTagsForUrl = (url, prefix) => {
3
+ const parsed = new URL(url instanceof Request ? url.url : String(url));
4
+ return Array.from(new Set([
5
+ `${prefix ?? "rest-rpc"}:${parsed.pathname}${parsed.search}`,
6
+ `${prefix ?? "rest-rpc"}:${parsed.pathname}`,
7
+ ]));
8
+ };
9
+ const fetchWithTags = (fetchImpl, tagPrefix) => {
10
+ return (url, init) => {
11
+ if (init?.method !== "GET")
12
+ return fetchImpl(url, init);
13
+ const nextInit = init;
14
+ const taggedInit = {
15
+ ...nextInit,
16
+ next: {
17
+ ...nextInit.next,
18
+ tags: [
19
+ ...(nextInit.next?.tags ?? []),
20
+ ...createRouteCacheTagsForUrl(url, tagPrefix),
21
+ ],
22
+ },
23
+ };
24
+ return fetchImpl(url, taggedInit);
25
+ };
26
+ };
27
+ export const initNextClient = (contract, options) => {
28
+ const { automaticFetchTags, fetch, ...clientOptions } = options;
29
+ if (!automaticFetchTags?.enabled) {
30
+ return initClient(contract, options);
31
+ }
32
+ const fetchImpl = fetch ?? ((url, init) => globalThis.fetch(url, init));
33
+ return initClient(contract, {
34
+ ...clientOptions,
35
+ fetch: fetchWithTags(fetchImpl, automaticFetchTags.tagPrefix),
36
+ });
37
+ };
38
+ export const getGeneratedTagsForRoute = (route, options) => getRouteCacheTags(route, {
39
+ request: options?.request,
40
+ prefix: options?.tagPrefix,
41
+ });
@@ -0,0 +1,4 @@
1
+ export { ContractResponseError } from "@rest-rpc/web";
2
+ export type { NextClientOptions } from "./client.ts";
3
+ export { getGeneratedTagsForRoute, initNextClient, } from "./client.ts";
4
+ export { type CreateRouteHandlerOptions, createRouteHandler, createRouterHandler, type NextRouteHandlerContext, type NextRouteParseBody, type NextRouteParseBodyInput, type RouteHandler, type RouteRequest, type RouteResponse, } from "./server.ts";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { ContractResponseError } from "@rest-rpc/web";
2
+ export { getGeneratedTagsForRoute, initNextClient, } from "./client.js";
3
+ export { createRouteHandler, createRouterHandler, } from "./server.js";
@@ -0,0 +1,30 @@
1
+ import type { HttpMethod, HttpRouteDeclaration, RequestBodySchema } from "@rest-rpc/core/contract";
2
+ import { type CreateWebHandlerOptions, type RouteHandler as WebRouteHandler, type WebRouteParseBodyInput, type RouteRequest as WebRouteRequest, type RouteResponse as WebRouteResponse } from "@rest-rpc/web";
3
+ type WebContract = HttpRouteDeclaration | {
4
+ [key: string]: WebContract;
5
+ };
6
+ type WebRequestHandler = (request: Request) => Promise<Response>;
7
+ type WebRouterHandlers<TContract extends WebContract> = TContract extends HttpRouteDeclaration ? RouteHandler<TContract> : {
8
+ [K in keyof TContract]: TContract[K] extends WebContract ? WebRouterHandlers<TContract[K]> : never;
9
+ };
10
+ type RouteHandlerMap<E extends HttpRouteDeclaration> = {
11
+ [K in E["method"]]: WebRequestHandler;
12
+ };
13
+ type RouterHandlerMap = {
14
+ [K in HttpMethod]: WebRequestHandler;
15
+ };
16
+ export type NextRouteHandlerContext = {
17
+ request: Request;
18
+ };
19
+ export type RouteRequest<E extends HttpRouteDeclaration> = WebRouteRequest<E, NextRouteHandlerContext>;
20
+ export type RouteResponse<E extends HttpRouteDeclaration> = WebRouteResponse<E>;
21
+ export type RouteHandler<E extends HttpRouteDeclaration> = WebRouteHandler<E, NextRouteHandlerContext>;
22
+ export type NextRouteParseBodyInput = WebRouteParseBodyInput;
23
+ export type NextRouteParseBody = (input: NextRouteParseBodyInput) => unknown | Promise<unknown>;
24
+ export type CreateRouteHandlerOptions = {
25
+ errorHandlers?: CreateWebHandlerOptions<NextRouteHandlerContext>["errorHandlers"];
26
+ parseBody?: NextRouteParseBody;
27
+ };
28
+ export declare function createRouteHandler<E extends HttpRouteDeclaration>(route: E, handler: RouteHandler<E>, options?: CreateRouteHandlerOptions): RouteHandlerMap<E>;
29
+ export declare const createRouterHandler: <const TContract extends WebContract>(contract: TContract, handlers: WebRouterHandlers<TContract>, options?: CreateRouteHandlerOptions) => RouterHandlerMap;
30
+ export type { RequestBodySchema };
package/dist/server.js ADDED
@@ -0,0 +1,26 @@
1
+ import { initWeb, } from "@rest-rpc/web";
2
+ export function createRouteHandler(route, handler, options) {
3
+ const web = initWeb();
4
+ const handle = web.createHandler(web.route(route, handler), {
5
+ errorHandlers: options?.errorHandlers,
6
+ parseBody: options?.parseBody,
7
+ });
8
+ return {
9
+ [route.method]: (request) => handle(request, { request }),
10
+ };
11
+ }
12
+ export const createRouterHandler = (contract, handlers, options) => {
13
+ const web = initWeb();
14
+ const handle = web.createHandler(web.router(contract, handlers), {
15
+ errorHandlers: options?.errorHandlers,
16
+ parseBody: options?.parseBody,
17
+ });
18
+ const nextHandler = (request) => handle(request, { request });
19
+ return {
20
+ DELETE: nextHandler,
21
+ GET: nextHandler,
22
+ PATCH: nextHandler,
23
+ POST: nextHandler,
24
+ PUT: nextHandler,
25
+ };
26
+ };
package/dist/tags.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { HttpRouteDeclaration } from "@rest-rpc/core/contract";
2
+ export declare const getUrlTag: (url: string) => string;
3
+ export declare const getRouteTags: (route: HttpRouteDeclaration, request?: Record<string, unknown>) => string[];
4
+ //# sourceMappingURL=tags.d.ts.map
package/dist/tags.js ADDED
@@ -0,0 +1,11 @@
1
+ import { constructBaseRequest } from "@rest-rpc/core";
2
+ const tagPrefix = "rest-rpc";
3
+ export const getUrlTag = (url) => {
4
+ const parsed = new URL(url, "http://rest-rpc.local");
5
+ return `${tagPrefix}:${parsed.pathname}${parsed.search}`;
6
+ };
7
+ export const getRouteTags = (route, request) => {
8
+ const { url } = constructBaseRequest("", route, request, "strip");
9
+ return [getUrlTag(url)];
10
+ };
11
+ //# sourceMappingURL=tags.js.map
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@rest-rpc/next",
3
+ "version": "0.1.0-beta.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "dependencies": {
18
+ "@rest-rpc/core": "^0.1.0-beta.0",
19
+ "@rest-rpc/web": "^0.1.0-beta.0"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.build.json",
23
+ "test": "node --test src/*.test.ts src/**/*.test.ts",
24
+ "typecheck": "pnpm run build && tsc -p tsconfig.json"
25
+ }
26
+ }