spiceflow 1.17.11 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +168 -3
- package/dist/client/errors.d.ts +2 -1
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +3 -1
- package/dist/client/errors.js.map +1 -1
- package/dist/client/fetch.d.ts +86 -0
- package/dist/client/fetch.d.ts.map +1 -0
- package/dist/client/fetch.js +143 -0
- package/dist/client/fetch.js.map +1 -0
- package/dist/client/index.d.ts +4 -9
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +39 -151
- package/dist/client/index.js.map +1 -1
- package/dist/client/shared.d.ts +47 -0
- package/dist/client/shared.d.ts.map +1 -0
- package/dist/client/shared.js +314 -0
- package/dist/client/shared.js.map +1 -0
- package/dist/client/types.d.ts +3 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client.test.js +43 -0
- package/dist/client.test.js.map +1 -1
- package/dist/fetch-client.test.d.ts +2 -0
- package/dist/fetch-client.test.d.ts.map +1 -0
- package/dist/fetch-client.test.js +362 -0
- package/dist/fetch-client.test.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-client-transport.d.ts.map +1 -1
- package/dist/mcp-client-transport.js +5 -2
- package/dist/mcp-client-transport.js.map +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/openapi.d.ts +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/spiceflow.d.ts +36 -14
- package/dist/spiceflow.d.ts.map +1 -1
- package/dist/spiceflow.js +49 -16
- package/dist/spiceflow.js.map +1 -1
- package/dist/spiceflow.test.js +205 -1
- package/dist/spiceflow.test.js.map +1 -1
- package/dist/stream.test.js +1 -1
- package/dist/stream.test.js.map +1 -1
- package/package.json +3 -3
- package/src/client/errors.ts +3 -0
- package/src/client/fetch.ts +447 -0
- package/src/client/index.ts +73 -192
- package/src/client/shared.ts +406 -0
- package/src/client/types.ts +3 -1
- package/src/client.test.ts +52 -0
- package/src/fetch-client.test.ts +411 -0
- package/src/index.ts +1 -1
- package/src/mcp-client-transport.ts +5 -2
- package/src/spiceflow.test.ts +315 -1
- package/src/spiceflow.ts +106 -32
- package/src/stream.test.ts +1 -1
package/src/client/errors.ts
CHANGED
|
@@ -3,9 +3,11 @@ export class SpiceflowFetchError<
|
|
|
3
3
|
Value extends any = any,
|
|
4
4
|
> extends Error {
|
|
5
5
|
value: Value
|
|
6
|
+
response?: Response
|
|
6
7
|
constructor(
|
|
7
8
|
public status: Status,
|
|
8
9
|
public passedValue: Value,
|
|
10
|
+
response?: Response,
|
|
9
11
|
) {
|
|
10
12
|
let message = String((passedValue as any)?.message || '')
|
|
11
13
|
if (!message) {
|
|
@@ -17,5 +19,6 @@ export class SpiceflowFetchError<
|
|
|
17
19
|
}
|
|
18
20
|
super(message)
|
|
19
21
|
this.value = passedValue
|
|
22
|
+
this.response = response
|
|
20
23
|
}
|
|
21
24
|
}
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
// Type-safe fetch-like client for Spiceflow. Uses a familiar fetch(path, options)
|
|
2
|
+
// interface instead of the proxy-based chainable API.
|
|
3
|
+
import type { AnySpiceflow, Spiceflow } from '../spiceflow.ts'
|
|
4
|
+
import type { ExtractParamsFromPath } from '../types.ts'
|
|
5
|
+
|
|
6
|
+
import type { SpiceflowClient } from './types.ts'
|
|
7
|
+
import type { ReplaceGeneratorWithAsyncGenerator } from './types.ts'
|
|
8
|
+
import { SpiceflowFetchError } from './errors.ts'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
processHeaders,
|
|
12
|
+
buildQueryString,
|
|
13
|
+
serializeBody,
|
|
14
|
+
parseResponseData,
|
|
15
|
+
executeWithRetries,
|
|
16
|
+
} from './shared.ts'
|
|
17
|
+
|
|
18
|
+
// ─── Type utilities ──────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
type HttpMethodLower =
|
|
21
|
+
| 'get'
|
|
22
|
+
| 'post'
|
|
23
|
+
| 'put'
|
|
24
|
+
| 'delete'
|
|
25
|
+
| 'patch'
|
|
26
|
+
| 'options'
|
|
27
|
+
| 'head'
|
|
28
|
+
| 'connect'
|
|
29
|
+
| 'subscribe'
|
|
30
|
+
|
|
31
|
+
// Navigate the nested ClientRoutes tree given a path string.
|
|
32
|
+
// Reverses what CreateClient does: `/users/:id` → Routes['users'][':id']
|
|
33
|
+
type NavigateRoutes<Routes, Path extends string> =
|
|
34
|
+
Path extends `/${infer Rest}`
|
|
35
|
+
? Rest extends ''
|
|
36
|
+
? 'index' extends keyof Routes
|
|
37
|
+
? Routes['index']
|
|
38
|
+
: never
|
|
39
|
+
: _NavigateRoutes<Routes, Rest>
|
|
40
|
+
: _NavigateRoutes<Routes, Path>
|
|
41
|
+
|
|
42
|
+
type _NavigateRoutes<Routes, Path extends string> =
|
|
43
|
+
Path extends `${infer Segment}/${infer Rest}`
|
|
44
|
+
? Segment extends keyof Routes
|
|
45
|
+
? _NavigateRoutes<Routes[Segment], Rest>
|
|
46
|
+
: never
|
|
47
|
+
: Path extends keyof Routes
|
|
48
|
+
? Routes[Path]
|
|
49
|
+
: never
|
|
50
|
+
|
|
51
|
+
type RouteAtPath<
|
|
52
|
+
Routes extends Record<string, any>,
|
|
53
|
+
Path extends string,
|
|
54
|
+
> = NavigateRoutes<Routes, Path>
|
|
55
|
+
|
|
56
|
+
type MethodsAtPath<
|
|
57
|
+
Routes extends Record<string, any>,
|
|
58
|
+
Path extends string,
|
|
59
|
+
> = Extract<keyof RouteAtPath<Routes, Path>, HttpMethodLower>
|
|
60
|
+
|
|
61
|
+
type AllowedMethod<
|
|
62
|
+
Routes extends Record<string, any>,
|
|
63
|
+
Path extends string,
|
|
64
|
+
> = Uppercase<MethodsAtPath<Routes, Path>> | MethodsAtPath<Routes, Path>
|
|
65
|
+
|
|
66
|
+
type RouteInfoForMethod<
|
|
67
|
+
Routes extends Record<string, any>,
|
|
68
|
+
Path extends string,
|
|
69
|
+
Method extends string,
|
|
70
|
+
> = Lowercase<Method> extends keyof RouteAtPath<Routes, Path>
|
|
71
|
+
? RouteAtPath<Routes, Path>[Lowercase<Method>]
|
|
72
|
+
: never
|
|
73
|
+
|
|
74
|
+
// ─── Options type ────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
// Params option: required if path has :params, omitted otherwise
|
|
77
|
+
type ParamsOption<Path extends string> =
|
|
78
|
+
ExtractParamsFromPath<Path> extends undefined
|
|
79
|
+
? { params?: Record<string, string> }
|
|
80
|
+
: { params: ExtractParamsFromPath<Path> }
|
|
81
|
+
|
|
82
|
+
// Query option: typed from route schema if available
|
|
83
|
+
type QueryOption<
|
|
84
|
+
Routes extends Record<string, any>,
|
|
85
|
+
Path extends string,
|
|
86
|
+
Method extends string,
|
|
87
|
+
> = RouteInfoForMethod<Routes, Path, Method> extends {
|
|
88
|
+
query: infer Q
|
|
89
|
+
}
|
|
90
|
+
? undefined extends Q
|
|
91
|
+
? { query?: Record<string, unknown> }
|
|
92
|
+
: { query: Q }
|
|
93
|
+
: { query?: Record<string, unknown> }
|
|
94
|
+
|
|
95
|
+
// Body option: typed from route schema, only for non-GET/HEAD/SUBSCRIBE methods
|
|
96
|
+
type BodyOption<
|
|
97
|
+
Routes extends Record<string, any>,
|
|
98
|
+
Path extends string,
|
|
99
|
+
Method extends string,
|
|
100
|
+
> = Lowercase<Method> extends 'get' | 'head' | 'subscribe'
|
|
101
|
+
? {}
|
|
102
|
+
: RouteInfoForMethod<Routes, Path, Method> extends {
|
|
103
|
+
request: infer Body
|
|
104
|
+
}
|
|
105
|
+
? undefined extends Body
|
|
106
|
+
? { body?: unknown }
|
|
107
|
+
: { body: Body }
|
|
108
|
+
: { body?: unknown }
|
|
109
|
+
|
|
110
|
+
// Check if options has any required fields
|
|
111
|
+
type HasRequiredFields<
|
|
112
|
+
Routes extends Record<string, any>,
|
|
113
|
+
Path extends string,
|
|
114
|
+
Method extends string,
|
|
115
|
+
> =
|
|
116
|
+
// params required?
|
|
117
|
+
ExtractParamsFromPath<Path> extends undefined
|
|
118
|
+
? // query required?
|
|
119
|
+
RouteInfoForMethod<Routes, Path, Method> extends { query: infer Q }
|
|
120
|
+
? undefined extends Q
|
|
121
|
+
? // body required?
|
|
122
|
+
Lowercase<Method> extends 'get' | 'head' | 'subscribe'
|
|
123
|
+
? false
|
|
124
|
+
: RouteInfoForMethod<Routes, Path, Method> extends {
|
|
125
|
+
request: infer Body
|
|
126
|
+
}
|
|
127
|
+
? undefined extends Body
|
|
128
|
+
? false
|
|
129
|
+
: true
|
|
130
|
+
: false
|
|
131
|
+
: true
|
|
132
|
+
: // body required?
|
|
133
|
+
Lowercase<Method> extends 'get' | 'head' | 'subscribe'
|
|
134
|
+
? false
|
|
135
|
+
: RouteInfoForMethod<Routes, Path, Method> extends {
|
|
136
|
+
request: infer Body
|
|
137
|
+
}
|
|
138
|
+
? undefined extends Body
|
|
139
|
+
? false
|
|
140
|
+
: true
|
|
141
|
+
: false
|
|
142
|
+
: true
|
|
143
|
+
|
|
144
|
+
type FetchOptionsTyped<
|
|
145
|
+
Routes extends Record<string, any>,
|
|
146
|
+
Path extends string,
|
|
147
|
+
Method extends string,
|
|
148
|
+
> = {
|
|
149
|
+
method?: Method
|
|
150
|
+
headers?: RequestInit['headers']
|
|
151
|
+
signal?: AbortSignal
|
|
152
|
+
} & ParamsOption<Path> &
|
|
153
|
+
QueryOption<Routes, Path, Method> &
|
|
154
|
+
BodyOption<Routes, Path, Method>
|
|
155
|
+
|
|
156
|
+
type FetchOptionsFallback = {
|
|
157
|
+
method?: string
|
|
158
|
+
body?: BodyInit | Record<string, unknown> | null
|
|
159
|
+
query?: Record<string, unknown>
|
|
160
|
+
params?: Record<string, string>
|
|
161
|
+
headers?: RequestInit['headers']
|
|
162
|
+
signal?: AbortSignal
|
|
163
|
+
[key: string]: unknown
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
type FetchOptions<
|
|
167
|
+
Routes extends Record<string, any>,
|
|
168
|
+
Path extends string,
|
|
169
|
+
Method extends string,
|
|
170
|
+
> = [RouteAtPath<Routes, Path>] extends [never]
|
|
171
|
+
? FetchOptionsFallback
|
|
172
|
+
: FetchOptionsTyped<Routes, Path, Method>
|
|
173
|
+
|
|
174
|
+
// ─── Response type ───────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
type FetchResultData<
|
|
177
|
+
Routes extends Record<string, any>,
|
|
178
|
+
Path extends string,
|
|
179
|
+
Method extends string,
|
|
180
|
+
> = [RouteAtPath<Routes, Path>] extends [never]
|
|
181
|
+
? any
|
|
182
|
+
: RouteInfoForMethod<Routes, Path, Method> extends {
|
|
183
|
+
response: infer Res extends Record<number, unknown>
|
|
184
|
+
}
|
|
185
|
+
? ReplaceGeneratorWithAsyncGenerator<Res>[200]
|
|
186
|
+
: any
|
|
187
|
+
|
|
188
|
+
type FetchResultError<
|
|
189
|
+
Routes extends Record<string, any>,
|
|
190
|
+
Path extends string,
|
|
191
|
+
Method extends string,
|
|
192
|
+
> = [RouteAtPath<Routes, Path>] extends [never]
|
|
193
|
+
? SpiceflowFetchError<number, any>
|
|
194
|
+
: RouteInfoForMethod<Routes, Path, Method> extends {
|
|
195
|
+
response: infer Res extends Record<number, unknown>
|
|
196
|
+
}
|
|
197
|
+
? Exclude<keyof Res, 200> extends never
|
|
198
|
+
? SpiceflowFetchError<number, any>
|
|
199
|
+
: {
|
|
200
|
+
[Status in keyof Res]: SpiceflowFetchError<Status, Res[Status]>
|
|
201
|
+
}[Exclude<keyof Res, 200>]
|
|
202
|
+
: SpiceflowFetchError<number, any>
|
|
203
|
+
|
|
204
|
+
type FetchResult<
|
|
205
|
+
Routes extends Record<string, any>,
|
|
206
|
+
Path extends string,
|
|
207
|
+
Method extends string,
|
|
208
|
+
> = FetchResultError<Routes, Path, Method> | FetchResultData<Routes, Path, Method>
|
|
209
|
+
|
|
210
|
+
// ─── Public type ─────────────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
// Resolves options for a given App/Path/Method combination.
|
|
213
|
+
// Returns the appropriate options type, or FetchOptionsFallback for unknown paths.
|
|
214
|
+
type ResolveOptions<
|
|
215
|
+
App extends AnySpiceflow,
|
|
216
|
+
Path extends string,
|
|
217
|
+
Method extends string,
|
|
218
|
+
> = App extends {
|
|
219
|
+
_types: { ClientRoutes: infer Routes extends Record<string, any> }
|
|
220
|
+
}
|
|
221
|
+
? FetchOptions<Routes, Path, Method>
|
|
222
|
+
: FetchOptionsFallback
|
|
223
|
+
|
|
224
|
+
// Resolves the result type for a given App/Path/Method combination.
|
|
225
|
+
type ResolveResult<
|
|
226
|
+
App extends AnySpiceflow,
|
|
227
|
+
Path extends string,
|
|
228
|
+
Method extends string,
|
|
229
|
+
> = App extends {
|
|
230
|
+
_types: { ClientRoutes: infer Routes extends Record<string, any> }
|
|
231
|
+
}
|
|
232
|
+
? FetchResult<Routes, Path, Method>
|
|
233
|
+
: SpiceflowFetchError<number, any> | any
|
|
234
|
+
|
|
235
|
+
// Check if options are required for a given App/Path/Method
|
|
236
|
+
type IsOptionsRequired<
|
|
237
|
+
App extends AnySpiceflow,
|
|
238
|
+
Path extends string,
|
|
239
|
+
Method extends string,
|
|
240
|
+
> = App extends {
|
|
241
|
+
_types: { ClientRoutes: infer Routes extends Record<string, any> }
|
|
242
|
+
}
|
|
243
|
+
? [RouteAtPath<Routes, Path>] extends [never]
|
|
244
|
+
? false
|
|
245
|
+
: HasRequiredFields<Routes, Path, Method>
|
|
246
|
+
: false
|
|
247
|
+
|
|
248
|
+
export interface SpiceflowFetch<App extends AnySpiceflow> {
|
|
249
|
+
// Overload: options required when route demands params/query/body
|
|
250
|
+
<const Path extends string, const Method extends string = 'GET'>(
|
|
251
|
+
...args: IsOptionsRequired<App, Path, Method> extends true
|
|
252
|
+
? [path: Path, options: ResolveOptions<App, Path, Method>]
|
|
253
|
+
: [path: Path, options?: ResolveOptions<App, Path, Method>]
|
|
254
|
+
): Promise<ResolveResult<App, Path, Method>>
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ─── Factory ─────────────────────────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
export function createSpiceflowFetch<const App extends AnySpiceflow>(
|
|
260
|
+
domain: App | string,
|
|
261
|
+
config: SpiceflowClient.Config &
|
|
262
|
+
(App extends Spiceflow<any, any, infer Singleton, any, any, any, any>
|
|
263
|
+
? { state?: Singleton['state'] }
|
|
264
|
+
: {}) = {} as any,
|
|
265
|
+
): SpiceflowFetch<App> {
|
|
266
|
+
let baseUrl: string
|
|
267
|
+
let instance: AnySpiceflow | undefined
|
|
268
|
+
|
|
269
|
+
if (typeof domain === 'string') {
|
|
270
|
+
baseUrl = domain.endsWith('/') ? domain.slice(0, -1) : domain
|
|
271
|
+
} else {
|
|
272
|
+
baseUrl = 'http://e.ly'
|
|
273
|
+
instance = domain
|
|
274
|
+
|
|
275
|
+
if (typeof window !== 'undefined') {
|
|
276
|
+
console.warn(
|
|
277
|
+
'Spiceflow instance server found on client side, this is not recommended for security reason. Use generic type instead.',
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if ((config as any).state && !instance) {
|
|
283
|
+
throw new Error('State is only available when using a Spiceflow instance')
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const spiceflowFetch = async (
|
|
287
|
+
path: string,
|
|
288
|
+
options: any = {},
|
|
289
|
+
): Promise<any> => {
|
|
290
|
+
let {
|
|
291
|
+
fetch: fetcher = fetch,
|
|
292
|
+
headers: configHeaders,
|
|
293
|
+
onRequest,
|
|
294
|
+
onResponse,
|
|
295
|
+
retries = 0,
|
|
296
|
+
} = config as SpiceflowClient.Config
|
|
297
|
+
|
|
298
|
+
const {
|
|
299
|
+
method: rawMethod = 'GET',
|
|
300
|
+
body,
|
|
301
|
+
query,
|
|
302
|
+
params,
|
|
303
|
+
headers: optionHeaders,
|
|
304
|
+
signal,
|
|
305
|
+
...restInit
|
|
306
|
+
} = options
|
|
307
|
+
|
|
308
|
+
const methodUpper = rawMethod.toUpperCase()
|
|
309
|
+
const isGetOrHead =
|
|
310
|
+
methodUpper === 'GET' ||
|
|
311
|
+
methodUpper === 'HEAD' ||
|
|
312
|
+
methodUpper === 'SUBSCRIBE'
|
|
313
|
+
|
|
314
|
+
// Resolve path params (replace :param with values)
|
|
315
|
+
// Sort by key length descending to avoid :id replacing inside :id2
|
|
316
|
+
let resolvedPath = path
|
|
317
|
+
if (params && typeof params === 'object') {
|
|
318
|
+
const entries = Object.entries(params).sort(
|
|
319
|
+
([a], [b]) => b.length - a.length,
|
|
320
|
+
)
|
|
321
|
+
for (const [key, value] of entries) {
|
|
322
|
+
if (key === '*') {
|
|
323
|
+
resolvedPath = resolvedPath.split('*').join(String(value))
|
|
324
|
+
} else {
|
|
325
|
+
resolvedPath = resolvedPath.split(`:${key}`).join(String(value))
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const queryString = buildQueryString(query)
|
|
331
|
+
|
|
332
|
+
// Support absolute URLs — skip baseUrl concatenation
|
|
333
|
+
const isAbsoluteUrl = /^https?:\/\//i.test(resolvedPath)
|
|
334
|
+
const url = isAbsoluteUrl
|
|
335
|
+
? resolvedPath + queryString
|
|
336
|
+
: baseUrl + resolvedPath + queryString
|
|
337
|
+
|
|
338
|
+
let headers = processHeaders(configHeaders, resolvedPath, {
|
|
339
|
+
method: methodUpper,
|
|
340
|
+
signal,
|
|
341
|
+
})
|
|
342
|
+
headers = {
|
|
343
|
+
...headers,
|
|
344
|
+
...processHeaders(optionHeaders, resolvedPath, {
|
|
345
|
+
method: methodUpper,
|
|
346
|
+
signal,
|
|
347
|
+
}),
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let fetchInit: RequestInit = {
|
|
351
|
+
method: methodUpper,
|
|
352
|
+
headers,
|
|
353
|
+
signal,
|
|
354
|
+
...restInit,
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Apply onRequest hooks (first pass, before body serialization)
|
|
358
|
+
if (onRequest) {
|
|
359
|
+
const hooks = Array.isArray(onRequest) ? onRequest : [onRequest]
|
|
360
|
+
for (const hook of hooks) {
|
|
361
|
+
const temp = await hook(resolvedPath, fetchInit)
|
|
362
|
+
if (typeof temp === 'object') {
|
|
363
|
+
fetchInit = {
|
|
364
|
+
...fetchInit,
|
|
365
|
+
...temp,
|
|
366
|
+
headers: {
|
|
367
|
+
...fetchInit.headers,
|
|
368
|
+
...processHeaders(temp.headers, resolvedPath, fetchInit),
|
|
369
|
+
},
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Ensure GET/HEAD has no body before serialization
|
|
376
|
+
if (isGetOrHead) delete fetchInit.body
|
|
377
|
+
|
|
378
|
+
// Serialize body
|
|
379
|
+
if (!isGetOrHead && body !== undefined) {
|
|
380
|
+
fetchInit.body = body
|
|
381
|
+
await serializeBody({ body, fetchInit, isGetOrHead })
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (isGetOrHead) {
|
|
385
|
+
delete fetchInit.body
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Add x-spiceflow-agent header
|
|
389
|
+
;(fetchInit.headers as Record<string, string>)['x-spiceflow-agent'] =
|
|
390
|
+
'spiceflow-client'
|
|
391
|
+
|
|
392
|
+
// Apply onRequest hooks (second pass, after body serialization — matches proxy client behavior)
|
|
393
|
+
if (onRequest) {
|
|
394
|
+
const hooks = Array.isArray(onRequest) ? onRequest : [onRequest]
|
|
395
|
+
for (const hook of hooks) {
|
|
396
|
+
const temp = await hook(resolvedPath, fetchInit)
|
|
397
|
+
if (typeof temp === 'object') {
|
|
398
|
+
fetchInit = {
|
|
399
|
+
...fetchInit,
|
|
400
|
+
...temp,
|
|
401
|
+
headers: {
|
|
402
|
+
...fetchInit.headers,
|
|
403
|
+
...processHeaders(temp.headers, resolvedPath, fetchInit),
|
|
404
|
+
} as Record<string, string>,
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Execute request with retries
|
|
411
|
+
const executeRequest = () =>
|
|
412
|
+
executeWithRetries({
|
|
413
|
+
url,
|
|
414
|
+
fetchInit,
|
|
415
|
+
fetcher: fetcher || fetch,
|
|
416
|
+
instance,
|
|
417
|
+
state: (config as any).state,
|
|
418
|
+
retries,
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
const response = await executeRequest()
|
|
422
|
+
|
|
423
|
+
// Process onResponse hooks
|
|
424
|
+
if (onResponse) {
|
|
425
|
+
const hooks = Array.isArray(onResponse) ? onResponse : [onResponse]
|
|
426
|
+
for (const hook of hooks) {
|
|
427
|
+
await hook(response.clone())
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Parse response
|
|
432
|
+
const { data, error } = await parseResponseData({
|
|
433
|
+
response,
|
|
434
|
+
executeRequest,
|
|
435
|
+
retries,
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
if (error) {
|
|
439
|
+
error.response = response
|
|
440
|
+
return error
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return data
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return spiceflowFetch as any
|
|
447
|
+
}
|