@elysiajs/eden 0.8.1 → 1.0.0-beta.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.
@@ -1,380 +0,0 @@
1
- import type { Elysia, InputSchema } from 'elysia'
2
-
3
- import { EdenFetchError } from '../errors'
4
-
5
- import { composePath, isNumericString } from './utils'
6
- import type { EdenTreaty } from './types'
7
-
8
- export type { EdenTreaty } from './types'
9
-
10
- // @ts-ignore
11
- const isServer = typeof FileList === 'undefined'
12
-
13
- const isFile = (v: any) => {
14
- // @ts-ignore
15
- if (isServer) {
16
- return v instanceof Blob
17
- } else {
18
- // @ts-ignore
19
- return v instanceof FileList || v instanceof File
20
- }
21
- }
22
-
23
- // FormData is 1 level deep
24
- const hasFile = (obj: Record<string, any>) => {
25
- if (!obj) return false
26
-
27
- for (const key in obj) {
28
- if (isFile(obj[key])) return true
29
- else if (
30
- Array.isArray(obj[key]) &&
31
- (obj[key] as unknown[]).find((x) => isFile(x))
32
- )
33
- return true
34
- }
35
-
36
- return false
37
- }
38
-
39
- // @ts-ignore
40
- const createNewFile = (v: File) =>
41
- isServer
42
- ? v
43
- : new Promise<File>((resolve) => {
44
- // @ts-ignore
45
- const reader = new FileReader()
46
-
47
- reader.onload = () => {
48
- const file = new File([reader.result!], v.name, {
49
- lastModified: v.lastModified,
50
- type: v.type
51
- })
52
- resolve(file)
53
- }
54
-
55
- reader.readAsArrayBuffer(v)
56
- })
57
-
58
- export class EdenWS<Schema extends InputSchema<any> = InputSchema> {
59
- ws: WebSocket
60
- url: string
61
-
62
- constructor(url: string) {
63
- this.ws = new WebSocket(url)
64
- this.url = url
65
- }
66
-
67
- send(data: Schema['body'] | Schema['body'][]) {
68
- if (Array.isArray(data)) {
69
- data.forEach((datum) => this.send(datum))
70
-
71
- return this
72
- }
73
-
74
- this.ws.send(
75
- typeof data === 'object' ? JSON.stringify(data) : data.toString()
76
- )
77
-
78
- return this
79
- }
80
-
81
- on<K extends keyof WebSocketEventMap>(
82
- type: K,
83
- listener: (event: EdenTreaty.WSEvent<K, Schema['response']>) => void,
84
- options?: boolean | AddEventListenerOptions
85
- ) {
86
- return this.addEventListener(type, listener, options)
87
- }
88
-
89
- off<K extends keyof WebSocketEventMap>(
90
- type: K,
91
- listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any,
92
- options?: boolean | EventListenerOptions
93
- ) {
94
- this.ws.removeEventListener(type, listener, options)
95
-
96
- return this
97
- }
98
-
99
- subscribe(
100
- onMessage: (
101
- event: EdenTreaty.WSEvent<'message', Schema['response']>
102
- ) => void,
103
- options?: boolean | AddEventListenerOptions
104
- ) {
105
- return this.addEventListener('message', onMessage, options)
106
- }
107
-
108
- addEventListener<K extends keyof WebSocketEventMap>(
109
- type: K,
110
- listener: (event: EdenTreaty.WSEvent<K, Schema['response']>) => void,
111
- options?: boolean | AddEventListenerOptions
112
- ) {
113
- this.ws.addEventListener(
114
- type,
115
- (ws) => {
116
- if (type === 'message') {
117
- let data = (ws as MessageEvent).data.toString() as any
118
- const start = data.charCodeAt(0)
119
-
120
- if (start === 47 || start === 123)
121
- try {
122
- data = JSON.parse(data)
123
- } catch {
124
- // Not Empty
125
- }
126
- else if (isNumericString(data)) data = +data
127
- else if (data === 'true') data = true
128
- else if (data === 'false') data = false
129
-
130
- listener({
131
- ...ws,
132
- data
133
- } as any)
134
- } else listener(ws as any)
135
- },
136
- options
137
- )
138
-
139
- return this
140
- }
141
-
142
- removeEventListener<K extends keyof WebSocketEventMap>(
143
- type: K,
144
- listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any,
145
- options?: boolean | EventListenerOptions
146
- ) {
147
- this.off(type, listener, options)
148
-
149
- return this
150
- }
151
-
152
- close() {
153
- this.ws.close()
154
-
155
- return this
156
- }
157
- }
158
-
159
- const createProxy = (
160
- domain: string,
161
- path = '',
162
- config: EdenTreaty.Config
163
- ): Record<string, unknown> =>
164
- new Proxy(() => {}, {
165
- get(target, key, value) {
166
- return createProxy(domain, `${path}/${key.toString()}`, config)
167
- },
168
- // @ts-ignore
169
- apply(
170
- target,
171
- _,
172
- [initialBody, options = {}]: [
173
- {
174
- [x: string]: any
175
- $fetch?: RequestInit
176
- $headers?: HeadersInit
177
- $query?: Record<string, string>
178
- getRaw?: boolean
179
- },
180
- {
181
- fetch?: RequestInit
182
- transform?: EdenTreaty.Transform
183
- headers?: Record<string, string>
184
- query?: Record<string, string | number>
185
- }
186
- ] = [{}, {}]
187
- ) {
188
- let bodyObj: any =
189
- initialBody !== undefined &&
190
- (typeof initialBody !== 'object' || Array.isArray(initialBody))
191
- ? initialBody
192
- : undefined
193
-
194
- const {
195
- $query,
196
- $fetch,
197
- $headers,
198
- $transform,
199
- getRaw,
200
- ...restBody
201
- } = initialBody ?? {}
202
-
203
- bodyObj ??= restBody
204
-
205
- const i = path.lastIndexOf('/'),
206
- method = path.slice(i + 1).toUpperCase(),
207
- url = composePath(
208
- domain,
209
- i === -1 ? '/' : path.slice(0, i),
210
- Object.assign(options.query ?? {}, $query)
211
- )
212
-
213
- const fetcher = config.fetcher ?? fetch
214
- let transforms = config.transform
215
- ? Array.isArray(config.transform)
216
- ? config.transform
217
- : [config.transform]
218
- : undefined
219
-
220
- const $transforms = $transform
221
- ? Array.isArray($transform)
222
- ? $transform
223
- : [$transform]
224
- : undefined
225
-
226
- if ($transforms) {
227
- if (transforms) transforms = $transforms.concat(transforms)
228
- else transforms = $transforms as any
229
- }
230
-
231
- if (method === 'SUBSCRIBE')
232
- return new EdenWS(
233
- url.replace(
234
- /^([^]+):\/\//,
235
- url.startsWith('https://') ? 'wss://' : 'ws://'
236
- )
237
- )
238
-
239
- const execute = async <T extends EdenTreaty.ExecuteOptions>(
240
- modifiers: T
241
- ): Promise<EdenTreaty.ExecuteReturnType<T>> => {
242
- let body: any
243
-
244
- const headers = {
245
- ...config.$fetch?.headers,
246
- ...$fetch?.headers,
247
- ...options.headers,
248
- ...$headers
249
- } as Record<string, string>
250
-
251
- if (method !== 'GET' && method !== 'HEAD') {
252
- body = Object.keys(bodyObj).length
253
- ? bodyObj
254
- : Array.isArray(bodyObj)
255
- ? bodyObj
256
- : undefined
257
-
258
- const isObject =
259
- typeof body === 'object' || Array.isArray(bodyObj)
260
- const isFormData = isObject && hasFile(body)
261
-
262
- if (isFormData) {
263
- const newBody = new FormData()
264
-
265
- // FormData is 1 level deep
266
- for (const [key, field] of Object.entries(body)) {
267
- if (isServer) {
268
- newBody.append(key, field as any)
269
- } else {
270
- // @ts-ignore
271
- if (field instanceof File)
272
- newBody.append(
273
- key,
274
- await createNewFile(field as any)
275
- )
276
- // @ts-ignore
277
- else if (field instanceof FileList) {
278
- // @ts-ignore
279
- for (let i = 0; i < field.length; i++) {
280
- newBody.append(
281
- key as any,
282
- await createNewFile(
283
- (field as any)[i]
284
- )
285
- )
286
- }
287
- } else if (Array.isArray(field)) {
288
- for (let i = 0; i < field.length; i++) {
289
- const value = (field as any)[i]
290
-
291
- newBody.append(
292
- key as any,
293
- value instanceof File
294
- ? await createNewFile(value)
295
- : value
296
- )
297
- }
298
- } else newBody.append(key, field as string)
299
- }
300
- }
301
-
302
- body = newBody
303
- } else {
304
- headers['content-type'] = isObject
305
- ? 'application/json'
306
- : 'text/plain'
307
-
308
- body = isObject ? JSON.stringify(body) : bodyObj
309
- }
310
- }
311
-
312
- const response = await fetcher(url, {
313
- method,
314
- body,
315
- ...config.$fetch,
316
- ...options.fetch,
317
- ...$fetch,
318
- headers
319
- })
320
-
321
- let data
322
-
323
- if (modifiers.getRaw) return response as any
324
- switch (response.headers.get('Content-Type')?.split(';')[0]) {
325
- case 'application/json':
326
- data = await response.json()
327
- break
328
-
329
- default:
330
- data = await response.text().then((data) => {
331
- if (isNumericString(data)) return +data
332
- if (data === 'true') return true
333
- if (data === 'false') return false
334
-
335
- return data
336
- })
337
- }
338
-
339
- const error =
340
- response.status >= 300 || response.status < 200
341
- ? new EdenFetchError(response.status, data)
342
- : null
343
-
344
- let executeReturn = {
345
- data,
346
- error,
347
- response,
348
- status: response.status,
349
- headers: response.headers
350
- }
351
-
352
- if (transforms)
353
- for (const transform of transforms) {
354
- let temp = transform(executeReturn)
355
- if (temp instanceof Promise) temp = await temp
356
- if (temp !== undefined && temp !== null)
357
- executeReturn = temp as any
358
- }
359
-
360
- return executeReturn as any
361
- }
362
-
363
- return execute({ getRaw })
364
- }
365
- }) as unknown as Record<string, unknown>
366
-
367
- export const edenTreaty = <App extends Elysia<any, any, any, any, any, any>>(
368
- domain: string,
369
- config: EdenTreaty.Config = {
370
- fetcher: fetch
371
- }
372
- ): EdenTreaty.Create<App> =>
373
- new Proxy(
374
- {},
375
- {
376
- get(target, key) {
377
- return createProxy(domain, key as string, config)
378
- }
379
- }
380
- ) as any
@@ -1,219 +0,0 @@
1
- /// <reference lib="dom" />
2
- import type { Elysia } from 'elysia'
3
- import type { EdenWS } from './index'
4
- import type {
5
- IsUnknown,
6
- IsNever,
7
- IsAny,
8
- UnionToIntersect,
9
- MapError
10
- } from '../types'
11
- import type { EdenFetchError } from '../errors'
12
-
13
- type Files = File | FileList
14
-
15
- type Replace<RecordType, TargetType, GenericType> = {
16
- [K in keyof RecordType]: RecordType[K] extends TargetType
17
- ? GenericType
18
- : RecordType[K]
19
- }
20
-
21
- type Split<S extends string> = S extends `${infer Head}/${infer Tail}`
22
- ? Head extends ''
23
- ? Tail extends ''
24
- ? []
25
- : Split<Tail>
26
- : [Head, ...Split<Tail>]
27
- : S extends `/`
28
- ? []
29
- : S extends `${infer Head}/`
30
- ? [Head]
31
- : [S]
32
-
33
- type Prettify<T> = {
34
- [K in keyof T]: T[K]
35
- } & {}
36
-
37
- type AnySchema = {
38
- body: unknown
39
- headers: unknown
40
- query: unknown
41
- params: unknown
42
- response: any
43
- }
44
-
45
- type MaybeArray<T> = T | T[]
46
-
47
- export namespace EdenTreaty {
48
- export type Create<App extends Elysia<any, any, any, any, any, any>> =
49
- App extends {
50
- schema: infer Schema extends Record<string, any>
51
- }
52
- ? UnionToIntersect<Sign<Schema>>
53
- : 'Please install Elysia before using Eden'
54
-
55
- type SplitKeys<T> = T extends [infer First, ...infer Rest]
56
- ? [First, Rest]
57
- : T extends [infer First, ...infer Rest][number]
58
- ? [First, Rest]
59
- : never
60
-
61
- type UnwrapPromise<T> = T extends Promise<infer A> ? A : T
62
-
63
- export type Transform<T = unknown> = MaybeArray<
64
- (
65
- response: unknown extends T
66
- ? {
67
- data: any
68
- error: any
69
- response: Response
70
- status: number
71
- headers: Headers
72
- }
73
- : UnwrapPromise<T>
74
- ) => UnwrapPromise<T> | void
75
- >
76
-
77
- export interface Config {
78
- /**
79
- * Default options to pass to fetch
80
- */
81
- $fetch?: RequestInit
82
- fetcher?: typeof fetch
83
- transform?: Transform
84
- }
85
-
86
- export type DetailedResponse = {
87
- data: any
88
- error: any
89
- response: Response
90
- status: number
91
- headers: Headers
92
- };
93
-
94
- export type Sign<
95
- Schema extends Record<string, Record<string, unknown>>,
96
- Paths extends (string | number)[] = Split<keyof Schema & string>,
97
- Carry extends string = ''
98
- > = Paths extends [
99
- infer Prefix extends string | number,
100
- ...infer Rest extends (string | number)[]
101
- ]
102
- ? {
103
- [Key in Prefix as Prefix extends `:${string}`
104
- ? (string & {}) | number | Prefix
105
- : Prefix]: Sign<Schema, Rest, `${Carry}/${Key}`>
106
- }
107
- : Schema[Carry extends '' ? '/' : Carry] extends infer Routes
108
- ? {
109
- [Method in keyof Routes]: Routes[Method] extends infer Route extends
110
- AnySchema
111
- ? Method extends 'subscribe'
112
- ? undefined extends Route['query']
113
- ? (params?: {
114
- $query?: Record<string, string>
115
- }) => EdenWS<Route>
116
- : (params: {
117
- $query: Route['query']
118
- }) => EdenWS<Route>
119
- : ((
120
- params: {
121
- $fetch?: RequestInit
122
- getRaw?: boolean
123
- } & (IsUnknown<Route['body']> extends false
124
- ? Replace<
125
- Route['body'],
126
- Blob | Blob[],
127
- Files
128
- >
129
- : {}) &
130
- (undefined extends Route['query']
131
- ? {
132
- $query?: Record<string, string>
133
- }
134
- : {
135
- $query: Route['query']
136
- }) &
137
- (undefined extends Route['headers']
138
- ? {
139
- $headers?: Record<string, unknown>
140
- }
141
- : {
142
- $headers: Route['headers']
143
- })
144
- ) => Promise<
145
- (
146
- | {
147
- data: Route['response'] extends {
148
- 200: infer ReturnedType
149
- }
150
- ? Awaited<ReturnedType>
151
- : unknown
152
- error: null
153
- }
154
- | {
155
- data: null
156
- error: MapError<
157
- Route['response']
158
- > extends infer Errors
159
- ? IsNever<Errors> extends true
160
- ? EdenFetchError<
161
- number,
162
- string
163
- >
164
- : Errors
165
- : EdenFetchError<number, string>
166
- }
167
- ) & {
168
- status: number
169
- response: Response
170
- headers: Record<string, string>
171
- }
172
- >) extends (params: infer Params) => infer Response
173
- ? {
174
- $params: undefined
175
- $headers: undefined
176
- $query: undefined
177
- } extends Params
178
- ? (
179
- params?: Params,
180
- options?: {
181
- fetch?: RequestInit
182
- transform?: EdenTreaty.Transform<Response>
183
- // @ts-ignore
184
- query?: Params['query']
185
- // @ts-ignore
186
- headers?: Params['headers']
187
- }
188
- ) => Response
189
- : (
190
- params: Params,
191
- options?: {
192
- fetch?: RequestInit
193
- transform?: EdenTreaty.Transform<Response>
194
- // @ts-ignore
195
- query?: Params['query']
196
- // @ts-ignore
197
- headers?: Params['headers']
198
- }
199
- ) => Response
200
- : never
201
- : never
202
- }
203
- : {}
204
-
205
- export interface OnMessage<Data = unknown> extends MessageEvent {
206
- data: Data
207
- rawData: MessageEvent['data']
208
- }
209
-
210
- export type ExecuteOptions = {
211
- getRaw?: boolean
212
- };
213
- export type ExecuteReturnType<T extends ExecuteOptions> = T['getRaw'] extends true ? Response : DetailedResponse;
214
-
215
- export type WSEvent<
216
- K extends keyof WebSocketEventMap,
217
- Data = unknown
218
- > = K extends 'message' ? OnMessage<Data> : WebSocketEventMap[K]
219
- }
@@ -1,18 +0,0 @@
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
- }
16
-
17
- export const isNumericString = (message: string) =>
18
- !Number.isNaN(parseInt(message))
package/src/types.ts DELETED
@@ -1,58 +0,0 @@
1
- import type { EdenFetchError } from './errors'
2
-
3
- // https://stackoverflow.com/a/39495173
4
- type Range<F extends number, T extends number> = Exclude<
5
- Enumerate<T>,
6
- Enumerate<F>
7
- >
8
-
9
- type Enumerate<
10
- N extends number,
11
- Acc extends number[] = []
12
- > = Acc['length'] extends N
13
- ? Acc[number]
14
- : Enumerate<N, [...Acc, Acc['length']]>
15
-
16
- type ErrorRange = Range<300, 599>
17
-
18
- export type MapError<T extends Record<number, unknown>> = [
19
- {
20
- [K in keyof T]-?: K extends ErrorRange ? K : never
21
- }[keyof T]
22
- ] extends [infer A extends number]
23
- ? {
24
- [K in A]: EdenFetchError<K, T[K]>
25
- }[A]
26
- : false
27
-
28
- export type UnionToIntersect<U> = (
29
- U extends any ? (arg: U) => any : never
30
- ) extends (arg: infer I) => void
31
- ? I
32
- : never
33
-
34
- export type UnionToTuple<T> = UnionToIntersect<
35
- T extends any ? (t: T) => T : never
36
- > extends (_: any) => infer W
37
- ? [...UnionToTuple<Exclude<T, W>>, W]
38
- : []
39
-
40
- export type IsAny<T> = 0 extends 1 & T ? true : false
41
-
42
- export type IsNever<T> = [T] extends [never] ? true : false
43
-
44
- export type IsUnknown<T> = IsAny<T> extends true
45
- ? false
46
- : unknown extends T
47
- ? true
48
- : false
49
-
50
- export type AnyTypedRoute = {
51
- body: unknown
52
- headers: Record<string, any> | undefined
53
- query: Record<string, any> | undefined
54
- params: Record<string, any> | undefined
55
- response: Record<string, unknown> & {
56
- '200': unknown
57
- }
58
- }