@elysiajs/eden 0.1.0 → 0.2.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/dist/index.d.ts CHANGED
@@ -1,3 +1,15 @@
1
- import type { Elysia } from 'elysia';
2
- import { Eden } from './types';
1
+ /// <reference types="bun-types" />
2
+ import type { Elysia, TypedSchema } from 'elysia';
3
+ import type { Eden, EdenWSEvent } from './types';
4
+ export declare class EdenWS<Schema extends TypedSchema<any> = TypedSchema> {
5
+ ws: WebSocket;
6
+ url: string;
7
+ constructor(url: string);
8
+ send(data: Schema['body'] | Schema['body'][]): this;
9
+ on<K extends keyof WebSocketEventMap>(type: K, listener: (event: EdenWSEvent<K, Schema['response']>) => void, options?: boolean | AddEventListenerOptions): this;
10
+ off<K extends keyof WebSocketEventMap>(type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any, options?: boolean | EventListenerOptions): this;
11
+ addEventListener<K extends keyof WebSocketEventMap>(type: K, listener: (event: EdenWSEvent<K, Schema['response']>) => void, options?: boolean | AddEventListenerOptions): this;
12
+ removeEventListener<K extends keyof WebSocketEventMap>(type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any, options?: boolean | EventListenerOptions): this;
13
+ close(): this;
14
+ }
3
15
  export declare const eden: <App extends Elysia<any>>(domain: string) => Eden<App>;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const g=t=>t.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`),y=(t,e,r)=>{if(t.endsWith("/")||(t+="/"),e=g(e.replace(/index/g,"")),!r||!Object.keys(r).length)return`${t}${e}`;let n="";for(const[a,o]of Object.entries(r))n+=`${a}=${o}&`;return`${t}${e}?${n.slice(0,-1)}`},u=(t,e="")=>new Proxy(()=>{},{get(r,n,a){return u(t,`${e}/${n.toString()}`)},apply(r,n,[{$query:a,$fetch:o,...c}={$fetch:void 0,$query:void 0}]=[{}]){const l=e.lastIndexOf("/");return fetch(y(t,e.slice(0,l),a),{method:e.slice(l+1),headers:{"content-type":"application/json",...o==null?void 0:o.headers},body:Object.keys(c).length?JSON.stringify(c):void 0,...o}).then(async s=>{if(s.status>=300)throw new Error(await s.text());if(s.headers.get("content-type")==="application/json")return s.json();const i=await s.text();return Number.isNaN(+i)?i==="true"?!0:i==="false"?!1:i:+i})}}),f=t=>new Proxy({},{get(e,r,n){return u(t,r)}});exports.eden=f;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class d{constructor(e){this.ws=new WebSocket(e),this.url=e}send(e){return Array.isArray(e)?(e.forEach(t=>this.send(t)),this):(this.ws.send(typeof e=="object"?JSON.stringify(e):e.toString()),this)}on(e,t,s){return this.addEventListener(e,t,s)}off(e,t,s){return this.ws.removeEventListener(e,t,s),this}addEventListener(e,t,s){return this.ws.addEventListener(e,i=>{if(e==="message"){let r=i.data.toString();const o=r.charCodeAt(0);if(o===47||o===123)try{r=JSON.parse(r)}catch{}else Number.isNaN(+r)?r==="true"?r=!0:r==="fase"&&(r=!1):r=+r;t({...i,data:r})}else t(i)},s),this}removeEventListener(e,t,s){return this.off(e,t,s),this}close(){return this.ws.close(),this}}const g=n=>n.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`),y=(n,e,t)=>{if(n.endsWith("/")||(n+="/"),e=g(e.replace(/index/g,"")),!t||!Object.keys(t).length)return`${n}${e}`;let s="";for(const[i,r]of Object.entries(t))s+=`${i}=${r}&`;return`${n}${e}?${s.slice(0,-1)}`},h=(n,e="")=>new Proxy(()=>{},{get(t,s,i){return h(n,`${e}/${s.toString()}`)},apply(t,s,[{$query:i,$fetch:r,...o}={$fetch:void 0,$query:void 0}]=[{}]){const u=e.lastIndexOf("/"),l=e.slice(u+1),f=y(n,e.slice(0,u),i);return l==="subscribe"?new d(f.replace(/^([^]+):\/\//,"ws://")):fetch(f,{method:l,headers:{"content-type":"application/json",...r==null?void 0:r.headers},body:Object.keys(o).length?JSON.stringify(o):void 0,...r}).then(async c=>{if(c.status>=300)throw new Error(await c.text());if(c.headers.get("content-type")==="application/json")return c.json();const a=await c.text();return Number.isNaN(+a)?a==="true"?!0:a==="false"?!1:a:+a})}}),v=n=>new Proxy({},{get(e,t,s){return h(n,t)}});exports.EdenWS=d;exports.eden=v;
package/dist/index.mjs CHANGED
@@ -1,47 +1,94 @@
1
- const g = (t) => t.replace(/[A-Z]/g, (e) => `-${e.toLowerCase()}`), f = (t, e, r) => {
2
- if (t.endsWith("/") || (t += "/"), e = g(e.replace(/index/g, "")), !r || !Object.keys(r).length)
3
- return `${t}${e}`;
4
- let n = "";
5
- for (const [a, o] of Object.entries(r))
6
- n += `${a}=${o}&`;
7
- return `${t}${e}?${n.slice(0, -1)}`;
8
- }, u = (t, e = "") => new Proxy(() => {
1
+ class d {
2
+ constructor(e) {
3
+ this.ws = new WebSocket(e), this.url = e;
4
+ }
5
+ send(e) {
6
+ return Array.isArray(e) ? (e.forEach((t) => this.send(t)), this) : (this.ws.send(
7
+ typeof e == "object" ? JSON.stringify(e) : e.toString()
8
+ ), this);
9
+ }
10
+ on(e, t, s) {
11
+ return this.addEventListener(e, t, s);
12
+ }
13
+ off(e, t, s) {
14
+ return this.ws.removeEventListener(e, t, s), this;
15
+ }
16
+ addEventListener(e, t, s) {
17
+ return this.ws.addEventListener(
18
+ e,
19
+ (i) => {
20
+ if (e === "message") {
21
+ let r = i.data.toString();
22
+ const o = r.charCodeAt(0);
23
+ if (o === 47 || o === 123)
24
+ try {
25
+ r = JSON.parse(r);
26
+ } catch {
27
+ }
28
+ else
29
+ Number.isNaN(+r) ? r === "true" ? r = !0 : r === "fase" && (r = !1) : r = +r;
30
+ t({
31
+ ...i,
32
+ data: r
33
+ });
34
+ } else
35
+ t(i);
36
+ },
37
+ s
38
+ ), this;
39
+ }
40
+ removeEventListener(e, t, s) {
41
+ return this.off(e, t, s), this;
42
+ }
43
+ close() {
44
+ return this.ws.close(), this;
45
+ }
46
+ }
47
+ const g = (n) => n.replace(/[A-Z]/g, (e) => `-${e.toLowerCase()}`), y = (n, e, t) => {
48
+ if (n.endsWith("/") || (n += "/"), e = g(e.replace(/index/g, "")), !t || !Object.keys(t).length)
49
+ return `${n}${e}`;
50
+ let s = "";
51
+ for (const [i, r] of Object.entries(t))
52
+ s += `${i}=${r}&`;
53
+ return `${n}${e}?${s.slice(0, -1)}`;
54
+ }, h = (n, e = "") => new Proxy(() => {
9
55
  }, {
10
- get(r, n, a) {
11
- return u(t, `${e}/${n.toString()}`);
56
+ get(t, s, i) {
57
+ return h(n, `${e}/${s.toString()}`);
12
58
  },
13
- apply(r, n, [
14
- { $query: a, $fetch: o, ...c } = {
59
+ apply(t, s, [
60
+ { $query: i, $fetch: r, ...o } = {
15
61
  $fetch: void 0,
16
62
  $query: void 0
17
63
  }
18
64
  ] = [{}]) {
19
- const l = e.lastIndexOf("/");
20
- return fetch(f(t, e.slice(0, l), a), {
21
- method: e.slice(l + 1),
65
+ const u = e.lastIndexOf("/"), l = e.slice(u + 1), f = y(n, e.slice(0, u), i);
66
+ return l === "subscribe" ? new d(f.replace(/^([^]+):\/\//, "ws://")) : fetch(f, {
67
+ method: l,
22
68
  headers: {
23
69
  "content-type": "application/json",
24
- ...o == null ? void 0 : o.headers
70
+ ...r == null ? void 0 : r.headers
25
71
  },
26
- body: Object.keys(c).length ? JSON.stringify(c) : void 0,
27
- ...o
28
- }).then(async (s) => {
29
- if (s.status >= 300)
30
- throw new Error(await s.text());
31
- if (s.headers.get("content-type") === "application/json")
32
- return s.json();
33
- const i = await s.text();
34
- return Number.isNaN(+i) ? i === "true" ? !0 : i === "false" ? !1 : i : +i;
72
+ body: Object.keys(o).length ? JSON.stringify(o) : void 0,
73
+ ...r
74
+ }).then(async (c) => {
75
+ if (c.status >= 300)
76
+ throw new Error(await c.text());
77
+ if (c.headers.get("content-type") === "application/json")
78
+ return c.json();
79
+ const a = await c.text();
80
+ return Number.isNaN(+a) ? a === "true" ? !0 : a === "false" ? !1 : a : +a;
35
81
  });
36
82
  }
37
- }), y = (t) => new Proxy(
83
+ }), v = (n) => new Proxy(
38
84
  {},
39
85
  {
40
- get(e, r, n) {
41
- return u(t, r);
86
+ get(e, t, s) {
87
+ return h(n, t);
42
88
  }
43
89
  }
44
90
  );
45
91
  export {
46
- y as eden
92
+ d as EdenWS,
93
+ v as eden
47
94
  };
package/dist/index.umd.js CHANGED
@@ -1 +1 @@
1
- (function(r,s){typeof exports=="object"&&typeof module<"u"?s(exports):typeof define=="function"&&define.amd?define(["exports"],s):(r=typeof globalThis<"u"?globalThis:r||self,s(r["@elysia/eden"]={}))})(this,function(r){"use strict";const s=t=>t.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`),y=(t,e,n)=>{if(t.endsWith("/")||(t+="/"),e=s(e.replace(/index/g,"")),!n||!Object.keys(n).length)return`${t}${e}`;let o="";for(const[c,i]of Object.entries(n))o+=`${c}=${i}&`;return`${t}${e}?${o.slice(0,-1)}`},a=(t,e="")=>new Proxy(()=>{},{get(n,o,c){return a(t,`${e}/${o.toString()}`)},apply(n,o,[{$query:c,$fetch:i,...f}={$fetch:void 0,$query:void 0}]=[{}]){const d=e.lastIndexOf("/");return fetch(y(t,e.slice(0,d),c),{method:e.slice(d+1),headers:{"content-type":"application/json",...i==null?void 0:i.headers},body:Object.keys(f).length?JSON.stringify(f):void 0,...i}).then(async u=>{if(u.status>=300)throw new Error(await u.text());if(u.headers.get("content-type")==="application/json")return u.json();const l=await u.text();return Number.isNaN(+l)?l==="true"?!0:l==="false"?!1:l:+l})}}),g=t=>new Proxy({},{get(e,n,o){return a(t,n)}});r.eden=g,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})});
1
+ (function(o,u){typeof exports=="object"&&typeof module<"u"?u(exports):typeof define=="function"&&define.amd?define(["exports"],u):(o=typeof globalThis<"u"?globalThis:o||self,u(o["@elysia/eden"]={}))})(this,function(o){"use strict";class u{constructor(e){this.ws=new WebSocket(e),this.url=e}send(e){return Array.isArray(e)?(e.forEach(t=>this.send(t)),this):(this.ws.send(typeof e=="object"?JSON.stringify(e):e.toString()),this)}on(e,t,n){return this.addEventListener(e,t,n)}off(e,t,n){return this.ws.removeEventListener(e,t,n),this}addEventListener(e,t,n){return this.ws.addEventListener(e,i=>{if(e==="message"){let r=i.data.toString();const c=r.charCodeAt(0);if(c===47||c===123)try{r=JSON.parse(r)}catch{}else Number.isNaN(+r)?r==="true"?r=!0:r==="fase"&&(r=!1):r=+r;t({...i,data:r})}else t(i)},n),this}removeEventListener(e,t,n){return this.off(e,t,n),this}close(){return this.ws.close(),this}}const g=s=>s.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`),v=(s,e,t)=>{if(s.endsWith("/")||(s+="/"),e=g(e.replace(/index/g,"")),!t||!Object.keys(t).length)return`${s}${e}`;let n="";for(const[i,r]of Object.entries(t))n+=`${i}=${r}&`;return`${s}${e}?${n.slice(0,-1)}`},l=(s,e="")=>new Proxy(()=>{},{get(t,n,i){return l(s,`${e}/${n.toString()}`)},apply(t,n,[{$query:i,$fetch:r,...c}={$fetch:void 0,$query:void 0}]=[{}]){const d=e.lastIndexOf("/"),h=e.slice(d+1),y=v(s,e.slice(0,d),i);return h==="subscribe"?new u(y.replace(/^([^]+):\/\//,"ws://")):fetch(y,{method:h,headers:{"content-type":"application/json",...r==null?void 0:r.headers},body:Object.keys(c).length?JSON.stringify(c):void 0,...r}).then(async f=>{if(f.status>=300)throw new Error(await f.text());if(f.headers.get("content-type")==="application/json")return f.json();const a=await f.text();return Number.isNaN(+a)?a==="true"?!0:a==="false"?!1:a:+a})}}),p=s=>new Proxy({},{get(e,t,n){return l(s,t)}});o.EdenWS=u,o.eden=p,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})});
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /// <reference types="bun-types" />
2
2
  import type { Elysia, SCHEMA, TypedRoute } from 'elysia';
3
+ import { EdenWS } from '.';
3
4
  export declare type Eden<App extends Elysia<any>> = App['store'] extends {
4
5
  [key in typeof SCHEMA]: any;
5
6
  } ? UnionToIntersection<CreateEden<App['store'][typeof SCHEMA]>> : never;
@@ -9,21 +10,36 @@ export interface EdenCall {
9
10
  $query?: Record<string, string | boolean | number>;
10
11
  }
11
12
  export declare type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
12
- declare type TypedRouteToParams<Route extends TypedRoute> = (Route['body'] extends NonNullable<Route['body']> ? Route['body'] : {}) & (Route['query'] extends NonNullable<Route['query']> ? {
13
+ declare type TypedRouteToParams<Route extends TypedRoute> = (Route['body'] extends NonNullable<Route['body']> ? Route['body'] extends Record<any, any> ? Route['body'] : {
14
+ $body: Route['body'];
15
+ } : {}) & (Route['query'] extends NonNullable<Route['query']> ? {
13
16
  $query: Route['query'];
14
17
  } : {});
15
18
  export declare type CreateEden<Server extends Record<string, Record<string, TypedRoute>>, Path extends string = keyof Server, Full extends string = ''> = Path extends `/${infer Start}` ? CreateEden<Server, Start, Path> : Path extends `${infer A}/${infer B}` ? {
16
19
  [key in A]: CreateEden<Server, B, Full>;
17
20
  } : {
18
21
  [key in Path extends '' ? 'index' : Path extends `:${infer params}` ? string : Path | CamelCase<Path>]: Full extends keyof Server ? {
19
- [key in keyof Server[Full]]: keyof TypedRouteToParams<Server[Full][key]> extends never ? (params?: {
22
+ [key in keyof Server[Full]]: keyof TypedRouteToParams<Server[Full][key]> extends never ? key extends 'subscribe' ? (params?: Server[Full][key]['query'] extends NonNullable<Server[Full][key]['query']> ? {
23
+ $query: Server[Full][key]['query'];
24
+ } : {
25
+ $query?: EdenCall['$query'];
26
+ }) => EdenWS<Server[Full][key]> : (params?: {
20
27
  $query?: EdenCall['$query'];
21
28
  $fetch?: EdenCall['$fetch'];
22
- }) => Promise<Server[Full][key]['response']> : (params: TypedRouteToParams<Server[Full][key]> & {
29
+ }) => Promise<Server[Full][key]['response']> : key extends 'subscribe' ? (params?: Server[Full][key]['query'] extends NonNullable<Server[Full][key]['query']> ? {
30
+ $query: Server[Full][key]['query'];
31
+ } : {
32
+ $query?: EdenCall['$query'];
33
+ }) => EdenWS<Server[Full][key]> : (params: TypedRouteToParams<Server[Full][key]> & {
23
34
  $query?: EdenCall['$query'];
24
35
  $fetch?: EdenCall['$fetch'];
25
36
  }) => Promise<Server[Full][key]['response']>;
26
37
  } : never;
27
38
  };
28
39
  declare type CamelCase<S extends string> = S extends `${infer P1}-${infer P2}${infer P3}` ? `${Lowercase<P1>}${Uppercase<P2>}${CamelCase<P3>}` : Lowercase<S>;
40
+ export interface EdenWSOnMessage<Data = unknown> extends MessageEvent {
41
+ data: Data;
42
+ rawData: MessageEvent['data'];
43
+ }
44
+ export declare type EdenWSEvent<K extends keyof WebSocketEventMap, Data = unknown> = K extends 'message' ? EdenWSOnMessage<Data> : WebSocketEventMap[K];
29
45
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elysiajs/eden",
3
- "version": "0.1.0",
3
+ "version": "0.2.0-rc.0",
4
4
  "description": "Fully type-safe Elysia client",
5
5
  "author": {
6
6
  "name": "saltyAom",
@@ -34,14 +34,16 @@
34
34
  "release": "npm run build && npm run test && npm publish --access public"
35
35
  },
36
36
  "peerDependencies": {
37
- "elysia": ">= 0.1.0-rc.5"
37
+ "elysia": ">= 0.1.0-rc.5",
38
+ "@elysiajs/websocket": "^0.2.1"
38
39
  },
39
40
  "devDependencies": {
40
- "@elysiajs/cors": "^0.1.0-rc.3",
41
+ "@elysiajs/cors": "^0.1.0",
42
+ "@elysiajs/websocket": "^0.2.1",
41
43
  "@sinclair/typebox": "^0.25.13",
42
44
  "@types/node": "^18.11.7",
43
45
  "bun-types": "^0.3.0",
44
- "elysia": "^0.1.0-rc.8",
46
+ "elysia": "^0.1.2",
45
47
  "eslint": "^8.26.0",
46
48
  "rimraf": "^3.0.2",
47
49
  "typescript": "^4.8.4",
package/src/index.ts CHANGED
@@ -1,7 +1,107 @@
1
- import type { Elysia } from 'elysia'
1
+ import type { Elysia, TypedSchema } from 'elysia'
2
2
  import type { HTTPMethod } from 'elysia'
3
3
 
4
- import { CreateEden, Eden, EdenCall, UnionToIntersection } from './types'
4
+ import type {
5
+ CreateEden,
6
+ Eden,
7
+ EdenCall,
8
+ EdenWSEvent,
9
+ UnionToIntersection
10
+ } from './types'
11
+
12
+ export class EdenWS<Schema extends TypedSchema<any> = TypedSchema> {
13
+ ws: WebSocket
14
+ url: string
15
+
16
+ constructor(url: string) {
17
+ this.ws = new WebSocket(url)
18
+ this.url = url
19
+ }
20
+
21
+ send(data: Schema['body'] | Schema['body'][]) {
22
+ if (Array.isArray(data)) {
23
+ data.forEach((datum) => this.send(datum))
24
+
25
+ return this
26
+ }
27
+
28
+ this.ws.send(
29
+ typeof data === 'object' ? JSON.stringify(data) : data.toString()
30
+ )
31
+
32
+ return this
33
+ }
34
+
35
+ on<K extends keyof WebSocketEventMap>(
36
+ type: K,
37
+ listener: (event: EdenWSEvent<K, Schema['response']>) => void,
38
+ options?: boolean | AddEventListenerOptions
39
+ ) {
40
+ return this.addEventListener(type, listener, options)
41
+ }
42
+
43
+ off<K extends keyof WebSocketEventMap>(
44
+ type: K,
45
+ listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any,
46
+ options?: boolean | EventListenerOptions
47
+ ) {
48
+ this.ws.removeEventListener(type, listener, options)
49
+
50
+ return this
51
+ }
52
+
53
+ addEventListener<K extends keyof WebSocketEventMap>(
54
+ type: K,
55
+ listener: (event: EdenWSEvent<K, Schema['response']>) => void,
56
+ options?: boolean | AddEventListenerOptions
57
+ ) {
58
+ this.ws.addEventListener(
59
+ type,
60
+ (ws) => {
61
+ if (type === 'message') {
62
+ let data = (ws as MessageEvent).data.toString() as any
63
+ const start = data.charCodeAt(0)
64
+
65
+ if (start === 47 || start === 123)
66
+ try {
67
+ data = JSON.parse(data)
68
+ } catch {}
69
+ else if (!Number.isNaN(+data)) data = +data
70
+ else if (data === 'true') data = true
71
+ else if (data === 'fase') data = false
72
+
73
+ // @ts-ignore
74
+ listener({
75
+ ...ws,
76
+ data
77
+ })
78
+ } else {
79
+ // @ts-ignore
80
+ listener(ws)
81
+ }
82
+ },
83
+ options
84
+ )
85
+
86
+ return this
87
+ }
88
+
89
+ removeEventListener<K extends keyof WebSocketEventMap>(
90
+ type: K,
91
+ listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any,
92
+ options?: boolean | EventListenerOptions
93
+ ) {
94
+ this.off(type, listener, options)
95
+
96
+ return this
97
+ }
98
+
99
+ close() {
100
+ this.ws.close()
101
+
102
+ return this
103
+ }
104
+ }
5
105
 
6
106
  const camelToDash = (str: string) =>
7
107
  str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)
@@ -40,10 +140,15 @@ const createProxy = (
40
140
  }
41
141
  ]: EdenCall[] = [{}]
42
142
  ) {
43
- const i = path.lastIndexOf('/')
143
+ const i = path.lastIndexOf('/'),
144
+ method = path.slice(i + 1),
145
+ url = composePath(domain, path.slice(0, i), $query)
146
+
147
+ if (method === 'subscribe')
148
+ return new EdenWS(url.replace(/^([^]+):\/\//, 'ws://'))
44
149
 
45
- return fetch(composePath(domain, path.slice(0, i), $query), {
46
- method: path.slice(i + 1),
150
+ return fetch(url, {
151
+ method,
47
152
  headers: {
48
153
  'content-type': 'application/json',
49
154
  ...$fetch?.['headers']
package/src/types.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import type { Elysia, SCHEMA, TypedRoute } from 'elysia'
2
+ import { TObject } from '@sinclair/typebox'
3
+ import { EdenWS } from '.'
2
4
 
3
5
  export type Eden<App extends Elysia<any>> = App['store'] extends {
4
6
  [key in typeof SCHEMA]: any
@@ -6,7 +8,6 @@ export type Eden<App extends Elysia<any>> = App['store'] extends {
6
8
  ? UnionToIntersection<CreateEden<App['store'][typeof SCHEMA]>>
7
9
  : never
8
10
 
9
-
10
11
  export interface EdenCall {
11
12
  [x: string]: any
12
13
  $fetch?: RequestInit
@@ -20,7 +21,13 @@ export type UnionToIntersection<U> = (
20
21
  : never
21
22
 
22
23
  type TypedRouteToParams<Route extends TypedRoute> =
23
- (Route['body'] extends NonNullable<Route['body']> ? Route['body'] : {}) &
24
+ (Route['body'] extends NonNullable<Route['body']>
25
+ ? Route['body'] extends Record<any, any>
26
+ ? Route['body']
27
+ : {
28
+ $body: Route['body']
29
+ }
30
+ : {}) &
24
31
  (Route['query'] extends NonNullable<Route['query']>
25
32
  ? {
26
33
  $query: Route['query']
@@ -48,10 +55,34 @@ export type CreateEden<
48
55
  [key in keyof Server[Full]]: keyof TypedRouteToParams<
49
56
  Server[Full][key]
50
57
  > extends never
51
- ? (params?: {
52
- $query?: EdenCall['$query']
53
- $fetch?: EdenCall['$fetch']
54
- }) => Promise<Server[Full][key]['response']>
58
+ ? key extends 'subscribe'
59
+ ? (
60
+ params?: Server[Full][key]['query'] extends NonNullable<
61
+ Server[Full][key]['query']
62
+ >
63
+ ? {
64
+ $query: Server[Full][key]['query']
65
+ }
66
+ : {
67
+ $query?: EdenCall['$query']
68
+ }
69
+ ) => EdenWS<Server[Full][key]>
70
+ : (params?: {
71
+ $query?: EdenCall['$query']
72
+ $fetch?: EdenCall['$fetch']
73
+ }) => Promise<Server[Full][key]['response']>
74
+ : key extends 'subscribe'
75
+ ? (
76
+ params?: Server[Full][key]['query'] extends NonNullable<
77
+ Server[Full][key]['query']
78
+ >
79
+ ? {
80
+ $query: Server[Full][key]['query']
81
+ }
82
+ : {
83
+ $query?: EdenCall['$query']
84
+ }
85
+ ) => EdenWS<Server[Full][key]>
55
86
  : (
56
87
  params: TypedRouteToParams<Server[Full][key]> & {
57
88
  $query?: EdenCall['$query']
@@ -68,4 +99,12 @@ type CamelCase<S extends string> =
68
99
  ? `${Lowercase<P1>}${Uppercase<P2>}${CamelCase<P3>}`
69
100
  : Lowercase<S>
70
101
 
71
- type A = CamelCase<'sign-in'>
102
+ export interface EdenWSOnMessage<Data = unknown> extends MessageEvent {
103
+ data: Data
104
+ rawData: MessageEvent['data']
105
+ }
106
+
107
+ export type EdenWSEvent<
108
+ K extends keyof WebSocketEventMap,
109
+ Data = unknown
110
+ > = K extends 'message' ? EdenWSOnMessage<Data> : WebSocketEventMap[K]