From 9034839999f92edc768f6b9fabf4dbb5095eacec Mon Sep 17 00:00:00 2001 From: Gabriel Massadas Date: Wed, 13 Nov 2024 15:06:44 +0000 Subject: [PATCH 1/4] added docsPageTitle to RouterOptions --- .../workflows/write-prerelease-comment.yml | 4 +- .gitignore | 2 +- dist/index.d.mts | 351 ++++++ dist/index.d.ts | 351 ++++++ dist/index.js | 995 ++++++++++++++++++ dist/index.mjs | 926 ++++++++++++++++ src/openapi.ts | 2 + src/types.ts | 1 + src/ui.ts | 8 +- 9 files changed, 2633 insertions(+), 7 deletions(-) create mode 100644 dist/index.d.mts create mode 100644 dist/index.d.ts create mode 100644 dist/index.js create mode 100644 dist/index.mjs diff --git a/.github/workflows/write-prerelease-comment.yml b/.github/workflows/write-prerelease-comment.yml index 31ab992..f0d4285 100644 --- a/.github/workflows/write-prerelease-comment.yml +++ b/.github/workflows/write-prerelease-comment.yml @@ -43,11 +43,11 @@ jobs: You can install this latest build in your project with: ```sh - npm install --save https://prerelease-registry.devprod.cloudflare.dev/chanfana/runs/${{ env.WORKFLOW_RUN_ID }}/npm-package-itty-router-openapi-${{ env.WORKFLOW_RUN_PR }} + npm install --save https://prerelease-registry.devprod.cloudflare.dev/chanfana/runs/${{ env.WORKFLOW_RUN_ID }}/npm-package-chanfana-${{ env.WORKFLOW_RUN_PR }} ``` Or you can immediately run this with `npx`: ```sh - npx https://prerelease-registry.devprod.cloudflare.dev/chanfana/runs/${{ env.WORKFLOW_RUN_ID }}/npm-package-itty-router-openapi-${{ env.WORKFLOW_RUN_PR }} + npx https://prerelease-registry.devprod.cloudflare.dev/chanfana/runs/${{ env.WORKFLOW_RUN_ID }}/npm-package-chanfana-${{ env.WORKFLOW_RUN_PR }} ``` diff --git a/.gitignore b/.gitignore index d6c47b6..5733d26 100644 --- a/.gitignore +++ b/.gitignore @@ -82,7 +82,7 @@ out # Nuxt.js build / generate output .nuxt -dist +# dist # Gatsby files .cache/ diff --git a/dist/index.d.mts b/dist/index.d.mts new file mode 100644 index 0000000..c62754e --- /dev/null +++ b/dist/index.d.mts @@ -0,0 +1,351 @@ +import * as _asteasolutions_zod_to_openapi from '@asteasolutions/zod-to-openapi'; +import { ZodMediaTypeObject, RouteConfig, OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'; +export { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'; +import * as zod from 'zod'; +import { AnyZodObject, ZodEffects, ZodType, z } from 'zod'; +import * as openapi3_ts_oas30 from 'openapi3-ts/oas30'; +import { OpenAPIObject, HeadersObject as HeadersObject$1, LinksObject as LinksObject$1 } from 'openapi3-ts/oas30'; +import * as openapi3_ts_oas31 from 'openapi3-ts/oas31'; +import { HeadersObject as HeadersObject$2, LinksObject as LinksObject$2 } from 'openapi3-ts/oas31'; + +type Simplify = { + [KeyType in keyof T]: T[KeyType]; +} & {}; +type OpenAPIObjectConfig = Omit; +type OpenAPIObjectConfigV31 = Omit; +type HeadersObject = HeadersObject$1 | HeadersObject$2; +type LinksObject = LinksObject$1 | LinksObject$2; +type ZodMediaType = "application/json" | "text/html" | "text/plain" | "application/xml" | (string & {}); +type ZodContentObject = Partial>; +interface ZodRequestBody { + description?: string; + content: ZodContentObject; + required?: boolean; +} +interface ResponseConfig { + description: string; + headers?: AnyZodObject | HeadersObject; + links?: LinksObject; + content?: ZodContentObject; +} +type RouteParameter = AnyZodObject | ZodEffects | undefined; +interface RouterOptions { + base?: string; + schema?: Partial; + docs_url?: string | null; + redoc_url?: string | null; + openapi_url?: string | null; + raiseUnknownParameters?: boolean; + generateOperationIds?: boolean; + openapiVersion?: "3" | "3.1"; + docsPageTitle?: string | null; +} +interface RouteOptions { + router: any; + raiseUnknownParameters: boolean; +} +interface ParameterType { + default?: string | number | boolean; + description?: string; + example?: string | number | boolean; + required?: boolean; + deprecated?: boolean; +} +interface StringParameterType extends ParameterType { + format?: string; +} +interface EnumerationParameterType extends StringParameterType { + values: Record; + enumCaseSensitive?: boolean; +} +interface RegexParameterType extends StringParameterType { + pattern: RegExp; + patternError?: string; +} +type RequestTypes = { + body?: ZodRequestBody; + params?: AnyZodObject; + query?: AnyZodObject; + cookies?: AnyZodObject; + headers?: AnyZodObject | ZodType[]; +}; +type OpenAPIRouteSchema = Simplify & { + request?: RequestTypes; + responses?: { + [statusCode: string]: ResponseConfig; + }; +}>; +type ValidatedData = S extends OpenAPIRouteSchema ? { + query: GetRequest extends NonNullable> ? GetOutput, "query"> : undefined; + params: GetRequest extends NonNullable> ? GetOutput, "params"> : undefined; + headers: GetRequest extends NonNullable> ? GetOutput, "headers"> : undefined; + body: GetRequest extends NonNullable> ? GetBody, "body">> : undefined; +} : { + query: undefined; + params: undefined; + headers: undefined; + body: undefined; +}; +type GetRequest = T["request"]; +type GetOutput = T extends NonNullable ? T[P] extends AnyZodObject ? z.output : undefined : undefined; +type GetPartBody = T[P] extends ZodRequestBody ? T[P] : undefined; +type GetBody = T extends NonNullable ? T["content"]["application/json"] extends NonNullable ? T["content"]["application/json"]["schema"] extends z.ZodTypeAny ? z.output : undefined : undefined : undefined; + +declare class OpenAPIRoute { + handle(...args: any[]): Response | Promise | object | Promise; + static isRoute: boolean; + args: any[]; + validatedData: any; + params: RouteOptions; + schema: OpenAPIRouteSchema; + constructor(params: RouteOptions); + getValidatedData(): Promise>; + getSchema(): OpenAPIRouteSchema; + getSchemaZod(): OpenAPIRouteSchema; + handleValidationError(errors: z.ZodIssue[]): Response; + execute(...args: any[]): Promise; + validateRequest(request: Request): Promise; +} + +declare class OpenAPIRegistryMerger extends OpenAPIRegistry { + _definitions: object[]; + merge(registry: OpenAPIRegistryMerger): void; +} + +type OpenAPIRouterType = { + original: M; + options: RouterOptions; + registry: OpenAPIRegistryMerger; + delete(path: string, endpoint: typeof OpenAPIRoute): M; + get(path: string, endpoint: typeof OpenAPIRoute): M; + head(path: string, endpoint: typeof OpenAPIRoute): M; + patch(path: string, endpoint: typeof OpenAPIRoute): M; + post(path: string, endpoint: typeof OpenAPIRoute): M; + put(path: string, endpoint: typeof OpenAPIRoute): M; + all(path: string, router: M): M; +}; +declare class OpenAPIHandler { + router: any; + options: RouterOptions; + registry: OpenAPIRegistryMerger; + allowedMethods: string[]; + constructor(router: any, options?: RouterOptions); + createDocsRoutes(): void; + getGeneratedSchema(): any; + registerNestedRouter(params: { + method: string; + path: string; + nestedRouter: any; + }): any[]; + parseRoute(path: string): string; + registerRoute(params: { + method: string; + path: string; + handlers: any[]; + }): any[]; + handleCommonProxy(target: any, prop: string, ...args: any[]): any; + getRequest(args: any[]): void; + getUrlParams(args: any[]): Record; +} + +declare function convertParams(field: any, params: any): M; +declare function Arr(innerType: any, params?: ParameterType): z.ZodArray; +declare function Obj(fields: object, params?: ParameterType): z.ZodObject; +declare function Num(params?: ParameterType): z.ZodNumber; +declare function Int(params?: ParameterType): z.ZodNumber; +declare function Str(params?: ParameterType): z.ZodString; +declare function DateTime(params?: ParameterType): z.ZodString; +declare function Regex(params: RegexParameterType): z.ZodString; +declare function Email(params?: ParameterType): z.ZodString; +declare function Uuid(params?: ParameterType): z.ZodString; +declare function Hostname(params?: ParameterType): z.ZodString; +declare function Ipv4(params?: ParameterType): z.ZodString; +declare function Ipv6(params?: ParameterType): z.ZodString; +declare function Ip(params?: ParameterType): z.ZodString; +declare function DateOnly(params?: ParameterType): z.ZodString; +declare function Bool(params?: ParameterType): z.ZodBoolean; +declare function Enumeration(params: EnumerationParameterType): z.ZodEnum; +declare function coerceInputs(data: Record, schema?: RouteParameter): Record | null; + +declare function getSwaggerUI(schemaUrl: string, docsPageTitle: string): string; +declare function getReDocUI(schemaUrl: string, docsPageTitle: string): string; + +declare function jsonResp(data: any, params?: object): Response; + +type JsonContent = { + content: { + "application/json": { + schema: z.ZodType; + }; + }; +}; +type InferSchemaType = T extends z.ZodType ? z.infer : T; +declare const contentJson: (schema: T) => JsonContent>; + +declare class IttyRouterOpenAPIHandler extends OpenAPIHandler { + getRequest(args: any[]): any; + getUrlParams(args: any[]): Record; +} +declare function fromIttyRouter(router: M, options?: RouterOptions): M & OpenAPIRouterType; + +declare class HonoOpenAPIHandler extends OpenAPIHandler { + getRequest(args: any[]): any; + getUrlParams(args: any[]): Record; +} +declare function fromHono(router: M, options?: RouterOptions): M & OpenAPIRouterType; + +declare function isAnyZodType(schema: object): schema is z.ZodType; +declare function isSpecificZodType(field: any, typeName: string): boolean; +declare function legacyTypeIntoZod(type: any, params?: any): z.ZodType; + +declare class ApiException extends Error { + isVisible: boolean; + message: string; + default_message: string; + status: number; + code: number; + includesPath: boolean; + constructor(message?: string); + buildResponse(): { + code: number; + message: string; + }[]; + static schema(): { + [x: number]: { + content: { + "application/json": { + schema: zod.ZodType<{ + success: boolean; + errors: { + code: number; + message: string; + }[]; + }, zod.ZodTypeDef, { + success: boolean; + errors: { + code: number; + message: string; + }[]; + }>; + }; + }; + description: string; + }; + }; +} +declare class InputValidationException extends ApiException { + isVisible: boolean; + default_message: string; + status: number; + code: number; + path: null; + includesPath: boolean; + constructor(message?: string, path?: any); + buildResponse(): { + code: number; + message: string; + path: null; + }[]; +} +declare class MultiException extends Error { + isVisible: boolean; + errors: Array; + status: number; + constructor(errors: Array); + buildResponse(): ({ + code: number; + message: string; + } | undefined)[]; +} +declare class NotFoundException extends ApiException { + isVisible: boolean; + default_message: string; + status: number; + code: number; +} + +type FilterCondition = { + field: string; + operator: string; + value: string | number | boolean | null; +}; +type ListFilters = { + filters: Array; + options: { + page?: number; + per_page?: number; + order_by?: string; + order_by_direction?: "asc" | "desc"; + }; +}; +type Filters = { + filters: Array; +}; +type UpdateFilters = { + filters: Array; + updatedData: Record; +}; + +declare class UpdateEndpoint extends OpenAPIRoute { + model: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>; + primaryKey?: Array; + pathParameters?: Array; + serializer: (obj: object) => object; + getSchema(): { + servers?: openapi3_ts_oas30.ServerObject[] | undefined; + security?: openapi3_ts_oas30.SecurityRequirementObject[] | openapi3_ts_oas31.SecurityRequirementObject[] | undefined; + tags?: string[] | undefined; + externalDocs?: openapi3_ts_oas30.ExternalDocumentationObject | openapi3_ts_oas31.ExternalDocumentationObject | undefined; + deprecated?: boolean | undefined; + description?: string | undefined; + summary?: string | undefined; + operationId?: string | undefined; + parameters?: (openapi3_ts_oas30.ParameterObject | openapi3_ts_oas30.ReferenceObject)[] | (openapi3_ts_oas31.ParameterObject | openapi3_ts_oas31.ReferenceObject)[] | undefined; + requestBody?: openapi3_ts_oas30.ReferenceObject | openapi3_ts_oas31.ReferenceObject | openapi3_ts_oas30.RequestBodyObject | openapi3_ts_oas31.RequestBodyObject | undefined; + callbacks?: openapi3_ts_oas30.CallbacksObject | openapi3_ts_oas31.CallbacksObject | undefined; + request: RequestTypes | { + body: ZodRequestBody | { + content: { + "application/json": { + schema: z.ZodType<{}, z.ZodTypeDef, {}>; + }; + }; + }; + params: z.AnyZodObject | z.ZodObject, "strip", z.ZodTypeAny, {}, {}>; + query?: z.AnyZodObject; + cookies?: z.AnyZodObject; + headers?: z.AnyZodObject | z.ZodType[]; + }; + responses: { + [statusCode: string]: ResponseConfig; + } | { + "200": { + description: string; + headers?: z.AnyZodObject | (openapi3_ts_oas30.HeadersObject | openapi3_ts_oas31.HeadersObject); + links?: openapi3_ts_oas30.LinksObject | openapi3_ts_oas31.LinksObject; + content: Partial> | { + "application/json": { + schema: z.ZodType<{ + success: BooleanConstructor; + result: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>; + }, z.ZodTypeDef, { + success: BooleanConstructor; + result: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>; + }>; + }; + }; + }; + }; + }; + getFilters(): Promise; + before(oldObj: object, filters: UpdateFilters): Promise; + after(data: object): Promise; + getObject(filters: UpdateFilters): Promise; + update(oldObj: object, filters: UpdateFilters): Promise; + handle(...args: any[]): Promise<{ + success: boolean; + result: object; + }>; +} + +export { ApiException, Arr, Bool, DateOnly, DateTime, Email, Enumeration, type EnumerationParameterType, type FilterCondition, type Filters, HonoOpenAPIHandler, Hostname, InputValidationException, Int, Ip, Ipv4, Ipv6, IttyRouterOpenAPIHandler, type ListFilters, MultiException, NotFoundException, Num, Obj, OpenAPIHandler, type OpenAPIObjectConfig, type OpenAPIObjectConfigV31, OpenAPIRegistryMerger, OpenAPIRoute, type OpenAPIRouteSchema, type OpenAPIRouterType, type ParameterType, Regex, type RegexParameterType, type RequestTypes, type ResponseConfig, type RouteOptions, type RouteParameter, type RouterOptions, type Simplify, Str, type StringParameterType, UpdateEndpoint, type UpdateFilters, Uuid, type ValidatedData, type ZodContentObject, type ZodMediaType, type ZodRequestBody, coerceInputs, contentJson, convertParams, fromHono, fromIttyRouter, getReDocUI, getSwaggerUI, isAnyZodType, isSpecificZodType, jsonResp, legacyTypeIntoZod }; diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..c62754e --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,351 @@ +import * as _asteasolutions_zod_to_openapi from '@asteasolutions/zod-to-openapi'; +import { ZodMediaTypeObject, RouteConfig, OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'; +export { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'; +import * as zod from 'zod'; +import { AnyZodObject, ZodEffects, ZodType, z } from 'zod'; +import * as openapi3_ts_oas30 from 'openapi3-ts/oas30'; +import { OpenAPIObject, HeadersObject as HeadersObject$1, LinksObject as LinksObject$1 } from 'openapi3-ts/oas30'; +import * as openapi3_ts_oas31 from 'openapi3-ts/oas31'; +import { HeadersObject as HeadersObject$2, LinksObject as LinksObject$2 } from 'openapi3-ts/oas31'; + +type Simplify = { + [KeyType in keyof T]: T[KeyType]; +} & {}; +type OpenAPIObjectConfig = Omit; +type OpenAPIObjectConfigV31 = Omit; +type HeadersObject = HeadersObject$1 | HeadersObject$2; +type LinksObject = LinksObject$1 | LinksObject$2; +type ZodMediaType = "application/json" | "text/html" | "text/plain" | "application/xml" | (string & {}); +type ZodContentObject = Partial>; +interface ZodRequestBody { + description?: string; + content: ZodContentObject; + required?: boolean; +} +interface ResponseConfig { + description: string; + headers?: AnyZodObject | HeadersObject; + links?: LinksObject; + content?: ZodContentObject; +} +type RouteParameter = AnyZodObject | ZodEffects | undefined; +interface RouterOptions { + base?: string; + schema?: Partial; + docs_url?: string | null; + redoc_url?: string | null; + openapi_url?: string | null; + raiseUnknownParameters?: boolean; + generateOperationIds?: boolean; + openapiVersion?: "3" | "3.1"; + docsPageTitle?: string | null; +} +interface RouteOptions { + router: any; + raiseUnknownParameters: boolean; +} +interface ParameterType { + default?: string | number | boolean; + description?: string; + example?: string | number | boolean; + required?: boolean; + deprecated?: boolean; +} +interface StringParameterType extends ParameterType { + format?: string; +} +interface EnumerationParameterType extends StringParameterType { + values: Record; + enumCaseSensitive?: boolean; +} +interface RegexParameterType extends StringParameterType { + pattern: RegExp; + patternError?: string; +} +type RequestTypes = { + body?: ZodRequestBody; + params?: AnyZodObject; + query?: AnyZodObject; + cookies?: AnyZodObject; + headers?: AnyZodObject | ZodType[]; +}; +type OpenAPIRouteSchema = Simplify & { + request?: RequestTypes; + responses?: { + [statusCode: string]: ResponseConfig; + }; +}>; +type ValidatedData = S extends OpenAPIRouteSchema ? { + query: GetRequest extends NonNullable> ? GetOutput, "query"> : undefined; + params: GetRequest extends NonNullable> ? GetOutput, "params"> : undefined; + headers: GetRequest extends NonNullable> ? GetOutput, "headers"> : undefined; + body: GetRequest extends NonNullable> ? GetBody, "body">> : undefined; +} : { + query: undefined; + params: undefined; + headers: undefined; + body: undefined; +}; +type GetRequest = T["request"]; +type GetOutput = T extends NonNullable ? T[P] extends AnyZodObject ? z.output : undefined : undefined; +type GetPartBody = T[P] extends ZodRequestBody ? T[P] : undefined; +type GetBody = T extends NonNullable ? T["content"]["application/json"] extends NonNullable ? T["content"]["application/json"]["schema"] extends z.ZodTypeAny ? z.output : undefined : undefined : undefined; + +declare class OpenAPIRoute { + handle(...args: any[]): Response | Promise | object | Promise; + static isRoute: boolean; + args: any[]; + validatedData: any; + params: RouteOptions; + schema: OpenAPIRouteSchema; + constructor(params: RouteOptions); + getValidatedData(): Promise>; + getSchema(): OpenAPIRouteSchema; + getSchemaZod(): OpenAPIRouteSchema; + handleValidationError(errors: z.ZodIssue[]): Response; + execute(...args: any[]): Promise; + validateRequest(request: Request): Promise; +} + +declare class OpenAPIRegistryMerger extends OpenAPIRegistry { + _definitions: object[]; + merge(registry: OpenAPIRegistryMerger): void; +} + +type OpenAPIRouterType = { + original: M; + options: RouterOptions; + registry: OpenAPIRegistryMerger; + delete(path: string, endpoint: typeof OpenAPIRoute): M; + get(path: string, endpoint: typeof OpenAPIRoute): M; + head(path: string, endpoint: typeof OpenAPIRoute): M; + patch(path: string, endpoint: typeof OpenAPIRoute): M; + post(path: string, endpoint: typeof OpenAPIRoute): M; + put(path: string, endpoint: typeof OpenAPIRoute): M; + all(path: string, router: M): M; +}; +declare class OpenAPIHandler { + router: any; + options: RouterOptions; + registry: OpenAPIRegistryMerger; + allowedMethods: string[]; + constructor(router: any, options?: RouterOptions); + createDocsRoutes(): void; + getGeneratedSchema(): any; + registerNestedRouter(params: { + method: string; + path: string; + nestedRouter: any; + }): any[]; + parseRoute(path: string): string; + registerRoute(params: { + method: string; + path: string; + handlers: any[]; + }): any[]; + handleCommonProxy(target: any, prop: string, ...args: any[]): any; + getRequest(args: any[]): void; + getUrlParams(args: any[]): Record; +} + +declare function convertParams(field: any, params: any): M; +declare function Arr(innerType: any, params?: ParameterType): z.ZodArray; +declare function Obj(fields: object, params?: ParameterType): z.ZodObject; +declare function Num(params?: ParameterType): z.ZodNumber; +declare function Int(params?: ParameterType): z.ZodNumber; +declare function Str(params?: ParameterType): z.ZodString; +declare function DateTime(params?: ParameterType): z.ZodString; +declare function Regex(params: RegexParameterType): z.ZodString; +declare function Email(params?: ParameterType): z.ZodString; +declare function Uuid(params?: ParameterType): z.ZodString; +declare function Hostname(params?: ParameterType): z.ZodString; +declare function Ipv4(params?: ParameterType): z.ZodString; +declare function Ipv6(params?: ParameterType): z.ZodString; +declare function Ip(params?: ParameterType): z.ZodString; +declare function DateOnly(params?: ParameterType): z.ZodString; +declare function Bool(params?: ParameterType): z.ZodBoolean; +declare function Enumeration(params: EnumerationParameterType): z.ZodEnum; +declare function coerceInputs(data: Record, schema?: RouteParameter): Record | null; + +declare function getSwaggerUI(schemaUrl: string, docsPageTitle: string): string; +declare function getReDocUI(schemaUrl: string, docsPageTitle: string): string; + +declare function jsonResp(data: any, params?: object): Response; + +type JsonContent = { + content: { + "application/json": { + schema: z.ZodType; + }; + }; +}; +type InferSchemaType = T extends z.ZodType ? z.infer : T; +declare const contentJson: (schema: T) => JsonContent>; + +declare class IttyRouterOpenAPIHandler extends OpenAPIHandler { + getRequest(args: any[]): any; + getUrlParams(args: any[]): Record; +} +declare function fromIttyRouter(router: M, options?: RouterOptions): M & OpenAPIRouterType; + +declare class HonoOpenAPIHandler extends OpenAPIHandler { + getRequest(args: any[]): any; + getUrlParams(args: any[]): Record; +} +declare function fromHono(router: M, options?: RouterOptions): M & OpenAPIRouterType; + +declare function isAnyZodType(schema: object): schema is z.ZodType; +declare function isSpecificZodType(field: any, typeName: string): boolean; +declare function legacyTypeIntoZod(type: any, params?: any): z.ZodType; + +declare class ApiException extends Error { + isVisible: boolean; + message: string; + default_message: string; + status: number; + code: number; + includesPath: boolean; + constructor(message?: string); + buildResponse(): { + code: number; + message: string; + }[]; + static schema(): { + [x: number]: { + content: { + "application/json": { + schema: zod.ZodType<{ + success: boolean; + errors: { + code: number; + message: string; + }[]; + }, zod.ZodTypeDef, { + success: boolean; + errors: { + code: number; + message: string; + }[]; + }>; + }; + }; + description: string; + }; + }; +} +declare class InputValidationException extends ApiException { + isVisible: boolean; + default_message: string; + status: number; + code: number; + path: null; + includesPath: boolean; + constructor(message?: string, path?: any); + buildResponse(): { + code: number; + message: string; + path: null; + }[]; +} +declare class MultiException extends Error { + isVisible: boolean; + errors: Array; + status: number; + constructor(errors: Array); + buildResponse(): ({ + code: number; + message: string; + } | undefined)[]; +} +declare class NotFoundException extends ApiException { + isVisible: boolean; + default_message: string; + status: number; + code: number; +} + +type FilterCondition = { + field: string; + operator: string; + value: string | number | boolean | null; +}; +type ListFilters = { + filters: Array; + options: { + page?: number; + per_page?: number; + order_by?: string; + order_by_direction?: "asc" | "desc"; + }; +}; +type Filters = { + filters: Array; +}; +type UpdateFilters = { + filters: Array; + updatedData: Record; +}; + +declare class UpdateEndpoint extends OpenAPIRoute { + model: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>; + primaryKey?: Array; + pathParameters?: Array; + serializer: (obj: object) => object; + getSchema(): { + servers?: openapi3_ts_oas30.ServerObject[] | undefined; + security?: openapi3_ts_oas30.SecurityRequirementObject[] | openapi3_ts_oas31.SecurityRequirementObject[] | undefined; + tags?: string[] | undefined; + externalDocs?: openapi3_ts_oas30.ExternalDocumentationObject | openapi3_ts_oas31.ExternalDocumentationObject | undefined; + deprecated?: boolean | undefined; + description?: string | undefined; + summary?: string | undefined; + operationId?: string | undefined; + parameters?: (openapi3_ts_oas30.ParameterObject | openapi3_ts_oas30.ReferenceObject)[] | (openapi3_ts_oas31.ParameterObject | openapi3_ts_oas31.ReferenceObject)[] | undefined; + requestBody?: openapi3_ts_oas30.ReferenceObject | openapi3_ts_oas31.ReferenceObject | openapi3_ts_oas30.RequestBodyObject | openapi3_ts_oas31.RequestBodyObject | undefined; + callbacks?: openapi3_ts_oas30.CallbacksObject | openapi3_ts_oas31.CallbacksObject | undefined; + request: RequestTypes | { + body: ZodRequestBody | { + content: { + "application/json": { + schema: z.ZodType<{}, z.ZodTypeDef, {}>; + }; + }; + }; + params: z.AnyZodObject | z.ZodObject, "strip", z.ZodTypeAny, {}, {}>; + query?: z.AnyZodObject; + cookies?: z.AnyZodObject; + headers?: z.AnyZodObject | z.ZodType[]; + }; + responses: { + [statusCode: string]: ResponseConfig; + } | { + "200": { + description: string; + headers?: z.AnyZodObject | (openapi3_ts_oas30.HeadersObject | openapi3_ts_oas31.HeadersObject); + links?: openapi3_ts_oas30.LinksObject | openapi3_ts_oas31.LinksObject; + content: Partial> | { + "application/json": { + schema: z.ZodType<{ + success: BooleanConstructor; + result: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>; + }, z.ZodTypeDef, { + success: BooleanConstructor; + result: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>; + }>; + }; + }; + }; + }; + }; + getFilters(): Promise; + before(oldObj: object, filters: UpdateFilters): Promise; + after(data: object): Promise; + getObject(filters: UpdateFilters): Promise; + update(oldObj: object, filters: UpdateFilters): Promise; + handle(...args: any[]): Promise<{ + success: boolean; + result: object; + }>; +} + +export { ApiException, Arr, Bool, DateOnly, DateTime, Email, Enumeration, type EnumerationParameterType, type FilterCondition, type Filters, HonoOpenAPIHandler, Hostname, InputValidationException, Int, Ip, Ipv4, Ipv6, IttyRouterOpenAPIHandler, type ListFilters, MultiException, NotFoundException, Num, Obj, OpenAPIHandler, type OpenAPIObjectConfig, type OpenAPIObjectConfigV31, OpenAPIRegistryMerger, OpenAPIRoute, type OpenAPIRouteSchema, type OpenAPIRouterType, type ParameterType, Regex, type RegexParameterType, type RequestTypes, type ResponseConfig, type RouteOptions, type RouteParameter, type RouterOptions, type Simplify, Str, type StringParameterType, UpdateEndpoint, type UpdateFilters, Uuid, type ValidatedData, type ZodContentObject, type ZodMediaType, type ZodRequestBody, coerceInputs, contentJson, convertParams, fromHono, fromIttyRouter, getReDocUI, getSwaggerUI, isAnyZodType, isSpecificZodType, jsonResp, legacyTypeIntoZod }; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..7a72513 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,995 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/index.ts +var src_exports = {}; +__export(src_exports, { + ApiException: () => ApiException, + Arr: () => Arr, + Bool: () => Bool, + DateOnly: () => DateOnly, + DateTime: () => DateTime, + Email: () => Email, + Enumeration: () => Enumeration, + HonoOpenAPIHandler: () => HonoOpenAPIHandler, + Hostname: () => Hostname, + InputValidationException: () => InputValidationException, + Int: () => Int, + Ip: () => Ip, + Ipv4: () => Ipv4, + Ipv6: () => Ipv6, + IttyRouterOpenAPIHandler: () => IttyRouterOpenAPIHandler, + MultiException: () => MultiException, + NotFoundException: () => NotFoundException, + Num: () => Num, + Obj: () => Obj, + OpenAPIHandler: () => OpenAPIHandler, + OpenAPIRegistryMerger: () => OpenAPIRegistryMerger, + OpenAPIRoute: () => OpenAPIRoute, + Regex: () => Regex, + Str: () => Str, + UpdateEndpoint: () => UpdateEndpoint, + Uuid: () => Uuid, + coerceInputs: () => coerceInputs, + contentJson: () => contentJson, + convertParams: () => convertParams, + extendZodWithOpenApi: () => import_zod_to_openapi5.extendZodWithOpenApi, + fromHono: () => fromHono, + fromIttyRouter: () => fromIttyRouter, + getReDocUI: () => getReDocUI, + getSwaggerUI: () => getSwaggerUI, + isAnyZodType: () => isAnyZodType, + isSpecificZodType: () => isSpecificZodType, + jsonResp: () => jsonResp, + legacyTypeIntoZod: () => legacyTypeIntoZod +}); +module.exports = __toCommonJS(src_exports); +var import_zod_to_openapi5 = require("@asteasolutions/zod-to-openapi"); + +// src/openapi.ts +var import_zod_to_openapi2 = require("@asteasolutions/zod-to-openapi"); +var import_js_yaml = __toESM(require("js-yaml")); +var import_zod = require("zod"); + +// src/ui.ts +function getSwaggerUI(schemaUrl, docsPageTitle) { + schemaUrl = schemaUrl.replace(/\/+(\/|$)/g, "$1"); + return ` + + + + + + ${docsPageTitle} + + + + +
+ + + + +`; +} +function getReDocUI(schemaUrl, docsPageTitle) { + schemaUrl = schemaUrl.replace(/\/+(\/|$)/g, "$1"); + return ` + + + ${docsPageTitle} + + + + + + + + + + + + + + + `; +} + +// src/zod/registry.ts +var import_zod_to_openapi = require("@asteasolutions/zod-to-openapi"); +var OpenAPIRegistryMerger = class extends import_zod_to_openapi.OpenAPIRegistry { + _definitions = []; + merge(registry) { + if (!registry || !registry._definitions) return; + for (const definition of registry._definitions) { + this._definitions.push({ ...definition }); + } + } +}; + +// src/openapi.ts +var OpenAPIHandler = class { + router; + options; + registry; + allowedMethods = ["get", "head", "post", "put", "delete", "patch"]; + constructor(router, options) { + this.router = router; + this.options = options || {}; + this.registry = new OpenAPIRegistryMerger(); + this.createDocsRoutes(); + } + createDocsRoutes() { + if (this.options?.docs_url !== null && this.options?.openapi_url !== null) { + this.router.get(this.options?.docs_url || "/docs", () => { + return new Response( + getSwaggerUI( + (this.options?.base || "") + (this.options?.openapi_url || "/openapi.json"), + this.options?.docsPageTitle ?? "SwaggerUI" + ), + { + headers: { + "content-type": "text/html; charset=UTF-8" + }, + status: 200 + } + ); + }); + } + if (this.options?.redoc_url !== null && this.options?.openapi_url !== null) { + this.router.get(this.options?.redoc_url || "/redocs", () => { + return new Response( + getReDocUI( + (this.options?.base || "") + (this.options?.openapi_url || "/openapi.json"), + this.options?.docsPageTitle || "ReDocUI" + ), + { + headers: { + "content-type": "text/html; charset=UTF-8" + }, + status: 200 + } + ); + }); + } + if (this.options?.openapi_url !== null) { + this.router.get(this.options?.openapi_url || "/openapi.json", () => { + return new Response(JSON.stringify(this.getGeneratedSchema()), { + headers: { + "content-type": "application/json;charset=UTF-8" + }, + status: 200 + }); + }); + this.router.get( + (this.options?.openapi_url || "/openapi.json").replace( + ".json", + ".yaml" + ), + () => { + return new Response(import_js_yaml.default.dump(this.getGeneratedSchema()), { + headers: { + "content-type": "text/yaml;charset=UTF-8" + }, + status: 200 + }); + } + ); + } + } + getGeneratedSchema() { + let openapiGenerator = import_zod_to_openapi2.OpenApiGeneratorV31; + if (this.options?.openapiVersion === "3") + openapiGenerator = import_zod_to_openapi2.OpenApiGeneratorV3; + const generator = new openapiGenerator(this.registry.definitions); + return generator.generateDocument({ + openapi: this.options?.openapiVersion === "3" ? "3.0.3" : "3.1.0", + info: { + version: this.options?.schema?.info?.version || "1.0.0", + title: this.options?.schema?.info?.title || "OpenAPI", + ...this.options?.schema?.info + }, + ...this.options?.schema + }); + } + registerNestedRouter(params) { + this.registry.merge(params.nestedRouter.registry); + return [params.nestedRouter.fetch]; + } + parseRoute(path) { + return ((this.options.base || "") + path).replaceAll(/\/+(\/|$)/g, "$1").replaceAll(/:(\w+)/g, "{$1}"); + } + registerRoute(params) { + const parsedRoute = this.parseRoute(params.path); + let schema = void 0; + let operationId = void 0; + for (const handler of params.handlers) { + if (handler.name) { + operationId = `${params.method}_${handler.name}`; + } + if (handler.isRoute === true) { + schema = new handler({}).getSchemaZod(); + break; + } + } + if (operationId === void 0) { + operationId = `${params.method}_${parsedRoute.replaceAll("/", "_")}`; + } + if (schema === void 0) { + schema = { + operationId, + responses: { + 200: { + description: "Successful response." + } + } + }; + const parsedParams = ((this.options.base || "") + params.path).match( + /:(\w+)/g + ); + if (parsedParams) { + schema.request = { + // TODO: make sure this works + params: import_zod.z.object( + parsedParams.reduce( + // matched parameters start with ':' so replace the first occurrence with nothing + (obj, item) => Object.assign(obj, { + [item.replace(":", "")]: import_zod.z.string() + }), + {} + ) + ) + }; + } + } else { + if (!schema.operationId) { + if (this.options?.generateOperationIds === false && !schema.operationId) { + throw new Error(`Route ${params.path} don't have operationId set!`); + } + schema.operationId = operationId; + } + } + this.registry.registerPath({ + ...schema, + // @ts-ignore + method: params.method, + path: parsedRoute + }); + return params.handlers.map((handler) => { + if (handler.isRoute) { + return (...params2) => new handler({ + router: this + // raiseUnknownParameters: openapiConfig.raiseUnknownParameters, TODO + }).execute(...params2); + } + return handler; + }); + } + handleCommonProxy(target, prop, ...args) { + if (prop === "middleware") { + return []; + } + if (prop === "isChanfana") { + return true; + } + if (prop === "original") { + return this.router; + } + if (prop === "schema") { + return this.getGeneratedSchema(); + } + if (prop === "registry") { + return this.registry; + } + return void 0; + } + getRequest(args) { + throw new Error("getRequest not implemented"); + } + getUrlParams(args) { + throw new Error("getUrlParams not implemented"); + } +}; + +// src/parameters.ts +var import_zod_to_openapi3 = require("@asteasolutions/zod-to-openapi"); +var import_zod2 = require("zod"); + +// src/zod/utils.ts +function isAnyZodType(schema) { + return schema._def !== void 0; +} +function isSpecificZodType(field, typeName) { + return field._def.typeName === typeName || field._def.innerType?._def.typeName === typeName || field._def.schema?._def.innerType?._def.typeName === typeName || field.unwrap?.()._def.typeName === typeName || field.unwrap?.().unwrap?.()._def.typeName === typeName || field._def.innerType?._def?.innerType?._def?.typeName === typeName; +} +function legacyTypeIntoZod(type, params) { + params = params || {}; + if (type === null) { + return Str({ required: false, ...params }); + } + if (isAnyZodType(type)) { + if (params) { + return convertParams(type, params); + } + return type; + } + if (type === String) { + return Str(params); + } + if (typeof type === "string") { + return Str({ example: type }); + } + if (type === Number) { + return Num(params); + } + if (typeof type === "number") { + return Num({ example: type }); + } + if (type === Boolean) { + return Bool(params); + } + if (typeof type === "boolean") { + return Bool({ example: type }); + } + if (type === Date) { + return DateTime(params); + } + if (Array.isArray(type)) { + if (type.length === 0) { + throw new Error("Arr must have a type"); + } + return Arr(type[0], params); + } + if (typeof type === "object") { + return Obj(type, params); + } + return type(params); +} + +// src/parameters.ts +(0, import_zod_to_openapi3.extendZodWithOpenApi)(import_zod2.z); +function convertParams(field, params) { + params = params || {}; + if (params.required === false) + field = field.optional(); + if (params.description) field = field.describe(params.description); + if (params.default) + field = field.default(params.default); + if (params.example) { + field = field.openapi({ example: params.example }); + } + if (params.format) { + field = field.openapi({ format: params.format }); + } + return field; +} +function Arr(innerType, params) { + return convertParams(legacyTypeIntoZod(innerType).array(), params); +} +function Obj(fields, params) { + const parsed = {}; + for (const [key, value] of Object.entries(fields)) { + parsed[key] = legacyTypeIntoZod(value); + } + return convertParams(import_zod2.z.object(parsed), params); +} +function Num(params) { + return convertParams(import_zod2.z.number(), params).openapi({ + type: "number" + }); +} +function Int(params) { + return convertParams(import_zod2.z.number().int(), params).openapi({ + type: "integer" + }); +} +function Str(params) { + return convertParams(import_zod2.z.string(), params); +} +function DateTime(params) { + return convertParams( + import_zod2.z.string().datetime({ + message: "Must be in the following format: YYYY-mm-ddTHH:MM:ssZ" + }), + params + ); +} +function Regex(params) { + return convertParams( + // @ts-ignore + import_zod2.z.string().regex(params.pattern, params.patternError || "Invalid"), + params + ); +} +function Email(params) { + return convertParams(import_zod2.z.string().email(), params); +} +function Uuid(params) { + return convertParams(import_zod2.z.string().uuid(), params); +} +function Hostname(params) { + return convertParams( + import_zod2.z.string().regex( + /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/ + ), + params + ); +} +function Ipv4(params) { + return convertParams(import_zod2.z.string().ip({ version: "v4" }), params); +} +function Ipv6(params) { + return convertParams(import_zod2.z.string().ip({ version: "v6" }), params); +} +function Ip(params) { + return convertParams(import_zod2.z.string().ip(), params); +} +function DateOnly(params) { + return convertParams(import_zod2.z.date(), params); +} +function Bool(params) { + return convertParams(import_zod2.z.boolean(), params).openapi({ + type: "boolean" + }); +} +function Enumeration(params) { + let { values } = params; + const originalValues = { ...values }; + if (Array.isArray(values)) + values = Object.fromEntries(values.map((x) => [x, x])); + const originalKeys = Object.keys(values); + if (params.enumCaseSensitive === false) { + values = Object.keys(values).reduce((accumulator, key) => { + accumulator[key.toLowerCase()] = values[key]; + return accumulator; + }, {}); + } + const keys = Object.keys(values); + let field; + if ([void 0, true].includes(params.enumCaseSensitive)) { + field = import_zod2.z.enum(keys); + } else { + field = import_zod2.z.preprocess((val) => String(val).toLowerCase(), import_zod2.z.enum(keys)).openapi({ enum: originalKeys }); + } + field = field.transform((val) => values[val]); + const result = convertParams(field, params); + result.values = originalValues; + return result; +} +function coerceInputs(data, schema) { + if (data.size === 0 || data.size === void 0 && typeof data === "object" && Object.keys(data).length === 0) { + return null; + } + const params = {}; + const entries = data.entries ? data.entries() : Object.entries(data); + for (let [key, value] of entries) { + if (value === "") { + value = null; + } + if (params[key] === void 0) { + params[key] = value; + } else if (!Array.isArray(params[key])) { + params[key] = [params[key], value]; + } else { + params[key].push(value); + } + let innerType; + if (schema && schema.shape && schema.shape[key]) { + innerType = schema.shape[key]; + } else if (schema) { + innerType = schema; + } + if (innerType) { + if (isSpecificZodType(innerType, "ZodArray") && !Array.isArray(params[key])) { + params[key] = [params[key]]; + } else if (isSpecificZodType(innerType, "ZodBoolean")) { + const _val = params[key].toLowerCase().trim(); + if (_val === "true" || _val === "false") { + params[key] = _val === "true"; + } + } else if (isSpecificZodType(innerType, "ZodNumber") || innerType instanceof import_zod2.z.ZodNumber) { + params[key] = Number.parseFloat(params[key]); + } else if (isSpecificZodType(innerType, "ZodBigInt") || innerType instanceof import_zod2.z.ZodBigInt) { + params[key] = Number.parseInt(params[key]); + } else if (isSpecificZodType(innerType, "ZodDate") || innerType instanceof import_zod2.z.ZodDate) { + params[key] = new Date(params[key]); + } + } + } + return params; +} + +// src/route.ts +var import_zod_to_openapi4 = require("@asteasolutions/zod-to-openapi"); +var import_zod3 = require("zod"); + +// src/utils.ts +function jsonResp(data, params) { + return new Response(JSON.stringify(data), { + headers: { + "content-type": "application/json;charset=UTF-8" + }, + // @ts-ignore + status: params?.status ? params.status : 200, + ...params + }); +} + +// src/route.ts +(0, import_zod_to_openapi4.extendZodWithOpenApi)(import_zod3.z); +var OpenAPIRoute = class { + handle(...args) { + throw new Error("Method not implemented."); + } + static isRoute = true; + args = []; + // Args the execute() was called with + validatedData = void 0; + // this acts as a cache, in case the users calls the validate method twice + params; + schema = {}; + constructor(params) { + this.params = params; + } + async getValidatedData() { + const request = this.params.router.getRequest(this.args); + if (this.validatedData !== void 0) return this.validatedData; + const data = await this.validateRequest(request); + this.validatedData = data; + return data; + } + getSchema() { + return this.schema; + } + getSchemaZod() { + const schema = { ...this.getSchema() }; + if (!schema.responses) { + schema.responses = { + "200": { + description: "Successful response", + content: { + "application/json": { + schema: {} + } + } + } + }; + } + return schema; + } + handleValidationError(errors) { + return jsonResp( + { + errors, + success: false, + result: {} + }, + { + status: 400 + } + ); + } + async execute(...args) { + this.validatedData = void 0; + this.args = args; + let resp; + try { + resp = await this.handle(...args); + } catch (e) { + if (e instanceof import_zod3.z.ZodError) { + return this.handleValidationError(e.errors); + } + throw e; + } + if (!(resp instanceof Response) && typeof resp === "object") { + return jsonResp(resp); + } + return resp; + } + async validateRequest(request) { + const schema = this.getSchemaZod(); + const unvalidatedData = {}; + const rawSchema = {}; + if (schema.request?.params) { + rawSchema.params = schema.request?.params; + unvalidatedData.params = coerceInputs( + this.params.router.getUrlParams(this.args), + schema.request?.params + ); + } + if (schema.request?.query) { + rawSchema.query = schema.request?.query; + unvalidatedData.query = {}; + } + if (schema.request?.headers) { + rawSchema.headers = schema.request?.headers; + unvalidatedData.headers = {}; + } + const { searchParams } = new URL(request.url); + const queryParams = coerceInputs(searchParams, schema.request?.query); + if (queryParams !== null) unvalidatedData.query = queryParams; + if (schema.request?.headers) { + const tmpHeaders = {}; + for (const header of Object.keys(schema.request?.headers.shape)) { + tmpHeaders[header] = request.headers.get(header); + } + unvalidatedData.headers = coerceInputs( + tmpHeaders, + schema.request?.headers + ); + } + if (request.method.toLowerCase() !== "get" && schema.request?.body && schema.request?.body.content["application/json"] && schema.request?.body.content["application/json"].schema) { + rawSchema.body = schema.request.body.content["application/json"].schema; + try { + unvalidatedData.body = await request.json(); + } catch (e) { + unvalidatedData.body = {}; + } + } + let validationSchema = import_zod3.z.object(rawSchema); + if (this.params?.raiseUnknownParameters === void 0 || this.params?.raiseUnknownParameters === true) { + validationSchema = validationSchema.strict(); + } + return await validationSchema.parseAsync(unvalidatedData); + } +}; + +// src/contentTypes.ts +var import_zod4 = require("zod"); +var contentJson = (schema) => ({ + content: { + "application/json": { + schema: schema instanceof import_zod4.z.ZodType ? schema : legacyTypeIntoZod(schema) + } + } +}); + +// src/adapters/ittyRouter.ts +var IttyRouterOpenAPIHandler = class extends OpenAPIHandler { + getRequest(args) { + return args[0]; + } + getUrlParams(args) { + return args[0].params; + } +}; +function fromIttyRouter(router, options) { + const openapiRouter = new IttyRouterOpenAPIHandler(router, options); + return new Proxy(router, { + get: (target, prop, ...args) => { + const _result = openapiRouter.handleCommonProxy(target, prop, ...args); + if (_result !== void 0) { + return _result; + } + return (route, ...handlers) => { + if (prop !== "fetch") { + if (handlers.length === 1 && handlers[0].isChanfana === true) { + handlers = openapiRouter.registerNestedRouter({ + method: prop, + path: route, + nestedRouter: handlers[0] + }); + } else if (openapiRouter.allowedMethods.includes(prop)) { + handlers = openapiRouter.registerRoute({ + method: prop, + path: route, + handlers + }); + } + } + return Reflect.get(target, prop, ...args)(route, ...handlers); + }; + } + }); +} + +// src/adapters/hono.ts +var HonoOpenAPIHandler = class extends OpenAPIHandler { + getRequest(args) { + return args[0].req.raw; + } + getUrlParams(args) { + return args[0].req.param(); + } +}; +function fromHono(router, options) { + const openapiRouter = new HonoOpenAPIHandler(router, options); + return new Proxy(router, { + get: (target, prop, ...args) => { + const _result = openapiRouter.handleCommonProxy(target, prop, ...args); + if (_result !== void 0) { + return _result; + } + return (route, ...handlers) => { + if (prop !== "fetch") { + if (handlers.length === 1 && handlers[0].isChanfana === true) { + handlers = openapiRouter.registerNestedRouter({ + method: prop, + path: route, + nestedRouter: handlers[0] + }); + } else if (openapiRouter.allowedMethods.includes(prop)) { + handlers = openapiRouter.registerRoute({ + method: prop, + path: route, + handlers + }); + } + } + return Reflect.get(target, prop, ...args)(route, ...handlers); + }; + } + }); +} + +// src/exceptions.ts +var ApiException = class _ApiException extends Error { + isVisible = false; + message; + default_message = "Internal Error"; + status = 500; + code = 7e3; + includesPath = false; + constructor(message) { + super(message); + this.message = message || this.default_message; + } + buildResponse() { + return [ + { + code: this.code, + message: this.isVisible ? this.message : "Internal Error" + } + ]; + } + static schema() { + const inst = new _ApiException(); + const innerError = { + code: inst.code, + message: inst.default_message + }; + if (inst.includesPath === true) { + innerError.path = ["body", "fieldName"]; + } + return { + [inst.status]: { + description: inst.default_message, + ...contentJson({ + success: false, + errors: [innerError] + }) + } + }; + } +}; +var InputValidationException = class extends ApiException { + isVisible = true; + default_message = "Input Validation Error"; + status = 400; + code = 7001; + path = null; + includesPath = true; + constructor(message, path) { + super(message); + this.path = path; + } + buildResponse() { + return [ + { + code: this.code, + message: this.isVisible ? this.message : "Internal Error", + path: this.path + } + ]; + } +}; +var MultiException = class extends Error { + isVisible = true; + errors; + status = 400; + constructor(errors) { + super("Multiple Exceptions"); + this.errors = errors; + for (const err of errors) { + if (err.status > this.status) { + this.status = err.status; + } + if (!err.isVisible && this.isVisible) { + this.isVisible = false; + } + } + } + buildResponse() { + return this.errors.map((err) => err.buildResponse()[0]); + } +}; +var NotFoundException = class extends ApiException { + isVisible = true; + default_message = "Not Found"; + status = 404; + code = 7002; +}; + +// src/endpoints/create.ts +var import_zod5 = require("zod"); + +// src/endpoints/delete.ts +var import_zod6 = require("zod"); + +// src/endpoints/fetch.ts +var import_zod7 = require("zod"); + +// src/endpoints/list.ts +var import_zod8 = require("zod"); + +// src/endpoints/update.ts +var import_zod9 = require("zod"); +var UpdateEndpoint = class extends OpenAPIRoute { + model = import_zod9.z.object({}); + primaryKey; + pathParameters; + serializer = (obj) => obj; + getSchema() { + const bodyParameters = this.model.omit( + (this.pathParameters || []).reduce((a, v) => ({ ...a, [v]: true }), {}) + ); + const pathParameters = this.model.pick( + (this.pathParameters || []).reduce((a, v) => ({ ...a, [v]: true }), {}) + ); + return { + request: { + body: contentJson(bodyParameters), + params: pathParameters, + ...this.schema?.request + }, + responses: { + "200": { + description: "Returns the updated Object", + ...contentJson({ + success: Boolean, + result: this.model + }), + ...this.schema?.responses?.[200] + }, + ...NotFoundException.schema(), + ...this.schema?.responses + }, + ...this.schema + }; + } + async getFilters() { + const data = await this.getValidatedData(); + const filters = []; + const updatedData = {}; + for (const part of [data.params, data.body]) { + if (part) { + for (const [key, value] of Object.entries(part)) { + if ((this.primaryKey || []).includes(key)) { + filters.push({ + field: key, + operator: "EQ", + value + }); + } else { + updatedData[key] = value; + } + } + } + } + return { + filters, + updatedData + }; + } + async before(oldObj, filters) { + return filters; + } + async after(data) { + return data; + } + async getObject(filters) { + return null; + } + async update(oldObj, filters) { + return oldObj; + } + async handle(...args) { + let filters = await this.getFilters(); + const oldObj = await this.getObject(filters); + if (oldObj === null) { + throw new NotFoundException(); + } + filters = await this.before(oldObj, filters); + let obj = await this.update(oldObj, filters); + obj = await this.after(obj); + return { + success: true, + result: this.serializer(obj) + }; + } +}; +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + ApiException, + Arr, + Bool, + DateOnly, + DateTime, + Email, + Enumeration, + HonoOpenAPIHandler, + Hostname, + InputValidationException, + Int, + Ip, + Ipv4, + Ipv6, + IttyRouterOpenAPIHandler, + MultiException, + NotFoundException, + Num, + Obj, + OpenAPIHandler, + OpenAPIRegistryMerger, + OpenAPIRoute, + Regex, + Str, + UpdateEndpoint, + Uuid, + coerceInputs, + contentJson, + convertParams, + extendZodWithOpenApi, + fromHono, + fromIttyRouter, + getReDocUI, + getSwaggerUI, + isAnyZodType, + isSpecificZodType, + jsonResp, + legacyTypeIntoZod +}); diff --git a/dist/index.mjs b/dist/index.mjs new file mode 100644 index 0000000..fd733c5 --- /dev/null +++ b/dist/index.mjs @@ -0,0 +1,926 @@ +// src/index.ts +import { extendZodWithOpenApi as extendZodWithOpenApi3 } from "@asteasolutions/zod-to-openapi"; + +// src/openapi.ts +import { + OpenApiGeneratorV3, + OpenApiGeneratorV31 +} from "@asteasolutions/zod-to-openapi"; +import yaml from "js-yaml"; +import { z } from "zod"; + +// src/ui.ts +function getSwaggerUI(schemaUrl, docsPageTitle) { + schemaUrl = schemaUrl.replace(/\/+(\/|$)/g, "$1"); + return ` + + + + + + ${docsPageTitle} + + + + +
+ + + + +`; +} +function getReDocUI(schemaUrl, docsPageTitle) { + schemaUrl = schemaUrl.replace(/\/+(\/|$)/g, "$1"); + return ` + + + ${docsPageTitle} + + + + + + + + + + + + + + + `; +} + +// src/zod/registry.ts +import { OpenAPIRegistry } from "@asteasolutions/zod-to-openapi"; +var OpenAPIRegistryMerger = class extends OpenAPIRegistry { + _definitions = []; + merge(registry) { + if (!registry || !registry._definitions) return; + for (const definition of registry._definitions) { + this._definitions.push({ ...definition }); + } + } +}; + +// src/openapi.ts +var OpenAPIHandler = class { + router; + options; + registry; + allowedMethods = ["get", "head", "post", "put", "delete", "patch"]; + constructor(router, options) { + this.router = router; + this.options = options || {}; + this.registry = new OpenAPIRegistryMerger(); + this.createDocsRoutes(); + } + createDocsRoutes() { + if (this.options?.docs_url !== null && this.options?.openapi_url !== null) { + this.router.get(this.options?.docs_url || "/docs", () => { + return new Response( + getSwaggerUI( + (this.options?.base || "") + (this.options?.openapi_url || "/openapi.json"), + this.options?.docsPageTitle ?? "SwaggerUI" + ), + { + headers: { + "content-type": "text/html; charset=UTF-8" + }, + status: 200 + } + ); + }); + } + if (this.options?.redoc_url !== null && this.options?.openapi_url !== null) { + this.router.get(this.options?.redoc_url || "/redocs", () => { + return new Response( + getReDocUI( + (this.options?.base || "") + (this.options?.openapi_url || "/openapi.json"), + this.options?.docsPageTitle || "ReDocUI" + ), + { + headers: { + "content-type": "text/html; charset=UTF-8" + }, + status: 200 + } + ); + }); + } + if (this.options?.openapi_url !== null) { + this.router.get(this.options?.openapi_url || "/openapi.json", () => { + return new Response(JSON.stringify(this.getGeneratedSchema()), { + headers: { + "content-type": "application/json;charset=UTF-8" + }, + status: 200 + }); + }); + this.router.get( + (this.options?.openapi_url || "/openapi.json").replace( + ".json", + ".yaml" + ), + () => { + return new Response(yaml.dump(this.getGeneratedSchema()), { + headers: { + "content-type": "text/yaml;charset=UTF-8" + }, + status: 200 + }); + } + ); + } + } + getGeneratedSchema() { + let openapiGenerator = OpenApiGeneratorV31; + if (this.options?.openapiVersion === "3") + openapiGenerator = OpenApiGeneratorV3; + const generator = new openapiGenerator(this.registry.definitions); + return generator.generateDocument({ + openapi: this.options?.openapiVersion === "3" ? "3.0.3" : "3.1.0", + info: { + version: this.options?.schema?.info?.version || "1.0.0", + title: this.options?.schema?.info?.title || "OpenAPI", + ...this.options?.schema?.info + }, + ...this.options?.schema + }); + } + registerNestedRouter(params) { + this.registry.merge(params.nestedRouter.registry); + return [params.nestedRouter.fetch]; + } + parseRoute(path) { + return ((this.options.base || "") + path).replaceAll(/\/+(\/|$)/g, "$1").replaceAll(/:(\w+)/g, "{$1}"); + } + registerRoute(params) { + const parsedRoute = this.parseRoute(params.path); + let schema = void 0; + let operationId = void 0; + for (const handler of params.handlers) { + if (handler.name) { + operationId = `${params.method}_${handler.name}`; + } + if (handler.isRoute === true) { + schema = new handler({}).getSchemaZod(); + break; + } + } + if (operationId === void 0) { + operationId = `${params.method}_${parsedRoute.replaceAll("/", "_")}`; + } + if (schema === void 0) { + schema = { + operationId, + responses: { + 200: { + description: "Successful response." + } + } + }; + const parsedParams = ((this.options.base || "") + params.path).match( + /:(\w+)/g + ); + if (parsedParams) { + schema.request = { + // TODO: make sure this works + params: z.object( + parsedParams.reduce( + // matched parameters start with ':' so replace the first occurrence with nothing + (obj, item) => Object.assign(obj, { + [item.replace(":", "")]: z.string() + }), + {} + ) + ) + }; + } + } else { + if (!schema.operationId) { + if (this.options?.generateOperationIds === false && !schema.operationId) { + throw new Error(`Route ${params.path} don't have operationId set!`); + } + schema.operationId = operationId; + } + } + this.registry.registerPath({ + ...schema, + // @ts-ignore + method: params.method, + path: parsedRoute + }); + return params.handlers.map((handler) => { + if (handler.isRoute) { + return (...params2) => new handler({ + router: this + // raiseUnknownParameters: openapiConfig.raiseUnknownParameters, TODO + }).execute(...params2); + } + return handler; + }); + } + handleCommonProxy(target, prop, ...args) { + if (prop === "middleware") { + return []; + } + if (prop === "isChanfana") { + return true; + } + if (prop === "original") { + return this.router; + } + if (prop === "schema") { + return this.getGeneratedSchema(); + } + if (prop === "registry") { + return this.registry; + } + return void 0; + } + getRequest(args) { + throw new Error("getRequest not implemented"); + } + getUrlParams(args) { + throw new Error("getUrlParams not implemented"); + } +}; + +// src/parameters.ts +import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi"; +import { z as z2 } from "zod"; + +// src/zod/utils.ts +function isAnyZodType(schema) { + return schema._def !== void 0; +} +function isSpecificZodType(field, typeName) { + return field._def.typeName === typeName || field._def.innerType?._def.typeName === typeName || field._def.schema?._def.innerType?._def.typeName === typeName || field.unwrap?.()._def.typeName === typeName || field.unwrap?.().unwrap?.()._def.typeName === typeName || field._def.innerType?._def?.innerType?._def?.typeName === typeName; +} +function legacyTypeIntoZod(type, params) { + params = params || {}; + if (type === null) { + return Str({ required: false, ...params }); + } + if (isAnyZodType(type)) { + if (params) { + return convertParams(type, params); + } + return type; + } + if (type === String) { + return Str(params); + } + if (typeof type === "string") { + return Str({ example: type }); + } + if (type === Number) { + return Num(params); + } + if (typeof type === "number") { + return Num({ example: type }); + } + if (type === Boolean) { + return Bool(params); + } + if (typeof type === "boolean") { + return Bool({ example: type }); + } + if (type === Date) { + return DateTime(params); + } + if (Array.isArray(type)) { + if (type.length === 0) { + throw new Error("Arr must have a type"); + } + return Arr(type[0], params); + } + if (typeof type === "object") { + return Obj(type, params); + } + return type(params); +} + +// src/parameters.ts +extendZodWithOpenApi(z2); +function convertParams(field, params) { + params = params || {}; + if (params.required === false) + field = field.optional(); + if (params.description) field = field.describe(params.description); + if (params.default) + field = field.default(params.default); + if (params.example) { + field = field.openapi({ example: params.example }); + } + if (params.format) { + field = field.openapi({ format: params.format }); + } + return field; +} +function Arr(innerType, params) { + return convertParams(legacyTypeIntoZod(innerType).array(), params); +} +function Obj(fields, params) { + const parsed = {}; + for (const [key, value] of Object.entries(fields)) { + parsed[key] = legacyTypeIntoZod(value); + } + return convertParams(z2.object(parsed), params); +} +function Num(params) { + return convertParams(z2.number(), params).openapi({ + type: "number" + }); +} +function Int(params) { + return convertParams(z2.number().int(), params).openapi({ + type: "integer" + }); +} +function Str(params) { + return convertParams(z2.string(), params); +} +function DateTime(params) { + return convertParams( + z2.string().datetime({ + message: "Must be in the following format: YYYY-mm-ddTHH:MM:ssZ" + }), + params + ); +} +function Regex(params) { + return convertParams( + // @ts-ignore + z2.string().regex(params.pattern, params.patternError || "Invalid"), + params + ); +} +function Email(params) { + return convertParams(z2.string().email(), params); +} +function Uuid(params) { + return convertParams(z2.string().uuid(), params); +} +function Hostname(params) { + return convertParams( + z2.string().regex( + /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/ + ), + params + ); +} +function Ipv4(params) { + return convertParams(z2.string().ip({ version: "v4" }), params); +} +function Ipv6(params) { + return convertParams(z2.string().ip({ version: "v6" }), params); +} +function Ip(params) { + return convertParams(z2.string().ip(), params); +} +function DateOnly(params) { + return convertParams(z2.date(), params); +} +function Bool(params) { + return convertParams(z2.boolean(), params).openapi({ + type: "boolean" + }); +} +function Enumeration(params) { + let { values } = params; + const originalValues = { ...values }; + if (Array.isArray(values)) + values = Object.fromEntries(values.map((x) => [x, x])); + const originalKeys = Object.keys(values); + if (params.enumCaseSensitive === false) { + values = Object.keys(values).reduce((accumulator, key) => { + accumulator[key.toLowerCase()] = values[key]; + return accumulator; + }, {}); + } + const keys = Object.keys(values); + let field; + if ([void 0, true].includes(params.enumCaseSensitive)) { + field = z2.enum(keys); + } else { + field = z2.preprocess((val) => String(val).toLowerCase(), z2.enum(keys)).openapi({ enum: originalKeys }); + } + field = field.transform((val) => values[val]); + const result = convertParams(field, params); + result.values = originalValues; + return result; +} +function coerceInputs(data, schema) { + if (data.size === 0 || data.size === void 0 && typeof data === "object" && Object.keys(data).length === 0) { + return null; + } + const params = {}; + const entries = data.entries ? data.entries() : Object.entries(data); + for (let [key, value] of entries) { + if (value === "") { + value = null; + } + if (params[key] === void 0) { + params[key] = value; + } else if (!Array.isArray(params[key])) { + params[key] = [params[key], value]; + } else { + params[key].push(value); + } + let innerType; + if (schema && schema.shape && schema.shape[key]) { + innerType = schema.shape[key]; + } else if (schema) { + innerType = schema; + } + if (innerType) { + if (isSpecificZodType(innerType, "ZodArray") && !Array.isArray(params[key])) { + params[key] = [params[key]]; + } else if (isSpecificZodType(innerType, "ZodBoolean")) { + const _val = params[key].toLowerCase().trim(); + if (_val === "true" || _val === "false") { + params[key] = _val === "true"; + } + } else if (isSpecificZodType(innerType, "ZodNumber") || innerType instanceof z2.ZodNumber) { + params[key] = Number.parseFloat(params[key]); + } else if (isSpecificZodType(innerType, "ZodBigInt") || innerType instanceof z2.ZodBigInt) { + params[key] = Number.parseInt(params[key]); + } else if (isSpecificZodType(innerType, "ZodDate") || innerType instanceof z2.ZodDate) { + params[key] = new Date(params[key]); + } + } + } + return params; +} + +// src/route.ts +import { extendZodWithOpenApi as extendZodWithOpenApi2 } from "@asteasolutions/zod-to-openapi"; +import { z as z3 } from "zod"; + +// src/utils.ts +function jsonResp(data, params) { + return new Response(JSON.stringify(data), { + headers: { + "content-type": "application/json;charset=UTF-8" + }, + // @ts-ignore + status: params?.status ? params.status : 200, + ...params + }); +} + +// src/route.ts +extendZodWithOpenApi2(z3); +var OpenAPIRoute = class { + handle(...args) { + throw new Error("Method not implemented."); + } + static isRoute = true; + args = []; + // Args the execute() was called with + validatedData = void 0; + // this acts as a cache, in case the users calls the validate method twice + params; + schema = {}; + constructor(params) { + this.params = params; + } + async getValidatedData() { + const request = this.params.router.getRequest(this.args); + if (this.validatedData !== void 0) return this.validatedData; + const data = await this.validateRequest(request); + this.validatedData = data; + return data; + } + getSchema() { + return this.schema; + } + getSchemaZod() { + const schema = { ...this.getSchema() }; + if (!schema.responses) { + schema.responses = { + "200": { + description: "Successful response", + content: { + "application/json": { + schema: {} + } + } + } + }; + } + return schema; + } + handleValidationError(errors) { + return jsonResp( + { + errors, + success: false, + result: {} + }, + { + status: 400 + } + ); + } + async execute(...args) { + this.validatedData = void 0; + this.args = args; + let resp; + try { + resp = await this.handle(...args); + } catch (e) { + if (e instanceof z3.ZodError) { + return this.handleValidationError(e.errors); + } + throw e; + } + if (!(resp instanceof Response) && typeof resp === "object") { + return jsonResp(resp); + } + return resp; + } + async validateRequest(request) { + const schema = this.getSchemaZod(); + const unvalidatedData = {}; + const rawSchema = {}; + if (schema.request?.params) { + rawSchema.params = schema.request?.params; + unvalidatedData.params = coerceInputs( + this.params.router.getUrlParams(this.args), + schema.request?.params + ); + } + if (schema.request?.query) { + rawSchema.query = schema.request?.query; + unvalidatedData.query = {}; + } + if (schema.request?.headers) { + rawSchema.headers = schema.request?.headers; + unvalidatedData.headers = {}; + } + const { searchParams } = new URL(request.url); + const queryParams = coerceInputs(searchParams, schema.request?.query); + if (queryParams !== null) unvalidatedData.query = queryParams; + if (schema.request?.headers) { + const tmpHeaders = {}; + for (const header of Object.keys(schema.request?.headers.shape)) { + tmpHeaders[header] = request.headers.get(header); + } + unvalidatedData.headers = coerceInputs( + tmpHeaders, + schema.request?.headers + ); + } + if (request.method.toLowerCase() !== "get" && schema.request?.body && schema.request?.body.content["application/json"] && schema.request?.body.content["application/json"].schema) { + rawSchema.body = schema.request.body.content["application/json"].schema; + try { + unvalidatedData.body = await request.json(); + } catch (e) { + unvalidatedData.body = {}; + } + } + let validationSchema = z3.object(rawSchema); + if (this.params?.raiseUnknownParameters === void 0 || this.params?.raiseUnknownParameters === true) { + validationSchema = validationSchema.strict(); + } + return await validationSchema.parseAsync(unvalidatedData); + } +}; + +// src/contentTypes.ts +import { z as z4 } from "zod"; +var contentJson = (schema) => ({ + content: { + "application/json": { + schema: schema instanceof z4.ZodType ? schema : legacyTypeIntoZod(schema) + } + } +}); + +// src/adapters/ittyRouter.ts +var IttyRouterOpenAPIHandler = class extends OpenAPIHandler { + getRequest(args) { + return args[0]; + } + getUrlParams(args) { + return args[0].params; + } +}; +function fromIttyRouter(router, options) { + const openapiRouter = new IttyRouterOpenAPIHandler(router, options); + return new Proxy(router, { + get: (target, prop, ...args) => { + const _result = openapiRouter.handleCommonProxy(target, prop, ...args); + if (_result !== void 0) { + return _result; + } + return (route, ...handlers) => { + if (prop !== "fetch") { + if (handlers.length === 1 && handlers[0].isChanfana === true) { + handlers = openapiRouter.registerNestedRouter({ + method: prop, + path: route, + nestedRouter: handlers[0] + }); + } else if (openapiRouter.allowedMethods.includes(prop)) { + handlers = openapiRouter.registerRoute({ + method: prop, + path: route, + handlers + }); + } + } + return Reflect.get(target, prop, ...args)(route, ...handlers); + }; + } + }); +} + +// src/adapters/hono.ts +var HonoOpenAPIHandler = class extends OpenAPIHandler { + getRequest(args) { + return args[0].req.raw; + } + getUrlParams(args) { + return args[0].req.param(); + } +}; +function fromHono(router, options) { + const openapiRouter = new HonoOpenAPIHandler(router, options); + return new Proxy(router, { + get: (target, prop, ...args) => { + const _result = openapiRouter.handleCommonProxy(target, prop, ...args); + if (_result !== void 0) { + return _result; + } + return (route, ...handlers) => { + if (prop !== "fetch") { + if (handlers.length === 1 && handlers[0].isChanfana === true) { + handlers = openapiRouter.registerNestedRouter({ + method: prop, + path: route, + nestedRouter: handlers[0] + }); + } else if (openapiRouter.allowedMethods.includes(prop)) { + handlers = openapiRouter.registerRoute({ + method: prop, + path: route, + handlers + }); + } + } + return Reflect.get(target, prop, ...args)(route, ...handlers); + }; + } + }); +} + +// src/exceptions.ts +var ApiException = class _ApiException extends Error { + isVisible = false; + message; + default_message = "Internal Error"; + status = 500; + code = 7e3; + includesPath = false; + constructor(message) { + super(message); + this.message = message || this.default_message; + } + buildResponse() { + return [ + { + code: this.code, + message: this.isVisible ? this.message : "Internal Error" + } + ]; + } + static schema() { + const inst = new _ApiException(); + const innerError = { + code: inst.code, + message: inst.default_message + }; + if (inst.includesPath === true) { + innerError.path = ["body", "fieldName"]; + } + return { + [inst.status]: { + description: inst.default_message, + ...contentJson({ + success: false, + errors: [innerError] + }) + } + }; + } +}; +var InputValidationException = class extends ApiException { + isVisible = true; + default_message = "Input Validation Error"; + status = 400; + code = 7001; + path = null; + includesPath = true; + constructor(message, path) { + super(message); + this.path = path; + } + buildResponse() { + return [ + { + code: this.code, + message: this.isVisible ? this.message : "Internal Error", + path: this.path + } + ]; + } +}; +var MultiException = class extends Error { + isVisible = true; + errors; + status = 400; + constructor(errors) { + super("Multiple Exceptions"); + this.errors = errors; + for (const err of errors) { + if (err.status > this.status) { + this.status = err.status; + } + if (!err.isVisible && this.isVisible) { + this.isVisible = false; + } + } + } + buildResponse() { + return this.errors.map((err) => err.buildResponse()[0]); + } +}; +var NotFoundException = class extends ApiException { + isVisible = true; + default_message = "Not Found"; + status = 404; + code = 7002; +}; + +// src/endpoints/create.ts +import { z as z5 } from "zod"; + +// src/endpoints/delete.ts +import { z as z6 } from "zod"; + +// src/endpoints/fetch.ts +import { z as z7 } from "zod"; + +// src/endpoints/list.ts +import { z as z8 } from "zod"; + +// src/endpoints/update.ts +import { z as z9 } from "zod"; +var UpdateEndpoint = class extends OpenAPIRoute { + model = z9.object({}); + primaryKey; + pathParameters; + serializer = (obj) => obj; + getSchema() { + const bodyParameters = this.model.omit( + (this.pathParameters || []).reduce((a, v) => ({ ...a, [v]: true }), {}) + ); + const pathParameters = this.model.pick( + (this.pathParameters || []).reduce((a, v) => ({ ...a, [v]: true }), {}) + ); + return { + request: { + body: contentJson(bodyParameters), + params: pathParameters, + ...this.schema?.request + }, + responses: { + "200": { + description: "Returns the updated Object", + ...contentJson({ + success: Boolean, + result: this.model + }), + ...this.schema?.responses?.[200] + }, + ...NotFoundException.schema(), + ...this.schema?.responses + }, + ...this.schema + }; + } + async getFilters() { + const data = await this.getValidatedData(); + const filters = []; + const updatedData = {}; + for (const part of [data.params, data.body]) { + if (part) { + for (const [key, value] of Object.entries(part)) { + if ((this.primaryKey || []).includes(key)) { + filters.push({ + field: key, + operator: "EQ", + value + }); + } else { + updatedData[key] = value; + } + } + } + } + return { + filters, + updatedData + }; + } + async before(oldObj, filters) { + return filters; + } + async after(data) { + return data; + } + async getObject(filters) { + return null; + } + async update(oldObj, filters) { + return oldObj; + } + async handle(...args) { + let filters = await this.getFilters(); + const oldObj = await this.getObject(filters); + if (oldObj === null) { + throw new NotFoundException(); + } + filters = await this.before(oldObj, filters); + let obj = await this.update(oldObj, filters); + obj = await this.after(obj); + return { + success: true, + result: this.serializer(obj) + }; + } +}; +export { + ApiException, + Arr, + Bool, + DateOnly, + DateTime, + Email, + Enumeration, + HonoOpenAPIHandler, + Hostname, + InputValidationException, + Int, + Ip, + Ipv4, + Ipv6, + IttyRouterOpenAPIHandler, + MultiException, + NotFoundException, + Num, + Obj, + OpenAPIHandler, + OpenAPIRegistryMerger, + OpenAPIRoute, + Regex, + Str, + UpdateEndpoint, + Uuid, + coerceInputs, + contentJson, + convertParams, + extendZodWithOpenApi3 as extendZodWithOpenApi, + fromHono, + fromIttyRouter, + getReDocUI, + getSwaggerUI, + isAnyZodType, + isSpecificZodType, + jsonResp, + legacyTypeIntoZod +}; diff --git a/src/openapi.ts b/src/openapi.ts index 6b8384f..867ce92 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -45,6 +45,7 @@ export class OpenAPIHandler { getSwaggerUI( (this.options?.base || "") + (this.options?.openapi_url || "/openapi.json"), + this.options?.docsPageTitle ?? 'SwaggerUI' ), { headers: { @@ -65,6 +66,7 @@ export class OpenAPIHandler { getReDocUI( (this.options?.base || "") + (this.options?.openapi_url || "/openapi.json"), + this.options?.docsPageTitle || "ReDocUI" ), { headers: { diff --git a/src/types.ts b/src/types.ts index 80de1d4..0d60dfc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -62,6 +62,7 @@ export interface RouterOptions { raiseUnknownParameters?: boolean; generateOperationIds?: boolean; openapiVersion?: "3" | "3.1"; + docsPageTitle?: string | null; } export interface RouteOptions { diff --git a/src/ui.ts b/src/ui.ts index f05152b..24c975a 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -1,4 +1,4 @@ -export function getSwaggerUI(schemaUrl: string): string { +export function getSwaggerUI(schemaUrl: string, docsPageTitle: string): string { schemaUrl = schemaUrl.replace(/\/+(\/|$)/g, "$1"); // strip double & trailing splash return ` @@ -6,7 +6,7 @@ export function getSwaggerUI(schemaUrl: string): string { - SwaggerUI + ${docsPageTitle} @@ -30,12 +30,12 @@ export function getSwaggerUI(schemaUrl: string): string { `; } -export function getReDocUI(schemaUrl: string): string { +export function getReDocUI(schemaUrl: string, docsPageTitle: string): string { schemaUrl = schemaUrl.replace(/\/+(\/|$)/g, "$1"); // strip double & trailing splash return ` - ReDocUI + ${docsPageTitle} From 7f361eff397ba67146a6726f645bd33a0974d7fd Mon Sep 17 00:00:00 2001 From: Troy Barnard Date: Thu, 21 Nov 2024 09:03:12 -0800 Subject: [PATCH 2/4] added faviconHref to RouterOptions --- dist/index.d.mts | 5 +++-- dist/index.d.ts | 5 +++-- dist/index.js | 14 ++++++++------ dist/index.mjs | 14 ++++++++------ src/openapi.ts | 6 ++++-- src/types.ts | 1 + src/ui.ts | 8 ++++---- 7 files changed, 31 insertions(+), 22 deletions(-) diff --git a/dist/index.d.mts b/dist/index.d.mts index c62754e..62fcc36 100644 --- a/dist/index.d.mts +++ b/dist/index.d.mts @@ -39,6 +39,7 @@ interface RouterOptions { generateOperationIds?: boolean; openapiVersion?: "3" | "3.1"; docsPageTitle?: string | null; + faviconHref?: string | null; } interface RouteOptions { router: any; @@ -167,8 +168,8 @@ declare function Bool(params?: ParameterType): z.ZodBoolean; declare function Enumeration(params: EnumerationParameterType): z.ZodEnum; declare function coerceInputs(data: Record, schema?: RouteParameter): Record | null; -declare function getSwaggerUI(schemaUrl: string, docsPageTitle: string): string; -declare function getReDocUI(schemaUrl: string, docsPageTitle: string): string; +declare function getSwaggerUI(schemaUrl: string, docsPageTitle: string, faviconHref: string): string; +declare function getReDocUI(schemaUrl: string, docsPageTitle: string, faviconHref: string): string; declare function jsonResp(data: any, params?: object): Response; diff --git a/dist/index.d.ts b/dist/index.d.ts index c62754e..62fcc36 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -39,6 +39,7 @@ interface RouterOptions { generateOperationIds?: boolean; openapiVersion?: "3" | "3.1"; docsPageTitle?: string | null; + faviconHref?: string | null; } interface RouteOptions { router: any; @@ -167,8 +168,8 @@ declare function Bool(params?: ParameterType): z.ZodBoolean; declare function Enumeration(params: EnumerationParameterType): z.ZodEnum; declare function coerceInputs(data: Record, schema?: RouteParameter): Record | null; -declare function getSwaggerUI(schemaUrl: string, docsPageTitle: string): string; -declare function getReDocUI(schemaUrl: string, docsPageTitle: string): string; +declare function getSwaggerUI(schemaUrl: string, docsPageTitle: string, faviconHref: string): string; +declare function getReDocUI(schemaUrl: string, docsPageTitle: string, faviconHref: string): string; declare function jsonResp(data: any, params?: object): Response; diff --git a/dist/index.js b/dist/index.js index 7a72513..7f86d0c 100644 --- a/dist/index.js +++ b/dist/index.js @@ -78,7 +78,7 @@ var import_js_yaml = __toESM(require("js-yaml")); var import_zod = require("zod"); // src/ui.ts -function getSwaggerUI(schemaUrl, docsPageTitle) { +function getSwaggerUI(schemaUrl, docsPageTitle, faviconHref) { schemaUrl = schemaUrl.replace(/\/+(\/|$)/g, "$1"); return ` @@ -88,7 +88,7 @@ function getSwaggerUI(schemaUrl, docsPageTitle) { ${docsPageTitle} - +
@@ -109,7 +109,7 @@ function getSwaggerUI(schemaUrl, docsPageTitle) { `; } -function getReDocUI(schemaUrl, docsPageTitle) { +function getReDocUI(schemaUrl, docsPageTitle, faviconHref) { schemaUrl = schemaUrl.replace(/\/+(\/|$)/g, "$1"); return ` @@ -120,7 +120,7 @@ function getReDocUI(schemaUrl, docsPageTitle) { - +