@naturalcycles/js-lib 14.133.1 → 14.135.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/env.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Use it to detect SSR/Node.js environment.
3
+ *
4
+ * Will return `true` in Node.js.
5
+ * Will return `false` in the Browser.
6
+ */
7
+ export declare function isServerSide(): boolean;
8
+ /**
9
+ * Use it to detect Browser (not SSR/Node) environment.
10
+ *
11
+ * Will return `true` in the Browser.
12
+ * Will return `false` in Node.js.
13
+ */
14
+ export declare function isClientSide(): boolean;
package/dist/env.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isClientSide = exports.isServerSide = void 0;
4
+ /**
5
+ * Use it to detect SSR/Node.js environment.
6
+ *
7
+ * Will return `true` in Node.js.
8
+ * Will return `false` in the Browser.
9
+ */
10
+ function isServerSide() {
11
+ return typeof window === 'undefined';
12
+ }
13
+ exports.isServerSide = isServerSide;
14
+ /**
15
+ * Use it to detect Browser (not SSR/Node) environment.
16
+ *
17
+ * Will return `true` in the Browser.
18
+ * Will return `false` in Node.js.
19
+ */
20
+ function isClientSide() {
21
+ return typeof window !== 'undefined';
22
+ }
23
+ exports.isClientSide = isClientSide;
@@ -38,10 +38,14 @@ export declare class Fetcher {
38
38
  * Never throws, returns `err` property in the response instead.
39
39
  */
40
40
  rawFetch<T = unknown>(url: string, rawOpt?: FetcherOptions): Promise<FetcherResponse<T>>;
41
+ private onOkResponse;
42
+ private onNotOkResponse;
41
43
  private processRetry;
42
44
  /**
43
45
  * Default is yes,
44
46
  * unless there's reason not to (e.g method is POST).
47
+ *
48
+ * statusCode of 0 (or absense of it) will BE retried.
45
49
  */
46
50
  private shouldRetry;
47
51
  private getStatusFamily;
@@ -2,6 +2,7 @@
2
2
  /// <reference lib="dom"/>
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.getFetcher = exports.Fetcher = void 0;
5
+ const env_1 = require("../env");
5
6
  const error_util_1 = require("../error/error.util");
6
7
  const http_error_1 = require("../error/http.error");
7
8
  const number_util_1 = require("../number/number.util");
@@ -12,7 +13,7 @@ const time_util_1 = require("../time/time.util");
12
13
  const http_model_1 = require("./http.model");
13
14
  const defRetryOptions = {
14
15
  count: 2,
15
- timeout: 500,
16
+ timeout: 1000,
16
17
  timeoutMax: 30000,
17
18
  timeoutMultiplier: 2,
18
19
  };
@@ -94,7 +95,7 @@ class Fetcher {
94
95
  async rawFetch(url, rawOpt = {}) {
95
96
  const { logger } = this.cfg;
96
97
  const req = this.normalizeOptions(url, rawOpt);
97
- const { timeoutSeconds, mode, init: { method }, } = req;
98
+ const { timeoutSeconds, init: { method }, } = req;
98
99
  // setup timeout
99
100
  let timeout;
100
101
  if (timeoutSeconds) {
@@ -107,6 +108,10 @@ class Fetcher {
107
108
  for await (const hook of this.cfg.hooks.beforeRequest || []) {
108
109
  await hook(req);
109
110
  }
111
+ const isFullUrl = req.url.includes('://');
112
+ const fullUrl = isFullUrl ? new URL(req.url) : undefined;
113
+ const shortUrl = fullUrl ? this.getShortUrl(fullUrl) : req.url;
114
+ const signature = [method, shortUrl].join(' ');
110
115
  const res = {
111
116
  req,
112
117
  retryStatus: {
@@ -114,10 +119,8 @@ class Fetcher {
114
119
  retryStopped: false,
115
120
  retryTimeout: req.retry.timeout,
116
121
  },
122
+ signature,
117
123
  };
118
- const fullUrl = new URL(req.url);
119
- const shortUrl = this.getShortUrl(fullUrl);
120
- const signature = [method, shortUrl].join(' ');
121
124
  /* eslint-disable no-await-in-loop */
122
125
  while (!res.retryStatus.retryStopped) {
123
126
  const started = Date.now();
@@ -141,95 +144,11 @@ class Fetcher {
141
144
  }
142
145
  res.statusFamily = this.getStatusFamily(res);
143
146
  if (res.fetchResponse?.ok) {
144
- if (mode === 'json') {
145
- if (res.fetchResponse.body) {
146
- const text = await res.fetchResponse.text();
147
- if (text) {
148
- try {
149
- res.body = text;
150
- res.body = JSON.parse(text, req.jsonReviver);
151
- }
152
- catch (err) {
153
- const { message } = (0, error_util_1._anyToError)(err);
154
- res.err = new http_error_1.HttpError([signature, message].join('\n'), {
155
- httpStatusCode: 0,
156
- url: req.url,
157
- });
158
- res.ok = false;
159
- }
160
- }
161
- else {
162
- // Body had a '' (empty string)
163
- res.body = {};
164
- }
165
- }
166
- else {
167
- // if no body: set responseBody as {}
168
- // do not throw a "cannot parse null as Json" error
169
- res.body = {};
170
- }
171
- }
172
- else if (mode === 'text') {
173
- res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
174
- }
175
- else if (mode === 'arrayBuffer') {
176
- res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {};
177
- }
178
- else if (mode === 'blob') {
179
- res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {};
180
- }
181
- clearTimeout(timeout);
182
- res.retryStatus.retryStopped = true;
183
- // res.err can happen on JSON.parse error
184
- if (!res.err && this.cfg.logResponse) {
185
- const { retryAttempt } = res.retryStatus;
186
- logger.log([
187
- ' <<',
188
- res.fetchResponse.status,
189
- signature,
190
- retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
191
- (0, time_util_1._since)(started),
192
- ]
193
- .filter(Boolean)
194
- .join(' '));
195
- if (this.cfg.logResponseBody) {
196
- logger.log(res.body);
197
- }
198
- }
147
+ await this.onOkResponse(res, started, timeout);
199
148
  }
200
149
  else {
201
150
  // !res.ok
202
- clearTimeout(timeout);
203
- let errObj;
204
- if (res.fetchResponse) {
205
- const body = (0, json_util_1._jsonParseIfPossible)(await res.fetchResponse.text());
206
- errObj = (0, error_util_1._anyToErrorObject)(body);
207
- }
208
- else if (res.err) {
209
- errObj = (0, error_util_1._errorToErrorObject)(res.err);
210
- }
211
- else {
212
- errObj = {};
213
- }
214
- const originalMessage = errObj.message;
215
- errObj.message = [
216
- [res.fetchResponse?.status, signature].filter(Boolean).join(' '),
217
- originalMessage,
218
- ]
219
- .filter(Boolean)
220
- .join('\n');
221
- res.err = new http_error_1.HttpError(errObj.message, (0, object_util_1._filterNullishValues)({
222
- ...errObj.data,
223
- originalMessage,
224
- httpStatusCode: res.fetchResponse?.status || 0,
225
- // These properties are provided to be used in e.g custom Sentry error grouping
226
- // Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
227
- // Enabled, cause `data` is not printed by default when error is HttpError
228
- // method: req.method,
229
- url: req.url,
230
- // tryCount: req.tryCount,
231
- }));
232
- await this.processRetry(res);
151
+ await this.onNotOkResponse(res, timeout);
233
152
  }
234
153
  }
235
154
  for await (const hook of this.cfg.hooks.afterResponse || []) {
@@ -237,6 +156,99 @@ class Fetcher {
237
156
  }
238
157
  return res;
239
158
  }
159
+ async onOkResponse(res, started, timeout) {
160
+ const { req } = res;
161
+ const { mode } = res.req;
162
+ if (mode === 'json') {
163
+ if (res.fetchResponse.body) {
164
+ const text = await res.fetchResponse.text();
165
+ if (text) {
166
+ try {
167
+ res.body = text;
168
+ res.body = JSON.parse(text, req.jsonReviver);
169
+ }
170
+ catch (err) {
171
+ const { message } = (0, error_util_1._anyToError)(err);
172
+ res.err = new http_error_1.HttpError([res.signature, message].join('\n'), {
173
+ httpStatusCode: 0,
174
+ url: req.url,
175
+ });
176
+ res.ok = false;
177
+ }
178
+ }
179
+ else {
180
+ // Body had a '' (empty string)
181
+ res.body = {};
182
+ }
183
+ }
184
+ else {
185
+ // if no body: set responseBody as {}
186
+ // do not throw a "cannot parse null as Json" error
187
+ res.body = {};
188
+ }
189
+ }
190
+ else if (mode === 'text') {
191
+ res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
192
+ }
193
+ else if (mode === 'arrayBuffer') {
194
+ res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {};
195
+ }
196
+ else if (mode === 'blob') {
197
+ res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {};
198
+ }
199
+ clearTimeout(timeout);
200
+ res.retryStatus.retryStopped = true;
201
+ // res.err can happen on JSON.parse error
202
+ if (!res.err && this.cfg.logResponse) {
203
+ const { retryAttempt } = res.retryStatus;
204
+ const { logger } = this.cfg;
205
+ logger.log([
206
+ ' <<',
207
+ res.fetchResponse.status,
208
+ res.signature,
209
+ retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
210
+ (0, time_util_1._since)(started),
211
+ ]
212
+ .filter(Boolean)
213
+ .join(' '));
214
+ if (this.cfg.logResponseBody) {
215
+ logger.log(res.body);
216
+ }
217
+ }
218
+ }
219
+ async onNotOkResponse(res, timeout) {
220
+ clearTimeout(timeout);
221
+ let errObj;
222
+ if (res.fetchResponse) {
223
+ const body = (0, json_util_1._jsonParseIfPossible)(await res.fetchResponse.text());
224
+ errObj = (0, error_util_1._anyToErrorObject)(body);
225
+ }
226
+ else if (res.err) {
227
+ errObj = (0, error_util_1._errorToErrorObject)(res.err);
228
+ }
229
+ else {
230
+ errObj = {};
231
+ }
232
+ const originalMessage = errObj.message;
233
+ errObj.message = [
234
+ [res.fetchResponse?.status, res.signature].filter(Boolean).join(' '),
235
+ originalMessage,
236
+ ]
237
+ .filter(Boolean)
238
+ .join('\n');
239
+ res.err = new http_error_1.HttpError(errObj.message, (0, object_util_1._filterNullishValues)({
240
+ ...errObj.data,
241
+ originalMessage,
242
+ httpStatusCode: res.fetchResponse?.status || 0,
243
+ // These properties are provided to be used in e.g custom Sentry error grouping
244
+ // Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
245
+ // Enabled, cause `data` is not printed by default when error is HttpError
246
+ // method: req.method,
247
+ url: res.req.url,
248
+ // tryCount: req.tryCount,
249
+ }));
250
+ await this.processRetry(res);
251
+ }
240
252
  async processRetry(res) {
241
253
  const { retryStatus } = res;
242
254
  if (!this.shouldRetry(res)) {
@@ -253,11 +265,14 @@ class Fetcher {
253
265
  return;
254
266
  retryStatus.retryAttempt++;
255
267
  retryStatus.retryTimeout = (0, number_util_1._clamp)(retryStatus.retryTimeout * timeoutMultiplier, 0, timeoutMax);
256
- await (0, pDelay_1.pDelay)(retryStatus.retryTimeout);
268
+ const noise = Math.random() * 500;
269
+ await (0, pDelay_1.pDelay)(retryStatus.retryTimeout + noise);
257
270
  }
258
271
  /**
259
272
  * Default is yes,
260
273
  * unless there's reason not to (e.g method is POST).
274
+ *
275
+ * statusCode of 0 (or absense of it) will BE retried.
261
276
  */
262
277
  shouldRetry(res) {
263
278
  const { retryPost, retry4xx, retry5xx } = res.req;
@@ -304,7 +319,7 @@ class Fetcher {
304
319
  if (!this.cfg.logWithSearchParams) {
305
320
  shortUrl = shortUrl.split('?')[0];
306
321
  }
307
- if (!this.cfg.logWithPrefixUrl && baseUrl && shortUrl.startsWith(baseUrl)) {
322
+ if (!this.cfg.logWithBaseUrl && baseUrl && shortUrl.startsWith(baseUrl)) {
308
323
  shortUrl = shortUrl.slice(baseUrl.length);
309
324
  }
310
325
  return shortUrl;
@@ -331,7 +346,7 @@ class Fetcher {
331
346
  logRequestBody: debug,
332
347
  logResponse: debug,
333
348
  logResponseBody: debug,
334
- logWithPrefixUrl: true,
349
+ logWithBaseUrl: (0, env_1.isServerSide)(),
335
350
  logWithSearchParams: true,
336
351
  retry: { ...defRetryOptions },
337
352
  init: {
@@ -43,10 +43,19 @@ export interface FetcherCfg {
43
43
  logResponse?: boolean;
44
44
  logResponseBody?: boolean;
45
45
  /**
46
- * Default to true.
47
- * Set to false to exclude `prefixUrl` from logs (both success and error)
46
+ * Controls if `baseUrl` should be included in logs (both success and error).
47
+ *
48
+ * Defaults to `true` on ServerSide and `false` on ClientSide.
49
+ *
50
+ * Reasoning.
51
+ *
52
+ * ClientSide often uses one main "backend host".
53
+ * Not including baseUrl improves Sentry error grouping.
54
+ *
55
+ * ServerSide often uses one Fetcher instance per 3rd-party API.
56
+ * Not including baseUrl can introduce confusion of "which API is it?".
48
57
  */
49
- logWithPrefixUrl?: boolean;
58
+ logWithBaseUrl?: boolean;
50
59
  /**
51
60
  * Default to true.
52
61
  * Set to false to strip searchParams from url when logging (both success and error)
@@ -138,6 +147,7 @@ export interface FetcherSuccessResponse<BODY = unknown> {
138
147
  req: FetcherRequest;
139
148
  statusFamily?: HttpStatusFamily;
140
149
  retryStatus: FetcherRetryStatus;
150
+ signature: string;
141
151
  }
142
152
  export interface FetcherErrorResponse<BODY = unknown> {
143
153
  ok: false;
@@ -147,6 +157,7 @@ export interface FetcherErrorResponse<BODY = unknown> {
147
157
  req: FetcherRequest;
148
158
  statusFamily?: HttpStatusFamily;
149
159
  retryStatus: FetcherRetryStatus;
160
+ signature: string;
150
161
  }
151
162
  export type FetcherResponse<BODY = unknown> = FetcherSuccessResponse<BODY> | FetcherErrorResponse<BODY>;
152
163
  export type FetcherMode = 'json' | 'text' | 'void' | 'arrayBuffer' | 'blob';
package/dist/index.d.ts CHANGED
@@ -72,6 +72,7 @@ export * from './datetime/localDate';
72
72
  export * from './datetime/localTime';
73
73
  export * from './datetime/dateInterval';
74
74
  export * from './datetime/timeInterval';
75
+ export * from './env';
75
76
  export * from './http/http.model';
76
77
  export * from './http/fetcher';
77
78
  export * from './http/fetcher.model';
package/dist/index.js CHANGED
@@ -76,6 +76,7 @@ tslib_1.__exportStar(require("./datetime/localDate"), exports);
76
76
  tslib_1.__exportStar(require("./datetime/localTime"), exports);
77
77
  tslib_1.__exportStar(require("./datetime/dateInterval"), exports);
78
78
  tslib_1.__exportStar(require("./datetime/timeInterval"), exports);
79
+ tslib_1.__exportStar(require("./env"), exports);
79
80
  tslib_1.__exportStar(require("./http/http.model"), exports);
80
81
  tslib_1.__exportStar(require("./http/fetcher"), exports);
81
82
  tslib_1.__exportStar(require("./http/fetcher.model"), exports);
@@ -81,6 +81,8 @@ function _stringifyAny(obj, opt = {}) {
81
81
  }
82
82
  if ((0, error_util_1._isErrorObject)(obj)) {
83
83
  if ((0, error_util_1._isHttpErrorObject)(obj)) {
84
+ // Only include (statusCode) if it's non-zero
85
+ // No: print (0), as it removes ambiguity
84
86
  // `replace` here works ONCE, exactly as we need it
85
87
  s = s.replace('HttpError', `HttpError(${obj.data.httpStatusCode})`);
86
88
  }
@@ -5,9 +5,9 @@
5
5
  /// <reference lib="dom" />
6
6
  import { Class, ObservableLike, Primitive, TypedArray } from '../typeFest';
7
7
  declare const objectTypeNames: readonly ["Function", "Generator", "AsyncGenerator", "GeneratorFunction", "AsyncGeneratorFunction", "AsyncFunction", "Observable", "Array", "Buffer", "Object", "RegExp", "Date", "Error", "Map", "Set", "WeakMap", "WeakSet", "ArrayBuffer", "SharedArrayBuffer", "DataView", "Promise", "URL", "FormData", "URLSearchParams", "HTMLElement", "Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array", "Int32Array", "Uint32Array", "Float32Array", "Float64Array", "BigInt64Array", "BigUint64Array"];
8
- type ObjectTypeName = typeof objectTypeNames[number];
8
+ type ObjectTypeName = (typeof objectTypeNames)[number];
9
9
  declare const primitiveTypeNames: readonly ["null", "undefined", "string", "number", "bigint", "boolean", "symbol"];
10
- type PrimitiveTypeName = typeof primitiveTypeNames[number];
10
+ type PrimitiveTypeName = (typeof primitiveTypeNames)[number];
11
11
  export type TypeName = ObjectTypeName | PrimitiveTypeName;
12
12
  export declare function is(value: unknown): TypeName;
13
13
  export declare namespace is {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Use it to detect SSR/Node.js environment.
3
+ *
4
+ * Will return `true` in Node.js.
5
+ * Will return `false` in the Browser.
6
+ */
7
+ export function isServerSide() {
8
+ return typeof window === 'undefined';
9
+ }
10
+ /**
11
+ * Use it to detect Browser (not SSR/Node) environment.
12
+ *
13
+ * Will return `true` in the Browser.
14
+ * Will return `false` in Node.js.
15
+ */
16
+ export function isClientSide() {
17
+ return typeof window !== 'undefined';
18
+ }
@@ -1,5 +1,6 @@
1
1
  /// <reference lib="dom"/>
2
2
  import { __asyncValues } from "tslib";
3
+ import { isServerSide } from '../env';
3
4
  import { _anyToError, _anyToErrorObject, _errorToErrorObject } from '../error/error.util';
4
5
  import { HttpError } from '../error/http.error';
5
6
  import { _clamp } from '../number/number.util';
@@ -10,7 +11,7 @@ import { _since } from '../time/time.util';
10
11
  import { HTTP_METHODS } from './http.model';
11
12
  const defRetryOptions = {
12
13
  count: 2,
13
- timeout: 500,
14
+ timeout: 1000,
14
15
  timeoutMax: 30000,
15
16
  timeoutMultiplier: 2,
16
17
  };
@@ -82,10 +83,10 @@ export class Fetcher {
82
83
  */
83
84
  async rawFetch(url, rawOpt = {}) {
84
85
  var _a, e_1, _b, _c, _d, e_2, _e, _f;
85
- var _g, _h, _j;
86
+ var _g;
86
87
  const { logger } = this.cfg;
87
88
  const req = this.normalizeOptions(url, rawOpt);
88
- const { timeoutSeconds, mode, init: { method }, } = req;
89
+ const { timeoutSeconds, init: { method }, } = req;
89
90
  // setup timeout
90
91
  let timeout;
91
92
  if (timeoutSeconds) {
@@ -96,25 +97,29 @@ export class Fetcher {
96
97
  }, timeoutSeconds * 1000);
97
98
  }
98
99
  try {
99
- for (var _k = true, _l = __asyncValues(this.cfg.hooks.beforeRequest || []), _m; _m = await _l.next(), _a = _m.done, !_a;) {
100
- _c = _m.value;
101
- _k = false;
100
+ for (var _h = true, _j = __asyncValues(this.cfg.hooks.beforeRequest || []), _k; _k = await _j.next(), _a = _k.done, !_a;) {
101
+ _c = _k.value;
102
+ _h = false;
102
103
  try {
103
104
  const hook = _c;
104
105
  await hook(req);
105
106
  }
106
107
  finally {
107
- _k = true;
108
+ _h = true;
108
109
  }
109
110
  }
110
111
  }
111
112
  catch (e_1_1) { e_1 = { error: e_1_1 }; }
112
113
  finally {
113
114
  try {
114
- if (!_k && !_a && (_b = _l.return)) await _b.call(_l);
115
+ if (!_h && !_a && (_b = _j.return)) await _b.call(_j);
115
116
  }
116
117
  finally { if (e_1) throw e_1.error; }
117
118
  }
119
+ const isFullUrl = req.url.includes('://');
120
+ const fullUrl = isFullUrl ? new URL(req.url) : undefined;
121
+ const shortUrl = fullUrl ? this.getShortUrl(fullUrl) : req.url;
122
+ const signature = [method, shortUrl].join(' ');
118
123
  const res = {
119
124
  req,
120
125
  retryStatus: {
@@ -122,10 +127,8 @@ export class Fetcher {
122
127
  retryStopped: false,
123
128
  retryTimeout: req.retry.timeout,
124
129
  },
130
+ signature,
125
131
  };
126
- const fullUrl = new URL(req.url);
127
- const shortUrl = this.getShortUrl(fullUrl);
128
- const signature = [method, shortUrl].join(' ');
129
132
  /* eslint-disable no-await-in-loop */
130
133
  while (!res.retryStatus.retryStopped) {
131
134
  const started = Date.now();
@@ -149,114 +152,124 @@ export class Fetcher {
149
152
  }
150
153
  res.statusFamily = this.getStatusFamily(res);
151
154
  if ((_g = res.fetchResponse) === null || _g === void 0 ? void 0 : _g.ok) {
152
- if (mode === 'json') {
153
- if (res.fetchResponse.body) {
154
- const text = await res.fetchResponse.text();
155
- if (text) {
156
- try {
157
- res.body = text;
158
- res.body = JSON.parse(text, req.jsonReviver);
159
- }
160
- catch (err) {
161
- const { message } = _anyToError(err);
162
- res.err = new HttpError([signature, message].join('\n'), {
163
- httpStatusCode: 0,
164
- url: req.url,
165
- });
166
- res.ok = false;
167
- }
168
- }
169
- else {
170
- // Body had a '' (empty string)
171
- res.body = {};
172
- }
173
- }
174
- else {
175
- // if no body: set responseBody as {}
176
- // do not throw a "cannot parse null as Json" error
177
- res.body = {};
178
- }
179
- }
180
- else if (mode === 'text') {
181
- res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
182
- }
183
- else if (mode === 'arrayBuffer') {
184
- res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {};
185
- }
186
- else if (mode === 'blob') {
187
- res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {};
188
- }
189
- clearTimeout(timeout);
190
- res.retryStatus.retryStopped = true;
191
- // res.err can happen on JSON.parse error
192
- if (!res.err && this.cfg.logResponse) {
193
- const { retryAttempt } = res.retryStatus;
194
- logger.log([
195
- ' <<',
196
- res.fetchResponse.status,
197
- signature,
198
- retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
199
- _since(started),
200
- ]
201
- .filter(Boolean)
202
- .join(' '));
203
- if (this.cfg.logResponseBody) {
204
- logger.log(res.body);
205
- }
206
- }
155
+ await this.onOkResponse(res, started, timeout);
207
156
  }
208
157
  else {
209
158
  // !res.ok
210
- clearTimeout(timeout);
211
- let errObj;
212
- if (res.fetchResponse) {
213
- const body = _jsonParseIfPossible(await res.fetchResponse.text());
214
- errObj = _anyToErrorObject(body);
215
- }
216
- else if (res.err) {
217
- errObj = _errorToErrorObject(res.err);
218
- }
219
- else {
220
- errObj = {};
221
- }
222
- const originalMessage = errObj.message;
223
- errObj.message = [
224
- [(_h = res.fetchResponse) === null || _h === void 0 ? void 0 : _h.status, signature].filter(Boolean).join(' '),
225
- originalMessage,
226
- ]
227
- .filter(Boolean)
228
- .join('\n');
229
- res.err = new HttpError(errObj.message, _filterNullishValues(Object.assign(Object.assign({}, errObj.data), { originalMessage, httpStatusCode: ((_j = res.fetchResponse) === null || _j === void 0 ? void 0 : _j.status) || 0,
230
- // These properties are provided to be used in e.g custom Sentry error grouping
231
- // Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
232
- // Enabled, cause `data` is not printed by default when error is HttpError
233
- // method: req.method,
234
- url: req.url })));
235
- await this.processRetry(res);
159
+ await this.onNotOkResponse(res, timeout);
236
160
  }
237
161
  }
238
162
  try {
239
- for (var _o = true, _p = __asyncValues(this.cfg.hooks.afterResponse || []), _q; _q = await _p.next(), _d = _q.done, !_d;) {
240
- _f = _q.value;
241
- _o = false;
163
+ for (var _l = true, _m = __asyncValues(this.cfg.hooks.afterResponse || []), _o; _o = await _m.next(), _d = _o.done, !_d;) {
164
+ _f = _o.value;
165
+ _l = false;
242
166
  try {
243
167
  const hook = _f;
244
168
  await hook(res);
245
169
  }
246
170
  finally {
247
- _o = true;
171
+ _l = true;
248
172
  }
249
173
  }
250
174
  }
251
175
  catch (e_2_1) { e_2 = { error: e_2_1 }; }
252
176
  finally {
253
177
  try {
254
- if (!_o && !_d && (_e = _p.return)) await _e.call(_p);
178
+ if (!_l && !_d && (_e = _m.return)) await _e.call(_m);
255
179
  }
256
180
  finally { if (e_2) throw e_2.error; }
257
181
  }
258
182
  return res;
259
183
  }
184
+ async onOkResponse(res, started, timeout) {
185
+ const { req } = res;
186
+ const { mode } = res.req;
187
+ if (mode === 'json') {
188
+ if (res.fetchResponse.body) {
189
+ const text = await res.fetchResponse.text();
190
+ if (text) {
191
+ try {
192
+ res.body = text;
193
+ res.body = JSON.parse(text, req.jsonReviver);
194
+ }
195
+ catch (err) {
196
+ const { message } = _anyToError(err);
197
+ res.err = new HttpError([res.signature, message].join('\n'), {
198
+ httpStatusCode: 0,
199
+ url: req.url,
200
+ });
201
+ res.ok = false;
202
+ }
203
+ }
204
+ else {
205
+ // Body had a '' (empty string)
206
+ res.body = {};
207
+ }
208
+ }
209
+ else {
210
+ // if no body: set responseBody as {}
211
+ // do not throw a "cannot parse null as Json" error
212
+ res.body = {};
213
+ }
214
+ }
215
+ else if (mode === 'text') {
216
+ res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
217
+ }
218
+ else if (mode === 'arrayBuffer') {
219
+ res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {};
220
+ }
221
+ else if (mode === 'blob') {
222
+ res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {};
223
+ }
224
+ clearTimeout(timeout);
225
+ res.retryStatus.retryStopped = true;
226
+ // res.err can happen on JSON.parse error
227
+ if (!res.err && this.cfg.logResponse) {
228
+ const { retryAttempt } = res.retryStatus;
229
+ const { logger } = this.cfg;
230
+ logger.log([
231
+ ' <<',
232
+ res.fetchResponse.status,
233
+ res.signature,
234
+ retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
235
+ _since(started),
236
+ ]
237
+ .filter(Boolean)
238
+ .join(' '));
239
+ if (this.cfg.logResponseBody) {
240
+ logger.log(res.body);
241
+ }
242
+ }
243
+ }
244
+ async onNotOkResponse(res, timeout) {
245
+ var _a, _b;
246
+ clearTimeout(timeout);
247
+ let errObj;
248
+ if (res.fetchResponse) {
249
+ const body = _jsonParseIfPossible(await res.fetchResponse.text());
250
+ errObj = _anyToErrorObject(body);
251
+ }
252
+ else if (res.err) {
253
+ errObj = _errorToErrorObject(res.err);
254
+ }
255
+ else {
256
+ errObj = {};
257
+ }
258
+ const originalMessage = errObj.message;
259
+ errObj.message = [
260
+ [(_a = res.fetchResponse) === null || _a === void 0 ? void 0 : _a.status, res.signature].filter(Boolean).join(' '),
261
+ originalMessage,
262
+ ]
263
+ .filter(Boolean)
264
+ .join('\n');
265
+ res.err = new HttpError(errObj.message, _filterNullishValues(Object.assign(Object.assign({}, errObj.data), { originalMessage, httpStatusCode: ((_b = res.fetchResponse) === null || _b === void 0 ? void 0 : _b.status) || 0,
266
+ // These properties are provided to be used in e.g custom Sentry error grouping
267
+ // Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
268
+ // Enabled, cause `data` is not printed by default when error is HttpError
269
+ // method: req.method,
270
+ url: res.req.url })));
271
+ await this.processRetry(res);
272
+ }
260
273
  async processRetry(res) {
261
274
  var _a, e_3, _b, _c;
262
275
  const { retryStatus } = res;
@@ -291,11 +304,14 @@ export class Fetcher {
291
304
  return;
292
305
  retryStatus.retryAttempt++;
293
306
  retryStatus.retryTimeout = _clamp(retryStatus.retryTimeout * timeoutMultiplier, 0, timeoutMax);
294
- await pDelay(retryStatus.retryTimeout);
307
+ const noise = Math.random() * 500;
308
+ await pDelay(retryStatus.retryTimeout + noise);
295
309
  }
296
310
  /**
297
311
  * Default is yes,
298
312
  * unless there's reason not to (e.g method is POST).
313
+ *
314
+ * statusCode of 0 (or absense of it) will BE retried.
299
315
  */
300
316
  shouldRetry(res) {
301
317
  var _a;
@@ -344,7 +360,7 @@ export class Fetcher {
344
360
  if (!this.cfg.logWithSearchParams) {
345
361
  shortUrl = shortUrl.split('?')[0];
346
362
  }
347
- if (!this.cfg.logWithPrefixUrl && baseUrl && shortUrl.startsWith(baseUrl)) {
363
+ if (!this.cfg.logWithBaseUrl && baseUrl && shortUrl.startsWith(baseUrl)) {
348
364
  shortUrl = shortUrl.slice(baseUrl.length);
349
365
  }
350
366
  return shortUrl;
@@ -372,7 +388,7 @@ export class Fetcher {
372
388
  logRequestBody: debug,
373
389
  logResponse: debug,
374
390
  logResponseBody: debug,
375
- logWithPrefixUrl: true,
391
+ logWithBaseUrl: isServerSide(),
376
392
  logWithSearchParams: true,
377
393
  retry: Object.assign({}, defRetryOptions),
378
394
  init: {
package/dist-esm/index.js CHANGED
@@ -72,6 +72,7 @@ export * from './datetime/localDate';
72
72
  export * from './datetime/localTime';
73
73
  export * from './datetime/dateInterval';
74
74
  export * from './datetime/timeInterval';
75
+ export * from './env';
75
76
  export * from './http/http.model';
76
77
  export * from './http/fetcher';
77
78
  export * from './http/fetcher.model';
@@ -77,6 +77,8 @@ export function _stringifyAny(obj, opt = {}) {
77
77
  }
78
78
  if (_isErrorObject(obj)) {
79
79
  if (_isHttpErrorObject(obj)) {
80
+ // Only include (statusCode) if it's non-zero
81
+ // No: print (0), as it removes ambiguity
80
82
  // `replace` here works ONCE, exactly as we need it
81
83
  s = s.replace('HttpError', `HttpError(${obj.data.httpStatusCode})`);
82
84
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.133.1",
3
+ "version": "14.135.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
package/src/env.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Use it to detect SSR/Node.js environment.
3
+ *
4
+ * Will return `true` in Node.js.
5
+ * Will return `false` in the Browser.
6
+ */
7
+ export function isServerSide(): boolean {
8
+ return typeof window === 'undefined'
9
+ }
10
+
11
+ /**
12
+ * Use it to detect Browser (not SSR/Node) environment.
13
+ *
14
+ * Will return `true` in the Browser.
15
+ * Will return `false` in Node.js.
16
+ */
17
+ export function isClientSide(): boolean {
18
+ return typeof window !== 'undefined'
19
+ }
@@ -49,10 +49,19 @@ export interface FetcherCfg {
49
49
  logResponseBody?: boolean
50
50
 
51
51
  /**
52
- * Default to true.
53
- * Set to false to exclude `prefixUrl` from logs (both success and error)
52
+ * Controls if `baseUrl` should be included in logs (both success and error).
53
+ *
54
+ * Defaults to `true` on ServerSide and `false` on ClientSide.
55
+ *
56
+ * Reasoning.
57
+ *
58
+ * ClientSide often uses one main "backend host".
59
+ * Not including baseUrl improves Sentry error grouping.
60
+ *
61
+ * ServerSide often uses one Fetcher instance per 3rd-party API.
62
+ * Not including baseUrl can introduce confusion of "which API is it?".
54
63
  */
55
- logWithPrefixUrl?: boolean
64
+ logWithBaseUrl?: boolean
56
65
 
57
66
  /**
58
67
  * Default to true.
@@ -165,6 +174,7 @@ export interface FetcherSuccessResponse<BODY = unknown> {
165
174
  req: FetcherRequest
166
175
  statusFamily?: HttpStatusFamily
167
176
  retryStatus: FetcherRetryStatus
177
+ signature: string
168
178
  }
169
179
 
170
180
  export interface FetcherErrorResponse<BODY = unknown> {
@@ -175,6 +185,7 @@ export interface FetcherErrorResponse<BODY = unknown> {
175
185
  req: FetcherRequest
176
186
  statusFamily?: HttpStatusFamily
177
187
  retryStatus: FetcherRetryStatus
188
+ signature: string
178
189
  }
179
190
 
180
191
  export type FetcherResponse<BODY = unknown> =
@@ -1,5 +1,6 @@
1
1
  /// <reference lib="dom"/>
2
2
 
3
+ import { isServerSide } from '../env'
3
4
  import { ErrorObject } from '../error/error.model'
4
5
  import { _anyToError, _anyToErrorObject, _errorToErrorObject } from '../error/error.util'
5
6
  import { HttpError } from '../error/http.error'
@@ -30,7 +31,7 @@ import type { HttpStatusFamily } from './http.model'
30
31
 
31
32
  const defRetryOptions: FetcherRetryOptions = {
32
33
  count: 2,
33
- timeout: 500,
34
+ timeout: 1000,
34
35
  timeoutMax: 30_000,
35
36
  timeoutMultiplier: 2,
36
37
  }
@@ -151,7 +152,6 @@ export class Fetcher {
151
152
  const req = this.normalizeOptions(url, rawOpt)
152
153
  const {
153
154
  timeoutSeconds,
154
- mode,
155
155
  init: { method },
156
156
  } = req
157
157
 
@@ -169,6 +169,11 @@ export class Fetcher {
169
169
  await hook(req)
170
170
  }
171
171
 
172
+ const isFullUrl = req.url.includes('://')
173
+ const fullUrl = isFullUrl ? new URL(req.url) : undefined
174
+ const shortUrl = fullUrl ? this.getShortUrl(fullUrl) : req.url
175
+ const signature = [method, shortUrl].join(' ')
176
+
172
177
  const res = {
173
178
  req,
174
179
  retryStatus: {
@@ -176,12 +181,9 @@ export class Fetcher {
176
181
  retryStopped: false,
177
182
  retryTimeout: req.retry.timeout,
178
183
  },
184
+ signature,
179
185
  } as FetcherResponse<any>
180
186
 
181
- const fullUrl = new URL(req.url)
182
- const shortUrl = this.getShortUrl(fullUrl)
183
- const signature = [method, shortUrl].join(' ')
184
-
185
187
  /* eslint-disable no-await-in-loop */
186
188
  while (!res.retryStatus.retryStopped) {
187
189
  const started = Date.now()
@@ -209,109 +211,129 @@ export class Fetcher {
209
211
  res.statusFamily = this.getStatusFamily(res)
210
212
 
211
213
  if (res.fetchResponse?.ok) {
212
- if (mode === 'json') {
213
- if (res.fetchResponse.body) {
214
- const text = await res.fetchResponse.text()
215
-
216
- if (text) {
217
- try {
218
- res.body = text
219
- res.body = JSON.parse(text, req.jsonReviver)
220
- } catch (err) {
221
- const { message } = _anyToError(err)
222
- res.err = new HttpError([signature, message].join('\n'), {
223
- httpStatusCode: 0,
224
- url: req.url,
225
- })
226
- res.ok = false
227
- }
228
- } else {
229
- // Body had a '' (empty string)
230
- res.body = {}
231
- }
232
- } else {
233
- // if no body: set responseBody as {}
234
- // do not throw a "cannot parse null as Json" error
235
- res.body = {}
236
- }
237
- } else if (mode === 'text') {
238
- res.body = res.fetchResponse.body ? await res.fetchResponse.text() : ''
239
- } else if (mode === 'arrayBuffer') {
240
- res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {}
241
- } else if (mode === 'blob') {
242
- res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {}
243
- }
244
-
245
- clearTimeout(timeout)
246
- res.retryStatus.retryStopped = true
247
-
248
- // res.err can happen on JSON.parse error
249
- if (!res.err && this.cfg.logResponse) {
250
- const { retryAttempt } = res.retryStatus
251
- logger.log(
252
- [
253
- ' <<',
254
- res.fetchResponse.status,
255
- signature,
256
- retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
257
- _since(started),
258
- ]
259
- .filter(Boolean)
260
- .join(' '),
261
- )
262
-
263
- if (this.cfg.logResponseBody) {
264
- logger.log(res.body)
265
- }
266
- }
214
+ await this.onOkResponse(
215
+ res as FetcherResponse<T> & { fetchResponse: Response },
216
+ started,
217
+ timeout,
218
+ )
267
219
  } else {
268
220
  // !res.ok
269
- clearTimeout(timeout)
221
+ await this.onNotOkResponse(res, timeout)
222
+ }
223
+ }
224
+
225
+ for await (const hook of this.cfg.hooks.afterResponse || []) {
226
+ await hook(res)
227
+ }
270
228
 
271
- let errObj: ErrorObject
229
+ return res
230
+ }
272
231
 
273
- if (res.fetchResponse) {
274
- const body = _jsonParseIfPossible(await res.fetchResponse.text())
275
- errObj = _anyToErrorObject(body)
276
- } else if (res.err) {
277
- errObj = _errorToErrorObject(res.err)
232
+ private async onOkResponse(
233
+ res: FetcherResponse<any> & { fetchResponse: Response },
234
+ started: number,
235
+ timeout?: number,
236
+ ): Promise<void> {
237
+ const { req } = res
238
+ const { mode } = res.req
239
+
240
+ if (mode === 'json') {
241
+ if (res.fetchResponse.body) {
242
+ const text = await res.fetchResponse.text()
243
+
244
+ if (text) {
245
+ try {
246
+ res.body = text
247
+ res.body = JSON.parse(text, req.jsonReviver)
248
+ } catch (err) {
249
+ const { message } = _anyToError(err)
250
+ res.err = new HttpError([res.signature, message].join('\n'), {
251
+ httpStatusCode: 0,
252
+ url: req.url,
253
+ })
254
+ res.ok = false
255
+ }
278
256
  } else {
279
- errObj = {} as ErrorObject
257
+ // Body had a '' (empty string)
258
+ res.body = {}
280
259
  }
260
+ } else {
261
+ // if no body: set responseBody as {}
262
+ // do not throw a "cannot parse null as Json" error
263
+ res.body = {}
264
+ }
265
+ } else if (mode === 'text') {
266
+ res.body = res.fetchResponse.body ? await res.fetchResponse.text() : ''
267
+ } else if (mode === 'arrayBuffer') {
268
+ res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {}
269
+ } else if (mode === 'blob') {
270
+ res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {}
271
+ }
281
272
 
282
- const originalMessage = errObj.message
283
- errObj.message = [
284
- [res.fetchResponse?.status, signature].filter(Boolean).join(' '),
285
- originalMessage,
273
+ clearTimeout(timeout)
274
+ res.retryStatus.retryStopped = true
275
+
276
+ // res.err can happen on JSON.parse error
277
+ if (!res.err && this.cfg.logResponse) {
278
+ const { retryAttempt } = res.retryStatus
279
+ const { logger } = this.cfg
280
+ logger.log(
281
+ [
282
+ ' <<',
283
+ res.fetchResponse.status,
284
+ res.signature,
285
+ retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
286
+ _since(started),
286
287
  ]
287
288
  .filter(Boolean)
288
- .join('\n')
289
-
290
- res.err = new HttpError(
291
- errObj.message,
292
-
293
- _filterNullishValues({
294
- ...errObj.data,
295
- originalMessage,
296
- httpStatusCode: res.fetchResponse?.status || 0,
297
- // These properties are provided to be used in e.g custom Sentry error grouping
298
- // Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
299
- // Enabled, cause `data` is not printed by default when error is HttpError
300
- // method: req.method,
301
- url: req.url,
302
- // tryCount: req.tryCount,
303
- }),
304
- )
289
+ .join(' '),
290
+ )
305
291
 
306
- await this.processRetry(res)
292
+ if (this.cfg.logResponseBody) {
293
+ logger.log(res.body)
307
294
  }
308
295
  }
296
+ }
309
297
 
310
- for await (const hook of this.cfg.hooks.afterResponse || []) {
311
- await hook(res)
298
+ private async onNotOkResponse(res: FetcherResponse, timeout?: number): Promise<void> {
299
+ clearTimeout(timeout)
300
+
301
+ let errObj: ErrorObject
302
+
303
+ if (res.fetchResponse) {
304
+ const body = _jsonParseIfPossible(await res.fetchResponse.text())
305
+ errObj = _anyToErrorObject(body)
306
+ } else if (res.err) {
307
+ errObj = _errorToErrorObject(res.err)
308
+ } else {
309
+ errObj = {} as ErrorObject
312
310
  }
313
311
 
314
- return res
312
+ const originalMessage = errObj.message
313
+ errObj.message = [
314
+ [res.fetchResponse?.status, res.signature].filter(Boolean).join(' '),
315
+ originalMessage,
316
+ ]
317
+ .filter(Boolean)
318
+ .join('\n')
319
+
320
+ res.err = new HttpError(
321
+ errObj.message,
322
+
323
+ _filterNullishValues({
324
+ ...errObj.data,
325
+ originalMessage,
326
+ httpStatusCode: res.fetchResponse?.status || 0,
327
+ // These properties are provided to be used in e.g custom Sentry error grouping
328
+ // Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
329
+ // Enabled, cause `data` is not printed by default when error is HttpError
330
+ // method: req.method,
331
+ url: res.req.url,
332
+ // tryCount: req.tryCount,
333
+ }),
334
+ )
335
+
336
+ await this.processRetry(res)
315
337
  }
316
338
 
317
339
  private async processRetry(res: FetcherResponse): Promise<void> {
@@ -336,12 +358,15 @@ export class Fetcher {
336
358
  retryStatus.retryAttempt++
337
359
  retryStatus.retryTimeout = _clamp(retryStatus.retryTimeout * timeoutMultiplier, 0, timeoutMax)
338
360
 
339
- await pDelay(retryStatus.retryTimeout)
361
+ const noise = Math.random() * 500
362
+ await pDelay(retryStatus.retryTimeout + noise)
340
363
  }
341
364
 
342
365
  /**
343
366
  * Default is yes,
344
367
  * unless there's reason not to (e.g method is POST).
368
+ *
369
+ * statusCode of 0 (or absense of it) will BE retried.
345
370
  */
346
371
  private shouldRetry(res: FetcherResponse): boolean {
347
372
  const { retryPost, retry4xx, retry5xx } = res.req
@@ -385,7 +410,7 @@ export class Fetcher {
385
410
  shortUrl = shortUrl.split('?')[0]!
386
411
  }
387
412
 
388
- if (!this.cfg.logWithPrefixUrl && baseUrl && shortUrl.startsWith(baseUrl)) {
413
+ if (!this.cfg.logWithBaseUrl && baseUrl && shortUrl.startsWith(baseUrl)) {
389
414
  shortUrl = shortUrl.slice(baseUrl.length)
390
415
  }
391
416
 
@@ -416,7 +441,7 @@ export class Fetcher {
416
441
  logRequestBody: debug,
417
442
  logResponse: debug,
418
443
  logResponseBody: debug,
419
- logWithPrefixUrl: true,
444
+ logWithBaseUrl: isServerSide(),
420
445
  logWithSearchParams: true,
421
446
  retry: { ...defRetryOptions },
422
447
  init: {
package/src/index.ts CHANGED
@@ -72,6 +72,7 @@ export * from './datetime/localDate'
72
72
  export * from './datetime/localTime'
73
73
  export * from './datetime/dateInterval'
74
74
  export * from './datetime/timeInterval'
75
+ export * from './env'
75
76
  export * from './http/http.model'
76
77
  export * from './http/fetcher'
77
78
  export * from './http/fetcher.model'
@@ -125,6 +125,8 @@ export function _stringifyAny(obj: any, opt: StringifyAnyOptions = {}): string {
125
125
 
126
126
  if (_isErrorObject(obj)) {
127
127
  if (_isHttpErrorObject(obj)) {
128
+ // Only include (statusCode) if it's non-zero
129
+ // No: print (0), as it removes ambiguity
128
130
  // `replace` here works ONCE, exactly as we need it
129
131
  s = s.replace('HttpError', `HttpError(${obj.data.httpStatusCode})`)
130
132
  }
package/src/vendor/is.ts CHANGED
@@ -25,7 +25,7 @@ const typedArrayTypeNames = [
25
25
  'BigUint64Array',
26
26
  ] as const
27
27
 
28
- type TypedArrayTypeName = typeof typedArrayTypeNames[number]
28
+ type TypedArrayTypeName = (typeof typedArrayTypeNames)[number]
29
29
 
30
30
  function isTypedArrayName(name: unknown): name is TypedArrayTypeName {
31
31
  return typedArrayTypeNames.includes(name as TypedArrayTypeName)
@@ -60,7 +60,7 @@ const objectTypeNames = [
60
60
  ...typedArrayTypeNames,
61
61
  ] as const
62
62
 
63
- type ObjectTypeName = typeof objectTypeNames[number]
63
+ type ObjectTypeName = (typeof objectTypeNames)[number]
64
64
 
65
65
  function isObjectTypeName(name: unknown): name is ObjectTypeName {
66
66
  return objectTypeNames.includes(name as ObjectTypeName)
@@ -76,7 +76,7 @@ const primitiveTypeNames = [
76
76
  'symbol',
77
77
  ] as const
78
78
 
79
- type PrimitiveTypeName = typeof primitiveTypeNames[number]
79
+ type PrimitiveTypeName = (typeof primitiveTypeNames)[number]
80
80
 
81
81
  function isPrimitiveTypeName(name: unknown): name is PrimitiveTypeName {
82
82
  return primitiveTypeNames.includes(name as PrimitiveTypeName)