@nitida/sdk 0.31.7 → 0.32.1

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/README.md CHANGED
@@ -283,7 +283,11 @@ const result = await aq.upload(file, { fileName: "cover.jpg" });
283
283
  await aq.upload(bytes, { fileName: "cover.webp" }); // MIME inferred from .webp ✓
284
284
  await aq.upload(bytes, { contentType: "image/webp" }); // or be explicit ✓
285
285
  // And request the variants you'll render — `presets` DEFAULTS TO ["original"] (just the raw
286
- // bytes), so getAssetUrl(sha, "md") style preset URLs 404 unless you ask for the ladder:
286
+ // bytes). ⚠️ Corrected 2026-08-25: a BARE sha does not 404 it THROWS
287
+ // (`assertSha`); and with the full object, a missing IMAGE preset does not
288
+ // 404 either — it falls back to `/t/` and serves 200. What 404s is a
289
+ // hand-built variant key. Ask for the ladder anyway when you want
290
+ // materialised bytes instead of an on-the-fly transform:
287
291
  await aq.upload(bytes, { contentType: "image/webp", presets: ["thumb", "sm", "md", "lg", "xl"] });
288
292
 
289
293
  // Bind a slot (admin operation).
package/dist/index.d.ts CHANGED
@@ -671,7 +671,62 @@ type UploadResult = {
671
671
  * can close (`.mpga`, `.docx`, `.m4a` all arrive as octet-stream).
672
672
  */
673
673
  oext?: string | null;
674
- cdnUrl: string;
674
+ /**
675
+ * La URL pública de una variante razonable — o **`null` si el asset es
676
+ * privado**, que no tiene ninguna.
677
+ *
678
+ * ⭐⭐ ERA `string`, Y ESO HACÍA QUE `upload()` PERDIERA EL HANDLE
679
+ *
680
+ * Los tres retornos de `upload()` arman este campo con `urlFor`, que llama a
681
+ * `assertPublic` y **tira** sobre un asset privado. Medido 2026-08-25 por una
682
+ * auditoría externa: subir bytes que deduplican contra una fila privada hacía
683
+ * que `upload()` lanzara DESPUÉS del PUT — los bytes quedaban guardados y el
684
+ * `assetId` se perdía, porque el error es un `Error` pelado sin `assetId`, sin
685
+ * `sha` y sin `cause`.
686
+ *
687
+ * ⚠️ Y el mensaje nombraba `getAssetUrl` y un preset `"lg"` que el llamador
688
+ * nunca pidió, así que iba a buscar en su código una función que no llamó.
689
+ *
690
+ * Un asset privado **no tiene** URL pública: eso es la feature. Lo que no
691
+ * puede pasar es que no tenerla cueste el resultado de una subida que ya
692
+ * ocurrió. Para servirlo, mirá `visibility` y usá `getPrivateAssetUrl` con la
693
+ * signing key, en tu backend.
694
+ */
695
+ cdnUrl: string | null;
696
+ /**
697
+ * `"public"` o `"private"`, tal como quedó el asset.
698
+ *
699
+ * ⭐ Existe porque `getAssetUrl(up, …)` —el patrón que enseña toda la doc—
700
+ * no podía detectar un privado: el guarda mira `asset.visibility`, y este
701
+ * objeto no lo llevaba. El resultado era una URL pública para un asset
702
+ * privado, servida sin una queja, que después da 404.
703
+ */
704
+ visibility: "public" | "private";
705
+ /**
706
+ * La cadena compacta de variantes que EXISTEN (`"lmoqs"`, `"o"`, …).
707
+ *
708
+ * ⭐⭐ EL QUINTO DEFECTO DE LA MISMA FAMILIA
709
+ *
710
+ * `getAssetUrl` **cambia de estrategia según este campo**. Con él, un preset
711
+ * de imagen ausente cae a `/t/…width=N/` y el borde lo genera al vuelo:
712
+ * **200**. Sin él, el builder no sabe qué existe, asume que todo existe, y
713
+ * devuelve la clave de variante — que da **404 en silencio**.
714
+ *
715
+ * Medido 2026-08-25 sobre un asset con `presets: "lmoqs"`:
716
+ *
717
+ * sin `presets` → /f/v/<sha>-x.webp 404
718
+ * con `presets` → /t/format=webp,width=3840/<sha>.webp 200
719
+ *
720
+ * `UploadResult` no lo llevaba, así que `getAssetUrl(up, "xl")` —el patrón
721
+ * que enseña toda la doc— caía del lado malo.
722
+ *
723
+ * ⚠️ Es el mismo error que `sha`, `mime` y `oext`, por cuarta vez: **un tipo
724
+ * de retorno que omite lo que la próxima llamada necesita convierte la
725
+ * llamada obvia en un 404 silencioso.** Y el guarda que escribí para esto
726
+ * fijaba los tres *hints* de `original`; `presets` no es un hint, es lo que
727
+ * gobierna el fallback, así que quedaba afuera de lo que el guarda miraba.
728
+ */
729
+ presets: string;
675
730
  };
676
731
  type SlotHistoryEntry = {
677
732
  id: string;
@@ -946,6 +1001,13 @@ declare class NitidaClient {
946
1001
  * ```
947
1002
  */
948
1003
  upload(input: File | Blob | Uint8Array, opts?: UploadOptions): Promise<UploadResult>;
1004
+ /**
1005
+ * `urlFor` sin la excepción: `null` cuando el asset es privado.
1006
+ *
1007
+ * `upload()` no puede fallar por no poder construir una URL pública. Los
1008
+ * bytes ya están; el handle tiene que volver igual.
1009
+ */
1010
+ private publicUrlOrNull;
949
1011
  private defaultPresetForMime;
950
1012
  /**
951
1013
  * Pick a sensible preset to build a URL for, given the asset's actual
package/dist/index.js CHANGED
@@ -777,7 +777,9 @@ var NitidaClient = class {
777
777
  // Same shape as the presign branch below, for the same reason. This
778
778
  // DTO does carry `sha` today — but relying on that is how the other
779
779
  // branch broke, and the value we hashed ourselves is authoritative.
780
- cdnUrl: this.urlFor(
780
+ visibility: existing.visibility ?? "public",
781
+ presets: existing.presets ?? "",
782
+ cdnUrl: this.publicUrlOrNull(
781
783
  { ...existing, sha: sha.slice(0, 16) },
782
784
  this.bestPresetForAsset(existing, mime)
783
785
  )
@@ -834,7 +836,9 @@ var NitidaClient = class {
834
836
  // ACTUALLY has, now that we waited for it — not from
835
837
  // `defaultPresetForMime`, which is a guess made before anything exists.
836
838
  // Guessing was safe only while this branch never ran.
837
- cdnUrl: this.urlFor(
839
+ visibility: settled.visibility ?? "public",
840
+ presets: settled.presets ?? "",
841
+ cdnUrl: this.publicUrlOrNull(
838
842
  { ...settled, sha: short },
839
843
  this.bestPresetForAsset(settled, mime)
840
844
  )
@@ -882,9 +886,21 @@ var NitidaClient = class {
882
886
  sha: sha.slice(0, 16),
883
887
  mime: final.mime ?? mime,
884
888
  oext: final.oext ?? null,
885
- cdnUrl: this.urlFor(final, this.bestPresetForAsset(final, mime))
889
+ visibility: final.visibility ?? "public",
890
+ presets: final.presets ?? "",
891
+ cdnUrl: this.publicUrlOrNull(final, this.bestPresetForAsset(final, mime))
886
892
  };
887
893
  }
894
+ /**
895
+ * `urlFor` sin la excepción: `null` cuando el asset es privado.
896
+ *
897
+ * `upload()` no puede fallar por no poder construir una URL pública. Los
898
+ * bytes ya están; el handle tiene que volver igual.
899
+ */
900
+ publicUrlOrNull(asset, preset) {
901
+ if (asset.visibility === "private") return null;
902
+ return this.urlFor(asset, preset);
903
+ }
888
904
  defaultPresetForMime(mime) {
889
905
  if (mime.startsWith("video/")) return "video";
890
906
  if (mime.startsWith("audio/")) return "original";