spiceflow 1.1.8 → 1.1.9

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.
Files changed (58) hide show
  1. package/README.md +177 -92
  2. package/dist/benchmark.benchmark.js.map +1 -1
  3. package/dist/client/errors.d.ts.map +1 -1
  4. package/dist/client/errors.js.map +1 -1
  5. package/dist/client/index.d.ts.map +1 -1
  6. package/dist/client/index.js +8 -12
  7. package/dist/client/index.js.map +1 -1
  8. package/dist/client/types.d.ts.map +1 -1
  9. package/dist/client/utils.js.map +1 -1
  10. package/dist/client/ws.d.ts.map +1 -1
  11. package/dist/client/ws.js +1 -3
  12. package/dist/client/ws.js.map +1 -1
  13. package/dist/client.test.js.map +1 -1
  14. package/dist/context.d.ts.map +1 -1
  15. package/dist/cors.d.ts.map +1 -1
  16. package/dist/cors.js.map +1 -1
  17. package/dist/cors.test.js.map +1 -1
  18. package/dist/error.d.ts.map +1 -1
  19. package/dist/error.js.map +1 -1
  20. package/dist/middleware.test.js.map +1 -1
  21. package/dist/openapi.d.ts.map +1 -1
  22. package/dist/openapi.js +1 -1
  23. package/dist/openapi.js.map +1 -1
  24. package/dist/openapi.test.js.map +1 -1
  25. package/dist/spiceflow.d.ts.map +1 -1
  26. package/dist/spiceflow.js +4 -2
  27. package/dist/spiceflow.js.map +1 -1
  28. package/dist/spiceflow.test.js.map +1 -1
  29. package/dist/stream.test.js.map +1 -1
  30. package/dist/types.d.ts.map +1 -1
  31. package/dist/types.js.map +1 -1
  32. package/dist/types.test.js +2 -6
  33. package/dist/types.test.js.map +1 -1
  34. package/dist/utils.d.ts.map +1 -1
  35. package/dist/utils.js.map +1 -1
  36. package/dist/zod.test.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/benchmark.benchmark.ts +8 -8
  39. package/src/client/errors.ts +17 -17
  40. package/src/client/index.ts +437 -469
  41. package/src/client/types.ts +168 -191
  42. package/src/client/utils.ts +5 -5
  43. package/src/client/ws.ts +87 -89
  44. package/src/client.test.ts +176 -183
  45. package/src/context.ts +82 -82
  46. package/src/cors.test.ts +38 -38
  47. package/src/cors.ts +87 -92
  48. package/src/error.ts +13 -13
  49. package/src/middleware.test.ts +201 -201
  50. package/src/openapi.test.ts +97 -97
  51. package/src/openapi.ts +365 -365
  52. package/src/spiceflow.test.ts +461 -467
  53. package/src/spiceflow.ts +1117 -1161
  54. package/src/stream.test.ts +310 -310
  55. package/src/types.test.ts +46 -50
  56. package/src/types.ts +698 -701
  57. package/src/utils.ts +79 -79
  58. package/src/zod.test.ts +64 -64
@@ -13,518 +13,486 @@ import { EdenFetchError } from './errors.js'
13
13
  import { parseStringifiedValue } from './utils.js'
14
14
 
15
15
  const method = [
16
- 'get',
17
- 'post',
18
- 'put',
19
- 'delete',
20
- 'patch',
21
- 'options',
22
- 'head',
23
- 'connect',
24
- 'subscribe',
16
+ 'get',
17
+ 'post',
18
+ 'put',
19
+ 'delete',
20
+ 'patch',
21
+ 'options',
22
+ 'head',
23
+ 'connect',
24
+ 'subscribe',
25
25
  ] as const
26
26
 
27
27
  const isServer = typeof FileList === 'undefined'
28
28
 
29
29
  const isFile = (v: any) => {
30
- if (isServer) return v instanceof Blob
30
+ if (isServer) return v instanceof Blob
31
31
 
32
- return v instanceof FileList || v instanceof File
32
+ return v instanceof FileList || v instanceof File
33
33
  }
34
34
 
35
35
  // FormData is 1 level deep
36
36
  const hasFile = (obj: Record<string, any>) => {
37
- if (!obj) return false
37
+ if (!obj) return false
38
38
 
39
- for (const key in obj) {
40
- if (isFile(obj[key])) return true
39
+ for (const key in obj) {
40
+ if (isFile(obj[key])) return true
41
41
 
42
- if (Array.isArray(obj[key]) && (obj[key] as unknown[]).find(isFile))
43
- return true
44
- }
42
+ if (Array.isArray(obj[key]) && (obj[key] as unknown[]).find(isFile))
43
+ return true
44
+ }
45
45
 
46
- return false
46
+ return false
47
47
  }
48
48
 
49
49
  const createNewFile = (v: File) =>
50
- isServer
51
- ? v
52
- : new Promise<File>((resolve) => {
53
- const reader = new FileReader()
54
-
55
- reader.onload = () => {
56
- const file = new File([reader.result!], v.name, {
57
- lastModified: v.lastModified,
58
- type: v.type,
59
- })
60
- resolve(file)
61
- }
62
-
63
- reader.readAsArrayBuffer(v)
64
- })
50
+ isServer
51
+ ? v
52
+ : new Promise<File>((resolve) => {
53
+ const reader = new FileReader()
54
+
55
+ reader.onload = () => {
56
+ const file = new File([reader.result!], v.name, {
57
+ lastModified: v.lastModified,
58
+ type: v.type,
59
+ })
60
+ resolve(file)
61
+ }
62
+
63
+ reader.readAsArrayBuffer(v)
64
+ })
65
65
 
66
66
  const processHeaders = (
67
- h: SpiceflowClient.Config['headers'],
68
- path: string,
69
- options: RequestInit = {},
70
- headers: Record<string, string> = {},
67
+ h: SpiceflowClient.Config['headers'],
68
+ path: string,
69
+ options: RequestInit = {},
70
+ headers: Record<string, string> = {},
71
71
  ): Record<string, string> => {
72
- if (Array.isArray(h)) {
73
- for (const value of h)
74
- if (!Array.isArray(value))
75
- headers = processHeaders(value, path, options, headers)
76
- else {
77
- const key = value[0]
78
- if (typeof key === 'string')
79
- headers[key.toLowerCase()] = value[1] as string
80
- else
81
- for (const [k, value] of key)
82
- headers[k.toLowerCase()] = value as string
83
- }
84
-
85
- return headers
86
- }
87
-
88
- if (!h) return headers
89
-
90
- switch (typeof h) {
91
- case 'function':
92
- if (typeof Headers !== 'undefined' && h instanceof Headers)
93
- return processHeaders(h, path, options, headers)
94
-
95
- const v = h(path, options)
96
- if (v) return processHeaders(v, path, options, headers)
97
- return headers
98
-
99
- case 'object':
100
- if (typeof Headers !== 'undefined' && h instanceof Headers) {
101
- h.forEach((value, key) => {
102
- headers[key.toLowerCase()] = value
103
- })
104
- return headers
105
- }
106
-
107
- for (const [key, value] of Object.entries(h))
108
- headers[key.toLowerCase()] = value as string
109
-
110
- return headers
111
-
112
- default:
113
- return headers
114
- }
72
+ if (Array.isArray(h)) {
73
+ for (const value of h)
74
+ if (!Array.isArray(value))
75
+ headers = processHeaders(value, path, options, headers)
76
+ else {
77
+ const key = value[0]
78
+ if (typeof key === 'string')
79
+ headers[key.toLowerCase()] = value[1] as string
80
+ else
81
+ for (const [k, value] of key)
82
+ headers[k.toLowerCase()] = value as string
83
+ }
84
+
85
+ return headers
86
+ }
87
+
88
+ if (!h) return headers
89
+
90
+ switch (typeof h) {
91
+ case 'function':
92
+ if (typeof Headers !== 'undefined' && h instanceof Headers)
93
+ return processHeaders(h, path, options, headers)
94
+
95
+ const v = h(path, options)
96
+ if (v) return processHeaders(v, path, options, headers)
97
+ return headers
98
+
99
+ case 'object':
100
+ if (typeof Headers !== 'undefined' && h instanceof Headers) {
101
+ h.forEach((value, key) => {
102
+ headers[key.toLowerCase()] = value
103
+ })
104
+ return headers
105
+ }
106
+
107
+ for (const [key, value] of Object.entries(h))
108
+ headers[key.toLowerCase()] = value as string
109
+
110
+ return headers
111
+
112
+ default:
113
+ return headers
114
+ }
115
115
  }
116
116
 
117
117
  interface SSEEvent {
118
- event: string
119
- data: any
120
- id?: string
118
+ event: string
119
+ data: any
120
+ id?: string
121
121
  }
122
122
 
123
123
  export class TextDecoderStream extends TransformStream<Uint8Array, string> {
124
- constructor() {
125
- const decoder = new TextDecoder('utf-8', {
126
- fatal: true,
127
- ignoreBOM: true,
128
- })
129
- super({
130
- transform(
131
- chunk: Uint8Array,
132
- controller: TransformStreamDefaultController<string>,
133
- ) {
134
- const decoded = decoder.decode(chunk, { stream: true })
135
- if (decoded.length > 0) {
136
- controller.enqueue(decoded)
137
- }
138
- },
139
- flush(controller: TransformStreamDefaultController<string>) {
140
- const output = decoder.decode()
141
- if (output.length > 0) {
142
- controller.enqueue(output)
143
- }
144
- },
145
- })
146
- }
124
+ constructor() {
125
+ const decoder = new TextDecoder('utf-8', {
126
+ fatal: true,
127
+ ignoreBOM: true,
128
+ })
129
+ super({
130
+ transform(
131
+ chunk: Uint8Array,
132
+ controller: TransformStreamDefaultController<string>,
133
+ ) {
134
+ const decoded = decoder.decode(chunk, { stream: true })
135
+ if (decoded.length > 0) {
136
+ controller.enqueue(decoded)
137
+ }
138
+ },
139
+ flush(controller: TransformStreamDefaultController<string>) {
140
+ const output = decoder.decode()
141
+ if (output.length > 0) {
142
+ controller.enqueue(output)
143
+ }
144
+ },
145
+ })
146
+ }
147
147
  }
148
148
 
149
149
  export async function* streamSSEResponse(
150
- response: Response,
150
+ response: Response,
151
151
  ): AsyncGenerator<SSEEvent> {
152
- const body = response.body
153
- if (!body) return
154
-
155
- const eventStream = response.body
156
- .pipeThrough(new TextDecoderStream())
157
- .pipeThrough(new EventSourceParserStream())
158
-
159
- let reader = eventStream.getReader()
160
- while (true) {
161
- const { done, value: event } = await reader.read()
162
- if (done) break
163
- if (event?.event === 'error') {
164
- throw new EdenFetchError(500, event.data)
165
- }
166
- if (event) {
167
- yield tryParsingJson(event.data)
168
- }
169
- }
152
+ const body = response.body
153
+ if (!body) return
154
+
155
+ const eventStream = response.body
156
+ .pipeThrough(new TextDecoderStream())
157
+ .pipeThrough(new EventSourceParserStream())
158
+
159
+ let reader = eventStream.getReader()
160
+ while (true) {
161
+ const { done, value: event } = await reader.read()
162
+ if (done) break
163
+ if (event?.event === 'error') {
164
+ throw new EdenFetchError(500, event.data)
165
+ }
166
+ if (event) {
167
+ yield tryParsingJson(event.data)
168
+ }
169
+ }
170
170
  }
171
171
 
172
172
  function tryParsingJson(data: string): any {
173
- try {
174
- return JSON.parse(data)
175
- } catch (error) {
176
- return null
177
- }
173
+ try {
174
+ return JSON.parse(data)
175
+ } catch (error) {
176
+ return null
177
+ }
178
178
  }
179
179
 
180
180
  const createProxy = (
181
- domain: string,
182
- config: SpiceflowClient.Config,
183
- paths: string[] = [],
184
- instance?: Spiceflow<any, any, any, any, any, any>,
181
+ domain: string,
182
+ config: SpiceflowClient.Config,
183
+ paths: string[] = [],
184
+ instance?: Spiceflow<any, any, any, any, any, any>,
185
185
  ): any =>
186
- new Proxy(() => {}, {
187
- get(_, param: string): any {
188
- return createProxy(
189
- domain,
190
- config,
191
- param === 'index' ? paths : [...paths, param],
192
- instance,
193
- )
194
- },
195
- apply(_, __, [body, options]) {
196
- if (
197
- !body ||
198
- options ||
199
- (typeof body === 'object' && Object.keys(body).length !== 1) ||
200
- method.includes(paths.at(-1) as any)
201
- ) {
202
- const methodPaths = [...paths]
203
- const method = methodPaths.pop()
204
- const path = '/' + methodPaths.join('/')
205
-
206
- let {
207
- fetch: fetcher = fetch,
208
- headers,
209
- onRequest,
210
- onResponse,
211
- } = config
212
-
213
- const isGetOrHead =
214
- method === 'get' ||
215
- method === 'head' ||
216
- method === 'subscribe'
217
-
218
- headers = processHeaders(headers, path, options)
219
-
220
- const query = isGetOrHead
221
- ? (body as Record<string, string | string[] | undefined>)
222
- ?.query
223
- : options?.query
224
-
225
- let q = ''
226
- if (query) {
227
- const append = (key: string, value: string) => {
228
- q +=
229
- (q ? '&' : '?') +
230
- `${encodeURIComponent(key)}=${encodeURIComponent(
231
- value,
232
- )}`
233
- }
234
-
235
- for (const [key, value] of Object.entries(query)) {
236
- if (Array.isArray(value)) {
237
- for (const v of value) append(key, v)
238
- continue
239
- }
240
-
241
- if (typeof value === 'object') {
242
- append(key, JSON.stringify(value))
243
- continue
244
- }
245
-
246
- append(key, `${value}`)
247
- }
248
- }
249
-
250
- // if (method === 'subscribe') {
251
- // const url =
252
- // domain.replace(
253
- // /^([^]+):\/\//,
254
- // domain.startsWith('https://')
255
- // ? 'wss://'
256
- // : domain.startsWith('http://')
257
- // ? 'ws://'
258
- // : locals.find((v) =>
259
- // (domain as string).includes(v)
260
- // )
261
- // ? 'ws://'
262
- // : 'wss://'
263
- // ) +
264
- // path +
265
- // q
266
-
267
- // return new EdenWS(url)
268
- // }
269
-
270
- return (async () => {
271
- let fetchInit = {
272
- method: method?.toUpperCase(),
273
- body,
274
- // ...conf,
275
- headers,
276
- } satisfies RequestInit
277
-
278
- fetchInit.headers = {
279
- ...headers,
280
- ...processHeaders(
281
- // For GET and HEAD, options is moved to body (1st param)
282
- isGetOrHead ? body?.headers : options?.headers,
283
- path,
284
- fetchInit,
285
- ),
286
- }
287
-
288
- const fetchOpts =
289
- isGetOrHead && typeof body === 'object'
290
- ? body.fetch
291
- : options?.fetch
292
-
293
- fetchInit = {
294
- ...fetchInit,
295
- ...fetchOpts,
296
- }
297
-
298
- if (isGetOrHead) delete fetchInit.body
299
-
300
- if (onRequest) {
301
- if (!Array.isArray(onRequest)) onRequest = [onRequest]
302
-
303
- for (const value of onRequest) {
304
- const temp = await value(path, fetchInit)
305
-
306
- if (typeof temp === 'object')
307
- fetchInit = {
308
- ...fetchInit,
309
- ...temp,
310
- headers: {
311
- ...fetchInit.headers,
312
- ...processHeaders(
313
- temp.headers,
314
- path,
315
- fetchInit,
316
- ),
317
- },
318
- }
319
- }
320
- }
321
-
322
- // ? Duplicate because end-user might add a body in onRequest
323
- if (isGetOrHead) delete fetchInit.body
324
-
325
- if (hasFile(body)) {
326
- const formData = new FormData()
327
-
328
- // FormData is 1 level deep
329
- for (const [key, field] of Object.entries(
330
- fetchInit.body,
331
- )) {
332
- if (isServer) {
333
- formData.append(key, field as any)
334
-
335
- continue
336
- }
337
-
338
- if (field instanceof File) {
339
- formData.append(
340
- key,
341
- await createNewFile(field as any),
342
- )
343
-
344
- continue
345
- }
346
-
347
- if (field instanceof FileList) {
348
- for (let i = 0; i < field.length; i++)
349
- formData.append(
350
- key as any,
351
- await createNewFile((field as any)[i]),
352
- )
353
-
354
- continue
355
- }
356
-
357
- if (Array.isArray(field)) {
358
- for (let i = 0; i < field.length; i++) {
359
- const value = (field as any)[i]
360
-
361
- formData.append(
362
- key as any,
363
- value instanceof File
364
- ? await createNewFile(value)
365
- : value,
366
- )
367
- }
368
-
369
- continue
370
- }
371
-
372
- formData.append(key, field as string)
373
- }
374
-
375
- // We don't do this because we need to let the browser set the content type with the correct boundary
376
- // fetchInit.headers['content-type'] = 'multipart/form-data'
377
- fetchInit.body = formData
378
- } else if (typeof body === 'object') {
379
- ;(fetchInit.headers as Record<string, string>)[
380
- 'content-type'
381
- ] = 'application/json'
382
-
383
- fetchInit.body = JSON.stringify(body)
384
- } else if (body !== undefined && body !== null) {
385
- ;(fetchInit.headers as Record<string, string>)[
386
- 'content-type'
387
- ] = 'text/plain'
388
- }
389
-
390
- if (isGetOrHead) delete fetchInit.body
391
-
392
- if (onRequest) {
393
- if (!Array.isArray(onRequest)) onRequest = [onRequest]
394
-
395
- for (const value of onRequest) {
396
- const temp = await value(path, fetchInit)
397
-
398
- if (typeof temp === 'object')
399
- fetchInit = {
400
- ...fetchInit,
401
- ...temp,
402
- headers: {
403
- ...fetchInit.headers,
404
- ...processHeaders(
405
- temp.headers,
406
- path,
407
- fetchInit,
408
- ),
409
- } as Record<string, string>,
410
- }
411
- }
412
- }
413
-
414
- const url = domain + path + q
415
- const response = await (instance?.handle(
416
- new Request(url, fetchInit),
417
- ) ?? fetcher!(url, fetchInit))
418
-
419
- let data = null as any
420
- let error = null as any
421
-
422
- if (onResponse) {
423
- if (!Array.isArray(onResponse))
424
- onResponse = [onResponse]
425
-
426
- for (const value of onResponse)
427
- try {
428
- const temp = await value(response.clone())
429
-
430
- if (temp !== undefined && temp !== null) {
431
- data = temp
432
- break
433
- }
434
- } catch (err) {
435
- if (err instanceof EdenFetchError) error = err
436
- else error = new EdenFetchError(422, err)
437
-
438
- break
439
- }
440
- }
441
-
442
- if (data !== null) {
443
- return {
444
- data,
445
- error,
446
- response,
447
- status: response.status,
448
- headers: response.headers,
449
- }
450
- }
451
-
452
- switch (
453
- response.headers.get('Content-Type')?.split(';')[0]
454
- ) {
455
- case 'text/event-stream':
456
- data = streamSSEResponse(response)
457
- break
458
-
459
- case 'application/json':
460
- data = await response.json()
461
- break
462
- case 'application/octet-stream':
463
- data = await response.arrayBuffer()
464
- break
465
-
466
- case 'multipart/form-data':
467
- const temp = await response.formData()
468
-
469
- data = {}
470
- temp.forEach((value, key) => {
471
- // @ts-ignore
472
- data[key] = value
473
- })
474
-
475
- break
476
-
477
- default:
478
- data = await response
479
- .text()
480
- .then(parseStringifiedValue)
481
- }
482
-
483
- if (response.status >= 300 || response.status < 200) {
484
- error = new EdenFetchError(response.status, data)
485
- data = null
486
- }
487
-
488
- return {
489
- data,
490
- error,
491
- response,
492
- status: response.status,
493
- headers: response.headers,
494
- }
495
- })()
496
- }
497
-
498
- if (typeof body === 'object')
499
- return createProxy(
500
- domain,
501
- config,
502
- [...paths, Object.values(body)[0] as string],
503
- instance,
504
- )
505
-
506
- return createProxy(domain, config, paths)
507
- },
508
- }) as any
186
+ new Proxy(() => {}, {
187
+ get(_, param: string): any {
188
+ return createProxy(
189
+ domain,
190
+ config,
191
+ param === 'index' ? paths : [...paths, param],
192
+ instance,
193
+ )
194
+ },
195
+ apply(_, __, [body, options]) {
196
+ if (
197
+ !body ||
198
+ options ||
199
+ (typeof body === 'object' && Object.keys(body).length !== 1) ||
200
+ method.includes(paths.at(-1) as any)
201
+ ) {
202
+ const methodPaths = [...paths]
203
+ const method = methodPaths.pop()
204
+ const path = '/' + methodPaths.join('/')
205
+
206
+ let { fetch: fetcher = fetch, headers, onRequest, onResponse } = config
207
+
208
+ const isGetOrHead =
209
+ method === 'get' || method === 'head' || method === 'subscribe'
210
+
211
+ headers = processHeaders(headers, path, options)
212
+
213
+ const query = isGetOrHead
214
+ ? (body as Record<string, string | string[] | undefined>)?.query
215
+ : options?.query
216
+
217
+ let q = ''
218
+ if (query) {
219
+ const append = (key: string, value: string) => {
220
+ q +=
221
+ (q ? '&' : '?') +
222
+ `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
223
+ }
224
+
225
+ for (const [key, value] of Object.entries(query)) {
226
+ if (Array.isArray(value)) {
227
+ for (const v of value) append(key, v)
228
+ continue
229
+ }
230
+
231
+ if (typeof value === 'object') {
232
+ append(key, JSON.stringify(value))
233
+ continue
234
+ }
235
+
236
+ append(key, `${value}`)
237
+ }
238
+ }
239
+
240
+ // if (method === 'subscribe') {
241
+ // const url =
242
+ // domain.replace(
243
+ // /^([^]+):\/\//,
244
+ // domain.startsWith('https://')
245
+ // ? 'wss://'
246
+ // : domain.startsWith('http://')
247
+ // ? 'ws://'
248
+ // : locals.find((v) =>
249
+ // (domain as string).includes(v)
250
+ // )
251
+ // ? 'ws://'
252
+ // : 'wss://'
253
+ // ) +
254
+ // path +
255
+ // q
256
+
257
+ // return new EdenWS(url)
258
+ // }
259
+
260
+ return (async () => {
261
+ let fetchInit = {
262
+ method: method?.toUpperCase(),
263
+ body,
264
+ // ...conf,
265
+ headers,
266
+ } satisfies RequestInit
267
+
268
+ fetchInit.headers = {
269
+ ...headers,
270
+ ...processHeaders(
271
+ // For GET and HEAD, options is moved to body (1st param)
272
+ isGetOrHead ? body?.headers : options?.headers,
273
+ path,
274
+ fetchInit,
275
+ ),
276
+ }
277
+
278
+ const fetchOpts =
279
+ isGetOrHead && typeof body === 'object'
280
+ ? body.fetch
281
+ : options?.fetch
282
+
283
+ fetchInit = {
284
+ ...fetchInit,
285
+ ...fetchOpts,
286
+ }
287
+
288
+ if (isGetOrHead) delete fetchInit.body
289
+
290
+ if (onRequest) {
291
+ if (!Array.isArray(onRequest)) onRequest = [onRequest]
292
+
293
+ for (const value of onRequest) {
294
+ const temp = await value(path, fetchInit)
295
+
296
+ if (typeof temp === 'object')
297
+ fetchInit = {
298
+ ...fetchInit,
299
+ ...temp,
300
+ headers: {
301
+ ...fetchInit.headers,
302
+ ...processHeaders(temp.headers, path, fetchInit),
303
+ },
304
+ }
305
+ }
306
+ }
307
+
308
+ // ? Duplicate because end-user might add a body in onRequest
309
+ if (isGetOrHead) delete fetchInit.body
310
+
311
+ if (hasFile(body)) {
312
+ const formData = new FormData()
313
+
314
+ // FormData is 1 level deep
315
+ for (const [key, field] of Object.entries(fetchInit.body)) {
316
+ if (isServer) {
317
+ formData.append(key, field as any)
318
+
319
+ continue
320
+ }
321
+
322
+ if (field instanceof File) {
323
+ formData.append(key, await createNewFile(field as any))
324
+
325
+ continue
326
+ }
327
+
328
+ if (field instanceof FileList) {
329
+ for (let i = 0; i < field.length; i++)
330
+ formData.append(
331
+ key as any,
332
+ await createNewFile((field as any)[i]),
333
+ )
334
+
335
+ continue
336
+ }
337
+
338
+ if (Array.isArray(field)) {
339
+ for (let i = 0; i < field.length; i++) {
340
+ const value = (field as any)[i]
341
+
342
+ formData.append(
343
+ key as any,
344
+ value instanceof File ? await createNewFile(value) : value,
345
+ )
346
+ }
347
+
348
+ continue
349
+ }
350
+
351
+ formData.append(key, field as string)
352
+ }
353
+
354
+ // We don't do this because we need to let the browser set the content type with the correct boundary
355
+ // fetchInit.headers['content-type'] = 'multipart/form-data'
356
+ fetchInit.body = formData
357
+ } else if (typeof body === 'object') {
358
+ ;(fetchInit.headers as Record<string, string>)['content-type'] =
359
+ 'application/json'
360
+
361
+ fetchInit.body = JSON.stringify(body)
362
+ } else if (body !== undefined && body !== null) {
363
+ ;(fetchInit.headers as Record<string, string>)['content-type'] =
364
+ 'text/plain'
365
+ }
366
+
367
+ if (isGetOrHead) delete fetchInit.body
368
+
369
+ if (onRequest) {
370
+ if (!Array.isArray(onRequest)) onRequest = [onRequest]
371
+
372
+ for (const value of onRequest) {
373
+ const temp = await value(path, fetchInit)
374
+
375
+ if (typeof temp === 'object')
376
+ fetchInit = {
377
+ ...fetchInit,
378
+ ...temp,
379
+ headers: {
380
+ ...fetchInit.headers,
381
+ ...processHeaders(temp.headers, path, fetchInit),
382
+ } as Record<string, string>,
383
+ }
384
+ }
385
+ }
386
+
387
+ const url = domain + path + q
388
+ const response = await (instance?.handle(
389
+ new Request(url, fetchInit),
390
+ ) ?? fetcher!(url, fetchInit))
391
+
392
+ let data = null as any
393
+ let error = null as any
394
+
395
+ if (onResponse) {
396
+ if (!Array.isArray(onResponse)) onResponse = [onResponse]
397
+
398
+ for (const value of onResponse)
399
+ try {
400
+ const temp = await value(response.clone())
401
+
402
+ if (temp !== undefined && temp !== null) {
403
+ data = temp
404
+ break
405
+ }
406
+ } catch (err) {
407
+ if (err instanceof EdenFetchError) error = err
408
+ else error = new EdenFetchError(422, err)
409
+
410
+ break
411
+ }
412
+ }
413
+
414
+ if (data !== null) {
415
+ return {
416
+ data,
417
+ error,
418
+ response,
419
+ status: response.status,
420
+ headers: response.headers,
421
+ }
422
+ }
423
+
424
+ switch (response.headers.get('Content-Type')?.split(';')[0]) {
425
+ case 'text/event-stream':
426
+ data = streamSSEResponse(response)
427
+ break
428
+
429
+ case 'application/json':
430
+ data = await response.json()
431
+ break
432
+ case 'application/octet-stream':
433
+ data = await response.arrayBuffer()
434
+ break
435
+
436
+ case 'multipart/form-data':
437
+ const temp = await response.formData()
438
+
439
+ data = {}
440
+ temp.forEach((value, key) => {
441
+ // @ts-ignore
442
+ data[key] = value
443
+ })
444
+
445
+ break
446
+
447
+ default:
448
+ data = await response.text().then(parseStringifiedValue)
449
+ }
450
+
451
+ if (response.status >= 300 || response.status < 200) {
452
+ error = new EdenFetchError(response.status, data)
453
+ data = null
454
+ }
455
+
456
+ return {
457
+ data,
458
+ error,
459
+ response,
460
+ status: response.status,
461
+ headers: response.headers,
462
+ }
463
+ })()
464
+ }
465
+
466
+ if (typeof body === 'object')
467
+ return createProxy(
468
+ domain,
469
+ config,
470
+ [...paths, Object.values(body)[0] as string],
471
+ instance,
472
+ )
473
+
474
+ return createProxy(domain, config, paths)
475
+ },
476
+ }) as any
509
477
 
510
478
  export const createSpiceflowClient = <
511
- const App extends Spiceflow<any, any, any, any, any, any>,
479
+ const App extends Spiceflow<any, any, any, any, any, any>,
512
480
  >(
513
- domain: string | App,
514
- config: SpiceflowClient.Config = {},
481
+ domain: string | App,
482
+ config: SpiceflowClient.Config = {},
515
483
  ): SpiceflowClient.Create<App> => {
516
- if (typeof domain === 'string') {
517
- if (domain.endsWith('/')) domain = domain.slice(0, -1)
484
+ if (typeof domain === 'string') {
485
+ if (domain.endsWith('/')) domain = domain.slice(0, -1)
518
486
 
519
- return createProxy(domain, config)
520
- }
487
+ return createProxy(domain, config)
488
+ }
521
489
 
522
- if (typeof window !== 'undefined')
523
- console.warn(
524
- 'Spiceflow instance server found on client side, this is not recommended for security reason. Use generic type instead.',
525
- )
490
+ if (typeof window !== 'undefined')
491
+ console.warn(
492
+ 'Spiceflow instance server found on client side, this is not recommended for security reason. Use generic type instead.',
493
+ )
526
494
 
527
- return createProxy('http://e.ly', config, [], domain)
495
+ return createProxy('http://e.ly', config, [], domain)
528
496
  }
529
497
 
530
498
  export type { SpiceflowClient as Treaty }