@swapkit/api-sdk-typescript 1.0.3 → 1.1.0-nightly.20260701161042

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.
@@ -25,7 +25,7 @@ interface SerializePrimitiveParam extends SerializePrimitiveOptions {
25
25
  value: string;
26
26
  }
27
27
 
28
- export const separatorArrayExplode = (style: ArraySeparatorStyle) => {
28
+ export const separatorArrayExplode = (style: ArraySeparatorStyle): '.' | ';' | ',' | '&' => {
29
29
  switch (style) {
30
30
  case 'label':
31
31
  return '.';
@@ -38,7 +38,7 @@ export const separatorArrayExplode = (style: ArraySeparatorStyle) => {
38
38
  }
39
39
  };
40
40
 
41
- export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => {
41
+ export const separatorArrayNoExplode = (style: ArraySeparatorStyle): ',' | '|' | '%20' => {
42
42
  switch (style) {
43
43
  case 'form':
44
44
  return ',';
@@ -51,7 +51,7 @@ export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => {
51
51
  }
52
52
  };
53
53
 
54
- export const separatorObjectExplode = (style: ObjectSeparatorStyle) => {
54
+ export const separatorObjectExplode = (style: ObjectSeparatorStyle): '.' | ';' | ',' | '&' => {
55
55
  switch (style) {
56
56
  case 'label':
57
57
  return '.';
@@ -72,7 +72,7 @@ export const serializeArrayParam = ({
72
72
  value,
73
73
  }: SerializeOptions<ArraySeparatorStyle> & {
74
74
  value: unknown[];
75
- }) => {
75
+ }): string => {
76
76
  if (!explode) {
77
77
  const joinedValues = (
78
78
  allowReserved ? value : value.map((v) => encodeURIComponent(v as string))
@@ -110,7 +110,7 @@ export const serializePrimitiveParam = ({
110
110
  allowReserved,
111
111
  name,
112
112
  value,
113
- }: SerializePrimitiveParam) => {
113
+ }: SerializePrimitiveParam): string => {
114
114
  if (value === undefined || value === null) {
115
115
  return '';
116
116
  }
@@ -134,7 +134,7 @@ export const serializeObjectParam = ({
134
134
  }: SerializeOptions<ObjectSeparatorStyle> & {
135
135
  value: Record<string, unknown> | Date;
136
136
  valueOnly?: boolean;
137
- }) => {
137
+ }): string => {
138
138
  if (value instanceof Date) {
139
139
  return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
140
140
  }
@@ -14,7 +14,7 @@ export type JsonValue =
14
14
  /**
15
15
  * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes.
16
16
  */
17
- export const queryKeyJsonReplacer = (_key: string, value: unknown) => {
17
+ export const queryKeyJsonReplacer = (_key: string, value: unknown): unknown | undefined => {
18
18
  if (value === undefined || typeof value === 'function' || typeof value === 'symbol') {
19
19
  return undefined;
20
20
  }
@@ -79,7 +79,7 @@ export type ServerSentEventsResult<TData = unknown, TReturn = void, TNext = unkn
79
79
  >;
80
80
  };
81
81
 
82
- export const createSseClient = <TData = unknown>({
82
+ export function createSseClient<TData = unknown>({
83
83
  onRequest,
84
84
  onSseError,
85
85
  onSseEvent,
@@ -91,7 +91,7 @@ export const createSseClient = <TData = unknown>({
91
91
  sseSleepFn,
92
92
  url,
93
93
  ...options
94
- }: ServerSentEventsOptions): ServerSentEventsResult<TData> => {
94
+ }: ServerSentEventsOptions): ServerSentEventsResult<TData> {
95
95
  let lastEventId: string | undefined;
96
96
 
97
97
  const sleep = sseSleepFn ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));
@@ -155,8 +155,7 @@ export const createSseClient = <TData = unknown>({
155
155
  const { done, value } = await reader.read();
156
156
  if (done) break;
157
157
  buffer += value;
158
- // Normalize line endings: CRLF -> LF, then CR -> LF
159
- buffer = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
158
+ buffer = buffer.replace(/\r\n?/g, '\n'); // normalize line endings
160
159
 
161
160
  const chunks = buffer.split('\n\n');
162
161
  buffer = chunks.pop() ?? '';
@@ -240,4 +239,4 @@ export const createSseClient = <TData = unknown>({
240
239
  const stream = createStream();
241
240
 
242
241
  return { stream };
243
- };
242
+ }
package/src/core/types.ts CHANGED
@@ -80,7 +80,7 @@ export interface Config {
80
80
  requestValidator?: (data: unknown) => Promise<unknown>;
81
81
  /**
82
82
  * A function transforming response data before it's returned. This is useful
83
- * for post-processing data, e.g. converting ISO strings into Date objects.
83
+ * for post-processing data, e.g., converting ISO strings into Date objects.
84
84
  */
85
85
  responseTransformer?: (data: unknown) => Promise<unknown>;
86
86
  /**
@@ -91,6 +91,12 @@ export interface Config {
91
91
  responseValidator?: (data: unknown) => Promise<unknown>;
92
92
  }
93
93
 
94
+ /**
95
+ * Arbitrary metadata passed through the `meta` request option.
96
+ */
97
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
98
+ export interface ClientMeta {}
99
+
94
100
  type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]
95
101
  ? true
96
102
  : [T] extends [never | undefined]
package/src/core/utils.ts CHANGED
@@ -13,9 +13,9 @@ export interface PathSerializer {
13
13
  url: string;
14
14
  }
15
15
 
16
- export const PATH_PARAM_RE = /\{[^{}]+\}/g;
16
+ export const PATH_PARAM_RE: RegExp = /\{[^{}]+\}/g;
17
17
 
18
- export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
18
+ export const defaultPathSerializer = ({ path, url: _url }: PathSerializer): string => {
19
19
  let url = _url;
20
20
  const matches = _url.match(PATH_PARAM_RE);
21
21
  if (matches) {
@@ -94,7 +94,7 @@ export const getUrl = ({
94
94
  query?: Record<string, unknown>;
95
95
  querySerializer: QuerySerializer;
96
96
  url: string;
97
- }) => {
97
+ }): string => {
98
98
  const pathUrl = _url.startsWith('/') ? _url : `/${_url}`;
99
99
  let url = (baseUrl ?? '') + pathUrl;
100
100
  if (path) {
@@ -114,7 +114,7 @@ export function getValidRequestBody(options: {
114
114
  body?: unknown;
115
115
  bodySerializer?: BodySerializer | null;
116
116
  serializedBody?: unknown;
117
- }) {
117
+ }): unknown {
118
118
  const hasBody = options.body !== undefined;
119
119
  const isSerializedBody = hasBody && options.bodySerializer;
120
120
 
@@ -126,7 +126,7 @@ export function getValidRequestBody(options: {
126
126
  return hasSerializedBody ? options.serializedBody : null;
127
127
  }
128
128
 
129
- // not all clients implement a serializedBody property (i.e. client-axios)
129
+ // not all clients implement a serializedBody property (i.e., client-axios)
130
130
  return options.body !== '' ? options.body : null;
131
131
  }
132
132
 
@@ -31,20 +31,24 @@ export const createClient = (config: Config = {}): Client => {
31
31
 
32
32
  const interceptors = createInterceptors<Request, Response, unknown, ResolvedRequestOptions>();
33
33
 
34
- const beforeRequest = async (options: RequestOptions) => {
34
+ const beforeRequest = async <
35
+ TData = unknown,
36
+ TResponseStyle extends 'data' | 'fields' = 'fields',
37
+ ThrowOnError extends boolean = boolean,
38
+ Url extends string = string,
39
+ >(
40
+ options: RequestOptions<TData, TResponseStyle, ThrowOnError, Url>,
41
+ ) => {
35
42
  const opts = {
36
43
  ..._config,
37
44
  ...options,
38
45
  fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
39
46
  headers: mergeHeaders(_config.headers, options.headers),
40
- serializedBody: undefined,
47
+ serializedBody: undefined as string | undefined,
41
48
  };
42
49
 
43
50
  if (opts.security) {
44
- await setAuthParams({
45
- ...opts,
46
- security: opts.security,
47
- });
51
+ await setAuthParams(opts);
48
52
  }
49
53
 
50
54
  if (opts.requestValidator) {
@@ -52,7 +56,7 @@ export const createClient = (config: Config = {}): Client => {
52
56
  }
53
57
 
54
58
  if (opts.body !== undefined && opts.bodySerializer) {
55
- opts.serializedBody = opts.bodySerializer(opts.body);
59
+ opts.serializedBody = opts.bodySerializer(opts.body) as string | undefined;
56
60
  }
57
61
 
58
62
  // remove Content-Type header if body is empty to avoid sending invalid requests
@@ -60,178 +64,162 @@ export const createClient = (config: Config = {}): Client => {
60
64
  opts.headers.delete('Content-Type');
61
65
  }
62
66
 
63
- const url = buildUrl(opts);
67
+ const resolvedOpts = opts as typeof opts &
68
+ ResolvedRequestOptions<TResponseStyle, ThrowOnError, Url>;
69
+ const url = buildUrl(resolvedOpts);
64
70
 
65
- return { opts, url };
71
+ return { opts: resolvedOpts, url };
66
72
  };
67
73
 
68
74
  const request: Client['request'] = async (options) => {
69
- // @ts-expect-error
70
- const { opts, url } = await beforeRequest(options);
71
- const requestInit: ReqInit = {
72
- redirect: 'follow',
73
- ...opts,
74
- body: getValidRequestBody(opts),
75
- };
76
-
77
- let request = new Request(url, requestInit);
75
+ const throwOnError = options.throwOnError ?? _config.throwOnError;
76
+ const responseStyle = options.responseStyle ?? _config.responseStyle;
78
77
 
79
- for (const fn of interceptors.request.fns) {
80
- if (fn) {
81
- request = await fn(request, opts);
82
- }
83
- }
84
-
85
- // fetch must be assigned here, otherwise it would throw the error:
86
- // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
87
- const _fetch = opts.fetch!;
88
- let response: Response;
78
+ let request: Request | undefined;
79
+ let response: Response | undefined;
89
80
 
90
81
  try {
91
- response = await _fetch(request);
92
- } catch (error) {
93
- // Handle fetch exceptions (AbortError, network errors, etc.)
94
- let finalError = error;
82
+ const { opts, url } = await beforeRequest(options);
83
+ const requestInit: ReqInit = {
84
+ redirect: 'follow',
85
+ ...opts,
86
+ body: getValidRequestBody(opts),
87
+ };
95
88
 
96
- for (const fn of interceptors.error.fns) {
89
+ request = new Request(url, requestInit);
90
+
91
+ for (const fn of interceptors.request.fns) {
97
92
  if (fn) {
98
- finalError = (await fn(error, undefined as any, request, opts)) as unknown;
93
+ request = await fn(request, opts);
99
94
  }
100
95
  }
101
96
 
102
- finalError = finalError || ({} as unknown);
97
+ // fetch must be assigned here, otherwise it would throw the error:
98
+ // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
99
+ const _fetch = opts.fetch!;
103
100
 
104
- if (opts.throwOnError) {
105
- throw finalError;
106
- }
107
-
108
- // Return error response
109
- return opts.responseStyle === 'data'
110
- ? undefined
111
- : {
112
- error: finalError,
113
- request,
114
- response: undefined as any,
115
- };
116
- }
101
+ response = await _fetch(request);
117
102
 
118
- for (const fn of interceptors.response.fns) {
119
- if (fn) {
120
- response = await fn(response, request, opts);
103
+ for (const fn of interceptors.response.fns) {
104
+ if (fn) {
105
+ response = await fn(response, request, opts);
106
+ }
121
107
  }
122
- }
123
-
124
- const result = {
125
- request,
126
- response,
127
- };
128
108
 
129
- if (response.ok) {
130
- const parseAs =
131
- (opts.parseAs === 'auto'
132
- ? getParseAs(response.headers.get('Content-Type'))
133
- : opts.parseAs) ?? 'json';
109
+ const result = {
110
+ request,
111
+ response,
112
+ };
113
+
114
+ if (response.ok) {
115
+ const parseAs =
116
+ (opts.parseAs === 'auto'
117
+ ? getParseAs(response.headers.get('Content-Type'))
118
+ : opts.parseAs) ?? 'json';
119
+
120
+ if (response.status === 204 || response.headers.get('Content-Length') === '0') {
121
+ let emptyData: any;
122
+ switch (parseAs) {
123
+ case 'arrayBuffer':
124
+ case 'blob':
125
+ case 'text':
126
+ emptyData = await response[parseAs]();
127
+ break;
128
+ case 'formData':
129
+ emptyData = new FormData();
130
+ break;
131
+ case 'stream':
132
+ emptyData = response.body;
133
+ break;
134
+ case 'json':
135
+ default:
136
+ emptyData = {};
137
+ break;
138
+ }
139
+ return opts.responseStyle === 'data'
140
+ ? emptyData
141
+ : {
142
+ data: emptyData,
143
+ ...result,
144
+ };
145
+ }
134
146
 
135
- if (response.status === 204 || response.headers.get('Content-Length') === '0') {
136
- let emptyData: any;
147
+ let data: any;
137
148
  switch (parseAs) {
138
149
  case 'arrayBuffer':
139
150
  case 'blob':
151
+ case 'formData':
140
152
  case 'text':
141
- emptyData = await response[parseAs]();
153
+ data = await response[parseAs]();
142
154
  break;
143
- case 'formData':
144
- emptyData = new FormData();
155
+ case 'json': {
156
+ // Some servers return 200 with no Content-Length and empty body.
157
+ // response.json() would throw; read as text and parse if non-empty.
158
+ const text = await response.text();
159
+ data = text ? JSON.parse(text) : {};
145
160
  break;
161
+ }
146
162
  case 'stream':
147
- emptyData = response.body;
148
- break;
149
- case 'json':
150
- default:
151
- emptyData = {};
152
- break;
163
+ return opts.responseStyle === 'data'
164
+ ? response.body
165
+ : {
166
+ data: response.body,
167
+ ...result,
168
+ };
169
+ }
170
+
171
+ if (parseAs === 'json') {
172
+ if (opts.responseValidator) {
173
+ await opts.responseValidator(data);
174
+ }
175
+
176
+ if (opts.responseTransformer) {
177
+ data = await opts.responseTransformer(data);
178
+ }
153
179
  }
180
+
154
181
  return opts.responseStyle === 'data'
155
- ? emptyData
182
+ ? data
156
183
  : {
157
- data: emptyData,
184
+ data,
158
185
  ...result,
159
186
  };
160
187
  }
161
188
 
162
- let data: any;
163
- switch (parseAs) {
164
- case 'arrayBuffer':
165
- case 'blob':
166
- case 'formData':
167
- case 'text':
168
- data = await response[parseAs]();
169
- break;
170
- case 'json': {
171
- // Some servers return 200 with no Content-Length and empty body.
172
- // response.json() would throw; read as text and parse if non-empty.
173
- const text = await response.text();
174
- data = text ? JSON.parse(text) : {};
175
- break;
176
- }
177
- case 'stream':
178
- return opts.responseStyle === 'data'
179
- ? response.body
180
- : {
181
- data: response.body,
182
- ...result,
183
- };
189
+ const textError = await response.text();
190
+ let jsonError: unknown;
191
+
192
+ try {
193
+ jsonError = JSON.parse(textError);
194
+ } catch {
195
+ // noop
184
196
  }
185
197
 
186
- if (parseAs === 'json') {
187
- if (opts.responseValidator) {
188
- await opts.responseValidator(data);
189
- }
198
+ throw jsonError ?? textError;
199
+ } catch (error) {
200
+ let finalError = error;
190
201
 
191
- if (opts.responseTransformer) {
192
- data = await opts.responseTransformer(data);
202
+ for (const fn of interceptors.error.fns) {
203
+ if (fn) {
204
+ finalError = await fn(finalError, response, request, options as ResolvedRequestOptions);
193
205
  }
194
206
  }
195
207
 
196
- return opts.responseStyle === 'data'
197
- ? data
198
- : {
199
- data,
200
- ...result,
201
- };
202
- }
208
+ finalError = finalError || {};
203
209
 
204
- const textError = await response.text();
205
- let jsonError: unknown;
206
-
207
- try {
208
- jsonError = JSON.parse(textError);
209
- } catch {
210
- // noop
211
- }
212
-
213
- const error = jsonError ?? textError;
214
- let finalError = error;
215
-
216
- for (const fn of interceptors.error.fns) {
217
- if (fn) {
218
- finalError = (await fn(error, response, request, opts)) as string;
210
+ if (throwOnError) {
211
+ throw finalError;
219
212
  }
220
- }
221
-
222
- finalError = finalError || ({} as string);
223
213
 
224
- if (opts.throwOnError) {
225
- throw finalError;
214
+ // TODO: we probably want to return error and improve types
215
+ return responseStyle === 'data'
216
+ ? undefined
217
+ : {
218
+ error: finalError,
219
+ request,
220
+ response,
221
+ };
226
222
  }
227
-
228
- // TODO: we probably want to return error and improve types
229
- return opts.responseStyle === 'data'
230
- ? undefined
231
- : {
232
- error: finalError,
233
- ...result,
234
- };
235
223
  };
236
224
 
237
225
  const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
@@ -242,7 +230,6 @@ export const createClient = (config: Config = {}): Client => {
242
230
  return createSseClient({
243
231
  ...opts,
244
232
  body: opts.body as BodyInit | null | undefined,
245
- headers: opts.headers as unknown as Record<string, string>,
246
233
  method,
247
234
  onRequest: async (url, init) => {
248
235
  let request = new Request(url, init);
@@ -258,8 +245,10 @@ export const createClient = (config: Config = {}): Client => {
258
245
  });
259
246
  };
260
247
 
248
+ const _buildUrl: Client['buildUrl'] = (options) => buildUrl({ ..._config, ...options });
249
+
261
250
  return {
262
- buildUrl,
251
+ buildUrl: _buildUrl,
263
252
  connect: makeMethodFn('CONNECT'),
264
253
  delete: makeMethodFn('DELETE'),
265
254
  get: makeMethodFn('GET'),
package/src/http/index.ts CHANGED
@@ -9,6 +9,8 @@ export {
9
9
  } from '../core/bodySerializer';
10
10
  export { buildClientParams } from '../core/params';
11
11
  export { serializeQueryKeyValue } from '../core/queryKeySerializer';
12
+ export type { ServerSentEventsResult } from '../core/serverSentEvents';
13
+ export type { ClientMeta } from '../core/types';
12
14
  export { createClient } from './client';
13
15
  export type {
14
16
  Client,
package/src/http/types.ts CHANGED
@@ -93,6 +93,7 @@ export interface ResolvedRequestOptions<
93
93
  ThrowOnError extends boolean = boolean,
94
94
  Url extends string = string,
95
95
  > extends RequestOptions<unknown, TResponseStyle, ThrowOnError, Url> {
96
+ headers: Headers;
96
97
  serializedBody?: string;
97
98
  }
98
99
 
@@ -126,8 +127,10 @@ export type RequestResult<
126
127
  error: TError extends Record<string, unknown> ? TError[keyof TError] : TError;
127
128
  }
128
129
  ) & {
129
- request: Request;
130
- response: Response;
130
+ /** request may be undefined, because error may be from building the request object itself */
131
+ request?: Request;
132
+ /** response may be undefined, because error may be from building the request object itself or from a network error */
133
+ response?: Response;
131
134
  }
132
135
  >;
133
136
 
@@ -148,12 +151,13 @@ type MethodFn = <
148
151
 
149
152
  type SseFn = <
150
153
  TData = unknown,
151
- TError = unknown,
154
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
155
+ _TError = unknown,
152
156
  ThrowOnError extends boolean = false,
153
157
  TResponseStyle extends ResponseStyle = 'fields',
154
158
  >(
155
- options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>,
156
- ) => Promise<ServerSentEventsResult<TData, TError>>;
159
+ options: Omit<RequestOptions<never, TResponseStyle, ThrowOnError>, 'method'>,
160
+ ) => Promise<ServerSentEventsResult<TData>>;
157
161
 
158
162
  type RequestFn = <
159
163
  TData = unknown,
@@ -190,7 +194,7 @@ export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn, SseFn>
190
194
  */
191
195
  export type CreateClientConfig<T extends ClientOptions = ClientOptions> = (
192
196
  override?: Config<ClientOptions & T>,
193
- ) => Config<Required<ClientOptions> & T> | Promise<Config<Required<ClientOptions> & T>>;
197
+ ) => Config<Required<ClientOptions> & T>;
194
198
 
195
199
  export interface TDataShape {
196
200
  body?: unknown;
package/src/http/utils.ts CHANGED
@@ -14,8 +14,8 @@ import type { Client, ClientOptions, Config, RequestOptions } from './types';
14
14
  export const createQuerySerializer = <T = unknown>({
15
15
  parameters = {},
16
16
  ...args
17
- }: QuerySerializerOptions = {}) => {
18
- const querySerializer = (queryParams: T) => {
17
+ }: QuerySerializerOptions = {}): ((queryParams: T) => string) => {
18
+ const querySerializer = (queryParams: T): string => {
19
19
  const search: string[] = [];
20
20
  if (queryParams && typeof queryParams === 'object') {
21
21
  for (const name in queryParams) {
@@ -118,14 +118,12 @@ const checkForExistence = (
118
118
  return false;
119
119
  };
120
120
 
121
- export const setAuthParams = async ({
122
- security,
123
- ...options
124
- }: Pick<Required<RequestOptions>, 'security'> &
125
- Pick<RequestOptions, 'auth' | 'query'> & {
121
+ export async function setAuthParams(
122
+ options: Pick<RequestOptions, 'auth' | 'query' | 'security'> & {
126
123
  headers: Headers;
127
- }) => {
128
- for (const auth of security) {
124
+ },
125
+ ): Promise<void> {
126
+ for (const auth of options.security ?? []) {
129
127
  if (checkForExistence(options, auth.name)) {
130
128
  continue;
131
129
  }
@@ -154,7 +152,7 @@ export const setAuthParams = async ({
154
152
  break;
155
153
  }
156
154
  }
157
- };
155
+ }
158
156
 
159
157
  export const buildUrl: Client['buildUrl'] = (options) =>
160
158
  getUrl({
@@ -204,7 +202,7 @@ export const mergeHeaders = (
204
202
  mergedHeaders.append(key, v as string);
205
203
  }
206
204
  } else if (value !== undefined) {
207
- // assume object headers are meant to be JSON stringified, i.e. their
205
+ // assume object headers are meant to be JSON stringified, i.e., their
208
206
  // content value in OpenAPI specification is 'application/json'
209
207
  mergedHeaders.set(
210
208
  key,
@@ -218,8 +216,10 @@ export const mergeHeaders = (
218
216
 
219
217
  type ErrInterceptor<Err, Res, Req, Options> = (
220
218
  error: Err,
221
- response: Res,
222
- request: Req,
219
+ /** response may be undefined due to a network error where no response object is produced */
220
+ response: Res | undefined,
221
+ /** request may be undefined, because error may be from building the request object itself */
222
+ request: Req | undefined,
223
223
  options: Options,
224
224
  ) => Err | Promise<Err>;
225
225