@compassdigital/sdk.typescript 3.0.0-beta.10 → 3.0.0-beta.12

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/src/base.ts CHANGED
@@ -19,6 +19,10 @@ export interface RequestOptions {
19
19
  intercept?: InterceptFn;
20
20
  }
21
21
 
22
+ /**
23
+ * The request data provided to intercept
24
+ * functions.
25
+ */
22
26
  export interface RequestData {
23
27
  name: string;
24
28
  service: string;
@@ -28,23 +32,91 @@ export interface RequestData {
28
32
  body?: string;
29
33
  }
30
34
 
35
+ /**
36
+ * The response data expected from
37
+ * intercept functions.
38
+ */
31
39
  export interface ResponseData {
32
40
  ok: boolean;
33
41
  status: number;
34
42
  body: string;
35
43
  }
36
44
 
45
+ /**
46
+ * A function that performs an https request.
47
+ */
37
48
  export type FetchFn = (req: RequestData) => Promise<ResponseData>;
38
49
 
50
+ /**
51
+ * A function that intercepts an http request and returns a response.
52
+ */
39
53
  export type InterceptFn = (req: RequestData, fetch: FetchFn) => Promise<ResponseData>;
40
54
 
41
- export type ServiceRequestOptions = Omit<RequestOptions, "allow">;
55
+ /**
56
+ * We have to re-implement the promise methods instead of just existing
57
+ * the existing Promise class due to: https://github.com/microsoft/TypeScript/issues/15202
58
+ */
59
+ export class ResponsePromise<T> implements Promise<T> {
42
60
 
43
- export interface ServiceClientOptions extends ServiceRequestOptions {
44
- services?: Record<string, ServiceRequestOptions>;
45
- methods?: Record<string, ServiceRequestOptions>;
61
+ /**
62
+ * Implements Promise interface.
63
+ */
64
+ [Symbol.toStringTag] = "[object ResponsePromise]";
65
+
66
+ constructor(private promise: Promise<T>) {}
67
+
68
+ /**
69
+ * Implements Promise interface.
70
+ */
71
+ then<TResult1 = T, TResult2 = never>(
72
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
73
+ onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
74
+ ): ResponsePromise<TResult1 | TResult2> {
75
+ return new ResponsePromise(this.promise.then(onfulfilled, onrejected));
76
+ }
77
+
78
+ /**
79
+ * Implements Promise interface.
80
+ */
81
+ catch<TResult = never>(
82
+ onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
83
+ ): ResponsePromise<T | TResult> {
84
+ return new ResponsePromise(this.promise.catch(onrejected));
85
+ }
86
+
87
+ /**
88
+ * Implements Promise interface.
89
+ */
90
+ finally(onfinally?: (() => void) | null): ResponsePromise<T> {
91
+ return new ResponsePromise(this.promise.finally(onfinally));
92
+ }
93
+
94
+ /**
95
+ * Returns a promise that resolves to null if http status code
96
+ * or service error code matches one of the provided codes.
97
+ * If no codes are specified, all codes are ignored.
98
+ */
99
+ ignore(...codes: number[]): ResponsePromise<T | null> {
100
+ return new ResponsePromise(ServiceError.ignore(codes, this.promise));
101
+ }
102
+
103
+ /**
104
+ * Returns a promise that resolves to either the response body
105
+ * or a ServiceError.
106
+ */
107
+ combine(): Promise<EitherResponse<T>> {
108
+ return new ResponsePromise(ServiceError.combine<T>(this.promise));
109
+ }
46
110
  }
47
111
 
112
+ /**
113
+ * Either a response body or a service error.
114
+ */
115
+ type EitherResponse<T> = { ok: true; body: T; err?: ServiceError } | { ok: false; body?: T, err: ServiceError; };
116
+
117
+ /**
118
+ * An error returned from an api service.
119
+ */
48
120
  export class ServiceError extends Error {
49
121
  constructor(
50
122
  public status: number, // http status
@@ -58,6 +130,10 @@ export class ServiceError extends Error {
58
130
  Object.setPrototypeOf(this, ServiceError.prototype);
59
131
  }
60
132
 
133
+ /**
134
+ * Returns the http status code from err.
135
+ * If err isn't an instance of ServiceError, -1 is returned.
136
+ */
61
137
  static status(err: any): number {
62
138
  if (err instanceof ServiceError) {
63
139
  return err.status;
@@ -65,6 +141,10 @@ export class ServiceError extends Error {
65
141
  return -1;
66
142
  }
67
143
 
144
+ /**
145
+ * Returns the service error code from err.
146
+ * If err isn't an instance of ServiceError, -1 is returned.
147
+ */
68
148
  static code(err: any): number {
69
149
  if (err instanceof ServiceError) {
70
150
  return err.code;
@@ -72,11 +152,19 @@ export class ServiceError extends Error {
72
152
  return -1;
73
153
  }
74
154
 
155
+ /**
156
+ * Returns a promise that resolves to null if http status code
157
+ * or service error code matches one of the provided codes.
158
+ * If no codes are specified, all codes are ignored.
159
+ */
75
160
  static async ignore<T>(codes: number[], response: Promise<T>): Promise<T | null> {
76
161
  try {
77
162
  return await response;
78
163
  } catch (err) {
79
164
  if (err instanceof ServiceError) {
165
+ if (codes.length === 0) {
166
+ return null;
167
+ }
80
168
  for (const code of codes) {
81
169
  if (err.status === code || err.code === code) {
82
170
  return null;
@@ -87,6 +175,24 @@ export class ServiceError extends Error {
87
175
  }
88
176
  }
89
177
 
178
+ /**
179
+ * Returns a promise that resolves to either T or a ServiceError.
180
+ */
181
+ static async combine<T>(response: Promise<T>): Promise<EitherResponse<T>> {
182
+ try {
183
+ const body = await response;
184
+ return { ok: true, body };
185
+ } catch (err) {
186
+ if (err instanceof ServiceError) {
187
+ return { ok: false, err }
188
+ }
189
+ throw err;
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Constructs a ServiceError from the provided response data.
195
+ */
90
196
  static from_res(res: ResponseData): ServiceError {
91
197
  let message = res.body;
92
198
  let code = res.status;
@@ -101,10 +207,14 @@ export class ServiceError extends Error {
101
207
  }
102
208
  }
103
209
 
210
+ /**
211
+ * BaseServiceClient contains the logic for executing http requests.
212
+ * This class is meant to be extended by the generated code.
213
+ */
104
214
  export abstract class BaseServiceClient {
105
- private options: ServiceClientOptions;
215
+ private options: RequestOptions;
106
216
 
107
- constructor(options?: ServiceClientOptions) {
217
+ constructor(options?: RequestOptions) {
108
218
  this.options = options ?? {};
109
219
  }
110
220
 
@@ -131,6 +241,10 @@ export abstract class BaseServiceClient {
131
241
  return url;
132
242
  }
133
243
 
244
+ /**
245
+ * Perform the http request or pass it to the intercept function
246
+ * if one was configured.
247
+ */
134
248
  private async fetch(req: RequestData, options?: RequestOptions): Promise<ResponseData> {
135
249
  if (options?.intercept) {
136
250
  return options.intercept(req, this.fetch.bind(this));
@@ -147,28 +261,47 @@ export abstract class BaseServiceClient {
147
261
  };
148
262
  }
149
263
 
264
+ /**
265
+ * Returns true if the response status was a 4xx error.
266
+ */
150
267
  private is_4xx(res: ResponseData): boolean {
151
268
  return 400 <= res.status && res.status < 500;
152
269
  }
153
270
 
154
- private get_options(service: string, name: string, options: RequestOptions): RequestOptions {
155
- const service_options = this.options.services?.[service];
156
- const method_options = this.options.methods?.[name];
271
+ /**
272
+ * Returns the merged options.
273
+ */
274
+ private get_options(options: RequestOptions): RequestOptions {
157
275
  return {
158
276
  ...this.options,
159
- ...service_options,
160
- ...method_options,
161
277
  ...options,
162
278
  headers: {
163
279
  ...this.options.headers,
164
- ...method_options?.headers,
165
- ...service_options?.headers,
166
280
  ...options.headers,
167
281
  }
168
282
  };
169
283
  }
170
284
 
171
- protected async request(
285
+ /**
286
+ * A delegate to _request that wraps the response in a ResponsePromise.
287
+ */
288
+ protected request(
289
+ service: string,
290
+ name: string,
291
+ method: string,
292
+ route: string,
293
+ body: any,
294
+ options: { query?: any } & RequestOptions = {}
295
+ ): ResponsePromise<any> {
296
+ const promise = this._request(service, name, method, route, body, options);
297
+ return new ResponsePromise(promise);
298
+ }
299
+
300
+ /**
301
+ * The main logic for fulfilling a request as perscribed by
302
+ * the request options.
303
+ */
304
+ private async _request(
172
305
  service: string,
173
306
  name: string,
174
307
  method: string,
@@ -176,7 +309,7 @@ export abstract class BaseServiceClient {
176
309
  body: any,
177
310
  options: { query?: any } & RequestOptions = {}
178
311
  ): Promise<any> {
179
- options = this.get_options(service, name, options);
312
+ options = this.get_options(options);
180
313
  const url = this.build_url(route, options, options.query ?? {});
181
314
  const headers = Object.assign({
182
315
  "User-Agent": "CDL/ServiceClient"