@compassdigital/sdk.typescript 3.70.0 → 3.71.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/.eslintignore +1 -0
- package/lib/base.d.ts +35 -36
- package/lib/base.d.ts.map +1 -1
- package/lib/base.js +66 -68
- package/lib/base.js.map +1 -1
- package/lib/interface/config.d.ts +83 -1
- package/lib/interface/config.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/base.ts +410 -425
- package/src/interface/config.ts +88 -1
package/src/base.ts
CHANGED
|
@@ -1,32 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
import fetch from
|
|
1
|
+
|
|
2
|
+
import fetch from "cross-fetch";
|
|
3
3
|
|
|
4
4
|
export interface RequestOptions {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// An http.Agent instance in NodeJS.
|
|
28
|
-
// This is not typed because this package must remain browser compatible.
|
|
29
|
-
agent?: any;
|
|
5
|
+
// environment to make requests to
|
|
6
|
+
stage?: string;
|
|
7
|
+
// authentication token
|
|
8
|
+
token?: string;
|
|
9
|
+
// additional headers
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
// query parameters
|
|
12
|
+
query?: { _provider?: string };
|
|
13
|
+
// log all requests and responses
|
|
14
|
+
debug?: boolean;
|
|
15
|
+
// the number of times to retry a request
|
|
16
|
+
retry?: number;
|
|
17
|
+
// make requests against this base url.
|
|
18
|
+
// per-service base urls are configured as an object
|
|
19
|
+
// eg. { order: 'http://localhost:3000', shoppingcart: 'http://localhost:4000' }
|
|
20
|
+
// note: the stage property is ignored if base_url is set
|
|
21
|
+
base_url?: string | Record<string, string>
|
|
22
|
+
// intercept outgoing http requests
|
|
23
|
+
intercept?: InterceptFn;
|
|
24
|
+
// throw this error value instead of a ServiceError
|
|
25
|
+
// note: the ServiceError will be set as the cause property.
|
|
26
|
+
throws?: any;
|
|
30
27
|
}
|
|
31
28
|
|
|
32
29
|
/**
|
|
@@ -34,12 +31,12 @@ export interface RequestOptions {
|
|
|
34
31
|
* functions.
|
|
35
32
|
*/
|
|
36
33
|
export interface RequestData {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
name: string;
|
|
35
|
+
service: string;
|
|
36
|
+
url: string;
|
|
37
|
+
method: string;
|
|
38
|
+
headers: Record<string, string>;
|
|
39
|
+
body?: string;
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
/**
|
|
@@ -47,10 +44,10 @@ export interface RequestData {
|
|
|
47
44
|
* intercept functions.
|
|
48
45
|
*/
|
|
49
46
|
export interface ResponseData {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
ok: boolean;
|
|
48
|
+
status: number;
|
|
49
|
+
body: string;
|
|
50
|
+
err?: any; // network error
|
|
54
51
|
}
|
|
55
52
|
|
|
56
53
|
/**
|
|
@@ -63,200 +60,196 @@ export type FetchFn = (req: RequestData) => Promise<ResponseData>;
|
|
|
63
60
|
*/
|
|
64
61
|
export type InterceptFn = (req: RequestData, fetch: FetchFn) => Promise<ResponseData>;
|
|
65
62
|
|
|
63
|
+
/**
|
|
64
|
+
* We have to re-implement the promise methods instead of just existing
|
|
65
|
+
* the existing Promise class due to: https://github.com/microsoft/TypeScript/issues/15202
|
|
66
|
+
*/
|
|
67
|
+
export class ResponsePromise<T> implements Promise<T> {
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Implements Promise interface.
|
|
71
|
+
*/
|
|
72
|
+
[Symbol.toStringTag] = "[object ResponsePromise]";
|
|
73
|
+
|
|
74
|
+
constructor(private promise: Promise<T>) {}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Implements Promise interface.
|
|
78
|
+
*/
|
|
79
|
+
then<TResult1 = T, TResult2 = never>(
|
|
80
|
+
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
|
81
|
+
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
|
82
|
+
): ResponsePromise<TResult1 | TResult2> {
|
|
83
|
+
return new ResponsePromise(this.promise.then(onfulfilled, onrejected));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Implements Promise interface.
|
|
88
|
+
*/
|
|
89
|
+
catch<TResult = never>(
|
|
90
|
+
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
|
|
91
|
+
): ResponsePromise<T | TResult> {
|
|
92
|
+
return new ResponsePromise(this.promise.catch(onrejected));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Implements Promise interface.
|
|
97
|
+
*/
|
|
98
|
+
finally(onfinally?: (() => void) | null): ResponsePromise<T> {
|
|
99
|
+
return new ResponsePromise(this.promise.finally(onfinally));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Returns a promise that resolves to null if http status code
|
|
104
|
+
* or service error code matches one of the provided codes.
|
|
105
|
+
* If no codes are specified, all codes are ignored.
|
|
106
|
+
*/
|
|
107
|
+
ignore(...codes: number[]): ResponsePromise<T | null> {
|
|
108
|
+
return new ResponsePromise(ServiceError.ignore(codes, this.promise));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Returns a promise that resolves to either the response body
|
|
113
|
+
* or a ServiceError.
|
|
114
|
+
*/
|
|
115
|
+
combine(...codes: number[]): Promise<EitherResponse<T>> {
|
|
116
|
+
return new ResponsePromise(ServiceError.combine<T>(codes, this.promise));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
66
120
|
/**
|
|
67
121
|
* Either a response body or a service error.
|
|
68
122
|
*/
|
|
69
|
-
export type EitherResponse<T> =
|
|
70
|
-
| { ok: true; data: T; err: null }
|
|
71
|
-
| { ok: false; data: null; err: ServiceError };
|
|
123
|
+
export type EitherResponse<T> = { ok: true; data: T; err: null } | { ok: false; data: null, err: ServiceError; };
|
|
72
124
|
|
|
73
125
|
/**
|
|
74
126
|
* This is a hack to make captureStackTrace visible.
|
|
75
127
|
*/
|
|
76
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
77
128
|
interface ErrorConstructor {
|
|
78
|
-
|
|
129
|
+
captureStackTrace?(target: object, constructorOpt?: Function): void;
|
|
79
130
|
}
|
|
80
131
|
|
|
81
132
|
/**
|
|
82
133
|
* An error returned from an api service.
|
|
83
134
|
*/
|
|
84
135
|
export class ServiceError extends Error {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return err;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* We have to re-implement the promise methods instead of just existing
|
|
208
|
-
* the existing Promise class due to: https://github.com/microsoft/TypeScript/issues/15202
|
|
209
|
-
*/
|
|
210
|
-
export class ResponsePromise<T> implements Promise<T> {
|
|
211
|
-
/**
|
|
212
|
-
* Implements Promise interface.
|
|
213
|
-
*/
|
|
214
|
-
[Symbol.toStringTag] = '[object ResponsePromise]';
|
|
215
|
-
|
|
216
|
-
constructor(private promise: Promise<T>) {}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Implements Promise interface.
|
|
220
|
-
*/
|
|
221
|
-
then<TResult1 = T, TResult2 = never>(
|
|
222
|
-
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
|
223
|
-
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
|
|
224
|
-
): ResponsePromise<TResult1 | TResult2> {
|
|
225
|
-
return new ResponsePromise(this.promise.then(onfulfilled, onrejected));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Implements Promise interface.
|
|
230
|
-
*/
|
|
231
|
-
catch<TResult = never>(
|
|
232
|
-
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null,
|
|
233
|
-
): ResponsePromise<T | TResult> {
|
|
234
|
-
return new ResponsePromise(this.promise.catch(onrejected));
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Implements Promise interface.
|
|
239
|
-
*/
|
|
240
|
-
finally(onfinally?: (() => void) | null): ResponsePromise<T> {
|
|
241
|
-
return new ResponsePromise(this.promise.finally(onfinally));
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Returns a promise that resolves to null if http status code
|
|
246
|
-
* or service error code matches one of the provided codes.
|
|
247
|
-
* If no codes are specified, all codes are ignored.
|
|
248
|
-
*/
|
|
249
|
-
ignore(...codes: number[]): ResponsePromise<T | null> {
|
|
250
|
-
return new ResponsePromise(ServiceError.ignore(codes, this.promise));
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Returns a promise that resolves to either the response body
|
|
255
|
-
* or a ServiceError.
|
|
256
|
-
*/
|
|
257
|
-
combine(...codes: number[]): Promise<EitherResponse<T>> {
|
|
258
|
-
return new ResponsePromise(ServiceError.combine<T>(codes, this.promise));
|
|
259
|
-
}
|
|
136
|
+
constructor(
|
|
137
|
+
public status: number, // http status
|
|
138
|
+
public code: number, // service error code
|
|
139
|
+
message: string // service error message
|
|
140
|
+
) {
|
|
141
|
+
super(message);
|
|
142
|
+
this.name = `ServiceError(${code})`;
|
|
143
|
+
|
|
144
|
+
// TODO: remove this if we change compilation target > es5
|
|
145
|
+
Object.setPrototypeOf(this, ServiceError.prototype);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Returns the http status code from err.
|
|
150
|
+
* If err isn't an instance of ServiceError, -1 is returned.
|
|
151
|
+
*/
|
|
152
|
+
static status(err: any): number {
|
|
153
|
+
if (err instanceof ServiceError) {
|
|
154
|
+
return err.status;
|
|
155
|
+
}
|
|
156
|
+
return -1;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Returns the service error code from err.
|
|
161
|
+
* If err isn't an instance of ServiceError, -1 is returned.
|
|
162
|
+
*/
|
|
163
|
+
static code(err: any): number {
|
|
164
|
+
if (err instanceof ServiceError) {
|
|
165
|
+
return err.code;
|
|
166
|
+
}
|
|
167
|
+
return -1;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Returns a promise that resolves to null if http status code
|
|
172
|
+
* or service error code matches one of the provided codes.
|
|
173
|
+
* If no codes are specified, all codes are ignored.
|
|
174
|
+
*/
|
|
175
|
+
static async ignore<T>(codes: number[], response: Promise<T>): Promise<T | null> {
|
|
176
|
+
try {
|
|
177
|
+
return await response;
|
|
178
|
+
} catch (err) {
|
|
179
|
+
if (ServiceError.unwrap(err, codes)) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Returns a promise that resolves to either T or a ServiceError.
|
|
188
|
+
*/
|
|
189
|
+
static async combine<T>(codes: number[], response: Promise<T>): Promise<EitherResponse<T>> {
|
|
190
|
+
try {
|
|
191
|
+
const data = await response;
|
|
192
|
+
return { ok: true, data, err: null };
|
|
193
|
+
} catch (err) {
|
|
194
|
+
const match = ServiceError.unwrap(err, codes);
|
|
195
|
+
if (match) {
|
|
196
|
+
return { ok: false, data: null, err: match };
|
|
197
|
+
}
|
|
198
|
+
throw err;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Returns the first ServiceError in the cause chain matches the provided codes.
|
|
204
|
+
* If no codes were provided, the first ServiceError is returned.
|
|
205
|
+
*/
|
|
206
|
+
private static unwrap(err: any, codes: number[]): ServiceError|null {
|
|
207
|
+
while (err) {
|
|
208
|
+
if (err instanceof ServiceError) {
|
|
209
|
+
if (codes.length === 0) {
|
|
210
|
+
return err
|
|
211
|
+
}
|
|
212
|
+
for (const code of codes) {
|
|
213
|
+
if (err.status === code || err.code === code) {
|
|
214
|
+
return err
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
err = err?.cause;
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Constructs a ServiceError from the provided response data.
|
|
225
|
+
*/
|
|
226
|
+
static from_res(res: ResponseData): ServiceError {
|
|
227
|
+
let message = res.body;
|
|
228
|
+
let code = res.status;
|
|
229
|
+
if (res.err) {
|
|
230
|
+
// network error
|
|
231
|
+
if (res.err instanceof Error) {
|
|
232
|
+
message = res.err.message;
|
|
233
|
+
} else {
|
|
234
|
+
message = "Network Error";
|
|
235
|
+
}
|
|
236
|
+
code = 0;
|
|
237
|
+
} else {
|
|
238
|
+
try {
|
|
239
|
+
let data = JSON.parse(res.body);
|
|
240
|
+
if (data.code && (data.message || data.error)) {
|
|
241
|
+
code = data.code;
|
|
242
|
+
message = data.message || data.error;
|
|
243
|
+
}
|
|
244
|
+
} catch (_) {}
|
|
245
|
+
}
|
|
246
|
+
const err = new ServiceError(res.status, code, message);
|
|
247
|
+
// override the stack trace so that it doesn't include the from_res helper.
|
|
248
|
+
if (Error.captureStackTrace) {
|
|
249
|
+
Error.captureStackTrace(err, ServiceError.from_res);
|
|
250
|
+
}
|
|
251
|
+
return err;
|
|
252
|
+
}
|
|
260
253
|
}
|
|
261
254
|
|
|
262
255
|
/**
|
|
@@ -264,212 +257,204 @@ export class ResponsePromise<T> implements Promise<T> {
|
|
|
264
257
|
* This class is meant to be extended by the generated code.
|
|
265
258
|
*/
|
|
266
259
|
export abstract class BaseServiceClient {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
throw err;
|
|
468
|
-
}
|
|
469
|
-
const data = res.body ? JSON.parse(res.body) : res.body;
|
|
470
|
-
if (options.debug) {
|
|
471
|
-
console.log(`RES(${res.status})`, url, JSON.stringify(data, null, 2));
|
|
472
|
-
}
|
|
473
|
-
return data;
|
|
474
|
-
}
|
|
260
|
+
private options: RequestOptions;
|
|
261
|
+
|
|
262
|
+
constructor(options?: RequestOptions) {
|
|
263
|
+
this.options = {
|
|
264
|
+
...this.env_options(),
|
|
265
|
+
...options,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Parse request options from environment variables.
|
|
271
|
+
*/
|
|
272
|
+
private env_options(): RequestOptions {
|
|
273
|
+
const options: RequestOptions = {};
|
|
274
|
+
const stage = process.env.P2_SDK_STAGE;
|
|
275
|
+
if (stage) {
|
|
276
|
+
options.stage = stage;
|
|
277
|
+
}
|
|
278
|
+
const token = process.env.P2_SDK_TOKEN;
|
|
279
|
+
if (token) {
|
|
280
|
+
options.token = token;
|
|
281
|
+
}
|
|
282
|
+
const debug = process.env.P2_SDK_DEBUG;
|
|
283
|
+
if (debug && debug !== "0" && `${debug}`.toLowerCase() !== "false") {
|
|
284
|
+
options.debug = true;
|
|
285
|
+
}
|
|
286
|
+
const base_url = process.env.P2_SDK_BASE_URL;
|
|
287
|
+
if (base_url) {
|
|
288
|
+
if (base_url.includes('=')) {
|
|
289
|
+
options.base_url = {};
|
|
290
|
+
for (const custom_service_url of base_url.split(',')) {
|
|
291
|
+
const [service, custom_url] = custom_service_url.split('=');
|
|
292
|
+
options.base_url[service] = custom_url
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
options.base_url = base_url;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return options;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Combine the route and query parameters into a full url.
|
|
303
|
+
*/
|
|
304
|
+
private build_url(route: string, service: string, { base_url, stage = "dev" }: RequestOptions, params: any): string {
|
|
305
|
+
let url = `https://api.compassdigital.org/${stage}${route}`;
|
|
306
|
+
// use the base url if one was provided.
|
|
307
|
+
if (base_url) {
|
|
308
|
+
if (typeof base_url === 'object' && base_url[service]) base_url = base_url[service];
|
|
309
|
+
if (typeof base_url === 'string') url = `${this.clean_url(base_url)}${route}`;
|
|
310
|
+
}
|
|
311
|
+
// add query parameters.
|
|
312
|
+
const query: string[] = [];
|
|
313
|
+
for (const [name, values] of Object.entries(params)) {
|
|
314
|
+
for (const value of Array.isArray(values) ? values : [values]) {
|
|
315
|
+
query.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (query.length > 0) {
|
|
319
|
+
url += "?" + query.join("&");
|
|
320
|
+
}
|
|
321
|
+
return url;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private clean_url(url: string): string {
|
|
325
|
+
if (url.endsWith("/")) {
|
|
326
|
+
url = url.slice(0, -1);
|
|
327
|
+
}
|
|
328
|
+
return url;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Perform the http request or pass it to the intercept function
|
|
333
|
+
* if one was configured.
|
|
334
|
+
*/
|
|
335
|
+
private async fetch(req: RequestData, options?: RequestOptions): Promise<ResponseData> {
|
|
336
|
+
if (options?.intercept) {
|
|
337
|
+
return options.intercept(req, this.fetch.bind(this));
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
const res = await fetch(req.url, {
|
|
341
|
+
method: req.method,
|
|
342
|
+
body: req.body,
|
|
343
|
+
headers: req.headers,
|
|
344
|
+
});
|
|
345
|
+
return {
|
|
346
|
+
ok: res.ok,
|
|
347
|
+
status: res.status,
|
|
348
|
+
body: await res.text(),
|
|
349
|
+
};
|
|
350
|
+
} catch (err) {
|
|
351
|
+
return {
|
|
352
|
+
ok: false,
|
|
353
|
+
status: 0,
|
|
354
|
+
body: "",
|
|
355
|
+
err: err,
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Returns true if the status code represents a transient error
|
|
362
|
+
* or it's a network error.
|
|
363
|
+
*/
|
|
364
|
+
private should_retry(res: ResponseData): boolean {
|
|
365
|
+
if (res.err) {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
switch (res.status) {
|
|
369
|
+
case 408: // timeout
|
|
370
|
+
case 413: // too large
|
|
371
|
+
case 429: // too many requests
|
|
372
|
+
case 500: // generic error response
|
|
373
|
+
case 502: // invalid upstream response
|
|
374
|
+
case 503: // service unavailable
|
|
375
|
+
case 504: // gateway timeout
|
|
376
|
+
case 522: // tcp connection timeout
|
|
377
|
+
return true;
|
|
378
|
+
default:
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Returns the merged options.
|
|
385
|
+
*/
|
|
386
|
+
private get_options(options: RequestOptions): RequestOptions {
|
|
387
|
+
return {
|
|
388
|
+
...this.options,
|
|
389
|
+
...options,
|
|
390
|
+
headers: {
|
|
391
|
+
...this.options.headers,
|
|
392
|
+
...options.headers,
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* A delegate to _request that wraps the response in a ResponsePromise.
|
|
399
|
+
*/
|
|
400
|
+
protected request(
|
|
401
|
+
service: string,
|
|
402
|
+
name: string,
|
|
403
|
+
method: string,
|
|
404
|
+
route: string,
|
|
405
|
+
body: any,
|
|
406
|
+
options: { query?: any } & RequestOptions = {}
|
|
407
|
+
): ResponsePromise<any> {
|
|
408
|
+
const promise = this._request(service, name, method, route, body, options);
|
|
409
|
+
return new ResponsePromise(promise);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* The main logic for fulfilling a request as perscribed by
|
|
414
|
+
* the request options.
|
|
415
|
+
*/
|
|
416
|
+
private async _request(
|
|
417
|
+
service: string,
|
|
418
|
+
name: string,
|
|
419
|
+
method: string,
|
|
420
|
+
route: string,
|
|
421
|
+
body: any,
|
|
422
|
+
options: { query?: any } & RequestOptions = {}
|
|
423
|
+
): Promise<any> {
|
|
424
|
+
options = this.get_options(options);
|
|
425
|
+
const url = this.build_url(route, service, options, options.query ?? {});
|
|
426
|
+
const headers = Object.assign({
|
|
427
|
+
"User-Agent": "CDL/ServiceClient"
|
|
428
|
+
}, options.headers);
|
|
429
|
+
if (options.token) {
|
|
430
|
+
headers["Authorization"] = `Bearer ${options.token}`;
|
|
431
|
+
}
|
|
432
|
+
const req: RequestData = { name, service, url, method, headers };
|
|
433
|
+
if (body) {
|
|
434
|
+
req.body = JSON.stringify(body);
|
|
435
|
+
}
|
|
436
|
+
if (options.debug) {
|
|
437
|
+
console.log(`REQ(${method.toUpperCase()})`, url, JSON.stringify(req, null, 2));
|
|
438
|
+
}
|
|
439
|
+
const res = await this.fetch(req, options);
|
|
440
|
+
if (!res.ok) {
|
|
441
|
+
const err = ServiceError.from_res(res);
|
|
442
|
+
if (options.debug) {
|
|
443
|
+
console.log(`ERR(${res.status})`, url, err.message);
|
|
444
|
+
}
|
|
445
|
+
if (typeof options.retry === "number" && options.retry > 0 && this.should_retry(res)) {
|
|
446
|
+
options.retry--;
|
|
447
|
+
return this.request(service, name, method, route, body, options);
|
|
448
|
+
}
|
|
449
|
+
if (options.throws) {
|
|
450
|
+
throw Object.assign(options.throws, { cause: err });
|
|
451
|
+
}
|
|
452
|
+
throw err;
|
|
453
|
+
}
|
|
454
|
+
const data = res.body ? JSON.parse(res.body) : res.body;
|
|
455
|
+
if (options.debug) {
|
|
456
|
+
console.log(`RES(${res.status})`, url, JSON.stringify(data, null, 2));
|
|
457
|
+
}
|
|
458
|
+
return data;
|
|
459
|
+
}
|
|
475
460
|
}
|