From 541bf2da210b07cb3c19ad0ec5c814d73eea2bc9 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Thu, 19 Jan 2023 23:04:00 +0700 Subject: [PATCH 01/11] :tada: feat: async plugin --- CHANGELOG.md | 13 ++++ example/a.ts | 20 +++++++ example/lazy/index.ts | 5 ++ package.json | 2 +- src/index.ts | 131 +++++++++++++++++++++++++++++------------ src/router.ts | 27 ++++++--- src/types.ts | 18 ++++-- src/utils.ts | 13 ++-- test/modules.test.ts | 117 ++++++++++++++++++++++++++++++++++++ test/modules.ts | 5 ++ test/timeout.ts | 9 +++ test/transform.test.ts | 16 +++++ 12 files changed, 317 insertions(+), 59 deletions(-) create mode 100644 example/a.ts create mode 100644 example/lazy/index.ts create mode 100644 test/modules.test.ts create mode 100644 test/modules.ts create mode 100644 test/timeout.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index eef9b71a..3fbf3485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# 0.2.0-beta.0 - 17 Jan 2023 +Feature: +- Support for Async / lazy-load plugin + +Improvement: +- Decode URI parameter path parameter +- Handle union type correctly + +# 0.1.3 - 12 Jan 2023 +Improvement: +- Validate `Response` object +- Union type inference on response + # 0.1.2 - 31 Dec 2022 Bug fix: - onRequest doesn't run in `group` and `guard` diff --git a/example/a.ts b/example/a.ts new file mode 100644 index 00000000..cbf2d457 --- /dev/null +++ b/example/a.ts @@ -0,0 +1,20 @@ +import { Elysia, SCHEMA } from '../src' + +const plugin = (app: Elysia) => app.get('/plugin', () => 'Plugin') +const asyncPlugin = async (app: Elysia) => app.get('/async', () => 'A') + +const app = new Elysia() + .use(plugin) + .use(asyncPlugin) + .use(import('./lazy')) + .use((app) => app.get('/inline', () => 'inline')) + .get('/', () => 'local') + .listen(3000) + +await app.modules + +type A = typeof app['store'][typeof SCHEMA] + +console.log( + `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` +) diff --git a/example/lazy/index.ts b/example/lazy/index.ts new file mode 100644 index 00000000..ab17b3b7 --- /dev/null +++ b/example/lazy/index.ts @@ -0,0 +1,5 @@ +import Elysia from "../../src"; + +export const lazy = (app: Elysia) => app.get('/lazy', () => 'Hi') + +export default lazy diff --git a/package.json b/package.json index d2a02a92..88b51f8b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "elysia", "description": "Fast, and friendly Bun web framework", - "version": "0.1.2", + "version": "0.2.0-beta.0", "author": { "name": "saltyAom", "url": "https://github.com/SaltyAom", diff --git a/src/index.ts b/src/index.ts index a499f70a..9149fcf6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -42,7 +42,9 @@ import type { MergeSchema, ListenCallback, NoReturnHandler, - ElysiaRoute + ElysiaRoute, + MaybePromise, + IsNever } from './types' /** @@ -84,12 +86,15 @@ export default class Elysia { private router = new Router() // This router is fallback for catch all route - private fallbackRoute: Partial>> = {} + private fallbackRoute: Partial> = {} protected routes: InternalRoute[] = [] + private lazyLoadModules: Promise[] = [] + constructor(config: Partial = {}) { this.config = { strictPath: false, + queryLimit: 48, ...config } } @@ -562,21 +567,57 @@ export default class Elysia { * ``` */ use< - NewElysia extends Elysia = Elysia, - Params extends Elysia = Elysia + NewElysia extends MaybePromise> = Elysia, + Params extends Elysia = Elysia, + LazyLoadElysia extends never | ElysiaInstance = never >( - plugin: ( - app: Params extends Elysia - ? IsAny extends true - ? this - : Params - : Params - ) => NewElysia - ): NewElysia extends Elysia + plugin: + | MaybePromise< + ( + app: Params extends Elysia + ? IsAny extends true + ? this + : Params + : Params + ) => MaybePromise + > + | Promise<{ + default: ( + elysia: Elysia + ) => MaybePromise> + }> + ): IsNever extends false + ? Elysia + : NewElysia extends Elysia + ? IsNever extends true + ? Elysia + : Elysia + : NewElysia extends Promise> ? Elysia : this { - // ? Type enforce on function already - return plugin(this as unknown as any) as unknown as any + if (plugin instanceof Promise) { + this.lazyLoadModules.push( + plugin.then((plugin) => { + if (typeof plugin === 'function') + return plugin(this as unknown as any) as unknown as any + + return plugin.default( + this as unknown as any + ) as unknown as any + }) + ) + + return this as unknown as any + } + + const instance = plugin(this as unknown as any) as unknown as any + if (instance instanceof Promise) { + this.lazyLoadModules.push(instance) + + return this as unknown as any + } + + return instance } /** @@ -1096,6 +1137,8 @@ export default class Elysia { set }) if (response instanceof Promise) response = await response + + response = mapEarlyResponse(response, set) if (response) return response } @@ -1225,12 +1268,27 @@ export default class Elysia { let response = handler.handle(context) if (response instanceof Promise) response = await response - if (handler.validator?.response?.Check(response) === false) - throw createValidationError( - 'response', - handler.validator.response, - response - ) + if (handler.validator?.response) + if (response instanceof Response) { + let res: string | Object + + // @ts-ignore + if (handler.validator.response.schema.type === 'object') + res = await response.clone().json() + else res = await response.clone().text() + + if (handler.validator.response.Check(res) === false) + throw createValidationError( + 'response', + handler.validator.response, + response + ) + } else if (handler.validator.response.Check(response) === false) + throw createValidationError( + 'response', + handler.validator.response, + response + ) for (let i = 0; i < hooks.afterHandle.length; i++) { let newResponse = hooks.afterHandle[i](context, response) @@ -1332,6 +1390,10 @@ export default class Elysia { if (callback) callback(this.server!) + Promise.all(this.lazyLoadModules).then(() => { + Bun.gc(true) + }) + return this } @@ -1361,25 +1423,14 @@ export default class Elysia { for (let i = 0; i < this.event.stop.length; i++) await this.event.stop[i](this) } -} -// // Hot reload -// if (typeof Bun !== 'undefined') { -// // @ts-ignore -// if (globalThis[key]) -// // @ts-ignore -// globalThis[key].reload({ -// port, -// fetch: fe -// }) -// else { -// // @ts-ignore -// globalThis[key] = Bun.serve({ -// port, -// fetch: fe -// }) -// } -// } + /** + * Wait until all lazy loaded modules all load is fully + */ + get modules() { + return Promise.all(this.lazyLoadModules) + } +} export { Elysia } export { Type as t } from '@sinclair/typebox' @@ -1417,5 +1468,7 @@ export type { UnwrapSchema, LifeCycleStore, VoidLifeCycle, - SchemaValidator + SchemaValidator, + ElysiaRoute, + ExtractPath } from './types' diff --git a/src/router.ts b/src/router.ts index 51a94be6..c8c8833c 100644 --- a/src/router.ts +++ b/src/router.ts @@ -25,10 +25,15 @@ * @see https://github.com/medleyjs/router */ +import type { ComposedHandler } from '../src' import { getPath } from './utils' export interface FindResult { - store: Record + store: Partial<{ + [k in string]: ComposedHandler + }> & Partial<{ + wildcardStore?: Map | null + }> params: Record } @@ -215,6 +220,7 @@ export class Router { if (node.wildcardStore === null) node.wildcardStore = Object.create(null) + // @ts-ignore return node.wildcardStore! } @@ -298,10 +304,12 @@ function matchRoute( if (slashIndex === -1 || slashIndex >= urlLength) { if (node.parametricChild.store) { const params: Record = {} // This is much faster than using a computed property - params[node.parametricChild.paramName] = url.slice( - pathPartEndIndex, - urlLength - ) + + let paramData = url.slice(pathPartEndIndex, urlLength) + if (paramData.includes('%')) + paramData = decodeURI(paramData) + + params[node.parametricChild.paramName] = paramData return { store: node.parametricChild.store, params @@ -316,10 +324,11 @@ function matchRoute( ) if (route) { - route.params[node.parametricChild.paramName] = url.slice( - pathPartEndIndex, - slashIndex - ) + let paramData = url.slice(pathPartEndIndex, slashIndex) + if (paramData.includes('%')) + paramData = decodeURI(paramData) + + route.params[node.parametricChild.paramName] = paramData return route } diff --git a/src/types.ts b/src/types.ts index 2f03167e..48c79414 100644 --- a/src/types.ts +++ b/src/types.ts @@ -35,7 +35,7 @@ export type Handler< CatchResponse = Route['response'] > = ( context: Context & Instance['request'] -) => CatchResponse extends Route['response'] +) => Route['response'] extends CatchResponse ? CatchResponse | Promise | Response : Route['response'] | Promise | Response @@ -105,7 +105,7 @@ export type BeforeRequestHandler< Store extends ElysiaInstance['store'] = ElysiaInstance['store'] > = ( context: PreContext -) => void | Promise | Response | Promise +) => any export interface RegisteredHook< Instance extends ElysiaInstance = ElysiaInstance @@ -244,11 +244,11 @@ export type ElysiaRoute< Record< Method, RouteToSchema & { - response: CatchResponse extends RouteToSchema< + response: RouteToSchema< Schema, Instance, Path - >['response'] + >['response'] extends CatchResponse ? CatchResponse : RouteToSchema['response'] } @@ -296,6 +296,12 @@ export interface ElysiaConfig { * @default false */ strictPath: boolean + /** + * Maximum querystring + * + * @default 48 + */ + queryLimit: number serve?: Partial } @@ -424,3 +430,7 @@ export type IsAny = unknown extends T ? false : true : false + +export type MaybePromise = T | Promise + +export type IsNever = [T] extends [never] ? true : false diff --git a/src/utils.ts b/src/utils.ts index 49a079dd..cc4b7613 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -8,7 +8,8 @@ import type { DeepMergeTwoTypes, LifeCycleStore, LocalHook, - TypedSchema + TypedSchema, + RegisteredHook } from './types' // ? Internal property @@ -22,7 +23,7 @@ export const mergeObjectArray = (a: T | T[], b: T | T[]): T[] => [ export const mergeHook = ( a: LocalHook | LifeCycleStore, b: LocalHook -): LocalHook => { +): RegisteredHook => { const aSchema = 'schema' in a ? (a.schema as TypedSchema) : null const bSchema = b && 'schema' in b ? b.schema : null @@ -37,7 +38,7 @@ export const mergeHook = ( query: bSchema?.query ?? aSchema?.query, response: bSchema?.response ?? aSchema?.response } as TypedSchema) - : null, + : undefined, transform: mergeObjectArray(a.transform ?? [], b?.transform ?? []), beforeHandle: mergeObjectArray( a.beforeHandle ?? [], @@ -67,7 +68,7 @@ export const getPath = (url: string, queryIndex = url.indexOf('?')): string => { export const mapQuery = ( url: string, - queryIndex = url.indexOf('?') + queryIndex = url.indexOf('?'), ): Record => { if (queryIndex === -1) return {} @@ -79,14 +80,14 @@ export const mapQuery = ( // Skip ?/&, and min length of query is 3, so start looking at 1 + 3 const sep = paths.indexOf('&', 4) if (sep === -1) { - const equal = paths.indexOf('=', 1) + const equal = paths.indexOf('=') let value = paths.slice(equal + 1) const hashIndex = value.indexOf('#') if (hashIndex !== -1) value = value.substring(0, hashIndex) if (value.indexOf('%') !== -1) value = decodeURI(value) - query[paths.slice(1, equal)] = value + query[paths.slice(1, equal)] = decodeURI(value) break } diff --git a/test/modules.test.ts b/test/modules.test.ts new file mode 100644 index 00000000..48ace350 --- /dev/null +++ b/test/modules.test.ts @@ -0,0 +1,117 @@ +import { Elysia, SCHEMA } from '../src' + +import { describe, expect, it } from 'bun:test' +import { req } from './utils' +const asyncPlugin = async (app: Elysia) => app.get('/async', () => 'async') +const lazyPlugin = import('./modules') +const lazyNamed = lazyPlugin.then((x) => x.lazy) + +describe('Modules', () => { + it('inline async', async () => { + const app = new Elysia().use(async (app) => + app.get('/async', () => 'async') + ) + + await app.modules + + const res = await app.handle(req('/async')).then((r) => r.text()) + + expect(res).toBe('async') + }) + + it('async', async () => { + const app = new Elysia().use(asyncPlugin) + + await app.modules + + const res = await app.handle(req('/async')).then((r) => r.text()) + + expect(res).toBe('async') + }) + + it('inline import', async () => { + const app = new Elysia().use(import('./modules')) + + await app.modules + + const res = await app.handle(req('/lazy')).then((r) => r.text()) + + expect(res).toBe('lazy') + }) + + it('import', async () => { + const app = new Elysia().use(lazyPlugin) + + await app.modules + + const res = await app.handle(req('/lazy')).then((r) => r.text()) + + expect(res).toBe('lazy') + }) + + it('import non default', async () => { + const app = new Elysia().use(lazyNamed) + + await app.modules + + const res = await app.handle(req('/lazy')).then((r) => r.text()) + + expect(res).toBe('lazy') + }) + + it('inline import non default', async () => { + const app = new Elysia().use(import('./modules')) + + await app.modules + + const res = await app.handle(req('/lazy')).then((r) => r.text()) + + expect(res).toBe('lazy') + }) + + it('register async and lazy path', async () => { + const app = new Elysia() + .use(import('./modules')) + .use(asyncPlugin) + .get('/', () => 'hi') + .get('/schema', ({ store }) => store[SCHEMA]) + + await app.modules + + const res = await app.handle(req('/schema')).then((r) => r.json()) + + expect(res).toEqual({ + '/async': { + get: {} + }, + '/': { + get: {} + }, + '/schema': { + get: {} + }, + '/lazy': { + get: {} + } + }) + }) + + it('Count lazy module correctly', async () => { + const app = new Elysia() + .use(import('./modules')) + .use(asyncPlugin) + .get('/', () => 'hi') + + const awaited = await app.modules + + expect(awaited.length).toBe(2) + }) + + it('Handle other routes while lazy load', async () => { + const app = new Elysia().use(import('./timeout')).get('/', () => 'hi') + + const res = await app.handle(req('/')).then((r) => r.text()) + + expect(res).toBe('hi') + }) +}) diff --git a/test/modules.ts b/test/modules.ts new file mode 100644 index 00000000..9e784c52 --- /dev/null +++ b/test/modules.ts @@ -0,0 +1,5 @@ +import { Elysia } from '../src' + +export const lazy = async (app: Elysia) => app.get('/lazy', () => 'lazy') + +export default lazy diff --git a/test/timeout.ts b/test/timeout.ts new file mode 100644 index 00000000..45567269 --- /dev/null +++ b/test/timeout.ts @@ -0,0 +1,9 @@ +import Elysia from '../src' + +const largePlugin = async (app: Elysia) => { + await new Promise((resolve) => setTimeout(resolve, 1000)) + + return app.get('/large', () => 'Hi') +} + +export default largePlugin diff --git a/test/transform.test.ts b/test/transform.test.ts index 212cb735..7d780fcc 100644 --- a/test/transform.test.ts +++ b/test/transform.test.ts @@ -192,4 +192,20 @@ describe('Transform', () => { expect(await res.text()).toBe('number') }) + + it('Map returned value', async () => { + const app = new Elysia() + .onTransform<{ + params: { + id?: number + } + }>((request) => { + if (request.params?.id) request.params.id = +request.params.id + }) + .get('/id/:id', ({ params: { id } }) => typeof id) + const res = await app.handle(req('/id/1')) + + expect(await res.text()).toBe('number') + }) + }) From 28e9e6117a23c25a3e4f0d0bd99fa300766d9d17 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 22 Jan 2023 16:18:03 +0700 Subject: [PATCH 02/11] :tada: feat: support application/x-www-form-urlencoded, content-type with extra attribute --- CHANGELOG.md | 12 +++ example/a.html | 11 +++ example/a.ts | 33 +++---- example/lazy-module.ts | 19 ++++ package.json | 2 +- src/context.ts | 8 +- src/index.ts | 96 +++++++++----------- src/types.ts | 22 +++-- src/utils.ts | 17 ++-- test/{body-parser.test.ts => parser.test.ts} | 54 +++++++++-- 10 files changed, 172 insertions(+), 102 deletions(-) create mode 100644 example/a.html create mode 100644 example/lazy-module.ts rename test/{body-parser.test.ts => parser.test.ts} (55%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fbf3485..71fe60a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 0.2.0-beta.1 - 22 Jan 2023 +Breaking Change: +- `onParse` now accepts `(context: PreContext, contentType: string)` instead of `(request: Request, contentType: string)` + - To migrate, add `.request` to context to access `Request` + +Feature: +- `onRequest` and `onParse` now can access `PreContext` +- Support `application/x-www-form-urlencoded` by default + +Improvement: +- body parser now parse `content-type` with extra attribute eg. `application/json;charset=utf-8` + # 0.2.0-beta.0 - 17 Jan 2023 Feature: - Support for Async / lazy-load plugin diff --git a/example/a.html b/example/a.html new file mode 100644 index 00000000..3bb17dda --- /dev/null +++ b/example/a.html @@ -0,0 +1,11 @@ + + + +
+ + + + +
+ + diff --git a/example/a.ts b/example/a.ts index cbf2d457..66c6610a 100644 --- a/example/a.ts +++ b/example/a.ts @@ -1,20 +1,13 @@ -import { Elysia, SCHEMA } from '../src' - -const plugin = (app: Elysia) => app.get('/plugin', () => 'Plugin') -const asyncPlugin = async (app: Elysia) => app.get('/async', () => 'A') - -const app = new Elysia() - .use(plugin) - .use(asyncPlugin) - .use(import('./lazy')) - .use((app) => app.get('/inline', () => 'inline')) - .get('/', () => 'local') - .listen(3000) - -await app.modules - -type A = typeof app['store'][typeof SCHEMA] - -console.log( - `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` -) +Bun.serve({ + port: 8080, + async fetch(req) { + console.log("Got") + console.log(req.headers.toJSON()) + + try { + return new Response(await req.text()) + } catch (error) { + return new Response('') + } + } +}) diff --git a/example/lazy-module.ts b/example/lazy-module.ts new file mode 100644 index 00000000..b71855c8 --- /dev/null +++ b/example/lazy-module.ts @@ -0,0 +1,19 @@ +import { Elysia, SCHEMA } from '../src' + +const plugin = (app: Elysia) => app.get('/plugin', () => 'Plugin') +const asyncPlugin = async (app: Elysia) => app.get('/async', () => 'A') + +const app = new Elysia() + .decorate('a', () => 'hello') + .use(plugin) + .use(asyncPlugin) + .use(import('./lazy')) + .use((app) => app.get('/inline', () => 'inline')) + .get('/', ({ a }) => a()) + .listen(3000) + +await app.modules + +console.log( + `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` +) diff --git a/package.json b/package.json index 88b51f8b..a8f73ca5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "elysia", "description": "Fast, and friendly Bun web framework", - "version": "0.2.0-beta.0", + "version": "0.2.0-beta.1", "author": { "name": "saltyAom", "url": "https://github.com/SaltyAom", diff --git a/src/context.ts b/src/context.ts index ecd11777..27cca929 100644 --- a/src/context.ts +++ b/src/context.ts @@ -21,7 +21,7 @@ export interface Context< } // Use to mimic request before mapping route -export type PreContext = Omit< - Context<{}, Store>, - 'query' | 'params' | 'body' -> +export type PreContext< + Route extends TypedRoute = TypedRoute, + Store extends Elysia['store'] = Elysia['store'] +> = Omit, 'query' | 'params' | 'body'> diff --git a/src/index.ts b/src/index.ts index 9149fcf6..bdc1fb90 100644 --- a/src/index.ts +++ b/src/index.ts @@ -135,7 +135,6 @@ export default class Elysia { ) registerSchemaPath({ - // @ts-ignore schema: this.store[SCHEMA], hook, method, @@ -209,7 +208,9 @@ export default class Elysia { * }) * ``` */ - onRequest(handler: BeforeRequestHandler) { + onRequest( + handler: BeforeRequestHandler + ) { this.event.request.push(handler) return this @@ -234,7 +235,7 @@ export default class Elysia { * }) * ``` */ - onParse(parser: BodyParser) { + onParse(parser: BodyParser) { this.event.parse.splice(this.event.parse.length - 1, 0, parser) return this @@ -1128,14 +1129,26 @@ export default class Elysia { headers: {} } + let context: Context + if (this.decorators) { + context = clone(this.decorators) as any as Context + + context.request = request + context.set = set + context.store = this.store + } else { + // @ts-ignore + context = { + set, + store: this.store, + request + } + } + try { for (let i = 0; i < this.event.request.length; i++) { const onRequest = this.event.request[i] - let response = onRequest({ - request, - store: this.store, - set - }) + let response = onRequest(context) if (response instanceof Promise) response = await response response = mapEarlyResponse(response, set) @@ -1153,13 +1166,18 @@ export default class Elysia { this.fallbackRoute[request.method as HTTPMethod] if (!handler) throw new Error('NOT_FOUND') + const hooks = handler.hooks + let body: string | Record | undefined if (request.method !== 'GET') { - const contentType = request.headers.get('content-type') + let contentType = request.headers.get('content-type') if (contentType) { + const index = contentType.indexOf(';') + if (index !== -1) contentType = contentType.slice(0, index) + for (let i = 0; i < this.event.parse.length; i++) { - let temp = this.event.parse[i](request, contentType) + let temp = this.event.parse[i](context, contentType) if (temp instanceof Promise) temp = await temp if (temp) { @@ -1169,7 +1187,7 @@ export default class Elysia { } // body might be empty string thus can't use !body - if (body === undefined) + if (body === undefined) { switch (contentType) { case 'application/json': body = await request.json() @@ -1178,31 +1196,18 @@ export default class Elysia { case 'text/plain': body = await request.text() break + + case 'application/x-www-form-urlencoded': + body = mapQuery(await request.text(), null) + break } + } } } - const hooks = handler.hooks - let context: Context - - if (this.decorators) { - context = clone(this.decorators) as any as Context - - context.request = request - context.body = body - context.set = set - context.store = this.store - context.params = route?.params || {} - context.query = mapQuery(request.url, index) - } else - context = { - request, - body, - set, - store: this.store, - params: route?.params || {}, - query: mapQuery(request.url, index) - } + context.body = body + context.params = route?.params || {} + context.query = mapQuery(request.url, index) for (let i = 0; i < hooks.transform.length; i++) { const operation = hooks.transform[i](context) @@ -1268,27 +1273,12 @@ export default class Elysia { let response = handler.handle(context) if (response instanceof Promise) response = await response - if (handler.validator?.response) - if (response instanceof Response) { - let res: string | Object - - // @ts-ignore - if (handler.validator.response.schema.type === 'object') - res = await response.clone().json() - else res = await response.clone().text() - - if (handler.validator.response.Check(res) === false) - throw createValidationError( - 'response', - handler.validator.response, - response - ) - } else if (handler.validator.response.Check(response) === false) - throw createValidationError( - 'response', - handler.validator.response, - response - ) + if (handler.validator?.response?.Check(response) === false) + throw createValidationError( + 'response', + handler.validator.response, + response + ) for (let i = 0; i < hooks.afterHandle.length; i++) { let newResponse = hooks.afterHandle[i](context, response) diff --git a/src/types.ts b/src/types.ts index 48c79414..ca54e497 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,15 +64,18 @@ export type VoidLifeCycle = | ((app: Elysia) => void) | ((app: Elysia) => Promise) -export type BodyParser = ( - request: Request, +export type BodyParser< + Route extends TypedRoute = TypedRoute, + Instance extends ElysiaInstance = ElysiaInstance +> = ( + context: PreContext & Instance['request'], contentType: string ) => any | Promise export interface LifeCycle { start: VoidLifeCycle - request: BeforeRequestHandler - parse: BodyParser + request: BeforeRequestHandler + parse: BodyParser transform: NoReturnHandler beforeHandle: Handler afterHandle: AfterRequestHandler @@ -92,8 +95,8 @@ export interface LifeCycleStore< Instance extends ElysiaInstance = ElysiaInstance > { start: VoidLifeCycle[] - request: BeforeRequestHandler[] - parse: BodyParser[] + request: BeforeRequestHandler[] + parse: BodyParser[] transform: NoReturnHandler[] beforeHandle: Handler[] afterHandle: AfterRequestHandler[] @@ -102,10 +105,9 @@ export interface LifeCycleStore< } export type BeforeRequestHandler< - Store extends ElysiaInstance['store'] = ElysiaInstance['store'] -> = ( - context: PreContext -) => any + Route extends TypedRoute = TypedRoute, + Instance extends ElysiaInstance = ElysiaInstance +> = (context: PreContext & Instance['request']) => any export interface RegisteredHook< Instance extends ElysiaInstance = ElysiaInstance diff --git a/src/utils.ts b/src/utils.ts index cc4b7613..fba31ffb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -68,31 +68,32 @@ export const getPath = (url: string, queryIndex = url.indexOf('?')): string => { export const mapQuery = ( url: string, - queryIndex = url.indexOf('?'), + queryIndex: number | null = url.indexOf('?') ): Record => { if (queryIndex === -1) return {} const query: Record = {} - let paths = url.slice(queryIndex) + if (queryIndex) url = url.slice(queryIndex) + else url = ';' + url // eslint-disable-next-line no-constant-condition while (true) { // Skip ?/&, and min length of query is 3, so start looking at 1 + 3 - const sep = paths.indexOf('&', 4) + const sep = url.indexOf('&', 4) if (sep === -1) { - const equal = paths.indexOf('=') + const equal = url.indexOf('=') - let value = paths.slice(equal + 1) + let value = url.slice(equal + 1) const hashIndex = value.indexOf('#') if (hashIndex !== -1) value = value.substring(0, hashIndex) if (value.indexOf('%') !== -1) value = decodeURI(value) - query[paths.slice(1, equal)] = decodeURI(value) + query[url.slice(1, equal)] = decodeURI(value) break } - const path = paths.slice(0, sep) + const path = url.slice(0, sep) const equal = path.indexOf('=') let value = path.slice(equal + 1) @@ -100,7 +101,7 @@ export const mapQuery = ( query[path.slice(1, equal)] = value - paths = paths.slice(sep) + url = url.slice(sep) } return query diff --git a/test/body-parser.test.ts b/test/parser.test.ts similarity index 55% rename from test/body-parser.test.ts rename to test/parser.test.ts index e65daf91..5c6d8718 100644 --- a/test/body-parser.test.ts +++ b/test/parser.test.ts @@ -2,13 +2,13 @@ import { Elysia } from '../src' import { describe, expect, it } from 'bun:test' -describe('Body Parser', () => { +describe('Parser', () => { it('handle onParse', async () => { const app = new Elysia() - .onParse((request, contentType) => { + .onParse((context, contentType) => { switch (contentType) { case 'application/Elysia': - return request.text() + return context.request.text() } }) .post('/', ({ body }) => body) @@ -30,10 +30,10 @@ describe('Body Parser', () => { it("handle .on('parse')", async () => { const app = new Elysia() - .on('parse', (request, contentType) => { + .on('parse', (context, contentType) => { switch (contentType) { case 'application/Elysia': - return request.text() + return context.request.text() } }) .post('/', ({ body }) => body) @@ -55,7 +55,7 @@ describe('Body Parser', () => { it('overwrite default parser', async () => { const app = new Elysia() - .onParse((request, contentType) => { + .onParse((context, contentType) => { switch (contentType) { case 'text/plain': return 'Overwrited' @@ -77,4 +77,46 @@ describe('Body Parser', () => { expect(await res.text()).toBe('Overwrited') }) + + it('parse x-www-form-urlencoded', async () => { + const app = new Elysia().post('/', ({ body }) => body).listen(8080) + + const body = { + username: 'salty aom', + password: '12345678' + } + + const res = await app.handle( + new Request('http://localhost/', { + method: 'POST', + body: `username=${body.username}&password=${body.password}`, + headers: { + 'content-type': 'application/x-www-form-urlencoded' + } + }) + ) + + expect(await res.json()).toEqual(body) + }) + + it('parse with extra content-type attribute', async () => { + const app = new Elysia().post('/', ({ body }) => body).listen(8080) + + const body = { + username: 'salty aom', + password: '12345678' + } + + const res = await app.handle( + new Request('http://localhost/', { + method: 'POST', + body: JSON.stringify(body), + headers: { + 'content-type': 'application/json;charset=utf-8' + } + }) + ) + + expect(await res.json()).toEqual(body) + }) }) From 998659100a69a696164af7679136ba7f75c03a4f Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 22 Jan 2023 17:45:11 +0700 Subject: [PATCH 03/11] :tada: feat: using includes instead of indexOf --- src/utils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index fba31ffb..75fb150a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -63,7 +63,7 @@ export const getPath = (url: string, queryIndex = url.indexOf('?')): string => { else queryIndex = url.length } - return url.substring(url.indexOf('/', 9), queryIndex) + return url.slice(url.indexOf('/', 9), queryIndex) } export const mapQuery = ( @@ -85,10 +85,10 @@ export const mapQuery = ( let value = url.slice(equal + 1) const hashIndex = value.indexOf('#') - if (hashIndex !== -1) value = value.substring(0, hashIndex) - if (value.indexOf('%') !== -1) value = decodeURI(value) + if (hashIndex !== -1) value = value.slice(0, hashIndex) + if (value.includes('%')) value = decodeURI(value) - query[url.slice(1, equal)] = decodeURI(value) + query[url.slice(1, equal)] = value break } @@ -97,7 +97,7 @@ export const mapQuery = ( const equal = path.indexOf('=') let value = path.slice(equal + 1) - if (value.indexOf('%') !== -1) value = decodeURI(value) + if (value.includes('%')) value = decodeURI(value) query[path.slice(1, equal)] = value From ae9eee4d349bd782f3f6de233a986dd36a4b67b1 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 22 Jan 2023 17:46:04 +0700 Subject: [PATCH 04/11] :tada: feat: using includes instead of indexOf --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 75fb150a..859b3dd4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -63,7 +63,7 @@ export const getPath = (url: string, queryIndex = url.indexOf('?')): string => { else queryIndex = url.length } - return url.slice(url.indexOf('/', 9), queryIndex) + return url.substring(url.indexOf('/', 9), queryIndex) } export const mapQuery = ( From 55c8ca232e281263beb4a9ca8e8b63360bf083ec Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 22 Jan 2023 21:46:05 +0700 Subject: [PATCH 05/11] :tada: feat: beta.2 --- CHANGELOG.md | 9 +++ bun.lockb | Bin 41156 -> 41532 bytes example/a.html | 11 ---- example/a.ts | 13 ---- example/body.ts | 7 +- example/schema.ts | 20 ++++-- package.json | 5 +- src/index.ts | 21 ++++-- src/schema.ts | 9 ++- src/types.ts | 159 +++++++++++++++++++++++----------------------- 10 files changed, 131 insertions(+), 123 deletions(-) delete mode 100644 example/a.html delete mode 100644 example/a.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 71fe60a2..e5a2a38e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 0.2.0-beta.2 - 22 Jan 2023 +Feature: +- Add support for custom openapi field using `schema.detail` +- Add support for custom code for `response` + +Improvement: +- Unioned status type for response +- Optimize TypeScript inference performance + # 0.2.0-beta.1 - 22 Jan 2023 Breaking Change: - `onParse` now accepts `(context: PreContext, contentType: string)` instead of `(request: Request, contentType: string)` diff --git a/bun.lockb b/bun.lockb index 0fde63383a2d02bb77edb6513e3d550ae51e21b1..6eb757948986bec3873fc4902893b2988324eb13 100755 GIT binary patch delta 12022 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 delta 11551 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 p?(Q<0M&vi=OdgRY?`(I - - -
- - - - -
- - diff --git a/example/a.ts b/example/a.ts deleted file mode 100644 index 66c6610a..00000000 --- a/example/a.ts +++ /dev/null @@ -1,13 +0,0 @@ -Bun.serve({ - port: 8080, - async fetch(req) { - console.log("Got") - console.log(req.headers.toJSON()) - - try { - return new Response(await req.text()) - } catch (error) { - return new Response('') - } - } -}) diff --git a/example/body.ts b/example/body.ts index a142ecb1..6751efc2 100644 --- a/example/body.ts +++ b/example/body.ts @@ -2,7 +2,7 @@ import { Elysia, t } from '../src' const app = new Elysia() // Add custom body parser - .onParse(async (request, contentType) => { + .onParse(async ({ request }, contentType) => { switch (contentType) { case 'application/Elysia': return request.text() @@ -25,7 +25,10 @@ const app = new Elysia() body: t.Object({ id: t.Number(), username: t.String() - }) + }), + detail: { + summary: 'A' + } } }) .post('/mirror', ({ body }) => body) diff --git a/example/schema.ts b/example/schema.ts index 4b451a8c..24f978eb 100644 --- a/example/schema.ts +++ b/example/schema.ts @@ -1,4 +1,4 @@ -import { Elysia, t } from '../src' +import { Elysia, t, SCHEMA } from '../src' const app = new Elysia() // Strictly validate response @@ -16,8 +16,10 @@ const app = new Elysia() profile: t.Object({ name: t.String() }) - }), - response: t.Number() + }) + // response: { + // 200: t.Number() + // } } }) // Strictly validate query, params, and body @@ -28,7 +30,13 @@ const app = new Elysia() }), params: t.Object({ id: t.String() - }) + }), + response: { + 200: t.String(), + 300: t.Object({ + error: t.String() + }) + } } }) .guard( @@ -53,7 +61,7 @@ const app = new Elysia() schema: { params: t.Object({ id: t.Number() - }), + }) }, transform: ({ params }) => { params.id = +params.id @@ -62,3 +70,5 @@ const app = new Elysia() ) ) .listen(8080) + +type A = typeof app['store'][typeof SCHEMA]['/query/:id']['GET']['query'] diff --git a/package.json b/package.json index a8f73ca5..4675ff35 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "elysia", "description": "Fast, and friendly Bun web framework", - "version": "0.2.0-beta.1", + "version": "0.2.0-beta.2", "author": { "name": "saltyAom", "url": "https://github.com/SaltyAom", @@ -35,7 +35,8 @@ "release": "npm run build && npm run test && npm publish" }, "dependencies": { - "@sinclair/typebox": "0.25.10" + "@sinclair/typebox": "0.25.10", + "openapi-types": "^12.1.0" }, "devDependencies": { "@types/node": "^18.11.10", diff --git a/src/index.ts b/src/index.ts index bdc1fb90..c6c4d107 100644 --- a/src/index.ts +++ b/src/index.ts @@ -94,7 +94,6 @@ export default class Elysia { constructor(config: Partial = {}) { this.config = { strictPath: false, - queryLimit: 48, ...config } } @@ -640,7 +639,7 @@ export default class Elysia { * ``` */ get< - Schema extends TypedSchema = {}, + Schema extends TypedSchema = TypedSchema, Path extends string = string, Response = unknown >( @@ -1117,7 +1116,9 @@ export default class Elysia { headers: getSchemaValidator(schema?.headers), params: getSchemaValidator(schema?.params), query: getSchemaValidator(schema?.query), - response: getSchemaValidator(schema?.response) + response: getSchemaValidator( + schema?.response?.['200'] ?? schema.response + ) } return this as unknown as NewInstance @@ -1422,7 +1423,7 @@ export default class Elysia { } } -export { Elysia } +export { Elysia, Router } export { Type as t } from '@sinclair/typebox' export { SCHEMA, @@ -1430,7 +1431,6 @@ export { createValidationError, getSchemaValidator } from './utils' -export { Router } from './router' export type { Context, PreContext } from './context' export type { @@ -1460,5 +1460,14 @@ export type { VoidLifeCycle, SchemaValidator, ElysiaRoute, - ExtractPath + ExtractPath, + IsPathParameter, + IsAny, + IsNever, + UnknownFallback, + WithArray, + ObjectValues, + PickInOrder, + MaybePromise, + MergeIfNotNull } from './types' diff --git a/src/schema.ts b/src/schema.ts index 9bd75ae5..eae82f82 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -1,4 +1,5 @@ import type { TSchema } from '@sinclair/typebox' +import type { OpenAPIV2 } from 'openapi-types' import type { HTTPMethod, LocalHook } from './types' @@ -23,10 +24,10 @@ export const registerSchemaPath = ({ method, hook }: { - schema: Record + schema: OpenAPIV2.PathsObject path: string method: HTTPMethod - hook?: LocalHook + hook?: LocalHook }) => { path = toOpenAPIPath(path) @@ -35,6 +36,7 @@ export const registerSchemaPath = ({ const headerSchema = hook?.schema?.headers const querySchema = hook?.schema?.query const responseSchema = hook?.schema?.response + const detail = hook?.schema?.detail const parameters = [ ...mapProperties('header', headerSchema), @@ -66,7 +68,8 @@ export const registerSchemaPath = ({ } } } - : {}) + : {}), + ...detail } } } diff --git a/src/types.ts b/src/types.ts index ca54e497..83822cfb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,20 +2,18 @@ import type { Elysia } from '.' import type { Serve, Server } from 'bun' import type { Context, PreContext } from './context' -import type { Static, TSchema } from '@sinclair/typebox' +import type { Static, TObject, TSchema } from '@sinclair/typebox' import type { TypeCheck } from '@sinclair/typebox/compiler' import type { SCHEMA } from './utils' +import type { OpenAPIV2 } from 'openapi-types' export type WithArray = T | T[] +export type ObjectValues = T[keyof T] export interface ElysiaInstance< Instance extends { store?: Record & - Record< - typeof SCHEMA, - Record>> - > - + Record> request?: Record schema?: TypedSchema } = { @@ -24,9 +22,13 @@ export interface ElysiaInstance< schema: {} } > { - request: Instance['request'] - store: Instance['store'] - schema: Instance['schema'] + request: Instance['request'] extends undefined + ? Record + : Instance['request'] + store: Instance['store'] extends undefined ? {} : Instance['store'] + schema: Instance['schema'] extends undefined + ? TypedSchema + : Instance['schema'] } export type Handler< @@ -36,8 +38,8 @@ export type Handler< > = ( context: Context & Instance['request'] ) => Route['response'] extends CatchResponse - ? CatchResponse | Promise | Response - : Route['response'] | Promise | Response + ? MaybePromise | Response + : MaybePromise | Response export type NoReturnHandler< Route extends TypedRoute = TypedRoute, @@ -119,26 +121,12 @@ export interface RegisteredHook< error: ErrorHandler[] } -export interface TypedSchema< - Schema extends { - body: TSchema - headers: TSchema - query: TSchema - params: TSchema - response: TSchema - } = { - body: TSchema - headers: TSchema - query: TSchema - params: TSchema - response: TSchema - } -> { - body?: Schema['body'] - headers?: Schema['headers'] - query?: Schema['query'] - params?: Schema['params'] - response?: Schema['response'] +export interface TypedSchema { + body?: TSchema + headers?: TObject + query?: TObject + params?: TObject + response?: TSchema | Record } export type UnwrapSchema< @@ -148,16 +136,30 @@ export type UnwrapSchema< export type TypedSchemaToRoute = { body: UnwrapSchema - headers: UnwrapSchema extends Record - ? UnwrapSchema + headers: UnwrapSchema< + Schema['headers'] + > extends infer Result extends Record + ? Result : undefined - query: UnwrapSchema extends Record - ? UnwrapSchema + query: UnwrapSchema extends infer Result extends Record< + string, + any + > + ? Result : undefined - params: UnwrapSchema extends Record - ? UnwrapSchema + params: UnwrapSchema extends infer Result extends Record< + string, + any + > + ? Result : undefined - response: UnwrapSchema + response: Schema['response'] extends TSchema + ? UnwrapSchema + : Schema['response'] extends { + [k in string]: TSchema + } + ? UnwrapSchema> + : unknown } export type SchemaValidator = { @@ -171,15 +173,16 @@ export type SchemaValidator = { export type HookHandler< Schema extends TypedSchema = TypedSchema, Instance extends ElysiaInstance = ElysiaInstance, - Path extends string = string + Path extends string = string, + Typed extends TypedSchemaToRoute = TypedSchemaToRoute > = Handler< - TypedSchemaToRoute['params'] extends {} - ? Omit, 'response'> & { - response: void | TypedSchemaToRoute['response'] + Typed['params'] extends {} + ? Omit & { + response: void | Typed['response'] } : Omit< - Omit, 'response'> & { - response: void | TypedSchemaToRoute['response'] + Omit & { + response: void | Typed['response'] }, 'params' > & { @@ -200,42 +203,46 @@ export type MergeSchema = { } export interface LocalHook< - Schema extends TypedSchema = TypedSchema, - Instance extends ElysiaInstance = ElysiaInstance, - Path extends string = string -> { - schema?: Schema - transform?: WithArray< - HookHandler, Instance, Path> - > - beforeHandle?: WithArray< - HookHandler, Instance, Path> + Schema extends TypedSchema = TypedSchema, + Instance extends ElysiaInstance = ElysiaInstance, + Path extends string = string, + FinalSchema extends MergeSchema = MergeSchema< + Schema, + Instance['schema'] > +> { + schema?: Schema & { detail?: Partial } + transform?: WithArray> + beforeHandle?: WithArray> afterHandle?: WithArray> error?: WithArray } export type RouteToSchema< - Schema extends TypedSchema = TypedSchema, - Instance extends ElysiaInstance = ElysiaInstance, - Path extends string = string -> = MergeSchema['params'] extends NonNullable< - Schema['params'] -> - ? TypedSchemaToRoute> - : Omit< - TypedSchemaToRoute>, - 'params' - > & { + Schema extends TypedSchema = TypedSchema, + Instance extends ElysiaInstance = ElysiaInstance, + Path extends string = string, + FinalSchema extends MergeSchema = MergeSchema< + Schema, + Instance['schema'] + > +> = FinalSchema['params'] extends NonNullable + ? TypedSchemaToRoute + : Omit, 'params'> & { params: Record, string> } export type ElysiaRoute< Method extends string = string, - Schema extends TypedSchema = TypedSchema, + Schema extends TypedSchema = TypedSchema, Instance extends ElysiaInstance = ElysiaInstance, Path extends string = string, - CatchResponse = unknown + CatchResponse = unknown, + Typed extends RouteToSchema = RouteToSchema< + Schema, + Instance, + Path + > > = Elysia<{ request: Instance['request'] store: Instance['store'] & @@ -245,14 +252,10 @@ export type ElysiaRoute< Path, Record< Method, - RouteToSchema & { - response: RouteToSchema< - Schema, - Instance, - Path - >['response'] extends CatchResponse + Typed & { + response: Typed['response'] extends CatchResponse ? CatchResponse - : RouteToSchema['response'] + : Typed['response'] } > > @@ -261,7 +264,7 @@ export type ElysiaRoute< }> export type LocalHandler< - Schema extends TypedSchema = TypedSchema, + Schema extends TypedSchema = TypedSchema, Instance extends ElysiaInstance = ElysiaInstance, Path extends string = string, CatchResponse = unknown @@ -298,12 +301,6 @@ export interface ElysiaConfig { * @default false */ strictPath: boolean - /** - * Maximum querystring - * - * @default 48 - */ - queryLimit: number serve?: Partial } From 7432f9202a87330f96fd224bb200295cf731d792 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 22 Jan 2023 22:01:02 +0700 Subject: [PATCH 06/11] :tada: feat: beta.2 --- bun.lockb | Bin 41532 -> 41532 bytes example/schema.ts | 8 ++++---- package.json | 2 +- src/index.ts | 1 + tsconfig.esm.json | 3 ++- tsconfig.json | 1 + 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/bun.lockb b/bun.lockb index 6eb757948986bec3873fc4902893b2988324eb13..9c54d32820d6e2465e7b7353c5475f77633942d0 100755 GIT binary patch delta 2386 zcmai0T}V@582+3)+s@h6Hs{bJ(kYrhWmDInSX9sthFMW7FbGx=QU(Q;UIaxKURYH4 zIjA6NlyuXDW#Ek$S!&Qlh_Eh7p-C4(M9~ivM)ZE?eD68m&Ny{m6O)3Oma+#H0nC$xTyTQD zO!G>Uq}fIkZkxh~jFOa8Ct49q3@Xl$Y6v0lQ$(XmOiPgRVkU1g$TOiCp;;wnJ`{a@ z=<7>*&=-7G4=Nrk&Y$$*4GRm2;nvA-Gk!}>mt9VY9TvvPmy2+0<57z zZ}$h3y%I=u(6rYQ)NQZ2LSEI;7BZ>REYetzS{Uyx7a^^h^6gT?I2h{D`qA}?P^ev- zp(q$Hw1gEE#PzmZ?;dZRj5M1#`gobwMOLK-C%=+Ynya)#7Kf$EG<0dNEx+Qbi6%iz z>Z-4I6Osb7=pChnzXzh279TPJ)N{37NP~xvb>K&sKs3i(wO)u@CgdQUua5K0U&8}r z@F7>DN7}DwkzlaH|EI=F)3RWtiNHc2&|(E%GFD-xR!=W*6~!%H$IFHHotZtD5@X4t zugyziQ(u_I!XOR}X2a5E9*S8^vnXwcRzF+-(DpKbao;AqglrWL&cVSx@n9AYkor?T zLUu9g-zqH_W&+OCFxJBVwjDUdjYx5$u7X2;NT91=G}825-neJOtsQ6Erp~+>>FW48 zoSmP&IQ_VB_f*GoY3;(m=b`H_?|poI_vzx*8{2;7ZtwIso?N>8?MPzqd-qsEZaF=1 rbaefi@l|2|&t|4h`)AV)|7bux^`r5_y~~Q?qYus>h`^U86ZiiCnONlo delta 2359 zcmai0T}TvB6u!Iex;wMG&8j>27wW7e{-%~0$OHyrWKmLWiG{coK~P2%SX5LH^cvna zA`IOAhzzoP2-Amtq=%p$jC~1$HXrJto_dMs@660Sckj-OwJ#25?m6c>-}jw6&dq!0 z=Dn9z)_iI`GqrekviIbkQ~jQqfvy8p#a|!4deFD+?dChz4-Wo%xaCE#^3T#g+C37Z zv5~0XOX>%1(S)S?%!ibu$Bc&pLdraZ=!Bf}5ke%IEpB3GRhgYl;6qB*{bmxah<1+2 zz9O?Il)#^dYI2*^AQ8e&Aw9}E;W0wfa+^IU=7uphtOqd{a$3jaAcBkNVSHnhg~ssK z$KIwQj-G{XJ^e;7zfQJHth3Bf$4k>;HlnK7FeJlbuHwk`&?M5MM)Rpe;8C1BN44Tg zHQ>l`cnvh@G$f?S2m_ZXa|U@sDmkhDW8jE+?Yq=DpPYnlmOh2e_X3Gj&oD| z1W+9S3P@IXTR=zpAP7_-jfoh}9CJV}Gz^uqfTlr*paYN)NE;G}C#;kRpTRPViPRP} z{gs1{W_2}AK@PrJ%HStT9q_#dpfUV$STYEZmnS*a_+I_;r>J_%&F~E90Ji9a+|IrO8n(rUl!AVab07^t zLb@Q27zVKtGjR?iAQ3V|-^V-H&0ot3r0^kA%PZ}YtxB-iCI6QuM3a(m$})jNK#;{r zygzpprdIIwf>s(EM6F|R;d^IS54I$C$zra{M`Qho653Xx+0y}o)0729O}i;wmzD=V z0%+aKiPCVIXoIqR~~-eurSg6?fkLD%bhdQyC1(Bn!juu z+CO>q%7vcF8(Z(qmKDC4YAGLnw0(BN`1HCpb1fybr2zg#;lHQp(j(xW`st2+qsu+3 Pr|+NLt-_yRsw46jCn(<> diff --git a/example/schema.ts b/example/schema.ts index 24f978eb..ab98deee 100644 --- a/example/schema.ts +++ b/example/schema.ts @@ -16,10 +16,10 @@ const app = new Elysia() profile: t.Object({ name: t.String() }) - }) - // response: { - // 200: t.Number() - // } + }), + response: { + 200: t.Number() + } } }) // Strictly validate query, params, and body diff --git a/package.json b/package.json index 4675ff35..7d7327f2 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,6 @@ "bun-types": "^0.3.0", "eslint": "^8.29.0", "rimraf": "^3.0.2", - "typescript": "^4.9.3" + "typescript": "^4.9.4" } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index c6c4d107..569a64bc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1425,6 +1425,7 @@ export default class Elysia { export { Elysia, Router } export { Type as t } from '@sinclair/typebox' + export { SCHEMA, getPath, diff --git a/tsconfig.esm.json b/tsconfig.esm.json index 6c047bdf..c98c2e0a 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -100,5 +100,6 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true, /* Skip type checking all .d.ts files. */ }, - "include": ["src/**/*"] + "include": ["src/**/*"], + // "exclude": ["node_modules"] } diff --git a/tsconfig.json b/tsconfig.json index 114f0f8f..c0c2b38c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -100,5 +100,6 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true, /* Skip type checking all .d.ts files. */ }, + "exclude": ["node_modules"] // "include": ["src/**/*"] } From 12b9b3bd302371cc62b0c324da874d3987488702 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 22 Jan 2023 22:20:18 +0700 Subject: [PATCH 07/11] :tada: feat: beta.2 --- bun.lockb | Bin 41532 -> 41827 bytes package.json | 14 +++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bun.lockb b/bun.lockb index 9c54d32820d6e2465e7b7353c5475f77633942d0..e853215ad9af989d0ea28955829b0f51469e35d6 100755 GIT binary patch delta 11965 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 bX#ZwE=|uWz^LxhQgU_~D)Ul@JTXz2^W{UX8 delta 11931 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 diff --git a/package.json b/package.json index 7d7327f2..a0ea2286 100644 --- a/package.json +++ b/package.json @@ -35,16 +35,16 @@ "release": "npm run build && npm run test && npm publish" }, "dependencies": { - "@sinclair/typebox": "0.25.10", + "@sinclair/typebox": "0.25.21", "openapi-types": "^12.1.0" }, "devDependencies": { - "@types/node": "^18.11.10", - "@typescript-eslint/eslint-plugin": "^5.45.0", - "@typescript-eslint/parser": "^5.45.0", - "bun-types": "^0.3.0", - "eslint": "^8.29.0", - "rimraf": "^3.0.2", + "@types/node": "^18.11.18", + "@typescript-eslint/eslint-plugin": "^5.48.2", + "@typescript-eslint/parser": "^5.48.2", + "bun-types": "^0.5.0", + "eslint": "^8.32.0", + "rimraf": "^4.1.1", "typescript": "^4.9.4" } } \ No newline at end of file From 77e89f70c76954aa7f73b2797580f0a9929e0c7b Mon Sep 17 00:00:00 2001 From: saltyaom Date: Sun, 22 Jan 2023 22:21:10 +0700 Subject: [PATCH 08/11] :tada: feat: beta.2 --- bun.lockb | Bin 41827 -> 41532 bytes package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index e853215ad9af989d0ea28955829b0f51469e35d6..9aa3c0dd9e0d90a0bb698c7595237de455f0171c 100755 GIT binary patch delta 10121 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 z zVghA$+k}-icbhHppvkiHx3>N2r{)Z8csL8U3Oa>${osjbjd1KKk*k16G} zWsjNecaOHnGbUGue|mP&#f=ZqC(W8}g*i=>xW{IZXH?oQ|2{KhR>7-iPsbS8TeLAO z^2|!sH&c!$z7=!bAaTd3)nBzbI37Ttpv=+Iu9ML&e%SygnS&>8*RAEJGx_ew!O z+iSPTvnbPFnRE5to{F7lN6PTXu{3|59sl@!qsL~EXHHHWpZE5AmsZE2SBBNbzW0Xt zk3BIFHQ2_nsd*YHw%2BOJ%pC_#^LggUbFUFjmUi3Yq!|3t7DV*MD{zz&VA|N7mpP_ zNXzz{#dGA|Zxb)mi~G%%3+Rho#dB-*w{I5w?$jsn9lT=2Zpb-hHe3m%`}^Yz?}t+8 zfu)9DhEnl?r51VK;k52h z9G`31dT2qUbdpz!|J?2z`e8&jcE(05y@8ya45~nB3o9?fX%5Mh#jrVE|gbA4!Er;tV$< zspd$!;dUgw08ZCMi=<>&F|*JtuuR6i7k4_((SLsHE%&&z9)rlR&d6eitLuQ+mcYu22EG`iXm zck#L7C1OHVv$e_PtZi_%)YQ4_-F42E${J>HzAVLA(Y&R$wZ%orC#FVt{|mHkYNl(a YEEm^)vry25=h`k7oj5KoJYN~{KU?JQ*8l(j delta 10134 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 m7n=W}NhED_XSF*WP5gEWnF1&f7-t^yW Date: Mon, 23 Jan 2023 13:34:23 +0700 Subject: [PATCH 09/11] :tada: feat: reference model --- CHANGELOG.md | 5 + bun.lockb | Bin 41532 -> 41532 bytes example/schema.ts | 32 +++-- package.json | 2 +- src/context.ts | 10 +- src/index.ts | 87 ++++++++---- src/schema.ts | 78 ++++++++--- src/types.ts | 93 ++++++++----- src/utils.ts | 54 +++++++- test/models.test.ts | 322 ++++++++++++++++++++++++++++++++++++++++++++ test/schema.test.ts | 65 ++++----- 11 files changed, 610 insertions(+), 138 deletions(-) create mode 100644 test/models.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e5a2a38e..c747d0a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.2.0-rc.0 - 23 Jan 2023 +Feature: +- Add support for reference model via `.setModel` +- Add support for OpenAPI's `definitions` field + # 0.2.0-beta.2 - 22 Jan 2023 Feature: - Add support for custom openapi field using `schema.detail` diff --git a/bun.lockb b/bun.lockb index 9aa3c0dd9e0d90a0bb698c7595237de455f0171c..2b95590eff1d0e47091c243ea695994c619f5c87 100755 GIT binary patch delta 2200 zcmaJ?&ui2`6n>i~`y;zC3ER-Mf;C$#(hBbC(n5(?FCN5$pi%TtSUuK*-aLr-H+0Co z`v(YOue}I8dk}LJT(MAZ>dB)olX=NxCc2j$-pu>nukU*~IyR1ujdz32^`D>Zy#@f> z`Dnatz@r^JR(=D3(-RBe_4B=F?|)k`O7r)|;N0m6i#w6Y4+$MoIs~@3is_Kpk(E8R z*s`~|Z<{{HKy3OoSZI%Oytqri-N@rk^a%gi%pR#m*|$f&y{JvT=yPS?Zd-ZYffM!U zCIiO6cx#H=#L;3l>tb4&v5GSv$T(*-9D~hzqQt;ea+vmHUsv;Ouqm}C@T#q(#67CK zY_(h4YRrSM5??r_z82DCb6;yU`Z`pDr@1V!nLjGq&Wob9BLk}K3fH8P(|!RqG68M_ zyup&GUGXB-wpctPPBb&(lrh5)QQMt@UI?NXW~w1~aSE556g)e{$a$o5x0WCaW%|LA zNIzcE(l@d$|6dwNwQ>*}sF?|Nr^7;5n-1@PE~0cGVg_d=A5SzpGi4L0p+S-0wmRvjx>uryXKT6xZu(2|NDHvz6< zXtO@_)RD-O*W*=~Ac|IZ4i}MX#6{%M5<6cMPfRuIMkWhNT?#Wk7}EC&Np(d-mZfq* zk=e9cJ6(C9gb|JTQVSr)X0Zc%ES7snVd;>R+vx{B-@)ftw=3#ShV*_|B+}{t?6NPz ziP-O};z>e>bhRkyp$)K&kSOcEHW`hhT{i7VuW}A(uXuXutSpA37d2ii!b`IXY0s^R zbn&aEf}lq9&r!{%;pWRe8}&og9dpp-fFhO`TF%K@$ri?M*tGjugK5KVb^&h5hF55K zAR9iBfkywL2XHqV{a1TTFdxvyVf4iRu^YsM*J;A*1qq%wh+mN4FxdFMW!~FvHQ29f LTZfaU?Hhjq8Y%?Y delta 2215 zcmaJ@&ui2`6n>i~-6Wfsgl*{BgEd=RODni5+X}K`y$Fg24@SX*ka`g*h&K-&_HXEr zd-o3z#9n(5dKM9K6kPG*)sshGCi9X>Cc2j$UgrJ$zV9Vx=i1r1_F-V%KDg?=?f}5U z&)WOWT)T$H+HU}0?im30UmYEN{B6Ko+}FmAxyNE_rt?Eehl~!PEgr>mNbQ-CUpCl= zzkh7&0gr*P>DOVAJuc$KJqp~L`P_*v;Xhm0f&kYDmho%avHg@9%ng_<6KbUXl&t#7DKO5L*mH3p5ohK35_T8>!YN?ed@d# zwbwdotb<~ufpE$KC8f@mfii0Jb*Tqmaam!DU|fxzH$`Jd2GqwDjii>dei>}613Unj zW6LzIc#V2nE3XkJR&(N1Iim=pnpR0K4AL+xnufx~EnNyy@ah^y&Lfq(tpr)A$`98? z`SH3^zLrn<|I$FJRY2^bW~MkDi$$J-4xfJ^qHG{?h8HCvEQ5R)&91y02t|?I9BNNi zN-ma@U{VcRnQakYFf!XHyZwVz<9eBEEa@p#ciV|phm|W>f|ZL_#<`YSQt`7@fE$=v zZ%>`L5_yV#{2CKP)6Jd3MbtFnBJyd8T`HR=rs{2w$%1Mwg#`zO^u0+^-BcpWQn}oa zS<-7oS6(R5j9}jG0>n5hc4AM&a*rr29nxw${le=9cpdxpMBnL 'hi', { - schema: { + .setModel( + 'a', + t.Object({ response: t.String() - } - }) + }) + ) + .setModel( + 'b', + t.Object({ + response: t.Number() + }) + ) + // Strictly validate response + .get('/', () => 'hi') // Strictly validate body and response - .post('/', ({ body }) => body.id, { + .post('/', ({ body, query }) => body.id, { schema: { body: t.Object({ id: t.Number(), @@ -16,10 +24,7 @@ const app = new Elysia() profile: t.Object({ name: t.String() }) - }), - response: { - 200: t.Number() - } + }) } }) // Strictly validate query, params, and body @@ -71,4 +76,7 @@ const app = new Elysia() ) .listen(8080) -type A = typeof app['store'][typeof SCHEMA]['/query/:id']['GET']['query'] +type A = typeof app['store'][typeof SCHEMA]['/']['POST']['query'] +type B = typeof app['store'][typeof DEFS] + +// const a = app.getModel('b') diff --git a/package.json b/package.json index 313a47d0..0e3cb067 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "elysia", "description": "Fast, and friendly Bun web framework", - "version": "0.2.0-beta.2", + "version": "0.2.0-rc.0", "author": { "name": "saltyAom", "url": "https://github.com/SaltyAom", diff --git a/src/context.ts b/src/context.ts index 27cca929..54c6d1e8 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,16 +1,18 @@ import { Elysia } from '.' import type { TypedRoute } from './types' +type UnwrapFn = T extends (...params: any) => any ? ReturnType : T + export interface Context< Route extends TypedRoute = TypedRoute, Store extends Elysia['store'] = Elysia['store'] > { request: Request - query: Route['query'] extends undefined + query: UnwrapFn extends undefined ? Record - : Route['query'] - params: Route['params'] - body: Route['body'] + : UnwrapFn + params: UnwrapFn + body: UnwrapFn store: Store set: { diff --git a/src/index.ts b/src/index.ts index 569a64bc..dd66d7f5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,8 +8,10 @@ import { mergeHook, mergeDeep, createValidationError, + getSchemaValidator, SCHEMA, - getSchemaValidator + DEFS, + getResponseSchemaValidator } from './utils' import { registerSchemaPath } from './schema' import { mapErrorCode, mapErrorStatus } from './error' @@ -44,8 +46,10 @@ import type { NoReturnHandler, ElysiaRoute, MaybePromise, - IsNever + IsNever, + InferSchema } from './types' +import { type TSchema } from '@sinclair/typebox' /** * ### Elysia Server @@ -65,7 +69,8 @@ export default class Elysia { config: ElysiaConfig store: Instance['store'] = { - [SCHEMA]: {} + [SCHEMA]: {}, + [DEFS]: {} } // Will be applied to Context protected decorators: Record | null = null @@ -99,7 +104,7 @@ export default class Elysia { } private _addHandler< - Schema extends TypedSchema = TypedSchema, + Schema extends TypedSchema, Path extends string = string >( method: HTTPMethod, @@ -116,28 +121,36 @@ export default class Elysia { hooks: mergeHook(clone(this.event), hook as RegisteredHook) }) + const defs = this.store[DEFS] + const body = getSchemaValidator( - hook?.schema?.body ?? this.$schema?.body + hook?.schema?.body ?? this.$schema?.body, + defs ) const header = getSchemaValidator( hook?.schema?.headers ?? this.$schema?.headers, + defs, true ) const params = getSchemaValidator( - hook?.schema?.params ?? this.$schema?.params + hook?.schema?.params ?? this.$schema?.params, + defs ) const query = getSchemaValidator( - hook?.schema?.query ?? this.$schema?.query + hook?.schema?.query ?? this.$schema?.query, + defs ) - const response = getSchemaValidator( - hook?.schema?.response ?? this.$schema?.response + const response = getResponseSchemaValidator( + hook?.schema?.response ?? this.$schema?.response, + defs ) registerSchemaPath({ schema: this.store[SCHEMA], hook, method, - path + path, + models: this.store[DEFS] }) const validator = @@ -639,7 +652,7 @@ export default class Elysia { * ``` */ get< - Schema extends TypedSchema = TypedSchema, + Schema extends InferSchema = InferSchema, Path extends string = string, Response = unknown >( @@ -671,7 +684,7 @@ export default class Elysia { * ``` */ post< - Schema extends TypedSchema = {}, + Schema extends InferSchema = InferSchema, Path extends string = string, Response = unknown >( @@ -703,7 +716,7 @@ export default class Elysia { * ``` */ put< - Schema extends TypedSchema = {}, + Schema extends InferSchema = InferSchema, Path extends string = string, Response = unknown >( @@ -735,7 +748,7 @@ export default class Elysia { * ``` */ patch< - Schema extends TypedSchema = {}, + Schema extends InferSchema = InferSchema, Path extends string = string, Response = unknown >( @@ -767,7 +780,7 @@ export default class Elysia { * ``` */ delete< - Schema extends TypedSchema = {}, + Schema extends InferSchema = InferSchema, Path extends string = string, Response = unknown >( @@ -799,7 +812,7 @@ export default class Elysia { * ``` */ options< - Schema extends TypedSchema = {}, + Schema extends InferSchema = InferSchema, Path extends string = string, Response = unknown >( @@ -826,7 +839,7 @@ export default class Elysia { * ``` */ all< - Schema extends TypedSchema = {}, + Schema extends InferSchema = InferSchema, Path extends string = string, Response = unknown >( @@ -858,7 +871,7 @@ export default class Elysia { * ``` */ head< - Schema extends TypedSchema = {}, + Schema extends InferSchema = InferSchema, Path extends string = string, Response = unknown >( @@ -890,7 +903,7 @@ export default class Elysia { * ``` */ trace< - Schema extends TypedSchema = {}, + Schema extends InferSchema = InferSchema, Path extends string = string, Response = unknown >( @@ -922,7 +935,7 @@ export default class Elysia { * ``` */ connect< - Schema extends TypedSchema = {}, + Schema extends InferSchema = InferSchema, Path extends string = string, Response = unknown >( @@ -955,7 +968,7 @@ export default class Elysia { */ route< Method extends HTTPMethod = HTTPMethod, - Schema extends TypedSchema = {}, + Schema extends InferSchema = InferSchema, Path extends string = string, Response = unknown >( @@ -1104,21 +1117,22 @@ export default class Elysia { * ``` */ schema< - Schema extends TypedSchema = TypedSchema, + Schema extends InferSchema = InferSchema, NewInstance = Elysia<{ request: Instance['request'] store: Instance['store'] schema: MergeSchema }> >(schema: Schema): NewInstance { + const defs = this.store[DEFS] + this.$schema = { - body: getSchemaValidator(schema?.body), - headers: getSchemaValidator(schema?.headers), - params: getSchemaValidator(schema?.params), - query: getSchemaValidator(schema?.query), - response: getSchemaValidator( - schema?.response?.['200'] ?? schema.response - ) + body: getSchemaValidator(schema.body, defs), + headers: getSchemaValidator(schema?.headers, defs, true), + params: getSchemaValidator(schema?.params, defs), + query: getSchemaValidator(schema?.query, defs), + // @ts-ignore + response: getSchemaValidator(schema?.response, defs) } return this as unknown as NewInstance @@ -1421,6 +1435,20 @@ export default class Elysia { get modules() { return Promise.all(this.lazyLoadModules) } + + setModel>( + record: Recorder + ): Elysia<{ + store: Instance['store'] & { + [Defs in typeof DEFS]: Recorder + } + request: Instance['request'] + schema: Instance['schema'] + }> { + Object.assign(this.store[DEFS], record) + + return this as unknown as any + } } export { Elysia, Router } @@ -1428,6 +1456,7 @@ export { Type as t } from '@sinclair/typebox' export { SCHEMA, + DEFS, getPath, createValidationError, getSchemaValidator diff --git a/src/schema.ts b/src/schema.ts index eae82f82..5b3f64fd 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -1,4 +1,4 @@ -import type { TSchema } from '@sinclair/typebox' +import { Kind, TSchema } from '@sinclair/typebox' import type { OpenAPIV2 } from 'openapi-types' import type { HTTPMethod, LocalHook } from './types' @@ -9,25 +9,39 @@ export const toOpenAPIPath = (path: string) => .map((x) => (x.startsWith(':') ? `{${x.slice(1, x.length)}}` : x)) .join('/') -export const mapProperties = (name: string, schema: TSchema | undefined) => - Object.entries(schema?.properties ?? []).map(([key, value]) => ({ +export const mapProperties = ( + name: string, + schema: TSchema | string | undefined, + models: Record +) => { + if (schema === undefined) return [] + + if (typeof schema === 'string') + if (schema in models) schema = models[schema] + else throw new Error(`Can't find model ${schema}`) + + return Object.entries(schema?.properties ?? []).map(([key, value]) => ({ in: name, name: key, // @ts-ignore type: value?.type, + // @ts-ignore required: schema!.required?.includes(key) ?? false })) +} export const registerSchemaPath = ({ schema, path, method, - hook + hook, + models }: { schema: OpenAPIV2.PathsObject path: string method: HTTPMethod hook?: LocalHook + models: Record }) => { path = toOpenAPIPath(path) @@ -35,13 +49,43 @@ export const registerSchemaPath = ({ const paramsSchema = hook?.schema?.params const headerSchema = hook?.schema?.headers const querySchema = hook?.schema?.query - const responseSchema = hook?.schema?.response - const detail = hook?.schema?.detail + let responseSchema = hook?.schema?.response + + if (typeof responseSchema === 'object') { + if (Kind in responseSchema) { + responseSchema = { + // @ts-ignore + '200': { + schema: responseSchema + } + } + } else { + Object.entries(responseSchema as Record).forEach( + ([key, value]) => { + if (typeof value === 'string') + // @ts-ignore + responseSchema[key] = { + schema: { + $ref: `#/definitions/${value}` + } + } + } + ) + } + } else if (typeof responseSchema === 'string') + responseSchema = { + // @ts-ignore + '200': { + schema: { + $ref: `#/definitions/${responseSchema}` + } + } + } const parameters = [ - ...mapProperties('header', headerSchema), - ...mapProperties('path', paramsSchema), - ...mapProperties('query', querySchema) + ...mapProperties('header', headerSchema, models), + ...mapProperties('path', paramsSchema, models), + ...mapProperties('query', querySchema, models) ] if (bodySchema) @@ -50,7 +94,12 @@ export const registerSchemaPath = ({ name: 'body', required: true, // @ts-ignore - schema: bodySchema + schema: + typeof bodySchema === 'string' + ? { + $ref: `#/definitions/${bodySchema}` + } + : bodySchema }) schema[path] = { @@ -61,15 +110,10 @@ export const registerSchemaPath = ({ : {}), ...(responseSchema ? { - responses: { - '200': { - description: 'Default response', - schema: responseSchema - } - } + responses: responseSchema } : {}), - ...detail + ...hook?.schema?.detail } } } diff --git a/src/types.ts b/src/types.ts index 83822cfb..c82346e4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,7 +4,7 @@ import type { Serve, Server } from 'bun' import type { Context, PreContext } from './context' import type { Static, TObject, TSchema } from '@sinclair/typebox' import type { TypeCheck } from '@sinclair/typebox/compiler' -import type { SCHEMA } from './utils' +import type { SCHEMA, DEFS } from './utils' import type { OpenAPIV2 } from 'openapi-types' export type WithArray = T | T[] @@ -13,11 +13,12 @@ export type ObjectValues = T[keyof T] export interface ElysiaInstance< Instance extends { store?: Record & - Record> + Record> & + Record request?: Record schema?: TypedSchema } = { - store: Record + store: Record request: {} schema: {} } @@ -37,7 +38,13 @@ export type Handler< CatchResponse = Route['response'] > = ( context: Context & Instance['request'] -) => Route['response'] extends CatchResponse +) => // Catch function +Route['response'] extends (models: Record) => TSchema + ? ReturnType extends CatchResponse + ? MaybePromise | Response + : MaybePromise> | Response + : // Catch non-function + Route['response'] extends CatchResponse ? MaybePromise | Response : MaybePromise | Response @@ -121,44 +128,65 @@ export interface RegisteredHook< error: ErrorHandler[] } -export interface TypedSchema { - body?: TSchema - headers?: TObject - query?: TObject - params?: TObject - response?: TSchema | Record +export interface TypedSchema { + body?: TSchema | ModelName + headers?: TObject | ModelName + query?: TObject | ModelName + params?: TObject | ModelName + response?: + | TSchema + | Record + | ModelName + | Record } +export type InferSchema = TypedSchema< + Exclude +> + export type UnwrapSchema< - Schema extends TSchema | undefined, + Schema extends TSchema | undefined | string, + Instance extends ElysiaInstance = ElysiaInstance, Fallback = unknown -> = Schema extends NonNullable ? Static> : Fallback - -export type TypedSchemaToRoute = { - body: UnwrapSchema +> = Schema extends string + ? Instance['store'][typeof DEFS] extends { + [name in Schema]: infer NamedSchema extends TSchema + } + ? UnwrapSchema + : keyof Instance['store'][typeof DEFS] + : NonNullable extends TSchema + ? Static> + : Fallback + +export type TypedSchemaToRoute< + Schema extends TypedSchema, + Instance extends ElysiaInstance +> = { + body: UnwrapSchema headers: UnwrapSchema< - Schema['headers'] + Schema['headers'], + Instance > extends infer Result extends Record ? Result : undefined - query: UnwrapSchema extends infer Result extends Record< - string, - any - > + query: UnwrapSchema< + Schema['query'], + Instance + > extends infer Result extends Record ? Result : undefined - params: UnwrapSchema extends infer Result extends Record< - string, - any - > + params: UnwrapSchema< + Schema['params'], + Instance + > extends infer Result extends Record ? Result : undefined - response: Schema['response'] extends TSchema - ? UnwrapSchema + response: Schema['response'] extends TSchema | string + ? UnwrapSchema : Schema['response'] extends { - [k in string]: TSchema + [k in string]: TSchema | string } - ? UnwrapSchema> + ? UnwrapSchema, Instance> : unknown } @@ -174,7 +202,10 @@ export type HookHandler< Schema extends TypedSchema = TypedSchema, Instance extends ElysiaInstance = ElysiaInstance, Path extends string = string, - Typed extends TypedSchemaToRoute = TypedSchemaToRoute + Typed extends TypedSchemaToRoute = TypedSchemaToRoute< + Schema, + Instance + > > = Handler< Typed['params'] extends {} ? Omit & { @@ -227,8 +258,8 @@ export type RouteToSchema< Instance['schema'] > > = FinalSchema['params'] extends NonNullable - ? TypedSchemaToRoute - : Omit, 'params'> & { + ? TypedSchemaToRoute + : Omit, 'params'> & { params: Record, string> } diff --git a/src/utils.ts b/src/utils.ts index 859b3dd4..41ad92c9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,9 +1,9 @@ +import { Kind, TSchema, Type } from '@sinclair/typebox' import { TypeCheck, TypeCompiler, type ValueError } from '@sinclair/typebox/compiler' -import type { TSchema } from '@sinclair/typebox' import type { DeepMergeTwoTypes, LifeCycleStore, @@ -14,6 +14,7 @@ import type { // ? Internal property export const SCHEMA: unique symbol = Symbol('schema') +export const DEFS: unique symbol = Symbol('definitions') export const mergeObjectArray = (a: T | T[], b: T | T[]): T[] => [ ...(Array.isArray(a) ? a : [a]), @@ -149,13 +150,54 @@ export const createValidationError = ( }) } -export const getSchemaValidator = < - Schema extends TSchema | undefined = undefined ->( - schema: Schema, +export const getSchemaValidator = ( + s: TSchema | string | undefined, + models: Record, additionalProperties = false ) => { - if (!schema) return + if (!s) return + if (typeof s === 'string' && !(s in models)) return + + const schema: TSchema = typeof s === 'string' ? models[s] : s + + // @ts-ignore + if (schema.type === 'object' && 'additionalProperties' in schema === false) + schema.additionalProperties = additionalProperties + + return TypeCompiler.Compile(schema) +} + +export const getResponseSchemaValidator = ( + s: TypedSchema['response'] | undefined, + models: Record, + additionalProperties = false +) => { + if (!s) return + if (typeof s === 'string' && !(s in models)) return + + const maybeSchemaOrRecord = typeof s === 'string' ? models[s] : s + + const schema: TSchema = + Kind in maybeSchemaOrRecord + ? maybeSchemaOrRecord + : Type.Union( + Object.keys(maybeSchemaOrRecord) + .map((key): TSchema | undefined => { + const maybeNameOrSchema = maybeSchemaOrRecord[key] + + if (typeof maybeNameOrSchema === 'string') { + if (maybeNameOrSchema in models) { + const schema = models[maybeNameOrSchema] + return schema + } + + return undefined + } + + return maybeNameOrSchema + }) + .filter((a) => a) as TSchema[] + ) // @ts-ignore if (schema.type === 'object' && 'additionalProperties' in schema === false) diff --git a/test/models.test.ts b/test/models.test.ts new file mode 100644 index 00000000..76c16a0f --- /dev/null +++ b/test/models.test.ts @@ -0,0 +1,322 @@ +import { Elysia, SCHEMA, DEFS, t } from '../src' + +import { describe, expect, it } from 'bun:test' +import { req } from './utils' + +describe('Models', () => { + it('register models', async () => { + const app = new Elysia() + .setModel({ + string: t.String(), + number: t.Number() + }) + .setModel({ + boolean: t.Boolean() + }) + .get('/', ({ store }) => Object.keys(store[DEFS])) + + const res = await app.handle(req('/')).then((r) => r.json()) + + expect(res).toEqual(['string', 'number', 'boolean']) + }) + + it('map model parameters as OpenAPI schema', async () => { + const app = new Elysia() + .setModel({ + number: t.Number(), + string: t.String(), + boolean: t.Boolean(), + object: t.Object({ + string: t.String(), + boolean: t.Boolean() + }) + }) + .get('/defs', ({ store }) => store[SCHEMA]) + .get('/', () => 1, { + schema: { + query: 'object', + body: 'object', + params: 'object', + response: { + 200: 'boolean', + 300: 'number' + } + } as const + }) + + const res = await app.handle(req('/defs')).then((r) => r.json()) + + expect(res).toEqual({ + '/defs': { + get: {} + }, + '/': { + get: { + parameters: [ + { + in: 'path', + name: 'string', + type: 'string', + required: true + }, + { + in: 'path', + name: 'boolean', + type: 'boolean', + required: true + }, + { + in: 'query', + name: 'string', + type: 'string', + required: true + }, + { + in: 'query', + name: 'boolean', + type: 'boolean', + required: true + }, + { + in: 'body', + name: 'body', + required: true, + schema: { + $ref: '#/definitions/object' + } + } + ], + responses: { + '200': { + schema: { + $ref: '#/definitions/boolean' + } + }, + '300': { + schema: { + $ref: '#/definitions/number' + } + } + } + } + } + }) + }) + + it('map model and inline parameters as OpenAPI schema', async () => { + const app = new Elysia() + .setModel({ + number: t.Number(), + string: t.String(), + boolean: t.Boolean(), + object: t.Object({ + string: t.String(), + boolean: t.Boolean() + }) + }) + .get('/defs', ({ store }) => store[SCHEMA]) + .get('/', () => 1, { + schema: { + query: 'object', + body: t.Object({ + number: t.Number() + }), + params: 'object', + response: { + 200: 'boolean', + 300: 'number' + } + } as const + }) + + const res = await app.handle(req('/defs')).then((r) => r.json()) + + expect(res).toEqual({ + '/defs': { + get: {} + }, + '/': { + get: { + parameters: [ + { + in: 'path', + name: 'string', + type: 'string', + required: true + }, + { + in: 'path', + name: 'boolean', + type: 'boolean', + required: true + }, + { + in: 'query', + name: 'string', + type: 'string', + required: true + }, + { + in: 'query', + name: 'boolean', + type: 'boolean', + required: true + }, + { + in: 'body', + name: 'body', + required: true, + schema: { + type: 'object', + properties: { + number: { + type: 'number' + } + }, + required: ['number'], + additionalProperties: false + } + } + ], + responses: { + '200': { + schema: { + $ref: '#/definitions/boolean' + } + }, + '300': { + schema: { + $ref: '#/definitions/number' + } + } + } + } + } + }) + }) + + it('map model and inline response as OpenAPI schema', async () => { + const app = new Elysia() + .setModel({ + number: t.Number(), + string: t.String(), + boolean: t.Boolean(), + object: t.Object({ + string: t.String(), + boolean: t.Boolean() + }) + }) + .get('/defs', ({ store }) => store[SCHEMA]) + .get('/', () => 1, { + schema: { + response: { + 200: t.String(), + 300: 'number' + } + } as const + }) + + const res = await app.handle(req('/defs')).then((r) => r.json()) + + expect(res).toEqual({ + '/defs': { + get: {} + }, + '/': { + get: { + responses: { + '200': { + type: 'string' + }, + '300': { + schema: { + $ref: '#/definitions/number' + } + } + } + } + } + }) + }) + + it('map model default response', async () => { + const app = new Elysia() + .setModel({ + number: t.Number(), + string: t.String(), + boolean: t.Boolean(), + object: t.Object({ + string: t.String(), + boolean: t.Boolean() + }) + }) + .get('/defs', ({ store }) => store[SCHEMA]) + .get('/', () => 1, { + schema: { + response: 'number' + } as const + }) + + const res = await app.handle(req('/defs')).then((r) => r.json()) + + expect(res).toEqual({ + '/defs': { + get: {} + }, + '/': { + get: { + responses: { + '200': { + schema: { + $ref: '#/definitions/number' + } + } + } + } + } + }) + }) + + it('validate reference model', async () => { + const app = new Elysia() + .setModel({ + number: t.Number() + }) + .post('/', ({ body: { data } }) => data, { + schema: { + body: t.Object({ + data: t.Union([t.String(), t.Number()]) + }), + response: 'number' + } as const + }) + + const correct = await app.handle( + new Request('http://localhost/', { + method: 'POST', + headers: { + 'content-type': 'application/json' + }, + body: JSON.stringify({ + data: 1 + }) + }) + ) + + expect(correct.status).toBe(200) + + const wrong = await app.handle( + new Request('http://localhost/', { + method: 'POST', + headers: { + 'content-type': 'application/json' + }, + body: JSON.stringify({ + data: true + }) + }) + ) + + expect(wrong.status).toBe(400) + }) +}) diff --git a/test/schema.test.ts b/test/schema.test.ts index 009205ba..b8d656bf 100644 --- a/test/schema.test.ts +++ b/test/schema.test.ts @@ -152,43 +152,32 @@ describe('Schema', () => { expect(await valid.text()).toBe('salt') expect(valid.status).toBe(200) - // const invalidQuery = await app.handle( - // new Request('/user', { - // method: 'POST', - // body: JSON.stringify({ - // id: 6, - // username: '', - // profile: { - // name: 'A' - // } - // }) - // }) - // ) - - // expect(await invalidQuery.text()).toBe( - // "Invalid query, root should have required property 'name'" - // ) - // expect(valid.status).toBe(400) - - // const invalidBody = await app.handle( - // new Request('/user?name=salt', { - // method: 'POST', - // body: JSON.stringify({ - // id: 6, - // username: '', - // profile: {} - // }) - // }) - // ) - - // expect(await invalidQuery.text()).toBe( - // "Invalid query, root should have required property 'name'" - // ) - // expect(invalidBody.status).toBe(400) - - // expect(await invalidBody.text()).toBe( - // "Invalid body, .profile should have required property 'name'" - // ) - // expect(invalidBody.status).toBe(400) + const invalidQuery = await app.handle( + new Request('http://localhost/user', { + method: 'POST', + body: JSON.stringify({ + id: 6, + username: '', + profile: { + name: 'A' + } + }) + }) + ) + + expect(invalidQuery.status).toBe(400) + + const invalidBody = await app.handle( + new Request('http://localhost/user?name=salt', { + method: 'POST', + body: JSON.stringify({ + id: 6, + username: '', + profile: {} + }) + }) + ) + + expect(invalidBody.status).toBe(400) }) }) From ee4f7ee20ad46defd79527fd9bbdb1cfe66df59c Mon Sep 17 00:00:00 2001 From: saltyaom Date: Tue, 24 Jan 2023 18:00:45 +0700 Subject: [PATCH 10/11] :tada: feat: .rc.1 --- CHANGELOG.md | 7 +++ bun.lockb | Bin 41532 -> 41532 bytes example/a.ts | 28 ++++++++++ package.json | 2 +- src/context.ts | 10 ++-- src/index.ts | 79 ++++++++++++++++++++++------ src/schema.ts | 58 ++++++++++++++++++-- src/types.ts | 125 +++++++++++++++++++++++++++++++------------- test/models.test.ts | 4 +- 9 files changed, 250 insertions(+), 63 deletions(-) create mode 100644 example/a.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c747d0a2..613205e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 0.2.0-rc.1 - 24 Jan 2023 +Improvement: +- Map OpenAPI's schema detail on response +- Fix Type instantiation is excessively deep and possibly infinite +- Improve TypeScript inference time by removing recursive type in generic +- Inferred body is never instead of unknown + # 0.2.0-rc.0 - 23 Jan 2023 Feature: - Add support for reference model via `.setModel` diff --git a/bun.lockb b/bun.lockb index 2b95590eff1d0e47091c243ea695994c619f5c87..0fe53c1d3847df10351ac51a6ead4177bb653b69 100755 GIT binary patch delta 2258 zcmZ`)zi-n(6n=4?U$%p7rE)_Dic?Xc7KsF+Rw4s1zyJ#a7>NNXFu(v3Y#kW*8+wxG zF&y3}naqM^RJ$(J<Xk=4>*q(YaQE?E&!WvbLC2xnlooR=>(d+t=)oJA&1ih(4TWM^} zj@u?}$2~1?t1fRdHI@Ud2Bkn3v_OVfIMiw2R?3(;C2v>rZipG<6nNBW1}az2b_I5~ z7IrL#!^J*@OMES_DOSD)J9_uH6;Jb76AOP-Fd%zl2C%d;aN&;HdeY}`BNN~Oz(;JI zfs59u)_9!{l1N&qmWsS#h=Ds@Rvd<@7#3AS=F(%AOd_(tic!i+mvgOJ)|3W<4Qe3T z&>ApOu=8O!XI@Zw1s{7&fC(bh6``w}M^28<^Xy z&pqoY63ROAN_xoKu3klQqN+x`pFAQoy2S01V9h$-O2bvXCl-<%LNwkYT9Yj;cxh}d zSB7q8*OFYiWx^@ZeYFkn1R)c1YfKM7$f@F-Qm4{JIel O5PxnSoK9YLZvO{x6W|;G delta 2258 zcmZ`)J8KkC6h1qdeay?*VZ&sNV4MVlB!aWLB#t&pop?MaWk(d*K_+3OS?_|C+pt28!d z$7`LgV@Ju`Y0BG9jnzQ&VI|NhC6Fd&Hg)Q|wKAr5#oJZ9TVl#{^83_jK2)xr?b6uY z#<62D94_`KT;eHtbusspv7@)ct@IV21u^r&5`nBYMgU8z4lY=iTTccYF4O^T0=&l7 zd2rDp)fzAILn28h$-WSffWVpOQ6h?!F1IgK<&n0|h6AvzC||(yN>j%B!r#I&y7+M4(>JbAAcAfUf;@ zD%m%chbSBMv(@nVxb|Wo!a>kbhAI$Lw!G(Zk~XTCjo)x?chfP`?%lEgZpen0XozIP zM=}r#p4$MoQx<$RM-=8g+OjZe;U}yM!g+&+7H=>a9y^$!B*US<`F&fzv(s&fKUcO7 J$4`1!{{u&51`7ZH diff --git a/example/a.ts b/example/a.ts new file mode 100644 index 00000000..b9a3bb33 --- /dev/null +++ b/example/a.ts @@ -0,0 +1,28 @@ +import { Elysia, SCHEMA, t } from '../src' + +const app = new Elysia() + .get('/', () => 'Elysia') + .post('/', () => 'Elysia') + .get('/id/:id', () => 1) + .post('/mirror', ({ body }) => body, { + schema: { + query: t.Object({ + n: t.String() + }), + body: t.Object({ + username: t.String(), + password: t.String() + }) + } + }) + .get('/sign-in', ({ body }) => 'ok') + .get('/products/nendoroid/skadi', ({ body }) => 1) + .get('/id2/:id', ({ params }) => 1, { + schema: { + detail: { + 'summary': 'a', + } + } + }) + +type App = typeof app['store'][typeof SCHEMA]['/mirror']['POST']['body'] \ No newline at end of file diff --git a/package.json b/package.json index 0e3cb067..1c8e43b4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "elysia", "description": "Fast, and friendly Bun web framework", - "version": "0.2.0-rc.0", + "version": "0.2.0-rc.1", "author": { "name": "saltyAom", "url": "https://github.com/SaltyAom", diff --git a/src/context.ts b/src/context.ts index 54c6d1e8..27cca929 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,18 +1,16 @@ import { Elysia } from '.' import type { TypedRoute } from './types' -type UnwrapFn = T extends (...params: any) => any ? ReturnType : T - export interface Context< Route extends TypedRoute = TypedRoute, Store extends Elysia['store'] = Elysia['store'] > { request: Request - query: UnwrapFn extends undefined + query: Route['query'] extends undefined ? Record - : UnwrapFn - params: UnwrapFn - body: UnwrapFn + : Route['query'] + params: Route['params'] + body: Route['body'] store: Store set: { diff --git a/src/index.ts b/src/index.ts index dd66d7f5..50fe0005 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,8 +46,7 @@ import type { NoReturnHandler, ElysiaRoute, MaybePromise, - IsNever, - InferSchema + IsNever } from './types' import { type TSchema } from '@sinclair/typebox' @@ -652,7 +651,11 @@ export default class Elysia { * ``` */ get< - Schema extends InferSchema = InferSchema, + Schema extends TypedSchema< + Exclude + > = TypedSchema< + Exclude + >, Path extends string = string, Response = unknown >( @@ -684,7 +687,11 @@ export default class Elysia { * ``` */ post< - Schema extends InferSchema = InferSchema, + Schema extends TypedSchema< + Exclude + > = TypedSchema< + Exclude + >, Path extends string = string, Response = unknown >( @@ -716,7 +723,11 @@ export default class Elysia { * ``` */ put< - Schema extends InferSchema = InferSchema, + Schema extends TypedSchema< + Exclude + > = TypedSchema< + Exclude + >, Path extends string = string, Response = unknown >( @@ -748,7 +759,11 @@ export default class Elysia { * ``` */ patch< - Schema extends InferSchema = InferSchema, + Schema extends TypedSchema< + Exclude + > = TypedSchema< + Exclude + >, Path extends string = string, Response = unknown >( @@ -780,7 +795,11 @@ export default class Elysia { * ``` */ delete< - Schema extends InferSchema = InferSchema, + Schema extends TypedSchema< + Exclude + > = TypedSchema< + Exclude + >, Path extends string = string, Response = unknown >( @@ -812,7 +831,11 @@ export default class Elysia { * ``` */ options< - Schema extends InferSchema = InferSchema, + Schema extends TypedSchema< + Exclude + > = TypedSchema< + Exclude + >, Path extends string = string, Response = unknown >( @@ -839,7 +862,11 @@ export default class Elysia { * ``` */ all< - Schema extends InferSchema = InferSchema, + Schema extends TypedSchema< + Exclude + > = TypedSchema< + Exclude + >, Path extends string = string, Response = unknown >( @@ -871,7 +898,11 @@ export default class Elysia { * ``` */ head< - Schema extends InferSchema = InferSchema, + Schema extends TypedSchema< + Exclude + > = TypedSchema< + Exclude + >, Path extends string = string, Response = unknown >( @@ -903,7 +934,11 @@ export default class Elysia { * ``` */ trace< - Schema extends InferSchema = InferSchema, + Schema extends TypedSchema< + Exclude + > = TypedSchema< + Exclude + >, Path extends string = string, Response = unknown >( @@ -935,7 +970,11 @@ export default class Elysia { * ``` */ connect< - Schema extends InferSchema = InferSchema, + Schema extends TypedSchema< + Exclude + > = TypedSchema< + Exclude + >, Path extends string = string, Response = unknown >( @@ -967,8 +1006,12 @@ export default class Elysia { * ``` */ route< + Schema extends TypedSchema< + Exclude + > = TypedSchema< + Exclude + >, Method extends HTTPMethod = HTTPMethod, - Schema extends InferSchema = InferSchema, Path extends string = string, Response = unknown >( @@ -1117,7 +1160,11 @@ export default class Elysia { * ``` */ schema< - Schema extends InferSchema = InferSchema, + Schema extends TypedSchema< + Exclude + > = TypedSchema< + Exclude + >, NewInstance = Elysia<{ request: Instance['request'] store: Instance['store'] @@ -1391,7 +1438,7 @@ export default class Elysia { } for (let i = 0; i < this.event.start.length; i++) - this.event.start[i](this) + this.event.start[i](this as any) if (callback) callback(this.server!) @@ -1426,7 +1473,7 @@ export default class Elysia { this.server.stop() for (let i = 0; i < this.event.stop.length; i++) - await this.event.stop[i](this) + await this.event.stop[i](this as any) } /** diff --git a/src/schema.ts b/src/schema.ts index 5b3f64fd..85c469f6 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -21,6 +21,8 @@ export const mapProperties = ( else throw new Error(`Can't find model ${schema}`) return Object.entries(schema?.properties ?? []).map(([key, value]) => ({ + // @ts-ignore + ...value, in: name, name: key, // @ts-ignore @@ -53,34 +55,82 @@ export const registerSchemaPath = ({ if (typeof responseSchema === 'object') { if (Kind in responseSchema) { + const { type, properties, required, ...rest } = + responseSchema as typeof responseSchema & { + type: string + properties: Object + required: string[] + } + responseSchema = { - // @ts-ignore '200': { - schema: responseSchema + ...rest, + schema: { + type, + properties, + required + } } } } else { Object.entries(responseSchema as Record).forEach( ([key, value]) => { - if (typeof value === 'string') + if (typeof value === 'string') { + const { type, properties, required, ...rest } = models[ + value + ] as TSchema & { + type: string + properties: Object + required: string[] + } + // @ts-ignore responseSchema[key] = { + ...rest, schema: { $ref: `#/definitions/${value}` } } + } else { + const { type, properties, required, ...rest } = + value as typeof value & { + type: string + properties: Object + required: string[] + } + + // @ts-ignore + responseSchema[key] = { + ...rest, + schema: { + type, + properties, + required + } + } + } } ) } - } else if (typeof responseSchema === 'string') + } else if (typeof responseSchema === 'string') { + const { type, properties, required, ...rest } = models[ + responseSchema + ] as TSchema & { + type: string + properties: Object + required: string[] + } + responseSchema = { // @ts-ignore '200': { + ...rest, schema: { $ref: `#/definitions/${responseSchema}` } } } + } const parameters = [ ...mapProperties('header', headerSchema, models), diff --git a/src/types.ts b/src/types.ts index c82346e4..708189d3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,7 +18,9 @@ export interface ElysiaInstance< request?: Record schema?: TypedSchema } = { - store: Record + store: Record & + Record & + Record request: {} schema: {} } @@ -40,11 +42,11 @@ export type Handler< context: Context & Instance['request'] ) => // Catch function Route['response'] extends (models: Record) => TSchema - ? ReturnType extends CatchResponse + ? undefined extends ReturnType ? MaybePromise | Response : MaybePromise> | Response : // Catch non-function - Route['response'] extends CatchResponse + undefined extends Route['response'] ? MaybePromise | Response : MaybePromise | Response @@ -140,10 +142,6 @@ export interface TypedSchema { | Record } -export type InferSchema = TypedSchema< - Exclude -> - export type UnwrapSchema< Schema extends TSchema | undefined | string, Instance extends ElysiaInstance = ElysiaInstance, @@ -152,9 +150,9 @@ export type UnwrapSchema< ? Instance['store'][typeof DEFS] extends { [name in Schema]: infer NamedSchema extends TSchema } - ? UnwrapSchema - : keyof Instance['store'][typeof DEFS] - : NonNullable extends TSchema + ? Static + : Fallback + : Schema extends TSchema ? Static> : Fallback @@ -190,6 +188,14 @@ export type TypedSchemaToRoute< : unknown } +export type AnyTypedSchema = { + body: unknown + headers: Record | undefined + query: Record | undefined + params: Record | undefined + response: TSchema | unknown | undefined +} + export type SchemaValidator = { body?: TypeCheck headers?: TypeCheck @@ -202,10 +208,7 @@ export type HookHandler< Schema extends TypedSchema = TypedSchema, Instance extends ElysiaInstance = ElysiaInstance, Path extends string = string, - Typed extends TypedSchemaToRoute = TypedSchemaToRoute< - Schema, - Instance - > + Typed extends AnyTypedSchema = TypedSchemaToRoute > = Handler< Typed['params'] extends {} ? Omit & { @@ -268,32 +271,84 @@ export type ElysiaRoute< Schema extends TypedSchema = TypedSchema, Instance extends ElysiaInstance = ElysiaInstance, Path extends string = string, - CatchResponse = unknown, - Typed extends RouteToSchema = RouteToSchema< - Schema, - Instance, - Path - > + CatchResponse = unknown > = Elysia<{ request: Instance['request'] - store: Instance['store'] & - Record< - typeof SCHEMA, - Record< - Path, - Record< - Method, - Typed & { - response: Typed['response'] extends CatchResponse - ? CatchResponse - : Typed['response'] - } - > - > - > + store: Instance['store'] & { + [SCHEMA]: { + [path in Path]: { + [method in Method]: TypedRouteToEden< + Schema, + Instance, + Path + > extends infer FinalSchema extends AnyTypedSchema + ? Omit & { + response: undefined extends FinalSchema['response'] + ? { + '200': CatchResponse + } + : FinalSchema['response'] + } + : never + } + } + } schema: Instance['schema'] }> +export type TypedRouteToEden< + Schema extends TypedSchema = TypedSchema, + Instance extends ElysiaInstance = ElysiaInstance, + Path extends string = string, + FinalSchema extends MergeSchema = MergeSchema< + Schema, + Instance['schema'] + > +> = FinalSchema['params'] extends NonNullable + ? TypedSchemaToEden + : Omit, 'params'> & { + params: Record, string> + } + +export type TypedSchemaToEden< + Schema extends TypedSchema, + Instance extends ElysiaInstance +> = { + body: UnwrapSchema + headers: UnwrapSchema< + Schema['headers'], + Instance + > extends infer Result extends Record + ? Result + : undefined + query: UnwrapSchema< + Schema['query'], + Instance + > extends infer Result extends Record + ? Result + : undefined + params: UnwrapSchema< + Schema['params'], + Instance + > extends infer Result extends Record + ? Result + : undefined + response: Schema['response'] extends TSchema | string + ? { + '200': UnwrapSchema + } + : Schema['response'] extends { + [x in string]: TSchema | string + } + ? { + [key in keyof Schema['response']]: UnwrapSchema< + Schema['response'][key], + Instance + > + } + : unknown +} + export type LocalHandler< Schema extends TypedSchema = TypedSchema, Instance extends ElysiaInstance = ElysiaInstance, diff --git a/test/models.test.ts b/test/models.test.ts index 76c16a0f..738e0e7c 100644 --- a/test/models.test.ts +++ b/test/models.test.ts @@ -226,7 +226,9 @@ describe('Models', () => { get: { responses: { '200': { - type: 'string' + schema: { + type: 'string' + } }, '300': { schema: { From cb89dcc0bf0f02d90a12f1bb02d09ae9c107b0c5 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Fri, 27 Jan 2023 17:48:21 +0700 Subject: [PATCH 11/11] :tada: feat: 0.2 release --- example/a.ts | 21 +++++++++++++++------ example/schema.ts | 14 +++++--------- package.json | 2 +- src/index.ts | 11 ++++++++--- src/schema.ts | 2 ++ 5 files changed, 31 insertions(+), 19 deletions(-) diff --git a/example/a.ts b/example/a.ts index b9a3bb33..fc8f81f8 100644 --- a/example/a.ts +++ b/example/a.ts @@ -9,10 +9,19 @@ const app = new Elysia() query: t.Object({ n: t.String() }), - body: t.Object({ - username: t.String(), - password: t.String() - }) + body: t.Object( + { + username: t.String(), + password: t.String() + }, + { + description: 'An expected body' + } + ), + detail: { + summary: 'Sign in the user', + tags: ['authentication'] + } } }) .get('/sign-in', ({ body }) => 'ok') @@ -20,9 +29,9 @@ const app = new Elysia() .get('/id2/:id', ({ params }) => 1, { schema: { detail: { - 'summary': 'a', + summary: 'a' } } }) -type App = typeof app['store'][typeof SCHEMA]['/mirror']['POST']['body'] \ No newline at end of file +type App = typeof app['store'][typeof SCHEMA]['/mirror']['POST']['body'] diff --git a/example/schema.ts b/example/schema.ts index deb07750..8966d6ca 100644 --- a/example/schema.ts +++ b/example/schema.ts @@ -1,18 +1,14 @@ import { Elysia, t, SCHEMA, DEFS } from '../src' const app = new Elysia() - .setModel( - 'a', - t.Object({ + .setModel({ + a: t.Object({ response: t.String() - }) - ) - .setModel( - 'b', - t.Object({ + }), + b: t.Object({ response: t.Number() }) - ) + }) // Strictly validate response .get('/', () => 'hi') // Strictly validate body and response diff --git a/package.json b/package.json index 1c8e43b4..d4f45acf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "elysia", "description": "Fast, and friendly Bun web framework", - "version": "0.2.0-rc.1", + "version": "0.2.0", "author": { "name": "saltyAom", "url": "https://github.com/SaltyAom", diff --git a/src/index.ts b/src/index.ts index 50fe0005..e9414ffa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1053,7 +1053,9 @@ export default class Elysia { schema: Instance['schema'] }> >(name: Key, value: Value): NewInstance { - ;(this.store as Record)[name] = value + if (!(name in this.store)) { + ;(this.store as Record)[name] = value + } return this as unknown as NewInstance } @@ -1080,7 +1082,7 @@ export default class Elysia { }> >(name: Name, value: Value): NewInstance { if (!this.decorators) this.decorators = {} - this.decorators[name] = value + if (!(name in this.decorators)) this.decorators[name] = value return this as unknown as NewInstance } @@ -1492,7 +1494,10 @@ export default class Elysia { request: Instance['request'] schema: Instance['schema'] }> { - Object.assign(this.store[DEFS], record) + Object.entries(record).forEach(([key, value]) => { + // @ts-ignore + if (!(key in this.store[DEFS])) this.store[DEFS][key] = value + }) return this as unknown as any } diff --git a/src/schema.ts b/src/schema.ts index 85c469f6..bdd9963e 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -76,6 +76,7 @@ export const registerSchemaPath = ({ Object.entries(responseSchema as Record).forEach( ([key, value]) => { if (typeof value === 'string') { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { type, properties, required, ...rest } = models[ value ] as TSchema & { @@ -113,6 +114,7 @@ export const registerSchemaPath = ({ ) } } else if (typeof responseSchema === 'string') { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { type, properties, required, ...rest } = models[ responseSchema ] as TSchema & {