api-def 0.14.0-alpha.1 → 0.14.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/cjs/ApiTypes.d.ts CHANGED
@@ -59,7 +59,7 @@ export type RequestConfig<TParams extends Params | undefined = Params | undefine
59
59
  body?: never;
60
60
  } : {
61
61
  body: TBody;
62
- }) & ({} extends TState ? {
62
+ }) & (Record<never, never> extends TState ? {
63
63
  state?: TState;
64
64
  } : {
65
65
  state: TState;
@@ -80,13 +80,13 @@ interface BaseEventResult<T extends EventResultType> {
80
80
  export type ResponseEventResult<R> = BaseEventResult<"respond"> & {
81
81
  response: ApiResponse<R>;
82
82
  };
83
- export type RetryEventResult<R> = BaseEventResult<"retry">;
83
+ export type RetryEventResult<_R> = BaseEventResult<"retry">;
84
84
  export type EventResult<R> = ResponseEventResult<R> | RetryEventResult<R>;
85
- export type RequestEventHandler<R> = (context: RequestContext<R>) => EventResult<R> | void | Promise<EventResult<R> | void>;
85
+ export type RequestEventHandler<R> = (context: RequestContext<R>) => EventResult<R> | undefined | Promise<EventResult<R> | undefined>;
86
86
  export type RequestEventHandlers<R> = {
87
87
  [key in RequestEvent]?: Array<RequestEventHandler<R>>;
88
88
  };
89
- export type RequestMiddleware<O = undefined> = {
89
+ export type RequestMiddleware<_O = undefined> = {
90
90
  [key in RequestEvent]?: RequestEventHandler<any> | undefined | false;
91
91
  };
92
92
  export interface RequestStats {
package/cjs/Endpoint.d.ts CHANGED
@@ -12,7 +12,7 @@ export interface EndpointResolveUrlOptions<TParams extends Params | undefined, T
12
12
  export interface EndpointResolvePathOptions<TParams extends Params | undefined> {
13
13
  params?: (TParams extends string | symbol | number ? Record<TParams, string> : never) | undefined;
14
14
  }
15
- export interface EndpointOptions<TResponse, TParams extends Params | undefined, TQuery extends Query | undefined, TBody extends Body | undefined, TState extends State = State, TPath extends string = string, TRequestHeaders extends RawHeaders | undefined = RawHeaders | undefined, TResponseHeaders extends RawHeaders | undefined = RawHeaders | undefined> {
15
+ export interface EndpointOptions<TResponse, TParams extends Params | undefined, TQuery extends Query | undefined, TBody extends Body | undefined, TState extends State = State, TPath extends string = string, _TRequestHeaders extends RawHeaders | undefined = RawHeaders | undefined, _TResponseHeaders extends RawHeaders | undefined = RawHeaders | undefined> {
16
16
  readonly id: string;
17
17
  readonly method: RequestMethod;
18
18
  readonly path: TPath;
@@ -2,15 +2,15 @@ import type { ApiResponse, Body, Params, Query, RawHeaders, State } from "./ApiT
2
2
  export interface ApiMockingConfig {
3
3
  enabled: boolean | (() => boolean);
4
4
  }
5
- export interface MockRequest<TResponse = any, TParams extends Params | undefined = Params | undefined, TQuery extends Query | undefined = Query | undefined, TBody extends Body | undefined = Body | undefined, TState extends State = State> {
6
- params: TParams extends Params ? Record<TParams, string> : {};
5
+ export interface MockRequest<_TResponse = any, TParams extends Params | undefined = Params | undefined, TQuery extends Query | undefined = Query | undefined, TBody extends Body | undefined = Body | undefined, TState extends State = State> {
6
+ params: TParams extends Params ? Record<TParams, string> : Record<keyof any, never>;
7
7
  body: TBody;
8
8
  query: TQuery;
9
9
  headers: Readonly<RawHeaders>;
10
10
  url: string;
11
11
  state: TState;
12
12
  }
13
- export interface MockResponse<TResponse = any, TParams extends Params | undefined = Params | undefined, TQuery extends Query | undefined = Query | undefined, TBody extends Body | undefined = Body | undefined, TState extends State = State> {
13
+ export interface MockResponse<TResponse = any, _TParams extends Params | undefined = Params | undefined, _TQuery extends Query | undefined = Query | undefined, _TBody extends Body | undefined = Body | undefined, _TState extends State = State> {
14
14
  statusCode: number;
15
15
  response: TResponse | undefined;
16
16
  headers: RawHeaders;
@@ -53,7 +53,7 @@ const convertToRequestError = (config) => {
53
53
  value: `A ${context.method.toUpperCase()} request to '${context.requestUrl.href}' failed${response?.status ? ` with status code ${response.status}` : ""} [${code}]: ${resultError.message}`,
54
54
  });
55
55
  }
56
- catch (e) {
56
+ catch (_e) {
57
57
  // ignore
58
58
  }
59
59
  resultError.config = undefined;
package/cjs/Requester.js CHANGED
@@ -199,7 +199,7 @@ const parseResponse = async (context, response, error) => {
199
199
  const decodedData = (response.data = (0, TextDecoding_1.textDecode)(data));
200
200
  response.data = JSON.parse(decodedData);
201
201
  }
202
- catch (e) {
202
+ catch (_e) {
203
203
  throw (0, RequestError_1.convertToRequestError)({
204
204
  error: new Error(`[api-def] Expected '${context.responseType}' response, got '${inferredResponseType}' (from 'Content-Type' of '${contentType}')`),
205
205
  code: RequestError_1.RequestErrorCode.REQUEST_MISMATCH_RESPONSE_TYPE,
@@ -1 +1 @@
1
- export declare const textDecode: (inputArrayOrBuffer: any, options?: any) => string;
1
+ export declare const textDecode: (inputArrayOrBuffer: any, _options?: any) => string;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.textDecode = void 0;
4
4
  // polyfill from https://github.com/anonyco/FastestSmallestTextEncoderDecoder/blob/master/EncoderDecoderTogether.src.js
5
- const textDecode = (inputArrayOrBuffer, options) => {
5
+ const textDecode = (inputArrayOrBuffer, _options) => {
6
6
  if (typeof TextDecoder !== "undefined") {
7
7
  return new TextDecoder("utf-8").decode(inputArrayOrBuffer);
8
8
  }
package/cjs/Utils.d.ts CHANGED
@@ -14,4 +14,6 @@ export declare const noop: () => void;
14
14
  export declare const delayThenReturn: <T>(value: T, delayMs: number) => Promise<T>;
15
15
  export declare const randInt: (min: number, max: number) => number;
16
16
  export declare const isFormData: (value: any) => value is FormData;
17
+ export declare const isFormDataLike: (value: any) => value is FormData;
17
18
  export declare const toFormData: (body: any) => FormData;
19
+ export declare const assertFetchCompatibleFormData: (body: FormData) => void;
package/cjs/Utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toFormData = exports.isFormData = exports.randInt = exports.delayThenReturn = exports.noop = exports.getGlobalFetch = exports.getGlobal = exports.padNumber = exports.assign = void 0;
3
+ exports.assertFetchCompatibleFormData = exports.toFormData = exports.isFormDataLike = exports.isFormData = exports.randInt = exports.delayThenReturn = exports.noop = exports.getGlobalFetch = exports.getGlobal = exports.padNumber = exports.assign = void 0;
4
4
  // polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
5
5
  const hasOwn = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
6
6
  exports.assign = Object.assign ||
@@ -76,8 +76,16 @@ const isFormData = (value) => {
76
76
  return typeof FormData !== "undefined" && value instanceof FormData;
77
77
  };
78
78
  exports.isFormData = isFormData;
79
+ const isFormDataLike = (value) => {
80
+ return value !== null && typeof value === "object" && typeof value.append === "function";
81
+ };
82
+ exports.isFormDataLike = isFormDataLike;
79
83
  const isPlainObject = (value) => {
80
- return Object.prototype.toString.call(value) === "[object Object]";
84
+ if (Object.prototype.toString.call(value) !== "[object Object]") {
85
+ return false;
86
+ }
87
+ const prototype = Object.getPrototypeOf(value);
88
+ return prototype === Object.prototype || prototype === null;
81
89
  };
82
90
  const isBlob = (value) => {
83
91
  return typeof Blob !== "undefined" && value instanceof Blob;
@@ -98,17 +106,23 @@ const toFormDataValue = (value) => {
98
106
  return String(value);
99
107
  };
100
108
  const toFormData = (body) => {
101
- if ((0, exports.isFormData)(body)) {
109
+ if ((0, exports.isFormDataLike)(body)) {
102
110
  return body;
103
111
  }
112
+ if (!isPlainObject(body)) {
113
+ throw new Error("[api-def] multipart/form-data body must be a plain object or FormData");
114
+ }
115
+ if (typeof FormData === "undefined") {
116
+ throw new Error("[api-def] multipart/form-data requires a FormData implementation");
117
+ }
104
118
  const formData = new FormData();
105
119
  const appendValue = (key, value) => {
106
120
  if (value === undefined || value === null) {
107
121
  return;
108
122
  }
109
123
  if (Array.isArray(value)) {
110
- for (const item of value) {
111
- appendValue(key, item);
124
+ for (const [index, item] of value.entries()) {
125
+ appendValue(`${key}.${index}`, item);
112
126
  }
113
127
  return;
114
128
  }
@@ -120,11 +134,31 @@ const toFormData = (body) => {
120
134
  }
121
135
  formData.append(key, toFormDataValue(value));
122
136
  };
123
- if (isPlainObject(body)) {
124
- for (const key of Object.keys(body)) {
125
- appendValue(key, body[key]);
126
- }
137
+ for (const key of Object.keys(body)) {
138
+ appendValue(key, body[key]);
127
139
  }
128
140
  return formData;
129
141
  };
130
142
  exports.toFormData = toFormData;
143
+ const assertFetchCompatibleFormData = (body) => {
144
+ if (typeof Request === "undefined") {
145
+ throw new Error("[api-def] multipart/form-data requires a fetch-compatible Request implementation");
146
+ }
147
+ let request;
148
+ try {
149
+ request = new Request("https://api-def.local", {
150
+ method: "POST",
151
+ body: body,
152
+ });
153
+ }
154
+ catch (error) {
155
+ throw Object.assign(new Error("[api-def] multipart/form-data requires a fetch-compatible FormData implementation"), {
156
+ cause: error,
157
+ });
158
+ }
159
+ const contentType = request.headers.get("content-type");
160
+ if (!contentType?.toLowerCase().startsWith("multipart/form-data")) {
161
+ throw new Error("[api-def] multipart/form-data requires a fetch-compatible FormData implementation");
162
+ }
163
+ };
164
+ exports.assertFetchCompatibleFormData = assertFetchCompatibleFormData;
@@ -10,5 +10,5 @@ export default class AxiosRequestBackend implements RequestBackend<AxiosResponse
10
10
  extractResponseFromError(error: Error): Promise<AxiosResponse | null | undefined>;
11
11
  convertResponse<T>(context: RequestContext, response: AxiosResponse): Promise<ConvertedApiResponse<T>>;
12
12
  makeRequest(context: RequestContext): RequestOperation<AxiosResponse>;
13
- getErrorInfo(error: Error, response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
13
+ getErrorInfo(_error: Error, _response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
14
14
  }
@@ -104,7 +104,7 @@ class AxiosRequestBackend {
104
104
  canceler: () => canceler?.(),
105
105
  };
106
106
  }
107
- getErrorInfo(error, response) {
107
+ getErrorInfo(_error, _response) {
108
108
  return undefined;
109
109
  }
110
110
  }
@@ -12,5 +12,5 @@ export default class FetchRequestBackend implements RequestBackend<Response> {
12
12
  __text?: string;
13
13
  }): Promise<ConvertedApiResponse<T>>;
14
14
  makeRequest(context: RequestContext): RequestOperation<Response>;
15
- getErrorInfo(error: Error, response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
15
+ getErrorInfo(_error: Error, _response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
16
16
  }
@@ -111,7 +111,10 @@ class FetchRequestBackend {
111
111
  let softAbort = false;
112
112
  let responded = false;
113
113
  const body = context.getParsedBody();
114
- const bodyJsonify = body !== null && typeof body === "object" && !Utils.isFormData(body) && !(body instanceof URLSearchParams);
114
+ const bodyJsonify = body !== null && typeof body === "object" && !Utils.isFormDataLike(body) && !(body instanceof URLSearchParams);
115
+ if (context.validation.bodyEncoding === "multipart/form-data" && Utils.isFormDataLike(body)) {
116
+ Utils.assertFetchCompatibleFormData(body);
117
+ }
115
118
  const headers = Utils.assign({
116
119
  // logic from axios
117
120
  "Content-Type": bodyJsonify ? "application/json;charset=utf-8" : undefined,
@@ -161,7 +164,7 @@ class FetchRequestBackend {
161
164
  },
162
165
  };
163
166
  }
164
- getErrorInfo(error, response) {
167
+ getErrorInfo(_error, _response) {
165
168
  return undefined;
166
169
  }
167
170
  }
@@ -4,9 +4,9 @@ import type RequestBackend from "./RequestBackend";
4
4
  import type { RequestBackendErrorInfo, RequestOperation } from "./RequestBackend";
5
5
  export default class MockRequestBackend implements RequestBackend<ApiResponse> {
6
6
  readonly id = "mock";
7
- convertResponse<T>(context: RequestContext, response: ApiResponse, error?: boolean): Promise<ApiResponse<T>>;
7
+ convertResponse<T>(_context: RequestContext, response: ApiResponse, _error?: boolean): Promise<ApiResponse<T>>;
8
8
  extractResponseFromError(error: Error): Promise<ApiResponse | null | undefined>;
9
9
  private runRequest;
10
10
  makeRequest(context: RequestContext): RequestOperation<ApiResponse>;
11
- getErrorInfo(error: Error, response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
11
+ getErrorInfo(_error: Error, _response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
12
12
  }
@@ -41,7 +41,7 @@ class MockRequestBackend {
41
41
  constructor() {
42
42
  this.id = "mock";
43
43
  }
44
- async convertResponse(context, response, error) {
44
+ async convertResponse(_context, response, _error) {
45
45
  return response;
46
46
  }
47
47
  async extractResponseFromError(error) {
@@ -157,7 +157,7 @@ class MockRequestBackend {
157
157
  promise: this.runRequest(context),
158
158
  };
159
159
  }
160
- getErrorInfo(error, response) {
160
+ getErrorInfo(_error, _response) {
161
161
  return undefined;
162
162
  }
163
163
  }
@@ -38,7 +38,7 @@ RetryOperation.prototype.retry = function (err) {
38
38
  if (!err) {
39
39
  return false;
40
40
  }
41
- const currentTime = new Date().getTime();
41
+ const currentTime = Date.now();
42
42
  if (err && currentTime - this._operationStart >= this._maxRetryTime) {
43
43
  this._errors.push(err);
44
44
  this._errors.unshift(new Error("RetryOperation timeout occurred"));
@@ -88,7 +88,7 @@ RetryOperation.prototype.attempt = function (fn, timeoutOps) {
88
88
  this._operationTimeoutCb();
89
89
  }, this._operationTimeout);
90
90
  }
91
- this._operationStart = new Date().getTime();
91
+ this._operationStart = Date.now();
92
92
  this._fn(this._attempts);
93
93
  };
94
94
  RetryOperation.prototype.try = function (fn) {
package/esm/ApiTypes.d.ts CHANGED
@@ -59,7 +59,7 @@ export type RequestConfig<TParams extends Params | undefined = Params | undefine
59
59
  body?: never;
60
60
  } : {
61
61
  body: TBody;
62
- }) & ({} extends TState ? {
62
+ }) & (Record<never, never> extends TState ? {
63
63
  state?: TState;
64
64
  } : {
65
65
  state: TState;
@@ -80,13 +80,13 @@ interface BaseEventResult<T extends EventResultType> {
80
80
  export type ResponseEventResult<R> = BaseEventResult<"respond"> & {
81
81
  response: ApiResponse<R>;
82
82
  };
83
- export type RetryEventResult<R> = BaseEventResult<"retry">;
83
+ export type RetryEventResult<_R> = BaseEventResult<"retry">;
84
84
  export type EventResult<R> = ResponseEventResult<R> | RetryEventResult<R>;
85
- export type RequestEventHandler<R> = (context: RequestContext<R>) => EventResult<R> | void | Promise<EventResult<R> | void>;
85
+ export type RequestEventHandler<R> = (context: RequestContext<R>) => EventResult<R> | undefined | Promise<EventResult<R> | undefined>;
86
86
  export type RequestEventHandlers<R> = {
87
87
  [key in RequestEvent]?: Array<RequestEventHandler<R>>;
88
88
  };
89
- export type RequestMiddleware<O = undefined> = {
89
+ export type RequestMiddleware<_O = undefined> = {
90
90
  [key in RequestEvent]?: RequestEventHandler<any> | undefined | false;
91
91
  };
92
92
  export interface RequestStats {
package/esm/Endpoint.d.ts CHANGED
@@ -12,7 +12,7 @@ export interface EndpointResolveUrlOptions<TParams extends Params | undefined, T
12
12
  export interface EndpointResolvePathOptions<TParams extends Params | undefined> {
13
13
  params?: (TParams extends string | symbol | number ? Record<TParams, string> : never) | undefined;
14
14
  }
15
- export interface EndpointOptions<TResponse, TParams extends Params | undefined, TQuery extends Query | undefined, TBody extends Body | undefined, TState extends State = State, TPath extends string = string, TRequestHeaders extends RawHeaders | undefined = RawHeaders | undefined, TResponseHeaders extends RawHeaders | undefined = RawHeaders | undefined> {
15
+ export interface EndpointOptions<TResponse, TParams extends Params | undefined, TQuery extends Query | undefined, TBody extends Body | undefined, TState extends State = State, TPath extends string = string, _TRequestHeaders extends RawHeaders | undefined = RawHeaders | undefined, _TResponseHeaders extends RawHeaders | undefined = RawHeaders | undefined> {
16
16
  readonly id: string;
17
17
  readonly method: RequestMethod;
18
18
  readonly path: TPath;
@@ -2,15 +2,15 @@ import type { ApiResponse, Body, Params, Query, RawHeaders, State } from "./ApiT
2
2
  export interface ApiMockingConfig {
3
3
  enabled: boolean | (() => boolean);
4
4
  }
5
- export interface MockRequest<TResponse = any, TParams extends Params | undefined = Params | undefined, TQuery extends Query | undefined = Query | undefined, TBody extends Body | undefined = Body | undefined, TState extends State = State> {
6
- params: TParams extends Params ? Record<TParams, string> : {};
5
+ export interface MockRequest<_TResponse = any, TParams extends Params | undefined = Params | undefined, TQuery extends Query | undefined = Query | undefined, TBody extends Body | undefined = Body | undefined, TState extends State = State> {
6
+ params: TParams extends Params ? Record<TParams, string> : Record<keyof any, never>;
7
7
  body: TBody;
8
8
  query: TQuery;
9
9
  headers: Readonly<RawHeaders>;
10
10
  url: string;
11
11
  state: TState;
12
12
  }
13
- export interface MockResponse<TResponse = any, TParams extends Params | undefined = Params | undefined, TQuery extends Query | undefined = Query | undefined, TBody extends Body | undefined = Body | undefined, TState extends State = State> {
13
+ export interface MockResponse<TResponse = any, _TParams extends Params | undefined = Params | undefined, _TQuery extends Query | undefined = Query | undefined, _TBody extends Body | undefined = Body | undefined, _TState extends State = State> {
14
14
  statusCode: number;
15
15
  response: TResponse | undefined;
16
16
  headers: RawHeaders;
@@ -49,7 +49,7 @@ export const convertToRequestError = (config) => {
49
49
  value: `A ${context.method.toUpperCase()} request to '${context.requestUrl.href}' failed${response?.status ? ` with status code ${response.status}` : ""} [${code}]: ${resultError.message}`,
50
50
  });
51
51
  }
52
- catch (e) {
52
+ catch (_e) {
53
53
  // ignore
54
54
  }
55
55
  resultError.config = undefined;
package/esm/Requester.js CHANGED
@@ -192,7 +192,7 @@ const parseResponse = async (context, response, error) => {
192
192
  const decodedData = (response.data = textDecode(data));
193
193
  response.data = JSON.parse(decodedData);
194
194
  }
195
- catch (e) {
195
+ catch (_e) {
196
196
  throw convertToRequestError({
197
197
  error: new Error(`[api-def] Expected '${context.responseType}' response, got '${inferredResponseType}' (from 'Content-Type' of '${contentType}')`),
198
198
  code: RequestErrorCode.REQUEST_MISMATCH_RESPONSE_TYPE,
@@ -1 +1 @@
1
- export declare const textDecode: (inputArrayOrBuffer: any, options?: any) => string;
1
+ export declare const textDecode: (inputArrayOrBuffer: any, _options?: any) => string;
@@ -1,5 +1,5 @@
1
1
  // polyfill from https://github.com/anonyco/FastestSmallestTextEncoderDecoder/blob/master/EncoderDecoderTogether.src.js
2
- export const textDecode = (inputArrayOrBuffer, options) => {
2
+ export const textDecode = (inputArrayOrBuffer, _options) => {
3
3
  if (typeof TextDecoder !== "undefined") {
4
4
  return new TextDecoder("utf-8").decode(inputArrayOrBuffer);
5
5
  }
package/esm/Utils.d.ts CHANGED
@@ -14,4 +14,6 @@ export declare const noop: () => void;
14
14
  export declare const delayThenReturn: <T>(value: T, delayMs: number) => Promise<T>;
15
15
  export declare const randInt: (min: number, max: number) => number;
16
16
  export declare const isFormData: (value: any) => value is FormData;
17
+ export declare const isFormDataLike: (value: any) => value is FormData;
17
18
  export declare const toFormData: (body: any) => FormData;
19
+ export declare const assertFetchCompatibleFormData: (body: FormData) => void;
package/esm/Utils.js CHANGED
@@ -66,8 +66,15 @@ export const randInt = (min, max) => {
66
66
  export const isFormData = (value) => {
67
67
  return typeof FormData !== "undefined" && value instanceof FormData;
68
68
  };
69
+ export const isFormDataLike = (value) => {
70
+ return value !== null && typeof value === "object" && typeof value.append === "function";
71
+ };
69
72
  const isPlainObject = (value) => {
70
- return Object.prototype.toString.call(value) === "[object Object]";
73
+ if (Object.prototype.toString.call(value) !== "[object Object]") {
74
+ return false;
75
+ }
76
+ const prototype = Object.getPrototypeOf(value);
77
+ return prototype === Object.prototype || prototype === null;
71
78
  };
72
79
  const isBlob = (value) => {
73
80
  return typeof Blob !== "undefined" && value instanceof Blob;
@@ -88,17 +95,23 @@ const toFormDataValue = (value) => {
88
95
  return String(value);
89
96
  };
90
97
  export const toFormData = (body) => {
91
- if (isFormData(body)) {
98
+ if (isFormDataLike(body)) {
92
99
  return body;
93
100
  }
101
+ if (!isPlainObject(body)) {
102
+ throw new Error("[api-def] multipart/form-data body must be a plain object or FormData");
103
+ }
104
+ if (typeof FormData === "undefined") {
105
+ throw new Error("[api-def] multipart/form-data requires a FormData implementation");
106
+ }
94
107
  const formData = new FormData();
95
108
  const appendValue = (key, value) => {
96
109
  if (value === undefined || value === null) {
97
110
  return;
98
111
  }
99
112
  if (Array.isArray(value)) {
100
- for (const item of value) {
101
- appendValue(key, item);
113
+ for (const [index, item] of value.entries()) {
114
+ appendValue(`${key}.${index}`, item);
102
115
  }
103
116
  return;
104
117
  }
@@ -110,10 +123,29 @@ export const toFormData = (body) => {
110
123
  }
111
124
  formData.append(key, toFormDataValue(value));
112
125
  };
113
- if (isPlainObject(body)) {
114
- for (const key of Object.keys(body)) {
115
- appendValue(key, body[key]);
116
- }
126
+ for (const key of Object.keys(body)) {
127
+ appendValue(key, body[key]);
117
128
  }
118
129
  return formData;
119
130
  };
131
+ export const assertFetchCompatibleFormData = (body) => {
132
+ if (typeof Request === "undefined") {
133
+ throw new Error("[api-def] multipart/form-data requires a fetch-compatible Request implementation");
134
+ }
135
+ let request;
136
+ try {
137
+ request = new Request("https://api-def.local", {
138
+ method: "POST",
139
+ body: body,
140
+ });
141
+ }
142
+ catch (error) {
143
+ throw Object.assign(new Error("[api-def] multipart/form-data requires a fetch-compatible FormData implementation"), {
144
+ cause: error,
145
+ });
146
+ }
147
+ const contentType = request.headers.get("content-type");
148
+ if (!contentType?.toLowerCase().startsWith("multipart/form-data")) {
149
+ throw new Error("[api-def] multipart/form-data requires a fetch-compatible FormData implementation");
150
+ }
151
+ };
@@ -10,5 +10,5 @@ export default class AxiosRequestBackend implements RequestBackend<AxiosResponse
10
10
  extractResponseFromError(error: Error): Promise<AxiosResponse | null | undefined>;
11
11
  convertResponse<T>(context: RequestContext, response: AxiosResponse): Promise<ConvertedApiResponse<T>>;
12
12
  makeRequest(context: RequestContext): RequestOperation<AxiosResponse>;
13
- getErrorInfo(error: Error, response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
13
+ getErrorInfo(_error: Error, _response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
14
14
  }
@@ -100,7 +100,7 @@ export default class AxiosRequestBackend {
100
100
  canceler: () => canceler?.(),
101
101
  };
102
102
  }
103
- getErrorInfo(error, response) {
103
+ getErrorInfo(_error, _response) {
104
104
  return undefined;
105
105
  }
106
106
  }
@@ -12,5 +12,5 @@ export default class FetchRequestBackend implements RequestBackend<Response> {
12
12
  __text?: string;
13
13
  }): Promise<ConvertedApiResponse<T>>;
14
14
  makeRequest(context: RequestContext): RequestOperation<Response>;
15
- getErrorInfo(error: Error, response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
15
+ getErrorInfo(_error: Error, _response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
16
16
  }
@@ -76,7 +76,10 @@ export default class FetchRequestBackend {
76
76
  let softAbort = false;
77
77
  let responded = false;
78
78
  const body = context.getParsedBody();
79
- const bodyJsonify = body !== null && typeof body === "object" && !Utils.isFormData(body) && !(body instanceof URLSearchParams);
79
+ const bodyJsonify = body !== null && typeof body === "object" && !Utils.isFormDataLike(body) && !(body instanceof URLSearchParams);
80
+ if (context.validation.bodyEncoding === "multipart/form-data" && Utils.isFormDataLike(body)) {
81
+ Utils.assertFetchCompatibleFormData(body);
82
+ }
80
83
  const headers = Utils.assign({
81
84
  // logic from axios
82
85
  "Content-Type": bodyJsonify ? "application/json;charset=utf-8" : undefined,
@@ -126,7 +129,7 @@ export default class FetchRequestBackend {
126
129
  },
127
130
  };
128
131
  }
129
- getErrorInfo(error, response) {
132
+ getErrorInfo(_error, _response) {
130
133
  return undefined;
131
134
  }
132
135
  }
@@ -4,9 +4,9 @@ import type RequestBackend from "./RequestBackend.js";
4
4
  import type { RequestBackendErrorInfo, RequestOperation } from "./RequestBackend.js";
5
5
  export default class MockRequestBackend implements RequestBackend<ApiResponse> {
6
6
  readonly id = "mock";
7
- convertResponse<T>(context: RequestContext, response: ApiResponse, error?: boolean): Promise<ApiResponse<T>>;
7
+ convertResponse<T>(_context: RequestContext, response: ApiResponse, _error?: boolean): Promise<ApiResponse<T>>;
8
8
  extractResponseFromError(error: Error): Promise<ApiResponse | null | undefined>;
9
9
  private runRequest;
10
10
  makeRequest(context: RequestContext): RequestOperation<ApiResponse>;
11
- getErrorInfo(error: Error, response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
11
+ getErrorInfo(_error: Error, _response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
12
12
  }
@@ -6,7 +6,7 @@ export default class MockRequestBackend {
6
6
  constructor() {
7
7
  this.id = "mock";
8
8
  }
9
- async convertResponse(context, response, error) {
9
+ async convertResponse(_context, response, _error) {
10
10
  return response;
11
11
  }
12
12
  async extractResponseFromError(error) {
@@ -122,7 +122,7 @@ export default class MockRequestBackend {
122
122
  promise: this.runRequest(context),
123
123
  };
124
124
  }
125
- getErrorInfo(error, response) {
125
+ getErrorInfo(_error, _response) {
126
126
  return undefined;
127
127
  }
128
128
  }
@@ -36,7 +36,7 @@ RetryOperation.prototype.retry = function (err) {
36
36
  if (!err) {
37
37
  return false;
38
38
  }
39
- const currentTime = new Date().getTime();
39
+ const currentTime = Date.now();
40
40
  if (err && currentTime - this._operationStart >= this._maxRetryTime) {
41
41
  this._errors.push(err);
42
42
  this._errors.unshift(new Error("RetryOperation timeout occurred"));
@@ -86,7 +86,7 @@ RetryOperation.prototype.attempt = function (fn, timeoutOps) {
86
86
  this._operationTimeoutCb();
87
87
  }, this._operationTimeout);
88
88
  }
89
- this._operationStart = new Date().getTime();
89
+ this._operationStart = Date.now();
90
90
  this._fn(this._attempts);
91
91
  };
92
92
  RetryOperation.prototype.try = function (fn) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api-def",
3
- "version": "0.14.0-alpha.1",
3
+ "version": "0.14.0",
4
4
  "description": "Typed API definitions with middleware support",
5
5
  "main": "cjs/index.js",
6
6
  "types": "esm/index.d.ts",