diff --git a/examples/express.ts b/examples/express.ts index 9e6f7a0..7bc03d0 100644 --- a/examples/express.ts +++ b/examples/express.ts @@ -1,5 +1,5 @@ import express from "express"; -import { asAsync, typed } from "../src"; +import { asAsync, typed } from "../src/express"; import { pathMap } from "./spec"; const newApp = () => { @@ -9,7 +9,7 @@ const newApp = () => { // ``` // // validatorMiddleware allows to use res.locals.validate method // app.use(validatorMiddleware(pathMap)); - // // wApp is same as app, but with additional type information + // // wApp is same as app, but with additional common information // const wApp = app as TRouter; // ``` const wApp = asAsync(typed(pathMap, app)); diff --git a/examples/fetch.ts b/examples/fetch.ts index c9df37e..a7635c2 100644 --- a/examples/fetch.ts +++ b/examples/fetch.ts @@ -1,12 +1,12 @@ import { PathMap } from "./spec"; -import { TFetch } from "../src"; -import { JSON$stringifyT } from "../src/json"; +import { JSON$stringifyT } from "../src"; import { unreachable } from "../src/utils"; +import FetchT from "../src/fetch"; -const fetchT = fetch as TFetch; +const fetchT = fetch as FetchT; const origin = "http://localhost:3000"; const headers = { "Content-Type": "application/json" }; -// stringify is same as JSON.stringify but with type information +// stringify is same as JSON.stringify but with common information const stringify = JSON.stringify as JSON$stringifyT; const main = async () => { @@ -34,7 +34,7 @@ const main = async () => { { // query parameter example - // TODO: Add type information for query parameter + // TODO: Add common information for query parameter const path = `${origin}/users?page=1`; const method = "get"; const res = await fetchT(path, { method }); @@ -56,7 +56,7 @@ const main = async () => { method, headers, // body is the request schema defined in pathMap["/users"]["post"].body - // stringify is same as JSON.stringify but with type information + // stringify is same as JSON.stringify but with common information body: stringify({ userName: "user1" }), }); if (res.ok) { diff --git a/package.json b/package.json index 43f9ad8..6de9191 100644 --- a/package.json +++ b/package.json @@ -43,10 +43,30 @@ "import": "./dist/index.mjs", "types": "./dist/index.d.ts" }, + "./common": { + "require": "./dist/common/index.js", + "import": "./dist/common/index.mjs", + "types": "./dist/common/index.d.ts" + }, "./express": { - "require": "./dist/express.js", - "import": "./dist/express.mjs", - "types": "./dist/express.d.ts" + "require": "./dist/express/index.js", + "import": "./dist/express/index.mjs", + "types": "./dist/express/index.d.ts" + }, + "./fetch": { + "require": "./dist/fetch/index.js", + "import": "./dist/fetch/index.mjs", + "types": "./dist/fetch/index.d.ts" + }, + "./json": { + "require": "./dist/json/index.js", + "import": "./dist/json/index.mjs", + "types": "./dist/json/index.d.ts" + }, + "./zod": { + "require": "./dist/zod/index.js", + "import": "./dist/zod/index.mjs", + "types": "./dist/zod/index.d.ts" } }, "main": "./dist/index.js", diff --git a/src/hono-types.ts b/src/common/hono-types.ts similarity index 100% rename from src/hono-types.ts rename to src/common/hono-types.ts diff --git a/src/common/index.ts b/src/common/index.ts new file mode 100644 index 0000000..d43c7be --- /dev/null +++ b/src/common/index.ts @@ -0,0 +1,5 @@ +export * from "./hono-types"; +export * from "./query-string"; +export * from "./spec"; +export * from "./type"; +export * from "./url"; diff --git a/src/query-string.ts b/src/common/query-string.ts similarity index 100% rename from src/query-string.ts rename to src/common/query-string.ts diff --git a/src/spec.test.ts b/src/common/spec.test.ts similarity index 100% rename from src/spec.test.ts rename to src/common/spec.test.ts diff --git a/src/spec.ts b/src/common/spec.ts similarity index 83% rename from src/spec.ts rename to src/common/spec.ts index 499dac2..be22bea 100644 --- a/src/spec.ts +++ b/src/common/spec.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { StatusCode } from "./hono-types"; +import { ClientResponse, StatusCode } from "./hono-types"; import { ParseUrlParams } from "./url"; export type ApiResponses = Partial>; @@ -68,3 +68,13 @@ export type Method = (typeof Method)[number]; export type ApiEndpoints = { [K in string]: Partial>>>; }; + +type ApiClientResponses = { + [SC in keyof AResponses & StatusCode]: ClientResponse< + z.infer>, + SC, + "json" + >; +}; +export type MergeApiResponses = + ApiClientResponses[keyof ApiClientResponses]; diff --git a/src/type-test.ts b/src/common/type-test.ts similarity index 100% rename from src/type-test.ts rename to src/common/type-test.ts diff --git a/src/common/type.ts b/src/common/type.ts new file mode 100644 index 0000000..6a6e372 --- /dev/null +++ b/src/common/type.ts @@ -0,0 +1,3 @@ +export type FilterNever> = { + [K in keyof T as T[K] extends never ? never : K]: T[K]; +}; diff --git a/src/url.t-test.ts b/src/common/url.t-test.ts similarity index 87% rename from src/url.t-test.ts rename to src/common/url.t-test.ts index 849b2c3..7e87490 100644 --- a/src/url.t-test.ts +++ b/src/common/url.t-test.ts @@ -1,8 +1,5 @@ import { Equal, Expect } from "./type-test"; -import { OriginPattern, ParseOrigin, ParseURL } from "./url"; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const o: OriginPattern = "https://example.com"; +import { ParseOrigin, ParseURL } from "./url"; // eslint-disable-next-line @typescript-eslint/no-unused-vars type cases = [ diff --git a/src/url.ts b/src/common/url.ts similarity index 100% rename from src/url.ts rename to src/common/url.ts diff --git a/src/express.ts b/src/express/index.ts similarity index 82% rename from src/express.ts rename to src/express/index.ts index c572299..44a1cd5 100644 --- a/src/express.ts +++ b/src/express/index.ts @@ -1,20 +1,26 @@ import { IRouter, RequestHandler, Router } from "express"; -import { ApiEndpoints, ApiResponses, ApiResSchema, ApiSpec, Method } from "./"; -import { Validator, Validators } from "./validator"; +import { + ApiEndpoints, + ApiResponses, + ApiResSchema, + ApiSpec, + Method, +} from "../index"; +import { ZodValidator, ZodValidators } from "../zod"; import { NextFunction, ParamsDictionary, Request, Response, } from "express-serve-static-core"; -import { StatusCode } from "./hono-types"; +import { StatusCode } from "../common"; import { z } from "zod"; -import { ParseUrlParams } from "./url"; +import { ParseUrlParams } from "../common"; -interface ParsedQs { +export interface ParsedQs { [key: string]: undefined | string | string[] | ParsedQs | ParsedQs[]; } -type Handler< +export type Handler< Spec extends ApiSpec | undefined, SC extends keyof NonNullable["res"] & StatusCode = 200, // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -26,7 +32,7 @@ type Handler< next: NextFunction, ) => void; -type ExpressResponse< +export type ExpressResponse< Responses extends ApiResponses, SC extends keyof Responses & StatusCode, // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -40,18 +46,18 @@ type ExpressResponse< ) => Response>, LocalsObj, SC>; }; -type ValidateLocals< +export type ValidateLocals< AS extends ApiSpec | undefined, QueryKeys extends string, > = AS extends ApiSpec ? { validate: ( req: Request, - ) => Validators; + ) => ZodValidators; } : Record; -type TRouter< +export type RouterT< Endpoints extends ApiEndpoints, SC extends StatusCode = StatusCode, > = Omit & { @@ -64,7 +70,7 @@ type TRouter< ValidateLocals> > > - ) => TRouter; + ) => RouterT; }; const validatorMiddleware = (pathMap: ApiEndpoints) => { @@ -78,7 +84,7 @@ const validatorMiddleware = (pathMap: ApiEndpoints) => { export const typed = ( pathMap: Endpoints, router: Router, -): TRouter => { +): RouterT => { router.use(validatorMiddleware(pathMap)); return router; }; @@ -92,21 +98,21 @@ export const newValidator = (endpoints: E) => { return { params: () => spec?.params?.safeParse(req.params) as E[Path][M] extends ApiSpec - ? Validator + ? ZodValidator : undefined, body: () => spec?.body?.safeParse(req.body) as E[Path][M] extends ApiSpec - ? Validator + ? ZodValidator : undefined, query: () => spec?.query?.safeParse(req.query) as E[Path][M] extends ApiSpec - ? Validator + ? ZodValidator : undefined, }; }; }; -type AsyncRequestHandler = ( +export type AsyncRequestHandler = ( req: Parameters>[0], res: Parameters>[1], next: Parameters>[2], @@ -123,13 +129,13 @@ export const wrap = ( const wrapHandlers = (handlers: never[]) => handlers.map((h) => wrap(h) as never); export const asAsync = ( - router: TRouter, -): TRouter => { + router: RouterT, +): RouterT => { return Method.reduce((acc, method) => { return { ...acc, [method]: (path: string, ...handlers: never[]) => router[method](path, ...wrapHandlers(handlers)), }; - }, {} as TRouter); + }, {} as RouterT); }; diff --git a/src/fetch.ts b/src/fetch/index.ts similarity index 53% rename from src/fetch.ts rename to src/fetch/index.ts index e1a8f24..7f47466 100644 --- a/src/fetch.ts +++ b/src/fetch/index.ts @@ -1,22 +1,19 @@ import { ApiBodySchema, ApiEndpoints, - ApiResponses, - ApiResSchema, InferOrUndefined, + MergeApiResponses, Method, -} from "./spec"; -import { StatusCode, ClientResponse } from "./hono-types"; -import { z } from "zod"; +} from "../common"; import { MatchedPatterns, OriginPattern, ParseURL, ToUrlParamPattern, -} from "./url"; -import { TypedString } from "./json"; +} from "../common"; +import { TypedString } from "../json"; -interface TRequestInit< +export interface RequestInitT< M extends Method, // eslint-disable-next-line @typescript-eslint/no-explicit-any Body extends Record | undefined, @@ -25,17 +22,7 @@ interface TRequestInit< body?: TypedString; } -type ApiClientResponses = { - [SC in keyof AResponses & StatusCode]: ClientResponse< - z.infer>, - SC, - "json" - >; -}; -export type MergeApiResponses = - ApiClientResponses[keyof ApiClientResponses]; - -export type TFetch = < +type FetchT = < Input extends | `${Origin}${ToUrlParamPattern}` | `${Origin}${ToUrlParamPattern}?${string}`, @@ -44,6 +31,8 @@ export type TFetch = < M extends Method = "get", >( input: Input, - init?: TRequestInit>>, + init?: RequestInitT>>, // FIXME: NonNullable ) => Promise["res"]>>; + +export default FetchT; diff --git a/src/index.ts b/src/index.ts index df75a7b..af3967d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,21 @@ -export * from "./spec"; -export * from "./fetch"; -export * from "./express"; +export * from "./common"; +export { + asAsync as expressAsAsync, + wrap as expressWrap, + AsyncRequestHandler as ExpressAsyncRequestHandler, + newValidator as expressNewValidator, + typed as expressTyped, + ExpressResponse, + ValidateLocals as ExpressValidateLocals, + RouterT as ExpressRouterT, + Handler as ExpressHandler, + ParsedQs as ExpressParsedQs, +} from "./express"; + +import FetchT, { RequestInitT } from "./fetch"; +export { FetchT, RequestInitT }; + +import JSONT, { JSON$stringifyT } from "./json"; +export { JSONT, JSON$stringifyT }; + +export * from "./zod"; diff --git a/src/json.ts b/src/json/index.ts similarity index 75% rename from src/json.ts rename to src/json/index.ts index 9e8a8fc..112e64a 100644 --- a/src/json.ts +++ b/src/json/index.ts @@ -6,3 +6,9 @@ export type JSON$stringifyT = ( replacer?: undefined, space?: number | string | undefined, ) => TypedString; + +type JSONT = JSON & { + stringify: JSON$stringifyT; +}; + +export default JSONT; diff --git a/src/validator.ts b/src/zod/index.ts similarity index 58% rename from src/validator.ts rename to src/zod/index.ts index a6b48b1..ce6f0ba 100644 --- a/src/validator.ts +++ b/src/zod/index.ts @@ -1,10 +1,11 @@ import { z } from "zod"; -import { ApiSpec } from "./spec"; +import { ApiSpec } from "../common"; +import { FilterNever } from "../common"; type SafeParse = ReturnType; -export type Validator = +export type ZodValidator = V extends z.ZodTypeAny ? () => ReturnType : never; -export type Validators< +export type ZodValidators< AS extends ApiSpec, QueryKeys extends string, > = FilterNever<{ @@ -13,10 +14,6 @@ export type Validators< : AS["params"] extends z.ZodTypeAny ? () => SafeParse : () => SafeParse>>; - query: Validator; - body: Validator; + query: ZodValidator; + body: ZodValidator; }>; - -type FilterNever> = { - [K in keyof T as T[K] extends never ? never : K]: T[K]; -};