img-upload-kit 1.1.1 → 2.0.1

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.
Files changed (111) hide show
  1. package/CHANGELOG.md +89 -74
  2. package/MIGRATION.md +192 -0
  3. package/README.md +243 -259
  4. package/dist/bin.js +1186 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/blur.cjs +40 -412
  7. package/dist/blur.cjs.map +1 -1
  8. package/dist/blur.d.cts +5 -5
  9. package/dist/blur.d.ts +5 -5
  10. package/dist/blur.js +39 -393
  11. package/dist/blur.js.map +1 -1
  12. package/dist/blurDataUtils-DveBRtqE.d.cts +43 -0
  13. package/dist/blurDataUtils-yx9Q6HlU.d.ts +43 -0
  14. package/dist/blurService-D5TpMNji.d.ts +83 -0
  15. package/dist/blurService-DacwylJx.d.cts +83 -0
  16. package/dist/chunk-25GYKQ6J.js +158 -0
  17. package/dist/chunk-25GYKQ6J.js.map +1 -0
  18. package/dist/chunk-34L4TEE3.cjs +226 -0
  19. package/dist/chunk-34L4TEE3.cjs.map +1 -0
  20. package/dist/chunk-DFCSN3X7.cjs +158 -0
  21. package/dist/chunk-DFCSN3X7.cjs.map +1 -0
  22. package/dist/chunk-EWVIL57J.js +294 -0
  23. package/dist/chunk-EWVIL57J.js.map +1 -0
  24. package/dist/chunk-FP5YIWG5.cjs +135 -0
  25. package/dist/chunk-FP5YIWG5.cjs.map +1 -0
  26. package/dist/chunk-HILELQGS.js +226 -0
  27. package/dist/chunk-HILELQGS.js.map +1 -0
  28. package/dist/chunk-IDLLCVGB.js +2370 -0
  29. package/dist/chunk-IDLLCVGB.js.map +1 -0
  30. package/dist/chunk-J6A44Y3W.js +514 -0
  31. package/dist/chunk-J6A44Y3W.js.map +1 -0
  32. package/dist/chunk-LOSVFO56.cjs +145 -0
  33. package/dist/chunk-LOSVFO56.cjs.map +1 -0
  34. package/dist/chunk-MBQJ6NVJ.cjs +294 -0
  35. package/dist/chunk-MBQJ6NVJ.cjs.map +1 -0
  36. package/dist/chunk-MHGQEBPW.js +145 -0
  37. package/dist/chunk-MHGQEBPW.js.map +1 -0
  38. package/dist/chunk-QHU7FUO4.cjs +37 -0
  39. package/dist/chunk-QHU7FUO4.cjs.map +1 -0
  40. package/dist/chunk-S2IFAWJK.js +37 -0
  41. package/dist/chunk-S2IFAWJK.js.map +1 -0
  42. package/dist/chunk-TA5Y64JM.cjs +2370 -0
  43. package/dist/chunk-TA5Y64JM.cjs.map +1 -0
  44. package/dist/chunk-VYVZVUNI.js +135 -0
  45. package/dist/chunk-VYVZVUNI.js.map +1 -0
  46. package/dist/chunk-XKQMCY5Z.cjs +514 -0
  47. package/dist/chunk-XKQMCY5Z.cjs.map +1 -0
  48. package/dist/cli.cjs +273 -76
  49. package/dist/cli.cjs.map +1 -1
  50. package/dist/cli.js +269 -73
  51. package/dist/cli.js.map +1 -1
  52. package/dist/headless.cjs +8 -1880
  53. package/dist/headless.cjs.map +1 -1
  54. package/dist/headless.d.cts +3 -1
  55. package/dist/headless.d.ts +3 -1
  56. package/dist/headless.js +25 -1886
  57. package/dist/headless.js.map +1 -1
  58. package/dist/index-BIdnqtW8.d.ts +234 -0
  59. package/dist/index-Brk_uHs6.d.cts +234 -0
  60. package/dist/{index-FVXXKf2v.d.ts → index-CPMlaQsC.d.ts} +76 -153
  61. package/dist/{index-FVXXKf2v.d.cts → index-DHINe-rA.d.cts} +76 -153
  62. package/dist/index.cjs +64 -532
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +7 -4
  65. package/dist/index.d.ts +7 -4
  66. package/dist/index.js +63 -504
  67. package/dist/index.js.map +1 -1
  68. package/dist/{memoryAdapter-CBu7_3Pp.d.ts → memoryAdapter-Ck0ccZbF.d.cts} +3 -2
  69. package/dist/{memoryAdapter-BJjyLR-1.d.cts → memoryAdapter-DxEKaG0b.d.ts} +3 -2
  70. package/dist/placeholders-C4ftwaII.d.ts +5 -0
  71. package/dist/placeholders-x_3rt65M.d.cts +5 -0
  72. package/dist/react.cjs +1952 -3257
  73. package/dist/react.cjs.map +1 -1
  74. package/dist/react.d.cts +188 -50
  75. package/dist/react.d.ts +188 -50
  76. package/dist/react.js +1763 -3013
  77. package/dist/react.js.map +1 -1
  78. package/dist/server-handler.cjs +14 -0
  79. package/dist/server-handler.cjs.map +1 -0
  80. package/dist/server-handler.d.cts +3 -0
  81. package/dist/server-handler.d.ts +3 -0
  82. package/dist/server-handler.js +14 -0
  83. package/dist/server-handler.js.map +1 -0
  84. package/dist/server.cjs +1096 -532
  85. package/dist/server.cjs.map +1 -1
  86. package/dist/server.d.cts +77 -154
  87. package/dist/server.d.ts +77 -154
  88. package/dist/server.js +1050 -448
  89. package/dist/server.js.map +1 -1
  90. package/dist/storage.cjs +239 -232
  91. package/dist/storage.cjs.map +1 -1
  92. package/dist/storage.d.cts +21 -5
  93. package/dist/storage.d.ts +21 -5
  94. package/dist/storage.js +227 -207
  95. package/dist/storage.js.map +1 -1
  96. package/dist/styles/animated-image.css +112 -0
  97. package/dist/styles/dropzone-and-tiles.css +247 -0
  98. package/dist/styles/modals-and-dock.css +248 -0
  99. package/dist/styles/primitives.css +197 -0
  100. package/dist/styles/quality-and-inputs.css +184 -0
  101. package/dist/styles/tokens.css +123 -0
  102. package/dist/styles.css +235 -220
  103. package/dist/types-BrP-o9nd.d.cts +85 -0
  104. package/dist/types-sZ3KoFQY.d.ts +85 -0
  105. package/dist/types-yShGNQnW.d.cts +61 -0
  106. package/dist/types-yShGNQnW.d.ts +61 -0
  107. package/dist/{types-CiLY0DwB.d.cts → upload-C5Trd6Dh.d.cts} +27 -53
  108. package/dist/{types-CiLY0DwB.d.ts → upload-C5Trd6Dh.d.ts} +27 -53
  109. package/package.json +92 -32
  110. package/dist/blurService-DqsnfPTd.d.cts +0 -172
  111. package/dist/blurService-DqsnfPTd.d.ts +0 -172
@@ -0,0 +1,514 @@
1
+ import {
2
+ parsePublicStorageUrl
3
+ } from "./chunk-S2IFAWJK.js";
4
+
5
+ // src/server/handler/responses.ts
6
+ var UploadValidationError = class extends Error {
7
+ status = 400;
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "UploadValidationError";
11
+ }
12
+ };
13
+ function isClientValidationError(error) {
14
+ if (!error) return false;
15
+ if (error instanceof UploadValidationError) return true;
16
+ if (typeof error === "object" && error !== null && "status" in error && error.status === 400) {
17
+ return true;
18
+ }
19
+ if (error instanceof Error && (error.name === "UploadValidationError" || error.name === "ValidationError")) {
20
+ return true;
21
+ }
22
+ return false;
23
+ }
24
+ function jsonResponse(body, status = 200) {
25
+ return new Response(JSON.stringify(body), {
26
+ status,
27
+ headers: { "Content-Type": "application/json" }
28
+ });
29
+ }
30
+ function handleUnexpectedError(error, ctx, onError) {
31
+ if (isClientValidationError(error)) {
32
+ const message = error instanceof Error ? error.message : "Invalid upload request";
33
+ return jsonResponse({ error: message }, 400);
34
+ }
35
+ if (typeof error === "object" && error !== null && "status" in error && typeof error.status === "number") {
36
+ const status = error.status;
37
+ if (status >= 400 && status < 500) {
38
+ const message = error instanceof Error ? error.message : "Request rejected";
39
+ return jsonResponse({ error: message }, status);
40
+ }
41
+ if (status === 503) {
42
+ return jsonResponse(
43
+ { error: "Server busy, please try again later" },
44
+ 503
45
+ );
46
+ }
47
+ }
48
+ try {
49
+ onError?.(error, ctx);
50
+ } catch {
51
+ }
52
+ return jsonResponse({ error: "Internal server error" }, 500);
53
+ }
54
+
55
+ // src/server/handler/guards.ts
56
+ var DEFAULT_FALLBACK_MAX_MB = 25;
57
+ var MIN_DEFAULT_BODY_BYTES = 1024 * 1024;
58
+ function resolveMaxBodyBytes(options) {
59
+ if (typeof options.maxBodyBytes === "number" && Number.isFinite(options.maxBodyBytes) && options.maxBodyBytes > 0) {
60
+ return Math.floor(options.maxBodyBytes);
61
+ }
62
+ const uploaderMaxMB = typeof options.uploader.getMaxSizeMB === "function" ? options.uploader.getMaxSizeMB(options.allowedPresets) : DEFAULT_FALLBACK_MAX_MB;
63
+ const computed = Math.ceil(1.25 * uploaderMaxMB * 1024 * 1024);
64
+ return Math.max(MIN_DEFAULT_BODY_BYTES, computed);
65
+ }
66
+ function extractRequestOrigin(request) {
67
+ const originHeader = request.headers.get("origin");
68
+ if (originHeader && originHeader !== "null") {
69
+ return originHeader.trim();
70
+ }
71
+ const refererHeader = request.headers.get("referer");
72
+ if (refererHeader) {
73
+ try {
74
+ return new URL(refererHeader).origin;
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+ return null;
80
+ }
81
+ function checkAllowedOrigin(request, allowedOrigins) {
82
+ if (!allowedOrigins) return null;
83
+ const origin = extractRequestOrigin(request);
84
+ if (!origin) {
85
+ return jsonResponse({ error: "Forbidden origin" }, 403);
86
+ }
87
+ let allowed = false;
88
+ try {
89
+ allowed = typeof allowedOrigins === "function" ? Boolean(allowedOrigins(origin)) : allowedOrigins.includes(origin);
90
+ } catch {
91
+ allowed = false;
92
+ }
93
+ if (!allowed) {
94
+ return jsonResponse({ error: "Forbidden origin" }, 403);
95
+ }
96
+ return null;
97
+ }
98
+ async function runAuthorize(authorize, ctx) {
99
+ if (!authorize) return null;
100
+ try {
101
+ const allowed = await authorize(ctx);
102
+ if (!allowed) {
103
+ return jsonResponse({ error: "Unauthorized" }, 401);
104
+ }
105
+ return null;
106
+ } catch {
107
+ return jsonResponse({ error: "Unauthorized" }, 401);
108
+ }
109
+ }
110
+ async function readBoundedFormData(request, maxBodyBytes) {
111
+ const contentLengthRaw = request.headers.get("content-length");
112
+ if (contentLengthRaw !== null && contentLengthRaw.trim() !== "") {
113
+ const contentLength = Number(contentLengthRaw);
114
+ if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) {
115
+ return {
116
+ earlyErrorResponse: jsonResponse(
117
+ {
118
+ error: `Payload too large: request body (${contentLength} bytes) exceeds limit of ${maxBodyBytes} bytes`
119
+ },
120
+ 413
121
+ )
122
+ };
123
+ }
124
+ }
125
+ if (!request.body) {
126
+ try {
127
+ const formData = await request.formData();
128
+ return { formData };
129
+ } catch {
130
+ return {};
131
+ }
132
+ }
133
+ const reader = request.body.getReader();
134
+ const chunks = [];
135
+ let totalBytes = 0;
136
+ try {
137
+ while (true) {
138
+ const { done, value } = await reader.read();
139
+ if (done) break;
140
+ if (value) {
141
+ totalBytes += value.byteLength;
142
+ if (totalBytes > maxBodyBytes) {
143
+ await reader.cancel().catch(() => {
144
+ });
145
+ return {
146
+ earlyErrorResponse: jsonResponse(
147
+ {
148
+ error: `Payload too large: streamed body exceeds limit of ${maxBodyBytes} bytes`
149
+ },
150
+ 413
151
+ )
152
+ };
153
+ }
154
+ chunks.push(value);
155
+ }
156
+ }
157
+ } finally {
158
+ try {
159
+ reader.releaseLock();
160
+ } catch {
161
+ }
162
+ }
163
+ if (totalBytes === 0) {
164
+ return {};
165
+ }
166
+ const combined = new Uint8Array(totalBytes);
167
+ let offset = 0;
168
+ for (const chunk of chunks) {
169
+ combined.set(chunk, offset);
170
+ offset += chunk.byteLength;
171
+ }
172
+ try {
173
+ const formData = await new Response(combined, {
174
+ headers: request.headers
175
+ }).formData();
176
+ return { formData };
177
+ } catch {
178
+ return {};
179
+ }
180
+ }
181
+
182
+ // src/server/handler/deleteRoute.ts
183
+ function parseVirtualHostedS3Bucket(url) {
184
+ try {
185
+ const parsed = new URL(url);
186
+ const s3Match = parsed.hostname.match(
187
+ /^([a-z0-9.-]+)\.s3(?:[.-][a-z0-9-]+)?\.amazonaws\.com$/i
188
+ );
189
+ if (s3Match) {
190
+ const key = decodeURIComponent(parsed.pathname).replace(/^\/+/, "");
191
+ if (key) {
192
+ return { bucket: s3Match[1], path: key };
193
+ }
194
+ }
195
+ } catch {
196
+ }
197
+ return null;
198
+ }
199
+ function resolveAllowedBuckets(options) {
200
+ if (options.allowedBuckets && options.allowedBuckets.length > 0) {
201
+ return new Set(options.allowedBuckets);
202
+ }
203
+ if (typeof options.uploader.getAllowedBuckets === "function") {
204
+ return new Set(options.uploader.getAllowedBuckets());
205
+ }
206
+ if (options.uploader.defaultBucket) {
207
+ return /* @__PURE__ */ new Set([options.uploader.defaultBucket]);
208
+ }
209
+ return /* @__PURE__ */ new Set(["images"]);
210
+ }
211
+ function createDeleteHandler(options) {
212
+ const {
213
+ uploader,
214
+ authorize,
215
+ allowedOrigins,
216
+ allowClientBucketOverride = false,
217
+ allowedBuckets,
218
+ onError
219
+ } = options;
220
+ return async function DELETE(request) {
221
+ try {
222
+ const originError = checkAllowedOrigin(request, allowedOrigins);
223
+ if (originError) return originError;
224
+ const { searchParams } = new URL(request.url);
225
+ const url = searchParams.get("url");
226
+ const storagePath = searchParams.get("path");
227
+ const bucketParam = searchParams.get("bucket");
228
+ const allowedBucketSet = resolveAllowedBuckets(options);
229
+ const parsedFromUrl = url ? (typeof uploader.parseUrl === "function" ? uploader.parseUrl(url) : null) ?? parsePublicStorageUrl(url) ?? parseVirtualHostedS3Bucket(url) : null;
230
+ const resolvedPath = parsedFromUrl?.path ?? storagePath ?? void 0;
231
+ const resolvedBucket = parsedFromUrl?.bucket ?? (bucketParam || uploader.defaultBucket || "images");
232
+ const authError = await runAuthorize(authorize, {
233
+ action: "delete",
234
+ request,
235
+ ...resolvedPath ? { path: resolvedPath } : {},
236
+ ...resolvedBucket ? { bucket: resolvedBucket } : {}
237
+ });
238
+ if (authError) return authError;
239
+ if (url) {
240
+ if (parsedFromUrl?.bucket) {
241
+ if (allowedBuckets && allowedBuckets.length > 0 && !allowedBuckets.includes(parsedFromUrl.bucket)) {
242
+ return jsonResponse(
243
+ { error: `Forbidden bucket "${parsedFromUrl.bucket}"` },
244
+ 403
245
+ );
246
+ }
247
+ if (!allowClientBucketOverride && allowedBucketSet.size > 0 && !allowedBucketSet.has(parsedFromUrl.bucket)) {
248
+ return jsonResponse(
249
+ { error: `Forbidden bucket "${parsedFromUrl.bucket}"` },
250
+ 403
251
+ );
252
+ }
253
+ }
254
+ const deleted = await uploader.deleteByUrl(url);
255
+ return jsonResponse({ success: deleted }, deleted ? 200 : 400);
256
+ }
257
+ if (bucketParam) {
258
+ if (allowedBuckets && allowedBuckets.length > 0 && !allowedBuckets.includes(bucketParam)) {
259
+ return jsonResponse(
260
+ { error: `Forbidden bucket "${bucketParam}"` },
261
+ 403
262
+ );
263
+ }
264
+ if (!allowClientBucketOverride && allowedBucketSet.size > 0 && !allowedBucketSet.has(bucketParam)) {
265
+ return jsonResponse(
266
+ { error: `Forbidden bucket "${bucketParam}"` },
267
+ 403
268
+ );
269
+ }
270
+ }
271
+ if (!storagePath) {
272
+ return jsonResponse(
273
+ { error: 'Missing required "path" or "url" query parameter' },
274
+ 400
275
+ );
276
+ }
277
+ const bucket = allowClientBucketOverride ? bucketParam || void 0 : void 0;
278
+ await uploader.deleteImage(storagePath, bucket);
279
+ return jsonResponse({ success: true }, 200);
280
+ } catch (error) {
281
+ return handleUnexpectedError(
282
+ error,
283
+ { action: "delete", request },
284
+ onError
285
+ );
286
+ }
287
+ };
288
+ }
289
+
290
+ // src/server/handler/parseUploadForm.ts
291
+ var ALLOWED_OUTPUT_FORMATS = /* @__PURE__ */ new Set(["jpeg", "png", "webp", "avif"]);
292
+ function parseJsonRecord(raw) {
293
+ if (!raw) return void 0;
294
+ try {
295
+ const parsed = JSON.parse(raw);
296
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
297
+ const clean = {};
298
+ for (const [k, v] of Object.entries(parsed)) {
299
+ if (v !== void 0 && v !== null) {
300
+ clean[k] = String(v);
301
+ }
302
+ }
303
+ return Object.keys(clean).length > 0 ? clean : void 0;
304
+ }
305
+ } catch {
306
+ }
307
+ return void 0;
308
+ }
309
+ function peekUploadAuthContext(formData, request, options) {
310
+ const preset = formData?.get("preset") || void 0;
311
+ const rawBucket = formData?.get("bucket") || void 0;
312
+ const rawFolder = formData?.get("folder") || void 0;
313
+ const presetConfig = preset && typeof options.uploader.resolvePreset === "function" ? options.uploader.resolvePreset(preset) : void 0;
314
+ const bucket = options.allowClientBucketOverride ? rawBucket || presetConfig?.bucket || options.uploader.defaultBucket : presetConfig?.bucket || options.uploader.defaultBucket;
315
+ const folder = options.allowClientOverrides?.folder ? rawFolder || presetConfig?.folder : presetConfig?.folder;
316
+ return {
317
+ action: "upload",
318
+ request,
319
+ ...folder ? { path: folder } : {},
320
+ ...bucket ? { bucket } : {},
321
+ ...preset ? { preset } : {}
322
+ };
323
+ }
324
+ function parseUploadForm(formData, authCtx, options) {
325
+ if (!formData) {
326
+ return {
327
+ errorResponse: jsonResponse({ error: "No file provided" }, 400)
328
+ };
329
+ }
330
+ const file = formData.get("file");
331
+ if (!file || typeof file === "string" || typeof file.size === "number" && file.size === 0 && !file.name) {
332
+ return {
333
+ errorResponse: jsonResponse({ error: "No file provided" }, 400)
334
+ };
335
+ }
336
+ if (typeof file.size === "number" && file.size === 0) {
337
+ return {
338
+ errorResponse: jsonResponse({ error: "File is empty" }, 400)
339
+ };
340
+ }
341
+ const {
342
+ uploader,
343
+ allowedPresets,
344
+ allowClientOverrides,
345
+ allowClientBucketOverride = false,
346
+ allowedBuckets
347
+ } = options;
348
+ const preset = formData.get("preset") || void 0;
349
+ if (preset) {
350
+ if (allowedPresets && allowedPresets.length > 0 && !allowedPresets.includes(preset)) {
351
+ return {
352
+ errorResponse: jsonResponse(
353
+ {
354
+ error: `Invalid preset "${preset}". Allowed presets: ${allowedPresets.join(", ")}`
355
+ },
356
+ 400
357
+ )
358
+ };
359
+ }
360
+ if (typeof uploader.resolvePreset === "function" && !uploader.resolvePreset(preset)) {
361
+ return {
362
+ errorResponse: jsonResponse(
363
+ { error: `Invalid preset "${preset}".` },
364
+ 400
365
+ )
366
+ };
367
+ }
368
+ }
369
+ const presetConfig = preset && typeof uploader.resolvePreset === "function" ? uploader.resolvePreset(preset) : void 0;
370
+ const rawBucket = formData.get("bucket") || void 0;
371
+ if (rawBucket && allowedBuckets && !allowedBuckets.includes(rawBucket)) {
372
+ return {
373
+ errorResponse: jsonResponse(
374
+ { error: `Bucket "${rawBucket}" is not allowed` },
375
+ 403
376
+ )
377
+ };
378
+ }
379
+ const bucket = allowClientBucketOverride ? rawBucket : void 0;
380
+ let folder;
381
+ const rawFolder = formData.get("folder") || void 0;
382
+ if (rawFolder !== void 0) {
383
+ if (allowClientOverrides?.folder === true) {
384
+ folder = rawFolder;
385
+ } else if (typeof allowClientOverrides?.folder === "function") {
386
+ const scoped = allowClientOverrides.folder(rawFolder, authCtx);
387
+ if (scoped === null) {
388
+ return {
389
+ errorResponse: jsonResponse(
390
+ { error: "Invalid or disallowed folder" },
391
+ 400
392
+ )
393
+ };
394
+ }
395
+ folder = scoped || void 0;
396
+ }
397
+ }
398
+ const rawFilename = formData.get("customFilename") || void 0;
399
+ const customFilename = allowClientOverrides?.filename ? rawFilename : void 0;
400
+ const outputFormatRaw = formData.get("outputFormat");
401
+ if (outputFormatRaw && !ALLOWED_OUTPUT_FORMATS.has(outputFormatRaw)) {
402
+ return {
403
+ errorResponse: jsonResponse(
404
+ {
405
+ error: `Invalid output format "${outputFormatRaw}". Allowed formats: jpeg, png, webp, avif`
406
+ },
407
+ 400
408
+ )
409
+ };
410
+ }
411
+ const outputFormat = allowClientOverrides?.format && outputFormatRaw ? outputFormatRaw : void 0;
412
+ const qualityRaw = formData.get("quality");
413
+ let quality;
414
+ if (qualityRaw !== null && qualityRaw.trim() !== "") {
415
+ const parsedQuality = Number(qualityRaw);
416
+ if (!Number.isFinite(parsedQuality) || !Number.isInteger(parsedQuality) || parsedQuality < 1 || parsedQuality > 100) {
417
+ return {
418
+ errorResponse: jsonResponse(
419
+ {
420
+ error: `Invalid quality "${qualityRaw}". Must be an integer between 1 and 100.`
421
+ },
422
+ 400
423
+ )
424
+ };
425
+ }
426
+ if (allowClientOverrides?.quality) {
427
+ quality = presetConfig?.quality ? Math.min(parsedQuality, presetConfig.quality) : parsedQuality;
428
+ }
429
+ }
430
+ const maxWidthRaw = formData.get("maxWidth");
431
+ const maxHeightRaw = formData.get("maxHeight");
432
+ let maxDimensions;
433
+ if (maxWidthRaw !== null && maxWidthRaw.trim() !== "" || maxHeightRaw !== null && maxHeightRaw.trim() !== "") {
434
+ const w = Number(maxWidthRaw);
435
+ const h = Number(maxHeightRaw);
436
+ if (!Number.isFinite(w) || !Number.isFinite(h) || w < 1 || h < 1) {
437
+ return {
438
+ errorResponse: jsonResponse(
439
+ { error: "Invalid maxWidth or maxHeight dimensions" },
440
+ 400
441
+ )
442
+ };
443
+ }
444
+ if (allowClientOverrides?.dimensions) {
445
+ const cap = presetConfig?.maxDimensions;
446
+ maxDimensions = cap ? {
447
+ width: Math.min(Math.floor(w), cap.width),
448
+ height: Math.min(Math.floor(h), cap.height)
449
+ } : { width: Math.floor(w), height: Math.floor(h) };
450
+ }
451
+ }
452
+ const altText = formData.get("altText") || void 0;
453
+ const metadata = parseJsonRecord(formData.get("metadata"));
454
+ return {
455
+ file,
456
+ uploadOptions: {
457
+ preset,
458
+ folder,
459
+ bucket,
460
+ altText,
461
+ customFilename,
462
+ ...customFilename ? { isClientFilename: true } : {},
463
+ outputFormat,
464
+ quality,
465
+ maxDimensions,
466
+ metadata,
467
+ signal: authCtx.request.signal
468
+ }
469
+ };
470
+ }
471
+
472
+ // src/server/handler/index.ts
473
+ function createUploadHttpHandler(options) {
474
+ const { uploader, authorize, allowedOrigins, onError } = options;
475
+ const maxBodyBytes = resolveMaxBodyBytes(options);
476
+ async function POST(request) {
477
+ try {
478
+ const originError = checkAllowedOrigin(request, allowedOrigins);
479
+ if (originError) return originError;
480
+ const { formData, earlyErrorResponse } = await readBoundedFormData(
481
+ request,
482
+ maxBodyBytes
483
+ );
484
+ if (earlyErrorResponse) return earlyErrorResponse;
485
+ const authCtx = peekUploadAuthContext(formData, request, options);
486
+ const authError = await runAuthorize(authorize, authCtx);
487
+ if (authError) return authError;
488
+ const parsed = parseUploadForm(formData, authCtx, options);
489
+ if (parsed.errorResponse) return parsed.errorResponse;
490
+ const result = await uploader.uploadImage(
491
+ parsed.file,
492
+ parsed.uploadOptions
493
+ );
494
+ return jsonResponse(result, 200);
495
+ } catch (error) {
496
+ return handleUnexpectedError(
497
+ error,
498
+ { action: "upload", request },
499
+ onError
500
+ );
501
+ }
502
+ }
503
+ const DELETE = createDeleteHandler(options);
504
+ return { POST, DELETE };
505
+ }
506
+ var createUploadHandler = createUploadHttpHandler;
507
+
508
+ export {
509
+ UploadValidationError,
510
+ isClientValidationError,
511
+ createUploadHttpHandler,
512
+ createUploadHandler
513
+ };
514
+ //# sourceMappingURL=chunk-J6A44Y3W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/handler/responses.ts","../src/server/handler/guards.ts","../src/server/handler/deleteRoute.ts","../src/server/handler/parseUploadForm.ts","../src/server/handler/index.ts"],"sourcesContent":["import type { UploadErrorContext } from './options';\r\n\r\nexport class UploadValidationError extends Error {\r\n readonly status = 400;\r\n constructor(message: string) {\r\n super(message);\r\n this.name = 'UploadValidationError';\r\n }\r\n}\r\n\r\nexport function isClientValidationError(error: unknown): boolean {\r\n if (!error) return false;\r\n if (error instanceof UploadValidationError) return true;\r\n if (\r\n typeof error === 'object' &&\r\n error !== null &&\r\n 'status' in error &&\r\n (error as { status: unknown }).status === 400\r\n ) {\r\n return true;\r\n }\r\n if (\r\n error instanceof Error &&\r\n (error.name === 'UploadValidationError' || error.name === 'ValidationError')\r\n ) {\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\nexport function jsonResponse(body: unknown, status: number = 200): Response {\r\n return new Response(JSON.stringify(body), {\r\n status,\r\n headers: { 'Content-Type': 'application/json' },\r\n });\r\n}\r\n\r\nexport function handleUnexpectedError(\r\n error: unknown,\r\n ctx: UploadErrorContext,\r\n onError?: (error: unknown, ctx: UploadErrorContext) => void,\r\n): Response {\r\n if (isClientValidationError(error)) {\r\n const message =\r\n error instanceof Error ? error.message : 'Invalid upload request';\r\n return jsonResponse({ error: message }, 400);\r\n }\r\n\r\n if (\r\n typeof error === 'object' &&\r\n error !== null &&\r\n 'status' in error &&\r\n typeof (error as { status: unknown }).status === 'number'\r\n ) {\r\n const status = (error as { status: number }).status;\r\n if (status >= 400 && status < 500) {\r\n const message =\r\n error instanceof Error ? error.message : 'Request rejected';\r\n return jsonResponse({ error: message }, status);\r\n }\r\n if (status === 503) {\r\n return jsonResponse(\r\n { error: 'Server busy, please try again later' },\r\n 503,\r\n );\r\n }\r\n }\r\n\r\n try {\r\n onError?.(error, ctx);\r\n } catch {\r\n // Ignore secondary logger failures\r\n }\r\n\r\n return jsonResponse({ error: 'Internal server error' }, 500);\r\n}\r\n","import type { AuthorizeContext, UploadHttpHandlerOptions } from './options';\r\nimport { jsonResponse } from './responses';\r\n\r\nconst DEFAULT_FALLBACK_MAX_MB = 25;\r\nconst MIN_DEFAULT_BODY_BYTES = 1024 * 1024; // 1 MB floor when maxBodyBytes is not explicitly set\r\n\r\nexport function resolveMaxBodyBytes<TPreset extends string>(\r\n options: UploadHttpHandlerOptions<TPreset>,\r\n): number {\r\n if (\r\n typeof options.maxBodyBytes === 'number' &&\r\n Number.isFinite(options.maxBodyBytes) &&\r\n options.maxBodyBytes > 0\r\n ) {\r\n return Math.floor(options.maxBodyBytes);\r\n }\r\n\r\n const uploaderMaxMB =\r\n typeof options.uploader.getMaxSizeMB === 'function'\r\n ? options.uploader.getMaxSizeMB(options.allowedPresets)\r\n : DEFAULT_FALLBACK_MAX_MB;\r\n\r\n const computed = Math.ceil(1.25 * uploaderMaxMB * 1024 * 1024);\r\n return Math.max(MIN_DEFAULT_BODY_BYTES, computed);\r\n}\r\n\r\nfunction extractRequestOrigin(request: Request): string | null {\r\n const originHeader = request.headers.get('origin');\r\n if (originHeader && originHeader !== 'null') {\r\n return originHeader.trim();\r\n }\r\n\r\n const refererHeader = request.headers.get('referer');\r\n if (refererHeader) {\r\n try {\r\n return new URL(refererHeader).origin;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\nexport function checkAllowedOrigin(\r\n request: Request,\r\n allowedOrigins?: string[] | ((origin: string) => boolean),\r\n): Response | null {\r\n if (!allowedOrigins) return null;\r\n\r\n const origin = extractRequestOrigin(request);\r\n if (!origin) {\r\n return jsonResponse({ error: 'Forbidden origin' }, 403);\r\n }\r\n\r\n let allowed = false;\r\n try {\r\n allowed =\r\n typeof allowedOrigins === 'function'\r\n ? Boolean(allowedOrigins(origin))\r\n : allowedOrigins.includes(origin);\r\n } catch {\r\n allowed = false;\r\n }\r\n\r\n if (!allowed) {\r\n return jsonResponse({ error: 'Forbidden origin' }, 403);\r\n }\r\n\r\n return null;\r\n}\r\n\r\nexport async function runAuthorize(\r\n authorize: UploadHttpHandlerOptions['authorize'],\r\n ctx: AuthorizeContext,\r\n): Promise<Response | null> {\r\n if (!authorize) return null;\r\n\r\n try {\r\n const allowed = await authorize(ctx);\r\n if (!allowed) {\r\n return jsonResponse({ error: 'Unauthorized' }, 401);\r\n }\r\n return null;\r\n } catch {\r\n return jsonResponse({ error: 'Unauthorized' }, 401);\r\n }\r\n}\r\n\r\nexport interface BoundedFormDataResult {\r\n formData?: FormData;\r\n earlyErrorResponse?: Response;\r\n}\r\n\r\nexport async function readBoundedFormData(\r\n request: Request,\r\n maxBodyBytes: number,\r\n): Promise<BoundedFormDataResult> {\r\n const contentLengthRaw = request.headers.get('content-length');\r\n if (contentLengthRaw !== null && contentLengthRaw.trim() !== '') {\r\n const contentLength = Number(contentLengthRaw);\r\n if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) {\r\n return {\r\n earlyErrorResponse: jsonResponse(\r\n {\r\n error: `Payload too large: request body (${contentLength} bytes) exceeds limit of ${maxBodyBytes} bytes`,\r\n },\r\n 413,\r\n ),\r\n };\r\n }\r\n }\r\n\r\n if (!request.body) {\r\n try {\r\n const formData = await request.formData();\r\n return { formData };\r\n } catch {\r\n return {};\r\n }\r\n }\r\n\r\n const reader = request.body.getReader();\r\n const chunks: Uint8Array[] = [];\r\n let totalBytes = 0;\r\n\r\n try {\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n if (value) {\r\n totalBytes += value.byteLength;\r\n if (totalBytes > maxBodyBytes) {\r\n await reader.cancel().catch(() => {});\r\n return {\r\n earlyErrorResponse: jsonResponse(\r\n {\r\n error: `Payload too large: streamed body exceeds limit of ${maxBodyBytes} bytes`,\r\n },\r\n 413,\r\n ),\r\n };\r\n }\r\n chunks.push(value);\r\n }\r\n }\r\n } finally {\r\n try {\r\n reader.releaseLock();\r\n } catch {\r\n // Ignore release errors\r\n }\r\n }\r\n\r\n if (totalBytes === 0) {\r\n return {};\r\n }\r\n\r\n const combined = new Uint8Array(totalBytes);\r\n let offset = 0;\r\n for (const chunk of chunks) {\r\n combined.set(chunk, offset);\r\n offset += chunk.byteLength;\r\n }\r\n\r\n try {\r\n const formData = await new Response(combined, {\r\n headers: request.headers,\r\n }).formData();\r\n return { formData };\r\n } catch {\r\n return {};\r\n }\r\n}\r\n","import { parsePublicStorageUrl } from '../../storage/urlParser';\r\nimport { checkAllowedOrigin, runAuthorize } from './guards';\r\nimport type { UploadHttpHandlerOptions } from './options';\r\nimport { handleUnexpectedError, jsonResponse } from './responses';\r\n\r\nfunction parseVirtualHostedS3Bucket(url: string): {\r\n bucket: string;\r\n path: string;\r\n} | null {\r\n try {\r\n const parsed = new URL(url);\r\n const s3Match = parsed.hostname.match(\r\n /^([a-z0-9.-]+)\\.s3(?:[.-][a-z0-9-]+)?\\.amazonaws\\.com$/i,\r\n );\r\n if (s3Match) {\r\n const key = decodeURIComponent(parsed.pathname).replace(/^\\/+/, '');\r\n if (key) {\r\n return { bucket: s3Match[1], path: key };\r\n }\r\n }\r\n } catch {\r\n // Ignore invalid URLs\r\n }\r\n return null;\r\n}\r\n\r\nfunction resolveAllowedBuckets<TPreset extends string>(\r\n options: UploadHttpHandlerOptions<TPreset>,\r\n): Set<string> {\r\n if (options.allowedBuckets && options.allowedBuckets.length > 0) {\r\n return new Set(options.allowedBuckets);\r\n }\r\n if (typeof options.uploader.getAllowedBuckets === 'function') {\r\n return new Set(options.uploader.getAllowedBuckets());\r\n }\r\n if (options.uploader.defaultBucket) {\r\n return new Set([options.uploader.defaultBucket]);\r\n }\r\n return new Set(['images']);\r\n}\r\n\r\nexport function createDeleteHandler<TPreset extends string>(\r\n options: UploadHttpHandlerOptions<TPreset>,\r\n) {\r\n const {\r\n uploader,\r\n authorize,\r\n allowedOrigins,\r\n allowClientBucketOverride = false,\r\n allowedBuckets,\r\n onError,\r\n } = options;\r\n\r\n return async function DELETE(request: Request): Promise<Response> {\r\n try {\r\n const originError = checkAllowedOrigin(request, allowedOrigins);\r\n if (originError) return originError;\r\n\r\n const { searchParams } = new URL(request.url);\r\n const url = searchParams.get('url');\r\n const storagePath = searchParams.get('path');\r\n const bucketParam = searchParams.get('bucket');\r\n\r\n const allowedBucketSet = resolveAllowedBuckets(options);\r\n\r\n const parsedFromUrl = url\r\n ? (typeof uploader.parseUrl === 'function'\r\n ? uploader.parseUrl(url)\r\n : null) ??\r\n parsePublicStorageUrl(url) ??\r\n parseVirtualHostedS3Bucket(url)\r\n : null;\r\n\r\n const resolvedPath = parsedFromUrl?.path ?? storagePath ?? undefined;\r\n const resolvedBucket =\r\n parsedFromUrl?.bucket ??\r\n (bucketParam || uploader.defaultBucket || 'images');\r\n\r\n const authError = await runAuthorize(authorize, {\r\n action: 'delete',\r\n request,\r\n ...(resolvedPath ? { path: resolvedPath } : {}),\r\n ...(resolvedBucket ? { bucket: resolvedBucket } : {}),\r\n });\r\n if (authError) return authError;\r\n\r\n if (url) {\r\n if (parsedFromUrl?.bucket) {\r\n if (\r\n allowedBuckets &&\r\n allowedBuckets.length > 0 &&\r\n !allowedBuckets.includes(parsedFromUrl.bucket)\r\n ) {\r\n return jsonResponse(\r\n { error: `Forbidden bucket \"${parsedFromUrl.bucket}\"` },\r\n 403,\r\n );\r\n }\r\n if (\r\n !allowClientBucketOverride &&\r\n allowedBucketSet.size > 0 &&\r\n !allowedBucketSet.has(parsedFromUrl.bucket)\r\n ) {\r\n return jsonResponse(\r\n { error: `Forbidden bucket \"${parsedFromUrl.bucket}\"` },\r\n 403,\r\n );\r\n }\r\n }\r\n\r\n const deleted = await uploader.deleteByUrl(url);\r\n return jsonResponse({ success: deleted }, deleted ? 200 : 400);\r\n }\r\n\r\n if (bucketParam) {\r\n if (\r\n allowedBuckets &&\r\n allowedBuckets.length > 0 &&\r\n !allowedBuckets.includes(bucketParam)\r\n ) {\r\n return jsonResponse(\r\n { error: `Forbidden bucket \"${bucketParam}\"` },\r\n 403,\r\n );\r\n }\r\n if (\r\n !allowClientBucketOverride &&\r\n allowedBucketSet.size > 0 &&\r\n !allowedBucketSet.has(bucketParam)\r\n ) {\r\n return jsonResponse(\r\n { error: `Forbidden bucket \"${bucketParam}\"` },\r\n 403,\r\n );\r\n }\r\n }\r\n\r\n if (!storagePath) {\r\n return jsonResponse(\r\n { error: 'Missing required \"path\" or \"url\" query parameter' },\r\n 400,\r\n );\r\n }\r\n\r\n const bucket = allowClientBucketOverride\r\n ? bucketParam || undefined\r\n : undefined;\r\n\r\n await uploader.deleteImage(storagePath, bucket);\r\n return jsonResponse({ success: true }, 200);\r\n } catch (error) {\r\n return handleUnexpectedError(\r\n error,\r\n { action: 'delete', request },\r\n onError,\r\n );\r\n }\r\n };\r\n}\r\n","import type {\r\n ExplicitOutputFormat,\r\n ImageDimensions,\r\n ImageUploadOptions,\r\n} from '../../types';\r\nimport type { AuthorizeContext, UploadHttpHandlerOptions } from './options';\r\nimport { jsonResponse } from './responses';\r\n\r\nconst ALLOWED_OUTPUT_FORMATS = new Set(['jpeg', 'png', 'webp', 'avif']);\r\n\r\nfunction parseJsonRecord(\r\n raw: string | null,\r\n): Record<string, string> | undefined {\r\n if (!raw) return undefined;\r\n try {\r\n const parsed: unknown = JSON.parse(raw);\r\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\r\n const clean: Record<string, string> = {};\r\n for (const [k, v] of Object.entries(parsed)) {\r\n if (v !== undefined && v !== null) {\r\n clean[k] = String(v);\r\n }\r\n }\r\n return Object.keys(clean).length > 0 ? clean : undefined;\r\n }\r\n } catch {\r\n // Ignore malformed metadata JSON\r\n }\r\n return undefined;\r\n}\r\n\r\nexport function peekUploadAuthContext<TPreset extends string>(\r\n formData: FormData | undefined,\r\n request: Request,\r\n options: UploadHttpHandlerOptions<TPreset>,\r\n): AuthorizeContext {\r\n const preset =\r\n (formData?.get('preset') as string | null) || undefined;\r\n const rawBucket =\r\n (formData?.get('bucket') as string | null) || undefined;\r\n const rawFolder =\r\n (formData?.get('folder') as string | null) || undefined;\r\n const presetConfig =\r\n preset && typeof options.uploader.resolvePreset === 'function'\r\n ? options.uploader.resolvePreset(preset)\r\n : undefined;\r\n\r\n const bucket = options.allowClientBucketOverride\r\n ? rawBucket || presetConfig?.bucket || options.uploader.defaultBucket\r\n : presetConfig?.bucket || options.uploader.defaultBucket;\r\n\r\n const folder = options.allowClientOverrides?.folder\r\n ? rawFolder || presetConfig?.folder\r\n : presetConfig?.folder;\r\n\r\n return {\r\n action: 'upload',\r\n request,\r\n ...(folder ? { path: folder } : {}),\r\n ...(bucket ? { bucket } : {}),\r\n ...(preset ? { preset } : {}),\r\n };\r\n}\r\n\r\nexport interface ParsedUploadFormResult<TPreset extends string> {\r\n file?: File;\r\n uploadOptions?: ImageUploadOptions<TPreset>;\r\n errorResponse?: Response;\r\n}\r\n\r\nexport function parseUploadForm<TPreset extends string>(\r\n formData: FormData | undefined,\r\n authCtx: AuthorizeContext,\r\n options: UploadHttpHandlerOptions<TPreset>,\r\n): ParsedUploadFormResult<TPreset> {\r\n if (!formData) {\r\n return {\r\n errorResponse: jsonResponse({ error: 'No file provided' }, 400),\r\n };\r\n }\r\n\r\n const file = formData.get('file') as File | null;\r\n if (\r\n !file ||\r\n typeof file === 'string' ||\r\n (typeof file.size === 'number' && file.size === 0 && !file.name)\r\n ) {\r\n return {\r\n errorResponse: jsonResponse({ error: 'No file provided' }, 400),\r\n };\r\n }\r\n if (typeof file.size === 'number' && file.size === 0) {\r\n return {\r\n errorResponse: jsonResponse({ error: 'File is empty' }, 400),\r\n };\r\n }\r\n\r\n const {\r\n uploader,\r\n allowedPresets,\r\n allowClientOverrides,\r\n allowClientBucketOverride = false,\r\n allowedBuckets,\r\n } = options;\r\n\r\n const preset = (formData.get('preset') as TPreset | null) || undefined;\r\n if (preset) {\r\n if (\r\n allowedPresets &&\r\n allowedPresets.length > 0 &&\r\n !allowedPresets.includes(preset)\r\n ) {\r\n return {\r\n errorResponse: jsonResponse(\r\n {\r\n error: `Invalid preset \"${preset}\". Allowed presets: ${allowedPresets.join(', ')}`,\r\n },\r\n 400,\r\n ),\r\n };\r\n }\r\n if (\r\n typeof uploader.resolvePreset === 'function' &&\r\n !uploader.resolvePreset(preset)\r\n ) {\r\n return {\r\n errorResponse: jsonResponse(\r\n { error: `Invalid preset \"${preset}\".` },\r\n 400,\r\n ),\r\n };\r\n }\r\n }\r\n\r\n const presetConfig =\r\n preset && typeof uploader.resolvePreset === 'function'\r\n ? uploader.resolvePreset(preset)\r\n : undefined;\r\n\r\n const rawBucket = (formData.get('bucket') as string | null) || undefined;\r\n if (rawBucket && allowedBuckets && !allowedBuckets.includes(rawBucket)) {\r\n return {\r\n errorResponse: jsonResponse(\r\n { error: `Bucket \"${rawBucket}\" is not allowed` },\r\n 403,\r\n ),\r\n };\r\n }\r\n const bucket = allowClientBucketOverride ? rawBucket : undefined;\r\n\r\n let folder: string | undefined;\r\n const rawFolder = (formData.get('folder') as string | null) || undefined;\r\n if (rawFolder !== undefined) {\r\n if (allowClientOverrides?.folder === true) {\r\n folder = rawFolder;\r\n } else if (typeof allowClientOverrides?.folder === 'function') {\r\n const scoped = allowClientOverrides.folder(rawFolder, authCtx);\r\n if (scoped === null) {\r\n return {\r\n errorResponse: jsonResponse(\r\n { error: 'Invalid or disallowed folder' },\r\n 400,\r\n ),\r\n };\r\n }\r\n folder = scoped || undefined;\r\n }\r\n }\r\n\r\n const rawFilename =\r\n (formData.get('customFilename') as string | null) || undefined;\r\n const customFilename = allowClientOverrides?.filename\r\n ? rawFilename\r\n : undefined;\r\n\r\n const outputFormatRaw = formData.get('outputFormat') as string | null;\r\n if (outputFormatRaw && !ALLOWED_OUTPUT_FORMATS.has(outputFormatRaw)) {\r\n return {\r\n errorResponse: jsonResponse(\r\n {\r\n error: `Invalid output format \"${outputFormatRaw}\". Allowed formats: jpeg, png, webp, avif`,\r\n },\r\n 400,\r\n ),\r\n };\r\n }\r\n const outputFormat =\r\n allowClientOverrides?.format && outputFormatRaw\r\n ? (outputFormatRaw as ExplicitOutputFormat)\r\n : undefined;\r\n\r\n const qualityRaw = formData.get('quality') as string | null;\r\n let quality: number | undefined;\r\n if (qualityRaw !== null && qualityRaw.trim() !== '') {\r\n const parsedQuality = Number(qualityRaw);\r\n if (\r\n !Number.isFinite(parsedQuality) ||\r\n !Number.isInteger(parsedQuality) ||\r\n parsedQuality < 1 ||\r\n parsedQuality > 100\r\n ) {\r\n return {\r\n errorResponse: jsonResponse(\r\n {\r\n error: `Invalid quality \"${qualityRaw}\". Must be an integer between 1 and 100.`,\r\n },\r\n 400,\r\n ),\r\n };\r\n }\r\n if (allowClientOverrides?.quality) {\r\n quality = presetConfig?.quality\r\n ? Math.min(parsedQuality, presetConfig.quality)\r\n : parsedQuality;\r\n }\r\n }\r\n\r\n const maxWidthRaw = formData.get('maxWidth') as string | null;\r\n const maxHeightRaw = formData.get('maxHeight') as string | null;\r\n let maxDimensions: ImageDimensions | undefined;\r\n if (\r\n (maxWidthRaw !== null && maxWidthRaw.trim() !== '') ||\r\n (maxHeightRaw !== null && maxHeightRaw.trim() !== '')\r\n ) {\r\n const w = Number(maxWidthRaw);\r\n const h = Number(maxHeightRaw);\r\n if (!Number.isFinite(w) || !Number.isFinite(h) || w < 1 || h < 1) {\r\n return {\r\n errorResponse: jsonResponse(\r\n { error: 'Invalid maxWidth or maxHeight dimensions' },\r\n 400,\r\n ),\r\n };\r\n }\r\n if (allowClientOverrides?.dimensions) {\r\n const cap = presetConfig?.maxDimensions;\r\n maxDimensions = cap\r\n ? {\r\n width: Math.min(Math.floor(w), cap.width),\r\n height: Math.min(Math.floor(h), cap.height),\r\n }\r\n : { width: Math.floor(w), height: Math.floor(h) };\r\n }\r\n }\r\n\r\n const altText = (formData.get('altText') as string | null) || undefined;\r\n const metadata = parseJsonRecord(formData.get('metadata') as string | null);\r\n\r\n return {\r\n file,\r\n uploadOptions: {\r\n preset,\r\n folder,\r\n bucket,\r\n altText,\r\n customFilename,\r\n ...(customFilename ? { isClientFilename: true } : {}),\r\n outputFormat,\r\n quality,\r\n maxDimensions,\r\n metadata,\r\n signal: authCtx.request.signal,\r\n },\r\n };\r\n}\r\n","import { createDeleteHandler } from './deleteRoute';\r\nimport {\r\n checkAllowedOrigin,\r\n readBoundedFormData,\r\n resolveMaxBodyBytes,\r\n runAuthorize,\r\n} from './guards';\r\nimport type {\r\n AllowClientOverrides,\r\n AuthorizeContext,\r\n UploadErrorContext,\r\n UploadHttpHandlerOptions,\r\n} from './options';\r\nimport { parseUploadForm, peekUploadAuthContext } from './parseUploadForm';\r\nimport {\r\n handleUnexpectedError,\r\n isClientValidationError,\r\n jsonResponse,\r\n UploadValidationError,\r\n} from './responses';\r\n\r\nexport {\r\n type AllowClientOverrides,\r\n type AuthorizeContext,\r\n isClientValidationError,\r\n type UploadErrorContext,\r\n type UploadHttpHandlerOptions,\r\n UploadValidationError,\r\n};\r\n\r\n/**\r\n * Standard Web Fetch (`Request` -> `Response`) upload & delete route handlers.\r\n * Works across Next.js App Router, Remix, React Router, SvelteKit, Astro, Hono, and Node Fetch servers.\r\n */\r\nexport function createUploadHttpHandler<TPreset extends string = string>(\r\n options: UploadHttpHandlerOptions<TPreset>,\r\n) {\r\n const { uploader, authorize, allowedOrigins, onError } = options;\r\n const maxBodyBytes = resolveMaxBodyBytes(options);\r\n\r\n async function POST(request: Request): Promise<Response> {\r\n try {\r\n const originError = checkAllowedOrigin(request, allowedOrigins);\r\n if (originError) return originError;\r\n\r\n const { formData, earlyErrorResponse } = await readBoundedFormData(\r\n request,\r\n maxBodyBytes,\r\n );\r\n if (earlyErrorResponse) return earlyErrorResponse;\r\n\r\n const authCtx = peekUploadAuthContext(formData, request, options);\r\n const authError = await runAuthorize(authorize, authCtx);\r\n if (authError) return authError;\r\n\r\n const parsed = parseUploadForm(formData, authCtx, options);\r\n if (parsed.errorResponse) return parsed.errorResponse;\r\n\r\n const result = await uploader.uploadImage(\r\n parsed.file!,\r\n parsed.uploadOptions,\r\n );\r\n\r\n return jsonResponse(result, 200);\r\n } catch (error) {\r\n return handleUnexpectedError(\r\n error,\r\n { action: 'upload', request },\r\n onError,\r\n );\r\n }\r\n }\r\n\r\n const DELETE = createDeleteHandler(options);\r\n\r\n return { POST, DELETE };\r\n}\r\n\r\nexport const createUploadHandler = createUploadHttpHandler;\r\n"],"mappings":"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","names":[]}