api-def 0.13.0 → 0.14.0-alpha.2
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/bin/index.js +93 -93
- package/cjs/ApiTypes.d.ts +2 -1
- package/cjs/EndpointBuilder.d.ts +17 -0
- package/cjs/EndpointBuilder.js +36 -8
- package/cjs/RequestContext.d.ts +1 -0
- package/cjs/RequestContext.js +18 -0
- package/cjs/Requester.js +1 -0
- package/cjs/Utils.d.ts +1 -0
- package/cjs/Utils.js +54 -2
- package/cjs/Validation.d.ts +8 -1
- package/cjs/backend/FetchRequestBackend.js +1 -1
- package/esm/ApiTypes.d.ts +2 -1
- package/esm/EndpointBuilder.d.ts +17 -0
- package/esm/EndpointBuilder.js +36 -8
- package/esm/RequestContext.d.ts +1 -0
- package/esm/RequestContext.js +18 -0
- package/esm/Requester.js +1 -0
- package/esm/Utils.d.ts +1 -0
- package/esm/Utils.js +52 -1
- package/esm/Validation.d.ts +8 -1
- package/esm/backend/FetchRequestBackend.js +1 -1
- package/package.json +2 -1
package/cjs/ApiTypes.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ export type AcceptableStatus = number | [min: number, max: number];
|
|
|
7
7
|
export type RawHeaders = Record<string, string | number | boolean | null | undefined>;
|
|
8
8
|
export type Params = string;
|
|
9
9
|
export type Query = string | undefined | Record<string, any>;
|
|
10
|
-
export type Body = string | number | Record<string, any
|
|
10
|
+
export type Body = string | number | Record<string, any> | FormData;
|
|
11
|
+
export type RequestBodyEncoding = "application/json" | "multipart/form-data" | "application/x-www-form-urlencoded";
|
|
11
12
|
export type State = Record<string, any>;
|
|
12
13
|
export interface ApiResponse<T = any> {
|
|
13
14
|
readonly method: RequestMethod;
|
package/cjs/EndpointBuilder.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Api } from "./Api";
|
|
|
3
3
|
import type { ResponseType } from "./ApiConstants";
|
|
4
4
|
import type { Body, Params, Query, RawHeaders, State } from "./ApiTypes";
|
|
5
5
|
import Endpoint, { type EndpointOptions } from "./Endpoint";
|
|
6
|
+
import type { BodyValidationOptions, ValidationOptions } from "./Validation";
|
|
6
7
|
type DefaultResponseOf<T extends ResponseType | undefined> = T extends "text" ? string : T extends "arraybuffer" ? ArrayBuffer : "stream" extends T ? AsyncIterable<Uint8Array> : unknown;
|
|
7
8
|
export type EndpointBuildOptions<TResponse = unknown, TParams extends Params | undefined = undefined, TQuery extends Query | undefined = undefined, TBody extends Body | undefined = undefined, TState extends State = State, TRequestHeaders extends RawHeaders | undefined = RawHeaders | undefined, TResponseHeaders extends RawHeaders | undefined = RawHeaders | undefined, TPath extends string = string, TResponseType extends ResponseType | undefined = undefined> = Omit<EndpointOptions<TResponse, TParams, TQuery, TBody, TState, TPath, TRequestHeaders, TResponseHeaders>, "validation"> & {
|
|
8
9
|
responseType?: TResponseType;
|
|
@@ -11,10 +12,26 @@ export default class EndpointBuilder<TResponse = unknown, TParams extends Params
|
|
|
11
12
|
private api;
|
|
12
13
|
private readonly validation;
|
|
13
14
|
constructor(api: Api);
|
|
15
|
+
queryOf<TNewQuery extends Query>(options?: ValidationOptions<TNewQuery>): EndpointBuilder<TResponse, TParams, TNewQuery, TBody, TState, TRequestHeaders, TResponseHeaders>;
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Pass `{ schema }` instead.
|
|
18
|
+
*/
|
|
14
19
|
queryOf<TNewQuery extends Query>(schema?: zod.Schema<TNewQuery>): EndpointBuilder<TResponse, TParams, TNewQuery, TBody, TState, TRequestHeaders, TResponseHeaders>;
|
|
15
20
|
paramsOf<TNewParams extends Params>(): EndpointBuilder<TResponse, TNewParams, TQuery, TBody, TState, TRequestHeaders, TResponseHeaders>;
|
|
21
|
+
bodyOf<TNewBody extends Body>(options?: BodyValidationOptions<TNewBody>): EndpointBuilder<TResponse, TParams, TQuery, TNewBody, TState, TRequestHeaders, TResponseHeaders>;
|
|
22
|
+
/**
|
|
23
|
+
* @deprecated Pass `{ schema }` instead.
|
|
24
|
+
*/
|
|
16
25
|
bodyOf<TNewBody extends Body>(schema?: zod.Schema<TNewBody>): EndpointBuilder<TResponse, TParams, TQuery, TNewBody, TState, TRequestHeaders, TResponseHeaders>;
|
|
26
|
+
responseOf<TNewResponse>(options?: ValidationOptions<TNewResponse>): EndpointBuilder<TNewResponse, TParams, TQuery, TBody, TState, TRequestHeaders, TResponseHeaders>;
|
|
27
|
+
/**
|
|
28
|
+
* @deprecated Pass `{ schema }` instead.
|
|
29
|
+
*/
|
|
17
30
|
responseOf<TNewResponse>(schema?: zod.Schema<TNewResponse>): EndpointBuilder<TNewResponse, TParams, TQuery, TBody, TState, TRequestHeaders, TResponseHeaders>;
|
|
31
|
+
stateOf<TNewState extends State>(options?: ValidationOptions<TNewState>): EndpointBuilder<TResponse, TParams, TQuery, TBody, TNewState, TRequestHeaders, TResponseHeaders>;
|
|
32
|
+
/**
|
|
33
|
+
* @deprecated Pass `{ schema }` instead.
|
|
34
|
+
*/
|
|
18
35
|
stateOf<TNewState extends State>(schema?: zod.Schema<TNewState>): EndpointBuilder<TResponse, TParams, TQuery, TBody, TNewState, TRequestHeaders, TResponseHeaders>;
|
|
19
36
|
requestHeadersOf<TNewRequestHeaders extends RawHeaders>(): EndpointBuilder<TResponse, TParams, TQuery, TBody, TState, TNewRequestHeaders, TResponseHeaders>;
|
|
20
37
|
responseHeadersOf<TNewResponseHeaders extends RawHeaders>(): EndpointBuilder<TResponse, TParams, TQuery, TBody, TState, TRequestHeaders, TNewResponseHeaders>;
|
package/cjs/EndpointBuilder.js
CHANGED
|
@@ -4,28 +4,56 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const Endpoint_1 = __importDefault(require("./Endpoint"));
|
|
7
|
+
const isSchema = (value) => {
|
|
8
|
+
return value !== undefined && "parse" in value;
|
|
9
|
+
};
|
|
10
|
+
const warnDeprecatedSchemaArgument = (methodName) => {
|
|
11
|
+
console.warn(`[api-def] ${methodName}(schema) is deprecated. Use ${methodName}({ schema }) instead.`);
|
|
12
|
+
};
|
|
7
13
|
class EndpointBuilder {
|
|
8
14
|
constructor(api) {
|
|
9
15
|
this.validation = {};
|
|
10
16
|
this.api = api;
|
|
11
17
|
}
|
|
12
|
-
queryOf(
|
|
13
|
-
|
|
18
|
+
queryOf(optionsOrSchema) {
|
|
19
|
+
if (optionsOrSchema && isSchema(optionsOrSchema)) {
|
|
20
|
+
warnDeprecatedSchemaArgument("queryOf");
|
|
21
|
+
this.validation.query = optionsOrSchema;
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
this.validation.query = optionsOrSchema?.schema;
|
|
14
25
|
return this;
|
|
15
26
|
}
|
|
16
27
|
paramsOf() {
|
|
17
28
|
return this;
|
|
18
29
|
}
|
|
19
|
-
bodyOf(
|
|
20
|
-
|
|
30
|
+
bodyOf(optionsOrSchema) {
|
|
31
|
+
if (optionsOrSchema && isSchema(optionsOrSchema)) {
|
|
32
|
+
warnDeprecatedSchemaArgument("bodyOf");
|
|
33
|
+
this.validation.body = optionsOrSchema;
|
|
34
|
+
this.validation.bodyEncoding = "application/json";
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
this.validation.body = optionsOrSchema?.schema;
|
|
38
|
+
this.validation.bodyEncoding = optionsOrSchema?.encoding ?? "application/json";
|
|
21
39
|
return this;
|
|
22
40
|
}
|
|
23
|
-
responseOf(
|
|
24
|
-
|
|
41
|
+
responseOf(optionsOrSchema) {
|
|
42
|
+
if (optionsOrSchema && isSchema(optionsOrSchema)) {
|
|
43
|
+
warnDeprecatedSchemaArgument("responseOf");
|
|
44
|
+
this.validation.response = optionsOrSchema;
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
this.validation.response = optionsOrSchema?.schema;
|
|
25
48
|
return this;
|
|
26
49
|
}
|
|
27
|
-
stateOf(
|
|
28
|
-
|
|
50
|
+
stateOf(optionsOrSchema) {
|
|
51
|
+
if (optionsOrSchema && isSchema(optionsOrSchema)) {
|
|
52
|
+
warnDeprecatedSchemaArgument("stateOf");
|
|
53
|
+
this.validation.state = optionsOrSchema;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
this.validation.state = optionsOrSchema?.schema;
|
|
29
57
|
return this;
|
|
30
58
|
}
|
|
31
59
|
requestHeadersOf() {
|
package/cjs/RequestContext.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export default class RequestContext<TResponse = any, TParams extends Params | un
|
|
|
42
42
|
updateParams(params: Partial<Record<string, string>>): this;
|
|
43
43
|
private parseRequestBody;
|
|
44
44
|
getParsedBody(): any;
|
|
45
|
+
parseBody(): void;
|
|
45
46
|
updateQuery(newQuery: Partial<TQuery>): this;
|
|
46
47
|
triggerEvent(eventType: RequestEvent): Promise<EventResult<TResponse> | undefined>;
|
|
47
48
|
addCanceller(canceler: () => void): void;
|
package/cjs/RequestContext.js
CHANGED
|
@@ -129,6 +129,10 @@ class RequestContext {
|
|
|
129
129
|
if (this.requestConfig.body && this.requestConfig.headers) {
|
|
130
130
|
const contentTypeKey = Object.keys(this.requestConfig.headers).find((key) => key.toLowerCase() === "content-type");
|
|
131
131
|
const contentType = contentTypeKey && this.requestConfig.headers[contentTypeKey]?.toString().split(";")[0].trim();
|
|
132
|
+
const bodyEncoding = this.validation.bodyEncoding ?? "application/json";
|
|
133
|
+
if (!contentTypeKey && bodyEncoding === "application/x-www-form-urlencoded") {
|
|
134
|
+
this.requestConfig.headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
135
|
+
}
|
|
132
136
|
if (contentType === "application/x-www-form-urlencoded") {
|
|
133
137
|
const searchParams = new URLSearchParams();
|
|
134
138
|
for (const key of Object.keys(this.requestConfig.body)) {
|
|
@@ -137,6 +141,17 @@ class RequestContext {
|
|
|
137
141
|
}
|
|
138
142
|
this.parsedBody = searchParams;
|
|
139
143
|
}
|
|
144
|
+
else if (bodyEncoding === "application/x-www-form-urlencoded") {
|
|
145
|
+
const searchParams = new URLSearchParams();
|
|
146
|
+
for (const key of Object.keys(this.requestConfig.body)) {
|
|
147
|
+
const value = this.requestConfig.body[key];
|
|
148
|
+
searchParams.append(key, value?.toString());
|
|
149
|
+
}
|
|
150
|
+
this.parsedBody = searchParams;
|
|
151
|
+
}
|
|
152
|
+
else if (bodyEncoding === "multipart/form-data") {
|
|
153
|
+
this.parsedBody = Utils.toFormData(this.requestConfig.body);
|
|
154
|
+
}
|
|
140
155
|
else {
|
|
141
156
|
this.parsedBody = this.requestConfig.body;
|
|
142
157
|
}
|
|
@@ -148,6 +163,9 @@ class RequestContext {
|
|
|
148
163
|
getParsedBody() {
|
|
149
164
|
return this.parsedBody;
|
|
150
165
|
}
|
|
166
|
+
parseBody() {
|
|
167
|
+
this.parseRequestBody();
|
|
168
|
+
}
|
|
151
169
|
updateQuery(newQuery) {
|
|
152
170
|
this.requestConfig.queryObject = Utils.assign({}, this.requestConfig.queryObject, newQuery);
|
|
153
171
|
this.computedRequestUrl = this.resolveRequestUrl();
|
package/cjs/Requester.js
CHANGED
package/cjs/Utils.d.ts
CHANGED
|
@@ -14,3 +14,4 @@ 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 toFormData: (body: any) => FormData;
|
package/cjs/Utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isFormData = exports.randInt = exports.delayThenReturn = exports.noop = exports.getGlobalFetch = exports.getGlobal = exports.padNumber = exports.assign = void 0;
|
|
3
|
+
exports.toFormData = 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 ||
|
|
@@ -73,6 +73,58 @@ const randInt = (min, max) => {
|
|
|
73
73
|
};
|
|
74
74
|
exports.randInt = randInt;
|
|
75
75
|
const isFormData = (value) => {
|
|
76
|
-
return value instanceof FormData;
|
|
76
|
+
return typeof FormData !== "undefined" && value instanceof FormData;
|
|
77
77
|
};
|
|
78
78
|
exports.isFormData = isFormData;
|
|
79
|
+
const isPlainObject = (value) => {
|
|
80
|
+
return Object.prototype.toString.call(value) === "[object Object]";
|
|
81
|
+
};
|
|
82
|
+
const isBlob = (value) => {
|
|
83
|
+
return typeof Blob !== "undefined" && value instanceof Blob;
|
|
84
|
+
};
|
|
85
|
+
const isArrayBuffer = (value) => {
|
|
86
|
+
return typeof ArrayBuffer !== "undefined" && value instanceof ArrayBuffer;
|
|
87
|
+
};
|
|
88
|
+
const isArrayBufferView = (value) => {
|
|
89
|
+
return typeof ArrayBuffer !== "undefined" && ArrayBuffer.isView(value);
|
|
90
|
+
};
|
|
91
|
+
const toFormDataValue = (value) => {
|
|
92
|
+
if (isBlob(value)) {
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
if ((isArrayBuffer(value) || isArrayBufferView(value)) && typeof Blob !== "undefined") {
|
|
96
|
+
return new Blob([value]);
|
|
97
|
+
}
|
|
98
|
+
return String(value);
|
|
99
|
+
};
|
|
100
|
+
const toFormData = (body) => {
|
|
101
|
+
if ((0, exports.isFormData)(body)) {
|
|
102
|
+
return body;
|
|
103
|
+
}
|
|
104
|
+
const formData = new FormData();
|
|
105
|
+
const appendValue = (key, value) => {
|
|
106
|
+
if (value === undefined || value === null) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (Array.isArray(value)) {
|
|
110
|
+
for (const [index, item] of value.entries()) {
|
|
111
|
+
appendValue(`${key}.${index}`, item);
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (isPlainObject(value) && !isBlob(value)) {
|
|
116
|
+
for (const nestedKey of Object.keys(value)) {
|
|
117
|
+
appendValue(`${key}.${nestedKey}`, value[nestedKey]);
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
formData.append(key, toFormDataValue(value));
|
|
122
|
+
};
|
|
123
|
+
if (isPlainObject(body)) {
|
|
124
|
+
for (const key of Object.keys(body)) {
|
|
125
|
+
appendValue(key, body[key]);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return formData;
|
|
129
|
+
};
|
|
130
|
+
exports.toFormData = toFormData;
|
package/cjs/Validation.d.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import type * as zod from "zod";
|
|
2
|
-
import type { Body, Params, Query, State } from "./ApiTypes";
|
|
2
|
+
import type { Body, Params, Query, RequestBodyEncoding, State } from "./ApiTypes";
|
|
3
|
+
export interface ValidationOptions<TValue> {
|
|
4
|
+
schema?: zod.Schema<TValue>;
|
|
5
|
+
}
|
|
6
|
+
export interface BodyValidationOptions<TBody extends Body> extends ValidationOptions<TBody> {
|
|
7
|
+
encoding?: RequestBodyEncoding;
|
|
8
|
+
}
|
|
3
9
|
export interface Validation<TResponse = any, TParams extends Params | undefined = Params | undefined, TQuery extends Query | undefined = Query | undefined, TBody extends Body | undefined = Body | undefined, TState extends State = State> {
|
|
4
10
|
query?: zod.Schema<TQuery>;
|
|
5
11
|
params?: zod.Schema<TParams>;
|
|
6
12
|
body?: zod.Schema<TBody>;
|
|
13
|
+
bodyEncoding?: RequestBodyEncoding;
|
|
7
14
|
response?: zod.Schema<TResponse>;
|
|
8
15
|
state?: zod.Schema<TState>;
|
|
9
16
|
}
|
|
@@ -111,7 +111,7 @@ 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);
|
|
114
|
+
const bodyJsonify = body !== null && typeof body === "object" && !Utils.isFormData(body) && !(body instanceof URLSearchParams);
|
|
115
115
|
const headers = Utils.assign({
|
|
116
116
|
// logic from axios
|
|
117
117
|
"Content-Type": bodyJsonify ? "application/json;charset=utf-8" : undefined,
|
package/esm/ApiTypes.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ export type AcceptableStatus = number | [min: number, max: number];
|
|
|
7
7
|
export type RawHeaders = Record<string, string | number | boolean | null | undefined>;
|
|
8
8
|
export type Params = string;
|
|
9
9
|
export type Query = string | undefined | Record<string, any>;
|
|
10
|
-
export type Body = string | number | Record<string, any
|
|
10
|
+
export type Body = string | number | Record<string, any> | FormData;
|
|
11
|
+
export type RequestBodyEncoding = "application/json" | "multipart/form-data" | "application/x-www-form-urlencoded";
|
|
11
12
|
export type State = Record<string, any>;
|
|
12
13
|
export interface ApiResponse<T = any> {
|
|
13
14
|
readonly method: RequestMethod;
|
package/esm/EndpointBuilder.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Api } from "./Api.js";
|
|
|
3
3
|
import type { ResponseType } from "./ApiConstants.js";
|
|
4
4
|
import type { Body, Params, Query, RawHeaders, State } from "./ApiTypes.js";
|
|
5
5
|
import Endpoint, { type EndpointOptions } from "./Endpoint.js";
|
|
6
|
+
import type { BodyValidationOptions, ValidationOptions } from "./Validation.js";
|
|
6
7
|
type DefaultResponseOf<T extends ResponseType | undefined> = T extends "text" ? string : T extends "arraybuffer" ? ArrayBuffer : "stream" extends T ? AsyncIterable<Uint8Array> : unknown;
|
|
7
8
|
export type EndpointBuildOptions<TResponse = unknown, TParams extends Params | undefined = undefined, TQuery extends Query | undefined = undefined, TBody extends Body | undefined = undefined, TState extends State = State, TRequestHeaders extends RawHeaders | undefined = RawHeaders | undefined, TResponseHeaders extends RawHeaders | undefined = RawHeaders | undefined, TPath extends string = string, TResponseType extends ResponseType | undefined = undefined> = Omit<EndpointOptions<TResponse, TParams, TQuery, TBody, TState, TPath, TRequestHeaders, TResponseHeaders>, "validation"> & {
|
|
8
9
|
responseType?: TResponseType;
|
|
@@ -11,10 +12,26 @@ export default class EndpointBuilder<TResponse = unknown, TParams extends Params
|
|
|
11
12
|
private api;
|
|
12
13
|
private readonly validation;
|
|
13
14
|
constructor(api: Api);
|
|
15
|
+
queryOf<TNewQuery extends Query>(options?: ValidationOptions<TNewQuery>): EndpointBuilder<TResponse, TParams, TNewQuery, TBody, TState, TRequestHeaders, TResponseHeaders>;
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Pass `{ schema }` instead.
|
|
18
|
+
*/
|
|
14
19
|
queryOf<TNewQuery extends Query>(schema?: zod.Schema<TNewQuery>): EndpointBuilder<TResponse, TParams, TNewQuery, TBody, TState, TRequestHeaders, TResponseHeaders>;
|
|
15
20
|
paramsOf<TNewParams extends Params>(): EndpointBuilder<TResponse, TNewParams, TQuery, TBody, TState, TRequestHeaders, TResponseHeaders>;
|
|
21
|
+
bodyOf<TNewBody extends Body>(options?: BodyValidationOptions<TNewBody>): EndpointBuilder<TResponse, TParams, TQuery, TNewBody, TState, TRequestHeaders, TResponseHeaders>;
|
|
22
|
+
/**
|
|
23
|
+
* @deprecated Pass `{ schema }` instead.
|
|
24
|
+
*/
|
|
16
25
|
bodyOf<TNewBody extends Body>(schema?: zod.Schema<TNewBody>): EndpointBuilder<TResponse, TParams, TQuery, TNewBody, TState, TRequestHeaders, TResponseHeaders>;
|
|
26
|
+
responseOf<TNewResponse>(options?: ValidationOptions<TNewResponse>): EndpointBuilder<TNewResponse, TParams, TQuery, TBody, TState, TRequestHeaders, TResponseHeaders>;
|
|
27
|
+
/**
|
|
28
|
+
* @deprecated Pass `{ schema }` instead.
|
|
29
|
+
*/
|
|
17
30
|
responseOf<TNewResponse>(schema?: zod.Schema<TNewResponse>): EndpointBuilder<TNewResponse, TParams, TQuery, TBody, TState, TRequestHeaders, TResponseHeaders>;
|
|
31
|
+
stateOf<TNewState extends State>(options?: ValidationOptions<TNewState>): EndpointBuilder<TResponse, TParams, TQuery, TBody, TNewState, TRequestHeaders, TResponseHeaders>;
|
|
32
|
+
/**
|
|
33
|
+
* @deprecated Pass `{ schema }` instead.
|
|
34
|
+
*/
|
|
18
35
|
stateOf<TNewState extends State>(schema?: zod.Schema<TNewState>): EndpointBuilder<TResponse, TParams, TQuery, TBody, TNewState, TRequestHeaders, TResponseHeaders>;
|
|
19
36
|
requestHeadersOf<TNewRequestHeaders extends RawHeaders>(): EndpointBuilder<TResponse, TParams, TQuery, TBody, TState, TNewRequestHeaders, TResponseHeaders>;
|
|
20
37
|
responseHeadersOf<TNewResponseHeaders extends RawHeaders>(): EndpointBuilder<TResponse, TParams, TQuery, TBody, TState, TRequestHeaders, TNewResponseHeaders>;
|
package/esm/EndpointBuilder.js
CHANGED
|
@@ -1,26 +1,54 @@
|
|
|
1
1
|
import Endpoint from "./Endpoint.js";
|
|
2
|
+
const isSchema = (value) => {
|
|
3
|
+
return value !== undefined && "parse" in value;
|
|
4
|
+
};
|
|
5
|
+
const warnDeprecatedSchemaArgument = (methodName) => {
|
|
6
|
+
console.warn(`[api-def] ${methodName}(schema) is deprecated. Use ${methodName}({ schema }) instead.`);
|
|
7
|
+
};
|
|
2
8
|
export default class EndpointBuilder {
|
|
3
9
|
constructor(api) {
|
|
4
10
|
this.validation = {};
|
|
5
11
|
this.api = api;
|
|
6
12
|
}
|
|
7
|
-
queryOf(
|
|
8
|
-
|
|
13
|
+
queryOf(optionsOrSchema) {
|
|
14
|
+
if (optionsOrSchema && isSchema(optionsOrSchema)) {
|
|
15
|
+
warnDeprecatedSchemaArgument("queryOf");
|
|
16
|
+
this.validation.query = optionsOrSchema;
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
this.validation.query = optionsOrSchema?.schema;
|
|
9
20
|
return this;
|
|
10
21
|
}
|
|
11
22
|
paramsOf() {
|
|
12
23
|
return this;
|
|
13
24
|
}
|
|
14
|
-
bodyOf(
|
|
15
|
-
|
|
25
|
+
bodyOf(optionsOrSchema) {
|
|
26
|
+
if (optionsOrSchema && isSchema(optionsOrSchema)) {
|
|
27
|
+
warnDeprecatedSchemaArgument("bodyOf");
|
|
28
|
+
this.validation.body = optionsOrSchema;
|
|
29
|
+
this.validation.bodyEncoding = "application/json";
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
this.validation.body = optionsOrSchema?.schema;
|
|
33
|
+
this.validation.bodyEncoding = optionsOrSchema?.encoding ?? "application/json";
|
|
16
34
|
return this;
|
|
17
35
|
}
|
|
18
|
-
responseOf(
|
|
19
|
-
|
|
36
|
+
responseOf(optionsOrSchema) {
|
|
37
|
+
if (optionsOrSchema && isSchema(optionsOrSchema)) {
|
|
38
|
+
warnDeprecatedSchemaArgument("responseOf");
|
|
39
|
+
this.validation.response = optionsOrSchema;
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
this.validation.response = optionsOrSchema?.schema;
|
|
20
43
|
return this;
|
|
21
44
|
}
|
|
22
|
-
stateOf(
|
|
23
|
-
|
|
45
|
+
stateOf(optionsOrSchema) {
|
|
46
|
+
if (optionsOrSchema && isSchema(optionsOrSchema)) {
|
|
47
|
+
warnDeprecatedSchemaArgument("stateOf");
|
|
48
|
+
this.validation.state = optionsOrSchema;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
this.validation.state = optionsOrSchema?.schema;
|
|
24
52
|
return this;
|
|
25
53
|
}
|
|
26
54
|
requestHeadersOf() {
|
package/esm/RequestContext.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export default class RequestContext<TResponse = any, TParams extends Params | un
|
|
|
42
42
|
updateParams(params: Partial<Record<string, string>>): this;
|
|
43
43
|
private parseRequestBody;
|
|
44
44
|
getParsedBody(): any;
|
|
45
|
+
parseBody(): void;
|
|
45
46
|
updateQuery(newQuery: Partial<TQuery>): this;
|
|
46
47
|
triggerEvent(eventType: RequestEvent): Promise<EventResult<TResponse> | undefined>;
|
|
47
48
|
addCanceller(canceler: () => void): void;
|
package/esm/RequestContext.js
CHANGED
|
@@ -94,6 +94,10 @@ export default class RequestContext {
|
|
|
94
94
|
if (this.requestConfig.body && this.requestConfig.headers) {
|
|
95
95
|
const contentTypeKey = Object.keys(this.requestConfig.headers).find((key) => key.toLowerCase() === "content-type");
|
|
96
96
|
const contentType = contentTypeKey && this.requestConfig.headers[contentTypeKey]?.toString().split(";")[0].trim();
|
|
97
|
+
const bodyEncoding = this.validation.bodyEncoding ?? "application/json";
|
|
98
|
+
if (!contentTypeKey && bodyEncoding === "application/x-www-form-urlencoded") {
|
|
99
|
+
this.requestConfig.headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
100
|
+
}
|
|
97
101
|
if (contentType === "application/x-www-form-urlencoded") {
|
|
98
102
|
const searchParams = new URLSearchParams();
|
|
99
103
|
for (const key of Object.keys(this.requestConfig.body)) {
|
|
@@ -102,6 +106,17 @@ export default class RequestContext {
|
|
|
102
106
|
}
|
|
103
107
|
this.parsedBody = searchParams;
|
|
104
108
|
}
|
|
109
|
+
else if (bodyEncoding === "application/x-www-form-urlencoded") {
|
|
110
|
+
const searchParams = new URLSearchParams();
|
|
111
|
+
for (const key of Object.keys(this.requestConfig.body)) {
|
|
112
|
+
const value = this.requestConfig.body[key];
|
|
113
|
+
searchParams.append(key, value?.toString());
|
|
114
|
+
}
|
|
115
|
+
this.parsedBody = searchParams;
|
|
116
|
+
}
|
|
117
|
+
else if (bodyEncoding === "multipart/form-data") {
|
|
118
|
+
this.parsedBody = Utils.toFormData(this.requestConfig.body);
|
|
119
|
+
}
|
|
105
120
|
else {
|
|
106
121
|
this.parsedBody = this.requestConfig.body;
|
|
107
122
|
}
|
|
@@ -113,6 +128,9 @@ export default class RequestContext {
|
|
|
113
128
|
getParsedBody() {
|
|
114
129
|
return this.parsedBody;
|
|
115
130
|
}
|
|
131
|
+
parseBody() {
|
|
132
|
+
this.parseRequestBody();
|
|
133
|
+
}
|
|
116
134
|
updateQuery(newQuery) {
|
|
117
135
|
this.requestConfig.queryObject = Utils.assign({}, this.requestConfig.queryObject, newQuery);
|
|
118
136
|
this.computedRequestUrl = this.resolveRequestUrl();
|
package/esm/Requester.js
CHANGED
package/esm/Utils.d.ts
CHANGED
|
@@ -14,3 +14,4 @@ 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 toFormData: (body: any) => FormData;
|
package/esm/Utils.js
CHANGED
|
@@ -64,5 +64,56 @@ export const randInt = (min, max) => {
|
|
|
64
64
|
return Math.floor(Math.random() * (maxI - minI + 1)) + minI;
|
|
65
65
|
};
|
|
66
66
|
export const isFormData = (value) => {
|
|
67
|
-
return value instanceof FormData;
|
|
67
|
+
return typeof FormData !== "undefined" && value instanceof FormData;
|
|
68
|
+
};
|
|
69
|
+
const isPlainObject = (value) => {
|
|
70
|
+
return Object.prototype.toString.call(value) === "[object Object]";
|
|
71
|
+
};
|
|
72
|
+
const isBlob = (value) => {
|
|
73
|
+
return typeof Blob !== "undefined" && value instanceof Blob;
|
|
74
|
+
};
|
|
75
|
+
const isArrayBuffer = (value) => {
|
|
76
|
+
return typeof ArrayBuffer !== "undefined" && value instanceof ArrayBuffer;
|
|
77
|
+
};
|
|
78
|
+
const isArrayBufferView = (value) => {
|
|
79
|
+
return typeof ArrayBuffer !== "undefined" && ArrayBuffer.isView(value);
|
|
80
|
+
};
|
|
81
|
+
const toFormDataValue = (value) => {
|
|
82
|
+
if (isBlob(value)) {
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
if ((isArrayBuffer(value) || isArrayBufferView(value)) && typeof Blob !== "undefined") {
|
|
86
|
+
return new Blob([value]);
|
|
87
|
+
}
|
|
88
|
+
return String(value);
|
|
89
|
+
};
|
|
90
|
+
export const toFormData = (body) => {
|
|
91
|
+
if (isFormData(body)) {
|
|
92
|
+
return body;
|
|
93
|
+
}
|
|
94
|
+
const formData = new FormData();
|
|
95
|
+
const appendValue = (key, value) => {
|
|
96
|
+
if (value === undefined || value === null) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (Array.isArray(value)) {
|
|
100
|
+
for (const [index, item] of value.entries()) {
|
|
101
|
+
appendValue(`${key}.${index}`, item);
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (isPlainObject(value) && !isBlob(value)) {
|
|
106
|
+
for (const nestedKey of Object.keys(value)) {
|
|
107
|
+
appendValue(`${key}.${nestedKey}`, value[nestedKey]);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
formData.append(key, toFormDataValue(value));
|
|
112
|
+
};
|
|
113
|
+
if (isPlainObject(body)) {
|
|
114
|
+
for (const key of Object.keys(body)) {
|
|
115
|
+
appendValue(key, body[key]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return formData;
|
|
68
119
|
};
|
package/esm/Validation.d.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import type * as zod from "zod";
|
|
2
|
-
import type { Body, Params, Query, State } from "./ApiTypes.js";
|
|
2
|
+
import type { Body, Params, Query, RequestBodyEncoding, State } from "./ApiTypes.js";
|
|
3
|
+
export interface ValidationOptions<TValue> {
|
|
4
|
+
schema?: zod.Schema<TValue>;
|
|
5
|
+
}
|
|
6
|
+
export interface BodyValidationOptions<TBody extends Body> extends ValidationOptions<TBody> {
|
|
7
|
+
encoding?: RequestBodyEncoding;
|
|
8
|
+
}
|
|
3
9
|
export interface Validation<TResponse = any, TParams extends Params | undefined = Params | undefined, TQuery extends Query | undefined = Query | undefined, TBody extends Body | undefined = Body | undefined, TState extends State = State> {
|
|
4
10
|
query?: zod.Schema<TQuery>;
|
|
5
11
|
params?: zod.Schema<TParams>;
|
|
6
12
|
body?: zod.Schema<TBody>;
|
|
13
|
+
bodyEncoding?: RequestBodyEncoding;
|
|
7
14
|
response?: zod.Schema<TResponse>;
|
|
8
15
|
state?: zod.Schema<TState>;
|
|
9
16
|
}
|
|
@@ -76,7 +76,7 @@ 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);
|
|
79
|
+
const bodyJsonify = body !== null && typeof body === "object" && !Utils.isFormData(body) && !(body instanceof URLSearchParams);
|
|
80
80
|
const headers = Utils.assign({
|
|
81
81
|
// logic from axios
|
|
82
82
|
"Content-Type": bodyJsonify ? "application/json;charset=utf-8" : undefined,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api-def",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0-alpha.2",
|
|
4
4
|
"description": "Typed API definitions with middleware support",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"types": "esm/index.d.ts",
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
"commander": "^15.0.0",
|
|
92
92
|
"cross-env": "10.1.0",
|
|
93
93
|
"esbuild": "^0.28.0",
|
|
94
|
+
"formdata-node": "6.0.3",
|
|
94
95
|
"jest": "30.4.2",
|
|
95
96
|
"kleur": "^4.1.5",
|
|
96
97
|
"lodash": "^4.18.1",
|