@naturalcycles/js-lib 14.265.0 → 14.266.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/bot.js CHANGED
@@ -64,7 +64,7 @@ class BotDetectionService {
64
64
  // if (userAgent.includes('Chrome') && !(globalThis as any).chrome) {
65
65
  // return BotReason.ChromeWithoutChrome // Headless Chrome
66
66
  // }
67
- if (this.cfg.treatCDPAsBotReason && this.detectCDP()) {
67
+ if (this.cfg.treatCDPAsBotReason && this.isCDP()) {
68
68
  return BotReason.CDP;
69
69
  }
70
70
  return null;
@@ -3,7 +3,7 @@
3
3
  /// <reference lib="dom.iterable" preserve="true" />
4
4
  import { HttpRequestError } from '../error/error.util';
5
5
  import { ErrorDataTuple } from '../types';
6
- import type { FetcherAfterResponseHook, FetcherBeforeRequestHook, FetcherBeforeRetryHook, FetcherCfg, FetcherNormalizedCfg, FetcherOnErrorHook, FetcherOptions, FetcherResponse, RequestInitNormalized } from './fetcher.model';
6
+ import { FetcherAfterResponseHook, FetcherBeforeRequestHook, FetcherBeforeRetryHook, FetcherCfg, FetcherGraphQLOptions, FetcherNormalizedCfg, FetcherOnErrorHook, FetcherOptions, FetcherResponse, RequestInitNormalized } from './fetcher.model';
7
7
  /**
8
8
  * Experimental wrapper around Fetch.
9
9
  * Works in both Browser and Node, using `globalThis.fetch`.
@@ -43,6 +43,18 @@ export declare class Fetcher {
43
43
  patchVoid: (url: string, opt?: FetcherOptions) => Promise<void>;
44
44
  deleteVoid: (url: string, opt?: FetcherOptions) => Promise<void>;
45
45
  headVoid: (url: string, opt?: FetcherOptions) => Promise<void>;
46
+ /**
47
+ * Small convenience wrapper that allows to issue GraphQL queries.
48
+ * In practice, all it does is:
49
+ * - Defines convenience `query` input option
50
+ * - Unwraps `response.data`
51
+ * - Unwraps `response.errors` and throws, if it's defined (as GQL famously returns http 200 even for errors)
52
+ *
53
+ * Currently it only unwraps and uses the first error from the `errors` array, for simplicity.
54
+ *
55
+ * @experimental
56
+ */
57
+ queryGraphQL<T = unknown>(opt: FetcherGraphQLOptions): Promise<T>;
46
58
  /**
47
59
  * Returns raw fetchResponse.body, which is a ReadableStream<Uint8Array>
48
60
  *
@@ -106,6 +106,43 @@ class Fetcher {
106
106
  static create(cfg = {}) {
107
107
  return new Fetcher(cfg);
108
108
  }
109
+ /**
110
+ * Small convenience wrapper that allows to issue GraphQL queries.
111
+ * In practice, all it does is:
112
+ * - Defines convenience `query` input option
113
+ * - Unwraps `response.data`
114
+ * - Unwraps `response.errors` and throws, if it's defined (as GQL famously returns http 200 even for errors)
115
+ *
116
+ * Currently it only unwraps and uses the first error from the `errors` array, for simplicity.
117
+ *
118
+ * @experimental
119
+ */
120
+ async queryGraphQL(opt) {
121
+ opt.json = {
122
+ query: opt.query,
123
+ variables: opt.variables,
124
+ };
125
+ const res = await this.doFetch(opt);
126
+ if (res.err) {
127
+ throw res.err;
128
+ }
129
+ if (res.body.errors) {
130
+ // unwrap errors and throw
131
+ const err = res.body.errors[0];
132
+ // todo: consider creating a new GraphQLError class for this
133
+ throw new error_util_1.HttpRequestError(err.message, {
134
+ errors: res.body.errors, // full errors payload returned
135
+ response: res.fetchResponse,
136
+ responseStatusCode: res.statusCode,
137
+ requestUrl: res.req.fullUrl,
138
+ requestBaseUrl: this.cfg.baseUrl,
139
+ requestMethod: res.req.init.method,
140
+ requestSignature: res.signature,
141
+ requestDuration: Date.now() - res.req.started,
142
+ });
143
+ }
144
+ return res.body.data;
145
+ }
109
146
  // responseType=readableStream
110
147
  /**
111
148
  * Returns raw fetchResponse.body, which is a ReadableStream<Uint8Array>
@@ -116,6 +116,10 @@ export interface FetcherRequest extends Omit<FetcherOptions, 'method' | 'headers
116
116
  retry5xx: boolean;
117
117
  started: UnixTimestampMillis;
118
118
  }
119
+ export interface FetcherGraphQLOptions extends FetcherOptions {
120
+ query: string;
121
+ variables?: AnyObject;
122
+ }
119
123
  export interface FetcherOptions {
120
124
  method?: HttpMethod;
121
125
  /**
@@ -253,3 +257,19 @@ export type FetcherResponseType = 'json' | 'text' | 'void' | 'arrayBuffer' | 'bl
253
257
  * e.g when mocking.
254
258
  */
255
259
  export type FetchFunction = (url: string, init: RequestInitNormalized) => Promise<Response>;
260
+ export type GraphQLResponse<DATA> = GraphQLSuccessResponse<DATA> | GraphQLErrorResponse;
261
+ export interface GraphQLSuccessResponse<DATA> {
262
+ data: DATA;
263
+ errors: never;
264
+ }
265
+ export interface GraphQLErrorResponse {
266
+ data: never;
267
+ errors: GraphQLFormattedError[];
268
+ }
269
+ /**
270
+ * Copy-pasted from `graphql` package, slimmed down.
271
+ * See: https://spec.graphql.org/draft/#sec-Errors
272
+ */
273
+ export interface GraphQLFormattedError {
274
+ message: string;
275
+ }
package/dist-esm/bot.js CHANGED
@@ -61,7 +61,7 @@ export class BotDetectionService {
61
61
  // if (userAgent.includes('Chrome') && !(globalThis as any).chrome) {
62
62
  // return BotReason.ChromeWithoutChrome // Headless Chrome
63
63
  // }
64
- if (this.cfg.treatCDPAsBotReason && this.detectCDP()) {
64
+ if (this.cfg.treatCDPAsBotReason && this.isCDP()) {
65
65
  return BotReason.CDP;
66
66
  }
67
67
  return null;
@@ -99,6 +99,43 @@ export class Fetcher {
99
99
  static create(cfg = {}) {
100
100
  return new _a(cfg);
101
101
  }
102
+ /**
103
+ * Small convenience wrapper that allows to issue GraphQL queries.
104
+ * In practice, all it does is:
105
+ * - Defines convenience `query` input option
106
+ * - Unwraps `response.data`
107
+ * - Unwraps `response.errors` and throws, if it's defined (as GQL famously returns http 200 even for errors)
108
+ *
109
+ * Currently it only unwraps and uses the first error from the `errors` array, for simplicity.
110
+ *
111
+ * @experimental
112
+ */
113
+ async queryGraphQL(opt) {
114
+ opt.json = {
115
+ query: opt.query,
116
+ variables: opt.variables,
117
+ };
118
+ const res = await this.doFetch(opt);
119
+ if (res.err) {
120
+ throw res.err;
121
+ }
122
+ if (res.body.errors) {
123
+ // unwrap errors and throw
124
+ const err = res.body.errors[0];
125
+ // todo: consider creating a new GraphQLError class for this
126
+ throw new HttpRequestError(err.message, {
127
+ errors: res.body.errors, // full errors payload returned
128
+ response: res.fetchResponse,
129
+ responseStatusCode: res.statusCode,
130
+ requestUrl: res.req.fullUrl,
131
+ requestBaseUrl: this.cfg.baseUrl,
132
+ requestMethod: res.req.init.method,
133
+ requestSignature: res.signature,
134
+ requestDuration: Date.now() - res.req.started,
135
+ });
136
+ }
137
+ return res.body.data;
138
+ }
102
139
  // responseType=readableStream
103
140
  /**
104
141
  * Returns raw fetchResponse.body, which is a ReadableStream<Uint8Array>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.265.0",
3
+ "version": "14.266.0",
4
4
  "scripts": {
5
5
  "prepare": "husky",
6
6
  "build": "dev-lib build-esm-cjs",
package/src/bot.ts CHANGED
@@ -96,7 +96,7 @@ export class BotDetectionService {
96
96
  // return BotReason.ChromeWithoutChrome // Headless Chrome
97
97
  // }
98
98
 
99
- if (this.cfg.treatCDPAsBotReason && this.detectCDP()) {
99
+ if (this.cfg.treatCDPAsBotReason && this.isCDP()) {
100
100
  return BotReason.CDP
101
101
  }
102
102
 
@@ -152,6 +152,11 @@ export interface FetcherRequest
152
152
  started: UnixTimestampMillis
153
153
  }
154
154
 
155
+ export interface FetcherGraphQLOptions extends FetcherOptions {
156
+ query: string
157
+ variables?: AnyObject
158
+ }
159
+
155
160
  export interface FetcherOptions {
156
161
  method?: HttpMethod
157
162
 
@@ -325,3 +330,23 @@ export type FetcherResponseType =
325
330
  * e.g when mocking.
326
331
  */
327
332
  export type FetchFunction = (url: string, init: RequestInitNormalized) => Promise<Response>
333
+
334
+ export type GraphQLResponse<DATA> = GraphQLSuccessResponse<DATA> | GraphQLErrorResponse
335
+
336
+ export interface GraphQLSuccessResponse<DATA> {
337
+ data: DATA
338
+ errors: never
339
+ }
340
+
341
+ export interface GraphQLErrorResponse {
342
+ data: never
343
+ errors: GraphQLFormattedError[]
344
+ }
345
+
346
+ /**
347
+ * Copy-pasted from `graphql` package, slimmed down.
348
+ * See: https://spec.graphql.org/draft/#sec-Errors
349
+ */
350
+ export interface GraphQLFormattedError {
351
+ message: string
352
+ }
@@ -29,11 +29,12 @@ import { _jsonParse, _jsonParseIfPossible } from '../string/json.util'
29
29
  import { _stringify } from '../string/stringify'
30
30
  import { _ms, _since } from '../time/time.util'
31
31
  import { ErrorDataTuple, NumberOfMilliseconds, UnixTimestampMillis } from '../types'
32
- import type {
32
+ import {
33
33
  FetcherAfterResponseHook,
34
34
  FetcherBeforeRequestHook,
35
35
  FetcherBeforeRetryHook,
36
36
  FetcherCfg,
37
+ FetcherGraphQLOptions,
37
38
  FetcherNormalizedCfg,
38
39
  FetcherOnErrorHook,
39
40
  FetcherOptions,
@@ -41,6 +42,7 @@ import type {
41
42
  FetcherResponse,
42
43
  FetcherResponseType,
43
44
  FetcherRetryOptions,
45
+ GraphQLResponse,
44
46
  RequestInitNormalized,
45
47
  } from './fetcher.model'
46
48
  import type { HttpStatusFamily } from './http.model'
@@ -171,6 +173,48 @@ export class Fetcher {
171
173
  deleteVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
172
174
  headVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
173
175
 
176
+ /**
177
+ * Small convenience wrapper that allows to issue GraphQL queries.
178
+ * In practice, all it does is:
179
+ * - Defines convenience `query` input option
180
+ * - Unwraps `response.data`
181
+ * - Unwraps `response.errors` and throws, if it's defined (as GQL famously returns http 200 even for errors)
182
+ *
183
+ * Currently it only unwraps and uses the first error from the `errors` array, for simplicity.
184
+ *
185
+ * @experimental
186
+ */
187
+ async queryGraphQL<T = unknown>(opt: FetcherGraphQLOptions): Promise<T> {
188
+ opt.json = {
189
+ query: opt.query,
190
+ variables: opt.variables,
191
+ }
192
+
193
+ const res = await this.doFetch<GraphQLResponse<T>>(opt)
194
+ if (res.err) {
195
+ throw res.err
196
+ }
197
+
198
+ if (res.body.errors) {
199
+ // unwrap errors and throw
200
+ const err = res.body.errors[0]!
201
+
202
+ // todo: consider creating a new GraphQLError class for this
203
+ throw new HttpRequestError(err.message, {
204
+ errors: res.body.errors, // full errors payload returned
205
+ response: res.fetchResponse,
206
+ responseStatusCode: res.statusCode,
207
+ requestUrl: res.req.fullUrl,
208
+ requestBaseUrl: this.cfg.baseUrl,
209
+ requestMethod: res.req.init.method,
210
+ requestSignature: res.signature,
211
+ requestDuration: Date.now() - res.req.started,
212
+ })
213
+ }
214
+
215
+ return res.body.data
216
+ }
217
+
174
218
  // responseType=readableStream
175
219
  /**
176
220
  * Returns raw fetchResponse.body, which is a ReadableStream<Uint8Array>