hydrousdb 3.5.3 → 3.5.5

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
@@ -706,7 +706,7 @@ await storage.uploadRaw('<html>…</html>', 'pages/home.html', { mimeType: 'text
706
706
 
707
707
  ### Large Files with Progress
708
708
 
709
- Recommended for files > 10 MB or when you need a progress indicator (browsers only).
709
+ Recommended for files > 10 MB or when you need a progress indicator (supported in both browser and Node.js environments).
710
710
 
711
711
  ```ts
712
712
  // Step 1 — get a signed GCS PUT URL
@@ -719,7 +719,7 @@ const { uploadUrl, path, expiresAt, expiresIn } = await storage.getUploadUrl({
719
719
  expiresInSeconds: 3600, // URL lifetime
720
720
  });
721
721
 
722
- // Step 2 — upload directly to GCS with progress callback (browser XHR)
722
+ // Step 2 — upload directly to GCS with progress callback (browser & Node.js)
723
723
  await storage.uploadToSignedUrl(uploadUrl, file, 'video/mp4', (percent) => {
724
724
  console.log(`${percent}% uploaded`);
725
725
  });
@@ -798,7 +798,7 @@ for (const err of failed) {
798
798
 
799
799
  ---
800
800
 
801
- ### List, Metadata & Signed URLs
801
+ ### List & Metadata
802
802
 
803
803
  ```ts
804
804
  // List files and folders (paginated)
@@ -817,12 +817,24 @@ if (hasMore) {
817
817
  // Get file metadata
818
818
  const meta = await storage.getMetadata('avatars/alice.jpg');
819
819
  // → { path, size, mimeType, isPublic, publicUrl, downloadUrl, createdAt, updatedAt }
820
+ ```
821
+
822
+ ---
823
+
824
+ ### Temporary Sharing (Signed URLs)
820
825
 
821
- // Generate a time-limited link anyone with the URL can download (no key needed)
826
+ Generate time-limited download URLs for temporary, unauthenticated sharing of private files. Anyone with the URL can access and download the file directly from Google Cloud Storage without needing an `X-Storage-Key` header.
827
+
828
+ *Note: Downloads via signed URLs bypass the Hydrous server entirely (client direct to GCS), meaning download statistics and monthly bandwidth quotas will not be tracked on the server for these requests.*
829
+
830
+ ```ts
822
831
  const { signedUrl, expiresAt, expiresIn } = await storage.getSignedUrl(
823
832
  'private/report.pdf',
824
- 3600, // lifetime in seconds (default: 3600)
833
+ 3600, // URL lifetime in seconds (default: 3600 = 1 hour, max: 604800 = 7 days)
825
834
  );
835
+
836
+ console.log(signedUrl); // Direct GCS download URL
837
+ console.log(expiresAt); // ISO timestamp of when the link expires
826
838
  ```
827
839
 
828
840
  ---
package/dist/index.cjs CHANGED
@@ -151,19 +151,77 @@ var HttpClient = class {
151
151
  * Uses XHR in browsers for onprogress support; fetch in Node.
152
152
  */
153
153
  async putToSignedUrl(signedUrl, data, mimeType, onProgress) {
154
- const body = data instanceof Blob ? data : data instanceof Uint8Array ? data.buffer : data;
155
- if (typeof XMLHttpRequest !== "undefined" && typeof onProgress === "function") {
154
+ const body = data;
155
+ if (typeof XMLHttpRequest !== "undefined") {
156
156
  await new Promise((resolve, reject) => {
157
157
  const xhr = new XMLHttpRequest();
158
- xhr.upload.onprogress = (e) => {
159
- if (e.lengthComputable) onProgress(Math.round(e.loaded / e.total * 100));
160
- };
158
+ if (typeof onProgress === "function") {
159
+ xhr.upload.onprogress = (e) => {
160
+ if (e.lengthComputable) onProgress(Math.round(e.loaded / e.total * 100));
161
+ };
162
+ }
161
163
  xhr.onload = () => xhr.status >= 200 && xhr.status < 300 ? resolve() : reject(new Error(`GCS upload failed: ${xhr.status}`));
162
164
  xhr.onerror = () => reject(new Error("GCS upload network error"));
163
165
  xhr.open("PUT", signedUrl);
164
166
  xhr.setRequestHeader("Content-Type", mimeType);
165
167
  xhr.send(body);
166
168
  });
169
+ } else if (typeof process !== "undefined" && process.versions && process.versions.node) {
170
+ const https = await import('https');
171
+ const { URL } = await import('url');
172
+ let buffer;
173
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(body)) {
174
+ buffer = body;
175
+ } else if (body instanceof ArrayBuffer) {
176
+ buffer = Buffer.from(body);
177
+ } else if (body instanceof Uint8Array) {
178
+ buffer = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
179
+ } else if (typeof Blob !== "undefined" && body instanceof Blob) {
180
+ const arrayBuffer = await body.arrayBuffer();
181
+ buffer = Buffer.from(arrayBuffer);
182
+ } else {
183
+ buffer = Buffer.from(String(body));
184
+ }
185
+ const urlObj = new URL(signedUrl);
186
+ const options = {
187
+ method: "PUT",
188
+ hostname: urlObj.hostname,
189
+ path: urlObj.pathname + urlObj.search,
190
+ headers: {
191
+ "Content-Type": mimeType,
192
+ "Content-Length": String(buffer.length)
193
+ }
194
+ };
195
+ await new Promise((resolve, reject) => {
196
+ const req = https.request(options, (res) => {
197
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
198
+ resolve();
199
+ } else {
200
+ reject(new Error(`GCS upload failed: ${res.statusCode}`));
201
+ }
202
+ });
203
+ req.on("error", (err) => {
204
+ reject(err);
205
+ });
206
+ const totalBytes = buffer.length;
207
+ let writtenBytes = 0;
208
+ const chunkSize = 64 * 1024;
209
+ const writeChunk = () => {
210
+ if (writtenBytes < totalBytes) {
211
+ const nextChunkSize = Math.min(chunkSize, totalBytes - writtenBytes);
212
+ const chunk = buffer.subarray(writtenBytes, writtenBytes + nextChunkSize);
213
+ req.write(chunk);
214
+ writtenBytes += nextChunkSize;
215
+ if (typeof onProgress === "function") {
216
+ onProgress(Math.round(writtenBytes / totalBytes * 100));
217
+ }
218
+ setImmediate(writeChunk);
219
+ } else {
220
+ req.end();
221
+ }
222
+ };
223
+ writeChunk();
224
+ });
167
225
  } else {
168
226
  const res = await fetch(signedUrl, {
169
227
  method: "PUT",
@@ -1063,8 +1121,32 @@ var StorageManager = class {
1063
1121
  * ```
1064
1122
  */
1065
1123
  async upload(data, path, options = {}) {
1066
- const { isPublic = false, overwrite = false, mimeType } = options;
1124
+ const { isPublic = false, overwrite = false, mimeType, onProgress } = options;
1067
1125
  const mime = mimeType ?? guessMimeType(path);
1126
+ let size = 0;
1127
+ if (data instanceof Blob) {
1128
+ size = data.size;
1129
+ } else if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
1130
+ size = data.byteLength;
1131
+ } else if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) {
1132
+ size = data.length;
1133
+ }
1134
+ const useDirectUpload = onProgress !== void 0 || size > 5 * 1024 * 1024;
1135
+ if (useDirectUpload) {
1136
+ const { uploadUrl, path: cleanPath } = await this.getUploadUrl({
1137
+ path,
1138
+ mimeType: mime,
1139
+ size,
1140
+ isPublic,
1141
+ overwrite
1142
+ });
1143
+ await this.uploadToSignedUrl(uploadUrl, data, mime, onProgress);
1144
+ return this.confirmUpload({
1145
+ path: cleanPath,
1146
+ mimeType: mime,
1147
+ isPublic
1148
+ });
1149
+ }
1068
1150
  const formData = new FormData();
1069
1151
  const blob = data instanceof Blob ? data : new Blob([data], { type: mime });
1070
1152
  formData.append("file", blob, path.split("/").pop() ?? "file");