@visulima/storage 1.0.11 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## @visulima/storage [2.0.0](https://github.com/visulima/visulima/compare/%40visulima%2Fstorage%401.0.11...%40visulima%2Fstorage%402.0.0) (2026-08-08)
2
+
3
+
4
+ ### Dependencies
5
+
6
+ * **@visulima/pagination:** upgraded to 7.0.0
7
+ * **@visulima/fs:** upgraded to 6.0.0
8
+ * **@visulima/humanizer:** upgraded to 4.0.0
9
+ * **@visulima/path:** upgraded to 4.0.0
10
+
1
11
  ## @visulima/storage [1.0.11](https://github.com/visulima/visulima/compare/%40visulima%2Fstorage%401.0.10...%40visulima%2Fstorage%401.0.11) (2026-08-02)
2
12
 
3
13
 
package/LICENSE.md CHANGED
@@ -375,9 +375,41 @@ Repository: git+https://github.com/visulima/visulima.git
375
375
  >
376
376
  > # Licenses of bundled types
377
377
  > The published @visulima/fs artifact additionally contains code with the following licenses:
378
- > ISC, MIT
378
+ > MIT
379
379
  >
380
380
  > # Bundled types:
381
+ > ## @visulima/yaml
382
+ > License: MIT
383
+ > By: Daniel Bannert
384
+ > Repository: git+https://github.com/visulima/visulima.git
385
+ >
386
+ > > MIT License
387
+ > >
388
+ > > Copyright (c) 2026 visulima
389
+ > >
390
+ > > Permission is hereby granted, free of charge, to any person obtaining a copy
391
+ > > of this software and associated documentation files (the "Software"), to deal
392
+ > > in the Software without restriction, including without limitation the rights
393
+ > > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
394
+ > > copies of the Software, and to permit persons to whom the Software is
395
+ > > furnished to do so, subject to the following conditions:
396
+ > >
397
+ > > The above copyright notice and this permission notice shall be included in all
398
+ > > copies or substantial portions of the Software.
399
+ > >
400
+ > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
401
+ > > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
402
+ > > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
403
+ > > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
404
+ > > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
405
+ > > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
406
+ > > SOFTWARE.
407
+ > >
408
+ > >
409
+ > >
410
+ >
411
+ > ---------------------------------------
412
+ >
381
413
  > ## is-fs-case-sensitive
382
414
  > License: MIT
383
415
  > By: Hiroki Osame
@@ -433,27 +465,6 @@ Repository: git+https://github.com/visulima/visulima.git
433
465
  > > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
434
466
  > > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
435
467
  > > SOFTWARE.
436
- >
437
- > ---------------------------------------
438
- >
439
- > ## yaml
440
- > License: ISC
441
- > By: Eemeli Aro
442
- > Repository: github:eemeli/yaml
443
- >
444
- > > Copyright Eemeli Aro <eemeli@gmail.com>
445
- > >
446
- > > Permission to use, copy, modify, and/or distribute this software for any purpose
447
- > > with or without fee is hereby granted, provided that the above copyright notice
448
- > > and this permission notice appear in all copies.
449
- > >
450
- > > THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
451
- > > REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
452
- > > FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
453
- > > INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
454
- > > OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
455
- > > TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
456
- > > THIS SOFTWARE.
457
468
 
458
469
  ---------------------------------------
459
470
 
@@ -1 +1 @@
1
- import{default as o}from"../../../packem_shared/Multipart-V64Bg5dt.js";import{default as f}from"../../../packem_shared/Rest-B7g1sgkq.js";import{Tus as p}from"../../../packem_shared/Tus-D9c7KDnl.js";import{TUS_RESUMABLE as s,TUS_VERSION as u}from"../../../packem_shared/TUS_RESUMABLE-CyTSRvPU.js";export{o as Multipart,f as Rest,s as TUS_RESUMABLE,u as TUS_VERSION,p as Tus};
1
+ import{default as o}from"../../../packem_shared/Multipart-V64Bg5dt.js";import{default as f}from"../../../packem_shared/Rest-aPVP2ww1.js";import{Tus as p}from"../../../packem_shared/Tus-D9c7KDnl.js";import{TUS_RESUMABLE as s,TUS_VERSION as u}from"../../../packem_shared/TUS_RESUMABLE-CyTSRvPU.js";export{o as Multipart,f as Rest,s as TUS_RESUMABLE,u as TUS_VERSION,p as Tus};
@@ -1 +1 @@
1
- import c from"../../../packem_shared/Multipart-V64Bg5dt.js";import i from"../../../packem_shared/Rest-B7g1sgkq.js";import{Tus as d}from"../../../packem_shared/Tus-D9c7KDnl.js";const u=t=>async o=>{try{const e=o.req.raw;return await t.fetch(e)}catch(e){const a=e,s=a.statusCode||(a.status?Number.parseInt(a.status,10):void 0)||500;return Response.json({error:a.message||"Request failed"},{status:s})}},m=(t,o)=>{const{path:e,type:a,...s}=o;let n;switch(a){case"multipart":{n=new c(s);break}case"rest":{n=new i(s);break}case"tus":{n=new d(s);break}default:throw new Error(`Unknown handler type: ${String(a??"unknown")}`)}const r=u(n);switch(a){case"multipart":{t.post(e,r),t.get(`${e}/:id?/:metadata?`,r),t.delete(`${e}/:id`,r),t.on("OPTIONS",e,r);break}case"rest":{t.post(e,r),t.put(`${e}/:id`,r),t.patch(`${e}/:id`,r),t.get(`${e}/:id?`,r),t.on("HEAD",`${e}/:id`,r),t.delete(`${e}/:id?`,r),t.on("OPTIONS",e,r);break}case"tus":{t.all(e,r),t.all(`${e}/:id`,r);break}default:throw new Error(`Unknown handler type: ${String(a)}`)}};export{m as createStorageHandler};
1
+ import c from"../../../packem_shared/Multipart-V64Bg5dt.js";import i from"../../../packem_shared/Rest-aPVP2ww1.js";import{Tus as d}from"../../../packem_shared/Tus-D9c7KDnl.js";const u=t=>async o=>{try{const e=o.req.raw;return await t.fetch(e)}catch(e){const a=e,s=a.statusCode||(a.status?Number.parseInt(a.status,10):void 0)||500;return Response.json({error:a.message||"Request failed"},{status:s})}},m=(t,o)=>{const{path:e,type:a,...s}=o;let n;switch(a){case"multipart":{n=new c(s);break}case"rest":{n=new i(s);break}case"tus":{n=new d(s);break}default:throw new Error(`Unknown handler type: ${String(a??"unknown")}`)}const r=u(n);switch(a){case"multipart":{t.post(e,r),t.get(`${e}/:id?/:metadata?`,r),t.delete(`${e}/:id`,r),t.on("OPTIONS",e,r);break}case"rest":{t.post(e,r),t.put(`${e}/:id`,r),t.patch(`${e}/:id`,r),t.get(`${e}/:id?`,r),t.on("HEAD",`${e}/:id`,r),t.delete(`${e}/:id?`,r),t.on("OPTIONS",e,r);break}case"tus":{t.all(e,r),t.all(`${e}/:id`,r);break}default:throw new Error(`Unknown handler type: ${String(a)}`)}};export{m as createStorageHandler};
@@ -1 +1 @@
1
- import{NextResponse as o}from"next/server.js";import u from"../../../packem_shared/Multipart-V64Bg5dt.js";import c from"../../../packem_shared/Rest-B7g1sgkq.js";import{Tus as m}from"../../../packem_shared/Tus-D9c7KDnl.js";const d=t=>{let r;switch(t.type){case"multipart":{r=new u(t);break}case"rest":{r=new c(t);break}case"tus":{r=new m(t);break}default:throw new Error(`Unknown handler type: ${String(t.type??"unknown")}`)}return async s=>{try{return await r.fetch(s)}catch(n){const e=n,a=e.statusCode||(e.status?Number.parseInt(e.status,10):void 0)||500;return o.json({error:e.message||"Request failed"},{status:a})}}};export{d as createHandler};
1
+ import{NextResponse as o}from"next/server.js";import u from"../../../packem_shared/Multipart-V64Bg5dt.js";import c from"../../../packem_shared/Rest-aPVP2ww1.js";import{Tus as m}from"../../../packem_shared/Tus-D9c7KDnl.js";const d=t=>{let r;switch(t.type){case"multipart":{r=new u(t);break}case"rest":{r=new c(t);break}case"tus":{r=new m(t);break}default:throw new Error(`Unknown handler type: ${String(t.type??"unknown")}`)}return async s=>{try{return await r.fetch(s)}catch(n){const e=n,a=e.statusCode||(e.status?Number.parseInt(e.status,10):void 0)||500;return o.json({error:e.message||"Request failed"},{status:a})}}};export{d as createHandler};
@@ -1 +1 @@
1
- import o from"../../../packem_shared/Multipart-V64Bg5dt.js";import u from"../../../packem_shared/Rest-B7g1sgkq.js";import{Tus as p}from"../../../packem_shared/Tus-D9c7KDnl.js";const w=t=>{let e;switch(t.type){case"multipart":{e=new o(t);break}case"rest":{e=new u(t);break}case"tus":{e=new p(t);break}default:throw new Error(`Unknown handler type: ${String(t.type??"unknown")}`)}return async n=>{try{return await e.fetch(n.request)}catch(s){const r=s,a=r.statusCode||(r.status?Number.parseInt(r.status,10):void 0)||500;return Response.json({error:r.message||"Request failed"},{headers:{"Content-Type":"application/json"},status:a})}}};export{w as createSolidStartHandler};
1
+ import o from"../../../packem_shared/Multipart-V64Bg5dt.js";import u from"../../../packem_shared/Rest-aPVP2ww1.js";import{Tus as p}from"../../../packem_shared/Tus-D9c7KDnl.js";const w=t=>{let e;switch(t.type){case"multipart":{e=new o(t);break}case"rest":{e=new u(t);break}case"tus":{e=new p(t);break}default:throw new Error(`Unknown handler type: ${String(t.type??"unknown")}`)}return async n=>{try{return await e.fetch(n.request)}catch(s){const r=s,a=r.statusCode||(r.status?Number.parseInt(r.status,10):void 0)||500;return Response.json({error:r.message||"Request failed"},{headers:{"Content-Type":"application/json"},status:a})}}};export{w as createSolidStartHandler};
@@ -1 +1 @@
1
- import"node:crypto";import"gaxios";import"google-auth-library";import{v as f}from"./gcs-meta-storage-C9F78Sqq.js";import"./MetaStorage-DPEUyWgN.js";import"./Metadata-B7CcBueg.js";import"./GCSConfig-BQ0OpVhe.js";export{f as default};
1
+ import"node:crypto";import"gaxios";import"google-auth-library";import{v as f}from"./gcs-meta-storage-Bu2upyHK.js";import"./MetaStorage-DPEUyWgN.js";import"./Metadata-B7CcBueg.js";import"./GCSConfig-BQ0OpVhe.js";export{f as default};
@@ -0,0 +1 @@
1
+ import{randomUUID as g}from"node:crypto";import{request as f}from"gaxios";import{GoogleAuth as y}from"google-auth-library";import{r as w,v as R,g as C,m,b}from"./gcs-meta-storage-Bu2upyHK.js";import{d as E}from"./detect-file-type-BA7UBLKA.js";import{throwErrorCode as k,ERRORS as U}from"./ERRORS-BIBxE2oR.js";import{B as I,c as S}from"./storage-DR7qdwBN.js";import{y as T}from"./local-meta-storage-BELb1YwO.js";import d from"./GCSConfig-BQ0OpVhe.js";import x from"./GCSFile-Cero7qZN.js";import{i as O}from"./part-match-D-9dGX42.js";import{e as v}from"./has-content-50BsqrWw.js";import{t as A}from"./get-file-status-y3wTtDQs.js";class $ extends Error{constructor(t,a,e){super(t),this.code=a,this.config=e}code;config;name="FetchError"}const B=l=>l>=200&&l<300||l===308||l===499;class p extends I{static name="gcs";checksumTypes=["md5","crc32c"];supportsDelimiter=!0;get raw(){return this.authClient}meta;bucket;authClient;storageBaseURI;uploadBaseURI;isCustomEndpoint=!1;retryOptions={};useAuthWithCustomEndpoint;userProject;constructor(t){super(t);const a=t.bucket||process.env.GCS_BUCKET;if(!a)throw new Error("GCS bucket is not defined");if(!t.projectId)throw new Error("Sorry, we cannot connect to Cloud Services without a project ID.");this.bucket=a,this.storageBaseURI=`${t.storageAPI||d.storageAPI}/${this.bucket}/o`,this.uploadBaseURI=`${t.uploadAPI||d.uploadAPI}/${this.bucket}/o`,this.isCustomEndpoint=new URL(this.storageBaseURI).hostname!=="storage.googleapis.com";const{retryOptions:e,useAuthWithCustomEndpoint:r,userProject:s}=t;this.userProject=s,this.useAuthWithCustomEndpoint=r||!1,this.retryOptions={...w,...e},t.scopes||=d.authScopes,this.authClient=new y(t);const{bucket:n,metaStorage:c,metaStorageConfig:o,projectId:i}=t;if(c)this.meta=c;else{let u={...t,...o,logger:this.logger};"directory"in u?(this.logger?.debug("Using local meta storage"),this.meta=new T(u)):(n===u.bucket&&i===u.projectId&&(u={...u,authClient:this.authClient}),this.meta=new R(u))}this.isReady=!1,this.accessCheck().then(()=>{this.isReady=!0}).catch(u=>{this.logger?.error("Storage access check failed: %O",u)})}normalizeError(t){if(t.config){const a=+t.code||500;return{code:`GCS${a}`,message:t.message,name:t.name,retryable:a>=499,statusCode:a}}return{code:"GenericUploadError",message:`[${this.constructor.name}] ${t.message}`,name:t.name,statusCode:500}}async create(t,a){return this.instrumentOperation("create",async()=>{const e={...t};if(t.ttl){const i=typeof t.ttl=="string"?S(t.ttl):t.ttl;i!==void 0&&(e.expiredAt=Date.now()+i)}const r=new x(e);r.name=this.namingFunction(r),await this.validate(r);try{const i=await this.getMeta(r.id);return i.bytesWritten=await this.internalWrite(i,a),i}catch{}const s={"Content-Type":"application/json; charset=utf-8","X-Goog-Upload-Command":"start","X-Goog-Upload-Protocol":"resumable","X-Upload-Content-Length":r.size.toString(),"X-Upload-Content-Type":r.contentType},n={body:JSON.stringify({metadata:r.metadata}),headers:s,method:"POST",params:{name:r.name,size:r.size,uploadType:"resumable"},url:this.uploadBaseURI},c=await this.makeRequest(n,a);if(c.status!==200)throw new Error("Expected 200 response from GCS");const o=c.headers.get("X-Goog-Upload-Status")||c.headers.get("x-goog-upload-status");if(o!=="active")throw new Error(`X-Goog-Upload-Status response header expected 'active' got: ${o}`);return r.uri=c.headers.get("location"),this.config.clientDirectUpload?(r.GCSUploadURI=r.uri,this.logger?.debug("send uploadURI to client: %s",r.GCSUploadURI),r.status="created",r):(r.bytesWritten=0,await this.saveMeta(r),r.status="created",await this.onCreate(r),r)})}async write(t,a){return this.instrumentOperation("write",async()=>{let e;if("contentType"in t&&"metadata"in t&&!("body"in t)&&!("start"in t)?e=t:(e=await this.getMeta(t.id),await this.checkIfExpired(e)),e.status==="completed")return e;if(!O(t,e))return k(U.FILE_CONFLICT);const r=await this.lock(t.id);try{if(v(t)&&(e.bytesWritten===0||Number.isNaN(e.bytesWritten))&&(!e.contentType||e.contentType==="application/octet-stream"))try{const{fileType:s,stream:n}=await E(t.body);s?.mime&&(e.contentType=s.mime),t.body=n}catch{}e.bytesWritten=await this.internalWrite({...e,...t},a),e.status=A(e),e.status==="completed"&&(e.uri=`${this.storageBaseURI}/${e.name}`,await this.internalOnComplete(e))}finally{await this.unlock(t.id,r)}return e})}async delete({id:t},a){return this.instrumentOperation("delete",async()=>{const e=await this.getMeta(t);if(!e.uri)throw new Error(`File ${t} does not have a valid URI`);e.status="deleted",await this.makeRequest({method:"DELETE",url:e.uri,validateStatus:B},a),await this.deleteMeta(e.id);const r={...e};return await this.onDelete(r),r})}async copy(t,a,e){return this.instrumentOperation("copy",async()=>{const r=new URL(`/${this.bucket}/${t}`,"file://"),s=new URL(encodeURI(a),r).pathname,[,n,...c]=s.split("/"),o=c.join("/"),i=`${this.storageBaseURI}/${t}/rewriteTo/b/${n}/o/${o}`;let u={};const h={body:"",headers:{"Content-Type":"application/json"},method:"POST",url:i};do h.body=u.rewriteToken?JSON.stringify({rewriteToken:u.rewriteToken}):"",u=(await this.makeRequest(h,e)).data||{};while(u.rewriteToken);return await this.getMeta(a)})}async move(t,a,e){return this.instrumentOperation("move",async()=>{const r=await this.copy(t,a,e),s=`${this.storageBaseURI}/${t}`;return await this.makeRequest({method:"DELETE",url:s},e),r})}async get({id:t},a){return this.instrumentOperation("get",async()=>{const{data:e}=await this.makeRequest({params:{alt:"json"},url:`${this.storageBaseURI}/${t}`},a);if(await this.checkIfExpired({expiredAt:e.timeDeleted}),!e.uri)throw new Error("File URI not found");const r=(await this.makeRequest({params:{alt:"media"},url:e.uri},a)).data;let s;if(typeof r=="string")s=Buffer.from(r,"utf8");else if(r&&typeof r=="object"&&"data"in r){const n=r.data;s=Buffer.from(n)}else s=Buffer.from(r);return{...e,content:s}})}async exists({id:t},a){return this.instrumentOperation("exists",async()=>{try{return await this.getMeta(t),await this.makeRequest({method:"HEAD",url:`${this.storageBaseURI}/${t}`},a),!0}catch{return!1}})}async list(t=1e3,a){return this.instrumentOperation("list",async()=>{const e=[],r=Math.min(t,1e3);let s=!0,n={params:{maxResults:r},url:this.storageBaseURI};for(;s&&e.length<t;)try{const{data:c}=await this.makeRequest(n,a);for(const{name:o,timeCreated:i,updated:u}of c?.items||[]){if(e.length>=t)break;e.push({createdAt:i,id:o,modifiedAt:u})}s=c?.nextPageToken!==void 0,s&&e.length<t&&(n={...n,params:{...n.params,maxResults:Math.min(r,t-e.length),pageToken:c.nextPageToken}})}catch(c){const o=this.normalizeError(c instanceof Error?c:new Error(String(c)));throw await this.onError(o),c}return e},{limit:t})}async listDirectory(t){return this.instrumentOperation("listDirectory",async()=>{const a=t?.limit??1e3,e=Math.min(a,1e3),r=[],s=new Set;let n=!0,c={params:{delimiter:t?.delimiter,maxResults:e,...t?.prefix!==void 0&&{prefix:t.prefix}},url:this.storageBaseURI};for(;n&&r.length<a;)try{const{data:o}=await this.makeRequest(c,t);for(const i of o?.prefixes||[])s.add(i);for(const{name:i,timeCreated:u,updated:h}of o?.items||[]){if(r.length>=a)break;r.push({createdAt:u,id:i,modifiedAt:h})}n=o?.nextPageToken!==void 0,n&&r.length<a&&(c={...c,params:{...c.params,maxResults:Math.min(e,a-r.length),pageToken:o.nextPageToken}})}catch(o){const i=this.normalizeError(o instanceof Error?o:new Error(String(o)));throw await this.onError(i),o}return{files:r,prefixes:[...s]}},{limit:t?.limit??1e3})}async internalWrite(t,a){const{body:e,bytesWritten:r,size:s,uri:n=""}=t,c=b(t),o={method:"PUT"};if(e?.on){const i=new AbortController;e.on("aborted",()=>{i.abort()}),o.body=e,o.signal=i.signal,o.retry=!1}o.headers={Accept:"application/json","Content-Range":c,...s===r?{"X-Goog-Upload-Command":"upload, finalize"}:{}};try{const i=await this.makeRequest({url:n,...o},a);if(i.status===308){const u=i.headers.get("range");return u?C(u):0}if(i.status===200)return this.logger?.debug("uploaded %O",i.data),s;throw new $(String(i.data),`GCS${i.status}`,{uri:n})}catch(i){this.logger?.error(n,i);const u=this.normalizeError(i instanceof Error?i:new Error(String(i)));throw await this.onError(u),i}}internalOnComplete=t=>this.deleteMeta(t.id);static buildCallSignal(t,a){const e=[];if(a&&e.push(a),t?.signal&&e.push(t.signal),e.length!==0)return e.length===1?e[0]:AbortSignal.any(e)}async makeRequest(t,a){typeof t.url=="string"&&(t.url=t.url.replaceAll("/:",":"));const e=p.buildCallSignal(a,t.signal);let r=this.retryOptions;if(a?.retries!==void 0){const n=typeof a.retries=="number"?a.retries:a.retries.maxRetries;typeof n=="number"&&(r={...this.retryOptions,retry:n})}const s=typeof a?.timeout=="number"&&a.timeout>0?a.timeout:6e4;return t={...t,headers:{"User-Agent":`${m.name}/${m.version}`,"x-goog-api-client":`gl-node/${process.versions.node} gccl/${m.version} gccl-invocation-id/${g()}`,...t.headers},params:{...this.userProject===void 0?{}:{userProject:this.userProject},...t.params},retry:t.retry===!1?!1:a?.retries!==0,retryConfig:r,timeout:s,...e?{signal:e}:{}},this.isCustomEndpoint&&!this.useAuthWithCustomEndpoint?f(t):this.authClient.request(t)}async accessCheck(){return this.makeRequest({url:this.storageBaseURI.replace("/o","")})}}export{p as default};
@@ -1 +1 @@
1
- import{n as i}from"./headers-BAiAKekR.js";import{B as b}from"./storage-DR7qdwBN.js";import{ERRORS as p}from"./ERRORS-BIBxE2oR.js";import{a as u}from"./base-handler-core-BJP9jHCE.js";import{C as w}from"./base-handler-fetch-yrYrukwL.js";import{X as N,p as f}from"./rest-base-BQ_dhWyV.js";class g extends w{static methods=["delete","download","get","head","options","patch","post","put"];restBase;constructor(e){super(e);const r=this;this.restBase=new class extends N{get storage(){return r.storage}buildFileUrl(s,t){return r.buildFileUrl({url:s},t)}}}compose(){this.registeredHandlers.set("POST",this.post.bind(this)),this.registeredHandlers.set("PUT",this.put.bind(this)),this.registeredHandlers.set("PATCH",this.patch.bind(this)),this.registeredHandlers.set("DELETE",this.delete.bind(this)),this.registeredHandlers.set("HEAD",this.head.bind(this)),this.registeredHandlers.set("GET",this.get.bind(this)),this.registeredHandlers.set("OPTIONS",this.options.bind(this)),this.logger?.debug("Registered handler: %s",[...this.registeredHandlers.keys()].join(", "))}async post(e){const r=e.headers.get("x-chunked-upload")==="true",s=e.headers.get("content-length"),t=s?Number.parseInt(s,10):0;if(!r){if(!s||Number.isNaN(t)||t===0)throw i(400,"Content-Length is required and must be greater than 0");if(e.body===null)throw i(400,"Request body is required")}if(t>this.storage.maxUploadSize)throw i(413,`File size exceeds maximum allowed size of ${this.storage.maxUploadSize} bytes`);const o=e.headers.get("content-type")||"application/octet-stream",n=B(e,t,o),a=e.url,h=e.body?u(e):void 0;return this.restBase.handlePost(n,r,a,h,t)}async put(e){const r=l(e.url);if(!r)throw i(400,"File ID is required in URL path");if(e.body===null)throw i(400,"Request body is required");const s=e.headers.get("content-length"),t=s?Number.parseInt(s,10):0;if(t===0)throw i(400,"Content-Length is required and must be greater than 0");if(t>this.storage.maxUploadSize)throw i(413,`File size exceeds maximum allowed size of ${this.storage.maxUploadSize} bytes`);const o=e.headers.get("content-type")||"application/octet-stream",n=e.headers.get("x-file-metadata");let a;if(n)try{a=JSON.parse(n)}catch{}const h=f(e.headers.get("content-disposition")),c={contentType:o,metadata:a||{},originalName:h,size:t},m=e.url,y=u(e);return this.restBase.handlePut(r,c,m,y,t,a)}async delete(e){const r=new URL(e.url).searchParams.get("ids");if(r){const t=r.split(",").map(o=>o.trim()).filter(Boolean);if(t.length===0)throw i(400,"No file IDs provided");return this.restBase.deleteBatch(t)}if((e.headers.get("content-type")||"").includes("application/json"))try{const t=Number.parseInt(e.headers.get("content-length")??"",10);if(Number.isFinite(t)&&t>1048576)throw i(413,"Batch delete body exceeds 1 MiB");const o=await e.text();if(Buffer.byteLength(o,"utf8")>1048576)throw i(413,"Batch delete body exceeds 1 MiB");const n=JSON.parse(o);if(Array.isArray(n)){if(n.length===0)throw i(400,"No file IDs provided");return this.restBase.deleteBatch(n)}if(typeof n=="object"&&n!==null&&"ids"in n&&Array.isArray(n.ids)){const a=n.ids;if(a.length===0)throw i(400,"No file IDs provided");return this.restBase.deleteBatch(a)}}catch(t){if(t.statusCode===400)throw t}const s=l(e.url);if(!s)throw i(404,"File not found");try{return this.restBase.deleteSingle(s)}catch(t){const o=t;throw o.UploadErrorCode===p.FILE_NOT_FOUND||o.code==="ENOENT"?i(404,"File not found"):t}}async patch(e){const r=l(e.url);if(!r)throw i(404,"File not found");if(e.body===null)throw i(400,"Request body is required");const s=e.headers.get("content-length"),t=s?Number.parseInt(s,10):0;if(t===0)throw i(400,"Content-Length is required and must be greater than 0");const o=e.headers.get("x-chunk-offset"),n=o?Number.parseInt(o,10):void 0;if(n===void 0)throw i(400,"X-Chunk-Offset header is required");const a=e.headers.get("x-chunk-checksum")||void 0,h=e.url,c=u(e);return this.restBase.handlePatch(r,n,t,a,h,c)}async head(e){const r=l(e.url);if(!r)throw i(404,"File not found");try{return this.restBase.handleHead(r)}catch(s){const t=s;throw t.UploadErrorCode===p.FILE_NOT_FOUND||t.code==="ENOENT"?i(404,"File not found"):s}}async options(e){return this.restBase.handleOptions(g.methods,this.storage.maxUploadSize)}async get(e){throw i(500,"GET requests should be handled via fetch() method")}}const B=(d,e,r)=>{const s=d.headers.get("x-total-size"),t=s?Number.parseInt(s,10):e,o=d.headers.get("x-file-metadata");let n={};if(o)try{n=JSON.parse(o)}catch{}const a=f(d.headers.get("content-disposition"));return{contentType:r,metadata:n,originalName:a,size:t}},l=d=>{let e;try{const r=new URL(d).pathname.split("/").filter(Boolean),s=r[r.length-1];if(!s)return null;e=s.replace(/\.[^.]+$/,"")||void 0}catch{return null}if(!e)return null;try{b.assertSafeId(e)}catch{throw i(400,`Invalid file id: "${e}"`)}return e};export{g as default};
1
+ import{n as i}from"./headers-BAiAKekR.js";import{B as b}from"./storage-DR7qdwBN.js";import{ERRORS as p}from"./ERRORS-BIBxE2oR.js";import{a as u}from"./base-handler-core-BJP9jHCE.js";import{C as w}from"./base-handler-fetch-yrYrukwL.js";import{X as N,p as f}from"./rest-base-BQ_dhWyV.js";class g extends w{static methods=["delete","download","get","head","options","patch","post","put"];restBase;constructor(e){super(e);const r=this;this.restBase=new class extends N{get storage(){return r.storage}buildFileUrl(s,t){return r.buildFileUrl({url:s},t)}}}compose(){this.registeredHandlers.set("POST",this.post.bind(this)),this.registeredHandlers.set("PUT",this.put.bind(this)),this.registeredHandlers.set("PATCH",this.patch.bind(this)),this.registeredHandlers.set("DELETE",this.delete.bind(this)),this.registeredHandlers.set("HEAD",this.head.bind(this)),this.registeredHandlers.set("GET",this.get.bind(this)),this.registeredHandlers.set("OPTIONS",this.options.bind(this)),this.logger?.debug("Registered handler: %s",[...this.registeredHandlers.keys()].join(", "))}async post(e){const r=e.headers.get("x-chunked-upload")==="true",s=e.headers.get("content-length"),t=s?Number.parseInt(s,10):0;if(!r){if(!s||Number.isNaN(t)||t===0)throw i(400,"Content-Length is required and must be greater than 0");if(e.body===null)throw i(400,"Request body is required")}if(t>this.storage.maxUploadSize)throw i(413,`File size exceeds maximum allowed size of ${this.storage.maxUploadSize} bytes`);const a=e.headers.get("content-type")||"application/octet-stream",o=B(e,t,a),n=e.url,h=e.body?u(e):void 0;return this.restBase.handlePost(o,r,n,h,t)}async put(e){const r=l(e.url);if(!r)throw i(400,"File ID is required in URL path");if(e.body===null)throw i(400,"Request body is required");const s=e.headers.get("content-length"),t=s?Number.parseInt(s,10):0;if(t===0)throw i(400,"Content-Length is required and must be greater than 0");if(t>this.storage.maxUploadSize)throw i(413,`File size exceeds maximum allowed size of ${this.storage.maxUploadSize} bytes`);const a=e.headers.get("content-type")||"application/octet-stream",o=e.headers.get("x-file-metadata");let n;if(o)try{n=JSON.parse(o)}catch{}const h=f(e.headers.get("content-disposition")),c={contentType:a,metadata:n||{},originalName:h,size:t},m=e.url,y=u(e);return this.restBase.handlePut(r,c,m,y,t,n)}async delete(e){const r=new URL(e.url).searchParams.get("ids");if(r){const t=r.split(",").map(a=>a.trim()).filter(Boolean);if(t.length===0)throw i(400,"No file IDs provided");return this.restBase.deleteBatch(t)}if((e.headers.get("content-type")||"").includes("application/json"))try{const t=Number.parseInt(e.headers.get("content-length")??"",10);if(Number.isFinite(t)&&t>1048576)throw i(413,"Batch delete body exceeds 1 MiB");const a=await e.text();if(Buffer.byteLength(a,"utf8")>1048576)throw i(413,"Batch delete body exceeds 1 MiB");const o=JSON.parse(a);if(Array.isArray(o)){if(o.length===0)throw i(400,"No file IDs provided");return this.restBase.deleteBatch(o)}if(typeof o=="object"&&o!==null&&"ids"in o&&Array.isArray(o.ids)){const n=o.ids;if(n.length===0)throw i(400,"No file IDs provided");return this.restBase.deleteBatch(n)}}catch(t){if(t.statusCode===400)throw t}const s=l(e.url);if(!s)throw i(404,"File not found");try{return this.restBase.deleteSingle(s)}catch(t){const a=t;throw a.UploadErrorCode===p.FILE_NOT_FOUND||a.code==="ENOENT"?i(404,"File not found"):t}}async patch(e){const r=l(e.url);if(!r)throw i(404,"File not found");if(e.body===null)throw i(400,"Request body is required");const s=e.headers.get("content-length"),t=s?Number.parseInt(s,10):0;if(t===0)throw i(400,"Content-Length is required and must be greater than 0");const a=e.headers.get("x-chunk-offset"),o=a?Number.parseInt(a,10):void 0;if(o===void 0)throw i(400,"X-Chunk-Offset header is required");const n=e.headers.get("x-chunk-checksum")||void 0,h=e.url,c=u(e);return this.restBase.handlePatch(r,o,t,n,h,c)}async head(e){const r=l(e.url);if(!r)throw i(404,"File not found");try{return this.restBase.handleHead(r)}catch(s){const t=s;throw t.UploadErrorCode===p.FILE_NOT_FOUND||t.code==="ENOENT"?i(404,"File not found"):s}}async options(e){return this.restBase.handleOptions(g.methods,this.storage.maxUploadSize)}async get(e){throw i(500,"GET requests should be handled via fetch() method")}}const B=(d,e,r)=>{const s=d.headers.get("x-total-size"),t=s?Number.parseInt(s,10):e,a=d.headers.get("x-file-metadata");let o={};if(a)try{o=JSON.parse(a)}catch{}const n=f(d.headers.get("content-disposition"));return{contentType:r,metadata:o,originalName:n,size:t}},l=d=>{let e;try{const r=new URL(d).pathname.split("/").filter(Boolean),s=r[r.length-1];if(!s)return null;e=s.replace(/\.[^.]+$/,"")||void 0}catch{return null}if(!e)return null;try{b.assertSafeId(e)}catch{throw i(400,`Invalid file id: "${e}"`)}return e};export{g as default};
@@ -0,0 +1 @@
1
+ import{randomUUID as p}from"node:crypto";import{request as l}from"gaxios";import{GoogleAuth as g}from"google-auth-library";import y from"./MetaStorage-DPEUyWgN.js";import{stringifyMetadata as f,parseMetadata as C}from"./Metadata-B7CcBueg.js";import a from"./GCSConfig-BQ0OpVhe.js";import{e as R}from"./has-content-50BsqrWw.js";var v="@visulima/storage",E="1.0.11";const n={name:v,version:E},b=o=>{const t=o.match(/(\d+)-(\d+)/),e=t?.[2]?+t[2]:0;return e>0?e+1:0},q=o=>{if(R(o)){const t=o.contentLength?o.start+o.contentLength-1:"*";return`bytes ${o.start}-${t}/${o.size??"*"}`}return`bytes */${o.size??"*"}`},I=o=>{if(o.response!==void 0){const{response:t}=o;return t.data?.error?.errors?.map(({reason:e=""})=>e==="rateLimitExceeded"||e==="userRateLimitExceeded"?!0:!!(e&&e.includes("EAI_AGAIN"))).includes(!0)??!1}return!1},A={retry:3,shouldRetry:I,statusCodesToRetry:[[408,429,500,502,503,504],[100,199],[429],[500,599]]};class M extends y{constructor(t){super(t),this.config=t;const{authClient:e,...s}=t,r=s.bucket||process.env.GCS_BUCKET;if(e===void 0){if(!r)throw new Error("GCS bucket is not defined");if(!s.projectId)throw new Error("Sorry, we cannot connect to Cloud Services without a project ID.");s.scopes||=a.authScopes,this.authClient=new g(s)}else this.authClient=e;this.storageBaseURI=`${s.storageAPI||a.storageAPI}/${r}/o`,this.uploadBaseURI=`${s.uploadAPI||a.uploadAPI}/${r}/o`;const u=["storage.googleapis.com"],c=new URL(this.storageBaseURI).hostname;this.isCustomEndpoint=!u.includes(c);const{retryOptions:h,useAuthWithCustomEndpoint:d,userProject:m}=t;this.userProject=m,this.useAuthWithCustomEndpoint=d||!1,this.retryOptions={...A,...h},e===void 0&&this.accessCheck().catch(i=>{throw i.code==="404"?new Error(`Bucket ${r} does not exist`):i})}config;authClient;storageBaseURI;uploadBaseURI;isCustomEndpoint=!1;retryOptions={};useAuthWithCustomEndpoint;userProject;async save(t,e){const s={...e};return s.metadata&&(s.metadata=f(e.metadata)),await this.makeRequest({body:JSON.stringify(s),headers:{"Content-Type":"application/json; charset=utf-8"},method:"POST",params:{name:encodeURIComponent(this.getMetaName(t)),uploadType:"media"},url:this.uploadBaseURI}),e}async delete(t){const e=this.getMetaPath(t);await this.makeRequest({method:"DELETE",url:e})}async get(t){const e=this.getMetaPath(t),{data:s}=await this.makeRequest({params:{alt:"media"},url:e});return s.metadata&&typeof s.metadata=="string"&&(s.metadata=C(s.metadata)),s}async touch(t,e){return this.save(t,e)}async accessCheck(){return this.makeRequest({url:this.storageBaseURI.replace("/o","")})}getMetaPath(t){return`${this.storageBaseURI}/${this.getMetaName(t)}`}async makeRequest(t){return typeof t.url=="string"&&(t.url=t.url.replaceAll("/:",":")),t={...t,headers:{"User-Agent":`${n.name}/${n.version}`,"x-goog-api-client":`gl-node/${process.versions.node} gccl/${n.version} gccl-invocation-id/${p()}`},params:{...this.userProject===void 0?{}:{userProject:this.userProject}},retry:!0,retryConfig:this.retryOptions,timeout:6e4},this.isCustomEndpoint&&!this.useAuthWithCustomEndpoint?l(t):this.authClient.request(t)}}export{q as b,b as g,n as m,A as r,M as v};
@@ -1 +1 @@
1
- import{default as a}from"../../packem_shared/GCSConfig-BQ0OpVhe.js";import{default as t}from"../../packem_shared/GCSFile-Cero7qZN.js";import{v as C}from"../../packem_shared/gcs-meta-storage-C9F78Sqq.js";import{default as l}from"../../packem_shared/GCStorage-CRd5vjok.js";export{a as GCSConfig,t as GCSFile,C as GCSMetaStorage,l as GCStorage};
1
+ import{default as a}from"../../packem_shared/GCSConfig-BQ0OpVhe.js";import{default as t}from"../../packem_shared/GCSFile-Cero7qZN.js";import{v as C}from"../../packem_shared/gcs-meta-storage-Bu2upyHK.js";import{default as l}from"../../packem_shared/GCStorage-DcvsI7eu.js";export{a as GCSConfig,t as GCSFile,C as GCSMetaStorage,l as GCStorage};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visulima/storage",
3
- "version": "1.0.11",
3
+ "version": "2.0.0",
4
4
  "description": "Server-side file-storage abstraction - store files in a web-accessible location via a simplified API. Includes S3, Azure, GCS, local filesystem and 20+ other backends with TUS/multipart/REST upload handlers and the most convenient features of each.",
5
5
  "keywords": [
6
6
  "anolilab",
@@ -271,7 +271,7 @@
271
271
  "dependencies": {
272
272
  "@remix-run/multipart-parser": "0.16.3",
273
273
  "@sindresorhus/fnv1a": "3.1.0",
274
- "@visulima/pagination": "6.0.0",
274
+ "@visulima/pagination": "7.0.0",
275
275
  "file-type": "22.0.1",
276
276
  "lru-cache": "11.5.1",
277
277
  "mime": "^4.1.0",
@@ -1 +0,0 @@
1
- import{randomUUID as g}from"node:crypto";import{request as f}from"gaxios";import{GoogleAuth as y}from"google-auth-library";import{r as w,v as R,g as C,m,b}from"./gcs-meta-storage-C9F78Sqq.js";import{d as E}from"./detect-file-type-BA7UBLKA.js";import{throwErrorCode as k,ERRORS as U}from"./ERRORS-BIBxE2oR.js";import{B as I,c as S}from"./storage-DR7qdwBN.js";import{y as T}from"./local-meta-storage-BELb1YwO.js";import d from"./GCSConfig-BQ0OpVhe.js";import x from"./GCSFile-Cero7qZN.js";import{i as O}from"./part-match-D-9dGX42.js";import{e as v}from"./has-content-50BsqrWw.js";import{t as A}from"./get-file-status-y3wTtDQs.js";class B extends Error{constructor(t,s,e){super(t),this.code=s,this.config=e}code;config;name="FetchError"}const $=l=>l>=200&&l<300||l===308||l===499;class p extends I{static name="gcs";checksumTypes=["md5","crc32c"];supportsDelimiter=!0;get raw(){return this.authClient}meta;bucket;authClient;storageBaseURI;uploadBaseURI;isCustomEndpoint=!1;retryOptions={};useAuthWithCustomEndpoint;userProject;constructor(t){super(t);const s=t.bucket||process.env.GCS_BUCKET;if(!s)throw new Error("GCS bucket is not defined");if(!t.projectId)throw new Error("Sorry, we cannot connect to Cloud Services without a project ID.");this.bucket=s,this.storageBaseURI=`${t.storageAPI||d.storageAPI}/${this.bucket}/o`,this.uploadBaseURI=`${t.uploadAPI||d.uploadAPI}/${this.bucket}/o`,this.isCustomEndpoint=new URL(this.storageBaseURI).hostname!=="storage.googleapis.com";const{retryOptions:e,useAuthWithCustomEndpoint:r,userProject:i}=t;this.userProject=i,this.useAuthWithCustomEndpoint=r||!1,this.retryOptions={...w,...e},t.scopes||=d.authScopes,this.authClient=new y(t);const{bucket:n,metaStorage:c,metaStorageConfig:o,projectId:a}=t;if(c)this.meta=c;else{let u={...t,...o,logger:this.logger};"directory"in u?(this.logger?.debug("Using local meta storage"),this.meta=new T(u)):(n===u.bucket&&a===u.projectId&&(u={...u,authClient:this.authClient}),this.meta=new R(u))}this.isReady=!1,this.accessCheck().then(()=>{this.isReady=!0}).catch(u=>{this.logger?.error("Storage access check failed: %O",u)})}normalizeError(t){if(t.config){const s=+t.code||500;return{code:`GCS${s}`,message:t.message,name:t.name,retryable:s>=499,statusCode:s}}return{code:"GenericUploadError",message:`[${this.constructor.name}] ${t.message}`,name:t.name,statusCode:500}}async create(t,s){return this.instrumentOperation("create",async()=>{const e={...t};if(t.ttl){const a=typeof t.ttl=="string"?S(t.ttl):t.ttl;a!==void 0&&(e.expiredAt=Date.now()+a)}const r=new x(e);r.name=this.namingFunction(r),await this.validate(r);try{const a=await this.getMeta(r.id);return a.bytesWritten=await this.internalWrite(a,s),a}catch{}const i={"Content-Type":"application/json; charset=utf-8","X-Goog-Upload-Command":"start","X-Goog-Upload-Protocol":"resumable","X-Upload-Content-Length":r.size.toString(),"X-Upload-Content-Type":r.contentType},n={body:JSON.stringify({metadata:r.metadata}),headers:i,method:"POST",params:{name:r.name,size:r.size,uploadType:"resumable"},url:this.uploadBaseURI},c=await this.makeRequest(n,s);if(c.status!==200)throw new Error("Expected 200 response from GCS");const o=c.headers.get("X-Goog-Upload-Status")||c.headers.get("x-goog-upload-status");if(o!=="active")throw new Error(`X-Goog-Upload-Status response header expected 'active' got: ${o}`);return r.uri=c.headers.get("location"),this.config.clientDirectUpload?(r.GCSUploadURI=r.uri,this.logger?.debug("send uploadURI to client: %s",r.GCSUploadURI),r.status="created",r):(r.bytesWritten=0,await this.saveMeta(r),r.status="created",await this.onCreate(r),r)})}async write(t,s){return this.instrumentOperation("write",async()=>{let e;if("contentType"in t&&"metadata"in t&&!("body"in t)&&!("start"in t)?e=t:(e=await this.getMeta(t.id),await this.checkIfExpired(e)),e.status==="completed")return e;if(!O(t,e))return k(U.FILE_CONFLICT);const r=await this.lock(t.id);try{if(v(t)&&(e.bytesWritten===0||Number.isNaN(e.bytesWritten))&&(!e.contentType||e.contentType==="application/octet-stream"))try{const{fileType:i,stream:n}=await E(t.body);i?.mime&&(e.contentType=i.mime),t.body=n}catch{}e.bytesWritten=await this.internalWrite({...e,...t},s),e.status=A(e),e.status==="completed"&&(e.uri=`${this.storageBaseURI}/${e.name}`,await this.internalOnComplete(e))}finally{await this.unlock(t.id,r)}return e})}async delete({id:t},s){return this.instrumentOperation("delete",async()=>{const e=await this.getMeta(t);if(!e.uri)throw new Error(`File ${t} does not have a valid URI`);e.status="deleted",await this.makeRequest({method:"DELETE",url:e.uri,validateStatus:$},s),await this.deleteMeta(e.id);const r={...e};return await this.onDelete(r),r})}async copy(t,s,e){return this.instrumentOperation("copy",async()=>{const r=new URL(`/${this.bucket}/${t}`,"file://"),i=new URL(encodeURI(s),r).pathname,[,n,...c]=i.split("/"),o=c.join("/"),a=`${this.storageBaseURI}/${t}/rewriteTo/b/${n}/o/${o}`;let u={};const h={body:"",headers:{"Content-Type":"application/json"},method:"POST",url:a};do h.body=u.rewriteToken?JSON.stringify({rewriteToken:u.rewriteToken}):"",u=(await this.makeRequest(h,e)).data||{};while(u.rewriteToken);return await this.getMeta(s)})}async move(t,s,e){return this.instrumentOperation("move",async()=>{const r=await this.copy(t,s,e),i=`${this.storageBaseURI}/${t}`;return await this.makeRequest({method:"DELETE",url:i},e),r})}async get({id:t},s){return this.instrumentOperation("get",async()=>{const{data:e}=await this.makeRequest({params:{alt:"json"},url:`${this.storageBaseURI}/${t}`},s);if(await this.checkIfExpired({expiredAt:e.timeDeleted}),!e.uri)throw new Error("File URI not found");const r=(await this.makeRequest({params:{alt:"media"},url:e.uri},s)).data;let i;if(typeof r=="string")i=Buffer.from(r,"utf8");else if(r&&typeof r=="object"&&"data"in r){const n=r.data;i=Buffer.from(n)}else i=Buffer.from(r);return{...e,content:i}})}async exists({id:t},s){return this.instrumentOperation("exists",async()=>{try{return await this.getMeta(t),await this.makeRequest({method:"HEAD",url:`${this.storageBaseURI}/${t}`},s),!0}catch{return!1}})}async list(t=1e3,s){return this.instrumentOperation("list",async()=>{const e=[],r=Math.min(t,1e3);let i=!0,n={params:{maxResults:r},url:this.storageBaseURI};for(;i&&e.length<t;)try{const{data:c}=await this.makeRequest(n,s);for(const{name:o,timeCreated:a,updated:u}of c?.items||[]){if(e.length>=t)break;e.push({createdAt:a,id:o,modifiedAt:u})}i=c?.nextPageToken!==void 0,i&&e.length<t&&(n={...n,params:{...n.params,maxResults:Math.min(r,t-e.length),pageToken:c.nextPageToken}})}catch(c){const o=this.normalizeError(c instanceof Error?c:new Error(String(c)));throw await this.onError(o),c}return e},{limit:t})}async listDirectory(t){return this.instrumentOperation("listDirectory",async()=>{const s=t?.limit??1e3,e=Math.min(s,1e3),r=[],i=new Set;let n=!0,c={params:{delimiter:t?.delimiter,maxResults:e,...t?.prefix!==void 0&&{prefix:t.prefix}},url:this.storageBaseURI};for(;n&&r.length<s;)try{const{data:o}=await this.makeRequest(c,t);for(const a of o?.prefixes||[])i.add(a);for(const{name:a,timeCreated:u,updated:h}of o?.items||[]){if(r.length>=s)break;r.push({createdAt:u,id:a,modifiedAt:h})}n=o?.nextPageToken!==void 0,n&&r.length<s&&(c={...c,params:{...c.params,maxResults:Math.min(e,s-r.length),pageToken:o.nextPageToken}})}catch(o){const a=this.normalizeError(o instanceof Error?o:new Error(String(o)));throw await this.onError(a),o}return{files:r,prefixes:[...i]}},{limit:t?.limit??1e3})}async internalWrite(t,s){const{body:e,bytesWritten:r,size:i,uri:n=""}=t,c=b(t),o={method:"PUT"};if(e?.on){const a=new AbortController;e.on("aborted",()=>{a.abort()}),o.body=e,o.signal=a.signal,o.retry=!1}o.headers={Accept:"application/json","Content-Range":c,...i===r?{"X-Goog-Upload-Command":"upload, finalize"}:{}};try{const a=await this.makeRequest({url:n,...o},s);if(a.status===308){const u=a.headers.get("range");return u?C(u):0}if(a.status===200)return this.logger?.debug("uploaded %O",a.data),i;throw new B(String(a.data),`GCS${a.status}`,{uri:n})}catch(a){this.logger?.error(n,a);const u=this.normalizeError(a instanceof Error?a:new Error(String(a)));throw await this.onError(u),a}}internalOnComplete=t=>this.deleteMeta(t.id);static buildCallSignal(t,s){const e=[];if(s&&e.push(s),t?.signal&&e.push(t.signal),e.length!==0)return e.length===1?e[0]:AbortSignal.any(e)}async makeRequest(t,s){typeof t.url=="string"&&(t.url=t.url.replaceAll("/:",":"));const e=p.buildCallSignal(s,t.signal);let r=this.retryOptions;if(s?.retries!==void 0){const n=typeof s.retries=="number"?s.retries:s.retries.maxRetries;typeof n=="number"&&(r={...this.retryOptions,retry:n})}const i=typeof s?.timeout=="number"&&s.timeout>0?s.timeout:6e4;return t={...t,headers:{"User-Agent":`${m.name}/${m.version}`,"x-goog-api-client":`gl-node/${process.versions.node} gccl/${m.version} gccl-invocation-id/${g()}`,...t.headers},params:{...this.userProject===void 0?{}:{userProject:this.userProject},...t.params},retry:t.retry===!1?!1:s?.retries!==0,retryConfig:r,timeout:i,...e?{signal:e}:{}},this.isCustomEndpoint&&!this.useAuthWithCustomEndpoint?f(t):this.authClient.request(t)}async accessCheck(){return this.makeRequest({url:this.storageBaseURI.replace("/o","")})}}export{p as default};
@@ -1 +0,0 @@
1
- import{randomUUID as p}from"node:crypto";import{request as l}from"gaxios";import{GoogleAuth as g}from"google-auth-library";import y from"./MetaStorage-DPEUyWgN.js";import{stringifyMetadata as f,parseMetadata as C}from"./Metadata-B7CcBueg.js";import a from"./GCSConfig-BQ0OpVhe.js";import{e as R}from"./has-content-50BsqrWw.js";var v="@visulima/storage",E="1.0.10";const n={name:v,version:E},b=r=>{const t=r.match(/(\d+)-(\d+)/),e=t?.[2]?+t[2]:0;return e>0?e+1:0},q=r=>{if(R(r)){const t=r.contentLength?r.start+r.contentLength-1:"*";return`bytes ${r.start}-${t}/${r.size??"*"}`}return`bytes */${r.size??"*"}`},I=r=>{if(r.response!==void 0){const{response:t}=r;return t?.data?.error?.errors?.map(({reason:e=""})=>e==="rateLimitExceeded"||e==="userRateLimitExceeded"?!0:!!(e&&e.includes("EAI_AGAIN"))).includes(!0)}return!1},A={retry:3,shouldRetry:I,statusCodesToRetry:[[408,429,500,502,503,504],[100,199],[429],[500,599]]};class M extends y{constructor(t){super(t),this.config=t;const{authClient:e,...s}=t,o=s.bucket||process.env.GCS_BUCKET;if(e===void 0){if(!o)throw new Error("GCS bucket is not defined");if(!s.projectId)throw new Error("Sorry, we cannot connect to Cloud Services without a project ID.");s.scopes||=a.authScopes,this.authClient=new g(s)}else this.authClient=e;this.storageBaseURI=`${s.storageAPI||a.storageAPI}/${o}/o`,this.uploadBaseURI=`${s.uploadAPI||a.uploadAPI}/${o}/o`;const u=["storage.googleapis.com"],c=new URL(this.storageBaseURI).hostname;this.isCustomEndpoint=!u.includes(c);const{retryOptions:h,useAuthWithCustomEndpoint:d,userProject:m}=t;this.userProject=m,this.useAuthWithCustomEndpoint=d||!1,this.retryOptions={...A,...h},e===void 0&&this.accessCheck().catch(i=>{throw i.code==="404"?new Error(`Bucket ${o} does not exist`):i})}config;authClient;storageBaseURI;uploadBaseURI;isCustomEndpoint=!1;retryOptions={};useAuthWithCustomEndpoint;userProject;async save(t,e){const s={...e};return s.metadata&&(s.metadata=f(e.metadata)),await this.makeRequest({body:JSON.stringify(s),headers:{"Content-Type":"application/json; charset=utf-8"},method:"POST",params:{name:encodeURIComponent(this.getMetaName(t)),uploadType:"media"},url:this.uploadBaseURI}),e}async delete(t){const e=this.getMetaPath(t);await this.makeRequest({method:"DELETE",url:e})}async get(t){const e=this.getMetaPath(t),{data:s}=await this.makeRequest({params:{alt:"media"},url:e});return s.metadata&&typeof s.metadata=="string"&&(s.metadata=C(s.metadata)),s}async touch(t,e){return this.save(t,e)}async accessCheck(){return this.makeRequest({url:this.storageBaseURI.replace("/o","")})}getMetaPath(t){return`${this.storageBaseURI}/${this.getMetaName(t)}`}async makeRequest(t){return typeof t.url=="string"&&(t.url=t.url.replaceAll("/:",":")),t={...t,headers:{"User-Agent":`${n.name}/${n.version}`,"x-goog-api-client":`gl-node/${process.versions.node} gccl/${n.version} gccl-invocation-id/${p()}`},params:{...this.userProject===void 0?{}:{userProject:this.userProject}},retry:!0,retryConfig:this.retryOptions,timeout:6e4},this.isCustomEndpoint&&!this.useAuthWithCustomEndpoint?l(t):this.authClient.request(t)}}export{q as b,b as g,n as m,A as r,M as v};