api-def 0.5.0 → 0.6.0-alpha10

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/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,5 +1,7 @@
1
1
  import { AcceptableStatus, ApiResponse, CancelledRequestError } from "./ApiTypes";
2
+ import { ResponseType } from "./ApiConstants";
2
3
  export declare const isCancelledError: (error: Error) => error is CancelledRequestError;
3
4
  export declare const isNetworkError: (error: Error) => boolean;
4
5
  export declare const parseResponseDataToObject: (response: ApiResponse) => void;
5
6
  export declare const isAcceptableStatus: (status: number, acceptableStatus?: AcceptableStatus[] | undefined) => boolean;
7
+ export declare const inferResponseType: (contentType: string | null | undefined) => ResponseType;
package/cjs/ApiUtils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isAcceptableStatus = exports.parseResponseDataToObject = exports.isNetworkError = exports.isCancelledError = void 0;
3
+ exports.inferResponseType = exports.isAcceptableStatus = exports.parseResponseDataToObject = exports.isNetworkError = exports.isCancelledError = void 0;
4
4
  var TextDecoding_1 = require("./TextDecoding");
5
5
  var isCancelledError = function (error) {
6
6
  return "isCancelledRequest" in error;
@@ -23,7 +23,8 @@ var parseResponseDataToObject = function (response) {
23
23
  response.data = JSON.parse(decodedData);
24
24
  }
25
25
  catch (e) {
26
- console.warn("Couldn't parse array buffer content to JSON response", e);
26
+ // eslint-disable-next-line
27
+ console.warn("[api-def] Couldn't parse array buffer content to JSON response", e);
27
28
  }
28
29
  }
29
30
  }
@@ -49,3 +50,12 @@ var isAcceptableStatus = function (status, acceptableStatus) {
49
50
  return (false);
50
51
  };
51
52
  exports.isAcceptableStatus = isAcceptableStatus;
53
+ var JSON_CONTENT_TYPES = ["text/json", "application/json"];
54
+ var inferResponseType = function (contentType) {
55
+ var contentTypePart = contentType === null || contentType === void 0 ? void 0 : contentType.split(";")[0].trim();
56
+ if (contentTypePart && JSON_CONTENT_TYPES.includes(contentTypePart)) {
57
+ return "json";
58
+ }
59
+ return "text";
60
+ };
61
+ exports.inferResponseType = inferResponseType;
@@ -5,6 +5,7 @@ export declare const RequestErrorCode: {
5
5
  readonly REQUEST_NETWORK_ERROR: "request/network-error";
6
6
  readonly REQUEST_INVALID_STATUS: "request/invalid-status";
7
7
  readonly REQUEST_INVALID_CONFIG: "request/invalid-config";
8
+ readonly REQUEST_MISMATCH_RESPONSE_TYPE: "request/mismatch-response-type";
8
9
  };
9
10
  export declare type RequestErrorCode = EnumOf<typeof RequestErrorCode>;
10
11
  export interface RequestError extends Error {
@@ -6,6 +6,7 @@ exports.RequestErrorCode = {
6
6
  REQUEST_NETWORK_ERROR: "request/network-error",
7
7
  REQUEST_INVALID_STATUS: "request/invalid-status",
8
8
  REQUEST_INVALID_CONFIG: "request/invalid-config",
9
+ REQUEST_MISMATCH_RESPONSE_TYPE: "request/mismatch-response-type",
9
10
  };
10
11
  var isRequestError = function (error) {
11
12
  return "isRequestError" in error;
package/cjs/Requester.js CHANGED
@@ -60,7 +60,7 @@ var locks = {};
60
60
  var runningOperations = {};
61
61
  var MOCK_REQUEST_BACKEND = new MockRequestBackend_1.default();
62
62
  var submit = function (host, config, mocking) { return __awaiter(void 0, void 0, void 0, function () {
63
- var computedConfig, backend, context, key, sameRequest, lock, lockedContext, response, successEventResult, error_1;
63
+ var computedConfig, backend, context, key, lock, lockedContext, response, successEventResult, error_1;
64
64
  return __generator(this, function (_a) {
65
65
  switch (_a.label) {
66
66
  case 0:
@@ -71,10 +71,6 @@ var submit = function (host, config, mocking) { return __awaiter(void 0, void 0,
71
71
  }
72
72
  context = new RequestContext_1.default(backend, host, computedConfig, host.computePath(host.path, config), mocking);
73
73
  key = context.key;
74
- sameRequest = runningOperations[key];
75
- if (sameRequest) {
76
- return [2 /*return*/, sameRequest];
77
- }
78
74
  lock = (context.computedConfig || {}).lock;
79
75
  if (lock) {
80
76
  lockedContext = locks[lock];
@@ -122,6 +118,7 @@ var makeRequest = function (context) { return __awaiter(void 0, void 0, void 0,
122
118
  if (process.env.NODE_ENV === "development") {
123
119
  if (Api.isRequestBackendDefault() && !defaultBackendMessageShown) {
124
120
  defaultBackendMessageShown = true;
121
+ // eslint-disable-next-line
125
122
  console.warn("[api-def] Using default fetch backend, you can use a different one with 'setRequestBackend()' (dev only message)");
126
123
  }
127
124
  }
@@ -0,0 +1,5 @@
1
+ import Endpoint from "./Endpoint";
2
+ export declare type ResponseOf<E extends Endpoint<any, any, any, any>> = E extends Endpoint<infer R, any, any, any> ? R : never;
3
+ export declare type ParamsOf<E extends Endpoint<any, any, any, any>> = E extends Endpoint<any, infer P, any, any> ? P : never;
4
+ export declare type QueryOf<E extends Endpoint<any, any, any, any>> = E extends Endpoint<any, any, infer Q, any> ? Q : never;
5
+ export declare type BodyOf<E extends Endpoint<any, any, any, any>> = E extends Endpoint<any, any, any, infer B> ? B : never;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,10 +1,11 @@
1
1
  import RequestBackend, { RequestBackendErrorInfo, RequestOperation } from "./RequestBackend";
2
2
  import { ApiResponse } from "../ApiTypes";
3
- import type { AxiosError, AxiosResponse, AxiosStatic } from "axios";
3
+ import type { AxiosError, AxiosResponse } from "axios";
4
4
  import RequestContext from "../RequestContext";
5
5
  export declare const isAxiosError: (error: Error) => error is AxiosError<any>;
6
6
  export default class AxiosRequestBackend implements RequestBackend<AxiosResponse> {
7
- constructor(axiosLibrary: AxiosStatic);
7
+ readonly id = "axios";
8
+ constructor(axiosLibrary: any);
8
9
  extractResponseFromError(error: Error): Promise<AxiosResponse | null | undefined>;
9
10
  convertResponse<T>(context: RequestContext, response: AxiosResponse): Promise<ApiResponse<T>>;
10
11
  makeRequest(context: RequestContext): RequestOperation<AxiosResponse>;
@@ -37,13 +37,17 @@ 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");
40
42
  var axios;
41
43
  var isAxiosError = function (error) {
42
44
  return "isAxiosError" in error;
43
45
  };
44
46
  exports.isAxiosError = isAxiosError;
45
47
  var AxiosRequestBackend = /** @class */ (function () {
48
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
46
49
  function AxiosRequestBackend(axiosLibrary) {
50
+ this.id = "axios";
47
51
  axios = axiosLibrary;
48
52
  }
49
53
  AxiosRequestBackend.prototype.extractResponseFromError = function (error) {
@@ -58,7 +62,18 @@ var AxiosRequestBackend = /** @class */ (function () {
58
62
  };
59
63
  AxiosRequestBackend.prototype.convertResponse = function (context, response) {
60
64
  return __awaiter(this, void 0, void 0, function () {
65
+ var contentType, inferredResponseType;
61
66
  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
+ response: response,
75
+ });
76
+ }
62
77
  return [2 /*return*/, response];
63
78
  });
64
79
  });
@@ -77,6 +92,7 @@ var AxiosRequestBackend = /** @class */ (function () {
77
92
  cancelToken: new axios.CancelToken(function (cancellerFunc) {
78
93
  canceler = cancellerFunc;
79
94
  }),
95
+ paramsSerializer: context.computedConfig.queryParser,
80
96
  });
81
97
  return {
82
98
  promise: promise,
@@ -2,12 +2,11 @@ import RequestBackend, { RequestBackendErrorInfo, RequestOperation } from "./Req
2
2
  import { ApiResponse } from "../ApiTypes";
3
3
  import RequestContext from "../RequestContext";
4
4
  import { Fetch } from "../Utils";
5
- import { ResponseType } from "../ApiConstants";
6
5
  export default class FetchRequestBackend implements RequestBackend<Response> {
7
6
  fetch: (((input: RequestInfo, init?: RequestInit | undefined) => Promise<Response>) & typeof fetch) | undefined;
7
+ readonly id = "fetch";
8
8
  constructor(fetchLibrary?: Fetch);
9
9
  extractResponseFromError(error: Error): Promise<Response | null | undefined>;
10
- inferResponseType(response: Response): ResponseType;
11
10
  convertResponse<T>(context: RequestContext, response: Response & {
12
11
  __text?: string;
13
12
  }, error?: boolean): Promise<ApiResponse<T>>;
@@ -54,6 +54,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
54
54
  var Utils = require("../Utils");
55
55
  var Utils_1 = require("../Utils");
56
56
  var ApiConstants_1 = require("../ApiConstants");
57
+ var ApiUtils_1 = require("../ApiUtils");
58
+ var RequestError_1 = require("../RequestError");
57
59
  var FetchError = /** @class */ (function (_super) {
58
60
  __extends(FetchError, _super);
59
61
  function FetchError() {
@@ -64,6 +66,7 @@ var FetchError = /** @class */ (function (_super) {
64
66
  var FetchRequestBackend = /** @class */ (function () {
65
67
  function FetchRequestBackend(fetchLibrary) {
66
68
  this.fetch = Utils_1.getGlobalFetch();
69
+ this.id = "fetch";
67
70
  if (fetchLibrary !== undefined) {
68
71
  this.fetch = fetchLibrary;
69
72
  }
@@ -80,30 +83,38 @@ var FetchRequestBackend = /** @class */ (function () {
80
83
  });
81
84
  });
82
85
  };
83
- FetchRequestBackend.prototype.inferResponseType = function (response) {
84
- var contentType = response.headers.get("Content-Type");
85
- if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith("json")) {
86
- return "json";
87
- }
88
- return "text";
89
- };
90
86
  FetchRequestBackend.prototype.convertResponse = function (context, response, error) {
91
87
  return __awaiter(this, void 0, void 0, function () {
92
- var data, responseType, _a, error_1, status;
88
+ var data, contentType, inferredResponseType, responseType, _a, status, headers, error_1;
93
89
  return __generator(this, function (_b) {
94
90
  switch (_b.label) {
95
91
  case 0:
96
- responseType = error ? this.inferResponseType(response) : context.responseType;
97
- _b.label = 1;
98
- case 1:
99
- _b.trys.push([1, 8, , 9]);
100
- if (!!response.__text) return [3 /*break*/, 3];
92
+ contentType = response.headers.get("Content-Type");
93
+ inferredResponseType = ApiUtils_1.inferResponseType(contentType);
94
+ responseType = error ? inferredResponseType : context.responseType;
95
+ if (!!response.__text) return [3 /*break*/, 2];
101
96
  _a = response;
102
97
  return [4 /*yield*/, response.clone().text()];
103
- case 2:
98
+ case 1:
104
99
  _a.__text = _b.sent();
100
+ _b.label = 2;
101
+ case 2:
102
+ status = response.status, headers = response.headers;
103
+ // expand to array buffer once we support that in inferResponseType
104
+ if (inferredResponseType === "text" && context.responseType === "json") {
105
+ throw RequestError_1.convertToRequestError({
106
+ error: new Error("[api-def] Expected '" + context.responseType + "' response, got '" + inferredResponseType + "' (from 'Content-Type' of '" + contentType + "')"),
107
+ code: RequestError_1.RequestErrorCode.REQUEST_MISMATCH_RESPONSE_TYPE,
108
+ response: {
109
+ data: response.__text,
110
+ status: status,
111
+ headers: headers,
112
+ },
113
+ });
114
+ }
105
115
  _b.label = 3;
106
116
  case 3:
117
+ _b.trys.push([3, 8, , 9]);
107
118
  if (!(responseType === ApiConstants_1.ResponseType.Text)) return [3 /*break*/, 4];
108
119
  data = response.__text;
109
120
  return [3 /*break*/, 7];
@@ -124,13 +135,11 @@ var FetchRequestBackend = /** @class */ (function () {
124
135
  throw Object.assign(new Error("[api-def] Invalid '" + context.responseType + "' response, got: '" + response.__text + "'"), {
125
136
  response: response,
126
137
  });
127
- case 9:
128
- status = response.status;
129
- return [2 /*return*/, {
130
- data: data,
131
- status: status,
132
- headers: response.headers,
133
- }];
138
+ case 9: return [2 /*return*/, {
139
+ data: data,
140
+ status: status,
141
+ headers: headers,
142
+ }];
134
143
  }
135
144
  });
136
145
  });
@@ -147,12 +156,21 @@ var FetchRequestBackend = /** @class */ (function () {
147
156
  path += context.computedPath.startsWith("/")
148
157
  ? context.computedPath.substring(1)
149
158
  : context.computedPath;
150
- var url = new URL(path);
159
+ var origin = undefined;
160
+ if (typeof window !== "undefined") {
161
+ origin = window.origin;
162
+ }
163
+ var url = new URL(path, origin);
151
164
  if (computedConfig.query) {
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()) || "");
165
+ if (context.computedConfig.queryParser) {
166
+ url.search = context.computedConfig.queryParser(computedConfig.query);
167
+ }
168
+ else {
169
+ var queryKeys = Object.keys(computedConfig.query);
170
+ for (var i = 0; i < queryKeys.length; i++) {
171
+ var key = queryKeys[i];
172
+ url.searchParams.append(key, ((_a = computedConfig.query[key]) === null || _a === void 0 ? void 0 : _a.toString()) || "");
173
+ }
156
174
  }
157
175
  }
158
176
  // abort controller is a newer feature than fetch
@@ -195,7 +213,6 @@ var FetchRequestBackend = /** @class */ (function () {
195
213
  canceler: abortSignal
196
214
  ? function () { return !responded && abortController.abort(); }
197
215
  : function () {
198
- console.warn("Request aborted");
199
216
  softAbort = true;
200
217
  },
201
218
  };
@@ -2,6 +2,7 @@ import RequestBackend, { RequestBackendErrorInfo, RequestOperation } from "./Req
2
2
  import { ApiResponse } from "../ApiTypes";
3
3
  import RequestContext from "../RequestContext";
4
4
  export default class MockRequestBackend implements RequestBackend<ApiResponse> {
5
+ readonly id = "mock";
5
6
  convertResponse<T>(context: RequestContext, response: ApiResponse, error?: boolean): Promise<ApiResponse<T>>;
6
7
  extractResponseFromError(error: Error): Promise<ApiResponse | null | undefined>;
7
8
  private runRequest;
@@ -41,6 +41,7 @@ var Utils_1 = require("../Utils");
41
41
  var RequestError_1 = require("../RequestError");
42
42
  var MockRequestBackend = /** @class */ (function () {
43
43
  function MockRequestBackend() {
44
+ this.id = "mock";
44
45
  }
45
46
  MockRequestBackend.prototype.convertResponse = function (context, response, error) {
46
47
  return __awaiter(this, void 0, void 0, function () {
@@ -8,6 +8,7 @@ export interface RequestBackendErrorInfo {
8
8
  code: string;
9
9
  }
10
10
  export default interface RequestBackend<R = any> {
11
+ readonly id: string;
11
12
  makeRequest(context: RequestContext): RequestOperation<R>;
12
13
  convertResponse<T>(context: RequestContext, response: R, error?: boolean): Promise<ApiResponse<T>>;
13
14
  extractResponseFromError(error: Error): Promise<R | null | undefined>;
package/cjs/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./Api";
2
2
  export * from "./ApiTypes";
3
+ export * from "./UtilTypes";
3
4
  export * from "./ApiConstants";
4
5
  export { isRequestError } from "./RequestError";
5
6
  export { clearCache, setCacheBackend } from "./cache/Caching";
package/cjs/index.js CHANGED
@@ -14,6 +14,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.LoggingMiddleware = exports.CacheMiddleware = exports.FetchRequestBackend = exports.AxiosRequestBackend = exports.LocaleForageCacheBackend = exports.LocalStorageCacheBackend = exports.setCacheBackend = exports.clearCache = exports.isRequestError = void 0;
15
15
  __exportStar(require("./Api"), exports);
16
16
  __exportStar(require("./ApiTypes"), exports);
17
+ __exportStar(require("./UtilTypes"), exports);
17
18
  __exportStar(require("./ApiConstants"), exports);
18
19
  var RequestError_1 = require("./RequestError");
19
20
  Object.defineProperty(exports, "isRequestError", { enumerable: true, get: function () { return RequestError_1.isRequestError; } });
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,5 +1,7 @@
1
1
  import { AcceptableStatus, ApiResponse, CancelledRequestError } from "./ApiTypes";
2
+ import { ResponseType } from "./ApiConstants";
2
3
  export declare const isCancelledError: (error: Error) => error is CancelledRequestError;
3
4
  export declare const isNetworkError: (error: Error) => boolean;
4
5
  export declare const parseResponseDataToObject: (response: ApiResponse) => void;
5
6
  export declare const isAcceptableStatus: (status: number, acceptableStatus?: AcceptableStatus[] | undefined) => boolean;
7
+ export declare const inferResponseType: (contentType: string | null | undefined) => ResponseType;
package/esm/ApiUtils.js CHANGED
@@ -18,7 +18,8 @@ export var parseResponseDataToObject = function (response) {
18
18
  response.data = JSON.parse(decodedData);
19
19
  }
20
20
  catch (e) {
21
- console.warn("Couldn't parse array buffer content to JSON response", e);
21
+ // eslint-disable-next-line
22
+ console.warn("[api-def] Couldn't parse array buffer content to JSON response", e);
22
23
  }
23
24
  }
24
25
  }
@@ -42,3 +43,11 @@ export var isAcceptableStatus = function (status, acceptableStatus) {
42
43
  }
43
44
  return (false);
44
45
  };
46
+ var JSON_CONTENT_TYPES = ["text/json", "application/json"];
47
+ export var inferResponseType = function (contentType) {
48
+ var contentTypePart = contentType === null || contentType === void 0 ? void 0 : contentType.split(";")[0].trim();
49
+ if (contentTypePart && JSON_CONTENT_TYPES.includes(contentTypePart)) {
50
+ return "json";
51
+ }
52
+ return "text";
53
+ };
@@ -5,6 +5,7 @@ export declare const RequestErrorCode: {
5
5
  readonly REQUEST_NETWORK_ERROR: "request/network-error";
6
6
  readonly REQUEST_INVALID_STATUS: "request/invalid-status";
7
7
  readonly REQUEST_INVALID_CONFIG: "request/invalid-config";
8
+ readonly REQUEST_MISMATCH_RESPONSE_TYPE: "request/mismatch-response-type";
8
9
  };
9
10
  export declare type RequestErrorCode = EnumOf<typeof RequestErrorCode>;
10
11
  export interface RequestError extends Error {
@@ -3,6 +3,7 @@ export var RequestErrorCode = {
3
3
  REQUEST_NETWORK_ERROR: "request/network-error",
4
4
  REQUEST_INVALID_STATUS: "request/invalid-status",
5
5
  REQUEST_INVALID_CONFIG: "request/invalid-config",
6
+ REQUEST_MISMATCH_RESPONSE_TYPE: "request/mismatch-response-type",
6
7
  };
7
8
  export var isRequestError = function (error) {
8
9
  return "isRequestError" in error;
package/esm/Requester.js CHANGED
@@ -57,7 +57,7 @@ var locks = {};
57
57
  var runningOperations = {};
58
58
  var MOCK_REQUEST_BACKEND = new MockRequestBackend();
59
59
  export var submit = function (host, config, mocking) { return __awaiter(void 0, void 0, void 0, function () {
60
- var computedConfig, backend, context, key, sameRequest, lock, lockedContext, response, successEventResult, error_1;
60
+ var computedConfig, backend, context, key, lock, lockedContext, response, successEventResult, error_1;
61
61
  return __generator(this, function (_a) {
62
62
  switch (_a.label) {
63
63
  case 0:
@@ -68,10 +68,6 @@ export var submit = function (host, config, mocking) { return __awaiter(void 0,
68
68
  }
69
69
  context = new RequestContext(backend, host, computedConfig, host.computePath(host.path, config), mocking);
70
70
  key = context.key;
71
- sameRequest = runningOperations[key];
72
- if (sameRequest) {
73
- return [2 /*return*/, sameRequest];
74
- }
75
71
  lock = (context.computedConfig || {}).lock;
76
72
  if (lock) {
77
73
  lockedContext = locks[lock];
@@ -118,6 +114,7 @@ var makeRequest = function (context) { return __awaiter(void 0, void 0, void 0,
118
114
  if (process.env.NODE_ENV === "development") {
119
115
  if (Api.isRequestBackendDefault() && !defaultBackendMessageShown) {
120
116
  defaultBackendMessageShown = true;
117
+ // eslint-disable-next-line
121
118
  console.warn("[api-def] Using default fetch backend, you can use a different one with 'setRequestBackend()' (dev only message)");
122
119
  }
123
120
  }
@@ -0,0 +1,5 @@
1
+ import Endpoint from "./Endpoint";
2
+ export declare type ResponseOf<E extends Endpoint<any, any, any, any>> = E extends Endpoint<infer R, any, any, any> ? R : never;
3
+ export declare type ParamsOf<E extends Endpoint<any, any, any, any>> = E extends Endpoint<any, infer P, any, any> ? P : never;
4
+ export declare type QueryOf<E extends Endpoint<any, any, any, any>> = E extends Endpoint<any, any, infer Q, any> ? Q : never;
5
+ export declare type BodyOf<E extends Endpoint<any, any, any, any>> = E extends Endpoint<any, any, any, infer B> ? B : never;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,10 +1,11 @@
1
1
  import RequestBackend, { RequestBackendErrorInfo, RequestOperation } from "./RequestBackend";
2
2
  import { ApiResponse } from "../ApiTypes";
3
- import type { AxiosError, AxiosResponse, AxiosStatic } from "axios";
3
+ import type { AxiosError, AxiosResponse } from "axios";
4
4
  import RequestContext from "../RequestContext";
5
5
  export declare const isAxiosError: (error: Error) => error is AxiosError<any>;
6
6
  export default class AxiosRequestBackend implements RequestBackend<AxiosResponse> {
7
- constructor(axiosLibrary: AxiosStatic);
7
+ readonly id = "axios";
8
+ constructor(axiosLibrary: any);
8
9
  extractResponseFromError(error: Error): Promise<AxiosResponse | null | undefined>;
9
10
  convertResponse<T>(context: RequestContext, response: AxiosResponse): Promise<ApiResponse<T>>;
10
11
  makeRequest(context: RequestContext): RequestOperation<AxiosResponse>;
@@ -34,12 +34,16 @@ 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";
37
39
  var axios;
38
40
  export var isAxiosError = function (error) {
39
41
  return "isAxiosError" in error;
40
42
  };
41
43
  var AxiosRequestBackend = /** @class */ (function () {
44
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
42
45
  function AxiosRequestBackend(axiosLibrary) {
46
+ this.id = "axios";
43
47
  axios = axiosLibrary;
44
48
  }
45
49
  AxiosRequestBackend.prototype.extractResponseFromError = function (error) {
@@ -54,7 +58,18 @@ var AxiosRequestBackend = /** @class */ (function () {
54
58
  };
55
59
  AxiosRequestBackend.prototype.convertResponse = function (context, response) {
56
60
  return __awaiter(this, void 0, void 0, function () {
61
+ var contentType, inferredResponseType;
57
62
  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
+ response: response,
71
+ });
72
+ }
58
73
  return [2 /*return*/, response];
59
74
  });
60
75
  });
@@ -73,6 +88,7 @@ var AxiosRequestBackend = /** @class */ (function () {
73
88
  cancelToken: new axios.CancelToken(function (cancellerFunc) {
74
89
  canceler = cancellerFunc;
75
90
  }),
91
+ paramsSerializer: context.computedConfig.queryParser,
76
92
  });
77
93
  return {
78
94
  promise: promise,
@@ -2,12 +2,11 @@ import RequestBackend, { RequestBackendErrorInfo, RequestOperation } from "./Req
2
2
  import { ApiResponse } from "../ApiTypes";
3
3
  import RequestContext from "../RequestContext";
4
4
  import { Fetch } from "../Utils";
5
- import { ResponseType } from "../ApiConstants";
6
5
  export default class FetchRequestBackend implements RequestBackend<Response> {
7
6
  fetch: (((input: RequestInfo, init?: RequestInit | undefined) => Promise<Response>) & typeof fetch) | undefined;
7
+ readonly id = "fetch";
8
8
  constructor(fetchLibrary?: Fetch);
9
9
  extractResponseFromError(error: Error): Promise<Response | null | undefined>;
10
- inferResponseType(response: Response): ResponseType;
11
10
  convertResponse<T>(context: RequestContext, response: Response & {
12
11
  __text?: string;
13
12
  }, error?: boolean): Promise<ApiResponse<T>>;
@@ -52,6 +52,8 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
52
52
  import * as Utils from "../Utils";
53
53
  import { getGlobalFetch } from "../Utils";
54
54
  import { ResponseType } from "../ApiConstants";
55
+ import { inferResponseType } from "../ApiUtils";
56
+ import { convertToRequestError, RequestErrorCode } from "../RequestError";
55
57
  var FetchError = /** @class */ (function (_super) {
56
58
  __extends(FetchError, _super);
57
59
  function FetchError() {
@@ -62,6 +64,7 @@ var FetchError = /** @class */ (function (_super) {
62
64
  var FetchRequestBackend = /** @class */ (function () {
63
65
  function FetchRequestBackend(fetchLibrary) {
64
66
  this.fetch = getGlobalFetch();
67
+ this.id = "fetch";
65
68
  if (fetchLibrary !== undefined) {
66
69
  this.fetch = fetchLibrary;
67
70
  }
@@ -78,30 +81,38 @@ var FetchRequestBackend = /** @class */ (function () {
78
81
  });
79
82
  });
80
83
  };
81
- FetchRequestBackend.prototype.inferResponseType = function (response) {
82
- var contentType = response.headers.get("Content-Type");
83
- if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith("json")) {
84
- return "json";
85
- }
86
- return "text";
87
- };
88
84
  FetchRequestBackend.prototype.convertResponse = function (context, response, error) {
89
85
  return __awaiter(this, void 0, void 0, function () {
90
- var data, responseType, _a, error_1, status;
86
+ var data, contentType, inferredResponseType, responseType, _a, status, headers, error_1;
91
87
  return __generator(this, function (_b) {
92
88
  switch (_b.label) {
93
89
  case 0:
94
- responseType = error ? this.inferResponseType(response) : context.responseType;
95
- _b.label = 1;
96
- case 1:
97
- _b.trys.push([1, 8, , 9]);
98
- if (!!response.__text) return [3 /*break*/, 3];
90
+ contentType = response.headers.get("Content-Type");
91
+ inferredResponseType = inferResponseType(contentType);
92
+ responseType = error ? inferredResponseType : context.responseType;
93
+ if (!!response.__text) return [3 /*break*/, 2];
99
94
  _a = response;
100
95
  return [4 /*yield*/, response.clone().text()];
101
- case 2:
96
+ case 1:
102
97
  _a.__text = _b.sent();
98
+ _b.label = 2;
99
+ case 2:
100
+ status = response.status, headers = response.headers;
101
+ // expand to array buffer once we support that in inferResponseType
102
+ if (inferredResponseType === "text" && context.responseType === "json") {
103
+ throw convertToRequestError({
104
+ error: new Error("[api-def] Expected '" + context.responseType + "' response, got '" + inferredResponseType + "' (from 'Content-Type' of '" + contentType + "')"),
105
+ code: RequestErrorCode.REQUEST_MISMATCH_RESPONSE_TYPE,
106
+ response: {
107
+ data: response.__text,
108
+ status: status,
109
+ headers: headers,
110
+ },
111
+ });
112
+ }
103
113
  _b.label = 3;
104
114
  case 3:
115
+ _b.trys.push([3, 8, , 9]);
105
116
  if (!(responseType === ResponseType.Text)) return [3 /*break*/, 4];
106
117
  data = response.__text;
107
118
  return [3 /*break*/, 7];
@@ -122,13 +133,11 @@ var FetchRequestBackend = /** @class */ (function () {
122
133
  throw Object.assign(new Error("[api-def] Invalid '" + context.responseType + "' response, got: '" + response.__text + "'"), {
123
134
  response: response,
124
135
  });
125
- case 9:
126
- status = response.status;
127
- return [2 /*return*/, {
128
- data: data,
129
- status: status,
130
- headers: response.headers,
131
- }];
136
+ case 9: return [2 /*return*/, {
137
+ data: data,
138
+ status: status,
139
+ headers: headers,
140
+ }];
132
141
  }
133
142
  });
134
143
  });
@@ -145,12 +154,21 @@ var FetchRequestBackend = /** @class */ (function () {
145
154
  path += context.computedPath.startsWith("/")
146
155
  ? context.computedPath.substring(1)
147
156
  : context.computedPath;
148
- var url = new URL(path);
157
+ var origin = undefined;
158
+ if (typeof window !== "undefined") {
159
+ origin = window.origin;
160
+ }
161
+ var url = new URL(path, origin);
149
162
  if (computedConfig.query) {
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()) || "");
163
+ if (context.computedConfig.queryParser) {
164
+ url.search = context.computedConfig.queryParser(computedConfig.query);
165
+ }
166
+ else {
167
+ var queryKeys = Object.keys(computedConfig.query);
168
+ for (var i = 0; i < queryKeys.length; i++) {
169
+ var key = queryKeys[i];
170
+ url.searchParams.append(key, ((_a = computedConfig.query[key]) === null || _a === void 0 ? void 0 : _a.toString()) || "");
171
+ }
154
172
  }
155
173
  }
156
174
  // abort controller is a newer feature than fetch
@@ -193,7 +211,6 @@ var FetchRequestBackend = /** @class */ (function () {
193
211
  canceler: abortSignal
194
212
  ? function () { return !responded && abortController.abort(); }
195
213
  : function () {
196
- console.warn("Request aborted");
197
214
  softAbort = true;
198
215
  },
199
216
  };
@@ -2,6 +2,7 @@ import RequestBackend, { RequestBackendErrorInfo, RequestOperation } from "./Req
2
2
  import { ApiResponse } from "../ApiTypes";
3
3
  import RequestContext from "../RequestContext";
4
4
  export default class MockRequestBackend implements RequestBackend<ApiResponse> {
5
+ readonly id = "mock";
5
6
  convertResponse<T>(context: RequestContext, response: ApiResponse, error?: boolean): Promise<ApiResponse<T>>;
6
7
  extractResponseFromError(error: Error): Promise<ApiResponse | null | undefined>;
7
8
  private runRequest;
@@ -39,6 +39,7 @@ import { delayThenReturn, randInt } from "../Utils";
39
39
  import { convertToRequestError, RequestErrorCode } from "../RequestError";
40
40
  var MockRequestBackend = /** @class */ (function () {
41
41
  function MockRequestBackend() {
42
+ this.id = "mock";
42
43
  }
43
44
  MockRequestBackend.prototype.convertResponse = function (context, response, error) {
44
45
  return __awaiter(this, void 0, void 0, function () {
@@ -8,6 +8,7 @@ export interface RequestBackendErrorInfo {
8
8
  code: string;
9
9
  }
10
10
  export default interface RequestBackend<R = any> {
11
+ readonly id: string;
11
12
  makeRequest(context: RequestContext): RequestOperation<R>;
12
13
  convertResponse<T>(context: RequestContext, response: R, error?: boolean): Promise<ApiResponse<T>>;
13
14
  extractResponseFromError(error: Error): Promise<R | null | undefined>;
package/esm/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./Api";
2
2
  export * from "./ApiTypes";
3
+ export * from "./UtilTypes";
3
4
  export * from "./ApiConstants";
4
5
  export { isRequestError } from "./RequestError";
5
6
  export { clearCache, setCacheBackend } from "./cache/Caching";
package/esm/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  "use strict";
3
3
  export * from "./Api";
4
4
  export * from "./ApiTypes";
5
+ export * from "./UtilTypes";
5
6
  export * from "./ApiConstants";
6
7
  export { isRequestError } from "./RequestError";
7
8
  export { clearCache, setCacheBackend } from "./cache/Caching";
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "api-def",
3
- "version": "0.5.0",
3
+ "version": "0.6.0-alpha10",
4
4
  "description": "Typed API definitions with middleware support",
5
5
  "main": "cjs/index.js",
6
6
  "types": "esm/index.d.ts",
7
7
  "module": "esm/index.js",
8
8
  "sideEffects": false,
9
- "type": "module",
10
9
  "scripts": {
11
10
  "test": "npm run test:types && npm run test:lint && npm run test:unit",
12
11
  "test:unit": "cross-env NODE_ENV=test jest",
package/CHANGELOG.md DELETED
@@ -1,55 +0,0 @@
1
- # 0.5.0
2
-
3
- ## Breaking Changes
4
-
5
- - Undocumented mocking API has been overhauled
6
-
7
- ## Changes
8
-
9
- - Add mocking!
10
- - Restructured mocking so that mocks are defined on the endpoint level (documentation updated)
11
- - Add `acceptableStatus` to specify which status codes are considered successful
12
- - Extend retry logic to use exponential back-off, rather than retrying immediately
13
- - Support for additional hot-request methods:
14
- - PUT, DELETE
15
-
16
- # 0.4.1
17
-
18
- - fix fetch backend not working with unbound fetch
19
- - add better error handling on parse
20
-
21
- # 0.4.0
22
-
23
- ## Breaking Changes
24
-
25
- - Move config values from options object up one layer
26
- - In config `retries` -> `retry`
27
- - `defaults` -> `config`
28
-
29
- ## Changes
30
-
31
- - Remove need for enum imports
32
- - Make `name` and `description` optional in endpoint definition
33
-
34
- # 0.3.11
35
-
36
- ## Changes
37
-
38
- - Make fetch backend default if fetch is present
39
- - Fix fetch backends text response type support
40
-
41
- # 0.3.0
42
-
43
- ## Features
44
-
45
- - allow defaults to be a function on api
46
- - add content type header in fetch backend to match axios backend
47
- - remove `flatted`
48
- - polyfill object.assign for ie
49
- - change retry logic so that if a middleware event responds with Retry we always attempt a retry
50
-
51
- # 0.2.5
52
-
53
- ## Changes
54
-
55
- - Remove `window` usages to allow node support