mirror of
https://github.com/honojs/hono.git
synced 2024-11-21 18:18:57 +01:00
feat(types): allow passing interface
s as Bindings / Variables [2]
Co-authored-by: Ottomated <otto@ottomated.net>
This commit is contained in:
parent
2d0135956c
commit
75584a2491
@ -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 = (
|
||||||
|
@ -47,7 +47,7 @@ const errorHandler = (err: Error | HTTPResponseError, c: Context) => {
|
|||||||
|
|
||||||
type GetPath<E extends Env> = (request: Request, options?: { env?: E['Bindings'] }) => string
|
type GetPath<E extends Env> = (request: Request, options?: { env?: E['Bindings'] }) => string
|
||||||
|
|
||||||
export type HonoOptions<E extends Env> = {
|
export type HonoOptions<E extends Env<any, any>> = {
|
||||||
/**
|
/**
|
||||||
* `strict` option specifies whether to distinguish whether the last path is a directory or not.
|
* `strict` option specifies whether to distinguish whether the last path is a directory or not.
|
||||||
*
|
*
|
||||||
@ -99,7 +99,7 @@ type MountOptions =
|
|||||||
replaceRequest?: MountReplaceRequest
|
replaceRequest?: MountReplaceRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string = '/'> {
|
class Hono<E extends Env<any, any> = Env, S extends Schema = {}, BasePath extends string = '/'> {
|
||||||
get!: HandlerInterface<E, 'get', S, BasePath>
|
get!: HandlerInterface<E, 'get', S, BasePath>
|
||||||
post!: HandlerInterface<E, 'post', S, BasePath>
|
post!: HandlerInterface<E, 'post', S, BasePath>
|
||||||
put!: HandlerInterface<E, 'put', S, BasePath>
|
put!: HandlerInterface<E, 'put', S, BasePath>
|
||||||
|
@ -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: 'my_value'
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Should not throw type errors', () => {
|
||||||
|
// @ts-expect-error Bindings should be Record<string, unknown> or interface
|
||||||
|
new Hono<{
|
||||||
|
Bindings: string[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
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!')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -13,8 +13,9 @@ import type { BlankEnv, BlankSchema, Env, Schema } from './types'
|
|||||||
* @template S - The schema type.
|
* @template S - The schema type.
|
||||||
* @template BasePath - The base path type.
|
* @template BasePath - The base path type.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class Hono<
|
export class Hono<
|
||||||
E extends Env = BlankEnv,
|
E extends Env<E['Bindings'], E['Variables']> = BlankEnv,
|
||||||
S extends Schema = BlankSchema,
|
S extends Schema = BlankSchema,
|
||||||
BasePath extends string = '/'
|
BasePath extends string = '/'
|
||||||
> extends HonoBase<E, S, BasePath> {
|
> extends HonoBase<E, S, BasePath> {
|
||||||
|
@ -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: 'my_value'
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Should not throw type errors', () => {
|
||||||
|
// @ts-expect-error Bindings should be Record<string, unknown> or interface
|
||||||
|
new Hono<{
|
||||||
|
Bindings: string[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
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!')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -11,7 +11,7 @@ import { TrieRouter } from '../router/trie-router'
|
|||||||
import type { BlankEnv, BlankSchema, Env, Schema } from '../types'
|
import type { BlankEnv, BlankSchema, Env, Schema } from '../types'
|
||||||
|
|
||||||
export class Hono<
|
export class Hono<
|
||||||
E extends Env = BlankEnv,
|
E extends Env<E['Bindings'], E['Variables']> = BlankEnv,
|
||||||
S extends Schema = BlankSchema,
|
S extends Schema = BlankSchema,
|
||||||
BasePath extends string = '/'
|
BasePath extends string = '/'
|
||||||
> extends HonoBase<E, S, BasePath> {
|
> extends HonoBase<E, S, BasePath> {
|
||||||
|
@ -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: 'my_value'
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Should not throw type errors', () => {
|
||||||
|
// @ts-expect-error Bindings should be Record<string, unknown> or interface
|
||||||
|
new Hono<{
|
||||||
|
Bindings: string[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
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!')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -9,7 +9,7 @@ import { PatternRouter } from '../router/pattern-router'
|
|||||||
import type { BlankEnv, BlankSchema, Env, Schema } from '../types'
|
import type { BlankEnv, BlankSchema, Env, Schema } from '../types'
|
||||||
|
|
||||||
export class Hono<
|
export class Hono<
|
||||||
E extends Env = BlankEnv,
|
E extends Env<E['Bindings'], E['Variables']> = BlankEnv,
|
||||||
S extends Schema = BlankSchema,
|
S extends Schema = BlankSchema,
|
||||||
BasePath extends string = '/'
|
BasePath extends string = '/'
|
||||||
> extends HonoBase<E, S, BasePath> {
|
> extends HonoBase<E, S, BasePath> {
|
||||||
|
12
src/types.ts
12
src/types.ts
@ -12,6 +12,7 @@ import type { StatusCode } from './utils/http-status'
|
|||||||
import type {
|
import type {
|
||||||
IfAnyThenEmptyObject,
|
IfAnyThenEmptyObject,
|
||||||
IsAny,
|
IsAny,
|
||||||
|
IsObject,
|
||||||
JSONValue,
|
JSONValue,
|
||||||
RemoveBlankRecord,
|
RemoveBlankRecord,
|
||||||
Simplify,
|
Simplify,
|
||||||
@ -24,13 +25,14 @@ import type {
|
|||||||
////// //////
|
////// //////
|
||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
|
|
||||||
export type Bindings = Record<string, unknown>
|
export type Bindings<B> = IsObject<B> extends true ? B : Record<string, unknown>
|
||||||
export type Variables = Record<string, unknown>
|
export type Variables<V> = IsObject<V> extends true ? V : Record<string, unknown>
|
||||||
|
|
||||||
export type BlankEnv = {}
|
export type BlankEnv = {}
|
||||||
export type Env = {
|
|
||||||
Bindings?: Bindings
|
export type Env<B = object, V = object> = {
|
||||||
Variables?: Variables
|
Bindings?: Bindings<B>
|
||||||
|
Variables?: Variables<V>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Next = () => Promise<void>
|
export type Next = () => Promise<void>
|
||||||
|
@ -93,3 +93,5 @@ export type HasRequiredKeys<BaseType extends object> = RequiredKeysOf<BaseType>
|
|||||||
: true
|
: true
|
||||||
|
|
||||||
export type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false
|
export type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false
|
||||||
|
|
||||||
|
export type IsObject<T> = T extends object ? (T extends any[] ? false : true) : false
|
||||||
|
Loading…
Reference in New Issue
Block a user