Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/typescript-axios-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"test": "jest"
},
"dependencies": {
"@nahkies/typescript-common-runtime": "workspace:^",
"qs": "^6.14.0",
"tslib": "^2.8.1"
},
Expand Down
36 changes: 13 additions & 23 deletions packages/typescript-axios-runtime/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import {
type Encoding,
requestBodyToUrlSearchParams,
} from "@nahkies/typescript-common-runtime/request-bodies/url-search-params"
import type {
HeaderParams,
QueryParams,
} from "@nahkies/typescript-common-runtime/types"
import axios, {
AxiosHeaders,
type AxiosInstance,
Expand All @@ -6,30 +14,12 @@ import axios, {
type RawAxiosRequestHeaders,
} from "axios"
import qs from "qs"
import {
type Encoding,
requestBodyToUrlSearchParams,
} from "./request-bodies/url-search-params"

export type QueryParams = {
[name: string]:
| string
| number
| number[]
| boolean
| string[]
| undefined
| null
| QueryParams
| QueryParams[]
}

export type HeaderParams =
| Record<string, string | number | boolean | undefined | null>
| [string, string | number | boolean | undefined | null][]
| Headers

export type Server<T> = string & {__server__: T}
export type {
HeaderParams,
QueryParams,
Server,
} from "@nahkies/typescript-common-runtime/types"

export interface AbstractAxiosConfig {
axios?: AxiosInstance
Expand Down
6 changes: 5 additions & 1 deletion packages/typescript-axios-runtime/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@
"rootDir": "./src"
},
"include": ["src/**/*"],
"references": []
"references": [
{
"path": "../typescript-common-runtime"
}
]
}
10 changes: 10 additions & 0 deletions packages/typescript-common-runtime/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# @nahkies/typescript-common-runtime

[![CI/CD](https://github.com/mnahkies/openapi-code-generator/actions/workflows/ci.yml/badge.svg)](https://github.com/mnahkies/openapi-code-generator/actions?query=branch%3Amain+event%3Apush)
[![npm](https://img.shields.io/npm/dm/%40nahkies%2Ftypescript-common-runtime.svg)](https://www.npmjs.com/package/@nahkies/typescript-common-runtime)

This is a supporting package for code generated using [@nahkies/openapi-code-generator](https://www.npmjs.com/package/@nahkies/openapi-code-generator) shared across the typescript templates.

You can [read the docs](https://openapi-code-generator.nahkies.co.nz/) to find out more!

It's not intended by be used standalone. Similar in spirit to [tslib](https://www.npmjs.com/package/tslib)
12 changes: 12 additions & 0 deletions packages/typescript-common-runtime/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const base = require("../../jest.base")
const {name: displayName} = require("./package.json")

/**
* @type { import('@jest/types').Config.ProjectConfig }
*/
const config = {
...base,
displayName,
}

module.exports = config
84 changes: 84 additions & 0 deletions packages/typescript-common-runtime/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{
"name": "@nahkies/typescript-common-runtime",
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Annoyingly, it looks like I'll have to generate an API token and manually publish the package for the first time, in order to then configure trusted publishing settings properly.

"version": "0.22.0",
"description": "Runtime package for code generated by @nahkies/openapi-code-generator using the typescript templates",
"license": "MIT",
"author": {
"name": "Michael Nahkies",
"email": "[email protected]"
},
"homepage": "https://openapi-code-generator.nahkies.co.nz/",
"repository": {
"type": "git",
"url": "https://github.com/mnahkies/openapi-code-generator.git",
"directory": "packages/typescript-common-runtime"
},
"bugs": {
"url": "https://github.com/mnahkies/openapi-code-generator/issues"
},
"exports": {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing a single barrel export didn't work, as for ESM consumers I ran into:

error TS1287: A top-level 'export' modifier cannot be used on value declarations in a CommonJS module when 'verbatimModuleSyntax' is enabled.

I don't fully understand why the same code works fine when exporting individual files like this, but it works 🤷‍♂️

"./errors": {
"require": "./dist/errors.js",
"import": "./dist/errors.js",
"types": "./dist/errors.d.ts"
},
"./validation": {
"require": "./dist/validation.js",
"import": "./dist/validation.js",
"types": "./dist/validation.d.ts"
},
"./types": {
"require": "./dist/types.js",
"import": "./dist/types.js",
"types": "./dist/types.d.ts"
},
"./request-bodies/url-search-params": {
"require": "./dist/request-bodies/url-search-params.js",
"import": "./dist/request-bodies/url-search-params.js",
"types": "./dist/request-bodies/url-search-params.d.ts"
}
},
"scripts": {
"clean": "rm -rf ./dist && rm tsconfig.tsbuildinfo",
"build": "tsc -p ./tsconfig.json",
"test": "jest"
},
"dependencies": {
"tslib": "^2.8.1"
},
"peerDependencies": {
"joi": "^17.13.3 || ^18.0.1",
"zod": "^3.25.74 || ^4.1.12"
},
"peerDependenciesMeta": {
"joi": {
"optional": true
},
"zod": {
"optional": true
}
},
"devDependencies": {
"@jest/globals": "^30.2.0",
"jest": "^30.2.0",
"joi": "^18.0.2",
"typescript": "^5.9.3",
"zod": "^3.25.74"
},
"files": [
"src",
"dist",
"README.md",
"CHANGELOG.md",
"tsconfig.json"
],
"keywords": [
"@nahkies/openapi-code-generator",
"openapi",
"runtime",
"typescript"
],
"publishConfig": {
"access": "public"
}
}
19 changes: 19 additions & 0 deletions packages/typescript-common-runtime/src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export enum RequestInputType {
RouteParam = "route params",
QueryString = "querystring",
RequestBody = "request body",
RequestHeader = "request header",
}

export abstract class AbstractRuntimeError extends Error {
protected constructor(
message: string,
cause: unknown,
public readonly phase:
| "request_validation"
| "request_handler"
| "response_validation",
) {
super(message, {cause})
}
}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these moved files were identical to the ones deleted form the other runtime

File renamed without changes.
50 changes: 50 additions & 0 deletions packages/typescript-common-runtime/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// from https://stackoverflow.com/questions/39494689/is-it-possible-to-restrict-number-to-a-certain-range
type Enumerate<
N extends number,
Acc extends number[] = [],
> = Acc["length"] extends N
? Acc[number]
: Enumerate<N, [...Acc, Acc["length"]]>

type IntRange<F extends number, T extends number> = F extends T
? F
: Exclude<Enumerate<T>, Enumerate<F>> extends never
? never
: Exclude<Enumerate<T>, Enumerate<F>> | T

export type StatusCode1xx = IntRange<100, 199>
export type StatusCode2xx = IntRange<200, 299>
export type StatusCode3xx = IntRange<300, 399>
export type StatusCode4xx = IntRange<400, 499>
export type StatusCode5xx = IntRange<500, 599>
export type StatusCode =
| StatusCode1xx
| StatusCode2xx
| StatusCode3xx
| StatusCode4xx
| StatusCode5xx

export type Response<Status extends StatusCode, Type> = {
status: Status
body: Type
}

export type QueryParams = {
[name: string]:
| string
| number
| number[]
| boolean
| string[]
| undefined
| null
| QueryParams
| QueryParams[]
}

export type HeaderParams =
| Record<string, string | number | boolean | undefined | null>
| [string, string | number | boolean | undefined | null][]
| Headers

export type Server<T> = string & {__server__: T}
18 changes: 18 additions & 0 deletions packages/typescript-common-runtime/src/validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export function isMatch(status: number, match: string) {
return (
(/^\d+$/.test(match) && String(status) === match) ||
(/^\d[xX]{2}$/.test(match) && String(status)[0] === match[0])
)
}

export function findMatchingSchema<Schema>(
status: number,
possibleResponses: [string, Schema][],
): Schema | undefined {
for (const [match, schema] of possibleResponses) {
if (isMatch(status, match)) {
return schema
}
}
return undefined
}
9 changes: 9 additions & 0 deletions packages/typescript-common-runtime/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src/**/*"],
"references": []
}
6 changes: 1 addition & 5 deletions packages/typescript-express-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@
"import": "./dist/joi.js",
"types": "./dist/joi.d.ts"
},
"./zod": {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drop deprecated export

"require": "./dist/zod.js",
"import": "./dist/zod.js",
"types": "./dist/zod.d.ts"
},
"./zod-v3": {
"require": "./dist/zod-v3.js",
"import": "./dist/zod-v3.js",
Expand All @@ -55,6 +50,7 @@
"test": "jest"
},
"dependencies": {
"@nahkies/typescript-common-runtime": "workspace:^",
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: I know that lerna won't update these correctly in peerDependencies (lerna/lerna#3671) - but I think it should be ok for dependencies

to be tested as part of an alpha version publish.

"tslib": "^2.8.1"
},
"peerDependencies": {
Expand Down
21 changes: 9 additions & 12 deletions packages/typescript-express-runtime/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
export enum RequestInputType {
RouteParam = "route params",
QueryString = "querystring",
RequestBody = "request body",
RequestHeader = "request header",
}
import {
AbstractRuntimeError,
RequestInputType,
} from "@nahkies/typescript-common-runtime/errors"

export {RequestInputType}

export class ExpressRuntimeError extends Error {
export class ExpressRuntimeError extends AbstractRuntimeError {
private constructor(
message: string,
cause: unknown,
public readonly phase:
| "request_validation"
| "request_handler"
| "response_validation",
phase: "request_validation" | "request_handler" | "response_validation",
) {
super(message, {cause})
super(message, cause, phase)
}

static RequestError(
Expand Down
29 changes: 15 additions & 14 deletions packages/typescript-express-runtime/src/joi.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {findMatchingSchema} from "@nahkies/typescript-common-runtime/validation"

import type {Schema as JoiSchema} from "joi"
import {ExpressRuntimeError, type RequestInputType} from "./errors"

Expand Down Expand Up @@ -45,12 +47,10 @@ export function responseValidationFactory(
possibleResponses.sort((x, y) => (x[0] < y[0] ? -1 : 1))

return (status: number, value: unknown) => {
for (const [match, schema] of possibleResponses) {
const isMatch =
(/^\d+$/.test(match) && String(status) === match) ||
(/^\d[xX]{2}$/.test(match) && String(status)[0] === match[0])
try {
const schema = findMatchingSchema(status, possibleResponses)

if (isMatch) {
if (schema) {
const result = schema.validate(value)

if (result.error) {
Expand All @@ -59,19 +59,20 @@ export function responseValidationFactory(

return result.value
}
}

// TODO: wrap thrown error.
if (defaultResponse) {
const result = defaultResponse.validate(value)
if (defaultResponse) {
const result = defaultResponse.validate(value)

if (result.error) {
throw result.error
}

if (result.error) {
throw result.error
return result.value
}

return result.value
return value
} catch (err) {
throw ExpressRuntimeError.ResponseError(err)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking change: previously joi response validation errors weren't wrapped correctly

}

return value
}
}
Loading