img-upload-kit 0.0.0-stage → 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.
Files changed (112) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATION.md +192 -0
  4. package/README.md +242 -2
  5. package/dist/bin.js +1186 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/blur.cjs +41 -0
  8. package/dist/blur.cjs.map +1 -0
  9. package/dist/blur.d.cts +17 -0
  10. package/dist/blur.d.ts +17 -0
  11. package/dist/blur.js +41 -0
  12. package/dist/blur.js.map +1 -0
  13. package/dist/blurDataUtils-DveBRtqE.d.cts +43 -0
  14. package/dist/blurDataUtils-yx9Q6HlU.d.ts +43 -0
  15. package/dist/blurService-D5TpMNji.d.ts +83 -0
  16. package/dist/blurService-DacwylJx.d.cts +83 -0
  17. package/dist/chunk-2OVNXHTN.cjs +514 -0
  18. package/dist/chunk-2OVNXHTN.cjs.map +1 -0
  19. package/dist/chunk-3ICG2SLL.cjs +135 -0
  20. package/dist/chunk-3ICG2SLL.cjs.map +1 -0
  21. package/dist/chunk-4ASFG3KB.js +514 -0
  22. package/dist/chunk-4ASFG3KB.js.map +1 -0
  23. package/dist/chunk-AI3JJLSA.js +2370 -0
  24. package/dist/chunk-AI3JJLSA.js.map +1 -0
  25. package/dist/chunk-CV23O4YU.js +145 -0
  26. package/dist/chunk-CV23O4YU.js.map +1 -0
  27. package/dist/chunk-G7LLSKBV.js +158 -0
  28. package/dist/chunk-G7LLSKBV.js.map +1 -0
  29. package/dist/chunk-IB3TEDLR.js +294 -0
  30. package/dist/chunk-IB3TEDLR.js.map +1 -0
  31. package/dist/chunk-KJVYWKIG.cjs +158 -0
  32. package/dist/chunk-KJVYWKIG.cjs.map +1 -0
  33. package/dist/chunk-NEDBIIE3.cjs +226 -0
  34. package/dist/chunk-NEDBIIE3.cjs.map +1 -0
  35. package/dist/chunk-QHU7FUO4.cjs +37 -0
  36. package/dist/chunk-QHU7FUO4.cjs.map +1 -0
  37. package/dist/chunk-QQHZJKNA.js +135 -0
  38. package/dist/chunk-QQHZJKNA.js.map +1 -0
  39. package/dist/chunk-S2IFAWJK.js +37 -0
  40. package/dist/chunk-S2IFAWJK.js.map +1 -0
  41. package/dist/chunk-TT5C7K3Q.js +226 -0
  42. package/dist/chunk-TT5C7K3Q.js.map +1 -0
  43. package/dist/chunk-U2TWTEBY.cjs +2370 -0
  44. package/dist/chunk-U2TWTEBY.cjs.map +1 -0
  45. package/dist/chunk-WISG74VL.cjs +294 -0
  46. package/dist/chunk-WISG74VL.cjs.map +1 -0
  47. package/dist/chunk-YRXXQUDE.cjs +145 -0
  48. package/dist/chunk-YRXXQUDE.cjs.map +1 -0
  49. package/dist/cli.cjs +1241 -0
  50. package/dist/cli.cjs.map +1 -0
  51. package/dist/cli.d.cts +99 -0
  52. package/dist/cli.d.ts +99 -0
  53. package/dist/cli.js +1194 -0
  54. package/dist/cli.js.map +1 -0
  55. package/dist/headless.cjs +27 -0
  56. package/dist/headless.cjs.map +1 -0
  57. package/dist/headless.d.cts +4 -0
  58. package/dist/headless.d.ts +4 -0
  59. package/dist/headless.js +27 -0
  60. package/dist/headless.js.map +1 -0
  61. package/dist/index-BIdnqtW8.d.ts +234 -0
  62. package/dist/index-Brk_uHs6.d.cts +234 -0
  63. package/dist/index-CPMlaQsC.d.ts +635 -0
  64. package/dist/index-DHINe-rA.d.cts +635 -0
  65. package/dist/index.cjs +65 -0
  66. package/dist/index.cjs.map +1 -0
  67. package/dist/index.d.cts +43 -0
  68. package/dist/index.d.ts +43 -0
  69. package/dist/index.js +65 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/memoryAdapter-Ck0ccZbF.d.cts +25 -0
  72. package/dist/memoryAdapter-DxEKaG0b.d.ts +25 -0
  73. package/dist/placeholders-C4ftwaII.d.ts +5 -0
  74. package/dist/placeholders-x_3rt65M.d.cts +5 -0
  75. package/dist/react.cjs +3779 -0
  76. package/dist/react.cjs.map +1 -0
  77. package/dist/react.d.cts +476 -0
  78. package/dist/react.d.ts +476 -0
  79. package/dist/react.js +3779 -0
  80. package/dist/react.js.map +1 -0
  81. package/dist/server-handler.cjs +14 -0
  82. package/dist/server-handler.cjs.map +1 -0
  83. package/dist/server-handler.d.cts +3 -0
  84. package/dist/server-handler.d.ts +3 -0
  85. package/dist/server-handler.js +14 -0
  86. package/dist/server-handler.js.map +1 -0
  87. package/dist/server.cjs +1801 -0
  88. package/dist/server.cjs.map +1 -0
  89. package/dist/server.d.cts +142 -0
  90. package/dist/server.d.ts +142 -0
  91. package/dist/server.js +1801 -0
  92. package/dist/server.js.map +1 -0
  93. package/dist/storage.cjs +447 -0
  94. package/dist/storage.cjs.map +1 -0
  95. package/dist/storage.d.cts +163 -0
  96. package/dist/storage.d.ts +163 -0
  97. package/dist/storage.js +447 -0
  98. package/dist/storage.js.map +1 -0
  99. package/dist/styles/animated-image.css +112 -0
  100. package/dist/styles/dropzone-and-tiles.css +247 -0
  101. package/dist/styles/modals-and-dock.css +248 -0
  102. package/dist/styles/primitives.css +197 -0
  103. package/dist/styles/quality-and-inputs.css +184 -0
  104. package/dist/styles/tokens.css +123 -0
  105. package/dist/styles.css +1122 -0
  106. package/dist/types-BrP-o9nd.d.cts +85 -0
  107. package/dist/types-sZ3KoFQY.d.ts +85 -0
  108. package/dist/types-yShGNQnW.d.cts +61 -0
  109. package/dist/types-yShGNQnW.d.ts +61 -0
  110. package/dist/upload-C5Trd6Dh.d.cts +154 -0
  111. package/dist/upload-C5Trd6Dh.d.ts +154 -0
  112. package/package.json +198 -4
@@ -0,0 +1,135 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/blur/blurDataUtils.ts
2
+ var _blurhash = require('blurhash');
3
+ function formatCssUrl(rawUrl) {
4
+ const escaped = rawUrl.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/[\r\n]/g, "");
5
+ return `url("${escaped}")`;
6
+ }
7
+ function getBlurData(image) {
8
+ if (!image) {
9
+ return {
10
+ blurDataUrl: "",
11
+ blurColor: "#E5E7EB",
12
+ hasBlur: false
13
+ };
14
+ }
15
+ return {
16
+ blurDataUrl: image.blurDataUrl || "",
17
+ blurColor: image.blurColor || "#E5E7EB",
18
+ ...image.blurHash ? { blurHash: image.blurHash } : {},
19
+ hasBlur: Boolean(image.blurDataUrl || image.blurHash)
20
+ };
21
+ }
22
+ function getImagePropsWithBlur(image) {
23
+ if (!_optionalChain([image, 'optionalAccess', _ => _.url])) {
24
+ return null;
25
+ }
26
+ const blurData = getBlurData(image);
27
+ return {
28
+ src: image.url,
29
+ alt: image.altText || "",
30
+ width: image.width || 1920,
31
+ height: image.height || 1080,
32
+ ...blurData.blurDataUrl && {
33
+ placeholder: "blur",
34
+ blurDataURL: blurData.blurDataUrl
35
+ }
36
+ };
37
+ }
38
+ function getBackgroundPropsWithBlur(image) {
39
+ if (!_optionalChain([image, 'optionalAccess', _2 => _2.url])) {
40
+ return {
41
+ backgroundColor: "#E5E7EB"
42
+ };
43
+ }
44
+ const blurData = getBlurData(image);
45
+ return {
46
+ backgroundColor: blurData.blurColor,
47
+ backgroundImage: formatCssUrl(image.url),
48
+ backgroundSize: "cover",
49
+ backgroundPosition: "center",
50
+ backgroundRepeat: "no-repeat"
51
+ };
52
+ }
53
+ function getBlurPlaceholderStyle(image) {
54
+ const blurData = getBlurData(image);
55
+ if (!blurData.blurDataUrl) {
56
+ return {
57
+ backgroundColor: blurData.blurColor
58
+ };
59
+ }
60
+ return {
61
+ backgroundColor: blurData.blurColor,
62
+ backgroundImage: formatCssUrl(blurData.blurDataUrl),
63
+ backgroundSize: "cover",
64
+ backgroundPosition: "center",
65
+ filter: "blur(20px)",
66
+ transform: "scale(1.1)"
67
+ };
68
+ }
69
+ function hasBlurData(image) {
70
+ return Boolean(_optionalChain([image, 'optionalAccess', _3 => _3.blurDataUrl]) || _optionalChain([image, 'optionalAccess', _4 => _4.blurColor]) || _optionalChain([image, 'optionalAccess', _5 => _5.blurHash]));
71
+ }
72
+ function getBulkBlurData(images) {
73
+ return images.map(getBlurData);
74
+ }
75
+ function createSrcSetWithBlur(image) {
76
+ if (!_optionalChain([image, 'optionalAccess', _6 => _6.url])) return "";
77
+ if (image.width && image.width > 0) {
78
+ return `${image.url} ${image.width}w`;
79
+ }
80
+ return image.url;
81
+ }
82
+ function preloadImageWithBlur(image) {
83
+ if (!_optionalChain([image, 'optionalAccess', _7 => _7.url]) || typeof document === "undefined" || !document.head) return;
84
+ const existingLinks = document.head.querySelectorAll(
85
+ 'link[rel="preload"][as="image"]'
86
+ );
87
+ for (let i = 0; i < existingLinks.length; i++) {
88
+ const el = existingLinks[i];
89
+ if (el.getAttribute("href") === image.url || el.href === image.url) {
90
+ return;
91
+ }
92
+ }
93
+ const link = document.createElement("link");
94
+ link.rel = "preload";
95
+ link.as = "image";
96
+ link.href = image.url;
97
+ document.head.appendChild(link);
98
+ }
99
+ function extractImageUrls(image) {
100
+ if (!_optionalChain([image, 'optionalAccess', _8 => _8.url])) return [];
101
+ return [image.url];
102
+ }
103
+ function decodeBlurHashToDataURL(hash, width = 32, height = 32, punch = 1) {
104
+ if (!hash || typeof document === "undefined") return null;
105
+ const validation = _blurhash.isBlurhashValid.call(void 0, hash);
106
+ if (!validation.result) return null;
107
+ try {
108
+ const pixels = _blurhash.decode.call(void 0, hash, width, height, punch);
109
+ const canvas = document.createElement("canvas");
110
+ canvas.width = width;
111
+ canvas.height = height;
112
+ const ctx = canvas.getContext("2d");
113
+ if (!ctx) return null;
114
+ const imageData = ctx.createImageData(width, height);
115
+ imageData.data.set(pixels);
116
+ ctx.putImageData(imageData, 0, 0);
117
+ return canvas.toDataURL("image/png");
118
+ } catch (e) {
119
+ return null;
120
+ }
121
+ }
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+ exports.getBlurData = getBlurData; exports.getImagePropsWithBlur = getImagePropsWithBlur; exports.getBackgroundPropsWithBlur = getBackgroundPropsWithBlur; exports.getBlurPlaceholderStyle = getBlurPlaceholderStyle; exports.hasBlurData = hasBlurData; exports.getBulkBlurData = getBulkBlurData; exports.createSrcSetWithBlur = createSrcSetWithBlur; exports.preloadImageWithBlur = preloadImageWithBlur; exports.extractImageUrls = extractImageUrls; exports.decodeBlurHashToDataURL = decodeBlurHashToDataURL;
135
+ //# sourceMappingURL=chunk-3ICG2SLL.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/image-upload-kit/image-upload-kit/dist/chunk-3ICG2SLL.cjs","../src/blur/blurDataUtils.ts"],"names":[],"mappings":"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","file":"/home/runner/work/image-upload-kit/image-upload-kit/dist/chunk-3ICG2SLL.cjs","sourcesContent":[null,"import { decode as decodeBlurHash, isBlurhashValid } from 'blurhash';\nimport type { BlurData, BlurImageSource } from './types';\n\nexport interface BlurPlaceholderStyle {\n backgroundColor: string;\n backgroundImage?: string;\n backgroundSize?: string;\n backgroundPosition?: string;\n backgroundRepeat?: string;\n filter?: string;\n transform?: string;\n}\n\nfunction formatCssUrl(rawUrl: string): string {\n const escaped = rawUrl\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/[\\r\\n]/g, '');\n return `url(\"${escaped}\")`;\n}\n\nexport function getBlurData(\n image: BlurImageSource | null | undefined,\n): BlurData {\n if (!image) {\n return {\n blurDataUrl: '',\n blurColor: '#E5E7EB',\n hasBlur: false,\n };\n }\n\n return {\n blurDataUrl: image.blurDataUrl || '',\n blurColor: image.blurColor || '#E5E7EB',\n ...(image.blurHash ? { blurHash: image.blurHash } : {}),\n hasBlur: Boolean(image.blurDataUrl || image.blurHash),\n };\n}\n\nexport function getImagePropsWithBlur(\n image: BlurImageSource | null | undefined,\n) {\n if (!image?.url) {\n return null;\n }\n\n const blurData = getBlurData(image);\n\n return {\n src: image.url,\n alt: image.altText || '',\n width: image.width || 1920,\n height: image.height || 1080,\n ...(blurData.blurDataUrl && {\n placeholder: 'blur' as const,\n blurDataURL: blurData.blurDataUrl,\n }),\n };\n}\n\nexport function getBackgroundPropsWithBlur(\n image: BlurImageSource | null | undefined,\n): BlurPlaceholderStyle {\n if (!image?.url) {\n return {\n backgroundColor: '#E5E7EB',\n };\n }\n\n const blurData = getBlurData(image);\n\n return {\n backgroundColor: blurData.blurColor,\n backgroundImage: formatCssUrl(image.url),\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n backgroundRepeat: 'no-repeat',\n };\n}\n\nexport function getBlurPlaceholderStyle(\n image: BlurImageSource | null | undefined,\n): BlurPlaceholderStyle {\n const blurData = getBlurData(image);\n\n if (!blurData.blurDataUrl) {\n return {\n backgroundColor: blurData.blurColor,\n };\n }\n\n return {\n backgroundColor: blurData.blurColor,\n backgroundImage: formatCssUrl(blurData.blurDataUrl),\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n filter: 'blur(20px)',\n transform: 'scale(1.1)',\n };\n}\n\nexport function hasBlurData(\n image: BlurImageSource | null | undefined,\n): boolean {\n return Boolean(image?.blurDataUrl || image?.blurColor || image?.blurHash);\n}\n\nexport function getBulkBlurData(\n images: Array<BlurImageSource | null | undefined>,\n): BlurData[] {\n return images.map(getBlurData);\n}\n\n/**\n * Builds a responsive `srcset` descriptor for the full-resolution image URL.\n * Never includes the low-resolution LQIP placeholder in `srcset` so browsers\n * cannot accidentally select the blurred placeholder as the final image.\n */\nexport function createSrcSetWithBlur(\n image: BlurImageSource | null | undefined,\n): string {\n if (!image?.url) return '';\n if (image.width && image.width > 0) {\n return `${image.url} ${image.width}w`;\n }\n return image.url;\n}\n\n/**\n * Preloads the target image URL in `document.head` via `<link rel=\"preload\" as=\"image\">`.\n * Does not set `imagesrcset` to the LQIP data URL and deduplicates existing preload links.\n */\nexport function preloadImageWithBlur(\n image: BlurImageSource | null | undefined,\n): void {\n if (!image?.url || typeof document === 'undefined' || !document.head) return;\n\n const existingLinks = document.head.querySelectorAll(\n 'link[rel=\"preload\"][as=\"image\"]',\n );\n for (let i = 0; i < existingLinks.length; i++) {\n const el = existingLinks[i] as HTMLLinkElement;\n if (el.getAttribute('href') === image.url || el.href === image.url) {\n return;\n }\n }\n\n const link = document.createElement('link');\n link.rel = 'preload';\n link.as = 'image';\n link.href = image.url;\n document.head.appendChild(link);\n}\n\nexport function extractImageUrls(\n image: BlurImageSource | null | undefined,\n): string[] {\n if (!image?.url) return [];\n return [image.url];\n}\n\n/**\n * Browser helper: Decodes a compact BlurHash string (e.g. \"LEHV6nWB2yk8pyo0adR*.7kCMdnj\")\n * into a `data:image/png;base64,...` URL using an offscreen `<canvas>`.\n */\nexport function decodeBlurHashToDataURL(\n hash: string,\n width: number = 32,\n height: number = 32,\n punch: number = 1,\n): string | null {\n if (!hash || typeof document === 'undefined') return null;\n const validation = isBlurhashValid(hash);\n if (!validation.result) return null;\n\n try {\n const pixels = decodeBlurHash(hash, width, height, punch);\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n const imageData = ctx.createImageData(width, height);\n imageData.data.set(pixels);\n ctx.putImageData(imageData, 0, 0);\n return canvas.toDataURL('image/png');\n } catch {\n return null;\n }\n}\n"]}
@@ -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-4ASFG3KB.js.map