Repository: sindresorhus/got (ts)

Issue:
Add a response.ok boolean similar to fetch API?
It's quite handy and would ease migrating to got.

It's set to false if `(res.statusCode < 200 || res.statusCode > 299)` or true if in 200-299.

Pull request description:
Add `response.ok`
Fixes #2036

Added a boolean ok to the response from got API to indicate whether status code is within 200 & 299 i.e. 2xx.

#### Checklist

- [X] I have read the documentation.
- [X] I have included a pull request description of my changes.
- [X] I have included some tests.
- [x] If it's a new feature, I have included documentation updates in both the README and the types.


Patch:
diff --git a/documentation/3-streams.md b/documentation/3-streams.md
index 8541851..ec029dc 100644
--- a/documentation/3-streams.md
+++ b/documentation/3-streams.md
@@ -354,6 +354,18 @@ The server's IP address.
 
 Whether the response comes from cache or not.
 
+### `ok`
+
+**Type: `boolean`**
+
+Whether the response was successful
+
+**Note:**
+> - A request is successful when the status code of the final request is `2xx` or `3xx`.
+> - When [following redirects](2-options.md#followredirect), a request is successful **only** when the status code of the final request is `2xx`.
+> - `304` responses are always considered successful.
+> - Got throws automatically when `response.ok` is `false` and `throwHttpErrors` is `true`.
+
 ### `statusCode`
 
 **Type: `number`**
diff --git a/source/core/index.ts b/source/core/index.ts
index 1d913c2..63e2c5e 100644
--- a/source/core/index.ts
+++ b/source/core/index.ts
@@ -632,6 +632,7 @@ export default class Request extends Duplex implements RequestEvents<Request> {
 		typedResponse.isFromCache = (this._nativeResponse as any).fromCache ?? false;
 		typedResponse.ip = this.ip;
 		typedResponse.retryCount = this.retryCount;
+		typedResponse.ok = isResponseOk(typedResponse);
 
 		this._isFromCache = typedResponse.isFromCache;
 
diff --git a/source/core/response.ts b/source/core/response.ts
index 413dcad..6fee258 100644
--- a/source/core/response.ts
+++ b/source/core/response.ts
@@ -91,6 +91,13 @@ export interface PlainResponse extends IncomingMessageWithTimings {
 	The result of the request.
 	*/
 	body?: unknown;
+
+	/**
+	Whether the response was successful.
+
+	__Note__: Got throws automatically when `response.ok` is `false` and `throwHttpErrors` is `true`.
+	*/
+	ok: boolean;
 }
 
 // For Promise support


Test patch:
diff --git a/test/http.ts b/test/http.ts
index 8ff2363..6621294 100644
--- a/test/http.ts
+++ b/test/http.ts
@@ -375,3 +375,43 @@ test('ClientRequest can throw before promise resolves', async t => {
 		message: /EINVAL|EHOSTUNREACH|ETIMEDOUT/,
 	});
 });
+
+test('status code 200 has response ok is true', withServer, async (t, server, got) => {
+	server.get('/', (_request, response) => {
+		response.statusCode = 200;
+		response.end();
+	});
+
+	const promise = got('');
+	await t.notThrowsAsync(promise);
+	const {statusCode, body, ok} = await promise;
+	t.true(ok);
+	t.is(statusCode, 200);
+	t.is(body, '');
+});
+
+test('status code 404 has response ok is false if error is not thrown', withServer, async (t, server, got) => {
+	server.get('/', (_request, response) => {
+		response.statusCode = 404;
+		response.end();
+	});
+
+	const promise = got('', {throwHttpErrors: false});
+	await t.notThrowsAsync(promise);
+	const {statusCode, body, ok} = await promise;
+	t.is(ok, false);
+	t.is(statusCode, 404);
+	t.is(body, '');
+});
+
+test('status code 404 has error response ok is false if error is thrown', withServer, async (t, server, got) => {
+	server.get('/', (_request, response) => {
+		response.statusCode = 404;
+		response.end('not');
+	});
+
+	const error = await t.throwsAsync<HTTPError>(got(''), {instanceOf: HTTPError});
+	t.is(error.response.statusCode, 404);
+	t.is(error.response.ok, false);
+	t.is(error.response.body, 'not');
+});