0
0
mirror of https://github.com/honojs/hono.git synced 2024-11-24 19:26:56 +01:00

feat(types): allow passing interfaces as Bindings / Variables (#3136)

* feat(types): allow passing `interface`s as Bindings / Variables

* test(context): add test for c.var

* fix lint warning

* test(types): add test for Bindings types
This commit is contained in:
ottomated 2024-07-16 05:41:38 -07:00 committed by GitHub
parent 9987b5908f
commit 1cee728193
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 119 additions and 9 deletions

View File

@ -88,6 +88,13 @@ describe('Context', () => {
expect(c.get('foo2')).toBe(undefined) expect(c.get('foo2')).toBe(undefined)
}) })
it('c.var', async () => {
expect(c.var.foo).toBe(undefined)
c.set('foo', 'bar')
expect(c.var.foo).toBe('bar')
expect(c.var.foo2).toBe(undefined)
})
it('c.notFound()', async () => { it('c.notFound()', async () => {
const res = c.notFound() const res = c.notFound()
expect(res).instanceOf(Response) expect(res).instanceOf(Response)

View File

@ -265,7 +265,7 @@ export class Context<
* ``` * ```
*/ */
env: E['Bindings'] = {} env: E['Bindings'] = {}
#var: E['Variables'] | undefined #var: Map<unknown, unknown> | undefined
finalized: boolean = false finalized: boolean = false
/** /**
* `.error` can get the error object from the middleware if the Handler throws an error. * `.error` can get the error object from the middleware if the Handler throws an error.
@ -521,9 +521,9 @@ export class Context<
* ``` * ```
``` ```
*/ */
set: Set<E> = (key: string, value: unknown) => { set: Set<E> = (key: unknown, value: unknown) => {
this.#var ??= {} this.#var ??= new Map()
this.#var[key as string] = value this.#var.set(key, value)
} }
/** /**
@ -539,8 +539,8 @@ export class Context<
* }) * })
* ``` * ```
*/ */
get: Get<E> = (key: string) => { get: Get<E> = (key: unknown) => {
return this.#var ? this.#var[key] : undefined return this.#var ? this.#var.get(key) : undefined
} }
/** /**
@ -558,7 +558,11 @@ export class Context<
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
ContextVariableMap & (IsAny<E['Variables']> extends true ? Record<string, any> : E['Variables']) ContextVariableMap & (IsAny<E['Variables']> extends true ? Record<string, any> : E['Variables'])
> { > {
return { ...this.#var } as never if (!this.#var) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return {} as any
}
return Object.fromEntries(this.#var)
} }
newResponse: NewResponse = ( newResponse: NewResponse = (

View File

@ -3585,3 +3585,36 @@ describe('Compatible with extended Hono classes, such Zod OpenAPI Hono.', () =>
expect(res.status).toBe(200) expect(res.status).toBe(200)
}) })
}) })
describe('Generics for Bindings and Variables', () => {
interface CloudflareBindings {
MY_VARIABLE: string
}
it('Should not throw type errors', () => {
// @ts-expect-error Bindings should extend object
new Hono<{
Bindings: number
}>()
const appWithInterface = new Hono<{
Bindings: CloudflareBindings
}>()
appWithInterface.get('/', (c) => {
expectTypeOf(c.env.MY_VARIABLE).toMatchTypeOf<string>()
return c.text('/')
})
const appWithType = new Hono<{
Bindings: {
foo: string
}
}>()
appWithType.get('/', (c) => {
expectTypeOf(c.env.foo).toMatchTypeOf<string>()
return c.text('Hello Hono!')
})
})
})

View File

@ -7,3 +7,36 @@ describe('hono/quick preset', () => {
expect(getRouterName(app)).toBe('SmartRouter + LinearRouter') expect(getRouterName(app)).toBe('SmartRouter + LinearRouter')
}) })
}) })
describe('Generics for Bindings and Variables', () => {
interface CloudflareBindings {
MY_VARIABLE: string
}
it('Should not throw type errors', () => {
// @ts-expect-error Bindings should extend object
new Hono<{
Bindings: number
}>()
const appWithInterface = new Hono<{
Bindings: CloudflareBindings
}>()
appWithInterface.get('/', (c) => {
expectTypeOf(c.env.MY_VARIABLE).toMatchTypeOf<string>()
return c.text('/')
})
const appWithType = new Hono<{
Bindings: {
foo: string
}
}>()
appWithType.get('/', (c) => {
expectTypeOf(c.env.foo).toMatchTypeOf<string>()
return c.text('Hello Hono!')
})
})
})

View File

@ -7,3 +7,36 @@ describe('hono/tiny preset', () => {
expect(getRouterName(app)).toBe('PatternRouter') expect(getRouterName(app)).toBe('PatternRouter')
}) })
}) })
describe('Generics for Bindings and Variables', () => {
interface CloudflareBindings {
MY_VARIABLE: string
}
it('Should not throw type errors', () => {
// @ts-expect-error Bindings should extend object
new Hono<{
Bindings: number
}>()
const appWithInterface = new Hono<{
Bindings: CloudflareBindings
}>()
appWithInterface.get('/', (c) => {
expectTypeOf(c.env.MY_VARIABLE).toMatchTypeOf<string>()
return c.text('/')
})
const appWithType = new Hono<{
Bindings: {
foo: string
}
}>()
appWithType.get('/', (c) => {
expectTypeOf(c.env.foo).toMatchTypeOf<string>()
return c.text('Hello Hono!')
})
})
})

View File

@ -24,8 +24,8 @@ import type {
////// ////// ////// //////
//////////////////////////////////////// ////////////////////////////////////////
export type Bindings = Record<string, unknown> export type Bindings = object
export type Variables = Record<string, unknown> export type Variables = object
export type BlankEnv = {} export type BlankEnv = {}
export type Env = { export type Env = {