@nitida/asset-client 0.20.2 → 0.20.4

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/src/index.ts CHANGED
@@ -648,7 +648,22 @@ type OriginalHints = {
648
648
  * ```
649
649
  */
650
650
  export function getAssetUrl(
651
- asset: Pick<AssetDTO, "sha"> & OriginalHints & VisibilityHint,
651
+ /**
652
+ * ⭐ `presets` ESTÁ EN EL TIPO PORQUE LA FUNCIÓN LO LEE.
653
+ *
654
+ * `transformFallbackFor` decide con este campo si devolver la clave de
655
+ * variante o caer a `/t/…width=N/`. Sin él en la firma, el tipo PROHIBÍA el
656
+ * campo que la implementación consulta: pasarlo era un error de compilación,
657
+ * y no pasarlo daba `…-x.webp` → 404 en silencio.
658
+ *
659
+ * Medido 2026-08-25 sobre un asset con `presets: "lmoqs"`:
660
+ * sin `presets` → /f/v/<sha>-x.webp 404
661
+ * con `presets` → /t/format=webp,width=3840/<sha>.webp 200
662
+ */
663
+ asset: Pick<AssetDTO, "sha"> &
664
+ OriginalHints &
665
+ VisibilityHint &
666
+ Partial<Pick<AssetDTO, "presets">>,
652
667
  preset: VariantPreset,
653
668
  ): string {
654
669
  // Refuses rather than returning a URL that 404s. See `assertPublic`.
@@ -863,11 +878,30 @@ export async function getPrivateTransformUrl(
863
878
  * ```
864
879
  */
865
880
  export function hasPreset(
866
- asset: Pick<AssetDTO, "presets">,
881
+ asset: { presets?: string | null },
867
882
  preset: VariantPreset,
868
883
  ): boolean {
869
- if (preset === "mp3") return asset.presets.includes("mp3");
870
- return stripMultiCharTokens(asset.presets).includes(PRESET_SHORT[preset]);
884
+ // ⭐⭐ `presets` PUEDE FALTAR, Y ASUMIR QUE NO ERA UN CRASH EN PRODUCCIÓN
885
+ //
886
+ // El DTO que `assets.byHash(sha)` devuelve no siempre trae `presets` — igual
887
+ // que no traía `sha`. Y `upload()` le pasa ese DTO crudo a `bestPresetForAsset`
888
+ // → `hasPreset` → acá. Con `presets` ausente, `asset.presets.includes(...)`
889
+ // y `stripMultiCharTokens(asset.presets)` reventaban con
890
+ // `undefined is not an object (evaluating 'presets.replace')`.
891
+ //
892
+ // Medido en el canario de neo 2026-08-25: verde con 0.30.2, rojo con 0.32.2,
893
+ // en la rama ORDINARIA del dedup (`existing.status === "ready"`) — o sea toda
894
+ // re-subida de bytes que ya existen, no el caso raro de ayer.
895
+ //
896
+ // ⭐ La invariante que faltaba no era «el objeto lleva `sha`» sino «todo
897
+ // objeto que `upload()` le pasa a un helper satisface la forma que el helper
898
+ // asume». Se arregla ensanchando la forma del helper —`presets` opcional—, no
899
+ // parcheando cada llamador de a uno. Un `presets` ausente significa «no sé qué
900
+ // variantes tiene», que es `false` para cualquier preset: el llamador cae a su
901
+ // default, que es lo correcto.
902
+ const presets = asset.presets ?? "";
903
+ if (preset === "mp3") return presets.includes("mp3");
904
+ return stripMultiCharTokens(presets).includes(PRESET_SHORT[preset]);
871
905
  }
872
906
 
873
907
  /**
@@ -894,8 +928,8 @@ export function hasPreset(
894
928
  * keep sending them, and this is the check every guide points at as the
895
929
  * reliable one. Order matters — strip the longest tokens first.
896
930
  */
897
- function stripMultiCharTokens(presets: string): string {
898
- return presets
931
+ function stripMultiCharTokens(presets: string | null | undefined): string {
932
+ return (presets ?? "")
899
933
  .replace(/transform-[0-9a-f]*/g, "")
900
934
  .replace(/upscale_[a-z0-9_]*/g, "")
901
935
  .replace(/mp3/g, "")