@mateosuarezdev/brpc 1.0.105 → 1.0.107

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/README.md CHANGED
@@ -29,10 +29,9 @@ npm install @mateosuarezdev/brpc zod
29
29
  - [Schemas](#schemas)
30
30
  - [Errors](#errors)
31
31
  - [Streaming Media](#streaming-media)
32
- - [Storage](#storage)
33
- - [S3](#s3)
34
- - [Images](#images)
35
- - [Local](#local)
32
+ - [Storage](#storage)
33
+ - [S3](#s3)
34
+ - [Images](#images)
36
35
  - [Auth](#auth)
37
36
  - [Server Setup](#server-setup)
38
37
  - [Auth Routes](#auth-routes)
@@ -825,14 +824,20 @@ const billingAccount = useQuery({
825
824
 
826
825
  Because the key changes with the session, signing in as somebody else reads that account's own entry, while the previous user's entry stays cached and is still there if they sign back in. That is the reason to scope keys rather than clear the cache on sign-out: clearing would throw away the offline data persistence exists to keep.
827
826
 
828
- ### Utilities
829
-
830
- ```ts
831
- // Update WebSocket auth token (e.g. after token refresh)
832
- await client.utils.updateWsAuth();
833
-
834
- // Update headers at runtime
835
- await client.utils.setHeader("Authorization", `Bearer ${newToken}`);
827
+ ### Utilities
828
+
829
+ ```ts
830
+ // Update WebSocket auth token (e.g. after token refresh)
831
+ await client.utils.updateWsAuth();
832
+
833
+ // Heal client-side state after a socket reconnect. The initial connection does
834
+ // not call this; the returned function removes this listener.
835
+ const unsubscribe = client.utils.onWsReconnect(() => {
836
+ invalidateLiveData();
837
+ });
838
+
839
+ // Update headers at runtime
840
+ await client.utils.setHeader("Authorization", `Bearer ${newToken}`);
836
841
  await client.utils.setHeaders({ "X-Custom": "value" });
837
842
  ```
838
843
 
@@ -859,7 +864,7 @@ try {
859
864
 
860
865
  ### React
861
866
 
862
- Create one subscription service, then bind the React hook once:
867
+ Create one subscription service, then bind the React hook once:
863
868
 
864
869
  ```ts
865
870
  import { SubscriptionService } from "@mateosuarezdev/brpc/client";
@@ -886,7 +891,28 @@ function ChatRoom({ roomId }: { roomId: string }) {
886
891
  </button>
887
892
  );
888
893
  }
889
- ```
894
+ ```
895
+
896
+ For ordinary React state that needs to heal after a missed WebSocket publish,
897
+ bind the reconnect listener once to the client instance. The hook retains the
898
+ latest callback across renders and removes its listener on unmount:
899
+
900
+ ```ts
901
+ import { createUseOnWsReconnect } from "@mateosuarezdev/brpc/client/react";
902
+
903
+ const useOnWsReconnect = createUseOnWsReconnect(client);
904
+
905
+ function BillingState() {
906
+ useOnWsReconnect(() => {
907
+ subscriptionsInfo.invalidate();
908
+ });
909
+ }
910
+ ```
911
+
912
+ Reconnect listeners are client lifecycle signals, not subscription messages:
913
+ they fire after a connection following a disconnection, including when no topic
914
+ is currently subscribed. Use them to refetch/invalidate local cache; server
915
+ authorization must continue to read authoritative state.
890
916
 
891
917
  ---
892
918
 
@@ -1004,14 +1030,20 @@ import {
1004
1030
  uploadGeneratedVariants,
1005
1031
  deleteOne,
1006
1032
  deleteMany,
1007
- generateVariants,
1008
- getOptimizedImage,
1009
- getImageMetadata,
1010
- local,
1011
- } from "@mateosuarezdev/brpc";
1033
+ generateVariants,
1034
+ getOptimizedImage,
1035
+ getImageMetadata,
1036
+ } from "@mateosuarezdev/brpc";
1012
1037
  ```
1013
1038
 
1014
- Requires Bun's S3 client to be configured via environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_ENDPOINT`, `AWS_BUCKET`).
1039
+ Requires Bun's S3 client to be configured via environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_ENDPOINT`, `AWS_BUCKET`).
1040
+
1041
+ S3 is the only persisted-object provider. Every stored upload has a non-empty
1042
+ `variants` object: `default` for a single representation, or named image sizes
1043
+ from `xs` through `xl`. A variant always has its S3 `key`, stored `name`, and
1044
+ typed metadata. Keys are canonical: derive public URLs from the configured
1045
+ endpoint and authorize/presign private URLs in the consuming application's
1046
+ domain route; do not store either URL on a variant.
1015
1047
 
1016
1048
  ### S3
1017
1049
 
@@ -1056,7 +1088,7 @@ const upload = await uploadSingleObject("/payouts", pdfFile, {
1056
1088
  });
1057
1089
 
1058
1090
  // upload.shared — name, resolvedType ("document" for a PDF), acl, isPrivate
1059
- // upload.variants.default — key, url, metadata
1091
+ // upload.variants.default — key, name, metadata
1060
1092
  ```
1061
1093
 
1062
1094
  Use this rather than `uploadOne` when the result is going into storage. `uploadOne` returns a `RawUploadResult`, which is what S3 returned; `UploadResult` is what an application stores, and this performs the conversion between them.
@@ -1075,10 +1107,14 @@ const uploadResult = await generateAndUploadImage("/exercises", imageFile, {
1075
1107
  });
1076
1108
 
1077
1109
  // uploadResult.shared — name, resolvedType, provider, acl, isPrivate
1078
- // uploadResult.variants — keyed object with each variant's key, url, metadata
1110
+ // uploadResult.variants — keyed object with each variant's key, name, metadata
1079
1111
  ```
1080
1112
 
1081
- Variants are keyed by `VariantName`, which is an image size (`xs`, `sm`, `md`, `lg`, `xl`) or `default` for a single-representation file.
1113
+ Variants are keyed by `VariantName`, which is an image size (`xs`, `sm`, `md`, `lg`, `xl`) or `default` for a single-representation file. An upload result is sparse: it contains only variants it actually wrote.
1114
+
1115
+ `uploadGeneratedVariants` settles all variant writes. If one fails, it deletes
1116
+ every fulfilled sibling key before rethrowing the original failure, so a failed
1117
+ generated upload does not leave partial S3 objects behind.
1082
1118
 
1083
1119
  #### `uploadGeneratedVariants(path, generated, opts?)`
1084
1120
 
@@ -1140,30 +1176,7 @@ if (!file) throw BRPCError.notFound();
1140
1176
  return file;
1141
1177
  ```
1142
1178
 
1143
- ### Local
1144
-
1145
- The `local` object provides a filesystem storage backend that mirrors the S3 API. Files are stored under `./buckets/`.
1146
-
1147
- ```ts
1148
- // Upload
1149
- const { data, error } = await local.uploadOne("public/avatars", file);
1150
- if (error) throw BRPCError.internalServerError();
1151
- // data.key, data.bytesWritten
1152
-
1153
- // Upload many
1154
- const results = await local.uploadMany("public/photos", files);
1155
-
1156
- // Get
1157
- const file = await local.getOne("public/avatars/abc.jpg");
1158
- if (!file) throw BRPCError.notFound();
1159
-
1160
- // Delete
1161
- const deleted = await local.deleteOne("public/avatars/abc.jpg");
1162
- ```
1163
-
1164
- ---
1165
-
1166
- ## Auth
1179
+ ## Auth
1167
1180
 
1168
1181
  Built-in auth service for SurrealDB. Supports email/phone sign-up and sign-in with password or OTP (passwordless), JWT sessions with refresh tokens, and cookie management.
1169
1182
 
@@ -1,4 +1,4 @@
1
1
  // @bun
2
- function u(q){return(J)=>{if(!J)return null;return`${q}/${J}`}}class V extends Error{status;code;clientCode;data;constructor(q,J,Q,N,G){super(q);this.name="BrpcClientError",this.status=J,this.code=Q,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 m 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}}function e(){return function(J,Q){if(Q)return J instanceof V&&J.clientCode===Q;return J instanceof V}}var c=(q)=>{return q instanceof File||q instanceof Blob||typeof q==="object"&&q!==null&&"uri"in q&&"name"in q&&"type"in q};function P(q){if(!q)return null;return q.replace(/\//g,"")}function l(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 p(q,J){let Q=J?`/${J}/ws`:"/ws";return`${q.replace(/^http/,"ws")}${Q}`}function i(q,J){if(q&&J)return`/${q}/${J}`;else if(q&&!J)return`/${q}`;else if(!q&&J)return`/${J}`;else return""}class v{baseUrl;ws=null;subscriptions=new Map;messageQueue=[];isConnected=!1;reconnectTimeout=null;WebSocketImpl;getHeaders;authToken=null;debug;pendingAuth=!1;authActions=[];constructor(q,J,Q,N=!1,G){this.baseUrl=q;this.WebSocketImpl=J,this.debug=N&&G==="development",this.getHeaders=async()=>{if(typeof Q==="function")return await Q();return Q??{}},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 Q of this.subscriptions.keys())this.startTopicSubscription(Q);this.authActions.push(()=>{if(this.messageQueue.length>0&&this.debug)console.log(`Sending ${this.messageQueue.length} queued messages`);this.messageQueue.forEach((Q)=>this.send(Q)),this.messageQueue=[]})}else{for(let Q of this.subscriptions.keys())this.startTopicSubscription(Q);if(this.messageQueue.length>0&&this.debug)console.log(`Sending ${this.messageQueue.length} queued messages`);this.messageQueue.forEach((Q)=>this.send(Q)),this.messageQueue=[]}},this.ws.onmessage=(Q)=>{try{if(this.debug)console.log("WebSocket message received:",Q.data);let N=JSON.parse(Q.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((w)=>w());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((w)=>w(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),w=this.subscriptions.get(N.topic);w.status="failed",w.callbacks.forEach((_)=>_(G,void 0))}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:",Q.data)}},this.ws.onclose=(Q)=>{if(this.isConnected=!1,this.pendingAuth=!1,this.subscriptions.forEach((N)=>{N.status="stale"}),this.debug)console.log(`WebSocket connection closed. Code: ${Q.code}, Reason: ${Q.reason}`);this.reconnectTimeout=setTimeout(()=>this.connect(),2000)},this.ws.onerror=(Q)=>{if(this.debug)console.log("WebSocket error:",Q)}}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 m(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 Q=JSON.stringify(q);if(this.ws.send(Q),this.debug)console.log("WebSocket message sent:",Q)}else if(this.messageQueue.push(q),this.debug)console.log("WebSocket message queued (not connected):",q)}subscribe(q,J,Q={}){let N=this.subscriptions.get(q);if(!N){if(N={auth:Q.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 Q=this.subscriptions.get(q);if(Q){if(Q.callbacks.delete(J),Q.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,Q){let N=this.subscriptions.get(q);if(N?.status!=="subscribed"){Q(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"}}),void 0);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||"",Q=J.startsWith("Bearer ")?J.substring(7).trim():J.trim(),N=Q.length>0,G=this.authToken!==null&&this.authToken.trim().length>0;if((N!==G||Q!==this.authToken)&&this.isConnected){if(this.authToken=Q,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 n(q){if(!q)return[];let J=[];return Object.keys(q).sort().forEach((Q)=>{let N=q[Q];if(N===void 0||N===null||N==="")return;J.push(Q,String(N))}),J}function Gq(q,J={}){let Q=J.fetch||fetch,N=J.WebSocket??(typeof WebSocket<"u"?WebSocket:null),G=J.prefix??"",w=J.apiPrefix??"",_=J.debug??!1,D=J.s3Endpoint,U=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 T=J.headers??{},k=async()=>{if(typeof T==="function")return await T();return T},S=l(q),g=P(J.prefix),o=P(J.apiPrefix),E=i(g,o),y=p(S,g),C=new v(y,N,k,_,U),x=async(Y=!0)=>{let R={};if(Y)R["Content-Type"]="application/json";let X=await k();return{...R,...X}},t=async(Y)=>{if(!Y.ok){let X;try{X=await Y.json()}catch{try{X={error:await Y.text()}}catch{X={error:"Failed to parse error response"}}}if(X?.error&&typeof X.error==="object"){let A=X.error;throw new V(A.message||Y.statusText,Y.status,A.code,A.clientCode,A.data)}let W=X?.error||X?.message||Y.statusText;throw new V(W,Y.status,void 0,void 0,X)}let R=Y.headers.get("Content-Type")||"";try{if(R.includes("application/json"))return(await Y.json()).data;else if(R.includes("text/"))return Y.text();else return Y.blob()}catch(X){throw new V(`Failed to parse response: ${X instanceof Error?X.message:"Unknown error"}`,Y.status,"PARSE_ERROR")}},f=async(Y,R)=>{try{let X=await Q(Y,R);return await t(X)}catch(X){if(X instanceof V)throw X;throw new V(X instanceof Error?X.message:"Network request failed",0,"NETWORK_ERROR")}},O=(Y,R)=>{return Y.split("/").map((X)=>{if(!X.startsWith(":"))return X;let W=X.slice(1),A=R?.[W];if(A===void 0||A===null)throw new V(`Missing route parameter: ${W}`,400,"MISSING_ROUTE_PARAM","MISSING_ROUTE_PARAM",{param:W});return encodeURIComponent(String(A))}).join("/")},d=(Y=[])=>{return new Proxy({},{get(R,X){if(typeof X==="symbol")return;if(X==="query"||X==="mutation"||X==="formMutation"||X==="subscription"||X==="getStringKey"||X==="getArrayKey"||X==="getNoInputsArrayKey"){let W=Y.join("/"),A=(Z)=>`${S}${E}/${Z}`;if(_)console.log(`BRPC ${X} procedure path: ${W}`);if(X==="query")return async(Z,M={})=>{let $=O(W,M.params),L=await x(!0);if(_)console.log(`BRPC query request to ${$}`,{input:Z,headers:L});let F=new URL(A($));if(Z&&typeof Z==="object")Object.entries(Z).forEach(([K,I])=>{if(I!==void 0)F.searchParams.append(K,String(I))});let z=await f(F.toString(),{method:"GET",headers:L,credentials:"include"});if(_)console.log(`BRPC query response from ${$}:`,z);return z};else if(X==="mutation")return async(Z,M={})=>{let $=O(W,M.params),L=await x(!0);if(_)console.log(`BRPC mutation request to ${$}`,{input:Z,headers:L});let F=JSON.stringify(Z??{}),z=await f(A($),{method:"POST",headers:L,body:F,credentials:"include"});if(_)console.log(`BRPC mutation response from ${$}:`,z);return z};else if(X==="formMutation")return async(Z,M={})=>{let $=O(W,M.params),L=await x(!1);if(_)console.log(`BRPC formMutation request to ${$}`,{input:Z,headers:L});let F=new FormData,z=(I,j,H)=>{let B=H?`${H}[${I}]`:I;if(j===null||j===void 0)return;else if(c(j))F.append(B,j);else if(Array.isArray(j))j.forEach((b,h)=>{z(String(h),b,B)});else if(typeof j==="object"&&j!==null)Object.entries(j).forEach(([b,h])=>{z(b,h,B)});else F.append(B,String(j))};Object.entries(Z).forEach(([I,j])=>{z(I,j)});let K=await f(A($),{method:"POST",headers:L,body:F,credentials:"include"});if(_)console.log(`BRPC formMutation response from ${$}:`,K);return K};else if(X==="subscription")return(Z,M={})=>{let $=O(W,M.params);if(_)console.log(`BRPC subscription to ${$}`);let L=E?`${E.slice(1)}/${$}`:$;return C.updateAuth(),C.subscribe(L,Z,M)};else if(X==="getStringKey")return(Z,M={})=>{let $=O(W,M.params);if(!Z||Object.keys(Z).length===0)return $;let L=Object.keys(Z).sort().reduce((z,K)=>{let I=Z[K];if(I!==void 0&&I!==null)z[K]=I;return z},{});if(Object.keys(L).length===0)return $;let F=JSON.stringify(L);return`${$}?${F}`};else if(X==="getArrayKey")return(Z,M={})=>{let $=O(W,M.params),L=n(M.keys),F=Z??{};if(Object.keys(F).length===0)return[$,...L];let z=[];if(Object.keys(F).sort().forEach((I)=>{let j=F[I];if(j!==void 0&&j!==null){let H=j;if(typeof j==="boolean"||typeof j==="number")H=String(j);else if(typeof j==="object")H=JSON.stringify(j);z.push([I,H])}}),z.length===0)return[$,...L];let K=z.flatMap(([I,j])=>[I,j]);return[$,...K,...L]};else if(X==="getNoInputsArrayKey")return(Z={})=>{return[O(W,Z.params),...n(Z.keys)]}}return d([...Y,X])}})},a=d();if(_)console.log("BRPC client created",{baseUrl:S,prefix:E,wsUrl:y});return{routes:a,storage:{getObjectUrl:u(D)},utils:{updateWsAuth:async()=>await C.updateAuth(),setHeader:async(Y,R)=>{if(typeof T==="function"){console.warn("Cannot use setHeader with function-based headers resolver");return}T={...T,[Y]:R},await C.updateAuth()},setHeaders:async(Y)=>{if(typeof T==="function"){console.warn("Cannot use setHeaders with function-based headers resolver");return}T={...T,...Y},await C.updateAuth()}}}}class s{entries=new Map;subscribe({key:q,subscription:J,options:Q,listener:N}){let G=JSON.stringify(q),w=(...D)=>N(...D),_=this.entries.get(G);if(!_){_={listeners:new Set,publish:()=>{},unsubscribe:()=>{}},this.entries.set(G,_);try{let D=J((...U)=>{_?.listeners.forEach((T)=>T(...U))},Q);_.publish=D.publish,_.unsubscribe=D.unsubscribe}catch(D){throw this.entries.delete(G),D}}return _.listeners.add(w),{publish:(D)=>{if(this.entries.get(G)===_)_.publish(D)},unsubscribe:()=>this.unsubscribe(G,_,w)}}unsubscribe(q,J,Q){if(J.listeners.delete(Q),J.listeners.size>0||this.entries.get(q)!==J)return;this.entries.delete(q),J.unsubscribe()}}export{e as isBrpcClientErrorUtility,u as createGetObjectUrl,Gq as createBrpcClient,s as SubscriptionService,m as BrpcClientSubscriptionError,V as BrpcClientError};
2
+ function u(q){return(J)=>{if(!J)return null;return`${q}/${J}`}}class M extends Error{status;code;clientCode;data;constructor(q,J,Q,N,_){super(q);this.name="BrpcClientError",this.status=J,this.code=Q,this.clientCode=N,this.data=_}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 m extends M{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}}function e(){return function(J,Q){if(Q)return J instanceof M&&J.clientCode===Q;return J instanceof M}}var c=(q)=>{return q instanceof File||q instanceof Blob||typeof q==="object"&&q!==null&&"uri"in q&&"name"in q&&"type"in q};function P(q){if(!q)return null;return q.replace(/\//g,"")}function p(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 l(q,J){let Q=J?`/${J}/ws`:"/ws";return`${q.replace(/^http/,"ws")}${Q}`}function i(q,J){if(q&&J)return`/${q}/${J}`;else if(q&&!J)return`/${q}`;else if(!q&&J)return`/${J}`;else return""}class v{baseUrl;reconnectDelayMs;ws=null;subscriptions=new Map;messageQueue=[];isConnected=!1;reconnectTimeout=null;hasConnected=!1;reconnectListeners=new Set;WebSocketImpl;getHeaders;authToken=null;debug;pendingAuth=!1;authActions=[];constructor(q,J,Q,N=!1,_,T=2000){this.baseUrl=q;this.reconnectDelayMs=T;this.WebSocketImpl=J,this.debug=N&&_==="development",this.getHeaders=async()=>{if(typeof Q==="function")return await Q();return Q??{}},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");let Q=this.hasConnected;if(this.hasConnected=!0,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 N of this.subscriptions.keys())this.startTopicSubscription(N);this.authActions.push(()=>{if(this.messageQueue.length>0&&this.debug)console.log(`Sending ${this.messageQueue.length} queued messages`);this.messageQueue.forEach((N)=>this.send(N)),this.messageQueue=[]})}else{for(let N of this.subscriptions.keys())this.startTopicSubscription(N);if(this.messageQueue.length>0&&this.debug)console.log(`Sending ${this.messageQueue.length} queued messages`);this.messageQueue.forEach((N)=>this.send(N)),this.messageQueue=[]}if(Q)queueMicrotask(()=>{this.reconnectListeners.forEach((N)=>{try{N()}catch(_){console.error("WebSocket reconnect listener failed:",_)}})})},this.ws.onmessage=(Q)=>{try{if(this.debug)console.log("WebSocket message received:",Q.data);let N=JSON.parse(Q.data);if(N.type==="auth_success"){let _=N.authenticated!==!1;if(this.debug)console.log(`WebSocket ${_?"authentication":"deauthentication"} successful`);if(this.pendingAuth=!1,_)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:_}=this.subscriptions.get(N.topic);if(this.debug)console.log("Calling subscription callbacks");_?.forEach((T)=>T(void 0,N.data))}else if(["subscribe_error","publish_error"].includes(N.type)&&N.topic&&this.subscriptions.has(N.topic)){let _=this.createSubscriptionError(N.type,N.error),T=this.subscriptions.get(N.topic);T.status="failed",T.callbacks.forEach(($)=>$(_,void 0))}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:",Q.data)}},this.ws.onclose=(Q)=>{if(this.isConnected=!1,this.pendingAuth=!1,this.subscriptions.forEach((N)=>{N.status="stale"}),this.debug)console.log(`WebSocket connection closed. Code: ${Q.code}, Reason: ${Q.reason}`);this.reconnectTimeout=setTimeout(()=>this.connect(),this.reconnectDelayMs)},this.ws.onerror=(Q)=>{if(this.debug)console.log("WebSocket error:",Q)}}catch(q){if(this.debug)console.log("Failed to create WebSocket connection:",q);this.pendingAuth=!1,this.reconnectTimeout=setTimeout(()=>this.connect(),this.reconnectDelayMs)}}createClientError(q){return new M(q?.message??"WebSocket request failed",q?.httpStatus??0,q?.code,q?.clientCode,q?.data)}createSubscriptionError(q,J){return new m(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(),this.reconnectListeners.clear()}onReconnect(q){return this.reconnectListeners.add(q),()=>this.reconnectListeners.delete(q)}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 Q=JSON.stringify(q);if(this.ws.send(Q),this.debug)console.log("WebSocket message sent:",Q)}else if(this.messageQueue.push(q),this.debug)console.log("WebSocket message queued (not connected):",q)}subscribe(q,J,Q={}){let N=this.subscriptions.get(q);if(!N){if(N={auth:Q.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:(_)=>this.publish(q,_,J)}}unsubscribe(q,J){let Q=this.subscriptions.get(q);if(Q){if(Q.callbacks.delete(J),Q.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,Q){let N=this.subscriptions.get(q);if(N?.status!=="subscribed"){Q(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"}}),void 0);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||"",Q=J.startsWith("Bearer ")?J.substring(7).trim():J.trim(),N=Q.length>0,_=this.authToken!==null&&this.authToken.trim().length>0;if((N!==_||Q!==this.authToken)&&this.isConnected){if(this.authToken=Q,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 n(q){if(!q)return[];let J=[];return Object.keys(q).sort().forEach((Q)=>{let N=q[Q];if(N===void 0||N===null||N==="")return;J.push(Q,String(N))}),J}function Gq(q,J={}){let Q=J.fetch||fetch,N=J.WebSocket??(typeof WebSocket<"u"?WebSocket:null),_=J.prefix??"",T=J.apiPrefix??"",$=J.debug??!1,D=J.s3Endpoint,U=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??{},k=async()=>{if(typeof w==="function")return await w();return w},S=p(q),g=P(J.prefix),o=P(J.apiPrefix),E=i(g,o),y=l(S,g),H=new v(y,N,k,$,U),x=async(Y=!0)=>{let R={};if(Y)R["Content-Type"]="application/json";let X=await k();return{...R,...X}},t=async(Y)=>{if(!Y.ok){let X;try{X=await Y.json()}catch{try{X={error:await Y.text()}}catch{X={error:"Failed to parse error response"}}}if(X?.error&&typeof X.error==="object"){let A=X.error;throw new M(A.message||Y.statusText,Y.status,A.code,A.clientCode,A.data)}let W=X?.error||X?.message||Y.statusText;throw new M(W,Y.status,void 0,void 0,X)}let R=Y.headers.get("Content-Type")||"";try{if(R.includes("application/json"))return(await Y.json()).data;else if(R.includes("text/"))return Y.text();else return Y.blob()}catch(X){throw new M(`Failed to parse response: ${X instanceof Error?X.message:"Unknown error"}`,Y.status,"PARSE_ERROR")}},f=async(Y,R)=>{try{let X=await Q(Y,R);return await t(X)}catch(X){if(X instanceof M)throw X;throw new M(X instanceof Error?X.message:"Network request failed",0,"NETWORK_ERROR")}},O=(Y,R)=>{return Y.split("/").map((X)=>{if(!X.startsWith(":"))return X;let W=X.slice(1),A=R?.[W];if(A===void 0||A===null)throw new M(`Missing route parameter: ${W}`,400,"MISSING_ROUTE_PARAM","MISSING_ROUTE_PARAM",{param:W});return encodeURIComponent(String(A))}).join("/")},d=(Y=[])=>{return new Proxy({},{get(R,X){if(typeof X==="symbol")return;if(X==="query"||X==="mutation"||X==="formMutation"||X==="subscription"||X==="getStringKey"||X==="getArrayKey"||X==="getNoInputsArrayKey"){let W=Y.join("/"),A=(Z)=>`${S}${E}/${Z}`;if($)console.log(`BRPC ${X} procedure path: ${W}`);if(X==="query")return async(Z,V={})=>{let j=O(W,V.params),L=await x(!0);if($)console.log(`BRPC query request to ${j}`,{input:Z,headers:L});let F=new URL(A(j));if(Z&&typeof Z==="object")Object.entries(Z).forEach(([K,I])=>{if(I!==void 0)F.searchParams.append(K,String(I))});let z=await f(F.toString(),{method:"GET",headers:L,credentials:"include"});if($)console.log(`BRPC query response from ${j}:`,z);return z};else if(X==="mutation")return async(Z,V={})=>{let j=O(W,V.params),L=await x(!0);if($)console.log(`BRPC mutation request to ${j}`,{input:Z,headers:L});let F=JSON.stringify(Z??{}),z=await f(A(j),{method:"POST",headers:L,body:F,credentials:"include"});if($)console.log(`BRPC mutation response from ${j}:`,z);return z};else if(X==="formMutation")return async(Z,V={})=>{let j=O(W,V.params),L=await x(!1);if($)console.log(`BRPC formMutation request to ${j}`,{input:Z,headers:L});let F=new FormData,z=(I,G,C)=>{let B=C?`${C}[${I}]`:I;if(G===null||G===void 0)return;else if(c(G))F.append(B,G);else if(Array.isArray(G))G.forEach((b,h)=>{z(String(h),b,B)});else if(typeof G==="object"&&G!==null)Object.entries(G).forEach(([b,h])=>{z(b,h,B)});else F.append(B,String(G))};Object.entries(Z).forEach(([I,G])=>{z(I,G)});let K=await f(A(j),{method:"POST",headers:L,body:F,credentials:"include"});if($)console.log(`BRPC formMutation response from ${j}:`,K);return K};else if(X==="subscription")return(Z,V={})=>{let j=O(W,V.params);if($)console.log(`BRPC subscription to ${j}`);let L=E?`${E.slice(1)}/${j}`:j;return H.updateAuth(),H.subscribe(L,Z,V)};else if(X==="getStringKey")return(Z,V={})=>{let j=O(W,V.params);if(!Z||Object.keys(Z).length===0)return j;let L=Object.keys(Z).sort().reduce((z,K)=>{let I=Z[K];if(I!==void 0&&I!==null)z[K]=I;return z},{});if(Object.keys(L).length===0)return j;let F=JSON.stringify(L);return`${j}?${F}`};else if(X==="getArrayKey")return(Z,V={})=>{let j=O(W,V.params),L=n(V.keys),F=Z??{};if(Object.keys(F).length===0)return[j,...L];let z=[];if(Object.keys(F).sort().forEach((I)=>{let G=F[I];if(G!==void 0&&G!==null){let C=G;if(typeof G==="boolean"||typeof G==="number")C=String(G);else if(typeof G==="object")C=JSON.stringify(G);z.push([I,C])}}),z.length===0)return[j,...L];let K=z.flatMap(([I,G])=>[I,G]);return[j,...K,...L]};else if(X==="getNoInputsArrayKey")return(Z={})=>{return[O(W,Z.params),...n(Z.keys)]}}return d([...Y,X])}})},a=d();if($)console.log("BRPC client created",{baseUrl:S,prefix:E,wsUrl:y});return{routes:a,storage:{getObjectUrl:u(D)},utils:{updateWsAuth:async()=>await H.updateAuth(),onWsReconnect:(Y)=>H.onReconnect(Y),setHeader:async(Y,R)=>{if(typeof w==="function"){console.warn("Cannot use setHeader with function-based headers resolver");return}w={...w,[Y]:R},await H.updateAuth()},setHeaders:async(Y)=>{if(typeof w==="function"){console.warn("Cannot use setHeaders with function-based headers resolver");return}w={...w,...Y},await H.updateAuth()}}}}class s{entries=new Map;subscribe({key:q,subscription:J,options:Q,listener:N}){let _=JSON.stringify(q),T=(...D)=>N(...D),$=this.entries.get(_);if(!$){$={listeners:new Set,publish:()=>{},unsubscribe:()=>{}},this.entries.set(_,$);try{let D=J((...U)=>{$?.listeners.forEach((w)=>w(...U))},Q);$.publish=D.publish,$.unsubscribe=D.unsubscribe}catch(D){throw this.entries.delete(_),D}}return $.listeners.add(T),{publish:(D)=>{if(this.entries.get(_)===$)$.publish(D)},unsubscribe:()=>this.unsubscribe(_,$,T)}}unsubscribe(q,J,Q){if(J.listeners.delete(Q),J.listeners.size>0||this.entries.get(q)!==J)return;this.entries.delete(q),J.unsubscribe()}}export{e as isBrpcClientErrorUtility,u as createGetObjectUrl,Gq as createBrpcClient,s as SubscriptionService,m as BrpcClientSubscriptionError,M as BrpcClientError};
3
3
 
4
- //# debugId=A332861D5D3C841F64756E2164756E21
4
+ //# debugId=0D9F302BFC80CAE164756E2164756E21
@@ -1 +1,2 @@
1
1
  export * from "./use-subscription";
2
+ export * from "./use-ws-reconnect";
@@ -1,4 +1,4 @@
1
1
  // @bun
2
- import{useCallback as D,useEffect as K,useRef as F}from"react";function O(G){return function({procedure:q,options:j,callback:w,enabled:z=!0}){let A=q.getNoInputsArrayKey(j),H=JSON.stringify(A),B=F(w),x=F(null);B.current=w;let I=D((...m)=>B.current(...m),[]);return K(()=>{if(!z){x.current=null;return}let{publish:m,unsubscribe:J}=G.subscribe({key:A,subscription:q.subscription,options:j,listener:I});return x.current=m,()=>{x.current=null,J()}},[z,H,j.auth]),{publish:D((m)=>{x.current?.(m)},[])}}}export{O as createUseSubscription};
2
+ import{useCallback as G,useEffect as M,useRef as H}from"react";function T(w){return function({procedure:j,options:x,callback:z,enabled:A=!0}){let D=j.getNoInputsArrayKey(x),J=JSON.stringify(D),F=H(z),q=H(null);F.current=z;let K=G((...m)=>F.current(...m),[]);return M(()=>{if(!A){q.current=null;return}let{publish:m,unsubscribe:L}=w.subscribe({key:D,subscription:j.subscription,options:x,listener:K});return q.current=m,()=>{q.current=null,L()}},[A,J,x.auth]),{publish:G((m)=>{q.current?.(m)},[])}}}import{useEffect as N,useRef as O}from"react";function X(w){return function(j){let x=O(j);x.current=j,N(()=>{return w.utils.onWsReconnect(()=>x.current())},[])}}export{T as createUseSubscription,X as createUseOnWsReconnect};
3
3
 
4
- //# debugId=1F36C573C33D7C1C64756E2164756E21
4
+ //# debugId=6911078E96D70E4164756E2164756E21
@@ -0,0 +1,10 @@
1
+ type WsReconnectSource = {
2
+ utils: {
3
+ onWsReconnect: (listener: () => void) => () => void;
4
+ };
5
+ };
6
+ /**
7
+ * Binds a client-scoped WebSocket reconnect listener to a component lifetime.
8
+ */
9
+ export declare function createUseOnWsReconnect(client: WsReconnectSource): (callback: () => void) => void;
10
+ export {};
@@ -137,6 +137,7 @@ export type BrpcClient<T> = {
137
137
  };
138
138
  utils: {
139
139
  updateWsAuth: () => Promise<void>;
140
+ onWsReconnect: (listener: () => void) => () => void;
140
141
  setHeader: (key: string, value: string) => Promise<void>;
141
142
  setHeaders: (headers: Record<string, string>) => Promise<void>;
142
143
  };
@@ -1,18 +1,22 @@
1
1
  import type { HeadersResolver, SubscriptionCallback, RuntimeSubscriptionOptions } from "./types";
2
+ type WsReconnectListener = () => void;
2
3
  declare class WebSocketManager {
3
4
  private baseUrl;
5
+ private reconnectDelayMs;
4
6
  private ws;
5
7
  private subscriptions;
6
8
  private messageQueue;
7
9
  private isConnected;
8
10
  private reconnectTimeout;
11
+ private hasConnected;
12
+ private reconnectListeners;
9
13
  private WebSocketImpl;
10
14
  private getHeaders;
11
15
  private authToken;
12
16
  private debug;
13
17
  private pendingAuth;
14
18
  private authActions;
15
- constructor(baseUrl: string, WebSocketImpl: typeof WebSocket, headersResolver: HeadersResolver, debug: boolean | undefined, nodeEnv: string);
19
+ constructor(baseUrl: string, WebSocketImpl: typeof WebSocket, headersResolver: HeadersResolver, debug: boolean | undefined, nodeEnv: string, reconnectDelayMs?: number);
16
20
  /**
17
21
  * Sends a topic subscription when its authentication requirement is met.
18
22
  */
@@ -21,6 +25,11 @@ declare class WebSocketManager {
21
25
  private createClientError;
22
26
  private createSubscriptionError;
23
27
  disconnect(): void;
28
+ /**
29
+ * Runs after the socket reconnects following a prior connection. The initial
30
+ * connection does not notify listeners.
31
+ */
32
+ onReconnect(listener: WsReconnectListener): () => boolean;
24
33
  send(message: any, options?: {
25
34
  allowDuringPendingAuth?: boolean;
26
35
  }): void;
@@ -1,7 +1,7 @@
1
1
  // @bun
2
- var z=($)=>$.startsWith("image/"),T=($)=>$.startsWith("video/"),w=($)=>$.startsWith("audio/"),I=($)=>$.startsWith("text/")||$.includes("pdf")||$.includes("word")||$.includes("sheet")||$.includes("presentation")||$.includes("opendocument")||$==="application/rtf",k=($)=>$.includes("zip")||$.includes("rar")||$.includes("tar")||$.includes("7z")||$.includes("gzip"),R=($)=>$==="application/javascript"||$==="application/json"||$==="text/html"||$==="text/css"||$==="application/xml"||$.includes("javascript"),b=($)=>$.startsWith("font/")||$.includes("font"),v=($)=>{if(!$)return"other";let W=$.toLowerCase();if(z(W))return"image";if(T(W))return"video";if(w(W))return"audio";if(I(W))return"document";if(k(W))return"archive";if(R(W))return"code";if(b(W))return"font";return"other"},O={isImage:z,isVideo:T,isAudio:w,isDocument:I,isArchive:k,isCode:R,isFont:b};function e(){let W=Buffer.from("Hello, this is a test file for S3 upload!","utf-8");return new File([W],"test.txt",{type:"text/plain"})}function U($,W){if(!$||$<1||!W||W<1)return"1:1";let H=(Q,Y)=>Y===0?Q:H(Y,Q%Y),J=H($,W);return`${$/J}:${W/J}`}function W0($,W){if(!$||!W||$<1||W<1)return"1:1";let H=$/W,J=[{ratio:1,label:"1:1"},{ratio:0.8,label:"4:5"},{ratio:0.75,label:"3:4"},{ratio:0.6666666666666666,label:"2:3"},{ratio:0.5625,label:"9:16"},{ratio:1.5,label:"3:2"},{ratio:1.3333333333333333,label:"4:3"},{ratio:1.7777777777777777,label:"16:9"},{ratio:2.3333333333333335,label:"21:9"}],Q=0.03;for(let{ratio:X,label:K}of J)if(Math.abs(H-X)/X<Q)return K;let Y=(X,K)=>K===0?X:Y(K,X%K),j=Y($,W);return`${$/j}:${W/j}`}class L 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=L.STATUS_MAP[$.code],this.data=$.data,this.cause=$.cause,Error.captureStackTrace)Error.captureStackTrace(this,L)}toJSON(){return{name:this.name,code:this.code,clientCode:this.clientCode,message:this.message,data:this.data,httpStatus:this.httpStatus}}static badRequest($,W,H){return new L({code:"BAD_REQUEST",message:$,clientCode:W,data:H})}static unauthorized($="Unauthorized",W,H){return new L({code:"UNAUTHORIZED",message:$,clientCode:W,data:H})}static forbidden($="Forbidden",W,H){return new L({code:"FORBIDDEN",message:$,clientCode:W,data:H})}static notFound($="Not Found",W,H){return new L({code:"NOT_FOUND",message:$,clientCode:W,data:H})}static preconditionFailed($="Precondition failed",W,H){//! Intentionally maps to NOT_FOUND instead of PRECONDITION_FAILED — this was changed to fix a bug
2
+ var N=($)=>$.startsWith("image/"),z=($)=>$.startsWith("video/"),T=($)=>$.startsWith("audio/"),w=($)=>$.startsWith("text/")||$.includes("pdf")||$.includes("word")||$.includes("sheet")||$.includes("presentation")||$.includes("opendocument")||$==="application/rtf",I=($)=>$.includes("zip")||$.includes("rar")||$.includes("tar")||$.includes("7z")||$.includes("gzip"),k=($)=>$==="application/javascript"||$==="application/json"||$==="text/html"||$==="text/css"||$==="application/xml"||$.includes("javascript"),R=($)=>$.startsWith("font/")||$.includes("font"),b=($)=>{if(!$)return"other";let W=$.toLowerCase();if(N(W))return"image";if(z(W))return"video";if(T(W))return"audio";if(w(W))return"document";if(I(W))return"archive";if(k(W))return"code";if(R(W))return"font";return"other"},U={isImage:N,isVideo:z,isAudio:T,isDocument:w,isArchive:I,isCode:k,isFont:R};function e(){let W=Buffer.from("Hello, this is a test file for S3 upload!","utf-8");return new File([W],"test.txt",{type:"text/plain"})}function O($,W){if(!$||$<1||!W||W<1)return"1:1";let H=(Q,Y)=>Y===0?Q:H(Y,Q%Y),J=H($,W);return`${$/J}:${W/J}`}function W0($,W){if(!$||!W||$<1||W<1)return"1:1";let H=$/W,J=[{ratio:1,label:"1:1"},{ratio:0.8,label:"4:5"},{ratio:0.75,label:"3:4"},{ratio:0.6666666666666666,label:"2:3"},{ratio:0.5625,label:"9:16"},{ratio:1.5,label:"3:2"},{ratio:1.3333333333333333,label:"4:3"},{ratio:1.7777777777777777,label:"16:9"},{ratio:2.3333333333333335,label:"21:9"}],Q=0.03;for(let{ratio:X,label:K}of J)if(Math.abs(H-X)/X<Q)return K;let Y=(X,K)=>K===0?X:Y(K,X%K),j=Y($,W);return`${$/j}:${W/j}`}class L 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=L.STATUS_MAP[$.code],this.data=$.data,this.cause=$.cause,Error.captureStackTrace)Error.captureStackTrace(this,L)}toJSON(){return{name:this.name,code:this.code,clientCode:this.clientCode,message:this.message,data:this.data,httpStatus:this.httpStatus}}static badRequest($,W,H){return new L({code:"BAD_REQUEST",message:$,clientCode:W,data:H})}static unauthorized($="Unauthorized",W,H){return new L({code:"UNAUTHORIZED",message:$,clientCode:W,data:H})}static forbidden($="Forbidden",W,H){return new L({code:"FORBIDDEN",message:$,clientCode:W,data:H})}static notFound($="Not Found",W,H){return new L({code:"NOT_FOUND",message:$,clientCode:W,data:H})}static preconditionFailed($="Precondition failed",W,H){//! 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 L({code:"NOT_FOUND",message:$,clientCode:W,data:H})}static conflict($,W,H){return new L({code:"CONFLICT",message:$,clientCode:W,data:H})}static unprocessableContent($,W,H){return new L({code:"UNPROCESSABLE_CONTENT",message:$,clientCode:W,data:H})}static tooManyRequests($="Too many requests",W,H){return new L({code:"TOO_MANY_REQUESTS",message:$,clientCode:W,data:H})}static internalServerError($="Internal Server Error",W,H){return new L({code:"INTERNAL_SERVER_ERROR",message:$,clientCode:W,data:H})}static timeout($="Request timeout",W,H){return new L({code:"TIMEOUT",message:$,clientCode:W,data:H})}}import m from"sharp";var P={xs:200,sm:400,md:800,lg:1200,xl:1920},A=1920;async function V($,W,H,J,Q){try{let{data:Y,info:j}=await m($,{limitInputPixels:1e8}).rotate().resize({width:W,height:W,fit:"inside",withoutEnlargement:!0}).webp({quality:H}).toBuffer({resolveWithObject:!0});return{buffer:Y,object:{name:J.replace(/\.[^/.]+$/,".webp"),resolvedType:"image",metadata:{type:"image/webp",size:Y.length,extension:".webp",width:j.width,height:j.height,aspectRatio:j.width&&j.height?j.width/j.height:1,aspectRatioStr:j.width&&j.height?U(j.width,j.height):"1:1",acl:Q}}}}catch(Y){throw L.conflict(`Failed to optimize image: ${Y instanceof Error?Y.message:"Unknown error"}`)}}async function E($){if(!$)throw L.conflict("A file must be provided");let W,H="unknown.webp";if(typeof $==="string"){if(console.log("I'm going to fail here for file",$),!$.startsWith("data:"))throw console.log("I never got here"),L.conflict("Invalid base64 format");let[,J]=$.split(",");if(!J)throw L.conflict("Invalid base64 data");W=Buffer.from(J,"base64")}else{if(!O.isImage($.type))throw L.conflict("File must be an image");let J=await $.arrayBuffer();W=Buffer.from(J),H=$.name??H}return{buffer:W,name:H}}async function K0($,{size:W,quality:H,acl:J}={}){let Q=await E($);return V(Q.buffer,W??A,H??80,Q.name,J??"public-read")}async function M0($,{name:W="unknown.webp",size:H,quality:J,acl:Q}={}){if(!$?.length)throw L.conflict("A buffer must be provided");return V($,H??A,J??80,W,Q??"public-read")}async function B($,{quality:W=80,acl:H="public-read"}={}){let J=await E($),Q=await Promise.all(Object.entries(P).map(async([Y,j])=>{let X=await V(J.buffer,j,W,J.name,H);return[Y,X]}));return{variants:Object.fromEntries(Q)}}import p from"sharp";import{randomUUID as y}from"crypto";import{extname as u}from"path";async function C($,W,H){if(!W)return{data:null,error:Error("File not found")};let J=$.replace(/^\/+|\/+$/g,""),Q=u(W.name),Y=`${y()}${Q}`,j=`${J}/${Y}`,X=`./buckets/${J}/${Y}`;try{let K=await Bun.write(X,W);return{data:{key:j,file:W,bytesWritten:K},error:null}}catch(K){return{data:null,error:Error(`Failed to upload ${W.name} to local filesystem`)}}}async function f($,W,H){if(!W||W.length===0)return[];let J=W.map((Y)=>{let j=H?.(Y)??Y.name;return C($,Y,j)});return await Promise.all(J)}async function d($){let W=Bun.file(`./buckets/${$}`);if(!await W.exists())return null;return W}async function n($){let W=Bun.file(`./buckets/${$}`);if(!await W.exists())return!1;return await W.delete(),!0}var S={uploadOne:C,uploadMany:f,getOne:d,deleteOne:n};async function A0($){let W=`./cache/images/${$.key}_${$.width}_${$.quality}.webp`,H=await S.getOne(W);if(H){if(!O.isImage(H.type))return null;return H}let J=$.key,Q=await S.getOne(J);if(!Q)return null;if(!O.isImage(Q.type))return null;let Y=await Q.arrayBuffer(),j=await p(Y).resize($.width,null,{withoutEnlargement:!0}).webp({quality:$.quality??75}).toBuffer();await Bun.write(W,j);let X=Bun.file(W);if(!await X.exists())return null;return X}import s from"sharp";async function N0($){if(!$?.length)return null;try{let W=await s($).metadata(),H=W.width??0,J=W.height??0,Q=W.height>0?H/J:1;return{type:`image/${W.format}`,size:W.size??0,extension:`.${W.format}`,width:H,height:J,aspectRatio:Q,aspectRatioStr:U(H,J)}}catch(W){return console.warn("Failed to read image metadata:",W),null}}var g=process.env.AWS_FOLDER,h=g?`${g}/`:"";var{s3:r}=globalThis.Bun;import{extname as t}from"path";function F($={}){return{default:void 0,xs:void 0,sm:void 0,md:void 0,lg:void 0,xl:void 0,...$}}var{s3:l}=globalThis.Bun;async function o($,W){if(!$||typeof $!=="string")throw L.badRequest("Key is required");let H=$.trim();if(!H||H.length===0)throw L.badRequest("Key must be a non-empty string");try{return await l.delete($),{key:$,deleted:!0,error:null}}catch(J){return{key:$,deleted:!1,error:J}}}async function c($,W){return(await Promise.allSettled($.map((J)=>o(J,W)))).map((J,Q)=>{if(J.status==="fulfilled")return J.value;return{key:$[Q],deleted:!1,error:J.reason}})}async function N($,W,H){let J="buffer"in W?W:{buffer:W,object:{name:W.name,resolvedType:v(W.type),metadata:{type:W.type,extension:t(W.name),size:W.size,acl:H?.acl??"public-read"}}};if("buffer"in W){if(!W.object?.name||!W.object?.metadata)throw L.badRequest("Complete metadata required when uploading buffer")}let Q=$.length>1?`${$.replace(/^\/+|\/+$/g,"")}/`:"",Y=J.object.metadata.extension.startsWith(".")?J.object.metadata.extension:`.${J.object.metadata.extension}`,j=J.object.name?J.object.name:`${crypto.randomUUID()}${Y}`,X=`${h}${Q}${j}`,K=r.file(X),_=J?.object?.metadata?.acl??"public-read",Z=_!=="public-read"&&_!=="public-read-write",x=await K.write(J.buffer,{acl:_,type:J.object.metadata.type});return{uploadedBy:H?.uploadedBy,key:X,name:J.object.name,resolvedType:J.object.resolvedType,provider:"s3",isPrivate:Z,metadata:{...J.object.metadata,size:x,acl:_},isActive:!0}}async function c0($,W,H={throwIf:"any"}){let J=await Promise.allSettled(W.map((j)=>N($,j,{acl:H?.acl,uploadedBy:H?.uploadedBy}))),Q=[],Y=[];if(J.forEach((j)=>{if(j.status==="fulfilled"){if(Q.push(j.value),H?.externalKeys)H.externalKeys.push(j.value.key)}else Y.push(j.reason)}),H?.throwIf==="any"&&Y.length>0||H?.throwIf==="all"&&Y.length===W.length)throw L.internalServerError(`Upload failed: ${Y.length} of ${W.length}`);return{data:Q,errors:Y.length?Y:void 0}}async function i($,W,H){let J=H?.baseId??crypto.randomUUID(),Q=Object.values(W.variants).find(Boolean),Y=await Promise.allSettled(Object.entries(W.variants).map(async([Z,x])=>{if(!x)return[Z,null];let G=x.object.metadata.extension,D=`${J}_${Z}${G}`,q=await N($,{...x,object:{...x.object,name:D}},H);if(!q.metadata)throw L.internalServerError("Upload returned no metadata, cannot build a storage variant");let M=q.metadata;return[Z,{key:q.key,name:D,metadata:{type:M.type,extension:M.extension,size:M.size,hash:M.hash,width:M.width,height:M.height,aspectRatio:M.aspectRatio,aspectRatioStr:M.aspectRatioStr,resolution:M.resolution,bitrate:M.bitrate,codec:M.codec,fps:M.fps}}]})),j=Y.flatMap((Z)=>Z.status==="fulfilled"?[Z.value]:[]),X=Y.flatMap((Z)=>Z.status==="rejected"?[Z.reason]:[]);if(X.length>0){let Z=j.flatMap(([,D])=>D?.key?[D.key]:[]),G=(await c(Z)).filter(({deleted:D})=>!D);if(G.length>0)console.error("Failed to clean up partially uploaded image variants",{keys:Z,failures:G});throw X[0]}let K=(H?.acl??"public-read")!=="public-read"&&(H?.acl??"public-read")!=="public-read-write",_=F();for(let[Z,x]of j)if(x)_[Z]=x;return{shared:{name:Q.object.name,resolvedType:Q.object.resolvedType,provider:"s3",acl:H?.acl??"public-read",isPrivate:K,uploadedBy:H?.uploadedBy},variants:_}}async function m0($,W,H){let J=await N($,W,H);if(!J.metadata)throw L.internalServerError("Upload returned no metadata, cannot build a storage object");let Q=J.name??J.key.split("/").pop()??J.key;return{shared:{name:Q,resolvedType:J.resolvedType,provider:J.provider,acl:J.metadata.acl,isPrivate:J.isPrivate,uploadedBy:H?.uploadedBy},variants:F({default:{key:J.key,name:Q,metadata:{type:J.metadata.type,extension:J.metadata.extension,size:J.metadata.size,hash:J.metadata.hash,width:J.metadata.width,height:J.metadata.height,aspectRatio:J.metadata.aspectRatio,aspectRatioStr:J.metadata.aspectRatioStr,resolution:J.metadata.resolution,bitrate:J.metadata.bitrate,codec:J.metadata.codec,fps:J.metadata.fps}}})}}async function y0($,W,H){if(Array.isArray(W))throw Error("File can't be an array, pass a single file");let J=await B(W,{quality:H?.quality,acl:H?.acl});return i($,J,{acl:H?.acl,uploadedBy:H?.uploadedBy})}var{s3:a}=globalThis.Bun;async function d0($,W={debug:!0}){try{return{exists:await a.exists($),error:null}}catch(H){if(W?.debug)console.error("There was an error checking for file existance",H);return{exists:null,error:H}}}export{m0 as uploadSingleObject,N as uploadOne,c0 as uploadMany,i as uploadGeneratedVariants,M0 as optimizeImageBuffer,K0 as optimizeImage,v as mimeTypeToResolvedType,A0 as getOptimizedImage,N0 as getImageMetadata,W0 as getAspectRatioStringRound,U as getAspectRatioString,B as generateVariants,e as generateTestFile,y0 as generateAndUploadImage,O as fileTypePredicates,o as deleteOne,c as deleteMany,F as createUploadVariants,d0 as checkFileExistance};
5
+ return new L({code:"NOT_FOUND",message:$,clientCode:W,data:H})}static conflict($,W,H){return new L({code:"CONFLICT",message:$,clientCode:W,data:H})}static unprocessableContent($,W,H){return new L({code:"UNPROCESSABLE_CONTENT",message:$,clientCode:W,data:H})}static tooManyRequests($="Too many requests",W,H){return new L({code:"TOO_MANY_REQUESTS",message:$,clientCode:W,data:H})}static internalServerError($="Internal Server Error",W,H){return new L({code:"INTERNAL_SERVER_ERROR",message:$,clientCode:W,data:H})}static timeout($="Request timeout",W,H){return new L({code:"TIMEOUT",message:$,clientCode:W,data:H})}}import m from"sharp";var v={xs:200,sm:400,md:800,lg:1200,xl:1920},V=1920;async function A($,W,H,J,Q){try{let{data:Y,info:j}=await m($,{limitInputPixels:1e8}).rotate().resize({width:W,height:W,fit:"inside",withoutEnlargement:!0}).webp({quality:H}).toBuffer({resolveWithObject:!0});return{buffer:Y,object:{name:J.replace(/\.[^/.]+$/,".webp"),resolvedType:"image",metadata:{type:"image/webp",size:Y.length,extension:".webp",width:j.width,height:j.height,aspectRatio:j.width&&j.height?j.width/j.height:1,aspectRatioStr:j.width&&j.height?O(j.width,j.height):"1:1",acl:Q}}}}catch(Y){throw L.conflict(`Failed to optimize image: ${Y instanceof Error?Y.message:"Unknown error"}`)}}async function P($){if(!$)throw L.conflict("A file must be provided");let W,H="unknown.webp";if(typeof $==="string"){if(console.log("I'm going to fail here for file",$),!$.startsWith("data:"))throw console.log("I never got here"),L.conflict("Invalid base64 format");let[,J]=$.split(",");if(!J)throw L.conflict("Invalid base64 data");W=Buffer.from(J,"base64")}else{if(!U.isImage($.type))throw L.conflict("File must be an image");let J=await $.arrayBuffer();W=Buffer.from(J),H=$.name??H}return{buffer:W,name:H}}async function K0($,{size:W,quality:H,acl:J}={}){let Q=await P($);return A(Q.buffer,W??V,H??80,Q.name,J??"public-read")}async function M0($,{name:W="unknown.webp",size:H,quality:J,acl:Q}={}){if(!$?.length)throw L.conflict("A buffer must be provided");return A($,H??V,J??80,W,Q??"public-read")}async function E($,{quality:W=80,acl:H="public-read"}={}){let J=await P($),Q=await Promise.all(Object.entries(v).map(async([Y,j])=>{let X=await A(J.buffer,j,W,J.name,H);return[Y,X]}));return{variants:Object.fromEntries(Q)}}import p from"sharp";import{randomUUID as y}from"crypto";import{extname as u}from"path";async function B($,W,H){if(!W)return{data:null,error:Error("File not found")};let J=$.replace(/^\/+|\/+$/g,""),Q=u(W.name),Y=`${y()}${Q}`,j=`${J}/${Y}`,X=`./buckets/${J}/${Y}`;try{let K=await Bun.write(X,W);return{data:{key:j,file:W,bytesWritten:K},error:null}}catch(K){return{data:null,error:Error(`Failed to upload ${W.name} to local filesystem`)}}}async function f($,W,H){if(!W||W.length===0)return[];let J=W.map((Y)=>{let j=H?.(Y)??Y.name;return B($,Y,j)});return await Promise.all(J)}async function d($){let W=Bun.file(`./buckets/${$}`);if(!await W.exists())return null;return W}async function n($){let W=Bun.file(`./buckets/${$}`);if(!await W.exists())return!1;return await W.delete(),!0}var S={uploadOne:B,uploadMany:f,getOne:d,deleteOne:n};async function V0($){let W=`./cache/images/${$.key}_${$.width}_${$.quality}.webp`,H=await S.getOne(W);if(H){if(!U.isImage(H.type))return null;return H}let J=$.key,Q=await S.getOne(J);if(!Q)return null;if(!U.isImage(Q.type))return null;let Y=await Q.arrayBuffer(),j=await p(Y).resize($.width,null,{withoutEnlargement:!0}).webp({quality:$.quality??75}).toBuffer();await Bun.write(W,j);let X=Bun.file(W);if(!await X.exists())return null;return X}import s from"sharp";async function N0($){if(!$?.length)return null;try{let W=await s($).metadata(),H=W.width??0,J=W.height??0,Q=W.height>0?H/J:1;return{type:`image/${W.format}`,size:W.size??0,extension:`.${W.format}`,width:H,height:J,aspectRatio:Q,aspectRatioStr:O(H,J)}}catch(W){return console.warn("Failed to read image metadata:",W),null}}var C=process.env.AWS_FOLDER,g=C?`${C}/`:"";var{s3:r}=globalThis.Bun;import{extname as t}from"path";function c($={}){return $}var{s3:l}=globalThis.Bun;async function o($,W){if(!$||typeof $!=="string")throw L.badRequest("Key is required");let H=$.trim();if(!H||H.length===0)throw L.badRequest("Key must be a non-empty string");try{return await l.delete($),{key:$,deleted:!0,error:null}}catch(J){return{key:$,deleted:!1,error:J}}}async function h($,W){return(await Promise.allSettled($.map((J)=>o(J,W)))).map((J,Q)=>{if(J.status==="fulfilled")return J.value;return{key:$[Q],deleted:!1,error:J.reason}})}async function F($,W,H){let J="buffer"in W?W:{buffer:W,object:{name:W.name,resolvedType:b(W.type),metadata:{type:W.type,extension:t(W.name),size:W.size,acl:H?.acl??"public-read"}}};if("buffer"in W){if(!W.object?.name||!W.object?.metadata)throw L.badRequest("Complete metadata required when uploading buffer")}let Q=$.length>1?`${$.replace(/^\/+|\/+$/g,"")}/`:"",Y=J.object.metadata.extension.startsWith(".")?J.object.metadata.extension:`.${J.object.metadata.extension}`,j=J.object.name?J.object.name:`${crypto.randomUUID()}${Y}`,X=`${g}${Q}${j}`,K=r.file(X),_=J?.object?.metadata?.acl??"public-read",Z=_!=="public-read"&&_!=="public-read-write",x=await K.write(J.buffer,{acl:_,type:J.object.metadata.type});return{uploadedBy:H?.uploadedBy,key:X,name:J.object.name,resolvedType:J.object.resolvedType,provider:"s3",isPrivate:Z,metadata:{...J.object.metadata,size:x,acl:_},isActive:!0}}async function h0($,W,H={throwIf:"any"}){let J=await Promise.allSettled(W.map((j)=>F($,j,{acl:H?.acl,uploadedBy:H?.uploadedBy}))),Q=[],Y=[];if(J.forEach((j)=>{if(j.status==="fulfilled"){if(Q.push(j.value),H?.externalKeys)H.externalKeys.push(j.value.key)}else Y.push(j.reason)}),H?.throwIf==="any"&&Y.length>0||H?.throwIf==="all"&&Y.length===W.length)throw L.internalServerError(`Upload failed: ${Y.length} of ${W.length}`);return{data:Q,errors:Y.length?Y:void 0}}async function i($,W,H){let J=H?.baseId??crypto.randomUUID(),Q=Object.values(W.variants).find(Boolean),Y=await Promise.allSettled(Object.entries(W.variants).map(async([Z,x])=>{if(!x)return[Z,null];let G=x.object.metadata.extension,D=`${J}_${Z}${G}`,q=await F($,{...x,object:{...x.object,name:D}},H);if(!q.metadata)throw L.internalServerError("Upload returned no metadata, cannot build a storage variant");let M=q.metadata;return[Z,{key:q.key,name:D,metadata:{type:M.type,extension:M.extension,size:M.size,hash:M.hash,width:M.width,height:M.height,aspectRatio:M.aspectRatio,aspectRatioStr:M.aspectRatioStr,resolution:M.resolution,bitrate:M.bitrate,codec:M.codec,fps:M.fps}}]})),j=Y.flatMap((Z)=>Z.status==="fulfilled"?[Z.value]:[]),X=Y.flatMap((Z)=>Z.status==="rejected"?[Z.reason]:[]);if(X.length>0){let Z=j.flatMap(([,D])=>D?.key?[D.key]:[]),G=(await h(Z)).filter(({deleted:D})=>!D);if(G.length>0)console.error("Failed to clean up partially uploaded image variants",{keys:Z,failures:G});throw X[0]}let K=(H?.acl??"public-read")!=="public-read"&&(H?.acl??"public-read")!=="public-read-write",_={};for(let[Z,x]of j)if(x)_[Z]=x;return{shared:{name:Q.object.name,resolvedType:Q.object.resolvedType,provider:"s3",acl:H?.acl??"public-read",isPrivate:K,uploadedBy:H?.uploadedBy},variants:_}}async function m0($,W,H){let J=await F($,W,H);if(!J.metadata)throw L.internalServerError("Upload returned no metadata, cannot build a storage object");let Q=J.name??J.key.split("/").pop()??J.key;return{shared:{name:Q,resolvedType:J.resolvedType,provider:J.provider,acl:J.metadata.acl,isPrivate:J.isPrivate,uploadedBy:H?.uploadedBy},variants:c({default:{key:J.key,name:Q,metadata:{type:J.metadata.type,extension:J.metadata.extension,size:J.metadata.size,hash:J.metadata.hash,width:J.metadata.width,height:J.metadata.height,aspectRatio:J.metadata.aspectRatio,aspectRatioStr:J.metadata.aspectRatioStr,resolution:J.metadata.resolution,bitrate:J.metadata.bitrate,codec:J.metadata.codec,fps:J.metadata.fps}}})}}async function y0($,W,H){if(Array.isArray(W))throw Error("File can't be an array, pass a single file");let J=await E(W,{quality:H?.quality,acl:H?.acl});return i($,J,{acl:H?.acl,uploadedBy:H?.uploadedBy})}var{s3:a}=globalThis.Bun;async function d0($,W={debug:!0}){try{return{exists:await a.exists($),error:null}}catch(H){if(W?.debug)console.error("There was an error checking for file existance",H);return{exists:null,error:H}}}export{m0 as uploadSingleObject,F as uploadOne,h0 as uploadMany,i as uploadGeneratedVariants,M0 as optimizeImageBuffer,K0 as optimizeImage,b as mimeTypeToResolvedType,V0 as getOptimizedImage,N0 as getImageMetadata,W0 as getAspectRatioStringRound,O as getAspectRatioString,E as generateVariants,e as generateTestFile,y0 as generateAndUploadImage,U as fileTypePredicates,o as deleteOne,h as deleteMany,c as createUploadVariants,d0 as checkFileExistance};
6
6
 
7
- //# debugId=CBF45F1E9DE8EB6164756E2164756E21
7
+ //# debugId=D07C2FC1790C739264756E2164756E21
@@ -22,8 +22,8 @@ type DeleteOneResult = DeleteOneNiceResult | DeleteOneBadResult;
22
22
  * surrender that everywhere to accommodate one case.
23
23
  */
24
24
  export type VariantName = ImageVariantName | "default";
25
- export type UploadVariants = Record<VariantName, StorageObjectVariant | undefined>;
26
- /** Builds the database-shaped variant object with every known key present. */
25
+ export type UploadVariants = Partial<Record<VariantName, StorageObjectVariant>>;
26
+ /** Builds the sparse stored-object variant shape from uploaded representations. */
27
27
  export declare function createUploadVariants(values?: Partial<Record<VariantName, StorageObjectVariant>>): UploadVariants;
28
28
  export type UploadResult = {
29
29
  shared: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mateosuarezdev/brpc",
3
- "version": "1.0.105",
3
+ "version": "1.0.107",
4
4
  "description": "A Type-Safe, Flexible Web application framework for Bun",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",