react-upload-pro 0.1.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.
@@ -0,0 +1,279 @@
1
+ "use client";
2
+ // src/cloud/shared.ts
3
+ function presignedXhr(params) {
4
+ return new Promise((resolve, reject) => {
5
+ const xhr = new XMLHttpRequest();
6
+ xhr.open(params.method ?? "PUT", params.url, true);
7
+ for (const [k, v] of Object.entries(params.headers ?? {})) {
8
+ try {
9
+ xhr.setRequestHeader(k, v);
10
+ } catch {
11
+ }
12
+ }
13
+ xhr.upload.addEventListener("progress", (ev) => {
14
+ if (ev.lengthComputable) params.onProgress(ev.loaded, ev.total);
15
+ });
16
+ xhr.addEventListener("load", () => {
17
+ const headers = parseHeaders(xhr.getAllResponseHeaders());
18
+ if (xhr.status >= 200 && xhr.status < 300) {
19
+ resolve({ status: xhr.status, responseText: xhr.responseText, responseHeaders: headers });
20
+ } else {
21
+ reject(
22
+ err(
23
+ `http-${xhr.status}`,
24
+ `Upload failed: ${xhr.status}`,
25
+ xhr.status >= 500 || xhr.status === 408 || xhr.status === 429
26
+ )
27
+ );
28
+ }
29
+ });
30
+ xhr.addEventListener("error", () => reject(err("network", "Network error", true)));
31
+ xhr.addEventListener("abort", () => reject(err("cancelled", "Upload cancelled", false)));
32
+ params.signal.addEventListener("abort", () => xhr.abort(), { once: true });
33
+ xhr.send(params.body);
34
+ });
35
+ }
36
+ function err(code, message, retryable, cause) {
37
+ return { code, message, retryable, cause };
38
+ }
39
+ function parseHeaders(raw) {
40
+ const out = {};
41
+ for (const line of raw.split(/\r?\n/)) {
42
+ const idx = line.indexOf(":");
43
+ if (idx > 0) {
44
+ const k = line.slice(0, idx).trim().toLowerCase();
45
+ const v = line.slice(idx + 1).trim();
46
+ if (k) out[k] = v;
47
+ }
48
+ }
49
+ return out;
50
+ }
51
+
52
+ // src/cloud/s3.ts
53
+ function createS3Adapter(opts) {
54
+ return {
55
+ name: "s3",
56
+ async upload(file, { onProgress, signal }) {
57
+ const presigned = await opts.getPresignedUrl(file);
58
+ let body = file.file;
59
+ const headers = { ...presigned.headers ?? {} };
60
+ const method = presigned.method ?? "PUT";
61
+ if (method === "POST" && presigned.fields) {
62
+ const fd = new FormData();
63
+ for (const [k, v] of Object.entries(presigned.fields)) fd.append(k, v);
64
+ fd.append("file", file.file, file.name);
65
+ body = fd;
66
+ } else if (method === "PUT") {
67
+ if (!headers["Content-Type"] && file.type) headers["Content-Type"] = file.type;
68
+ }
69
+ const { responseHeaders } = await presignedXhr({
70
+ url: presigned.url,
71
+ method,
72
+ headers,
73
+ body,
74
+ signal,
75
+ onProgress
76
+ });
77
+ const publicUrl = opts.buildPublicUrl ? opts.buildPublicUrl(file, presigned.url) : presigned.url.split("?")[0] ?? presigned.url;
78
+ const result = { url: publicUrl };
79
+ if (responseHeaders.etag) result.etag = responseHeaders.etag.replace(/"/g, "");
80
+ return result;
81
+ }
82
+ };
83
+ }
84
+
85
+ // src/cloud/cloudinary.ts
86
+ function createCloudinaryAdapter(opts) {
87
+ const resource = opts.resourceType ?? "auto";
88
+ const endpoint = `https://api.cloudinary.com/v1_1/${opts.cloudName}/${resource}/upload`;
89
+ return {
90
+ name: "cloudinary",
91
+ async upload(file, { onProgress, signal }) {
92
+ const fd = new FormData();
93
+ fd.append("file", file.file, file.name);
94
+ if (opts.uploadPreset) {
95
+ fd.append("upload_preset", opts.uploadPreset);
96
+ } else if (opts.getSignature) {
97
+ const sig = await opts.getSignature(file);
98
+ fd.append("api_key", sig.apiKey);
99
+ fd.append("timestamp", String(sig.timestamp));
100
+ fd.append("signature", sig.signature);
101
+ for (const [k, v] of Object.entries(sig)) {
102
+ if (["apiKey", "timestamp", "signature"].includes(k)) continue;
103
+ if (v !== void 0) fd.append(k, String(v));
104
+ }
105
+ } else {
106
+ throw err("config-error", "Cloudinary adapter needs uploadPreset or getSignature", false);
107
+ }
108
+ if (opts.folder) fd.append("folder", opts.folder);
109
+ const { responseText } = await presignedXhr({
110
+ url: endpoint,
111
+ method: "POST",
112
+ body: fd,
113
+ signal,
114
+ onProgress
115
+ });
116
+ try {
117
+ const json = JSON.parse(responseText);
118
+ return {
119
+ url: json.secure_url ?? "",
120
+ key: json.public_id,
121
+ etag: json.etag,
122
+ metadata: json
123
+ };
124
+ } catch {
125
+ throw err("parse-error", "Invalid Cloudinary response", false);
126
+ }
127
+ }
128
+ };
129
+ }
130
+
131
+ // src/cloud/firebase.ts
132
+ function createFirebaseStorageAdapter(opts) {
133
+ return {
134
+ name: "firebase-storage",
135
+ async upload(file, { onProgress, signal }) {
136
+ const path = opts.buildPath ? opts.buildPath(file) : `uploads/${file.name}`;
137
+ const token = await opts.getIdToken();
138
+ const url = `https://firebasestorage.googleapis.com/v0/b/${encodeURIComponent(
139
+ opts.bucket
140
+ )}/o?name=${encodeURIComponent(path)}&uploadType=media`;
141
+ const headers = {
142
+ Authorization: `Firebase ${token}`,
143
+ "Content-Type": file.type || "application/octet-stream"
144
+ };
145
+ const custom = opts.customMetadata?.(file);
146
+ if (custom) {
147
+ for (const [k, v] of Object.entries(custom)) {
148
+ headers[`X-Goog-Meta-${k}`] = v;
149
+ }
150
+ }
151
+ const { responseText } = await presignedXhr({
152
+ url,
153
+ method: "POST",
154
+ headers,
155
+ body: file.file,
156
+ signal,
157
+ onProgress
158
+ });
159
+ try {
160
+ const meta = JSON.parse(responseText);
161
+ const downloadUrl = meta.downloadTokens ? `https://firebasestorage.googleapis.com/v0/b/${meta.bucket}/o/${encodeURIComponent(
162
+ meta.name ?? path
163
+ )}?alt=media&token=${meta.downloadTokens.split(",")[0]}` : `https://firebasestorage.googleapis.com/v0/b/${meta.bucket}/o/${encodeURIComponent(
164
+ meta.name ?? path
165
+ )}?alt=media`;
166
+ return {
167
+ url: downloadUrl,
168
+ key: meta.name,
169
+ metadata: meta
170
+ };
171
+ } catch {
172
+ throw err("parse-error", "Invalid Firebase response", false);
173
+ }
174
+ }
175
+ };
176
+ }
177
+
178
+ // src/cloud/supabase.ts
179
+ function createSupabaseAdapter(opts) {
180
+ return {
181
+ name: "supabase-storage",
182
+ async upload(file, { onProgress, signal }) {
183
+ const path = opts.buildPath ? opts.buildPath(file) : file.name;
184
+ const token = await opts.getToken();
185
+ const url = `${opts.projectUrl.replace(/\/$/, "")}/storage/v1/object/${opts.bucket}/${encodeURI(path)}`;
186
+ const headers = {
187
+ Authorization: `Bearer ${token}`,
188
+ apikey: token,
189
+ "Content-Type": file.type || "application/octet-stream",
190
+ "x-upsert": opts.upsert ? "true" : "false"
191
+ };
192
+ const { responseText } = await presignedXhr({
193
+ url,
194
+ method: "POST",
195
+ headers,
196
+ body: file.file,
197
+ signal,
198
+ onProgress
199
+ });
200
+ const publicUrl = `${opts.projectUrl.replace(/\/$/, "")}/storage/v1/object/public/${opts.bucket}/${encodeURI(path)}`;
201
+ let metadata = {};
202
+ try {
203
+ metadata = JSON.parse(responseText);
204
+ } catch {
205
+ }
206
+ return { url: publicUrl, key: path, metadata };
207
+ }
208
+ };
209
+ }
210
+
211
+ // src/cloud/digitalocean.ts
212
+ function createDigitalOceanAdapter(opts) {
213
+ const inner = createS3Adapter(opts);
214
+ return { ...inner, name: "digitalocean-spaces" };
215
+ }
216
+
217
+ // src/cloud/azure.ts
218
+ function createAzureBlobAdapter(opts) {
219
+ return {
220
+ name: "azure-blob",
221
+ async upload(file, { onProgress, signal }) {
222
+ const sasUrl = await opts.getSasUrl(file);
223
+ const blobName = opts.buildBlobName ? opts.buildBlobName(file) : file.name;
224
+ const url = injectBlobName(sasUrl, blobName);
225
+ const headers = {
226
+ "x-ms-blob-type": "BlockBlob",
227
+ "Content-Type": file.type || "application/octet-stream"
228
+ };
229
+ await presignedXhr({
230
+ url,
231
+ method: "PUT",
232
+ headers,
233
+ body: file.file,
234
+ signal,
235
+ onProgress
236
+ });
237
+ return {
238
+ url: url.split("?")[0] ?? url,
239
+ key: blobName
240
+ };
241
+ }
242
+ };
243
+ }
244
+ function injectBlobName(sasUrl, blobName) {
245
+ const [base, query] = sasUrl.split("?");
246
+ const cleanBase = (base ?? "").replace(/\/$/, "");
247
+ const path = `${cleanBase}/${encodeURI(blobName)}`;
248
+ return query ? `${path}?${query}` : path;
249
+ }
250
+
251
+ // src/cloud/gcs.ts
252
+ function createGcsAdapter(opts) {
253
+ return {
254
+ name: "gcs",
255
+ async upload(file, { onProgress, signal }) {
256
+ const url = await opts.getSignedUrl(file);
257
+ if (!url) throw err("config-error", "No signed URL returned", false);
258
+ const headers = {
259
+ "Content-Type": file.type || "application/octet-stream"
260
+ };
261
+ const { responseHeaders } = await presignedXhr({
262
+ url,
263
+ method: "PUT",
264
+ headers,
265
+ body: file.file,
266
+ signal,
267
+ onProgress
268
+ });
269
+ const publicUrl = opts.buildPublicUrl ? opts.buildPublicUrl(file, url) : url.split("?")[0] ?? url;
270
+ const result = { url: publicUrl };
271
+ if (responseHeaders.etag) result.etag = responseHeaders.etag.replace(/"/g, "");
272
+ return result;
273
+ }
274
+ };
275
+ }
276
+
277
+ export { createAzureBlobAdapter, createCloudinaryAdapter, createDigitalOceanAdapter, createFirebaseStorageAdapter, createGcsAdapter, createS3Adapter, createSupabaseAdapter };
278
+ //# sourceMappingURL=index.js.map
279
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cloud/shared.ts","../../src/cloud/s3.ts","../../src/cloud/cloudinary.ts","../../src/cloud/firebase.ts","../../src/cloud/supabase.ts","../../src/cloud/digitalocean.ts","../../src/cloud/azure.ts","../../src/cloud/gcs.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { UploadError, UploadFile } from '../types';\n\n/**\n * Shared XHR helper for cloud adapters. Sends a single request with progress\n * callbacks. Returns response headers as a plain object so adapters can\n * pluck ETags etc.\n */\nexport interface PresignedPutParams {\n url: string;\n method?: 'PUT' | 'POST' | 'PATCH';\n headers?: Record<string, string>;\n body: Blob | FormData;\n signal: AbortSignal;\n onProgress: (loaded: number, total: number) => void;\n}\n\nexport function presignedXhr(\n params: PresignedPutParams,\n): Promise<{ status: number; responseText: string; responseHeaders: Record<string, string> }> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open(params.method ?? 'PUT', params.url, true);\n for (const [k, v] of Object.entries(params.headers ?? {})) {\n try {\n xhr.setRequestHeader(k, v);\n } catch {\n /* ignore */\n }\n }\n xhr.upload.addEventListener('progress', (ev) => {\n if (ev.lengthComputable) params.onProgress(ev.loaded, ev.total);\n });\n xhr.addEventListener('load', () => {\n const headers = parseHeaders(xhr.getAllResponseHeaders());\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve({ status: xhr.status, responseText: xhr.responseText, responseHeaders: headers });\n } else {\n reject(\n err(\n `http-${xhr.status}`,\n `Upload failed: ${xhr.status}`,\n xhr.status >= 500 || xhr.status === 408 || xhr.status === 429,\n ),\n );\n }\n });\n xhr.addEventListener('error', () => reject(err('network', 'Network error', true)));\n xhr.addEventListener('abort', () => reject(err('cancelled', 'Upload cancelled', false)));\n params.signal.addEventListener('abort', () => xhr.abort(), { once: true });\n xhr.send(params.body);\n });\n}\n\nexport function err(code: string, message: string, retryable: boolean, cause?: unknown): UploadError {\n return { code, message, retryable, cause };\n}\n\nfunction parseHeaders(raw: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const line of raw.split(/\\r?\\n/)) {\n const idx = line.indexOf(':');\n if (idx > 0) {\n const k = line.slice(0, idx).trim().toLowerCase();\n const v = line.slice(idx + 1).trim();\n if (k) out[k] = v;\n }\n }\n return out;\n}\n\nexport type PresignFn = (\n file: UploadFile,\n) => Promise<{ url: string; method?: 'PUT' | 'POST'; headers?: Record<string, string>; fields?: Record<string, string> }>;\n","import type { CloudAdapter, CloudUploadResult, UploadFile } from '../types';\nimport { presignedXhr, type PresignFn } from './shared';\n\nexport interface S3AdapterOptions {\n /**\n * Returns a presigned upload URL for a single file. The caller is responsible\n * for generating this server-side; this adapter never holds AWS credentials.\n *\n * For PUT uploads, return { url, method: 'PUT' }.\n * For POST uploads with form fields, return { url, method: 'POST', fields }.\n */\n getPresignedUrl: PresignFn;\n /**\n * Optional callback to derive the final object URL. Defaults to the\n * presigned URL with query strings stripped.\n */\n buildPublicUrl?: (file: UploadFile, presignedUrl: string) => string;\n}\n\n/**\n * AWS S3 adapter using presigned URLs. Supports both POST (with policy fields)\n * and PUT uploads. For multipart uploads, generate a separate presigned URL\n * per part on the server and use the chunkUpload + getUploadToken combo\n * instead.\n */\nexport function createS3Adapter(opts: S3AdapterOptions): CloudAdapter {\n return {\n name: 's3',\n async upload(file, { onProgress, signal }) {\n const presigned = await opts.getPresignedUrl(file);\n let body: Blob | FormData = file.file;\n const headers: Record<string, string> = { ...(presigned.headers ?? {}) };\n const method = presigned.method ?? 'PUT';\n\n if (method === 'POST' && presigned.fields) {\n const fd = new FormData();\n for (const [k, v] of Object.entries(presigned.fields)) fd.append(k, v);\n fd.append('file', file.file, file.name);\n body = fd;\n } else if (method === 'PUT') {\n if (!headers['Content-Type'] && file.type) headers['Content-Type'] = file.type;\n }\n\n const { responseHeaders } = await presignedXhr({\n url: presigned.url,\n method,\n headers,\n body,\n signal,\n onProgress,\n });\n\n const publicUrl = opts.buildPublicUrl\n ? opts.buildPublicUrl(file, presigned.url)\n : presigned.url.split('?')[0] ?? presigned.url;\n\n const result: CloudUploadResult = { url: publicUrl };\n if (responseHeaders.etag) result.etag = responseHeaders.etag.replace(/\"/g, '');\n return result;\n },\n };\n}\n","import type { CloudAdapter, UploadFile } from '../types';\nimport { err, presignedXhr } from './shared';\n\nexport interface CloudinaryAdapterOptions {\n cloudName: string;\n /** Unsigned upload preset, or omit if using `getSignature`. */\n uploadPreset?: string;\n /**\n * For signed uploads, returns { signature, timestamp, apiKey, ...extra }\n * generated server-side. Required when uploadPreset is omitted.\n */\n getSignature?: (file: UploadFile) => Promise<{\n signature: string;\n timestamp: number;\n apiKey: string;\n folder?: string;\n publicId?: string;\n [k: string]: string | number | undefined;\n }>;\n /** Optional folder prefix for the asset. */\n folder?: string;\n /** Resource type (auto handles images, videos, raw). */\n resourceType?: 'auto' | 'image' | 'video' | 'raw';\n}\n\n/**\n * Cloudinary adapter. Uses the upload endpoint with either an unsigned preset\n * or a server-side signature. Returns the secure_url from the response.\n */\nexport function createCloudinaryAdapter(opts: CloudinaryAdapterOptions): CloudAdapter {\n const resource = opts.resourceType ?? 'auto';\n const endpoint = `https://api.cloudinary.com/v1_1/${opts.cloudName}/${resource}/upload`;\n\n return {\n name: 'cloudinary',\n async upload(file, { onProgress, signal }) {\n const fd = new FormData();\n fd.append('file', file.file, file.name);\n\n if (opts.uploadPreset) {\n fd.append('upload_preset', opts.uploadPreset);\n } else if (opts.getSignature) {\n const sig = await opts.getSignature(file);\n fd.append('api_key', sig.apiKey);\n fd.append('timestamp', String(sig.timestamp));\n fd.append('signature', sig.signature);\n for (const [k, v] of Object.entries(sig)) {\n if (['apiKey', 'timestamp', 'signature'].includes(k)) continue;\n if (v !== undefined) fd.append(k, String(v));\n }\n } else {\n throw err('config-error', 'Cloudinary adapter needs uploadPreset or getSignature', false);\n }\n if (opts.folder) fd.append('folder', opts.folder);\n\n const { responseText } = await presignedXhr({\n url: endpoint,\n method: 'POST',\n body: fd,\n signal,\n onProgress,\n });\n try {\n const json = JSON.parse(responseText) as {\n secure_url?: string;\n public_id?: string;\n etag?: string;\n };\n return {\n url: json.secure_url ?? '',\n key: json.public_id,\n etag: json.etag,\n metadata: json as unknown as Record<string, unknown>,\n };\n } catch {\n throw err('parse-error', 'Invalid Cloudinary response', false);\n }\n },\n };\n}\n","import type { CloudAdapter, UploadFile } from '../types';\nimport { err, presignedXhr } from './shared';\n\nexport interface FirebaseStorageAdapterOptions {\n bucket: string;\n /** Returns a Firebase Storage auth token for the user. */\n getIdToken: () => Promise<string>;\n /** Path builder. Defaults to `uploads/<file.name>`. */\n buildPath?: (file: UploadFile) => string;\n /** Custom metadata. */\n customMetadata?: (file: UploadFile) => Record<string, string>;\n}\n\n/**\n * Firebase Storage adapter using the v0 REST upload API.\n * Avoids pulling in firebase-js-sdk so the package stays lightweight.\n *\n * Endpoint: POST https://firebasestorage.googleapis.com/v0/b/{BUCKET}/o?name={ENCODED_PATH}\n */\nexport function createFirebaseStorageAdapter(\n opts: FirebaseStorageAdapterOptions,\n): CloudAdapter {\n return {\n name: 'firebase-storage',\n async upload(file, { onProgress, signal }) {\n const path = opts.buildPath ? opts.buildPath(file) : `uploads/${file.name}`;\n const token = await opts.getIdToken();\n const url = `https://firebasestorage.googleapis.com/v0/b/${encodeURIComponent(\n opts.bucket,\n )}/o?name=${encodeURIComponent(path)}&uploadType=media`;\n\n const headers: Record<string, string> = {\n Authorization: `Firebase ${token}`,\n 'Content-Type': file.type || 'application/octet-stream',\n };\n const custom = opts.customMetadata?.(file);\n if (custom) {\n for (const [k, v] of Object.entries(custom)) {\n headers[`X-Goog-Meta-${k}`] = v;\n }\n }\n\n const { responseText } = await presignedXhr({\n url,\n method: 'POST',\n headers,\n body: file.file,\n signal,\n onProgress,\n });\n\n try {\n const meta = JSON.parse(responseText) as {\n name?: string;\n downloadTokens?: string;\n bucket?: string;\n generation?: string;\n md5Hash?: string;\n };\n const downloadUrl = meta.downloadTokens\n ? `https://firebasestorage.googleapis.com/v0/b/${meta.bucket}/o/${encodeURIComponent(\n meta.name ?? path,\n )}?alt=media&token=${meta.downloadTokens.split(',')[0]}`\n : `https://firebasestorage.googleapis.com/v0/b/${meta.bucket}/o/${encodeURIComponent(\n meta.name ?? path,\n )}?alt=media`;\n return {\n url: downloadUrl,\n key: meta.name,\n metadata: meta as unknown as Record<string, unknown>,\n };\n } catch {\n throw err('parse-error', 'Invalid Firebase response', false);\n }\n },\n };\n}\n","import type { CloudAdapter, UploadFile } from '../types';\nimport { presignedXhr } from './shared';\n\nexport interface SupabaseStorageAdapterOptions {\n /** e.g. https://xyz.supabase.co */\n projectUrl: string;\n bucket: string;\n /** Anon key or user JWT — depending on RLS policy. */\n getToken: () => string | Promise<string>;\n buildPath?: (file: UploadFile) => string;\n /** Overwrite if path already exists. Default false. */\n upsert?: boolean;\n}\n\n/**\n * Supabase Storage adapter using the public REST endpoint:\n * POST {projectUrl}/storage/v1/object/{bucket}/{path}\n */\nexport function createSupabaseAdapter(\n opts: SupabaseStorageAdapterOptions,\n): CloudAdapter {\n return {\n name: 'supabase-storage',\n async upload(file, { onProgress, signal }) {\n const path = opts.buildPath ? opts.buildPath(file) : file.name;\n const token = await opts.getToken();\n const url = `${opts.projectUrl.replace(/\\/$/, '')}/storage/v1/object/${\n opts.bucket\n }/${encodeURI(path)}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${token}`,\n apikey: token,\n 'Content-Type': file.type || 'application/octet-stream',\n 'x-upsert': opts.upsert ? 'true' : 'false',\n };\n const { responseText } = await presignedXhr({\n url,\n method: 'POST',\n headers,\n body: file.file,\n signal,\n onProgress,\n });\n const publicUrl = `${opts.projectUrl.replace(/\\/$/, '')}/storage/v1/object/public/${\n opts.bucket\n }/${encodeURI(path)}`;\n let metadata: Record<string, unknown> = {};\n try {\n metadata = JSON.parse(responseText) as Record<string, unknown>;\n } catch {\n /* not JSON */\n }\n return { url: publicUrl, key: path, metadata };\n },\n };\n}\n","import type { CloudAdapter } from '../types';\nimport { createS3Adapter, type S3AdapterOptions } from './s3';\n\n/**\n * DigitalOcean Spaces is S3-compatible, so this is a thin re-export.\n * Use createS3Adapter under the hood with a Spaces presigned URL endpoint.\n */\nexport function createDigitalOceanAdapter(opts: S3AdapterOptions): CloudAdapter {\n const inner = createS3Adapter(opts);\n return { ...inner, name: 'digitalocean-spaces' };\n}\n","import type { CloudAdapter, UploadFile } from '../types';\nimport { presignedXhr } from './shared';\n\nexport interface AzureBlobAdapterOptions {\n /** The container URL with a SAS token, e.g. https://acct.blob.core.windows.net/container?sv=... */\n getSasUrl: (file: UploadFile) => Promise<string>;\n /** Blob name builder. Defaults to file.name. */\n buildBlobName?: (file: UploadFile) => string;\n}\n\n/**\n * Azure Blob Storage adapter using a SAS-token URL. The SAS URL must grant\n * the 'create'/'write' permission. This adapter uses a single PutBlob request\n * (no block-blob staging) — for files larger than 256MB, use the chunkUpload\n * path with a server-side block-id mint.\n */\nexport function createAzureBlobAdapter(opts: AzureBlobAdapterOptions): CloudAdapter {\n return {\n name: 'azure-blob',\n async upload(file, { onProgress, signal }) {\n const sasUrl = await opts.getSasUrl(file);\n const blobName = opts.buildBlobName ? opts.buildBlobName(file) : file.name;\n // Inject blob name as a path segment before any query string.\n const url = injectBlobName(sasUrl, blobName);\n const headers: Record<string, string> = {\n 'x-ms-blob-type': 'BlockBlob',\n 'Content-Type': file.type || 'application/octet-stream',\n };\n await presignedXhr({\n url,\n method: 'PUT',\n headers,\n body: file.file,\n signal,\n onProgress,\n });\n return {\n url: url.split('?')[0] ?? url,\n key: blobName,\n };\n },\n };\n}\n\nfunction injectBlobName(sasUrl: string, blobName: string): string {\n const [base, query] = sasUrl.split('?');\n const cleanBase = (base ?? '').replace(/\\/$/, '');\n const path = `${cleanBase}/${encodeURI(blobName)}`;\n return query ? `${path}?${query}` : path;\n}\n","import type { CloudAdapter, UploadFile } from '../types';\nimport { err, presignedXhr } from './shared';\n\nexport interface GcsAdapterOptions {\n /** Returns a v4-signed PUT URL for the file. */\n getSignedUrl: (file: UploadFile) => Promise<string>;\n /** Public download URL builder. */\n buildPublicUrl?: (file: UploadFile, signedUrl: string) => string;\n}\n\n/**\n * Google Cloud Storage adapter using a v4-signed URL. The signing must happen\n * server-side; this adapter just performs the PUT.\n */\nexport function createGcsAdapter(opts: GcsAdapterOptions): CloudAdapter {\n return {\n name: 'gcs',\n async upload(file, { onProgress, signal }) {\n const url = await opts.getSignedUrl(file);\n if (!url) throw err('config-error', 'No signed URL returned', false);\n const headers: Record<string, string> = {\n 'Content-Type': file.type || 'application/octet-stream',\n };\n const { responseHeaders } = await presignedXhr({\n url,\n method: 'PUT',\n headers,\n body: file.file,\n signal,\n onProgress,\n });\n const publicUrl = opts.buildPublicUrl\n ? opts.buildPublicUrl(file, url)\n : url.split('?')[0] ?? url;\n const result: { url: string; etag?: string } = { url: publicUrl };\n if (responseHeaders.etag) result.etag = responseHeaders.etag.replace(/\"/g, '');\n return result;\n },\n };\n}\n"]}