@mateosuarezdev/brpc 1.0.107 → 1.0.109

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
@@ -1025,11 +1025,13 @@ All storage utilities are exported from the main package:
1025
1025
  import {
1026
1026
  uploadOne,
1027
1027
  uploadMany,
1028
- uploadSingleObject,
1029
- generateAndUploadImage,
1030
- uploadGeneratedVariants,
1031
- deleteOne,
1032
- deleteMany,
1028
+ uploadSingleObject,
1029
+ generateAndUploadImage,
1030
+ uploadGeneratedVariants,
1031
+ deleteOne,
1032
+ deleteManyKeys,
1033
+ deleteManyObjects,
1034
+ extractObjectVariantKeys,
1033
1035
  generateVariants,
1034
1036
  getOptimizedImage,
1035
1037
  getImageMetadata,
@@ -1129,13 +1131,35 @@ const { deleted, error } = await deleteOne("images/avatars/abc.webp");
1129
1131
  if (!deleted) console.error("Failed to delete:", error);
1130
1132
  ```
1131
1133
 
1132
- #### `deleteMany(keys, opts?)`
1133
-
1134
- Deletes multiple keys in parallel. Returns a per-key result array.
1135
-
1136
- ```ts
1137
- const results = await deleteMany(variantKeys);
1138
- ```
1134
+ #### `deleteManyKeys(keys, opts?)`
1135
+
1136
+ Deletes multiple keys in parallel. Returns a per-key result array.
1137
+
1138
+ ```ts
1139
+ const results = await deleteManyKeys(variantKeys);
1140
+ ```
1141
+
1142
+ #### `extractObjectVariantKeys(objects)` and `deleteManyObjects(objects, opts?)`
1143
+
1144
+ Stored objects hold sparse variants: an image may have `xs` through `xl`, while
1145
+ a document normally has only `default`. `extractObjectVariantKeys` reads every
1146
+ real variant key and removes duplicates. `deleteManyObjects` is the convenience
1147
+ form that extracts those keys and deletes them.
1148
+
1149
+ ```ts
1150
+ const results = await deleteManyObjects([voucher, previousAvatar]);
1151
+
1152
+ if (results.some((result) => !result.deleted)) {
1153
+ throw new Error("Could not delete every stored representation");
1154
+ }
1155
+ ```
1156
+
1157
+ These functions delete S3 data only; an application owns its database records
1158
+ and domain authorization. When removing a domain record and its objects, run
1159
+ the object delete plus database deletes in its database transaction and abort
1160
+ the transaction on any failed result. S3 is not transactional, so a partial S3
1161
+ failure cannot restore already-deleted siblings; log and reconcile that rare
1162
+ case rather than claiming distributed atomicity.
1139
1163
 
1140
1164
  ### Images
1141
1165
 
@@ -1,7 +1,7 @@
1
1
  // @bun
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
2
+ var T=($)=>$.startsWith("image/"),w=($)=>$.startsWith("video/"),z=($)=>$.startsWith("audio/"),k=($)=>$.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"),R=($)=>$==="application/javascript"||$==="application/json"||$==="text/html"||$==="text/css"||$==="application/xml"||$.includes("javascript"),b=($)=>$.startsWith("font/")||$.includes("font"),P=($)=>{if(!$)return"other";let W=$.toLowerCase();if(T(W))return"image";if(w(W))return"video";if(z(W))return"audio";if(k(W))return"document";if(I(W))return"archive";if(R(W))return"code";if(b(W))return"font";return"other"},U={isImage:T,isVideo:w,isAudio:z,isDocument:k,isArchive:I,isCode:R,isFont:b};function $0(){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 q($,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 H0($,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:Z,label:_}of J)if(Math.abs(H-Z)/Z<Q)return _;let Y=(Z,_)=>_===0?Z:Y(_,Z%_),L=Y($,W);return`${$/L}:${W/L}`}class X 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=X.STATUS_MAP[$.code],this.data=$.data,this.cause=$.cause,Error.captureStackTrace)Error.captureStackTrace(this,X)}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 X({code:"BAD_REQUEST",message:$,clientCode:W,data:H})}static unauthorized($="Unauthorized",W,H){return new X({code:"UNAUTHORIZED",message:$,clientCode:W,data:H})}static forbidden($="Forbidden",W,H){return new X({code:"FORBIDDEN",message:$,clientCode:W,data:H})}static notFound($="Not Found",W,H){return new X({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 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};
5
+ return new X({code:"NOT_FOUND",message:$,clientCode:W,data:H})}static conflict($,W,H){return new X({code:"CONFLICT",message:$,clientCode:W,data:H})}static unprocessableContent($,W,H){return new X({code:"UNPROCESSABLE_CONTENT",message:$,clientCode:W,data:H})}static tooManyRequests($="Too many requests",W,H){return new X({code:"TOO_MANY_REQUESTS",message:$,clientCode:W,data:H})}static internalServerError($="Internal Server Error",W,H){return new X({code:"INTERNAL_SERVER_ERROR",message:$,clientCode:W,data:H})}static timeout($="Request timeout",W,H){return new X({code:"TIMEOUT",message:$,clientCode:W,data:H})}}import m from"sharp";var E={xs:200,sm:400,md:800,lg:1200,xl:1920},A=1920;async function S($,W,H,J,Q){try{let{data:Y,info:L}=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:L.width,height:L.height,aspectRatio:L.width&&L.height?L.width/L.height:1,aspectRatioStr:L.width&&L.height?q(L.width,L.height):"1:1",acl:Q}}}}catch(Y){throw X.conflict(`Failed to optimize image: ${Y instanceof Error?Y.message:"Unknown error"}`)}}async function B($){if(!$)throw X.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"),X.conflict("Invalid base64 format");let[,J]=$.split(",");if(!J)throw X.conflict("Invalid base64 data");W=Buffer.from(J,"base64")}else{if(!U.isImage($.type))throw X.conflict("File must be an image");let J=await $.arrayBuffer();W=Buffer.from(J),H=$.name??H}return{buffer:W,name:H}}async function x0($,{size:W,quality:H,acl:J}={}){let Q=await B($);return S(Q.buffer,W??A,H??80,Q.name,J??"public-read")}async function D0($,{name:W="unknown.webp",size:H,quality:J,acl:Q}={}){if(!$?.length)throw X.conflict("A buffer must be provided");return S($,H??A,J??80,W,Q??"public-read")}async function v($,{quality:W=80,acl:H="public-read"}={}){let J=await B($),Q=await Promise.all(Object.entries(E).map(async([Y,L])=>{let Z=await S(J.buffer,L,W,J.name,H);return[Y,Z]}));return{variants:Object.fromEntries(Q)}}import n 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}`,L=`${J}/${Y}`,Z=`./buckets/${J}/${Y}`;try{let _=await Bun.write(Z,W);return{data:{key:L,file:W,bytesWritten:_},error:null}}catch(_){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 L=H?.(Y)??Y.name;return C($,Y,L)});return await Promise.all(J)}async function d($){let W=Bun.file(`./buckets/${$}`);if(!await W.exists())return null;return W}async function p($){let W=Bun.file(`./buckets/${$}`);if(!await W.exists())return!1;return await W.delete(),!0}var V={uploadOne:C,uploadMany:f,getOne:d,deleteOne:p};async function S0($){let W=`./cache/images/${$.key}_${$.width}_${$.quality}.webp`,H=await V.getOne(W);if(H){if(!U.isImage(H.type))return null;return H}let J=$.key,Q=await V.getOne(J);if(!Q)return null;if(!U.isImage(Q.type))return null;let Y=await Q.arrayBuffer(),L=await n(Y).resize($.width,null,{withoutEnlargement:!0}).webp({quality:$.quality??75}).toBuffer();await Bun.write(W,L);let Z=Bun.file(W);if(!await Z.exists())return null;return Z}import s from"sharp";async function T0($){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:q(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:i}=globalThis.Bun;import{extname as t}from"path";function c($={}){return $}var{s3:l}=globalThis.Bun;function o($){return[...new Set($.flatMap(({variants:W})=>Object.values(W).flatMap((H)=>H&&typeof H==="object"&&("key"in H)&&typeof H.key==="string"&&H.key.length>0?[H.key]:[])))]}async function r($,W){if(!$||typeof $!=="string")throw X.badRequest("Key is required");let H=$.trim();if(!H||H.length===0)throw X.badRequest("Key must be a non-empty string");try{return await l.delete(H),{key:H,deleted:!0,error:null}}catch(J){return{key:$,deleted:!1,error:J}}}async function F($,W){return(await Promise.allSettled($.map((J)=>r(J,W)))).map((J,Q)=>{if(J.status==="fulfilled")return J.value;return{key:$[Q],deleted:!1,error:J.reason}})}async function b0($,W){return F(o($),W)}async function N($,W,H){let J="buffer"in W?W:{buffer:W,object:{name:W.name,resolvedType:P(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 X.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}`,L=J.object.name?J.object.name:`${crypto.randomUUID()}${Y}`,Z=`${h}${Q}${L}`,_=i.file(Z),G=J?.object?.metadata?.acl??"public-read",M=G!=="public-read"&&G!=="public-read-write",D=await _.write(J.buffer,{acl:G,type:J.object.metadata.type});return{uploadedBy:H?.uploadedBy,key:Z,name:J.object.name,resolvedType:J.object.resolvedType,provider:"s3",isPrivate:M,metadata:{...J.object.metadata,size:D,acl:G},isActive:!0}}async function y0($,W,H={throwIf:"any"}){let J=await Promise.allSettled(W.map((L)=>N($,L,{acl:H?.acl,uploadedBy:H?.uploadedBy}))),Q=[],Y=[];if(J.forEach((L)=>{if(L.status==="fulfilled"){if(Q.push(L.value),H?.externalKeys)H.externalKeys.push(L.value.key)}else Y.push(L.reason)}),H?.throwIf==="any"&&Y.length>0||H?.throwIf==="all"&&Y.length===W.length)throw X.internalServerError(`Upload failed: ${Y.length} of ${W.length}`);return{data:Q,errors:Y.length?Y:void 0}}async function a($,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([M,D])=>{if(!D)return[M,null];let K=D.object.metadata.extension,j=`${J}_${M}${K}`,O=await N($,{...D,object:{...D.object,name:j}},H);if(!O.metadata)throw X.internalServerError("Upload returned no metadata, cannot build a storage variant");let x=O.metadata;return[M,{key:O.key,name:j,metadata:{type:x.type,extension:x.extension,size:x.size,hash:x.hash,width:x.width,height:x.height,aspectRatio:x.aspectRatio,aspectRatioStr:x.aspectRatioStr,resolution:x.resolution,bitrate:x.bitrate,codec:x.codec,fps:x.fps}}]})),L=Y.flatMap((M)=>M.status==="fulfilled"?[M.value]:[]),Z=Y.flatMap((M)=>M.status==="rejected"?[M.reason]:[]);if(Z.length>0){let M=L.flatMap(([,j])=>j?.key?[j.key]:[]),K=(await F(M)).filter(({deleted:j})=>!j);if(K.length>0)console.error("Failed to clean up partially uploaded image variants",{keys:M,failures:K});throw Z[0]}let _=(H?.acl??"public-read")!=="public-read"&&(H?.acl??"public-read")!=="public-read-write",G={};for(let[M,D]of L)if(D)G[M]=D;return{shared:{name:Q.object.name,resolvedType:Q.object.resolvedType,provider:"s3",acl:H?.acl??"public-read",isPrivate:_,uploadedBy:H?.uploadedBy},variants:G}}async function u0($,W,H){let J=await N($,W,H);if(!J.metadata)throw X.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 f0($,W,H){if(Array.isArray(W))throw Error("File can't be an array, pass a single file");let J=await v(W,{quality:H?.quality,acl:H?.acl});return a($,J,{acl:H?.acl,uploadedBy:H?.uploadedBy})}var{s3:e}=globalThis.Bun;async function n0($,W={debug:!0}){try{return{exists:await e.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{u0 as uploadSingleObject,N as uploadOne,y0 as uploadMany,a as uploadGeneratedVariants,D0 as optimizeImageBuffer,x0 as optimizeImage,P as mimeTypeToResolvedType,S0 as getOptimizedImage,T0 as getImageMetadata,H0 as getAspectRatioStringRound,q as getAspectRatioString,v as generateVariants,$0 as generateTestFile,f0 as generateAndUploadImage,U as fileTypePredicates,o as extractObjectVariantKeys,r as deleteOne,b0 as deleteManyObjects,F as deleteManyKeys,c as createUploadVariants,n0 as checkFileExistance};
6
6
 
7
- //# debugId=D07C2FC1790C739264756E2164756E21
7
+ //# debugId=BF9212D9C6E0325364756E2164756E21
@@ -1,4 +1,9 @@
1
1
  import type { DeleteOneResult } from "./types";
2
+ type ObjectWithVariants = {
3
+ variants: object;
4
+ };
5
+ /** Returns the unique S3 keys represented by one or more sparse stored objects. */
6
+ export declare function extractObjectVariantKeys<T extends ObjectWithVariants>(objects: T[]): string[];
2
7
  export declare function deleteOne(key: string, opts?: {
3
8
  debug?: boolean;
4
9
  }): Promise<DeleteOneResult>;
@@ -6,6 +11,11 @@ export declare function deleteOne(key: string, opts?: {
6
11
  * Deletes multiple S3 keys in parallel.
7
12
  * Returns per-key result so caller can handle failures/rollback.
8
13
  */
9
- export declare function deleteMany(keys: string[], opts?: {
14
+ export declare function deleteManyKeys(keys: string[], opts?: {
10
15
  debug?: boolean;
11
16
  }): Promise<DeleteOneResult[]>;
17
+ /** Deletes every stored representation of the supplied sparse objects. */
18
+ export declare function deleteManyObjects<T extends ObjectWithVariants>(objects: T[], opts?: {
19
+ debug?: boolean;
20
+ }): Promise<DeleteOneResult[]>;
21
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mateosuarezdev/brpc",
3
- "version": "1.0.107",
3
+ "version": "1.0.109",
4
4
  "description": "A Type-Safe, Flexible Web application framework for Bun",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",