@mateosuarezdev/brpc 1.0.82 → 1.0.83
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/client/brpc-client-error.d.ts +15 -2
- package/dist/client/index.js +2 -2
- package/dist/client/types.d.ts +6 -1
- package/dist/client/ws-manager.d.ts +12 -4
- package/dist/index.js +4 -4
- package/dist/procedure.d.ts +9 -5
- package/dist/router/router.d.ts +18 -0
- package/dist/types.d.ts +6 -1
- package/package.json +2 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare class BrpcClientError extends Error {
|
|
1
|
+
export declare class BrpcClientError extends Error {
|
|
2
2
|
readonly status: number;
|
|
3
3
|
readonly code?: string;
|
|
4
4
|
readonly clientCode?: string;
|
|
@@ -10,4 +10,17 @@ declare class BrpcClientError extends Error {
|
|
|
10
10
|
isNotFound(): boolean;
|
|
11
11
|
isValidationError(): boolean;
|
|
12
12
|
}
|
|
13
|
-
export
|
|
13
|
+
export type BrpcSubscriptionErrorType = "subscribe_error" | "publish_error";
|
|
14
|
+
/**
|
|
15
|
+
* Represents a normalized server error produced while using a subscription.
|
|
16
|
+
*/
|
|
17
|
+
export declare class BrpcClientSubscriptionError extends BrpcClientError {
|
|
18
|
+
readonly type: BrpcSubscriptionErrorType;
|
|
19
|
+
constructor(type: BrpcSubscriptionErrorType, error: {
|
|
20
|
+
message?: string;
|
|
21
|
+
httpStatus?: number;
|
|
22
|
+
code?: string;
|
|
23
|
+
clientCode?: string;
|
|
24
|
+
data?: any;
|
|
25
|
+
});
|
|
26
|
+
}
|
package/dist/client/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
function g(q){return(J)=>{if(!J)return null;return`${q}/${J}`}}class A extends Error{status;code;clientCode;data;constructor(q,J,O,Q,C){super(q);this.name="BrpcClientError",this.status=J,this.code=O,this.clientCode=Q,this.data=C}isClientError(q){return this.clientCode===q}isUnauthorized(){return this.status===401}isForbidden(){return this.status===403}isNotFound(){return this.status===404}isValidationError(){return this.status===400||this.code==="BAD_REQUEST"}}var U=(q)=>{return q instanceof File||q instanceof Blob||typeof q==="object"&&q!==null&&"uri"in q&&"name"in q&&"type"in q};function x(q){if(!q)return null;return q.replace(/\//g,"")}function v(q){let J=q&&q.length>0?q:typeof window<"u"?window.location.origin:"";return J=J.endsWith("/")?J.slice(0,-1):J,J}function m(q,J){let O=J?`/${J}/ws`:"/ws";return`${q.replace(/^http/,"ws")}${O}`}function y(q,J){if(q&&J)return`/${q}/${J}`;else if(q&&!J)return`/${q}`;else if(!q&&J)return`/${J}`;else return""}class b{baseUrl;ws=null;subscriptions=new Map;messageQueue=[];isConnected=!1;reconnectTimeout=null;WebSocketImpl;getHeaders;authToken=null;debug;pendingAuth=!1;authActions=[];constructor(q,J,O,Q=!1,C){this.baseUrl=q;this.WebSocketImpl=J,this.debug=Q&&C==="development",this.getHeaders=async()=>{if(typeof O==="function")return await O();return O??{}},this.connect()}async connect(){try{let q=await this.getHeaders();this.authToken=q.Authorization||null;let J=this.baseUrl;if(this.debug)console.log(`Connecting to WebSocket: ${J}`);this.ws=new this.WebSocketImpl(J),this.ws.onopen=async()=>{if(this.debug)console.log("WebSocket connection established");if(this.isConnected=!0,this.authActions=[],this.authToken){if(this.pendingAuth=!0,this.send({type:"authenticate",token:this.authToken}),this.debug)console.log("Sent authentication token, waiting for response");this.authActions.push(()=>{for(let O of this.subscriptions.keys())if(this.send({type:"subscribe",topic:O}),this.debug)console.log(`Resubscribed to topic: ${O}`);if(this.messageQueue.length>0&&this.debug)console.log(`Sending ${this.messageQueue.length} queued messages`);this.messageQueue.forEach((O)=>this.send(O)),this.messageQueue=[]})}else{for(let O of this.subscriptions.keys())if(this.send({type:"subscribe",topic:O}),this.debug)console.log(`Resubscribed to topic: ${O}`);if(this.messageQueue.length>0&&this.debug)console.log(`Sending ${this.messageQueue.length} queued messages`);this.messageQueue.forEach((O)=>this.send(O)),this.messageQueue=[]}},this.ws.onmessage=(O)=>{try{if(this.debug)console.log("WebSocket message received:",O.data);let Q=JSON.parse(O.data);if(Q.type==="auth_success"){let C=Q.authenticated!==!1;if(this.debug)console.log(`WebSocket ${C?"authentication":"deauthentication"} successful`);if(this.pendingAuth=!1,C)this.authActions.forEach((B)=>B());this.authActions=[];return}if(Q.type==="auth_error"){console.error("WebSocket authentication failed:",Q.error),this.pendingAuth=!1,this.authActions=[];return}if(this.debug)console.log("Got new message",Q);if(Q.topic&&this.subscriptions.has(Q.topic)){let C=this.subscriptions.get(Q.topic);if(this.debug)console.log("Calling subscription callbacks");C?.forEach((B)=>B(Q.data))}else if(Q.error)console.error("WebSocket error:",Q.error);else console.warn("Unhandled WebSocket message format:",Q)}catch(Q){if(this.debug)console.error("Error processing WebSocket message:",Q),console.error("Raw message:",O.data)}},this.ws.onclose=(O)=>{if(this.isConnected=!1,this.pendingAuth=!1,this.debug)console.log(`WebSocket connection closed. Code: ${O.code}, Reason: ${O.reason}`);this.reconnectTimeout=setTimeout(()=>this.connect(),2000)},this.ws.onerror=(O)=>{if(this.debug)console.log("WebSocket error:",O)}}catch(q){if(this.debug)console.log("Failed to create WebSocket connection:",q);this.pendingAuth=!1,this.reconnectTimeout=setTimeout(()=>this.connect(),2000)}}disconnect(){if(this.reconnectTimeout)clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null;if(this.ws)this.ws.close(),this.ws=null;this.isConnected=!1,this.pendingAuth=!1,this.authActions=[],this.subscriptions.clear()}send(q){if(this.isConnected&&this.ws?.readyState===this.WebSocketImpl.OPEN){if(this.pendingAuth&&q.type!=="authenticate"){if(this.messageQueue.push(q),this.debug)console.log("Message queued until authentication completes:",q);return}let J=JSON.stringify(q);if(this.ws.send(J),this.debug)console.log("WebSocket message sent:",J)}else if(this.messageQueue.push(q),this.debug)console.log("WebSocket message queued (not connected):",q)}subscribe(q,J){if(!this.subscriptions.has(q)){if(this.subscriptions.set(q,new Set),this.isConnected){if(this.pendingAuth)this.authActions.push(()=>{if(this.send({type:"subscribe",topic:q}),this.debug)console.log(`Subscribed to topic after auth: ${q}`)});else if(this.send({type:"subscribe",topic:q}),this.debug)console.log(`Subscribed to topic: ${q}`)}}return this.subscriptions.get(q).add(J),{unsubscribe:()=>this.unsubscribe(q,J),publish:(O)=>this.publish(q,O)}}unsubscribe(q,J){let O=this.subscriptions.get(q);if(O){if(O.delete(J),O.size===0){if(this.subscriptions.delete(q),this.send({type:"unsubscribe",topic:q}),this.debug)console.log(`Unsubscribed from topic: ${q}`)}}}publish(q,J){if(this.send({type:"publish",topic:q,data:J}),this.debug)console.log(`Published to topic: ${q}`,J)}async updateAuth(){let J=(await this.getHeaders()).Authorization||"",O=J.startsWith("Bearer ")?J.substring(7).trim():J.trim(),Q=O.length>0,C=this.authToken!==null&&this.authToken.trim().length>0;if((Q!==C||O!==this.authToken)&&this.isConnected){if(this.authToken=O,this.pendingAuth=Q,this.send({type:"authenticate",token:J}),this.debug)if(Q)console.log("Updated authentication token, waiting for confirmation");else console.log("Sent empty token for deauthentication"),this.pendingAuth=!1,this.authActions=[]}}}function Qq(q,J={}){let O=J.fetch||fetch,Q=J.WebSocket??(typeof WebSocket<"u"?WebSocket:null),C=J.prefix??"",B=J.apiPrefix??"",F=J.debug??!1,k=J.s3Endpoint,c=J.nodeEnv;if(!J.s3Endpoint)throw Error("BRPC Client: Pass s3Endpoint option to createBrpcClient");if(!J.nodeEnv)throw Error("BRPC Client: Pass nodeEnv option to createBrpcClient");if(!Q)throw Error("WebSocket is not available in this environment");let W=J.headers??{},d=async()=>{if(typeof W==="function")return await W();return W},D=v(q),f=x(J.prefix),u=x(J.apiPrefix),E=y(f,u),h=m(D,f),T=new b(h,Q,W,F,c),K=async(X=!0)=>{let M={};if(X)M["Content-Type"]="application/json";let N=await d();return{...M,...N}},l=async(X)=>{if(!X.ok){let N;try{N=await X.json()}catch{try{N={error:await X.text()}}catch{N={error:"Failed to parse error response"}}}if(N?.error&&typeof N.error==="object"){let V=N.error;throw new A(V.message||X.statusText,X.status,V.code,V.clientCode,V.data)}let $=N?.error||N?.message||X.statusText;throw new A($,X.status,void 0,void 0,N)}let M=X.headers.get("Content-Type")||"";try{if(M.includes("application/json"))return(await X.json()).data;else if(M.includes("text/"))return X.text();else return X.blob()}catch(N){throw new A(`Failed to parse response: ${N instanceof Error?N.message:"Unknown error"}`,X.status,"PARSE_ERROR")}},H=async(X,M)=>{try{let N=await O(X,M);return await l(N)}catch(N){if(N instanceof A)throw N;throw new A(N instanceof Error?N.message:"Network request failed",0,"NETWORK_ERROR")}},P=(X=[])=>{return new Proxy({},{get(M,N){if(typeof N==="symbol")return;if(N==="query"||N==="mutation"||N==="formMutation"||N==="subscription"||N==="getStringKey"||N==="getArrayKey"||N==="getNoInputsArrayKey"){let $=X.join("/"),V=`${D}${E}/${$}`;if(F)console.log(`BRPC ${N} procedure path: ${$}`),console.log(`BRPC ${N} full URL: ${V}`);if(N==="query")return async(Z)=>{let j=await K(!0);if(F)console.log(`BRPC query request to ${$}`,{input:Z,headers:j});let _=new URL(V);if(Z&&typeof Z==="object")Object.entries(Z).forEach(([I,z])=>{if(z!==void 0)_.searchParams.append(I,String(z))});let G=await H(_.toString(),{method:"GET",headers:j,credentials:"include"});if(F)console.log(`BRPC query response from ${$}:`,G);return G};else if(N==="mutation")return async(Z)=>{let j=await K(!0);if(F)console.log(`BRPC mutation request to ${$}`,{input:Z,headers:j});let _=JSON.stringify(Z),G=await H(V,{method:"POST",headers:j,body:_,credentials:"include"});if(F)console.log(`BRPC mutation response from ${$}:`,G);return G};else if(N==="formMutation")return async(Z)=>{let j=await K(!1);if(F)console.log(`BRPC formMutation request to ${$}`,{input:Z,headers:j});let _=new FormData,G=(z,Y,L)=>{let R=L?`${L}[${z}]`:z;if(Y===null||Y===void 0)return;else if(U(Y))_.append(R,Y);else if(Array.isArray(Y))Y.forEach((w,S)=>{if(U(w))_.append(R,w);else if(typeof w==="object"&&w!==null)Object.entries(w).forEach(([i,p])=>{G(i,p,`${R}[${S}]`)});else _.append(`${R}[${S}]`,String(w))});else if(typeof Y==="object"&&Y!==null)Object.entries(Y).forEach(([w,S])=>{G(w,S,R)});else _.append(R,String(Y))};Object.entries(Z).forEach(([z,Y])=>{G(z,Y)});let I=await H(V,{method:"POST",headers:j,body:_,credentials:"include"});if(F)console.log(`BRPC formMutation response from ${$}:`,I);return I};else if(N==="subscription")return(Z)=>{if(F)console.log(`BRPC subscription to ${$}`);let j=E?`${E.slice(1)}/${$}`:$;return T.updateAuth(),T.subscribe(j,Z)};else if(N==="getStringKey")return(Z)=>{let j=$;if(!Z||Object.keys(Z).length===0)return j;let _=Object.keys(Z).sort().reduce((I,z)=>{let Y=Z[z];if(Y!==void 0&&Y!==null)I[z]=Y;return I},{});if(Object.keys(_).length===0)return j;let G=JSON.stringify(_);return`${j}?${G}`};else if(N==="getArrayKey")return(Z,j)=>{let _=$,G={...Z??{},...j??{}};if(Object.keys(G).length===0)return[_];let I=[];if(Object.keys(G).sort().forEach((Y)=>{let L=G[Y];if(L!==void 0&&L!==null){let R=L;if(typeof L==="boolean"||typeof L==="number")R=String(L);else if(typeof L==="object")R=JSON.stringify(L);I.push([Y,R])}}),I.length===0)return[_];let z=I.flatMap(([Y,L])=>[Y,L]);return[_,...z]};else if(N==="getNoInputsArrayKey")return()=>{return[$]}}return P([...X,N])}})},n=P();if(F)console.log("BRPC client created",{baseUrl:D,prefix:E,wsUrl:h});return{routes:n,storage:{getObjectUrl:g(k)},utils:{updateWsAuth:async()=>await T.updateAuth(),setHeader:async(X,M)=>{if(typeof W==="function"){console.warn("Cannot use setHeader with function-based headers resolver");return}W={...W,[X]:M},await T.updateAuth()},setHeaders:async(X)=>{if(typeof W==="function"){console.warn("Cannot use setHeaders with function-based headers resolver");return}W={...W,...X},await T.updateAuth()}}}}export{g as createGetObjectUrl,Qq as createBrpcClient,A as BrpcClientError};
|
|
2
|
+
function y(q){return(J)=>{if(!J)return null;return`${q}/${J}`}}class V extends Error{status;code;clientCode;data;constructor(q,J,O,N,G){super(q);this.name="BrpcClientError",this.status=J,this.code=O,this.clientCode=N,this.data=G}isClientError(q){return this.clientCode===q}isUnauthorized(){return this.status===401}isForbidden(){return this.status===403}isNotFound(){return this.status===404}isValidationError(){return this.status===400||this.code==="BAD_REQUEST"}}class U extends V{type;constructor(q,J){super(J.message??"WebSocket request failed",J.httpStatus??0,J.code,J.clientCode,J.data);this.name="BrpcClientSubscriptionError",this.type=q}}var x=(q)=>{return q instanceof File||q instanceof Blob||typeof q==="object"&&q!==null&&"uri"in q&&"name"in q&&"type"in q};function f(q){if(!q)return null;return q.replace(/\//g,"")}function g(q){let J=q&&q.length>0?q:typeof window<"u"?window.location.origin:"";return J=J.endsWith("/")?J.slice(0,-1):J,J}function m(q,J){let O=J?`/${J}/ws`:"/ws";return`${q.replace(/^http/,"ws")}${O}`}function k(q,J){if(q&&J)return`/${q}/${J}`;else if(q&&!J)return`/${q}`;else if(!q&&J)return`/${J}`;else return""}class h{baseUrl;ws=null;subscriptions=new Map;messageQueue=[];isConnected=!1;reconnectTimeout=null;WebSocketImpl;getHeaders;authToken=null;debug;pendingAuth=!1;authActions=[];constructor(q,J,O,N=!1,G){this.baseUrl=q;this.WebSocketImpl=J,this.debug=N&&G==="development",this.getHeaders=async()=>{if(typeof O==="function")return await O();return O??{}},this.connect()}startTopicSubscription(q){let J=this.subscriptions.get(q);if(!J)return;if(J.status="pending",J.auth==="required"&&this.pendingAuth){this.authActions.push(()=>this.startTopicSubscription(q));return}this.send({type:"subscribe",topic:q},{allowDuringPendingAuth:J.auth==="none"})}async connect(){try{let q=await this.getHeaders();this.authToken=q.Authorization||null;let J=this.baseUrl;if(this.debug)console.log(`Connecting to WebSocket: ${J}`);this.ws=new this.WebSocketImpl(J),this.ws.onopen=async()=>{if(this.debug)console.log("WebSocket connection established");if(this.isConnected=!0,this.authActions=[],this.authToken){if(this.pendingAuth=!0,this.send({type:"authenticate",token:this.authToken}),this.debug)console.log("Sent authentication token, waiting for response");for(let O of this.subscriptions.keys())this.startTopicSubscription(O);this.authActions.push(()=>{if(this.messageQueue.length>0&&this.debug)console.log(`Sending ${this.messageQueue.length} queued messages`);this.messageQueue.forEach((O)=>this.send(O)),this.messageQueue=[]})}else{for(let O of this.subscriptions.keys())this.startTopicSubscription(O);if(this.messageQueue.length>0&&this.debug)console.log(`Sending ${this.messageQueue.length} queued messages`);this.messageQueue.forEach((O)=>this.send(O)),this.messageQueue=[]}},this.ws.onmessage=(O)=>{try{if(this.debug)console.log("WebSocket message received:",O.data);let N=JSON.parse(O.data);if(N.type==="auth_success"){let G=N.authenticated!==!1;if(this.debug)console.log(`WebSocket ${G?"authentication":"deauthentication"} successful`);if(this.pendingAuth=!1,G)this.authActions.forEach((T)=>T());this.authActions=[];return}if(N.type==="auth_error"){console.error("WebSocket authentication failed:",this.createClientError(N.error)),this.pendingAuth=!1,this.authActions=[];return}if(this.debug)console.log("Got new message",N);if(N.type==="subscribe_success"&&N.topic&&this.subscriptions.has(N.topic))this.subscriptions.get(N.topic).status="subscribed";else if(N.type==="publish"&&N.topic&&this.subscriptions.has(N.topic)){let{callbacks:G}=this.subscriptions.get(N.topic);if(this.debug)console.log("Calling subscription callbacks");G?.forEach((T)=>T(void 0,N.data))}else if(["subscribe_error","publish_error"].includes(N.type)&&N.topic&&this.subscriptions.has(N.topic)){let G=this.createSubscriptionError(N.type,N.error),T=this.subscriptions.get(N.topic);T.status="failed",T.callbacks.forEach((z)=>z(G))}else if(N.type==="unsubscribe_error")console.error(`WebSocket unsubscribe failed for topic ${N.topic??"unknown"}:`,this.createClientError(N.error));else if(N.error)console.error("WebSocket error:",this.createClientError(N.error));else console.warn("Unhandled WebSocket message format:",N)}catch(N){if(this.debug)console.error("Error processing WebSocket message:",N),console.error("Raw message:",O.data)}},this.ws.onclose=(O)=>{if(this.isConnected=!1,this.pendingAuth=!1,this.subscriptions.forEach((N)=>{N.status="stale"}),this.debug)console.log(`WebSocket connection closed. Code: ${O.code}, Reason: ${O.reason}`);this.reconnectTimeout=setTimeout(()=>this.connect(),2000)},this.ws.onerror=(O)=>{if(this.debug)console.log("WebSocket error:",O)}}catch(q){if(this.debug)console.log("Failed to create WebSocket connection:",q);this.pendingAuth=!1,this.reconnectTimeout=setTimeout(()=>this.connect(),2000)}}createClientError(q){return new V(q?.message??"WebSocket request failed",q?.httpStatus??0,q?.code,q?.clientCode,q?.data)}createSubscriptionError(q,J){return new U(q,J??{})}disconnect(){if(this.reconnectTimeout)clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null;if(this.ws)this.ws.close(),this.ws=null;this.isConnected=!1,this.pendingAuth=!1,this.authActions=[],this.subscriptions.clear()}send(q,J={}){if(this.isConnected&&this.ws?.readyState===this.WebSocketImpl.OPEN){if(this.pendingAuth&&q.type!=="authenticate"&&!J.allowDuringPendingAuth){if(this.messageQueue.push(q),this.debug)console.log("Message queued until authentication completes:",q);return}let O=JSON.stringify(q);if(this.ws.send(O),this.debug)console.log("WebSocket message sent:",O)}else if(this.messageQueue.push(q),this.debug)console.log("WebSocket message queued (not connected):",q)}subscribe(q,J,O={}){let N=this.subscriptions.get(q);if(!N){if(N={auth:O.auth??"required",callbacks:new Set,status:"pending"},this.subscriptions.set(q,N),N.callbacks.add(J),this.isConnected)this.startTopicSubscription(q)}else N.callbacks.add(J);return{unsubscribe:()=>this.unsubscribe(q,J),publish:(G)=>this.publish(q,G,J)}}unsubscribe(q,J){let O=this.subscriptions.get(q);if(O){if(O.callbacks.delete(J),O.callbacks.size===0){if(this.subscriptions.delete(q),this.send({type:"unsubscribe",topic:q}),this.debug)console.log(`Unsubscribed from topic: ${q}`)}}}publish(q,J,O){let N=this.subscriptions.get(q);if(N?.status!=="subscribed"){O(this.createSubscriptionError("publish_error",{message:"Cannot publish to a topic that is not subscribed",code:"SUBSCRIPTION_NOT_SUBSCRIBED",clientCode:"SUBSCRIPTION_NOT_SUBSCRIBED",data:{status:N?.status??"unsubscribed"}}));return}if(this.send({type:"publish",topic:q,data:J}),this.debug)console.log(`Published to topic: ${q}`,J)}async updateAuth(){let J=(await this.getHeaders()).Authorization||"",O=J.startsWith("Bearer ")?J.substring(7).trim():J.trim(),N=O.length>0,G=this.authToken!==null&&this.authToken.trim().length>0;if((N!==G||O!==this.authToken)&&this.isConnected){if(this.authToken=O,this.pendingAuth=N,this.send({type:"authenticate",token:J}),this.debug)if(N)console.log("Updated authentication token, waiting for confirmation");else console.log("Sent empty token for deauthentication"),this.pendingAuth=!1,this.authActions=[]}}}function Yq(q,J={}){let O=J.fetch||fetch,N=J.WebSocket??(typeof WebSocket<"u"?WebSocket:null),G=J.prefix??"",T=J.apiPrefix??"",z=J.debug??!1,c=J.s3Endpoint,d=J.nodeEnv;if(!J.s3Endpoint)throw Error("BRPC Client: Pass s3Endpoint option to createBrpcClient");if(!J.nodeEnv)throw Error("BRPC Client: Pass nodeEnv option to createBrpcClient");if(!N)throw Error("WebSocket is not available in this environment");let W=J.headers??{},u=async()=>{if(typeof W==="function")return await W();return W},K=g(q),P=f(J.prefix),l=f(J.apiPrefix),E=k(P,l),v=m(K,P),B=new h(v,N,W,z,d),S=async(X=!0)=>{let M={};if(X)M["Content-Type"]="application/json";let Q=await u();return{...M,...Q}},n=async(X)=>{if(!X.ok){let Q;try{Q=await X.json()}catch{try{Q={error:await X.text()}}catch{Q={error:"Failed to parse error response"}}}if(Q?.error&&typeof Q.error==="object"){let w=Q.error;throw new V(w.message||X.statusText,X.status,w.code,w.clientCode,w.data)}let $=Q?.error||Q?.message||X.statusText;throw new V($,X.status,void 0,void 0,Q)}let M=X.headers.get("Content-Type")||"";try{if(M.includes("application/json"))return(await X.json()).data;else if(M.includes("text/"))return X.text();else return X.blob()}catch(Q){throw new V(`Failed to parse response: ${Q instanceof Error?Q.message:"Unknown error"}`,X.status,"PARSE_ERROR")}},H=async(X,M)=>{try{let Q=await O(X,M);return await n(Q)}catch(Q){if(Q instanceof V)throw Q;throw new V(Q instanceof Error?Q.message:"Network request failed",0,"NETWORK_ERROR")}},b=(X=[])=>{return new Proxy({},{get(M,Q){if(typeof Q==="symbol")return;if(Q==="query"||Q==="mutation"||Q==="formMutation"||Q==="subscription"||Q==="getStringKey"||Q==="getArrayKey"||Q==="getNoInputsArrayKey"){let $=X.join("/"),w=`${K}${E}/${$}`;if(z)console.log(`BRPC ${Q} procedure path: ${$}`),console.log(`BRPC ${Q} full URL: ${w}`);if(Q==="query")return async(Z)=>{let j=await S(!0);if(z)console.log(`BRPC query request to ${$}`,{input:Z,headers:j});let _=new URL(w);if(Z&&typeof Z==="object")Object.entries(Z).forEach(([C,F])=>{if(F!==void 0)_.searchParams.append(C,String(F))});let L=await H(_.toString(),{method:"GET",headers:j,credentials:"include"});if(z)console.log(`BRPC query response from ${$}:`,L);return L};else if(Q==="mutation")return async(Z)=>{let j=await S(!0);if(z)console.log(`BRPC mutation request to ${$}`,{input:Z,headers:j});let _=JSON.stringify(Z),L=await H(w,{method:"POST",headers:j,body:_,credentials:"include"});if(z)console.log(`BRPC mutation response from ${$}:`,L);return L};else if(Q==="formMutation")return async(Z)=>{let j=await S(!1);if(z)console.log(`BRPC formMutation request to ${$}`,{input:Z,headers:j});let _=new FormData,L=(F,Y,I)=>{let R=I?`${I}[${F}]`:F;if(Y===null||Y===void 0)return;else if(x(Y))_.append(R,Y);else if(Array.isArray(Y))Y.forEach((A,D)=>{if(x(A))_.append(R,A);else if(typeof A==="object"&&A!==null)Object.entries(A).forEach(([i,o])=>{L(i,o,`${R}[${D}]`)});else _.append(`${R}[${D}]`,String(A))});else if(typeof Y==="object"&&Y!==null)Object.entries(Y).forEach(([A,D])=>{L(A,D,R)});else _.append(R,String(Y))};Object.entries(Z).forEach(([F,Y])=>{L(F,Y)});let C=await H(w,{method:"POST",headers:j,body:_,credentials:"include"});if(z)console.log(`BRPC formMutation response from ${$}:`,C);return C};else if(Q==="subscription")return(Z,j)=>{if(z)console.log(`BRPC subscription to ${$}`);let _=E?`${E.slice(1)}/${$}`:$;return B.updateAuth(),B.subscribe(_,Z,j)};else if(Q==="getStringKey")return(Z)=>{let j=$;if(!Z||Object.keys(Z).length===0)return j;let _=Object.keys(Z).sort().reduce((C,F)=>{let Y=Z[F];if(Y!==void 0&&Y!==null)C[F]=Y;return C},{});if(Object.keys(_).length===0)return j;let L=JSON.stringify(_);return`${j}?${L}`};else if(Q==="getArrayKey")return(Z,j)=>{let _=$,L={...Z??{},...j??{}};if(Object.keys(L).length===0)return[_];let C=[];if(Object.keys(L).sort().forEach((Y)=>{let I=L[Y];if(I!==void 0&&I!==null){let R=I;if(typeof I==="boolean"||typeof I==="number")R=String(I);else if(typeof I==="object")R=JSON.stringify(I);C.push([Y,R])}}),C.length===0)return[_];let F=C.flatMap(([Y,I])=>[Y,I]);return[_,...F]};else if(Q==="getNoInputsArrayKey")return()=>{return[$]}}return b([...X,Q])}})},p=b();if(z)console.log("BRPC client created",{baseUrl:K,prefix:E,wsUrl:v});return{routes:p,storage:{getObjectUrl:y(c)},utils:{updateWsAuth:async()=>await B.updateAuth(),setHeader:async(X,M)=>{if(typeof W==="function"){console.warn("Cannot use setHeader with function-based headers resolver");return}W={...W,[X]:M},await B.updateAuth()},setHeaders:async(X)=>{if(typeof W==="function"){console.warn("Cannot use setHeaders with function-based headers resolver");return}W={...W,...X},await B.updateAuth()}}}}export{y as createGetObjectUrl,Yq as createBrpcClient,U as BrpcClientSubscriptionError,V as BrpcClientError};
|
|
3
3
|
|
|
4
|
-
//# debugId=
|
|
4
|
+
//# debugId=0A98DC5F1697239C64756E2164756E21
|
package/dist/client/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { BaseProcedure } from "../types";
|
|
3
|
+
import type { BrpcClientSubscriptionError } from "./brpc-client-error";
|
|
3
4
|
import type { GetObjectUrl } from "./storage";
|
|
4
5
|
export type InferProcedureInput<T> = T extends {
|
|
5
6
|
_input: z.ZodType<infer I>;
|
|
@@ -14,6 +15,10 @@ export type InferRouterOutput<T> = {
|
|
|
14
15
|
[K in keyof T]: T[K] extends BaseProcedure ? InferProcedureOutput<T[K]> : T[K] extends Record<string, any> ? InferRouterOutput<T[K]> : never;
|
|
15
16
|
};
|
|
16
17
|
export type IsOptionalInput<T> = undefined extends InferProcedureInput<T> ? true : {} extends InferProcedureInput<T> & {} ? true : false;
|
|
18
|
+
export type SubscriptionCallback<T> = (error: BrpcClientSubscriptionError | undefined, message?: T) => void;
|
|
19
|
+
export type SubscriptionOptions = {
|
|
20
|
+
auth?: "required" | "none";
|
|
21
|
+
};
|
|
17
22
|
export type SubscriptionHandlers<InputType, _OutputType> = {
|
|
18
23
|
unsubscribe: () => void;
|
|
19
24
|
publish: (data: InputType) => void;
|
|
@@ -49,7 +54,7 @@ export type ProcedureMethods<T extends BaseProcedure> = T["_type"] extends "quer
|
|
|
49
54
|
getArrayKey: (input?: InferProcedureInput<T>, context?: Record<string, string | number | boolean>) => (string | number | boolean)[];
|
|
50
55
|
getNoInputsArrayKey: () => (string | number | boolean)[];
|
|
51
56
|
} : T["_type"] extends "subscription" ? {
|
|
52
|
-
subscription: (callback:
|
|
57
|
+
subscription: (callback: SubscriptionCallback<InferProcedureOutput<T>>, options?: SubscriptionOptions) => SubscriptionHandlers<InferProcedureInput<T>, InferProcedureOutput<T>>;
|
|
53
58
|
getStringKey: (input?: InferProcedureInput<T>) => string;
|
|
54
59
|
getArrayKey: (input?: InferProcedureInput<T>, context?: Record<string, string | number | boolean>) => string[];
|
|
55
60
|
} : never;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HeadersResolver } from "./types";
|
|
1
|
+
import type { HeadersResolver, SubscriptionCallback, SubscriptionOptions } from "./types";
|
|
2
2
|
declare class WebSocketManager {
|
|
3
3
|
private baseUrl;
|
|
4
4
|
private ws;
|
|
@@ -13,15 +13,23 @@ declare class WebSocketManager {
|
|
|
13
13
|
private pendingAuth;
|
|
14
14
|
private authActions;
|
|
15
15
|
constructor(baseUrl: string, WebSocketImpl: typeof WebSocket, headersResolver: HeadersResolver, debug: boolean | undefined, nodeEnv: string);
|
|
16
|
+
/**
|
|
17
|
+
* Sends a topic subscription when its authentication requirement is met.
|
|
18
|
+
*/
|
|
19
|
+
private startTopicSubscription;
|
|
16
20
|
private connect;
|
|
21
|
+
private createClientError;
|
|
22
|
+
private createSubscriptionError;
|
|
17
23
|
disconnect(): void;
|
|
18
|
-
send(message: any
|
|
19
|
-
|
|
24
|
+
send(message: any, options?: {
|
|
25
|
+
allowDuringPendingAuth?: boolean;
|
|
26
|
+
}): void;
|
|
27
|
+
subscribe(topic: string, callback: SubscriptionCallback<any>, options?: SubscriptionOptions): {
|
|
20
28
|
unsubscribe: () => void;
|
|
21
29
|
publish: (data: any) => void;
|
|
22
30
|
};
|
|
23
31
|
private unsubscribe;
|
|
24
|
-
publish(topic: string, data: any): void;
|
|
32
|
+
publish(topic: string, data: any, callback: SubscriptionCallback<any>): void;
|
|
25
33
|
updateAuth(): Promise<void>;
|
|
26
34
|
}
|
|
27
35
|
export { WebSocketManager };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{z as
|
|
2
|
+
import{z as I}from"zod";class Q{middlewares=[];subscribeMiddlewares=[];constructor($=[],F=[]){this.middlewares=$,this.subscribeMiddlewares=F}use($){return new Q([...this.middlewares,$],this.subscribeMiddlewares)}onSubscribe($){return new Q(this.middlewares,[...this.subscribeMiddlewares,$])}input($){return new N(this.middlewares,$,void 0,this.subscribeMiddlewares)}query($){return{_input:I.object({}),_output:null,_ctx:null,_type:"query",handler:$,middlewares:this.middlewares}}mutation($){return{_input:I.object({}),_output:null,_ctx:null,_type:"mutation",handler:$,middlewares:this.middlewares}}formMutation($){return{_input:I.object({}),_output:null,_ctx:null,_type:"formMutation",handler:$,middlewares:this.middlewares}}subscription($){return{_input:I.object({}),_output:null,_ctx:null,_type:"subscription",handler:$,middlewares:this.middlewares,subscribeMiddlewares:this.subscribeMiddlewares}}file($){return{_input:I.object({}),_output:null,_ctx:null,_type:"file",handler:$,middlewares:this.middlewares}}fileStream($){return{_input:I.object({}),_output:null,_ctx:null,_type:"fileStream",handler:$,middlewares:this.middlewares}}html($){return{_input:I.object({}),_output:null,_ctx:null,_type:"html",handler:$,middlewares:this.middlewares}}}class N{middlewares;inputSchema;timeoutMs;subscribeMiddlewares;constructor($,F,J,_=[]){this.middlewares=$;this.inputSchema=F;this.timeoutMs=J;this.subscribeMiddlewares=_}timeout($){return new N(this.middlewares,this.inputSchema,$,this.subscribeMiddlewares)}onSubscribe($){return new N(this.middlewares,this.inputSchema,this.timeoutMs,[...this.subscribeMiddlewares,$])}query($){return{_input:this.inputSchema,_output:null,_ctx:null,_type:"query",_timeout:this.timeoutMs,handler:$,middlewares:this.middlewares}}mutation($){return{_input:this.inputSchema,_output:null,_ctx:null,_type:"mutation",_timeout:this.timeoutMs,handler:$,middlewares:this.middlewares}}formMutation($){return{_input:this.inputSchema,_output:null,_ctx:null,_type:"formMutation",_timeout:this.timeoutMs,handler:$,middlewares:this.middlewares}}subscription($){return{_input:this.inputSchema,_output:null,_ctx:null,_type:"subscription",_timeout:this.timeoutMs,handler:$,middlewares:this.middlewares,subscribeMiddlewares:this.subscribeMiddlewares}}file($){return{_input:this.inputSchema,_output:null,_ctx:null,_type:"file",_timeout:this.timeoutMs,handler:$,middlewares:this.middlewares}}fileStream($){return{_input:this.inputSchema,_output:null,_ctx:null,_type:"fileStream",_timeout:this.timeoutMs,handler:$,middlewares:this.middlewares}}html($){return{_input:I.object({}),_output:null,_ctx:null,_type:"html",_timeout:this.timeoutMs,handler:$,middlewares:this.middlewares}}}function k($){return new Q}class D extends Error{code;clientCode;httpStatus;data;cause;static STATUS_MAP={BAD_REQUEST:400,UNAUTHORIZED:401,FORBIDDEN:403,NOT_FOUND:404,METHOD_NOT_SUPPORTED:405,TIMEOUT:408,CONFLICT:409,PRECONDITION_FAILED:412,PAYLOAD_TOO_LARGE:413,UNPROCESSABLE_CONTENT:422,TOO_MANY_REQUESTS:429,CLIENT_CLOSED_REQUEST:499,INTERNAL_SERVER_ERROR:500,NOT_IMPLEMENTED:501,BAD_GATEWAY:502,SERVICE_UNAVAILABLE:503,GATEWAY_TIMEOUT:504};constructor($){super($.message);if(this.name="BRPCError",this.code=$.code,this.clientCode=$.clientCode,this.httpStatus=D.STATUS_MAP[$.code],this.data=$.data,this.cause=$.cause,Error.captureStackTrace)Error.captureStackTrace(this,D)}toJSON(){return{name:this.name,code:this.code,clientCode:this.clientCode,message:this.message,data:this.data,httpStatus:this.httpStatus}}static badRequest($,F,J){return new D({code:"BAD_REQUEST",message:$,clientCode:F,data:J})}static unauthorized($="Unauthorized",F,J){return new D({code:"UNAUTHORIZED",message:$,clientCode:F,data:J})}static forbidden($="Forbidden",F,J){return new D({code:"FORBIDDEN",message:$,clientCode:F,data:J})}static notFound($="Not Found",F,J){return new D({code:"NOT_FOUND",message:$,clientCode:F,data:J})}static preconditionFailed($="Precondition failed",F,J){//! Intentionally maps to NOT_FOUND instead of PRECONDITION_FAILED — this was changed to fix a bug
|
|
3
3
|
//! but the exact cause is unknown. Suspected: some client/proxy/middleware misbehaved on 412
|
|
4
4
|
//! (not a browser auto-retry issue — 412 doesn't trigger those). Investigate before changing back.
|
|
5
|
-
return new D({code:"NOT_FOUND",message:$,clientCode:F,data:J})}static conflict($,F,J){return new D({code:"CONFLICT",message:$,clientCode:F,data:J})}static unprocessableContent($,F,J){return new D({code:"UNPROCESSABLE_CONTENT",message:$,clientCode:F,data:J})}static tooManyRequests($="Too many requests",F,J){return new D({code:"TOO_MANY_REQUESTS",message:$,clientCode:F,data:J})}static internalServerError($="Internal Server Error",F,J){return new D({code:"INTERNAL_SERVER_ERROR",message:$,clientCode:F,data:J})}static timeout($="Request timeout",F,J){return new D({code:"TIMEOUT",message:$,clientCode:F,data:J})}}class
|
|
5
|
+
return new D({code:"NOT_FOUND",message:$,clientCode:F,data:J})}static conflict($,F,J){return new D({code:"CONFLICT",message:$,clientCode:F,data:J})}static unprocessableContent($,F,J){return new D({code:"UNPROCESSABLE_CONTENT",message:$,clientCode:F,data:J})}static tooManyRequests($="Too many requests",F,J){return new D({code:"TOO_MANY_REQUESTS",message:$,clientCode:F,data:J})}static internalServerError($="Internal Server Error",F,J){return new D({code:"INTERNAL_SERVER_ERROR",message:$,clientCode:F,data:J})}static timeout($="Request timeout",F,J){return new D({code:"TIMEOUT",message:$,clientCode:F,data:J})}}class M{routeTree;routeCache;options;routeStats;constructor($,F={}){if(this.options={enableCaching:!0,maxCacheSize:1000,debug:!1,...F},this.routeCache=new Map,this.routeStats=this.analyzeRoutes($),this.routeTree=this.compileRoutes($),this.options.debug)console.log("[ROUTEMATCHER]","initialized with stats:",this.routeStats)}match($){if(this.options.enableCaching)return this.matchWithCache($);return this.matchDirect($)}matchWithCache($){let F=$.join("/");if(this.routeCache.has(F)){let _=this.routeCache.get(F);if(this.options.debug&&_)console.log("[ROUTEMATCHER]",`\uD83C\uDFAF Cache hit for route: /${F}`);return _}let J=this.matchDirect($);if(this.cacheResult(F,J),this.options.debug)console.log("[ROUTEMATCHER]",`\uD83D\uDCBE Cached route: /${F} -> ${J?"found":"not found"}`);return J}matchDirect($){let F=this.options.debug?performance.now():0,J=this.traverseTree(this.routeTree,$,0,{});if(this.options.debug){let _=performance.now()-F,W=J?.route._type||"none";console.log("[ROUTEMATCHER]",`\u26A1 Route match: /${$.join("/")} (${W}) took ${_.toFixed(3)}ms`)}return J}traverseTree($,F,J,_){if(J>=F.length){if($.handler)return{params:_,route:$.handler.handler};if($.index)return{params:_,route:$.index.handler};//! This was the last change I made trying to fix
|
|
6
6
|
//! Wildacrd not matching root (/) when index wasn't defined
|
|
7
|
-
if($.wildcard&&F.length===0)return{params:{..._,wildcardPath:""},route:$.wildcard.handler};return null}let W=F[J],X=decodeURIComponent(W),Y=$.static.get(X);if(Y){let j=this.traverseTree(Y,F,J+1,_);if(j)return j}if($.param){let j={..._,[$.param.name]:X},A=this.traverseTree($.param.node,F,J+1,j);if(A)return A}if($.wildcard){let j=F.slice(J).join("/");return{params:{..._,wildcardPath:j},route:$.wildcard.handler}}return null}compileRoutes($,F=[]){let J={static:new Map};for(let[_,W]of Object.entries($)){if(_==="index"&&this.isProcedure(W)){J.index={handler:W,paramNames:[...F],hasWildcard:!1};continue}if(_==="*"){if(this.isProcedure(W))J.wildcard={handler:W,paramNames:[...F,"wildcardPath"],hasWildcard:!0,wildcardIndex:F.length};continue}if(this.isProcedure(W)){let X={handler:W,paramNames:[...F],hasWildcard:!1};if(_.startsWith(":")){let Y=_.slice(1),j={static:new Map,handler:X};J.param={name:Y,node:j}}else{let Y={static:new Map,handler:X};J.static.set(_,Y)}}else if(typeof W==="object"&&W!==null)if(_.startsWith(":")){let X=_.slice(1),Y=this.compileRoutes(W,[...F,X]);J.param={name:X,node:Y}}else{let X=this.compileRoutes(W,F);J.static.set(_,X)}}return J}isProcedure($){return $&&typeof $==="object"&&"_type"in $&&"_input"in $}cacheResult($,F){if(this.routeCache.size>=this.options.maxCacheSize){let J=this.routeCache.keys().next().value;if(J!==void 0){if(this.routeCache.delete(J),this.options.debug)console.log(`\uD83D\uDDD1\uFE0F Evicted oldest cache entry: ${J}`)}}this.routeCache.set($,F)}analyzeRoutes($){let F={totalRoutes:0,staticRoutes:0,paramRoutes:0,wildcardRoutes:0,indexRoutes:0,maxDepth:0,procedureTypes:{query:0,mutation:0,subscription:0,file:0,fileStream:0,streamQuery:0,streamMutation:0,formMutation:0,html:0}},J=(_,W=0)=>{F.maxDepth=Math.max(F.maxDepth,W);for(let[X,Y]of Object.entries(_))if(this.isProcedure(Y)){if(F.totalRoutes++,Y._type in F.procedureTypes)F.procedureTypes[Y._type]++;if(X==="index")F.indexRoutes++;else if(X.startsWith(":"))F.paramRoutes++;else if(X==="*")F.wildcardRoutes++;else F.staticRoutes++}else if(typeof Y==="object"&&Y!==null)J(Y,W+1)};return J($),F}clearCache(){if(this.routeCache.clear(),this.options.debug)console.log("[ROUTEMATCHER]","\uD83E\uDDF9 Route cache cleared")}getCacheStats(){return{size:this.routeCache.size,maxSize:this.options.maxCacheSize,routeStats:this.routeStats}}getOptimizationSuggestions(){let $=[],{routeStats:F}=this;if(F.paramRoutes>F.staticRoutes*2)$.push("\uD83D\uDE80 Consider grouping routes to reduce parameter route overhead");if(F.maxDepth>6)$.push("\uD83D\uDCCA Deep route nesting detected - consider flattening some routes");if(F.totalRoutes>200)$.push("\u26A1 Large route table - consider route splitting or lazy loading");if(!this.options.enableCaching&&F.totalRoutes>50)$.push("\uD83D\uDCBE Enable caching for better performance with many routes");if(F.procedureTypes.file>20)$.push("\uD83D\uDCC1 Many file routes detected - consider static file serving");if(F.procedureTypes.subscription>10)$.push("\uD83D\uDD04 Many subscription routes - ensure WebSocket optimization");return $}findRoutesByPattern($){return[]}getRouteInfo($){let F=$.split("/").filter(Boolean),J=F.join("/"),_=this.routeCache.has(J),W=performance.now(),X=this.matchDirect(F),Y=performance.now()-W;return{found:!!X,route:X?.route,params:X?.params,cached:_,matchTime:Y}}benchmark($,F=1000){let J=$.map((G)=>G.split("/").filter(Boolean)),_=new Map(this.routeCache);this.routeCache.clear();let W=performance.now();for(let G=0;G<F;G++)for(let I of J)this.matchDirect(I);let Y=performance.now()-W,j=F*$.length,A=Y/j,L=j/Y*1000;return this.routeCache=_,{averageTime:A,totalTime:Y,routesPerSecond:L}}warmupCache($){if(!this.options.enableCaching)return;let F=performance.now(),J=0;for(let W of $){let X=W.split("/").filter(Boolean),Y=this.matchDirect(X);if(Y)this.cacheResult(X.join("/"),Y),J++}let _=performance.now();if(this.options.debug)console.log("[ROUTEMATCHER]",`\uD83D\uDD25 Cache warmed up: ${J} routes in ${(_-F).toFixed(2)}ms`)}}var C=import.meta.require,x={NO_CACHE:{cacheControl:"no-cache, no-store, must-revalidate",expires:new Date(0).toUTCString()},ONE_YEAR:{cacheControl:"public, max-age=31536000",expires:new Date(Date.now()+31536000000).toUTCString()},IMMUTABLE:{cacheControl:"public, max-age=31536000, immutable",expires:new Date(Date.now()+31536000000).toUTCString()},ONE_MONTH:{cacheControl:"public, max-age=2592000"},ONE_WEEK:{cacheControl:"public, max-age=604800"},ONE_DAY:{cacheControl:"public, max-age=86400, must-revalidate"},ONE_HOUR:{cacheControl:"public, max-age=3600, must-revalidate"},FIVE_MINUTES:{cacheControl:"public, max-age=300"},API:{cacheControl:"public, max-age=300, s-maxage=3600"},STYLESHEET:{cacheControl:"public, max-age=2592000, must-revalidate"},JAVASCRIPT:{cacheControl:"public, max-age=2592000, must-revalidate"},HASHED_ASSET:{cacheControl:"public, max-age=31536000, immutable",expires:new Date(Date.now()+31536000000).toUTCString()},FONT:{cacheControl:"public, max-age=31536000",expires:new Date(Date.now()+31536000000).toUTCString()},IMAGE:{cacheControl:"public, max-age=2592000"},FAVICON:{cacheControl:"public, max-age=31536000",expires:new Date(Date.now()+31536000000).toUTCString()},HTML_PAGE:{cacheControl:"public, max-age=3600, must-revalidate"},API_RESPONSE:{cacheControl:"public, max-age=300, s-maxage=900"},FEED:{cacheControl:"public, max-age=3600"},SITEMAP:{cacheControl:"public, max-age=86400"},MANIFEST:{cacheControl:"public, max-age=86400, must-revalidate"},SERVICE_WORKER:{cacheControl:"no-cache, no-store, must-revalidate",expires:new Date(0).toUTCString()},MEDIA_STREAM:{cacheControl:"no-cache, no-store"},DOCUMENT:{cacheControl:"public, max-age=86400"},ARCHIVE:{cacheControl:"public, max-age=604800"}};function q($){return x[$]}var v={html:"text/html; charset=utf-8",css:"text/css",javascript:"application/javascript",json:"application/json",xml:"application/xml",text:"text/plain; charset=utf-8",csv:"text/csv",markdown:"text/markdown; charset=utf-8",png:"image/png",jpeg:"image/jpeg",gif:"image/gif",svg:"image/svg+xml",webp:"image/webp",icon:"image/x-icon",bmp:"image/bmp",woff:"font/woff",woff2:"font/woff2",truetype:"font/ttf","embedded-opentype":"application/vnd.ms-fontobject",opentype:"font/otf",mp4:"video/mp4",webm:"video/webm",ogg:"video/ogg",mpeg:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",zip:"application/zip",tar:"application/x-tar",gzip:"application/gzip","7z":"application/x-7z-compressed",js:"application/javascript",txt:"text/plain; charset=utf-8",jpg:"image/jpeg",ico:"image/x-icon",ttf:"font/ttf",eot:"application/vnd.ms-fontobject",otf:"font/otf",mp3:"audio/mpeg",gz:"application/gzip"};function K($){return v[$]||"application/octet-stream"}function y($){if(typeof Bun<"u"&&Bun.hash)return Bun.hash($).toString(16);try{let F=C("crypto"),J;if(typeof $==="string")J=Buffer.from($);else if($ instanceof ArrayBuffer)J=Buffer.from(new Uint8Array($));else J=Buffer.from($);return F.createHash("md5").update(J).digest("hex")}catch{let F=typeof $==="string"?new TextEncoder().encode($):$ instanceof ArrayBuffer?new Uint8Array($):$,J=0;for(let _=0;_<F.length;_++)J=(J<<5)-J+F[_],J=J&J;return Math.abs(J).toString(16).padStart(16,"0")}}function V($,F,J){let _=$.headers.get("Origin"),W=!0;if((J?.allowAll??!0)&&_)return _;if(F.length===0)return _||"*";if(!_)return F[0];if(F.includes(_))return _;return F[0]}class S{headers={};contentType($){return this.headers["Content-Type"]=K($),this}filePath($){let F=$.split(".").pop()?.toLowerCase()||"";return this.headers["Content-Type"]=K(F),this}mimeType($){return this.headers["Content-Type"]=$,this}cache($){let{cacheControl:F,expires:J}=q($);if(this.headers["Cache-Control"]=F,J)this.headers.Expires=J;return this}eTag($){if(!$)return this;let F;if(typeof $==="string"&&$.startsWith('"'))F=$;else F=`"${y($)}"`;return this.headers.ETag=F,this}lastModified($){return this.headers["Last-Modified"]=$.toUTCString(),this}contentLength($){return this.headers["Content-Length"]=$.toString(),this}redirect($,F=!1){return this.headers.Location=$,this.headers["X-Redirect-Type"]=F?"permanent":"temporary",this}cors({origin:$="*",methods:F=["GET","POST","PUT","DELETE","OPTIONS"],headers:J=["Content-Type","Authorization"],maxAge:_=86400,credentials:W}={}){let X=W??$!=="*";if(X&&$==="*")throw Error('CORS: Cannot use credentials with wildcard origin "*". Specify an exact origin or set credentials to false.');if(this.headers["Access-Control-Allow-Origin"]=$,this.headers["Access-Control-Allow-Methods"]=F.join(", "),this.headers["Access-Control-Allow-Headers"]=J.join(", "),X)this.headers["Access-Control-Allow-Credentials"]="true";return this.headers["Access-Control-Max-Age"]=_.toString(),this}security($){let F={hsts:!0,noSniff:!0,frameOptions:"SAMEORIGIN",xssProtection:!0,...$};if(F.csp)this.headers["Content-Security-Policy"]=F.csp;if(F.hsts){let J=typeof F.hsts==="number"?F.hsts:31536000;this.headers["Strict-Transport-Security"]=`max-age=${J}; includeSubDomains`}if(F.noSniff)this.headers["X-Content-Type-Options"]="nosniff";if(F.frameOptions)this.headers["X-Frame-Options"]=F.frameOptions;if(F.xssProtection)this.headers["X-XSS-Protection"]="1; mode=block";return this}custom($,F){return this.headers[$]=F,this}customHeaders($){return Object.assign(this.headers,$),this}vary(...$){let F=this.headers.Vary,J=F?`${F}, ${$.join(", ")}`:$.join(", ");return this.headers.Vary=J,this}compress($){if($)this.headers["Content-Encoding"]=$;return this}build($){let F={...this.headers};if($&&!F["Content-Length"]){let J=typeof $==="string"?new TextEncoder().encode($).length:$.byteLength;F["Content-Length"]=J.toString()}return F}buildHeaders($){return new Headers(this.build($))}}function Z(){return new S}function l($,F){let J=Z().contentType($);if(F)J.cache(F);return J.build()}var c={css:($)=>Z().contentType("css").cache("STYLESHEET").eTag($),javascript:($)=>Z().contentType("javascript").cache("JAVASCRIPT").eTag($),hashedAsset:($,F)=>Z().filePath($).cache("HASHED_ASSET").eTag(F),font:($)=>Z().filePath($).cache("FONT").cors({origin:"*"}),image:($)=>Z().filePath($).cache("IMAGE"),favicon:()=>Z().contentType("icon").cache("FAVICON"),api:()=>Z().contentType("json").cache("API_RESPONSE").cors(),expensiveApi:($)=>Z().contentType("json").cache("API_RESPONSE").cors().eTag($),realtime:()=>Z().contentType("json").cache("NO_CACHE").cors(),html:()=>Z().contentType("html").cache("HTML_PAGE").security(),expensiveHtml:($)=>Z().contentType("html").cache("HTML_PAGE").security().eTag($),manifest:($)=>Z().contentType("json").cache("MANIFEST").eTag($),serviceWorker:()=>Z().contentType("javascript").cache("SERVICE_WORKER"),sitemap:($)=>Z().contentType("xml").cache("SITEMAP").eTag($),feed:($,F="rss")=>Z().contentType("xml").cache("FEED").eTag($).mimeType(F==="rss"?"application/rss+xml":"application/atom+xml"),video:($)=>Z().filePath($).cache("MEDIA_STREAM").custom("Accept-Ranges","bytes"),audio:($)=>Z().filePath($).cache("MEDIA_STREAM").custom("Accept-Ranges","bytes"),pdf:($)=>Z().contentType("pdf").cache("DOCUMENT").custom("Content-Disposition","inline").eTag($),download:($,F,J)=>Z().filePath(F).cache("ARCHIVE").custom("Content-Disposition",`attachment; filename="${$}"`).eTag(J),secure:()=>Z().security({csp:"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'",hsts:31536000}).custom("X-Powered-By","Bun"),permanentRedirect:($)=>Z().redirect($,!0),temporaryRedirect:($)=>Z().redirect($,!1)};async function B($,F,J,_={}){let{maxChunkSize:W=2097152,cacheMaxAge:X=3600,acceptedExtensions:Y}=_;if(!await $.exists())return new Response("Media file not found",{status:404,headers:Z().contentType("text").cors({origin:V(F,J)}).build()});if(Y&&Y.length>0){let b=$.name?.toLowerCase().split(".").pop();if(!b||!Y.includes(b))return new Response("File type not supported",{status:415,headers:Z().contentType("text").cors({origin:V(F,J)}).build()})}let A=$.size,L=F.headers.get("Range");if(!L)return new Response($,{headers:Z().filePath($.name||"media").custom("Accept-Ranges","bytes").custom("Cache-Control",`public, max-age=${X}`).cors({origin:V(F,J)}).build()});let G=L.match(/bytes=(\d*)-(\d*)/);if(!G)return new Response("Invalid Range header",{status:416,headers:Z().contentType("text").cors({origin:V(F,J)}).custom("Content-Range",`bytes */${A}`).build()});let I=G[1]?parseInt(G[1]):0,R=G[2]?parseInt(G[2]):A-1,N=Math.min(R,I+W-1,A-1);if(I>=A||N>=A||I>N)return new Response("Range not satisfiable",{status:416,headers:Z().contentType("text").cors({origin:V(F,J)}).custom("Content-Range",`bytes */${A}`).build()});return new Response($.slice(I,N+1),{status:206,headers:Z().filePath($.name||"media").custom("Accept-Ranges","bytes").custom("Cache-Control",`public, max-age=${X}`).cors({origin:V(F,J)}).build()})}function f($,F,J){return{type:"stream",file:$,request:F,options:J}}class T{routes;routeMatcher;contextCreator;prefix;globalMiddlewares;server=null;wsConfig;integrations;development=!0;debug=!1;allowedOrigins=[];onError;activeConnections=new Set;maxConnections=parseInt(process.env.MAX_WS_CONNECTIONS??"1000");connectionTimeout=parseInt(process.env.WS_TIMEOUT??"30000");maxRequestSize=parseInt(process.env.MAX_REQUEST_SIZE??"10485760");requestTimeout=parseInt(process.env.REQUEST_TIMEOUT??"30000");constructor($){if(this.contextCreator=$.context,this.prefix=$.prefix??"",this.routes=$.routes,this.globalMiddlewares=$.globalMiddlewares??[],this.wsConfig=$.websocket??{},this.integrations=$.integrations,this.debug=!!$.debug&&!0,this.allowedOrigins=$.allowedOrigins??[],this.onError=$.onError,this.routeMatcher=new Q($.routes,{enableCaching:!this.development,maxCacheSize:parseInt(process.env.ROUTE_CACHE_SIZE??"1000"),debug:this.debug}),!this.development)setInterval(()=>this.cleanupStaleConnections(),60000);if(this.debug)console.log("Router initialized with route matcher"),console.log("Route cache stats:",this.routeMatcher.getCacheStats())}findRoute($){return this.routeMatcher.match($)}async parseInput($,F,J){let _={},W=new URL($.url),X=Object.fromEntries(W.searchParams),Y=$.method;if(Y==="POST"){if(parseInt($.headers.get("content-length")||"0")>this.maxRequestSize)throw Error("Request payload too large")}if(Y==="GET"&&["query","streamQuery","file","fileStream","html"].includes(J))_=X;else if(Y==="POST"&&["mutation","formMutation","streamMutation"].includes(J)){let j=$.headers.get("content-type");if(j?.includes("application/json"))_=await $.json();else if(j?.includes("multipart/form-data")){let A=await $.formData();_=await this.parseFormData(A)}else throw new D({code:"BAD_REQUEST",message:"Unsupported content type"})}else throw new D({code:"METHOD_NOT_SUPPORTED",message:"Unsupported request method"});return F.parse(_)}async parseFormData($){let F={},J={};$.forEach(async(_,W)=>{if(_ instanceof File){if(!J[W])J[W]=[];J[W].push(_)}else if(typeof _==="string")try{F[W]=JSON.parse(_)}catch{F[W]=_}});for(let[_,W]of Object.entries(J))F[_]=W;return F}handleError($,F){if(this.onError)try{this.onError($,{req:F,route:F.url})}catch(_){console.error("Error logger failed:",_)}let J=$?.issues??$?.errors;if(Array.isArray(J)&&$.name==="ZodError")return new Response(JSON.stringify({error:{name:"ValidationError",code:"BAD_REQUEST",clientCode:"VALIDATION_ERROR",message:"Input validation failed",data:{validationErrors:J.map((_)=>({path:_.path.join("."),message:_.message,code:_.code,received:_.received}))},httpStatus:400}}),{status:400,headers:Z().contentType("json").cors({origin:V(F,this.allowedOrigins)}).build()});if($ instanceof D)return new Response(JSON.stringify({error:$.toJSON()}),{status:$.httpStatus,headers:Z().contentType("json").cors({origin:V(F,this.allowedOrigins)}).build()});if($ instanceof Error){let _=$.message==="Not Found"?404:$.message==="Unauthorized"?401:$.message==="Forbidden"?403:$.message==="Request payload too large"?413:$.message==="Request timeout"?504:500,W=_===404?"NOT_FOUND":_===401?"UNAUTHORIZED":_===403?"FORBIDDEN":_===413?"PAYLOAD_TOO_LARGE":_===504?"GATEWAY_TIMEOUT":"INTERNAL_SERVER_ERROR",X=_<500?$.message:"Internal Server Error";return new Response(JSON.stringify({error:{code:W,message:X}}),{status:_,headers:Z().contentType("json").cors({origin:V(F,this.allowedOrigins)}).build()})}return new Response(JSON.stringify({error:{code:"INTERNAL_SERVER_ERROR",message:"Internal Server Error"}}),{status:500,headers:Z().contentType("json").cors({origin:V(F,this.allowedOrigins)}).build()})}handleFileCacheControl($){let F;if($?.includes("text/html"))F="public, max-age=0, must-revalidate";else if($?.includes("text/css")||$?.includes("text/javascript")||$?.includes("application/javascript"))F="public, max-age=31536000, immutable";else F="public, max-age=86400, stale-while-revalidate=604800";return F}async handleRequest($,F){try{if($.method==="OPTIONS")return new Response(null,{status:204,headers:Z().cors({origin:V($,this.allowedOrigins)}).build()});let _=new URL($.url).pathname;if(_=_.startsWith(this.prefix)?_.slice(this.prefix.length):_,_=_.startsWith("/")?_:"/"+_,_==="/ws"&&F){if(this.debug)console.log("\uD83D\uDD0C WebSocket upgrade requested");return this.handleWebSocketUpgrade($,F)}let W=_==="/"?[]:_.split("/").filter(Boolean),X=this.findRoute(W);if(!X)return new Response("Not Found",{status:404,headers:Z().contentType("text").cors({origin:V($,this.allowedOrigins)}).build()});let{route:Y}=X,j=Y._timeout??this.requestTimeout,A=new Promise((L,G)=>{setTimeout(()=>G(Error("Request timeout")),j)});return await Promise.race([this._handleRequest($,F,X),A])}catch(J){return this.handleError(J,$)}}async _handleRequest($,F,J){try{if(!J)return new Response("Not Found",{status:404,headers:Z().contentType("text").cors({origin:V($,this.allowedOrigins)}).build()});let{params:_,route:W}=J;if(!W.handler)return new Response("Handler not implemented",{status:501,headers:Z().contentType("text").cors({origin:V($,this.allowedOrigins)}).build()});let X=await this.contextCreator($);Object.assign(X,{req:$,params:_,publishToProcedure:async(L,G,I)=>{await this.publishToProcedure(L,G,X,I)}});for(let L of this.globalMiddlewares){let G=await L(X);if(G!==null&&G!==void 0&&typeof G==="object")Object.assign(X,G)}for(let L of W.middlewares){let G=await L(X);if(G!==null&&G!==void 0&&typeof G==="object")Object.assign(X,G)}let Y=await this.parseInput($,W._input,W._type),j=await W.handler({ctx:X,input:Y}),A=Z().cors({origin:V($,X.allowedOrigins??this.allowedOrigins)}).build();return Object.keys(A).forEach((L)=>{X.headers.set(L,A[L])}),await this.handleProcedureResponse(W._type,j,X.headers)}catch(_){return this.handleError(_,$)}}async testRequest($){return this.handleRequest($)}async handleProcedureResponse($,F,J){switch($){case"file":if(F instanceof Response)return F;let _=F.type;return J.append("Content-Type",_),J.append("Cache-Control",this.development?"public, max-age=0, must-revalidate":this.handleFileCacheControl(_)),new Response(F,{headers:J});case"fileStream":if(typeof F==="object"&&F!==null&&"type"in F&&F.type==="stream"){let X=F;return await B(F.file,F.request,this.allowedOrigins,X.options)}let W=F.type;return J.append("Content-Type",W),J.append("Cache-Control",this.development?"public, max-age=0, must-revalidate":this.handleFileCacheControl(W)),new Response(F,{headers:J});case"html":if(typeof F==="string")return J.append("Content-Type","text/html"),new Response(F,{headers:J});break;case"query":case"mutation":case"formMutation":case"streamQuery":case"streamMutation":case"subscription":default:return J.append("Content-Type","application/json"),new Response(JSON.stringify({data:F}),{headers:J})}return J.append("Content-Type","application/json"),new Response(JSON.stringify({data:F}),{headers:J})}cleanupStaleConnections(){let $=Date.now();for(let F of this.activeConnections)if($-F.data.lastActivity>this.connectionTimeout)F.close(),this.activeConnections.delete(F)}async handleWebSocketUpgrade($,F){if(this.activeConnections.size>=this.maxConnections)return new Response("Too many connections",{status:503});let J=await this.contextCreator($);Object.assign(J,{req:$});for(let W of this.globalMiddlewares){let X=await W(J);if(X!==null&&X!==void 0&&typeof X==="object")Object.assign(J,X)}if(F.upgrade($,{data:{ctx:J,topics:new Set,lastActivity:Date.now()}}))return new Response(null,{status:101});return new Response("WebSocket upgrade failed",{status:500})}async handleAuthenticate($,F,J){try{let _=F.startsWith("Bearer ")?F.substring(7):F;if($.data.ctx.authToken=_,$.send(JSON.stringify({type:"auth_success",message:"Authentication successful"})),this.debug)console.log("[BRPC]","WebSocket authenticated successfully")}catch(_){if($.send(JSON.stringify({type:"auth_error",error:_ instanceof Error?_.message:"Authentication failed"})),this.debug)console.error("WebSocket authentication error:",_)}}async handleSubscribe($,F,J){let _=this.findRoute(F.split("/").filter(Boolean));if(!_||_.route._type!=="subscription")throw Error("Invalid subscription route");$.subscribe(F),$.data.topics.add(F)}async handleUnsubscribe($,F,J){let _=this.findRoute(F.split("/").filter(Boolean));if(!_||_.route._type!=="subscription")throw Error("Invalid subscription route");$.unsubscribe(F),$.data.topics.delete(F)}async handlePublish($,F,J,_){let W=this.findRoute(F.split("/").filter(Boolean));if(!W)throw Error("Subscription not found");let{params:X,route:Y}=W;if(Y._type!=="subscription"||!Y.handler)throw Error("Invalid subscription route");Object.assign(_,{params:X});let j=Y._input.fields?Y._input.parse(J):J,A=await Y.handler({ctx:_,input:j}),L={topic:F,data:A,type:"publish"};if(this.debug)console.log(`Server will publish to ${F}`,L);this.server.publish(F,JSON.stringify(L))}async publishToProcedure($,F,J,_){if($._type!=="subscription"||!$.handler)throw Error("Can only publish to subscription procedures");let W=this.findRoutePath(this.routes,$);if(!W)throw Error("Subscription route not found");let X=W.split("/").map((A)=>{if(A.startsWith(":")){let L=A.slice(1),G=_[L];if(!G)throw Error(`Missing parameter: ${L}`);return G}return A}).join("/"),Y=await $.handler({ctx:{...J,params:_},input:F}),j={topic:X,data:Y,type:"publish"};if(this.debug)console.log(`Server will publish to ${X}`,j);return this.server?.publish(X,JSON.stringify(j)),Y}async publish($,F,J){if($._type!=="subscription"||!$.handler)throw Error("Can only publish to subscription procedures");let _=this.findRoutePath(this.routes,$);if(!_)throw Error("Subscription route not found");let W=_.split("/").map((j)=>{if(j.startsWith(":")){let A=j.slice(1),L=J?J[A]:null;if(!L)throw Error(`Missing parameter: ${A}`);return L}return j}).join("/"),X=await $.handler({ctx:{params:J},input:F}),Y={topic:W,data:X,type:"publish"};if(this.debug)console.log(`Server will publish to ${W}`,Y);return this.server?.publish(W,JSON.stringify(Y)),X}freePublish($,F){let J={topic:$,data:F,type:"publish"};this.server?.publish($,JSON.stringify(J))}findRoutePath($,F,J=""){for(let[_,W]of Object.entries($)){let X=J?`${J}/${_}`:_;if(W===F)return X;if(W&&typeof W==="object"&&!("_type"in W)){let Y=this.findRoutePath(W,F,X);if(Y)return Y}}return null}getIntegrationRoutes(){if(!this.integrations)return;let $={};if(this.integrations.betterAuth){let F=this.integrations.betterAuth.handler;if(!F)throw Error("Please provide a handler for betterAuth integration");$["/api/auth/*"]=async(J)=>{return await F(J)}}if(this.integrations.rawRoutes)Object.keys(this.integrations.rawRoutes).forEach((F)=>{$[F]=this.integrations.rawRoutes[F]});return Object.keys($).length>0?$:void 0}getRouteStats(){return{cache:this.routeMatcher.getCacheStats(),suggestions:this.routeMatcher.getOptimizationSuggestions()}}clearRouteCache(){if(this.routeMatcher.clearCache(),this.debug)console.log("[ROUTEMATCHER] Route cache cleared")}benchmarkRoutes($=["/users/123","/posts/456/comments"],F=1000){if(this.debug){let J=this.routeMatcher.benchmark($,F);return console.log("[ROUTEMATCHER]","Route matching benchmark:",J),J}}updateRoutes($){if(this.development)this.routes=$,this.routeMatcher=new Q($,{enableCaching:!1,debug:this.debug}),console.log("[HMR]","Server Routes updated");else console.warn("Route updates are only allowed in development mode")}get url(){return this.server?.url.toString()??null}listen($,F){if(this.server=Bun.serve({port:$,routes:this.getIntegrationRoutes(),fetch:(J,_)=>this.handleRequest(J,_),websocket:{open:(J)=>{if(this.activeConnections.add(J),this.wsConfig?.onOpen)this.wsConfig.onOpen(J,J.data.ctx);if(this.debug)console.log("[BRPC]","WebSocket connection opened",{connections:this.activeConnections.size})},message:async(J,_)=>{try{J.data.lastActivity=Date.now();let{type:W,topic:X,token:Y,data:j}=JSON.parse(_),A=J.data.ctx;if(this.debug)console.log("[BRPC]","WebSocket message received:",{type:W,topic:X});switch(W){case"authenticate":await this.handleAuthenticate(J,Y,A);break;case"subscribe":await this.handleSubscribe(J,X,A);break;case"unsubscribe":await this.handleUnsubscribe(J,X,A);break;case"publish":await this.handlePublish(J,X,j,A);break;default:if(J.send(JSON.stringify({error:"Unknown message type"})),this.debug)console.error("Unknown WebSocket message type:",W)}}catch(W){if(this.debug)console.error("[BRPC]","WebSocket message error:",W);if(W instanceof z)J.send(JSON.stringify({error:"Invalid message format"}));else if(W instanceof Error)J.send(JSON.stringify({error:W.message}))}},close:(J,_,W)=>{if(this.activeConnections.delete(J),J.data.topics.forEach((X)=>{J.unsubscribe(X)}),this.debug)console.log("[BRPC]","WebSocket connection closed",{code:_,reason:W,remainingConnections:this.activeConnections.size});if(this.wsConfig?.onClose)this.wsConfig.onClose(J,_,W,J.data.ctx)}}}),F)F()}}function e($){return new T($)}function F$($){return $}import{z as P}from"zod";var w={image:["image/jpeg","image/png","image/gif","image/webp","image/svg+xml"],video:["video/mp4","video/webm","video/ogg"],audio:["audio/mpeg","audio/ogg","audio/wav"],document:["application/pdf","application/msword","application/vnd.openxmlformats-officedocument.wordprocessingml.document","application/vnd.ms-excel","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"]},E=($,F="MB")=>{switch(F){case"MB":return $*1024*1024;case"KB":return $*1024;case"B":return $}},W$=({acceptedTypes:$,maxSize:F=1/0,minSize:J,messages:_={}}={})=>{let W=F?E(F):1/0,X=J?E(J):0,Y=$?Object.entries($).flatMap(([A,L])=>{let G=A;if(L==="*")return w[G];if(Array.isArray(L))return L;return[]}):void 0,j=P.instanceof(File,{message:_.required??"File is required"});if(j=j.refine((A)=>A.size<=W,_.maxSize??`File size must be less than ${F}MB`).refine((A)=>A.size>=X,_.minSize??`File size must be at least ${J}MB`),Y?.length)j=j.refine((A)=>Y.includes(A.type),_.type??`File must be of type: ${Y.join(", ")}`);return j};var Y$=($)=>{let F=$.paths.map((_)=>new RegExp(_)),J="Not Found";return async(_)=>{let X=new URL(_.req.url).pathname;if(F.some((Y)=>Y.test(X)))throw Error("Not Found")}};class H{requests=new Map;lastCleanupTime=Date.now();alertThreshold;config;constructor($){this.config={windowMs:$.windowMs,maxRequests:$.maxRequests,maxEntries:$.maxEntries??1e4,cleanupIntervalMs:$.cleanupIntervalMs??60000,message:$.message??"Too Many Requests",statusCode:$.statusCode??429,headerPrefix:$.headerPrefix??"X-RateLimit"},this.alertThreshold=this.config.maxEntries*0.8}cleanup(){let $=Date.now(),F=0;for(let[J,_]of this.requests.entries()){let W=$-_.startTime>this.config.windowMs,X=$-_.lastAccessed>this.config.windowMs*2;if(W||X)this.requests.delete(J),F++}return this.lastCleanupTime=$,F}handleMaxEntries($){let F=this.requests.size;if(Date.now()-this.lastCleanupTime>this.config.cleanupIntervalMs)this.cleanup();if(F>=this.alertThreshold)console.warn(`Rate limiter at ${Math.round(F/this.config.maxEntries*100)}% capacity`);if(this.requests.has($))return!0;return F<this.config.maxEntries}check($){if(!this.handleMaxEntries($))return{isLimited:!0,remaining:0,resetTime:Date.now()+this.config.windowMs};let F=Date.now(),J=this.requests.get($);if(!J||F-J.startTime>=this.config.windowMs)return this.requests.set($,{count:1,startTime:F,lastAccessed:F}),{isLimited:!1,remaining:this.config.maxRequests-1,resetTime:F+this.config.windowMs};J.lastAccessed=F;let _=J.count>=this.config.maxRequests;if(!_)J.count++;return{isLimited:_,remaining:Math.max(0,this.config.maxRequests-J.count),resetTime:J.startTime+this.config.windowMs}}getConfig(){return this.config}}var A$=($)=>{let F=new H($);return async(J)=>{if(J.req.headers.get("range"))return;let _=J.req.headers.get("x-forwarded-for")?.split(",")[0]||J.req.headers.get("x-real-ip")||"unknown",{isLimited:W,remaining:X,resetTime:Y}=F.check(_),{headerPrefix:j}=F.getConfig();if(J.headers.set(`${j}-Remaining`,X.toString()),J.headers.set(`${j}-Reset`,Y.toString()),W)throw J.headers.set(`${j}-Exceeded`,"true"),Error(F.getConfig().message)}};function G$($,F){return F??$}async function I$($,F){let J=performance.now();try{return await F()}catch(_){let W=performance.now();throw console.log(`[PERF] ${$} failed after ${(W-J).toFixed(4)} ms`),_}finally{let _=performance.now();console.log(`[PERF] ${$}: ${(_-J).toFixed(4)} ms`)}}export{f as streamMedia,l as quickHeaders,I$ as measure,y as hashContent,K as getMimeType,V as getCorsOrigin,e as createRouter,A$ as createRateLimiter,h as createProcedure,Y$ as createPathBlocker,G$ as createMiddleware,W$ as createFileSchema,F$ as createContext,c as commonHeaders,Z as buildHeaders,T as Router,D as BRPCError};
|
|
7
|
+
if($.wildcard&&F.length===0)return{params:{..._,wildcardPath:""},route:$.wildcard.handler};return null}let W=F[J],X=decodeURIComponent(W),Y=$.static.get(X);if(Y){let Z=this.traverseTree(Y,F,J+1,_);if(Z)return Z}if($.param){let Z={..._,[$.param.name]:X},j=this.traverseTree($.param.node,F,J+1,Z);if(j)return j}if($.wildcard){let Z=F.slice(J).join("/");return{params:{..._,wildcardPath:Z},route:$.wildcard.handler}}return null}compileRoutes($,F=[]){let J={static:new Map};for(let[_,W]of Object.entries($)){if(_==="index"&&this.isProcedure(W)){J.index={handler:W,paramNames:[...F],hasWildcard:!1};continue}if(_==="*"){if(this.isProcedure(W))J.wildcard={handler:W,paramNames:[...F,"wildcardPath"],hasWildcard:!0,wildcardIndex:F.length};continue}if(this.isProcedure(W)){let X={handler:W,paramNames:[...F],hasWildcard:!1};if(_.startsWith(":")){let Y=_.slice(1),Z={static:new Map,handler:X};J.param={name:Y,node:Z}}else{let Y={static:new Map,handler:X};J.static.set(_,Y)}}else if(typeof W==="object"&&W!==null)if(_.startsWith(":")){let X=_.slice(1),Y=this.compileRoutes(W,[...F,X]);J.param={name:X,node:Y}}else{let X=this.compileRoutes(W,F);J.static.set(_,X)}}return J}isProcedure($){return $&&typeof $==="object"&&"_type"in $&&"_input"in $}cacheResult($,F){if(this.routeCache.size>=this.options.maxCacheSize){let J=this.routeCache.keys().next().value;if(J!==void 0){if(this.routeCache.delete(J),this.options.debug)console.log(`\uD83D\uDDD1\uFE0F Evicted oldest cache entry: ${J}`)}}this.routeCache.set($,F)}analyzeRoutes($){let F={totalRoutes:0,staticRoutes:0,paramRoutes:0,wildcardRoutes:0,indexRoutes:0,maxDepth:0,procedureTypes:{query:0,mutation:0,subscription:0,file:0,fileStream:0,streamQuery:0,streamMutation:0,formMutation:0,html:0}},J=(_,W=0)=>{F.maxDepth=Math.max(F.maxDepth,W);for(let[X,Y]of Object.entries(_))if(this.isProcedure(Y)){if(F.totalRoutes++,Y._type in F.procedureTypes)F.procedureTypes[Y._type]++;if(X==="index")F.indexRoutes++;else if(X.startsWith(":"))F.paramRoutes++;else if(X==="*")F.wildcardRoutes++;else F.staticRoutes++}else if(typeof Y==="object"&&Y!==null)J(Y,W+1)};return J($),F}clearCache(){if(this.routeCache.clear(),this.options.debug)console.log("[ROUTEMATCHER]","\uD83E\uDDF9 Route cache cleared")}getCacheStats(){return{size:this.routeCache.size,maxSize:this.options.maxCacheSize,routeStats:this.routeStats}}getOptimizationSuggestions(){let $=[],{routeStats:F}=this;if(F.paramRoutes>F.staticRoutes*2)$.push("\uD83D\uDE80 Consider grouping routes to reduce parameter route overhead");if(F.maxDepth>6)$.push("\uD83D\uDCCA Deep route nesting detected - consider flattening some routes");if(F.totalRoutes>200)$.push("\u26A1 Large route table - consider route splitting or lazy loading");if(!this.options.enableCaching&&F.totalRoutes>50)$.push("\uD83D\uDCBE Enable caching for better performance with many routes");if(F.procedureTypes.file>20)$.push("\uD83D\uDCC1 Many file routes detected - consider static file serving");if(F.procedureTypes.subscription>10)$.push("\uD83D\uDD04 Many subscription routes - ensure WebSocket optimization");return $}findRoutesByPattern($){return[]}getRouteInfo($){let F=$.split("/").filter(Boolean),J=F.join("/"),_=this.routeCache.has(J),W=performance.now(),X=this.matchDirect(F),Y=performance.now()-W;return{found:!!X,route:X?.route,params:X?.params,cached:_,matchTime:Y}}benchmark($,F=1000){let J=$.map((G)=>G.split("/").filter(Boolean)),_=new Map(this.routeCache);this.routeCache.clear();let W=performance.now();for(let G=0;G<F;G++)for(let U of J)this.matchDirect(U);let Y=performance.now()-W,Z=F*$.length,j=Y/Z,L=Z/Y*1000;return this.routeCache=_,{averageTime:j,totalTime:Y,routesPerSecond:L}}warmupCache($){if(!this.options.enableCaching)return;let F=performance.now(),J=0;for(let W of $){let X=W.split("/").filter(Boolean),Y=this.matchDirect(X);if(Y)this.cacheResult(X.join("/"),Y),J++}let _=performance.now();if(this.options.debug)console.log("[ROUTEMATCHER]",`\uD83D\uDD25 Cache warmed up: ${J} routes in ${(_-F).toFixed(2)}ms`)}}var C=import.meta.require,q={NO_CACHE:{cacheControl:"no-cache, no-store, must-revalidate",expires:new Date(0).toUTCString()},ONE_YEAR:{cacheControl:"public, max-age=31536000",expires:new Date(Date.now()+31536000000).toUTCString()},IMMUTABLE:{cacheControl:"public, max-age=31536000, immutable",expires:new Date(Date.now()+31536000000).toUTCString()},ONE_MONTH:{cacheControl:"public, max-age=2592000"},ONE_WEEK:{cacheControl:"public, max-age=604800"},ONE_DAY:{cacheControl:"public, max-age=86400, must-revalidate"},ONE_HOUR:{cacheControl:"public, max-age=3600, must-revalidate"},FIVE_MINUTES:{cacheControl:"public, max-age=300"},API:{cacheControl:"public, max-age=300, s-maxage=3600"},STYLESHEET:{cacheControl:"public, max-age=2592000, must-revalidate"},JAVASCRIPT:{cacheControl:"public, max-age=2592000, must-revalidate"},HASHED_ASSET:{cacheControl:"public, max-age=31536000, immutable",expires:new Date(Date.now()+31536000000).toUTCString()},FONT:{cacheControl:"public, max-age=31536000",expires:new Date(Date.now()+31536000000).toUTCString()},IMAGE:{cacheControl:"public, max-age=2592000"},FAVICON:{cacheControl:"public, max-age=31536000",expires:new Date(Date.now()+31536000000).toUTCString()},HTML_PAGE:{cacheControl:"public, max-age=3600, must-revalidate"},API_RESPONSE:{cacheControl:"public, max-age=300, s-maxage=900"},FEED:{cacheControl:"public, max-age=3600"},SITEMAP:{cacheControl:"public, max-age=86400"},MANIFEST:{cacheControl:"public, max-age=86400, must-revalidate"},SERVICE_WORKER:{cacheControl:"no-cache, no-store, must-revalidate",expires:new Date(0).toUTCString()},MEDIA_STREAM:{cacheControl:"no-cache, no-store"},DOCUMENT:{cacheControl:"public, max-age=86400"},ARCHIVE:{cacheControl:"public, max-age=604800"}};function v($){return q[$]}var x={html:"text/html; charset=utf-8",css:"text/css",javascript:"application/javascript",json:"application/json",xml:"application/xml",text:"text/plain; charset=utf-8",csv:"text/csv",markdown:"text/markdown; charset=utf-8",png:"image/png",jpeg:"image/jpeg",gif:"image/gif",svg:"image/svg+xml",webp:"image/webp",icon:"image/x-icon",bmp:"image/bmp",woff:"font/woff",woff2:"font/woff2",truetype:"font/ttf","embedded-opentype":"application/vnd.ms-fontobject",opentype:"font/otf",mp4:"video/mp4",webm:"video/webm",ogg:"video/ogg",mpeg:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",zip:"application/zip",tar:"application/x-tar",gzip:"application/gzip","7z":"application/x-7z-compressed",js:"application/javascript",txt:"text/plain; charset=utf-8",jpg:"image/jpeg",ico:"image/x-icon",ttf:"font/ttf",eot:"application/vnd.ms-fontobject",otf:"font/otf",mp3:"audio/mpeg",gz:"application/gzip"};function K($){return x[$]||"application/octet-stream"}function f($){if(typeof Bun<"u"&&Bun.hash)return Bun.hash($).toString(16);try{let F=C("crypto"),J;if(typeof $==="string")J=Buffer.from($);else if($ instanceof ArrayBuffer)J=Buffer.from(new Uint8Array($));else J=Buffer.from($);return F.createHash("md5").update(J).digest("hex")}catch{let F=typeof $==="string"?new TextEncoder().encode($):$ instanceof ArrayBuffer?new Uint8Array($):$,J=0;for(let _=0;_<F.length;_++)J=(J<<5)-J+F[_],J=J&J;return Math.abs(J).toString(16).padStart(16,"0")}}function V($,F,J){let _=$.headers.get("Origin"),W=!0;if((J?.allowAll??!0)&&_)return _;if(F.length===0)return _||"*";if(!_)return F[0];if(F.includes(_))return _;return F[0]}class S{headers={};contentType($){return this.headers["Content-Type"]=K($),this}filePath($){let F=$.split(".").pop()?.toLowerCase()||"";return this.headers["Content-Type"]=K(F),this}mimeType($){return this.headers["Content-Type"]=$,this}cache($){let{cacheControl:F,expires:J}=v($);if(this.headers["Cache-Control"]=F,J)this.headers.Expires=J;return this}eTag($){if(!$)return this;let F;if(typeof $==="string"&&$.startsWith('"'))F=$;else F=`"${f($)}"`;return this.headers.ETag=F,this}lastModified($){return this.headers["Last-Modified"]=$.toUTCString(),this}contentLength($){return this.headers["Content-Length"]=$.toString(),this}redirect($,F=!1){return this.headers.Location=$,this.headers["X-Redirect-Type"]=F?"permanent":"temporary",this}cors({origin:$="*",methods:F=["GET","POST","PUT","DELETE","OPTIONS"],headers:J=["Content-Type","Authorization"],maxAge:_=86400,credentials:W}={}){let X=W??$!=="*";if(X&&$==="*")throw Error('CORS: Cannot use credentials with wildcard origin "*". Specify an exact origin or set credentials to false.');if(this.headers["Access-Control-Allow-Origin"]=$,this.headers["Access-Control-Allow-Methods"]=F.join(", "),this.headers["Access-Control-Allow-Headers"]=J.join(", "),X)this.headers["Access-Control-Allow-Credentials"]="true";return this.headers["Access-Control-Max-Age"]=_.toString(),this}security($){let F={hsts:!0,noSniff:!0,frameOptions:"SAMEORIGIN",xssProtection:!0,...$};if(F.csp)this.headers["Content-Security-Policy"]=F.csp;if(F.hsts){let J=typeof F.hsts==="number"?F.hsts:31536000;this.headers["Strict-Transport-Security"]=`max-age=${J}; includeSubDomains`}if(F.noSniff)this.headers["X-Content-Type-Options"]="nosniff";if(F.frameOptions)this.headers["X-Frame-Options"]=F.frameOptions;if(F.xssProtection)this.headers["X-XSS-Protection"]="1; mode=block";return this}custom($,F){return this.headers[$]=F,this}customHeaders($){return Object.assign(this.headers,$),this}vary(...$){let F=this.headers.Vary,J=F?`${F}, ${$.join(", ")}`:$.join(", ");return this.headers.Vary=J,this}compress($){if($)this.headers["Content-Encoding"]=$;return this}build($){let F={...this.headers};if($&&!F["Content-Length"]){let J=typeof $==="string"?new TextEncoder().encode($).length:$.byteLength;F["Content-Length"]=J.toString()}return F}buildHeaders($){return new Headers(this.build($))}}function A(){return new S}function p($,F){let J=A().contentType($);if(F)J.cache(F);return J.build()}var l={css:($)=>A().contentType("css").cache("STYLESHEET").eTag($),javascript:($)=>A().contentType("javascript").cache("JAVASCRIPT").eTag($),hashedAsset:($,F)=>A().filePath($).cache("HASHED_ASSET").eTag(F),font:($)=>A().filePath($).cache("FONT").cors({origin:"*"}),image:($)=>A().filePath($).cache("IMAGE"),favicon:()=>A().contentType("icon").cache("FAVICON"),api:()=>A().contentType("json").cache("API_RESPONSE").cors(),expensiveApi:($)=>A().contentType("json").cache("API_RESPONSE").cors().eTag($),realtime:()=>A().contentType("json").cache("NO_CACHE").cors(),html:()=>A().contentType("html").cache("HTML_PAGE").security(),expensiveHtml:($)=>A().contentType("html").cache("HTML_PAGE").security().eTag($),manifest:($)=>A().contentType("json").cache("MANIFEST").eTag($),serviceWorker:()=>A().contentType("javascript").cache("SERVICE_WORKER"),sitemap:($)=>A().contentType("xml").cache("SITEMAP").eTag($),feed:($,F="rss")=>A().contentType("xml").cache("FEED").eTag($).mimeType(F==="rss"?"application/rss+xml":"application/atom+xml"),video:($)=>A().filePath($).cache("MEDIA_STREAM").custom("Accept-Ranges","bytes"),audio:($)=>A().filePath($).cache("MEDIA_STREAM").custom("Accept-Ranges","bytes"),pdf:($)=>A().contentType("pdf").cache("DOCUMENT").custom("Content-Disposition","inline").eTag($),download:($,F,J)=>A().filePath(F).cache("ARCHIVE").custom("Content-Disposition",`attachment; filename="${$}"`).eTag(J),secure:()=>A().security({csp:"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'",hsts:31536000}).custom("X-Powered-By","Bun"),permanentRedirect:($)=>A().redirect($,!0),temporaryRedirect:($)=>A().redirect($,!1)};async function T($,F,J,_={}){let{maxChunkSize:W=2097152,cacheMaxAge:X=3600,acceptedExtensions:Y}=_;if(!await $.exists())return new Response("Media file not found",{status:404,headers:A().contentType("text").cors({origin:V(F,J)}).build()});if(Y&&Y.length>0){let b=$.name?.toLowerCase().split(".").pop();if(!b||!Y.includes(b))return new Response("File type not supported",{status:415,headers:A().contentType("text").cors({origin:V(F,J)}).build()})}let j=$.size,L=F.headers.get("Range");if(!L)return new Response($,{headers:A().filePath($.name||"media").custom("Accept-Ranges","bytes").custom("Cache-Control",`public, max-age=${X}`).cors({origin:V(F,J)}).build()});let G=L.match(/bytes=(\d*)-(\d*)/);if(!G)return new Response("Invalid Range header",{status:416,headers:A().contentType("text").cors({origin:V(F,J)}).custom("Content-Range",`bytes */${j}`).build()});let U=G[1]?parseInt(G[1]):0,R=G[2]?parseInt(G[2]):j-1,O=Math.min(R,U+W-1,j-1);if(U>=j||O>=j||U>O)return new Response("Range not satisfiable",{status:416,headers:A().contentType("text").cors({origin:V(F,J)}).custom("Content-Range",`bytes */${j}`).build()});return new Response($.slice(U,O+1),{status:206,headers:A().filePath($.name||"media").custom("Accept-Ranges","bytes").custom("Cache-Control",`public, max-age=${X}`).cors({origin:V(F,J)}).build()})}function y($,F,J){return{type:"stream",file:$,request:F,options:J}}class H{routes;routeMatcher;contextCreator;prefix;globalMiddlewares;server=null;wsConfig;integrations;development=!0;debug=!1;allowedOrigins=[];onError;activeConnections=new Set;maxConnections=parseInt(process.env.MAX_WS_CONNECTIONS??"1000");connectionTimeout=parseInt(process.env.WS_TIMEOUT??"30000");maxRequestSize=parseInt(process.env.MAX_REQUEST_SIZE??"10485760");requestTimeout=parseInt(process.env.REQUEST_TIMEOUT??"30000");constructor($){if(this.contextCreator=$.context,this.prefix=$.prefix??"",this.routes=$.routes,this.globalMiddlewares=$.globalMiddlewares??[],this.wsConfig=$.websocket??{},this.integrations=$.integrations,this.debug=!!$.debug&&!0,this.allowedOrigins=$.allowedOrigins??[],this.onError=$.onError,this.routeMatcher=new M($.routes,{enableCaching:!this.development,maxCacheSize:parseInt(process.env.ROUTE_CACHE_SIZE??"1000"),debug:this.debug}),!this.development)setInterval(()=>this.cleanupStaleConnections(),60000);if(this.debug)console.log("Router initialized with route matcher"),console.log("Route cache stats:",this.routeMatcher.getCacheStats())}findRoute($){return this.routeMatcher.match($)}async runMiddlewares($,F){for(let J of $){let _=await J(F);if(_&&typeof _==="object")Object.assign(F,_)}}async parseInput($,F,J){let _={},W=new URL($.url),X=Object.fromEntries(W.searchParams),Y=$.method;if(Y==="POST"){if(parseInt($.headers.get("content-length")||"0")>this.maxRequestSize)throw Error("Request payload too large")}if(Y==="GET"&&["query","streamQuery","file","fileStream","html"].includes(J))_=X;else if(Y==="POST"&&["mutation","formMutation","streamMutation"].includes(J)){let Z=$.headers.get("content-type");if(Z?.includes("application/json"))_=await $.json();else if(Z?.includes("multipart/form-data")){let j=await $.formData();_=await this.parseFormData(j)}else throw new D({code:"BAD_REQUEST",message:"Unsupported content type"})}else throw new D({code:"METHOD_NOT_SUPPORTED",message:"Unsupported request method"});return F.parse(_)}async parseFormData($){let F={},J={};$.forEach(async(_,W)=>{if(_ instanceof File){if(!J[W])J[W]=[];J[W].push(_)}else if(typeof _==="string")try{F[W]=JSON.parse(_)}catch{F[W]=_}});for(let[_,W]of Object.entries(J))F[_]=W;return F}handleError($,F){if(this.onError)try{this.onError($,{req:F,route:F.url})}catch(_){console.error("Error logger failed:",_)}let J=this.serializeError($);return new Response(JSON.stringify({error:J.error}),{status:J.status,headers:A().contentType("json").cors({origin:V(F,this.allowedOrigins)}).build()})}handleFileCacheControl($){let F;if($?.includes("text/html"))F="public, max-age=0, must-revalidate";else if($?.includes("text/css")||$?.includes("text/javascript")||$?.includes("application/javascript"))F="public, max-age=31536000, immutable";else F="public, max-age=86400, stale-while-revalidate=604800";return F}async handleRequest($,F){try{if($.method==="OPTIONS")return new Response(null,{status:204,headers:A().cors({origin:V($,this.allowedOrigins)}).build()});let _=new URL($.url).pathname;if(_=_.startsWith(this.prefix)?_.slice(this.prefix.length):_,_=_.startsWith("/")?_:"/"+_,_==="/ws"&&F){if(this.debug)console.log("\uD83D\uDD0C WebSocket upgrade requested");return this.handleWebSocketUpgrade($,F)}let W=_==="/"?[]:_.split("/").filter(Boolean),X=this.findRoute(W);if(!X)return new Response("Not Found",{status:404,headers:A().contentType("text").cors({origin:V($,this.allowedOrigins)}).build()});let{route:Y}=X,Z=Y._timeout??this.requestTimeout,j=new Promise((L,G)=>{setTimeout(()=>G(Error("Request timeout")),Z)});return await Promise.race([this._handleRequest($,F,X),j])}catch(J){return this.handleError(J,$)}}async _handleRequest($,F,J){try{if(!J)return new Response("Not Found",{status:404,headers:A().contentType("text").cors({origin:V($,this.allowedOrigins)}).build()});let{params:_,route:W}=J;if(!W.handler)return new Response("Handler not implemented",{status:501,headers:A().contentType("text").cors({origin:V($,this.allowedOrigins)}).build()});let X=await this.contextCreator($);Object.assign(X,{req:$,params:_,publishToProcedure:async(L,G,U)=>{await this.publishToProcedure(L,G,X,U)}});for(let L of this.globalMiddlewares){let G=await L(X);if(G!==null&&G!==void 0&&typeof G==="object")Object.assign(X,G)}for(let L of W.middlewares){let G=await L(X);if(G!==null&&G!==void 0&&typeof G==="object")Object.assign(X,G)}let Y=await this.parseInput($,W._input,W._type),Z=await W.handler({ctx:X,input:Y}),j=A().cors({origin:V($,X.allowedOrigins??this.allowedOrigins)}).build();return Object.keys(j).forEach((L)=>{X.headers.set(L,j[L])}),await this.handleProcedureResponse(W._type,Z,X.headers)}catch(_){return this.handleError(_,$)}}async testRequest($){return this.handleRequest($)}async handleProcedureResponse($,F,J){switch($){case"file":if(F instanceof Response)return F;let _=F.type;return J.append("Content-Type",_),J.append("Cache-Control",this.development?"public, max-age=0, must-revalidate":this.handleFileCacheControl(_)),new Response(F,{headers:J});case"fileStream":if(typeof F==="object"&&F!==null&&"type"in F&&F.type==="stream"){let X=F;return await T(F.file,F.request,this.allowedOrigins,X.options)}let W=F.type;return J.append("Content-Type",W),J.append("Cache-Control",this.development?"public, max-age=0, must-revalidate":this.handleFileCacheControl(W)),new Response(F,{headers:J});case"html":if(typeof F==="string")return J.append("Content-Type","text/html"),new Response(F,{headers:J});break;case"query":case"mutation":case"formMutation":case"streamQuery":case"streamMutation":case"subscription":default:return J.append("Content-Type","application/json"),new Response(JSON.stringify({data:F}),{headers:J})}return J.append("Content-Type","application/json"),new Response(JSON.stringify({data:F}),{headers:J})}cleanupStaleConnections(){let $=Date.now();for(let F of this.activeConnections)if($-F.data.lastActivity>this.connectionTimeout)F.close(),this.activeConnections.delete(F)}async handleWebSocketUpgrade($,F){if(this.activeConnections.size>=this.maxConnections)return new Response("Too many connections",{status:503});let J=await this.createWebSocketContext($);if(F.upgrade($,{data:{ctx:J,topics:new Set,lastActivity:Date.now()}}))return new Response(null,{status:101});return new Response("WebSocket upgrade failed",{status:500})}async createWebSocketContext($){let F=await this.contextCreator($);return Object.assign(F,{req:$}),await this.runMiddlewares(this.globalMiddlewares,F),F}createWebSocketOperationContext($,F){return{...$,params:F}}serializeError($){let F=$,J=F?.issues??F?.errors;if(Array.isArray(J)&&F.name==="ZodError")return{status:400,error:{name:"ValidationError",code:"BAD_REQUEST",clientCode:"VALIDATION_ERROR",message:"Input validation failed",data:{validationErrors:J.map((_)=>({path:_.path.join("."),message:_.message,code:_.code,received:_.received}))},httpStatus:400}};if($ instanceof D)return{status:$.httpStatus,error:$.toJSON()};if($ instanceof Error){let _=$.message==="Not Found"?404:$.message==="Unauthorized"?401:$.message==="Forbidden"?403:$.message==="Request payload too large"?413:$.message==="Request timeout"?504:500;return{status:_,error:{code:_===404?"NOT_FOUND":_===401?"UNAUTHORIZED":_===403?"FORBIDDEN":_===413?"PAYLOAD_TOO_LARGE":_===504?"GATEWAY_TIMEOUT":"INTERNAL_SERVER_ERROR",message:_<500?$.message:"Internal Server Error"}}}return{status:500,error:{code:"INTERNAL_SERVER_ERROR",message:"Internal Server Error"}}}sendWebSocketError($,F,J,_){let W=F==="authenticate"?"auth_error":F==="subscribe"?"subscribe_error":F==="unsubscribe"?"unsubscribe_error":F==="publish"?"publish_error":"error",X=this.serializeError(_);$.send(JSON.stringify({type:W,...J?{topic:J}:{},error:{...X.error,httpStatus:X.error.httpStatus??X.status}}))}async handleAuthenticate($,F,J){try{let _=F.startsWith("Bearer ")?F.substring(7):F,W=new Headers(J.req.headers);if(_)W.set("authorization",`Bearer ${_}`);else W.delete("authorization");let X=new Request(J.req,{headers:W});if($.data.ctx=await this.createWebSocketContext(X),$.send(JSON.stringify({type:"auth_success",message:"Authentication successful"})),this.debug)console.log("[BRPC]","WebSocket authenticated successfully")}catch(_){if(this.sendWebSocketError($,"authenticate",void 0,_),this.debug)console.error("WebSocket authentication error:",_)}}async handleSubscribe($,F,J){let _=this.findRoute(F.split("/").filter(Boolean));if(!_||_.route._type!=="subscription")throw Error("Invalid subscription route");let W=this.createWebSocketOperationContext(J,_.params);await this.runMiddlewares(_.route.subscribeMiddlewares??[],W),$.subscribe(F),$.data.topics.add(F),$.send(JSON.stringify({type:"subscribe_success",topic:F}))}async handleUnsubscribe($,F,J){let _=this.findRoute(F.split("/").filter(Boolean));if(!_||_.route._type!=="subscription")throw Error("Invalid subscription route");$.unsubscribe(F),$.data.topics.delete(F)}async handlePublish($,F,J,_){let W=this.findRoute(F.split("/").filter(Boolean));if(!W)throw Error("Subscription not found");let{params:X,route:Y}=W;if(Y._type!=="subscription"||!Y.handler)throw Error("Invalid subscription route");let Z=this.createWebSocketOperationContext(_,X);await this.runMiddlewares(Y.middlewares,Z);let j=Y._input.fields?Y._input.parse(J):J,L=await Y.handler({ctx:Z,input:j}),G={topic:F,data:L,type:"publish"};if(this.debug)console.log(`Server will publish to ${F}`,G);this.server.publish(F,JSON.stringify(G))}async publishToProcedure($,F,J,_){if($._type!=="subscription"||!$.handler)throw Error("Can only publish to subscription procedures");let W=this.findRoutePath(this.routes,$);if(!W)throw Error("Subscription route not found");let X=W.split("/").map((j)=>{if(j.startsWith(":")){let L=j.slice(1),G=_[L];if(!G)throw Error(`Missing parameter: ${L}`);return G}return j}).join("/"),Y=await $.handler({ctx:{...J,params:_},input:F}),Z={topic:X,data:Y,type:"publish"};if(this.debug)console.log(`Server will publish to ${X}`,Z);return this.server?.publish(X,JSON.stringify(Z)),Y}async publish($,F,J){if($._type!=="subscription"||!$.handler)throw Error("Can only publish to subscription procedures");let _=this.findRoutePath(this.routes,$);if(!_)throw Error("Subscription route not found");let W=_.split("/").map((Z)=>{if(Z.startsWith(":")){let j=Z.slice(1),L=J?J[j]:null;if(!L)throw Error(`Missing parameter: ${j}`);return L}return Z}).join("/"),X=await $.handler({ctx:{params:J},input:F}),Y={topic:W,data:X,type:"publish"};if(this.debug)console.log(`Server will publish to ${W}`,Y);return this.server?.publish(W,JSON.stringify(Y)),X}freePublish($,F){let J={topic:$,data:F,type:"publish"};this.server?.publish($,JSON.stringify(J))}findRoutePath($,F,J=""){for(let[_,W]of Object.entries($)){let X=J?`${J}/${_}`:_;if(W===F)return X;if(W&&typeof W==="object"&&!("_type"in W)){let Y=this.findRoutePath(W,F,X);if(Y)return Y}}return null}getIntegrationRoutes(){if(!this.integrations)return;let $={};if(this.integrations.betterAuth){let F=this.integrations.betterAuth.handler;if(!F)throw Error("Please provide a handler for betterAuth integration");$["/api/auth/*"]=async(J)=>{return await F(J)}}if(this.integrations.rawRoutes)Object.keys(this.integrations.rawRoutes).forEach((F)=>{$[F]=this.integrations.rawRoutes[F]});return Object.keys($).length>0?$:void 0}getRouteStats(){return{cache:this.routeMatcher.getCacheStats(),suggestions:this.routeMatcher.getOptimizationSuggestions()}}clearRouteCache(){if(this.routeMatcher.clearCache(),this.debug)console.log("[ROUTEMATCHER] Route cache cleared")}benchmarkRoutes($=["/users/123","/posts/456/comments"],F=1000){if(this.debug){let J=this.routeMatcher.benchmark($,F);return console.log("[ROUTEMATCHER]","Route matching benchmark:",J),J}}updateRoutes($){if(this.development)this.routes=$,this.routeMatcher=new M($,{enableCaching:!1,debug:this.debug}),console.log("[HMR]","Server Routes updated");else console.warn("Route updates are only allowed in development mode")}get url(){return this.server?.url.toString()??null}listen($,F){if(this.server=Bun.serve({port:$,routes:this.getIntegrationRoutes(),fetch:(J,_)=>this.handleRequest(J,_),websocket:{open:(J)=>{if(this.activeConnections.add(J),this.wsConfig?.onOpen)this.wsConfig.onOpen(J,J.data.ctx);if(this.debug)console.log("[BRPC]","WebSocket connection opened",{connections:this.activeConnections.size})},message:async(J,_)=>{let W,X;try{J.data.lastActivity=Date.now();let Y=JSON.parse(_),{token:Z,data:j}=Y;W=Y.type,X=Y.topic;let L=J.data.ctx;if(this.debug)console.log("[BRPC]","WebSocket message received:",{type:W,topic:X});switch(W){case"authenticate":await this.handleAuthenticate(J,Z,L);break;case"subscribe":if(!X)throw D.badRequest("Subscription topic is required");await this.handleSubscribe(J,X,L);break;case"unsubscribe":if(!X)throw D.badRequest("Subscription topic is required");await this.handleUnsubscribe(J,X,L);break;case"publish":if(!X)throw D.badRequest("Subscription topic is required");await this.handlePublish(J,X,j,L);break;default:if(this.sendWebSocketError(J,W,X,D.badRequest("Unknown message type")),this.debug)console.error("Unknown WebSocket message type:",W)}}catch(Y){if(this.debug)console.error("[BRPC]","WebSocket message error:",Y);this.sendWebSocketError(J,W,X,Y)}},close:(J,_,W)=>{if(this.activeConnections.delete(J),J.data.topics.forEach((X)=>{J.unsubscribe(X)}),this.debug)console.log("[BRPC]","WebSocket connection closed",{code:_,reason:W,remainingConnections:this.activeConnections.size});if(this.wsConfig?.onClose)this.wsConfig.onClose(J,_,W,J.data.ctx)}}}),F)F()}stop(){this.server?.stop(!0),this.server=null,this.activeConnections.clear()}}function a($){return new H($)}function r($){return $}import{z}from"zod";var P={image:["image/jpeg","image/png","image/gif","image/webp","image/svg+xml"],video:["video/mp4","video/webm","video/ogg"],audio:["audio/mpeg","audio/ogg","audio/wav"],document:["application/pdf","application/msword","application/vnd.openxmlformats-officedocument.wordprocessingml.document","application/vnd.ms-excel","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"]},B=($,F="MB")=>{switch(F){case"MB":return $*1024*1024;case"KB":return $*1024;case"B":return $}},F$=({acceptedTypes:$,maxSize:F=1/0,minSize:J,messages:_={}}={})=>{let W=F?B(F):1/0,X=J?B(J):0,Y=$?Object.entries($).flatMap(([j,L])=>{let G=j;if(L==="*")return P[G];if(Array.isArray(L))return L;return[]}):void 0,Z=z.instanceof(File,{message:_.required??"File is required"});if(Z=Z.refine((j)=>j.size<=W,_.maxSize??`File size must be less than ${F}MB`).refine((j)=>j.size>=X,_.minSize??`File size must be at least ${J}MB`),Y?.length)Z=Z.refine((j)=>Y.includes(j.type),_.type??`File must be of type: ${Y.join(", ")}`);return Z};var _$=($)=>{let F=$.paths.map((_)=>new RegExp(_)),J="Not Found";return async(_)=>{let X=new URL(_.req.url).pathname;if(F.some((Y)=>Y.test(X)))throw Error("Not Found")}};class E{requests=new Map;lastCleanupTime=Date.now();alertThreshold;config;constructor($){this.config={windowMs:$.windowMs,maxRequests:$.maxRequests,maxEntries:$.maxEntries??1e4,cleanupIntervalMs:$.cleanupIntervalMs??60000,message:$.message??"Too Many Requests",statusCode:$.statusCode??429,headerPrefix:$.headerPrefix??"X-RateLimit"},this.alertThreshold=this.config.maxEntries*0.8}cleanup(){let $=Date.now(),F=0;for(let[J,_]of this.requests.entries()){let W=$-_.startTime>this.config.windowMs,X=$-_.lastAccessed>this.config.windowMs*2;if(W||X)this.requests.delete(J),F++}return this.lastCleanupTime=$,F}handleMaxEntries($){let F=this.requests.size;if(Date.now()-this.lastCleanupTime>this.config.cleanupIntervalMs)this.cleanup();if(F>=this.alertThreshold)console.warn(`Rate limiter at ${Math.round(F/this.config.maxEntries*100)}% capacity`);if(this.requests.has($))return!0;return F<this.config.maxEntries}check($){if(!this.handleMaxEntries($))return{isLimited:!0,remaining:0,resetTime:Date.now()+this.config.windowMs};let F=Date.now(),J=this.requests.get($);if(!J||F-J.startTime>=this.config.windowMs)return this.requests.set($,{count:1,startTime:F,lastAccessed:F}),{isLimited:!1,remaining:this.config.maxRequests-1,resetTime:F+this.config.windowMs};J.lastAccessed=F;let _=J.count>=this.config.maxRequests;if(!_)J.count++;return{isLimited:_,remaining:Math.max(0,this.config.maxRequests-J.count),resetTime:J.startTime+this.config.windowMs}}getConfig(){return this.config}}var X$=($)=>{let F=new E($);return async(J)=>{if(J.req.headers.get("range"))return;let _=J.req.headers.get("x-forwarded-for")?.split(",")[0]||J.req.headers.get("x-real-ip")||"unknown",{isLimited:W,remaining:X,resetTime:Y}=F.check(_),{headerPrefix:Z}=F.getConfig();if(J.headers.set(`${Z}-Remaining`,X.toString()),J.headers.set(`${Z}-Reset`,Y.toString()),W)throw J.headers.set(`${Z}-Exceeded`,"true"),Error(F.getConfig().message)}};function Z$($,F){return F??$}async function L$($,F){let J=performance.now();try{return await F()}catch(_){let W=performance.now();throw console.log(`[PERF] ${$} failed after ${(W-J).toFixed(4)} ms`),_}finally{let _=performance.now();console.log(`[PERF] ${$}: ${(_-J).toFixed(4)} ms`)}}export{y as streamMedia,p as quickHeaders,L$ as measure,f as hashContent,K as getMimeType,V as getCorsOrigin,a as createRouter,X$ as createRateLimiter,k as createProcedure,_$ as createPathBlocker,Z$ as createMiddleware,F$ as createFileSchema,r as createContext,l as commonHeaders,A as buildHeaders,H as Router,D as BRPCError};
|
|
8
8
|
|
|
9
|
-
//# debugId=
|
|
9
|
+
//# debugId=821D3C5A1AE5704064756E2164756E21
|
package/dist/procedure.d.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import type { BaseContext, Handler, InferInput, Procedure, StreamableResponse } from "./types";
|
|
2
|
+
import type { BaseContext, Handler, InferInput, Middleware, Procedure, StreamableResponse, SubscriptionProcedure } from "./types";
|
|
3
3
|
import type { BunFile } from "bun";
|
|
4
4
|
declare class ProcedureBuilder<C extends BaseContext> {
|
|
5
5
|
private middlewares;
|
|
6
|
-
|
|
6
|
+
private subscribeMiddlewares;
|
|
7
|
+
constructor(middlewares?: ((ctx: any) => Promise<Record<string, any> | void>)[], subscribeMiddlewares?: Middleware<C>[]);
|
|
7
8
|
use<M extends (ctx: C) => Promise<Record<string, any> | void>>(middleware: M): ProcedureBuilder<Awaited<ReturnType<M>> extends Record<string, any> ? C & Awaited<ReturnType<M>> : C>;
|
|
9
|
+
onSubscribe(middleware: Middleware<C>): ProcedureBuilder<C>;
|
|
8
10
|
input<I extends z.ZodType>(schema: I): InputProcedureBuilder<C, I>;
|
|
9
11
|
query<O>(handler: Handler<C, {}, O>): Procedure<C, z.ZodObject<{}>, O, "query">;
|
|
10
12
|
mutation<O>(handler: Handler<C, {}, O>): Procedure<C, z.ZodObject<{}>, O, "mutation">;
|
|
11
13
|
formMutation<O>(handler: Handler<C, {}, O>): Procedure<C, z.ZodObject<{}>, O, "formMutation">;
|
|
12
|
-
subscription<O>(handler: Handler<C, {}, O>):
|
|
14
|
+
subscription<O>(handler: Handler<C, {}, O>): SubscriptionProcedure<C, z.ZodObject<{}>, O>;
|
|
13
15
|
file(handler: Handler<C, {}, BunFile | Response>): Procedure<C, z.ZodObject<{}>, BunFile | Response, "file">;
|
|
14
16
|
fileStream(handler: Handler<C, {}, Blob | string>): Procedure<C, z.ZodObject<{}>, StreamableResponse | Blob | string, "fileStream">;
|
|
15
17
|
html(handler: Handler<C, {}, string>): Procedure<C, z.ZodObject<{}>, string, "html">;
|
|
@@ -18,16 +20,18 @@ declare class InputProcedureBuilder<C extends BaseContext, I extends z.ZodType>
|
|
|
18
20
|
private middlewares;
|
|
19
21
|
private inputSchema;
|
|
20
22
|
private timeoutMs?;
|
|
21
|
-
|
|
23
|
+
private subscribeMiddlewares;
|
|
24
|
+
constructor(middlewares: ((ctx: any) => Promise<Record<string, any> | void>)[], inputSchema: I, timeoutMs?: number | undefined, subscribeMiddlewares?: Middleware<C>[]);
|
|
22
25
|
/**
|
|
23
26
|
* Set a custom timeout for this procedure (in milliseconds).
|
|
24
27
|
* Overrides the default router timeout.
|
|
25
28
|
*/
|
|
26
29
|
timeout(ms: number): InputProcedureBuilder<C, I>;
|
|
30
|
+
onSubscribe(middleware: Middleware<C>): InputProcedureBuilder<C, I>;
|
|
27
31
|
query<O>(handler: Handler<C, InferInput<I>, O>): Procedure<C, I, O, "query">;
|
|
28
32
|
mutation<O>(handler: Handler<C, InferInput<I>, O>): Procedure<C, I, O, "mutation">;
|
|
29
33
|
formMutation<O>(handler: Handler<C, InferInput<I>, O>): Procedure<C, I, O, "formMutation">;
|
|
30
|
-
subscription<O>(handler: Handler<C, InferInput<I>, O>):
|
|
34
|
+
subscription<O>(handler: Handler<C, InferInput<I>, O>): SubscriptionProcedure<C, I, O>;
|
|
31
35
|
file(handler: Handler<C, InferInput<I>, BunFile | Response>): Procedure<C, I, BunFile | Response, "file">;
|
|
32
36
|
fileStream(handler: Handler<C, InferInput<I>, StreamableResponse | Blob | string>): Procedure<C, I, StreamableResponse | Blob | string, "fileStream">;
|
|
33
37
|
html(handler: Handler<C, {}, string>): Procedure<C, z.ZodObject<{}>, string, "html">;
|
package/dist/router/router.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export declare class Router<C extends BaseContext> {
|
|
|
22
22
|
* Simplified route finding using RouteMatcher
|
|
23
23
|
*/
|
|
24
24
|
private findRoute;
|
|
25
|
+
private runMiddlewares;
|
|
25
26
|
/**
|
|
26
27
|
* Parses and validates the request input
|
|
27
28
|
*/
|
|
@@ -72,6 +73,19 @@ export declare class Router<C extends BaseContext> {
|
|
|
72
73
|
* a websocket connection
|
|
73
74
|
*/
|
|
74
75
|
private handleWebSocketUpgrade;
|
|
76
|
+
/**
|
|
77
|
+
* Builds the authenticated base context shared by one WebSocket connection.
|
|
78
|
+
*/
|
|
79
|
+
private createWebSocketContext;
|
|
80
|
+
/**
|
|
81
|
+
* Isolates dynamic topic params and middleware extensions for one socket action.
|
|
82
|
+
*/
|
|
83
|
+
private createWebSocketOperationContext;
|
|
84
|
+
/**
|
|
85
|
+
* Serializes an unknown error consistently for every BRPC transport.
|
|
86
|
+
*/
|
|
87
|
+
private serializeError;
|
|
88
|
+
private sendWebSocketError;
|
|
75
89
|
/**
|
|
76
90
|
* Handles WebSocket authentication
|
|
77
91
|
*/
|
|
@@ -131,6 +145,10 @@ export declare class Router<C extends BaseContext> {
|
|
|
131
145
|
*/
|
|
132
146
|
get url(): string | null;
|
|
133
147
|
listen(port: number, callback?: () => void): void;
|
|
148
|
+
/**
|
|
149
|
+
* Stops the Bun server and closes active WebSocket connections.
|
|
150
|
+
*/
|
|
151
|
+
stop(): void;
|
|
134
152
|
}
|
|
135
153
|
/**
|
|
136
154
|
* createRouter
|
package/dist/types.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export type Handler<C extends BaseContext, I, O> = (params: {
|
|
|
30
30
|
ctx: C;
|
|
31
31
|
input: I;
|
|
32
32
|
}) => Promise<O>;
|
|
33
|
+
export type Middleware<C extends BaseContext> = (ctx: C) => Promise<Record<string, any> | void>;
|
|
33
34
|
export type InferInput<T> = T extends z.ZodType ? z.infer<T> : never;
|
|
34
35
|
export type InferOutput<T> = T extends (...args: any[]) => Promise<infer R> ? R : never;
|
|
35
36
|
export type Procedure<C extends BaseContext, I extends z.ZodType, O, P> = {
|
|
@@ -38,9 +39,13 @@ export type Procedure<C extends BaseContext, I extends z.ZodType, O, P> = {
|
|
|
38
39
|
_ctx: C;
|
|
39
40
|
_type: P;
|
|
40
41
|
_timeout?: number;
|
|
41
|
-
middlewares:
|
|
42
|
+
middlewares: Middleware<any>[];
|
|
43
|
+
subscribeMiddlewares?: Middleware<C>[];
|
|
42
44
|
handler?: Handler<C, InferInput<I>, O>;
|
|
43
45
|
};
|
|
46
|
+
export type SubscriptionProcedure<C extends BaseContext, I extends z.ZodType, O> = Procedure<C, I, O, "subscription"> & {
|
|
47
|
+
subscribeMiddlewares: Middleware<C>[];
|
|
48
|
+
};
|
|
44
49
|
export type Routes = {
|
|
45
50
|
[key: string]: Procedure<any, any, any, any> | Routes;
|
|
46
51
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mateosuarezdev/brpc",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.83",
|
|
4
4
|
"description": "A Type-Safe, Flexible Web application framework for Bun",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"sideEffects": false,
|
|
47
47
|
"scripts": {
|
|
48
48
|
"typecheck": "tsc --noEmit",
|
|
49
|
+
"test": "bun test",
|
|
49
50
|
"clean": "rimraf dist",
|
|
50
51
|
"build": "yarn clean && bun run build.ts && tsc --emitDeclarationOnly",
|
|
51
52
|
"prepublish": "yarn typecheck && yarn build",
|