spiceflow 0.0.6 → 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.
- package/README.md +1 -171
- package/dist/client/errors.d.ts +7 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +18 -0
- package/dist/client/errors.js.map +1 -0
- package/dist/client/index.d.ts +14 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +376 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.d.ts +87 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/utils.d.ts +2 -0
- package/dist/client/utils.d.ts.map +1 -0
- package/dist/client/utils.js +9 -0
- package/dist/client/utils.js.map +1 -0
- package/dist/client/ws.d.ts +15 -0
- package/dist/client/ws.d.ts.map +1 -0
- package/dist/client/ws.js +51 -0
- package/dist/client/ws.js.map +1 -0
- package/dist/client.test.d.ts +2 -0
- package/dist/client.test.d.ts.map +1 -0
- package/dist/client.test.js +237 -0
- package/dist/client.test.js.map +1 -0
- package/dist/elysia-fork/context.d.ts +87 -0
- package/dist/elysia-fork/context.d.ts.map +1 -0
- package/dist/elysia-fork/context.js +2 -0
- package/dist/elysia-fork/context.js.map +1 -0
- package/dist/elysia-fork/error.d.ts +246 -0
- package/dist/elysia-fork/error.d.ts.map +1 -0
- package/dist/elysia-fork/error.js +195 -0
- package/dist/elysia-fork/error.js.map +1 -0
- package/dist/elysia-fork/types.d.ts +652 -0
- package/dist/elysia-fork/types.d.ts.map +1 -0
- package/dist/elysia-fork/types.js +3 -0
- package/dist/elysia-fork/types.js.map +1 -0
- package/dist/elysia-fork/utils.d.ts +134 -0
- package/dist/elysia-fork/utils.d.ts.map +1 -0
- package/dist/elysia-fork/utils.js +70 -0
- package/dist/elysia-fork/utils.js.map +1 -0
- package/dist/spiceflow.d.ts +253 -0
- package/dist/spiceflow.d.ts.map +1 -0
- package/dist/spiceflow.js +500 -0
- package/dist/spiceflow.js.map +1 -0
- package/dist/spiceflow.test.d.ts +2 -0
- package/dist/spiceflow.test.d.ts.map +1 -0
- package/dist/spiceflow.test.js +225 -0
- package/dist/spiceflow.test.js.map +1 -0
- package/dist/stream.test.d.ts +2 -0
- package/dist/stream.test.d.ts.map +1 -0
- package/dist/stream.test.js +286 -0
- package/dist/stream.test.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +4 -20
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +17 -46
- package/dist/utils.js.map +1 -1
- package/package.json +12 -36
- package/src/client/errors.ts +21 -0
- package/src/client/index.ts +539 -0
- package/src/client/types.ts +233 -0
- package/src/client/utils.ts +7 -0
- package/src/client/ws.ts +99 -0
- package/src/client.test.ts +235 -0
- package/src/elysia-fork/context.ts +196 -0
- package/src/elysia-fork/error.ts +293 -0
- package/src/elysia-fork/types.ts +1454 -0
- package/src/elysia-fork/utils.ts +85 -0
- package/src/spiceflow.test.ts +290 -0
- package/src/spiceflow.ts +1266 -0
- package/src/stream.test.ts +342 -0
- package/src/types.ts +0 -0
- package/src/utils.ts +21 -70
- package/context.d.ts +0 -2
- package/context.js +0 -1
- package/dist/babel.test.d.ts +0 -2
- package/dist/babel.test.d.ts.map +0 -1
- package/dist/babel.test.js +0 -27
- package/dist/babel.test.js.map +0 -1
- package/dist/babelDebugOutputs.d.ts +0 -9
- package/dist/babelDebugOutputs.d.ts.map +0 -1
- package/dist/babelDebugOutputs.js +0 -34
- package/dist/babelDebugOutputs.js.map +0 -1
- package/dist/babelTransformRpc.d.ts +0 -19
- package/dist/babelTransformRpc.d.ts.map +0 -1
- package/dist/babelTransformRpc.js +0 -285
- package/dist/babelTransformRpc.js.map +0 -1
- package/dist/browser.d.ts +0 -8
- package/dist/browser.d.ts.map +0 -1
- package/dist/browser.js +0 -126
- package/dist/browser.js.map +0 -1
- package/dist/build.d.ts +0 -13
- package/dist/build.d.ts.map +0 -1
- package/dist/build.js +0 -230
- package/dist/build.js.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -111
- package/dist/cli.js.map +0 -1
- package/dist/context-internal.d.ts +0 -20
- package/dist/context-internal.d.ts.map +0 -1
- package/dist/context-internal.js +0 -16
- package/dist/context-internal.js.map +0 -1
- package/dist/context.d.ts +0 -2
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -2
- package/dist/context.js.map +0 -1
- package/dist/expose.d.ts +0 -6
- package/dist/expose.d.ts.map +0 -1
- package/dist/expose.js +0 -32
- package/dist/expose.js.map +0 -1
- package/dist/headers.d.ts +0 -2
- package/dist/headers.d.ts.map +0 -1
- package/dist/headers.js +0 -18
- package/dist/headers.js.map +0 -1
- package/dist/index.d.ts +0 -8
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -23
- package/dist/index.js.map +0 -1
- package/dist/jsonRpc.d.ts +0 -32
- package/dist/jsonRpc.d.ts.map +0 -1
- package/dist/jsonRpc.js +0 -3
- package/dist/jsonRpc.js.map +0 -1
- package/dist/server.d.ts +0 -32
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -292
- package/dist/server.js.map +0 -1
- package/headers.d.ts +0 -2
- package/headers.js +0 -1
- package/sdk-template/package.json +0 -22
- package/sdk-template/src/index.ts +0 -2
- package/sdk-template/src/v1/example.ts +0 -5
- package/sdk-template/src/v1/generator.ts +0 -12
- package/sdk-template/tsconfig.json +0 -16
- package/src/babel.test.ts +0 -35
- package/src/babelDebugOutputs.ts +0 -56
- package/src/babelTransformRpc.ts +0 -394
- package/src/browser.ts +0 -141
- package/src/build.ts +0 -298
- package/src/cli.ts +0 -132
- package/src/context-internal.ts +0 -36
- package/src/context.ts +0 -5
- package/src/expose.ts +0 -34
- package/src/headers.ts +0 -19
- package/src/index.ts +0 -34
- package/src/jsonRpc.ts +0 -43
- package/src/server.ts +0 -384
package/dist/utils.js
CHANGED
|
@@ -1,50 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
48
|
-
return
|
|
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":"
|
|
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
|
|
4
|
-
"description": "
|
|
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
|
-
"@
|
|
28
|
-
"@
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
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
|
-
"@
|
|
46
|
-
"@
|
|
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
|
|
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 }
|