Skip to content

Commit

Permalink
fix(types): #2912: interfaces array's respond typed as never (#2915)
Browse files Browse the repository at this point in the history
* feat: add `DeepSimplify` type

* fix(types): #2912: interfaces array's respond typed as `never`

* refactor: wording `DeepSimplify` => `SimplifyDeepArray`

* docs(jsdoc): add jsdoc for `Simplify`, `SimplifyDeepArray`
  • Loading branch information
NamesMT authored Jun 6, 2024
1 parent fe0d787 commit 5beac4e
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 10 deletions.
25 changes: 25 additions & 0 deletions src/client/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,13 @@ describe('Merge path with `app.route()`', () => {
ok: true,
})
}),
http.get('http://localhost/api/searchArray', async () => {
return HttpResponse.json([
{
ok: true,
},
])
}),
http.get('http://localhost/api/foo', async () => {
return HttpResponse.json({
ok: true,
Expand Down Expand Up @@ -537,6 +544,24 @@ describe('Merge path with `app.route()`', () => {
expect(data.ok).toBe(true)
})

it('Should have correct types - with array of interfaces', async () => {
interface Result {
ok: boolean
okUndefined?: boolean
}
type Results = Result[]

const results: Results = [{ ok: true }]
const base = new Hono<Env>().basePath('/api')
const app = base.get('/searchArray', (c) => c.json(results))
type AppType = typeof app
const client = hc<AppType>('http://localhost')
const res = await client.api.searchArray.$get()
const data = await res.json()
type verify = Expect<Equal<Results, typeof data>>
expect(data[0].ok).toBe(true)
})

it('Should allow a Date object and return it as a string', async () => {
const app = new Hono()
const route = app.get('/api/foo', (c) => c.json({ datetime: new Date() }))
Expand Down
18 changes: 9 additions & 9 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { HonoRequest } from './request'
import type { Env, FetchEventLike, Input, NotFoundHandler, TypedResponse } from './types'
import { HtmlEscapedCallbackPhase, resolveCallback } from './utils/html'
import type { RedirectStatusCode, StatusCode } from './utils/http-status'
import type { IsAny, JSONParsed, JSONValue, Simplify } from './utils/types'
import type { IsAny, JSONParsed, JSONValue, Simplify, SimplifyDeepArray } from './utils/types'

type HeaderRecord = Record<string, string | string[]>

Expand Down Expand Up @@ -127,30 +127,30 @@ interface TextRespond {
* @param {U} [status] - An optional status code for the response.
* @param {HeaderRecord} [headers] - An optional record of headers to include in the response.
*
* @returns {Response & TypedResponse<Simplify<T> extends JSONValue ? (JSONValue extends Simplify<T> ? never : JSONParsed<T>) : never, U, 'json'>} - The response after rendering the JSON object, typed with the provided object and status code types.
* @returns {Response & TypedResponse<SimplifyDeepArray<T> extends JSONValue ? (JSONValue extends SimplifyDeepArray<T> ? never : JSONParsed<T>) : never, U, 'json'>} - The response after rendering the JSON object, typed with the provided object and status code types.
*/
interface JSONRespond {
<T extends JSONValue | Simplify<unknown>, U extends StatusCode>(
<T extends JSONValue | SimplifyDeepArray<unknown>, U extends StatusCode>(
object: T,
status?: U,
headers?: HeaderRecord
): Response &
TypedResponse<
Simplify<T> extends JSONValue
? JSONValue extends Simplify<T>
SimplifyDeepArray<T> extends JSONValue
? JSONValue extends SimplifyDeepArray<T>
? never
: JSONParsed<T>
: never,
U,
'json'
>
<T extends JSONValue | Simplify<unknown>, U extends StatusCode>(
object: Simplify<T> extends JSONValue ? T : Simplify<T>,
<T extends JSONValue | SimplifyDeepArray<unknown>, U extends StatusCode>(
object: SimplifyDeepArray<T> extends JSONValue ? T : SimplifyDeepArray<T>,
init?: ResponseInit
): Response &
TypedResponse<
Simplify<T> extends JSONValue
? JSONValue extends Simplify<T>
SimplifyDeepArray<T> extends JSONValue
? JSONValue extends SimplifyDeepArray<T>
? never
: JSONParsed<T>
: never,
Expand Down
12 changes: 11 additions & 1 deletion src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,19 @@ export type JSONParsed<T> = T extends { toJSON(): infer J }
? { [K in keyof T]: JSONParsed<T[K]> }
: never

// from sindresorhus/type-fest
/**
* Useful to flatten the type output to improve type hints shown in editors. And also to transform an interface into a type to aide with assignability.
* @copyright from sindresorhus/type-fest
*/
export type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {}

/**
* A simple extension of Simplify that works for array of interfaces.
*/
export type SimplifyDeepArray<T> = T extends any[]
? { [E in keyof T]: Simplify<T[E]> }
: { [KeyType in keyof T]: T[KeyType] } & {}

export type InterfaceToType<T> = T extends Function ? T : { [K in keyof T]: InterfaceToType<T[K]> }

export type RequiredKeysOf<BaseType extends object> = Exclude<
Expand Down

0 comments on commit 5beac4e

Please sign in to comment.