• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OtterJS / otterhttp / 10586912205

27 Aug 2024 10:37PM UTC coverage: 84.954% (+0.01%) from 84.942%
10586912205

push

github

Lordfirespeed
chore: add changeset

905 of 1149 branches covered (78.76%)

Branch coverage included in aggregate %.

1427 of 1596 relevant lines covered (89.41%)

81.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.12
/packages/response/src/prototype.ts
1
import { type OutgoingHttpHeader, type OutgoingHttpHeaders, ServerResponse } from "node:http"
2
import { HttpError } from "@otterhttp/errors"
3
import type { Request } from "@otterhttp/request"
4
import { type JSONLiteral, type SendFileOptions, json, send, sendFile, sendStatus } from "@otterhttp/send"
5

6
import { type SetCookieOptions, clearCookie, setCookie } from "./cookie"
7
import { type DownloadOptions, attachment, download } from "./download"
8
import { type FormatProps, formatResponse } from "./format"
9
import {
10
  appendResponseHeaders,
11
  appendResponseVaryHeader,
12
  setResponseContentTypeHeader,
13
  setResponseHeaderSpecialCases,
14
  setResponseHeaders,
15
  setResponseLinkHeader,
16
  setResponseLocationHeader,
17
} from "./headers"
18
import { validatePreconditions } from "./preconditions"
19
import { redirect } from "./redirect"
20
import type { AppendHeaders, Headers, Input, ResponseAppSettings } from "./types"
21

22
export class Response<Req extends Request<unknown> = Request<unknown>> extends ServerResponse<Req> {
23
  // assigned by App
24
  declare appSettings: ResponseAppSettings | undefined
25

26
  // own members (assigned by constructor)
27
  locals: Record<string, unknown>
28
  private _lateHeaderActions?: Map<symbol, (res: unknown) => void>
29

30
  constructor(request: Req) {
31
    super(request)
263✔
32

33
    this.locals = {}
263✔
34
  }
35

36
  // header-related overrides/extensions
37
  getHeader<HeaderName extends string>(headerName: HeaderName): Headers[Lowercase<HeaderName>] {
38
    return super.getHeader(headerName)
336✔
39
  }
40

41
  setHeader<HeaderName extends string>(headerName: HeaderName, value: Input<Headers[Lowercase<HeaderName>]>): this {
42
    const lowerCaseHeaderName = headerName.toLowerCase() as Lowercase<HeaderName>
713✔
43
    const specialCase = setResponseHeaderSpecialCases.get<Lowercase<HeaderName>>(lowerCaseHeaderName)
713✔
44
    if (specialCase != null) value = specialCase(this, value)
713✔
45
    super.setHeader(headerName, value)
713✔
46
    return this
713✔
47
  }
48

49
  setHeaders(headers: Headers): this {
50
    setResponseHeaders(this, headers)
1✔
51
    return this
1✔
52
  }
53

54
  appendHeader<HeaderName extends string>(
55
    headerName: HeaderName,
56
    value: Input<AppendHeaders[Lowercase<HeaderName>]>,
57
  ): this {
58
    super.appendHeader(headerName, value)
25✔
59
    return this
25✔
60
  }
61

62
  appendHeaders(headers: AppendHeaders): this {
63
    appendResponseHeaders(this, headers)
×
64
    return this
×
65
  }
66

67
  writeHead(statusCode: number, statusMessage?: string, headers?: OutgoingHttpHeaders | OutgoingHttpHeader[]): this
68
  writeHead(statusCode: number, headers?: OutgoingHttpHeaders | OutgoingHttpHeader[]): this
69
  override writeHead(
70
    statusCode: number,
71
    statusMessage?: string | OutgoingHttpHeaders | OutgoingHttpHeader[],
72
    headers?: OutgoingHttpHeaders | OutgoingHttpHeader[],
73
  ): this {
74
    if (this._lateHeaderActions != null) {
263✔
75
      for (const action of this._lateHeaderActions.values()) {
3✔
76
        action(this)
3✔
77
      }
78
    }
79
    // @ts-expect-error typescript doesn't handle overloads very well
80
    return super.writeHead(statusCode, statusMessage, headers)
263✔
81
  }
82

83
  registerLateHeaderAction(symbol: symbol, action: (res: this) => void) {
84
    this._lateHeaderActions ??= new Map()
4✔
85
    this._lateHeaderActions.set(symbol, action as (res: unknown) => void)
4✔
86
  }
87

88
  location(url: string | URL): this {
89
    setResponseLocationHeader(this, url)
4✔
90
    return this
4✔
91
  }
92

93
  links(links: Record<string, string | URL>): this {
94
    setResponseLinkHeader(this, links)
4✔
95
    return this
4✔
96
  }
97

98
  vary(header: string | string[]): this {
99
    appendResponseVaryHeader(this, header)
1✔
100
    return this
1✔
101
  }
102

103
  contentType(type: string): this {
104
    setResponseContentTypeHeader(this, type)
4✔
105
    return this
4✔
106
  }
107

108
  // cookies & in-place state modification
109

110
  status(statusCode: number): this {
111
    this.statusCode = statusCode
7✔
112
    return this
7✔
113
  }
114

115
  attachment(filename?: string): this {
116
    attachment(this, filename)
2✔
117
    return this
2✔
118
  }
119

120
  cookie(name: string, value: string, options?: SetCookieOptions): this {
121
    if (options == null) options = this.appSettings?.setCookieOptions
7✔
122
    else {
123
      options = [this.appSettings?.setCookieOptions, options].reduce<SetCookieOptions>(
2✔
124
        (optionsAccumulator, optionsSource) => {
125
          if (optionsSource == null) return optionsAccumulator
4✔
126
          for (const [key, value] of Object.entries(optionsSource)) {
2✔
127
            if (value === undefined) continue
2!
128
            optionsAccumulator[key] = value
2✔
129
          }
130
          return optionsAccumulator
2✔
131
        },
132
        {},
133
      )
134
    }
135
    setCookie(this, name, value, options)
7✔
136
    return this
7✔
137
  }
138

139
  clearCookie(name: string, options?: SetCookieOptions): this {
140
    clearCookie(this, name, options)
1✔
141
    return this
1✔
142
  }
143

144
  // terminators (anything that calls `.end()`)
145
  sendStatus(statusCode: number): this {
146
    sendStatus(this, statusCode)
2✔
147
    return this
2✔
148
  }
149

150
  async redirect(url: string, statusCode?: number): Promise<this> {
151
    await redirect(this, url, statusCode)
4✔
152
    return this
4✔
153
  }
154

155
  send(body: string | Buffer | null): this {
156
    send(this, body)
92✔
157
    return this
92✔
158
  }
159

160
  async sendFile(path: string, options?: SendFileOptions): Promise<this> {
161
    await sendFile(this, path, options)
×
162
    return this
×
163
  }
164

165
  json(body: JSONLiteral): this {
166
    json(this, body)
33✔
167
    return this
33✔
168
  }
169

170
  async download(path: string, filename?: string, options?: DownloadOptions): Promise<this> {
171
    await download(this, path, filename, options)
6✔
172
    return this
5✔
173
  }
174

175
  // content-negotiation utility
176
  async format(obj: FormatProps): Promise<this> {
177
    await formatResponse(this, obj)
4✔
178
    return this
3✔
179
  }
180

181
  // preconditions/caching
182
  validatePreconditions(): this {
183
    validatePreconditions(this)
153✔
184
    return this
150✔
185
  }
186

187
  isFresh(): boolean {
188
    try {
4✔
189
      this.validatePreconditions()
4✔
190
    } catch (error) {
191
      if (error instanceof HttpError && error.code != null && error.code.startsWith("ERR_PRECONDITION_FAILED"))
1!
192
        return true
1✔
193
      throw error
×
194
    }
195
    return false
3✔
196
  }
197

198
  isStale(): boolean {
199
    return !this.isFresh()
×
200
  }
201
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc