@zilfu/sdk 0.0.1 → 0.1.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.
@@ -3,12 +3,7 @@
3
3
  import { createSseClient } from '../core/serverSentEvents.gen';
4
4
  import type { HttpMethod } from '../core/types.gen';
5
5
  import { getValidRequestBody } from '../core/utils.gen';
6
- import type {
7
- Client,
8
- Config,
9
- RequestOptions,
10
- ResolvedRequestOptions,
11
- } from './types.gen';
6
+ import type { Client, Config, RequestOptions, ResolvedRequestOptions } from './types.gen';
12
7
  import {
13
8
  buildUrl,
14
9
  createConfig,
@@ -34,20 +29,22 @@ export const createClient = (config: Config = {}): Client => {
34
29
  return getConfig();
35
30
  };
36
31
 
37
- const interceptors = createInterceptors<
38
- Request,
39
- Response,
40
- unknown,
41
- ResolvedRequestOptions
42
- >();
32
+ const interceptors = createInterceptors<Request, Response, unknown, ResolvedRequestOptions>();
43
33
 
44
- 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
+ ) => {
45
42
  const opts = {
46
43
  ..._config,
47
44
  ...options,
48
45
  fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
49
46
  headers: mergeHeaders(_config.headers, options.headers),
50
- serializedBody: undefined,
47
+ serializedBody: undefined as string | undefined,
51
48
  };
52
49
 
53
50
  if (opts.security) {
@@ -62,7 +59,7 @@ export const createClient = (config: Config = {}): Client => {
62
59
  }
63
60
 
64
61
  if (opts.body !== undefined && opts.bodySerializer) {
65
- opts.serializedBody = opts.bodySerializer(opts.body);
62
+ opts.serializedBody = opts.bodySerializer(opts.body) as string | undefined;
66
63
  }
67
64
 
68
65
  // remove Content-Type header if body is empty to avoid sending invalid requests
@@ -70,176 +67,191 @@ export const createClient = (config: Config = {}): Client => {
70
67
  opts.headers.delete('Content-Type');
71
68
  }
72
69
 
73
- const url = buildUrl(opts);
70
+ const resolvedOpts = opts as typeof opts &
71
+ ResolvedRequestOptions<TResponseStyle, ThrowOnError, Url>;
72
+ const url = buildUrl(resolvedOpts);
74
73
 
75
- return { opts, url };
74
+ return { opts: resolvedOpts, url };
76
75
  };
77
76
 
78
77
  const request: Client['request'] = async (options) => {
79
- // @ts-expect-error
80
- const { opts, url } = await beforeRequest(options);
81
- const requestInit: ReqInit = {
82
- redirect: 'follow',
83
- ...opts,
84
- body: getValidRequestBody(opts),
85
- };
78
+ const throwOnError = options.throwOnError ?? _config.throwOnError;
79
+ const responseStyle = options.responseStyle ?? _config.responseStyle;
80
+
81
+ let request: Request | undefined;
82
+ let response: Response | undefined;
83
+
84
+ try {
85
+ const { opts, url } = await beforeRequest(options);
86
+ const requestInit: ReqInit = {
87
+ redirect: 'follow',
88
+ ...opts,
89
+ body: getValidRequestBody(opts),
90
+ };
86
91
 
87
- let request = new Request(url, requestInit);
92
+ request = new Request(url, requestInit);
88
93
 
89
- for (const fn of interceptors.request.fns) {
90
- if (fn) {
91
- request = await fn(request, opts);
94
+ for (const fn of interceptors.request.fns) {
95
+ if (fn) {
96
+ request = await fn(request, opts);
97
+ }
92
98
  }
93
- }
94
99
 
95
- // fetch must be assigned here, otherwise it would throw the error:
96
- // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
97
- const _fetch = opts.fetch!;
98
- let response = await _fetch(request);
100
+ // fetch must be assigned here, otherwise it would throw the error:
101
+ // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
102
+ const _fetch = opts.fetch!;
99
103
 
100
- for (const fn of interceptors.response.fns) {
101
- if (fn) {
102
- response = await fn(response, request, opts);
104
+ response = await _fetch(request);
105
+
106
+ for (const fn of interceptors.response.fns) {
107
+ if (fn) {
108
+ response = await fn(response, request, opts);
109
+ }
103
110
  }
104
- }
105
111
 
106
- const result = {
107
- request,
108
- response,
109
- };
112
+ const result = {
113
+ request,
114
+ response,
115
+ };
116
+
117
+ if (response.ok) {
118
+ const parseAs =
119
+ (opts.parseAs === 'auto'
120
+ ? getParseAs(response.headers.get('Content-Type'))
121
+ : opts.parseAs) ?? 'json';
122
+
123
+ if (response.status === 204 || response.headers.get('Content-Length') === '0') {
124
+ let emptyData: any;
125
+ switch (parseAs) {
126
+ case 'arrayBuffer':
127
+ case 'blob':
128
+ case 'text':
129
+ emptyData = await response[parseAs]();
130
+ break;
131
+ case 'formData':
132
+ emptyData = new FormData();
133
+ break;
134
+ case 'stream':
135
+ emptyData = response.body;
136
+ break;
137
+ case 'json':
138
+ default:
139
+ emptyData = {};
140
+ break;
141
+ }
142
+ return opts.responseStyle === 'data'
143
+ ? emptyData
144
+ : {
145
+ data: emptyData,
146
+ ...result,
147
+ };
148
+ }
110
149
 
111
- if (response.ok) {
112
- const parseAs =
113
- (opts.parseAs === 'auto'
114
- ? getParseAs(response.headers.get('Content-Type'))
115
- : opts.parseAs) ?? 'json';
116
-
117
- if (
118
- response.status === 204 ||
119
- response.headers.get('Content-Length') === '0'
120
- ) {
121
- let emptyData: any;
150
+ let data: any;
122
151
  switch (parseAs) {
123
152
  case 'arrayBuffer':
124
153
  case 'blob':
154
+ case 'formData':
125
155
  case 'text':
126
- emptyData = await response[parseAs]();
156
+ data = await response[parseAs]();
127
157
  break;
128
- case 'formData':
129
- emptyData = new FormData();
158
+ case 'json': {
159
+ // Some servers return 200 with no Content-Length and empty body.
160
+ // response.json() would throw; read as text and parse if non-empty.
161
+ const text = await response.text();
162
+ data = text ? JSON.parse(text) : {};
130
163
  break;
164
+ }
131
165
  case 'stream':
132
- emptyData = response.body;
133
- break;
134
- case 'json':
135
- default:
136
- emptyData = {};
137
- break;
166
+ return opts.responseStyle === 'data'
167
+ ? response.body
168
+ : {
169
+ data: response.body,
170
+ ...result,
171
+ };
172
+ }
173
+
174
+ if (parseAs === 'json') {
175
+ if (opts.responseValidator) {
176
+ await opts.responseValidator(data);
177
+ }
178
+
179
+ if (opts.responseTransformer) {
180
+ data = await opts.responseTransformer(data);
181
+ }
138
182
  }
183
+
139
184
  return opts.responseStyle === 'data'
140
- ? emptyData
185
+ ? data
141
186
  : {
142
- data: emptyData,
187
+ data,
143
188
  ...result,
144
189
  };
145
190
  }
146
191
 
147
- let data: any;
148
- switch (parseAs) {
149
- case 'arrayBuffer':
150
- case 'blob':
151
- case 'formData':
152
- case 'json':
153
- case 'text':
154
- data = await response[parseAs]();
155
- break;
156
- case 'stream':
157
- return opts.responseStyle === 'data'
158
- ? response.body
159
- : {
160
- data: response.body,
161
- ...result,
162
- };
192
+ const textError = await response.text();
193
+ let jsonError: unknown;
194
+
195
+ try {
196
+ jsonError = JSON.parse(textError);
197
+ } catch {
198
+ // noop
163
199
  }
164
200
 
165
- if (parseAs === 'json') {
166
- if (opts.responseValidator) {
167
- await opts.responseValidator(data);
168
- }
201
+ throw jsonError ?? textError;
202
+ } catch (error) {
203
+ let finalError = error;
169
204
 
170
- if (opts.responseTransformer) {
171
- data = await opts.responseTransformer(data);
205
+ for (const fn of interceptors.error.fns) {
206
+ if (fn) {
207
+ finalError = await fn(finalError, response, request, options as ResolvedRequestOptions);
172
208
  }
173
209
  }
174
210
 
175
- return opts.responseStyle === 'data'
176
- ? data
177
- : {
178
- data,
179
- ...result,
180
- };
181
- }
182
-
183
- const textError = await response.text();
184
- let jsonError: unknown;
185
-
186
- try {
187
- jsonError = JSON.parse(textError);
188
- } catch {
189
- // noop
190
- }
191
-
192
- const error = jsonError ?? textError;
193
- let finalError = error;
211
+ finalError = finalError || {};
194
212
 
195
- for (const fn of interceptors.error.fns) {
196
- if (fn) {
197
- finalError = (await fn(error, response, request, opts)) as string;
213
+ if (throwOnError) {
214
+ throw finalError;
198
215
  }
199
- }
200
-
201
- finalError = finalError || ({} as string);
202
216
 
203
- if (opts.throwOnError) {
204
- throw finalError;
217
+ // TODO: we probably want to return error and improve types
218
+ return responseStyle === 'data'
219
+ ? undefined
220
+ : {
221
+ error: finalError,
222
+ request,
223
+ response,
224
+ };
205
225
  }
206
-
207
- // TODO: we probably want to return error and improve types
208
- return opts.responseStyle === 'data'
209
- ? undefined
210
- : {
211
- error: finalError,
212
- ...result,
213
- };
214
226
  };
215
227
 
216
- const makeMethodFn =
217
- (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
218
- request({ ...options, method });
228
+ const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
229
+ request({ ...options, method });
219
230
 
220
- const makeSseFn =
221
- (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
222
- const { opts, url } = await beforeRequest(options);
223
- return createSseClient({
224
- ...opts,
225
- body: opts.body as BodyInit | null | undefined,
226
- headers: opts.headers as unknown as Record<string, string>,
227
- method,
228
- onRequest: async (url, init) => {
229
- let request = new Request(url, init);
230
- for (const fn of interceptors.request.fns) {
231
- if (fn) {
232
- request = await fn(request, opts);
233
- }
231
+ const makeSseFn = (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
232
+ const { opts, url } = await beforeRequest(options);
233
+ return createSseClient({
234
+ ...opts,
235
+ body: opts.body as BodyInit | null | undefined,
236
+ method,
237
+ onRequest: async (url, init) => {
238
+ let request = new Request(url, init);
239
+ for (const fn of interceptors.request.fns) {
240
+ if (fn) {
241
+ request = await fn(request, opts);
234
242
  }
235
- return request;
236
- },
237
- url,
238
- });
239
- };
243
+ }
244
+ return request;
245
+ },
246
+ serializedBody: getValidRequestBody(opts) as BodyInit | null | undefined,
247
+ url,
248
+ });
249
+ };
250
+
251
+ const _buildUrl: Client['buildUrl'] = (options) => buildUrl({ ..._config, ...options });
240
252
 
241
253
  return {
242
- buildUrl,
254
+ buildUrl: _buildUrl,
243
255
  connect: makeMethodFn('CONNECT'),
244
256
  delete: makeMethodFn('DELETE'),
245
257
  get: makeMethodFn('GET'),
@@ -8,6 +8,7 @@ export {
8
8
  urlSearchParamsBodySerializer,
9
9
  } from '../core/bodySerializer.gen';
10
10
  export { buildClientParams } from '../core/params.gen';
11
+ export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen';
11
12
  export { createClient } from './client.gen';
12
13
  export type {
13
14
  Client,
@@ -15,7 +16,6 @@ export type {
15
16
  Config,
16
17
  CreateClientConfig,
17
18
  Options,
18
- OptionsLegacyParser,
19
19
  RequestOptions,
20
20
  RequestResult,
21
21
  ResolvedRequestOptions,
@@ -5,17 +5,13 @@ import type {
5
5
  ServerSentEventsOptions,
6
6
  ServerSentEventsResult,
7
7
  } from '../core/serverSentEvents.gen';
8
- import type {
9
- Client as CoreClient,
10
- Config as CoreConfig,
11
- } from '../core/types.gen';
8
+ import type { Client as CoreClient, Config as CoreConfig } from '../core/types.gen';
12
9
  import type { Middleware } from './utils.gen';
13
10
 
14
11
  export type ResponseStyle = 'data' | 'fields';
15
12
 
16
13
  export interface Config<T extends ClientOptions = ClientOptions>
17
- extends Omit<RequestInit, 'body' | 'headers' | 'method'>,
18
- CoreConfig {
14
+ extends Omit<RequestInit, 'body' | 'headers' | 'method'>, CoreConfig {
19
15
  /**
20
16
  * Base URL for all requests made by this client.
21
17
  */
@@ -42,14 +38,7 @@ export interface Config<T extends ClientOptions = ClientOptions>
42
38
  *
43
39
  * @default 'auto'
44
40
  */
45
- parseAs?:
46
- | 'arrayBuffer'
47
- | 'auto'
48
- | 'blob'
49
- | 'formData'
50
- | 'json'
51
- | 'stream'
52
- | 'text';
41
+ parseAs?: 'arrayBuffer' | 'auto' | 'blob' | 'formData' | 'json' | 'stream' | 'text';
53
42
  /**
54
43
  * Should we return only data or multiple fields (data, error, response, etc.)?
55
44
  *
@@ -69,12 +58,15 @@ export interface RequestOptions<
69
58
  TResponseStyle extends ResponseStyle = 'fields',
70
59
  ThrowOnError extends boolean = boolean,
71
60
  Url extends string = string,
72
- > extends Config<{
61
+ >
62
+ extends
63
+ Config<{
73
64
  responseStyle: TResponseStyle;
74
65
  throwOnError: ThrowOnError;
75
66
  }>,
76
67
  Pick<
77
68
  ServerSentEventsOptions<TData>,
69
+ | 'onRequest'
78
70
  | 'onSseError'
79
71
  | 'onSseEvent'
80
72
  | 'sseDefaultRetryDelay'
@@ -101,6 +93,7 @@ export interface ResolvedRequestOptions<
101
93
  ThrowOnError extends boolean = boolean,
102
94
  Url extends string = string,
103
95
  > extends RequestOptions<unknown, TResponseStyle, ThrowOnError, Url> {
96
+ headers: Headers;
104
97
  serializedBody?: string;
105
98
  }
106
99
 
@@ -116,36 +109,28 @@ export type RequestResult<
116
109
  ? TData[keyof TData]
117
110
  : TData
118
111
  : {
119
- data: TData extends Record<string, unknown>
120
- ? TData[keyof TData]
121
- : TData;
112
+ data: TData extends Record<string, unknown> ? TData[keyof TData] : TData;
122
113
  request: Request;
123
114
  response: Response;
124
115
  }
125
116
  >
126
117
  : Promise<
127
118
  TResponseStyle extends 'data'
128
- ?
129
- | (TData extends Record<string, unknown>
130
- ? TData[keyof TData]
131
- : TData)
132
- | undefined
119
+ ? (TData extends Record<string, unknown> ? TData[keyof TData] : TData) | undefined
133
120
  : (
134
121
  | {
135
- data: TData extends Record<string, unknown>
136
- ? TData[keyof TData]
137
- : TData;
122
+ data: TData extends Record<string, unknown> ? TData[keyof TData] : TData;
138
123
  error: undefined;
139
124
  }
140
125
  | {
141
126
  data: undefined;
142
- error: TError extends Record<string, unknown>
143
- ? TError[keyof TError]
144
- : TError;
127
+ error: TError extends Record<string, unknown> ? TError[keyof TError] : TError;
145
128
  }
146
129
  ) & {
147
- request: Request;
148
- 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;
149
134
  }
150
135
  >;
151
136
 
@@ -170,7 +155,7 @@ type SseFn = <
170
155
  ThrowOnError extends boolean = false,
171
156
  TResponseStyle extends ResponseStyle = 'fields',
172
157
  >(
173
- options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>,
158
+ options: Omit<RequestOptions<never, TResponseStyle, ThrowOnError>, 'method'>,
174
159
  ) => Promise<ServerSentEventsResult<TData, TError>>;
175
160
 
176
161
  type RequestFn = <
@@ -180,10 +165,7 @@ type RequestFn = <
180
165
  TResponseStyle extends ResponseStyle = 'fields',
181
166
  >(
182
167
  options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'> &
183
- Pick<
184
- Required<RequestOptions<TData, TResponseStyle, ThrowOnError>>,
185
- 'method'
186
- >,
168
+ Pick<Required<RequestOptions<TData, TResponseStyle, ThrowOnError>>, 'method'>,
187
169
  ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
188
170
 
189
171
  type BuildUrlFn = <
@@ -194,16 +176,10 @@ type BuildUrlFn = <
194
176
  url: string;
195
177
  },
196
178
  >(
197
- options: Pick<TData, 'url'> & Options<TData>,
179
+ options: TData & Options<TData>,
198
180
  ) => string;
199
181
 
200
- export type Client = CoreClient<
201
- RequestFn,
202
- Config,
203
- MethodFn,
204
- BuildUrlFn,
205
- SseFn
206
- > & {
182
+ export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn, SseFn> & {
207
183
  interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>;
208
184
  };
209
185
 
@@ -238,31 +214,4 @@ export type Options<
238
214
  RequestOptions<TResponse, TResponseStyle, ThrowOnError>,
239
215
  'body' | 'path' | 'query' | 'url'
240
216
  > &
241
- Omit<TData, 'url'>;
242
-
243
- export type OptionsLegacyParser<
244
- TData = unknown,
245
- ThrowOnError extends boolean = boolean,
246
- TResponseStyle extends ResponseStyle = 'fields',
247
- > = TData extends { body?: any }
248
- ? TData extends { headers?: any }
249
- ? OmitKeys<
250
- RequestOptions<unknown, TResponseStyle, ThrowOnError>,
251
- 'body' | 'headers' | 'url'
252
- > &
253
- TData
254
- : OmitKeys<
255
- RequestOptions<unknown, TResponseStyle, ThrowOnError>,
256
- 'body' | 'url'
257
- > &
258
- TData &
259
- Pick<RequestOptions<unknown, TResponseStyle, ThrowOnError>, 'headers'>
260
- : TData extends { headers?: any }
261
- ? OmitKeys<
262
- RequestOptions<unknown, TResponseStyle, ThrowOnError>,
263
- 'headers' | 'url'
264
- > &
265
- TData &
266
- Pick<RequestOptions<unknown, TResponseStyle, ThrowOnError>, 'body'>
267
- : OmitKeys<RequestOptions<unknown, TResponseStyle, ThrowOnError>, 'url'> &
268
- TData;
217
+ ([TData] extends [never] ? unknown : Omit<TData, 'url'>);