api-def 0.14.3 → 0.15.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,5 +29,6 @@ export declare const ResponseType: {
29
29
  readonly TEXT: "text";
30
30
  readonly ARRAY_BUFFER: "arraybuffer";
31
31
  readonly STREAM: "stream";
32
+ readonly WEBSOCKET: "websocket";
32
33
  };
33
34
  export type ResponseType = EnumOf<typeof ResponseType>;
@@ -27,4 +27,5 @@ exports.ResponseType = {
27
27
  TEXT: "text",
28
28
  ARRAY_BUFFER: "arraybuffer",
29
29
  STREAM: "stream",
30
+ WEBSOCKET: "websocket",
30
31
  };
@@ -4,7 +4,7 @@ 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
6
  import type { BodyValidationOptions, ValidationOptions } from "./Validation";
7
- type DefaultResponseOf<T extends ResponseType | undefined> = T extends "text" ? string : T extends "arraybuffer" ? ArrayBuffer : "stream" extends T ? AsyncIterable<Uint8Array> : unknown;
7
+ type DefaultResponseOf<T extends ResponseType | undefined> = T extends "text" ? string : T extends "arraybuffer" ? ArrayBuffer : T extends "stream" ? AsyncIterable<Uint8Array> : T extends "websocket" ? WebSocket : unknown;
8
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"> & {
9
9
  responseType?: TResponseType;
10
10
  };
package/cjs/Requester.js CHANGED
@@ -112,7 +112,8 @@ const makeRequest = async (context) => {
112
112
  context.addCanceller(canceler);
113
113
  const response = await promise;
114
114
  const parsedResponse = (await parseResponse(context, response));
115
- if (!(0, ApiUtils_1.isAcceptableStatus)(parsedResponse.status, context.requestConfig.acceptableStatus)) {
115
+ if (!(context.responseType === "websocket" && parsedResponse.status === 101) &&
116
+ !(0, ApiUtils_1.isAcceptableStatus)(parsedResponse.status, context.requestConfig.acceptableStatus)) {
116
117
  throw (0, RequestError_1.convertToRequestError)({
117
118
  error: new Error(`[api-def] Invalid response status code '${parsedResponse.status}'`),
118
119
  response: parsedResponse,
@@ -3,12 +3,16 @@ import type { ApiResponse } from "../ApiTypes";
3
3
  import type RequestContext from "../RequestContext";
4
4
  import type RequestBackend from "./RequestBackend";
5
5
  import type { ConvertedApiResponse, RequestBackendErrorInfo, RequestOperation } from "./RequestBackend";
6
+ import { type WebSocketConstructor, type WebSocketResponse } from "./WebSocketRequest";
6
7
  export declare const isAxiosError: (error: Error) => error is AxiosError;
7
- export default class AxiosRequestBackend implements RequestBackend<AxiosResponse> {
8
+ type AxiosBackendResponse = AxiosResponse | WebSocketResponse;
9
+ export default class AxiosRequestBackend implements RequestBackend<AxiosBackendResponse> {
8
10
  readonly id = "axios";
9
- constructor(axiosLibrary: any);
10
- extractResponseFromError(error: Error): Promise<AxiosResponse | null | undefined>;
11
- convertResponse<T>(context: RequestContext, response: AxiosResponse): Promise<ConvertedApiResponse<T>>;
12
- makeRequest(context: RequestContext): RequestOperation<AxiosResponse>;
11
+ webSocketConstructor: WebSocketConstructor | undefined;
12
+ constructor(axiosLibrary: any, webSocketConstructor?: WebSocketConstructor);
13
+ extractResponseFromError(error: Error): Promise<AxiosBackendResponse | null | undefined>;
14
+ convertResponse<T>(context: RequestContext, response: AxiosBackendResponse): Promise<ConvertedApiResponse<T>>;
15
+ makeRequest(context: RequestContext): RequestOperation<AxiosBackendResponse>;
13
16
  getErrorInfo(_error: Error, _response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
14
17
  }
18
+ export {};
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isAxiosError = void 0;
4
4
  const ApiUtils_1 = require("../ApiUtils");
5
+ const WebSocketRequest_1 = require("./WebSocketRequest");
5
6
  let axios;
6
7
  const isAxiosError = (error) => {
7
8
  return "isAxiosError" in error;
@@ -32,9 +33,13 @@ const getCacheHeaders = (browserCache) => {
32
33
  }
33
34
  };
34
35
  class AxiosRequestBackend {
35
- constructor(axiosLibrary) {
36
+ constructor(axiosLibrary, webSocketConstructor) {
36
37
  this.id = "axios";
38
+ this.webSocketConstructor = (0, WebSocketRequest_1.getGlobalWebSocketConstructor)();
37
39
  axios = axiosLibrary;
40
+ if (webSocketConstructor !== undefined) {
41
+ this.webSocketConstructor = webSocketConstructor;
42
+ }
38
43
  }
39
44
  async extractResponseFromError(error) {
40
45
  if ((0, exports.isAxiosError)(error)) {
@@ -43,17 +48,30 @@ class AxiosRequestBackend {
43
48
  return undefined;
44
49
  }
45
50
  async convertResponse(context, response) {
46
- const contentType = response.headers["content-type"];
51
+ if (context.responseType === "websocket") {
52
+ const webSocketResponse = response;
53
+ return {
54
+ method: context.method,
55
+ url: webSocketResponse.url,
56
+ data: webSocketResponse.webSocket,
57
+ headers: webSocketResponse.headers,
58
+ status: webSocketResponse.status,
59
+ state: context.requestConfig.state,
60
+ stats: context.stats,
61
+ };
62
+ }
63
+ const axiosResponse = response;
64
+ const contentType = axiosResponse.headers["content-type"];
47
65
  const responseType = context.responseType ?? (0, ApiUtils_1.inferResponseType)(contentType == null ? contentType : String(contentType));
48
66
  let data;
49
67
  if (responseType === "stream") {
50
68
  // For streaming responses, we create an async iterator from the response data
51
- if (!response.data) {
69
+ if (!axiosResponse.data) {
52
70
  throw new Error("[api-def] Response data is null for streaming response");
53
71
  }
54
72
  data = {
55
73
  async *[Symbol.asyncIterator]() {
56
- const stream = response.data;
74
+ const stream = axiosResponse.data;
57
75
  if (stream[Symbol.asyncIterator]) {
58
76
  yield* stream;
59
77
  }
@@ -68,20 +86,23 @@ class AxiosRequestBackend {
68
86
  };
69
87
  }
70
88
  else {
71
- data = response.data;
89
+ data = axiosResponse.data;
72
90
  }
73
91
  return {
74
92
  method: context.method,
75
- url: response.request.res?.responseUrl ?? response.request?._redirectable?._currentUrl,
93
+ url: axiosResponse.request.res?.responseUrl ?? axiosResponse.request?._redirectable?._currentUrl,
76
94
  data: data,
77
- headers: response.headers,
78
- status: response.status,
95
+ headers: axiosResponse.headers,
96
+ status: axiosResponse.status,
79
97
  state: context.requestConfig.state,
80
- __lowercaseHeaders: response._lowerCaseResponseHeaders,
98
+ __lowercaseHeaders: axiosResponse._lowerCaseResponseHeaders,
81
99
  stats: context.stats,
82
100
  };
83
101
  }
84
102
  makeRequest(context) {
103
+ if (context.responseType === "websocket") {
104
+ return (0, WebSocketRequest_1.makeWebSocketRequest)(context, this.webSocketConstructor);
105
+ }
85
106
  const { requestConfig } = context;
86
107
  const url = context.requestUrl;
87
108
  let canceler = null;
@@ -3,14 +3,18 @@ import type RequestContext from "../RequestContext";
3
3
  import { type Fetch } from "../Utils";
4
4
  import type RequestBackend from "./RequestBackend";
5
5
  import type { ConvertedApiResponse, RequestBackendErrorInfo, RequestOperation } from "./RequestBackend";
6
- export default class FetchRequestBackend implements RequestBackend<Response> {
6
+ import { type WebSocketConstructor, type WebSocketResponse } from "./WebSocketRequest";
7
+ type FetchBackendResponse = Response | WebSocketResponse;
8
+ export default class FetchRequestBackend implements RequestBackend<FetchBackendResponse> {
7
9
  fetch: (((input: RequestInfo | URL, init?: RequestInit) => Promise<Response>) & typeof fetch) | undefined;
10
+ webSocketConstructor: WebSocketConstructor | undefined;
8
11
  readonly id = "fetch";
9
- constructor(fetchLibrary?: Fetch);
10
- extractResponseFromError(error: Error): Promise<Response | null | undefined>;
11
- convertResponse<T>(context: RequestContext, response: Response & {
12
+ constructor(fetchLibrary?: Fetch, webSocketConstructor?: WebSocketConstructor);
13
+ extractResponseFromError(error: Error): Promise<FetchBackendResponse | null | undefined>;
14
+ convertResponse<T>(context: RequestContext, response: FetchBackendResponse & {
12
15
  __text?: string;
13
16
  }): Promise<ConvertedApiResponse<T>>;
14
- makeRequest(context: RequestContext): RequestOperation<Response>;
17
+ makeRequest(context: RequestContext): RequestOperation<FetchBackendResponse>;
15
18
  getErrorInfo(_error: Error, _response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
16
19
  }
20
+ export {};
@@ -37,11 +37,13 @@ const ApiUtils_1 = require("../ApiUtils");
37
37
  const RequestError_1 = require("../RequestError");
38
38
  const Utils = __importStar(require("../Utils"));
39
39
  const Utils_1 = require("../Utils");
40
+ const WebSocketRequest_1 = require("./WebSocketRequest");
40
41
  class FetchError extends Error {
41
42
  }
42
43
  class FetchRequestBackend {
43
- constructor(fetchLibrary) {
44
+ constructor(fetchLibrary, webSocketConstructor) {
44
45
  this.fetch = (0, Utils_1.getGlobalFetch)();
46
+ this.webSocketConstructor = (0, WebSocketRequest_1.getGlobalWebSocketConstructor)();
45
47
  this.id = "fetch";
46
48
  if (fetchLibrary !== undefined) {
47
49
  this.fetch = fetchLibrary;
@@ -50,6 +52,9 @@ class FetchRequestBackend {
50
52
  this.fetch = fetchLibrary.bind((0, Utils_1.getGlobal)());
51
53
  }
52
54
  }
55
+ if (webSocketConstructor !== undefined) {
56
+ this.webSocketConstructor = webSocketConstructor;
57
+ }
53
58
  }
54
59
  async extractResponseFromError(error) {
55
60
  if ("response" in error) {
@@ -73,7 +78,13 @@ class FetchRequestBackend {
73
78
  let text;
74
79
  let data;
75
80
  try {
76
- if (responseType === "arraybuffer") {
81
+ if (responseType === "websocket") {
82
+ data = response.webSocket;
83
+ if (!data) {
84
+ throw new Error("[api-def] WebSocket response did not include a webSocket");
85
+ }
86
+ }
87
+ else if (responseType === "arraybuffer") {
77
88
  data = await response.arrayBuffer();
78
89
  }
79
90
  else if (responseType === "json") {
@@ -110,6 +121,10 @@ class FetchRequestBackend {
110
121
  const abortSignal = abortController ? abortController.signal : undefined;
111
122
  let softAbort = false;
112
123
  let responded = false;
124
+ if (context.responseType === "websocket") {
125
+ responded = true;
126
+ return (0, WebSocketRequest_1.makeWebSocketRequest)(context, this.webSocketConstructor);
127
+ }
113
128
  const body = context.getParsedBody();
114
129
  const bodyJsonify = body !== null && typeof body === "object" && !Utils.isFormDataLike(body) && !(body instanceof URLSearchParams);
115
130
  const headers = Utils.assign({
@@ -0,0 +1,11 @@
1
+ import type RequestContext from "../RequestContext";
2
+ import type { RequestOperation } from "./RequestBackend";
3
+ export type WebSocketConstructor = new (url: string | URL, protocols?: string | string[]) => WebSocket;
4
+ export type WebSocketResponse = {
5
+ status: 101;
6
+ headers: Headers;
7
+ url: string;
8
+ webSocket: WebSocket;
9
+ };
10
+ export declare const getGlobalWebSocketConstructor: () => WebSocketConstructor | undefined;
11
+ export declare const makeWebSocketRequest: (context: RequestContext, webSocketConstructor: WebSocketConstructor | undefined) => RequestOperation<WebSocketResponse>;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeWebSocketRequest = exports.getGlobalWebSocketConstructor = void 0;
4
+ const getGlobalWebSocketConstructor = () => {
5
+ return typeof WebSocket !== "undefined" ? WebSocket : undefined;
6
+ };
7
+ exports.getGlobalWebSocketConstructor = getGlobalWebSocketConstructor;
8
+ const makeWebSocketRequest = (context, webSocketConstructor) => {
9
+ if (!webSocketConstructor) {
10
+ throw new Error("[api-def] No WebSocket constructor was provided");
11
+ }
12
+ const url = context.requestUrl;
13
+ if (url.protocol === "http:") {
14
+ url.protocol = "ws:";
15
+ }
16
+ else if (url.protocol === "https:") {
17
+ url.protocol = "wss:";
18
+ }
19
+ const protocolHeader = context.requestConfig.headers?.["Sec-WebSocket-Protocol"];
20
+ const protocols = typeof protocolHeader === "string"
21
+ ? protocolHeader.split(",").map((protocol) => protocol.trim())
22
+ : typeof protocolHeader === "number" || typeof protocolHeader === "boolean"
23
+ ? String(protocolHeader)
24
+ : undefined;
25
+ const webSocket = new webSocketConstructor(url.href, protocols);
26
+ return {
27
+ promise: Promise.resolve({
28
+ status: 101,
29
+ headers: new Headers(),
30
+ url: url.href,
31
+ webSocket,
32
+ }),
33
+ canceler: () => webSocket.close(),
34
+ };
35
+ };
36
+ exports.makeWebSocketRequest = makeWebSocketRequest;
@@ -29,5 +29,6 @@ export declare const ResponseType: {
29
29
  readonly TEXT: "text";
30
30
  readonly ARRAY_BUFFER: "arraybuffer";
31
31
  readonly STREAM: "stream";
32
+ readonly WEBSOCKET: "websocket";
32
33
  };
33
34
  export type ResponseType = EnumOf<typeof ResponseType>;
@@ -24,4 +24,5 @@ export const ResponseType = {
24
24
  TEXT: "text",
25
25
  ARRAY_BUFFER: "arraybuffer",
26
26
  STREAM: "stream",
27
+ WEBSOCKET: "websocket",
27
28
  };
@@ -4,7 +4,7 @@ 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
6
  import type { BodyValidationOptions, ValidationOptions } from "./Validation.js";
7
- type DefaultResponseOf<T extends ResponseType | undefined> = T extends "text" ? string : T extends "arraybuffer" ? ArrayBuffer : "stream" extends T ? AsyncIterable<Uint8Array> : unknown;
7
+ type DefaultResponseOf<T extends ResponseType | undefined> = T extends "text" ? string : T extends "arraybuffer" ? ArrayBuffer : T extends "stream" ? AsyncIterable<Uint8Array> : T extends "websocket" ? WebSocket : unknown;
8
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"> & {
9
9
  responseType?: TResponseType;
10
10
  };
package/esm/Requester.js CHANGED
@@ -105,7 +105,8 @@ const makeRequest = async (context) => {
105
105
  context.addCanceller(canceler);
106
106
  const response = await promise;
107
107
  const parsedResponse = (await parseResponse(context, response));
108
- if (!isAcceptableStatus(parsedResponse.status, context.requestConfig.acceptableStatus)) {
108
+ if (!(context.responseType === "websocket" && parsedResponse.status === 101) &&
109
+ !isAcceptableStatus(parsedResponse.status, context.requestConfig.acceptableStatus)) {
109
110
  throw convertToRequestError({
110
111
  error: new Error(`[api-def] Invalid response status code '${parsedResponse.status}'`),
111
112
  response: parsedResponse,
@@ -3,12 +3,16 @@ import type { ApiResponse } from "../ApiTypes.js";
3
3
  import type RequestContext from "../RequestContext.js";
4
4
  import type RequestBackend from "./RequestBackend.js";
5
5
  import type { ConvertedApiResponse, RequestBackendErrorInfo, RequestOperation } from "./RequestBackend.js";
6
+ import { type WebSocketConstructor, type WebSocketResponse } from "./WebSocketRequest.js";
6
7
  export declare const isAxiosError: (error: Error) => error is AxiosError;
7
- export default class AxiosRequestBackend implements RequestBackend<AxiosResponse> {
8
+ type AxiosBackendResponse = AxiosResponse | WebSocketResponse;
9
+ export default class AxiosRequestBackend implements RequestBackend<AxiosBackendResponse> {
8
10
  readonly id = "axios";
9
- constructor(axiosLibrary: any);
10
- extractResponseFromError(error: Error): Promise<AxiosResponse | null | undefined>;
11
- convertResponse<T>(context: RequestContext, response: AxiosResponse): Promise<ConvertedApiResponse<T>>;
12
- makeRequest(context: RequestContext): RequestOperation<AxiosResponse>;
11
+ webSocketConstructor: WebSocketConstructor | undefined;
12
+ constructor(axiosLibrary: any, webSocketConstructor?: WebSocketConstructor);
13
+ extractResponseFromError(error: Error): Promise<AxiosBackendResponse | null | undefined>;
14
+ convertResponse<T>(context: RequestContext, response: AxiosBackendResponse): Promise<ConvertedApiResponse<T>>;
15
+ makeRequest(context: RequestContext): RequestOperation<AxiosBackendResponse>;
13
16
  getErrorInfo(_error: Error, _response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
14
17
  }
18
+ export {};
@@ -1,4 +1,5 @@
1
1
  import { inferResponseType } from "../ApiUtils.js";
2
+ import { getGlobalWebSocketConstructor, makeWebSocketRequest, } from "./WebSocketRequest.js";
2
3
  let axios;
3
4
  export const isAxiosError = (error) => {
4
5
  return "isAxiosError" in error;
@@ -28,9 +29,13 @@ const getCacheHeaders = (browserCache) => {
28
29
  }
29
30
  };
30
31
  export default class AxiosRequestBackend {
31
- constructor(axiosLibrary) {
32
+ constructor(axiosLibrary, webSocketConstructor) {
32
33
  this.id = "axios";
34
+ this.webSocketConstructor = getGlobalWebSocketConstructor();
33
35
  axios = axiosLibrary;
36
+ if (webSocketConstructor !== undefined) {
37
+ this.webSocketConstructor = webSocketConstructor;
38
+ }
34
39
  }
35
40
  async extractResponseFromError(error) {
36
41
  if (isAxiosError(error)) {
@@ -39,17 +44,30 @@ export default class AxiosRequestBackend {
39
44
  return undefined;
40
45
  }
41
46
  async convertResponse(context, response) {
42
- const contentType = response.headers["content-type"];
47
+ if (context.responseType === "websocket") {
48
+ const webSocketResponse = response;
49
+ return {
50
+ method: context.method,
51
+ url: webSocketResponse.url,
52
+ data: webSocketResponse.webSocket,
53
+ headers: webSocketResponse.headers,
54
+ status: webSocketResponse.status,
55
+ state: context.requestConfig.state,
56
+ stats: context.stats,
57
+ };
58
+ }
59
+ const axiosResponse = response;
60
+ const contentType = axiosResponse.headers["content-type"];
43
61
  const responseType = context.responseType ?? inferResponseType(contentType == null ? contentType : String(contentType));
44
62
  let data;
45
63
  if (responseType === "stream") {
46
64
  // For streaming responses, we create an async iterator from the response data
47
- if (!response.data) {
65
+ if (!axiosResponse.data) {
48
66
  throw new Error("[api-def] Response data is null for streaming response");
49
67
  }
50
68
  data = {
51
69
  async *[Symbol.asyncIterator]() {
52
- const stream = response.data;
70
+ const stream = axiosResponse.data;
53
71
  if (stream[Symbol.asyncIterator]) {
54
72
  yield* stream;
55
73
  }
@@ -64,20 +82,23 @@ export default class AxiosRequestBackend {
64
82
  };
65
83
  }
66
84
  else {
67
- data = response.data;
85
+ data = axiosResponse.data;
68
86
  }
69
87
  return {
70
88
  method: context.method,
71
- url: response.request.res?.responseUrl ?? response.request?._redirectable?._currentUrl,
89
+ url: axiosResponse.request.res?.responseUrl ?? axiosResponse.request?._redirectable?._currentUrl,
72
90
  data: data,
73
- headers: response.headers,
74
- status: response.status,
91
+ headers: axiosResponse.headers,
92
+ status: axiosResponse.status,
75
93
  state: context.requestConfig.state,
76
- __lowercaseHeaders: response._lowerCaseResponseHeaders,
94
+ __lowercaseHeaders: axiosResponse._lowerCaseResponseHeaders,
77
95
  stats: context.stats,
78
96
  };
79
97
  }
80
98
  makeRequest(context) {
99
+ if (context.responseType === "websocket") {
100
+ return makeWebSocketRequest(context, this.webSocketConstructor);
101
+ }
81
102
  const { requestConfig } = context;
82
103
  const url = context.requestUrl;
83
104
  let canceler = null;
@@ -3,14 +3,18 @@ import type RequestContext from "../RequestContext.js";
3
3
  import { type Fetch } from "../Utils.js";
4
4
  import type RequestBackend from "./RequestBackend.js";
5
5
  import type { ConvertedApiResponse, RequestBackendErrorInfo, RequestOperation } from "./RequestBackend.js";
6
- export default class FetchRequestBackend implements RequestBackend<Response> {
6
+ import { type WebSocketConstructor, type WebSocketResponse } from "./WebSocketRequest.js";
7
+ type FetchBackendResponse = Response | WebSocketResponse;
8
+ export default class FetchRequestBackend implements RequestBackend<FetchBackendResponse> {
7
9
  fetch: (((input: RequestInfo | URL, init?: RequestInit) => Promise<Response>) & typeof fetch) | undefined;
10
+ webSocketConstructor: WebSocketConstructor | undefined;
8
11
  readonly id = "fetch";
9
- constructor(fetchLibrary?: Fetch);
10
- extractResponseFromError(error: Error): Promise<Response | null | undefined>;
11
- convertResponse<T>(context: RequestContext, response: Response & {
12
+ constructor(fetchLibrary?: Fetch, webSocketConstructor?: WebSocketConstructor);
13
+ extractResponseFromError(error: Error): Promise<FetchBackendResponse | null | undefined>;
14
+ convertResponse<T>(context: RequestContext, response: FetchBackendResponse & {
12
15
  __text?: string;
13
16
  }): Promise<ConvertedApiResponse<T>>;
14
- makeRequest(context: RequestContext): RequestOperation<Response>;
17
+ makeRequest(context: RequestContext): RequestOperation<FetchBackendResponse>;
15
18
  getErrorInfo(_error: Error, _response: ApiResponse | undefined | null): RequestBackendErrorInfo | undefined;
16
19
  }
20
+ export {};
@@ -2,11 +2,13 @@ import { inferResponseType } from "../ApiUtils.js";
2
2
  import { convertToRequestError, RequestErrorCode } from "../RequestError.js";
3
3
  import * as Utils from "../Utils.js";
4
4
  import { getGlobal, getGlobalFetch } from "../Utils.js";
5
+ import { getGlobalWebSocketConstructor, makeWebSocketRequest, } from "./WebSocketRequest.js";
5
6
  class FetchError extends Error {
6
7
  }
7
8
  export default class FetchRequestBackend {
8
- constructor(fetchLibrary) {
9
+ constructor(fetchLibrary, webSocketConstructor) {
9
10
  this.fetch = getGlobalFetch();
11
+ this.webSocketConstructor = getGlobalWebSocketConstructor();
10
12
  this.id = "fetch";
11
13
  if (fetchLibrary !== undefined) {
12
14
  this.fetch = fetchLibrary;
@@ -15,6 +17,9 @@ export default class FetchRequestBackend {
15
17
  this.fetch = fetchLibrary.bind(getGlobal());
16
18
  }
17
19
  }
20
+ if (webSocketConstructor !== undefined) {
21
+ this.webSocketConstructor = webSocketConstructor;
22
+ }
18
23
  }
19
24
  async extractResponseFromError(error) {
20
25
  if ("response" in error) {
@@ -38,7 +43,13 @@ export default class FetchRequestBackend {
38
43
  let text;
39
44
  let data;
40
45
  try {
41
- if (responseType === "arraybuffer") {
46
+ if (responseType === "websocket") {
47
+ data = response.webSocket;
48
+ if (!data) {
49
+ throw new Error("[api-def] WebSocket response did not include a webSocket");
50
+ }
51
+ }
52
+ else if (responseType === "arraybuffer") {
42
53
  data = await response.arrayBuffer();
43
54
  }
44
55
  else if (responseType === "json") {
@@ -75,6 +86,10 @@ export default class FetchRequestBackend {
75
86
  const abortSignal = abortController ? abortController.signal : undefined;
76
87
  let softAbort = false;
77
88
  let responded = false;
89
+ if (context.responseType === "websocket") {
90
+ responded = true;
91
+ return makeWebSocketRequest(context, this.webSocketConstructor);
92
+ }
78
93
  const body = context.getParsedBody();
79
94
  const bodyJsonify = body !== null && typeof body === "object" && !Utils.isFormDataLike(body) && !(body instanceof URLSearchParams);
80
95
  const headers = Utils.assign({
@@ -0,0 +1,11 @@
1
+ import type RequestContext from "../RequestContext.js";
2
+ import type { RequestOperation } from "./RequestBackend.js";
3
+ export type WebSocketConstructor = new (url: string | URL, protocols?: string | string[]) => WebSocket;
4
+ export type WebSocketResponse = {
5
+ status: 101;
6
+ headers: Headers;
7
+ url: string;
8
+ webSocket: WebSocket;
9
+ };
10
+ export declare const getGlobalWebSocketConstructor: () => WebSocketConstructor | undefined;
11
+ export declare const makeWebSocketRequest: (context: RequestContext, webSocketConstructor: WebSocketConstructor | undefined) => RequestOperation<WebSocketResponse>;
@@ -0,0 +1,31 @@
1
+ export const getGlobalWebSocketConstructor = () => {
2
+ return typeof WebSocket !== "undefined" ? WebSocket : undefined;
3
+ };
4
+ export const makeWebSocketRequest = (context, webSocketConstructor) => {
5
+ if (!webSocketConstructor) {
6
+ throw new Error("[api-def] No WebSocket constructor was provided");
7
+ }
8
+ const url = context.requestUrl;
9
+ if (url.protocol === "http:") {
10
+ url.protocol = "ws:";
11
+ }
12
+ else if (url.protocol === "https:") {
13
+ url.protocol = "wss:";
14
+ }
15
+ const protocolHeader = context.requestConfig.headers?.["Sec-WebSocket-Protocol"];
16
+ const protocols = typeof protocolHeader === "string"
17
+ ? protocolHeader.split(",").map((protocol) => protocol.trim())
18
+ : typeof protocolHeader === "number" || typeof protocolHeader === "boolean"
19
+ ? String(protocolHeader)
20
+ : undefined;
21
+ const webSocket = new webSocketConstructor(url.href, protocols);
22
+ return {
23
+ promise: Promise.resolve({
24
+ status: 101,
25
+ headers: new Headers(),
26
+ url: url.href,
27
+ webSocket,
28
+ }),
29
+ canceler: () => webSocket.close(),
30
+ };
31
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api-def",
3
- "version": "0.14.3",
3
+ "version": "0.15.0-alpha.1",
4
4
  "description": "Typed API definitions with middleware support",
5
5
  "main": "cjs/index.js",
6
6
  "types": "esm/index.d.ts",