@elysiajs/eden 0.3.0-exp-230224.1831 → 0.3.0-rc.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/src/index.ts CHANGED
@@ -1,253 +1,34 @@
1
- import type { Elysia, TypedSchema, HTTPMethod } from 'elysia'
2
-
3
- import type {
4
- CreateEden,
5
- Eden,
6
- EdenCall,
7
- EdenConfig,
8
- EdenWSEvent,
9
- UnionToIntersection
10
- } from './types'
11
- import { composePath, EdenFetchError, Signal } from './utils'
12
-
13
- export class EdenWS<Schema extends TypedSchema<any> = TypedSchema> {
14
- ws: WebSocket
15
- url: string
16
-
17
- constructor(url: string) {
18
- this.ws = new WebSocket(url)
19
- this.url = url
20
- }
21
-
22
- send(data: Schema['body'] | Schema['body'][]) {
23
- if (Array.isArray(data)) {
24
- data.forEach((datum) => this.send(datum))
25
-
26
- return this
27
- }
28
-
29
- this.ws.send(
30
- typeof data === 'object' ? JSON.stringify(data) : data.toString()
31
- )
32
-
33
- return this
34
- }
35
-
36
- on<K extends keyof WebSocketEventMap>(
37
- type: K,
38
- listener: (event: EdenWSEvent<K, Schema['response']>) => void,
39
- options?: boolean | AddEventListenerOptions
40
- ) {
41
- return this.addEventListener(type, listener, options)
42
- }
43
-
44
- off<K extends keyof WebSocketEventMap>(
45
- type: K,
46
- listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any,
47
- options?: boolean | EventListenerOptions
48
- ) {
49
- this.ws.removeEventListener(type, listener, options)
50
-
51
- return this
52
- }
53
-
54
- addEventListener<K extends keyof WebSocketEventMap>(
55
- type: K,
56
- listener: (event: EdenWSEvent<K, Schema['response']>) => void,
57
- options?: boolean | AddEventListenerOptions
58
- ) {
59
- this.ws.addEventListener(
60
- type,
61
- (ws) => {
62
- if (type === 'message') {
63
- let data = (ws as MessageEvent).data.toString() as any
64
- const start = data.charCodeAt(0)
65
-
66
- if (start === 47 || start === 123)
67
- try {
68
- data = JSON.parse(data)
69
- } catch {}
70
- else if (!Number.isNaN(+data)) data = +data
71
- else if (data === 'true') data = true
72
- else if (data === 'fase') data = false
73
-
74
- // @ts-ignore
75
- listener({
76
- ...ws,
77
- data
78
- })
79
- } else {
80
- // @ts-ignore
81
- listener(ws)
82
- }
83
- },
84
- options
85
- )
86
-
87
- return this
88
- }
89
-
90
- removeEventListener<K extends keyof WebSocketEventMap>(
91
- type: K,
92
- listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any,
93
- options?: boolean | EventListenerOptions
94
- ) {
95
- this.off(type, listener, options)
96
-
97
- return this
98
- }
99
-
100
- close() {
101
- this.ws.close()
102
-
103
- return this
104
- }
105
- }
106
-
107
- const createFn = (
108
- domain: string,
109
- procedure: string[],
110
- signal: Signal
111
- ): Record<string, unknown> =>
112
- // @ts-ignore
113
- new Proxy((...v: any[]) => {}, {
114
- get(target, key, value) {
115
- return createFn(domain, [...procedure, key as string], signal)
116
- },
117
- apply(target, _, params) {
118
- const param = params[0]
119
-
120
- if (procedure.length === 1) {
121
- if (
122
- procedure[0] in Object.prototype ||
123
- procedure[0] in Promise.prototype
124
- )
125
- return target(params)
126
-
127
- switch (procedure[0]) {
128
- case 'toJSON':
129
- return target(params)
130
-
131
- case '$set':
132
- return signal.setConfig(param)
133
-
134
- case '$clone':
135
- return createFn(domain, [], signal.clone(param))
136
- }
137
- }
138
-
139
- return signal.run(procedure, params).then((result) => {
140
- if (result instanceof Error) throw result
141
-
142
- return result
143
- })
144
- }
145
- })
146
-
147
- const createProxy = (
148
- domain: string,
149
- path: string = '',
150
- config: EdenConfig
151
- ): Record<string, unknown> =>
152
- new Proxy(() => {}, {
153
- get(target, key, value) {
154
- return createProxy(domain, `${path}/${key.toString()}`, config)
155
- },
156
- apply(
157
- target,
158
- _,
159
- [
160
- { $query, $fetch, $body, ...bodyObj } = {
161
- $fetch: undefined,
162
- $query: undefined,
163
- $body: undefined
164
- }
165
- ]: EdenCall[] = [{}]
166
- ) {
167
- const i = path.lastIndexOf('/'),
168
- method = path.slice(i + 1),
169
- url = composePath(domain, path.slice(0, i), $query)
170
-
171
- if (method === 'subscribe')
172
- return new EdenWS(
173
- url.replace(
174
- /^([^]+):\/\//,
175
- url.startsWith('https://') ? 'wss://' : 'ws://'
176
- )
177
- )
178
-
179
- const body =
180
- $body ?? (Object.keys(bodyObj).length ? bodyObj : undefined)
181
- const isObject = typeof body === 'object'
182
-
183
- return fetch(url, {
184
- method,
185
- body: isObject ? JSON.stringify(body) : body,
186
- ...config.fetch,
187
- ...$fetch,
188
- headers: body
189
- ? {
190
- 'content-type': isObject
191
- ? 'application/json'
192
- : 'text/plain',
193
- ...config.fetch?.headers,
194
- ...$fetch?.['headers']
195
- }
196
- : undefined
197
- }).then(async (res) => {
198
- if (res.status > 300) {
199
- let data
200
-
201
- if (
202
- res.headers
203
- .get('content-type')
204
- ?.includes('application/json')
205
- )
206
- try {
207
- data = await res.json()
208
- } catch (_) {
209
- data = await res.text()
210
- }
211
- else data = await res.text()
212
-
213
- return new EdenFetchError(res.status, data)
214
- }
215
-
216
- if (
217
- res.headers
218
- .get('content-type')
219
- ?.includes('application/json')
220
- )
221
- try {
222
- return await res.json()
223
- } catch (_) {
224
- // if json is error then it's string
225
- // flow down
226
- }
227
-
228
- let data = await res.text()
229
-
230
- if (!Number.isNaN(+data)) return +data
231
- if (data === 'true') return true
232
- if (data === 'false') return false
233
-
234
- return data
235
- })
236
- }
237
- }) as unknown as Record<string, unknown>
238
-
239
- export const eden = <App extends Elysia<any>>(
240
- domain: string,
241
- config: EdenConfig = {}
242
- ): Eden<App> =>
243
- new Proxy(
244
- {},
245
- {
246
- get(target, key) {
247
- if (key === '$fn')
248
- return createFn(domain, [], new Signal(domain, config))
249
-
250
- return createProxy(domain, key as string, config)
251
- }
252
- }
253
- ) as any
1
+ import type { Elysia } from 'elysia'
2
+
3
+ import type { EdenTreaty } from './treaty'
4
+ import type { EdenFetch } from './fetch'
5
+ import type { EdenFn } from './fn'
6
+
7
+ export { edenTreaty } from './treaty'
8
+ export { edenFetch } from './fetch'
9
+ export { edenFn } from './fn'
10
+
11
+ // @ts-ignore
12
+ // export const eden: Eden = (domain: string) => ({
13
+ // treaty(config) {
14
+ // return import('./treaty').then((x) => x.edenTreaty(domain, config))
15
+ // },
16
+ // fn(config) {
17
+ // return import('./fn').then((x) => x.edenFn(domain, config))
18
+ // },
19
+ // fetch(config) {
20
+ // return import('./fetch').then((x) => x.edenFetch(domain, config))
21
+ // }
22
+ // })
23
+
24
+ // type Eden = (domain: string) => {
25
+ // treaty<App extends Elysia<any>>(
26
+ // config?: EdenTreaty.Config
27
+ // ): EdenTreaty.Create<App>
28
+
29
+ // fetch<App extends Elysia<any>>(
30
+ // config?: EdenFetch.Config
31
+ // ): EdenFetch.Create<App>
32
+
33
+ // fn<App extends Elysia<any>>(config?: EdenFn.Config): EdenFn.Create<App>
34
+ // }
@@ -0,0 +1,192 @@
1
+ import type { Elysia, TypedSchema } from 'elysia'
2
+
3
+ import { EdenFetchError } from '../utils'
4
+
5
+ import { composePath } from './utils'
6
+ import type { EdenTreaty } from './types'
7
+
8
+ export type { EdenTreaty } from './types'
9
+
10
+ export class EdenWS<Schema extends TypedSchema<any> = TypedSchema> {
11
+ ws: WebSocket
12
+ url: string
13
+
14
+ constructor(url: string) {
15
+ this.ws = new WebSocket(url)
16
+ this.url = url
17
+ }
18
+
19
+ send(data: Schema['body'] | Schema['body'][]) {
20
+ if (Array.isArray(data)) {
21
+ data.forEach((datum) => this.send(datum))
22
+
23
+ return this
24
+ }
25
+
26
+ this.ws.send(
27
+ typeof data === 'object' ? JSON.stringify(data) : data.toString()
28
+ )
29
+
30
+ return this
31
+ }
32
+
33
+ on<K extends keyof WebSocketEventMap>(
34
+ type: K,
35
+ listener: (event: EdenTreaty.WSEvent<K, Schema['response']>) => void,
36
+ options?: boolean | AddEventListenerOptions
37
+ ) {
38
+ return this.addEventListener(type, listener, options)
39
+ }
40
+
41
+ off<K extends keyof WebSocketEventMap>(
42
+ type: K,
43
+ listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any,
44
+ options?: boolean | EventListenerOptions
45
+ ) {
46
+ this.ws.removeEventListener(type, listener, options)
47
+
48
+ return this
49
+ }
50
+
51
+ addEventListener<K extends keyof WebSocketEventMap>(
52
+ type: K,
53
+ listener: (event: EdenTreaty.WSEvent<K, Schema['response']>) => void,
54
+ options?: boolean | AddEventListenerOptions
55
+ ) {
56
+ this.ws.addEventListener(
57
+ type,
58
+ (ws) => {
59
+ if (type === 'message') {
60
+ let data = (ws as MessageEvent).data.toString() as any
61
+ const start = data.charCodeAt(0)
62
+
63
+ if (start === 47 || start === 123)
64
+ try {
65
+ data = JSON.parse(data)
66
+ } catch {}
67
+ else if (!Number.isNaN(+data)) data = +data
68
+ else if (data === 'true') data = true
69
+ else if (data === 'fase') data = false
70
+
71
+ // @ts-ignore
72
+ listener({
73
+ ...ws,
74
+ data
75
+ })
76
+ } else {
77
+ // @ts-ignore
78
+ listener(ws)
79
+ }
80
+ },
81
+ options
82
+ )
83
+
84
+ return this
85
+ }
86
+
87
+ removeEventListener<K extends keyof WebSocketEventMap>(
88
+ type: K,
89
+ listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any,
90
+ options?: boolean | EventListenerOptions
91
+ ) {
92
+ this.off(type, listener, options)
93
+
94
+ return this
95
+ }
96
+
97
+ close() {
98
+ this.ws.close()
99
+
100
+ return this
101
+ }
102
+ }
103
+
104
+ const createProxy = (
105
+ domain: string,
106
+ path: string = '',
107
+ config: {}
108
+ ): Record<string, unknown> =>
109
+ new Proxy(() => {}, {
110
+ get(target, key, value) {
111
+ return createProxy(domain, `${path}/${key.toString()}`, config)
112
+ },
113
+ apply(
114
+ target,
115
+ _,
116
+ [
117
+ { $query, $fetch, $body, ...bodyObj } = {
118
+ $fetch: undefined,
119
+ $query: undefined,
120
+ $body: undefined
121
+ }
122
+ ]: EdenTreaty.CallOption[] = [{}]
123
+ ) {
124
+ const i = path.lastIndexOf('/'),
125
+ method = path.slice(i + 1),
126
+ url = composePath(domain, path.slice(0, i), $query)
127
+
128
+ if (method === 'subscribe')
129
+ return new EdenWS(
130
+ url.replace(
131
+ /^([^]+):\/\//,
132
+ url.startsWith('https://') ? 'wss://' : 'ws://'
133
+ )
134
+ )
135
+
136
+ const body =
137
+ $body ?? (Object.keys(bodyObj).length ? bodyObj : undefined)
138
+ const isObject = typeof body === 'object'
139
+
140
+ return fetch(url, {
141
+ method,
142
+ body: isObject ? JSON.stringify(body) : body,
143
+ // ...config.fetch,
144
+ ...$fetch,
145
+ headers: body
146
+ ? {
147
+ 'content-type': isObject
148
+ ? 'application/json'
149
+ : 'text/plain',
150
+ // ...config.fetch?.headers,
151
+ ...$fetch?.['headers']
152
+ }
153
+ : undefined
154
+ }).then(async (res) => {
155
+ let data: Promise<unknown>
156
+
157
+ switch (res.headers.get('Content-Type')?.split(';')[0]) {
158
+ case 'application/json':
159
+ data = res.json()
160
+ break
161
+
162
+ default:
163
+ data = res.text().then((data) => {
164
+ if (!Number.isNaN(+data)) return +data
165
+ if (data === 'true') return true
166
+ if (data === 'false') return false
167
+
168
+ return data
169
+ })
170
+ }
171
+
172
+ if (res.status > 300)
173
+ return new EdenFetchError(res.status, await data)
174
+
175
+ return data
176
+ })
177
+ }
178
+ }) as unknown as Record<string, unknown>
179
+
180
+ export const edenTreaty = <App extends Elysia<any>>(
181
+ domain: string,
182
+ config: {} = {}
183
+ ): EdenTreaty.Create<App> =>
184
+ new Proxy(
185
+ {},
186
+ {
187
+ get(target, key) {
188
+ return createProxy(domain, key as string, config)
189
+ }
190
+ }
191
+ ) as any
192
+
@@ -0,0 +1,140 @@
1
+ import type { Elysia, SCHEMA, AnyTypedSchema } from 'elysia'
2
+
3
+ import type { EdenWS } from './index'
4
+ import type { IsNever, IsUnknown, MapError, UnionToIntersect } from '../types'
5
+ import { EdenFetchError } from '../utils'
6
+
7
+ export namespace EdenTreaty {
8
+ export type Create<App extends Elysia<any>> = App['meta'] extends Record<
9
+ typeof SCHEMA,
10
+ infer Schema extends Record<string, any>
11
+ >
12
+ ? EdenTreaty.Sign<Schema>
13
+ : 'Please install Elysia before using Eden'
14
+
15
+ export interface Config {}
16
+
17
+ export type Sign<A> = {
18
+ [Path in keyof A as Path extends `/${infer Prefix}/${infer _}`
19
+ ? Prefix
20
+ : Path extends `/`
21
+ ? 'index'
22
+ : Path extends `/${infer Prefix}`
23
+ ? Prefix
24
+ : never]: UnionToIntersect<
25
+ Path extends `/${infer _}/${infer Rest}`
26
+ ? NestPath<
27
+ Rest,
28
+ {
29
+ [Method in keyof A[Path]]: A[Path][Method] extends infer Route extends AnyTypedSchema
30
+ ? Method extends 'subscribe'
31
+ ? undefined extends Route['query']
32
+ ? (params?: {
33
+ $query?: Record<string, string>
34
+ }) => EdenWS<Route>
35
+ : undefined extends Route['query']
36
+ ? (params: {
37
+ $query: Route['query']
38
+ }) => EdenWS<Route>
39
+ : (params?: {
40
+ $query?: Record<string, string>
41
+ }) => EdenWS<Route>
42
+ : IsUnknown<Route['body']> extends true
43
+ ? (params?: {
44
+ $query?: Record<string, string>
45
+ $fetch?: RequestInit
46
+ }) =>
47
+ | Promise<Route['response']['200']>
48
+ | (MapError<
49
+ Route['response']
50
+ > extends infer Errors
51
+ ? IsNever<Errors> extends true
52
+ ? EdenFetchError<
53
+ number,
54
+ string
55
+ >
56
+ : Errors
57
+ : never)
58
+ : (
59
+ params: Route['body'] & {
60
+ $query?: Record<string, string>
61
+ $fetch?: RequestInit
62
+ }
63
+ ) => Promise<
64
+ Route['response'] extends {
65
+ 200: infer ReturnedType
66
+ }
67
+ ? ReturnedType
68
+ : unknown
69
+ >
70
+ : never
71
+ }
72
+ >
73
+ : {
74
+ [Method in keyof A[Path]]: A[Path][Method] extends infer Route extends AnyTypedSchema
75
+ ? Method extends 'subscribe'
76
+ ? IsUnknown<Route['query']> extends true
77
+ ? (params?: {
78
+ $query?: Record<string, string>
79
+ }) => EdenWS<Route>
80
+ : undefined extends Route['query']
81
+ ? (params: {
82
+ $query: Route['query']
83
+ }) => EdenWS<Route>
84
+ : (params?: {
85
+ $query?: Record<string, string>
86
+ }) => EdenWS<Route>
87
+ : IsUnknown<Route['body']> extends true
88
+ ? (params?: {
89
+ $query?: Record<string, string>
90
+ $fetch?: RequestInit
91
+ }) =>
92
+ | Promise<Route['response']['200']>
93
+ | (MapError<
94
+ Route['response']
95
+ > extends infer Errors
96
+ ? IsNever<Errors> extends true
97
+ ? EdenFetchError<number, string>
98
+ : Errors
99
+ : never)
100
+ : (
101
+ params: Route['body'] & {
102
+ $query?: Record<string, string>
103
+ $fetch?: RequestInit
104
+ }
105
+ ) => Promise<
106
+ Route['response'] extends {
107
+ 200: infer ReturnedType
108
+ }
109
+ ? ReturnedType
110
+ : unknown
111
+ >
112
+ : never
113
+ }
114
+ >
115
+ }
116
+
117
+ export interface OnMessage<Data = unknown> extends MessageEvent {
118
+ data: Data
119
+ rawData: MessageEvent['data']
120
+ }
121
+
122
+ export type WSEvent<
123
+ K extends keyof WebSocketEventMap,
124
+ Data = unknown
125
+ > = K extends 'message' ? OnMessage<Data> : WebSocketEventMap[K]
126
+
127
+ export interface CallOption {
128
+ [x: string]: any
129
+ $fetch?: RequestInit
130
+ $query?: Record<string, string>
131
+ }
132
+ }
133
+
134
+ type NestPath<T extends string, V> = T extends `${infer First}/${infer Rest}`
135
+ ? First extends `:${infer Parameter}`
136
+ ? Record<(string & {}) | `:${Parameter}`, NestPath<Rest, V>>
137
+ : Record<First, NestPath<Rest, V>>
138
+ : T extends `:${infer Parameter}`
139
+ ? Record<(string & {}) | T, V>
140
+ : Record<T, V>
@@ -0,0 +1,15 @@
1
+ export const composePath = (
2
+ domain: string,
3
+ path: string,
4
+ query: Record<string, string> | undefined
5
+ ) => {
6
+ if (!domain.endsWith('/')) domain += '/'
7
+ if (path === 'index') path = ''
8
+
9
+ if (!query || !Object.keys(query).length) return `${domain}${path}`
10
+
11
+ let q = ''
12
+ for (const [key, value] of Object.entries(query)) q += `${key}=${value}&`
13
+
14
+ return `${domain}${path}?${q.slice(0, -1)}`
15
+ }