-
Notifications
You must be signed in to change notification settings - Fork 5
feat!: introduce a new common runtime #401
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # @nahkies/typescript-common-runtime | ||
|
|
||
| [](https://github.com/mnahkies/openapi-code-generator/actions?query=branch%3Amain+event%3Apush) | ||
| [](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) |
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| { | ||
| "name": "@nahkies/typescript-common-runtime", | ||
| "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": { | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: 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" | ||
| } | ||
| } | ||
| 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}) | ||
| } | ||
| } |
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
| 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} |
| 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 | ||
| } |
| 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": [] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,11 +32,6 @@ | |
| "import": "./dist/joi.js", | ||
| "types": "./dist/joi.d.ts" | ||
| }, | ||
| "./zod": { | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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", | ||
|
|
@@ -55,6 +50,7 @@ | |
| "test": "jest" | ||
| }, | ||
| "dependencies": { | ||
| "@nahkies/typescript-common-runtime": "workspace:^", | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: I know that lerna won't update these correctly in to be tested as part of an alpha version publish. |
||
| "tslib": "^2.8.1" | ||
| }, | ||
| "peerDependencies": { | ||
|
|
||
| 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" | ||
|
|
||
|
|
@@ -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) { | ||
|
|
@@ -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) | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Breaking change: previously |
||
| } | ||
|
|
||
| return value | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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.