api-def 0.6.0-alpha8 → 0.6.1

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/cjs/Api.d.ts CHANGED
@@ -28,4 +28,5 @@ export declare class Api implements ApiInfo {
28
28
  post: <R = unknown>(path: string, config: RequestConfig) => Promise<ApiResponse<R>>;
29
29
  put: <R = unknown>(path: string, config: RequestConfig) => Promise<ApiResponse<R>>;
30
30
  delete: <R = unknown>(path: string, config: RequestConfig) => Promise<ApiResponse<R>>;
31
+ patch: <R = unknown>(path: string, config: RequestConfig) => Promise<ApiResponse<R>>;
31
32
  }
package/cjs/Api.js CHANGED
@@ -98,6 +98,7 @@ var Api = /** @class */ (function () {
98
98
  this.post = this.hotRequest(ApiConstants_1.RequestMethod.POST);
99
99
  this.put = this.hotRequest(ApiConstants_1.RequestMethod.PUT);
100
100
  this.delete = this.hotRequest(ApiConstants_1.RequestMethod.DELETE);
101
+ this.patch = this.hotRequest(ApiConstants_1.RequestMethod.PATCH);
101
102
  this.name = info.name;
102
103
  this.baseUrl = info.baseUrl;
103
104
  this.middleware = info.middleware || [];
@@ -8,6 +8,7 @@ export declare const RequestMethod: {
8
8
  readonly GET: "get";
9
9
  readonly PUT: "put";
10
10
  readonly DELETE: "delete";
11
+ readonly PATCH: "patch";
11
12
  };
12
13
  export declare type RequestMethod = EnumOf<typeof RequestMethod>;
13
14
  export declare const RequestEvent: {
@@ -10,6 +10,7 @@ exports.RequestMethod = {
10
10
  GET: "get",
11
11
  PUT: "put",
12
12
  DELETE: "delete",
13
+ PATCH: "patch",
13
14
  };
14
15
  exports.RequestEvent = {
15
16
  /** @deprecated use 'BEFORE_SEND' */
package/cjs/ApiTypes.d.ts CHANGED
@@ -4,7 +4,7 @@ import { CacheSource, EventResultType, RequestEvent, RequestMethod, ResponseType
4
4
  export declare type AcceptableStatus = number | [min: number, max: number];
5
5
  export declare type Headers = Record<string, string | number | boolean | null | undefined>;
6
6
  export declare type Params = string;
7
- export declare type Query = Record<string, string | number | boolean | undefined | null>;
7
+ export declare type Query = Record<string, any>;
8
8
  export declare type Body = string | number | Record<string, any>;
9
9
  export interface ApiResponse<T = any> {
10
10
  status: number;
@@ -17,6 +17,7 @@ export interface BaseRequestConfig {
17
17
  retry?: number | false;
18
18
  headers?: Readonly<Headers>;
19
19
  acceptableStatus?: AcceptableStatus[];
20
+ queryParser?: (query: any) => string;
20
21
  }
21
22
  export declare type RequestConfig<P extends Params | undefined = Params | undefined, Q extends Query | undefined = Query | undefined, B extends Body | undefined = Body | undefined> = (P extends undefined ? {
22
23
  params?: never;
package/cjs/ApiUtils.d.ts CHANGED
@@ -1,7 +1,6 @@
1
- import { AcceptableStatus, ApiResponse, CancelledRequestError } from "./ApiTypes";
1
+ import { AcceptableStatus, CancelledRequestError } from "./ApiTypes";
2
2
  import { ResponseType } from "./ApiConstants";
3
3
  export declare const isCancelledError: (error: Error) => error is CancelledRequestError;
4
4
  export declare const isNetworkError: (error: Error) => boolean;
5
- export declare const parseResponseDataToObject: (response: ApiResponse) => void;
6
5
  export declare const isAcceptableStatus: (status: number, acceptableStatus?: AcceptableStatus[] | undefined) => boolean;
7
6
  export declare const inferResponseType: (contentType: string | null | undefined) => ResponseType;
package/cjs/ApiUtils.js CHANGED
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.inferResponseType = exports.isAcceptableStatus = exports.parseResponseDataToObject = exports.isNetworkError = exports.isCancelledError = void 0;
4
- var TextDecoding_1 = require("./TextDecoding");
3
+ exports.inferResponseType = exports.isAcceptableStatus = exports.isNetworkError = exports.isCancelledError = void 0;
5
4
  var isCancelledError = function (error) {
6
5
  return "isCancelledRequest" in error;
7
6
  };
@@ -13,22 +12,6 @@ var isNetworkError = function (error) {
13
12
  ((_a = error.constructor) === null || _a === void 0 ? void 0 : _a.name) === "NetworkError");
14
13
  };
15
14
  exports.isNetworkError = isNetworkError;
16
- var parseResponseDataToObject = function (response) {
17
- if (response.data &&
18
- typeof response.data === "object") {
19
- var data = response.data;
20
- if (data.constructor && data.constructor.name === "ArrayBuffer") {
21
- try {
22
- var decodedData = (response.data = TextDecoding_1.textDecode(data));
23
- response.data = JSON.parse(decodedData);
24
- }
25
- catch (e) {
26
- console.warn("Couldn't parse array buffer content to JSON response", e);
27
- }
28
- }
29
- }
30
- };
31
- exports.parseResponseDataToObject = parseResponseDataToObject;
32
15
  var DEFAULT_ACCEPTABLE_STATUS = [[200, 299], 304];
33
16
  var isAcceptableStatus = function (status, acceptableStatus) {
34
17
  var acceptable = acceptableStatus !== null && acceptableStatus !== void 0 ? acceptableStatus : DEFAULT_ACCEPTABLE_STATUS;
@@ -49,11 +32,21 @@ var isAcceptableStatus = function (status, acceptableStatus) {
49
32
  return (false);
50
33
  };
51
34
  exports.isAcceptableStatus = isAcceptableStatus;
35
+ var TEXT_CONTENT_TYPES = ["text/plain"];
52
36
  var JSON_CONTENT_TYPES = ["text/json", "application/json"];
37
+ var ARRRAY_BUFFER_CONTENT_TYPES = ["application/octet-stream"];
53
38
  var inferResponseType = function (contentType) {
54
39
  var contentTypePart = contentType === null || contentType === void 0 ? void 0 : contentType.split(";")[0].trim();
55
- if (contentTypePart && JSON_CONTENT_TYPES.includes(contentTypePart)) {
56
- return "json";
40
+ if (contentTypePart) {
41
+ if (TEXT_CONTENT_TYPES.includes(contentTypePart)) {
42
+ return "text";
43
+ }
44
+ else if (JSON_CONTENT_TYPES.includes(contentTypePart)) {
45
+ return "json";
46
+ }
47
+ else if (ARRRAY_BUFFER_CONTENT_TYPES.includes(contentTypePart)) {
48
+ return "arraybuffer";
49
+ }
57
50
  }
58
51
  return "text";
59
52
  };
@@ -15,10 +15,13 @@ exports.isRequestError = isRequestError;
15
15
  var convertToRequestError = function (config) {
16
16
  var error = config.error, response = config.response, code = config.code;
17
17
  return Object.assign(error, {
18
- name: error.name === "Error" ? "RequestError" : error.name,
18
+ name: "RequestError",
19
19
  response: response,
20
20
  code: code,
21
21
  isRequestError: true,
22
+ config: undefined,
23
+ request: undefined,
24
+ toJSON: undefined,
22
25
  });
23
26
  };
24
27
  exports.convertToRequestError = convertToRequestError;
package/cjs/Requester.js CHANGED
@@ -56,6 +56,7 @@ var ApiConstants_1 = require("./ApiConstants");
56
56
  var retry_1 = require("./util/retry");
57
57
  var MockRequestBackend_1 = require("./backend/MockRequestBackend");
58
58
  var RequestError_1 = require("./RequestError");
59
+ var TextDecoding_1 = require("./TextDecoding");
59
60
  var locks = {};
60
61
  var runningOperations = {};
61
62
  var MOCK_REQUEST_BACKEND = new MockRequestBackend_1.default();
@@ -118,6 +119,7 @@ var makeRequest = function (context) { return __awaiter(void 0, void 0, void 0,
118
119
  if (process.env.NODE_ENV === "development") {
119
120
  if (Api.isRequestBackendDefault() && !defaultBackendMessageShown) {
120
121
  defaultBackendMessageShown = true;
122
+ // eslint-disable-next-line
121
123
  console.warn("[api-def] Using default fetch backend, you can use a different one with 'setRequestBackend()' (dev only message)");
122
124
  }
123
125
  }
@@ -173,10 +175,6 @@ var makeRequest = function (context) { return __awaiter(void 0, void 0, void 0,
173
175
  error = _b.sent();
174
176
  context.error = error;
175
177
  context.response = error.response;
176
- // transform array buffer responses to objs
177
- if (context.response) {
178
- ApiUtils.parseResponseDataToObject(context.response);
179
- }
180
178
  return [4 /*yield*/, context.triggerEvent(ApiConstants_1.RequestEvent.Error)];
181
179
  case 6:
182
180
  errorEventResult = _b.sent();
@@ -213,18 +211,53 @@ var makeRequest = function (context) { return __awaiter(void 0, void 0, void 0,
213
211
  });
214
212
  }); };
215
213
  var parseResponse = function (context, response, error) { return __awaiter(void 0, void 0, void 0, function () {
216
- var parsedResponse;
217
- return __generator(this, function (_a) {
218
- switch (_a.label) {
214
+ var parsedResponse_1, contentType, inferredResponseType, data, decodedData;
215
+ var _a;
216
+ return __generator(this, function (_b) {
217
+ switch (_b.label) {
219
218
  case 0:
220
219
  if (!response) return [3 /*break*/, 2];
221
- return [4 /*yield*/, context.backend.convertResponse(context, response, error)];
220
+ return [4 /*yield*/, context.backend.convertResponse(context, response)];
222
221
  case 1:
223
- parsedResponse = _a.sent();
224
- if (parsedResponse) {
225
- ApiUtils.parseResponseDataToObject(parsedResponse);
222
+ parsedResponse_1 = _b.sent();
223
+ // lowercase all header names
224
+ parsedResponse_1.headers = parsedResponse_1.__lowercaseHeaders || Object.keys(parsedResponse_1.headers).reduce(function (headers, header) {
225
+ headers[header.toLowerCase()] = parsedResponse_1.headers[header];
226
+ return headers;
227
+ }, {});
228
+ contentType = parsedResponse_1.headers["content-type"];
229
+ inferredResponseType = ApiUtils_1.inferResponseType(contentType);
230
+ if (!error) {
231
+ // expand to array buffer once we support that in inferResponseType
232
+ if (inferredResponseType === "text" && context.responseType === "json") {
233
+ throw RequestError_1.convertToRequestError({
234
+ error: new Error("[api-def] Expected '" + context.responseType + "' response, got '" + inferredResponseType + "' (from 'Content-Type' of '" + contentType + "')"),
235
+ code: RequestError_1.RequestErrorCode.REQUEST_MISMATCH_RESPONSE_TYPE,
236
+ response: parsedResponse_1,
237
+ });
238
+ }
239
+ // transform arrayBuffer to json
240
+ if (inferredResponseType === "arraybuffer" && context.responseType === "json") {
241
+ if (parsedResponse_1.data &&
242
+ typeof parsedResponse_1.data === "object") {
243
+ data = response.data;
244
+ if (((_a = data.constructor) === null || _a === void 0 ? void 0 : _a.name) === "ArrayBuffer") {
245
+ try {
246
+ decodedData = (response.data = TextDecoding_1.textDecode(data));
247
+ response.data = JSON.parse(decodedData);
248
+ }
249
+ catch (e) {
250
+ throw RequestError_1.convertToRequestError({
251
+ error: new Error("[api-def] Expected '" + context.responseType + "' response, got '" + inferredResponseType + "' (from 'Content-Type' of '" + contentType + "')"),
252
+ code: RequestError_1.RequestErrorCode.REQUEST_MISMATCH_RESPONSE_TYPE,
253
+ response: parsedResponse_1,
254
+ });
255
+ }
256
+ }
257
+ }
258
+ }
226
259
  }
227
- return [2 /*return*/, parsedResponse];
260
+ return [2 /*return*/, parsedResponse_1];
228
261
  case 2: return [2 /*return*/, response];
229
262
  }
230
263
  });
@@ -1,4 +1,4 @@
1
- import RequestBackend, { RequestBackendErrorInfo, RequestOperation } from "./RequestBackend";
1
+ import RequestBackend, { ConvertedApiResponse, RequestBackendErrorInfo, RequestOperation } from "./RequestBackend";
2
2
  import { ApiResponse } from "../ApiTypes";
3
3
  import type { AxiosError, AxiosResponse } from "axios";
4
4
  import RequestContext from "../RequestContext";
@@ -7,7 +7,7 @@ export default class AxiosRequestBackend implements RequestBackend<AxiosResponse
7
7
  readonly id = "axios";
8
8
  constructor(axiosLibrary: any);
9
9
  extractResponseFromError(error: Error): Promise<AxiosResponse | null | undefined>;
10
- convertResponse<T>(context: RequestContext, response: AxiosResponse): Promise<ApiResponse<T>>;
10
+ convertResponse<T>(context: RequestContext, response: AxiosResponse): Promise<ConvertedApiResponse<T>>;
11
11
  makeRequest(context: RequestContext): RequestOperation<AxiosResponse>;
12
12
  getErrorInfo(error: Error, response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
13
13
  }
@@ -37,8 +37,6 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.isAxiosError = void 0;
40
- var ApiUtils_1 = require("../ApiUtils");
41
- var RequestError_1 = require("../RequestError");
42
40
  var axios;
43
41
  var isAxiosError = function (error) {
44
42
  return "isAxiosError" in error;
@@ -62,18 +60,13 @@ var AxiosRequestBackend = /** @class */ (function () {
62
60
  };
63
61
  AxiosRequestBackend.prototype.convertResponse = function (context, response) {
64
62
  return __awaiter(this, void 0, void 0, function () {
65
- var contentType, inferredResponseType;
66
63
  return __generator(this, function (_a) {
67
- contentType = response.headers["content-type"];
68
- inferredResponseType = ApiUtils_1.inferResponseType(contentType);
69
- // expand to array buffer once we support that in inferResponseType
70
- if (inferredResponseType === "text" && context.responseType === "json") {
71
- throw RequestError_1.convertToRequestError({
72
- error: new Error("[api-def] Expected '" + context.responseType + "' response, got '" + inferredResponseType + "' (from 'Content-Type' of '" + contentType + "')"),
73
- code: RequestError_1.RequestErrorCode.REQUEST_MISMATCH_RESPONSE_TYPE,
74
- });
75
- }
76
- return [2 /*return*/, response];
64
+ return [2 /*return*/, {
65
+ data: response.data,
66
+ headers: response.headers,
67
+ status: response.status,
68
+ __lowercaseHeaders: response._lowerCaseResponseHeaders,
69
+ }];
77
70
  });
78
71
  });
79
72
  };
@@ -91,6 +84,7 @@ var AxiosRequestBackend = /** @class */ (function () {
91
84
  cancelToken: new axios.CancelToken(function (cancellerFunc) {
92
85
  canceler = cancellerFunc;
93
86
  }),
87
+ paramsSerializer: context.computedConfig.queryParser,
94
88
  });
95
89
  return {
96
90
  promise: promise,
@@ -1,4 +1,4 @@
1
- import RequestBackend, { RequestBackendErrorInfo, RequestOperation } from "./RequestBackend";
1
+ import RequestBackend, { ConvertedApiResponse, RequestBackendErrorInfo, RequestOperation } from "./RequestBackend";
2
2
  import { ApiResponse } from "../ApiTypes";
3
3
  import RequestContext from "../RequestContext";
4
4
  import { Fetch } from "../Utils";
@@ -9,7 +9,7 @@ export default class FetchRequestBackend implements RequestBackend<Response> {
9
9
  extractResponseFromError(error: Error): Promise<Response | null | undefined>;
10
10
  convertResponse<T>(context: RequestContext, response: Response & {
11
11
  __text?: string;
12
- }, error?: boolean): Promise<ApiResponse<T>>;
12
+ }): Promise<ConvertedApiResponse<T>>;
13
13
  makeRequest(context: RequestContext): RequestOperation<Response>;
14
14
  getErrorInfo(error: Error, response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
15
15
  }
@@ -55,7 +55,6 @@ var Utils = require("../Utils");
55
55
  var Utils_1 = require("../Utils");
56
56
  var ApiConstants_1 = require("../ApiConstants");
57
57
  var ApiUtils_1 = require("../ApiUtils");
58
- var RequestError_1 = require("../RequestError");
59
58
  var FetchError = /** @class */ (function (_super) {
60
59
  __extends(FetchError, _super);
61
60
  function FetchError() {
@@ -83,59 +82,47 @@ var FetchRequestBackend = /** @class */ (function () {
83
82
  });
84
83
  });
85
84
  };
86
- FetchRequestBackend.prototype.convertResponse = function (context, response, error) {
85
+ FetchRequestBackend.prototype.convertResponse = function (context, response) {
87
86
  return __awaiter(this, void 0, void 0, function () {
88
- var data, contentType, inferredResponseType, responseType, _a, error_1, status;
87
+ var contentType, responseType, _a, data, status, headers, error_1;
89
88
  return __generator(this, function (_b) {
90
89
  switch (_b.label) {
91
90
  case 0:
92
91
  contentType = response.headers.get("Content-Type");
93
- inferredResponseType = ApiUtils_1.inferResponseType(contentType);
94
- responseType = error ? inferredResponseType : context.responseType;
95
- // expand to array buffer once we support that in inferResponseType
96
- if (inferredResponseType === "text" && context.responseType === "json") {
97
- throw RequestError_1.convertToRequestError({
98
- error: new Error("[api-def] Expected '" + context.responseType + "' response, got '" + inferredResponseType + "' (from 'Content-Type' of '" + contentType + "')"),
99
- code: RequestError_1.RequestErrorCode.REQUEST_MISMATCH_RESPONSE_TYPE,
100
- });
101
- }
102
- _b.label = 1;
103
- case 1:
104
- _b.trys.push([1, 8, , 9]);
105
- if (!!response.__text) return [3 /*break*/, 3];
92
+ responseType = ApiUtils_1.inferResponseType(contentType);
93
+ if (!!response.__text) return [3 /*break*/, 2];
106
94
  _a = response;
107
95
  return [4 /*yield*/, response.clone().text()];
108
- case 2:
96
+ case 1:
109
97
  _a.__text = _b.sent();
98
+ _b.label = 2;
99
+ case 2:
100
+ data = response.__text;
101
+ status = response.status, headers = response.headers;
110
102
  _b.label = 3;
111
103
  case 3:
112
- if (!(responseType === ApiConstants_1.ResponseType.Text)) return [3 /*break*/, 4];
113
- data = response.__text;
114
- return [3 /*break*/, 7];
115
- case 4:
116
- if (!(responseType === ApiConstants_1.ResponseType.ArrayBuffer)) return [3 /*break*/, 6];
104
+ _b.trys.push([3, 7, , 8]);
105
+ if (!(responseType === ApiConstants_1.ResponseType.ArrayBuffer)) return [3 /*break*/, 5];
117
106
  return [4 /*yield*/, response.clone().arrayBuffer()];
118
- case 5:
107
+ case 4:
119
108
  data = _b.sent();
120
- return [3 /*break*/, 7];
121
- case 6:
109
+ return [3 /*break*/, 6];
110
+ case 5:
122
111
  if (responseType === ApiConstants_1.ResponseType.Json) {
123
112
  data = JSON.parse(response.__text);
124
113
  }
125
- _b.label = 7;
126
- case 7: return [3 /*break*/, 9];
127
- case 8:
114
+ _b.label = 6;
115
+ case 6: return [3 /*break*/, 8];
116
+ case 7:
128
117
  error_1 = _b.sent();
129
118
  throw Object.assign(new Error("[api-def] Invalid '" + context.responseType + "' response, got: '" + response.__text + "'"), {
130
119
  response: response,
131
120
  });
132
- case 9:
133
- status = response.status;
134
- return [2 /*return*/, {
135
- data: data,
136
- status: status,
137
- headers: response.headers,
138
- }];
121
+ case 8: return [2 /*return*/, {
122
+ data: data,
123
+ status: status,
124
+ headers: headers,
125
+ }];
139
126
  }
140
127
  });
141
128
  });
@@ -158,10 +145,15 @@ var FetchRequestBackend = /** @class */ (function () {
158
145
  }
159
146
  var url = new URL(path, origin);
160
147
  if (computedConfig.query) {
161
- var queryKeys = Object.keys(computedConfig.query);
162
- for (var i = 0; i < queryKeys.length; i++) {
163
- var key = queryKeys[i];
164
- url.searchParams.append(key, ((_a = computedConfig.query[key]) === null || _a === void 0 ? void 0 : _a.toString()) || "");
148
+ if (context.computedConfig.queryParser) {
149
+ url.search = context.computedConfig.queryParser(computedConfig.query);
150
+ }
151
+ else {
152
+ var queryKeys = Object.keys(computedConfig.query);
153
+ for (var i = 0; i < queryKeys.length; i++) {
154
+ var key = queryKeys[i];
155
+ url.searchParams.append(key, ((_a = computedConfig.query[key]) === null || _a === void 0 ? void 0 : _a.toString()) || "");
156
+ }
165
157
  }
166
158
  }
167
159
  // abort controller is a newer feature than fetch
@@ -204,7 +196,6 @@ var FetchRequestBackend = /** @class */ (function () {
204
196
  canceler: abortSignal
205
197
  ? function () { return !responded && abortController.abort(); }
206
198
  : function () {
207
- console.warn("Request aborted");
208
199
  softAbort = true;
209
200
  },
210
201
  };
@@ -7,10 +7,13 @@ export interface RequestOperation<R> {
7
7
  export interface RequestBackendErrorInfo {
8
8
  code: string;
9
9
  }
10
+ export declare type ConvertedApiResponse<T> = ApiResponse<T> & {
11
+ __lowercaseHeaders?: any;
12
+ };
10
13
  export default interface RequestBackend<R = any> {
11
14
  readonly id: string;
12
15
  makeRequest(context: RequestContext): RequestOperation<R>;
13
- convertResponse<T>(context: RequestContext, response: R, error?: boolean): Promise<ApiResponse<T>>;
16
+ convertResponse<T>(context: RequestContext, response: R): Promise<ConvertedApiResponse<T>>;
14
17
  extractResponseFromError(error: Error): Promise<R | null | undefined>;
15
18
  getErrorInfo(error: Error, response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
16
19
  }
package/esm/Api.d.ts CHANGED
@@ -28,4 +28,5 @@ export declare class Api implements ApiInfo {
28
28
  post: <R = unknown>(path: string, config: RequestConfig) => Promise<ApiResponse<R>>;
29
29
  put: <R = unknown>(path: string, config: RequestConfig) => Promise<ApiResponse<R>>;
30
30
  delete: <R = unknown>(path: string, config: RequestConfig) => Promise<ApiResponse<R>>;
31
+ patch: <R = unknown>(path: string, config: RequestConfig) => Promise<ApiResponse<R>>;
31
32
  }
package/esm/Api.js CHANGED
@@ -92,6 +92,7 @@ var Api = /** @class */ (function () {
92
92
  this.post = this.hotRequest(RequestMethod.POST);
93
93
  this.put = this.hotRequest(RequestMethod.PUT);
94
94
  this.delete = this.hotRequest(RequestMethod.DELETE);
95
+ this.patch = this.hotRequest(RequestMethod.PATCH);
95
96
  this.name = info.name;
96
97
  this.baseUrl = info.baseUrl;
97
98
  this.middleware = info.middleware || [];
@@ -8,6 +8,7 @@ export declare const RequestMethod: {
8
8
  readonly GET: "get";
9
9
  readonly PUT: "put";
10
10
  readonly DELETE: "delete";
11
+ readonly PATCH: "patch";
11
12
  };
12
13
  export declare type RequestMethod = EnumOf<typeof RequestMethod>;
13
14
  export declare const RequestEvent: {
@@ -7,6 +7,7 @@ export var RequestMethod = {
7
7
  GET: "get",
8
8
  PUT: "put",
9
9
  DELETE: "delete",
10
+ PATCH: "patch",
10
11
  };
11
12
  export var RequestEvent = {
12
13
  /** @deprecated use 'BEFORE_SEND' */
package/esm/ApiTypes.d.ts CHANGED
@@ -4,7 +4,7 @@ import { CacheSource, EventResultType, RequestEvent, RequestMethod, ResponseType
4
4
  export declare type AcceptableStatus = number | [min: number, max: number];
5
5
  export declare type Headers = Record<string, string | number | boolean | null | undefined>;
6
6
  export declare type Params = string;
7
- export declare type Query = Record<string, string | number | boolean | undefined | null>;
7
+ export declare type Query = Record<string, any>;
8
8
  export declare type Body = string | number | Record<string, any>;
9
9
  export interface ApiResponse<T = any> {
10
10
  status: number;
@@ -17,6 +17,7 @@ export interface BaseRequestConfig {
17
17
  retry?: number | false;
18
18
  headers?: Readonly<Headers>;
19
19
  acceptableStatus?: AcceptableStatus[];
20
+ queryParser?: (query: any) => string;
20
21
  }
21
22
  export declare type RequestConfig<P extends Params | undefined = Params | undefined, Q extends Query | undefined = Query | undefined, B extends Body | undefined = Body | undefined> = (P extends undefined ? {
22
23
  params?: never;
package/esm/ApiUtils.d.ts CHANGED
@@ -1,7 +1,6 @@
1
- import { AcceptableStatus, ApiResponse, CancelledRequestError } from "./ApiTypes";
1
+ import { AcceptableStatus, CancelledRequestError } from "./ApiTypes";
2
2
  import { ResponseType } from "./ApiConstants";
3
3
  export declare const isCancelledError: (error: Error) => error is CancelledRequestError;
4
4
  export declare const isNetworkError: (error: Error) => boolean;
5
- export declare const parseResponseDataToObject: (response: ApiResponse) => void;
6
5
  export declare const isAcceptableStatus: (status: number, acceptableStatus?: AcceptableStatus[] | undefined) => boolean;
7
6
  export declare const inferResponseType: (contentType: string | null | undefined) => ResponseType;
package/esm/ApiUtils.js CHANGED
@@ -1,4 +1,3 @@
1
- import { textDecode } from "./TextDecoding";
2
1
  export var isCancelledError = function (error) {
3
2
  return "isCancelledRequest" in error;
4
3
  };
@@ -8,21 +7,6 @@ export var isNetworkError = function (error) {
8
7
  error.message === "Network Error" ||
9
8
  ((_a = error.constructor) === null || _a === void 0 ? void 0 : _a.name) === "NetworkError");
10
9
  };
11
- export var parseResponseDataToObject = function (response) {
12
- if (response.data &&
13
- typeof response.data === "object") {
14
- var data = response.data;
15
- if (data.constructor && data.constructor.name === "ArrayBuffer") {
16
- try {
17
- var decodedData = (response.data = textDecode(data));
18
- response.data = JSON.parse(decodedData);
19
- }
20
- catch (e) {
21
- console.warn("Couldn't parse array buffer content to JSON response", e);
22
- }
23
- }
24
- }
25
- };
26
10
  var DEFAULT_ACCEPTABLE_STATUS = [[200, 299], 304];
27
11
  export var isAcceptableStatus = function (status, acceptableStatus) {
28
12
  var acceptable = acceptableStatus !== null && acceptableStatus !== void 0 ? acceptableStatus : DEFAULT_ACCEPTABLE_STATUS;
@@ -42,11 +26,21 @@ export var isAcceptableStatus = function (status, acceptableStatus) {
42
26
  }
43
27
  return (false);
44
28
  };
29
+ var TEXT_CONTENT_TYPES = ["text/plain"];
45
30
  var JSON_CONTENT_TYPES = ["text/json", "application/json"];
31
+ var ARRRAY_BUFFER_CONTENT_TYPES = ["application/octet-stream"];
46
32
  export var inferResponseType = function (contentType) {
47
33
  var contentTypePart = contentType === null || contentType === void 0 ? void 0 : contentType.split(";")[0].trim();
48
- if (contentTypePart && JSON_CONTENT_TYPES.includes(contentTypePart)) {
49
- return "json";
34
+ if (contentTypePart) {
35
+ if (TEXT_CONTENT_TYPES.includes(contentTypePart)) {
36
+ return "text";
37
+ }
38
+ else if (JSON_CONTENT_TYPES.includes(contentTypePart)) {
39
+ return "json";
40
+ }
41
+ else if (ARRRAY_BUFFER_CONTENT_TYPES.includes(contentTypePart)) {
42
+ return "arraybuffer";
43
+ }
50
44
  }
51
45
  return "text";
52
46
  };
@@ -11,9 +11,12 @@ export var isRequestError = function (error) {
11
11
  export var convertToRequestError = function (config) {
12
12
  var error = config.error, response = config.response, code = config.code;
13
13
  return Object.assign(error, {
14
- name: error.name === "Error" ? "RequestError" : error.name,
14
+ name: "RequestError",
15
15
  response: response,
16
16
  code: code,
17
17
  isRequestError: true,
18
+ config: undefined,
19
+ request: undefined,
20
+ toJSON: undefined,
18
21
  });
19
22
  };
package/esm/Requester.js CHANGED
@@ -46,13 +46,14 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
46
46
  }
47
47
  };
48
48
  import * as ApiUtils from "./ApiUtils";
49
- import { isAcceptableStatus, isNetworkError } from "./ApiUtils";
49
+ import { inferResponseType, isAcceptableStatus, isNetworkError } from "./ApiUtils";
50
50
  import RequestContext from "./RequestContext";
51
51
  import * as Api from "./Api";
52
52
  import { EventResultType, RequestEvent } from "./ApiConstants";
53
53
  import retry from "./util/retry";
54
54
  import MockRequestBackend from "./backend/MockRequestBackend";
55
55
  import { convertToRequestError, isRequestError, RequestErrorCode } from "./RequestError";
56
+ import { textDecode } from "./TextDecoding";
56
57
  var locks = {};
57
58
  var runningOperations = {};
58
59
  var MOCK_REQUEST_BACKEND = new MockRequestBackend();
@@ -114,6 +115,7 @@ var makeRequest = function (context) { return __awaiter(void 0, void 0, void 0,
114
115
  if (process.env.NODE_ENV === "development") {
115
116
  if (Api.isRequestBackendDefault() && !defaultBackendMessageShown) {
116
117
  defaultBackendMessageShown = true;
118
+ // eslint-disable-next-line
117
119
  console.warn("[api-def] Using default fetch backend, you can use a different one with 'setRequestBackend()' (dev only message)");
118
120
  }
119
121
  }
@@ -169,10 +171,6 @@ var makeRequest = function (context) { return __awaiter(void 0, void 0, void 0,
169
171
  error = _b.sent();
170
172
  context.error = error;
171
173
  context.response = error.response;
172
- // transform array buffer responses to objs
173
- if (context.response) {
174
- ApiUtils.parseResponseDataToObject(context.response);
175
- }
176
174
  return [4 /*yield*/, context.triggerEvent(RequestEvent.Error)];
177
175
  case 6:
178
176
  errorEventResult = _b.sent();
@@ -209,18 +207,53 @@ var makeRequest = function (context) { return __awaiter(void 0, void 0, void 0,
209
207
  });
210
208
  }); };
211
209
  var parseResponse = function (context, response, error) { return __awaiter(void 0, void 0, void 0, function () {
212
- var parsedResponse;
213
- return __generator(this, function (_a) {
214
- switch (_a.label) {
210
+ var parsedResponse_1, contentType, inferredResponseType, data, decodedData;
211
+ var _a;
212
+ return __generator(this, function (_b) {
213
+ switch (_b.label) {
215
214
  case 0:
216
215
  if (!response) return [3 /*break*/, 2];
217
- return [4 /*yield*/, context.backend.convertResponse(context, response, error)];
216
+ return [4 /*yield*/, context.backend.convertResponse(context, response)];
218
217
  case 1:
219
- parsedResponse = _a.sent();
220
- if (parsedResponse) {
221
- ApiUtils.parseResponseDataToObject(parsedResponse);
218
+ parsedResponse_1 = _b.sent();
219
+ // lowercase all header names
220
+ parsedResponse_1.headers = parsedResponse_1.__lowercaseHeaders || Object.keys(parsedResponse_1.headers).reduce(function (headers, header) {
221
+ headers[header.toLowerCase()] = parsedResponse_1.headers[header];
222
+ return headers;
223
+ }, {});
224
+ contentType = parsedResponse_1.headers["content-type"];
225
+ inferredResponseType = inferResponseType(contentType);
226
+ if (!error) {
227
+ // expand to array buffer once we support that in inferResponseType
228
+ if (inferredResponseType === "text" && context.responseType === "json") {
229
+ throw convertToRequestError({
230
+ error: new Error("[api-def] Expected '" + context.responseType + "' response, got '" + inferredResponseType + "' (from 'Content-Type' of '" + contentType + "')"),
231
+ code: RequestErrorCode.REQUEST_MISMATCH_RESPONSE_TYPE,
232
+ response: parsedResponse_1,
233
+ });
234
+ }
235
+ // transform arrayBuffer to json
236
+ if (inferredResponseType === "arraybuffer" && context.responseType === "json") {
237
+ if (parsedResponse_1.data &&
238
+ typeof parsedResponse_1.data === "object") {
239
+ data = response.data;
240
+ if (((_a = data.constructor) === null || _a === void 0 ? void 0 : _a.name) === "ArrayBuffer") {
241
+ try {
242
+ decodedData = (response.data = textDecode(data));
243
+ response.data = JSON.parse(decodedData);
244
+ }
245
+ catch (e) {
246
+ throw convertToRequestError({
247
+ error: new Error("[api-def] Expected '" + context.responseType + "' response, got '" + inferredResponseType + "' (from 'Content-Type' of '" + contentType + "')"),
248
+ code: RequestErrorCode.REQUEST_MISMATCH_RESPONSE_TYPE,
249
+ response: parsedResponse_1,
250
+ });
251
+ }
252
+ }
253
+ }
254
+ }
222
255
  }
223
- return [2 /*return*/, parsedResponse];
256
+ return [2 /*return*/, parsedResponse_1];
224
257
  case 2: return [2 /*return*/, response];
225
258
  }
226
259
  });
@@ -1,4 +1,4 @@
1
- import RequestBackend, { RequestBackendErrorInfo, RequestOperation } from "./RequestBackend";
1
+ import RequestBackend, { ConvertedApiResponse, RequestBackendErrorInfo, RequestOperation } from "./RequestBackend";
2
2
  import { ApiResponse } from "../ApiTypes";
3
3
  import type { AxiosError, AxiosResponse } from "axios";
4
4
  import RequestContext from "../RequestContext";
@@ -7,7 +7,7 @@ export default class AxiosRequestBackend implements RequestBackend<AxiosResponse
7
7
  readonly id = "axios";
8
8
  constructor(axiosLibrary: any);
9
9
  extractResponseFromError(error: Error): Promise<AxiosResponse | null | undefined>;
10
- convertResponse<T>(context: RequestContext, response: AxiosResponse): Promise<ApiResponse<T>>;
10
+ convertResponse<T>(context: RequestContext, response: AxiosResponse): Promise<ConvertedApiResponse<T>>;
11
11
  makeRequest(context: RequestContext): RequestOperation<AxiosResponse>;
12
12
  getErrorInfo(error: Error, response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
13
13
  }
@@ -34,8 +34,6 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
34
34
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
35
  }
36
36
  };
37
- import { inferResponseType } from "../ApiUtils";
38
- import { convertToRequestError, RequestErrorCode } from "../RequestError";
39
37
  var axios;
40
38
  export var isAxiosError = function (error) {
41
39
  return "isAxiosError" in error;
@@ -58,18 +56,13 @@ var AxiosRequestBackend = /** @class */ (function () {
58
56
  };
59
57
  AxiosRequestBackend.prototype.convertResponse = function (context, response) {
60
58
  return __awaiter(this, void 0, void 0, function () {
61
- var contentType, inferredResponseType;
62
59
  return __generator(this, function (_a) {
63
- contentType = response.headers["content-type"];
64
- inferredResponseType = inferResponseType(contentType);
65
- // expand to array buffer once we support that in inferResponseType
66
- if (inferredResponseType === "text" && context.responseType === "json") {
67
- throw convertToRequestError({
68
- error: new Error("[api-def] Expected '" + context.responseType + "' response, got '" + inferredResponseType + "' (from 'Content-Type' of '" + contentType + "')"),
69
- code: RequestErrorCode.REQUEST_MISMATCH_RESPONSE_TYPE,
70
- });
71
- }
72
- return [2 /*return*/, response];
60
+ return [2 /*return*/, {
61
+ data: response.data,
62
+ headers: response.headers,
63
+ status: response.status,
64
+ __lowercaseHeaders: response._lowerCaseResponseHeaders,
65
+ }];
73
66
  });
74
67
  });
75
68
  };
@@ -87,6 +80,7 @@ var AxiosRequestBackend = /** @class */ (function () {
87
80
  cancelToken: new axios.CancelToken(function (cancellerFunc) {
88
81
  canceler = cancellerFunc;
89
82
  }),
83
+ paramsSerializer: context.computedConfig.queryParser,
90
84
  });
91
85
  return {
92
86
  promise: promise,
@@ -1,4 +1,4 @@
1
- import RequestBackend, { RequestBackendErrorInfo, RequestOperation } from "./RequestBackend";
1
+ import RequestBackend, { ConvertedApiResponse, RequestBackendErrorInfo, RequestOperation } from "./RequestBackend";
2
2
  import { ApiResponse } from "../ApiTypes";
3
3
  import RequestContext from "../RequestContext";
4
4
  import { Fetch } from "../Utils";
@@ -9,7 +9,7 @@ export default class FetchRequestBackend implements RequestBackend<Response> {
9
9
  extractResponseFromError(error: Error): Promise<Response | null | undefined>;
10
10
  convertResponse<T>(context: RequestContext, response: Response & {
11
11
  __text?: string;
12
- }, error?: boolean): Promise<ApiResponse<T>>;
12
+ }): Promise<ConvertedApiResponse<T>>;
13
13
  makeRequest(context: RequestContext): RequestOperation<Response>;
14
14
  getErrorInfo(error: Error, response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
15
15
  }
@@ -53,7 +53,6 @@ import * as Utils from "../Utils";
53
53
  import { getGlobalFetch } from "../Utils";
54
54
  import { ResponseType } from "../ApiConstants";
55
55
  import { inferResponseType } from "../ApiUtils";
56
- import { convertToRequestError, RequestErrorCode } from "../RequestError";
57
56
  var FetchError = /** @class */ (function (_super) {
58
57
  __extends(FetchError, _super);
59
58
  function FetchError() {
@@ -81,59 +80,47 @@ var FetchRequestBackend = /** @class */ (function () {
81
80
  });
82
81
  });
83
82
  };
84
- FetchRequestBackend.prototype.convertResponse = function (context, response, error) {
83
+ FetchRequestBackend.prototype.convertResponse = function (context, response) {
85
84
  return __awaiter(this, void 0, void 0, function () {
86
- var data, contentType, inferredResponseType, responseType, _a, error_1, status;
85
+ var contentType, responseType, _a, data, status, headers, error_1;
87
86
  return __generator(this, function (_b) {
88
87
  switch (_b.label) {
89
88
  case 0:
90
89
  contentType = response.headers.get("Content-Type");
91
- inferredResponseType = inferResponseType(contentType);
92
- responseType = error ? inferredResponseType : context.responseType;
93
- // expand to array buffer once we support that in inferResponseType
94
- if (inferredResponseType === "text" && context.responseType === "json") {
95
- throw convertToRequestError({
96
- error: new Error("[api-def] Expected '" + context.responseType + "' response, got '" + inferredResponseType + "' (from 'Content-Type' of '" + contentType + "')"),
97
- code: RequestErrorCode.REQUEST_MISMATCH_RESPONSE_TYPE,
98
- });
99
- }
100
- _b.label = 1;
101
- case 1:
102
- _b.trys.push([1, 8, , 9]);
103
- if (!!response.__text) return [3 /*break*/, 3];
90
+ responseType = inferResponseType(contentType);
91
+ if (!!response.__text) return [3 /*break*/, 2];
104
92
  _a = response;
105
93
  return [4 /*yield*/, response.clone().text()];
106
- case 2:
94
+ case 1:
107
95
  _a.__text = _b.sent();
96
+ _b.label = 2;
97
+ case 2:
98
+ data = response.__text;
99
+ status = response.status, headers = response.headers;
108
100
  _b.label = 3;
109
101
  case 3:
110
- if (!(responseType === ResponseType.Text)) return [3 /*break*/, 4];
111
- data = response.__text;
112
- return [3 /*break*/, 7];
113
- case 4:
114
- if (!(responseType === ResponseType.ArrayBuffer)) return [3 /*break*/, 6];
102
+ _b.trys.push([3, 7, , 8]);
103
+ if (!(responseType === ResponseType.ArrayBuffer)) return [3 /*break*/, 5];
115
104
  return [4 /*yield*/, response.clone().arrayBuffer()];
116
- case 5:
105
+ case 4:
117
106
  data = _b.sent();
118
- return [3 /*break*/, 7];
119
- case 6:
107
+ return [3 /*break*/, 6];
108
+ case 5:
120
109
  if (responseType === ResponseType.Json) {
121
110
  data = JSON.parse(response.__text);
122
111
  }
123
- _b.label = 7;
124
- case 7: return [3 /*break*/, 9];
125
- case 8:
112
+ _b.label = 6;
113
+ case 6: return [3 /*break*/, 8];
114
+ case 7:
126
115
  error_1 = _b.sent();
127
116
  throw Object.assign(new Error("[api-def] Invalid '" + context.responseType + "' response, got: '" + response.__text + "'"), {
128
117
  response: response,
129
118
  });
130
- case 9:
131
- status = response.status;
132
- return [2 /*return*/, {
133
- data: data,
134
- status: status,
135
- headers: response.headers,
136
- }];
119
+ case 8: return [2 /*return*/, {
120
+ data: data,
121
+ status: status,
122
+ headers: headers,
123
+ }];
137
124
  }
138
125
  });
139
126
  });
@@ -156,10 +143,15 @@ var FetchRequestBackend = /** @class */ (function () {
156
143
  }
157
144
  var url = new URL(path, origin);
158
145
  if (computedConfig.query) {
159
- var queryKeys = Object.keys(computedConfig.query);
160
- for (var i = 0; i < queryKeys.length; i++) {
161
- var key = queryKeys[i];
162
- url.searchParams.append(key, ((_a = computedConfig.query[key]) === null || _a === void 0 ? void 0 : _a.toString()) || "");
146
+ if (context.computedConfig.queryParser) {
147
+ url.search = context.computedConfig.queryParser(computedConfig.query);
148
+ }
149
+ else {
150
+ var queryKeys = Object.keys(computedConfig.query);
151
+ for (var i = 0; i < queryKeys.length; i++) {
152
+ var key = queryKeys[i];
153
+ url.searchParams.append(key, ((_a = computedConfig.query[key]) === null || _a === void 0 ? void 0 : _a.toString()) || "");
154
+ }
163
155
  }
164
156
  }
165
157
  // abort controller is a newer feature than fetch
@@ -202,7 +194,6 @@ var FetchRequestBackend = /** @class */ (function () {
202
194
  canceler: abortSignal
203
195
  ? function () { return !responded && abortController.abort(); }
204
196
  : function () {
205
- console.warn("Request aborted");
206
197
  softAbort = true;
207
198
  },
208
199
  };
@@ -7,10 +7,13 @@ export interface RequestOperation<R> {
7
7
  export interface RequestBackendErrorInfo {
8
8
  code: string;
9
9
  }
10
+ export declare type ConvertedApiResponse<T> = ApiResponse<T> & {
11
+ __lowercaseHeaders?: any;
12
+ };
10
13
  export default interface RequestBackend<R = any> {
11
14
  readonly id: string;
12
15
  makeRequest(context: RequestContext): RequestOperation<R>;
13
- convertResponse<T>(context: RequestContext, response: R, error?: boolean): Promise<ApiResponse<T>>;
16
+ convertResponse<T>(context: RequestContext, response: R): Promise<ConvertedApiResponse<T>>;
14
17
  extractResponseFromError(error: Error): Promise<R | null | undefined>;
15
18
  getErrorInfo(error: Error, response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
16
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api-def",
3
- "version": "0.6.0-alpha8",
3
+ "version": "0.6.1",
4
4
  "description": "Typed API definitions with middleware support",
5
5
  "main": "cjs/index.js",
6
6
  "types": "esm/index.d.ts",
package/CHANGELOG.md DELETED
@@ -1,70 +0,0 @@
1
- # 0.6.0
2
-
3
- ## Breaking Changes
4
-
5
- - axios will enforce `ResponseType`
6
-
7
- ## Features
8
-
9
- - add `ResponseOf`, `QueryOf`, `BodyOf`, `ParamsOf` to quickly get types from endpoints
10
-
11
- ## Fixes
12
-
13
- - absolute paths as `baseUrl` is now supported in fetch backend
14
- - don't re-use same requests based upon URL to allow for concurrent POST requests
15
-
16
- # 0.5.0
17
-
18
- ## Breaking Changes
19
-
20
- - Undocumented mocking API has been overhauled
21
-
22
- ## Changes
23
-
24
- - Add mocking!
25
- - Restructured mocking so that mocks are defined on the endpoint level (documentation updated)
26
- - Add `acceptableStatus` to specify which status codes are considered successful
27
- - Extend retry logic to use exponential back-off, rather than retrying immediately
28
- - Support for additional hot-request methods:
29
- - PUT, DELETE
30
-
31
- # 0.4.1
32
-
33
- - fix fetch backend not working with unbound fetch
34
- - add better error handling on parse
35
-
36
- # 0.4.0
37
-
38
- ## Breaking Changes
39
-
40
- - Move config values from options object up one layer
41
- - In config `retries` -> `retry`
42
- - `defaults` -> `config`
43
-
44
- ## Changes
45
-
46
- - Remove need for enum imports
47
- - Make `name` and `description` optional in endpoint definition
48
-
49
- # 0.3.11
50
-
51
- ## Changes
52
-
53
- - Make fetch backend default if fetch is present
54
- - Fix fetch backends text response type support
55
-
56
- # 0.3.0
57
-
58
- ## Features
59
-
60
- - allow defaults to be a function on api
61
- - add content type header in fetch backend to match axios backend
62
- - remove `flatted`
63
- - polyfill object.assign for ie
64
- - change retry logic so that if a middleware event responds with Retry we always attempt a retry
65
-
66
- # 0.2.5
67
-
68
- ## Changes
69
-
70
- - Remove `window` usages to allow node support