@elysiajs/eden 0.2.1 → 0.3.0-exp-230222.2137

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,6 +1,6 @@
1
1
  /// <reference types="bun-types" />
2
2
  import type { Elysia, TypedSchema } from 'elysia';
3
- import type { Eden, EdenWSEvent } from './types';
3
+ import type { Eden, EdenConfig, EdenWSEvent } from './types';
4
4
  export declare class EdenWS<Schema extends TypedSchema<any> = TypedSchema> {
5
5
  ws: WebSocket;
6
6
  url: string;
@@ -12,4 +12,4 @@ export declare class EdenWS<Schema extends TypedSchema<any> = TypedSchema> {
12
12
  removeEventListener<K extends keyof WebSocketEventMap>(type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any, options?: boolean | EventListenerOptions): this;
13
13
  close(): this;
14
14
  }
15
- export declare const eden: <App extends Elysia<any>>(domain: string) => Eden<App>;
15
+ export declare const eden: <App extends Elysia<any>>(domain: string, config?: EdenConfig) => Eden<App>;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class v{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 l=r.charCodeAt(0);if(l===47||l===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 b=n=>n.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`),p=(n,e,t)=>{if(n.endsWith("/")||(n+="/"),e=b(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)}`},w=(n,e="")=>new Proxy(()=>{},{get(t,s,i){return w(n,`${e}/${s.toString()}`)},apply(t,s,[{$query:i,$fetch:r,$body:l,...f}={$fetch:void 0,$query:void 0,$body:void 0}]=[{}]){const h=e.lastIndexOf("/"),d=e.slice(h+1),u=p(n,e.slice(0,h),i);if(d==="subscribe")return new v(u.replace(/^([^]+):\/\//,u.startsWith("https://")?"wss://":"ws://"));const o=l??(Object.keys(f).length?f:void 0),g=typeof o=="object";return fetch(u,{method:d,body:g?JSON.stringify(o):o,headers:o?{"content-type":g?"application/json":"text/plain","content-length":o==null?void 0:o.length,...r==null?void 0:r.headers}:void 0,...r}).then(async c=>{var y;if(c.status>300)throw new Error(await c.text());if((y=c.headers.get("content-type"))!=null&&y.includes("application/json"))try{return await c.json()}catch{}let a=await c.text();return Number.isNaN(+a)?a==="true"?!0:a==="false"?!1:a:+a})}}),S=n=>new Proxy({},{get(e,t,s){return w(n,t)}});exports.EdenWS=v;exports.eden=S;
1
+ "use strict";var O=Object.create;var E=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var z=Object.getPrototypeOf,F=Object.prototype.hasOwnProperty;var J=(s,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of P(t))!F.call(s,r)&&r!==e&&E(s,r,{get:()=>t[r],enumerable:!(n=N(t,r))||n.enumerable});return s};var L=(s,t,e)=>(e=s!=null?O(z(s)):{},J(t||!s||!s.__esModule?E(e,"default",{value:s,enumerable:!0}):e,s));Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class m extends Error{constructor(t,e){super(),this.status=t,this.value=e}}const A=s=>s.replace(/[A-Z]/g,t=>`-${t.toLowerCase()}`),W=(s,t,e)=>{if(s.endsWith("/")||(s+="/"),t=A(t.replace(/index/g,"")),!e||!Object.keys(e).length)return`${s}${t}`;let n="";for(const[r,i]of Object.entries(e))n+=`${r}=${i}&`;return`${s}${t}?${n.slice(0,-1)}`};class p{constructor(t,e){this.pendings=[],this.operation=null,this.isFetching=!1,this.url=t,this.config=e,this.sJson=import("superjson").then(n=>({serialize:n.serialize,deserialize:n.deserialize}))}setConfig(t){this.config=t}clone(t){return new p(this.url,t??this.config)}async run(t,e){var i;const n=+this.pendings.length;if(this.pendings.push(e!==void 0?{n:t,p:e}:{n:t}),this.isFetching)return(i=this.operation)==null?void 0:i.then(o=>o[n]);this.isFetching=!0,this.operation=new Promise(o=>{setTimeout(async()=>{var l;const c=[...this.pendings];this.pendings=[];const{serialize:g,deserialize:y}=await this.sJson,h=await fetch(`${this.url}${this.config.fn??"/~fn"}`,{method:"POST",...this.config.fetch,headers:{"content-type":"elysia/fn",...(l=this.config.fetch)==null?void 0:l.headers},body:JSON.stringify(g(c))});h.status===200?o(h.json().then(u=>y(u))):o(Array(c.length).fill(new Error(await h.text())))},33)});const r=await this.operation.then(o=>o[n]);return this.operation=null,this.isFetching=!1,r}}class ${constructor(t){this.ws=new WebSocket(t),this.url=t}send(t){return Array.isArray(t)?(t.forEach(e=>this.send(e)),this):(this.ws.send(typeof t=="object"?JSON.stringify(t):t.toString()),this)}on(t,e,n){return this.addEventListener(t,e,n)}off(t,e,n){return this.ws.removeEventListener(t,e,n),this}addEventListener(t,e,n){return this.ws.addEventListener(t,r=>{if(t==="message"){let i=r.data.toString();const o=i.charCodeAt(0);if(o===47||o===123)try{i=JSON.parse(i)}catch{}else Number.isNaN(+i)?i==="true"?i=!0:i==="fase"&&(i=!1):i=+i;e({...r,data:i})}else e(r)},n),this}removeEventListener(t,e,n){return this.off(t,e,n),this}close(){return this.ws.close(),this}}const w=(s,t,e)=>new Proxy((...n)=>{},{get(n,r,i){return w(s,[...t,r],e)},apply(n,r,i){const o=i[0];if(t.length===1){if(t[0]in Object.prototype||t[0]in Promise.prototype)return n(i);switch(t[0]){case"toJSON":return n(i);case"$set":return e.setConfig(o);case"$clone":return w(s,[],e.clone(o))}}return e.run(t,o).then(c=>{if(c instanceof Error)throw c;return c})}}),x=(s,t="",e)=>new Proxy(()=>{},{get(n,r,i){return x(s,`${t}/${r.toString()}`,e)},apply(n,r,[{$query:i,$fetch:o,$body:c,...g}={$fetch:void 0,$query:void 0,$body:void 0}]=[{}]){var b;const y=t.lastIndexOf("/"),h=t.slice(y+1),l=W(s,t.slice(0,y),i);if(h==="subscribe")return new $(l.replace(/^([^]+):\/\//,l.startsWith("https://")?"wss://":"ws://"));const u=c??(Object.keys(g).length?g:void 0),v=typeof u=="object";return fetch(l,{method:h,body:v?JSON.stringify(u):u,...e.fetch,...o,headers:u?{"content-type":v?"application/json":"text/plain",...(b=e.fetch)==null?void 0:b.headers,...o==null?void 0:o.headers}:void 0}).then(async a=>{var S,j;if(a.status>300){let d;if((S=a.headers.get("content-type"))!=null&&S.includes("application/json"))try{d=await a.json()}catch{d=await a.text()}else d=await a.text();return new m(a.status,d)}if((j=a.headers.get("content-type"))!=null&&j.includes("application/json"))try{return await a.json()}catch{}let f=await a.text();return Number.isNaN(+f)?f==="true"?!0:f==="false"?!1:f:+f})}}),C=(s,t={})=>new Proxy({},{get(e,n){return n==="$fn"?w(s,[],new p(s,t)):x(s,n,t)}});exports.EdenWS=$;exports.eden=C;
package/dist/index.mjs CHANGED
@@ -1,108 +1,199 @@
1
- class w {
2
- constructor(e) {
3
- this.ws = new WebSocket(e), this.url = e;
1
+ class E extends Error {
2
+ constructor(t, e) {
3
+ super(), this.status = t, this.value = e;
4
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()
5
+ }
6
+ const N = (i) => i.replace(/[A-Z]/g, (t) => `-${t.toLowerCase()}`), O = (i, t, e) => {
7
+ if (i.endsWith("/") || (i += "/"), t = N(t.replace(/index/g, "")), !e || !Object.keys(e).length)
8
+ return `${i}${t}`;
9
+ let s = "";
10
+ for (const [o, n] of Object.entries(e))
11
+ s += `${o}=${n}&`;
12
+ return `${i}${t}?${s.slice(0, -1)}`;
13
+ };
14
+ class p {
15
+ constructor(t, e) {
16
+ this.pendings = [], this.operation = null, this.isFetching = !1, this.url = t, this.config = e, this.sJson = import("superjson").then((s) => ({
17
+ serialize: s.serialize,
18
+ deserialize: s.deserialize
19
+ }));
20
+ }
21
+ setConfig(t) {
22
+ this.config = t;
23
+ }
24
+ clone(t) {
25
+ return new p(this.url, t ?? this.config);
26
+ }
27
+ async run(t, e) {
28
+ var n;
29
+ const s = +this.pendings.length;
30
+ if (this.pendings.push(
31
+ e !== void 0 ? { n: t, p: e } : { n: t }
32
+ ), this.isFetching)
33
+ return (n = this.operation) == null ? void 0 : n.then((r) => r[s]);
34
+ this.isFetching = !0, this.operation = new Promise((r) => {
35
+ setTimeout(async () => {
36
+ var l;
37
+ const c = [...this.pendings];
38
+ this.pendings = [];
39
+ const { serialize: d, deserialize: y } = await this.sJson, h = await fetch(
40
+ `${this.url}${this.config.fn ?? "/~fn"}`,
41
+ {
42
+ method: "POST",
43
+ ...this.config.fetch,
44
+ headers: {
45
+ "content-type": "elysia/fn",
46
+ ...(l = this.config.fetch) == null ? void 0 : l.headers
47
+ },
48
+ body: JSON.stringify(d(c))
49
+ }
50
+ );
51
+ h.status === 200 ? r(h.json().then((u) => y(u))) : r(
52
+ Array(c.length).fill(
53
+ new Error(await h.text())
54
+ )
55
+ );
56
+ }, 33);
57
+ });
58
+ const o = await this.operation.then((r) => r[s]);
59
+ return this.operation = null, this.isFetching = !1, o;
60
+ }
61
+ }
62
+ class S {
63
+ constructor(t) {
64
+ this.ws = new WebSocket(t), this.url = t;
65
+ }
66
+ send(t) {
67
+ return Array.isArray(t) ? (t.forEach((e) => this.send(e)), this) : (this.ws.send(
68
+ typeof t == "object" ? JSON.stringify(t) : t.toString()
8
69
  ), this);
9
70
  }
10
- on(e, t, s) {
11
- return this.addEventListener(e, t, s);
71
+ on(t, e, s) {
72
+ return this.addEventListener(t, e, s);
12
73
  }
13
- off(e, t, s) {
14
- return this.ws.removeEventListener(e, t, s), this;
74
+ off(t, e, s) {
75
+ return this.ws.removeEventListener(t, e, s), this;
15
76
  }
16
- addEventListener(e, t, s) {
77
+ addEventListener(t, e, s) {
17
78
  return this.ws.addEventListener(
18
- e,
19
- (i) => {
20
- if (e === "message") {
21
- let r = i.data.toString();
22
- const l = r.charCodeAt(0);
23
- if (l === 47 || l === 123)
79
+ t,
80
+ (o) => {
81
+ if (t === "message") {
82
+ let n = o.data.toString();
83
+ const r = n.charCodeAt(0);
84
+ if (r === 47 || r === 123)
24
85
  try {
25
- r = JSON.parse(r);
86
+ n = JSON.parse(n);
26
87
  } catch {
27
88
  }
28
89
  else
29
- Number.isNaN(+r) ? r === "true" ? r = !0 : r === "fase" && (r = !1) : r = +r;
30
- t({
31
- ...i,
32
- data: r
90
+ Number.isNaN(+n) ? n === "true" ? n = !0 : n === "fase" && (n = !1) : n = +n;
91
+ e({
92
+ ...o,
93
+ data: n
33
94
  });
34
95
  } else
35
- t(i);
96
+ e(o);
36
97
  },
37
98
  s
38
99
  ), this;
39
100
  }
40
- removeEventListener(e, t, s) {
41
- return this.off(e, t, s), this;
101
+ removeEventListener(t, e, s) {
102
+ return this.off(t, e, s), this;
42
103
  }
43
104
  close() {
44
105
  return this.ws.close(), this;
45
106
  }
46
107
  }
47
- const p = (n) => n.replace(/[A-Z]/g, (e) => `-${e.toLowerCase()}`), b = (n, e, t) => {
48
- if (n.endsWith("/") || (n += "/"), e = p(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
- }, v = (n, e = "") => new Proxy(() => {
108
+ const w = (i, t, e) => new Proxy((...s) => {
109
+ }, {
110
+ get(s, o, n) {
111
+ return w(i, [...t, o], e);
112
+ },
113
+ apply(s, o, n) {
114
+ const r = n[0];
115
+ if (t.length === 1) {
116
+ if (t[0] in Object.prototype || t[0] in Promise.prototype)
117
+ return s(n);
118
+ switch (t[0]) {
119
+ case "toJSON":
120
+ return s(n);
121
+ case "$set":
122
+ return e.setConfig(r);
123
+ case "$clone":
124
+ return w(i, [], e.clone(r));
125
+ }
126
+ }
127
+ return e.run(t, r).then((c) => {
128
+ if (c instanceof Error)
129
+ throw c;
130
+ return c;
131
+ });
132
+ }
133
+ }), j = (i, t = "", e) => new Proxy(() => {
55
134
  }, {
56
- get(t, s, i) {
57
- return v(n, `${e}/${s.toString()}`);
135
+ get(s, o, n) {
136
+ return j(i, `${t}/${o.toString()}`, e);
58
137
  },
59
- apply(t, s, [
60
- { $query: i, $fetch: r, $body: l, ...f } = {
138
+ apply(s, o, [
139
+ { $query: n, $fetch: r, $body: c, ...d } = {
61
140
  $fetch: void 0,
62
141
  $query: void 0,
63
142
  $body: void 0
64
143
  }
65
144
  ] = [{}]) {
66
- const h = e.lastIndexOf("/"), d = e.slice(h + 1), u = b(n, e.slice(0, h), i);
67
- if (d === "subscribe")
68
- return new w(
69
- u.replace(
145
+ var b;
146
+ const y = t.lastIndexOf("/"), h = t.slice(y + 1), l = O(i, t.slice(0, y), n);
147
+ if (h === "subscribe")
148
+ return new S(
149
+ l.replace(
70
150
  /^([^]+):\/\//,
71
- u.startsWith("https://") ? "wss://" : "ws://"
151
+ l.startsWith("https://") ? "wss://" : "ws://"
72
152
  )
73
153
  );
74
- const o = l ?? (Object.keys(f).length ? f : void 0), g = typeof o == "object";
75
- return fetch(u, {
76
- method: d,
77
- body: g ? JSON.stringify(o) : o,
78
- headers: o ? {
79
- "content-type": g ? "application/json" : "text/plain",
80
- "content-length": o == null ? void 0 : o.length,
154
+ const u = c ?? (Object.keys(d).length ? d : void 0), v = typeof u == "object";
155
+ return fetch(l, {
156
+ method: h,
157
+ body: v ? JSON.stringify(u) : u,
158
+ ...e.fetch,
159
+ ...r,
160
+ headers: u ? {
161
+ "content-type": v ? "application/json" : "text/plain",
162
+ ...(b = e.fetch) == null ? void 0 : b.headers,
81
163
  ...r == null ? void 0 : r.headers
82
- } : void 0,
83
- ...r
84
- }).then(async (c) => {
85
- var y;
86
- if (c.status > 300)
87
- throw new Error(await c.text());
88
- if ((y = c.headers.get("content-type")) != null && y.includes("application/json"))
164
+ } : void 0
165
+ }).then(async (a) => {
166
+ var x, $;
167
+ if (a.status > 300) {
168
+ let g;
169
+ if ((x = a.headers.get("content-type")) != null && x.includes("application/json"))
170
+ try {
171
+ g = await a.json();
172
+ } catch {
173
+ g = await a.text();
174
+ }
175
+ else
176
+ g = await a.text();
177
+ return new E(a.status, g);
178
+ }
179
+ if (($ = a.headers.get("content-type")) != null && $.includes("application/json"))
89
180
  try {
90
- return await c.json();
181
+ return await a.json();
91
182
  } catch {
92
183
  }
93
- let a = await c.text();
94
- return Number.isNaN(+a) ? a === "true" ? !0 : a === "false" ? !1 : a : +a;
184
+ let f = await a.text();
185
+ return Number.isNaN(+f) ? f === "true" ? !0 : f === "false" ? !1 : f : +f;
95
186
  });
96
187
  }
97
- }), N = (n) => new Proxy(
188
+ }), z = (i, t = {}) => new Proxy(
98
189
  {},
99
190
  {
100
- get(e, t, s) {
101
- return v(n, t);
191
+ get(e, s) {
192
+ return s === "$fn" ? w(i, [], new p(i, t)) : j(i, s, t);
102
193
  }
103
194
  }
104
195
  );
105
196
  export {
106
- w as EdenWS,
107
- N as eden
197
+ S as EdenWS,
198
+ z as eden
108
199
  };
package/dist/index.umd.js CHANGED
@@ -1 +1 @@
1
- (function(o,a){typeof exports=="object"&&typeof module<"u"?a(exports):typeof define=="function"&&define.amd?define(["exports"],a):(o=typeof globalThis<"u"?globalThis:o||self,a(o["@elysia/eden"]={}))})(this,function(o){"use strict";class a{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 n=i.data.toString();const l=n.charCodeAt(0);if(l===47||l===123)try{n=JSON.parse(n)}catch{}else Number.isNaN(+n)?n==="true"?n=!0:n==="fase"&&(n=!1):n=+n;t({...i,data:n})}else t(i)},s),this}removeEventListener(e,t,s){return this.off(e,t,s),this}close(){return this.ws.close(),this}}const b=r=>r.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`),S=(r,e,t)=>{if(r.endsWith("/")||(r+="/"),e=b(e.replace(/index/g,"")),!t||!Object.keys(t).length)return`${r}${e}`;let s="";for(const[i,n]of Object.entries(t))s+=`${i}=${n}&`;return`${r}${e}?${s.slice(0,-1)}`},h=(r,e="")=>new Proxy(()=>{},{get(t,s,i){return h(r,`${e}/${s.toString()}`)},apply(t,s,[{$query:i,$fetch:n,$body:l,...y}={$fetch:void 0,$query:void 0,$body:void 0}]=[{}]){const g=e.lastIndexOf("/"),p=e.slice(g+1),d=S(r,e.slice(0,g),i);if(p==="subscribe")return new a(d.replace(/^([^]+):\/\//,d.startsWith("https://")?"wss://":"ws://"));const c=l??(Object.keys(y).length?y:void 0),v=typeof c=="object";return fetch(d,{method:p,body:v?JSON.stringify(c):c,headers:c?{"content-type":v?"application/json":"text/plain","content-length":c==null?void 0:c.length,...n==null?void 0:n.headers}:void 0,...n}).then(async u=>{var w;if(u.status>300)throw new Error(await u.text());if((w=u.headers.get("content-type"))!=null&&w.includes("application/json"))try{return await u.json()}catch{}let f=await u.text();return Number.isNaN(+f)?f==="true"?!0:f==="false"?!1:f:+f})}}),j=r=>new Proxy({},{get(e,t,s){return h(r,t)}});o.EdenWS=a,o.eden=j,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})});
1
+ (function(h,d){typeof exports=="object"&&typeof module<"u"?d(exports):typeof define=="function"&&define.amd?define(["exports"],d):(h=typeof globalThis<"u"?globalThis:h||self,d(h["@elysia/eden"]={}))})(this,function(h){"use strict";class d extends Error{constructor(t,e){super(),this.status=t,this.value=e}}const N=i=>i.replace(/[A-Z]/g,t=>`-${t.toLowerCase()}`),P=(i,t,e)=>{if(i.endsWith("/")||(i+="/"),t=N(t.replace(/index/g,"")),!e||!Object.keys(e).length)return`${i}${t}`;let s="";for(const[o,n]of Object.entries(e))s+=`${o}=${n}&`;return`${i}${t}?${s.slice(0,-1)}`};class v{constructor(t,e){this.pendings=[],this.operation=null,this.isFetching=!1,this.url=t,this.config=e,this.sJson=import("superjson").then(s=>({serialize:s.serialize,deserialize:s.deserialize}))}setConfig(t){this.config=t}clone(t){return new v(this.url,t??this.config)}async run(t,e){var n;const s=+this.pendings.length;if(this.pendings.push(e!==void 0?{n:t,p:e}:{n:t}),this.isFetching)return(n=this.operation)==null?void 0:n.then(r=>r[s]);this.isFetching=!0,this.operation=new Promise(r=>{setTimeout(async()=>{var u;const a=[...this.pendings];this.pendings=[];const{serialize:p,deserialize:w}=await this.sJson,l=await fetch(`${this.url}${this.config.fn??"/~fn"}`,{method:"POST",...this.config.fetch,headers:{"content-type":"elysia/fn",...(u=this.config.fetch)==null?void 0:u.headers},body:JSON.stringify(p(a))});l.status===200?r(l.json().then(f=>w(f))):r(Array(a.length).fill(new Error(await l.text())))},33)});const o=await this.operation.then(r=>r[s]);return this.operation=null,this.isFetching=!1,o}}class j{constructor(t){this.ws=new WebSocket(t),this.url=t}send(t){return Array.isArray(t)?(t.forEach(e=>this.send(e)),this):(this.ws.send(typeof t=="object"?JSON.stringify(t):t.toString()),this)}on(t,e,s){return this.addEventListener(t,e,s)}off(t,e,s){return this.ws.removeEventListener(t,e,s),this}addEventListener(t,e,s){return this.ws.addEventListener(t,o=>{if(t==="message"){let n=o.data.toString();const r=n.charCodeAt(0);if(r===47||r===123)try{n=JSON.parse(n)}catch{}else Number.isNaN(+n)?n==="true"?n=!0:n==="fase"&&(n=!1):n=+n;e({...o,data:n})}else e(o)},s),this}removeEventListener(t,e,s){return this.off(t,e,s),this}close(){return this.ws.close(),this}}const b=(i,t,e)=>new Proxy((...s)=>{},{get(s,o,n){return b(i,[...t,o],e)},apply(s,o,n){const r=n[0];if(t.length===1){if(t[0]in Object.prototype||t[0]in Promise.prototype)return s(n);switch(t[0]){case"toJSON":return s(n);case"$set":return e.setConfig(r);case"$clone":return b(i,[],e.clone(r))}}return e.run(t,r).then(a=>{if(a instanceof Error)throw a;return a})}}),S=(i,t="",e)=>new Proxy(()=>{},{get(s,o,n){return S(i,`${t}/${o.toString()}`,e)},apply(s,o,[{$query:n,$fetch:r,$body:a,...p}={$fetch:void 0,$query:void 0,$body:void 0}]=[{}]){var E;const w=t.lastIndexOf("/"),l=t.slice(w+1),u=P(i,t.slice(0,w),n);if(l==="subscribe")return new j(u.replace(/^([^]+):\/\//,u.startsWith("https://")?"wss://":"ws://"));const f=a??(Object.keys(p).length?p:void 0),x=typeof f=="object";return fetch(u,{method:l,body:x?JSON.stringify(f):f,...e.fetch,...r,headers:f?{"content-type":x?"application/json":"text/plain",...(E=e.fetch)==null?void 0:E.headers,...r==null?void 0:r.headers}:void 0}).then(async c=>{var $,O;if(c.status>300){let y;if(($=c.headers.get("content-type"))!=null&&$.includes("application/json"))try{y=await c.json()}catch{y=await c.text()}else y=await c.text();return new d(c.status,y)}if((O=c.headers.get("content-type"))!=null&&O.includes("application/json"))try{return await c.json()}catch{}let g=await c.text();return Number.isNaN(+g)?g==="true"?!0:g==="false"?!1:g:+g})}}),m=(i,t={})=>new Proxy({},{get(e,s){return s==="$fn"?b(i,[],new v(i,t)):S(i,s,t)}});h.EdenWS=j,h.eden=m,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})});
package/dist/types.d.ts CHANGED
@@ -1,16 +1,34 @@
1
1
  /// <reference types="bun-types" />
2
- import type { Elysia, SCHEMA, TypedRoute, IsPathParameter } from 'elysia';
2
+ import type { Elysia, SCHEMA, TypedRoute, IsPathParameter, EXPOSED } from 'elysia';
3
3
  import type { EdenWS } from '.';
4
+ import { type EdenFetchError } from './utils';
4
5
  declare type IsAny<T> = unknown extends T ? [T] extends [object] ? true : false : false;
6
+ declare type Promisify<T extends (...args: any[]) => any> = T extends (...args: infer Args) => infer Return ? Return extends Promise<any> ? T : (...args: Args) => Promise<Return> : never;
7
+ declare type Asynctify<T> = T extends infer Fn extends (...args: any) => any ? Promisify<Fn> : T extends Record<string, any> ? {
8
+ [K in keyof T]: EdenFn<T[K]>;
9
+ } : never;
10
+ declare type EdenFn<T> = T extends {
11
+ [EXPOSED]: true;
12
+ value: infer Value;
13
+ } ? Asynctify<Value> : Asynctify<T>;
14
+ declare type CreateEdenFn<Exposed extends Record<string, any>> = EdenFn<Exposed> & {
15
+ $set(config: EdenConfig): void;
16
+ $clone(config?: EdenConfig): CreateEdenFn<Exposed>;
17
+ };
5
18
  export declare type Eden<App extends Elysia<any>> = App['store'] extends {
6
19
  [key in typeof SCHEMA]: any;
7
- } ? IsAny<Elysia> extends true ? 'Please installed Elysia before using Eden' : UnionToIntersection<CreateEden<App['store'][typeof SCHEMA]>> : never;
20
+ } ? IsAny<Elysia> extends true ? 'Please install Elysia before using Eden' : UnionToIntersection<CreateEden<App['store'][typeof SCHEMA]>> & {
21
+ $fn: CreateEdenFn<App['store'][typeof EXPOSED]>;
22
+ } : never;
8
23
  export interface EdenCall {
9
24
  [x: string]: any;
10
25
  $fetch?: RequestInit;
11
26
  $query?: Record<string, string | boolean | number>;
12
27
  }
13
28
  export declare type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
29
+ declare type Prettify<T> = {
30
+ [K in keyof T]: T[K];
31
+ } & {};
14
32
  declare type TypedRouteToParams<Route extends TypedRoute> = (Route['body'] extends NonNullable<Route['body']> ? Route['body'] extends Record<any, any> ? Route['body'] : {
15
33
  $body: Route['body'];
16
34
  } : {}) & (Route['query'] extends NonNullable<Route['query']> ? unknown extends Route['query'] ? {} : {
@@ -21,31 +39,31 @@ export declare type CreateEden<Server extends Record<string, Record<string, Type
21
39
  } : Record<string, CreateEden<Server, B, Full>> & Record<`$${A}`, `Expected path parameters ':${A}', replace this with any string`> : // Iterate until last string then catch method
22
40
  {
23
41
  [key in Path extends '' ? 'index' : Path extends `:${infer params}` ? string : Path | CamelCase<Path>]: Full extends keyof Server ? {
24
- [key in keyof Server[Full] extends string ? Lowercase<keyof Server[Full]> : keyof Server[Full]]: keyof TypedRouteToParams<Server[Full][key extends string ? Uppercase<key> : key]> extends never ? key extends 'subscribe' ? unknown extends NonNullable<Server[Full][key]['query']> ? (params?: {
25
- $query?: EdenCall['$query'];
26
- }) => EdenWS<Server[Full][key]> : Server[Full][key]['query'] extends NonNullable<Server[Full][key]['query']> ? (params: {
27
- $query: Server[Full][key]['query'];
28
- }) => EdenWS<Server[Full][key]> : (params?: {
29
- $query?: EdenCall['$query'];
30
- }) => EdenWS<Server[Full][key]> : (params?: {
42
+ [key in keyof Server[Full] extends string ? Lowercase<keyof Server[Full]> : keyof Server[Full]]: [
43
+ Server[Full][key extends string ? Uppercase<key> : key]
44
+ ] extends [infer Route extends TypedRoute] ? undefined extends Route['body'] ? (params?: {
31
45
  $query?: EdenCall['$query'];
32
46
  $fetch?: EdenCall['$fetch'];
33
- }) => Promise<key extends string ? Server[Full][Uppercase<key>]['response'] extends {
47
+ }) => Promise<Route['response'] extends {
34
48
  200: infer ReturnedType;
35
- } ? ReturnedType : unknown : Server[Full][key]['response'] extends {
36
- 200: infer ReturnedType;
37
- } ? ReturnedType : unknown> : key extends 'subscribe' ? unknown extends NonNullable<Server[Full][key]['query']> ? (params?: {
38
- $query?: EdenCall['$query'];
39
- }) => EdenWS<Server[Full][key]> : Server[Full][key]['query'] extends NonNullable<Server[Full][key]['query']> ? (params: {
40
- $query: Server[Full][key]['query'];
41
- }) => EdenWS<Server[Full][key]> : (params?: {
42
- $query?: EdenCall['$query'];
43
- }) => EdenWS<Server[Full][key]> : (params: TypedRouteToParams<Server[Full][key extends string ? Uppercase<key> : key]> & {
49
+ } ? ReturnedType | MapError<Route['response']> : unknown> : (params: Prettify<TypedRouteToParams<Route> & {
44
50
  $query?: EdenCall['$query'];
45
51
  $fetch?: EdenCall['$fetch'];
46
- }) => Promise<Server[Full][key extends string ? Uppercase<key> : key]['response'] extends {
52
+ }>) => Promise<Route['response'] extends {
47
53
  200: infer ReturnedType;
48
- } ? ReturnedType : unknown>;
54
+ } ? ReturnedType | MapError<Route['response']> : unknown> : key extends 'subscribe' ? [
55
+ Server[Full][key],
56
+ Server[Full][key]['query']
57
+ ] extends [
58
+ infer Route extends TypedRoute,
59
+ infer Query extends TypedRoute['query']
60
+ ] ? unknown extends NonNullable<Query> ? (params?: {
61
+ $query?: EdenCall['$query'];
62
+ }) => EdenWS<Route> : Query extends NonNullable<Query> ? (params: {
63
+ $query: Query;
64
+ }) => EdenWS<Route> : (params?: {
65
+ $query?: EdenCall['$query'];
66
+ }) => EdenWS<Route> : never : never;
49
67
  } : never;
50
68
  } & (Path extends `:${infer params}` ? Record<`$${params}`, `Expected path parameters ':${params}', replace this with any string`> : {});
51
69
  declare type CamelCase<S extends string> = S extends `${infer P1}-${infer P2}${infer P3}` ? `${Lowercase<P1>}${Uppercase<P2>}${CamelCase<P3>}` : Lowercase<S>;
@@ -54,4 +72,18 @@ export interface EdenWSOnMessage<Data = unknown> extends MessageEvent {
54
72
  rawData: MessageEvent['data'];
55
73
  }
56
74
  export declare type EdenWSEvent<K extends keyof WebSocketEventMap, Data = unknown> = K extends 'message' ? EdenWSOnMessage<Data> : WebSocketEventMap[K];
75
+ export interface EdenConfig {
76
+ fn?: string;
77
+ fetch?: Omit<RequestInit, 'body'>;
78
+ }
79
+ declare type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N ? Acc[number] : Enumerate<N, [...Acc, Acc['length']]>;
80
+ declare type Range<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>;
81
+ declare type ErrorRange = Range<300, 599>;
82
+ declare type MapError<T extends Record<number, unknown>> = [
83
+ {
84
+ [K in keyof T]-?: K extends ErrorRange ? K : never;
85
+ }[keyof T]
86
+ ] extends [infer A extends number] ? {
87
+ [K in A]: EdenFetchError<K, T[K]>;
88
+ }[A] : false;
57
89
  export {};
@@ -0,0 +1,19 @@
1
+ import type { EdenCall, EdenConfig } from './types';
2
+ export declare class EdenFetchError<Status extends number = number, Value = unknown> extends Error {
3
+ status: Status;
4
+ value: Value;
5
+ constructor(status: Status, value: Value);
6
+ }
7
+ export declare const composePath: (domain: string, path: string, query: EdenCall['$query'] | undefined) => string;
8
+ export declare class Signal {
9
+ private url;
10
+ private config;
11
+ private pendings;
12
+ private operation;
13
+ private isFetching;
14
+ private sJson;
15
+ constructor(url: string, config: EdenConfig);
16
+ setConfig(config: EdenConfig): void;
17
+ clone(config?: EdenConfig): Signal;
18
+ run(procedure: string[], params: any): Promise<any>;
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elysiajs/eden",
3
- "version": "0.2.1",
3
+ "version": "0.3.0-exp-230222.2137",
4
4
  "description": "Fully type-safe Elysia client",
5
5
  "author": {
6
6
  "name": "saltyAom",
@@ -9,10 +9,12 @@
9
9
  },
10
10
  "main": "./dist/index.js",
11
11
  "exports": {
12
- "require": "./dist/index.js",
13
- "import": "./dist/index.mjs",
14
- "node": "./dist/index.js",
15
- "default": "./dist/index.js"
12
+ ".": {
13
+ "require": "./dist/index.js",
14
+ "import": "./dist/index.mjs",
15
+ "node": "./dist/index.js",
16
+ "default": "./dist/index.js"
17
+ }
16
18
  },
17
19
  "types": "./src/index.ts",
18
20
  "keywords": [
@@ -34,19 +36,22 @@
34
36
  "release": "npm run build && npm run test && npm publish --access public"
35
37
  },
36
38
  "peerDependencies": {
37
- "elysia": ">= 0.2.0"
39
+ "elysia": ">= 0.3.0-exp-230222.2123"
38
40
  },
39
41
  "devDependencies": {
40
42
  "@elysiajs/cors": "^0.1.0",
41
43
  "@elysiajs/websocket": "^0.2.6",
42
44
  "@sinclair/typebox": "^0.25.21",
43
45
  "@types/node": "^18.11.7",
44
- "bun-types": "^0.3.0",
46
+ "bun-types": "^0.5.0",
45
47
  "eslint": "^8.26.0",
46
- "elysia": "^0.2.0",
48
+ "elysia": "../elysia",
47
49
  "rimraf": "^3.0.2",
48
50
  "typescript": "^4.8.4",
49
51
  "vite": "^4.0.1",
50
52
  "vite-plugin-dts": "^1.7.1"
53
+ },
54
+ "dependencies": {
55
+ "superjson": "^1.12.2"
51
56
  }
52
57
  }
package/src/index.ts CHANGED
@@ -4,9 +4,11 @@ import type {
4
4
  CreateEden,
5
5
  Eden,
6
6
  EdenCall,
7
+ EdenConfig,
7
8
  EdenWSEvent,
8
9
  UnionToIntersection
9
10
  } from './types'
11
+ import { composePath, EdenFetchError, Signal } from './utils'
10
12
 
11
13
  export class EdenWS<Schema extends TypedSchema<any> = TypedSchema> {
12
14
  ws: WebSocket
@@ -102,32 +104,54 @@ export class EdenWS<Schema extends TypedSchema<any> = TypedSchema> {
102
104
  }
103
105
  }
104
106
 
105
- const camelToDash = (str: string) =>
106
- str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)
107
-
108
- const composePath = (
107
+ const createFn = (
109
108
  domain: string,
110
- path: string,
111
- query: EdenCall['$query'] | undefined
112
- ) => {
113
- if (!domain.endsWith('/')) domain += '/'
114
- path = camelToDash(path.replace(/index/g, ''))
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]
115
119
 
116
- if (!query || !Object.keys(query).length) return `${domain}${path}`
120
+ if (procedure.length === 1) {
121
+ if (
122
+ procedure[0] in Object.prototype ||
123
+ procedure[0] in Promise.prototype
124
+ )
125
+ return target(params)
117
126
 
118
- let q = ''
119
- for (const [key, value] of Object.entries(query)) q += `${key}=${value}&`
127
+ switch (procedure[0]) {
128
+ case 'toJSON':
129
+ return target(params)
120
130
 
121
- return `${domain}${path}?${q.slice(0, -1)}`
122
- }
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, param).then((result) => {
140
+ if (result instanceof Error) throw result
141
+
142
+ return result
143
+ })
144
+ }
145
+ })
123
146
 
124
147
  const createProxy = (
125
148
  domain: string,
126
- path: string = ''
149
+ path: string = '',
150
+ config: EdenConfig
127
151
  ): Record<string, unknown> =>
128
152
  new Proxy(() => {}, {
129
153
  get(target, key, value) {
130
- return createProxy(domain, `${path}/${key.toString()}`)
154
+ return createProxy(domain, `${path}/${key.toString()}`, config)
131
155
  },
132
156
  apply(
133
157
  target,
@@ -159,18 +183,35 @@ const createProxy = (
159
183
  return fetch(url, {
160
184
  method,
161
185
  body: isObject ? JSON.stringify(body) : body,
186
+ ...config.fetch,
187
+ ...$fetch,
162
188
  headers: body
163
189
  ? {
164
190
  'content-type': isObject
165
191
  ? 'application/json'
166
192
  : 'text/plain',
167
- 'content-length': body?.length,
193
+ ...config.fetch?.headers,
168
194
  ...$fetch?.['headers']
169
195
  }
170
- : undefined,
171
- ...$fetch
196
+ : undefined
172
197
  }).then(async (res) => {
173
- if (res.status > 300) throw new Error(await res.text())
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
+ }
174
215
 
175
216
  if (
176
217
  res.headers
@@ -195,12 +236,18 @@ const createProxy = (
195
236
  }
196
237
  }) as unknown as Record<string, unknown>
197
238
 
198
- export const eden = <App extends Elysia<any>>(domain: string): Eden<App> =>
239
+ export const eden = <App extends Elysia<any>>(
240
+ domain: string,
241
+ config: EdenConfig = {}
242
+ ): Eden<App> =>
199
243
  new Proxy(
200
244
  {},
201
245
  {
202
- get(target, key, value) {
203
- return createProxy(domain, key as string)
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)
204
251
  }
205
252
  }
206
253
  ) as any
package/src/types.ts CHANGED
@@ -1,7 +1,14 @@
1
- import type { Elysia, SCHEMA, TypedRoute, IsPathParameter } from 'elysia'
1
+ import type {
2
+ Elysia,
3
+ SCHEMA,
4
+ TypedRoute,
5
+ IsPathParameter,
6
+ EXPOSED
7
+ } from 'elysia'
2
8
  import type { TObject } from '@sinclair/typebox'
3
9
 
4
10
  import type { EdenWS } from '.'
11
+ import { type EdenFetchError, type Signal } from './utils'
5
12
 
6
13
  type IsAny<T> = unknown extends T
7
14
  ? [T] extends [object]
@@ -9,12 +16,42 @@ type IsAny<T> = unknown extends T
9
16
  : false
10
17
  : false
11
18
 
19
+ type Promisify<T extends (...args: any[]) => any> = T extends (
20
+ ...args: infer Args
21
+ ) => infer Return
22
+ ? Return extends Promise<any>
23
+ ? T
24
+ : (...args: Args) => Promise<Return>
25
+ : never
26
+
27
+ type Asynctify<T> = T extends infer Fn extends (...args: any) => any
28
+ ? Promisify<Fn>
29
+ : T extends Record<string, any>
30
+ ? {
31
+ [K in keyof T]: EdenFn<T[K]>
32
+ }
33
+ : never
34
+
35
+ type EdenFn<T> = T extends {
36
+ [EXPOSED]: true
37
+ value: infer Value
38
+ }
39
+ ? Asynctify<Value>
40
+ : Asynctify<T>
41
+
42
+ type CreateEdenFn<Exposed extends Record<string, any>> = EdenFn<Exposed> & {
43
+ $set(config: EdenConfig): void
44
+ $clone(config?: EdenConfig): CreateEdenFn<Exposed>
45
+ }
46
+
12
47
  export type Eden<App extends Elysia<any>> = App['store'] extends {
13
48
  [key in typeof SCHEMA]: any
14
49
  }
15
50
  ? IsAny<Elysia> extends true
16
- ? 'Please installed Elysia before using Eden'
17
- : UnionToIntersection<CreateEden<App['store'][typeof SCHEMA]>>
51
+ ? 'Please install Elysia before using Eden'
52
+ : UnionToIntersection<CreateEden<App['store'][typeof SCHEMA]>> & {
53
+ $fn: CreateEdenFn<App['store'][typeof EXPOSED]>
54
+ }
18
55
  : never
19
56
 
20
57
  export interface EdenCall {
@@ -29,6 +66,11 @@ export type UnionToIntersection<U> = (
29
66
  ? I
30
67
  : never
31
68
 
69
+ // https://twitter.com/mattpocockuk/status/1622730173446557697?s=20
70
+ type Prettify<T> = {
71
+ [K in keyof T]: T[K]
72
+ } & {}
73
+
32
74
  type TypedRouteToParams<Route extends TypedRoute> =
33
75
  (Route['body'] extends NonNullable<Route['body']>
34
76
  ? Route['body'] extends Record<any, any>
@@ -75,75 +117,58 @@ export type CreateEden<
75
117
  // Check if is method
76
118
  [key in keyof Server[Full] extends string
77
119
  ? Lowercase<keyof Server[Full]>
78
- : keyof Server[Full]]: keyof TypedRouteToParams<
120
+ : keyof Server[Full]]: [
79
121
  Server[Full][key extends string ? Uppercase<key> : key]
80
- > extends never
81
- ? key extends 'subscribe'
82
- ? unknown extends NonNullable<
83
- Server[Full][key]['query']
84
- >
85
- ? (params?: {
86
- $query?: EdenCall['$query']
87
- }) => EdenWS<Server[Full][key]>
88
- : Server[Full][key]['query'] extends NonNullable<
89
- Server[Full][key]['query']
90
- >
91
- ? (params: {
92
- $query: Server[Full][key]['query']
93
- }) => EdenWS<Server[Full][key]>
94
- : (params?: {
95
- $query?: EdenCall['$query']
96
- }) => EdenWS<Server[Full][key]>
97
- : (params?: {
122
+ ] extends [infer Route extends TypedRoute]
123
+ ? undefined extends Route['body']
124
+ ? (params?: {
98
125
  $query?: EdenCall['$query']
99
126
  $fetch?: EdenCall['$fetch']
100
127
  }) => Promise<
101
- key extends string
102
- ? Server[Full][Uppercase<key>]['response'] extends {
103
- 200: infer ReturnedType
104
- }
105
- ? ReturnedType
106
- : unknown
107
- : Server[Full][key]['response'] extends {
108
- 200: infer ReturnedType
109
- }
110
- ? ReturnedType
128
+ Route['response'] extends {
129
+ 200: infer ReturnedType
130
+ }
131
+ ?
132
+ | ReturnedType
133
+ | MapError<Route['response']>
134
+ : unknown
135
+ >
136
+ : (
137
+ params: Prettify<
138
+ TypedRouteToParams<Route> & {
139
+ $query?: EdenCall['$query']
140
+ $fetch?: EdenCall['$fetch']
141
+ }
142
+ >
143
+ ) => Promise<
144
+ Route['response'] extends {
145
+ 200: infer ReturnedType
146
+ }
147
+ ?
148
+ | ReturnedType
149
+ | MapError<Route['response']>
111
150
  : unknown
112
151
  >
113
152
  : key extends 'subscribe'
114
- ? unknown extends NonNullable<
153
+ ? // Since subscribe key is only a lower letter
154
+ [
155
+ Server[Full][key],
115
156
  Server[Full][key]['query']
116
- >
117
- ? (params?: {
118
- $query?: EdenCall['$query']
119
- }) => EdenWS<Server[Full][key]>
120
- : Server[Full][key]['query'] extends NonNullable<
121
- Server[Full][key]['query']
122
- >
123
- ? (params: {
124
- $query: Server[Full][key]['query']
125
- }) => EdenWS<Server[Full][key]>
126
- : (params?: {
127
- $query?: EdenCall['$query']
128
- }) => EdenWS<Server[Full][key]>
129
- : (
130
- params: TypedRouteToParams<
131
- Server[Full][key extends string
132
- ? Uppercase<key>
133
- : key]
134
- > & {
135
- $query?: EdenCall['$query']
136
- $fetch?: EdenCall['$fetch']
137
- }
138
- ) => Promise<
139
- Server[Full][key extends string
140
- ? Uppercase<key>
141
- : key]['response'] extends {
142
- 200: infer ReturnedType
143
- }
144
- ? ReturnedType
145
- : unknown
146
- >
157
+ ] extends [
158
+ infer Route extends TypedRoute,
159
+ infer Query extends TypedRoute['query']
160
+ ]
161
+ ? unknown extends NonNullable<Query>
162
+ ? (params?: {
163
+ $query?: EdenCall['$query']
164
+ }) => EdenWS<Route>
165
+ : Query extends NonNullable<Query>
166
+ ? (params: { $query: Query }) => EdenWS<Route>
167
+ : (params?: {
168
+ $query?: EdenCall['$query']
169
+ }) => EdenWS<Route>
170
+ : never
171
+ : never
147
172
  }
148
173
  : never
149
174
  } & (Path extends `:${infer params}`
@@ -168,3 +193,33 @@ export type EdenWSEvent<
168
193
  K extends keyof WebSocketEventMap,
169
194
  Data = unknown
170
195
  > = K extends 'message' ? EdenWSOnMessage<Data> : WebSocketEventMap[K]
196
+
197
+ export interface EdenConfig {
198
+ fn?: string
199
+ fetch?: Omit<RequestInit, 'body'>
200
+ }
201
+
202
+ type Enumerate<
203
+ N extends number,
204
+ Acc extends number[] = []
205
+ > = Acc['length'] extends N
206
+ ? Acc[number]
207
+ : Enumerate<N, [...Acc, Acc['length']]>
208
+
209
+ // https://stackoverflow.com/a/39495173
210
+ type Range<F extends number, T extends number> = Exclude<
211
+ Enumerate<T>,
212
+ Enumerate<F>
213
+ >
214
+
215
+ type ErrorRange = Range<300, 599>
216
+
217
+ type MapError<T extends Record<number, unknown>> = [
218
+ {
219
+ [K in keyof T]-?: K extends ErrorRange ? K : never
220
+ }[keyof T]
221
+ ] extends [infer A extends number]
222
+ ? {
223
+ [K in A]: EdenFetchError<K, T[K]>
224
+ }[A]
225
+ : false
package/src/utils.ts ADDED
@@ -0,0 +1,121 @@
1
+ import type { serialize, deserialize } from 'superjson'
2
+ import type { EdenCall, EdenConfig } from './types'
3
+
4
+ export class EdenFetchError<
5
+ Status extends number = number,
6
+ Value = unknown
7
+ > extends Error {
8
+ status: Status
9
+ value: Value
10
+
11
+ constructor(status: Status, value: Value) {
12
+ super()
13
+
14
+ this.status = status
15
+ this.value = value
16
+ }
17
+ }
18
+
19
+
20
+ const camelToDash = (str: string) =>
21
+ str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)
22
+
23
+ export const composePath = (
24
+ domain: string,
25
+ path: string,
26
+ query: EdenCall['$query'] | undefined
27
+ ) => {
28
+ if (!domain.endsWith('/')) domain += '/'
29
+ path = camelToDash(path.replace(/index/g, ''))
30
+
31
+ if (!query || !Object.keys(query).length) return `${domain}${path}`
32
+
33
+ let q = ''
34
+ for (const [key, value] of Object.entries(query)) q += `${key}=${value}&`
35
+
36
+ return `${domain}${path}?${q.slice(0, -1)}`
37
+ }
38
+
39
+ export class Signal {
40
+ private url: string
41
+ private config: EdenConfig
42
+
43
+ private pendings: Array<{ n: string[] } | { n: string[]; p: any }> = []
44
+ private operation: Promise<any[]> | null = null
45
+ private isFetching = false
46
+
47
+ private sJson: Promise<{
48
+ serialize: typeof serialize
49
+ deserialize: typeof deserialize
50
+ }>
51
+
52
+ constructor(url: string, config: EdenConfig) {
53
+ this.url = url
54
+ this.config = config
55
+
56
+ this.sJson = import('superjson').then((superJson) => {
57
+ return {
58
+ serialize: superJson.serialize,
59
+ deserialize: superJson.deserialize
60
+ }
61
+ })
62
+ }
63
+
64
+ setConfig(config: EdenConfig) {
65
+ this.config = config
66
+ }
67
+
68
+ clone(config?: EdenConfig) {
69
+ return new Signal(this.url, config ?? this.config)
70
+ }
71
+
72
+ async run(procedure: string[], params: any) {
73
+ const current = +this.pendings.length
74
+ this.pendings.push(
75
+ params !== undefined
76
+ ? { n: procedure, p: params }
77
+ : { n: procedure }
78
+ )
79
+
80
+ if (this.isFetching) return this.operation?.then((x) => x[current])
81
+ this.isFetching = true
82
+
83
+ this.operation = new Promise((resolve) => {
84
+ setTimeout(async () => {
85
+ const requests = [...this.pendings]
86
+ this.pendings = []
87
+
88
+ const { serialize, deserialize } = await this.sJson
89
+
90
+ const results = await fetch(
91
+ `${this.url}${this.config.fn ?? '/~fn'}`,
92
+ {
93
+ method: 'POST',
94
+ ...this.config.fetch,
95
+ headers: {
96
+ 'content-type': 'elysia/fn',
97
+ ...this.config.fetch?.headers
98
+ },
99
+ body: JSON.stringify(serialize(requests))
100
+ }
101
+ )
102
+
103
+ if (results.status === 200)
104
+ resolve(results.json().then((x) => deserialize(x as any)))
105
+ else
106
+ resolve(
107
+ Array(requests.length).fill(
108
+ new Error(await results.text())
109
+ )
110
+ )
111
+ }, 33)
112
+ })
113
+
114
+ const result = await this.operation.then((results) => results[current])
115
+
116
+ this.operation = null
117
+ this.isFetching = false
118
+
119
+ return result
120
+ }
121
+ }