Repository: honojs/hono
File: src/helper/websocket/index.ts (TypeScript)

Code before:
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Context } from '../../context'
import type { MiddlewareHandler } from '../../types'

/**
 * WebSocket Event Listeners type
 */
export interface WSEvents<T = unknown> {
  onOpen?: (evt: Event, ws: WSContext<T>) => void
  onMessage?: (evt: MessageEvent<WSMessageReceive>, ws: WSContext<T>) => void
  onClose?: (evt: CloseEvent, ws: WSContext<T>) => void
  onError?: (evt: Event, ws: WSContext<T>) => void
}

/**
 * Upgrade WebSocket Type
 */
export interface UpgradeWebSocket<T = unknown, U = any, _WSEvents = WSEvents<T>> {
  (createEvents: (c: Context) => _WSEvents | Promise<_WSEvents>, options?: U): MiddlewareHandler<
    any,
    string,
    {
      outputFormat: 'ws'
    }
  >
  (c: Context, events: _WSEvents, options?: U): Promise<Response>
}

/**
 * ReadyState for WebSocket
 */
export type WSReadyState = 0 | 1 | 2 | 3

/**
 * An argument for WSContext class
 */
export interface WSContextInit<T = unknown> {
  send(data: string | ArrayBuffer | Uint8Array, options: SendOptions): void
  close(code?: number, reason?: string): void

  raw?: T
  readyState: WSReadyState
  url?: string | URL | null
  protocol?: string | null
}

/**
 * Options for sending message
 */
export interface SendOptions {
  compress?: boolean

Review comment (refactor):
For the RPC feature, it's better than the `Response` be `TypedResponse.` The `outputFormat` in the following will not be inferred.

```ts
const routes = app.get('/ws', (c) => {
  return upgradeWebSocket(c, {})
})

expectTypeOf(routes).toMatchTypeOf<
  Hono<
    BlankEnv,
    {
      '/ws': {
        $get: {
          input: {}
          output: {}
          outputFormat: 'ws' // This can't be inferred with the current code.
          status: StatusCode
        }
      }
    },
    '/'
  >
>()
```

You can resolve it with the following patch.

```diff
diff --git a/src/helper/websocket/index.ts b/src/helper/websocket/index.ts
index 6e9c5e72..48a75db0 100644
--- a/src/helper/websocket/index.ts
+++ b/src/helper/websocket/index.ts
@@ -5,7 +5,8 @@

 /* eslint-disable @typescript-eslint/no-explicit-any */
 import type { Context } from '../../context'
-import type { MiddlewareHandler } from '../../types'
+import type { MiddlewareHandler, TypedResponse } from '../../types'
+import type { StatusCode } from '../../utils/http-status'

 /**
  * WebSocket Event Listeners type
@@ -28,7 +29,7 @@ export interface UpgradeWebSocket<T = unknown, U = any, _WSEvents = WSEvents<T>>
       outputFormat: 'ws'
     }
   >
-  (c: Context, events: _WSEvents, options?: U): Promise<Response>
+  (c: Context, events: _WSEvents, options?: U): Promise<TypedResponse<{}, StatusCode, 'ws'>>
 }

 /**
```

What do you think of this?

Code after:
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Context } from '../../context'
import type { MiddlewareHandler, TypedResponse } from '../../types'
import type { StatusCode } from '../../utils/http-status'

/**
 * WebSocket Event Listeners type
 */
export interface WSEvents<T = unknown> {
  onOpen?: (evt: Event, ws: WSContext<T>) => void
  onMessage?: (evt: MessageEvent<WSMessageReceive>, ws: WSContext<T>) => void
  onClose?: (evt: CloseEvent, ws: WSContext<T>) => void
  onError?: (evt: Event, ws: WSContext<T>) => void
}

/**
 * Upgrade WebSocket Type
 */
export interface UpgradeWebSocket<T = unknown, U = any, _WSEvents = WSEvents<T>> {
  (createEvents: (c: Context) => _WSEvents | Promise<_WSEvents>, options?: U): MiddlewareHandler<
    any,
    string,
    {
      outputFormat: 'ws'
    }
  >
  (c: Context, events: _WSEvents, options?: U): Promise<
    Response & TypedResponse<{}, StatusCode, 'ws'>
  >
}

/**
 * ReadyState for WebSocket
 */
export type WSReadyState = 0 | 1 | 2 | 3

/**
 * An argument for WSContext class
 */
export interface WSContextInit<T = unknown> {
  send(data: string | ArrayBuffer | Uint8Array, options: SendOptions): void
  close(code?: number, reason?: string): void

  raw?: T
  readyState: WSReadyState
  url?: string | URL | null
  protocol?: string | null
}

/**
 * Options for sending message