Skip to content

Commit

Permalink
fix(types): SimplifyDeepArray should now actually be "deep" (#2920)
Browse files Browse the repository at this point in the history
* fix(types): `SimplifyDeepArray` should now actually be "deep"

Also now uses `Simplify` instead of inline declare the type

* tests(types): extra tests for `Should have correct types - interfaces`

* style: apply lint&style fixes

* docs: rewording SimplifyDeepArray jsdoc

* fix: type is possibly infinite

* fix: mismatching implementation of interface

* refactor: split JSONRespond return declaration to it's own type

DRY-er code and fixes a weird type is possibly infinite yelling
  • Loading branch information
NamesMT authored Jun 6, 2024
1 parent 731d7ae commit 00f32b7
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 29 deletions.
32 changes: 31 additions & 1 deletion src/client/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { expectTypeOf, vi } from 'vitest'
import { upgradeWebSocket } from '../adapter/deno/websocket'
import { Hono } from '../hono'
import { parse } from '../utils/cookie'
import type { Equal, Expect } from '../utils/types'
import type { Equal, Expect, JSONValue, SimplifyDeepArray } from '../utils/types'
import { validator } from '../validator'
import { hc } from './client'
import type { ClientResponse, InferRequestType, InferResponseType } from './types'
Expand Down Expand Up @@ -542,6 +542,22 @@ describe('Merge path with `app.route()`', () => {
const data = await res.json()
type verify = Expect<Equal<Result, typeof data>>
expect(data.ok).toBe(true)

// A few more types only tests
interface DeepInterface {
l2: {
l3: Result
}
}
interface ExtraDeepInterface {
l4: DeepInterface
}
type verifyDeepInterface = Expect<
Equal<SimplifyDeepArray<DeepInterface> extends JSONValue ? true : false, true>
>
type verifyExtraDeepInterface = Expect<
Equal<SimplifyDeepArray<ExtraDeepInterface> extends JSONValue ? true : false, true>
>
})

it('Should have correct types - with array of interfaces', async () => {
Expand All @@ -560,6 +576,20 @@ describe('Merge path with `app.route()`', () => {
const data = await res.json()
type verify = Expect<Equal<Results, typeof data>>
expect(data[0].ok).toBe(true)

// A few more types only tests
type verifyNestedArrayTyped = Expect<
Equal<SimplifyDeepArray<[string, Results]> extends JSONValue ? true : false, true>
>
type verifyNestedArrayInterfaceArray = Expect<
Equal<SimplifyDeepArray<[string, Result[]]> extends JSONValue ? true : false, true>
>
type verifyExtraNestedArrayTyped = Expect<
Equal<SimplifyDeepArray<[string, Results[]]> extends JSONValue ? true : false, true>
>
type verifyExtraNestedArrayInterfaceArray = Expect<
Equal<SimplifyDeepArray<[string, Result[][]]> extends JSONValue ? true : false, true>
>
})

it('Should allow a Date object and return it as a string', async () => {
Expand Down
52 changes: 27 additions & 25 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, SimplifyDeepArray } from './utils/types'
import type { IsAny, JSONParsed, JSONValue, SimplifyDeepArray } from './utils/types'

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

Expand Down Expand Up @@ -127,38 +127,40 @@ 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<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.
* @returns {JSONRespondReturn<T, U>} - The response after rendering the JSON object, typed with the provided object and status code types.
*/
interface JSONRespond {
<T extends JSONValue | SimplifyDeepArray<unknown>, U extends StatusCode>(
object: T,
status?: U,
headers?: HeaderRecord
): Response &
TypedResponse<
SimplifyDeepArray<T> extends JSONValue
? JSONValue extends SimplifyDeepArray<T>
? never
: JSONParsed<T>
: never,
U,
'json'
>
): JSONRespondReturn<T, U>
<T extends JSONValue | SimplifyDeepArray<unknown>, U extends StatusCode>(
object: SimplifyDeepArray<T> extends JSONValue ? T : SimplifyDeepArray<T>,
object: T,
init?: ResponseInit
): Response &
TypedResponse<
SimplifyDeepArray<T> extends JSONValue
? JSONValue extends SimplifyDeepArray<T>
? never
: JSONParsed<T>
: never,
U,
'json'
>
): JSONRespondReturn<T, U>
}

/**
* @template T - The type of the JSON value or simplified unknown type.
* @template U - The type of the status code.
*
* @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.
*/
type JSONRespondReturn<
T extends JSONValue | SimplifyDeepArray<unknown>,
U extends StatusCode
> = Response &
TypedResponse<
SimplifyDeepArray<T> extends JSONValue
? JSONValue extends SimplifyDeepArray<T>
? never
: JSONParsed<T>
: never,
U,
'json'
>

/**
* Interface representing a function that responds with HTML content.
*
Expand Down Expand Up @@ -659,11 +661,11 @@ export class Context<
* })
* ```
*/
json: JSONRespond = <T extends JSONValue | Simplify<unknown>, U extends StatusCode>(
json: JSONRespond = <T extends JSONValue | SimplifyDeepArray<unknown>, U extends StatusCode>(
object: T,
arg?: U | ResponseInit,
headers?: HeaderRecord
): ReturnType<JSONRespond> => {
): JSONRespondReturn<T, U> => {
const body = JSON.stringify(object)
this.#preparedHeaders ??= {}
this.#preparedHeaders['content-type'] = 'application/json; charset=UTF-8'
Expand Down
6 changes: 3 additions & 3 deletions src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ export type JSONParsed<T> = T extends { toJSON(): infer J }
export type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {}

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

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

Expand Down

0 comments on commit 00f32b7

Please sign in to comment.