getta 0.2.3 → 0.4.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/CHANGELOG.md +12 -0
- package/lib/browser/index.js +1 -1
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/production.analysis.txt +20 -20
- package/lib/main/constants.js +8 -2
- package/lib/main/main.js +104 -27
- package/lib/module/constants.js +4 -1
- package/lib/module/main.js +111 -28
- package/lib/types/constants.d.ts +3 -0
- package/lib/types/constants.d.ts.map +1 -1
- package/lib/types/main.d.ts +13 -4
- package/lib/types/main.d.ts.map +1 -1
- package/lib/types/types.d.ts +9 -0
- package/lib/types/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +4 -0
- package/src/main.test.ts +46 -5
- package/src/main.ts +111 -34
- package/src/types.ts +13 -0
package/src/main.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
DEFAULT_MAX_REDIRECTS,
|
|
13
13
|
DEFAULT_MAX_RETRIES,
|
|
14
14
|
DEFAULT_PATH_TEMPLATE_REGEX,
|
|
15
|
+
DEFAULT_RATE_LIMIT,
|
|
15
16
|
DEFAULT_REQUEST_RETRY_WAIT,
|
|
16
17
|
DELETE_METHOD,
|
|
17
18
|
ETAG_HEADER,
|
|
@@ -31,7 +32,9 @@ import {
|
|
|
31
32
|
POST_METHOD,
|
|
32
33
|
PUT_METHOD,
|
|
33
34
|
REDIRECTION_REPSONSE,
|
|
35
|
+
REQUEST_SENT,
|
|
34
36
|
RESOURCE_NOT_FOUND_ERROR,
|
|
37
|
+
RESPONSE_RECEIVED,
|
|
35
38
|
SERVER_ERROR_REPSONSE,
|
|
36
39
|
} from "./constants";
|
|
37
40
|
import buildEndpoint from "./helpers/build-endpoint";
|
|
@@ -44,10 +47,13 @@ import {
|
|
|
44
47
|
FetchOptions,
|
|
45
48
|
FetchRedirectHandlerOptions,
|
|
46
49
|
FetchResponse,
|
|
50
|
+
Log,
|
|
47
51
|
PathTemplateCallback,
|
|
48
52
|
PendingRequestResolver,
|
|
49
53
|
PendingRequestResolvers,
|
|
54
|
+
Performance,
|
|
50
55
|
RequestOptions,
|
|
56
|
+
RequestQueue,
|
|
51
57
|
RequestTracker,
|
|
52
58
|
ShortcutProperties,
|
|
53
59
|
Shortcuts,
|
|
@@ -61,12 +67,18 @@ export class Getta {
|
|
|
61
67
|
private _conditionalRequestsEnabled: boolean;
|
|
62
68
|
private _fetchTimeout: number;
|
|
63
69
|
private _headers: StringObject;
|
|
70
|
+
private _log: Log | undefined;
|
|
64
71
|
private _maxRedirects: number;
|
|
65
72
|
private _maxRetries: number;
|
|
66
73
|
private _optionalPathTemplateRegExp: RegExp;
|
|
67
74
|
private _pathTemplateCallback: PathTemplateCallback;
|
|
68
75
|
private _pathTemplateRegExp: RegExp;
|
|
76
|
+
private _performance: Performance;
|
|
69
77
|
private _queryParams: PlainObject;
|
|
78
|
+
private _rateLimitCount: number = 0;
|
|
79
|
+
private _rateLimitedRequestQueue: RequestQueue = [];
|
|
80
|
+
private _rateLimitPerSecond: number;
|
|
81
|
+
private _rateLimitTimer: NodeJS.Timer | null = null;
|
|
70
82
|
private _requestRetryWait: number;
|
|
71
83
|
private _requestTracker: RequestTracker = { active: [], pending: new Map() };
|
|
72
84
|
private _streamReader: StreamReader;
|
|
@@ -79,12 +91,15 @@ export class Getta {
|
|
|
79
91
|
enableConditionalRequests = true,
|
|
80
92
|
fetchTimeout = DEFAULT_FETCH_TIMEOUT,
|
|
81
93
|
headers,
|
|
94
|
+
log,
|
|
82
95
|
maxRedirects = DEFAULT_MAX_REDIRECTS,
|
|
83
96
|
maxRetries = DEFAULT_MAX_RETRIES,
|
|
84
97
|
optionalPathTemplateRegExp = OPTIONAL_PATH_TEMPLATE_REGEX,
|
|
85
98
|
pathTemplateCallback = defaultPathTemplateCallback,
|
|
86
99
|
pathTemplateRegExp = DEFAULT_PATH_TEMPLATE_REGEX,
|
|
100
|
+
performance,
|
|
87
101
|
queryParams = {},
|
|
102
|
+
rateLimitPerSecond = DEFAULT_RATE_LIMIT,
|
|
88
103
|
requestRetryWait = DEFAULT_REQUEST_RETRY_WAIT,
|
|
89
104
|
streamReader = JSON_FORMAT,
|
|
90
105
|
} = options;
|
|
@@ -99,12 +114,15 @@ export class Getta {
|
|
|
99
114
|
this._conditionalRequestsEnabled = enableConditionalRequests;
|
|
100
115
|
this._fetchTimeout = fetchTimeout;
|
|
101
116
|
this._headers = { ...DEFAULT_HEADERS, ...(headers || {}) };
|
|
117
|
+
this._log = log;
|
|
102
118
|
this._maxRedirects = maxRedirects;
|
|
103
119
|
this._maxRetries = maxRetries;
|
|
104
120
|
this._optionalPathTemplateRegExp = optionalPathTemplateRegExp;
|
|
105
121
|
this._pathTemplateCallback = pathTemplateCallback;
|
|
106
122
|
this._pathTemplateRegExp = pathTemplateRegExp;
|
|
123
|
+
this._performance = performance;
|
|
107
124
|
this._queryParams = queryParams;
|
|
125
|
+
this._rateLimitPerSecond = rateLimitPerSecond;
|
|
108
126
|
this._requestRetryWait = requestRetryWait;
|
|
109
127
|
this._streamReader = streamReader;
|
|
110
128
|
}
|
|
@@ -124,20 +142,26 @@ export class Getta {
|
|
|
124
142
|
this[requestMethod ?? method](path, merge({}, rest, requestRest)) as Promise<FetchResponse<Resource>>;
|
|
125
143
|
}
|
|
126
144
|
|
|
127
|
-
public async delete(path: string, options: Omit<RequestOptions, "method"> = {}) {
|
|
128
|
-
return this._delete(path, options);
|
|
145
|
+
public async delete(path: string, options: Omit<RequestOptions, "method"> = {}, context?: PlainObject) {
|
|
146
|
+
return this._delete(path, options, context);
|
|
129
147
|
}
|
|
130
148
|
|
|
131
|
-
public async get(path: string, options: Omit<RequestOptions, "method"> = {}) {
|
|
132
|
-
return this._get(path, options);
|
|
149
|
+
public async get(path: string, options: Omit<RequestOptions, "method"> = {}, context?: PlainObject) {
|
|
150
|
+
return this._get(path, options, context);
|
|
133
151
|
}
|
|
134
152
|
|
|
135
|
-
public async post(path: string, options: Omit<Required<RequestOptions, "body">, "method"
|
|
136
|
-
return this._request(path, { ...options, method: POST_METHOD });
|
|
153
|
+
public async post(path: string, options: Omit<Required<RequestOptions, "body">, "method">, context?: PlainObject) {
|
|
154
|
+
return this._request(path, { ...options, method: POST_METHOD }, context);
|
|
137
155
|
}
|
|
138
156
|
|
|
139
|
-
public async put(path: string, options: Omit<Required<RequestOptions, "body">, "methood"
|
|
140
|
-
return this._request(path, { ...options, method: PUT_METHOD });
|
|
157
|
+
public async put(path: string, options: Omit<Required<RequestOptions, "body">, "methood">, context?: PlainObject) {
|
|
158
|
+
return this._request(path, { ...options, method: PUT_METHOD }, context);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private _addRequestToRateLimitedQueue(endpoint: string, options: FetchOptions) {
|
|
162
|
+
return new Promise((resolve: (value: FetchResponse) => void) => {
|
|
163
|
+
this._rateLimitedRequestQueue.push([resolve, endpoint, options]);
|
|
164
|
+
});
|
|
141
165
|
}
|
|
142
166
|
|
|
143
167
|
private async _cacheEntryDelete(requestHash: string): Promise<boolean> {
|
|
@@ -183,6 +207,7 @@ export class Getta {
|
|
|
183
207
|
private async _delete(
|
|
184
208
|
path: string,
|
|
185
209
|
{ headers = {}, pathTemplateData, queryParams = {}, ...rest }: Omit<RequestOptions, "method">,
|
|
210
|
+
context?: PlainObject,
|
|
186
211
|
) {
|
|
187
212
|
const endpoint = buildEndpoint(this._basePath, path, {
|
|
188
213
|
optionalPathTemplateRegExp: this._optionalPathTemplateRegExp,
|
|
@@ -199,22 +224,52 @@ export class Getta {
|
|
|
199
224
|
this._cacheEntryDelete(requestHash);
|
|
200
225
|
}
|
|
201
226
|
|
|
202
|
-
return this._fetch(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
227
|
+
return this._fetch(
|
|
228
|
+
endpoint,
|
|
229
|
+
{
|
|
230
|
+
headers: { ...this._headers, ...headers },
|
|
231
|
+
method: DELETE_METHOD,
|
|
232
|
+
...rest,
|
|
233
|
+
},
|
|
234
|
+
context,
|
|
235
|
+
);
|
|
207
236
|
}
|
|
208
237
|
|
|
209
|
-
private async _fetch(
|
|
238
|
+
private async _fetch(
|
|
239
|
+
endpoint: string,
|
|
240
|
+
{ redirects, retries, ...rest }: FetchOptions,
|
|
241
|
+
context: PlainObject = {},
|
|
242
|
+
): Promise<FetchResponse> {
|
|
210
243
|
try {
|
|
211
244
|
return new Promise(async (resolve: (value: FetchResponse) => void, reject) => {
|
|
212
245
|
const fetchTimer = setTimeout(() => {
|
|
213
246
|
reject(new Error(`${FETCH_TIMEOUT_ERROR} ${this._fetchTimeout}ms.`));
|
|
214
247
|
}, this._fetchTimeout);
|
|
215
248
|
|
|
249
|
+
this._rateLimit();
|
|
250
|
+
|
|
251
|
+
if (!(this._rateLimitCount < this._rateLimitPerSecond)) {
|
|
252
|
+
resolve(await this._addRequestToRateLimitedQueue(endpoint, { redirects, retries, ...rest }));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const startTime = this._performance.now();
|
|
257
|
+
|
|
258
|
+
this._log?.(REQUEST_SENT, {
|
|
259
|
+
context: { redirects, retries, url: endpoint, ...rest, ...context },
|
|
260
|
+
stats: { startTime },
|
|
261
|
+
});
|
|
262
|
+
|
|
216
263
|
const res = await fetch(endpoint, rest);
|
|
217
264
|
|
|
265
|
+
const endTime = this._performance.now();
|
|
266
|
+
const duration = endTime - startTime;
|
|
267
|
+
|
|
268
|
+
this._log?.(RESPONSE_RECEIVED, {
|
|
269
|
+
context: { redirects, retries, url: endpoint, ...rest, ...context },
|
|
270
|
+
stats: { duration, endTime, startTime },
|
|
271
|
+
});
|
|
272
|
+
|
|
218
273
|
clearTimeout(fetchTimer);
|
|
219
274
|
|
|
220
275
|
const { headers, status } = res;
|
|
@@ -228,6 +283,8 @@ export class Getta {
|
|
|
228
283
|
...rest,
|
|
229
284
|
}),
|
|
230
285
|
);
|
|
286
|
+
|
|
287
|
+
return;
|
|
231
288
|
}
|
|
232
289
|
|
|
233
290
|
if (responseGroup === SERVER_ERROR_REPSONSE) {
|
|
@@ -237,27 +294,17 @@ export class Getta {
|
|
|
237
294
|
...rest,
|
|
238
295
|
})) as FetchResponse,
|
|
239
296
|
);
|
|
297
|
+
|
|
298
|
+
return;
|
|
240
299
|
}
|
|
241
300
|
|
|
242
301
|
const fetchRes = res as FetchResponse;
|
|
243
|
-
const resClone = res.clone();
|
|
244
302
|
|
|
245
303
|
try {
|
|
246
304
|
fetchRes.data = res.body ? this._bodyParser(await res[this._streamReader]()) : undefined;
|
|
247
305
|
resolve(fetchRes);
|
|
248
306
|
} catch (e) {
|
|
249
|
-
|
|
250
|
-
if (this._streamReader === "json" && res.body) {
|
|
251
|
-
const fetchResClone = resClone as FetchResponse;
|
|
252
|
-
const text = await resClone.text();
|
|
253
|
-
fetchResClone.data = JSON.parse(text);
|
|
254
|
-
resolve(fetchResClone);
|
|
255
|
-
} else {
|
|
256
|
-
reject([e, new Error(`Unable to ${rest.method} ${endpoint} due to previous error`)]);
|
|
257
|
-
}
|
|
258
|
-
} catch {
|
|
259
|
-
reject([e, new Error(`Unable to ${rest.method} ${endpoint} due to previous error`)]);
|
|
260
|
-
}
|
|
307
|
+
reject([e, new Error(`Unable to ${rest.method} ${endpoint} due to previous error`)]);
|
|
261
308
|
}
|
|
262
309
|
});
|
|
263
310
|
} catch (error) {
|
|
@@ -298,6 +345,7 @@ export class Getta {
|
|
|
298
345
|
private async _get(
|
|
299
346
|
path: string,
|
|
300
347
|
{ headers = {}, pathTemplateData, queryParams = {} }: Omit<RequestOptions, "method">,
|
|
348
|
+
context?: PlainObject,
|
|
301
349
|
) {
|
|
302
350
|
const endpoint = buildEndpoint(this._basePath, path, {
|
|
303
351
|
optionalPathTemplateRegExp: this._optionalPathTemplateRegExp,
|
|
@@ -329,7 +377,7 @@ export class Getta {
|
|
|
329
377
|
|
|
330
378
|
return this._getResolve(
|
|
331
379
|
requestHash,
|
|
332
|
-
await this._fetch(endpoint, { headers: { ...this._headers, ...headers }, method: GET_METHOD }),
|
|
380
|
+
await this._fetch(endpoint, { headers: { ...this._headers, ...headers }, method: GET_METHOD }, context),
|
|
333
381
|
);
|
|
334
382
|
}
|
|
335
383
|
|
|
@@ -367,9 +415,34 @@ export class Getta {
|
|
|
367
415
|
return res;
|
|
368
416
|
}
|
|
369
417
|
|
|
418
|
+
private _rateLimit() {
|
|
419
|
+
if (!this._rateLimitTimer) {
|
|
420
|
+
this._rateLimitTimer = setTimeout(() => {
|
|
421
|
+
this._rateLimitTimer = null;
|
|
422
|
+
this._rateLimitCount = 0;
|
|
423
|
+
|
|
424
|
+
if (this._rateLimitedRequestQueue.length) {
|
|
425
|
+
this._releaseRateLimitedRequestQueue();
|
|
426
|
+
}
|
|
427
|
+
}, 1000);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
this._rateLimitCount += 1;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private _releaseRateLimitedRequestQueue() {
|
|
434
|
+
this._rateLimitedRequestQueue.forEach(async ([resolve, endpoint, options]) => {
|
|
435
|
+
// @ts-ignore
|
|
436
|
+
resolve(await this._fetch(endpoint, options));
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
this._rateLimitedRequestQueue = [];
|
|
440
|
+
}
|
|
441
|
+
|
|
370
442
|
private async _request(
|
|
371
443
|
path: string,
|
|
372
444
|
{ body, headers, method, pathTemplateData, queryParams, ...rest }: Required<RequestOptions, "method">,
|
|
445
|
+
context?: PlainObject,
|
|
373
446
|
) {
|
|
374
447
|
const endpoint = buildEndpoint(this._basePath, path, {
|
|
375
448
|
optionalPathTemplateRegExp: this._optionalPathTemplateRegExp,
|
|
@@ -379,12 +452,16 @@ export class Getta {
|
|
|
379
452
|
queryParams: { ...this._queryParams, ...queryParams },
|
|
380
453
|
});
|
|
381
454
|
|
|
382
|
-
return this._fetch(
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
455
|
+
return this._fetch(
|
|
456
|
+
endpoint,
|
|
457
|
+
{
|
|
458
|
+
body,
|
|
459
|
+
headers: { ...this._headers, ...headers },
|
|
460
|
+
method,
|
|
461
|
+
...rest,
|
|
462
|
+
},
|
|
463
|
+
context,
|
|
464
|
+
);
|
|
388
465
|
}
|
|
389
466
|
|
|
390
467
|
private _resolvePendingRequests(requestHash: string, responseData: FetchResponse) {
|
package/src/types.ts
CHANGED
|
@@ -17,12 +17,15 @@ export interface ConstructorOptions {
|
|
|
17
17
|
enableConditionalRequests?: boolean;
|
|
18
18
|
fetchTimeout?: number;
|
|
19
19
|
headers?: StringObject;
|
|
20
|
+
log?: Log;
|
|
20
21
|
maxRedirects?: number;
|
|
21
22
|
maxRetries?: number;
|
|
22
23
|
optionalPathTemplateRegExp?: RegExp;
|
|
23
24
|
pathTemplateCallback?: PathTemplateCallback;
|
|
24
25
|
pathTemplateRegExp?: RegExp;
|
|
26
|
+
performance: Performance;
|
|
25
27
|
queryParams?: PlainObject;
|
|
28
|
+
rateLimitPerSecond?: number;
|
|
26
29
|
requestRetryWait?: number;
|
|
27
30
|
streamReader?: StreamReader;
|
|
28
31
|
}
|
|
@@ -41,6 +44,14 @@ export interface FetchRedirectHandlerOptions extends FetchOptions {
|
|
|
41
44
|
status: number;
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
export type Log = (message: string, data: PlainObject, logLevel?: LogLevel) => void;
|
|
48
|
+
|
|
49
|
+
export type LogLevel = "error" | "warn" | "info" | "http" | "verbose" | "debug" | "silly";
|
|
50
|
+
|
|
51
|
+
export interface Performance {
|
|
52
|
+
now(): number;
|
|
53
|
+
}
|
|
54
|
+
|
|
44
55
|
export interface RequestOptions {
|
|
45
56
|
body?: BodyInit;
|
|
46
57
|
headers?: StringObject;
|
|
@@ -49,6 +60,8 @@ export interface RequestOptions {
|
|
|
49
60
|
queryParams?: PlainObject;
|
|
50
61
|
}
|
|
51
62
|
|
|
63
|
+
export type RequestQueue = [(value: FetchResponse) => void, string, FetchOptions][];
|
|
64
|
+
|
|
52
65
|
export interface ResponseDataWithErrors<Resource = PlainObject> {
|
|
53
66
|
data?: Resource;
|
|
54
67
|
errors?: Error[];
|