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