img-upload-kit 2.0.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +89 -89
- package/MIGRATION.md +192 -192
- package/README.md +243 -243
- package/dist/bin.js +1 -1
- package/dist/bin.js.map +1 -1
- package/dist/blur.cjs +3 -3
- package/dist/blur.cjs.map +1 -1
- package/dist/blur.js +2 -2
- package/dist/{chunk-XKQMCY5Z.cjs → chunk-2OVNXHTN.cjs} +1 -1
- package/dist/chunk-2OVNXHTN.cjs.map +1 -0
- package/dist/{chunk-FP5YIWG5.cjs → chunk-3ICG2SLL.cjs} +1 -1
- package/dist/chunk-3ICG2SLL.cjs.map +1 -0
- package/dist/{chunk-J6A44Y3W.js → chunk-4ASFG3KB.js} +1 -1
- package/dist/chunk-4ASFG3KB.js.map +1 -0
- package/dist/{chunk-IDLLCVGB.js → chunk-AI3JJLSA.js} +3 -3
- package/dist/chunk-AI3JJLSA.js.map +1 -0
- package/dist/{chunk-MHGQEBPW.js → chunk-CV23O4YU.js} +1 -1
- package/dist/chunk-CV23O4YU.js.map +1 -0
- package/dist/{chunk-25GYKQ6J.js → chunk-G7LLSKBV.js} +1 -1
- package/dist/chunk-G7LLSKBV.js.map +1 -0
- package/dist/{chunk-EWVIL57J.js → chunk-IB3TEDLR.js} +1 -1
- package/dist/chunk-IB3TEDLR.js.map +1 -0
- package/dist/{chunk-DFCSN3X7.cjs → chunk-KJVYWKIG.cjs} +1 -1
- package/dist/chunk-KJVYWKIG.cjs.map +1 -0
- package/dist/{chunk-34L4TEE3.cjs → chunk-NEDBIIE3.cjs} +1 -1
- package/dist/chunk-NEDBIIE3.cjs.map +1 -0
- package/dist/chunk-QHU7FUO4.cjs.map +1 -1
- package/dist/{chunk-VYVZVUNI.js → chunk-QQHZJKNA.js} +1 -1
- package/dist/chunk-QQHZJKNA.js.map +1 -0
- package/dist/{chunk-HILELQGS.js → chunk-TT5C7K3Q.js} +1 -1
- package/dist/chunk-TT5C7K3Q.js.map +1 -0
- package/dist/{chunk-TA5Y64JM.cjs → chunk-U2TWTEBY.cjs} +24 -24
- package/dist/chunk-U2TWTEBY.cjs.map +1 -0
- package/dist/{chunk-MBQJ6NVJ.cjs → chunk-WISG74VL.cjs} +1 -1
- package/dist/chunk-WISG74VL.cjs.map +1 -0
- package/dist/{chunk-LOSVFO56.cjs → chunk-YRXXQUDE.cjs} +1 -1
- package/dist/chunk-YRXXQUDE.cjs.map +1 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/headless.cjs +4 -4
- package/dist/headless.cjs.map +1 -1
- package/dist/headless.js +3 -3
- package/dist/index.cjs +5 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/react.cjs +138 -138
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +5 -5
- package/dist/react.js.map +1 -1
- package/dist/server-handler.cjs +2 -2
- package/dist/server-handler.cjs.map +1 -1
- package/dist/server-handler.js +1 -1
- package/dist/server.cjs +30 -30
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/storage.cjs +9 -9
- package/dist/storage.cjs.map +1 -1
- package/dist/storage.js +1 -1
- package/dist/storage.js.map +1 -1
- package/dist/styles/animated-image.css +112 -112
- package/dist/styles/tokens.css +123 -123
- package/dist/styles.css +235 -235
- package/package.json +1 -1
- package/dist/chunk-25GYKQ6J.js.map +0 -1
- package/dist/chunk-34L4TEE3.cjs.map +0 -1
- package/dist/chunk-DFCSN3X7.cjs.map +0 -1
- package/dist/chunk-EWVIL57J.js.map +0 -1
- package/dist/chunk-FP5YIWG5.cjs.map +0 -1
- package/dist/chunk-HILELQGS.js.map +0 -1
- package/dist/chunk-IDLLCVGB.js.map +0 -1
- package/dist/chunk-J6A44Y3W.js.map +0 -1
- package/dist/chunk-LOSVFO56.cjs.map +0 -1
- package/dist/chunk-MBQJ6NVJ.cjs.map +0 -1
- package/dist/chunk-MHGQEBPW.js.map +0 -1
- package/dist/chunk-TA5Y64JM.cjs.map +0 -1
- package/dist/chunk-VYVZVUNI.js.map +0 -1
- package/dist/chunk-XKQMCY5Z.cjs.map +0 -1
package/dist/storage.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
var
|
|
6
|
+
var _chunkYRXXQUDEcjs = require('./chunk-YRXXQUDE.cjs');
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
|
|
@@ -185,10 +185,10 @@ function createLocalFsStorageAdapter(options) {
|
|
|
185
185
|
const limit = _nullishCoalesce(_optionalChain([listOptions, 'optionalAccess', _2 => _2.limit]), () => ( results.length));
|
|
186
186
|
const sliced = _optionalChain([listOptions, 'optionalAccess', _3 => _3.limit]) !== void 0 || offset > 0 ? results.slice(offset, offset + limit) : results;
|
|
187
187
|
const nextCursor = _optionalChain([listOptions, 'optionalAccess', _4 => _4.limit]) !== void 0 && offset + sliced.length < results.length ? String(offset + sliced.length) : void 0;
|
|
188
|
-
return
|
|
188
|
+
return _chunkYRXXQUDEcjs.toPaginatedListResult.call(void 0, sliced, nextCursor);
|
|
189
189
|
} catch (err) {
|
|
190
190
|
if (err.code === "ENOENT") {
|
|
191
|
-
return
|
|
191
|
+
return _chunkYRXXQUDEcjs.toPaginatedListResult.call(void 0, []);
|
|
192
192
|
}
|
|
193
193
|
throw err;
|
|
194
194
|
}
|
|
@@ -227,7 +227,7 @@ function createS3StorageAdapter(options) {
|
|
|
227
227
|
return {
|
|
228
228
|
async upload(params) {
|
|
229
229
|
const key = normalizeS3Key(params.path);
|
|
230
|
-
|
|
230
|
+
_chunkYRXXQUDEcjs.validateStorageMetadata.call(void 0, params.metadata);
|
|
231
231
|
const isImmutable = params.upsert === false && params.contentType.includes("image/avif");
|
|
232
232
|
const cacheControl = _nullishCoalesce(_nullishCoalesce(params.cacheControl, () => ( options.cacheControl)), () => ( (isImmutable ? "public, max-age=31536000, immutable" : "public, max-age=2592000, must-revalidate")));
|
|
233
233
|
const metadata = params.metadata || params.contentHash ? {
|
|
@@ -301,7 +301,7 @@ function createS3StorageAdapter(options) {
|
|
|
301
301
|
...item.Size !== void 0 ? { size: item.Size } : {}
|
|
302
302
|
};
|
|
303
303
|
}).filter((entry) => entry.name.length > 0);
|
|
304
|
-
return
|
|
304
|
+
return _chunkYRXXQUDEcjs.toPaginatedListResult.call(void 0, entries, _optionalChain([output, 'optionalAccess', _10 => _10.NextContinuationToken]));
|
|
305
305
|
},
|
|
306
306
|
parseUrl(url) {
|
|
307
307
|
if (typeof options.publicBaseUrl !== "string" || !url) return null;
|
|
@@ -346,7 +346,7 @@ function createSupabaseStorageAdapter(client, options = {}) {
|
|
|
346
346
|
const publicPrefix = options.publicUrlPrefix || _chunkQHU7FUO4cjs.DEFAULT_PUBLIC_STORAGE_PREFIX;
|
|
347
347
|
return {
|
|
348
348
|
async upload(params) {
|
|
349
|
-
|
|
349
|
+
_chunkYRXXQUDEcjs.validateStorageMetadata.call(void 0, params.metadata);
|
|
350
350
|
const isImmutable = params.upsert === false && params.contentType.includes("image/avif");
|
|
351
351
|
const cacheControl = params.cacheControl || (isImmutable ? "public, max-age=31536000, immutable" : "public, max-age=2592000, must-revalidate");
|
|
352
352
|
const { data, error } = await client.storage.from(params.bucket).upload(params.path, params.data, {
|
|
@@ -396,7 +396,7 @@ function createSupabaseStorageAdapter(client, options = {}) {
|
|
|
396
396
|
async list(bucket, folder, listOptions) {
|
|
397
397
|
const bucketClient = client.storage.from(bucket);
|
|
398
398
|
if (!bucketClient.list) {
|
|
399
|
-
return
|
|
399
|
+
return _chunkYRXXQUDEcjs.toPaginatedListResult.call(void 0, []);
|
|
400
400
|
}
|
|
401
401
|
const limit = _nullishCoalesce(_optionalChain([listOptions, 'optionalAccess', _14 => _14.limit]), () => ( 1e3));
|
|
402
402
|
const offset = _optionalChain([listOptions, 'optionalAccess', _15 => _15.cursor]) ? Number(listOptions.cursor) || 0 : 0;
|
|
@@ -414,7 +414,7 @@ function createSupabaseStorageAdapter(client, options = {}) {
|
|
|
414
414
|
createdAt: _nullishCoalesce(f.created_at, () => ( void 0))
|
|
415
415
|
}));
|
|
416
416
|
const nextCursor = _optionalChain([listOptions, 'optionalAccess', _16 => _16.limit]) !== void 0 && entries.length >= limit ? String(offset + entries.length) : void 0;
|
|
417
|
-
return
|
|
417
|
+
return _chunkYRXXQUDEcjs.toPaginatedListResult.call(void 0, entries, nextCursor);
|
|
418
418
|
},
|
|
419
419
|
parseUrl(url) {
|
|
420
420
|
const info = _chunkQHU7FUO4cjs.parsePublicStorageUrl.call(void 0, url, publicPrefix);
|
|
@@ -443,5 +443,5 @@ function createSupabaseStorageAdapter(client, options = {}) {
|
|
|
443
443
|
|
|
444
444
|
|
|
445
445
|
|
|
446
|
-
exports.DEFAULT_PUBLIC_STORAGE_PREFIX = _chunkQHU7FUO4cjs.DEFAULT_PUBLIC_STORAGE_PREFIX; exports.createLocalFsStorageAdapter = createLocalFsStorageAdapter; exports.createMemoryStorageAdapter =
|
|
446
|
+
exports.DEFAULT_PUBLIC_STORAGE_PREFIX = _chunkQHU7FUO4cjs.DEFAULT_PUBLIC_STORAGE_PREFIX; exports.createLocalFsStorageAdapter = createLocalFsStorageAdapter; exports.createMemoryStorageAdapter = _chunkYRXXQUDEcjs.createMemoryStorageAdapter; exports.createS3StorageAdapter = createS3StorageAdapter; exports.createStorageAdapter = _chunkYRXXQUDEcjs.createStorageAdapter; exports.createSupabaseStorageAdapter = createSupabaseStorageAdapter; exports.parsePublicStorageUrl = _chunkQHU7FUO4cjs.parsePublicStorageUrl; exports.toPaginatedListResult = _chunkYRXXQUDEcjs.toPaginatedListResult; exports.validateStorageMetadata = _chunkYRXXQUDEcjs.validateStorageMetadata;
|
|
447
447
|
//# sourceMappingURL=storage.cjs.map
|
package/dist/storage.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["e:\\Personal-Projects\\image-upload-kit\\dist\\storage.cjs"],"names":[],"mappings":"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,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,YAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,YAAA;AACA,YAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACA,QAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"E:\\Personal-Projects\\image-upload-kit\\dist\\storage.cjs","sourcesContent":[null]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/image-upload-kit/image-upload-kit/dist/storage.cjs","../src/storage/localFsAdapter.ts","../src/storage/s3Adapter.ts","../src/storage/supabaseAdapter.ts"],"names":["path"],"mappings":"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","file":"/home/runner/work/image-upload-kit/image-upload-kit/dist/storage.cjs","sourcesContent":[null,"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"]}
|
package/dist/storage.js
CHANGED
package/dist/storage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/storage/localFsAdapter.ts","../src/storage/s3Adapter.ts","../src/storage/supabaseAdapter.ts"],"sourcesContent":["import fs from 'node:fs/promises';\r\nimport path from 'node:path';\r\nimport {\r\n type StorageAdapter,\r\n type StorageFileEntry,\r\n type StorageListOptions,\r\n type StoragePaginatedListResult,\r\n type StorageUploadParams,\r\n type StorageUploadResult,\r\n toPaginatedListResult,\r\n} from './types';\r\n\r\nexport interface LocalFsStorageOptions {\r\n /** Absolute or relative root directory on disk (e.g. `./public/uploads`) */\r\n rootDir: string;\r\n /** Public URL prefix matching `rootDir` (e.g. `/uploads` or `https://cdn.example.com/uploads`) */\r\n baseUrl?: string;\r\n /** Include the bucket segment as a subdirectory under `rootDir`. Defaults to `true`. */\r\n includeBucketInPath?: boolean;\r\n}\r\n\r\nfunction sanitizeRelativePath(inputPath: string): string {\r\n if (!inputPath || typeof inputPath !== 'string') {\r\n throw new Error('Invalid storage path');\r\n }\r\n if (inputPath.includes(':') || inputPath.includes('\\0')) {\r\n throw new Error('Path traversal blocked in LocalFsStorageAdapter');\r\n }\r\n\r\n const normalized = inputPath.replace(/\\\\/g, '/').replace(/^\\/+/, '');\r\n const rawSegments = normalized.split('/').filter(Boolean);\r\n\r\n if (rawSegments.length === 0) {\r\n throw new Error('Invalid storage path');\r\n }\r\n\r\n for (const segment of rawSegments) {\r\n if (segment === '.' || segment === '..') {\r\n throw new Error('Invalid storage path: path traversal blocked');\r\n }\r\n }\r\n\r\n return rawSegments.join('/');\r\n}\r\n\r\nfunction assertContainedInRoot(resolvedRoot: string, targetPath: string): void {\r\n const relative = path.relative(resolvedRoot, targetPath);\r\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\r\n throw new Error('Path traversal blocked in LocalFsStorageAdapter');\r\n }\r\n}\r\n\r\nexport interface LocalFsStorageAdapter extends StorageAdapter {\r\n exists(bucket: string, relativePath: string): Promise<boolean>;\r\n getPublicUrl(bucket: string, relativePath: string): string;\r\n list(\r\n bucket: string,\r\n folder?: string,\r\n options?: StorageListOptions,\r\n ): Promise<StoragePaginatedListResult>;\r\n deleteByUrl(url: string): Promise<boolean>;\r\n parseUrl(url: string): { bucket?: string; path: string } | null;\r\n}\r\n\r\nexport function createLocalFsStorageAdapter(\r\n options: LocalFsStorageOptions,\r\n): LocalFsStorageAdapter {\r\n const resolvedRoot = path.resolve(options.rootDir);\r\n const baseUrl = (options.baseUrl ?? '/uploads').replace(/\\/+$/, '');\r\n const includeBucket = options.includeBucketInPath !== false;\r\n\r\n const resolveDiskTarget = (bucket: string, relativePath: string) => {\r\n const cleanBucket = sanitizeRelativePath(bucket);\r\n const cleanRel = sanitizeRelativePath(relativePath);\r\n const subPath = includeBucket ? `${cleanBucket}/${cleanRel}` : cleanRel;\r\n const diskPath = path.resolve(resolvedRoot, subPath);\r\n\r\n assertContainedInRoot(resolvedRoot, diskPath);\r\n\r\n return { diskPath, subPath, cleanRel };\r\n };\r\n\r\n const getPublicUrl = (bucket: string, relativePath: string): string => {\r\n const { subPath } = resolveDiskTarget(bucket, relativePath);\r\n return `${baseUrl}/${subPath}`;\r\n };\r\n\r\n const parseUrl = (url: string): { bucket?: string; path: string } | null => {\r\n if (!url || typeof url !== 'string') return null;\r\n\r\n try {\r\n const cleanUrl = url.split(/[?#]/)[0];\r\n const prefix = `${baseUrl}/`;\r\n let subPath: string | null = null;\r\n\r\n if (cleanUrl.startsWith(prefix)) {\r\n subPath = cleanUrl.slice(prefix.length);\r\n } else if (baseUrl.startsWith('/')) {\r\n const parsed = new URL(cleanUrl, 'http://localhost');\r\n const isLocal =\r\n parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';\r\n const isRelative =\r\n !cleanUrl.startsWith('http://') && !cleanUrl.startsWith('https://');\r\n if ((isLocal || isRelative) && parsed.pathname.startsWith(prefix)) {\r\n subPath = parsed.pathname.slice(prefix.length);\r\n }\r\n }\r\n\r\n if (!subPath) return null;\r\n\r\n const decoded = decodeURIComponent(subPath)\r\n .replace(/\\\\/g, '/')\r\n .replace(/^\\/+/, '');\r\n if (!decoded || decoded.includes(':') || decoded.includes('\\0')) {\r\n return null;\r\n }\r\n\r\n const segments = decoded.split('/').filter(Boolean);\r\n if (\r\n segments.length === 0 ||\r\n segments.includes('..') ||\r\n segments.includes('.')\r\n ) {\r\n return null;\r\n }\r\n\r\n const diskPath = path.resolve(resolvedRoot, ...segments);\r\n const relative = path.relative(resolvedRoot, diskPath);\r\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\r\n return null;\r\n }\r\n\r\n if (includeBucket) {\r\n if (segments.length < 2) return null;\r\n return {\r\n bucket: segments[0],\r\n path: segments.slice(1).join('/'),\r\n };\r\n }\r\n\r\n return { path: segments.join('/') };\r\n } catch {\r\n return null;\r\n }\r\n };\r\n\r\n return {\r\n async upload(params: StorageUploadParams): Promise<StorageUploadResult> {\r\n const { diskPath, cleanRel } = resolveDiskTarget(\r\n params.bucket,\r\n params.path,\r\n );\r\n\r\n await fs.mkdir(path.dirname(diskPath), { recursive: true });\r\n\r\n try {\r\n await fs.writeFile(\r\n diskPath,\r\n params.data,\r\n params.upsert === false ? { flag: 'wx' } : undefined,\r\n );\r\n } catch (err) {\r\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\r\n const existsError = new Error(\r\n `File already exists at ${params.bucket}/${cleanRel}`,\r\n ) as NodeJS.ErrnoException;\r\n existsError.code = 'EEXIST';\r\n throw existsError;\r\n }\r\n throw err;\r\n }\r\n\r\n return {\r\n bucket: params.bucket,\r\n path: cleanRel,\r\n url: getPublicUrl(params.bucket, cleanRel),\r\n };\r\n },\r\n\r\n async delete(bucket: string, relativePath: string): Promise<void> {\r\n const { diskPath } = resolveDiskTarget(bucket, relativePath);\r\n try {\r\n await fs.unlink(diskPath);\r\n } catch (err) {\r\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\r\n throw err;\r\n }\r\n }\r\n },\r\n\r\n async exists(bucket: string, relativePath: string): Promise<boolean> {\r\n try {\r\n const { diskPath } = resolveDiskTarget(bucket, relativePath);\r\n const stat = await fs.stat(diskPath);\r\n return stat.isFile();\r\n } catch {\r\n return false;\r\n }\r\n },\r\n\r\n getPublicUrl,\r\n parseUrl,\r\n\r\n async deleteByUrl(url: string): Promise<boolean> {\r\n const parsed = parseUrl(url);\r\n if (!parsed) return false;\r\n\r\n try {\r\n const subSegments =\r\n includeBucket && parsed.bucket\r\n ? [parsed.bucket, ...parsed.path.split('/')]\r\n : parsed.path.split('/');\r\n const diskPath = path.resolve(resolvedRoot, ...subSegments);\r\n assertContainedInRoot(resolvedRoot, diskPath);\r\n await fs.unlink(diskPath);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n },\r\n\r\n async list(\r\n bucket: string,\r\n folder?: string,\r\n listOptions?: StorageListOptions,\r\n ): Promise<StoragePaginatedListResult> {\r\n const cleanBucket = sanitizeRelativePath(bucket);\r\n const cleanFolder = folder ? sanitizeRelativePath(folder) : '';\r\n const targetDir = path.resolve(\r\n resolvedRoot,\r\n includeBucket ? cleanBucket : '',\r\n cleanFolder,\r\n );\r\n\r\n assertContainedInRoot(resolvedRoot, targetDir);\r\n\r\n try {\r\n const dirEntries = await fs.readdir(targetDir, { withFileTypes: true });\r\n const results: StorageFileEntry[] = [];\r\n\r\n for (const entry of dirEntries) {\r\n if (!entry.isFile()) continue;\r\n const full = path.join(targetDir, entry.name);\r\n const stat = await fs.stat(full);\r\n results.push({\r\n name: entry.name,\r\n id: entry.name,\r\n createdAt: stat.birthtime.toISOString(),\r\n size: stat.size,\r\n });\r\n }\r\n\r\n const offset = listOptions?.cursor ? Number(listOptions.cursor) || 0 : 0;\r\n const limit = listOptions?.limit ?? results.length;\r\n const sliced =\r\n listOptions?.limit !== undefined || offset > 0\r\n ? results.slice(offset, offset + limit)\r\n : results;\r\n const nextCursor =\r\n listOptions?.limit !== undefined && offset + sliced.length < results.length\r\n ? String(offset + sliced.length)\r\n : undefined;\r\n\r\n return toPaginatedListResult(sliced, nextCursor);\r\n } catch (err) {\r\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\r\n return toPaginatedListResult([]);\r\n }\r\n throw err;\r\n }\r\n },\r\n };\r\n}\r\n","import {\r\n type StorageAdapter,\r\n type StorageFileEntry,\r\n type StorageListOptions,\r\n type StoragePaginatedListResult,\r\n type StorageUploadParams,\r\n type StorageUploadResult,\r\n toPaginatedListResult,\r\n validateStorageMetadata,\r\n} from './types';\r\n\r\nexport interface S3PutObjectInput {\r\n Bucket: string;\r\n Key: string;\r\n Body: Buffer | Uint8Array;\r\n ContentType: string;\r\n CacheControl?: string;\r\n Metadata?: Record<string, string>;\r\n IfNoneMatch?: string;\r\n}\r\n\r\nexport interface S3DeleteObjectInput {\r\n Bucket: string;\r\n Key: string;\r\n}\r\n\r\nexport interface S3HeadObjectInput {\r\n Bucket: string;\r\n Key: string;\r\n}\r\n\r\nexport interface S3ListObjectsInput {\r\n Bucket: string;\r\n Prefix?: string;\r\n MaxKeys?: number;\r\n ContinuationToken?: string;\r\n}\r\n\r\nexport interface S3ListedObject {\r\n Key?: string;\r\n Size?: number;\r\n LastModified?: Date | string;\r\n}\r\n\r\nexport interface S3ListObjectsOutput {\r\n Contents?: S3ListedObject[];\r\n NextContinuationToken?: string;\r\n IsTruncated?: boolean;\r\n}\r\n\r\n/** Structural S3 / Cloudflare R2 / MinIO transport with zero runtime SDK dependency. */\r\nexport interface S3TransportLike {\r\n putObject(input: S3PutObjectInput): Promise<unknown>;\r\n deleteObject(input: S3DeleteObjectInput): Promise<unknown>;\r\n headObject?(input: S3HeadObjectInput): Promise<unknown>;\r\n listObjects?(input: S3ListObjectsInput): Promise<S3ListObjectsOutput>;\r\n listObjectsV2?(input: S3ListObjectsInput): Promise<S3ListObjectsOutput>;\r\n}\r\n\r\nexport type S3PublicUrlBuilder = (bucket: string, path: string) => string;\r\n\r\nexport interface S3StorageBaseOptions {\r\n /** Public base URL prefix (e.g. `'https://cdn.example.com'`) or `(bucket, path) => string` builder. */\r\n publicBaseUrl: string | S3PublicUrlBuilder;\r\n /** Default `Cache-Control` header applied when `StorageUploadParams.cacheControl` is omitted. */\r\n cacheControl?: string;\r\n /** Include `bucket` segment in string-prefix URLs (`${publicBaseUrl}/${bucket}/${path}`). Defaults to `true`. */\r\n includeBucketInUrl?: boolean;\r\n /** Fallback bucket used by `deleteByUrl` when `includeBucketInUrl` is `false`. */\r\n defaultBucket?: string;\r\n}\r\n\r\nexport type S3StorageAdapterOptions = S3StorageBaseOptions &\r\n (\r\n | (S3TransportLike & { client?: never; transport?: never })\r\n | { client: S3TransportLike; transport?: never; putObject?: never }\r\n | { transport: S3TransportLike; client?: never; putObject?: never }\r\n );\r\n\r\nfunction normalizeS3Key(rawPath: string): string {\r\n const normalized = rawPath.replace(/\\\\/g, '/').replace(/^\\/+/, '');\r\n if (!normalized) throw new Error('Invalid S3 object key');\r\n return normalized;\r\n}\r\n\r\nfunction resolveTransport(options: S3StorageAdapterOptions): S3TransportLike {\r\n if ('client' in options && options.client) return options.client;\r\n if ('transport' in options && options.transport) return options.transport;\r\n if ('putObject' in options && typeof options.putObject === 'function') return options;\r\n throw new Error('S3StorageAdapter requires putObject/deleteObject or client/transport');\r\n}\r\n\r\nfunction isS3PreconditionError(err: unknown): boolean {\r\n if (!err || typeof err !== 'object') return false;\r\n const e = err as {\r\n code?: unknown;\r\n name?: unknown;\r\n statusCode?: unknown;\r\n $metadata?: { httpStatusCode?: number };\r\n };\r\n const status = e.$metadata?.httpStatusCode ?? Number(e.statusCode);\r\n const code = String(e.code ?? e.name ?? '').toLowerCase();\r\n return status === 412 || code === 'preconditionfailed' || code === 'eexist';\r\n}\r\n\r\n/** Zero-dependency S3 / Cloudflare R2 / MinIO `StorageAdapter`. */\r\nexport function createS3StorageAdapter(options: S3StorageAdapterOptions): StorageAdapter {\r\n const transport = resolveTransport(options);\r\n const includeBucket = options.includeBucketInUrl !== false;\r\n\r\n const getPublicUrl = (bucket: string, path: string): string => {\r\n const key = normalizeS3Key(path);\r\n if (typeof options.publicBaseUrl === 'function') return options.publicBaseUrl(bucket, key);\r\n const base = options.publicBaseUrl.replace(/\\/+$/, '');\r\n return includeBucket ? `${base}/${bucket}/${key}` : `${base}/${key}`;\r\n };\r\n\r\n return {\r\n async upload(params: StorageUploadParams): Promise<StorageUploadResult> {\r\n const key = normalizeS3Key(params.path);\r\n validateStorageMetadata(params.metadata);\r\n\r\n const isImmutable =\r\n params.upsert === false && params.contentType.includes('image/avif');\r\n const cacheControl =\r\n params.cacheControl ??\r\n options.cacheControl ??\r\n (isImmutable\r\n ? 'public, max-age=31536000, immutable'\r\n : 'public, max-age=2592000, must-revalidate');\r\n\r\n const metadata =\r\n params.metadata || params.contentHash\r\n ? {\r\n ...params.metadata,\r\n ...(params.contentHash ? { contentHash: params.contentHash } : {}),\r\n }\r\n : undefined;\r\n\r\n try {\r\n await transport.putObject({\r\n Bucket: params.bucket,\r\n Key: key,\r\n Body: params.data,\r\n ContentType: params.contentType,\r\n ...(cacheControl ? { CacheControl: cacheControl } : {}),\r\n ...(metadata ? { Metadata: metadata } : {}),\r\n ...(params.upsert === false ? { IfNoneMatch: '*' } : {}),\r\n });\r\n } catch (err) {\r\n if (isS3PreconditionError(err)) {\r\n const existsErr = new Error(\r\n `Object already exists at ${params.bucket}/${key}`,\r\n ) as Error & { code?: string; statusCode?: number };\r\n existsErr.code = 'EEXIST';\r\n existsErr.statusCode = 412;\r\n throw existsErr;\r\n }\r\n throw err;\r\n }\r\n\r\n return {\r\n bucket: params.bucket,\r\n path: key,\r\n url: getPublicUrl(params.bucket, key),\r\n };\r\n },\r\n\r\n async delete(bucket: string, path: string): Promise<void> {\r\n await transport.deleteObject({ Bucket: bucket, Key: normalizeS3Key(path) });\r\n },\r\n\r\n async exists(bucket: string, path: string): Promise<boolean> {\r\n const key = normalizeS3Key(path);\r\n if (transport.headObject) {\r\n try {\r\n await transport.headObject({ Bucket: bucket, Key: key });\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n }\r\n const lastSlash = key.lastIndexOf('/');\r\n const folder = lastSlash > 0 ? key.slice(0, lastSlash) : undefined;\r\n const targetName = lastSlash > 0 ? key.slice(lastSlash + 1) : key;\r\n const listed = await this.list!(bucket, folder);\r\n return listed.some((item) => item.name === targetName);\r\n },\r\n\r\n getPublicUrl,\r\n\r\n async list(\r\n bucket: string,\r\n folder?: string,\r\n listOptions?: StorageListOptions,\r\n ): Promise<StoragePaginatedListResult> {\r\n const cleanFolder = folder ? normalizeS3Key(folder).replace(/\\/+$/, '') : '';\r\n const prefix = cleanFolder ? `${cleanFolder}/` : undefined;\r\n const input: S3ListObjectsInput = {\r\n Bucket: bucket,\r\n ...(prefix ? { Prefix: prefix } : {}),\r\n MaxKeys: listOptions?.limit ?? 1000,\r\n ...(listOptions?.cursor ? { ContinuationToken: listOptions.cursor } : {}),\r\n };\r\n\r\n const output = transport.listObjects\r\n ? await transport.listObjects(input)\r\n : transport.listObjectsV2\r\n ? await transport.listObjectsV2(input)\r\n : undefined;\r\n\r\n const entries: StorageFileEntry[] = (output?.Contents ?? [])\r\n .filter((item): item is S3ListedObject & { Key: string } => Boolean(item.Key))\r\n .map((item) => {\r\n const name =\r\n prefix && item.Key.startsWith(prefix)\r\n ? item.Key.slice(prefix.length)\r\n : item.Key;\r\n const createdAt =\r\n item.LastModified instanceof Date\r\n ? item.LastModified.toISOString()\r\n : item.LastModified;\r\n return {\r\n name,\r\n id: `${bucket}/${item.Key}`,\r\n ...(createdAt !== undefined ? { createdAt } : {}),\r\n ...(item.Size !== undefined ? { size: item.Size } : {}),\r\n };\r\n })\r\n .filter((entry) => entry.name.length > 0);\r\n\r\n return toPaginatedListResult(entries, output?.NextContinuationToken);\r\n },\r\n\r\n parseUrl(url: string): { bucket?: string; path: string } | null {\r\n if (typeof options.publicBaseUrl !== 'string' || !url) return null;\r\n const prefix = `${options.publicBaseUrl.replace(/\\/+$/, '')}/`;\r\n const cleanUrl = url.split(/[?#]/)[0];\r\n if (!cleanUrl.startsWith(prefix)) return null;\r\n\r\n try {\r\n const rest = decodeURIComponent(cleanUrl.slice(prefix.length)).replace(/^\\/+/, '');\r\n if (!rest) return null;\r\n\r\n if (includeBucket) {\r\n const slashIdx = rest.indexOf('/');\r\n if (slashIdx <= 0 || slashIdx === rest.length - 1) return null;\r\n return {\r\n bucket: rest.slice(0, slashIdx),\r\n path: rest.slice(slashIdx + 1),\r\n };\r\n }\r\n\r\n if (!options.defaultBucket) return null;\r\n return { bucket: options.defaultBucket, path: rest };\r\n } catch {\r\n return null;\r\n }\r\n },\r\n\r\n async deleteByUrl(url: string): Promise<boolean> {\r\n const parsed = this.parseUrl?.(url);\r\n if (!parsed || !parsed.bucket) return false;\r\n\r\n try {\r\n await transport.deleteObject({\r\n Bucket: parsed.bucket,\r\n Key: parsed.path,\r\n });\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n },\r\n };\r\n}\r\n","import {\r\n type StorageAdapter,\r\n type StorageListOptions,\r\n type StoragePaginatedListResult,\r\n type StorageUploadParams,\r\n type StorageUploadResult,\r\n toPaginatedListResult,\r\n validateStorageMetadata,\r\n} from './types';\r\nimport {\r\n DEFAULT_PUBLIC_STORAGE_PREFIX,\r\n parsePublicStorageUrl,\r\n} from './urlParser';\r\n\r\n/**\r\n * Minimal structural interface matching `@supabase/supabase-js` Storage client,\r\n * avoiding a mandatory `@supabase/supabase-js` runtime dependency.\r\n */\r\nexport interface SupabaseBucketClientLike {\r\n upload(\r\n path: string,\r\n fileBody: Buffer | Uint8Array,\r\n fileOptions?: {\r\n contentType?: string;\r\n upsert?: boolean;\r\n cacheControl?: string;\r\n metadata?: Record<string, string>;\r\n },\r\n ): Promise<{\r\n data: { path: string } | null;\r\n error: { message: string } | null;\r\n }>;\r\n remove(paths: string[]): Promise<{\r\n data?: unknown;\r\n error: { message: string } | null;\r\n }>;\r\n getPublicUrl(path: string): {\r\n data: { publicUrl: string };\r\n };\r\n list?(\r\n path?: string,\r\n options?: {\r\n limit?: number;\r\n offset?: number;\r\n sortBy?: { column: string; order: string };\r\n },\r\n ): Promise<{\r\n data: Array<{\r\n name: string;\r\n id: string | null;\r\n created_at: string | null;\r\n }> | null;\r\n error: { message: string } | null;\r\n }>;\r\n}\r\n\r\nexport interface SupabaseStorageClientLike {\r\n storage: {\r\n from(bucket: string): SupabaseBucketClientLike;\r\n };\r\n}\r\n\r\nexport interface SupabaseStorageAdapterOptions {\r\n publicUrlPrefix?: string;\r\n}\r\n\r\n/**\r\n * Plug in any `@supabase/supabase-js` client instance (e.g., service-role client)\r\n * as a `StorageAdapter` with zero extra wrapper boilerplate.\r\n */\r\nexport function createSupabaseStorageAdapter(\r\n client: SupabaseStorageClientLike,\r\n options: SupabaseStorageAdapterOptions = {},\r\n): StorageAdapter {\r\n const publicPrefix =\r\n options.publicUrlPrefix || DEFAULT_PUBLIC_STORAGE_PREFIX;\r\n\r\n return {\r\n async upload(params: StorageUploadParams): Promise<StorageUploadResult> {\r\n validateStorageMetadata(params.metadata);\r\n\r\n const isImmutable =\r\n params.upsert === false && params.contentType.includes('image/avif');\r\n const cacheControl =\r\n params.cacheControl ||\r\n (isImmutable\r\n ? 'public, max-age=31536000, immutable'\r\n : 'public, max-age=2592000, must-revalidate');\r\n\r\n const { data, error } = await client.storage\r\n .from(params.bucket)\r\n .upload(params.path, params.data, {\r\n contentType: params.contentType,\r\n upsert: params.upsert === undefined ? true : params.upsert,\r\n cacheControl,\r\n metadata: {\r\n ...params.metadata,\r\n uploadedAt: new Date().toISOString(),\r\n contentHash: params.contentHash || 'unknown',\r\n },\r\n });\r\n\r\n if (error || !data) {\r\n throw new Error(\r\n `Failed to upload to Supabase storage (bucket: ${params.bucket}, path: ${params.path}): ${error?.message || 'Unknown error'}`,\r\n );\r\n }\r\n\r\n const {\r\n data: { publicUrl },\r\n } = client.storage.from(params.bucket).getPublicUrl(data.path);\r\n\r\n return {\r\n bucket: params.bucket,\r\n path: data.path,\r\n url: publicUrl,\r\n };\r\n },\r\n\r\n async delete(bucket: string, path: string): Promise<void> {\r\n const { error } = await client.storage.from(bucket).remove([path]);\r\n if (error) {\r\n throw new Error(`Failed to delete from Supabase storage: ${error.message}`);\r\n }\r\n },\r\n\r\n async exists(bucket: string, path: string): Promise<boolean> {\r\n const cleanPath = path.replace(/^\\/+/, '');\r\n const lastSlash = cleanPath.lastIndexOf('/');\r\n const folder = lastSlash > 0 ? cleanPath.slice(0, lastSlash) : undefined;\r\n const targetName = lastSlash > 0 ? cleanPath.slice(lastSlash + 1) : cleanPath;\r\n const entries = await this.list!(bucket, folder);\r\n return entries.some((entry) => entry.name === targetName);\r\n },\r\n\r\n getPublicUrl(bucket: string, path: string): string {\r\n const {\r\n data: { publicUrl },\r\n } = client.storage.from(bucket).getPublicUrl(path);\r\n return publicUrl;\r\n },\r\n\r\n async list(\r\n bucket: string,\r\n folder?: string,\r\n listOptions?: StorageListOptions,\r\n ): Promise<StoragePaginatedListResult> {\r\n const bucketClient = client.storage.from(bucket);\r\n if (!bucketClient.list) {\r\n return toPaginatedListResult([]);\r\n }\r\n\r\n const limit = listOptions?.limit ?? 1000;\r\n const offset = listOptions?.cursor ? Number(listOptions.cursor) || 0 : 0;\r\n\r\n const { data, error } = await bucketClient.list(folder, {\r\n limit,\r\n ...(offset > 0 ? { offset } : {}),\r\n sortBy: { column: 'created_at', order: 'desc' },\r\n });\r\n\r\n if (error) {\r\n throw new Error(`Failed to list Supabase bucket files: ${error.message}`);\r\n }\r\n\r\n const entries = (data || [])\r\n .filter((f) => f.id !== null)\r\n .map((f) => ({\r\n name: f.name,\r\n id: f.id ?? undefined,\r\n createdAt: f.created_at ?? undefined,\r\n }));\r\n\r\n const nextCursor =\r\n listOptions?.limit !== undefined && entries.length >= limit\r\n ? String(offset + entries.length)\r\n : undefined;\r\n\r\n return toPaginatedListResult(entries, nextCursor);\r\n },\r\n\r\n parseUrl(url: string): { bucket?: string; path: string } | null {\r\n const info = parsePublicStorageUrl(url, publicPrefix);\r\n if (!info) return null;\r\n return { bucket: info.bucket, path: info.path };\r\n },\r\n\r\n async deleteByUrl(url: string): Promise<boolean> {\r\n const info = parsePublicStorageUrl(url, publicPrefix);\r\n if (!info) return false;\r\n\r\n try {\r\n const { error } = await client.storage\r\n .from(info.bucket)\r\n .remove([info.path]);\r\n return !error;\r\n } catch {\r\n return false;\r\n }\r\n },\r\n };\r\n}\r\n"],"mappings":"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","names":["path","path"]}
|
|
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"]}
|
|
@@ -1,112 +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
|
-
}
|
|
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
|
+
}
|