UploadResult
type UploadResult = { assetId: string; cdnUrl: string | null; kind: AssetDTO["kind"]; mime: string; oext?: string | null; presets: string; sha: string; sha256: string; variants: AssetVariant[]; visibility: "public" | "private";};Defined in: packages/sdk/src/index.ts:1136
Properties
Section titled “Properties”assetId
Section titled “assetId”assetId: string;Defined in: packages/sdk/src/index.ts:1137
cdnUrl
Section titled “cdnUrl”cdnUrl: string | null;Defined in: packages/sdk/src/index.ts:1202
La URL pública de una variante razonable — o null si el asset es
privado, que no tiene ninguna.
⭐⭐ ERA string, Y ESO HACÍA QUE upload() PERDIERA EL HANDLE
Los tres retornos de upload() arman este campo con urlFor, que llama a
assertPublic y tira sobre un asset privado. Medido 2026-08-25 por una
auditoría externa: subir bytes que deduplican contra una fila privada hacía
que upload() lanzara DESPUÉS del PUT — los bytes quedaban guardados y el
assetId se perdía, porque el error es un Error pelado sin assetId, sin
sha y sin cause.
⚠️ Y el mensaje nombraba getAssetUrl y un preset "lg" que el llamador
nunca pidió, así que iba a buscar en su código una función que no llamó.
Un asset privado no tiene URL pública: eso es la feature. Lo que no
puede pasar es que no tenerla cueste el resultado de una subida que ya
ocurrió. Para servirlo, mirá visibility y usá getPrivateAssetUrl con la
signing key, en tu backend.
kind: AssetDTO["kind"];Defined in: packages/sdk/src/index.ts:1226
What the asset IS (image · video · audio · document · other).
⭐ It exists because it did not, and the guard caught it before anyone
else did — the FIFTH member of this family, after sha, mime, oext
and presets. When getAssetUrl learned to refuse hls on something
that can never have a ladder, it started READING kind; an
UploadResult without it silently opted out of the refusal and went
right back to building /t/format=hls/<sha>.m3u8 — which does not 404,
it answers 200 with the image bytes.
The rule this keeps re-teaching: a return type that omits what the next call reads is a silent 404 (or worse, a silent 200). The fix is always to carry the field, never to write a better error message about its absence.
mime: string;Defined in: packages/sdk/src/index.ts:1173
The MIME the upload was stored under.
⭐ It exists because it did not, and that cost the THIRD 404 of this exact
family. Found 2026-08-23 by an agent running the getting-started doc
verbatim: getAssetUrl(up, "original") built <sha>-o.bin and 404’d,
while the object served fine at -o.jpg.
original is the one preset whose extension is not fixed — it is the
bytes you uploaded, so the extension comes from the MIME (ORIGINAL_EXT_BY_MIME),
and PRESET_EXT.original is only the "bin" fallback for when nothing
says otherwise. UploadResult said nothing, so every caller handing an
upload result straight to a URL builder got the fallback.
The pattern is now three for three — sha256 vs sha, and this:
a result type that omits what the next call needs turns the obvious
call into a silent 404. The fix is never a better error message.
optional oext?: string | null;Defined in: packages/sdk/src/index.ts:1180
The extension the server actually stored the original under, when it said
so. Authoritative — it beats any client-side MIME table, because the
server keys the object off the uploaded filename for the cases no table
can close (.mpga, .docx, .m4a all arrive as octet-stream).
presets
Section titled “presets”presets: string;Defined in: packages/sdk/src/index.ts:1252
La cadena compacta de variantes que EXISTEN ("lmoqs", "o", …).
⭐⭐ EL QUINTO DEFECTO DE LA MISMA FAMILIA
getAssetUrl cambia de estrategia según este campo. Con él, un preset
de imagen ausente cae a /t/…width=N/ y el borde lo genera al vuelo:
200. Sin él, el builder no sabe qué existe, asume que todo existe, y
devuelve la clave de variante — que da 404 en silencio.
Medido 2026-08-25 sobre un asset con presets: "lmoqs":
sin `presets` → /f/v/<sha>-x.webp 404con `presets` → /t/format=webp,width=3840/<sha>.webp 200UploadResult no lo llevaba, así que getAssetUrl(up, "xl") —el patrón
que enseña toda la doc— caía del lado malo.
⚠️ Es el mismo error que sha, mime y oext, por cuarta vez: un tipo
de retorno que omite lo que la próxima llamada necesita convierte la
llamada obvia en un 404 silencioso. Y el guarda que escribí para esto
fijaba los tres hints de original; presets no es un hint, es lo que
gobierna el fallback, así que quedaba afuera de lo que el guarda miraba.
sha: string;Defined in: packages/sdk/src/index.ts:1154
The SAME 16-hex prefix an AssetDTO carries, so the result of an upload can
be handed straight to any URL builder.
⭐ It exists because it did not, and that cost a real 404. A Haiku agent
evaluating the SDK on 2026-08-23 did the most natural thing there is —
transform(await upload(file), { width: 1280 }) — and got
https://8ok.uk/t/width=1280/undefined.webp. The builders read sha; this
type only had sha256. TypeScript caught it; running through bun, or in
plain JS, nothing did.
The guard (assertSha) is the backstop. This field is the actual fix: the
obvious call is now the correct one, which is worth more than a good error
message about the wrong one.
sha256
Section titled “sha256”sha256: string;Defined in: packages/sdk/src/index.ts:1138
variants
Section titled “variants”variants: AssetVariant[];Defined in: packages/sdk/src/index.ts:1266
La lista de variantes tal como el servidor las guardó.
⭐ La SEXTA instancia del mismo defecto, y la primera que encontró un guarda en vez de un usuario.
Para original, getAssetUrl prefiere la URL almacenada sobre cualquier
derivación — porque ESA es la clave real, y derivarla del mime es una
suposición que ya falló dos veces (-o.bin, -o.jpeg). Sin este campo, el
resultado de upload() obligaba a suponer.
Sale gratis: upload() ya espera el DTO, que la trae.
visibility
Section titled “visibility”visibility: "public" | "private";Defined in: packages/sdk/src/index.ts:1227