From 58f2cd4a856eaf60a9e1109b280ab77ea5002254 Mon Sep 17 00:00:00 2001 From: netanelC Date: Sun, 7 Jun 2026 18:41:04 +0300 Subject: [PATCH 1/7] feat(openapi-helpers): migrate handler to a dedicated package --- packages/openapi-helpers/package.json | 8 +- .../openapi-typed-request-handler/README.md | 29 + .../api-extractor.json} | 4 +- .../package.json | 54 ++ .../src/common/types.ts | 31 + .../src/index.ts} | 3 +- .../tests/openapi3.yaml | 317 +++++++++ .../tests/test-with-errors.yaml | 59 ++ .../typedRequestHandler.test-d.ts | 2 +- .../tests/types.d.ts | 621 ++++++++++++++++++ .../tsconfig.build.json | 4 + .../tsconfig.json | 8 + .../vitest.config.ts | 14 + pnpm-lock.yaml | 113 ++-- 14 files changed, 1216 insertions(+), 51 deletions(-) create mode 100644 packages/openapi-typed-request-handler/README.md rename packages/{openapi-helpers/api-extractor.handler.json => openapi-typed-request-handler/api-extractor.json} (58%) create mode 100644 packages/openapi-typed-request-handler/package.json create mode 100644 packages/openapi-typed-request-handler/src/common/types.ts rename packages/{openapi-helpers/src/typedRequestHandler/typedRequestHandler.ts => openapi-typed-request-handler/src/index.ts} (95%) create mode 100644 packages/openapi-typed-request-handler/tests/openapi3.yaml create mode 100644 packages/openapi-typed-request-handler/tests/test-with-errors.yaml rename packages/{openapi-helpers => openapi-typed-request-handler}/tests/typedRequestHandler/typedRequestHandler.test-d.ts (94%) create mode 100644 packages/openapi-typed-request-handler/tests/types.d.ts create mode 100644 packages/openapi-typed-request-handler/tsconfig.build.json create mode 100644 packages/openapi-typed-request-handler/tsconfig.json create mode 100644 packages/openapi-typed-request-handler/vitest.config.ts diff --git a/packages/openapi-helpers/package.json b/packages/openapi-helpers/package.json index bca1791a..3d3d7c67 100644 --- a/packages/openapi-helpers/package.json +++ b/packages/openapi-helpers/package.json @@ -8,10 +8,6 @@ "types": "./dist/requestSender/requestSender.d.ts", "default": "./dist/requestSender/requestSender.js" }, - "./typedRequestHandler": { - "types": "./dist/typedRequestHandler/typedRequestHandler.d.ts", - "default": "./dist/typedRequestHandler/typedRequestHandler.js" - }, "./generators": { "types": "./dist/generator/index.d.ts", "default": "./dist/generator/index.js" @@ -28,9 +24,7 @@ "prepack": "turbo run build", "check-dist": "publint && attw --profile node16 --pack .", "knip": "knip --directory ../.. --workspace packages/openapi-helpers", - "api:check": "pnpm run handler:api:check && pnpm run sender:api:check && pnpm run generators:api:check", - "handler:api": "api-extractor run --local --verbose --config ./api-extractor.handler.json", - "handler:api:check": "api-extractor run --verbose --config ./api-extractor.handler.json", + "api:check": "pnpm run sender:api:check && pnpm run generators:api:check", "sender:api": "api-extractor run --local --verbose --config ./api-extractor.sender.json", "sender:api:check": "api-extractor run --verbose --config ./api-extractor.sender.json", "generators:api": "api-extractor run --local --verbose --config ./api-extractor.generators.json", diff --git a/packages/openapi-typed-request-handler/README.md b/packages/openapi-typed-request-handler/README.md new file mode 100644 index 00000000..74c2dbf1 --- /dev/null +++ b/packages/openapi-typed-request-handler/README.md @@ -0,0 +1,29 @@ +# openapi-typed-request-handler + +Express request handler type utilities for building type-safe APIs based on OpenAPI specifications. + +## Installation + +```bash +npm install @map-colonies/openapi-typed-request-handler +``` + +## Usage + +```typescript +import { TypedRequestHandlers } from '@map-colonies/openapi-typed-request-handler'; +import type { paths, operations } from './types.d.ts'; + +// Initialize the TypedRequestHandlers with the paths and operations types +type MyHandlers = TypedRequestHandlers; + +export const getUser: MyHandlers['getUserById'] = (req, res) => { + const id = req.params.id; // Autocomplete! + res.status(200).json({ id, name: 'John' }); // Type-safe response body! +}; + +// Or using method/path mapping +export const getResource: MyHandlers['GET /resource'] = (req, res) => { + res.status(200).json({ id: 1, name: 'name' }); +}; +``` diff --git a/packages/openapi-helpers/api-extractor.handler.json b/packages/openapi-typed-request-handler/api-extractor.json similarity index 58% rename from packages/openapi-helpers/api-extractor.handler.json rename to packages/openapi-typed-request-handler/api-extractor.json index 63f778c6..93a3c0e2 100644 --- a/packages/openapi-helpers/api-extractor.handler.json +++ b/packages/openapi-typed-request-handler/api-extractor.json @@ -1,9 +1,9 @@ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", "extends": "../../api-extractor.json", - "mainEntryPointFilePath": "dist/typedRequestHandler/typedRequestHandler.d.ts", + "mainEntryPointFilePath": "dist/index.d.ts", "apiReport": { "enabled": true, - "reportFileName": "handler." + "reportFileName": "openapi-typed-request-handler.api.md" } } diff --git a/packages/openapi-typed-request-handler/package.json b/packages/openapi-typed-request-handler/package.json new file mode 100644 index 00000000..01fc3dae --- /dev/null +++ b/packages/openapi-typed-request-handler/package.json @@ -0,0 +1,54 @@ +{ + "name": "@map-colonies/openapi-typed-request-handler", + "version": "1.0.0", + "description": "Express request handler type utilities for OpenAPI-defined APIs", + "type": "commonjs", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "scripts": { + "test": "vitest run", + "lint": "eslint .", + "lint:fix": "eslint --fix .", + "prebuild": "pnpm run clean", + "build": "tsc --project tsconfig.build.json", + "clean": "rimraf dist", + "prepack": "turbo run build", + "check-dist": "publint && attw --profile node16 --pack .", + "api:check": "api-extractor run --verbose --config ./api-extractor.json", + "api": "api-extractor run --local --verbose --config ./api-extractor.json" + }, + "repository": "github:MapColonies/infra-packages", + "files": [ + "dist/**/*" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=24" + }, + "dependencies": { + "ts-essentials": "^10.1.1" + }, + "peerDependencies": { + "@types/express": "^4.17.21", + "openapi-typescript": "^7.4.1" + }, + "devDependencies": { + "@map-colonies/eslint-config": "workspace:^", + "@map-colonies/tsconfig": "workspace:^", + "@types/node": "catalog:", + "express": "5.2.1", + "eslint": "catalog:", + "rimraf": "catalog:", + "typescript": "catalog:", + "vitest-config": "workspace:^", + "vitest": "catalog:", + "@microsoft/api-extractor": "catalog:", + "openapi-types": "12.1.3" + } +} diff --git a/packages/openapi-typed-request-handler/src/common/types.ts b/packages/openapi-typed-request-handler/src/common/types.ts new file mode 100644 index 00000000..2596788d --- /dev/null +++ b/packages/openapi-typed-request-handler/src/common/types.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { WritableKeys } from 'ts-essentials'; + +export type AddIfNotNever = [U] extends [never] ? T : T & U; +export type PickWritable> = Pick>; + +export type Methods = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head' | 'options' | 'trace'; + +export type OperationsTemplate = Record; + +export type PathsTemplate = Record< + string, + { + parameters: { + query?: any; + header?: any; + path?: any; + cookie?: any; + }; + } & { + [key in Methods]?: OperationsTemplate; + } +>; + +type HasContent = [T] extends [{ content: any }] ? T['content']['application/json'] : never; + +export type ResponseObjectToFlat = [T] extends [{ responses: any }] + ? { + [res in keyof T['responses']]: { status: res; body: HasContent }; + }[keyof T['responses']] + : never; diff --git a/packages/openapi-helpers/src/typedRequestHandler/typedRequestHandler.ts b/packages/openapi-typed-request-handler/src/index.ts similarity index 95% rename from packages/openapi-helpers/src/typedRequestHandler/typedRequestHandler.ts rename to packages/openapi-typed-request-handler/src/index.ts index 5b104b16..0755d59a 100644 --- a/packages/openapi-helpers/src/typedRequestHandler/typedRequestHandler.ts +++ b/packages/openapi-typed-request-handler/src/index.ts @@ -3,8 +3,7 @@ */ import type { RequestHandler } from 'express'; import type { OptionalKeys } from 'ts-essentials'; -import type { ResponseObjectToFlat } from '../requestSender/types'; -import type { OperationsTemplate, PathsTemplate } from '../common/types'; +import type { OperationsTemplate, PathsTemplate, ResponseObjectToFlat } from './common/types'; // The types only work with any, so we need to disable the eslint rule // It doesn't affect the resulting types as its only used for the condition diff --git a/packages/openapi-typed-request-handler/tests/openapi3.yaml b/packages/openapi-typed-request-handler/tests/openapi3.yaml new file mode 100644 index 00000000..972ea85b --- /dev/null +++ b/packages/openapi-typed-request-handler/tests/openapi3.yaml @@ -0,0 +1,317 @@ +openapi: 3.0.1 +info: + title: config-server + description: This is a config server that provides the means to manage all the configurations + version: 1.0.0 + license: + name: MIT + url: https://opensource.org/licenses/MIT +servers: + - url: / + - url: /api +paths: + /simple-request: + get: + operationId: simpleRequest + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + /request-with-required-query-parameters: + get: + operationId: requestWithRequiredQueryParameters + parameters: + - name: name + in: query + required: true + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + /request-with-optional-query-parameters: + post: + operationId: requestWithOptionalQueryParameters + parameters: + - name: name + in: query + required: false + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + propertyName: + type: string + responses: + '201': + description: OK + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + /request-with-mixed-query-parameters: + get: + operationId: requestWithMixedQueryParameters + parameters: + - name: name + in: query + required: true + schema: + type: string + - name: age + in: query + required: false + schema: + type: number + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + /request-with-path-parameters/{name}: + get: + operationId: requestWithPathParameters + parameters: + - name: name + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + /request-with-empty-response: + get: + operationId: requestWithEmptyResponse + responses: + '204': + description: No Content + /request-with-headers: + get: + operationId: requestWithHeaders + parameters: + - name: name + in: header + required: true + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + /request-with-all/{name}: + post: + operationId: requestWithAll + parameters: + - name: name + in: path + required: true + schema: + type: string + - name: first + in: query + required: true + schema: + type: string + - name: second + in: header + required: true + schema: + type: number + responses: + '201': + description: OK + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + /post-request: + post: + operationId: postRequest + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + message: + type: string + responses: + '201': + description: OK + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + /endpoint-with-multiple-methods: + parameters: + - name: test + in: query + schema: + type: string + get: + operationId: endpointWithMultipleMethodsGet + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + post: + operationId: endpointWithMultipleMethodsPost + responses: + '201': + description: OK + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + /multiple-status-codes: + get: + operationId: multipleStatusCodes + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + number: + type: number + example: 42 + '201': + description: Created + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + type: + type: string + example: test + '400': + description: Bad Request + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + /with-5xx-response: + get: + operationId: with5xxResponse + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + '5xx': + description: Internal Server Error + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! + /optional-request-body: + post: + operationId: optionalRequestBody + requestBody: + required: false + content: + application/json: + schema: + type: object + properties: + message: + type: string + responses: + '201': + description: OK + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Hello, World! diff --git a/packages/openapi-typed-request-handler/tests/test-with-errors.yaml b/packages/openapi-typed-request-handler/tests/test-with-errors.yaml new file mode 100644 index 00000000..bab5c66c --- /dev/null +++ b/packages/openapi-typed-request-handler/tests/test-with-errors.yaml @@ -0,0 +1,59 @@ +openapi: 3.0.0 +info: + title: Test API with Errors + version: 1.0.0 +paths: + /test: + get: + operationId: testOperation + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + properties: + message: + type: string + '400': + description: Bad Request + content: + application/json: + schema: + type: object + properties: + code: + type: string + enum: + - INVALID_INPUT + - MISSING_PARAMETER + message: + type: string + '404': + description: Not Found + content: + application/json: + schema: + type: object + properties: + code: + type: string + enum: + - RESOURCE_NOT_FOUND + message: + type: string + '500': + description: Internal Server Error + content: + application/json: + schema: + type: object + properties: + code: + type: string + enum: + - INTERNAL_ERROR + - DATABASE_ERROR + message: + type: string diff --git a/packages/openapi-helpers/tests/typedRequestHandler/typedRequestHandler.test-d.ts b/packages/openapi-typed-request-handler/tests/typedRequestHandler/typedRequestHandler.test-d.ts similarity index 94% rename from packages/openapi-helpers/tests/typedRequestHandler/typedRequestHandler.test-d.ts rename to packages/openapi-typed-request-handler/tests/typedRequestHandler/typedRequestHandler.test-d.ts index 481bbd8f..f42dbd99 100644 --- a/packages/openapi-helpers/tests/typedRequestHandler/typedRequestHandler.test-d.ts +++ b/packages/openapi-typed-request-handler/tests/typedRequestHandler/typedRequestHandler.test-d.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { expectTypeOf, it, describe } from 'vitest'; -import type { TypedRequestHandlers } from '../../src/typedRequestHandler/typedRequestHandler'; +import type { TypedRequestHandlers } from '../../src/index'; import type { paths, operations } from '../types'; describe('typedRequestHandler', () => { diff --git a/packages/openapi-typed-request-handler/tests/types.d.ts b/packages/openapi-typed-request-handler/tests/types.d.ts new file mode 100644 index 00000000..abd3e852 --- /dev/null +++ b/packages/openapi-typed-request-handler/tests/types.d.ts @@ -0,0 +1,621 @@ +/* eslint-disable */ +export type paths = { + '/simple-request': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations['simpleRequest']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/request-with-required-query-parameters': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations['requestWithRequiredQueryParameters']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/request-with-optional-query-parameters': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations['requestWithOptionalQueryParameters']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/request-with-mixed-query-parameters': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations['requestWithMixedQueryParameters']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/request-with-path-parameters/{name}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations['requestWithPathParameters']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/request-with-empty-response': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations['requestWithEmptyResponse']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/request-with-headers': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations['requestWithHeaders']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/request-with-all/{name}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations['requestWithAll']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/post-request': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations['postRequest']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/endpoint-with-multiple-methods': { + parameters: { + query?: { + test?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + get: operations['endpointWithMultipleMethodsGet']; + put?: never; + post: operations['endpointWithMultipleMethodsPost']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/multiple-status-codes': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations['multipleStatusCodes']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/with-5xx-response': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations['with5xxResponse']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/optional-request-body': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations['optionalRequestBody']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +}; +export type webhooks = Record; +export type components = { + schemas: never; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +}; +export type $defs = Record; +export interface operations { + simpleRequest: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + }; + }; + }; + }; + }; + requestWithRequiredQueryParameters: { + parameters: { + query: { + name: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + }; + }; + }; + }; + }; + requestWithOptionalQueryParameters: { + parameters: { + query?: { + name?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + 'application/json': { + propertyName?: string; + }; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + }; + }; + }; + }; + }; + requestWithMixedQueryParameters: { + parameters: { + query: { + name: string; + age?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + }; + }; + }; + }; + }; + requestWithPathParameters: { + parameters: { + query?: never; + header?: never; + path: { + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + }; + }; + }; + }; + }; + requestWithEmptyResponse: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + requestWithHeaders: { + parameters: { + query?: never; + header: { + name: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + }; + }; + }; + }; + }; + requestWithAll: { + parameters: { + query: { + first: string; + }; + header: { + second: number; + }; + path: { + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + }; + }; + }; + }; + }; + postRequest: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': { + message?: string; + }; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + }; + }; + }; + }; + }; + endpointWithMultipleMethodsGet: { + parameters: { + query?: { + test?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + }; + }; + }; + }; + }; + endpointWithMultipleMethodsPost: { + parameters: { + query?: { + test?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + }; + }; + }; + }; + }; + multipleStatusCodes: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + /** @example 42 */ + number?: number; + }; + }; + }; + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + /** @example test */ + type?: string; + }; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + }; + }; + }; + }; + }; + with5xxResponse: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + }; + }; + }; + /** @description Internal Server Error */ + '5xx': { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + }; + }; + }; + }; + }; + optionalRequestBody: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + 'application/json': { + message?: string; + }; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + /** @example Hello, World! */ + message?: string; + }; + }; + }; + }; + }; +} diff --git a/packages/openapi-typed-request-handler/tsconfig.build.json b/packages/openapi-typed-request-handler/tsconfig.build.json new file mode 100644 index 00000000..f37867a7 --- /dev/null +++ b/packages/openapi-typed-request-handler/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["tests/**"] +} diff --git a/packages/openapi-typed-request-handler/tsconfig.json b/packages/openapi-typed-request-handler/tsconfig.json new file mode 100644 index 00000000..0bb5cd8b --- /dev/null +++ b/packages/openapi-typed-request-handler/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@map-colonies/tsconfig/tsconfig-library", + "compilerOptions": { + "lib": ["ESNext"] + }, + "include": ["src", "tests"], + "exclude": ["dist", "node_modules"] +} diff --git a/packages/openapi-typed-request-handler/vitest.config.ts b/packages/openapi-typed-request-handler/vitest.config.ts new file mode 100644 index 00000000..d8c82e93 --- /dev/null +++ b/packages/openapi-typed-request-handler/vitest.config.ts @@ -0,0 +1,14 @@ +import { mergeConfig, defineProject } from 'vitest/config'; +import sharedConfig from 'vitest-config'; + +export default mergeConfig( + sharedConfig, + defineProject({ + test: { + root: __dirname, + typecheck: { + enabled: true, + }, + }, + }) +); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f6c1271..32251dcd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -615,6 +615,52 @@ importers: specifier: workspace:^ version: link:../../internal/vitest-config + packages/openapi-typed-request-handler: + dependencies: + '@types/express': + specifier: ^4.17.21 + version: 4.17.25 + openapi-typescript: + specifier: ^7.4.1 + version: 7.10.1(typescript@5.9.3) + ts-essentials: + specifier: ^10.1.1 + version: 10.1.1(typescript@5.9.3) + devDependencies: + '@map-colonies/eslint-config': + specifier: workspace:^ + version: link:../eslint-config + '@map-colonies/tsconfig': + specifier: workspace:^ + version: link:../typescript-config + '@microsoft/api-extractor': + specifier: 'catalog:' + version: 7.55.2(@types/node@24.10.9) + '@types/node': + specifier: 'catalog:' + version: 24.10.9 + eslint: + specifier: 'catalog:' + version: 9.39.1(jiti@2.6.1) + express: + specifier: 5.2.1 + version: 5.2.1 + openapi-types: + specifier: 12.1.3 + version: 12.1.3 + rimraf: + specifier: 'catalog:' + version: 6.1.2 + typescript: + specifier: 'catalog:' + version: 5.9.3 + vitest: + specifier: 'catalog:' + version: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.10.9)(@vitest/ui@4.0.18)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(yaml@2.8.2)) + vitest-config: + specifier: workspace:^ + version: link:../../internal/vitest-config + packages/prettier-config: devDependencies: publint: @@ -6439,7 +6485,7 @@ snapshots: '@babel/types': 7.28.6 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -6511,7 +6557,7 @@ snapshots: '@babel/parser': 7.28.6 '@babel/template': 7.28.6 '@babel/types': 7.28.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -6768,7 +6814,7 @@ snapshots: '@eslint/config-array@0.21.1': dependencies: '@eslint/object-schema': 2.1.7 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -6784,7 +6830,7 @@ snapshots: '@eslint/eslintrc@3.3.3': dependencies: ajv: 6.12.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -6904,7 +6950,7 @@ snapshots: '@kwsites/file-exists@1.1.1': dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -8491,7 +8537,7 @@ snapshots: '@typescript-eslint/types': 8.49.0 '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.49.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -8503,7 +8549,7 @@ snapshots: '@typescript-eslint/types': 8.53.0 '@typescript-eslint/typescript-estree': 8.53.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.53.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -8513,7 +8559,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.53.0(typescript@5.9.3) '@typescript-eslint/types': 8.53.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -8522,7 +8568,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.53.0(typescript@5.9.3) '@typescript-eslint/types': 8.53.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -8531,7 +8577,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) '@typescript-eslint/types': 8.55.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -8582,7 +8628,7 @@ snapshots: '@typescript-eslint/types': 8.49.0 '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.1(jiti@2.6.1) ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 @@ -8601,7 +8647,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) '@typescript-eslint/types': 8.49.0 '@typescript-eslint/visitor-keys': 8.49.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 9.0.5 semver: 7.7.3 tinyglobby: 0.2.17 @@ -8616,7 +8662,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.53.0(typescript@5.9.3) '@typescript-eslint/types': 8.53.0 '@typescript-eslint/visitor-keys': 8.53.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 9.0.5 semver: 7.7.3 tinyglobby: 0.2.17 @@ -8631,7 +8677,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) '@typescript-eslint/types': 8.55.0 '@typescript-eslint/visitor-keys': 8.55.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 9.0.5 semver: 7.7.3 tinyglobby: 0.2.17 @@ -8690,7 +8736,7 @@ snapshots: '@typescript/vfs@1.6.2(typescript@5.4.5)': dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) typescript: 5.4.5 transitivePeerDependencies: - supports-color @@ -9157,7 +9203,7 @@ snapshots: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) http-errors: 2.0.1 iconv-lite: 0.7.1 on-finished: 2.4.1 @@ -9452,10 +9498,6 @@ snapshots: dependencies: ms: 2.0.0 - debug@4.4.3: - dependencies: - ms: 2.1.3 - debug@4.4.3(supports-color@10.2.2): dependencies: ms: 2.1.3 @@ -9497,7 +9539,7 @@ snapshots: docker-modem@5.0.7: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) readable-stream: 3.6.2 split-ca: 1.0.1 ssh2: 1.17.0 @@ -9720,7 +9762,7 @@ snapshots: eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.55.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.1(jiti@2.6.1) eslint-import-context: 0.1.9(unrs-resolver@1.11.1) get-tsconfig: 4.13.0 @@ -9737,7 +9779,7 @@ snapshots: dependencies: '@typescript-eslint/types': 8.49.0 comment-parser: 1.4.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.1(jiti@2.6.1) eslint-import-context: 0.1.9(unrs-resolver@1.11.1) is-glob: 4.0.3 @@ -9839,7 +9881,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -9955,7 +9997,7 @@ snapshots: content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 @@ -10042,7 +10084,7 @@ snapshots: finalhandler@2.1.1: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -10151,7 +10193,7 @@ snapshots: gaxios@6.7.1: dependencies: extend: 3.0.2 - https-proxy-agent: 7.0.6 + https-proxy-agent: 7.0.6(supports-color@10.2.2) is-stream: 2.0.1 node-fetch: 2.7.0 uuid: 9.0.1 @@ -10304,13 +10346,6 @@ snapshots: http2-client@1.3.5: {} - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - https-proxy-agent@7.0.6(supports-color@10.2.2): dependencies: agent-base: 7.1.4 @@ -11543,7 +11578,7 @@ snapshots: require-in-the-middle@8.0.1: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) module-details-from-path: 1.0.4 transitivePeerDependencies: - supports-color @@ -11613,7 +11648,7 @@ snapshots: router@2.2.0: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -11693,7 +11728,7 @@ snapshots: send@1.2.1: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -11986,7 +12021,7 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) fast-safe-stringify: 2.1.1 form-data: 4.0.5 formidable: 3.5.4 @@ -12106,7 +12141,7 @@ snapshots: archiver: 7.0.1 async-lock: 1.4.1 byline: 5.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) docker-compose: 1.4.2 dockerode: 5.0.0 get-port: 7.2.0 From 545bb418d613fe8c57c48459ddc6dcf15797579a Mon Sep 17 00:00:00 2001 From: netanelC Date: Sun, 7 Jun 2026 19:08:30 +0300 Subject: [PATCH 2/7] style(openapi-helpers): fix lints --- packages/openapi-typed-request-handler/eslint.config.mjs | 4 ++++ packages/openapi-typed-request-handler/src/common/types.ts | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 packages/openapi-typed-request-handler/eslint.config.mjs diff --git a/packages/openapi-typed-request-handler/eslint.config.mjs b/packages/openapi-typed-request-handler/eslint.config.mjs new file mode 100644 index 00000000..d65b7eef --- /dev/null +++ b/packages/openapi-typed-request-handler/eslint.config.mjs @@ -0,0 +1,4 @@ +import baseConfig from '@map-colonies/eslint-config/ts-base'; +import { defineConfig } from 'eslint/config'; + +export default defineConfig(baseConfig, { ignores: ['vitest.config.ts'] }); diff --git a/packages/openapi-typed-request-handler/src/common/types.ts b/packages/openapi-typed-request-handler/src/common/types.ts index 2596788d..1ee0d1eb 100644 --- a/packages/openapi-typed-request-handler/src/common/types.ts +++ b/packages/openapi-typed-request-handler/src/common/types.ts @@ -1,6 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { WritableKeys } from 'ts-essentials'; +type HasContent = [T] extends [{ content: any }] ? T['content']['application/json'] : never; + export type AddIfNotNever = [U] extends [never] ? T : T & U; export type PickWritable> = Pick>; @@ -22,8 +24,6 @@ export type PathsTemplate = Record< } >; -type HasContent = [T] extends [{ content: any }] ? T['content']['application/json'] : never; - export type ResponseObjectToFlat = [T] extends [{ responses: any }] ? { [res in keyof T['responses']]: { status: res; body: HasContent }; From bac281731c8f0d34d3cfea8e76781a323ebbf172 Mon Sep 17 00:00:00 2001 From: netanelC Date: Tue, 9 Jun 2026 08:26:56 +0300 Subject: [PATCH 3/7] chore(openapi-typed-request-handler): release please --- .release-please-manifest.json | 1 + packages/openapi-typed-request-handler/package.json | 2 +- release-please-config.json | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2d10a295..afa92bf9 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -10,6 +10,7 @@ "packages/read-pkg": "2.0.0", "packages/openapi-express-viewer": "4.1.0", "packages/openapi-helpers": "5.1.0", + "packages/openapi-typed-request-handler": "0.1.0", "packages/semantic-conventions": "1.0.0", "packages/tracing": "1.0.0", "packages/tracing-utils": "1.0.0", diff --git a/packages/openapi-typed-request-handler/package.json b/packages/openapi-typed-request-handler/package.json index 01fc3dae..afe8ba9c 100644 --- a/packages/openapi-typed-request-handler/package.json +++ b/packages/openapi-typed-request-handler/package.json @@ -1,6 +1,6 @@ { "name": "@map-colonies/openapi-typed-request-handler", - "version": "1.0.0", + "version": "0.1.0", "description": "Express request handler type utilities for OpenAPI-defined APIs", "type": "commonjs", "exports": { diff --git a/release-please-config.json b/release-please-config.json index 79ad8eb1..3d22d995 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -102,6 +102,10 @@ "package-name": "@map-colonies/openapi-helpers", "release-type": "node" }, + "packages/openapi-typed-request-handler": { + "package-name": "@map-colonies/openapi-typed-request-handler", + "release-type": "node" + }, "packages/semantic-conventions": { "package-name": "@map-colonies/semantic-conventions", "release-type": "node" From 6d22319af544dfc431b228fc48a059f4f7b6718b Mon Sep 17 00:00:00 2001 From: netanelC Date: Wed, 10 Jun 2026 11:14:14 +0300 Subject: [PATCH 4/7] fix(openapi-typed-request-handler): api extractor --- .../etc/openapi-typed-request-handler.api.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 packages/openapi-typed-request-handler/etc/openapi-typed-request-handler.api.md diff --git a/packages/openapi-typed-request-handler/etc/openapi-typed-request-handler.api.md b/packages/openapi-typed-request-handler/etc/openapi-typed-request-handler.api.md new file mode 100644 index 00000000..08031fbb --- /dev/null +++ b/packages/openapi-typed-request-handler/etc/openapi-typed-request-handler.api.md @@ -0,0 +1,20 @@ +## API Report File for "@map-colonies/openapi-typed-request-handler" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import type { OptionalKeys } from 'ts-essentials'; +import type { RequestHandler } from 'express'; + +// Warning: (ae-forgotten-export) The symbol "PathsTemplate" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "OperationsTemplate" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "OperationHandlers" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "PathHandlers" needs to be exported by the entry point index.d.ts +// +// @public +export type TypedRequestHandlers = OperationHandlers & PathHandlers; + +// (No @packageDocumentation comment for this package) + +``` From 0de0dcceafae77ecff7ad7f0660fae28ee95fbdb Mon Sep 17 00:00:00 2001 From: netanelC Date: Wed, 10 Jun 2026 11:14:43 +0300 Subject: [PATCH 5/7] fix(openapi-typed-request-handler): add to vscode settings Co-authored-by: Copilot --- .vscode/project.code-workspace | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.vscode/project.code-workspace b/.vscode/project.code-workspace index 51815ec5..67f8e763 100644 --- a/.vscode/project.code-workspace +++ b/.vscode/project.code-workspace @@ -52,6 +52,10 @@ "name": "openapi-helpers", "path": "../packages/openapi-helpers", }, + { + "name": "openapi-typed-request-handler", + "path": "../packages/openapi-typed-request-handler", + }, { "name": "semantic-conventions", "path": "../packages/semantic-conventions", From aa22b793f0f8b40650afeaa7168387b44b3f02fa Mon Sep 17 00:00:00 2001 From: netanelC Date: Wed, 10 Jun 2026 11:23:32 +0300 Subject: [PATCH 6/7] fix(openapi-typed-request-handler): pr issues Co-authored-by: Copilot --- packages/openapi-typed-request-handler/package.json | 1 + packages/openapi-typed-request-handler/tsconfig.json | 3 --- .../typedoc.json | 0 3 files changed, 1 insertion(+), 3 deletions(-) rename packages/{vitest-utils => openapi-typed-request-handler}/typedoc.json (100%) diff --git a/packages/openapi-typed-request-handler/package.json b/packages/openapi-typed-request-handler/package.json index afe8ba9c..2b58c90d 100644 --- a/packages/openapi-typed-request-handler/package.json +++ b/packages/openapi-typed-request-handler/package.json @@ -18,6 +18,7 @@ "clean": "rimraf dist", "prepack": "turbo run build", "check-dist": "publint && attw --profile node16 --pack .", + "knip": "knip --directory ../.. --workspace packages/openapi-typed-request-handler", "api:check": "api-extractor run --verbose --config ./api-extractor.json", "api": "api-extractor run --local --verbose --config ./api-extractor.json" }, diff --git a/packages/openapi-typed-request-handler/tsconfig.json b/packages/openapi-typed-request-handler/tsconfig.json index 0bb5cd8b..317c576e 100644 --- a/packages/openapi-typed-request-handler/tsconfig.json +++ b/packages/openapi-typed-request-handler/tsconfig.json @@ -1,8 +1,5 @@ { "extends": "@map-colonies/tsconfig/tsconfig-library", - "compilerOptions": { - "lib": ["ESNext"] - }, "include": ["src", "tests"], "exclude": ["dist", "node_modules"] } diff --git a/packages/vitest-utils/typedoc.json b/packages/openapi-typed-request-handler/typedoc.json similarity index 100% rename from packages/vitest-utils/typedoc.json rename to packages/openapi-typed-request-handler/typedoc.json From 44eb99c07db60a42c369729a10680014d6c28af2 Mon Sep 17 00:00:00 2001 From: netanelC Date: Wed, 10 Jun 2026 11:39:17 +0300 Subject: [PATCH 7/7] fix(openapi-typed-request-handler): knip --- packages/openapi-helpers/tsconfig.json | 3 --- packages/openapi-typed-request-handler/package.json | 3 +-- packages/openapi-typed-request-handler/src/common/types.ts | 5 ----- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/openapi-helpers/tsconfig.json b/packages/openapi-helpers/tsconfig.json index 0bb5cd8b..317c576e 100644 --- a/packages/openapi-helpers/tsconfig.json +++ b/packages/openapi-helpers/tsconfig.json @@ -1,8 +1,5 @@ { "extends": "@map-colonies/tsconfig/tsconfig-library", - "compilerOptions": { - "lib": ["ESNext"] - }, "include": ["src", "tests"], "exclude": ["dist", "node_modules"] } diff --git a/packages/openapi-typed-request-handler/package.json b/packages/openapi-typed-request-handler/package.json index 2b58c90d..7620472e 100644 --- a/packages/openapi-typed-request-handler/package.json +++ b/packages/openapi-typed-request-handler/package.json @@ -49,7 +49,6 @@ "typescript": "catalog:", "vitest-config": "workspace:^", "vitest": "catalog:", - "@microsoft/api-extractor": "catalog:", - "openapi-types": "12.1.3" + "@microsoft/api-extractor": "catalog:" } } diff --git a/packages/openapi-typed-request-handler/src/common/types.ts b/packages/openapi-typed-request-handler/src/common/types.ts index 1ee0d1eb..e69cb6df 100644 --- a/packages/openapi-typed-request-handler/src/common/types.ts +++ b/packages/openapi-typed-request-handler/src/common/types.ts @@ -1,11 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { WritableKeys } from 'ts-essentials'; - type HasContent = [T] extends [{ content: any }] ? T['content']['application/json'] : never; -export type AddIfNotNever = [U] extends [never] ? T : T & U; -export type PickWritable> = Pick>; - export type Methods = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head' | 'options' | 'trace'; export type OperationsTemplate = Record;