spiceflow 0.0.7 → 1.0.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.
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 +14 -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 +652 -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/spiceflow.d.ts +253 -0
  43. package/dist/spiceflow.d.ts.map +1 -0
  44. package/dist/spiceflow.js +500 -0
  45. package/dist/spiceflow.js.map +1 -0
  46. package/dist/spiceflow.test.d.ts +2 -0
  47. package/dist/spiceflow.test.d.ts.map +1 -0
  48. package/dist/spiceflow.test.js +225 -0
  49. package/dist/spiceflow.test.js.map +1 -0
  50. package/dist/stream.test.d.ts +2 -0
  51. package/dist/stream.test.d.ts.map +1 -0
  52. package/dist/stream.test.js +286 -0
  53. package/dist/stream.test.js.map +1 -0
  54. package/dist/types.d.ts +1 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +2 -0
  57. package/dist/types.js.map +1 -0
  58. package/dist/utils.d.ts +4 -20
  59. package/dist/utils.d.ts.map +1 -1
  60. package/dist/utils.js +17 -46
  61. package/dist/utils.js.map +1 -1
  62. package/package.json +12 -36
  63. package/src/client/errors.ts +21 -0
  64. package/src/client/index.ts +539 -0
  65. package/src/client/types.ts +233 -0
  66. package/src/client/utils.ts +7 -0
  67. package/src/client/ws.ts +99 -0
  68. package/src/client.test.ts +235 -0
  69. package/src/elysia-fork/context.ts +196 -0
  70. package/src/elysia-fork/error.ts +293 -0
  71. package/src/elysia-fork/types.ts +1454 -0
  72. package/src/elysia-fork/utils.ts +85 -0
  73. package/src/spiceflow.test.ts +290 -0
  74. package/src/spiceflow.ts +1266 -0
  75. package/src/stream.test.ts +342 -0
  76. package/src/types.ts +0 -0
  77. package/src/utils.ts +21 -70
  78. package/context.d.ts +0 -2
  79. package/context.js +0 -1
  80. package/dist/babel.test.d.ts +0 -2
  81. package/dist/babel.test.d.ts.map +0 -1
  82. package/dist/babel.test.js +0 -27
  83. package/dist/babel.test.js.map +0 -1
  84. package/dist/babelDebugOutputs.d.ts +0 -9
  85. package/dist/babelDebugOutputs.d.ts.map +0 -1
  86. package/dist/babelDebugOutputs.js +0 -34
  87. package/dist/babelDebugOutputs.js.map +0 -1
  88. package/dist/babelTransformRpc.d.ts +0 -19
  89. package/dist/babelTransformRpc.d.ts.map +0 -1
  90. package/dist/babelTransformRpc.js +0 -285
  91. package/dist/babelTransformRpc.js.map +0 -1
  92. package/dist/browser.d.ts +0 -8
  93. package/dist/browser.d.ts.map +0 -1
  94. package/dist/browser.js +0 -126
  95. package/dist/browser.js.map +0 -1
  96. package/dist/build.d.ts +0 -13
  97. package/dist/build.d.ts.map +0 -1
  98. package/dist/build.js +0 -230
  99. package/dist/build.js.map +0 -1
  100. package/dist/cli.d.ts +0 -3
  101. package/dist/cli.d.ts.map +0 -1
  102. package/dist/cli.js +0 -111
  103. package/dist/cli.js.map +0 -1
  104. package/dist/context-internal.d.ts +0 -20
  105. package/dist/context-internal.d.ts.map +0 -1
  106. package/dist/context-internal.js +0 -16
  107. package/dist/context-internal.js.map +0 -1
  108. package/dist/context.d.ts +0 -2
  109. package/dist/context.d.ts.map +0 -1
  110. package/dist/context.js +0 -2
  111. package/dist/context.js.map +0 -1
  112. package/dist/expose.d.ts +0 -6
  113. package/dist/expose.d.ts.map +0 -1
  114. package/dist/expose.js +0 -32
  115. package/dist/expose.js.map +0 -1
  116. package/dist/headers.d.ts +0 -2
  117. package/dist/headers.d.ts.map +0 -1
  118. package/dist/headers.js +0 -18
  119. package/dist/headers.js.map +0 -1
  120. package/dist/index.d.ts +0 -8
  121. package/dist/index.d.ts.map +0 -1
  122. package/dist/index.js +0 -23
  123. package/dist/index.js.map +0 -1
  124. package/dist/jsonRpc.d.ts +0 -32
  125. package/dist/jsonRpc.d.ts.map +0 -1
  126. package/dist/jsonRpc.js +0 -3
  127. package/dist/jsonRpc.js.map +0 -1
  128. package/dist/server.d.ts +0 -32
  129. package/dist/server.d.ts.map +0 -1
  130. package/dist/server.js +0 -292
  131. package/dist/server.js.map +0 -1
  132. package/headers.d.ts +0 -2
  133. package/headers.js +0 -1
  134. package/sdk-template/package.json +0 -22
  135. package/sdk-template/src/index.ts +0 -2
  136. package/sdk-template/src/v1/example.ts +0 -5
  137. package/sdk-template/src/v1/generator.ts +0 -12
  138. package/sdk-template/tsconfig.json +0 -16
  139. package/src/babel.test.ts +0 -35
  140. package/src/babelDebugOutputs.ts +0 -56
  141. package/src/babelTransformRpc.ts +0 -394
  142. package/src/browser.ts +0 -141
  143. package/src/build.ts +0 -298
  144. package/src/cli.ts +0 -132
  145. package/src/context-internal.ts +0 -36
  146. package/src/context.ts +0 -5
  147. package/src/expose.ts +0 -34
  148. package/src/headers.ts +0 -19
  149. package/src/index.ts +0 -34
  150. package/src/jsonRpc.ts +0 -43
  151. package/src/server.ts +0 -384
package/dist/utils.js CHANGED
@@ -1,50 +1,21 @@
1
- const PURE_ANNOTATION = '#__PURE__';
2
- export function annotateAsPure(t, node) {
3
- t.addComment(node, 'leading', PURE_ANNOTATION);
4
- return node;
5
- }
6
- const enabled = !!process.env.DEBUG_ACTIONS;
7
- export const logger = {
8
- log(...args) {
9
- enabled && console.log('[actions]:', ...args);
10
- },
11
- error(...args) {
12
- enabled && console.log('[actions]:', ...args);
13
- },
14
- };
15
- export function getFileName(state) {
16
- const { filename, cwd } = state;
17
- if (!filename) {
18
- return undefined;
1
+ // deno-lint-ignore no-explicit-any
2
+ export const deepFreeze = (value) => {
3
+ for (const key of Reflect.ownKeys(value)) {
4
+ if (value[key] && typeof value[key] === 'object') {
5
+ deepFreeze(value[key]);
6
+ }
19
7
  }
20
- if (cwd && filename.startsWith(cwd)) {
21
- return filename.slice(cwd.length + 1);
22
- }
23
- return filename;
24
- }
25
- export const directive = 'use spiceflow';
26
- export function jsonRpcError({ id = null, message, code = 1, }) {
27
- return {
28
- jsonrpc: '2.0',
29
- id,
30
- error: {
31
- code,
32
- message,
33
- data: null,
34
- },
35
- };
36
- }
37
- export function camelCaseCapitalized(str) {
38
- // functionName -> FunctionName
39
- // CamelCase -> CamelCase
40
- // camelCase -> CamelCase
41
- if (str.length === 0) {
42
- return str;
43
- }
44
- const first = str[0].toUpperCase();
45
- return first + str.slice(1);
8
+ return Object.freeze(value);
9
+ };
10
+ export const req = (path, options) => new Request(`http://localhost${path}`, options);
11
+ export function isAsyncIterable(obj) {
12
+ return (typeof obj === 'object' &&
13
+ typeof obj.next === 'function' &&
14
+ typeof obj.return === 'function' &&
15
+ typeof obj.throw === 'function' &&
16
+ typeof obj.return === 'function');
46
17
  }
47
- export function removeExtension(filePath) {
48
- return filePath.replace(/\.[j|t]sx?$/, '');
18
+ export function sleep(ms) {
19
+ return new Promise((resolve) => setTimeout(resolve, ms));
49
20
  }
50
21
  //# sourceMappingURL=utils.js.map
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAKA,MAAM,eAAe,GAAG,WAAW,CAAC;AAEpC,MAAM,UAAU,cAAc,CAAiB,CAAe,EAAE,IAAO;IACrE,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;IAC/C,OAAO,IAAI,CAAC;AACd,CAAC;AASD,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;AAC5C,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,GAAG,CAAC,GAAG,IAAI;QACT,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,IAAI,CAAC,CAAC;IAChD,CAAC;IACD,KAAK,CAAC,GAAG,IAAI;QACX,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,IAAI,CAAC,CAAC;IAChD,CAAC;CACF,CAAC;AAEF,MAAM,UAAU,WAAW,CAAC,KAAiB;IAC3C,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;IAEhC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,GAAG,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACpC,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACxC,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AACD,MAAM,CAAC,MAAM,SAAS,GAAG,eAAe,CAAC;AAEzC,MAAM,UAAU,YAAY,CAAC,EAC3B,EAAE,GAAG,IAAI,EACT,OAAO,EACP,IAAI,GAAG,CAAC,GACT;IACC,OAAO;QACL,OAAO,EAAE,KAAK;QACd,EAAE;QACF,KAAK,EAAE;YACL,IAAI;YACJ,OAAO;YACP,IAAI,EAAE,IAAI;SACX;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,+BAA+B;IAC/B,yBAAyB;IACzB,yBAAyB;IAEzB,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,GAAG,CAAC;IACb,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACnC,OAAO,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,OAAO,QAAQ,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;AAC7C,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAU,EAAE,EAAE;IACxC,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1C,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;YAClD,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;QACvB,CAAC;IACF,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC5B,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,OAAqB,EAAE,EAAE,CAC1D,IAAI,OAAO,CAAC,mBAAmB,IAAI,EAAE,EAAE,OAAO,CAAC,CAAA;AAEhD,MAAM,UAAU,eAAe,CAAC,GAAQ;IACvC,OAAO,CACN,OAAO,GAAG,KAAK,QAAQ;QACvB,OAAO,GAAG,CAAC,IAAI,KAAK,UAAU;QAC9B,OAAO,GAAG,CAAC,MAAM,KAAK,UAAU;QAChC,OAAO,GAAG,CAAC,KAAK,KAAK,UAAU;QAC/B,OAAO,GAAG,CAAC,MAAM,KAAK,UAAU,CAChC,CAAA;AACF,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,EAAU;IAC/B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AACzD,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "spiceflow",
3
- "version": "0.0.7",
4
- "description": "If GraphQL, JSON-RPC and React server actions had a baby, it would be called spiceflow",
3
+ "version": "1.0.0",
4
+ "description": "Simple API framework with RPC and type safety",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "type": "module",
@@ -9,49 +9,25 @@
9
9
  "bin": "dist/cli.js",
10
10
  "files": [
11
11
  "dist",
12
- "src",
13
- "sdk-template",
14
- "esm",
15
- "context.d.ts",
16
- "context.js",
17
- "headers.d.ts",
18
- "headers.js"
12
+ "src"
19
13
  ],
20
14
  "keywords": [],
21
15
  "author": "Tommaso De Rossi, morse <beats.by.morse@gmail.com>",
22
16
  "license": "",
23
- "peerDependencies": {
24
- "next": ">=10"
25
- },
26
17
  "dependencies": {
27
- "@babel/core": "^7.24.0",
28
- "@babel/parser": "^7.24.0",
29
- "@babel/plugin-syntax-jsx": "^7.23.3",
30
- "@babel/plugin-syntax-typescript": "^7.23.3",
31
- "@babel/plugin-transform-typescript": "^7.23.6",
32
- "@manypkg/get-packages": "^2.2.1",
33
- "@microsoft/api-extractor": "^7.43.1",
34
- "@types/fs-extra": "^11.0.4",
35
- "cac": "^6.7.14",
36
- "chokidar": "^3.6.0",
37
- "eventsource-parser": "^1.1.2",
38
- "fast-glob": "^3.3.2",
39
- "find-up": "^7.0.0",
40
- "fs-extra": "^11.2.0",
41
- "picocolors": "^1.0.0",
42
- "superjson": "^1.13.3"
18
+ "@medley/router": "^0.2.1",
19
+ "@sinclair/typebox": "^0.33.5",
20
+ "ajv": "^8.17.1",
21
+ "eventsource-parser": "^2.0.1",
22
+ "fast-querystring": "^1.1.2",
23
+ "openapi-types": "^12.1.3"
43
24
  },
44
25
  "devDependencies": {
45
- "@babel/generator": "^7.23.6",
46
- "@babel/types": "^7.24.0",
47
- "@types/babel__core": "^7.20.5",
48
- "@types/node": "20.2.5",
49
- "next": "14.2.1",
50
- "ts-json-schema-generator": "^2.1.1",
51
- "webpack": "^5.88.2"
26
+ "@types/bun": "^1.1.6",
27
+ "@types/node": "20.2.5"
52
28
  },
53
29
  "scripts": {
54
- "build": "cp ../README.md ./README.md && rm -rf ./sdk-template ../sdk-template/dist ../sdk-template/node_modules && cp -r ../sdk-template ./sdk-template && rm -rf dist && tsc",
30
+ "build": "cp ../README.md ./README.md && rm -rf dist && tsc",
55
31
  "watch": "tsc -w"
56
32
  }
57
33
  }
@@ -0,0 +1,21 @@
1
+ export class EdenFetchError<
2
+ Status = number,
3
+ Value extends any = any
4
+ > extends Error {
5
+ value: Value
6
+ constructor(
7
+ public status: Status,
8
+ public passedValue: Value
9
+ ) {
10
+ let message = String((passedValue as any)?.message || '')
11
+ if (!message) {
12
+ if (typeof passedValue === 'object') {
13
+ message = JSON.stringify(passedValue)
14
+ } else {
15
+ message = String(passedValue || '')
16
+ }
17
+ }
18
+ super(message)
19
+ this.value = passedValue
20
+ }
21
+ }
@@ -0,0 +1,539 @@
1
+ /* eslint-disable no-extra-semi */
2
+ /* eslint-disable no-case-declarations */
3
+ /* eslint-disable prefer-const */
4
+ import type { Elysia } from '../spiceflow'
5
+ import { EventSourceParserStream } from 'eventsource-parser/stream'
6
+
7
+ import type { Treaty as SpiceflowClient } from './types'
8
+
9
+ import { EdenFetchError } from './errors'
10
+ // import { EdenWS } from './ws'
11
+ import { parseStringifiedValue } from './utils'
12
+
13
+ const method = [
14
+ 'get',
15
+ 'post',
16
+ 'put',
17
+ 'delete',
18
+ 'patch',
19
+ 'options',
20
+ 'head',
21
+ 'connect',
22
+ 'subscribe'
23
+ ] as const
24
+
25
+ const locals = ['localhost', '127.0.0.1', '0.0.0.0']
26
+
27
+ const isServer = typeof FileList === 'undefined'
28
+
29
+ const isFile = (v: any) => {
30
+ if (isServer) return v instanceof Blob
31
+
32
+ return v instanceof FileList || v instanceof File
33
+ }
34
+
35
+ // FormData is 1 level deep
36
+ const hasFile = (obj: Record<string, any>) => {
37
+ if (!obj) return false
38
+
39
+ for (const key in obj) {
40
+ if (isFile(obj[key])) return true
41
+
42
+ if (Array.isArray(obj[key]) && (obj[key] as unknown[]).find(isFile))
43
+ return true
44
+ }
45
+
46
+ return false
47
+ }
48
+
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
+ })
65
+
66
+ const processHeaders = (
67
+ h: SpiceflowClient.Config['headers'],
68
+ path: string,
69
+ options: RequestInit = {},
70
+ headers: Record<string, string> = {}
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 (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 (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
+ }
116
+
117
+ interface SSEEvent {
118
+ event: string
119
+ data: any
120
+ id?: string
121
+ }
122
+
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
+ }
147
+ }
148
+
149
+ export async function* streamSSEResponse(
150
+ response: Response
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
+ }
170
+ }
171
+
172
+ function tryParsingJson(data: string): any {
173
+ try {
174
+ return JSON.parse(data)
175
+ } catch (error) {
176
+ return null
177
+ }
178
+ }
179
+
180
+ const createProxy = (
181
+ domain: string,
182
+ config: SpiceflowClient.Config,
183
+ paths: string[] = [],
184
+ elysia?: Elysia<any, any, any, any, any, any>
185
+ ): any =>
186
+ new Proxy(() => {}, {
187
+ get(_, param: string): any {
188
+ return createProxy(
189
+ domain,
190
+ config,
191
+ param === 'index' ? paths : [...paths, param],
192
+ elysia
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
+ fetcher = fetch,
208
+ headers,
209
+ onRequest,
210
+ onResponse,
211
+ fetch: conf
212
+ } = config
213
+
214
+ const isGetOrHead =
215
+ method === 'get' ||
216
+ method === 'head' ||
217
+ method === 'subscribe'
218
+
219
+ headers = processHeaders(headers, path, options)
220
+
221
+ const query = isGetOrHead
222
+ ? (body as Record<string, string | string[] | undefined>)
223
+ ?.query
224
+ : options?.query
225
+
226
+ let q = ''
227
+ if (query) {
228
+ const append = (key: string, value: string) => {
229
+ q +=
230
+ (q ? '&' : '?') +
231
+ `${encodeURIComponent(key)}=${encodeURIComponent(
232
+ value
233
+ )}`
234
+ }
235
+
236
+ for (const [key, value] of Object.entries(query)) {
237
+ if (Array.isArray(value)) {
238
+ for (const v of value) append(key, v)
239
+ continue
240
+ }
241
+
242
+ if (typeof value === 'object') {
243
+ append(key, JSON.stringify(value))
244
+ continue
245
+ }
246
+
247
+ append(key, `${value}`)
248
+ }
249
+ }
250
+
251
+ // if (method === 'subscribe') {
252
+ // const url =
253
+ // domain.replace(
254
+ // /^([^]+):\/\//,
255
+ // domain.startsWith('https://')
256
+ // ? 'wss://'
257
+ // : domain.startsWith('http://')
258
+ // ? 'ws://'
259
+ // : locals.find((v) =>
260
+ // (domain as string).includes(v)
261
+ // )
262
+ // ? 'ws://'
263
+ // : 'wss://'
264
+ // ) +
265
+ // path +
266
+ // q
267
+
268
+ // return new EdenWS(url)
269
+ // }
270
+
271
+ return (async () => {
272
+ let fetchInit = {
273
+ method: method?.toUpperCase(),
274
+ body,
275
+ ...conf,
276
+ headers
277
+ } satisfies RequestInit
278
+
279
+ fetchInit.headers = {
280
+ ...headers,
281
+ ...processHeaders(
282
+ // For GET and HEAD, options is moved to body (1st param)
283
+ isGetOrHead ? body?.headers : options?.headers,
284
+ path,
285
+ fetchInit
286
+ )
287
+ }
288
+
289
+ const fetchOpts =
290
+ isGetOrHead && typeof body === 'object'
291
+ ? body.fetch
292
+ : options?.fetch
293
+
294
+ fetchInit = {
295
+ ...fetchInit,
296
+ ...fetchOpts
297
+ }
298
+
299
+ if (isGetOrHead) delete fetchInit.body
300
+
301
+ if (onRequest) {
302
+ if (!Array.isArray(onRequest)) onRequest = [onRequest]
303
+
304
+ for (const value of onRequest) {
305
+ const temp = await value(path, fetchInit)
306
+
307
+ if (typeof temp === 'object')
308
+ fetchInit = {
309
+ ...fetchInit,
310
+ ...temp,
311
+ headers: {
312
+ ...fetchInit.headers,
313
+ ...processHeaders(
314
+ temp.headers,
315
+ path,
316
+ fetchInit
317
+ )
318
+ }
319
+ }
320
+ }
321
+ }
322
+
323
+ // ? Duplicate because end-user might add a body in onRequest
324
+ if (isGetOrHead) delete fetchInit.body
325
+
326
+ if (hasFile(body)) {
327
+ const formData = new FormData()
328
+
329
+ // FormData is 1 level deep
330
+ for (const [key, field] of Object.entries(
331
+ fetchInit.body
332
+ )) {
333
+ if (isServer) {
334
+ formData.append(key, field as any)
335
+
336
+ continue
337
+ }
338
+
339
+ if (field instanceof File) {
340
+ formData.append(
341
+ key,
342
+ await createNewFile(field as any)
343
+ )
344
+
345
+ continue
346
+ }
347
+
348
+ if (field instanceof FileList) {
349
+ for (let i = 0; i < field.length; i++)
350
+ formData.append(
351
+ key as any,
352
+ await createNewFile((field as any)[i])
353
+ )
354
+
355
+ continue
356
+ }
357
+
358
+ if (Array.isArray(field)) {
359
+ for (let i = 0; i < field.length; i++) {
360
+ const value = (field as any)[i]
361
+
362
+ formData.append(
363
+ key as any,
364
+ value instanceof File
365
+ ? await createNewFile(value)
366
+ : value
367
+ )
368
+ }
369
+
370
+ continue
371
+ }
372
+
373
+ formData.append(key, field as string)
374
+ }
375
+
376
+ // We don't do this because we need to let the browser set the content type with the correct boundary
377
+ // fetchInit.headers['content-type'] = 'multipart/form-data'
378
+ fetchInit.body = formData
379
+ } else if (typeof body === 'object') {
380
+ ;(fetchInit.headers as Record<string, string>)[
381
+ 'content-type'
382
+ ] = 'application/json'
383
+
384
+ fetchInit.body = JSON.stringify(body)
385
+ } else if (body !== undefined && body !== null) {
386
+ ;(fetchInit.headers as Record<string, string>)[
387
+ 'content-type'
388
+ ] = 'text/plain'
389
+ }
390
+
391
+ if (isGetOrHead) delete fetchInit.body
392
+
393
+ if (onRequest) {
394
+ if (!Array.isArray(onRequest)) onRequest = [onRequest]
395
+
396
+ for (const value of onRequest) {
397
+ const temp = await value(path, fetchInit)
398
+
399
+ if (typeof temp === 'object')
400
+ fetchInit = {
401
+ ...fetchInit,
402
+ ...temp,
403
+ headers: {
404
+ ...fetchInit.headers,
405
+ ...processHeaders(
406
+ temp.headers,
407
+ path,
408
+ fetchInit
409
+ )
410
+ } as Record<string, string>
411
+ }
412
+ }
413
+ }
414
+
415
+ const url = domain + path + q
416
+ const response = await (elysia?.handle(
417
+ new Request(url, fetchInit)
418
+ ) ?? fetcher!(url, fetchInit))
419
+
420
+ let data = null as any
421
+ let error = null as any
422
+
423
+ if (onResponse) {
424
+ if (!Array.isArray(onResponse))
425
+ onResponse = [onResponse]
426
+
427
+ for (const value of onResponse)
428
+ try {
429
+ const temp = await value(response.clone())
430
+
431
+ if (temp !== undefined && temp !== null) {
432
+ data = temp
433
+ break
434
+ }
435
+ } catch (err) {
436
+ if (err instanceof EdenFetchError) error = err
437
+ else error = new EdenFetchError(422, err)
438
+
439
+ break
440
+ }
441
+ }
442
+
443
+ if (data !== null) {
444
+ return {
445
+ data,
446
+ error,
447
+ response,
448
+ status: response.status,
449
+ headers: response.headers
450
+ }
451
+ }
452
+
453
+ switch (
454
+ response.headers.get('Content-Type')?.split(';')[0]
455
+ ) {
456
+ case 'text/event-stream':
457
+ data = streamSSEResponse(response)
458
+ break
459
+
460
+ case 'application/json':
461
+ data = await response.json()
462
+ break
463
+ case 'application/octet-stream':
464
+ data = await response.arrayBuffer()
465
+ break
466
+
467
+ case 'multipart/form-data':
468
+ const temp = await response.formData()
469
+
470
+ data = {}
471
+ temp.forEach((value, key) => {
472
+ // @ts-ignore
473
+ data[key] = value
474
+ })
475
+
476
+ break
477
+
478
+ default:
479
+ data = await response
480
+ .text()
481
+ .then(parseStringifiedValue)
482
+ }
483
+
484
+ if (response.status >= 300 || response.status < 200) {
485
+ error = new EdenFetchError(response.status, data)
486
+ data = null
487
+ }
488
+
489
+ return {
490
+ data,
491
+ error,
492
+ response,
493
+ status: response.status,
494
+ headers: response.headers
495
+ }
496
+ })()
497
+ }
498
+
499
+ if (typeof body === 'object')
500
+ return createProxy(
501
+ domain,
502
+ config,
503
+ [...paths, Object.values(body)[0] as string],
504
+ elysia
505
+ )
506
+
507
+ return createProxy(domain, config, paths)
508
+ }
509
+ }) as any
510
+
511
+ export const createSpiceflowClient = <
512
+ const App extends Elysia<any, any, any, any, any, any, any, any>
513
+ >(
514
+ domain: string | App,
515
+ config: SpiceflowClient.Config = {}
516
+ ): SpiceflowClient.Create<App> => {
517
+ if (typeof domain === 'string') {
518
+ if (!config.keepDomain) {
519
+ if (!domain.includes('://'))
520
+ domain =
521
+ (locals.find((v) => (domain as string).includes(v))
522
+ ? 'http://'
523
+ : 'https://') + domain
524
+
525
+ if (domain.endsWith('/')) domain = domain.slice(0, -1)
526
+ }
527
+
528
+ return createProxy(domain, config)
529
+ }
530
+
531
+ if (typeof window !== 'undefined')
532
+ console.warn(
533
+ 'Elysia instance server found on client side, this is not recommended for security reason. Use generic type instead.'
534
+ )
535
+
536
+ return createProxy('http://e.ly', config, [], domain)
537
+ }
538
+
539
+ export type { SpiceflowClient as Treaty }