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,447 @@
1
+ import {
2
+ createMemoryStorageAdapter,
3
+ createStorageAdapter,
4
+ toPaginatedListResult,
5
+ validateStorageMetadata
6
+ } from "./chunk-CV23O4YU.js";
7
+ import {
8
+ DEFAULT_PUBLIC_STORAGE_PREFIX,
9
+ parsePublicStorageUrl
10
+ } from "./chunk-S2IFAWJK.js";
11
+
12
+ // src/storage/localFsAdapter.ts
13
+ import fs from "fs/promises";
14
+ import path from "path";
15
+ function sanitizeRelativePath(inputPath) {
16
+ if (!inputPath || typeof inputPath !== "string") {
17
+ throw new Error("Invalid storage path");
18
+ }
19
+ if (inputPath.includes(":") || inputPath.includes("\0")) {
20
+ throw new Error("Path traversal blocked in LocalFsStorageAdapter");
21
+ }
22
+ const normalized = inputPath.replace(/\\/g, "/").replace(/^\/+/, "");
23
+ const rawSegments = normalized.split("/").filter(Boolean);
24
+ if (rawSegments.length === 0) {
25
+ throw new Error("Invalid storage path");
26
+ }
27
+ for (const segment of rawSegments) {
28
+ if (segment === "." || segment === "..") {
29
+ throw new Error("Invalid storage path: path traversal blocked");
30
+ }
31
+ }
32
+ return rawSegments.join("/");
33
+ }
34
+ function assertContainedInRoot(resolvedRoot, targetPath) {
35
+ const relative = path.relative(resolvedRoot, targetPath);
36
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
37
+ throw new Error("Path traversal blocked in LocalFsStorageAdapter");
38
+ }
39
+ }
40
+ function createLocalFsStorageAdapter(options) {
41
+ const resolvedRoot = path.resolve(options.rootDir);
42
+ const baseUrl = (options.baseUrl ?? "/uploads").replace(/\/+$/, "");
43
+ const includeBucket = options.includeBucketInPath !== false;
44
+ const resolveDiskTarget = (bucket, relativePath) => {
45
+ const cleanBucket = sanitizeRelativePath(bucket);
46
+ const cleanRel = sanitizeRelativePath(relativePath);
47
+ const subPath = includeBucket ? `${cleanBucket}/${cleanRel}` : cleanRel;
48
+ const diskPath = path.resolve(resolvedRoot, subPath);
49
+ assertContainedInRoot(resolvedRoot, diskPath);
50
+ return { diskPath, subPath, cleanRel };
51
+ };
52
+ const getPublicUrl = (bucket, relativePath) => {
53
+ const { subPath } = resolveDiskTarget(bucket, relativePath);
54
+ return `${baseUrl}/${subPath}`;
55
+ };
56
+ const parseUrl = (url) => {
57
+ if (!url || typeof url !== "string") return null;
58
+ try {
59
+ const cleanUrl = url.split(/[?#]/)[0];
60
+ const prefix = `${baseUrl}/`;
61
+ let subPath = null;
62
+ if (cleanUrl.startsWith(prefix)) {
63
+ subPath = cleanUrl.slice(prefix.length);
64
+ } else if (baseUrl.startsWith("/")) {
65
+ const parsed = new URL(cleanUrl, "http://localhost");
66
+ const isLocal = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
67
+ const isRelative = !cleanUrl.startsWith("http://") && !cleanUrl.startsWith("https://");
68
+ if ((isLocal || isRelative) && parsed.pathname.startsWith(prefix)) {
69
+ subPath = parsed.pathname.slice(prefix.length);
70
+ }
71
+ }
72
+ if (!subPath) return null;
73
+ const decoded = decodeURIComponent(subPath).replace(/\\/g, "/").replace(/^\/+/, "");
74
+ if (!decoded || decoded.includes(":") || decoded.includes("\0")) {
75
+ return null;
76
+ }
77
+ const segments = decoded.split("/").filter(Boolean);
78
+ if (segments.length === 0 || segments.includes("..") || segments.includes(".")) {
79
+ return null;
80
+ }
81
+ const diskPath = path.resolve(resolvedRoot, ...segments);
82
+ const relative = path.relative(resolvedRoot, diskPath);
83
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
84
+ return null;
85
+ }
86
+ if (includeBucket) {
87
+ if (segments.length < 2) return null;
88
+ return {
89
+ bucket: segments[0],
90
+ path: segments.slice(1).join("/")
91
+ };
92
+ }
93
+ return { path: segments.join("/") };
94
+ } catch {
95
+ return null;
96
+ }
97
+ };
98
+ return {
99
+ async upload(params) {
100
+ const { diskPath, cleanRel } = resolveDiskTarget(
101
+ params.bucket,
102
+ params.path
103
+ );
104
+ await fs.mkdir(path.dirname(diskPath), { recursive: true });
105
+ try {
106
+ await fs.writeFile(
107
+ diskPath,
108
+ params.data,
109
+ params.upsert === false ? { flag: "wx" } : void 0
110
+ );
111
+ } catch (err) {
112
+ if (err.code === "EEXIST") {
113
+ const existsError = new Error(
114
+ `File already exists at ${params.bucket}/${cleanRel}`
115
+ );
116
+ existsError.code = "EEXIST";
117
+ throw existsError;
118
+ }
119
+ throw err;
120
+ }
121
+ return {
122
+ bucket: params.bucket,
123
+ path: cleanRel,
124
+ url: getPublicUrl(params.bucket, cleanRel)
125
+ };
126
+ },
127
+ async delete(bucket, relativePath) {
128
+ const { diskPath } = resolveDiskTarget(bucket, relativePath);
129
+ try {
130
+ await fs.unlink(diskPath);
131
+ } catch (err) {
132
+ if (err.code !== "ENOENT") {
133
+ throw err;
134
+ }
135
+ }
136
+ },
137
+ async exists(bucket, relativePath) {
138
+ try {
139
+ const { diskPath } = resolveDiskTarget(bucket, relativePath);
140
+ const stat = await fs.stat(diskPath);
141
+ return stat.isFile();
142
+ } catch {
143
+ return false;
144
+ }
145
+ },
146
+ getPublicUrl,
147
+ parseUrl,
148
+ async deleteByUrl(url) {
149
+ const parsed = parseUrl(url);
150
+ if (!parsed) return false;
151
+ try {
152
+ const subSegments = includeBucket && parsed.bucket ? [parsed.bucket, ...parsed.path.split("/")] : parsed.path.split("/");
153
+ const diskPath = path.resolve(resolvedRoot, ...subSegments);
154
+ assertContainedInRoot(resolvedRoot, diskPath);
155
+ await fs.unlink(diskPath);
156
+ return true;
157
+ } catch {
158
+ return false;
159
+ }
160
+ },
161
+ async list(bucket, folder, listOptions) {
162
+ const cleanBucket = sanitizeRelativePath(bucket);
163
+ const cleanFolder = folder ? sanitizeRelativePath(folder) : "";
164
+ const targetDir = path.resolve(
165
+ resolvedRoot,
166
+ includeBucket ? cleanBucket : "",
167
+ cleanFolder
168
+ );
169
+ assertContainedInRoot(resolvedRoot, targetDir);
170
+ try {
171
+ const dirEntries = await fs.readdir(targetDir, { withFileTypes: true });
172
+ const results = [];
173
+ for (const entry of dirEntries) {
174
+ if (!entry.isFile()) continue;
175
+ const full = path.join(targetDir, entry.name);
176
+ const stat = await fs.stat(full);
177
+ results.push({
178
+ name: entry.name,
179
+ id: entry.name,
180
+ createdAt: stat.birthtime.toISOString(),
181
+ size: stat.size
182
+ });
183
+ }
184
+ const offset = listOptions?.cursor ? Number(listOptions.cursor) || 0 : 0;
185
+ const limit = listOptions?.limit ?? results.length;
186
+ const sliced = listOptions?.limit !== void 0 || offset > 0 ? results.slice(offset, offset + limit) : results;
187
+ const nextCursor = listOptions?.limit !== void 0 && offset + sliced.length < results.length ? String(offset + sliced.length) : void 0;
188
+ return toPaginatedListResult(sliced, nextCursor);
189
+ } catch (err) {
190
+ if (err.code === "ENOENT") {
191
+ return toPaginatedListResult([]);
192
+ }
193
+ throw err;
194
+ }
195
+ }
196
+ };
197
+ }
198
+
199
+ // src/storage/s3Adapter.ts
200
+ function normalizeS3Key(rawPath) {
201
+ const normalized = rawPath.replace(/\\/g, "/").replace(/^\/+/, "");
202
+ if (!normalized) throw new Error("Invalid S3 object key");
203
+ return normalized;
204
+ }
205
+ function resolveTransport(options) {
206
+ if ("client" in options && options.client) return options.client;
207
+ if ("transport" in options && options.transport) return options.transport;
208
+ if ("putObject" in options && typeof options.putObject === "function") return options;
209
+ throw new Error("S3StorageAdapter requires putObject/deleteObject or client/transport");
210
+ }
211
+ function isS3PreconditionError(err) {
212
+ if (!err || typeof err !== "object") return false;
213
+ const e = err;
214
+ const status = e.$metadata?.httpStatusCode ?? Number(e.statusCode);
215
+ const code = String(e.code ?? e.name ?? "").toLowerCase();
216
+ return status === 412 || code === "preconditionfailed" || code === "eexist";
217
+ }
218
+ function createS3StorageAdapter(options) {
219
+ const transport = resolveTransport(options);
220
+ const includeBucket = options.includeBucketInUrl !== false;
221
+ const getPublicUrl = (bucket, path2) => {
222
+ const key = normalizeS3Key(path2);
223
+ if (typeof options.publicBaseUrl === "function") return options.publicBaseUrl(bucket, key);
224
+ const base = options.publicBaseUrl.replace(/\/+$/, "");
225
+ return includeBucket ? `${base}/${bucket}/${key}` : `${base}/${key}`;
226
+ };
227
+ return {
228
+ async upload(params) {
229
+ const key = normalizeS3Key(params.path);
230
+ validateStorageMetadata(params.metadata);
231
+ const isImmutable = params.upsert === false && params.contentType.includes("image/avif");
232
+ const cacheControl = params.cacheControl ?? options.cacheControl ?? (isImmutable ? "public, max-age=31536000, immutable" : "public, max-age=2592000, must-revalidate");
233
+ const metadata = params.metadata || params.contentHash ? {
234
+ ...params.metadata,
235
+ ...params.contentHash ? { contentHash: params.contentHash } : {}
236
+ } : void 0;
237
+ try {
238
+ await transport.putObject({
239
+ Bucket: params.bucket,
240
+ Key: key,
241
+ Body: params.data,
242
+ ContentType: params.contentType,
243
+ ...cacheControl ? { CacheControl: cacheControl } : {},
244
+ ...metadata ? { Metadata: metadata } : {},
245
+ ...params.upsert === false ? { IfNoneMatch: "*" } : {}
246
+ });
247
+ } catch (err) {
248
+ if (isS3PreconditionError(err)) {
249
+ const existsErr = new Error(
250
+ `Object already exists at ${params.bucket}/${key}`
251
+ );
252
+ existsErr.code = "EEXIST";
253
+ existsErr.statusCode = 412;
254
+ throw existsErr;
255
+ }
256
+ throw err;
257
+ }
258
+ return {
259
+ bucket: params.bucket,
260
+ path: key,
261
+ url: getPublicUrl(params.bucket, key)
262
+ };
263
+ },
264
+ async delete(bucket, path2) {
265
+ await transport.deleteObject({ Bucket: bucket, Key: normalizeS3Key(path2) });
266
+ },
267
+ async exists(bucket, path2) {
268
+ const key = normalizeS3Key(path2);
269
+ if (transport.headObject) {
270
+ try {
271
+ await transport.headObject({ Bucket: bucket, Key: key });
272
+ return true;
273
+ } catch {
274
+ return false;
275
+ }
276
+ }
277
+ const lastSlash = key.lastIndexOf("/");
278
+ const folder = lastSlash > 0 ? key.slice(0, lastSlash) : void 0;
279
+ const targetName = lastSlash > 0 ? key.slice(lastSlash + 1) : key;
280
+ const listed = await this.list(bucket, folder);
281
+ return listed.some((item) => item.name === targetName);
282
+ },
283
+ getPublicUrl,
284
+ async list(bucket, folder, listOptions) {
285
+ const cleanFolder = folder ? normalizeS3Key(folder).replace(/\/+$/, "") : "";
286
+ const prefix = cleanFolder ? `${cleanFolder}/` : void 0;
287
+ const input = {
288
+ Bucket: bucket,
289
+ ...prefix ? { Prefix: prefix } : {},
290
+ MaxKeys: listOptions?.limit ?? 1e3,
291
+ ...listOptions?.cursor ? { ContinuationToken: listOptions.cursor } : {}
292
+ };
293
+ const output = transport.listObjects ? await transport.listObjects(input) : transport.listObjectsV2 ? await transport.listObjectsV2(input) : void 0;
294
+ const entries = (output?.Contents ?? []).filter((item) => Boolean(item.Key)).map((item) => {
295
+ const name = prefix && item.Key.startsWith(prefix) ? item.Key.slice(prefix.length) : item.Key;
296
+ const createdAt = item.LastModified instanceof Date ? item.LastModified.toISOString() : item.LastModified;
297
+ return {
298
+ name,
299
+ id: `${bucket}/${item.Key}`,
300
+ ...createdAt !== void 0 ? { createdAt } : {},
301
+ ...item.Size !== void 0 ? { size: item.Size } : {}
302
+ };
303
+ }).filter((entry) => entry.name.length > 0);
304
+ return toPaginatedListResult(entries, output?.NextContinuationToken);
305
+ },
306
+ parseUrl(url) {
307
+ if (typeof options.publicBaseUrl !== "string" || !url) return null;
308
+ const prefix = `${options.publicBaseUrl.replace(/\/+$/, "")}/`;
309
+ const cleanUrl = url.split(/[?#]/)[0];
310
+ if (!cleanUrl.startsWith(prefix)) return null;
311
+ try {
312
+ const rest = decodeURIComponent(cleanUrl.slice(prefix.length)).replace(/^\/+/, "");
313
+ if (!rest) return null;
314
+ if (includeBucket) {
315
+ const slashIdx = rest.indexOf("/");
316
+ if (slashIdx <= 0 || slashIdx === rest.length - 1) return null;
317
+ return {
318
+ bucket: rest.slice(0, slashIdx),
319
+ path: rest.slice(slashIdx + 1)
320
+ };
321
+ }
322
+ if (!options.defaultBucket) return null;
323
+ return { bucket: options.defaultBucket, path: rest };
324
+ } catch {
325
+ return null;
326
+ }
327
+ },
328
+ async deleteByUrl(url) {
329
+ const parsed = this.parseUrl?.(url);
330
+ if (!parsed || !parsed.bucket) return false;
331
+ try {
332
+ await transport.deleteObject({
333
+ Bucket: parsed.bucket,
334
+ Key: parsed.path
335
+ });
336
+ return true;
337
+ } catch {
338
+ return false;
339
+ }
340
+ }
341
+ };
342
+ }
343
+
344
+ // src/storage/supabaseAdapter.ts
345
+ function createSupabaseStorageAdapter(client, options = {}) {
346
+ const publicPrefix = options.publicUrlPrefix || DEFAULT_PUBLIC_STORAGE_PREFIX;
347
+ return {
348
+ async upload(params) {
349
+ validateStorageMetadata(params.metadata);
350
+ const isImmutable = params.upsert === false && params.contentType.includes("image/avif");
351
+ const cacheControl = params.cacheControl || (isImmutable ? "public, max-age=31536000, immutable" : "public, max-age=2592000, must-revalidate");
352
+ const { data, error } = await client.storage.from(params.bucket).upload(params.path, params.data, {
353
+ contentType: params.contentType,
354
+ upsert: params.upsert === void 0 ? true : params.upsert,
355
+ cacheControl,
356
+ metadata: {
357
+ ...params.metadata,
358
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
359
+ contentHash: params.contentHash || "unknown"
360
+ }
361
+ });
362
+ if (error || !data) {
363
+ throw new Error(
364
+ `Failed to upload to Supabase storage (bucket: ${params.bucket}, path: ${params.path}): ${error?.message || "Unknown error"}`
365
+ );
366
+ }
367
+ const {
368
+ data: { publicUrl }
369
+ } = client.storage.from(params.bucket).getPublicUrl(data.path);
370
+ return {
371
+ bucket: params.bucket,
372
+ path: data.path,
373
+ url: publicUrl
374
+ };
375
+ },
376
+ async delete(bucket, path2) {
377
+ const { error } = await client.storage.from(bucket).remove([path2]);
378
+ if (error) {
379
+ throw new Error(`Failed to delete from Supabase storage: ${error.message}`);
380
+ }
381
+ },
382
+ async exists(bucket, path2) {
383
+ const cleanPath = path2.replace(/^\/+/, "");
384
+ const lastSlash = cleanPath.lastIndexOf("/");
385
+ const folder = lastSlash > 0 ? cleanPath.slice(0, lastSlash) : void 0;
386
+ const targetName = lastSlash > 0 ? cleanPath.slice(lastSlash + 1) : cleanPath;
387
+ const entries = await this.list(bucket, folder);
388
+ return entries.some((entry) => entry.name === targetName);
389
+ },
390
+ getPublicUrl(bucket, path2) {
391
+ const {
392
+ data: { publicUrl }
393
+ } = client.storage.from(bucket).getPublicUrl(path2);
394
+ return publicUrl;
395
+ },
396
+ async list(bucket, folder, listOptions) {
397
+ const bucketClient = client.storage.from(bucket);
398
+ if (!bucketClient.list) {
399
+ return toPaginatedListResult([]);
400
+ }
401
+ const limit = listOptions?.limit ?? 1e3;
402
+ const offset = listOptions?.cursor ? Number(listOptions.cursor) || 0 : 0;
403
+ const { data, error } = await bucketClient.list(folder, {
404
+ limit,
405
+ ...offset > 0 ? { offset } : {},
406
+ sortBy: { column: "created_at", order: "desc" }
407
+ });
408
+ if (error) {
409
+ throw new Error(`Failed to list Supabase bucket files: ${error.message}`);
410
+ }
411
+ const entries = (data || []).filter((f) => f.id !== null).map((f) => ({
412
+ name: f.name,
413
+ id: f.id ?? void 0,
414
+ createdAt: f.created_at ?? void 0
415
+ }));
416
+ const nextCursor = listOptions?.limit !== void 0 && entries.length >= limit ? String(offset + entries.length) : void 0;
417
+ return toPaginatedListResult(entries, nextCursor);
418
+ },
419
+ parseUrl(url) {
420
+ const info = parsePublicStorageUrl(url, publicPrefix);
421
+ if (!info) return null;
422
+ return { bucket: info.bucket, path: info.path };
423
+ },
424
+ async deleteByUrl(url) {
425
+ const info = parsePublicStorageUrl(url, publicPrefix);
426
+ if (!info) return false;
427
+ try {
428
+ const { error } = await client.storage.from(info.bucket).remove([info.path]);
429
+ return !error;
430
+ } catch {
431
+ return false;
432
+ }
433
+ }
434
+ };
435
+ }
436
+ export {
437
+ DEFAULT_PUBLIC_STORAGE_PREFIX,
438
+ createLocalFsStorageAdapter,
439
+ createMemoryStorageAdapter,
440
+ createS3StorageAdapter,
441
+ createStorageAdapter,
442
+ createSupabaseStorageAdapter,
443
+ parsePublicStorageUrl,
444
+ toPaginatedListResult,
445
+ validateStorageMetadata
446
+ };
447
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/storage/localFsAdapter.ts","../src/storage/s3Adapter.ts","../src/storage/supabaseAdapter.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport {\n type StorageAdapter,\n type StorageFileEntry,\n type StorageListOptions,\n type StoragePaginatedListResult,\n type StorageUploadParams,\n type StorageUploadResult,\n toPaginatedListResult,\n} from './types';\n\nexport interface LocalFsStorageOptions {\n /** Absolute or relative root directory on disk (e.g. `./public/uploads`) */\n rootDir: string;\n /** Public URL prefix matching `rootDir` (e.g. `/uploads` or `https://cdn.example.com/uploads`) */\n baseUrl?: string;\n /** Include the bucket segment as a subdirectory under `rootDir`. Defaults to `true`. */\n includeBucketInPath?: boolean;\n}\n\nfunction sanitizeRelativePath(inputPath: string): string {\n if (!inputPath || typeof inputPath !== 'string') {\n throw new Error('Invalid storage path');\n }\n if (inputPath.includes(':') || inputPath.includes('\\0')) {\n throw new Error('Path traversal blocked in LocalFsStorageAdapter');\n }\n\n const normalized = inputPath.replace(/\\\\/g, '/').replace(/^\\/+/, '');\n const rawSegments = normalized.split('/').filter(Boolean);\n\n if (rawSegments.length === 0) {\n throw new Error('Invalid storage path');\n }\n\n for (const segment of rawSegments) {\n if (segment === '.' || segment === '..') {\n throw new Error('Invalid storage path: path traversal blocked');\n }\n }\n\n return rawSegments.join('/');\n}\n\nfunction assertContainedInRoot(resolvedRoot: string, targetPath: string): void {\n const relative = path.relative(resolvedRoot, targetPath);\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n throw new Error('Path traversal blocked in LocalFsStorageAdapter');\n }\n}\n\nexport interface LocalFsStorageAdapter extends StorageAdapter {\n exists(bucket: string, relativePath: string): Promise<boolean>;\n getPublicUrl(bucket: string, relativePath: string): string;\n list(\n bucket: string,\n folder?: string,\n options?: StorageListOptions,\n ): Promise<StoragePaginatedListResult>;\n deleteByUrl(url: string): Promise<boolean>;\n parseUrl(url: string): { bucket?: string; path: string } | null;\n}\n\nexport function createLocalFsStorageAdapter(\n options: LocalFsStorageOptions,\n): LocalFsStorageAdapter {\n const resolvedRoot = path.resolve(options.rootDir);\n const baseUrl = (options.baseUrl ?? '/uploads').replace(/\\/+$/, '');\n const includeBucket = options.includeBucketInPath !== false;\n\n const resolveDiskTarget = (bucket: string, relativePath: string) => {\n const cleanBucket = sanitizeRelativePath(bucket);\n const cleanRel = sanitizeRelativePath(relativePath);\n const subPath = includeBucket ? `${cleanBucket}/${cleanRel}` : cleanRel;\n const diskPath = path.resolve(resolvedRoot, subPath);\n\n assertContainedInRoot(resolvedRoot, diskPath);\n\n return { diskPath, subPath, cleanRel };\n };\n\n const getPublicUrl = (bucket: string, relativePath: string): string => {\n const { subPath } = resolveDiskTarget(bucket, relativePath);\n return `${baseUrl}/${subPath}`;\n };\n\n const parseUrl = (url: string): { bucket?: string; path: string } | null => {\n if (!url || typeof url !== 'string') return null;\n\n try {\n const cleanUrl = url.split(/[?#]/)[0];\n const prefix = `${baseUrl}/`;\n let subPath: string | null = null;\n\n if (cleanUrl.startsWith(prefix)) {\n subPath = cleanUrl.slice(prefix.length);\n } else if (baseUrl.startsWith('/')) {\n const parsed = new URL(cleanUrl, 'http://localhost');\n const isLocal =\n parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';\n const isRelative =\n !cleanUrl.startsWith('http://') && !cleanUrl.startsWith('https://');\n if ((isLocal || isRelative) && parsed.pathname.startsWith(prefix)) {\n subPath = parsed.pathname.slice(prefix.length);\n }\n }\n\n if (!subPath) return null;\n\n const decoded = decodeURIComponent(subPath)\n .replace(/\\\\/g, '/')\n .replace(/^\\/+/, '');\n if (!decoded || decoded.includes(':') || decoded.includes('\\0')) {\n return null;\n }\n\n const segments = decoded.split('/').filter(Boolean);\n if (\n segments.length === 0 ||\n segments.includes('..') ||\n segments.includes('.')\n ) {\n return null;\n }\n\n const diskPath = path.resolve(resolvedRoot, ...segments);\n const relative = path.relative(resolvedRoot, diskPath);\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n return null;\n }\n\n if (includeBucket) {\n if (segments.length < 2) return null;\n return {\n bucket: segments[0],\n path: segments.slice(1).join('/'),\n };\n }\n\n return { path: segments.join('/') };\n } catch {\n return null;\n }\n };\n\n return {\n async upload(params: StorageUploadParams): Promise<StorageUploadResult> {\n const { diskPath, cleanRel } = resolveDiskTarget(\n params.bucket,\n params.path,\n );\n\n await fs.mkdir(path.dirname(diskPath), { recursive: true });\n\n try {\n await fs.writeFile(\n diskPath,\n params.data,\n params.upsert === false ? { flag: 'wx' } : undefined,\n );\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\n const existsError = new Error(\n `File already exists at ${params.bucket}/${cleanRel}`,\n ) as NodeJS.ErrnoException;\n existsError.code = 'EEXIST';\n throw existsError;\n }\n throw err;\n }\n\n return {\n bucket: params.bucket,\n path: cleanRel,\n url: getPublicUrl(params.bucket, cleanRel),\n };\n },\n\n async delete(bucket: string, relativePath: string): Promise<void> {\n const { diskPath } = resolveDiskTarget(bucket, relativePath);\n try {\n await fs.unlink(diskPath);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw err;\n }\n }\n },\n\n async exists(bucket: string, relativePath: string): Promise<boolean> {\n try {\n const { diskPath } = resolveDiskTarget(bucket, relativePath);\n const stat = await fs.stat(diskPath);\n return stat.isFile();\n } catch {\n return false;\n }\n },\n\n getPublicUrl,\n parseUrl,\n\n async deleteByUrl(url: string): Promise<boolean> {\n const parsed = parseUrl(url);\n if (!parsed) return false;\n\n try {\n const subSegments =\n includeBucket && parsed.bucket\n ? [parsed.bucket, ...parsed.path.split('/')]\n : parsed.path.split('/');\n const diskPath = path.resolve(resolvedRoot, ...subSegments);\n assertContainedInRoot(resolvedRoot, diskPath);\n await fs.unlink(diskPath);\n return true;\n } catch {\n return false;\n }\n },\n\n async list(\n bucket: string,\n folder?: string,\n listOptions?: StorageListOptions,\n ): Promise<StoragePaginatedListResult> {\n const cleanBucket = sanitizeRelativePath(bucket);\n const cleanFolder = folder ? sanitizeRelativePath(folder) : '';\n const targetDir = path.resolve(\n resolvedRoot,\n includeBucket ? cleanBucket : '',\n cleanFolder,\n );\n\n assertContainedInRoot(resolvedRoot, targetDir);\n\n try {\n const dirEntries = await fs.readdir(targetDir, { withFileTypes: true });\n const results: StorageFileEntry[] = [];\n\n for (const entry of dirEntries) {\n if (!entry.isFile()) continue;\n const full = path.join(targetDir, entry.name);\n const stat = await fs.stat(full);\n results.push({\n name: entry.name,\n id: entry.name,\n createdAt: stat.birthtime.toISOString(),\n size: stat.size,\n });\n }\n\n const offset = listOptions?.cursor ? Number(listOptions.cursor) || 0 : 0;\n const limit = listOptions?.limit ?? results.length;\n const sliced =\n listOptions?.limit !== undefined || offset > 0\n ? results.slice(offset, offset + limit)\n : results;\n const nextCursor =\n listOptions?.limit !== undefined && offset + sliced.length < results.length\n ? String(offset + sliced.length)\n : undefined;\n\n return toPaginatedListResult(sliced, nextCursor);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return toPaginatedListResult([]);\n }\n throw err;\n }\n },\n };\n}\n","import {\n type StorageAdapter,\n type StorageFileEntry,\n type StorageListOptions,\n type StoragePaginatedListResult,\n type StorageUploadParams,\n type StorageUploadResult,\n toPaginatedListResult,\n validateStorageMetadata,\n} from './types';\n\nexport interface S3PutObjectInput {\n Bucket: string;\n Key: string;\n Body: Buffer | Uint8Array;\n ContentType: string;\n CacheControl?: string;\n Metadata?: Record<string, string>;\n IfNoneMatch?: string;\n}\n\nexport interface S3DeleteObjectInput {\n Bucket: string;\n Key: string;\n}\n\nexport interface S3HeadObjectInput {\n Bucket: string;\n Key: string;\n}\n\nexport interface S3ListObjectsInput {\n Bucket: string;\n Prefix?: string;\n MaxKeys?: number;\n ContinuationToken?: string;\n}\n\nexport interface S3ListedObject {\n Key?: string;\n Size?: number;\n LastModified?: Date | string;\n}\n\nexport interface S3ListObjectsOutput {\n Contents?: S3ListedObject[];\n NextContinuationToken?: string;\n IsTruncated?: boolean;\n}\n\n/** Structural S3 / Cloudflare R2 / MinIO transport with zero runtime SDK dependency. */\nexport interface S3TransportLike {\n putObject(input: S3PutObjectInput): Promise<unknown>;\n deleteObject(input: S3DeleteObjectInput): Promise<unknown>;\n headObject?(input: S3HeadObjectInput): Promise<unknown>;\n listObjects?(input: S3ListObjectsInput): Promise<S3ListObjectsOutput>;\n listObjectsV2?(input: S3ListObjectsInput): Promise<S3ListObjectsOutput>;\n}\n\nexport type S3PublicUrlBuilder = (bucket: string, path: string) => string;\n\nexport interface S3StorageBaseOptions {\n /** Public base URL prefix (e.g. `'https://cdn.example.com'`) or `(bucket, path) => string` builder. */\n publicBaseUrl: string | S3PublicUrlBuilder;\n /** Default `Cache-Control` header applied when `StorageUploadParams.cacheControl` is omitted. */\n cacheControl?: string;\n /** Include `bucket` segment in string-prefix URLs (`${publicBaseUrl}/${bucket}/${path}`). Defaults to `true`. */\n includeBucketInUrl?: boolean;\n /** Fallback bucket used by `deleteByUrl` when `includeBucketInUrl` is `false`. */\n defaultBucket?: string;\n}\n\nexport type S3StorageAdapterOptions = S3StorageBaseOptions &\n (\n | (S3TransportLike & { client?: never; transport?: never })\n | { client: S3TransportLike; transport?: never; putObject?: never }\n | { transport: S3TransportLike; client?: never; putObject?: never }\n );\n\nfunction normalizeS3Key(rawPath: string): string {\n const normalized = rawPath.replace(/\\\\/g, '/').replace(/^\\/+/, '');\n if (!normalized) throw new Error('Invalid S3 object key');\n return normalized;\n}\n\nfunction resolveTransport(options: S3StorageAdapterOptions): S3TransportLike {\n if ('client' in options && options.client) return options.client;\n if ('transport' in options && options.transport) return options.transport;\n if ('putObject' in options && typeof options.putObject === 'function') return options;\n throw new Error('S3StorageAdapter requires putObject/deleteObject or client/transport');\n}\n\nfunction isS3PreconditionError(err: unknown): boolean {\n if (!err || typeof err !== 'object') return false;\n const e = err as {\n code?: unknown;\n name?: unknown;\n statusCode?: unknown;\n $metadata?: { httpStatusCode?: number };\n };\n const status = e.$metadata?.httpStatusCode ?? Number(e.statusCode);\n const code = String(e.code ?? e.name ?? '').toLowerCase();\n return status === 412 || code === 'preconditionfailed' || code === 'eexist';\n}\n\n/** Zero-dependency S3 / Cloudflare R2 / MinIO `StorageAdapter`. */\nexport function createS3StorageAdapter(options: S3StorageAdapterOptions): StorageAdapter {\n const transport = resolveTransport(options);\n const includeBucket = options.includeBucketInUrl !== false;\n\n const getPublicUrl = (bucket: string, path: string): string => {\n const key = normalizeS3Key(path);\n if (typeof options.publicBaseUrl === 'function') return options.publicBaseUrl(bucket, key);\n const base = options.publicBaseUrl.replace(/\\/+$/, '');\n return includeBucket ? `${base}/${bucket}/${key}` : `${base}/${key}`;\n };\n\n return {\n async upload(params: StorageUploadParams): Promise<StorageUploadResult> {\n const key = normalizeS3Key(params.path);\n validateStorageMetadata(params.metadata);\n\n const isImmutable =\n params.upsert === false && params.contentType.includes('image/avif');\n const cacheControl =\n params.cacheControl ??\n options.cacheControl ??\n (isImmutable\n ? 'public, max-age=31536000, immutable'\n : 'public, max-age=2592000, must-revalidate');\n\n const metadata =\n params.metadata || params.contentHash\n ? {\n ...params.metadata,\n ...(params.contentHash ? { contentHash: params.contentHash } : {}),\n }\n : undefined;\n\n try {\n await transport.putObject({\n Bucket: params.bucket,\n Key: key,\n Body: params.data,\n ContentType: params.contentType,\n ...(cacheControl ? { CacheControl: cacheControl } : {}),\n ...(metadata ? { Metadata: metadata } : {}),\n ...(params.upsert === false ? { IfNoneMatch: '*' } : {}),\n });\n } catch (err) {\n if (isS3PreconditionError(err)) {\n const existsErr = new Error(\n `Object already exists at ${params.bucket}/${key}`,\n ) as Error & { code?: string; statusCode?: number };\n existsErr.code = 'EEXIST';\n existsErr.statusCode = 412;\n throw existsErr;\n }\n throw err;\n }\n\n return {\n bucket: params.bucket,\n path: key,\n url: getPublicUrl(params.bucket, key),\n };\n },\n\n async delete(bucket: string, path: string): Promise<void> {\n await transport.deleteObject({ Bucket: bucket, Key: normalizeS3Key(path) });\n },\n\n async exists(bucket: string, path: string): Promise<boolean> {\n const key = normalizeS3Key(path);\n if (transport.headObject) {\n try {\n await transport.headObject({ Bucket: bucket, Key: key });\n return true;\n } catch {\n return false;\n }\n }\n const lastSlash = key.lastIndexOf('/');\n const folder = lastSlash > 0 ? key.slice(0, lastSlash) : undefined;\n const targetName = lastSlash > 0 ? key.slice(lastSlash + 1) : key;\n const listed = await this.list!(bucket, folder);\n return listed.some((item) => item.name === targetName);\n },\n\n getPublicUrl,\n\n async list(\n bucket: string,\n folder?: string,\n listOptions?: StorageListOptions,\n ): Promise<StoragePaginatedListResult> {\n const cleanFolder = folder ? normalizeS3Key(folder).replace(/\\/+$/, '') : '';\n const prefix = cleanFolder ? `${cleanFolder}/` : undefined;\n const input: S3ListObjectsInput = {\n Bucket: bucket,\n ...(prefix ? { Prefix: prefix } : {}),\n MaxKeys: listOptions?.limit ?? 1000,\n ...(listOptions?.cursor ? { ContinuationToken: listOptions.cursor } : {}),\n };\n\n const output = transport.listObjects\n ? await transport.listObjects(input)\n : transport.listObjectsV2\n ? await transport.listObjectsV2(input)\n : undefined;\n\n const entries: StorageFileEntry[] = (output?.Contents ?? [])\n .filter((item): item is S3ListedObject & { Key: string } => Boolean(item.Key))\n .map((item) => {\n const name =\n prefix && item.Key.startsWith(prefix)\n ? item.Key.slice(prefix.length)\n : item.Key;\n const createdAt =\n item.LastModified instanceof Date\n ? item.LastModified.toISOString()\n : item.LastModified;\n return {\n name,\n id: `${bucket}/${item.Key}`,\n ...(createdAt !== undefined ? { createdAt } : {}),\n ...(item.Size !== undefined ? { size: item.Size } : {}),\n };\n })\n .filter((entry) => entry.name.length > 0);\n\n return toPaginatedListResult(entries, output?.NextContinuationToken);\n },\n\n parseUrl(url: string): { bucket?: string; path: string } | null {\n if (typeof options.publicBaseUrl !== 'string' || !url) return null;\n const prefix = `${options.publicBaseUrl.replace(/\\/+$/, '')}/`;\n const cleanUrl = url.split(/[?#]/)[0];\n if (!cleanUrl.startsWith(prefix)) return null;\n\n try {\n const rest = decodeURIComponent(cleanUrl.slice(prefix.length)).replace(/^\\/+/, '');\n if (!rest) return null;\n\n if (includeBucket) {\n const slashIdx = rest.indexOf('/');\n if (slashIdx <= 0 || slashIdx === rest.length - 1) return null;\n return {\n bucket: rest.slice(0, slashIdx),\n path: rest.slice(slashIdx + 1),\n };\n }\n\n if (!options.defaultBucket) return null;\n return { bucket: options.defaultBucket, path: rest };\n } catch {\n return null;\n }\n },\n\n async deleteByUrl(url: string): Promise<boolean> {\n const parsed = this.parseUrl?.(url);\n if (!parsed || !parsed.bucket) return false;\n\n try {\n await transport.deleteObject({\n Bucket: parsed.bucket,\n Key: parsed.path,\n });\n return true;\n } catch {\n return false;\n }\n },\n };\n}\n","import {\n type StorageAdapter,\n type StorageListOptions,\n type StoragePaginatedListResult,\n type StorageUploadParams,\n type StorageUploadResult,\n toPaginatedListResult,\n validateStorageMetadata,\n} from './types';\nimport {\n DEFAULT_PUBLIC_STORAGE_PREFIX,\n parsePublicStorageUrl,\n} from './urlParser';\n\n/**\n * Minimal structural interface matching `@supabase/supabase-js` Storage client,\n * avoiding a mandatory `@supabase/supabase-js` runtime dependency.\n */\nexport interface SupabaseBucketClientLike {\n upload(\n path: string,\n fileBody: Buffer | Uint8Array,\n fileOptions?: {\n contentType?: string;\n upsert?: boolean;\n cacheControl?: string;\n metadata?: Record<string, string>;\n },\n ): Promise<{\n data: { path: string } | null;\n error: { message: string } | null;\n }>;\n remove(paths: string[]): Promise<{\n data?: unknown;\n error: { message: string } | null;\n }>;\n getPublicUrl(path: string): {\n data: { publicUrl: string };\n };\n list?(\n path?: string,\n options?: {\n limit?: number;\n offset?: number;\n sortBy?: { column: string; order: string };\n },\n ): Promise<{\n data: Array<{\n name: string;\n id: string | null;\n created_at: string | null;\n }> | null;\n error: { message: string } | null;\n }>;\n}\n\nexport interface SupabaseStorageClientLike {\n storage: {\n from(bucket: string): SupabaseBucketClientLike;\n };\n}\n\nexport interface SupabaseStorageAdapterOptions {\n publicUrlPrefix?: string;\n}\n\n/**\n * Plug in any `@supabase/supabase-js` client instance (e.g., service-role client)\n * as a `StorageAdapter` with zero extra wrapper boilerplate.\n */\nexport function createSupabaseStorageAdapter(\n client: SupabaseStorageClientLike,\n options: SupabaseStorageAdapterOptions = {},\n): StorageAdapter {\n const publicPrefix =\n options.publicUrlPrefix || DEFAULT_PUBLIC_STORAGE_PREFIX;\n\n return {\n async upload(params: StorageUploadParams): Promise<StorageUploadResult> {\n validateStorageMetadata(params.metadata);\n\n const isImmutable =\n params.upsert === false && params.contentType.includes('image/avif');\n const cacheControl =\n params.cacheControl ||\n (isImmutable\n ? 'public, max-age=31536000, immutable'\n : 'public, max-age=2592000, must-revalidate');\n\n const { data, error } = await client.storage\n .from(params.bucket)\n .upload(params.path, params.data, {\n contentType: params.contentType,\n upsert: params.upsert === undefined ? true : params.upsert,\n cacheControl,\n metadata: {\n ...params.metadata,\n uploadedAt: new Date().toISOString(),\n contentHash: params.contentHash || 'unknown',\n },\n });\n\n if (error || !data) {\n throw new Error(\n `Failed to upload to Supabase storage (bucket: ${params.bucket}, path: ${params.path}): ${error?.message || 'Unknown error'}`,\n );\n }\n\n const {\n data: { publicUrl },\n } = client.storage.from(params.bucket).getPublicUrl(data.path);\n\n return {\n bucket: params.bucket,\n path: data.path,\n url: publicUrl,\n };\n },\n\n async delete(bucket: string, path: string): Promise<void> {\n const { error } = await client.storage.from(bucket).remove([path]);\n if (error) {\n throw new Error(`Failed to delete from Supabase storage: ${error.message}`);\n }\n },\n\n async exists(bucket: string, path: string): Promise<boolean> {\n const cleanPath = path.replace(/^\\/+/, '');\n const lastSlash = cleanPath.lastIndexOf('/');\n const folder = lastSlash > 0 ? cleanPath.slice(0, lastSlash) : undefined;\n const targetName = lastSlash > 0 ? cleanPath.slice(lastSlash + 1) : cleanPath;\n const entries = await this.list!(bucket, folder);\n return entries.some((entry) => entry.name === targetName);\n },\n\n getPublicUrl(bucket: string, path: string): string {\n const {\n data: { publicUrl },\n } = client.storage.from(bucket).getPublicUrl(path);\n return publicUrl;\n },\n\n async list(\n bucket: string,\n folder?: string,\n listOptions?: StorageListOptions,\n ): Promise<StoragePaginatedListResult> {\n const bucketClient = client.storage.from(bucket);\n if (!bucketClient.list) {\n return toPaginatedListResult([]);\n }\n\n const limit = listOptions?.limit ?? 1000;\n const offset = listOptions?.cursor ? Number(listOptions.cursor) || 0 : 0;\n\n const { data, error } = await bucketClient.list(folder, {\n limit,\n ...(offset > 0 ? { offset } : {}),\n sortBy: { column: 'created_at', order: 'desc' },\n });\n\n if (error) {\n throw new Error(`Failed to list Supabase bucket files: ${error.message}`);\n }\n\n const entries = (data || [])\n .filter((f) => f.id !== null)\n .map((f) => ({\n name: f.name,\n id: f.id ?? undefined,\n createdAt: f.created_at ?? undefined,\n }));\n\n const nextCursor =\n listOptions?.limit !== undefined && entries.length >= limit\n ? String(offset + entries.length)\n : undefined;\n\n return toPaginatedListResult(entries, nextCursor);\n },\n\n parseUrl(url: string): { bucket?: string; path: string } | null {\n const info = parsePublicStorageUrl(url, publicPrefix);\n if (!info) return null;\n return { bucket: info.bucket, path: info.path };\n },\n\n async deleteByUrl(url: string): Promise<boolean> {\n const info = parsePublicStorageUrl(url, publicPrefix);\n if (!info) return false;\n\n try {\n const { error } = await client.storage\n .from(info.bucket)\n .remove([info.path]);\n return !error;\n } catch {\n return false;\n }\n },\n };\n}\n"],"mappings":"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","names":["path","path"]}
@@ -0,0 +1,112 @@
1
+ /* ── AnimatedImage: blur-up crossfade image (Imagine-Global parity) ─────── */
2
+ .iuk-animated-image {
3
+ position: relative;
4
+ width: 100%;
5
+ height: 100%;
6
+ overflow: hidden;
7
+ overflow: clip;
8
+ border-radius: inherit;
9
+ clip-path: inset(0 round inherit);
10
+ contain: paint;
11
+ transition: background-color var(--iuk-animated-image-color-transition, 300ms ease);
12
+ }
13
+
14
+ .iuk-animated-image--fill {
15
+ position: absolute;
16
+ inset: 0;
17
+ }
18
+
19
+ .iuk-animated-image__placeholder {
20
+ position: absolute;
21
+ inset: 0;
22
+ width: 100%;
23
+ height: 100%;
24
+ object-position: center;
25
+ /* Tiny placeholder stretched to full size is blurred and scaled slightly so
26
+ blurred edges stay clipped inside the overflow: hidden wrapper. */
27
+ filter: blur(var(--iuk-animated-image-blur, 20px));
28
+ transform: scale(var(--iuk-animated-image-placeholder-scale, 1.1));
29
+ transform-origin: center;
30
+ z-index: 1;
31
+ opacity: 1;
32
+ transition: opacity var(--iuk-ai-ph-dur, 500ms) ease-in-out;
33
+ pointer-events: none;
34
+ }
35
+
36
+ .iuk-animated-image__placeholder--hidden {
37
+ opacity: 0;
38
+ }
39
+
40
+ .iuk-animated-image__img {
41
+ position: relative;
42
+ display: block;
43
+ z-index: 2;
44
+ width: 100%;
45
+ height: auto;
46
+ opacity: 0;
47
+ transform: scale(var(--iuk-animated-image-reveal-scale, 1.02));
48
+ border-radius: inherit;
49
+ user-select: none;
50
+ -webkit-user-select: none;
51
+ -webkit-user-drag: none;
52
+ transition:
53
+ opacity var(--iuk-animated-image-duration, 600ms) ease-in-out,
54
+ transform var(--iuk-animated-image-duration, 600ms) ease-in-out;
55
+ }
56
+
57
+ .iuk-animated-image__img[data-state='loaded'] {
58
+ opacity: 1;
59
+ transform: scale(1);
60
+ }
61
+
62
+ .iuk-animated-image__img[data-state='error'] {
63
+ opacity: 0;
64
+ visibility: hidden;
65
+ }
66
+
67
+ .iuk-animated-image--fill .iuk-animated-image__img {
68
+ position: absolute;
69
+ inset: 0;
70
+ height: 100%;
71
+ }
72
+
73
+ /* In contain mode, the generated blur placeholder stays visible with object-fit: cover
74
+ and scale(1.1) behind the contained image so letterbox/pillarbox bars show the blur. */
75
+ .iuk-animated-image--contain .iuk-animated-image__img {
76
+ transform: none;
77
+ }
78
+
79
+ .iuk-animated-image__shimmer {
80
+ position: absolute;
81
+ inset: 0;
82
+ z-index: 3;
83
+ background: linear-gradient(
84
+ 90deg,
85
+ transparent 0%,
86
+ var(--iuk-animated-image-shimmer, rgba(255, 255, 255, 0.1)) 50%,
87
+ transparent 100%
88
+ );
89
+ animation: iuk-animated-image-shimmer 2s infinite;
90
+ pointer-events: none;
91
+ }
92
+
93
+ .iuk-animated-image__shimmer--hidden {
94
+ opacity: 0;
95
+ }
96
+
97
+ @keyframes iuk-animated-image-shimmer {
98
+ from { transform: translateX(-100%); }
99
+ to { transform: translateX(100%); }
100
+ }
101
+
102
+ @media (prefers-reduced-motion: reduce) {
103
+ .iuk-animated-image__img,
104
+ .iuk-animated-image__placeholder {
105
+ transform: none !important;
106
+ transition-duration: 0ms !important;
107
+ transition-delay: 0ms !important;
108
+ }
109
+ .iuk-animated-image__shimmer {
110
+ animation: none;
111
+ }
112
+ }