img-upload-kit 1.1.1 → 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 (111) hide show
  1. package/CHANGELOG.md +56 -41
  2. package/MIGRATION.md +192 -0
  3. package/README.md +138 -154
  4. package/dist/bin.js +1186 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/blur.cjs +40 -412
  7. package/dist/blur.cjs.map +1 -1
  8. package/dist/blur.d.cts +5 -5
  9. package/dist/blur.d.ts +5 -5
  10. package/dist/blur.js +39 -393
  11. package/dist/blur.js.map +1 -1
  12. package/dist/blurDataUtils-DveBRtqE.d.cts +43 -0
  13. package/dist/blurDataUtils-yx9Q6HlU.d.ts +43 -0
  14. package/dist/blurService-D5TpMNji.d.ts +83 -0
  15. package/dist/blurService-DacwylJx.d.cts +83 -0
  16. package/dist/chunk-2OVNXHTN.cjs +514 -0
  17. package/dist/chunk-2OVNXHTN.cjs.map +1 -0
  18. package/dist/chunk-3ICG2SLL.cjs +135 -0
  19. package/dist/chunk-3ICG2SLL.cjs.map +1 -0
  20. package/dist/chunk-4ASFG3KB.js +514 -0
  21. package/dist/chunk-4ASFG3KB.js.map +1 -0
  22. package/dist/chunk-AI3JJLSA.js +2370 -0
  23. package/dist/chunk-AI3JJLSA.js.map +1 -0
  24. package/dist/chunk-CV23O4YU.js +145 -0
  25. package/dist/chunk-CV23O4YU.js.map +1 -0
  26. package/dist/chunk-G7LLSKBV.js +158 -0
  27. package/dist/chunk-G7LLSKBV.js.map +1 -0
  28. package/dist/chunk-IB3TEDLR.js +294 -0
  29. package/dist/chunk-IB3TEDLR.js.map +1 -0
  30. package/dist/chunk-KJVYWKIG.cjs +158 -0
  31. package/dist/chunk-KJVYWKIG.cjs.map +1 -0
  32. package/dist/chunk-NEDBIIE3.cjs +226 -0
  33. package/dist/chunk-NEDBIIE3.cjs.map +1 -0
  34. package/dist/chunk-QHU7FUO4.cjs +37 -0
  35. package/dist/chunk-QHU7FUO4.cjs.map +1 -0
  36. package/dist/chunk-QQHZJKNA.js +135 -0
  37. package/dist/chunk-QQHZJKNA.js.map +1 -0
  38. package/dist/chunk-S2IFAWJK.js +37 -0
  39. package/dist/chunk-S2IFAWJK.js.map +1 -0
  40. package/dist/chunk-TT5C7K3Q.js +226 -0
  41. package/dist/chunk-TT5C7K3Q.js.map +1 -0
  42. package/dist/chunk-U2TWTEBY.cjs +2370 -0
  43. package/dist/chunk-U2TWTEBY.cjs.map +1 -0
  44. package/dist/chunk-WISG74VL.cjs +294 -0
  45. package/dist/chunk-WISG74VL.cjs.map +1 -0
  46. package/dist/chunk-YRXXQUDE.cjs +145 -0
  47. package/dist/chunk-YRXXQUDE.cjs.map +1 -0
  48. package/dist/cli.cjs +273 -76
  49. package/dist/cli.cjs.map +1 -1
  50. package/dist/cli.js +269 -73
  51. package/dist/cli.js.map +1 -1
  52. package/dist/headless.cjs +8 -1880
  53. package/dist/headless.cjs.map +1 -1
  54. package/dist/headless.d.cts +3 -1
  55. package/dist/headless.d.ts +3 -1
  56. package/dist/headless.js +25 -1886
  57. package/dist/headless.js.map +1 -1
  58. package/dist/index-BIdnqtW8.d.ts +234 -0
  59. package/dist/index-Brk_uHs6.d.cts +234 -0
  60. package/dist/{index-FVXXKf2v.d.ts → index-CPMlaQsC.d.ts} +76 -153
  61. package/dist/{index-FVXXKf2v.d.cts → index-DHINe-rA.d.cts} +76 -153
  62. package/dist/index.cjs +64 -532
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +7 -4
  65. package/dist/index.d.ts +7 -4
  66. package/dist/index.js +63 -504
  67. package/dist/index.js.map +1 -1
  68. package/dist/{memoryAdapter-CBu7_3Pp.d.ts → memoryAdapter-Ck0ccZbF.d.cts} +3 -2
  69. package/dist/{memoryAdapter-BJjyLR-1.d.cts → memoryAdapter-DxEKaG0b.d.ts} +3 -2
  70. package/dist/placeholders-C4ftwaII.d.ts +5 -0
  71. package/dist/placeholders-x_3rt65M.d.cts +5 -0
  72. package/dist/react.cjs +1952 -3257
  73. package/dist/react.cjs.map +1 -1
  74. package/dist/react.d.cts +188 -50
  75. package/dist/react.d.ts +188 -50
  76. package/dist/react.js +1763 -3013
  77. package/dist/react.js.map +1 -1
  78. package/dist/server-handler.cjs +14 -0
  79. package/dist/server-handler.cjs.map +1 -0
  80. package/dist/server-handler.d.cts +3 -0
  81. package/dist/server-handler.d.ts +3 -0
  82. package/dist/server-handler.js +14 -0
  83. package/dist/server-handler.js.map +1 -0
  84. package/dist/server.cjs +1096 -532
  85. package/dist/server.cjs.map +1 -1
  86. package/dist/server.d.cts +77 -154
  87. package/dist/server.d.ts +77 -154
  88. package/dist/server.js +1050 -448
  89. package/dist/server.js.map +1 -1
  90. package/dist/storage.cjs +239 -232
  91. package/dist/storage.cjs.map +1 -1
  92. package/dist/storage.d.cts +21 -5
  93. package/dist/storage.d.ts +21 -5
  94. package/dist/storage.js +227 -207
  95. package/dist/storage.js.map +1 -1
  96. package/dist/styles/animated-image.css +112 -0
  97. package/dist/styles/dropzone-and-tiles.css +247 -0
  98. package/dist/styles/modals-and-dock.css +248 -0
  99. package/dist/styles/primitives.css +197 -0
  100. package/dist/styles/quality-and-inputs.css +184 -0
  101. package/dist/styles/tokens.css +123 -0
  102. package/dist/styles.css +39 -24
  103. package/dist/types-BrP-o9nd.d.cts +85 -0
  104. package/dist/types-sZ3KoFQY.d.ts +85 -0
  105. package/dist/types-yShGNQnW.d.cts +61 -0
  106. package/dist/types-yShGNQnW.d.ts +61 -0
  107. package/dist/{types-CiLY0DwB.d.cts → upload-C5Trd6Dh.d.cts} +27 -53
  108. package/dist/{types-CiLY0DwB.d.ts → upload-C5Trd6Dh.d.ts} +27 -53
  109. package/package.json +92 -32
  110. package/dist/blurService-DqsnfPTd.d.cts +0 -172
  111. package/dist/blurService-DqsnfPTd.d.ts +0 -172
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/image-upload-kit/image-upload-kit/dist/chunk-WISG74VL.cjs","../src/blur/lruCache.ts","../src/blur/pathMapper.ts","../src/blur/placeholders.ts","../src/blur/blurService.ts"],"names":[],"mappings":"AAAA;ACAO,IAAM,SAAA,EAAN,MAAqB;AAAA,EACT;AAAA,EACA;AAAA,EAEjB,WAAA,CAAY,QAAA,EAAkB,GAAA,EAAK;AACjC,IAAA,IAAA,CAAK,QAAA,EAAU,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,OAAO,CAAA;AAClC,IAAA,IAAA,CAAK,MAAA,kBAAQ,IAAI,GAAA,CAAI,CAAA;AAAA,EACvB;AAAA,EAEA,GAAA,CAAI,GAAA,EAAuB;AACzB,IAAA,GAAA,CAAI,CAAC,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA,EAAG,OAAO,KAAA,CAAA;AAEjC,IAAA,MAAM,MAAA,EAAQ,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAChC,IAAA,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,GAAG,CAAA;AACrB,IAAA,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAA,EAAK,KAAK,CAAA;AACzB,IAAA,OAAO,KAAA;AAAA,EACT;AAAA,EAEA,GAAA,CAAI,GAAA,EAAQ,KAAA,EAAgB;AAC1B,IAAA,GAAA,CAAI,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA,EAAG;AACvB,MAAA,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,GAAG,CAAA;AAAA,IACvB;AAEA,IAAA,GAAA,CAAI,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,IAAA,CAAK,OAAA,EAAS;AACnC,MAAA,MAAM,SAAA,EAAW,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,CAAA,CAAE,IAAA,CAAK,CAAA,CAAE,KAAA;AAC1C,MAAA,GAAA,CAAI,SAAA,IAAa,KAAA,CAAA,EAAW;AAC1B,QAAA,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,QAAQ,CAAA;AAAA,MAC5B;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAA,EAAK,KAAK,CAAA;AAAA,EAC3B;AAAA,EAEA,GAAA,CAAI,GAAA,EAAiB;AACnB,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAAA,EAC3B;AAAA,EAEA,KAAA,CAAA,EAAc;AACZ,IAAA,IAAA,CAAK,KAAA,CAAM,KAAA,CAAM,CAAA;AAAA,EACnB;AAAA,EAEA,IAAI,IAAA,CAAA,EAAe;AACjB,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,IAAA;AAAA,EACpB;AACF,CAAA;ADPA;AACA;AEtCO,IAAM,qBAAA,EAAiC;AAAA,EAC5C,8EAAA;AAAA,EACA,mCAAA;AAAA,EACA,0CAAA;AAAA,EACA,2CAAA;AAAA,EACA;AACF,CAAA;AAqBO,SAAS,kBAAA,CACd,QAAA,EACA,QAAA,EAAgC,CAAC,CAAA,EACvB;AACV,EAAA,MAAM,aAAA,EAAe,QAAA;AACrB,EAAA,IAAI,eAAA,EAAiB,QAAA;AACrB,EAAA,IAAI,WAAA,EAAa,KAAA;AAEjB,EAAA,IAAI;AACF,IAAA,GAAA,CAAI,QAAA,CAAS,UAAA,CAAW,OAAO,EAAA,GAAK,QAAA,CAAS,UAAA,CAAW,OAAO,CAAA,EAAG;AAChE,MAAA,OAAO;AAAA,QACL,cAAA,EAAgB,QAAA;AAAA,QAChB,UAAA,EAAY,KAAA;AAAA,QACZ;AAAA,MACF,CAAA;AAAA,IACF;AAEA,IAAA,MAAM,SAAA,EAAW,OAAA,CAAQ,YAAA,GAAe,oBAAA;AAExC,IAAA,GAAA,CAAI,QAAA,CAAS,UAAA,CAAW,SAAS,EAAA,GAAK,QAAA,CAAS,UAAA,CAAW,UAAU,CAAA,EAAG;AACrE,MAAA,MAAM,IAAA,EAAM,IAAI,GAAA,CAAI,QAAQ,CAAA;AAC5B,MAAA,WAAA,EAAa,IAAA;AACb,MAAA,MAAM,gBAAA,EAAkB,CAAA,EAAA;AAEF,MAAA;AACN,QAAA;AACH,QAAA;AACY,UAAA;AACJ,UAAA;AACA,UAAA;AACjB,UAAA;AACF,QAAA;AACF,MAAA;AAEuB,MAAA;AACA,QAAA;AACvB,MAAA;AACK,IAAA;AACY,MAAA;AACnB,IAAA;AAEiB,IAAA;AAEG,IAAA;AACK,MAAA;AACzB,IAAA;AAEwB,IAAA;AACH,IAAA;AACA,MAAA;AACM,QAAA;AACzB,MAAA;AACF,IAAA;AAEiB,IAAA;AACS,IAAA;AAEf,IAAA;AACe,IAAA;AAExB,IAAA;AAEK,IAAA;AACL,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACM,EAAA;AACC,IAAA;AACW,MAAA;AACJ,MAAA;AACZ,MAAA;AACF,IAAA;AACF,EAAA;AACF;AFK+B;AACA;AG5G0C;AAErE,EAAA;AAEA,EAAA;AAEA,EAAA;AAEA,EAAA;AACJ;AH0G+B;AACA;AIpGN;AACf,EAAA;AACS,EAAA;AACA,EAAA;AACT,EAAA;AAKA,EAAA;AAEsC,EAAA;AAC3B,IAAA;AACU,IAAA;AACX,IAAA;AACL,MAAA;AACI,MAAA;AACA,MAAA;AACf,IAAA;AAC0B,IAAA;AACX,IAAA;AACE,MAAA;AACJ,MAAA;AACE,MAAA;AACL,MAAA;AACV,IAAA;AAEsB,IAAA;AACF,MAAA;AACpB,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAOQ,EAAA;AACW,IAAA;AAED,IAAA;AACA,MAAA;AACO,MAAA;AACL,MAAA;AACC,QAAA;AACI,QAAA;AACC,QAAA;AACtB,MAAA;AACK,IAAA;AACiB,MAAA;AACN,MAAA;AACL,QAAA;AACI,QAAA;AACO,QAAA;AACtB,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKiC,EAAA;AACJ,IAAA;AACb,IAAA;AACG,IAAA;AACnB,EAAA;AAEkD,EAAA;AACzC,IAAA;AACT,EAAA;AAEuE,EAAA;AAC5C,IAAA;AACR,IAAA;AAEA,IAAA;AACI,IAAA;AACJ,IAAA;AAEU,IAAA;AAGN,IAAA;AAEA,IAAA;AACJ,IAAA;AAEK,IAAA;AACG,IAAA;AACD,MAAA;AAEN,MAAA;AACC,MAAA;AACnB,IAAA;AAE2B,IAAA;AACJ,MAAA;AACJ,MAAA;AACnB,IAAA;AAEO,IAAA;AACT,EAAA;AAE2C,EAAA;AACvB,IAAA;AACI,IAAA;AACxB,EAAA;AAIE,EAAA;AAK0B,IAAA;AAEb,IAAA;AAEO,IAAA;AACO,IAAA;AACZ,MAAA;AACS,MAAA;AACxB,IAAA;AAEa,IAAA;AAEQ,IAAA;AACP,MAAA;AACd,IAAA;AAEkB,IAAA;AACP,IAAA;AACG,MAAA;AACd,IAAA;AAGO,IAAA;AAIkB,IAAA;AACd,MAAA;AACW,MAAA;AACrB,IAAA;AAEM,IAAA;AACT,EAAA;AAIE,EAAA;AAKwC,IAAA;AACjB,IAAA;AACG,MAAA;AAC1B,IAAA;AACO,IAAA;AACT,EAAA;AAEqC,EAAA;AAC5B,IAAA;AACO,MAAA;AACD,MAAA;AACG,MAAA;AAChB,IAAA;AACF,EAAA;AAEuC,EAAA;AACjB,IAAA;AACtB,EAAA;AAEc,EAAA;AAEL,IAAA;AAOA,IAAA;AACG,MAAA;AACc,MAAA;AACb,MAAA;AACC,QAAA;AACS,QAAA;AACnB,MAAA;AACF,IAAA;AACF,EAAA;AAEmB,EAAA;AACA,IAAA;AACnB,EAAA;AAE6C,EAAA;AACzB,IAAA;AACG,IAAA;AACvB,EAAA;AAEqC,EAAA;AACZ,IAAA;AACD,MAAA;AACtB,IAAA;AACF,EAAA;AAGE,EAAA;AAGuB,IAAA;AACzB,EAAA;AAE6B,EAAA;AACH,IAAA;AAC1B,EAAA;AAE+C,EAAA;AACpB,IAAA;AACD,IAAA;AACN,MAAA;AAClB,IAAA;AACF,EAAA;AACF;AAEkC;AACT,EAAA;AACzB;AAE+B;AJiCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/image-upload-kit/image-upload-kit/dist/chunk-WISG74VL.cjs","sourcesContent":[null,"export class LRUCache<K, V> {\n private readonly maxSize: number;\n private readonly cache: Map<K, V>;\n\n constructor(maxSize: number = 500) {\n this.maxSize = Math.max(1, maxSize);\n this.cache = new Map();\n }\n\n get(key: K): V | undefined {\n if (!this.cache.has(key)) return undefined;\n\n const value = this.cache.get(key)!;\n this.cache.delete(key);\n this.cache.set(key, value);\n return value;\n }\n\n set(key: K, value: V): void {\n if (this.cache.has(key)) {\n this.cache.delete(key);\n }\n\n if (this.cache.size >= this.maxSize) {\n const firstKey = this.cache.keys().next().value;\n if (firstKey !== undefined) {\n this.cache.delete(firstKey);\n }\n }\n\n this.cache.set(key, value);\n }\n\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n get size(): number {\n return this.cache.size;\n }\n}\n","export const DEFAULT_CDN_PATTERNS: RegExp[] = [\n /^https?:\\/\\/[^/]+\\.supabase\\.co\\/storage\\/v1\\/object\\/public\\/([^/]+)\\/(.+)$/,\n /^https?:\\/\\/[^/]+\\.r2\\.dev\\/(.+)$/,\n /^https?:\\/\\/[^/]+\\.amazonaws\\.com\\/(.+)$/,\n /^https?:\\/\\/[^/]+\\.cloudflare\\.com\\/(.+)$/,\n /^https?:\\/\\/cdn\\.[^/]+\\/(.+)$/,\n];\n\nexport interface NormalizePathOptions {\n cdnPatterns?: RegExp[];\n stripPrefixes?: string[];\n}\n\nexport interface PathInfo {\n normalizedPath: string;\n folder?: string;\n filename?: string;\n extension?: string;\n isExternal: boolean;\n originalPath: string;\n}\n\n/**\n * Project-agnostic image URL / path normalizer.\n * Strips query parameters, leading `/public/`, relative `./`, and common CDN prefixes\n * so manifest keys match consistently across local and CDN environments.\n */\nexport function normalizeImagePath(\n imageSrc: string,\n options: NormalizePathOptions = {},\n): PathInfo {\n const originalPath = imageSrc;\n let normalizedPath = imageSrc;\n let isExternal = false;\n\n try {\n if (imageSrc.startsWith('data:') || imageSrc.startsWith('blob:')) {\n return {\n normalizedPath: imageSrc,\n isExternal: false,\n originalPath,\n };\n }\n\n const patterns = options.cdnPatterns || DEFAULT_CDN_PATTERNS;\n\n if (imageSrc.startsWith('http://') || imageSrc.startsWith('https://')) {\n const url = new URL(imageSrc);\n isExternal = true;\n const cleanUrlNoQuery = `${url.origin}${url.pathname}`;\n\n for (const pattern of patterns) {\n const match = cleanUrlNoQuery.match(pattern);\n if (match) {\n const groups = match.slice(1).filter(Boolean);\n const pathPart = groups.join('/');\n normalizedPath = '/' + pathPart.replace(/^\\/+/, '');\n break;\n }\n }\n\n if (normalizedPath === imageSrc) {\n normalizedPath = url.pathname;\n }\n } else {\n normalizedPath = normalizedPath.split('?')[0].split('#')[0];\n }\n\n normalizedPath = normalizedPath.replace(/\\\\/g, '/').replace(/^\\.\\.?\\//g, '');\n\n if (!normalizedPath.startsWith('/')) {\n normalizedPath = '/' + normalizedPath;\n }\n\n const prefixesToStrip = options.stripPrefixes || ['/public/'];\n for (const prefix of prefixesToStrip) {\n if (normalizedPath.startsWith(prefix)) {\n normalizedPath = '/' + normalizedPath.slice(prefix.length).replace(/^\\/+/, '');\n }\n }\n\n const segments = normalizedPath.split('/').filter(Boolean);\n const filename = segments[segments.length - 1];\n const folder =\n segments.length > 1 ? segments.slice(0, -1).join('/') : undefined;\n const dotIdx = filename ? filename.lastIndexOf('.') : -1;\n const extension =\n filename && dotIdx > 0 ? filename.slice(dotIdx + 1).toLowerCase() : undefined;\n\n return {\n normalizedPath,\n folder,\n filename,\n extension,\n isExternal,\n originalPath,\n };\n } catch {\n return {\n normalizedPath: imageSrc,\n isExternal: false,\n originalPath,\n };\n }\n}\n","import type { BlurPlaceholderType } from './types';\n\nexport const DEFAULT_PLACEHOLDERS: Record<BlurPlaceholderType, string> = {\n default:\n 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTUwIiBoZWlnaHQ9IjE1MCIgdmlld0JveD0iMCAwIDE1MCAxNTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkaWVudCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3R5bGU9InN0b3AtY29sb3I6IzJhMzIzZDtzdG9wLW9wYWNpdHk6MSIvPjxzdG9wIG9mZnNldD0iNTAlIiBzdHlsZT0ic3RvcC1jb2xvcjojMjEyNzMwO3N0b3Atb3BhY2l0eToxIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdHlsZT0ic3RvcC1jb2xvcjojMTgxZDI0O3N0b3Atb3BhY2l0eToxIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3Qgd2lkdGg9IjE1MCIgaGVpZ2h0PSIxNTAiIGZpbGw9InVybCgjZ3JhZGllbnQpIi8+PC9zdmc+',\n hero:\n 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTkyMCIgaGVpZ2h0PSIxMDgwIiB2aWV3Qm94PSIwIDAgMTkyMCAxMDgwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iaGVyb0dyYWRpZW50IiB4MT0iMCUiIHkxPSIwJSIgeDI9IjEwMCUiIHkyPSIxMDAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdHlsZT0ic3RvcC1jb2xvcjojMmQzNzQ4O3N0b3Atb3BhY2l0eToxIi8+PHN0b3Agb2Zmc2V0PSIzMCUiIHN0eWxlPSJzdG9wLWNvbG9yOiMyMzJiMzg7c3RvcC1vcGFjaXR5OjEiLz48c3RvcCBvZmZzZXQ9IjcwJSIgc3R5bGU9InN0b3AtY29sb3I6IzFlMjUzMDtzdG9wLW9wYWNpdHk6MSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3R5bGU9InN0b3AtY29sb3I6IzE3MWQyNztzdG9wLW9wYWNpdHk6MSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHdpZHRoPSIxOTIwIiBoZWlnaHQ9IjEwODAiIGZpbGw9InVybCgjaGVyb0dyYWRpZW50KSIvPjwvc3ZnPg==',\n card:\n 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgdmlld0JveD0iMCAwIDQwMCAzMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJjYXJkR3JhZGllbnQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0eWxlPSJzdG9wLWNvbG9yOiMyYTMyM2Q7c3RvcC1vcGFjaXR5OjEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0eWxlPSJzdG9wLWNvbG9yOiMxYzIyMmI7c3RvcC1vcGFjaXR5OjEiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgZmlsbD0idXJsKCNjYXJkR3JhZGllbnQpIi8+PC9zdmc+',\n thumbnail:\n 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIGZpbGw9IiMyNDJiMzUiLz48L3N2Zz4=',\n};\n","import { LRUCache } from './lruCache';\nimport { normalizeImagePath, type NormalizePathOptions } from './pathMapper';\nimport { DEFAULT_PLACEHOLDERS } from './placeholders';\nimport type {\n BlurCacheEntry,\n BlurManifestData,\n BlurManifestEntry,\n BlurPlaceholderType,\n BlurServiceMetrics,\n BlurSize,\n} from './types';\n\nexport interface BlurServiceOptions {\n cacheSize?: number;\n manifest?: BlurManifestData | Record<string, BlurManifestEntry>;\n pathOptions?: NormalizePathOptions;\n}\n\nexport class BlurService {\n private blurData: Record<string, BlurManifestEntry>;\n private readonly cache: LRUCache<string, BlurCacheEntry>;\n private readonly pathOptions?: NormalizePathOptions;\n private metadata: {\n version: string;\n generatedAt: string;\n totalImages: number;\n };\n private metrics: BlurServiceMetrics;\n\n constructor(options: BlurServiceOptions = {}) {\n this.blurData = {};\n this.pathOptions = options.pathOptions;\n this.metadata = {\n version: '2.0',\n generatedAt: new Date().toISOString(),\n totalImages: 0,\n };\n this.cache = new LRUCache(options.cacheSize ?? 500);\n this.metrics = {\n totalRequests: 0,\n cacheHits: 0,\n cacheMisses: 0,\n errors: 0,\n };\n\n if (options.manifest) {\n this.loadManifest(options.manifest);\n }\n }\n\n /**\n * Hydrate the service with a generated `blur-hashes.json` manifest or hash map.\n */\n loadManifest(\n manifest: BlurManifestData | Record<string, BlurManifestEntry>,\n ): void {\n this.cache.clear();\n\n if ('hashes' in manifest && typeof manifest.hashes === 'object' && manifest.hashes !== null) {\n const typed = manifest as BlurManifestData;\n this.blurData = { ...typed.hashes };\n this.metadata = {\n version: typed.version || '2.0',\n generatedAt: typed.generatedAt || new Date().toISOString(),\n totalImages: Object.keys(this.blurData).length,\n };\n } else {\n this.blurData = { ...(manifest as Record<string, BlurManifestEntry>) };\n this.metadata = {\n version: '2.0',\n generatedAt: new Date().toISOString(),\n totalImages: Object.keys(this.blurData).length,\n };\n }\n }\n\n /**\n * Register or update a single image's blur data at runtime.\n */\n registerEntry(imagePath: string, entry: BlurManifestEntry): void {\n this.blurData[imagePath] = entry;\n this.metadata.totalImages = Object.keys(this.blurData).length;\n this.cache.clear();\n }\n\n getDefaultPlaceholder(type: BlurPlaceholderType = 'default'): string {\n return DEFAULT_PLACEHOLDERS[type];\n }\n\n private findBlurDataByPath(imageSrc: string): BlurManifestEntry | null {\n let data = this.blurData[imageSrc];\n if (data) return data;\n\n const pathInfo = normalizeImagePath(imageSrc, this.pathOptions);\n data = this.blurData[pathInfo.normalizedPath];\n if (data) return data;\n\n const withSlash = pathInfo.normalizedPath.startsWith('/')\n ? pathInfo.normalizedPath\n : '/' + pathInfo.normalizedPath;\n const withoutSlash = pathInfo.normalizedPath.replace(/^\\/+/, '');\n\n data = this.blurData[withSlash] || this.blurData[withoutSlash];\n if (data) return data;\n\n const firstSlashIdx = withoutSlash.indexOf('/');\n if (firstSlashIdx > 0 && firstSlashIdx < withoutSlash.length - 1) {\n const withoutBucket = withoutSlash.slice(firstSlashIdx + 1);\n data =\n this.blurData['/' + withoutBucket] || this.blurData[withoutBucket];\n if (data) return data;\n }\n\n if (pathInfo.isExternal && pathInfo.originalPath !== imageSrc) {\n data = this.blurData[pathInfo.originalPath];\n if (data) return data;\n }\n\n return null;\n }\n\n getDominantColor(imageSrc: string): string {\n const data = this.findBlurDataByPath(imageSrc);\n return data?.color || '#E5E7EB';\n }\n\n getBlurDataURL(\n imageSrc: string,\n options: {\n type?: BlurPlaceholderType;\n size?: BlurSize;\n } = {},\n ): string {\n const { type = 'default', size = 'small' } = options;\n\n this.metrics.totalRequests++;\n\n const cacheKey = `${imageSrc}-${size}-${type}`;\n if (this.cache.has(cacheKey)) {\n this.metrics.cacheHits++;\n return this.cache.get(cacheKey)!.dataUrl;\n }\n\n this.metrics.cacheMisses++;\n\n if (Object.keys(this.blurData).length === 0) {\n return this.getDefaultPlaceholder(type);\n }\n\n const data = this.findBlurDataByPath(imageSrc);\n if (!data) {\n return this.getDefaultPlaceholder(type);\n }\n\n const blurDataURL =\n data.variants?.[size] ||\n data.variants?.small ||\n this.getDefaultPlaceholder(type);\n\n this.cache.set(cacheKey, {\n dataUrl: blurDataURL,\n timestamp: Date.now(),\n });\n\n return blurDataURL;\n }\n\n getBulkBlurData(\n imageSrcs: string[],\n options: {\n type?: BlurPlaceholderType;\n size?: BlurSize;\n } = {},\n ): Record<string, string> {\n const result: Record<string, string> = {};\n for (const imageSrc of imageSrcs) {\n result[imageSrc] = this.getBlurDataURL(imageSrc, options);\n }\n return result;\n }\n\n getProgressiveData(imageSrc: string, size: BlurSize = 'small') {\n return {\n color: this.getDominantColor(imageSrc),\n blur: this.getBlurDataURL(imageSrc, { size }),\n hasBlur: this.hasBlurHash(imageSrc),\n };\n }\n\n hasBlurHash(imageSrc: string): boolean {\n return Boolean(this.findBlurDataByPath(imageSrc));\n }\n\n getMetadata() {\n const cacheHitRate =\n this.metrics.totalRequests > 0\n ? (\n (this.metrics.cacheHits / this.metrics.totalRequests) *\n 100\n ).toFixed(2)\n : '0.00';\n\n return {\n ...this.metadata,\n cacheSize: this.cache.size,\n metrics: {\n ...this.metrics,\n cacheHitRate: `${cacheHitRate}%`,\n },\n };\n }\n\n clearCache(): void {\n this.cache.clear();\n }\n\n getBlurHash(imageSrc: string): string | null {\n const data = this.findBlurDataByPath(imageSrc);\n return data?.hash || null;\n }\n\n preloadBlurData(imageSrcs: string[], size: BlurSize = 'small'): void {\n for (const imageSrc of imageSrcs) {\n this.getBlurDataURL(imageSrc, { size });\n }\n }\n\n async fetchBlurDataForImage(\n imageSrc: string,\n type: BlurPlaceholderType = 'default',\n ): Promise<string> {\n return Promise.resolve(this.getBlurDataURL(imageSrc, { type }));\n }\n\n getAvailableKeys(): string[] {\n return Object.keys(this.blurData);\n }\n\n findMatchingKeys(searchTerm: string): string[] {\n const lower = searchTerm.toLowerCase();\n return Object.keys(this.blurData).filter((key) =>\n key.toLowerCase().includes(lower),\n );\n }\n}\n\nexport function createBlurService(options?: BlurServiceOptions): BlurService {\n return new BlurService(options);\n}\n\nexport const blurService = new BlurService();\n"]}
@@ -0,0 +1,145 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/storage/types.ts
2
+ function toPaginatedListResult(entries, nextCursor) {
3
+ const result = entries;
4
+ Object.defineProperties(result, {
5
+ items: {
6
+ value: entries,
7
+ enumerable: false,
8
+ configurable: true
9
+ },
10
+ nextCursor: {
11
+ value: nextCursor,
12
+ enumerable: false,
13
+ configurable: true
14
+ }
15
+ });
16
+ return result;
17
+ }
18
+ var PRINTABLE_ASCII_REGEX = /^[\x20-\x7E]*$/;
19
+ var MAX_METADATA_BYTES = 2048;
20
+ function validateStorageMetadata(metadata) {
21
+ if (!metadata) return;
22
+ let totalBytes = 0;
23
+ for (const [key, value] of Object.entries(metadata)) {
24
+ if (!key || typeof value !== "string" || !PRINTABLE_ASCII_REGEX.test(key) || !PRINTABLE_ASCII_REGEX.test(value)) {
25
+ const err = new Error(
26
+ "Invalid storage metadata: keys and values must contain printable ASCII characters only"
27
+ );
28
+ err.status = 400;
29
+ throw err;
30
+ }
31
+ totalBytes += key.length + value.length;
32
+ if (totalBytes > MAX_METADATA_BYTES) {
33
+ const err = new Error(
34
+ `Storage metadata exceeds maximum size of ${MAX_METADATA_BYTES} bytes`
35
+ );
36
+ err.status = 400;
37
+ throw err;
38
+ }
39
+ }
40
+ }
41
+ function createStorageAdapter(adapter) {
42
+ return adapter;
43
+ }
44
+
45
+ // src/storage/memoryAdapter.ts
46
+ function createMemoryStorageAdapter(options = {}) {
47
+ const baseUrl = (options.baseUrl || "https://memory.local/uploads").replace(
48
+ /\/+$/,
49
+ ""
50
+ );
51
+ const store = /* @__PURE__ */ new Map();
52
+ const toKey = (bucket, path) => `${bucket}::${path.replace(/^\/+/, "")}`;
53
+ const getPublicUrl = (bucket, path) => {
54
+ const cleanPath = path.replace(/^\/+/, "");
55
+ return `${baseUrl}/${bucket}/${cleanPath}`;
56
+ };
57
+ return {
58
+ async upload(params) {
59
+ const cleanPath = params.path.replace(/^\/+/, "");
60
+ const key = toKey(params.bucket, cleanPath);
61
+ if (params.upsert === false && store.has(key)) {
62
+ const existsErr = new Error(
63
+ `Object already exists at ${params.bucket}/${cleanPath}`
64
+ );
65
+ existsErr.code = "EEXIST";
66
+ throw existsErr;
67
+ }
68
+ store.set(key, {
69
+ bucket: params.bucket,
70
+ path: cleanPath,
71
+ data: new Uint8Array(params.data),
72
+ contentType: params.contentType,
73
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
74
+ metadata: params.metadata
75
+ });
76
+ return {
77
+ bucket: params.bucket,
78
+ path: cleanPath,
79
+ url: getPublicUrl(params.bucket, cleanPath)
80
+ };
81
+ },
82
+ async delete(bucket, path) {
83
+ const key = toKey(bucket, path);
84
+ store.delete(key);
85
+ },
86
+ async exists(bucket, path) {
87
+ return store.has(toKey(bucket, path));
88
+ },
89
+ getPublicUrl,
90
+ async list(bucket, folder, listOptions) {
91
+ const cleanFolder = folder ? folder.replace(/^\/+|\/+$/g, "") : "";
92
+ const entries = [];
93
+ for (const obj of store.values()) {
94
+ if (obj.bucket !== bucket) continue;
95
+ if (cleanFolder && !obj.path.startsWith(`${cleanFolder}/`)) continue;
96
+ const name = cleanFolder ? obj.path.slice(cleanFolder.length + 1) : obj.path;
97
+ entries.push({
98
+ name,
99
+ id: `${obj.bucket}/${obj.path}`,
100
+ createdAt: obj.createdAt,
101
+ size: obj.data.byteLength
102
+ });
103
+ }
104
+ const offset = _optionalChain([listOptions, 'optionalAccess', _ => _.cursor]) ? Number(listOptions.cursor) || 0 : 0;
105
+ const limit = _nullishCoalesce(_optionalChain([listOptions, 'optionalAccess', _2 => _2.limit]), () => ( entries.length));
106
+ const sliced = _optionalChain([listOptions, 'optionalAccess', _3 => _3.limit]) !== void 0 || offset > 0 ? entries.slice(offset, offset + limit) : entries;
107
+ const nextCursor = _optionalChain([listOptions, 'optionalAccess', _4 => _4.limit]) !== void 0 && offset + sliced.length < entries.length ? String(offset + sliced.length) : void 0;
108
+ return toPaginatedListResult(sliced, nextCursor);
109
+ },
110
+ async deleteByUrl(url) {
111
+ const parsed = _optionalChain([this, 'access', _5 => _5.parseUrl, 'optionalCall', _6 => _6(url)]);
112
+ if (!parsed || !parsed.bucket) return false;
113
+ const key = toKey(parsed.bucket, parsed.path);
114
+ return store.delete(key);
115
+ },
116
+ parseUrl(url) {
117
+ const prefix = `${baseUrl}/`;
118
+ if (!url || !url.startsWith(prefix)) return null;
119
+ const cleanUrl = url.split(/[?#]/)[0];
120
+ const rest = cleanUrl.slice(prefix.length);
121
+ const slashIdx = rest.indexOf("/");
122
+ if (slashIdx <= 0 || slashIdx === rest.length - 1) return null;
123
+ const bucket = rest.slice(0, slashIdx);
124
+ const filePath = rest.slice(slashIdx + 1);
125
+ return { bucket, path: filePath };
126
+ },
127
+ getObject(bucket, path) {
128
+ return store.get(toKey(bucket, path));
129
+ },
130
+ clear() {
131
+ store.clear();
132
+ },
133
+ get size() {
134
+ return store.size;
135
+ }
136
+ };
137
+ }
138
+
139
+
140
+
141
+
142
+
143
+
144
+ exports.toPaginatedListResult = toPaginatedListResult; exports.validateStorageMetadata = validateStorageMetadata; exports.createStorageAdapter = createStorageAdapter; exports.createMemoryStorageAdapter = createMemoryStorageAdapter;
145
+ //# sourceMappingURL=chunk-YRXXQUDE.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/image-upload-kit/image-upload-kit/dist/chunk-YRXXQUDE.cjs","../src/storage/types.ts","../src/storage/memoryAdapter.ts"],"names":[],"mappings":"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","file":"/home/runner/work/image-upload-kit/image-upload-kit/dist/chunk-YRXXQUDE.cjs","sourcesContent":[null,"import type { ImageUploadRecord } from '../types';\n\nexport interface StorageUploadParams {\n bucket: string;\n path: string;\n data: Buffer | Uint8Array;\n contentType: string;\n upsert?: boolean;\n cacheControl?: string;\n contentHash?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface StorageUploadResult {\n bucket: string;\n path: string;\n url: string;\n}\n\nexport interface StorageFileEntry {\n name: string;\n id?: string;\n createdAt?: string;\n size?: number;\n}\n\nexport interface StorageListOptions {\n cursor?: string;\n limit?: number;\n}\n\nexport type StoragePaginatedListResult = StorageFileEntry[] & {\n items: StorageFileEntry[];\n nextCursor?: string;\n};\n\nexport type StorageUploadOptions = StorageUploadParams;\nexport type StorageFileMetadata = StorageFileEntry;\nexport type StorageListResult = StoragePaginatedListResult;\n\nexport interface StorageAdapter {\n /**\n * Upload optimized image bytes to your storage backend and return its public URL.\n */\n upload(params: StorageUploadParams): Promise<StorageUploadResult>;\n\n /**\n * Delete an image from your storage backend by bucket and relative path.\n */\n delete(bucket: string, path: string): Promise<void>;\n\n /**\n * Optional: Check whether an object exists at the given bucket and relative path.\n */\n exists?(bucket: string, path: string): Promise<boolean>;\n\n /**\n * Optional: Resolve a public URL for a bucket and path.\n */\n getPublicUrl?(bucket: string, path: string): string | Promise<string>;\n\n /**\n * Optional: List files inside a bucket/folder (supports optional cursor/limit pagination).\n */\n list?(\n bucket: string,\n folder?: string,\n options?: StorageListOptions,\n ): Promise<StoragePaginatedListResult>;\n\n /**\n * Optional: Delete an asset directly from its public URL.\n */\n deleteByUrl?(url: string): Promise<boolean>;\n\n /**\n * Optional: Parse a public storage URL into its bucket and relative path.\n */\n parseUrl?(url: string): { bucket?: string; path: string } | null;\n}\n\nexport type UploadRecordHook = (\n record: ImageUploadRecord,\n) => void | Promise<void>;\n\n/**\n * Attaches non-enumerable `.items` and `.nextCursor` properties to a `StorageFileEntry[]`\n * array so both array callers (`entries.map(...)`) and paginated destructuring callers\n * (`const { items, nextCursor } = await adapter.list(...)`) work seamlessly.\n */\nexport function toPaginatedListResult(\n entries: StorageFileEntry[],\n nextCursor?: string,\n): StoragePaginatedListResult {\n const result = entries as StoragePaginatedListResult;\n Object.defineProperties(result, {\n items: {\n value: entries,\n enumerable: false,\n configurable: true,\n },\n nextCursor: {\n value: nextCursor,\n enumerable: false,\n configurable: true,\n },\n });\n return result;\n}\n\nconst PRINTABLE_ASCII_REGEX = /^[\\x20-\\x7E]*$/;\nconst MAX_METADATA_BYTES = 2048;\n\n/**\n * Validates that storage metadata keys and values contain only printable ASCII\n * characters (preventing HTTP header/CRLF injection) and do not exceed 2 KB total.\n */\nexport function validateStorageMetadata(\n metadata?: Record<string, string>,\n): void {\n if (!metadata) return;\n let totalBytes = 0;\n\n for (const [key, value] of Object.entries(metadata)) {\n if (\n !key ||\n typeof value !== 'string' ||\n !PRINTABLE_ASCII_REGEX.test(key) ||\n !PRINTABLE_ASCII_REGEX.test(value)\n ) {\n const err = new Error(\n 'Invalid storage metadata: keys and values must contain printable ASCII characters only',\n ) as Error & { status?: number };\n err.status = 400;\n throw err;\n }\n totalBytes += key.length + value.length;\n if (totalBytes > MAX_METADATA_BYTES) {\n const err = new Error(\n `Storage metadata exceeds maximum size of ${MAX_METADATA_BYTES} bytes`,\n ) as Error & { status?: number };\n err.status = 400;\n throw err;\n }\n }\n}\n\n/**\n * Type-safe helper for authoring a custom StorageAdapter (S3, R2, Vercel Blob, Firebase, etc.).\n */\nexport function createStorageAdapter(adapter: StorageAdapter): StorageAdapter {\n return adapter;\n}\n","import {\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 StoredMemoryObject {\n bucket: string;\n path: string;\n data: Uint8Array;\n contentType: string;\n createdAt: string;\n metadata?: Record<string, string>;\n}\n\nexport interface MemoryStorageAdapter extends StorageAdapter {\n exists(bucket: string, path: string): Promise<boolean>;\n getPublicUrl(bucket: string, path: string): string;\n list(\n bucket: string,\n folder?: string,\n options?: StorageListOptions,\n ): Promise<StoragePaginatedListResult>;\n deleteByUrl(url: string): Promise<boolean>;\n getObject(bucket: string, path: string): StoredMemoryObject | undefined;\n clear(): void;\n readonly size: number;\n}\n\nexport interface MemoryStorageOptions {\n baseUrl?: string;\n}\n\nexport function createMemoryStorageAdapter(\n options: MemoryStorageOptions = {},\n): MemoryStorageAdapter {\n const baseUrl = (options.baseUrl || 'https://memory.local/uploads').replace(\n /\\/+$/,\n '',\n );\n const store = new Map<string, StoredMemoryObject>();\n\n const toKey = (bucket: string, path: string) =>\n `${bucket}::${path.replace(/^\\/+/, '')}`;\n\n const getPublicUrl = (bucket: string, path: string): string => {\n const cleanPath = path.replace(/^\\/+/, '');\n return `${baseUrl}/${bucket}/${cleanPath}`;\n };\n\n return {\n async upload(params: StorageUploadParams): Promise<StorageUploadResult> {\n const cleanPath = params.path.replace(/^\\/+/, '');\n const key = toKey(params.bucket, cleanPath);\n\n if (params.upsert === false && store.has(key)) {\n const existsErr = new Error(\n `Object already exists at ${params.bucket}/${cleanPath}`,\n ) as Error & { code?: string };\n existsErr.code = 'EEXIST';\n throw existsErr;\n }\n\n store.set(key, {\n bucket: params.bucket,\n path: cleanPath,\n data: new Uint8Array(params.data),\n contentType: params.contentType,\n createdAt: new Date().toISOString(),\n metadata: params.metadata,\n });\n\n return {\n bucket: params.bucket,\n path: cleanPath,\n url: getPublicUrl(params.bucket, cleanPath),\n };\n },\n\n async delete(bucket: string, path: string): Promise<void> {\n const key = toKey(bucket, path);\n store.delete(key);\n },\n\n async exists(bucket: string, path: string): Promise<boolean> {\n return store.has(toKey(bucket, path));\n },\n\n getPublicUrl,\n\n async list(\n bucket: string,\n folder?: string,\n listOptions?: StorageListOptions,\n ): Promise<StoragePaginatedListResult> {\n const cleanFolder = folder ? folder.replace(/^\\/+|\\/+$/g, '') : '';\n const entries: StorageFileEntry[] = [];\n\n for (const obj of store.values()) {\n if (obj.bucket !== bucket) continue;\n if (cleanFolder && !obj.path.startsWith(`${cleanFolder}/`)) continue;\n const name = cleanFolder\n ? obj.path.slice(cleanFolder.length + 1)\n : obj.path;\n entries.push({\n name,\n id: `${obj.bucket}/${obj.path}`,\n createdAt: obj.createdAt,\n size: obj.data.byteLength,\n });\n }\n\n const offset = listOptions?.cursor ? Number(listOptions.cursor) || 0 : 0;\n const limit = listOptions?.limit ?? entries.length;\n const sliced =\n listOptions?.limit !== undefined || offset > 0\n ? entries.slice(offset, offset + limit)\n : entries;\n const nextCursor =\n listOptions?.limit !== undefined && offset + sliced.length < entries.length\n ? String(offset + sliced.length)\n : undefined;\n\n return toPaginatedListResult(sliced, nextCursor);\n },\n\n async deleteByUrl(url: string): Promise<boolean> {\n const parsed = this.parseUrl?.(url);\n if (!parsed || !parsed.bucket) return false;\n const key = toKey(parsed.bucket, parsed.path);\n return store.delete(key);\n },\n\n parseUrl(url: string): { bucket?: string; path: string } | null {\n const prefix = `${baseUrl}/`;\n if (!url || !url.startsWith(prefix)) return null;\n const cleanUrl = url.split(/[?#]/)[0];\n const rest = cleanUrl.slice(prefix.length);\n const slashIdx = rest.indexOf('/');\n if (slashIdx <= 0 || slashIdx === rest.length - 1) return null;\n const bucket = rest.slice(0, slashIdx);\n const filePath = rest.slice(slashIdx + 1);\n return { bucket, path: filePath };\n },\n\n getObject(bucket: string, path: string): StoredMemoryObject | undefined {\n return store.get(toKey(bucket, path));\n },\n\n clear(): void {\n store.clear();\n },\n\n get size(): number {\n return store.size;\n },\n };\n}\n"]}